mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
fix(cli): add --dry-run option
This commit is contained in:
@@ -4,6 +4,7 @@ export interface SyncOptions {
|
||||
stateful?: boolean;
|
||||
raw?: boolean;
|
||||
yes?: boolean;
|
||||
dryRun?: boolean;
|
||||
skipPull?: boolean;
|
||||
failConflicts?: boolean;
|
||||
plainSecrets?: boolean;
|
||||
|
||||
+9
-1
@@ -36,7 +36,6 @@ import {
|
||||
type SimplifiedSettings,
|
||||
} from "./settings.ts";
|
||||
import { deepEqual } from "./utils.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { getActiveWorkspace } from "./workspace.ts";
|
||||
|
||||
export interface Instance {
|
||||
@@ -182,6 +181,7 @@ export type InstanceSyncOptions = {
|
||||
baseUrl?: string;
|
||||
token?: string;
|
||||
folderPerInstance?: boolean;
|
||||
dryRun?: boolean;
|
||||
yes?: boolean;
|
||||
prefix?: string;
|
||||
prefixSettings?: boolean;
|
||||
@@ -290,6 +290,10 @@ async function instancePull(opts: InstanceSyncOptions) {
|
||||
|
||||
if (totalChanges > 0) {
|
||||
let confirm = true;
|
||||
if (opts.dryRun) {
|
||||
log.info(colors.gray(`Dry run complete.`));
|
||||
return;
|
||||
}
|
||||
if (opts.yes !== true) {
|
||||
confirm = await Confirm.prompt({
|
||||
message: `Do you want to pull these ${totalChanges} instance-level changes?`,
|
||||
@@ -368,6 +372,7 @@ async function instancePull(opts: InstanceSyncOptions) {
|
||||
includeUsers: true,
|
||||
includeKey: true,
|
||||
yes: opts.yes,
|
||||
dryRun: opts.dryRun,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -535,6 +540,7 @@ async function instancePush(opts: InstanceSyncOptions) {
|
||||
includeUsers: true,
|
||||
includeKey: true,
|
||||
yes: opts.yes,
|
||||
dryRun: opts.dryRun,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -711,6 +717,7 @@ const command = new Command()
|
||||
"Pull instance settings, users, configs, instance groups and overwrite local"
|
||||
)
|
||||
.option("--yes", "Pull without needing confirmation")
|
||||
.option("--dry-run", "Perform a dry run without making changes")
|
||||
.option("--skip-users", "Skip pulling users")
|
||||
.option("--skip-settings", "Skip pulling settings")
|
||||
.option("--skip-configs", "Skip pulling configs (worker groups and SMTP)")
|
||||
@@ -735,6 +742,7 @@ const command = new Command()
|
||||
"Push instance settings, users, configs, group and overwrite remote"
|
||||
)
|
||||
.option("--yes", "Push without needing confirmation")
|
||||
.option("--dry-run", "Perform a dry run without making changes")
|
||||
.option("--skip-users", "Skip pushing users")
|
||||
.option("--skip-settings", "Skip pushing settings")
|
||||
.option("--skip-configs", "Skip pushing configs (worker groups and SMTP)")
|
||||
|
||||
+32
-25
@@ -119,7 +119,7 @@ export async function findResourceFile(path: string) {
|
||||
if (validCandidates.length > 1) {
|
||||
throw new Error(
|
||||
"Found two resource files for the same resource" +
|
||||
validCandidates.join(", ")
|
||||
validCandidates.join(", ")
|
||||
);
|
||||
}
|
||||
if (validCandidates.length < 1) {
|
||||
@@ -249,20 +249,20 @@ export async function handleFile(
|
||||
let typed = opts?.skipScriptsMetadata
|
||||
? undefined
|
||||
: (
|
||||
await parseMetadataFile(
|
||||
remotePath,
|
||||
opts
|
||||
? {
|
||||
...opts,
|
||||
path,
|
||||
workspaceRemote: workspace,
|
||||
schemaOnly: codebase ? true : undefined,
|
||||
}
|
||||
: undefined,
|
||||
globalDeps,
|
||||
codebases
|
||||
)
|
||||
)?.payload;
|
||||
await parseMetadataFile(
|
||||
remotePath,
|
||||
opts
|
||||
? {
|
||||
...opts,
|
||||
path,
|
||||
workspaceRemote: workspace,
|
||||
schemaOnly: codebase ? true : undefined,
|
||||
}
|
||||
: undefined,
|
||||
globalDeps,
|
||||
codebases
|
||||
)
|
||||
)?.payload;
|
||||
|
||||
const workspaceId = workspace.workspaceId;
|
||||
|
||||
@@ -347,19 +347,19 @@ export async function handleFile(
|
||||
deepEqual(typed.schema, remote.schema) &&
|
||||
typed.tag == remote.tag &&
|
||||
(typed.ws_error_handler_muted ?? false) ==
|
||||
remote.ws_error_handler_muted &&
|
||||
remote.ws_error_handler_muted &&
|
||||
typed.dedicated_worker == remote.dedicated_worker &&
|
||||
typed.cache_ttl == remote.cache_ttl &&
|
||||
typed.concurrency_time_window_s ==
|
||||
remote.concurrency_time_window_s &&
|
||||
remote.concurrency_time_window_s &&
|
||||
typed.concurrent_limit == remote.concurrent_limit &&
|
||||
Boolean(typed.restart_unless_cancelled) ==
|
||||
Boolean(remote.restart_unless_cancelled) &&
|
||||
Boolean(remote.restart_unless_cancelled) &&
|
||||
Boolean(typed.visible_to_runner_only) ==
|
||||
Boolean(remote.visible_to_runner_only) &&
|
||||
Boolean(remote.visible_to_runner_only) &&
|
||||
Boolean(typed.no_main_func) == Boolean(remote.no_main_func) &&
|
||||
Boolean(typed.has_preprocessor) ==
|
||||
Boolean(remote.has_preprocessor) &&
|
||||
Boolean(remote.has_preprocessor) &&
|
||||
typed.priority == Boolean(remote.priority) &&
|
||||
typed.timeout == remote.timeout &&
|
||||
//@ts-ignore
|
||||
@@ -450,7 +450,8 @@ async function createScript(
|
||||
});
|
||||
} catch (e: any) {
|
||||
throw Error(
|
||||
`Script creation for ${body.path} with parent ${body.parent_hash
|
||||
`Script creation for ${body.path} with parent ${
|
||||
body.parent_hash
|
||||
} was not successful: ${e.body ?? e.message} `
|
||||
);
|
||||
}
|
||||
@@ -476,7 +477,8 @@ async function createScript(
|
||||
});
|
||||
if (req.status != 201) {
|
||||
throw Error(
|
||||
`Script snapshot creation was not successful: ${req.status} - ${req.statusText
|
||||
`Script snapshot creation was not successful: ${req.status} - ${
|
||||
req.statusText
|
||||
} - ${await req.text()} `
|
||||
);
|
||||
}
|
||||
@@ -488,8 +490,8 @@ export async function findContentFile(filePath: string) {
|
||||
const candidates = filePath.endsWith("script.json")
|
||||
? exts.map((x) => filePath.replace(".script.json", x))
|
||||
: filePath.endsWith("script.lock")
|
||||
? exts.map((x) => filePath.replace(".script.lock", x))
|
||||
: exts.map((x) => filePath.replace(".script.yaml", x));
|
||||
? exts.map((x) => filePath.replace(".script.lock", x))
|
||||
: exts.map((x) => filePath.replace(".script.yaml", x));
|
||||
|
||||
const validCandidates = (
|
||||
await Promise.all(
|
||||
@@ -508,7 +510,7 @@ export async function findContentFile(filePath: string) {
|
||||
if (validCandidates.length > 1) {
|
||||
throw new Error(
|
||||
"No content path given and more than one candidate found: " +
|
||||
validCandidates.join(", ")
|
||||
validCandidates.join(", ")
|
||||
);
|
||||
}
|
||||
if (validCandidates.length < 1) {
|
||||
@@ -964,6 +966,10 @@ async function generateMetadata(
|
||||
}
|
||||
}
|
||||
if (hasAny) {
|
||||
if (opts.dryRun) {
|
||||
log.info(colors.gray(`Dry run complete.`));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!opts.yes &&
|
||||
!(await Confirm.prompt({
|
||||
@@ -1027,6 +1033,7 @@ const command = new Command()
|
||||
)
|
||||
.arguments("[script:file]")
|
||||
.option("--yes", "Skip confirmation prompt")
|
||||
.option("--dry-run", "Perform a dry run without making changes")
|
||||
.option("--lock-only", "re-generate only the lock")
|
||||
.option("--schema-only", "re-generate only script schema")
|
||||
.option(
|
||||
|
||||
+56
-40
@@ -117,7 +117,7 @@ async function addCodebaseDigestIfRelevant(
|
||||
const parsed: any = yamlParseContent(path, content);
|
||||
if (parsed && typeof parsed == "object") {
|
||||
if (ignoreCodebaseChanges) {
|
||||
parsed["codebase"] = undefined
|
||||
parsed["codebase"] = undefined;
|
||||
} else {
|
||||
parsed["codebase"] = await c.getDigest();
|
||||
}
|
||||
@@ -367,12 +367,12 @@ function ZipFSElement(
|
||||
)
|
||||
? "flow"
|
||||
: p.endsWith("app.json")
|
||||
? "app"
|
||||
: p.endsWith("script.json")
|
||||
? "script"
|
||||
: p.endsWith("resource.json")
|
||||
? "resource"
|
||||
: "other";
|
||||
? "app"
|
||||
: p.endsWith("script.json")
|
||||
? "script"
|
||||
: p.endsWith("resource.json")
|
||||
? "resource"
|
||||
: "other";
|
||||
|
||||
const isJson = p.endsWith(".json");
|
||||
|
||||
@@ -402,7 +402,7 @@ function ZipFSElement(
|
||||
yield {
|
||||
isDirectory: false,
|
||||
path: path.join(finalPath, s.path),
|
||||
async *getChildren() { },
|
||||
async *getChildren() {},
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return s.content;
|
||||
@@ -413,7 +413,7 @@ function ZipFSElement(
|
||||
yield {
|
||||
isDirectory: false,
|
||||
path: path.join(finalPath, "flow.yaml"),
|
||||
async *getChildren() { },
|
||||
async *getChildren() {},
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return yamlStringify(flow, yamlOptions);
|
||||
@@ -429,7 +429,7 @@ function ZipFSElement(
|
||||
yield {
|
||||
isDirectory: false,
|
||||
path: path.join(finalPath, s.path),
|
||||
async *getChildren() { },
|
||||
async *getChildren() {},
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return s.content;
|
||||
@@ -440,7 +440,7 @@ function ZipFSElement(
|
||||
yield {
|
||||
isDirectory: false,
|
||||
path: path.join(finalPath, "app.yaml"),
|
||||
async *getChildren() { },
|
||||
async *getChildren() {},
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return yamlStringify(app, yamlOptions);
|
||||
@@ -508,7 +508,7 @@ function ZipFSElement(
|
||||
r.push({
|
||||
isDirectory: false,
|
||||
path: removeSuffix(finalPath, ".json") + ".lock",
|
||||
async *getChildren() { },
|
||||
async *getChildren() {},
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return lock;
|
||||
@@ -531,7 +531,7 @@ function ZipFSElement(
|
||||
removeSuffix(finalPath, ".resource.json") +
|
||||
".resource.file." +
|
||||
formatExtension,
|
||||
async *getChildren() { },
|
||||
async *getChildren() {},
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return fileContent;
|
||||
@@ -592,19 +592,19 @@ export async function* readDirRecursiveWithIgnore(
|
||||
// getContentBytes(): Promise<Uint8Array>;
|
||||
getContentText(): Promise<string>;
|
||||
}[] = [
|
||||
{
|
||||
path: root.path,
|
||||
ignored: ignore(root.path, root.isDirectory),
|
||||
isDirectory: root.isDirectory,
|
||||
c: root.getChildren,
|
||||
// getContentBytes(): Promise<Uint8Array> {
|
||||
// throw undefined;
|
||||
// },
|
||||
getContentText(): Promise<string> {
|
||||
throw undefined;
|
||||
},
|
||||
{
|
||||
path: root.path,
|
||||
ignored: ignore(root.path, root.isDirectory),
|
||||
isDirectory: root.isDirectory,
|
||||
c: root.getChildren,
|
||||
// getContentBytes(): Promise<Uint8Array> {
|
||||
// throw undefined;
|
||||
// },
|
||||
getContentText(): Promise<string> {
|
||||
throw undefined;
|
||||
},
|
||||
];
|
||||
},
|
||||
];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const e = stack.pop()!;
|
||||
@@ -667,7 +667,8 @@ export async function elementsToMap(
|
||||
if (!skips.includeSettings && path === "settings" + ext) continue;
|
||||
if (!skips.includeKey && path === "encryption_key") continue;
|
||||
if (skips.skipResources && path.endsWith(".resource" + ext)) continue;
|
||||
if (skips.skipResourceTypes && path.endsWith(".resource-type" + ext)) continue;
|
||||
if (skips.skipResourceTypes && path.endsWith(".resource-type" + ext))
|
||||
continue;
|
||||
|
||||
if (skips.skipVariables && path.endsWith(".variable" + ext)) continue;
|
||||
|
||||
@@ -745,9 +746,9 @@ async function compareDynFSElement(
|
||||
): Promise<Change[]> {
|
||||
const [m1, m2] = els2
|
||||
? await Promise.all([
|
||||
elementsToMap(els1, ignore, json, skips),
|
||||
elementsToMap(els2, ignore, json, skips),
|
||||
])
|
||||
elementsToMap(els1, ignore, json, skips),
|
||||
elementsToMap(els2, ignore, json, skips),
|
||||
])
|
||||
: [await elementsToMap(els1, ignore, json, skips), {}];
|
||||
|
||||
const changes: Change[] = [];
|
||||
@@ -808,7 +809,6 @@ async function compareDynFSElement(
|
||||
continue;
|
||||
}
|
||||
if (!ignoreCodebaseChanges) {
|
||||
|
||||
if (before.codebase != undefined) {
|
||||
delete before.codebase;
|
||||
m2[k] = yamlStringify(before, yamlOptions);
|
||||
@@ -835,7 +835,6 @@ async function compareDynFSElement(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const remoteCodebase: Record<string, string> = {};
|
||||
for (const [k] of Object.entries(m2)) {
|
||||
if (m1[k] === undefined) {
|
||||
@@ -864,7 +863,7 @@ async function compareDynFSElement(
|
||||
continue;
|
||||
}
|
||||
const c = findCodebase(tsFile, codebases);
|
||||
if (await c?.getDigest() != v) {
|
||||
if ((await c?.getDigest()) != v) {
|
||||
changes.push({
|
||||
name: "edited",
|
||||
path: tsFile,
|
||||
@@ -1126,7 +1125,7 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
opts.includeGroups,
|
||||
opts.includeSettings,
|
||||
opts.includeKey,
|
||||
opts.defaultTs,
|
||||
opts.defaultTs
|
||||
))!,
|
||||
!opts.json,
|
||||
opts.defaultTs ?? "bun",
|
||||
@@ -1152,6 +1151,10 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
);
|
||||
if (changes.length > 0) {
|
||||
prettyChanges(changes);
|
||||
if (opts.dryRun) {
|
||||
log.info(colors.gray(`Dry run complete.`));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!opts.yes &&
|
||||
!(await Confirm.prompt({
|
||||
@@ -1317,8 +1320,8 @@ function prettyChanges(changes: Change[]) {
|
||||
log.info(
|
||||
colors.yellow(
|
||||
`~ ${getTypeStrFromPath(change.path)} ` +
|
||||
change.path +
|
||||
(change.codebase ? ` (codebase changed)` : "")
|
||||
change.path +
|
||||
(change.codebase ? ` (codebase changed)` : "")
|
||||
)
|
||||
);
|
||||
if (change.before != change.after) {
|
||||
@@ -1401,7 +1404,7 @@ export async function push(opts: GlobalOptions & SyncOptions) {
|
||||
opts.includeGroups,
|
||||
opts.includeSettings,
|
||||
opts.includeKey,
|
||||
opts.defaultTs,
|
||||
opts.defaultTs
|
||||
))!,
|
||||
!opts.json,
|
||||
opts.defaultTs ?? "bun",
|
||||
@@ -1409,7 +1412,7 @@ export async function push(opts: GlobalOptions & SyncOptions) {
|
||||
false
|
||||
);
|
||||
|
||||
const local = await FSFSElement(path.join(Deno.cwd(), "",), codebases, false);
|
||||
const local = await FSFSElement(path.join(Deno.cwd(), ""), codebases, false);
|
||||
const changes = await compareDynFSElement(
|
||||
local,
|
||||
remote,
|
||||
@@ -1423,7 +1426,6 @@ export async function push(opts: GlobalOptions & SyncOptions) {
|
||||
|
||||
const globalDeps = await findGlobalDeps();
|
||||
|
||||
|
||||
const tracker: ChangeTracker = await buildTracker(changes);
|
||||
|
||||
const staleScripts: string[] = [];
|
||||
@@ -1489,6 +1491,10 @@ export async function push(opts: GlobalOptions & SyncOptions) {
|
||||
|
||||
if (changes.length > 0) {
|
||||
prettyChanges(changes);
|
||||
if (opts.dryRun) {
|
||||
log.info(colors.gray(`Dry run complete.`));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!opts.yes &&
|
||||
!(await Confirm.prompt({
|
||||
@@ -1527,7 +1533,8 @@ export async function push(opts: GlobalOptions & SyncOptions) {
|
||||
}
|
||||
const groupedChangesArray = Array.from(groupedChanges.entries());
|
||||
log.info(
|
||||
`found changes for ${groupedChangesArray.length
|
||||
`found changes for ${
|
||||
groupedChangesArray.length
|
||||
} items with a total of ${groupedChangesArray.reduce(
|
||||
(acc, [_, changes]) => acc + changes.length,
|
||||
0
|
||||
@@ -1840,7 +1847,8 @@ export async function push(opts: GlobalOptions & SyncOptions) {
|
||||
}
|
||||
log.info(
|
||||
colors.bold.green.underline(
|
||||
`\nDone! All ${changes.length} changes pushed to the remote workspace ${workspace.workspaceId
|
||||
`\nDone! All ${changes.length} changes pushed to the remote workspace ${
|
||||
workspace.workspaceId
|
||||
} named ${workspace.name} (${(performance.now() - start).toFixed(0)}ms)`
|
||||
)
|
||||
);
|
||||
@@ -1858,6 +1866,10 @@ const command = new Command()
|
||||
.command("pull")
|
||||
.description("Pull any remote changes and apply them locally.")
|
||||
.option("--yes", "Pull without needing confirmation")
|
||||
.option(
|
||||
"--dry-run",
|
||||
"Show changes that would be pulled without actually pushing"
|
||||
)
|
||||
.option("--plain-secrets", "Pull secrets as plain text")
|
||||
.option("--json", "Use JSON instead of YAML")
|
||||
.option("--skip-variables", "Skip syncing variables (including secrets)")
|
||||
@@ -1888,6 +1900,10 @@ const command = new Command()
|
||||
.command("push")
|
||||
.description("Push any local changes and apply them remotely.")
|
||||
.option("--yes", "Push without needing confirmation")
|
||||
.option(
|
||||
"--dry-run",
|
||||
"Show changes that would be pushed without actually pushing"
|
||||
)
|
||||
.option("--plain-secrets", "Push secrets as plain text")
|
||||
.option("--json", "Use JSON instead of YAML")
|
||||
.option("--skip-variables", "Skip syncing variables (including secrets)")
|
||||
|
||||
Reference in New Issue
Block a user