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
1 change: 1 addition & 0 deletions packages/cli/src/cli/cmd/run/_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const flagsSchema = z.object({
apiKey: z.string().optional(),
force: z.boolean().optional(),
frozen: z.boolean().optional(),
dryRun: z.boolean().optional(),
verbose: z.boolean().optional(),
strict: z.boolean().optional(),
interactive: z.boolean().default(false),
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/src/cli/cmd/run/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
renderHero,
pauseIfDebug,
renderSummary,
renderDryRun,
} from "../../utils/ui";
import trackEvent from "../../utils/observability";
import { determineAuthId } from "./_utils";
Expand Down Expand Up @@ -95,6 +96,10 @@ export default new Command()
"--frozen",
"Validate translations are up-to-date without making changes - fails if source files, target files, or lockfile are out of sync. Ideal for CI/CD to ensure translation consistency before deployment",
)
.option(
"--dry-run",
"Preview planned changes without writing any files or checksums",
)
.option(
"--api-key <api-key>",
"Override API key from settings or environment variables",
Expand Down Expand Up @@ -153,6 +158,15 @@ export default new Command()
await frozen(ctx);
await renderSpacer();

if (ctx.flags.dryRun) {
await renderDryRun(ctx.tasks);
await trackEvent(authId, "cmd.run.success", {
config: ctx.config,
flags: ctx.flags,
});
return;
}
Comment on lines +161 to +168
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

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

The frozen function runs before the dry-run check (line 158), but dry-run mode skips provider initialization which may be required by the frozen validation. Consider either: 1) moving the dry-run check before the frozen(ctx) call, or 2) making the frozen function also skip when dryRun is enabled to avoid potential null reference errors when accessing ctx.localizer.

Copilot uses AI. Check for mistakes.

await execute(ctx);
await renderSpacer();

Expand Down
8 changes: 6 additions & 2 deletions packages/cli/src/cli/cmd/run/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default async function setup(input: CmdRunContext) {
},
{
title: "Selecting localization provider",
enabled: (ctx) => !ctx.flags.dryRun,
task: async (ctx, task) => {
ctx.localizer = createLocalizer(
ctx.config?.provider,
Expand All @@ -67,7 +68,8 @@ export default async function setup(input: CmdRunContext) {
},
{
title: "Checking authentication",
enabled: (ctx) => ctx.localizer?.id === "Lingo.dev",
enabled: (ctx) =>
!ctx.flags.dryRun && ctx.localizer?.id === "Lingo.dev",
task: async (ctx, task) => {
const authStatus = await ctx.localizer!.checkAuth();
if (!authStatus.authenticated) {
Expand All @@ -80,7 +82,8 @@ export default async function setup(input: CmdRunContext) {
},
{
title: "Validating configuration",
enabled: (ctx) => ctx.localizer?.id !== "Lingo.dev",
enabled: (ctx) =>
!ctx.flags.dryRun && ctx.localizer?.id !== "Lingo.dev",
task: async (ctx, task) => {
const validationStatus = await ctx.localizer!.validateSettings!();
if (!validationStatus.valid) {
Expand All @@ -93,6 +96,7 @@ export default async function setup(input: CmdRunContext) {
},
{
title: "Initializing localization provider",
enabled: (ctx) => !ctx.flags.dryRun,
async task(ctx, task) {
const isLingoDotDev = ctx.localizer!.id === "Lingo.dev";

Expand Down
15 changes: 15 additions & 0 deletions packages/cli/src/cli/utils/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import figlet from "figlet";
import { vice } from "gradient-string";
import readline from "readline";
import { colors } from "../constants";
import type { CmdRunTask } from "../cmd/run/_types";

export async function renderClear() {
console.log("\x1Bc");
Expand Down Expand Up @@ -137,3 +138,17 @@ export async function renderSummary(results: Map<any, any>) {
}
}
}

export async function renderDryRun(tasks: CmdRunTask[]) {
console.log(chalk.hex(colors.orange)("[Dry Run]"));
if (!tasks.length) {
console.log(chalk.dim("No tasks would be executed."));
return;
}
for (const t of tasks) {
const displayPath = t.bucketPathPattern.replace("[locale]", t.targetLocale);
console.log(
` • ${chalk.dim(displayPath)} ${chalk.hex(colors.yellow)(`(${t.sourceLocale} → ${t.targetLocale})`)}`,
);
}
}
Loading