windmill-cli 1.508.0 → 1.509.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.
Files changed (54) hide show
  1. package/esm/apps.js +2 -1
  2. package/esm/auth.js +40 -0
  3. package/esm/conf.js +13 -0
  4. package/esm/context.js +2 -32
  5. package/esm/dev.js +2 -1
  6. package/esm/flow.js +7 -4
  7. package/esm/folder.js +2 -1
  8. package/esm/gen/core/OpenAPI.js +1 -1
  9. package/esm/gen/services.gen.js +42 -1
  10. package/esm/gitsync-settings.js +216 -44
  11. package/esm/hub.js +2 -1
  12. package/esm/main.js +4 -2
  13. package/esm/metadata.js +79 -56
  14. package/esm/resource-type.js +2 -1
  15. package/esm/resource.js +2 -1
  16. package/esm/schedule.js +2 -1
  17. package/esm/script.js +15 -23
  18. package/esm/script_common.js +6 -1
  19. package/esm/sync.js +9 -60
  20. package/esm/trigger.js +2 -3
  21. package/esm/user.js +1 -1
  22. package/esm/utils.js +2 -2
  23. package/esm/variable.js +2 -1
  24. package/esm/workspace.js +1 -1
  25. package/package.json +1 -1
  26. package/types/apps.d.ts.map +1 -1
  27. package/types/auth.d.ts +8 -0
  28. package/types/auth.d.ts.map +1 -0
  29. package/types/conf.d.ts.map +1 -1
  30. package/types/context.d.ts +0 -2
  31. package/types/context.d.ts.map +1 -1
  32. package/types/dev.d.ts.map +1 -1
  33. package/types/flow.d.ts.map +1 -1
  34. package/types/folder.d.ts.map +1 -1
  35. package/types/gen/services.gen.d.ts +21 -1
  36. package/types/gen/services.gen.d.ts.map +1 -1
  37. package/types/gen/types.gen.d.ts +29 -5
  38. package/types/gen/types.gen.d.ts.map +1 -1
  39. package/types/gitsync-settings.d.ts +4 -0
  40. package/types/gitsync-settings.d.ts.map +1 -1
  41. package/types/hub.d.ts.map +1 -1
  42. package/types/main.d.ts +1 -1
  43. package/types/metadata.d.ts +5 -2
  44. package/types/metadata.d.ts.map +1 -1
  45. package/types/resource-type.d.ts.map +1 -1
  46. package/types/resource.d.ts.map +1 -1
  47. package/types/schedule.d.ts.map +1 -1
  48. package/types/script.d.ts +2 -7
  49. package/types/script.d.ts.map +1 -1
  50. package/types/script_common.d.ts +14 -0
  51. package/types/script_common.d.ts.map +1 -1
  52. package/types/sync.d.ts.map +1 -1
  53. package/types/trigger.d.ts.map +1 -1
  54. package/types/variable.d.ts.map +1 -1
package/esm/apps.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // deno-lint-ignore-file no-explicit-any
2
- import { requireLogin, resolveWorkspace, validatePath } from "./context.js";
2
+ import { requireLogin } from "./auth.js";
3
+ import { resolveWorkspace, validatePath } from "./context.js";
3
4
  import { colors, Command, log, SEP, Table, yamlParseFile } from "./deps.js";
4
5
  import * as wmill from "./gen/services.gen.js";
5
6
  import { isSuperset } from "./types.js";
