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
3 changes: 3 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ const routes = buildRouteMap({

export const app = buildApplication(addGlobalFlags(routes), {
name: CLI_NAME,
scanner: {
allowArgumentEscapeSequence: true

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.

This affects all commands btw, not just scratch, so might be a good moment to think if this might negatively affect any other command.

},
versionInfo: {
currentVersion: getCLIVersion(),
getLatestVersion,
Expand Down
16 changes: 9 additions & 7 deletions src/commands/scratch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,14 @@ export async function implementation(this: LocalContext, flags: Flags, ...comman
const hasBinary = command.length > 0;

if (hasQuery && hasBinary) {
this.process.stderr.write(chalk.red('Use either --execute/-x or a binary command, not both.\n'));
this.process.stderr.write(
chalk.red('Use either --execute/-x <sql> or -- <command> [arguments...], not both.\n')
);
this.process.exit(1);
}

if (!hasQuery && !hasBinary) {
this.process.stderr.write(
chalk.red("Expected --execute/-x <sql> or a binary command, for example 'xata scratch psql'.\n")
);
this.process.stderr.write(chalk.red('Expected --execute/-x <sql> or -- <command> [arguments...].\n'));
this.process.exit(1);
}

Expand Down Expand Up @@ -379,8 +379,10 @@ export const ScratchCommand = buildCommand({
fullDescription:
'Creates a branch from the parent given, runs what it is asked to, and deletes the branch afterwards, so a query or a migration can be tried against real data without touching an existing branch.',
customUsage: [
{ input: '--execute "select count(*) from users"', brief: 'Run a query against a throwaway copy' },
{ input: 'psql', brief: 'Open a Postgres client on the scratch branch' }
{ input: '--execute "select count(*) from users"', brief: 'Run SQL with the built-in client' },
{ input: '-x "select count(*) from users"', brief: 'Run SQL using the short execute flag' },
{ input: '-- psql -c "select count(*) from users"', brief: 'Run psql with arguments' },
{ input: '-- npm run migrate', brief: 'Run a database tool against the scratch branch' }
]
},
parameters: {
Expand Down Expand Up @@ -428,7 +430,7 @@ export const ScratchCommand = buildCommand({
kind: 'array',
minimum: 0,
parameter: {
brief: 'Binary command to run with scratch database environment variables',
brief: 'Binary command to run with scratch database environment variables; pass arguments to the binary after --',
parse: String,
placeholder: 'command'
}
Expand Down
6 changes: 4 additions & 2 deletions src/commands/scratch.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,13 @@ describe('scratch command', () => {
const binary = path.join(tempDir, 'check-env');
fs.writeFileSync(
binary,
`#!/usr/bin/env bun\nif (!process.env.DATABASE_URL || !process.env.XATA_DATABASE_URL || !process.env.PGHOST || process.env.PGDATABASE !== 'app') process.exit(6);\nprocess.exit(7);\n`
`#!/usr/bin/env bun\nif (!process.env.DATABASE_URL || !process.env.XATA_DATABASE_URL || !process.env.PGHOST || process.env.PGDATABASE !== 'app') process.exit(6);\nif (JSON.stringify(Bun.argv.slice(2)) !== JSON.stringify(['-c', 'SELECT version();', '--profile', 'child', '--debug'])) process.exit(8);\nprocess.exit(7);\n`
);
fs.chmodSync(binary, 0o755);

await expect(implementation.call(context, { json: false }, binary)).rejects.toThrow('exit:7');
await expect(
implementation.call(context, { json: false }, binary, '-c', 'SELECT version();', '--profile', 'child', '--debug')
).rejects.toThrow('exit:7');

expect(deleteBranch).toHaveBeenCalledTimes(1);
});
Expand Down
4 changes: 4 additions & 0 deletions src/lib/global-flags.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ describe('getDebugFlag', () => {
expect(getDebugFlag(['branch', 'list'])).toBe(false);
});

test('does not read a child command flag after --', () => {
expect(getDebugFlag(['scratch', '--', 'tool', '--debug'])).toBe(false);
});

test('is false for arguments it cannot parse', () => {
expect(getDebugFlag(['--'])).toBe(false);
});
Expand Down
4 changes: 4 additions & 0 deletions src/lib/profile.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ describe('getProfileFlag', () => {
expect(getProfileFlag(['branch', 'list', '--json'])).toBeUndefined();
});

test('does not read a child command flag after --', () => {
expect(getProfileFlag(['scratch', '--', 'psql', '--profile', 'child'])).toBeUndefined();
});

test('returns undefined when the flag has no value', () => {
expect(getProfileFlag(['branch', 'list', '--profile'])).toBeUndefined();
});
Expand Down
21 changes: 21 additions & 0 deletions test-binary/e2e/smoke.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@ describe('CLI binary smoke tests', () => {
expect(result.stdout).toMatch(/branch|project|organization/i);
});

test('scratch help shows conventional command forwarding', async () => {
const result = await runCli(['scratch', '--help']);

expect(result.code).toBe(0);
expect(result.stdout).toContain('-- psql -c "select count(*) from users"');
});

test('scratch accepts child flags after the argument delimiter', async () => {
const result = await runCli([
'scratch',
'--',
'definitely-missing-xata-scratch-binary',
'-c',
'select 1'
]);

expect(result.code).toBe(1);
expect(result.stderr).toContain('Executable not found: definitely-missing-xata-scratch-binary');
expect(result.stderr).not.toContain('No alias registered for -c');
});

test('status command works when not initialized', async () => {
const result = await runCli(['status']);

Expand Down
Loading