windmill-cli 1.607.0 → 1.610.1
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/deps/jsr.io/@windmill-labs/shared-utils/{1.0.11 → 1.0.12}/lib.es.js +202 -35
- package/esm/deps.js +5 -6
- package/esm/gen/core/OpenAPI.js +1 -1
- package/esm/gen/services.gen.js +101 -0
- package/esm/src/commands/app/dev.js +182 -4
- package/esm/src/commands/app/wmillTsDev.js +40 -1
- package/esm/src/commands/script/script.js +2 -1
- package/esm/src/commands/sync/sync.js +34 -32
- package/esm/src/core/conf.js +47 -45
- package/esm/src/core/context.js +34 -22
- package/esm/src/core/specific_items.js +28 -10
- package/esm/src/main.js +1 -1
- package/esm/src/utils/resource_folders.js +3 -1
- package/package.json +1 -1
- package/types/deps/jsr.io/@windmill-labs/shared-utils/{1.0.11 → 1.0.12}/lib.es.d.ts.map +1 -1
- package/types/deps.d.ts +2 -2
- package/types/deps.d.ts.map +1 -1
- package/types/gen/services.gen.d.ts +50 -1
- package/types/gen/services.gen.d.ts.map +1 -1
- package/types/gen/types.gen.d.ts +73 -0
- package/types/gen/types.gen.d.ts.map +1 -1
- package/types/src/commands/app/dev.d.ts.map +1 -1
- package/types/src/commands/app/metadata.d.ts +1 -1
- package/types/src/commands/app/metadata.d.ts.map +1 -1
- package/types/src/commands/app/wmillTsDev.d.ts.map +1 -1
- package/types/src/commands/script/script.d.ts.map +1 -1
- package/types/src/commands/sync/sync.d.ts +5 -1
- package/types/src/commands/sync/sync.d.ts.map +1 -1
- package/types/src/core/conf.d.ts +1 -1
- package/types/src/core/conf.d.ts.map +1 -1
- package/types/src/core/context.d.ts +2 -2
- package/types/src/core/context.d.ts.map +1 -1
- package/types/src/core/specific_items.d.ts +3 -3
- package/types/src/core/specific_items.d.ts.map +1 -1
- package/types/src/main.d.ts +1 -1
- package/types/src/utils/resource_folders.d.ts.map +1 -1
- package/types/windmill-utils-internal/src/gen/types.gen.d.ts +73 -0
- package/types/windmill-utils-internal/src/gen/types.gen.d.ts.map +1 -1
- /package/types/deps/jsr.io/@windmill-labs/shared-utils/{1.0.11 → 1.0.12}/lib.es.d.ts +0 -0
|
@@ -17,9 +17,41 @@ import { replaceInlineScripts } from "./app.js";
|
|
|
17
17
|
import { APP_BACKEND_FOLDER, inferRunnableSchemaFromFile, } from "./app_metadata.js";
|
|
18
18
|
import { loadRunnablesFromBackend } from "./raw_apps.js";
|
|
19
19
|
import { regenerateAgentDocs } from "./generate_agents.js";
|
|
20
|
-
import { hasFolderSuffix,
|
|
20
|
+
import { getFolderSuffix, hasFolderSuffix, setNonDottedPaths, } from "../../utils/resource_folders.js";
|
|
21
21
|
const DEFAULT_PORT = 4000;
|
|
22
22
|
const DEFAULT_HOST = "localhost";
|
|
23
|
+
/**
|
|
24
|
+
* Search for wmill.yaml by traversing upward from the current directory.
|
|
25
|
+
* Unlike the standard findWmillYaml() in conf.ts, this does not stop at
|
|
26
|
+
* the git root - it continues searching until the filesystem root.
|
|
27
|
+
* This is needed for `app dev` which runs from inside a raw_app folder
|
|
28
|
+
* that may be deeply nested within a larger git repository.
|
|
29
|
+
*/
|
|
30
|
+
async function findAndLoadNonDottedPathsSetting() {
|
|
31
|
+
let currentDir = process.cwd();
|
|
32
|
+
while (true) {
|
|
33
|
+
const wmillYamlPath = path.join(currentDir, "wmill.yaml");
|
|
34
|
+
if (fs.existsSync(wmillYamlPath)) {
|
|
35
|
+
try {
|
|
36
|
+
const config = await yamlParseFile(wmillYamlPath);
|
|
37
|
+
setNonDottedPaths(config?.nonDottedPaths ?? false);
|
|
38
|
+
log.debug(`Found wmill.yaml at ${wmillYamlPath}, nonDottedPaths=${config?.nonDottedPaths ?? false}`);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
log.debug(`Failed to parse wmill.yaml at ${wmillYamlPath}: ${e}`);
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Check if we've reached the filesystem root
|
|
46
|
+
const parentDir = path.dirname(currentDir);
|
|
47
|
+
if (parentDir === currentDir) {
|
|
48
|
+
// Reached filesystem root without finding wmill.yaml
|
|
49
|
+
log.debug("No wmill.yaml found, using default dotted paths");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
currentDir = parentDir;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
23
55
|
// HTML template with live reload and SQL migration modal
|
|
24
56
|
const createHTML = (jsPath, cssPath) => `
|
|
25
57
|
<!DOCTYPE html>
|
|
@@ -275,6 +307,9 @@ const createHTML = (jsPath, cssPath) => `
|
|
|
275
307
|
`;
|
|
276
308
|
async function dev(opts) {
|
|
277
309
|
GLOBAL_CONFIG_OPT.noCdToRoot = true;
|
|
310
|
+
// Search for wmill.yaml by traversing upward (without git root constraint)
|
|
311
|
+
// to initialize nonDottedPaths setting before using folder suffix functions
|
|
312
|
+
await findAndLoadNonDottedPathsSetting();
|
|
278
313
|
// Validate that we're in a .raw_app folder
|
|
279
314
|
const cwd = process.cwd();
|
|
280
315
|
const currentDirName = path.basename(cwd);
|
|
@@ -356,9 +391,12 @@ async function dev(opts) {
|
|
|
356
391
|
});
|
|
357
392
|
// Provide the virtual module content
|
|
358
393
|
build.onLoad({ filter: /.*/, namespace: "wmill-virtual" }, (args) => {
|
|
394
|
+
const contents = wmillTs(port);
|
|
359
395
|
log.info(colors.yellow(`[wmill-virtual] Loading virtual module: ${args.path}`));
|
|
396
|
+
log.info(colors.gray(`[wmill-virtual] Exports: ${contents.match(/export (const|function) \w+/g)?.join(", ") ??
|
|
397
|
+
"none"}`));
|
|
360
398
|
return {
|
|
361
|
-
contents
|
|
399
|
+
contents,
|
|
362
400
|
loader: "ts",
|
|
363
401
|
};
|
|
364
402
|
});
|
|
@@ -745,6 +783,23 @@ async function dev(opts) {
|
|
|
745
783
|
}
|
|
746
784
|
break;
|
|
747
785
|
}
|
|
786
|
+
case "streamJob": {
|
|
787
|
+
// Stream job results using SSE
|
|
788
|
+
log.info(colors.blue(`[streamJob] Streaming job: ${jobId}`));
|
|
789
|
+
try {
|
|
790
|
+
await streamJobWithSSE(workspaceId, jobId, reqId, ws, workspace.remote, workspace.token);
|
|
791
|
+
}
|
|
792
|
+
catch (error) {
|
|
793
|
+
log.error(colors.red(`[streamJob] Error: ${error.message}`));
|
|
794
|
+
ws.send(JSON.stringify({
|
|
795
|
+
type: "streamJobRes",
|
|
796
|
+
reqId,
|
|
797
|
+
error: true,
|
|
798
|
+
result: { message: error.message, stack: error.stack },
|
|
799
|
+
}));
|
|
800
|
+
}
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
748
803
|
case "applySqlMigration": {
|
|
749
804
|
// Execute SQL migration against a datatable
|
|
750
805
|
const { sql, datatable, fileName } = message;
|
|
@@ -992,6 +1047,25 @@ async function genRunnablesTs(schemaOverrides = {}) {
|
|
|
992
1047
|
log.error(colors.red(`Failed to generate wmill.d.ts: ${error.message}`));
|
|
993
1048
|
}
|
|
994
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Convert runnables from file format to API format.
|
|
1052
|
+
* File format uses type: "script"|"hubscript"|"flow" for path-based runnables.
|
|
1053
|
+
* API format uses type: "path" with runType: "script"|"hubscript"|"flow".
|
|
1054
|
+
*/
|
|
1055
|
+
function convertRunnablesToApiFormat(runnables) {
|
|
1056
|
+
for (const [runnableId, runnable] of Object.entries(runnables)) {
|
|
1057
|
+
if (runnable?.type === "script" ||
|
|
1058
|
+
runnable?.type === "hubscript" ||
|
|
1059
|
+
runnable?.type === "flow") {
|
|
1060
|
+
// Convert from file format to API format
|
|
1061
|
+
// { type: "script" } -> { type: "path", runType: "script" }
|
|
1062
|
+
const originalType = runnable.type;
|
|
1063
|
+
runnable.runType = originalType;
|
|
1064
|
+
runnable.type = "path";
|
|
1065
|
+
log.debug(`Converted runnable '${runnableId}' from type='${originalType}' to type='path', runType='${originalType}'`);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
995
1069
|
async function loadRunnables() {
|
|
996
1070
|
try {
|
|
997
1071
|
const localPath = process.cwd();
|
|
@@ -1003,6 +1077,9 @@ async function loadRunnables() {
|
|
|
1003
1077
|
const rawApp = (await yamlParseFile(path.join(localPath, "raw_app.yaml")));
|
|
1004
1078
|
runnables = rawApp?.runnables ?? {};
|
|
1005
1079
|
}
|
|
1080
|
+
// Always convert path-based runnables from file format to API format
|
|
1081
|
+
// This handles both backend folder runnables and raw_app.yaml runnables
|
|
1082
|
+
convertRunnablesToApiFormat(runnables);
|
|
1006
1083
|
replaceInlineScripts(runnables, backendPath + SEP, true);
|
|
1007
1084
|
return runnables;
|
|
1008
1085
|
}
|
|
@@ -1044,13 +1121,24 @@ async function executeRunnable(runnable, workspace, appPath, runnableId, args) {
|
|
|
1044
1121
|
cache_ttl: inlineScript.cache_ttl,
|
|
1045
1122
|
};
|
|
1046
1123
|
}
|
|
1047
|
-
else if (runnable.type === "path"
|
|
1048
|
-
|
|
1124
|
+
else if ((runnable.type === "path" || runnable.type === "runnableByPath") &&
|
|
1125
|
+
runnable.runType &&
|
|
1126
|
+
runnable.path) {
|
|
1127
|
+
// Path-based runnables have type: "path" (or legacy "runnableByPath") and runType: "script"|"hubscript"|"flow"
|
|
1049
1128
|
const prefix = runnable.runType;
|
|
1050
1129
|
requestBody.path = prefix !== "hubscript"
|
|
1051
1130
|
? `${prefix}/${runnable.path}`
|
|
1052
1131
|
: `script/${runnable.path}`;
|
|
1053
1132
|
}
|
|
1133
|
+
else {
|
|
1134
|
+
// Neither inline script nor valid path-based runnable
|
|
1135
|
+
const debugInfo = `type=${runnable.type}, runType=${runnable.runType}, ` +
|
|
1136
|
+
`path=${runnable.path}, hasInlineScript=${!!runnable
|
|
1137
|
+
.inlineScript}`;
|
|
1138
|
+
log.error(colors.red(`[executeRunnable] Invalid runnable configuration for '${runnableId}': ${debugInfo}`));
|
|
1139
|
+
throw new Error(`Invalid runnable '${runnableId}': ${debugInfo}. ` +
|
|
1140
|
+
`Must have either inlineScript (for inline type) or type="path" with runType and path fields`);
|
|
1141
|
+
}
|
|
1054
1142
|
const uuid = await wmill.executeComponent({
|
|
1055
1143
|
workspace,
|
|
1056
1144
|
path: appPath,
|
|
@@ -1108,3 +1196,93 @@ async function getJobStatus(workspace, jobId) {
|
|
|
1108
1196
|
id: jobId,
|
|
1109
1197
|
});
|
|
1110
1198
|
}
|
|
1199
|
+
async function streamJobWithSSE(workspace, jobId, reqId, ws, baseUrl, token) {
|
|
1200
|
+
const sseUrl = `${baseUrl}api/w/${workspace}/jobs_u/getupdate_sse/${jobId}?fast=true`;
|
|
1201
|
+
const response = await fetch(sseUrl, {
|
|
1202
|
+
headers: {
|
|
1203
|
+
Accept: "text/event-stream",
|
|
1204
|
+
Authorization: `Bearer ${token}`,
|
|
1205
|
+
},
|
|
1206
|
+
});
|
|
1207
|
+
if (!response.ok) {
|
|
1208
|
+
throw new Error(`SSE request failed: ${response.status} ${response.statusText}`);
|
|
1209
|
+
}
|
|
1210
|
+
const reader = response.body?.getReader();
|
|
1211
|
+
if (!reader) {
|
|
1212
|
+
throw new Error("No response body for SSE stream");
|
|
1213
|
+
}
|
|
1214
|
+
const decoder = new TextDecoder();
|
|
1215
|
+
let buffer = "";
|
|
1216
|
+
try {
|
|
1217
|
+
while (true) {
|
|
1218
|
+
const { done, value } = await reader.read();
|
|
1219
|
+
if (done)
|
|
1220
|
+
break;
|
|
1221
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1222
|
+
const lines = buffer.split("\n");
|
|
1223
|
+
buffer = lines.pop() || "";
|
|
1224
|
+
for (const line of lines) {
|
|
1225
|
+
if (line.startsWith("data: ")) {
|
|
1226
|
+
const data = line.slice(6);
|
|
1227
|
+
try {
|
|
1228
|
+
const update = JSON.parse(data);
|
|
1229
|
+
const type = update.type;
|
|
1230
|
+
if (type === "ping" || type === "timeout") {
|
|
1231
|
+
if (type === "timeout") {
|
|
1232
|
+
reader.cancel();
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (type === "error") {
|
|
1238
|
+
ws.send(JSON.stringify({
|
|
1239
|
+
type: "streamJobRes",
|
|
1240
|
+
reqId,
|
|
1241
|
+
error: true,
|
|
1242
|
+
result: { message: update.error || "SSE error" },
|
|
1243
|
+
}));
|
|
1244
|
+
reader.cancel();
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (type === "not_found") {
|
|
1248
|
+
ws.send(JSON.stringify({
|
|
1249
|
+
type: "streamJobRes",
|
|
1250
|
+
reqId,
|
|
1251
|
+
error: true,
|
|
1252
|
+
result: { message: "Job not found" },
|
|
1253
|
+
}));
|
|
1254
|
+
reader.cancel();
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
// Send stream update if there's new stream data
|
|
1258
|
+
if (update.new_result_stream !== undefined) {
|
|
1259
|
+
ws.send(JSON.stringify({
|
|
1260
|
+
type: "streamJobUpdate",
|
|
1261
|
+
reqId,
|
|
1262
|
+
new_result_stream: update.new_result_stream,
|
|
1263
|
+
stream_offset: update.stream_offset,
|
|
1264
|
+
}));
|
|
1265
|
+
}
|
|
1266
|
+
// Check if job is completed
|
|
1267
|
+
if (update.completed) {
|
|
1268
|
+
ws.send(JSON.stringify({
|
|
1269
|
+
type: "streamJobRes",
|
|
1270
|
+
reqId,
|
|
1271
|
+
error: false,
|
|
1272
|
+
result: update.only_result,
|
|
1273
|
+
}));
|
|
1274
|
+
reader.cancel();
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
catch (parseErr) {
|
|
1279
|
+
log.warn(`Failed to parse SSE data: ${parseErr}`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
finally {
|
|
1286
|
+
reader.releaseLock();
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
@@ -20,7 +20,27 @@ function initWebSocket() {
|
|
|
20
20
|
|
|
21
21
|
ws.onmessage = (event) => {
|
|
22
22
|
const data = JSON.parse(event.data)
|
|
23
|
-
if (data.type === '
|
|
23
|
+
if (data.type === 'streamJobUpdate') {
|
|
24
|
+
// Handle streaming update
|
|
25
|
+
const job = reqs[data.reqId]
|
|
26
|
+
if (job && job.onUpdate) {
|
|
27
|
+
job.onUpdate({
|
|
28
|
+
new_result_stream: data.new_result_stream,
|
|
29
|
+
stream_offset: data.stream_offset
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
} else if (data.type === 'streamJobRes') {
|
|
33
|
+
// Handle stream completion
|
|
34
|
+
const job = reqs[data.reqId]
|
|
35
|
+
if (job) {
|
|
36
|
+
if (data.error) {
|
|
37
|
+
job.reject(new Error(data.result?.stack ?? data.result?.message ?? 'Stream error'))
|
|
38
|
+
} else {
|
|
39
|
+
job.resolve(data.result)
|
|
40
|
+
}
|
|
41
|
+
delete reqs[data.reqId]
|
|
42
|
+
}
|
|
43
|
+
} else if (data.type === 'backendRes' || data.type === 'backendAsyncRes') {
|
|
24
44
|
console.log('Message from WebSocket backend', data)
|
|
25
45
|
const job = reqs[data.reqId]
|
|
26
46
|
if (job) {
|
|
@@ -85,5 +105,24 @@ export function waitJob(jobId: string) {
|
|
|
85
105
|
export function getJob(jobId: string) {
|
|
86
106
|
return doRequest('getJob', { jobId })
|
|
87
107
|
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Stream job results using SSE. Calls onUpdate for each stream update,
|
|
111
|
+
* and resolves with the final result when the job completes.
|
|
112
|
+
* @param jobId - The job ID to stream
|
|
113
|
+
* @param onUpdate - Callback for stream updates with new_result_stream data
|
|
114
|
+
* @returns Promise that resolves with the final job result
|
|
115
|
+
*/
|
|
116
|
+
export function streamJob(
|
|
117
|
+
jobId: string,
|
|
118
|
+
onUpdate?: (data: { new_result_stream?: string; stream_offset?: number }) => void
|
|
119
|
+
): Promise<any> {
|
|
120
|
+
return new Promise(async (resolve, reject) => {
|
|
121
|
+
await wsReady
|
|
122
|
+
const reqId = Math.random().toString(36)
|
|
123
|
+
reqs[reqId] = { resolve, reject, onUpdate }
|
|
124
|
+
ws?.send(JSON.stringify({ jobId, type: 'streamJob', reqId }))
|
|
125
|
+
})
|
|
126
|
+
}
|
|
88
127
|
`;
|
|
89
128
|
}
|
|
@@ -172,6 +172,7 @@ export async function handleFile(path, workspace, alreadySynced, message, opts,
|
|
|
172
172
|
continue;
|
|
173
173
|
}
|
|
174
174
|
log.info(`Adding file: ${file.path.substring(1)}`);
|
|
175
|
+
// deno-lint-ignore no-explicit-any
|
|
175
176
|
const fil = new File([file.contents], file.path.substring(1));
|
|
176
177
|
tarball.append(fil);
|
|
177
178
|
}
|
|
@@ -350,7 +351,7 @@ async function streamToBlob(stream) {
|
|
|
350
351
|
// Push the chunk to the array
|
|
351
352
|
chunks.push(value);
|
|
352
353
|
}
|
|
353
|
-
//
|
|
354
|
+
// deno-lint-ignore no-explicit-any
|
|
354
355
|
const blob = new Blob(chunks);
|
|
355
356
|
return blob;
|
|
356
357
|
}
|
|
@@ -10,7 +10,7 @@ import { handleFile } from "../script/script.js";
|
|
|
10
10
|
import { deepEqual, fetchRemoteVersion, isFileResource, isRawAppFile, isWorkspaceDependencies, } from "../../utils/utils.js";
|
|
11
11
|
import { getEffectiveSettings, mergeConfigWithConfigFile, validateBranchConfiguration, } from "../../core/conf.js";
|
|
12
12
|
import { fromBranchSpecificPath, getBranchSpecificPath, getSpecificItemsForCurrentBranch, isBranchSpecificFile, isCurrentBranchFile, isSpecificItem, } from "../../core/specific_items.js";
|
|
13
|
-
import { getCurrentGitBranch } from "../../utils/git.js";
|
|
13
|
+
import { getCurrentGitBranch, isGitRepository } from "../../utils/git.js";
|
|
14
14
|
import { removePathPrefix } from "../../types.js";
|
|
15
15
|
import { listSyncCodebases } from "../../utils/codebase.js";
|
|
16
16
|
import { generateScriptMetadataInternal, getRawWorkspaceDependencies, readLockfile, workspaceDependenciesPathToLanguageAndFilename, } from "../../utils/metadata.js";
|
|
@@ -27,8 +27,8 @@ function mergeCliWithEffectiveOptions(cliOpts, effectiveOpts) {
|
|
|
27
27
|
return Object.assign({}, effectiveOpts, cliOpts);
|
|
28
28
|
}
|
|
29
29
|
// Resolve effective sync options using branch-based configuration
|
|
30
|
-
async function resolveEffectiveSyncOptions(workspace, localConfig, promotion) {
|
|
31
|
-
return await getEffectiveSettings(localConfig, promotion);
|
|
30
|
+
async function resolveEffectiveSyncOptions(workspace, localConfig, promotion, branchOverride) {
|
|
31
|
+
return await getEffectiveSettings(localConfig, promotion, false, false, branchOverride);
|
|
32
32
|
}
|
|
33
33
|
export function findCodebase(path, codebases) {
|
|
34
34
|
if (!path.endsWith(".ts")) {
|
|
@@ -1014,7 +1014,7 @@ export async function* readDirRecursiveWithIgnore(ignore, root) {
|
|
|
1014
1014
|
}
|
|
1015
1015
|
}
|
|
1016
1016
|
}
|
|
1017
|
-
export async function elementsToMap(els, ignore, json, skips, specificItems) {
|
|
1017
|
+
export async function elementsToMap(els, ignore, json, skips, specificItems, branchOverride) {
|
|
1018
1018
|
const map = {};
|
|
1019
1019
|
const processedBasePaths = new Set();
|
|
1020
1020
|
for await (const entry of readDirRecursiveWithIgnore(ignore, els)) {
|
|
@@ -1115,8 +1115,7 @@ export async function elementsToMap(els, ignore, json, skips, specificItems) {
|
|
|
1115
1115
|
}
|
|
1116
1116
|
// Handle branch-specific files - skip files for other branches
|
|
1117
1117
|
if (specificItems && isBranchSpecificFile(path)) {
|
|
1118
|
-
|
|
1119
|
-
if (!currentBranch || !isCurrentBranchFile(path)) {
|
|
1118
|
+
if (!isCurrentBranchFile(path, branchOverride)) {
|
|
1120
1119
|
// Skip branch-specific files for other branches
|
|
1121
1120
|
continue;
|
|
1122
1121
|
}
|
|
@@ -1153,9 +1152,10 @@ export async function elementsToMap(els, ignore, json, skips, specificItems) {
|
|
|
1153
1152
|
}
|
|
1154
1153
|
// Handle branch-specific path mapping after all filtering
|
|
1155
1154
|
if (specificItems) {
|
|
1156
|
-
|
|
1157
|
-
if (currentBranch && isCurrentBranchFile(path)) {
|
|
1155
|
+
if (isCurrentBranchFile(path, branchOverride)) {
|
|
1158
1156
|
// This is a branch-specific file for current branch
|
|
1157
|
+
// Safe to compute branch here since isCurrentBranchFile already validated it exists
|
|
1158
|
+
const currentBranch = branchOverride || getCurrentGitBranch();
|
|
1159
1159
|
const basePath = fromBranchSpecificPath(path, currentBranch);
|
|
1160
1160
|
if (isSpecificItem(basePath, specificItems)) {
|
|
1161
1161
|
// Map to base path for push operations
|
|
@@ -1183,13 +1183,13 @@ export async function elementsToMap(els, ignore, json, skips, specificItems) {
|
|
|
1183
1183
|
}
|
|
1184
1184
|
return map;
|
|
1185
1185
|
}
|
|
1186
|
-
async function compareDynFSElement(els1, els2, ignore, json, skips, ignoreMetadataDeletion, codebases, ignoreCodebaseChanges, specificItems) {
|
|
1186
|
+
async function compareDynFSElement(els1, els2, ignore, json, skips, ignoreMetadataDeletion, codebases, ignoreCodebaseChanges, specificItems, branchOverride) {
|
|
1187
1187
|
const [m1, m2] = els2
|
|
1188
1188
|
? await Promise.all([
|
|
1189
|
-
elementsToMap(els1, ignore, json, skips, specificItems),
|
|
1190
|
-
elementsToMap(els2, ignore, json, skips, specificItems),
|
|
1189
|
+
elementsToMap(els1, ignore, json, skips, specificItems, branchOverride),
|
|
1190
|
+
elementsToMap(els2, ignore, json, skips, specificItems, branchOverride),
|
|
1191
1191
|
])
|
|
1192
|
-
: [await elementsToMap(els1, ignore, json, skips, specificItems), {}];
|
|
1192
|
+
: [await elementsToMap(els1, ignore, json, skips, specificItems, branchOverride), {}];
|
|
1193
1193
|
const changes = [];
|
|
1194
1194
|
function parseYaml(k, v) {
|
|
1195
1195
|
if (k.endsWith(".script.yaml")) {
|
|
@@ -1573,12 +1573,12 @@ export async function pull(opts) {
|
|
|
1573
1573
|
if (opts.stateful) {
|
|
1574
1574
|
await ensureDir(path.join(dntShim.Deno.cwd(), ".wmill"));
|
|
1575
1575
|
}
|
|
1576
|
-
const workspace = await resolveWorkspace(opts);
|
|
1576
|
+
const workspace = await resolveWorkspace(opts, opts.branch);
|
|
1577
1577
|
await requireLogin(opts);
|
|
1578
1578
|
// Resolve effective sync options with branch awareness
|
|
1579
|
-
const effectiveOpts = await resolveEffectiveSyncOptions(workspace, opts, opts.promotion);
|
|
1579
|
+
const effectiveOpts = await resolveEffectiveSyncOptions(workspace, opts, opts.promotion, opts.branch);
|
|
1580
1580
|
// Extract specific items configuration before merging overwrites gitBranches
|
|
1581
|
-
const specificItems = getSpecificItemsForCurrentBranch(opts);
|
|
1581
|
+
const specificItems = getSpecificItemsForCurrentBranch(opts, opts.branch);
|
|
1582
1582
|
// Merge CLI flags with resolved settings (CLI flags take precedence only for explicit overrides)
|
|
1583
1583
|
opts = mergeCliWithEffectiveOptions(originalCliOpts, effectiveOpts);
|
|
1584
1584
|
const codebases = await listSyncCodebases(opts);
|
|
@@ -1597,7 +1597,7 @@ export async function pull(opts) {
|
|
|
1597
1597
|
const local = !opts.stateful
|
|
1598
1598
|
? await FSFSElement(dntShim.Deno.cwd(), codebases, true)
|
|
1599
1599
|
: await FSFSElement(path.join(dntShim.Deno.cwd(), ".wmill"), [], true);
|
|
1600
|
-
const changes = await compareDynFSElement(remote, local, await ignoreF(opts), opts.json ?? false, opts, false, codebases, true, specificItems);
|
|
1600
|
+
const changes = await compareDynFSElement(remote, local, await ignoreF(opts), opts.json ?? false, opts, false, codebases, true, specificItems, opts.branch);
|
|
1601
1601
|
log.info(`remote (${workspace.name}) -> local: ${changes.length} changes to apply`);
|
|
1602
1602
|
// Handle JSON output for dry-run
|
|
1603
1603
|
if (opts.dryRun && opts.jsonOutput) {
|
|
@@ -1612,7 +1612,7 @@ export async function pull(opts) {
|
|
|
1612
1612
|
...(specificItems && isSpecificItem(change.path, specificItems)
|
|
1613
1613
|
? {
|
|
1614
1614
|
branch_specific: true,
|
|
1615
|
-
branch_specific_path: getBranchSpecificPath(change.path, specificItems),
|
|
1615
|
+
branch_specific_path: getBranchSpecificPath(change.path, specificItems, opts.branch),
|
|
1616
1616
|
}
|
|
1617
1617
|
: {}),
|
|
1618
1618
|
})),
|
|
@@ -1623,7 +1623,7 @@ export async function pull(opts) {
|
|
|
1623
1623
|
}
|
|
1624
1624
|
if (changes.length > 0) {
|
|
1625
1625
|
if (!opts.jsonOutput) {
|
|
1626
|
-
prettyChanges(changes, specificItems);
|
|
1626
|
+
prettyChanges(changes, specificItems, opts.branch);
|
|
1627
1627
|
}
|
|
1628
1628
|
if (opts.dryRun) {
|
|
1629
1629
|
log.info(colors.gray(`Dry run complete.`));
|
|
@@ -1642,7 +1642,7 @@ export async function pull(opts) {
|
|
|
1642
1642
|
// Determine if this file should be written to a branch-specific path
|
|
1643
1643
|
let targetPath = change.path;
|
|
1644
1644
|
if (specificItems && isSpecificItem(change.path, specificItems)) {
|
|
1645
|
-
const branchSpecificPath = getBranchSpecificPath(change.path, specificItems);
|
|
1645
|
+
const branchSpecificPath = getBranchSpecificPath(change.path, specificItems, opts.branch);
|
|
1646
1646
|
if (branchSpecificPath) {
|
|
1647
1647
|
targetPath = branchSpecificPath;
|
|
1648
1648
|
}
|
|
@@ -1776,7 +1776,7 @@ export async function pull(opts) {
|
|
|
1776
1776
|
...(specificItems && isSpecificItem(change.path, specificItems)
|
|
1777
1777
|
? {
|
|
1778
1778
|
branch_specific: true,
|
|
1779
|
-
branch_specific_path: getBranchSpecificPath(change.path, specificItems),
|
|
1779
|
+
branch_specific_path: getBranchSpecificPath(change.path, specificItems, opts.branch),
|
|
1780
1780
|
}
|
|
1781
1781
|
: {}),
|
|
1782
1782
|
})),
|
|
@@ -1792,13 +1792,13 @@ export async function pull(opts) {
|
|
|
1792
1792
|
console.log(JSON.stringify({ success: true, message: "No changes to apply", total: 0 }, null, 2));
|
|
1793
1793
|
}
|
|
1794
1794
|
}
|
|
1795
|
-
function prettyChanges(changes, specificItems) {
|
|
1795
|
+
function prettyChanges(changes, specificItems, branchOverride) {
|
|
1796
1796
|
for (const change of changes) {
|
|
1797
1797
|
let displayPath = change.path;
|
|
1798
1798
|
let branchNote = "";
|
|
1799
1799
|
// Check if this will be written as a branch-specific file
|
|
1800
1800
|
if (specificItems && isSpecificItem(change.path, specificItems)) {
|
|
1801
|
-
const branchSpecificPath = getBranchSpecificPath(change.path, specificItems);
|
|
1801
|
+
const branchSpecificPath = getBranchSpecificPath(change.path, specificItems, branchOverride);
|
|
1802
1802
|
if (branchSpecificPath) {
|
|
1803
1803
|
displayPath = branchSpecificPath;
|
|
1804
1804
|
branchNote = " (branch-specific)";
|
|
@@ -1875,12 +1875,12 @@ export async function push(opts) {
|
|
|
1875
1875
|
}
|
|
1876
1876
|
throw error;
|
|
1877
1877
|
}
|
|
1878
|
-
const workspace = await resolveWorkspace(opts);
|
|
1878
|
+
const workspace = await resolveWorkspace(opts, opts.branch);
|
|
1879
1879
|
await requireLogin(opts);
|
|
1880
1880
|
// Resolve effective sync options with branch awareness
|
|
1881
|
-
const effectiveOpts = await resolveEffectiveSyncOptions(workspace, opts, opts.promotion);
|
|
1881
|
+
const effectiveOpts = await resolveEffectiveSyncOptions(workspace, opts, opts.promotion, opts.branch);
|
|
1882
1882
|
// Extract specific items configuration BEFORE merging overwrites gitBranches
|
|
1883
|
-
const specificItems = getSpecificItemsForCurrentBranch(opts);
|
|
1883
|
+
const specificItems = getSpecificItemsForCurrentBranch(opts, opts.branch);
|
|
1884
1884
|
// Merge CLI flags with resolved settings (CLI flags take precedence only for explicit overrides)
|
|
1885
1885
|
opts = mergeCliWithEffectiveOptions(originalCliOpts, effectiveOpts);
|
|
1886
1886
|
const codebases = await listSyncCodebases(opts);
|
|
@@ -1907,7 +1907,7 @@ export async function push(opts) {
|
|
|
1907
1907
|
}
|
|
1908
1908
|
const remote = ZipFSElement((await downloadZip(workspace, opts.plainSecrets, opts.skipVariables, opts.skipResources, opts.skipResourceTypes, opts.skipSecrets, opts.includeSchedules, opts.includeTriggers, opts.includeUsers, opts.includeGroups, opts.includeSettings, opts.includeKey, opts.skipWorkspaceDependencies, opts.defaultTs)), !opts.json, opts.defaultTs ?? "bun", resourceTypeToFormatExtension, false);
|
|
1909
1909
|
const local = await FSFSElement(path.join(dntShim.Deno.cwd(), ""), codebases, false);
|
|
1910
|
-
const changes = await compareDynFSElement(local, remote, await ignoreF(opts), opts.json ?? false, opts, true, codebases, false, specificItems);
|
|
1910
|
+
const changes = await compareDynFSElement(local, remote, await ignoreF(opts), opts.json ?? false, opts, true, codebases, false, specificItems, opts.branch);
|
|
1911
1911
|
const rawWorkspaceDependencies = await getRawWorkspaceDependencies();
|
|
1912
1912
|
const tracker = await buildTracker(changes);
|
|
1913
1913
|
const staleScripts = [];
|
|
@@ -1974,7 +1974,7 @@ export async function push(opts) {
|
|
|
1974
1974
|
...(specificItems && isSpecificItem(change.path, specificItems)
|
|
1975
1975
|
? {
|
|
1976
1976
|
branch_specific: true,
|
|
1977
|
-
branch_specific_path: getBranchSpecificPath(change.path, specificItems),
|
|
1977
|
+
branch_specific_path: getBranchSpecificPath(change.path, specificItems, opts.branch),
|
|
1978
1978
|
}
|
|
1979
1979
|
: {}),
|
|
1980
1980
|
})),
|
|
@@ -1985,7 +1985,7 @@ export async function push(opts) {
|
|
|
1985
1985
|
}
|
|
1986
1986
|
if (changes.length > 0) {
|
|
1987
1987
|
if (!opts.jsonOutput) {
|
|
1988
|
-
prettyChanges(changes, specificItems);
|
|
1988
|
+
prettyChanges(changes, specificItems, opts.branch);
|
|
1989
1989
|
}
|
|
1990
1990
|
if (opts.dryRun) {
|
|
1991
1991
|
log.info(colors.gray(`Dry run complete.`));
|
|
@@ -2083,7 +2083,7 @@ export async function push(opts) {
|
|
|
2083
2083
|
// For branch-specific resources, push to the base path on the workspace server
|
|
2084
2084
|
// This ensures branch-specific files are stored with their base names in the workspace
|
|
2085
2085
|
let serverPath = resourceFilePath;
|
|
2086
|
-
const currentBranch = getCurrentGitBranch();
|
|
2086
|
+
const currentBranch = opts.branch || (isGitRepository() ? getCurrentGitBranch() : null);
|
|
2087
2087
|
if (currentBranch && isBranchSpecificFile(resourceFilePath)) {
|
|
2088
2088
|
serverPath = fromBranchSpecificPath(resourceFilePath, currentBranch);
|
|
2089
2089
|
}
|
|
@@ -2099,7 +2099,7 @@ export async function push(opts) {
|
|
|
2099
2099
|
// Check if this is a branch-specific item and get the original branch-specific path
|
|
2100
2100
|
let originalBranchSpecificPath;
|
|
2101
2101
|
if (specificItems && isSpecificItem(change.path, specificItems)) {
|
|
2102
|
-
originalBranchSpecificPath = getBranchSpecificPath(change.path, specificItems);
|
|
2102
|
+
originalBranchSpecificPath = getBranchSpecificPath(change.path, specificItems, opts.branch);
|
|
2103
2103
|
}
|
|
2104
2104
|
await pushObj(workspace.workspaceId, change.path, oldObj, newObj, opts.plainSecrets ?? false, alreadySynced, opts.message, originalBranchSpecificPath);
|
|
2105
2105
|
if (stateTarget) {
|
|
@@ -2125,7 +2125,7 @@ export async function push(opts) {
|
|
|
2125
2125
|
// For branch-specific items, we read from branch-specific files but push to base server paths
|
|
2126
2126
|
let localFilePath = change.path;
|
|
2127
2127
|
if (specificItems && isSpecificItem(change.path, specificItems)) {
|
|
2128
|
-
const branchSpecificPath = getBranchSpecificPath(change.path, specificItems);
|
|
2128
|
+
const branchSpecificPath = getBranchSpecificPath(change.path, specificItems, opts.branch);
|
|
2129
2129
|
if (branchSpecificPath) {
|
|
2130
2130
|
localFilePath = branchSpecificPath;
|
|
2131
2131
|
}
|
|
@@ -2330,7 +2330,7 @@ export async function push(opts) {
|
|
|
2330
2330
|
...(specificItems && isSpecificItem(change.path, specificItems)
|
|
2331
2331
|
? {
|
|
2332
2332
|
branch_specific: true,
|
|
2333
|
-
branch_specific_path: getBranchSpecificPath(change.path, specificItems),
|
|
2333
|
+
branch_specific_path: getBranchSpecificPath(change.path, specificItems, opts.branch),
|
|
2334
2334
|
}
|
|
2335
2335
|
: {}),
|
|
2336
2336
|
})),
|
|
@@ -2379,6 +2379,7 @@ const command = new Command()
|
|
|
2379
2379
|
.option("--extra-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). Useful to still take wmill.yaml into account and act as a second pattern to satisfy")
|
|
2380
2380
|
.option("--repository <repo:string>", "Specify repository path (e.g., u/user/repo) when multiple repositories exist")
|
|
2381
2381
|
.option("--promotion <branch:string>", "Use promotionOverrides from the specified branch instead of regular overrides")
|
|
2382
|
+
.option("--branch <branch:string>", "Override the current git branch (works even outside a git repository)")
|
|
2382
2383
|
// deno-lint-ignore no-explicit-any
|
|
2383
2384
|
.action(pull)
|
|
2384
2385
|
.command("push")
|
|
@@ -2411,6 +2412,7 @@ const command = new Command()
|
|
|
2411
2412
|
.option("--message <message:string>", "Include a message that will be added to all scripts/flows/apps updated during this push")
|
|
2412
2413
|
.option("--parallel <number>", "Number of changes to process in parallel")
|
|
2413
2414
|
.option("--repository <repo:string>", "Specify repository path (e.g., u/user/repo) when multiple repositories exist")
|
|
2415
|
+
.option("--branch <branch:string>", "Override the current git branch (works even outside a git repository)")
|
|
2414
2416
|
// deno-lint-ignore no-explicit-any
|
|
2415
2417
|
.action(push);
|
|
2416
2418
|
export default command;
|