mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-12 08:05:44 +00:00
fix(cli): handle better apps pushing back-compatibility
This commit is contained in:
@@ -16,6 +16,7 @@ import { ListableApp, Policy } from "../../../gen/types.gen.ts";
|
||||
import { GlobalOptions, isSuperset } from "../../types.ts";
|
||||
import { readInlinePathSync } from "../../utils/utils.ts";
|
||||
import devCommand from "./dev.ts";
|
||||
import { isVersionsGeq15851 } from "../sync/global.ts";
|
||||
|
||||
export interface AppFile {
|
||||
value: any;
|
||||
@@ -58,9 +59,13 @@ export function replaceInlineScripts(rec: any, localPath: string) {
|
||||
if (typeof rec == "object") {
|
||||
return Object.entries(rec).flatMap(([k, v]) => {
|
||||
if (k == "runType") {
|
||||
rec["type"] = "path";
|
||||
if (isVersionsGeq15851()) {
|
||||
rec["type"] = "path";
|
||||
}
|
||||
} else if (k == "inlineScript" && typeof v == "object") {
|
||||
rec["type"] = "inline";
|
||||
if (isVersionsGeq15851()) {
|
||||
rec["type"] = "inline";
|
||||
}
|
||||
const o: Record<string, any> = v as any;
|
||||
|
||||
if (o["content"] && o["content"].startsWith("!inline")) {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { colors, log } from "../../../deps.ts";
|
||||
|
||||
let GLOBAL_VERSIONS: {
|
||||
remoteMajor: number | undefined;
|
||||
remoteMinor: number | undefined;
|
||||
} = {
|
||||
remoteMajor: undefined,
|
||||
remoteMinor: undefined,
|
||||
};
|
||||
|
||||
export function updateGlobalVersions(version: string) {
|
||||
try {
|
||||
const [prefix, remoteMinorStr] = version.split(".");
|
||||
|
||||
GLOBAL_VERSIONS = {
|
||||
remoteMajor: parseInt(prefix.split("v")[1]),
|
||||
remoteMinor: parseInt(remoteMinorStr),
|
||||
};
|
||||
} catch (e) {
|
||||
log.info(colors.gray(`Error reading remote version: ${e}`));
|
||||
}
|
||||
}
|
||||
|
||||
export function isVersionsGeq15851(): boolean {
|
||||
return (
|
||||
GLOBAL_VERSIONS.remoteMajor !== undefined &&
|
||||
GLOBAL_VERSIONS.remoteMajor >= 1 &&
|
||||
GLOBAL_VERSIONS.remoteMinor !== undefined &&
|
||||
GLOBAL_VERSIONS.remoteMinor >= 5851
|
||||
);
|
||||
}
|
||||
+135
-71
@@ -34,7 +34,12 @@ import {
|
||||
} from "../script/script.ts";
|
||||
|
||||
import { handleFile } from "../script/script.ts";
|
||||
import { deepEqual, isFileResource, isRawAppFile, isWorkspaceDependencies } from "../../utils/utils.ts";
|
||||
import {
|
||||
deepEqual,
|
||||
isFileResource,
|
||||
isRawAppFile,
|
||||
isWorkspaceDependencies,
|
||||
} from "../../utils/utils.ts";
|
||||
import {
|
||||
getEffectiveSettings,
|
||||
mergeConfigWithConfigFile,
|
||||
@@ -70,6 +75,7 @@ import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../w
|
||||
import { generateFlowLockInternal } from "../flow/flow_metadata.ts";
|
||||
import { isExecutionModeAnonymous } from "../app/apps.ts";
|
||||
import { generateAppLocksInternal } from "../app/app_metadata.ts";
|
||||
import { updateGlobalVersions } from "./global.ts";
|
||||
|
||||
// Merge CLI options with effective settings, preserving CLI flags as overrides
|
||||
function mergeCliWithEffectiveOptions<
|
||||
@@ -268,29 +274,28 @@ function extractFields(fields: Record<string, any>) {
|
||||
Object.entries(fields).forEach(([k, v]) => {
|
||||
if (typeof v == "object") {
|
||||
if (v.type == "static") {
|
||||
fields[k] = { value: v.value }
|
||||
fields[k] = { value: v.value };
|
||||
} else if (v.type == "javascript") {
|
||||
fields[k] = { expr: v.expr, allowUserResources: v.allowUserResources }
|
||||
fields[k] = { expr: v.expr, allowUserResources: v.allowUserResources };
|
||||
} else if (v.type == "user") {
|
||||
fields[k] = undefined
|
||||
fields[k] = undefined;
|
||||
}
|
||||
}
|
||||
// if (k == 'runType') {
|
||||
// fields["type"] = undefined
|
||||
// fields["schema"] = undefined
|
||||
// }
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function extractFieldsForRawApps(runnables: Record<string, any>) {
|
||||
Object.values(runnables).forEach((v) => {
|
||||
if (typeof v == "object") {
|
||||
if (v.fields !== undefined) {
|
||||
extractFields(v.fields)
|
||||
extractFields(v.fields);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
export function extractInlineScriptsForApps(
|
||||
key: string | undefined,
|
||||
@@ -303,12 +308,12 @@ export function extractInlineScriptsForApps(
|
||||
}
|
||||
if (typeof rec == "object") {
|
||||
return Object.entries(rec).flatMap(([k, v]) => {
|
||||
if (k == 'runType') {
|
||||
rec["type"] = undefined
|
||||
rec["schema"] = undefined
|
||||
return []
|
||||
if (k == "runType") {
|
||||
rec["type"] = undefined;
|
||||
rec["schema"] = undefined;
|
||||
return [];
|
||||
} else if (k == "inlineScript" && typeof v == "object") {
|
||||
rec["type"] = undefined
|
||||
rec["type"] = undefined;
|
||||
const o: Record<string, any> = v as any;
|
||||
const name = toId(key ?? "", rec);
|
||||
const [basePathO, ext] = pathAssigner.assignPath(name, o["language"]);
|
||||
@@ -351,20 +356,26 @@ function ZipFSElement(
|
||||
p: string,
|
||||
f: JSZip.JSZipObject
|
||||
): Promise<DynFSElement[]> {
|
||||
const kind: "flow" | "app" | "script" | "resource" | "other" | "raw_app" | "dependencies" =
|
||||
p.endsWith(".flow.json")
|
||||
? "flow"
|
||||
: p.endsWith(".app.json")
|
||||
? "app"
|
||||
: p.endsWith(".raw_app.json")
|
||||
? "raw_app"
|
||||
: p.endsWith(".script.json")
|
||||
? "script"
|
||||
: p.endsWith(".resource.json")
|
||||
? "resource"
|
||||
: p.startsWith("dependencies/")
|
||||
const kind:
|
||||
| "flow"
|
||||
| "app"
|
||||
| "script"
|
||||
| "resource"
|
||||
| "other"
|
||||
| "raw_app"
|
||||
| "dependencies" = p.endsWith(".flow.json")
|
||||
? "flow"
|
||||
: p.endsWith(".app.json")
|
||||
? "app"
|
||||
: p.endsWith(".raw_app.json")
|
||||
? "raw_app"
|
||||
: p.endsWith(".script.json")
|
||||
? "script"
|
||||
: p.endsWith(".resource.json")
|
||||
? "resource"
|
||||
: p.startsWith("dependencies/")
|
||||
? "dependencies"
|
||||
: "other";
|
||||
: "other";
|
||||
|
||||
const isJson = p.endsWith(".json");
|
||||
|
||||
@@ -502,7 +513,7 @@ function ZipFSElement(
|
||||
undefined,
|
||||
value,
|
||||
newPathAssigner(defaultTs),
|
||||
(key, val_) => key
|
||||
(key, val_) => key
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
@@ -759,7 +770,11 @@ export async function* readDirRecursiveWithIgnore(
|
||||
for await (const e2 of e.c()) {
|
||||
if (e2.isDirectory) {
|
||||
const dirName = e2.path.split(SEP).pop();
|
||||
if (dirName == "node_modules" || dirName == ".claude" || dirName?.startsWith(".")) {
|
||||
if (
|
||||
dirName == "node_modules" ||
|
||||
dirName == ".claude" ||
|
||||
dirName?.startsWith(".")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -792,7 +807,7 @@ export async function elementsToMap(
|
||||
ignore: (path: string, isDirectory: boolean) => boolean,
|
||||
json: boolean,
|
||||
skips: Skips,
|
||||
specificItems?: SpecificItemsConfig,
|
||||
specificItems?: SpecificItemsConfig
|
||||
): Promise<{ [key: string]: string }> {
|
||||
const map: { [key: string]: string } = {};
|
||||
const processedBasePaths = new Set<string>();
|
||||
@@ -802,7 +817,11 @@ export async function elementsToMap(
|
||||
continue;
|
||||
}
|
||||
const path = entry.path;
|
||||
if (!isFileResource(path) && !isRawAppFile(path) && !isWorkspaceDependencies(path)) {
|
||||
if (
|
||||
!isFileResource(path) &&
|
||||
!isRawAppFile(path) &&
|
||||
!isWorkspaceDependencies(path)
|
||||
) {
|
||||
if (json && path.endsWith(".yaml")) continue;
|
||||
if (!json && path.endsWith(".json")) continue;
|
||||
|
||||
@@ -835,7 +854,11 @@ export async function elementsToMap(
|
||||
|
||||
if (isRawAppFile(path)) {
|
||||
const suffix = path.split(".raw_app" + SEP).pop();
|
||||
if (suffix?.startsWith("dist/") || suffix == "wmill.d.ts" || suffix == "package-lock.json") {
|
||||
if (
|
||||
suffix?.startsWith("dist/") ||
|
||||
suffix == "wmill.d.ts" ||
|
||||
suffix == "package-lock.json"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -867,7 +890,6 @@ export async function elementsToMap(
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Use getTypeStrFromPath for consistent type detection
|
||||
try {
|
||||
const fileType = getTypeStrFromPath(path);
|
||||
@@ -876,12 +898,15 @@ export async function elementsToMap(
|
||||
if (skips.skipFlows && fileType === "flow") continue;
|
||||
if (skips.skipApps && fileType === "app") continue;
|
||||
if (skips.skipFolders && fileType === "folder") continue;
|
||||
if (skips.skipWorkspaceDependencies && fileType === "workspace_dependencies") continue;
|
||||
if (
|
||||
skips.skipWorkspaceDependencies &&
|
||||
fileType === "workspace_dependencies"
|
||||
)
|
||||
continue;
|
||||
} catch {
|
||||
// If getTypeStrFromPath can't determine the type, continue processing the file
|
||||
}
|
||||
|
||||
|
||||
// Handle branch-specific files - skip files for other branches
|
||||
if (specificItems && isBranchSpecificFile(path)) {
|
||||
const currentBranch = getCurrentGitBranch();
|
||||
@@ -945,7 +970,7 @@ export async function elementsToMap(
|
||||
// No specific items configuration, use regular path
|
||||
map[entry.path] = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -1090,7 +1115,6 @@ async function compareDynFSElement(
|
||||
|
||||
const remoteCodebase: Record<string, string> = {};
|
||||
for (const [k] of Object.entries(m2)) {
|
||||
|
||||
if (m1[k] === undefined) {
|
||||
if (
|
||||
!ignoreMetadataDeletion ||
|
||||
@@ -1239,7 +1263,7 @@ export const isWhitelisted = (p: string) => {
|
||||
p == "f" ||
|
||||
p == "g" ||
|
||||
p == "users" ||
|
||||
p == "groups" ||
|
||||
p == "groups" ||
|
||||
p == "dependencies"
|
||||
);
|
||||
};
|
||||
@@ -1308,7 +1332,10 @@ export async function ignoreF(wmillconf: {
|
||||
if (wmillconf.includeKey && fileType === "encryption_key") {
|
||||
return false; // Don't ignore, always include
|
||||
}
|
||||
if (!wmillconf.skipWorkspaceDependencies && fileType === "workspace_dependencies") {
|
||||
if (
|
||||
!wmillconf.skipWorkspaceDependencies &&
|
||||
fileType === "workspace_dependencies"
|
||||
) {
|
||||
return false; // Don't ignore workspace dependencies (they are always included unless explicitly skipped)
|
||||
}
|
||||
} catch {
|
||||
@@ -1440,22 +1467,22 @@ export async function pull(
|
||||
// ignore
|
||||
}
|
||||
const zipFile = await downloadZip(
|
||||
workspace,
|
||||
opts.plainSecrets,
|
||||
opts.skipVariables,
|
||||
opts.skipResources,
|
||||
opts.skipResourceTypes,
|
||||
opts.skipSecrets,
|
||||
opts.includeSchedules,
|
||||
opts.includeTriggers,
|
||||
opts.includeUsers,
|
||||
opts.includeGroups,
|
||||
opts.includeSettings,
|
||||
opts.includeKey,
|
||||
opts.skipWorkspaceDependencies,
|
||||
opts.defaultTs
|
||||
);
|
||||
|
||||
workspace,
|
||||
opts.plainSecrets,
|
||||
opts.skipVariables,
|
||||
opts.skipResources,
|
||||
opts.skipResourceTypes,
|
||||
opts.skipSecrets,
|
||||
opts.includeSchedules,
|
||||
opts.includeTriggers,
|
||||
opts.includeUsers,
|
||||
opts.includeGroups,
|
||||
opts.includeSettings,
|
||||
opts.includeKey,
|
||||
opts.skipWorkspaceDependencies,
|
||||
opts.defaultTs
|
||||
);
|
||||
|
||||
const remote = ZipFSElement(
|
||||
zipFile!,
|
||||
!opts.json,
|
||||
@@ -1482,7 +1509,6 @@ export async function pull(
|
||||
`remote (${workspace.name}) -> local: ${changes.length} changes to apply`
|
||||
);
|
||||
|
||||
|
||||
// Handle JSON output for dry-run
|
||||
if (opts.dryRun && opts.jsonOutput) {
|
||||
const result = {
|
||||
@@ -1671,7 +1697,8 @@ export async function pull(
|
||||
await readLockfile(); // ensure wmill-lock.yaml exists
|
||||
|
||||
const tracker: ChangeTracker = await buildTracker(changes);
|
||||
const rawWorkspaceDependencies: Record<string, string> = await getRawWorkspaceDependencies();
|
||||
const rawWorkspaceDependencies: Record<string, string> =
|
||||
await getRawWorkspaceDependencies();
|
||||
|
||||
for (const change of tracker.scripts) {
|
||||
await generateScriptMetadataInternal(
|
||||
@@ -1687,7 +1714,14 @@ export async function pull(
|
||||
}
|
||||
for (const change of tracker.flows) {
|
||||
log.info(`Updating lock for flow ${change}`);
|
||||
await generateFlowLockInternal(change, false, workspace, opts, true, false);
|
||||
await generateFlowLockInternal(
|
||||
change,
|
||||
false,
|
||||
workspace,
|
||||
opts,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
if (tracker.apps.length > 0) {
|
||||
log.info(
|
||||
@@ -1700,7 +1734,14 @@ export async function pull(
|
||||
}
|
||||
for (const change of tracker.rawApps) {
|
||||
log.info(`Updating lock metadata for raw app ${change}`);
|
||||
await generateAppLocksInternal(change, false, workspace, opts, true, true);
|
||||
await generateAppLocksInternal(
|
||||
change,
|
||||
false,
|
||||
workspace,
|
||||
opts,
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
if (opts.jsonOutput) {
|
||||
const result = {
|
||||
@@ -1899,6 +1940,7 @@ export async function push(
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const remote = ZipFSElement(
|
||||
(await downloadZip(
|
||||
workspace,
|
||||
@@ -1935,13 +1977,13 @@ export async function push(
|
||||
specificItems
|
||||
);
|
||||
|
||||
|
||||
const rawWorkspaceDependencies = await getRawWorkspaceDependencies();
|
||||
|
||||
const tracker: ChangeTracker = await buildTracker(changes);
|
||||
|
||||
const staleScripts: string[] = [];
|
||||
const staleFlows: string[] = [];
|
||||
|
||||
for (const change of tracker.scripts) {
|
||||
const stale = await generateScriptMetadataInternal(
|
||||
change,
|
||||
@@ -1995,7 +2037,9 @@ export async function push(
|
||||
}
|
||||
|
||||
const version = await fetchVersion(workspace.remote);
|
||||
|
||||
if (version) {
|
||||
updateGlobalVersions(version);
|
||||
}
|
||||
log.info(colors.gray("Remote version: " + version));
|
||||
|
||||
log.info(
|
||||
@@ -2097,14 +2141,18 @@ export async function push(
|
||||
const alreadySynced: string[] = [];
|
||||
const isRawApp = isRawAppFile(changes[0].path);
|
||||
if (isRawApp) {
|
||||
const deleteRawApp = changes.find(change => change.name === "deleted" && change.path.endsWith(".raw_app/raw_app.yaml"))
|
||||
const deleteRawApp = changes.find(
|
||||
(change) =>
|
||||
change.name === "deleted" &&
|
||||
change.path.endsWith(".raw_app/raw_app.yaml")
|
||||
);
|
||||
if (deleteRawApp) {
|
||||
changes = [deleteRawApp];
|
||||
} else {
|
||||
changes.splice(1, changes.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for await (const change of changes) {
|
||||
let stateTarget = undefined;
|
||||
if (stateful) {
|
||||
@@ -2324,13 +2372,16 @@ export async function push(
|
||||
});
|
||||
break;
|
||||
case "raw_app":
|
||||
if (target.endsWith(".raw_app/raw_app.yaml") || target.endsWith(".raw_app/raw_app.json")) {
|
||||
if (
|
||||
target.endsWith(".raw_app/raw_app.yaml") ||
|
||||
target.endsWith(".raw_app/raw_app.json")
|
||||
) {
|
||||
await wmill.deleteApp({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(target, ".raw_app/raw_app.json"),
|
||||
});
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case "schedule":
|
||||
await wmill.deleteSchedule({
|
||||
workspace: workspaceId,
|
||||
@@ -2426,18 +2477,25 @@ export async function push(
|
||||
});
|
||||
break;
|
||||
case "workspace_dependencies":
|
||||
const relativePath = removePathPrefix(change.path, "dependencies");
|
||||
|
||||
const res = workspaceDependenciesPathToLanguageAndFilename(change.path);
|
||||
const relativePath = removePathPrefix(
|
||||
change.path,
|
||||
"dependencies"
|
||||
);
|
||||
|
||||
const res = workspaceDependenciesPathToLanguageAndFilename(
|
||||
change.path
|
||||
);
|
||||
if (!res) {
|
||||
throw new Error(`Unknown workspace dependencies file format: ${change.path}`);
|
||||
throw new Error(
|
||||
`Unknown workspace dependencies file format: ${change.path}`
|
||||
);
|
||||
}
|
||||
const { name, language } = res;
|
||||
|
||||
await wmill.deleteWorkspaceDependencies({
|
||||
workspace: workspaceId,
|
||||
language,
|
||||
name
|
||||
name,
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -2537,7 +2595,10 @@ const command = new Command()
|
||||
.option("--skip-flows", "Skip syncing flows")
|
||||
.option("--skip-apps", "Skip syncing apps")
|
||||
.option("--skip-folders", "Skip syncing folders")
|
||||
.option("--skip-workspace-dependencies", "Skip syncing workspace dependencies")
|
||||
.option(
|
||||
"--skip-workspace-dependencies",
|
||||
"Skip syncing workspace dependencies"
|
||||
)
|
||||
// .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic")
|
||||
.option("--include-schedules", "Include syncing schedules")
|
||||
.option("--include-triggers", "Include syncing triggers")
|
||||
@@ -2586,7 +2647,10 @@ const command = new Command()
|
||||
.option("--skip-flows", "Skip syncing flows")
|
||||
.option("--skip-apps", "Skip syncing apps")
|
||||
.option("--skip-folders", "Skip syncing folders")
|
||||
.option("--skip-workspace-dependencies", "Skip syncing workspace dependencies")
|
||||
.option(
|
||||
"--skip-workspace-dependencies",
|
||||
"Skip syncing workspace dependencies"
|
||||
)
|
||||
// .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic")
|
||||
.option("--include-schedules", "Include syncing schedules")
|
||||
.option("--include-triggers", "Include syncing triggers")
|
||||
|
||||
Reference in New Issue
Block a user