Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stale-rockets-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'sv': patch
---

fix: improve package manager detection
10 changes: 7 additions & 3 deletions packages/adders/drizzle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,14 +380,18 @@ export default defineAdder({
}
}
],
nextSteps: ({ options, highlighter }) => {
nextSteps: ({ options, highlighter, packageManager }) => {
const steps = [
`You will need to set ${highlighter.env('DATABASE_URL')} in your production environment`
];
if (options.docker) {
steps.push(`Run ${highlighter.command('npm run db:start')} to start the docker container`);
steps.push(
`Run ${highlighter.command(`${packageManager} run db:start`)} to start the docker container`
);
}
steps.push(`Run ${highlighter.command('npm run db:push')} to update your database schema`);
steps.push(
`Run ${highlighter.command(`${packageManager} run db:push`)} to update your database schema`
);

return steps;
}
Expand Down
6 changes: 4 additions & 2 deletions packages/adders/lucia/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,8 +590,10 @@ export default defineAdder({
}
}
],
nextSteps: ({ highlighter, options }) => {
const steps = [`Run ${highlighter.command('npm run db:push')} to update your database schema`];
nextSteps: ({ highlighter, options, packageManager }) => {
const steps = [
`Run ${highlighter.command(`${packageManager} run db:push`)} to update your database schema`
];
if (options.demo) {
steps.push(`Visit ${highlighter.route('/demo/lucia')} route to view the demo`);
}
Expand Down
50 changes: 27 additions & 23 deletions packages/cli/commands/add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { exec } from 'tinyexec';
import { Command, Option } from 'commander';
import * as p from '@sveltejs/clack-prompts';
import * as pkg from 'empathic/package';
import { resolveCommand } from 'package-manager-detector';
import { resolveCommand, type AgentName } from 'package-manager-detector';
import pc from 'picocolors';
import {
officialAdders,
Expand Down Expand Up @@ -95,7 +95,7 @@ type SelectedAdder = { type: 'official' | 'community'; adder: AdderWithoutExplic
export async function runAddCommand(
options: Options,
selectedAdderIds: string[]
): Promise<{ nextSteps?: string }> {
): Promise<{ nextSteps?: string; packageManager?: AgentName | null }> {
const selectedAdders: SelectedAdder[] = selectedAdderIds.map((id) => ({
type: 'official',
adder: getAdderDetails(id)
Expand Down Expand Up @@ -267,7 +267,7 @@ export async function runAddCommand(

// prompt which adders to apply
if (selectedAdders.length === 0) {
const workspace = createWorkspace(options.cwd);
const workspace = createWorkspace({ cwd: options.cwd });
const projectType = workspace.kit ? 'kit' : 'svelte';
const adderOptions = officialAdders
.map((adder) => {
Expand Down Expand Up @@ -301,7 +301,7 @@ export async function runAddCommand(
const dependents =
adder.dependsOn?.filter((dep) => !selectedAdders.some((a) => a.adder.id === dep)) ?? [];

const workspace = createWorkspace(options.cwd);
const workspace = createWorkspace({ cwd: options.cwd });
for (const depId of dependents) {
const dependent = officialAdders.find((a) => a.id === depId) as AdderWithoutExplicitArgs;
if (!dependent) throw new Error(`Adder '${adder.id}' depends on an invalid '${depId}'`);
Expand Down Expand Up @@ -330,7 +330,7 @@ export async function runAddCommand(
// run precondition checks
if (options.preconditions && selectedAdders.length > 0) {
// add global checks
const { kit } = createWorkspace(options.cwd);
const { kit } = createWorkspace({ cwd: options.cwd });
const projectType = kit ? 'kit' : 'svelte';
const adders = selectedAdders.map(({ adder }) => adder);
const { preconditions } = common.getGlobalPreconditions(options.cwd, projectType, adders);
Expand Down Expand Up @@ -418,30 +418,32 @@ export async function runAddCommand(
}
}

// apply adders
let filesToFormat: string[] = [];
if (Object.keys({ ...official, ...community }).length > 0) {
filesToFormat = await runAdders({ cwd: options.cwd, official, community });
p.log.success('Successfully setup integrations');
// we'll return early when no adders are selected,
// indicating that installing deps was skipped and no PM was selected
if (selectedAdders.length === 0) return { packageManager: null };

// prompt for package manager
let packageManager: AgentName | undefined;
if (options.install) {
packageManager = await common.packageManagerPrompt(options.cwd);
}

// apply adders
const filesToFormat = await runAdders({ cwd: options.cwd, packageManager, official, community });
p.log.success('Successfully setup integrations');

// install dependencies
let depsStatus: 'installed' | 'skipped' = 'skipped';
if (options.install && selectedAdders.length > 0) {
depsStatus = await common.suggestInstallingDependencies(options.cwd);
if (packageManager && options.install) {
await common.installDependencies(packageManager, options.cwd);
}

// format modified/created files with prettier (if available)
const workspace = createWorkspace(options.cwd);
if (
filesToFormat.length > 0 &&
depsStatus === 'installed' &&
!!workspace.dependencyVersion('prettier')
) {
const workspace = createWorkspace({ cwd: options.cwd, packageManager });
if (filesToFormat.length > 0 && packageManager && !!workspace.dependencyVersion('prettier')) {
const { start, stop } = p.spinner();
start('Formatting modified files');
try {
await common.formatFiles(options.cwd, filesToFormat);
await common.formatFiles({ packageManager, cwd: options.cwd, paths: filesToFormat });
stop('Successfully formatted modified files');
} catch (e) {
stop('Failed to format files');
Expand Down Expand Up @@ -472,7 +474,7 @@ export async function runAddCommand(
// instead of returning an empty string, we'll return `undefined`
.join('\n\n') || undefined;

return { nextSteps };
return { nextSteps, packageManager };
}

type AdderId = string;
Expand All @@ -481,6 +483,7 @@ export type AdderOption = Record<AdderId, QuestionValues>;

export type InstallAdderOptions = {
cwd: string;
packageManager?: AgentName;
official?: AdderOption;
community?: AdderOption;
};
Expand All @@ -491,7 +494,8 @@ export type InstallAdderOptions = {
async function runAdders({
cwd,
official = {},
community = {}
community = {},
packageManager
}: InstallAdderOptions): Promise<string[]> {
const adderDetails = Object.keys(official).map((id) => getAdderDetails(id));
const commDetails = Object.keys(community).map(
Expand All @@ -514,7 +518,7 @@ async function runAdders({
const filesToFormat = new Set<string>();
for (const config of details) {
const adderId = config.id;
const workspace = createWorkspace(cwd);
const workspace = createWorkspace({ cwd, packageManager });

workspace.options = official[adderId] ?? community[adderId]!;

Expand Down
48 changes: 20 additions & 28 deletions packages/cli/commands/add/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,28 @@ import * as find from 'empathic/find';
import { common, object, type AstTypes } from '@sveltejs/cli-core/js';
import { parseScript } from '@sveltejs/cli-core/parsers';
import { TESTING } from '../../env.ts';
import { getUserAgent } from '../../common.ts';
import { commonFilePaths, getPackageJson, readFile } from './utils.ts';
import { detectPackageManager } from '../../common.ts';
import type { OptionDefinition, Workspace } from '@sveltejs/cli-core';
import type { Workspace } from '@sveltejs/cli-core';
import type { AgentName } from 'package-manager-detector';

export function createEmptyWorkspace<Args extends OptionDefinition>() {
return {
options: {},
cwd: '',
dependencyVersion: (_pkg) => undefined,
typescript: false,
kit: undefined
} as Workspace<Args>;
}

export function createWorkspace<Args extends OptionDefinition>(cwd: string): Workspace<Args> {
const workspace = createEmptyWorkspace<Args>();
workspace.cwd = path.resolve(cwd);

let usesTypescript = fs.existsSync(path.join(cwd, commonFilePaths.viteConfigTS));
type CreateWorkspaceOptions = { cwd: string; packageManager?: AgentName };
export function createWorkspace({ cwd, packageManager }: CreateWorkspaceOptions): Workspace<any> {
const resolvedCwd = path.resolve(cwd);
const viteConfigPath = path.join(resolvedCwd, commonFilePaths.viteConfigTS);
let usesTypescript = fs.existsSync(viteConfigPath);

if (TESTING) {
// while executing tests, we only look into the direct `cwd`
// as we might detect the monorepo `tsconfig.json` otherwise.
usesTypescript ||= fs.existsSync(path.join(cwd, commonFilePaths.tsconfig));
usesTypescript ||= fs.existsSync(path.join(resolvedCwd, commonFilePaths.tsconfig));
} else {
usesTypescript ||= find.up(commonFilePaths.tsconfig, { cwd }) !== undefined;
}

let dependencies: Record<string, string> = {};
let directory = workspace.cwd;
const root = findRoot(workspace.cwd);
let directory = resolvedCwd;
const root = findRoot(resolvedCwd);
while (directory && directory !== root) {
if (fs.existsSync(path.join(directory, commonFilePaths.packageJson))) {
const { data: packageJson } = getPackageJson(directory);
Expand All @@ -51,13 +42,14 @@ export function createWorkspace<Args extends OptionDefinition>(cwd: string): Wor
dependencies[key] = value.replaceAll(/[^\d|.]/g, '');
}

workspace.dependencyVersion = (pkg) => {
return dependencies[pkg];
return {
kit: dependencies['@sveltejs/kit'] ? parseKitOptions(resolvedCwd) : undefined,
packageManager: packageManager ?? getUserAgent() ?? 'npm',
cwd: resolvedCwd,
dependencyVersion: (pkg) => dependencies[pkg],
typescript: usesTypescript,
options: {}
};
workspace.typescript = usesTypescript;
workspace.packageManager = detectPackageManager(cwd);
if (workspace.dependencyVersion('@sveltejs/kit')) workspace.kit = parseKitOptions(workspace);
return workspace;
}

function findRoot(cwd: string): string {
Expand All @@ -78,8 +70,8 @@ function findRoot(cwd: string): string {
return root;
}

function parseKitOptions(workspace: Workspace<any>) {
const configSource = readFile(workspace.cwd, commonFilePaths.svelteConfig);
function parseKitOptions(cwd: string) {
const configSource = readFile(cwd, commonFilePaths.svelteConfig);
const { ast } = parseScript(configSource);

const defaultExport = ast.body.find((s) => s.type === 'ExportDefaultDeclaration');
Expand Down
36 changes: 24 additions & 12 deletions packages/cli/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '@sveltejs/create';
import * as common from '../common.js';
import { runAddCommand } from './add/index.ts';
import { detectSync, type AgentName } from 'package-manager-detector';

const langs = ['typescript', 'checkjs', 'none'] as const;
const templateChoices = templates.map((t) => t.name);
Expand Down Expand Up @@ -42,17 +43,17 @@ export const create = new Command('create')
const cwd = v.parse(ProjectPathSchema, projectPath);
const options = v.parse(OptionsSchema, opts);
common.runCommand(async () => {
const { directory, integrationNextSteps } = await createProject(cwd, options);
const { directory, integrationNextSteps, packageManager } = await createProject(cwd, options);
const highlight = (str: string) => pc.bold(pc.cyan(str));

let i = 1;
const initialSteps: string[] = [];
const relative = path.relative(process.cwd(), directory);
const pm = common.detectPackageManager(cwd);
const pm = packageManager ?? detectSync({ cwd })?.name ?? common.getUserAgent() ?? 'npm';
if (relative !== '') {
initialSteps.push(`${i++}: ${highlight(`cd ${relative}`)}`);
}
if (!common.packageManager) {
if (!packageManager) {
initialSteps.push(`${i++}: ${highlight(`${pm} install`)}`);
}

Expand Down Expand Up @@ -137,20 +138,31 @@ async function createProject(cwd: string, options: Options) {

p.log.success('Project created');

let integrationNextSteps;
let packageManager: AgentName | undefined | null;
let integrationNextSteps: string | undefined;
const installDeps = async () => {
packageManager = await common.packageManagerPrompt(projectPath);
if (packageManager) await common.installDependencies(packageManager, projectPath);
};

if (options.integrations) {
const { nextSteps } = await runAddCommand(
{ cwd: projectPath, install: false, preconditions: true, community: [] },
// `runAddCommand` includes installing dependencies
const { nextSteps, packageManager: pm } = await runAddCommand(
{ cwd: projectPath, install: options.install, preconditions: true, community: [] },
[]
);
packageManager = pm;
integrationNextSteps = nextSteps;
} else if (options.install) {
// `--no-integrations` was set, so we'll prompt to install deps manually
await installDeps();
}
// show install prompt even if no integrations are selected
if (options.install) {
// `runAddCommand` includes the installing dependencies prompt. if it's skipped,
// then we'll prompt to install dependencies here
await common.suggestInstallingDependencies(projectPath);

// no integrations were selected (which means the install prompt was skipped in `runAddCommand`),
// so we'll prompt to install
if (packageManager === null && options.install) {
await installDeps();
}

return { directory: projectPath, integrationNextSteps };
return { directory: projectPath, integrationNextSteps, packageManager };
}
Loading