package/esm/auth.js ADDED
@@ -0,0 +1,40 @@
1
+ // deno-lint-ignore-file no-explicit-any
2
+ import { log, setClient } from "./deps.js";
3
+ import * as wmill from "./gen/services.gen.js";
4
+ import { loginInteractive, tryGetLoginInfo } from "./login.js";
5
+ import { addWorkspace, removeWorkspace } from "./workspace.js";
6
+ /**
7
+ * Main authentication function - moved from context.ts to break circular dependencies
8
+ * This function maintains the original API signature from context.ts
9
+ */
10
+ export async function requireLogin(opts) {
11
+ // Import resolveWorkspace to avoid circular dependency at module level
12
+ const { resolveWorkspace } = await import("./context.js");
13
+ const workspace = await resolveWorkspace(opts);
14
+ let token = await tryGetLoginInfo(opts);
15
+ if (!token) {
16
+ token = workspace.token;
17
+ }
18
+ setClient(token, workspace.remote.substring(0, workspace.remote.length - 1));
19
+ try {
20
+ return await wmill.globalWhoami();
21
+ }
22
+ catch (error) {
23
+ // Check for network errors and provide clearer messages
24
+ const errorMsg = error instanceof Error ? error.message : String(error);
25
+ if (errorMsg.includes('fetch') || errorMsg.includes('connection') || errorMsg.includes('ECONNREFUSED') || errorMsg.includes('refused')) {
26
+ throw new Error(`Network error: Could not connect to Windmill server at ${workspace.remote}`);
27
+ }
28
+ log.info("! Could not reach API given existing credentials. Attempting to reauth...");
29
+ const newToken = await loginInteractive(workspace.remote);
30
+ if (!newToken) {
31
+ throw new Error("Unauthorized: Could not authenticate with the provided credentials");
32
+ }
33
+ // Update workspace token
34
+ removeWorkspace(workspace.name, false, opts);
35
+ workspace.token = newToken;
36
+ addWorkspace(workspace, opts);
37
+ setClient(newToken, workspace.remote.substring(0, workspace.remote.length - 1));
38
+ return await wmill.globalWhoami();
39
+ }
40
+ }
package/esm/conf.js CHANGED
@@ -48,18 +48,31 @@ export function getEffectiveSettings(config, baseUrl, workspaceId, repo) {
48
48
  }
49
49
  });
50
50
  if (!config.overrides) {
51
+ if (repo) {
52
+ log.info(`No overrides found in wmill.yaml, using top-level settings (repository flag ignored)`);
53
+ }
51
54
  return effective;
52
55
  }
53
56
  // Construct override keys using the single format
54
57
  const workspaceKey = `${baseUrl}:${workspaceId}:*`;
55
58
  const repoKey = `${baseUrl}:${workspaceId}:${repo}`;
59
+ let appliedOverrides = [];
56
60
  // Apply workspace-level overrides
57
61
  if (config.overrides[workspaceKey]) {
58
62
  Object.assign(effective, config.overrides[workspaceKey]);
63
+ appliedOverrides.push("workspace-level");
59
64
  }
60
65
  // Apply repository-specific overrides (overrides workspace-level)
61
66
  if (config.overrides[repoKey]) {
62
67
  Object.assign(effective, config.overrides[repoKey]);
68
+ appliedOverrides.push("repository-specific");
69
+ }
70
+ else if (repo) {
71
+ // Repository was specified but no override found
72
+ log.info(`Repository override not found for "${repo}", using ${appliedOverrides.length > 0 ? appliedOverrides.join(" + ") : "top-level"} settings`);
73
+ }
74
+ if (appliedOverrides.length > 0) {
75
+ log.info(`Applied ${appliedOverrides.join(" + ")} overrides${repo ? ` for repository "${repo}"` : ""}`);
63
76
  }
64
77
  return effective;
65
78
  }
package/esm/context.js CHANGED
@@ -1,10 +1,8 @@
1
1
  // deno-lint-ignore-file no-explicit-any
2
2
  import * as dntShim from "./_dnt.shims.js";
3
- import { colors, log, setClient } from "./deps.js";
4
- import * as wmill from "./gen/services.gen.js";
5
- import { loginInteractive, tryGetLoginInfo } from "./login.js";
3
+ import { colors, log } from "./deps.js";
6
4
  import { getHeaders } from "./utils.js";
7
- import { addWorkspace, getActiveWorkspace, getWorkspaceByName, removeWorkspace, } from "./workspace.js";
5
+ import { getActiveWorkspace, getWorkspaceByName, } from "./workspace.js";
8
6
  async function tryResolveWorkspace(opts) {
9
7
  const cache = opts.__secret_workspace;
10
8
  if (cache)
@@ -82,34 +80,6 @@ export async function resolveWorkspace(opts) {
82
80
  return res.value;
83
81
  }
84
82
  }
