windmill-utils-internal 1.3.4 → 1.3.6

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.
@@ -1,13 +1,16 @@
1
1
  import { PathAssigner } from "../path-utils/path-assigner";
2
- import { FlowModule } from "../gen/types.gen";
2
+ import { FlowModule, ScriptLang } from "../gen/types.gen";
3
3
  /**
4
4
  * Represents an inline script extracted from a flow module
5
5
  */
6
- interface InlineScript {
6
+ export interface InlineScript {
7
7
  /** File path where the script content should be written */
8
8
  path: string;
9
9
  /** The actual script content */
10
10
  content: string;
11
+ /** The script language */
12
+ language: ScriptLang;
13
+ is_lock: boolean;
11
14
  }
12
15
  /**
13
16
  * Options for extractInlineScripts function
@@ -37,5 +40,4 @@ export declare function extractInlineScripts(modules: FlowModule[], mapping?: Re
37
40
  * @param mapping - Existing mapping to extend (defaults to empty object)
38
41
  * @returns Record mapping module IDs to their corresponding file paths
39
42
  */
40
- export declare function extractCurrentMapping(modules: FlowModule[] | undefined, mapping?: Record<string, string>): Record<string, string>;
41
- export {};
43
+ export declare function extractCurrentMapping(modules: FlowModule[] | undefined, mapping?: Record<string, string>, failureModule?: FlowModule, preprocessorModule?: FlowModule): Record<string, string>;
@@ -2,14 +2,15 @@ import { newPathAssigner } from "../path-utils/path-assigner";
2
2
  function extractRawscriptInline(id, summary, rawscript, mapping, separator, assigner) {
3
3
  const [basePath, ext] = assigner.assignPath(summary ?? id, rawscript.language);
4
4
  const path = mapping[id] ?? basePath + ext;
5
+ const language = rawscript.language;
5
6
  const content = rawscript.content;
6
- const r = [{ path: path, content: content }];
7
+ const r = [{ path: path, content: content, language, is_lock: false }];
7
8
  rawscript.content = "!inline " + path.replaceAll(separator, "/");
8
9
  const lock = rawscript.lock;
9
10
  if (lock && lock != "") {
10
11
  const lockPath = basePath + "lock";
11
12
  rawscript.lock = "!inline " + lockPath.replaceAll(separator, "/");
12
- r.push({ path: lockPath, content: lock });
13
+ r.push({ path: lockPath, content: lock, language, is_lock: true });
13
14
  }
14
15
  return r;
15
16
  }
@@ -70,7 +71,13 @@ export function extractInlineScripts(modules, mapping = {}, separator = "/", def
70
71
  * @param mapping - Existing mapping to extend (defaults to empty object)
71
72
  * @returns Record mapping module IDs to their corresponding file paths
72
73
  */
73
- export function extractCurrentMapping(modules, mapping = {}) {
74
+ export function extractCurrentMapping(modules, mapping = {}, failureModule, preprocessorModule) {
75
+ if (failureModule) {
76
+ extractCurrentMapping([failureModule], mapping);
77
+ }
78
+ if (preprocessorModule) {
79
+ extractCurrentMapping([preprocessorModule], mapping);
80
+ }
74
81
  if (!modules || !Array.isArray(modules)) {
75
82
  return mapping;
76
83
  }
@@ -1,4 +1,10 @@
1
- import { FlowModule } from "../gen/types.gen";
1
+ import { FlowModule, FlowValue, RawScript } from "../gen/types.gen";
2
+ export type LocalScriptInfo = {
3
+ content: string;
4
+ language: RawScript["language"];
5
+ lock?: string;
6
+ tag?: string;
7
+ };
2
8
  /**
3
9
  * Replaces inline script references with actual file content from the filesystem.
4
10
  * This function recursively processes all flow modules and their nested structures.
@@ -14,3 +20,24 @@ export declare function replaceInlineScripts(modules: FlowModule[], fileReader:
14
20
  info: (message: string) => void;
15
21
  error: (message: string) => void;
16
22
  } | undefined, localPath: string, separator?: string, removeLocks?: string[]): Promise<void>;
23
+ /**
24
+ * Replaces PathScript ("script" type) modules with RawScript ("rawscript" type) using local file content.
25
+ * This is used during flow preview so that local script changes are tested instead of remote versions.
26
+ *
27
+ * @param modules - Array of flow modules to process
28
+ * @param scriptReader - Function that takes a script path and returns local content/language/lock, or undefined if not found locally
29
+ * @param logger - Logger for info/error messages
30
+ */
31
+ export declare function replacePathScriptsWithLocal(modules: FlowModule[], scriptReader: (scriptPath: string) => Promise<LocalScriptInfo | undefined>, logger?: {
32
+ info: (message: string) => void;
33
+ error: (message: string) => void;
34
+ }): Promise<void>;
35
+ /**
36
+ * Replaces all PathScript modules in a flow value (modules, failure_module, preprocessor_module)
37
+ * with RawScript using local file content.
38
+ */
39
+ export declare function replaceAllPathScriptsWithLocal(flowValue: FlowValue, scriptReader: (scriptPath: string) => Promise<LocalScriptInfo | undefined>, logger?: {
40
+ info: (message: string) => void;
41
+ error: (message: string) => void;
42
+ }): Promise<void>;
43
+ export declare function collectPathScriptPaths(flowValue: FlowValue): string[];
@@ -83,3 +83,134 @@ export async function replaceInlineScripts(modules, fileReader, logger = {
83
83
  }
84
84
  }));
85
85
  }
86
+ /**
87
+ * Replaces PathScript ("script" type) modules with RawScript ("rawscript" type) using local file content.
88
+ * This is used during flow preview so that local script changes are tested instead of remote versions.
89
+ *
90
+ * @param modules - Array of flow modules to process
91
+ * @param scriptReader - Function that takes a script path and returns local content/language/lock, or undefined if not found locally
92
+ * @param logger - Logger for info/error messages
93
+ */
94
+ export async function replacePathScriptsWithLocal(modules, scriptReader, logger = {
95
+ info: () => { },
96
+ error: () => { },
97
+ }) {
98
+ await Promise.all(modules.map(async (module) => {
99
+ if (!module.value) {
100
+ return;
101
+ }
102
+ if (module.value.type === "script") {
103
+ const scriptPath = module.value.path;
104
+ const localScript = await scriptReader(scriptPath);
105
+ if (localScript) {
106
+ const pathScript = module.value;
107
+ module.value = {
108
+ type: "rawscript",
109
+ content: localScript.content,
110
+ language: localScript.language,
111
+ lock: localScript.lock,
112
+ path: scriptPath,
113
+ input_transforms: pathScript.input_transforms,
114
+ tag: pathScript.tag_override ?? localScript.tag,
115
+ };
116
+ }
117
+ }
118
+ else if (module.value.type === "forloopflow" || module.value.type === "whileloopflow") {
119
+ await replacePathScriptsWithLocal(module.value.modules, scriptReader, logger);
120
+ }
121
+ else if (module.value.type === "branchall") {
122
+ await Promise.all(module.value.branches.map(async (branch) => {
123
+ await replacePathScriptsWithLocal(branch.modules, scriptReader, logger);
124
+ }));
125
+ }
126
+ else if (module.value.type === "branchone") {
127
+ await Promise.all(module.value.branches.map(async (branch) => {
128
+ await replacePathScriptsWithLocal(branch.modules, scriptReader, logger);
129
+ }));
130
+ await replacePathScriptsWithLocal(module.value.default, scriptReader, logger);
131
+ }
132
+ else if (module.value.type === "aiagent") {
133
+ await Promise.all((module.value.tools ?? []).map(async (tool) => {
134
+ const toolValue = tool.value;
135
+ if (!toolValue || toolValue.tool_type !== "flowmodule" || toolValue.type !== "script") {
136
+ return;
137
+ }
138
+ const localScript = await scriptReader(toolValue.path);
139
+ if (localScript) {
140
+ tool.value = {
141
+ tool_type: "flowmodule",
142
+ type: "rawscript",
143
+ content: localScript.content,
144
+ language: localScript.language,
145
+ lock: localScript.lock,
146
+ path: toolValue.path,
147
+ input_transforms: toolValue.input_transforms,
148
+ tag: toolValue.tag_override ?? localScript.tag,
149
+ };
150
+ }
151
+ }));
152
+ }
153
+ }));
154
+ }
155
+ function collectPathScriptPathsFromModules(modules, paths) {
156
+ for (const module of modules) {
157
+ if (!module.value) {
158
+ continue;
159
+ }
160
+ if (module.value.type === "script") {
161
+ paths.add(module.value.path);
162
+ }
163
+ else if (module.value.type === "forloopflow" ||
164
+ module.value.type === "whileloopflow") {
165
+ collectPathScriptPathsFromModules(module.value.modules, paths);
166
+ }
167
+ else if (module.value.type === "branchall") {
168
+ for (const branch of module.value.branches) {
169
+ collectPathScriptPathsFromModules(branch.modules, paths);
170
+ }
171
+ }
172
+ else if (module.value.type === "branchone") {
173
+ for (const branch of module.value.branches) {
174
+ collectPathScriptPathsFromModules(branch.modules, paths);
175
+ }
176
+ collectPathScriptPathsFromModules(module.value.default, paths);
177
+ }
178
+ else if (module.value.type === "aiagent") {
179
+ for (const tool of module.value.tools ?? []) {
180
+ const toolValue = tool.value;
181
+ if (toolValue &&
182
+ toolValue.tool_type === "flowmodule" &&
183
+ toolValue.type === "script") {
184
+ paths.add(toolValue.path);
185
+ }
186
+ }
187
+ }
188
+ }
189
+ }
190
+ /**
191
+ * Replaces all PathScript modules in a flow value (modules, failure_module, preprocessor_module)
192
+ * with RawScript using local file content.
193
+ */
194
+ export async function replaceAllPathScriptsWithLocal(flowValue, scriptReader, logger = {
195
+ info: () => { },
196
+ error: () => { },
197
+ }) {
198
+ await replacePathScriptsWithLocal(flowValue.modules, scriptReader, logger);
199
+ if (flowValue.failure_module) {
200
+ await replacePathScriptsWithLocal([flowValue.failure_module], scriptReader, logger);
201
+ }
202
+ if (flowValue.preprocessor_module) {
203
+ await replacePathScriptsWithLocal([flowValue.preprocessor_module], scriptReader, logger);
204
+ }
205
+ }
206
+ export function collectPathScriptPaths(flowValue) {
207
+ const paths = new Set();
208
+ collectPathScriptPathsFromModules(flowValue.modules, paths);
209
+ if (flowValue.failure_module) {
210
+ collectPathScriptPathsFromModules([flowValue.failure_module], paths);
211
+ }
212
+ if (flowValue.preprocessor_module) {
213
+ collectPathScriptPathsFromModules([flowValue.preprocessor_module], paths);
214
+ }
215
+ return [...paths];
216
+ }
@@ -29,6 +29,11 @@ export declare const EXTENSION_TO_LANGUAGE: Record<string, SupportedLanguage>;
29
29
  * @returns The language, or undefined if not recognized
30
30
  */
31
31
  export declare function getLanguageFromExtension(ext: string, defaultTs?: "bun" | "deno"): SupportedLanguage | undefined;
32
+ /**
33
+ * Sanitizes a summary string for use as a filesystem-safe name.
34
+ * Removes or replaces characters that are invalid on common filesystems.
35
+ */
36
+ export declare function sanitizeForFilesystem(summary: string): string;
32
37
  export interface PathAssigner {
33
38
  assignPath(summary: string | undefined, language: SupportedLanguage): [string, string];
34
39
  }
@@ -93,6 +93,21 @@ export function getLanguageFromExtension(ext, defaultTs = "bun") {
93
93
  }
94
94
  return undefined;
95
95
  }
