Skip to content
Open
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
9 changes: 6 additions & 3 deletions packages/sv/src/addons/tests/better-auth/test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'tinyexec';
import betterAuth from '../../better-auth.ts';
import drizzle from '../../drizzle.ts';
import { setupTest } from '../_setup/suite.ts';
Expand Down Expand Up @@ -37,7 +37,7 @@ test.concurrent.for(testCases)('better-auth $variant', async (testCase, { page,
fs.writeFileSync(envPath, envContent, 'utf8');

// Generate auth schema using better-auth CLI
execSync('npm run auth:schema', { cwd, stdio: 'pipe' });
execSync('npm', ['run', 'auth:schema'], { nodeOptions: { cwd }, throwOnError: true });

// Verify schema has auth tables
const schemaPath = path.resolve(cwd, `src/lib/server/db/schema.${language}`);
Expand All @@ -46,7 +46,10 @@ test.concurrent.for(testCases)('better-auth $variant', async (testCase, { page,
expect(schemaContent).toContain('./auth.schema');

// Push schema to DB
execSync('npm run db:push -- --force', { cwd, stdio: 'pipe' });
execSync('npm', ['run', 'db:push', '--', '--force'], {
nodeOptions: { cwd },
throwOnError: true
});

/** ----- BROWSER SECTION ----- */
const { url, close } = await prepareServer({ cwd, page });
Expand Down
19 changes: 13 additions & 6 deletions packages/sv/src/addons/tests/drizzle/test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { execSync } from 'tinyexec';
import { beforeAll, expect } from 'vitest';
import drizzle from '../../drizzle.ts';
import { setupTest } from '../_setup/suite.ts';
Expand Down Expand Up @@ -42,21 +42,28 @@ beforeAll(() => {
const cwd = path.dirname(fileURLToPath(import.meta.url));

try {
execSync('docker --version', { cwd, stdio: 'pipe' });
execSync('docker', ['--version'], { nodeOptions: { cwd }, throwOnError: true });
dockerInstalled = true;
} catch {
dockerInstalled = false;
}

if (dockerInstalled) execSync('docker compose up --detach', { cwd, stdio: 'pipe' });
if (dockerInstalled) {
execSync('docker', ['compose', 'up', '--detach'], {
nodeOptions: { cwd },
throwOnError: true
});
}

// cleans up the containers on interrupts (ctrl+c)
process.addListener('SIGINT', () => {
if (dockerInstalled) execSync('docker compose down --volumes', { cwd, stdio: 'pipe' });
if (dockerInstalled)
execSync('docker', ['compose', 'down', '--volumes'], { nodeOptions: { cwd } });
});

return () => {
if (dockerInstalled) execSync('docker compose down --volumes', { cwd, stdio: 'pipe' });
if (dockerInstalled)
execSync('docker', ['compose', 'down', '--volumes'], { nodeOptions: { cwd } });
};
});

Expand Down Expand Up @@ -92,7 +99,7 @@ test.concurrent.for(testCases)(
const pageServerPath = path.resolve(routes, `+page.server.${ts ? 'ts' : 'js'}`);
fs.writeFileSync(pageServerPath, pageServer(ts ? 'ts' : 'js'), 'utf8');

execSync('npm run db:push', { cwd, stdio: 'pipe' });
execSync('npm', ['run', 'db:push'], { nodeOptions: { cwd }, throwOnError: true });

const { close } = await prepareServer({ cwd, page });
// kill server process when we're done
Expand Down
17 changes: 13 additions & 4 deletions packages/sv/src/addons/tests/eslint/test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'tinyexec';
import eslint from '../../eslint.ts';
import { setupTest } from '../_setup/suite.ts';

Expand All @@ -15,9 +15,18 @@ test.concurrent.for(testCases)('eslint $variant', (testCase, { expect, ...ctx })
const unlintedFile = 'let foo = "";\nif (Boolean(foo)) {\n//\n}';
fs.writeFileSync(path.resolve(cwd, 'src/lib/foo.js'), unlintedFile, 'utf8');

expect(() => execSync('pnpm lint', { cwd, stdio: 'pipe' })).toThrow();
expect(
execSync('pnpm', ['lint'], { nodeOptions: { cwd } }).exitCode,
'lint should fail on unlinted file'
).not.toBe(0);

expect(() => execSync('pnpm eslint --fix .', { cwd, stdio: 'pipe' })).not.toThrow();
expect(
execSync('pnpm', ['eslint', '--fix', '.'], { nodeOptions: { cwd } }).exitCode,
'eslint --fix should succeed'
).toBe(0);

expect(() => execSync('pnpm lint', { cwd, stdio: 'pipe' })).not.toThrow();
expect(
execSync('pnpm', ['lint'], { nodeOptions: { cwd } }).exitCode,
'lint should pass after fix'
).toBe(0);
});
17 changes: 13 additions & 4 deletions packages/sv/src/addons/tests/prettier/test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { log } from '@clack/prompts';
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'tinyexec';
import { vi } from 'vitest';
import { ESLINT_VERSION } from '../../common.ts';
import prettier from '../../prettier.ts';
Expand Down Expand Up @@ -48,11 +48,20 @@ test.concurrent.for(testCases)('prettier $kind.type $variant', (testCase, { expe
const unformattedFile = 'const foo = "bar"';
fs.writeFileSync(path.resolve(cwd, 'src/lib/foo.js'), unformattedFile, 'utf8');

expect(() => execSync('pnpm lint', { cwd, stdio: 'pipe' })).toThrow();
expect(
execSync('pnpm', ['lint'], { nodeOptions: { cwd } }).exitCode,
'lint should fail on unformatted file'
).not.toBe(0);

expect(() => execSync('pnpm format', { cwd, stdio: 'pipe' })).not.toThrow();
expect(
execSync('pnpm', ['format'], { nodeOptions: { cwd } }).exitCode,
'format should succeed'
).toBe(0);

expect(() => execSync('pnpm lint', { cwd, stdio: 'pipe' })).not.toThrow();
expect(
execSync('pnpm', ['lint'], { nodeOptions: { cwd } }).exitCode,
'lint should pass after format'
).toBe(0);
} else if (testCase.kind.type === 'supported-eslint') {
expect(fs.existsSync(path.resolve(cwd, 'eslint.config.js'))).toBe(true);

Expand Down
17 changes: 9 additions & 8 deletions packages/sv/src/addons/tests/vitest/test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'tinyexec';
Comment thread
sacrosanctic marked this conversation as resolved.
import vitest from '../../vitest-addon.ts';
import { setupTest } from '../_setup/suite.ts';

Expand All @@ -13,16 +13,17 @@ test.concurrent.for(testCases)('vitest $variant', (testCase, { expect, ...ctx })
const cwd = ctx.cwd(testCase);

expect(
spawnSync('pnpm exec playwright install chromium', {
cwd,
stdio: 'pipe',
shell: true,
timeout: 2 * 60_000
}).status
execSync('pnpm', ['exec', 'playwright', 'install', 'chromium'], {
nodeOptions: {
cwd,
timeout: 2 * 60_000,
shell: true
}
}).exitCode
).toBe(0);

expect(
spawnSync('pnpm test', { cwd, stdio: 'pipe', shell: true, timeout: 2 * 60_000 }).status
execSync('pnpm', ['test'], { nodeOptions: { cwd, shell: true, timeout: 2 * 60_000 } }).exitCode
).toBe(0);

const viteFile = ['vite.config.ts', 'vite.config.js']
Expand Down
11 changes: 7 additions & 4 deletions packages/sv/src/cli/check.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { color, resolveCommandArray } from '@sveltejs/sv-utils';
import { color, resolveCommand, resolveCommandArray } from '@sveltejs/sv-utils';
import { Command } from 'commander';
import * as resolve from 'empathic/resolve';
import { execSync } from 'node:child_process';
import process from 'node:process';
import { execSync } from 'tinyexec';
import { forwardExitCode } from '../core/common.ts';
import { detectPackageManager } from '../core/package-manager.ts';

Expand Down Expand Up @@ -39,8 +39,11 @@ async function runCheck(cwd: string, args: string[]) {

// avoids printing the stack trace for `sv` when `svelte-check` exits with an error code
try {
const cmd = resolveCommandArray(pm, 'execute-local', ['svelte-check', ...args]).join(' ');
execSync(cmd, { stdio: 'inherit', cwd });
const cmd = resolveCommand(pm, 'execute-local', ['svelte-check', ...args])!;
execSync(cmd.command, cmd.args, {
nodeOptions: { cwd, stdio: 'inherit' },
throwOnError: true
});
} catch (error) {
forwardExitCode(error);
} finally {
Expand Down
41 changes: 20 additions & 21 deletions packages/sv/src/cli/tests/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,19 @@ describe('cli', () => {
...args
];

/**
* Same as `exec`. but `cwd` defaults to `testOutputPath`
*/
const run = (...params: Parameters<typeof exec>) => {
const [command, args, options = {}] = params;
options.nodeOptions ??= {};
options.nodeOptions.cwd ??= testOutputPath;
return exec(command, args, options);
};

// useful for debugging
// console.log(`command`, `node ${allArgs.join(' ')}`);
const result = await exec('node', allArgs, { nodeOptions: { stdio: 'pipe' } });
const result = await exec('node', allArgs);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if this should be using run instead? im not really sure myself


// cli finished well
expect(
Expand Down Expand Up @@ -191,24 +201,18 @@ describe('cli', () => {

if (projectName === 'create-with-all-addons' && process.platform !== 'win32') {
// the generated project lives inside this repo, so it must not join its workspace
const installResult = await exec(
'pnpm',
['install', '--no-frozen-lockfile', '--ignore-workspace'],
{ nodeOptions: { stdio: 'pipe', cwd: testOutputPath } }
);
const installResult = await run('pnpm', [
'install',
'--no-frozen-lockfile',
'--ignore-workspace'
]);
expect(
installResult.exitCode,
`pnpm install failed:\n stdout: ${installResult.stdout}\n stderr: ${installResult.stderr}`
).toBe(0);
await exec('pnpm', ['build'], {
nodeOptions: { stdio: 'pipe', cwd: testOutputPath }
});
await exec('pnpm', ['auth:schema'], {
nodeOptions: { stdio: 'pipe', cwd: testOutputPath }
});
const check = await exec('pnpm', ['check'], {
nodeOptions: { stdio: 'pipe', cwd: testOutputPath }
});
await run('pnpm', ['build']);
await run('pnpm', ['auth:schema']);
const check = await run('pnpm', ['check']);
expect(
check.exitCode,
`svelte-check failed:\n stdout: ${check.stdout}\n stderr: ${check.stderr}`
Expand All @@ -217,9 +221,6 @@ describe('cli', () => {

// `kit@next` moves fast - only a real install/build/check catches options it removed
if (projectName === 'create-experimental-next' && process.platform !== 'win32') {
const run = (cmd: string, cmdArgs: string[]) =>
exec(cmd, cmdArgs, { nodeOptions: { stdio: 'pipe', cwd: testOutputPath } });

const install = await run('pnpm', [
'install',
'--no-frozen-lockfile',
Expand Down Expand Up @@ -279,10 +280,8 @@ describe('cli', () => {
for (const cmd of cmds) {
// use npm here so the install doesn't walk up into the monorepo's
// pnpm workspace and try to resolve packages from there
const res = await exec('npm', cmd, {
const res = await run('npm', cmd, {
nodeOptions: {
stdio: 'pipe',
cwd: testOutputPath,
env: {
...process.env,
// allow npm under a repo whose packageManager is pnpm
Expand Down
4 changes: 4 additions & 0 deletions packages/sv/src/core/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,7 @@ export async function runAndValidateVerifications(verifications: Verification[])
}
}
}

export function isNodeError(err: unknown): err is Error & NodeJS.ErrnoException {
return err instanceof Error;
}
11 changes: 7 additions & 4 deletions packages/sv/src/core/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,13 @@ export function prepareSvApi(
throwOnError: true
});
} catch (error) {
const typedError = error as NonZeroExitError;
throw new Error(`Failed to execute scripts '${executedCommand}': ${typedError.message}`, {
cause: error
});
let msg;
if (error instanceof NonZeroExitError || error instanceof Error) {
msg = `Failed to execute scripts '${executedCommand}': ${error.message}`;
} else {
msg = 'unknown error';
}
throw new Error(msg, { cause: error });
}
},
dependency: (pkg, version) => {
Expand Down
25 changes: 16 additions & 9 deletions packages/sv/src/core/formatFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import * as p from '@clack/prompts';
import { type AgentName, loadPackageJson, resolveCommand } from '@sveltejs/sv-utils';
import fs from 'node:fs';
import path from 'node:path';
import { exec } from 'tinyexec';
import { exec, NonZeroExitError } from 'tinyexec';
import { isNodeError } from './common.ts';
import { detectPackageManager } from './package-manager.ts';
import { findWorkspaceRoot } from './workspace.ts';

Expand Down Expand Up @@ -85,15 +86,21 @@ async function run(
cwd: string
): Promise<{ error?: string; notFound?: boolean }> {
try {
await exec(command, args, { nodeOptions: { cwd, stdio: 'pipe' }, throwOnError: true });
await exec(command, args, { nodeOptions: { cwd }, throwOnError: true });
return {};
} catch (e) {
// @ts-expect-error tinyexec rethrows the spawn error as-is
if (e?.code === 'ENOENT') return { notFound: true, error: `${command} not found` };
// @ts-expect-error `output` is only present on tinyexec's `NonZeroExitError`
const output = e?.output as { stderr?: string; stdout?: string } | undefined;
// failures can land on either stream, so report both
const message = [output?.stderr, output?.stdout].filter(Boolean).join('\n').trim();
return { error: message || (e instanceof Error ? e.message : 'unknown error') };
if (isNodeError(e) && e.code === 'ENOENT') {
return { notFound: true, error: `${command} not found` };
}
if (e instanceof NonZeroExitError) {
// failures can land on either stream, so report both
const { stderr, stdout } = e.output ?? {};
const message = [stderr, stdout].filter(Boolean).join('\n').trim();
return { error: message || e.message };
}
if (e instanceof Error) {
return { error: e.message };
}
return { error: 'unknown error' };
}
}
17 changes: 3 additions & 14 deletions packages/sv/src/core/package-manager.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import * as p from '@clack/prompts';
import {
AGENTS,
type AgentName,
COMMANDS,
color,
constructCommand,
detect,
pnpm
} from '@sveltejs/sv-utils';
import { AGENTS, type AgentName, color, detect, pnpm, resolveCommand } from '@sveltejs/sv-utils';
import { Option } from 'commander';
import * as find from 'empathic/find';
import fs from 'node:fs';
Expand Down Expand Up @@ -73,12 +65,9 @@ export async function installDependencies(agent: AgentName, cwd: string): Promis
retainLog: true
});

const { command, args } = constructCommand(COMMANDS[agent].install, [])!;
const { command, args } = resolveCommand(agent, 'install', [])!;

const proc = exec(command, args, {
nodeOptions: { cwd, stdio: 'pipe' },
throwOnError: false
});
const proc = exec(command, args, { nodeOptions: { cwd }, throwOnError: false });

const output: string[] = [];
try {
Expand Down
Loading