windmill-cli 1.589.0 → 1.589.2
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/src/commands/app/app_metadata.js +224 -33
- package/esm/src/commands/app/apps.js +18 -14
- package/esm/src/commands/app/dev.js +10 -8
- package/esm/src/commands/app/raw_apps.js +7 -59
- package/esm/src/commands/flow/flow_metadata.js +5 -2
- package/esm/src/commands/init/init.js +5 -7
- package/esm/src/commands/sync/sync.js +29 -10
- package/esm/src/commands/trigger/trigger.js +3 -7
- package/esm/src/main.js +2 -2
- package/esm/src/utils/utils.js +9 -0
- package/package.json +1 -1
- package/types/gen/types.gen.d.ts +2 -0
- package/types/gen/types.gen.d.ts.map +1 -1
- package/types/src/commands/app/app_metadata.d.ts +8 -1
- package/types/src/commands/app/app_metadata.d.ts.map +1 -1
- package/types/src/commands/app/apps.d.ts +1 -1
- package/types/src/commands/app/apps.d.ts.map +1 -1
- package/types/src/commands/app/dev.d.ts.map +1 -1
- package/types/src/commands/app/raw_apps.d.ts +0 -7
- package/types/src/commands/app/raw_apps.d.ts.map +1 -1
- package/types/src/commands/flow/flow_metadata.d.ts.map +1 -1
- package/types/src/commands/init/init.d.ts.map +1 -1
- package/types/src/commands/sync/sync.d.ts.map +1 -1
- package/types/src/commands/trigger/trigger.d.ts.map +1 -1
- package/types/src/main.d.ts +1 -1
- package/types/src/main.d.ts.map +1 -1
- package/types/src/utils/utils.d.ts +1 -0
- package/types/src/utils/utils.d.ts.map +1 -1
- package/types/windmill-utils-internal/src/gen/types.gen.d.ts +2 -0
- package/types/windmill-utils-internal/src/gen/types.gen.d.ts.map +1 -1
package/esm/gen/core/OpenAPI.js
CHANGED
|
@@ -3,35 +3,35 @@ import * as dntShim from "../../../_dnt.shims.js";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { SEP, colors, log, yamlParseFile, yamlStringify, } from "../../../deps.js";
|
|
5
5
|
import { checkifMetadataUptodate, blueColor, clearGlobalLock, updateMetadataGlobalLock, inferSchema, getRawWorkspaceDependencies, } from "../../utils/metadata.js";
|
|
6
|
-
import { workspaceDependenciesLanguages } from "../../utils/script_common.js";
|
|
7
|
-
import { inferContentTypeFromFilePath, } from "../../utils/script_common.js";
|
|
6
|
+
import { workspaceDependenciesLanguages, } from "../../utils/script_common.js";
|
|
8
7
|
import { generateHash, getHeaders, writeIfChanged } from "../../utils/utils.js";
|
|
9
8
|
import { exts } from "../script/script.js";
|
|
10
9
|
import { FSFSElement, yamlOptions } from "../sync/sync.js";
|
|
11
10
|
import { replaceInlineScripts } from "./apps.js";
|
|
12
11
|
import { newPathAssigner, } from "../../../windmill-utils-internal/src/path-utils/path-assigner.js";
|
|
12
|
+
import { mergeConfigWithConfigFile } from "../../core/conf.js";
|
|
13
|
+
import { resolveWorkspace } from "../../core/context.js";
|
|
14
|
+
import { requireLogin } from "../../core/auth.js";
|
|
13
15
|
const TOP_HASH = "__app_hash";
|
|
16
|
+
export const APP_BACKEND_FOLDER = "backend";
|
|
14
17
|
/**
|
|
15
18
|
* Generates a hash for all inline scripts in an app directory
|
|
16
19
|
*/
|
|
17
|
-
async function generateAppHash(rawReqs, folder, defaultTs) {
|
|
18
|
-
const runnablesFolder =
|
|
20
|
+
async function generateAppHash(rawReqs, folder, rawApp, defaultTs) {
|
|
21
|
+
const runnablesFolder = rawApp
|
|
22
|
+
? path.join(folder, APP_BACKEND_FOLDER)
|
|
23
|
+
: folder;
|
|
19
24
|
const hashes = {};
|
|
20
25
|
try {
|
|
21
26
|
const elems = await FSFSElement(runnablesFolder, [], true);
|
|
22
27
|
for await (const f of elems.getChildren()) {
|
|
28
|
+
if (!rawApp && !f.path.includes(".inline_script.")) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
23
31
|
if (exts.some((e) => f.path.endsWith(e))) {
|
|
24
|
-
let reqs;
|
|
25
|
-
if (rawReqs) {
|
|
26
|
-
// Get language name from path
|
|
27
|
-
const lang = inferContentTypeFromFilePath(f.path, defaultTs);
|
|
28
|
-
// Get lock for that language
|
|
29
|
-
[, reqs] =
|
|
30
|
-
Object.entries(rawReqs).find(([lang2, _]) => lang == lang2) ?? [];
|
|
31
|
-
}
|
|
32
32
|
// Embed lock into hash
|
|
33
33
|
const relativePath = f.path.replace(runnablesFolder + SEP, "");
|
|
34
|
-
hashes[relativePath] = await generateHash((await f.getContentText()) + (
|
|
34
|
+
hashes[relativePath] = await generateHash((await f.getContentText()) + JSON.stringify(rawReqs));
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -46,7 +46,7 @@ async function generateAppHash(rawReqs, folder, defaultTs) {
|
|
|
46
46
|
/**
|
|
47
47
|
* Updates locks for inline scripts in an app
|
|
48
48
|
*/
|
|
49
|
-
export async function generateAppLocksInternal(appFolder, dryRun, workspace, opts, justUpdateMetadataLock, noStaleMessage) {
|
|
49
|
+
export async function generateAppLocksInternal(appFolder, rawApp, dryRun, workspace, opts, justUpdateMetadataLock, noStaleMessage) {
|
|
50
50
|
if (appFolder.endsWith(SEP)) {
|
|
51
51
|
appFolder = appFolder.substring(0, appFolder.length - 1);
|
|
52
52
|
}
|
|
@@ -55,7 +55,7 @@ export async function generateAppLocksInternal(appFolder, dryRun, workspace, opt
|
|
|
55
55
|
log.info(`Generating locks for app ${appFolder} at ${remote_path}`);
|
|
56
56
|
}
|
|
57
57
|
const rawWorkspaceDependencies = await getRawWorkspaceDependencies();
|
|
58
|
-
let hashes = await generateAppHash(rawWorkspaceDependencies, appFolder, opts.defaultTs);
|
|
58
|
+
let hashes = await generateAppHash(rawWorkspaceDependencies, appFolder, rawApp, opts.defaultTs);
|
|
59
59
|
const conf = await import("../../utils/metadata.js").then((m) => m.readLockfile());
|
|
60
60
|
if (await checkifMetadataUptodate(appFolder, hashes[TOP_HASH], conf, TOP_HASH)) {
|
|
61
61
|
if (!noStaleMessage) {
|
|
@@ -72,7 +72,7 @@ export async function generateAppLocksInternal(appFolder, dryRun, workspace, opt
|
|
|
72
72
|
.join("/")}) for ${appFolder}, using them`));
|
|
73
73
|
}
|
|
74
74
|
// Read the app file
|
|
75
|
-
const appFilePath = path.join(appFolder, "raw_app.yaml");
|
|
75
|
+
const appFilePath = path.join(appFolder, rawApp ? "raw_app.yaml" : "app.yaml");
|
|
76
76
|
const appFile = (await yamlParseFile(appFilePath));
|
|
77
77
|
if (!justUpdateMetadataLock) {
|
|
78
78
|
const changedScripts = [];
|
|
@@ -87,11 +87,21 @@ export async function generateAppLocksInternal(appFolder, dryRun, workspace, opt
|
|
|
87
87
|
}
|
|
88
88
|
if (changedScripts.length > 0) {
|
|
89
89
|
log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${appFolder}`);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
if (rawApp) {
|
|
91
|
+
const runnablesPath = path.join(appFolder, APP_BACKEND_FOLDER) + SEP;
|
|
92
|
+
const rawAppFile = appFile;
|
|
93
|
+
// Replace inline scripts for changed runnables
|
|
94
|
+
replaceInlineScripts(rawAppFile.runnables, runnablesPath, false);
|
|
95
|
+
// Update the app runnables with new locks
|
|
96
|
+
rawAppFile.runnables = await updateRawAppRunnables(workspace, rawAppFile.runnables, remote_path, appFolder, rawWorkspaceDependencies, opts.defaultTs);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const normalAppFile = appFile;
|
|
100
|
+
// Replace inline scripts for normal apps
|
|
101
|
+
replaceInlineScripts(normalAppFile.value, appFolder + SEP, false);
|
|
102
|
+
// Update the app value with new locks
|
|
103
|
+
normalAppFile.value = await updateAppInlineScripts(workspace, normalAppFile.value, remote_path, appFolder, rawWorkspaceDependencies, opts.defaultTs);
|
|
104
|
+
}
|
|
95
105
|
// Write the updated app file
|
|
96
106
|
writeIfChanged(appFilePath, yamlStringify(appFile, yamlOptions));
|
|
97
107
|
}
|
|
@@ -100,7 +110,7 @@ export async function generateAppLocksInternal(appFolder, dryRun, workspace, opt
|
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
112
|
// Regenerate hashes after updates
|
|
103
|
-
hashes = await generateAppHash(rawWorkspaceDependencies, appFolder, opts.defaultTs);
|
|
113
|
+
hashes = await generateAppHash(rawWorkspaceDependencies, appFolder, rawApp, opts.defaultTs);
|
|
104
114
|
await clearGlobalLock(appFolder);
|
|
105
115
|
for (const [scriptPath, hash] of Object.entries(hashes)) {
|
|
106
116
|
await updateMetadataGlobalLock(appFolder, hash, scriptPath);
|
|
@@ -108,12 +118,45 @@ export async function generateAppLocksInternal(appFolder, dryRun, workspace, opt
|
|
|
108
118
|
log.info(colors.green(`App ${remote_path} lockfiles updated`));
|
|
109
119
|
}
|
|
110
120
|
/**
|
|
111
|
-
*
|
|
121
|
+
* Traverses an app structure (either app.value for normal apps or app.runnables for raw apps)
|
|
122
|
+
* and processes all inline scripts found, returning the updated structure
|
|
123
|
+
*/
|
|
124
|
+
async function traverseAndProcessInlineScripts(obj, processor, currentPath = []) {
|
|
125
|
+
if (!obj || typeof obj !== "object") {
|
|
126
|
+
return obj;
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(obj)) {
|
|
129
|
+
return await Promise.all(obj.map((item, index) => traverseAndProcessInlineScripts(item, processor, [
|
|
130
|
+
...currentPath,
|
|
131
|
+
`[${index}]`,
|
|
132
|
+
])));
|
|
133
|
+
}
|
|
134
|
+
const result = {};
|
|
135
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
136
|
+
if (key === "inlineScript" && typeof value === "object") {
|
|
137
|
+
// Found an inline script - process it
|
|
138
|
+
result[key] = await processor(value, {
|
|
139
|
+
path: currentPath,
|
|
140
|
+
parentKey: key,
|
|
141
|
+
parentObject: obj,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Recursively process nested objects
|
|
146
|
+
result[key] = await traverseAndProcessInlineScripts(value, processor, [
|
|
147
|
+
...currentPath,
|
|
148
|
+
key,
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Updates locks for all runnables in a raw app, generating locks inline script by inline script
|
|
112
156
|
* Also writes content and locks back to the runnables folder
|
|
113
157
|
*/
|
|
114
|
-
async function
|
|
115
|
-
const
|
|
116
|
-
const runnablesFolder = path.join(appFolder, "runnables");
|
|
158
|
+
async function updateRawAppRunnables(workspace, runnables, remotePath, appFolder, rawDeps, defaultTs = "bun") {
|
|
159
|
+
const runnablesFolder = path.join(appFolder, APP_BACKEND_FOLDER);
|
|
117
160
|
// Ensure runnables folder exists
|
|
118
161
|
try {
|
|
119
162
|
await dntShim.Deno.mkdir(runnablesFolder, { recursive: true });
|
|
@@ -122,31 +165,36 @@ async function updateAppRunnables(workspace, runnables, remotePath, appFolder, r
|
|
|
122
165
|
// Folder may already exist
|
|
123
166
|
}
|
|
124
167
|
const pathAssigner = newPathAssigner(defaultTs);
|
|
168
|
+
// Process each runnable
|
|
169
|
+
const updatedRunnables = {};
|
|
125
170
|
for (const [runnableId, runnable] of Object.entries(runnables)) {
|
|
126
171
|
// Only process inline scripts (runnableByName with inlineScript)
|
|
127
172
|
if (runnable?.type !== "runnableByName" || !runnable?.inlineScript) {
|
|
173
|
+
updatedRunnables[runnableId] = runnable;
|
|
128
174
|
continue;
|
|
129
175
|
}
|
|
130
176
|
const inlineScript = runnable.inlineScript;
|
|
131
177
|
const language = inlineScript.language;
|
|
132
178
|
const content = inlineScript.content;
|
|
133
179
|
if (!content || !language) {
|
|
180
|
+
updatedRunnables[runnableId] = runnable;
|
|
134
181
|
continue;
|
|
135
182
|
}
|
|
136
183
|
// Skip if content is still an !inline reference (should have been replaced by replaceInlineScripts)
|
|
137
184
|
if (typeof content === "string" && content.startsWith("!inline ")) {
|
|
138
185
|
log.warn(colors.yellow(`Runnable ${runnableId} content is still an !inline reference, skipping`));
|
|
186
|
+
updatedRunnables[runnableId] = runnable;
|
|
139
187
|
continue;
|
|
140
188
|
}
|
|
141
189
|
// Skip frontend scripts - they don't need locks
|
|
142
190
|
if (language === "frontend") {
|
|
191
|
+
updatedRunnables[runnableId] = runnable;
|
|
143
192
|
continue;
|
|
144
193
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
log.info(colors.gray(`Generating lock for runnable ${runnableId} (${language})${langRawDeps ? " with raw deps" : ""}`));
|
|
194
|
+
log.info(colors.gray(`Generating lock for runnable ${runnableId} (${language})
|
|
195
|
+
}`));
|
|
148
196
|
try {
|
|
149
|
-
const lock = await generateInlineScriptLock(workspace, content, language, `${remotePath}/${runnableId}`,
|
|
197
|
+
const lock = await generateInlineScriptLock(workspace, content, language, `${remotePath}/${runnableId}`, rawDeps);
|
|
150
198
|
// Determine file extension for this language
|
|
151
199
|
const [basePathO, ext] = pathAssigner.assignPath(runnable.name, language);
|
|
152
200
|
const basePath = basePathO.replaceAll(SEP, "/");
|
|
@@ -174,14 +222,72 @@ async function updateAppRunnables(workspace, runnables, remotePath, appFolder, r
|
|
|
174
222
|
catch (error) {
|
|
175
223
|
log.error(colors.red(`Failed to generate lock for runnable ${runnableId}: ${error.message}`));
|
|
176
224
|
// Continue with other runnables even if one fails
|
|
225
|
+
updatedRunnables[runnableId] = runnable;
|
|
177
226
|
}
|
|
178
227
|
}
|
|
179
228
|
return updatedRunnables;
|
|
180
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Updates locks for all inline scripts in a normal app, similar to updateRawAppRunnables
|
|
232
|
+
* but for the app.value structure instead of app.runnables
|
|
233
|
+
*/
|
|
234
|
+
async function updateAppInlineScripts(workspace, appValue, remotePath, appFolder, rawDeps, defaultTs = "bun") {
|
|
235
|
+
const pathAssigner = newPathAssigner(defaultTs);
|
|
236
|
+
const processor = async (inlineScript, context) => {
|
|
237
|
+
const language = inlineScript.language;
|
|
238
|
+
const content = inlineScript.content;
|
|
239
|
+
if (!content || !language) {
|
|
240
|
+
return inlineScript;
|
|
241
|
+
}
|
|
242
|
+
// Skip if content is still an !inline reference (should have been replaced by replaceInlineScripts)
|
|
243
|
+
if (typeof content === "string" && content.startsWith("!inline ")) {
|
|
244
|
+
log.warn(colors.yellow(`Inline script at ${context.path.join(".")} is still an !inline reference, skipping`));
|
|
245
|
+
return inlineScript;
|
|
246
|
+
}
|
|
247
|
+
// Skip frontend scripts - they don't need locks
|
|
248
|
+
if (language === "frontend") {
|
|
249
|
+
return inlineScript;
|
|
250
|
+
}
|
|
251
|
+
// Get the name from the parent object (following extractInlineScriptsForApps pattern)
|
|
252
|
+
// For normal apps, the name is stored in the component's "name" property
|
|
253
|
+
const scriptName = context.parentObject?.["name"] || "unnamed";
|
|
254
|
+
const scriptPath = `${remotePath}/${context.path.join("/")}`;
|
|
255
|
+
log.info(colors.gray(`Generating lock for inline script "${scriptName}" at ${context.path.join(".")} (${language})`));
|
|
256
|
+
try {
|
|
257
|
+
const lock = await generateInlineScriptLock(workspace, content, language, scriptPath, rawDeps);
|
|
258
|
+
// Determine file extension for this language (following extractInlineScriptsForApps pattern)
|
|
259
|
+
const [basePathO, ext] = pathAssigner.assignPath(scriptName, language);
|
|
260
|
+
const basePath = basePathO.replaceAll(SEP, "/");
|
|
261
|
+
const contentPath = path.join(appFolder, `${basePath}${ext}`);
|
|
262
|
+
const lockPath = path.join(appFolder, `${basePath}lock`);
|
|
263
|
+
// Write content to file
|
|
264
|
+
writeIfChanged(contentPath, content);
|
|
265
|
+
// Write lock to file if it exists
|
|
266
|
+
if (lock && lock !== "") {
|
|
267
|
+
writeIfChanged(lockPath, lock);
|
|
268
|
+
}
|
|
269
|
+
// Update the inline script with !inline references
|
|
270
|
+
const inlineContentRef = `!inline ${basePath}${ext}`;
|
|
271
|
+
const inlineLockRef = lock && lock !== "" ? `!inline ${basePath}lock` : "";
|
|
272
|
+
log.info(colors.gray(` Written ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}`));
|
|
273
|
+
return {
|
|
274
|
+
...inlineScript,
|
|
275
|
+
content: inlineContentRef,
|
|
276
|
+
lock: inlineLockRef,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
log.error(colors.red(`Failed to generate lock for inline script at ${context.path.join(".")}: ${error.message}`));
|
|
281
|
+
// Return original on error
|
|
282
|
+
return inlineScript;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
return await traverseAndProcessInlineScripts(appValue, processor);
|
|
286
|
+
}
|
|
181
287
|
/**
|
|
182
288
|
* Generates a lock for a single inline script using the dependencies endpoint
|
|
183
289
|
*/
|
|
184
|
-
async function generateInlineScriptLock(workspace, content, language, scriptPath,
|
|
290
|
+
async function generateInlineScriptLock(workspace, content, language, scriptPath, rawWorkspaceDependencies) {
|
|
185
291
|
const extraHeaders = getHeaders();
|
|
186
292
|
const rawResponse = await fetch(`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/dependencies`, {
|
|
187
293
|
method: "POST",
|
|
@@ -198,7 +304,10 @@ async function generateInlineScriptLock(workspace, content, language, scriptPath
|
|
|
198
304
|
script_path: scriptPath,
|
|
199
305
|
},
|
|
200
306
|
],
|
|
201
|
-
|
|
307
|
+
raw_workspace_dependencies: rawWorkspaceDependencies &&
|
|
308
|
+
Object.keys(rawWorkspaceDependencies).length > 0
|
|
309
|
+
? rawWorkspaceDependencies
|
|
310
|
+
: null,
|
|
202
311
|
entrypoint: scriptPath,
|
|
203
312
|
}),
|
|
204
313
|
});
|
|
@@ -260,7 +369,7 @@ export async function inferRunnableSchemaFromFile(appFolder, runnableFilePath) {
|
|
|
260
369
|
const inlineScript = runnable.inlineScript;
|
|
261
370
|
const language = inlineScript.language;
|
|
262
371
|
// Read the actual content from the file
|
|
263
|
-
const fullFilePath = path.join(appFolder,
|
|
372
|
+
const fullFilePath = path.join(appFolder, APP_BACKEND_FOLDER, runnableFilePath);
|
|
264
373
|
let content;
|
|
265
374
|
try {
|
|
266
375
|
content = await dntShim.Deno.readTextFile(fullFilePath);
|
|
@@ -285,3 +394,85 @@ export async function inferRunnableSchemaFromFile(appFolder, runnableFilePath) {
|
|
|
285
394
|
return undefined;
|
|
286
395
|
}
|
|
287
396
|
}
|
|
397
|
+
function getAppFolders(elems, extension) {
|
|
398
|
+
return Object.keys(elems)
|
|
399
|
+
.filter((p) => p.endsWith(SEP + extension))
|
|
400
|
+
.map((p) => p.substring(0, p.length - (SEP + extension).length));
|
|
401
|
+
}
|
|
402
|
+
export async function generateLocksCommand(opts, appPath) {
|
|
403
|
+
const { generateAppLocksInternal } = await import("./app_metadata.js");
|
|
404
|
+
const { elementsToMap, FSFSElement } = await import("../sync/sync.js");
|
|
405
|
+
const { ignoreF } = await import("../sync/sync.js");
|
|
406
|
+
const { Confirm } = await import("../../../deps.js");
|
|
407
|
+
if (appPath == "") {
|
|
408
|
+
appPath = undefined;
|
|
409
|
+
}
|
|
410
|
+
const workspace = await resolveWorkspace(opts);
|
|
411
|
+
await requireLogin(opts);
|
|
412
|
+
opts = await mergeConfigWithConfigFile(opts);
|
|
413
|
+
if (appPath) {
|
|
414
|
+
//TODO: Generate metadata for a specific raw app but handle normal apps to
|
|
415
|
+
throw new Error("Not implemented");
|
|
416
|
+
// Generate metadata for a specific app
|
|
417
|
+
// await generateAppLocksInternal(
|
|
418
|
+
// appPath,
|
|
419
|
+
// true,
|
|
420
|
+
// false,
|
|
421
|
+
// workspace,
|
|
422
|
+
// opts,
|
|
423
|
+
// false,
|
|
424
|
+
// false
|
|
425
|
+
// );
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
// Generate metadata for all apps
|
|
429
|
+
const ignore = await ignoreF(opts);
|
|
430
|
+
const elems = await elementsToMap(await FSFSElement(dntShim.Deno.cwd(), [], true), (p, isD) => {
|
|
431
|
+
return (ignore(p, isD) ||
|
|
432
|
+
(!isD &&
|
|
433
|
+
!p.endsWith(SEP + "raw_app.yaml") &&
|
|
434
|
+
!p.endsWith(SEP + "app.yaml")));
|
|
435
|
+
}, false, {});
|
|
436
|
+
const rawAppFolders = getAppFolders(elems, "raw_app.yaml");
|
|
437
|
+
const appFolders = getAppFolders(elems, "app.yaml");
|
|
438
|
+
let hasAny = false;
|
|
439
|
+
log.info(`Checking metadata for all apps (${appFolders.length}) and raw apps (${rawAppFolders.length})`);
|
|
440
|
+
for (const appFolder of rawAppFolders) {
|
|
441
|
+
const candidate = await generateAppLocksInternal(appFolder, true, true, workspace, opts, false, true);
|
|
442
|
+
if (candidate) {
|
|
443
|
+
hasAny = true;
|
|
444
|
+
log.info(colors.green(`+ ${candidate}`));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
for (const appFolder of appFolders) {
|
|
448
|
+
const candidate = await generateAppLocksInternal(appFolder, false, true, workspace, opts, false, true);
|
|
449
|
+
if (candidate) {
|
|
450
|
+
hasAny = true;
|
|
451
|
+
log.info(colors.green(`+ ${candidate}`));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (hasAny) {
|
|
455
|
+
if (opts.dryRun) {
|
|
456
|
+
log.info(colors.gray(`Dry run complete.`));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (!opts.yes &&
|
|
460
|
+
!(await Confirm.prompt({
|
|
461
|
+
message: "Update the metadata of the above apps?",
|
|
462
|
+
default: true,
|
|
463
|
+
}))) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
log.info(colors.green.bold("No metadata to update"));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
for (const appFolder of rawAppFolders) {
|
|
472
|
+
await generateAppLocksInternal(appFolder, true, false, workspace, opts, false, true);
|
|
473
|
+
}
|
|
474
|
+
for (const appFolder of appFolders) {
|
|
475
|
+
await generateAppLocksInternal(appFolder, false, false, workspace, opts, false, true);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
@@ -33,26 +33,30 @@ export function repopulateFields(runnables) {
|
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
export function replaceInlineScripts(rec, localPath) {
|
|
36
|
+
export function replaceInlineScripts(rec, localPath, addType) {
|
|
37
37
|
if (!rec) {
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
if (typeof rec == "object") {
|
|
41
41
|
return Object.entries(rec).flatMap(([k, v]) => {
|
|
42
42
|
if (k == "runType") {
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
if (addType) {
|
|
44
|
+
if (isVersionsGeq1585()) {
|
|
45
|
+
rec["type"] = "path";
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
rec["type"] = "runnableByPath";
|
|
49
|
+
}
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
else if (k == "inlineScript" && typeof v == "object") {
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if (addType) {
|
|
54
|
+
if (isVersionsGeq1585()) {
|
|
55
|
+
rec["type"] = "inline";
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
rec["type"] = "runnableByName";
|
|
59
|
+
}
|
|
56
60
|
}
|
|
57
61
|
const o = v;
|
|
58
62
|
if (o["content"] && o["content"].startsWith("!inline")) {
|
|
@@ -65,7 +69,7 @@ export function replaceInlineScripts(rec, localPath) {
|
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
else {
|
|
68
|
-
replaceInlineScripts(v, localPath);
|
|
72
|
+
replaceInlineScripts(v, localPath, addType);
|
|
69
73
|
}
|
|
70
74
|
});
|
|
71
75
|
}
|
|
@@ -103,7 +107,7 @@ export async function pushApp(workspace, remotePath, localPath, message) {
|
|
|
103
107
|
}
|
|
104
108
|
const path = localPath + "app.yaml";
|
|
105
109
|
const localApp = (await yamlParseFile(path));
|
|
106
|
-
replaceInlineScripts(localApp.value, localPath);
|
|
110
|
+
replaceInlineScripts(localApp.value, localPath, true);
|
|
107
111
|
await generatingPolicy(localApp, remotePath, localApp?.["public"] ??
|
|
108
112
|
localApp?.["policy"]?.["execution_mode"] == "anonymous");
|
|
109
113
|
if (app) {
|
|
@@ -192,7 +196,7 @@ const command = new Command()
|
|
|
192
196
|
.option("--dry-run", "Perform a dry run without making changes")
|
|
193
197
|
.option("--default-ts <runtime:string>", "Default TypeScript runtime (bun or deno)")
|
|
194
198
|
.action(async (opts, appFolder) => {
|
|
195
|
-
const { generateLocksCommand } = await import("./
|
|
199
|
+
const { generateLocksCommand } = await import("./app_metadata.js");
|
|
196
200
|
await generateLocksCommand(opts, appFolder);
|
|
197
201
|
});
|
|
198
202
|
export default command;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
// deno-lint-ignore-file no-explicit-any
|
|
2
2
|
import * as dntShim from "../../../_dnt.shims.js";
|
|
3
|
-
import { Command, colors, log, getPort, open, windmillUtils, yamlParseFile, } from "../../../deps.js";
|
|
3
|
+
import { Command, colors, log, getPort, open, windmillUtils, yamlParseFile, SEP, } from "../../../deps.js";
|
|
4
4
|
import * as http from "node:http";
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
import { writeFileSync } from "node:fs";
|
|
9
9
|
import { WebSocketServer } from "ws";
|
|
10
|
-
import { getDevBuildOptions, ensureNodeModules, createFrameworkPlugins, detectFrameworks } from "./bundle.js";
|
|
10
|
+
import { getDevBuildOptions, ensureNodeModules, createFrameworkPlugins, detectFrameworks, } from "./bundle.js";
|
|
11
11
|
import { wmillTsDev as wmillTs } from "./wmillTsDev.js";
|
|
12
12
|
import * as wmill from "../../../gen/services.gen.js";
|
|
13
13
|
import { resolveWorkspace } from "../../core/context.js";
|
|
14
14
|
import { requireLogin } from "../../core/auth.js";
|
|
15
15
|
import { GLOBAL_CONFIG_OPT } from "../../core/conf.js";
|
|
16
16
|
import { replaceInlineScripts } from "./apps.js";
|
|
17
|
-
import { inferRunnableSchemaFromFile } from "./app_metadata.js";
|
|
17
|
+
import { APP_BACKEND_FOLDER, inferRunnableSchemaFromFile, } from "./app_metadata.js";
|
|
18
18
|
const DEFAULT_PORT = 4000;
|
|
19
19
|
const DEFAULT_HOST = "localhost";
|
|
20
20
|
// HTML template with live reload
|
|
@@ -96,7 +96,7 @@ async function dev(opts) {
|
|
|
96
96
|
const shouldOpen = opts.open ?? true;
|
|
97
97
|
// Detect frameworks to determine default entry point
|
|
98
98
|
const frameworks = detectFrameworks(process.cwd());
|
|
99
|
-
const defaultEntry =
|
|
99
|
+
const defaultEntry = frameworks.svelte || frameworks.vue ? "index.ts" : "index.tsx";
|
|
100
100
|
const entryPoint = opts.entry ?? defaultEntry;
|
|
101
101
|
// Verify entry point exists
|
|
102
102
|
if (!fs.existsSync(entryPoint)) {
|
|
@@ -177,7 +177,7 @@ async function dev(opts) {
|
|
|
177
177
|
// Initial build
|
|
178
178
|
await ctx.rebuild();
|
|
179
179
|
// Watch runnables folder for changes
|
|
180
|
-
const runnablesPath = path.join(process.cwd(),
|
|
180
|
+
const runnablesPath = path.join(process.cwd(), APP_BACKEND_FOLDER);
|
|
181
181
|
let runnablesWatcher;
|
|
182
182
|
if (fs.existsSync(runnablesPath)) {
|
|
183
183
|
log.info(colors.blue(`👁️ Watching runnables folder at: ${runnablesPath}\n`));
|
|
@@ -498,7 +498,7 @@ async function loadRunnables() {
|
|
|
498
498
|
try {
|
|
499
499
|
const localPath = process.cwd();
|
|
500
500
|
const rawApp = (await yamlParseFile(path.join(localPath, "raw_app.yaml")));
|
|
501
|
-
replaceInlineScripts(rawApp.runnables, path.join(localPath,
|
|
501
|
+
replaceInlineScripts(rawApp.runnables, path.join(localPath, APP_BACKEND_FOLDER) + SEP, true);
|
|
502
502
|
return rawApp?.runnables ?? {};
|
|
503
503
|
}
|
|
504
504
|
catch (error) {
|
|
@@ -525,7 +525,8 @@ async function executeRunnable(runnable, workspace, appPath, runnableId, args) {
|
|
|
525
525
|
}
|
|
526
526
|
}
|
|
527
527
|
}
|
|
528
|
-
if ((runnable.type === "inline" || runnable.type === "runnableByName") &&
|
|
528
|
+
if ((runnable.type === "inline" || runnable.type === "runnableByName") &&
|
|
529
|
+
runnable.inlineScript) {
|
|
529
530
|
const inlineScript = runnable.inlineScript;
|
|
530
531
|
if (inlineScript.id !== undefined) {
|
|
531
532
|
requestBody.id = inlineScript.id;
|
|
@@ -538,7 +539,8 @@ async function executeRunnable(runnable, workspace, appPath, runnableId, args) {
|
|
|
538
539
|
cache_ttl: inlineScript.cache_ttl,
|
|
539
540
|
};
|
|
540
541
|
}
|
|
541
|
-
else if ((runnable.type === "path" || runnable.type === "runnableByPath") &&
|
|
542
|
+
else if ((runnable.type === "path" || runnable.type === "runnableByPath") &&
|
|
543
|
+
runnable.path) {
|
|
542
544
|
const runType = runnable.runType ?? "script";
|
|
543
545
|
requestBody.path =
|
|
544
546
|
runType !== "hubscript"
|
|
@@ -7,7 +7,7 @@ import * as wmill from "../../../gen/services.gen.js";
|
|
|
7
7
|
import { isSuperset } from "../../types.js";
|
|
8
8
|
import { replaceInlineScripts, repopulateFields } from "./apps.js";
|
|
9
9
|
import { createBundle, detectFrameworks } from "./bundle.js";
|
|
10
|
-
import {
|
|
10
|
+
import { APP_BACKEND_FOLDER } from "./app_metadata.js";
|
|
11
11
|
const alreadySynced = [];
|
|
12
12
|
async function collectAppFiles(localPath) {
|
|
13
13
|
const files = {};
|
|
@@ -17,7 +17,10 @@ async function collectAppFiles(localPath) {
|
|
|
17
17
|
const relativePath = basePath + entry.name;
|
|
18
18
|
if (entry.isDirectory) {
|
|
19
19
|
// Skip the runnables and node_modules subfolders
|
|
20
|
-
if (entry.name ===
|
|
20
|
+
if (entry.name === APP_BACKEND_FOLDER ||
|
|
21
|
+
entry.name === "node_modules" ||
|
|
22
|
+
entry.name === "dist" ||
|
|
23
|
+
entry.name === ".claude") {
|
|
21
24
|
continue;
|
|
22
25
|
}
|
|
23
26
|
await readDirRecursive(fullPath + SEP, relativePath + SEP);
|
|
@@ -66,7 +69,7 @@ export async function pushRawApp(workspace, remotePath, localPath, message) {
|
|
|
66
69
|
}
|
|
67
70
|
const path = localPath + "raw_app.yaml";
|
|
68
71
|
const localApp = (await yamlParseFile(path));
|
|
69
|
-
replaceInlineScripts(localApp.runnables, localPath + SEP +
|
|
72
|
+
replaceInlineScripts(localApp.runnables, localPath + SEP + APP_BACKEND_FOLDER + SEP, true);
|
|
70
73
|
repopulateFields(localApp.runnables);
|
|
71
74
|
await generatingPolicy(localApp, remotePath, localApp?.["public"] ?? false);
|
|
72
75
|
const files = await collectAppFiles(localPath);
|
|
@@ -74,7 +77,7 @@ export async function pushRawApp(workspace, remotePath, localPath, message) {
|
|
|
74
77
|
log.info(colors.yellow.bold(`Creating raw app ${remotePath} bundle...`));
|
|
75
78
|
// Detect frameworks to determine entry point
|
|
76
79
|
const frameworks = detectFrameworks(localPath);
|
|
77
|
-
const entryFile =
|
|
80
|
+
const entryFile = frameworks.svelte || frameworks.vue ? "index.ts" : "index.tsx";
|
|
78
81
|
const entryPoint = localPath + entryFile;
|
|
79
82
|
return await createBundle({
|
|
80
83
|
entryPoint: entryPoint,
|
|
@@ -146,61 +149,6 @@ export async function generatingPolicy(app, path, publicApp) {
|
|
|
146
149
|
throw e;
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
|
-
export async function generateLocksCommand(opts, appPath) {
|
|
150
|
-
const { generateAppLocksInternal } = await import("./app_metadata.js");
|
|
151
|
-
const { elementsToMap, FSFSElement } = await import("../sync/sync.js");
|
|
152
|
-
const { ignoreF } = await import("../sync/sync.js");
|
|
153
|
-
const { Confirm } = await import("../../../deps.js");
|
|
154
|
-
if (appPath == "") {
|
|
155
|
-
appPath = undefined;
|
|
156
|
-
}
|
|
157
|
-
const workspace = await resolveWorkspace(opts);
|
|
158
|
-
await requireLogin(opts);
|
|
159
|
-
opts = await mergeConfigWithConfigFile(opts);
|
|
160
|
-
if (appPath) {
|
|
161
|
-
// Generate metadata for a specific app
|
|
162
|
-
await generateAppLocksInternal(appPath, false, workspace, opts, false, false);
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
// Generate metadata for all apps
|
|
166
|
-
const ignore = await ignoreF(opts);
|
|
167
|
-
const elems = await elementsToMap(await FSFSElement(dntShim.Deno.cwd(), [], true), (p, isD) => {
|
|
168
|
-
return ignore(p, isD) || (!isD && !p.endsWith(SEP + "raw_app.yaml"));
|
|
169
|
-
}, false, {});
|
|
170
|
-
const appFolders = Object.keys(elems)
|
|
171
|
-
.filter((p) => p.endsWith(SEP + "raw_app.yaml"))
|
|
172
|
-
.map((p) => p.substring(0, p.length - (SEP + "raw_app.yaml").length));
|
|
173
|
-
let hasAny = false;
|
|
174
|
-
log.info("Checking metadata for all apps:");
|
|
175
|
-
for (const appFolder of appFolders) {
|
|
176
|
-
const candidate = await generateAppLocksInternal(appFolder, true, workspace, opts, false, true);
|
|
177
|
-
if (candidate) {
|
|
178
|
-
hasAny = true;
|
|
179
|
-
log.info(colors.green(`+ ${candidate}`));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (hasAny) {
|
|
183
|
-
if (opts.dryRun) {
|
|
184
|
-
log.info(colors.gray(`Dry run complete.`));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (!opts.yes &&
|
|
188
|
-
!(await Confirm.prompt({
|
|
189
|
-
message: "Update the metadata of the above apps?",
|
|
190
|
-
default: true,
|
|
191
|
-
}))) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
log.info(colors.green.bold("No metadata to update"));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
for (const appFolder of appFolders) {
|
|
200
|
-
await generateAppLocksInternal(appFolder, false, workspace, opts, false, true);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
152
|
async function pushRawAppCommand(opts, filePath, remotePath) {
|
|
205
153
|
if (!validatePath(remotePath)) {
|
|
206
154
|
return;
|
|
@@ -3,7 +3,7 @@ import { SEP, colors, log, path, yamlParseFile, yamlStringify, } from "../../../
|
|
|
3
3
|
import { readLockfile, checkifMetadataUptodate, blueColor, clearGlobalLock, updateMetadataGlobalLock, LockfileGenerationError, getRawWorkspaceDependencies, } from "../../utils/metadata.js";
|
|
4
4
|
import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../windmill-utils-internal/src/inline-scripts/extractor.js";
|
|
5
5
|
import { generateHash, getHeaders, writeIfChanged } from "../../utils/utils.js";
|
|
6
|
-
import { exts
|
|
6
|
+
import { exts } from "../script/script.js";
|
|
7
7
|
import { FSFSElement } from "../sync/sync.js";
|
|
8
8
|
import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.js";
|
|
9
9
|
import { workspaceDependenciesLanguages } from "../../utils/script_common.js";
|
|
@@ -60,7 +60,10 @@ export async function generateFlowLockInternal(folder, dryRun, workspace, opts,
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${folder}`);
|
|
63
|
-
await replaceInlineScripts(flowValue.value.modules, async (path) => await dntShim.Deno.readTextFile(folder + SEP + path), log, folder + SEP, SEP, changedScripts
|
|
63
|
+
await replaceInlineScripts(flowValue.value.modules, async (path) => await dntShim.Deno.readTextFile(folder + SEP + path), log, folder + SEP, SEP, changedScripts
|
|
64
|
+
// (path: string, newPath: string) => Deno.renameSync(path, newPath),
|
|
65
|
+
// (path: string) => Deno.removeSync(path)
|
|
66
|
+
);
|
|
64
67
|
//removeChangedLocks
|
|
65
68
|
flowValue.value = await updateFlow(workspace, flowValue.value, remote_path, rawWorkspaceDependencies);
|
|
66
69
|
const inlineScripts = extractInlineScriptsForFlows(flowValue.value.modules, {}, SEP, opts.defaultTs);
|