85
- export async function requireLogin(opts) {
86
- const workspace = await resolveWorkspace(opts);
87
- let token = await tryGetLoginInfo(opts);
88
- if (!token) {
89
- token = workspace.token;
90
- }
91
- setClient(token, workspace.remote.substring(0, workspace.remote.length - 1));
92
- try {
93
- return await wmill.globalWhoami();
94
- }
95
- catch (error) {
96
- // Check for network errors and provide clearer messages
97
- const errorMsg = error instanceof Error ? error.message : String(error);
98
- if (errorMsg.includes('fetch') || errorMsg.includes('connection') || errorMsg.includes('ECONNREFUSED') || errorMsg.includes('refused')) {
99
- throw new Error(`Network error: Could not connect to Windmill server at ${workspace.remote}`);
100
- }
101
- log.info("! Could not reach API given existing credentials. Attempting to reauth...");
102
- const newToken = await loginInteractive(workspace.remote);
103
- if (!newToken) {
104
- throw new Error("Unauthorized: Could not authenticate with the provided credentials");
105
- }
106
- removeWorkspace(workspace.name, false, opts);
107
- workspace.token = newToken;
108
- addWorkspace(workspace, opts);
109
- setClient(newToken, workspace.remote.substring(0, workspace.remote.length - 1));
110
- return await wmill.globalWhoami();
111
- }
112
- }
113
83
  export async function fetchVersion(baseUrl) {
114
84
  const requestHeaders = new Headers();
115
85
  const extraHeaders = getHeaders();
package/esm/dev.js CHANGED
@@ -2,7 +2,8 @@ import * as dntShim from "./_dnt.shims.js";
2
2
  import { Command, SEP, WebSocketServer, express, getPort, http, log, open, yamlParseFile, } from "./deps.js";
3
3
  import { getTypeStrFromPath } from "./types.js";
4
4
  import { ignoreF } from "./sync.js";
5
- import { requireLogin, resolveWorkspace } from "./context.js";
5
+ import { requireLogin } from "./auth.js";
6
+ import { resolveWorkspace } from "./context.js";
6
7
  import { mergeConfigWithConfigFile, readConfigFile, } from "./conf.js";
7
8
  import { exts, findGlobalDeps, removeExtensionToPath } from "./script.js";
8
9
  import { inferContentTypeFromFilePath } from "./script_common.js";
package/esm/flow.js CHANGED
@@ -4,7 +4,8 @@ import { isSuperset } from "./types.js";
4
4
  import { Confirm, SEP, log, yamlStringify } from "./deps.js";
5
5
  import { colors, Command, Table, yamlParseFile } from "./deps.js";
6
6
  import * as wmill from "./gen/services.gen.js";
7
- import { requireLogin, resolveWorkspace, validatePath } from "./context.js";
7
+ import { requireLogin } from "./auth.js";
8
+ import { resolveWorkspace, validatePath } from "./context.js";
8
9
  import { resolve, track_job } from "./script.js";
9
10
  import { defaultFlowDefinition } from "./bootstrap/flow_bootstrap.js";
10
11
  import { generateFlowLockInternal } from "./metadata.js";
@@ -191,12 +192,13 @@ async function run(opts, path) {
191
192
  log.info(jobInfo.result ?? {});
192
193
  }
193
194
  async function generateLocks(opts, folder) {
195
+ const useRawReqs = opts.useRawRequirements || dntShim.Deno.env.get("USE_RAW_REQUIREMENTS") === "true";
194
196
  const workspace = await resolveWorkspace(opts);
195
197
  await requireLogin(opts);
196
198
  opts = await mergeConfigWithConfigFile(opts);
197
199
  if (folder) {
198
200
  // read script metadata file
199
- await generateFlowLockInternal(folder, false, workspace);
201
+ await generateFlowLockInternal(folder, false, workspace, opts, undefined, undefined, useRawReqs);
200
202
  }
201
203
  else {
202
204
  const ignore = await ignoreF(opts);
@@ -208,7 +210,7 @@ async function generateLocks(opts, folder) {
208
210
  }, false, {})).map((x) => x.substring(0, x.lastIndexOf(SEP)));
209
211
  let hasAny = false;
210
212
  for (const folder of elems) {
211
- const candidate = await generateFlowLockInternal(folder, true, workspace);
213
+ const candidate = await generateFlowLockInternal(folder, true, workspace, opts, undefined, undefined, useRawReqs);
212
214
  if (candidate) {
213
215
  hasAny = true;
214
216
  log.info(colors.green(`+ ${candidate}`));
@@ -228,7 +230,7 @@ async function generateLocks(opts, folder) {
228
230
  return;
229
231
  }
230
232
  for (const folder of elems) {
231
- await generateFlowLockInternal(folder, false, workspace);
233
+ await generateFlowLockInternal(folder, false, workspace, opts, undefined, undefined, useRawReqs);
232
234
  }
233
235
  }
234
236
  }
@@ -264,6 +266,7 @@ const command = new Command()
264
266
  .command("generate-locks", "re-generate the lock files of all inline scripts of all updated flows")
265
267
  .arguments("[flow:file]")
266
268
  .option("--yes", "Skip confirmation prompt")
269
+ .option("-r --use-raw-requirements", "Use raw requirements (requirements.txt, go.mod, package.json, etc) instead of generating them on the server (can also be set with USE_RAW_REQUIREMENTS=true environment variable)")
267
270
  .option("-i --includes <patterns:file[]>", "Comma separated patterns to specify which file to take into account (among files that are compatible with windmill). Patterns can include * (any string until '/') and ** (any string)")
268
271
  .option("-e --excludes <patterns:file[]>", "Comma separated patterns to specify which file to NOT take into account.")
269
272
  .action(generateLocks)
package/esm/folder.js CHANGED
@@ -2,7 +2,8 @@
2
2
  import * as dntShim from "./_dnt.shims.js";
3
3
  import { colors, Command, log, SEP, Table } from "./deps.js";
4
4
  import * as wmill from "./gen/services.gen.js";
5
- import { requireLogin, resolveWorkspace, validatePath } from "./context.js";
5
+ import { requireLogin } from "./auth.js";
6
+ import { resolveWorkspace, validatePath } from "./context.js";
6
7
  import { isSuperset, parseFromFile } from "./types.js";
7
8
  async function list(opts) {
8
9
  const workspace = await resolveWorkspace(opts);
@@ -32,7 +32,7 @@ export const OpenAPI = {
32
32
  PASSWORD: undefined,
33
33
  TOKEN: getEnv("WM_TOKEN"),
34
34
  USERNAME: undefined,
35
- VERSION: '1.508.0',
35
+ VERSION: '1.509.0',
36
36
  WITH_CREDENTIALS: true,
37
37
  interceptors: {
38
38
  request: new Interceptors(),
@@ -1875,6 +1875,17 @@ export const usernameToEmail = (data) => {
1875
1875
  }
1876
1876
  });
1877
1877
  };
1878
+ /**
1879
+ * list of available scopes
1880
+ * @returns ScopeDomain list of available scopes
1881
+ * @throws ApiError
1882
+ */
1883
+ export const listAvailableScopes = () => {
1884
+ return __request(OpenAPI, {
1885
+ method: 'GET',
1886
+ url: '/tokens/list/scopes'
1887
+ });
1888
+ };
1878
1889
  /**
1879
1890
  * create token
1880
1891
  * @param data The data for the request.
@@ -5595,6 +5606,7 @@ export const countJobsByTag = (data = {}) => {
5595
5606
  * @param data.workspace
5596
5607
  * @param data.id
5597
5608
  * @param data.noLogs
5609
+ * @param data.noCode
5598
5610
  * @returns Job job details
5599
5611
  * @throws ApiError
5600
5612
  */
@@ -5607,7 +5619,8 @@ export const getJob = (data) => {
5607
5619
  id: data.id
5608
5620
  },
5609
5621
  query: {
5610
- no_logs: data.noLogs
5622
+ no_logs: data.noLogs,
5623
+ no_code: data.noCode
5611
5624
  }
5612
5625
  });
