feat(app-dev): add dry-run preview to dev sync (#21251)

Split out of #21240. Stacked on #21250 (review/merge that first).

`yarn twenty dev --once --dry-run` computes the migration plan and
prints the diff **without applying anything** (no migration, no
app-record update, no SDK generation). Also renders the diff on a normal
`dev --once` sync.

<img width="646" height="179" alt="image"
src="https://github.com/user-attachments/assets/59f3ddcd-2a5b-4b8a-b21a-c659abe16af0"
/>
This commit is contained in:
martmull
2026-06-05 19:49:02 +02:00
committed by GitHub
parent bfb83e93b2
commit 6c65d26ced
19 changed files with 257 additions and 51 deletions
@@ -7,6 +7,7 @@ import chalk from 'chalk';
export type AppDevOnceCommandOptions = {
appPath?: string;
verbose?: boolean;
dryRun?: boolean;
};
export class AppDevOnceCommand {
@@ -17,12 +18,17 @@ export class AppDevOnceCommand {
const remoteName = ConfigService.getActiveRemote();
console.log(chalk.blue(`Syncing application on ${remoteName}...`));
console.log(
chalk.blue(
`${options.dryRun ? 'Previewing application diff' : 'Syncing application'} on ${remoteName}...`,
),
);
console.log(chalk.gray(`App path: ${appPath}\n`));
const result = await appDevOnce({
appPath,
verbose: options.verbose,
dryRun: options.dryRun,
onProgress: (message) => console.log(chalk.gray(message)),
});
@@ -31,6 +37,16 @@ export class AppDevOnceCommand {
process.exit(1);
}
if (options.dryRun) {
console.log(
chalk.green(
`\n✓ Dry run complete for ${result.data.applicationDisplayName} — no changes were applied`,
),
);
return;
}
console.log(
chalk.green(
`\n✓ Synced ${result.data.applicationDisplayName} (${result.data.fileCount} file${result.data.fileCount === 1 ? '' : 's'})`,
@@ -22,6 +22,7 @@ export const registerDevCommands = (program: Command): void => {
verbose?: boolean;
debug?: boolean;
debounceMs?: string;
dryRun?: boolean;
},
) => {
const commonOptions = {
@@ -33,7 +34,10 @@ export const registerDevCommands = (program: Command): void => {
};
if (options.once) {
await devOnceCommand.execute(commonOptions);
await devOnceCommand.execute({
...commonOptions,
dryRun: options.dryRun,
});
return;
}
@@ -48,6 +52,10 @@ export const registerDevCommands = (program: Command): void => {
'-o, --once',
'Build and sync once, then exit (useful for CI, scripts, and pre-commit hooks)',
)
.option(
'--dry-run',
'Preview the metadata changes without applying them (requires --once)',
)
.option('--debounceMs <ms>', 'Debounce in ms (default: 2 000)')
.option('-v, --verbose', 'Show detailed logs')
.option('-d, --debug', 'Show detailed logs (alias for --verbose)')