windmill-cli 1.505.4 → 1.507.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/conf.js CHANGED
@@ -12,7 +12,54 @@ export async function readConfigFile() {
12
12
  return {};
13
13
  }
14
14
  }
15
+ // Default sync options - shared across the codebase to prevent duplication
16
+ export const DEFAULT_SYNC_OPTIONS = {
17
+ defaultTs: 'bun',
18
+ includes: ['f/**'],
19
+ excludes: [],
20
+ codebases: [],
21
+ skipVariables: false,
22
+ skipResources: false,
23
+ skipResourceTypes: false,
24
+ skipSecrets: true,
25
+ skipScripts: false,
26
+ skipFlows: false,
27
+ skipApps: false,
28
+ skipFolders: false,
29
+ includeSchedules: false,
30
+ includeTriggers: false,
31
+ includeUsers: false,
32
+ includeGroups: false,
33
+ includeSettings: false,
34
+ includeKey: false
35
+ };
15
36
  export async function mergeConfigWithConfigFile(opts) {
16
37
  const configFile = await readConfigFile();
17
38
  return Object.assign(configFile ?? {}, opts);
18
39
  }
40
+ // Get effective settings by merging top-level settings and overrides
41
+ export function getEffectiveSettings(config, baseUrl, workspaceId, repo) {
42
+ // Start with empty object - no defaults
43
+ let effective = {};
44
+ // Merge top-level settings from config (which contains user's chosen defaults)
45
+ Object.keys(config).forEach(key => {
46
+ if (key !== 'overrides' && config[key] !== undefined) {
47
+ effective[key] = config[key];
48
+ }
49
+ });
50
+ if (!config.overrides) {
51
+ return effective;
52
+ }
53
+ // Construct override keys using the single format
54
+ const workspaceKey = `${baseUrl}:${workspaceId}:*`;
55
+ const repoKey = `${baseUrl}:${workspaceId}:${repo}`;
56
+ // Apply workspace-level overrides
57
+ if (config.overrides[workspaceKey]) {
58
+ Object.assign(effective, config.overrides[workspaceKey]);
59
+ }
60
+ // Apply repository-specific overrides (overrides workspace-level)
61
+ if (config.overrides[repoKey]) {
62
+ Object.assign(effective, config.overrides[repoKey]);
63
+ }
64
+ return effective;
65
+ }
package/esm/context.js CHANGED
@@ -10,7 +10,7 @@ async function tryResolveWorkspace(opts) {
10
10
  if (cache)
11
11
  return { isError: false, value: cache };
12
12
  if (opts.workspace) {
13
- const e = await getWorkspaceByName(opts.workspace);
13
+ const e = await getWorkspaceByName(opts.workspace, opts.configDir);
14
14
  if (!e) {
15
15
  return {
16
16
  isError: true,
@@ -32,8 +32,37 @@ async function tryResolveWorkspace(opts) {
32
32
  export async function resolveWorkspace(opts) {
33
33
  if (opts.baseUrl) {
34
34
  if (opts.workspace && opts.token) {
35
+ const normalizedBaseUrl = new URL(opts.baseUrl).toString(); // add trailing slash if not present
36
+ // Try to find existing workspace profile by name, then by workspaceId + remote
37
+ if (opts.workspace) {
38
+ // Try by workspace name first
39
+ let existingWorkspace = await getWorkspaceByName(opts.workspace, opts.configDir);
40
+ // If not found by name, try to find by workspaceId + remote match
41
+ if (!existingWorkspace) {
42
+ const { allWorkspaces } = await import("./workspace.js");
43
+ const workspaces = await allWorkspaces(opts.configDir);
44
+ const matchingWorkspaces = workspaces.filter(w => w.workspaceId === opts.workspace && w.remote === normalizedBaseUrl);
45
+ // Due to uniqueness constraint, there can only be 0 or 1 match
46
+ if (matchingWorkspaces.length === 1) {
47
+ existingWorkspace = matchingWorkspaces[0];
48
+ }
49
+ }
50
+ if (existingWorkspace) {
51
+ // Validate that the base URL matches the profile's remote
52
+ if (existingWorkspace.remote !== normalizedBaseUrl) {
53
+ log.info(colors.red(`Base URL mismatch: --base-url is ${normalizedBaseUrl} but workspace profile "${opts.workspace}" uses ${existingWorkspace.remote}`));
54
+ return dntShim.Deno.exit(-1);
55
+ }
56
+ // Use the existing workspace profile (preserves workspace name)
57
+ return {
58
+ ...existingWorkspace,
59
+ token: opts.token, // Use the provided token
60
+ };
61
+ }
62
+ }
63
+ // No existing profile found, create temporary workspace
35
64
  return {
36
- remote: new URL(opts.baseUrl).toString(), // add trailing slash if not present
65
+ remote: normalizedBaseUrl,
37
66
  workspaceId: opts.workspace,
38
67
  name: opts.workspace,
39
68
  token: opts.token,
@@ -63,16 +92,21 @@ export async function requireLogin(opts) {
63
92
  try {
64
93
  return await wmill.globalWhoami();
65
94
  }
66
- catch {
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
+ }
67
101
  log.info("! Could not reach API given existing credentials. Attempting to reauth...");
68
102
  const newToken = await loginInteractive(workspace.remote);
69
103
  if (!newToken) {
70
- throw new Error("Could not reauth");
104
+ throw new Error("Unauthorized: Could not authenticate with the provided credentials");
71
105
  }
72
106
  removeWorkspace(workspace.name, false, opts);
73
107
  workspace.token = newToken;
74
108
  addWorkspace(workspace, opts);
75
- setClient(token, workspace.remote.substring(0, workspace.remote.length - 1));
109
+ setClient(newToken, workspace.remote.substring(0, workspace.remote.length - 1));
76
110
  return await wmill.globalWhoami();
77
111
  }
78
112
  }
@@ -86,6 +120,11 @@ export async function fetchVersion(baseUrl) {
86
120
  }
87
121
  }
88
122
  const response = await fetch(new URL(new URL(baseUrl).origin + "/api/version"), { headers: requestHeaders, method: "GET" });
123
+ if (!response.ok) {
124
+ // Consume response body even on error to avoid resource leak
125
+ await response.text();
126
+ throw new Error(`Failed to fetch version: ${response.status} ${response.statusText}`);
127
+ }
89
128
  return await response.text();
90
129
  }
91
130
  export async function tryResolveVersion(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.505.4',
35
+ VERSION: '1.507.0',
36
36
  WITH_CREDENTIALS: true,
37
37
  interceptors: {
38
38
  request: new Interceptors(),
@@ -2332,6 +2332,25 @@ export const createAccount = (data) => {
2332
2332
  mediaType: 'application/json'
2333
2333
  });
2334
2334
  };
2335
+ /**
2336
+ * connect OAuth using client credentials
2337
+ * @param data The data for the request.
2338
+ * @param data.client OAuth client name
2339
+ * @param data.requestBody client credentials flow parameters
2340
+ * @returns TokenResponse OAuth token response
2341
+ * @throws ApiError
2342
+ */
2343
+ export const connectClientCredentials = (data) => {
2344
+ return __request(OpenAPI, {
2345
+ method: 'POST',
2346
+ url: '/oauth/connect_client_credentials/{client}',
2347
+ path: {
2348
+ client: data.client
2349
+ },
2350
+ body: data.requestBody,
2351
+ mediaType: 'application/json'
2352
+ });
2353
+ };
2335
2354
  /**
2336
2355
  * refresh token
2337
2356
  * @param data The data for the request.