5613
5626
  };
@@ -5691,6 +5704,34 @@ export const getJobUpdates = (data) => {
5691
5704
  }
5692
5705
  });
5693
5706
  };
5707
+ /**
5708
+ * get job updates via server-sent events
5709
+ * @param data The data for the request.
5710
+ * @param data.workspace
5711
+ * @param data.id
5712
+ * @param data.running
5713
+ * @param data.logOffset
5714
+ * @param data.getProgress
5715
+ * @param data.onlyResult
5716
+ * @returns string server-sent events stream of job updates
5717
+ * @throws ApiError
5718
+ */
5719
+ export const getJobUpdatesSse = (data) => {
5720
+ return __request(OpenAPI, {
5721
+ method: 'GET',
5722
+ url: '/w/{workspace}/jobs_u/getupdate_sse/{id}',
5723
+ path: {
5724
+ workspace: data.workspace,
5725
+ id: data.id
5726
+ },
5727
+ query: {
5728
+ running: data.running,
5729
+ log_offset: data.logOffset,
5730
+ get_progress: data.getProgress,
5731
+ only_result: data.onlyResult
5732
+ }
5733
+ });
5734
+ };
5694
5735
  /**
5695
5736
  * get log file from object store
5696
5737
  * @param data The data for the request.
@@ -1,6 +1,7 @@
1
1
  import * as dntShim from "./_dnt.shims.js";
2
- import { colors, Command, log, yamlStringify } from "./deps.js";
3
- import { requireLogin, resolveWorkspace } from "./context.js";
2
+ import { colors, Command, Confirm, log, yamlStringify } from "./deps.js";
3
+ import { requireLogin } from "./auth.js";
4
+ import { resolveWorkspace } from "./context.js";
4
5
  import * as wmill from "./gen/services.gen.js";
5
6
  import { DEFAULT_SYNC_OPTIONS, getEffectiveSettings, readConfigFile, } from "./conf.js";
6
7
  import { deepEqual, selectRepository } from "./utils.js";
@@ -125,6 +126,122 @@ function includeTypeToSyncOptions(includeTypes) {
125
126
  includeKey: includeTypes.includes("key"),
126
127
  };
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
+ }
128
245
  // Convert SyncOptions boolean flags to backend include_type array
129
246
  function syncOptionsToIncludeType(opts) {
130
247
  const includeTypes = [];
@@ -217,6 +334,23 @@ function displayChanges(changes) {
217
334
  }
218
335
  }
219
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
+ }
220
354
  async function pullGitSyncSettings(opts) {
221
355
  const workspace = await resolveWorkspace(opts);
222
356
  await requireLogin(opts);
@@ -300,18 +434,9 @@ async function pullGitSyncSettings(opts) {
300
434
  return;
301
435
  }
302
436
  // Find the repository to work with
303
- let selectedRepo;
304
- if (opts.repository) {
305
- const found = settings.git_sync.repositories.find((r) => r.git_repo_resource_path === opts.repository ||
306
- r.git_repo_resource_path === `$res:${opts.repository}`);
307
- if (!found) {
308
- throw new Error(`Repository ${opts.repository} not found`);
309
- }
310
- selectedRepo = found;
311
- }
312
- else {
313
- selectedRepo = await selectRepository(settings.git_sync.repositories);
314
- }
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");
315
440
  // Convert backend settings to SyncOptions format
316
441
  const backendSyncOptions = {
317
442
  includes: selectedRepo.settings.include_path || [],
@@ -392,7 +517,7 @@ async function pullGitSyncSettings(opts) {
392
517
  }
393
518
  else {
394
519
  if (hasChanges) {
395
- log.info("Changes that would be made:");
520
+ log.info("Changes that would be applied locally:");
396
521
  const changes = generateChanges(normalizedCurrent, normalizedBackend);
397
522
  if (Object.keys(changes).length === 0) {
398
523
  log.info(colors.green("No differences found"));
@@ -419,7 +544,13 @@ async function pullGitSyncSettings(opts) {
419
544
  const gitSyncBackend = extractGitSyncFields(normalizeSyncOptions(backendSyncOptions));
420
545
  const gitSyncCurrent = extractGitSyncFields(normalizeSyncOptions(currentSettings));
421
546
  const hasConflict = !deepEqual(gitSyncBackend, gitSyncCurrent);
422
- if (hasConflict && dntShim.Deno.stdin.isTerminal()) {
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
+ }
423
554
  // Interactive mode - ask user
424
555
  const { Select } = await import("./deps.js");
425
556
  const choice = await Select.prompt({
@@ -446,6 +577,13 @@ async function pullGitSyncSettings(opts) {
446
577
  overrideKey = constructOverrideKey(workspace.remote, workspace.workspaceId, repoPath);
447
578
  }
448
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
+ }
449
587
  else if (hasConflict) {
450
588
  // Non-interactive mode with conflicts - show message and exit
451
589
  if (opts.jsonOutput) {
@@ -482,11 +620,19 @@ async function pullGitSyncSettings(opts) {
482
620
  }
483
621
  // Apply the settings based on write mode
484
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
+ }
485
631
  if (writeMode === "replace") {
486
632
  // Preserve existing local config and update only git-sync fields
487
633
  updatedConfig = { ...localConfig };
488
- // Remove overrides since we're in replace mode
489
- delete updatedConfig.overrides;
634
+ // Clear overrides since we're in replace mode, but keep empty object for consistency
635
+ updatedConfig.overrides = {};
490
636
  // Update with backend git-sync settings
491
637
  Object.assign(updatedConfig, backendSyncOptions);
492
638
  }
@@ -648,17 +794,12 @@ async function pushGitSyncSettings(opts) {
648
794
  return;
649
795
  }
650
796
  // Find the repository to work with
651
- let selectedRepo;
652
- if (opts.repository) {
653
- const found = settings.git_sync.repositories.find((r) => r.git_repo_resource_path === opts.repository ||
654
- r.git_repo_resource_path === `$res:${opts.repository}`);
655
- if (!found) {
656
- throw new Error(`Repository ${opts.repository} not found`);
657
- }
658
- selectedRepo = found;
659
- }
660
- else {
661
- selectedRepo = await selectRepository(settings.git_sync.repositories);
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;
662
803
  }
663
804
  // Get effective settings for this workspace/repo
664
805
  const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path);
@@ -670,21 +811,22 @@ async function pushGitSyncSettings(opts) {
670
811
  exclude_path: effectiveSettings.excludes || [],
671
812
  extra_include_path: effectiveSettings.extraIncludes || [],
672
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);
673
828
  if (opts.diff) {
674
- // Show what would be pushed
675
- const currentBackend = selectedRepo.settings;
676
- // Convert current backend settings to SyncOptions for user-friendly display
677
- const currentSyncOptions = {
678
- includes: currentBackend.include_path || [],
679
- excludes: currentBackend.exclude_path || [],
680
- extraIncludes: currentBackend.extra_include_path || [],
681
- ...includeTypeToSyncOptions(currentBackend.include_type || []),
682
- };
683
- const normalizedCurrent = normalizeSyncOptions(currentSyncOptions);
684
- const normalizedEffective = normalizeSyncOptions(effectiveSettings);
685
- const gitSyncCurrent = extractGitSyncFields(normalizedCurrent);
686
- const gitSyncEffective = extractGitSyncFields(normalizedEffective);
687
- const hasChanges = !deepEqual(gitSyncEffective, gitSyncCurrent);
829
+ // --diff flag: show differences and exit
688
830
  if (opts.jsonOutput) {
689
831
  // Generate structured diff using the same normalized objects
690
832
  const structuredDiff = hasChanges
@@ -701,7 +843,7 @@ async function pushGitSyncSettings(opts) {
701
843
  }
702
844
  else {
703
845
  if (hasChanges) {
704
- log.info("Changes that would be pushed:");
846
+ log.info("Changes that would be pushed to Windmill:");
705
847
  const changes = generateChanges(normalizedCurrent, normalizedEffective);
706
848
  if (Object.keys(changes).length === 0) {
707
849
  log.info(colors.green("No changes to push"));
@@ -716,6 +858,34 @@ async function pushGitSyncSettings(opts) {
716
858
  }
717
859
  return;
718
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
+ }
719
889
  if (opts.withBackendSettings) {
720
890
  // Skip backend update when using simulated settings
721
891
  if (opts.jsonOutput) {
@@ -800,6 +970,7 @@ const command = new Command()
800
970
  .option("--diff", "Show differences without applying changes")
801
971
  .option("--json-output", "Output in JSON format")
802
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")
803
974
  .action(pullGitSyncSettings)
804
975
  .command("push")
805
976
  .description("Push git-sync settings from local wmill.yaml to Windmill backend")
@@ -807,6 +978,7 @@ const command = new Command()
807
978
  .option("--diff", "Show what would be pushed without applying changes")
808
979
  .option("--json-output", "Output in JSON format")
809
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")
810
982
  .action(pushGitSyncSettings);
811
983
  export { pullGitSyncSettings, pushGitSyncSettings };
812
984
  export default command;
package/esm/hub.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // deno-lint-ignore-file no-explicit-any
2
2
  import { Command, log } from "./deps.js";
3
3
  import * as wmill from "./gen/services.gen.js";
4
- import { requireLogin, resolveWorkspace } from "./context.js";
4
+ import { requireLogin } from "./auth.js";
5
+ import { resolveWorkspace } from "./context.js";
5
6
  import { pushResourceType } from "./resource-type.js";
6
7
  import { deepEqual } from "./utils.js";
7
8
  const DEFAULT_HUB_BASE_URL = "https://hub.windmill.dev";