windmill-cli 1.518.2 → 1.519.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/gen/core/OpenAPI.js +1 -1
- package/esm/gen/services.gen.js +74 -2
- package/esm/{apps.js → src/commands/app/apps.js} +6 -6
- package/esm/{dev.js → src/commands/dev/dev.js} +11 -11
- package/esm/{flow.js → src/commands/flow/flow.js} +13 -13
- package/esm/{folder.js → src/commands/folder/folder.js} +6 -6
- package/esm/src/commands/gitsync-settings/converter.js +134 -0
- package/esm/src/commands/gitsync-settings/gitsync-settings.js +2 -0
- package/esm/src/commands/gitsync-settings/index.js +28 -0
- package/esm/src/commands/gitsync-settings/legacySettings.js +119 -0
- package/esm/src/commands/gitsync-settings/pull.js +372 -0
- package/esm/src/commands/gitsync-settings/push.js +263 -0
- package/esm/src/commands/gitsync-settings/types.js +37 -0
- package/esm/src/commands/gitsync-settings/utils.js +129 -0
- package/esm/{hub.js → src/commands/hub/hub.js} +6 -6
- package/esm/src/commands/init/init.js +214 -0
- package/esm/{instance.js → src/commands/instance/instance.js} +18 -18
- package/esm/{queues.js → src/commands/queues/queues.js} +4 -4
- package/esm/{resource.js → src/commands/resource/resource.js} +7 -7
- package/esm/{resource-type.js → src/commands/resource-type/resource-type.js} +6 -6
- package/esm/{schedule.js → src/commands/schedule/schedule.js} +6 -6
- package/esm/{script.js → src/commands/script/script.js} +14 -14
- package/esm/{pull.js → src/commands/sync/pull.js} +2 -2
- package/esm/{sync.js → src/commands/sync/sync.js} +47 -146
- package/esm/{trigger.js → src/commands/trigger/trigger.js} +6 -6
- package/esm/{user.js → src/commands/user/user.js} +6 -6
- package/esm/{variable.js → src/commands/variable/variable.js} +6 -6
- package/esm/{worker_groups.js → src/commands/worker-groups/worker_groups.js} +5 -5
- package/esm/{workers.js → src/commands/workers/workers.js} +4 -4
- package/esm/{workspace.js → src/commands/workspace/workspace.js} +78 -44
- package/esm/{auth.js → src/core/auth.js} +3 -3
- package/esm/src/core/branch-profiles.js +46 -0
- package/esm/src/core/conf.js +162 -0
- package/esm/src/core/context.js +263 -0
- package/esm/{login.js → src/core/login.js} +2 -2
- package/esm/{settings.js → src/core/settings.js} +8 -8
- package/esm/src/core/store.js +19 -0
- package/esm/src/main.js +174 -0
- package/esm/{types.js → src/types.js} +16 -16
- package/esm/{codebase.js → src/utils/codebase.js} +1 -1
- package/esm/src/utils/git.js +29 -0
- package/esm/{metadata.js → src/utils/metadata.js} +31 -31
- package/esm/{upgrade.js → src/utils/upgrade.js} +1 -1
- package/esm/{utils.js → src/utils/utils.js} +11 -3
- package/esm/windmill-utils-internal/src/config/config.js +190 -0
- package/esm/windmill-utils-internal/src/inline-scripts/extractor.js +13 -9
- package/esm/windmill-utils-internal/src/path-utils/path-assigner.js +25 -9
- package/package.json +6 -6
- package/types/gen/services.gen.d.ts +37 -1
- package/types/gen/services.gen.d.ts.map +1 -1
- package/types/gen/types.gen.d.ts +48 -3
- package/types/gen/types.gen.d.ts.map +1 -1
- package/types/src/commands/app/apps.d.ts +17 -0
- package/types/src/commands/app/apps.d.ts.map +1 -0
- package/types/src/commands/dev/dev.d.ts +12 -0
- package/types/src/commands/dev/dev.d.ts.map +1 -0
- package/types/{flow.d.ts → src/commands/flow/flow.d.ts} +5 -5
- package/types/src/commands/flow/flow.d.ts.map +1 -0
- package/types/{folder.d.ts → src/commands/folder/folder.d.ts} +3 -3
- package/types/src/commands/folder/folder.d.ts.map +1 -0
- package/types/src/commands/gitsync-settings/converter.d.ts +10 -0
- package/types/src/commands/gitsync-settings/converter.d.ts.map +1 -0
- package/types/src/commands/gitsync-settings/gitsync-settings.d.ts +3 -0
- package/types/src/commands/gitsync-settings/gitsync-settings.d.ts.map +1 -0
- package/types/src/commands/gitsync-settings/index.d.ts +25 -0
- package/types/src/commands/gitsync-settings/index.d.ts.map +1 -0
- package/types/src/commands/gitsync-settings/legacySettings.d.ts +4 -0
- package/types/src/commands/gitsync-settings/legacySettings.d.ts.map +1 -0
- package/types/src/commands/gitsync-settings/pull.d.ts +14 -0
- package/types/src/commands/gitsync-settings/pull.d.ts.map +1 -0
- package/types/src/commands/gitsync-settings/push.d.ts +10 -0
- package/types/src/commands/gitsync-settings/push.d.ts.map +1 -0
- package/types/src/commands/gitsync-settings/types.d.ts +35 -0
- package/types/src/commands/gitsync-settings/types.d.ts.map +1 -0
- package/types/src/commands/gitsync-settings/utils.d.ts +36 -0
- package/types/src/commands/gitsync-settings/utils.d.ts.map +1 -0
- package/types/{hub.d.ts → src/commands/hub/hub.d.ts} +2 -2
- package/types/src/commands/hub/hub.d.ts.map +1 -0
- package/types/src/commands/init/init.d.ts +34 -0
- package/types/src/commands/init/init.d.ts.map +1 -0
- package/types/{instance.d.ts → src/commands/instance/instance.d.ts} +1 -1
- package/types/src/commands/instance/instance.d.ts.map +1 -0
- package/types/src/commands/queues/queues.d.ts +14 -0
- package/types/src/commands/queues/queues.d.ts.map +1 -0
- package/types/{resource.d.ts → src/commands/resource/resource.d.ts} +3 -3
- package/types/src/commands/resource/resource.d.ts.map +1 -0
- package/types/src/commands/resource-type/resource-type.d.ts +16 -0
- package/types/src/commands/resource-type/resource-type.d.ts.map +1 -0
- package/types/{schedule.d.ts → src/commands/schedule/schedule.d.ts} +3 -3
- package/types/src/commands/schedule/schedule.d.ts.map +1 -0
- package/types/{script.d.ts → src/commands/script/script.d.ts} +9 -9
- package/types/src/commands/script/script.d.ts.map +1 -0
- package/types/{pull.d.ts → src/commands/sync/pull.d.ts} +3 -3
- package/types/src/commands/sync/pull.d.ts.map +1 -0
- package/types/{sync.d.ts → src/commands/sync/sync.d.ts} +11 -13
- package/types/src/commands/sync/sync.d.ts.map +1 -0
- package/types/{trigger.d.ts → src/commands/trigger/trigger.d.ts} +3 -3
- package/types/src/commands/trigger/trigger.d.ts.map +1 -0
- package/types/{user.d.ts → src/commands/user/user.d.ts} +5 -5
- package/types/src/commands/user/user.d.ts.map +1 -0
- package/types/{variable.d.ts → src/commands/variable/variable.d.ts} +3 -3
- package/types/src/commands/variable/variable.d.ts.map +1 -0
- package/types/{worker_groups.d.ts → src/commands/worker-groups/worker_groups.d.ts} +2 -3
- package/types/src/commands/worker-groups/worker_groups.d.ts.map +1 -0
- package/types/{workers.d.ts → src/commands/workers/workers.d.ts} +1 -1
- package/types/src/commands/workers/workers.d.ts.map +1 -0
- package/types/{workspace.d.ts → src/commands/workspace/workspace.d.ts} +6 -4
- package/types/src/commands/workspace/workspace.d.ts.map +1 -0
- package/types/{auth.d.ts → src/core/auth.d.ts} +2 -2
- package/types/src/core/auth.d.ts.map +1 -0
- package/types/src/core/branch-profiles.d.ts +12 -0
- package/types/src/core/branch-profiles.d.ts.map +1 -0
- package/types/{conf.d.ts → src/core/conf.d.ts} +12 -3
- package/types/src/core/conf.d.ts.map +1 -0
- package/types/{context.d.ts → src/core/context.d.ts} +2 -2
- package/types/src/core/context.d.ts.map +1 -0
- package/types/{login.d.ts → src/core/login.d.ts} +1 -1
- package/types/src/core/login.d.ts.map +1 -0
- package/types/{settings.d.ts → src/core/settings.d.ts} +2 -2
- package/types/src/core/settings.d.ts.map +1 -0
- package/types/src/core/store.d.ts +2 -0
- package/types/src/core/store.d.ts.map +1 -0
- package/types/src/guidance/flow_guidance.d.ts.map +1 -0
- package/types/src/guidance/script_guidance.d.ts.map +1 -0
- package/types/src/main.d.ts +74 -0
- package/types/src/main.d.ts.map +1 -0
- package/types/src/types.d.ts.map +1 -0
- package/types/{codebase.d.ts → src/utils/codebase.d.ts} +1 -1
- package/types/src/utils/codebase.d.ts.map +1 -0
- package/types/src/utils/git.d.ts +3 -0
- package/types/src/utils/git.d.ts.map +1 -0
- package/types/src/utils/local_encryption.d.ts.map +1 -0
- package/types/{metadata.d.ts → src/utils/metadata.d.ts} +4 -4
- package/types/src/utils/metadata.d.ts.map +1 -0
- package/types/src/utils/script_common.d.ts.map +1 -0
- package/types/{upgrade.d.ts → src/utils/upgrade.d.ts} +1 -1
- package/types/src/utils/upgrade.d.ts.map +1 -0
- package/types/{utils.d.ts → src/utils/utils.d.ts} +2 -1
- package/types/src/utils/utils.d.ts.map +1 -0
- package/types/windmill-utils-internal/src/config/config.d.ts +12 -0
- package/types/windmill-utils-internal/src/config/config.d.ts.map +1 -0
- package/types/windmill-utils-internal/src/gen/types.gen.d.ts +48 -3
- package/types/windmill-utils-internal/src/gen/types.gen.d.ts.map +1 -1
- package/types/windmill-utils-internal/src/inline-scripts/extractor.d.ts +1 -4
- package/types/windmill-utils-internal/src/inline-scripts/extractor.d.ts.map +1 -1
- package/types/windmill-utils-internal/src/path-utils/path-assigner.d.ts +6 -6
- package/types/windmill-utils-internal/src/path-utils/path-assigner.d.ts.map +1 -1
- package/esm/conf.js +0 -78
- package/esm/context.js +0 -121
- package/esm/gitsync-settings.js +0 -984
- package/esm/main.js +0 -337
- package/esm/store.js +0 -76
- package/types/apps.d.ts +0 -17
- package/types/apps.d.ts.map +0 -1
- package/types/auth.d.ts.map +0 -1
- package/types/codebase.d.ts.map +0 -1
- package/types/conf.d.ts.map +0 -1
- package/types/context.d.ts.map +0 -1
- package/types/dev.d.ts +0 -12
- package/types/dev.d.ts.map +0 -1
- package/types/flow.d.ts.map +0 -1
- package/types/flow_guidance.d.ts.map +0 -1
- package/types/folder.d.ts.map +0 -1
- package/types/gitsync-settings.d.ts +0 -40
- package/types/gitsync-settings.d.ts.map +0 -1
- package/types/hub.d.ts.map +0 -1
- package/types/instance.d.ts.map +0 -1
- package/types/local_encryption.d.ts.map +0 -1
- package/types/login.d.ts.map +0 -1
- package/types/main.d.ts +0 -70
- package/types/main.d.ts.map +0 -1
- package/types/metadata.d.ts.map +0 -1
- package/types/pull.d.ts.map +0 -1
- package/types/queues.d.ts +0 -14
- package/types/queues.d.ts.map +0 -1
- package/types/resource-type.d.ts +0 -16
- package/types/resource-type.d.ts.map +0 -1
- package/types/resource.d.ts.map +0 -1
- package/types/schedule.d.ts.map +0 -1
- package/types/script.d.ts.map +0 -1
- package/types/script_common.d.ts.map +0 -1
- package/types/script_guidance.d.ts.map +0 -1
- package/types/settings.d.ts.map +0 -1
- package/types/store.d.ts +0 -3
- package/types/store.d.ts.map +0 -1
- package/types/sync.d.ts.map +0 -1
- package/types/trigger.d.ts.map +0 -1
- package/types/types.d.ts.map +0 -1
- package/types/upgrade.d.ts.map +0 -1
- package/types/user.d.ts.map +0 -1
- package/types/utils.d.ts.map +0 -1
- package/types/variable.d.ts.map +0 -1
- package/types/worker_groups.d.ts.map +0 -1
- package/types/workers.d.ts.map +0 -1
- package/types/workspace.d.ts.map +0 -1
- /package/esm/{flow_guidance.js → src/guidance/flow_guidance.js} +0 -0
- /package/esm/{script_guidance.js → src/guidance/script_guidance.js} +0 -0
- /package/esm/{local_encryption.js → src/utils/local_encryption.js} +0 -0
- /package/esm/{script_common.js → src/utils/script_common.js} +0 -0
- /package/types/{flow_guidance.d.ts → src/guidance/flow_guidance.d.ts} +0 -0
- /package/types/{script_guidance.d.ts → src/guidance/script_guidance.d.ts} +0 -0
- /package/types/{types.d.ts → src/types.d.ts} +0 -0
- /package/types/{local_encryption.d.ts → src/utils/local_encryption.d.ts} +0 -0
- /package/types/{script_common.d.ts → src/utils/script_common.d.ts} +0 -0
package/esm/gitsync-settings.js
DELETED
|
@@ -1,984 +0,0 @@
|
|
|
1
|
-
import * as dntShim from "./_dnt.shims.js";
|
|
2
|
-
import { colors, Command, Confirm, log, yamlStringify } from "./deps.js";
|
|
3
|
-
import { requireLogin } from "./auth.js";
|
|
4
|
-
import { resolveWorkspace } from "./context.js";
|
|
5
|
-
import * as wmill from "./gen/services.gen.js";
|
|
6
|
-
import { DEFAULT_SYNC_OPTIONS, getEffectiveSettings, readConfigFile, } from "./conf.js";
|
|
7
|
-
import { deepEqual, selectRepository } from "./utils.js";
|
|
8
|
-
// Constants for git-sync fields to avoid duplication
|
|
9
|
-
const GIT_SYNC_FIELDS = [
|
|
10
|
-
"includes",
|
|
11
|
-
"excludes",
|
|
12
|
-
"extraIncludes",
|
|
13
|
-
"skipScripts",
|
|
14
|
-
"skipFlows",
|
|
15
|
-
"skipApps",
|
|
16
|
-
"skipFolders",
|
|
17
|
-
"skipVariables",
|
|
18
|
-
"skipResources",
|
|
19
|
-
"skipResourceTypes",
|
|
20
|
-
"skipSecrets",
|
|
21
|
-
"includeSchedules",
|
|
22
|
-
"includeTriggers",
|
|
23
|
-
"includeUsers",
|
|
24
|
-
"includeGroups",
|
|
25
|
-
"includeSettings",
|
|
26
|
-
"includeKey",
|
|
27
|
-
];
|
|
28
|
-
// Helper to normalize repository path by removing $res: prefix
|
|
29
|
-
function normalizeRepoPath(path) {
|
|
30
|
-
return path.replace(/^\$res:/, "");
|
|
31
|
-
}
|
|
32
|
-
// Helper to get typed field value from SyncOptions
|
|
33
|
-
function getFieldValue(opts, field) {
|
|
34
|
-
return opts[field];
|
|
35
|
-
}
|
|
36
|
-
// Construct override key using the single format: baseUrl:workspaceId:repo
|
|
37
|
-
function constructOverrideKey(baseUrl, workspaceId, repoPath, workspaceLevel = false) {
|
|
38
|
-
// Validate that components don't contain colons to avoid key collisions
|
|
39
|
-
if (baseUrl.includes(':') && !baseUrl.startsWith('http')) {
|
|
40
|
-
throw new Error(`Invalid baseUrl contains colon: ${baseUrl}`);
|
|
41
|
-
}
|
|
42
|
-
if (workspaceId.includes(':')) {
|
|
43
|
-
throw new Error(`Invalid workspaceId contains colon: ${workspaceId}`);
|
|
44
|
-
}
|
|
45
|
-
if (repoPath.includes(':') && !repoPath.startsWith('$res:')) {
|
|
46
|
-
throw new Error(`Invalid repoPath contains colon: ${repoPath}`);
|
|
47
|
-
}
|
|
48
|
-
if (workspaceLevel) {
|
|
49
|
-
return `${baseUrl}:${workspaceId}:*`;
|
|
50
|
-
}
|
|
51
|
-
return `${baseUrl}:${workspaceId}:${repoPath}`;
|
|
52
|
-
}
|
|
53
|
-
// Helper to compare string arrays (used for includes/excludes/extraIncludes)
|
|
54
|
-
function arraysEqual(arr1, arr2) {
|
|
55
|
-
if (arr1.length !== arr2.length) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
const sorted1 = [...arr1].sort();
|
|
59
|
-
const sorted2 = [...arr2].sort();
|
|
60
|
-
return sorted1.every((item, index) => item === sorted2[index]);
|
|
61
|
-
}
|
|
62
|
-
// Normalize SyncOptions for semantic comparison - treat undefined arrays as empty arrays
|
|
63
|
-
function normalizeSyncOptions(opts) {
|
|
64
|
-
return {
|
|
65
|
-
...opts,
|
|
66
|
-
includes: opts.includes || [],
|
|
67
|
-
excludes: opts.excludes || [],
|
|
68
|
-
extraIncludes: opts.extraIncludes || [],
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
// Extract only git-sync relevant fields for comparison
|
|
72
|
-
function extractGitSyncFields(opts) {
|
|
73
|
-
return {
|
|
74
|
-
includes: opts.includes || [],
|
|
75
|
-
excludes: opts.excludes || [],
|
|
76
|
-
extraIncludes: opts.extraIncludes || [],
|
|
77
|
-
skipScripts: opts.skipScripts,
|
|
78
|
-
skipFlows: opts.skipFlows,
|
|
79
|
-
skipApps: opts.skipApps,
|
|
80
|
-
skipFolders: opts.skipFolders,
|
|
81
|
-
skipVariables: opts.skipVariables,
|
|
82
|
-
skipResources: opts.skipResources,
|
|
83
|
-
skipResourceTypes: opts.skipResourceTypes,
|
|
84
|
-
skipSecrets: opts.skipSecrets,
|
|
85
|
-
includeSchedules: opts.includeSchedules,
|
|
86
|
-
includeTriggers: opts.includeTriggers,
|
|
87
|
-
includeUsers: opts.includeUsers,
|
|
88
|
-
includeGroups: opts.includeGroups,
|
|
89
|
-
includeSettings: opts.includeSettings,
|
|
90
|
-
includeKey: opts.includeKey,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
// Helper function to determine current settings based on write mode and conflicts
|
|
94
|
-
function getCurrentSettings(localConfig, writeMode, overrideKey) {
|
|
95
|
-
if (writeMode === "override" &&
|
|
96
|
-
overrideKey &&
|
|
97
|
-
localConfig.overrides?.[overrideKey]) {
|
|
98
|
-
return {
|
|
99
|
-
...DEFAULT_SYNC_OPTIONS,
|
|
100
|
-
...localConfig,
|
|
101
|
-
...localConfig.overrides[overrideKey],
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
// For "replace" mode, exclude overrides since they're never accessed
|
|
106
|
-
const { overrides, ...configWithoutOverrides } = localConfig;
|
|
107
|
-
return { ...DEFAULT_SYNC_OPTIONS, ...configWithoutOverrides };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// Convert backend include_type array to SyncOptions boolean flags
|
|
111
|
-
function includeTypeToSyncOptions(includeTypes) {
|
|
112
|
-
return {
|
|
113
|
-
skipScripts: !includeTypes.includes("script"),
|
|
114
|
-
skipFlows: !includeTypes.includes("flow"),
|
|
115
|
-
skipApps: !includeTypes.includes("app"),
|
|
116
|
-
skipFolders: !includeTypes.includes("folder"),
|
|
117
|
-
skipVariables: !includeTypes.includes("variable"),
|
|
118
|
-
skipResources: !includeTypes.includes("resource"),
|
|
119
|
-
skipResourceTypes: !includeTypes.includes("resourcetype"),
|
|
120
|
-
skipSecrets: !includeTypes.includes("secret"),
|
|
121
|
-
includeSchedules: includeTypes.includes("schedule"),
|
|
122
|
-
includeTriggers: includeTypes.includes("trigger"),
|
|
123
|
-
includeUsers: includeTypes.includes("user"),
|
|
124
|
-
includeGroups: includeTypes.includes("group"),
|
|
125
|
-
includeSettings: includeTypes.includes("settings"),
|
|
126
|
-
includeKey: includeTypes.includes("key"),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
// Shared migration function for legacy repositories
|
|
130
|
-
async function handleLegacyRepositoryMigration(selectedRepo, gitSyncSettings, workspace, opts, operationName = "operation") {
|
|
131
|
-
if (selectedRepo.settings) {
|
|
132
|
-
return selectedRepo; // Already migrated
|
|
133
|
-
}
|
|
134
|
-
// This repository is in legacy format - handle migration
|
|
135
|
-
if (!gitSyncSettings.include_path || !gitSyncSettings.include_type) {
|
|
136
|
-
throw new Error(`Repository "${selectedRepo.git_repo_resource_path}" has legacy format but workspace-level include_path or include_type is missing. This indicates corrupted git-sync settings.`);
|
|
137
|
-
}
|
|
138
|
-
const workspaceIncludePath = gitSyncSettings.include_path;
|
|
139
|
-
const workspaceIncludeType = gitSyncSettings.include_type;
|
|
140
|
-
if (dntShim.Deno.stdout.isTerminal() && !opts.yes) {
|
|
141
|
-
// Interactive mode - show migration prompt
|
|
142
|
-
console.log(colors.yellow('\n⚠️ Legacy git-sync settings detected!'));
|
|
143
|
-
console.log(`\nRepository "${selectedRepo.git_repo_resource_path}" has legacy settings format.`);
|
|
144
|
-
console.log('The new format allows per-repository filter configuration.');
|
|
145
|
-
if (operationName === "push") {
|
|
146
|
-
console.log('This repository must be migrated before pushing settings.\n');
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
console.log('\n');
|
|
150
|
-
}
|
|
151
|
-
console.log(colors.bold('Current workspace-level settings:'));
|
|
152
|
-
console.log(` Include paths: ${workspaceIncludePath.join(', ')}`);
|
|
153
|
-
console.log(` Include types: ${workspaceIncludeType.join(', ')}\n`);
|
|
154
|
-
// Show what the migration will do
|
|
155
|
-
let finalIncludeType = [...workspaceIncludeType];
|
|
156
|
-
if (selectedRepo.exclude_types_override && selectedRepo.exclude_types_override.length > 0) {
|
|
157
|
-
const originalCount = finalIncludeType.length;
|
|
158
|
-
finalIncludeType = finalIncludeType.filter(type => !selectedRepo.exclude_types_override.includes(type));
|
|
159
|
-
const excludedCount = originalCount - finalIncludeType.length;
|
|
160
|
-
console.log(colors.yellow(`Repository excludes ${excludedCount} types: ${selectedRepo.exclude_types_override.join(', ')}`));
|
|
161
|
-
}
|
|
162
|
-
console.log(colors.bold('\nAfter migration, repository will have:'));
|
|
163
|
-
console.log(` Include paths: ${workspaceIncludePath.join(', ')}`);
|
|
164
|
-
console.log(` Include types: ${finalIncludeType.join(', ')}\n`);
|
|
165
|
-
const confirm = await Confirm.prompt({
|
|
166
|
-
message: operationName === "push"
|
|
167
|
-
? 'Do you want to migrate this repository before pushing?'
|
|
168
|
-
: 'Do you want to migrate this repository?',
|
|
169
|
-
default: true
|
|
170
|
-
});
|
|
171
|
-
if (!confirm) {
|
|
172
|
-
const message = operationName === "push"
|
|
173
|
-
? '\n⚠️ Migration skipped. Cannot push to legacy repository.'
|
|
174
|
-
: '\n⚠️ Migration skipped. You can migrate later via the UI.';
|
|
175
|
-
console.log(colors.yellow(message));
|
|
176
|
-
if (operationName === "push") {
|
|
177
|
-
return null; // Signal to exit push operation
|
|
178
|
-
}
|
|
179
|
-
throw new Error('Migration cancelled by user');
|
|
180
|
-
}
|
|
181
|
-
// Perform the migration
|
|
182
|
-
let migratedIncludeType = [...workspaceIncludeType];
|
|
183
|
-
if (selectedRepo.exclude_types_override && selectedRepo.exclude_types_override.length > 0) {
|
|
184
|
-
migratedIncludeType = migratedIncludeType.filter(type => !selectedRepo.exclude_types_override.includes(type));
|
|
185
|
-
}
|
|
186
|
-
const migratedRepo = {
|
|
187
|
-
...selectedRepo,
|
|
188
|
-
settings: {
|
|
189
|
-
include_path: [...workspaceIncludePath],
|
|
190
|
-
include_type: migratedIncludeType,
|
|
191
|
-
exclude_path: [],
|
|
192
|
-
extra_include_path: []
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
// Remove the old field
|
|
196
|
-
delete migratedRepo.exclude_types_override;
|
|
197
|
-
// Update the backend with migrated repository
|
|
198
|
-
const updatedRepositories = gitSyncSettings.repositories.map((repo) => {
|
|
199
|
-
if (repo.git_repo_resource_path === selectedRepo.git_repo_resource_path) {
|
|
200
|
-
return migratedRepo;
|
|
201
|
-
}
|
|
202
|
-
return repo;
|
|
203
|
-
});
|
|
204
|
-
await wmill.editWorkspaceGitSyncConfig({
|
|
205
|
-
workspace: workspace.workspaceId,
|
|
206
|
-
requestBody: {
|
|
207
|
-
git_sync_settings: {
|
|
208
|
-
repositories: updatedRepositories,
|
|
209
|
-
// Keep workspace-level settings if other repos are still legacy
|
|
210
|
-
...(gitSyncSettings.repositories.some((r) => r.git_repo_resource_path !== selectedRepo.git_repo_resource_path && !r.settings) && {
|
|
211
|
-
include_path: workspaceIncludePath,
|
|
212
|
-
include_type: workspaceIncludeType
|
|
213
|
-
})
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
console.log(colors.green('\n✓ Repository migration completed successfully!'));
|
|
218
|
-
if (operationName === "push") {
|
|
219
|
-
console.log('Now proceeding with push operation...\n');
|
|
220
|
-
}
|
|
221
|
-
return migratedRepo;
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
// Non-interactive mode - show error
|
|
225
|
-
console.error(colors.red('\n❌ Legacy git-sync settings detected!'));
|
|
226
|
-
console.error(`\nRepository "${selectedRepo.git_repo_resource_path}" has legacy settings format.`);
|
|
227
|
-
if (operationName === "push") {
|
|
228
|
-
console.error('This repository must be migrated before pushing settings.');
|
|
229
|
-
}
|
|
230
|
-
console.error('Please choose one of the following options:\n');
|
|
231
|
-
console.error('1. Go to the Windmill UI > Workspace Settings > Git Sync');
|
|
232
|
-
console.error(' Review and save this repository to migrate to the new format.\n');
|
|
233
|
-
console.error('2. Run this command in interactive mode (with TTY) to migrate.');
|
|
234
|
-
console.error(` Example: wmill gitsync-settings ${operationName}\n`);
|
|
235
|
-
if (operationName === "push") {
|
|
236
|
-
console.error('3. Pull settings first to migrate: wmill gitsync-settings pull\n');
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
console.error('3. Push local settings to override backend settings:');
|
|
240
|
-
console.error(' wmill gitsync-settings push\n');
|
|
241
|
-
}
|
|
242
|
-
dntShim.Deno.exit(1);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
// Convert SyncOptions boolean flags to backend include_type array
|
|
246
|
-
function syncOptionsToIncludeType(opts) {
|
|
247
|
-
const includeTypes = [];
|
|
248
|
-
if (!opts.skipScripts)
|
|
249
|
-
includeTypes.push("script");
|
|
250
|
-
if (!opts.skipFlows)
|
|
251
|
-
includeTypes.push("flow");
|
|
252
|
-
if (!opts.skipApps)
|
|
253
|
-
includeTypes.push("app");
|
|
254
|
-
if (!opts.skipFolders)
|
|
255
|
-
includeTypes.push("folder");
|
|
256
|
-
if (!opts.skipVariables)
|
|
257
|
-
includeTypes.push("variable");
|
|
258
|
-
if (!opts.skipResources)
|
|
259
|
-
includeTypes.push("resource");
|
|
260
|
-
if (!opts.skipResourceTypes)
|
|
261
|
-
includeTypes.push("resourcetype");
|
|
262
|
-
if (!opts.skipSecrets)
|
|
263
|
-
includeTypes.push("secret");
|
|
264
|
-
if (opts.includeSchedules)
|
|
265
|
-
includeTypes.push("schedule");
|
|
266
|
-
if (opts.includeTriggers)
|
|
267
|
-
includeTypes.push("trigger");
|
|
268
|
-
if (opts.includeUsers)
|
|
269
|
-
includeTypes.push("user");
|
|
270
|
-
if (opts.includeGroups)
|
|
271
|
-
includeTypes.push("group");
|
|
272
|
-
if (opts.includeSettings)
|
|
273
|
-
includeTypes.push("settings");
|
|
274
|
-
if (opts.includeKey)
|
|
275
|
-
includeTypes.push("key");
|
|
276
|
-
return includeTypes;
|
|
277
|
-
}
|
|
278
|
-
// Convert SyncOptions to backend format used by both Windmill backend and UI
|
|
279
|
-
function syncOptionsToBackendFormat(opts) {
|
|
280
|
-
return {
|
|
281
|
-
include_path: opts.includes || [],
|
|
282
|
-
exclude_path: opts.excludes || [],
|
|
283
|
-
extra_include_path: opts.extraIncludes || [],
|
|
284
|
-
include_type: syncOptionsToIncludeType(opts),
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
// Select repository interactively if multiple exist
|
|
288
|
-
// Generate structured diff showing field changes
|
|
289
|
-
function generateStructuredDiff(current, backend) {
|
|
290
|
-
const diff = {};
|
|
291
|
-
// Get all unique keys from both objects
|
|
292
|
-
const allKeys = new Set([...Object.keys(current), ...Object.keys(backend)]);
|
|
293
|
-
for (const key of allKeys) {
|
|
294
|
-
const currentValue = current[key];
|
|
295
|
-
const backendValue = backend[key];
|
|
296
|
-
if (!deepEqual(currentValue, backendValue)) {
|
|
297
|
-
diff[key] = {
|
|
298
|
-
from: currentValue,
|
|
299
|
-
to: backendValue,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return diff;
|
|
304
|
-
}
|
|
305
|
-
// Helper to generate changes between two normalized SyncOptions objects
|
|
306
|
-
function generateChanges(normalizedCurrent, normalizedNew) {
|
|
307
|
-
const changes = {};
|
|
308
|
-
for (const field of GIT_SYNC_FIELDS) {
|
|
309
|
-
const currentValue = getFieldValue(normalizedCurrent, field);
|
|
310
|
-
const newValue = getFieldValue(normalizedNew, field);
|
|
311
|
-
if (!deepEqual(currentValue, newValue)) {
|
|
312
|
-
changes[field] = {
|
|
313
|
-
from: currentValue,
|
|
314
|
-
to: newValue,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return changes;
|
|
319
|
-
}
|
|
320
|
-
// Helper to display changes in human-readable format
|
|
321
|
-
function displayChanges(changes) {
|
|
322
|
-
for (const [field, change] of Object.entries(changes)) {
|
|
323
|
-
if (Array.isArray(change.from) ||
|
|
324
|
-
Array.isArray(change.to)) {
|
|
325
|
-
console.log(colors.yellow(` ${field}:`));
|
|
326
|
-
console.log(colors.red(` - ${JSON.stringify(change.from)}`));
|
|
327
|
-
console.log(colors.green(` + ${JSON.stringify(change.to)}`));
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
console.log(colors.yellow(` ${field}: `) +
|
|
331
|
-
colors.red(`${change.from}`) +
|
|
332
|
-
" → " +
|
|
333
|
-
colors.green(`${change.to}`));
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
async function selectAndLogRepository(repositories, repository) {
|
|
338
|
-
let selectedRepo;
|
|
339
|
-
if (repository) {
|
|
340
|
-
const found = repositories.find((r) => r.git_repo_resource_path === repository ||
|
|
341
|
-
r.git_repo_resource_path === `$res:${repository}`);
|
|
342
|
-
if (!found) {
|
|
343
|
-
throw new Error(`Repository ${repository} not found`);
|
|
344
|
-
}
|
|
345
|
-
selectedRepo = found;
|
|
346
|
-
const repoPath = selectedRepo.git_repo_resource_path.replace(/^\$res:/, "");
|
|
347
|
-
log.info(colors.cyan(`Using repository: ${colors.bold(repoPath)}`));
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
selectedRepo = await selectRepository(repositories);
|
|
351
|
-
}
|
|
352
|
-
return selectedRepo;
|
|
353
|
-
}
|
|
354
|
-
async function pullGitSyncSettings(opts) {
|
|
355
|
-
const workspace = await resolveWorkspace(opts);
|
|
356
|
-
await requireLogin(opts);
|
|
357
|
-
try {
|
|
358
|
-
// Parse and validate --with-backend-settings if provided
|
|
359
|
-
let settings;
|
|
360
|
-
if (opts.withBackendSettings) {
|
|
361
|
-
try {
|
|
362
|
-
const parsedSettings = JSON.parse(opts.withBackendSettings);
|
|
363
|
-
// Validate the structure matches expected test format (raw settings object)
|
|
364
|
-
if (!parsedSettings.include_path ||
|
|
365
|
-
!Array.isArray(parsedSettings.include_path)) {
|
|
366
|
-
throw new Error("Invalid settings format. Expected include_path array");
|
|
367
|
-
}
|
|
368
|
-
if (!parsedSettings.include_type ||
|
|
369
|
-
!Array.isArray(parsedSettings.include_type)) {
|
|
370
|
-
throw new Error("Invalid settings format. Expected include_type array");
|
|
371
|
-
}
|
|
372
|
-
// Create mock backend response with single repository using provided settings
|
|
373
|
-
const mockRepositoryPath = opts.repository || "u/mock/repo";
|
|
374
|
-
settings = {
|
|
375
|
-
git_sync: {
|
|
376
|
-
repositories: [{
|
|
377
|
-
git_repo_resource_path: mockRepositoryPath,
|
|
378
|
-
settings: {
|
|
379
|
-
include_path: parsedSettings.include_path,
|
|
380
|
-
include_type: parsedSettings.include_type,
|
|
381
|
-
exclude_path: parsedSettings.exclude_path || [],
|
|
382
|
-
extra_include_path: parsedSettings.extra_include_path || [],
|
|
383
|
-
},
|
|
384
|
-
}],
|
|
385
|
-
},
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
catch (parseError) {
|
|
389
|
-
const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
|
|
390
|
-
if (opts.jsonOutput) {
|
|
391
|
-
console.log(JSON.stringify({
|
|
392
|
-
success: false,
|
|
393
|
-
error: `Failed to parse --with-backend-settings JSON: ${errorMessage}`,
|
|
394
|
-
}));
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
log.error(colors.red(`Failed to parse --with-backend-settings JSON: ${errorMessage}`));
|
|
398
|
-
}
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
// Fetch workspace settings to get git-sync configuration
|
|
404
|
-
try {
|
|
405
|
-
settings = await wmill.getSettings({
|
|
406
|
-
workspace: workspace.workspaceId,
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
catch (apiError) {
|
|
410
|
-
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
411
|
-
if (opts.jsonOutput) {
|
|
412
|
-
console.log(JSON.stringify({
|
|
413
|
-
success: false,
|
|
414
|
-
error: `Failed to fetch workspace settings: ${errorMessage}`,
|
|
415
|
-
}));
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
log.error(colors.red(`Failed to fetch workspace settings: ${errorMessage}`));
|
|
419
|
-
}
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
if (!settings.git_sync?.repositories ||
|
|
424
|
-
settings.git_sync.repositories.length === 0) {
|
|
425
|
-
if (opts.jsonOutput) {
|
|
426
|
-
console.log(JSON.stringify({
|
|
427
|
-
success: false,
|
|
428
|
-
error: "No git-sync repositories configured",
|
|
429
|
-
}));
|
|
430
|
-
}
|
|
431
|
-
else {
|
|
432
|
-
log.error(colors.red("No git-sync repositories configured in workspace"));
|
|
433
|
-
}
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
// Find the repository to work with
|
|
437
|
-
let selectedRepo = await selectAndLogRepository(settings.git_sync.repositories, opts.repository);
|
|
438
|
-
// Check if the selected repository needs migration and handle it
|
|
439
|
-
selectedRepo = await handleLegacyRepositoryMigration(selectedRepo, settings.git_sync, workspace, opts, "pull");
|
|
440
|
-
// Convert backend settings to SyncOptions format
|
|
441
|
-
const backendSyncOptions = {
|
|
442
|
-
includes: selectedRepo.settings.include_path || [],
|
|
443
|
-
excludes: selectedRepo.settings.exclude_path || [],
|
|
444
|
-
extraIncludes: selectedRepo.settings.extra_include_path || [],
|
|
445
|
-
...includeTypeToSyncOptions(selectedRepo.settings.include_type || []),
|
|
446
|
-
};
|
|
447
|
-
// Check if wmill.yaml exists - require it for git-sync settings commands
|
|
448
|
-
try {
|
|
449
|
-
await dntShim.Deno.stat("wmill.yaml");
|
|
450
|
-
}
|
|
451
|
-
catch (error) {
|
|
452
|
-
log.error(colors.red("No wmill.yaml file found. Please run 'wmill init' first to create the configuration file."));
|
|
453
|
-
dntShim.Deno.exit(1);
|
|
454
|
-
}
|
|
455
|
-
// Read current local configuration
|
|
456
|
-
const localConfig = await readConfigFile();
|
|
457
|
-
// Determine where to write the settings for diff display
|
|
458
|
-
let overrideKey;
|
|
459
|
-
let writeMode = "replace";
|
|
460
|
-
// For diff mode, determine what the write mode would be without interactive prompts
|
|
461
|
-
if (opts.default) {
|
|
462
|
-
writeMode = "replace";
|
|
463
|
-
}
|
|
464
|
-
else if (opts.replace) {
|
|
465
|
-
writeMode = "replace";
|
|
466
|
-
}
|
|
467
|
-
else if (opts.override || opts.workspaceLevel) {
|
|
468
|
-
writeMode = "override";
|
|
469
|
-
if (opts.workspaceLevel) {
|
|
470
|
-
overrideKey = constructOverrideKey(workspace.remote, workspace.workspaceId, "", true);
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path);
|
|
474
|
-
overrideKey = constructOverrideKey(workspace.remote, workspace.workspaceId, repoPath);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
// Default behavior for existing files with no explicit flags
|
|
479
|
-
// Use same logic as diff to determine if there's a real conflict
|
|
480
|
-
const currentSettings = getCurrentSettings(localConfig, "replace", // Check against replace mode
|
|
481
|
-
undefined);
|
|
482
|
-
const gitSyncBackend = extractGitSyncFields(normalizeSyncOptions(backendSyncOptions));
|
|
483
|
-
const gitSyncCurrent = extractGitSyncFields(normalizeSyncOptions(currentSettings));
|
|
484
|
-
const hasConflict = !deepEqual(gitSyncBackend, gitSyncCurrent);
|
|
485
|
-
if (hasConflict) {
|
|
486
|
-
// For diff mode, show what override would look like
|
|
487
|
-
writeMode = "override";
|
|
488
|
-
const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path);
|
|
489
|
-
overrideKey = constructOverrideKey(workspace.remote, workspace.workspaceId, repoPath);
|
|
490
|
-
}
|
|
491
|
-
else {
|
|
492
|
-
writeMode = "replace";
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
if (opts.diff) {
|
|
496
|
-
// Show differences between local and backend
|
|
497
|
-
const currentSettings = getCurrentSettings(localConfig, writeMode, overrideKey);
|
|
498
|
-
const normalizedCurrent = normalizeSyncOptions(currentSettings);
|
|
499
|
-
const normalizedBackend = normalizeSyncOptions(backendSyncOptions);
|
|
500
|
-
const gitSyncCurrent = extractGitSyncFields(normalizedCurrent);
|
|
501
|
-
const gitSyncBackend = extractGitSyncFields(normalizedBackend);
|
|
502
|
-
const hasChanges = !deepEqual(gitSyncBackend, gitSyncCurrent);
|
|
503
|
-
if (opts.jsonOutput) {
|
|
504
|
-
const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path);
|
|
505
|
-
// Generate structured diff using the same normalized objects
|
|
506
|
-
const structuredDiff = hasChanges
|
|
507
|
-
? generateStructuredDiff(gitSyncCurrent, gitSyncBackend)
|
|
508
|
-
: {};
|
|
509
|
-
console.log(JSON.stringify({
|
|
510
|
-
success: true,
|
|
511
|
-
hasChanges,
|
|
512
|
-
local: syncOptionsToBackendFormat(normalizedCurrent),
|
|
513
|
-
backend: syncOptionsToBackendFormat(normalizedBackend),
|
|
514
|
-
repository: selectedRepo.git_repo_resource_path,
|
|
515
|
-
diff: structuredDiff,
|
|
516
|
-
}));
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
if (hasChanges) {
|
|
520
|
-
log.info("Changes that would be applied locally:");
|
|
521
|
-
const changes = generateChanges(normalizedCurrent, normalizedBackend);
|
|
522
|
-
if (Object.keys(changes).length === 0) {
|
|
523
|
-
log.info(colors.green("No differences found"));
|
|
524
|
-
}
|
|
525
|
-
else {
|
|
526
|
-
displayChanges(changes);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
else {
|
|
530
|
-
log.info(colors.green("No differences found"));
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
// For non-diff mode, handle interactive logic if needed
|
|
536
|
-
// Only show interactive prompts for existing files with conflicts
|
|
537
|
-
if (!opts.diff &&
|
|
538
|
-
!opts.default &&
|
|
539
|
-
!opts.replace &&
|
|
540
|
-
!opts.override &&
|
|
541
|
-
!opts.workspaceLevel) {
|
|
542
|
-
// Use the same logic as diff to determine current settings
|
|
543
|
-
const currentSettings = getCurrentSettings(localConfig, writeMode, overrideKey);
|
|
544
|
-
const gitSyncBackend = extractGitSyncFields(normalizeSyncOptions(backendSyncOptions));
|
|
545
|
-
const gitSyncCurrent = extractGitSyncFields(normalizeSyncOptions(currentSettings));
|
|
546
|
-
const hasConflict = !deepEqual(gitSyncBackend, gitSyncCurrent);
|
|
547
|
-
if (hasConflict && !opts.yes && dntShim.Deno.stdin.isTerminal()) {
|
|
548
|
-
// Show the diff first
|
|
549
|
-
log.info("Changes that would be applied locally:");
|
|
550
|
-
const changes = generateChanges(currentSettings, backendSyncOptions);
|
|
551
|
-
if (Object.keys(changes).length > 0) {
|
|
552
|
-
displayChanges(changes);
|
|
553
|
-
}
|
|
554
|
-
// Interactive mode - ask user
|
|
555
|
-
const { Select } = await import("./deps.js");
|
|
556
|
-
const choice = await Select.prompt({
|
|
557
|
-
message: "Settings conflict detected. How would you like to proceed?",
|
|
558
|
-
options: [
|
|
559
|
-
{
|
|
560
|
-
name: "Replace existing settings",
|
|
561
|
-
value: "replace",
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
name: "Add repository-specific override",
|
|
565
|
-
value: "override",
|
|
566
|
-
},
|
|
567
|
-
{ name: "Cancel", value: "cancel" },
|
|
568
|
-
],
|
|
569
|
-
});
|
|
570
|
-
if (choice === "cancel") {
|
|
571
|
-
log.info("Operation cancelled");
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
writeMode = choice;
|
|
575
|
-
if (writeMode === "override") {
|
|
576
|
-
const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path);
|
|
577
|
-
overrideKey = constructOverrideKey(workspace.remote, workspace.workspaceId, repoPath);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
else if (hasConflict && opts.yes) {
|
|
581
|
-
// --yes flag: default to override behavior for conflicts
|
|
582
|
-
writeMode = "override";
|
|
583
|
-
const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path);
|
|
584
|
-
overrideKey = constructOverrideKey(workspace.remote, workspace.workspaceId, repoPath);
|
|
585
|
-
log.info(colors.yellow("Settings conflict detected. Using --override behavior (default for --yes)."));
|
|
586
|
-
}
|
|
587
|
-
else if (hasConflict) {
|
|
588
|
-
// Non-interactive mode with conflicts - show message and exit
|
|
589
|
-
if (opts.jsonOutput) {
|
|
590
|
-
console.log(JSON.stringify({
|
|
591
|
-
success: false,
|
|
592
|
-
error: "Settings conflict detected. Use --replace or --override flags to resolve.",
|
|
593
|
-
hasConflict: true,
|
|
594
|
-
}));
|
|
595
|
-
}
|
|
596
|
-
else {
|
|
597
|
-
log.error(colors.red("Settings conflict detected."));
|
|
598
|
-
log.info("Use --replace to overwrite existing settings or --override to add repository-specific override.");
|
|
599
|
-
}
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
// Check if there are actually any changes before writing
|
|
604
|
-
const currentSettingsForCheck = getCurrentSettings(localConfig, writeMode, overrideKey);
|
|
605
|
-
const gitSyncBackend = extractGitSyncFields(normalizeSyncOptions(backendSyncOptions));
|
|
606
|
-
const gitSyncCurrent = extractGitSyncFields(normalizeSyncOptions(currentSettingsForCheck));
|
|
607
|
-
const hasActualChanges = !deepEqual(gitSyncBackend, gitSyncCurrent);
|
|
608
|
-
if (!hasActualChanges) {
|
|
609
|
-
if (opts.jsonOutput) {
|
|
610
|
-
console.log(JSON.stringify({
|
|
611
|
-
success: true,
|
|
612
|
-
message: "No changes needed",
|
|
613
|
-
repository: selectedRepo.git_repo_resource_path,
|
|
614
|
-
}));
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
log.info(colors.green("No changes needed - settings are already up to date"));
|
|
618
|
-
}
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
// Apply the settings based on write mode
|
|
622
|
-
let updatedConfig;
|
|
623
|
-
// Log which settings mode is being used
|
|
624
|
-
const repoPath = selectedRepo.git_repo_resource_path.replace(/^\$res:/, "");
|
|
625
|
-
if (writeMode === "override") {
|
|
626
|
-
log.info(`Applied repository-specific overrides for repository "${repoPath}"`);
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
log.info(`Applied settings for repository "${repoPath}"`);
|
|
630
|
-
}
|
|
631
|
-
if (writeMode === "replace") {
|
|
632
|
-
// Preserve existing local config and update only git-sync fields
|
|
633
|
-
updatedConfig = { ...localConfig };
|
|
634
|
-
// Clear overrides since we're in replace mode, but keep empty object for consistency
|
|
635
|
-
updatedConfig.overrides = {};
|
|
636
|
-
// Update with backend git-sync settings
|
|
637
|
-
Object.assign(updatedConfig, backendSyncOptions);
|
|
638
|
-
}
|
|
639
|
-
else if (writeMode === "override" && overrideKey) {
|
|
640
|
-
// Add repository-specific override
|
|
641
|
-
updatedConfig = { ...localConfig };
|
|
642
|
-
if (!updatedConfig.overrides) {
|
|
643
|
-
updatedConfig.overrides = {};
|
|
644
|
-
}
|
|
645
|
-
// Only store the delta - settings that differ from current effective settings
|
|
646
|
-
const currentEffective = getCurrentSettings(localConfig, "replace");
|
|
647
|
-
const deltaSettings = {};
|
|
648
|
-
// Compare each setting and only include differences
|
|
649
|
-
for (const [key, value] of Object.entries(backendSyncOptions)) {
|
|
650
|
-
if (key === "overrides")
|
|
651
|
-
continue; // Skip overrides field
|
|
652
|
-
const currentValue = currentEffective[key];
|
|
653
|
-
const newValue = value;
|
|
654
|
-
// Compare arrays by content, primitives by value
|
|
655
|
-
const isDifferent = Array.isArray(currentValue) && Array.isArray(newValue)
|
|
656
|
-
? !arraysEqual(currentValue, newValue)
|
|
657
|
-
: currentValue !== newValue;
|
|
658
|
-
if (isDifferent) {
|
|
659
|
-
deltaSettings[key] = newValue;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
updatedConfig.overrides[overrideKey] = deltaSettings;
|
|
663
|
-
}
|
|
664
|
-
else {
|
|
665
|
-
// Replace top-level settings
|
|
666
|
-
updatedConfig = { ...localConfig };
|
|
667
|
-
// Copy all backend settings to top level, excluding overrides
|
|
668
|
-
const { overrides, ...topLevelSettings } = backendSyncOptions;
|
|
669
|
-
Object.assign(updatedConfig, topLevelSettings);
|
|
670
|
-
}
|
|
671
|
-
// Write updated configuration
|
|
672
|
-
await dntShim.Deno.writeTextFile("wmill.yaml", yamlStringify(updatedConfig));
|
|
673
|
-
if (opts.jsonOutput) {
|
|
674
|
-
console.log(JSON.stringify({
|
|
675
|
-
success: true,
|
|
676
|
-
message: `Git-sync settings pulled successfully`,
|
|
677
|
-
repository: selectedRepo.git_repo_resource_path,
|
|
678
|
-
overrideKey,
|
|
679
|
-
}));
|
|
680
|
-
}
|
|
681
|
-
else {
|
|
682
|
-
log.info(colors.green(`Git-sync settings pulled successfully from ${selectedRepo.git_repo_resource_path}`));
|
|
683
|
-
if (writeMode === "override" && overrideKey) {
|
|
684
|
-
log.info(colors.gray(`Settings written to override key: ${overrideKey}`));
|
|
685
|
-
}
|
|
686
|
-
else if (writeMode === "replace") {
|
|
687
|
-
log.info(colors.gray(`Settings written as simple configuration`));
|
|
688
|
-
}
|
|
689
|
-
else {
|
|
690
|
-
log.info(colors.gray(`Settings written to top-level defaults`));
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
catch (error) {
|
|
695
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
696
|
-
if (opts.jsonOutput) {
|
|
697
|
-
console.log(JSON.stringify({ success: false, error: errorMessage }));
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
log.error(colors.red(`Failed to pull git-sync settings: ${errorMessage}`));
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
async function pushGitSyncSettings(opts) {
|
|
705
|
-
const workspace = await resolveWorkspace(opts);
|
|
706
|
-
await requireLogin(opts);
|
|
707
|
-
try {
|
|
708
|
-
// Check if wmill.yaml exists - require it for git-sync settings commands
|
|
709
|
-
try {
|
|
710
|
-
await dntShim.Deno.stat("wmill.yaml");
|
|
711
|
-
}
|
|
712
|
-
catch (error) {
|
|
713
|
-
log.error(colors.red("No wmill.yaml file found. Please run 'wmill init' first to create the configuration file."));
|
|
714
|
-
dntShim.Deno.exit(1);
|
|
715
|
-
}
|
|
716
|
-
// Read local configuration
|
|
717
|
-
const localConfig = await readConfigFile();
|
|
718
|
-
// Parse and validate --with-backend-settings if provided, otherwise fetch from backend
|
|
719
|
-
let settings;
|
|
720
|
-
if (opts.withBackendSettings) {
|
|
721
|
-
try {
|
|
722
|
-
const parsedSettings = JSON.parse(opts.withBackendSettings);
|
|
723
|
-
// Validate the structure matches expected test format (raw settings object)
|
|
724
|
-
if (!parsedSettings.include_path ||
|
|
725
|
-
!Array.isArray(parsedSettings.include_path)) {
|
|
726
|
-
throw new Error("Invalid settings format. Expected include_path array");
|
|
727
|
-
}
|
|
728
|
-
if (!parsedSettings.include_type ||
|
|
729
|
-
!Array.isArray(parsedSettings.include_type)) {
|
|
730
|
-
throw new Error("Invalid settings format. Expected include_type array");
|
|
731
|
-
}
|
|
732
|
-
// Create mock backend response with single repository using provided settings
|
|
733
|
-
const mockRepositoryPath = opts.repository || "u/mock/repo";
|
|
734
|
-
settings = {
|
|
735
|
-
git_sync: {
|
|
736
|
-
repositories: [{
|
|
737
|
-
git_repo_resource_path: mockRepositoryPath,
|
|
738
|
-
settings: {
|
|
739
|
-
include_path: parsedSettings.include_path,
|
|
740
|
-
include_type: parsedSettings.include_type,
|
|
741
|
-
exclude_path: parsedSettings.exclude_path || [],
|
|
742
|
-
extra_include_path: parsedSettings.extra_include_path || [],
|
|
743
|
-
},
|
|
744
|
-
}],
|
|
745
|
-
},
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
catch (parseError) {
|
|
749
|
-
const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
|
|
750
|
-
if (opts.jsonOutput) {
|
|
751
|
-
console.log(JSON.stringify({
|
|
752
|
-
success: false,
|
|
753
|
-
error: `Failed to parse --with-backend-settings JSON: ${errorMessage}`,
|
|
754
|
-
}));
|
|
755
|
-
}
|
|
756
|
-
else {
|
|
757
|
-
log.error(colors.red(`Failed to parse --with-backend-settings JSON: ${errorMessage}`));
|
|
758
|
-
}
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
else {
|
|
763
|
-
// Fetch current backend settings
|
|
764
|
-
try {
|
|
765
|
-
settings = await wmill.getSettings({
|
|
766
|
-
workspace: workspace.workspaceId,
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
catch (apiError) {
|
|
770
|
-
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
771
|
-
if (opts.jsonOutput) {
|
|
772
|
-
console.log(JSON.stringify({
|
|
773
|
-
success: false,
|
|
774
|
-
error: `Failed to fetch workspace settings: ${errorMessage}`,
|
|
775
|
-
}));
|
|
776
|
-
}
|
|
777
|
-
else {
|
|
778
|
-
log.error(colors.red(`Failed to fetch workspace settings: ${errorMessage}`));
|
|
779
|
-
}
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
if (!settings.git_sync?.repositories ||
|
|
784
|
-
settings.git_sync.repositories.length === 0) {
|
|
785
|
-
if (opts.jsonOutput) {
|
|
786
|
-
console.log(JSON.stringify({
|
|
787
|
-
success: false,
|
|
788
|
-
error: "No git-sync repositories configured",
|
|
789
|
-
}));
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
log.error(colors.red("No git-sync repositories configured in workspace"));
|
|
793
|
-
}
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
// Find the repository to work with
|
|
797
|
-
let selectedRepo = await selectAndLogRepository(settings.git_sync.repositories, opts.repository);
|
|
798
|
-
// Check if the selected repository needs migration and handle it
|
|
799
|
-
selectedRepo = await handleLegacyRepositoryMigration(selectedRepo, settings.git_sync, workspace, opts, "push");
|
|
800
|
-
// If migration was cancelled, exit
|
|
801
|
-
if (selectedRepo === null) {
|
|
802
|
-
return;
|
|
803
|
-
}
|
|
804
|
-
// Get effective settings for this workspace/repo
|
|
805
|
-
const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path);
|
|
806
|
-
const effectiveSettings = getEffectiveSettings(localConfig, workspace.remote, workspace.workspaceId, repoPath);
|
|
807
|
-
// Convert to backend format
|
|
808
|
-
const backendFormat = {
|
|
809
|
-
include_path: effectiveSettings.includes || [],
|
|
810
|
-
include_type: syncOptionsToIncludeType(effectiveSettings),
|
|
811
|
-
exclude_path: effectiveSettings.excludes || [],
|
|
812
|
-
extra_include_path: effectiveSettings.extraIncludes || [],
|
|
813
|
-
};
|
|
814
|
-
// Calculate diff for all modes
|
|
815
|
-
const currentBackend = selectedRepo.settings;
|
|
816
|
-
// Convert current backend settings to SyncOptions for user-friendly display
|
|
817
|
-
const currentSyncOptions = {
|
|
818
|
-
includes: currentBackend.include_path || [],
|
|
819
|
-
excludes: currentBackend.exclude_path || [],
|
|
820
|
-
extraIncludes: currentBackend.extra_include_path || [],
|
|
821
|
-
...includeTypeToSyncOptions(currentBackend.include_type || []),
|
|
822
|
-
};
|
|
823
|
-
const normalizedCurrent = normalizeSyncOptions(currentSyncOptions);
|
|
824
|
-
const normalizedEffective = normalizeSyncOptions(effectiveSettings);
|
|
825
|
-
const gitSyncCurrent = extractGitSyncFields(normalizedCurrent);
|
|
826
|
-
const gitSyncEffective = extractGitSyncFields(normalizedEffective);
|
|
827
|
-
const hasChanges = !deepEqual(gitSyncEffective, gitSyncCurrent);
|
|
828
|
-
if (opts.diff) {
|
|
829
|
-
// --diff flag: show differences and exit
|
|
830
|
-
if (opts.jsonOutput) {
|
|
831
|
-
// Generate structured diff using the same normalized objects
|
|
832
|
-
const structuredDiff = hasChanges
|
|
833
|
-
? generateStructuredDiff(gitSyncCurrent, gitSyncEffective)
|
|
834
|
-
: {};
|
|
835
|
-
console.log(JSON.stringify({
|
|
836
|
-
success: true,
|
|
837
|
-
hasChanges,
|
|
838
|
-
local: syncOptionsToBackendFormat(normalizedEffective),
|
|
839
|
-
backend: syncOptionsToBackendFormat(normalizedCurrent),
|
|
840
|
-
repository: selectedRepo.git_repo_resource_path,
|
|
841
|
-
diff: structuredDiff,
|
|
842
|
-
}));
|
|
843
|
-
}
|
|
844
|
-
else {
|
|
845
|
-
if (hasChanges) {
|
|
846
|
-
log.info("Changes that would be pushed to Windmill:");
|
|
847
|
-
const changes = generateChanges(normalizedCurrent, normalizedEffective);
|
|
848
|
-
if (Object.keys(changes).length === 0) {
|
|
849
|
-
log.info(colors.green("No changes to push"));
|
|
850
|
-
}
|
|
851
|
-
else {
|
|
852
|
-
displayChanges(changes);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
else {
|
|
856
|
-
log.info(colors.green("No changes to push"));
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
// Default behavior: show changes and ask for confirmation (unless --yes is passed)
|
|
862
|
-
if (hasChanges) {
|
|
863
|
-
if (!opts.jsonOutput) {
|
|
864
|
-
const changes = generateChanges(normalizedCurrent, normalizedEffective);
|
|
865
|
-
if (Object.keys(changes).length === 0) {
|
|
866
|
-
log.info(colors.green("No changes to push"));
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
else {
|
|
870
|
-
log.info("Changes that would be pushed to Windmill:");
|
|
871
|
-
displayChanges(changes);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
// Ask for confirmation unless --yes is passed or not in TTY
|
|
875
|
-
if (!opts.yes && dntShim.Deno.stdin.isTerminal()) {
|
|
876
|
-
const confirmed = await Confirm.prompt({
|
|
877
|
-
message: `Do you want to apply these changes to the remote?`,
|
|
878
|
-
default: true,
|
|
879
|
-
});
|
|
880
|
-
if (!confirmed) {
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
else {
|
|
886
|
-
log.info(colors.green("No changes to push"));
|
|
887
|
-
return;
|
|
888
|
-
}
|
|
889
|
-
if (opts.withBackendSettings) {
|
|
890
|
-
// Skip backend update when using simulated settings
|
|
891
|
-
if (opts.jsonOutput) {
|
|
892
|
-
console.log(JSON.stringify({
|
|
893
|
-
success: true,
|
|
894
|
-
message: `Git-sync settings push simulated (--with-backend-settings used)`,
|
|
895
|
-
repository: selectedRepo.git_repo_resource_path,
|
|
896
|
-
simulated: true,
|
|
897
|
-
}));
|
|
898
|
-
}
|
|
899
|
-
else {
|
|
900
|
-
log.info(colors.green(`Git-sync settings push simulated for ${selectedRepo.git_repo_resource_path} (--with-backend-settings used)`));
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
else {
|
|
904
|
-
// Update the specific repository settings
|
|
905
|
-
const updatedRepos = settings.git_sync.repositories.map((repo) => {
|
|
906
|
-
if (repo.git_repo_resource_path ===
|
|
907
|
-
selectedRepo.git_repo_resource_path) {
|
|
908
|
-
return {
|
|
909
|
-
...repo,
|
|
910
|
-
settings: backendFormat,
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
return repo;
|
|
914
|
-
});
|
|
915
|
-
// Push updated settings to backend
|
|
916
|
-
try {
|
|
917
|
-
await wmill.editWorkspaceGitSyncConfig({
|
|
918
|
-
workspace: workspace.workspaceId,
|
|
919
|
-
requestBody: {
|
|
920
|
-
git_sync_settings: {
|
|
921
|
-
repositories: updatedRepos,
|
|
922
|
-
},
|
|
923
|
-
},
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
catch (apiError) {
|
|
927
|
-
const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
|
|
928
|
-
if (opts.jsonOutput) {
|
|
929
|
-
console.log(JSON.stringify({
|
|
930
|
-
success: false,
|
|
931
|
-
error: `Failed to update workspace git-sync config: ${errorMessage}`,
|
|
932
|
-
}));
|
|
933
|
-
}
|
|
934
|
-
else {
|
|
935
|
-
log.error(colors.red(`Failed to update workspace git-sync config: ${errorMessage}`));
|
|
936
|
-
}
|
|
937
|
-
return;
|
|
938
|
-
}
|
|
939
|
-
if (opts.jsonOutput) {
|
|
940
|
-
console.log(JSON.stringify({
|
|
941
|
-
success: true,
|
|
942
|
-
message: `Git-sync settings pushed successfully`,
|
|
943
|
-
repository: selectedRepo.git_repo_resource_path,
|
|
944
|
-
}));
|
|
945
|
-
}
|
|
946
|
-
else {
|
|
947
|
-
log.info(colors.green(`Git-sync settings pushed successfully to ${selectedRepo.git_repo_resource_path}`));
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
catch (error) {
|
|
952
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
953
|
-
if (opts.jsonOutput) {
|
|
954
|
-
console.log(JSON.stringify({ success: false, error: errorMessage }));
|
|
955
|
-
}
|
|
956
|
-
else {
|
|
957
|
-
log.error(colors.red(`Failed to push git-sync settings: ${errorMessage}`));
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
const command = new Command()
|
|
962
|
-
.description("Manage git-sync settings between local wmill.yaml and Windmill backend")
|
|
963
|
-
.command("pull")
|
|
964
|
-
.description("Pull git-sync settings from Windmill backend to local wmill.yaml")
|
|
965
|
-
.option("--repository <repo:string>", "Specify repository path (e.g., u/user/repo)")
|
|
966
|
-
.option("--workspace-level", "Write settings to workspace:* override instead of workspace:repo")
|
|
967
|
-
.option("--default", "Write settings to top-level defaults instead of overrides")
|
|
968
|
-
.option("--replace", "Replace existing settings (non-interactive mode)")
|
|
969
|
-
.option("--override", "Add repository-specific override (non-interactive mode)")
|
|
970
|
-
.option("--diff", "Show differences without applying changes")
|
|
971
|
-
.option("--json-output", "Output in JSON format")
|
|
972
|
-
.option("--with-backend-settings <json:string>", "Use provided JSON settings instead of querying backend (for testing)")
|
|
973
|
-
.option("--yes", "Skip interactive prompts and use default behavior")
|
|
974
|
-
.action(pullGitSyncSettings)
|
|
975
|
-
.command("push")
|
|
976
|
-
.description("Push git-sync settings from local wmill.yaml to Windmill backend")
|
|
977
|
-
.option("--repository <repo:string>", "Specify repository path (e.g., u/user/repo)")
|
|
978
|
-
.option("--diff", "Show what would be pushed without applying changes")
|
|
979
|
-
.option("--json-output", "Output in JSON format")
|
|
980
|
-
.option("--with-backend-settings <json:string>", "Use provided JSON settings instead of querying backend (for testing)")
|
|
981
|
-
.option("--yes", "Skip interactive prompts and use default behavior")
|
|
982
|
-
.action(pushGitSyncSettings);
|
|
983
|
-
export { pullGitSyncSettings, pushGitSyncSettings };
|
|
984
|
-
export default command;
|