96
+ /**
97
+ * Sanitizes a summary string for use as a filesystem-safe name.
98
+ * Removes or replaces characters that are invalid on common filesystems.
99
+ */
100
+ export function sanitizeForFilesystem(summary) {
101
+ return summary
102
+ .toLowerCase()
103
+ .replaceAll(" ", "_")
104
+ // Remove characters invalid on Windows/Unix/Mac: / \ : * ? " < > |
105
+ // Also remove control characters (0x00-0x1F) and DEL (0x7F)
106
+ // deno-lint-ignore no-control-regex
107
+ .replace(/[/\\:*?"<>|\x00-\x1f\x7f]/g, "")
108
+ // Trim leading/trailing dots and underscores (hidden files, Windows edge cases)
109
+ .replace(/^[._]+|[._]+$/g, "");
110
+ }
96
111
  /**
97
112
  * Creates a new path assigner for inline scripts.
98
113
  *
@@ -110,7 +125,7 @@ export function newPathAssigner(defaultTs, options) {
110
125
  const seen_names = new Set();
111
126
  function assignPath(summary, language) {
112
127
  let name;
113
- name = summary?.toLowerCase()?.replaceAll(" ", "_") ?? "";
128
+ name = summary ? sanitizeForFilesystem(summary) : "";
114
129
  let original_name = name;
115
130
  if (name == "") {
116
131
  original_name = INLINE_SCRIPT_PREFIX;
@@ -141,7 +156,7 @@ export function newRawAppPathAssigner(defaultTs) {
141
156
  const seen_names = new Set();
142
157
  function assignPath(summary, language) {
143
158
  let name;
144
- name = summary?.toLowerCase()?.replaceAll(" ", "_") ?? "";
159
+ name = summary ? sanitizeForFilesystem(summary) : "";
145
160
  let original_name = name;
146
161
  if (name == "") {
147
162
  original_name = "runnable";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windmill-utils-internal",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "Internal utility functions for Windmill",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -31,4 +31,4 @@
31
31
  "files": [
32
32
  "dist/**/*"
33
33
  ]
34
- }
34
+ }