@keystrokehq/cli 0.0.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/AGENTS-blurb.md +123 -0
- package/LICENSE +42 -0
- package/README.md +177 -0
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/bin/keystroke.mjs +107 -0
- package/dist/_manifest-JSRE3H8k.mjs +385 -0
- package/dist/agent-bundle-package-DWV6B_5q-BtV7Xycc.mjs +2344 -0
- package/dist/agent-manifest-CDnbkR2f.mjs +245 -0
- package/dist/agents-CZJGxVqV.mjs +228 -0
- package/dist/api-keys-D2lgguuY.mjs +40 -0
- package/dist/auth-DN2VusyU.mjs +59 -0
- package/dist/auth.handler-CT1BQUvu.mjs +340 -0
- package/dist/browser-qwFrUH82.mjs +24 -0
- package/dist/build-agents-BmM_AsSd-BGi9wtzt.mjs +514 -0
- package/dist/build-metadata-BWS7uhd_-DR8gJjTX.mjs +1422 -0
- package/dist/build-progress-DgYKb4hB.mjs +183 -0
- package/dist/build-tasks-CdihpudT-D5r5HUHe.mjs +91 -0
- package/dist/build-workflows-CfxBnIWh-CdYPv8w2.mjs +370 -0
- package/dist/build.handler-4799CjWH.mjs +36 -0
- package/dist/chunk-CH6r78ws.mjs +37 -0
- package/dist/clear-cache.handler-B9tqSoSM.mjs +11 -0
- package/dist/clear.handler-BTIXXPTJ.mjs +42 -0
- package/dist/clear.handler-BydlX-zE.mjs +11 -0
- package/dist/commander-DfTVqQ-3.mjs +133 -0
- package/dist/concurrency-gXn9Rw8x-DNl2YtrS.mjs +20 -0
- package/dist/connect-BUXkeH0F.mjs +43 -0
- package/dist/connect.handler-CYel9cy6.mjs +430 -0
- package/dist/constants-CPpPdSNg.mjs +8 -0
- package/dist/context-T7HZuB97.mjs +138 -0
- package/dist/credential-env-map-CI8yWHVy.mjs +28 -0
- package/dist/credential-schema-mismatch-BKo5PjcQ.mjs +76 -0
- package/dist/credentials-CvmjU0lK.mjs +171 -0
- package/dist/credentials-OfVHOtG3.mjs +151216 -0
- package/dist/current-deployment-workflow-poHt27i3.mjs +94 -0
- package/dist/current.handler-B8zKzfPp.mjs +21 -0
- package/dist/delete.handler-bAu1iXVQ.mjs +17 -0
- package/dist/deploy-7Jjls436.mjs +26 -0
- package/dist/deploy-BOPIpRWm.mjs +74 -0
- package/dist/deploy-progress-BmGUNFKg.mjs +70 -0
- package/dist/deploy.handler-BAzgiNhd.mjs +370 -0
- package/dist/detect-env-access-CwkOYeYM-D_BCZqV6.mjs +209 -0
- package/dist/diff-utils-NEfcjqxt.mjs +185 -0
- package/dist/diff.handler-Du7SY8K4.mjs +47 -0
- package/dist/dist-BkJUoBiG.mjs +1116 -0
- package/dist/dist-CUK7yBM0.mjs +308 -0
- package/dist/env-91KwMKov.mjs +140 -0
- package/dist/env.handler-BAzBuMzQ.mjs +277 -0
- package/dist/error-boundary-VL-JLfIa.mjs +34 -0
- package/dist/file-metadata-D1vm-XY2.mjs +191 -0
- package/dist/get-intrinsic-zLxwtrLK.mjs +658 -0
- package/dist/import-module-CV84H5fZ-B_CBCmb4.mjs +1747 -0
- package/dist/init-DpMCotSK.mjs +45 -0
- package/dist/init.handler-CPRnif52.mjs +585 -0
- package/dist/inspect.handler-DT_cD036.mjs +146 -0
- package/dist/integration-catalog-Bt-L3GjF.mjs +104 -0
- package/dist/integrations-DlatPK4W.mjs +79 -0
- package/dist/keystroke.d.mts +3 -0
- package/dist/keystroke.mjs +707 -0
- package/dist/layout-CbMtQ2tm.mjs +67 -0
- package/dist/list-enrichment-y-cwizLr.mjs +189 -0
- package/dist/list.handler-BTWvCyjA.mjs +52 -0
- package/dist/list.handler-CWF_Dj15.mjs +24 -0
- package/dist/list.handler-CZ6G2x_G.mjs +75 -0
- package/dist/list.handler-DWaQkJaR.mjs +51 -0
- package/dist/list.handler-DqbFcBW7.mjs +180 -0
- package/dist/list.handler-lq3ZGAn4.mjs +104 -0
- package/dist/logs-BEg9L5l8.mjs +28 -0
- package/dist/logs.handler-6hoMBzqw.mjs +35 -0
- package/dist/logs.handler-BD_dXiL1.mjs +231 -0
- package/dist/metadata-layout-GUYIUo0i-_aG2zjue.mjs +5877 -0
- package/dist/normalize-path-CojS-CgQ-DLCOvnD1.mjs +20 -0
- package/dist/options-CeaTcFxP.mjs +43 -0
- package/dist/org-xLzBtt2_.mjs +41 -0
- package/dist/output-DM4b7KgY.mjs +72 -0
- package/dist/oxc-B3KI3rf_-n9d1hKNq.mjs +119 -0
- package/dist/paused.handler-BMFm9Cff.mjs +94 -0
- package/dist/project-config-D1qsQlO7.mjs +107 -0
- package/dist/projects-CHkRE9rS.mjs +1574 -0
- package/dist/projects-Cjb7sovS.mjs +30 -0
- package/dist/read-credential-keys-77a91T8M-KA0Iw0Z1.mjs +9 -0
- package/dist/register.handler-BPCdor1_.mjs +86 -0
- package/dist/requirements.handler-DPXdSks3.mjs +201 -0
- package/dist/resolve-project-DDJ29sCF.mjs +35 -0
- package/dist/rolldown-runtime-twds-ZHy-BWWzu8VG.mjs +15 -0
- package/dist/run-polling-CAgFRdK3.mjs +20 -0
- package/dist/runs-D9hNLb9A.mjs +259 -0
- package/dist/schedule-BXx3uXwr.mjs +1142 -0
- package/dist/schema-17qMfNyI.mjs +18 -0
- package/dist/schema-display-CgmeKigW.mjs +130 -0
- package/dist/schemas-CDib1RhE.mjs +125 -0
- package/dist/skills-sync.handler-DIy8GR16.mjs +34 -0
- package/dist/skills.command-CrjI2dN9.mjs +35 -0
- package/dist/skills.handler-Bz8bJKql.mjs +9 -0
- package/dist/source-analysis-Cj-ADyu--BJQcFPCG.mjs +144 -0
- package/dist/spinner-progress-DMVwgqO9.mjs +173 -0
- package/dist/src-C0X6u_Mw.mjs +1340 -0
- package/dist/src-eHwu-Gfw.mjs +369 -0
- package/dist/status.handler-BO4nwvWn.mjs +101 -0
- package/dist/switch.handler-D_9213Vf.mjs +51 -0
- package/dist/sync-BL_Mo5st.mjs +39 -0
- package/dist/sync-keystroke-agent-skills-Kx_H7UTd.mjs +70 -0
- package/dist/sync.handler-BUFPdzWz.mjs +82 -0
- package/dist/task-B2sZMaZu.mjs +8 -0
- package/dist/task-target-build-CBeCKbu2.mjs +432 -0
- package/dist/task-target-deploy-C5X-USeR.mjs +4 -0
- package/dist/task-target-deploy-CA6elFpF-BEr4gkol.mjs +271 -0
- package/dist/task-target-deploy-runner.d.mts +3 -0
- package/dist/task-target-deploy-runner.mjs +202 -0
- package/dist/test-BHTgR3UA.mjs +698 -0
- package/dist/test.handler-BcPQ8b74.mjs +13 -0
- package/dist/trigger-artifacts-DQPbQNqC-B4yeeFBY.mjs +239 -0
- package/dist/trigger-manifest-CY7brZeg.mjs +30 -0
- package/dist/try-deploy.handler-DqybNhXx.mjs +490 -0
- package/dist/upload-CkU--iDC.mjs +207 -0
- package/dist/upload.handler-DCtiznQp.mjs +441 -0
- package/dist/utils-CywxCDM7.mjs +14 -0
- package/dist/validate.handler-DOcTaJL0.mjs +280 -0
- package/dist/workflow-build-DBQaBfnn.mjs +1819 -0
- package/dist/workflow-bundler-BPiqVscj-X1PFFAuP.mjs +167 -0
- package/dist/workflows-g9z87AJJ.mjs +799 -0
- package/dist/writer-BG8poUm3-BbXlU2kI.mjs +426 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1422 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { i as __toESM } from "./chunk-CH6r78ws.mjs";
|
|
4
|
+
import { n as FileMetadataSchema, t as FILE_METADATA_SCHEMA_VERSION } from "./file-metadata-D1vm-XY2.mjs";
|
|
5
|
+
import { d as getMetadataRoot, l as createMetadataOutputFile, n as BUILD_OUTPUT_DIR_NAME, u as getFileMetadataPath } from "./layout-CbMtQ2tm.mjs";
|
|
6
|
+
import { l as require_out, s as sha256String, t as BASE_IGNORE_PATTERNS } from "./metadata-layout-GUYIUo0i-_aG2zjue.mjs";
|
|
7
|
+
import { a as literalString, c as readStringProperty, i as literalNumber, l as unwrapExpression, n as identifierName, o as parseSourceFile, r as isNode, s as readPropertyValue, t as getFirstObjectArgument, u as visitNode } from "./oxc-B3KI3rf_-n9d1hKNq.mjs";
|
|
8
|
+
import { a as getLocalModuleSpecifier, f as resolveLocalModulePath, l as removeEmptyMetadataDirectories, o as getVariableDeclarators, s as isExportedVariableStatement } from "./source-analysis-Cj-ADyu--BJQcFPCG.mjs";
|
|
9
|
+
import { n as parseRelativeFilePath, r as toRelativeFilePath, t as normalizeRelativeFilePath } from "./normalize-path-CojS-CgQ-DLCOvnD1.mjs";
|
|
10
|
+
import { t as RESERVED_NAMESPACES } from "./constants-CPpPdSNg.mjs";
|
|
11
|
+
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { performance } from "node:perf_hooks";
|
|
14
|
+
//#region ../../packages/workflow-builder/dist/build-metadata-BWS7uhd_.mjs
|
|
15
|
+
var import_out = /* @__PURE__ */ __toESM(require_out(), 1);
|
|
16
|
+
const IGNORED_IDENTIFIERS = new Set([
|
|
17
|
+
"undefined",
|
|
18
|
+
"null",
|
|
19
|
+
"NaN",
|
|
20
|
+
"Infinity",
|
|
21
|
+
"globalThis",
|
|
22
|
+
"arguments",
|
|
23
|
+
"console",
|
|
24
|
+
"Promise",
|
|
25
|
+
"Math",
|
|
26
|
+
"JSON",
|
|
27
|
+
"Date",
|
|
28
|
+
"Error",
|
|
29
|
+
"Array",
|
|
30
|
+
"Object",
|
|
31
|
+
"String",
|
|
32
|
+
"Number",
|
|
33
|
+
"Boolean",
|
|
34
|
+
"Map",
|
|
35
|
+
"Set",
|
|
36
|
+
"WeakMap",
|
|
37
|
+
"WeakSet",
|
|
38
|
+
"Symbol",
|
|
39
|
+
"RegExp",
|
|
40
|
+
"parseInt",
|
|
41
|
+
"parseFloat",
|
|
42
|
+
"isNaN",
|
|
43
|
+
"isFinite",
|
|
44
|
+
"setTimeout",
|
|
45
|
+
"setInterval",
|
|
46
|
+
"clearTimeout",
|
|
47
|
+
"clearInterval",
|
|
48
|
+
"fetch",
|
|
49
|
+
"Buffer",
|
|
50
|
+
"process",
|
|
51
|
+
"URL",
|
|
52
|
+
"URLSearchParams",
|
|
53
|
+
"AbortController",
|
|
54
|
+
"AbortSignal",
|
|
55
|
+
"TextEncoder",
|
|
56
|
+
"TextDecoder",
|
|
57
|
+
"crypto",
|
|
58
|
+
"performance",
|
|
59
|
+
"queueMicrotask",
|
|
60
|
+
"structuredClone",
|
|
61
|
+
"atob",
|
|
62
|
+
"btoa"
|
|
63
|
+
]);
|
|
64
|
+
const IGNORED_IMPORT_SPECIFIERS = new Set(["zod", "zod/v4"]);
|
|
65
|
+
const IGNORED_IMPORT_PREFIXES = ["@keystroke/workflow-core"];
|
|
66
|
+
function analyzeCapturedVariables(sourceFile, definitionAnalysis, relativeFilePath, imports) {
|
|
67
|
+
const result = /* @__PURE__ */ new Map();
|
|
68
|
+
const fileScopeConsts = collectFileScopeConsts(sourceFile);
|
|
69
|
+
const importBindings = buildImportBindingsFromEntries(imports);
|
|
70
|
+
const definitionNames = new Set(definitionAnalysis.localDefinitions.keys());
|
|
71
|
+
const ignoredImportNames = /* @__PURE__ */ new Set();
|
|
72
|
+
for (const binding of importBindings.values()) if (isIgnoredImport(binding.moduleSpecifier)) ignoredImportNames.add(binding.localName);
|
|
73
|
+
for (const statement of sourceFile.statements) for (const declaration of getVariableDeclarators(statement)) {
|
|
74
|
+
const varName = identifierName(declaration.id);
|
|
75
|
+
if (!varName || !isNode(declaration.init)) continue;
|
|
76
|
+
const localDef = definitionAnalysis.localDefinitions.get(varName);
|
|
77
|
+
if (!localDef || localDef.kind !== "workflow" && localDef.kind !== "operation" && localDef.kind !== "agent") continue;
|
|
78
|
+
const configObject = getFirstObjectArgument(declaration.init);
|
|
79
|
+
if (!configObject) continue;
|
|
80
|
+
const runFn = findRunProperty(configObject);
|
|
81
|
+
if (!runFn) continue;
|
|
82
|
+
const functionScopeNames = collectFunctionScopeNames(runFn);
|
|
83
|
+
const referencedIdentifiers = collectReferencedIdentifiers(runFn);
|
|
84
|
+
const captured = /* @__PURE__ */ new Map();
|
|
85
|
+
for (const identifier of referencedIdentifiers) {
|
|
86
|
+
if (IGNORED_IDENTIFIERS.has(identifier) || functionScopeNames.has(identifier) || definitionNames.has(identifier) || ignoredImportNames.has(identifier) || captured.has(identifier)) continue;
|
|
87
|
+
const fileScopeConst = fileScopeConsts.get(identifier);
|
|
88
|
+
if (fileScopeConst) {
|
|
89
|
+
captured.set(identifier, buildLocalConstVariable(fileScopeConst, sourceFile, relativeFilePath));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const importBinding = importBindings.get(identifier);
|
|
93
|
+
if (importBinding) captured.set(identifier, buildImportVariable(importBinding, relativeFilePath));
|
|
94
|
+
}
|
|
95
|
+
if (captured.size > 0) result.set(varName, Array.from(captured.values()));
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
function collectFileScopeConsts(sourceFile) {
|
|
100
|
+
const consts = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const statement of sourceFile.statements) {
|
|
102
|
+
const declaration = statement.type === "VariableDeclaration" ? statement : statement.type === "ExportNamedDeclaration" && isNode(statement.declaration) ? statement.declaration : null;
|
|
103
|
+
if (!declaration || declaration.type !== "VariableDeclaration" || declaration.kind !== "const") continue;
|
|
104
|
+
for (const variable of getVariableDeclarators(statement)) {
|
|
105
|
+
const name = identifierName(variable.id);
|
|
106
|
+
if (!name || !isNode(variable.init)) continue;
|
|
107
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(isNode(variable.id) ? variable.id.start : variable.start);
|
|
108
|
+
consts.set(name, {
|
|
109
|
+
name,
|
|
110
|
+
initializer: variable.init,
|
|
111
|
+
line: line + 1
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return consts;
|
|
116
|
+
}
|
|
117
|
+
function buildImportBindingsFromEntries(imports) {
|
|
118
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const entry of imports) {
|
|
120
|
+
if (entry.isNamespaceImport && entry.namespaceName) {
|
|
121
|
+
bindings.set(entry.namespaceName, {
|
|
122
|
+
localName: entry.namespaceName,
|
|
123
|
+
importedName: "*",
|
|
124
|
+
moduleSpecifier: entry.moduleSpecifier
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
for (const name of entry.importNames) bindings.set(name, {
|
|
129
|
+
localName: name,
|
|
130
|
+
importedName: name,
|
|
131
|
+
moduleSpecifier: entry.moduleSpecifier
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return bindings;
|
|
135
|
+
}
|
|
136
|
+
function findRunProperty(objectLiteral) {
|
|
137
|
+
const runValue = readPropertyValue(objectLiteral, "run");
|
|
138
|
+
if (runValue?.type === "ArrowFunctionExpression" || runValue?.type === "FunctionExpression" || runValue?.type === "FunctionDeclaration") return runValue;
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
function collectFunctionScopeNames(fn) {
|
|
142
|
+
const names = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const param of Array.isArray(fn.params) ? fn.params : []) collectBindingNames(param, names);
|
|
144
|
+
if (isNode(fn.body) && fn.body.type === "BlockStatement") collectLocalDeclarations(fn.body, names);
|
|
145
|
+
return names;
|
|
146
|
+
}
|
|
147
|
+
function collectBindingNames(name, names) {
|
|
148
|
+
if (!isNode(name)) return;
|
|
149
|
+
const identifier = identifierName(name);
|
|
150
|
+
if (identifier) {
|
|
151
|
+
names.add(identifier);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (name.type === "ObjectPattern") {
|
|
155
|
+
for (const property of Array.isArray(name.properties) ? name.properties : []) if (isNode(property) && property.type === "Property") collectBindingNames(property.value, names);
|
|
156
|
+
else if (isNode(property) && property.type === "RestElement") collectBindingNames(property.argument, names);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (name.type === "ArrayPattern") {
|
|
160
|
+
for (const element of Array.isArray(name.elements) ? name.elements : []) collectBindingNames(element, names);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (name.type === "AssignmentPattern") {
|
|
164
|
+
collectBindingNames(name.left, names);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (name.type === "RestElement") collectBindingNames(name.argument, names);
|
|
168
|
+
}
|
|
169
|
+
function collectLocalDeclarations(block, names) {
|
|
170
|
+
visitNode(block, (node) => {
|
|
171
|
+
if (node.type === "VariableDeclarator") collectBindingNames(node.id, names);
|
|
172
|
+
if ((node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") && isNode(node.id)) {
|
|
173
|
+
const name = identifierName(node.id);
|
|
174
|
+
if (name) names.add(name);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
if (node !== block && (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression")) return false;
|
|
178
|
+
if (node.type === "CatchClause") collectBindingNames(node.param, names);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function collectReferencedIdentifiers(fn) {
|
|
182
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
183
|
+
const body = fn.body;
|
|
184
|
+
if (!isNode(body)) return identifiers;
|
|
185
|
+
visitNode(body, (node, parent) => {
|
|
186
|
+
const identifier = identifierName(node);
|
|
187
|
+
if (!identifier) return;
|
|
188
|
+
if (parent?.type === "MemberExpression" && parent.computed === false && parent.property === node) return;
|
|
189
|
+
if (parent?.type === "Property" && parent.key === node && parent.value !== node) return;
|
|
190
|
+
identifiers.add(identifier);
|
|
191
|
+
});
|
|
192
|
+
return identifiers;
|
|
193
|
+
}
|
|
194
|
+
function buildLocalConstVariable(fileScopeConst, sourceFile, relativeFilePath) {
|
|
195
|
+
const { valueType, value, sourceText } = classifyInitializer(fileScopeConst.initializer, sourceFile);
|
|
196
|
+
const resolvable = value !== void 0;
|
|
197
|
+
return {
|
|
198
|
+
name: fileScopeConst.name,
|
|
199
|
+
...value !== void 0 ? { value } : {},
|
|
200
|
+
...sourceText !== void 0 ? { sourceText } : {},
|
|
201
|
+
valueType,
|
|
202
|
+
resolvable,
|
|
203
|
+
source: "local-const",
|
|
204
|
+
declaration: {
|
|
205
|
+
filePath: relativeFilePath,
|
|
206
|
+
line: fileScopeConst.line
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function buildImportVariable(binding, relativeFilePath) {
|
|
211
|
+
const source = binding.moduleSpecifier.startsWith(".") || binding.moduleSpecifier.startsWith("/") ? "relative-import" : "package-import";
|
|
212
|
+
return {
|
|
213
|
+
name: binding.localName,
|
|
214
|
+
valueType: "unknown",
|
|
215
|
+
resolvable: false,
|
|
216
|
+
source,
|
|
217
|
+
importPath: binding.moduleSpecifier,
|
|
218
|
+
declaration: {
|
|
219
|
+
filePath: relativeFilePath,
|
|
220
|
+
line: 1
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function classifyInitializer(node, sourceFile) {
|
|
225
|
+
const unwrapped = unwrapExpression(node);
|
|
226
|
+
const stringValue = literalString(unwrapped);
|
|
227
|
+
if (stringValue !== void 0) return {
|
|
228
|
+
valueType: "string",
|
|
229
|
+
value: stringValue
|
|
230
|
+
};
|
|
231
|
+
const numberValue = literalNumber(unwrapped);
|
|
232
|
+
if (numberValue !== void 0) return {
|
|
233
|
+
valueType: "number",
|
|
234
|
+
value: numberValue
|
|
235
|
+
};
|
|
236
|
+
if (unwrapped.type === "Literal" && typeof unwrapped.value === "boolean") return {
|
|
237
|
+
valueType: "boolean",
|
|
238
|
+
value: unwrapped.value
|
|
239
|
+
};
|
|
240
|
+
if (unwrapped.type === "UnaryExpression" && unwrapped.operator === "-" && isNode(unwrapped.argument)) {
|
|
241
|
+
const operand = literalNumber(unwrapped.argument);
|
|
242
|
+
if (operand !== void 0) return {
|
|
243
|
+
valueType: "number",
|
|
244
|
+
value: -operand
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (unwrapped.type === "ObjectExpression") return {
|
|
248
|
+
valueType: "object",
|
|
249
|
+
sourceText: truncateSourceText(sourceFile.getText(unwrapped))
|
|
250
|
+
};
|
|
251
|
+
if (unwrapped.type === "ArrayExpression") return {
|
|
252
|
+
valueType: "array",
|
|
253
|
+
sourceText: truncateSourceText(sourceFile.getText(unwrapped))
|
|
254
|
+
};
|
|
255
|
+
if (unwrapped.type === "ArrowFunctionExpression" || unwrapped.type === "FunctionExpression") return {
|
|
256
|
+
valueType: "function",
|
|
257
|
+
sourceText: truncateSourceText(sourceFile.getText(unwrapped))
|
|
258
|
+
};
|
|
259
|
+
return {
|
|
260
|
+
valueType: "unknown",
|
|
261
|
+
sourceText: truncateSourceText(sourceFile.getText(unwrapped))
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function truncateSourceText(text, maxLength = 200) {
|
|
265
|
+
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
266
|
+
if (trimmed.length <= maxLength) return trimmed;
|
|
267
|
+
return `${trimmed.slice(0, maxLength - 3)}...`;
|
|
268
|
+
}
|
|
269
|
+
function isIgnoredImport(moduleSpecifier) {
|
|
270
|
+
if (IGNORED_IMPORT_SPECIFIERS.has(moduleSpecifier)) return true;
|
|
271
|
+
return IGNORED_IMPORT_PREFIXES.some((prefix) => moduleSpecifier.startsWith(prefix));
|
|
272
|
+
}
|
|
273
|
+
const CORE_IMPORT_PREFIX$1 = "@keystroke/workflow-core";
|
|
274
|
+
function analyzeCredentials(sourceFile) {
|
|
275
|
+
const localCredentialSetIds = collectLocalCredentialSetIds(sourceFile, collectImportedCoreSymbols$1(sourceFile));
|
|
276
|
+
const credentials = /* @__PURE__ */ new Map();
|
|
277
|
+
for (const statement of sourceFile.statements) for (const declaration of getVariableDeclarators(statement)) {
|
|
278
|
+
if (!isNode(declaration.init)) continue;
|
|
279
|
+
collectCredentialRefsFromExpression(declaration.init, localCredentialSetIds, credentials);
|
|
280
|
+
}
|
|
281
|
+
return Array.from(credentials.values()).sort((left, right) => left.resolvedId.localeCompare(right.resolvedId));
|
|
282
|
+
}
|
|
283
|
+
function collectImportedCoreSymbols$1(sourceFile) {
|
|
284
|
+
const importedSymbols = new Map([["CredentialSet", "CredentialSet"]]);
|
|
285
|
+
for (const statement of sourceFile.statements) {
|
|
286
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
287
|
+
if (!literalString(statement.source)?.startsWith(CORE_IMPORT_PREFIX$1) || !Array.isArray(statement.specifiers)) continue;
|
|
288
|
+
for (const element of statement.specifiers) {
|
|
289
|
+
if (!isNode(element) || element.type !== "ImportSpecifier") continue;
|
|
290
|
+
const localName = identifierName(element.local);
|
|
291
|
+
const importedName = identifierName(element.imported);
|
|
292
|
+
if (localName && importedName) importedSymbols.set(localName, importedName);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return importedSymbols;
|
|
296
|
+
}
|
|
297
|
+
function collectLocalCredentialSetIds(sourceFile, importedCoreSymbols) {
|
|
298
|
+
const credentialSetIds = /* @__PURE__ */ new Map();
|
|
299
|
+
for (const statement of sourceFile.statements) for (const declaration of getVariableDeclarators(statement)) {
|
|
300
|
+
const localName = identifierName(declaration.id);
|
|
301
|
+
if (!localName || !isNode(declaration.init)) continue;
|
|
302
|
+
const initializer = unwrapExpression(declaration.init);
|
|
303
|
+
if (initializer.type !== "NewExpression") continue;
|
|
304
|
+
const calleeName = identifierName(initializer.callee);
|
|
305
|
+
if (!calleeName || importedCoreSymbols.get(calleeName) !== "CredentialSet") continue;
|
|
306
|
+
const configObject = getFirstObjectArgument(initializer);
|
|
307
|
+
const credentialSetId = readStringProperty(configObject, "id");
|
|
308
|
+
const namespace = readStringProperty(configObject, "namespace");
|
|
309
|
+
if (namespace && RESERVED_NAMESPACES.includes(namespace)) throw new Error(`CredentialSet "${credentialSetId ?? localName}": namespace "${namespace}" is reserved for official Keystroke integrations. Use a different namespace or remove it.`);
|
|
310
|
+
if (credentialSetId) {
|
|
311
|
+
const resolvedId = namespace ? `${namespace}:${credentialSetId}` : credentialSetId;
|
|
312
|
+
credentialSetIds.set(localName, resolvedId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return credentialSetIds;
|
|
316
|
+
}
|
|
317
|
+
function collectCredentialRefsFromExpression(expression, localCredentialSetIds, credentials) {
|
|
318
|
+
const unwrapped = unwrapExpression(expression);
|
|
319
|
+
if (unwrapped.type === "NewExpression") {
|
|
320
|
+
addCredentialRefsFromConfigObject(getFirstObjectArgument(unwrapped), localCredentialSetIds, credentials);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (unwrapped.type === "CallExpression" && isNode(unwrapped.callee)) {
|
|
324
|
+
if (unwrapped.callee.type !== "MemberExpression") return;
|
|
325
|
+
for (const argument of Array.isArray(unwrapped.arguments) ? unwrapped.arguments : []) {
|
|
326
|
+
const objectArgument = isNode(argument) ? unwrapExpression(argument) : null;
|
|
327
|
+
if (objectArgument?.type === "ObjectExpression") addCredentialRefsFromConfigObject(objectArgument, localCredentialSetIds, credentials);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function addCredentialRefsFromConfigObject(objectLiteral, localCredentialSetIds, credentials) {
|
|
332
|
+
const credentialArray = unwrapArrayLiteral(readPropertyValue(objectLiteral, "credentialSets"));
|
|
333
|
+
if (!credentialArray) return;
|
|
334
|
+
for (const element of credentialArray.elements ?? []) {
|
|
335
|
+
if (!isNode(element)) continue;
|
|
336
|
+
const identifier = identifierName(element);
|
|
337
|
+
if (identifier) {
|
|
338
|
+
const resolvedId = localCredentialSetIds.get(identifier);
|
|
339
|
+
if (resolvedId) credentials.set(resolvedId, { resolvedId });
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const unwrapped = unwrapExpression(element);
|
|
343
|
+
if (unwrapped.type === "NewExpression") {
|
|
344
|
+
const configObject = getFirstObjectArgument(unwrapped);
|
|
345
|
+
const credentialSetId = readStringProperty(configObject, "id");
|
|
346
|
+
if (credentialSetId) {
|
|
347
|
+
const namespace = readStringProperty(configObject, "namespace");
|
|
348
|
+
const resolvedId = namespace ? `${namespace}:${credentialSetId}` : credentialSetId;
|
|
349
|
+
credentials.set(resolvedId, { resolvedId });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function unwrapArrayLiteral(expression) {
|
|
355
|
+
if (!expression) return null;
|
|
356
|
+
const unwrapped = unwrapExpression(expression);
|
|
357
|
+
return unwrapped.type === "ArrayExpression" && Array.isArray(unwrapped.elements) ? unwrapped : null;
|
|
358
|
+
}
|
|
359
|
+
function createSourceLocation(sourceFile, node, relativeFilePath) {
|
|
360
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.start);
|
|
361
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.end);
|
|
362
|
+
return {
|
|
363
|
+
filePath: relativeFilePath,
|
|
364
|
+
line: start.line + 1,
|
|
365
|
+
column: start.character + 1,
|
|
366
|
+
endLine: end.line + 1,
|
|
367
|
+
endColumn: end.character + 1
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
const CORE_IMPORT_PREFIX = "@keystroke/workflow-core";
|
|
371
|
+
const IMPORTED_CORE_SYMBOLS = new Set([
|
|
372
|
+
"Workflow",
|
|
373
|
+
"Step",
|
|
374
|
+
"Tool",
|
|
375
|
+
"Operation",
|
|
376
|
+
"Agent",
|
|
377
|
+
"CredentialSet",
|
|
378
|
+
"Sandbox",
|
|
379
|
+
"McpServer",
|
|
380
|
+
"CronTrigger",
|
|
381
|
+
"WebhookTrigger",
|
|
382
|
+
"PollingTrigger",
|
|
383
|
+
"cronTrigger",
|
|
384
|
+
"webhookTrigger",
|
|
385
|
+
"pollingTrigger"
|
|
386
|
+
]);
|
|
387
|
+
function analyzeDefinitions(sourceFile, relativeFilePath) {
|
|
388
|
+
const importedCoreSymbols = collectImportedCoreSymbols(sourceFile);
|
|
389
|
+
const importedTriggerBindings = collectImportedTriggerBindings(sourceFile);
|
|
390
|
+
const localDefinitions = /* @__PURE__ */ new Map();
|
|
391
|
+
const definitions = {
|
|
392
|
+
workflows: [],
|
|
393
|
+
operations: [],
|
|
394
|
+
agents: [],
|
|
395
|
+
triggers: { defined: [] },
|
|
396
|
+
credentialSets: [],
|
|
397
|
+
sandboxes: [],
|
|
398
|
+
mcpServers: []
|
|
399
|
+
};
|
|
400
|
+
for (const statement of sourceFile.statements) for (const declaration of getVariableDeclarators(statement)) {
|
|
401
|
+
const localName = identifierName(declaration.id);
|
|
402
|
+
if (!localName || !isNode(declaration.init)) continue;
|
|
403
|
+
const recognizedDefinition = getRecognizedDefinition(declaration.init, importedCoreSymbols, importedTriggerBindings);
|
|
404
|
+
if (!recognizedDefinition) continue;
|
|
405
|
+
localDefinitions.set(localName, recognizedDefinition);
|
|
406
|
+
if (!isExportedVariableStatement(statement)) continue;
|
|
407
|
+
addExportedDefinition({
|
|
408
|
+
definitions,
|
|
409
|
+
exportName: localName,
|
|
410
|
+
record: recognizedDefinition,
|
|
411
|
+
relativeFilePath,
|
|
412
|
+
sourceFile
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
for (const statement of sourceFile.statements) {
|
|
416
|
+
if (statement.type !== "ExportNamedDeclaration" || !Array.isArray(statement.specifiers)) continue;
|
|
417
|
+
for (const specifier of statement.specifiers) {
|
|
418
|
+
if (!isNode(specifier) || specifier.type !== "ExportSpecifier") continue;
|
|
419
|
+
const localName = identifierName(specifier.local);
|
|
420
|
+
const exportName = identifierName(specifier.exported);
|
|
421
|
+
if (!localName || !exportName) continue;
|
|
422
|
+
const localDefinition = localDefinitions.get(localName);
|
|
423
|
+
if (!localDefinition) continue;
|
|
424
|
+
addExportedDefinition({
|
|
425
|
+
definitions,
|
|
426
|
+
exportName,
|
|
427
|
+
record: localDefinition,
|
|
428
|
+
relativeFilePath,
|
|
429
|
+
sourceFile
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
for (const statement of sourceFile.statements) {
|
|
434
|
+
if (statement.type !== "ExportDefaultDeclaration" || !isNode(statement.declaration)) continue;
|
|
435
|
+
const record = getDefaultExportDefinition(statement.declaration, importedCoreSymbols, importedTriggerBindings, localDefinitions);
|
|
436
|
+
if (!record) continue;
|
|
437
|
+
addExportedDefinition({
|
|
438
|
+
definitions,
|
|
439
|
+
exportName: "default",
|
|
440
|
+
record,
|
|
441
|
+
relativeFilePath,
|
|
442
|
+
sourceFile
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
definitions,
|
|
447
|
+
localDefinitions
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function collectImportedCoreSymbols(sourceFile) {
|
|
451
|
+
const importedSymbols = new Map([...IMPORTED_CORE_SYMBOLS].map((symbol) => [symbol, symbol]));
|
|
452
|
+
for (const statement of sourceFile.statements) {
|
|
453
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
454
|
+
if (!literalString(statement.source)?.startsWith(CORE_IMPORT_PREFIX) || !Array.isArray(statement.specifiers)) continue;
|
|
455
|
+
for (const element of statement.specifiers) {
|
|
456
|
+
if (!isNode(element) || element.type !== "ImportSpecifier") continue;
|
|
457
|
+
const importedName = identifierName(element.imported);
|
|
458
|
+
const localName = identifierName(element.local);
|
|
459
|
+
if (!importedName || !localName || !IMPORTED_CORE_SYMBOLS.has(importedName)) continue;
|
|
460
|
+
importedSymbols.set(localName, importedName);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return importedSymbols;
|
|
464
|
+
}
|
|
465
|
+
function collectImportedTriggerBindings(sourceFile) {
|
|
466
|
+
const importedBindings = /* @__PURE__ */ new Map();
|
|
467
|
+
for (const statement of sourceFile.statements) {
|
|
468
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
469
|
+
const moduleSpecifier = literalString(statement.source);
|
|
470
|
+
if (!moduleSpecifier || !moduleSpecifier.endsWith("/triggers") && moduleSpecifier !== "./triggers" || !Array.isArray(statement.specifiers)) continue;
|
|
471
|
+
for (const element of statement.specifiers) {
|
|
472
|
+
if (!isNode(element)) continue;
|
|
473
|
+
const localName = element.type === "ImportNamespaceSpecifier" || element.type === "ImportSpecifier" ? identifierName(element.local) : void 0;
|
|
474
|
+
if (localName) importedBindings.set(localName, { moduleSpecifier });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return importedBindings;
|
|
478
|
+
}
|
|
479
|
+
function getRecognizedDefinition(expression, importedCoreSymbols, importedTriggerBindings) {
|
|
480
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
481
|
+
if (unwrappedExpression.type === "NewExpression") {
|
|
482
|
+
const callee = isNode(unwrappedExpression.callee) ? unwrappedExpression.callee : null;
|
|
483
|
+
const importedSymbol = callee ? importedCoreSymbols.get(identifierName(callee) ?? "") : void 0;
|
|
484
|
+
if (!importedSymbol || !callee) return null;
|
|
485
|
+
const configObject = getFirstObjectArgument(unwrappedExpression);
|
|
486
|
+
const displayName = readStringProperty(configObject, "name");
|
|
487
|
+
if (importedSymbol === "Workflow") return {
|
|
488
|
+
exportEntryKind: "workflow",
|
|
489
|
+
kind: "workflow",
|
|
490
|
+
name: displayName,
|
|
491
|
+
sourceNode: callee
|
|
492
|
+
};
|
|
493
|
+
if (importedSymbol === "Step" || importedSymbol === "Tool" || importedSymbol === "Operation") return {
|
|
494
|
+
exportEntryKind: "operation",
|
|
495
|
+
kind: "operation",
|
|
496
|
+
name: displayName,
|
|
497
|
+
sourceNode: callee
|
|
498
|
+
};
|
|
499
|
+
if (importedSymbol === "Agent") return {
|
|
500
|
+
exportEntryKind: "agent",
|
|
501
|
+
kind: "agent",
|
|
502
|
+
name: displayName,
|
|
503
|
+
sourceNode: callee
|
|
504
|
+
};
|
|
505
|
+
if (importedSymbol === "CredentialSet") {
|
|
506
|
+
const id = readStringProperty(configObject, "id");
|
|
507
|
+
const namespace = readStringProperty(configObject, "namespace");
|
|
508
|
+
return {
|
|
509
|
+
exportEntryKind: "credential-set",
|
|
510
|
+
kind: "credential-set",
|
|
511
|
+
name: displayName,
|
|
512
|
+
resolvedCredentialSetId: id ? namespace ? `${namespace}:${id}` : id : void 0,
|
|
513
|
+
sourceNode: callee
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
if (importedSymbol === "Sandbox") return {
|
|
517
|
+
exportEntryKind: "sandbox",
|
|
518
|
+
kind: "sandbox",
|
|
519
|
+
name: displayName,
|
|
520
|
+
sourceNode: callee
|
|
521
|
+
};
|
|
522
|
+
if (importedSymbol === "McpServer") return {
|
|
523
|
+
exportEntryKind: "mcp-server",
|
|
524
|
+
kind: "mcp-server",
|
|
525
|
+
name: displayName,
|
|
526
|
+
sourceNode: callee
|
|
527
|
+
};
|
|
528
|
+
if (importedSymbol === "CronTrigger" || importedSymbol === "WebhookTrigger" || importedSymbol === "PollingTrigger") return {
|
|
529
|
+
exportEntryKind: "trigger",
|
|
530
|
+
kind: "trigger",
|
|
531
|
+
name: displayName,
|
|
532
|
+
triggerType: importedSymbol === "CronTrigger" ? "cron" : importedSymbol === "WebhookTrigger" ? "webhook" : "polling",
|
|
533
|
+
sourceNode: callee
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (unwrappedExpression.type === "CallExpression") {
|
|
537
|
+
const callee = isNode(unwrappedExpression.callee) ? unwrappedExpression.callee : null;
|
|
538
|
+
const importedSymbol = callee ? importedCoreSymbols.get(identifierName(callee) ?? "") : void 0;
|
|
539
|
+
if (callee && (importedSymbol === "cronTrigger" || importedSymbol === "webhookTrigger" || importedSymbol === "pollingTrigger")) return {
|
|
540
|
+
exportEntryKind: "trigger",
|
|
541
|
+
kind: "trigger",
|
|
542
|
+
name: readStringProperty(getFirstObjectArgument(unwrappedExpression), "name"),
|
|
543
|
+
triggerType: importedSymbol === "cronTrigger" ? "cron" : importedSymbol === "webhookTrigger" ? "webhook" : "polling",
|
|
544
|
+
sourceNode: callee
|
|
545
|
+
};
|
|
546
|
+
if (callee) {
|
|
547
|
+
const importedTriggerCall = getImportedTriggerCall(callee, importedTriggerBindings);
|
|
548
|
+
if (importedTriggerCall) return {
|
|
549
|
+
exportEntryKind: "trigger",
|
|
550
|
+
kind: "trigger",
|
|
551
|
+
name: readStringProperty(getFirstObjectArgument(unwrappedExpression), "name"),
|
|
552
|
+
triggerType: inferImportedTriggerType(importedTriggerCall),
|
|
553
|
+
sourceNode: callee
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
function getDefaultExportDefinition(expression, importedCoreSymbols, importedTriggerBindings, localDefinitions) {
|
|
560
|
+
const localName = identifierName(expression);
|
|
561
|
+
if (localName) return localDefinitions.get(localName) ?? null;
|
|
562
|
+
return getRecognizedDefinition(expression, importedCoreSymbols, importedTriggerBindings);
|
|
563
|
+
}
|
|
564
|
+
function getImportedTriggerCall(expression, importedTriggerBindings) {
|
|
565
|
+
const directBindingName = identifierName(expression);
|
|
566
|
+
if (directBindingName) {
|
|
567
|
+
const importedBinding = importedTriggerBindings.get(directBindingName);
|
|
568
|
+
return importedBinding ? {
|
|
569
|
+
bindingName: directBindingName,
|
|
570
|
+
moduleSpecifier: importedBinding.moduleSpecifier
|
|
571
|
+
} : null;
|
|
572
|
+
}
|
|
573
|
+
if (expression.type === "MemberExpression" && expression.computed === false) {
|
|
574
|
+
const objectName = identifierName(expression.object);
|
|
575
|
+
const memberName = identifierName(expression.property);
|
|
576
|
+
const importedBinding = objectName ? importedTriggerBindings.get(objectName) : void 0;
|
|
577
|
+
if (objectName && importedBinding) return {
|
|
578
|
+
bindingName: objectName,
|
|
579
|
+
...memberName ? { memberName } : {},
|
|
580
|
+
moduleSpecifier: importedBinding.moduleSpecifier
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
function inferImportedTriggerType(triggerCall) {
|
|
586
|
+
const bindingName = triggerCall.bindingName.toLowerCase();
|
|
587
|
+
const memberName = triggerCall.memberName?.toLowerCase();
|
|
588
|
+
if (bindingName === "polling" || memberName?.includes("poll")) return "polling";
|
|
589
|
+
return "webhook";
|
|
590
|
+
}
|
|
591
|
+
function addExportedDefinition(options) {
|
|
592
|
+
const source = createSourceLocation(options.sourceFile, options.record.sourceNode, options.relativeFilePath);
|
|
593
|
+
if (options.record.kind === "workflow") {
|
|
594
|
+
options.definitions.workflows.push({
|
|
595
|
+
name: options.record.name ?? options.exportName,
|
|
596
|
+
exportName: options.exportName,
|
|
597
|
+
source
|
|
598
|
+
});
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (options.record.kind === "operation") {
|
|
602
|
+
options.definitions.operations.push({
|
|
603
|
+
name: options.record.name ?? options.exportName,
|
|
604
|
+
exportName: options.exportName,
|
|
605
|
+
source,
|
|
606
|
+
...options.record.isBundle ? { isBundle: true } : {}
|
|
607
|
+
});
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (options.record.kind === "agent") {
|
|
611
|
+
options.definitions.agents.push({
|
|
612
|
+
agentName: options.record.name ?? options.exportName,
|
|
613
|
+
exportName: options.exportName,
|
|
614
|
+
source
|
|
615
|
+
});
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (options.record.kind === "credential-set") {
|
|
619
|
+
options.definitions.credentialSets.push({
|
|
620
|
+
credentialSetName: options.record.name ?? options.exportName,
|
|
621
|
+
...options.record.resolvedCredentialSetId ? { resolvedCredentialSetId: options.record.resolvedCredentialSetId } : {},
|
|
622
|
+
exportName: options.exportName,
|
|
623
|
+
source
|
|
624
|
+
});
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (options.record.kind === "sandbox") {
|
|
628
|
+
options.definitions.sandboxes.push({
|
|
629
|
+
sandboxName: options.record.name ?? options.exportName,
|
|
630
|
+
exportName: options.exportName,
|
|
631
|
+
source
|
|
632
|
+
});
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (options.record.kind === "mcp-server") {
|
|
636
|
+
options.definitions.mcpServers.push({
|
|
637
|
+
mcpServerName: options.record.name ?? options.exportName,
|
|
638
|
+
exportName: options.exportName,
|
|
639
|
+
source
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (options.record.kind === "trigger") options.definitions.triggers.defined.push({
|
|
644
|
+
name: options.record.name,
|
|
645
|
+
triggerType: options.record.triggerType ?? "webhook",
|
|
646
|
+
exportName: options.exportName,
|
|
647
|
+
source
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
function analyzeExports(sourceFile, localDefinitions) {
|
|
651
|
+
const exports = /* @__PURE__ */ new Map();
|
|
652
|
+
for (const statement of sourceFile.statements) {
|
|
653
|
+
if (isExportedVariableStatement(statement)) {
|
|
654
|
+
for (const declaration of getVariableDeclarators(statement)) {
|
|
655
|
+
const localName = identifierName(declaration.id);
|
|
656
|
+
if (!localName) continue;
|
|
657
|
+
const kind = localDefinitions.get(localName)?.exportEntryKind ?? "other";
|
|
658
|
+
exports.set(localName, kind);
|
|
659
|
+
}
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (statement.type === "ExportAllDeclaration") {
|
|
663
|
+
const namespaceExportName = identifierName(statement.exported);
|
|
664
|
+
if (namespaceExportName) {
|
|
665
|
+
exports.set(namespaceExportName, "other");
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (statement.type === "ExportNamedDeclaration" && Array.isArray(statement.specifiers)) {
|
|
670
|
+
for (const specifier of statement.specifiers) {
|
|
671
|
+
if (!isNode(specifier) || specifier.type !== "ExportSpecifier") continue;
|
|
672
|
+
const localName = identifierName(specifier.local);
|
|
673
|
+
const exportName = identifierName(specifier.exported);
|
|
674
|
+
if (!localName || !exportName) continue;
|
|
675
|
+
const kind = localDefinitions.get(localName)?.exportEntryKind ?? "other";
|
|
676
|
+
exports.set(exportName, kind);
|
|
677
|
+
}
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (statement.type === "ExportDefaultDeclaration") {
|
|
681
|
+
const localName = identifierName(statement.declaration);
|
|
682
|
+
const kind = localName ? localDefinitions.get(localName)?.exportEntryKind : void 0;
|
|
683
|
+
exports.set("default", kind ?? "other");
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return Array.from(exports.entries()).map(([exportName, kind]) => ({
|
|
687
|
+
exportName,
|
|
688
|
+
kind
|
|
689
|
+
})).sort((left, right) => left.exportName.localeCompare(right.exportName));
|
|
690
|
+
}
|
|
691
|
+
async function analyzeImports(options) {
|
|
692
|
+
const imports = [];
|
|
693
|
+
for (const statement of options.sourceFile.statements) {
|
|
694
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
695
|
+
const moduleSpecifier = literalString(statement.source);
|
|
696
|
+
if (!moduleSpecifier) continue;
|
|
697
|
+
const entry = {
|
|
698
|
+
moduleSpecifier,
|
|
699
|
+
importNames: []
|
|
700
|
+
};
|
|
701
|
+
for (const specifier of Array.isArray(statement.specifiers) ? statement.specifiers : []) {
|
|
702
|
+
if (!isNode(specifier)) continue;
|
|
703
|
+
if (specifier.type === "ImportDefaultSpecifier") {
|
|
704
|
+
const localName = identifierName(specifier.local);
|
|
705
|
+
if (localName) entry.importNames.push(localName);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (specifier.type === "ImportSpecifier") {
|
|
709
|
+
const importedName = identifierName(specifier.imported);
|
|
710
|
+
if (importedName) entry.importNames.push(importedName);
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (specifier.type === "ImportNamespaceSpecifier") {
|
|
714
|
+
const namespaceName = identifierName(specifier.local);
|
|
715
|
+
if (namespaceName) {
|
|
716
|
+
entry.isNamespaceImport = true;
|
|
717
|
+
entry.namespaceName = namespaceName;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const localModuleSpecifier = getLocalModuleSpecifier(moduleSpecifier);
|
|
722
|
+
if (localModuleSpecifier) {
|
|
723
|
+
const resolvedPath = await resolveLocalModulePath(options.filePath, localModuleSpecifier);
|
|
724
|
+
if (resolvedPath) entry.resolvedPath = toRelativeFilePath(options.projectRoot, resolvedPath);
|
|
725
|
+
}
|
|
726
|
+
entry.importNames.sort((left, right) => left.localeCompare(right));
|
|
727
|
+
imports.push(entry);
|
|
728
|
+
}
|
|
729
|
+
return imports.sort(compareImportEntries);
|
|
730
|
+
}
|
|
731
|
+
function compareImportEntries(left, right) {
|
|
732
|
+
return left.moduleSpecifier.localeCompare(right.moduleSpecifier);
|
|
733
|
+
}
|
|
734
|
+
function analyzeWorkflowDelegations(options) {
|
|
735
|
+
if (options.workflowDefinitions.length === 0) return [...options.workflowDefinitions];
|
|
736
|
+
const importedBindings = collectImportedBindings(options.sourceFile, options.imports);
|
|
737
|
+
const localDefinitionInfo = collectLocalDefinitionInfo(options.sourceFile, options.localDefinitions);
|
|
738
|
+
return options.workflowDefinitions.map((workflowDefinition) => {
|
|
739
|
+
const runNode = findWorkflowRunFunction(options.sourceFile, workflowDefinition.exportName);
|
|
740
|
+
if (!runNode) return workflowDefinition;
|
|
741
|
+
const delegatedAgents = /* @__PURE__ */ new Map();
|
|
742
|
+
const delegatedWorkflows = /* @__PURE__ */ new Map();
|
|
743
|
+
visitNode(runNode, (node) => {
|
|
744
|
+
if (node.type !== "CallExpression" || !isNode(node.callee)) return;
|
|
745
|
+
const callee = node.callee;
|
|
746
|
+
if (callee.type !== "MemberExpression" || callee.computed !== false) return;
|
|
747
|
+
if (identifierName(callee.property) !== "run") return;
|
|
748
|
+
const bindingName = identifierName(callee.object);
|
|
749
|
+
if (!bindingName) return;
|
|
750
|
+
const imported = importedBindings.get(bindingName);
|
|
751
|
+
const local = localDefinitionInfo.get(bindingName);
|
|
752
|
+
if (imported?.resolvedPath?.endsWith(".agent.ts")) delegatedAgents.set(bindingName, {
|
|
753
|
+
exportName: imported.importedName,
|
|
754
|
+
agentName: imported.importedName,
|
|
755
|
+
sourceFilePath: imported.resolvedPath
|
|
756
|
+
});
|
|
757
|
+
else if (imported?.resolvedPath?.endsWith(".workflow.ts")) delegatedWorkflows.set(bindingName, {
|
|
758
|
+
exportName: imported.importedName,
|
|
759
|
+
workflowName: imported.importedName,
|
|
760
|
+
sourceFilePath: imported.resolvedPath
|
|
761
|
+
});
|
|
762
|
+
else if (local?.agentId || (options.localDefinitions.get(bindingName)?.kind ?? "") === "agent") delegatedAgents.set(bindingName, {
|
|
763
|
+
exportName: bindingName,
|
|
764
|
+
authoredAgentId: local?.agentId,
|
|
765
|
+
agentName: local?.name ?? bindingName,
|
|
766
|
+
...local?.sourceFilePath ? { sourceFilePath: local.sourceFilePath } : {}
|
|
767
|
+
});
|
|
768
|
+
else if (local?.workflowId || (options.localDefinitions.get(bindingName)?.kind ?? "") === "workflow") delegatedWorkflows.set(bindingName, {
|
|
769
|
+
exportName: bindingName,
|
|
770
|
+
authoredWorkflowId: local?.workflowId,
|
|
771
|
+
workflowName: local?.name ?? bindingName,
|
|
772
|
+
...local?.sourceFilePath ? { sourceFilePath: local.sourceFilePath } : {}
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
return {
|
|
776
|
+
...workflowDefinition,
|
|
777
|
+
...delegatedAgents.size > 0 ? { delegatedAgents: [...delegatedAgents.values()].sort((a, b) => a.exportName.localeCompare(b.exportName)) } : {},
|
|
778
|
+
...delegatedWorkflows.size > 0 ? { delegatedWorkflows: [...delegatedWorkflows.values()].sort((a, b) => a.exportName.localeCompare(b.exportName)) } : {}
|
|
779
|
+
};
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
function collectImportedBindings(sourceFile, imports) {
|
|
783
|
+
const importsByModule = new Map(imports.map((entry) => [entry.moduleSpecifier, entry]));
|
|
784
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
785
|
+
for (const statement of sourceFile.statements) {
|
|
786
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
787
|
+
const moduleSpecifier = literalString(statement.source);
|
|
788
|
+
const entry = moduleSpecifier ? importsByModule.get(moduleSpecifier) : void 0;
|
|
789
|
+
if (!entry?.resolvedPath || !Array.isArray(statement.specifiers)) continue;
|
|
790
|
+
for (const specifier of statement.specifiers) {
|
|
791
|
+
if (!isNode(specifier)) continue;
|
|
792
|
+
if (specifier.type === "ImportDefaultSpecifier") {
|
|
793
|
+
const localName = identifierName(specifier.local);
|
|
794
|
+
if (localName) bindings.set(localName, {
|
|
795
|
+
localName,
|
|
796
|
+
importedName: "default",
|
|
797
|
+
resolvedPath: entry.resolvedPath
|
|
798
|
+
});
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
if (specifier.type === "ImportSpecifier") {
|
|
802
|
+
const localName = identifierName(specifier.local);
|
|
803
|
+
const importedName = identifierName(specifier.imported);
|
|
804
|
+
if (localName && importedName) bindings.set(localName, {
|
|
805
|
+
localName,
|
|
806
|
+
importedName,
|
|
807
|
+
resolvedPath: entry.resolvedPath
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return bindings;
|
|
813
|
+
}
|
|
814
|
+
function collectLocalDefinitionInfo(sourceFile, localDefinitions) {
|
|
815
|
+
const info = /* @__PURE__ */ new Map();
|
|
816
|
+
for (const statement of sourceFile.statements) for (const declaration of getVariableDeclarators(statement)) {
|
|
817
|
+
const localName = identifierName(declaration.id);
|
|
818
|
+
if (!localName || !isNode(declaration.init)) continue;
|
|
819
|
+
const local = localDefinitions.get(localName);
|
|
820
|
+
if (!local) continue;
|
|
821
|
+
const objectLiteral = getConfigObject(declaration.init);
|
|
822
|
+
info.set(localName, {
|
|
823
|
+
name: readStringProperty(objectLiteral, "name") ?? local.name ?? localName,
|
|
824
|
+
workflowId: local.kind === "workflow" ? readStringProperty(objectLiteral, "id") : void 0,
|
|
825
|
+
agentId: local.kind === "agent" ? readStringProperty(objectLiteral, "id") : void 0
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
return info;
|
|
829
|
+
}
|
|
830
|
+
function findWorkflowRunFunction(sourceFile, exportName) {
|
|
831
|
+
for (const statement of sourceFile.statements) for (const declaration of getVariableDeclarators(statement)) {
|
|
832
|
+
if (identifierName(declaration.id) !== exportName || !isNode(declaration.init)) continue;
|
|
833
|
+
const objectLiteral = getConfigObject(declaration.init);
|
|
834
|
+
if (!objectLiteral) return null;
|
|
835
|
+
const runValue = objectLiteral.properties.filter((property) => isNode(property)).find((property) => property.type === "Property" && identifierName(property.key) === "run")?.value;
|
|
836
|
+
return isNode(runValue) && (runValue.type === "ArrowFunctionExpression" || runValue.type === "FunctionExpression") ? runValue : null;
|
|
837
|
+
}
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
function getConfigObject(expression) {
|
|
841
|
+
return getFirstObjectArgument(expression);
|
|
842
|
+
}
|
|
843
|
+
function computeSourceChecksum(source) {
|
|
844
|
+
return sha256String(source);
|
|
845
|
+
}
|
|
846
|
+
async function analyzeFile(options) {
|
|
847
|
+
const source = await readFile(options.filePath, "utf-8");
|
|
848
|
+
const relativeFilePath = options.relativeFilePath ?? toRelativeFilePath(options.projectRoot, options.filePath);
|
|
849
|
+
const sourceFile = parseSourceFile(options.filePath, source);
|
|
850
|
+
const definitionAnalysis = analyzeDefinitions(sourceFile, relativeFilePath);
|
|
851
|
+
const imports = await analyzeImports({
|
|
852
|
+
sourceFile,
|
|
853
|
+
filePath: options.filePath,
|
|
854
|
+
projectRoot: options.projectRoot
|
|
855
|
+
});
|
|
856
|
+
const exports = analyzeExports(sourceFile, definitionAnalysis.localDefinitions);
|
|
857
|
+
const credentials = analyzeCredentials(sourceFile);
|
|
858
|
+
const capturedVariablesByDefinition = analyzeCapturedVariables(sourceFile, definitionAnalysis, relativeFilePath, imports);
|
|
859
|
+
const definitions = {
|
|
860
|
+
...definitionAnalysis.definitions,
|
|
861
|
+
workflows: analyzeWorkflowDelegations({
|
|
862
|
+
sourceFile,
|
|
863
|
+
imports,
|
|
864
|
+
workflowDefinitions: definitionAnalysis.definitions.workflows,
|
|
865
|
+
localDefinitions: definitionAnalysis.localDefinitions
|
|
866
|
+
})
|
|
867
|
+
};
|
|
868
|
+
return {
|
|
869
|
+
filePath: relativeFilePath,
|
|
870
|
+
sourceChecksum: computeSourceChecksum(source),
|
|
871
|
+
definitions,
|
|
872
|
+
imports,
|
|
873
|
+
exports,
|
|
874
|
+
credentials,
|
|
875
|
+
capturedVariablesByDefinition
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
async function restoreMetadataIndexFromCache(options) {
|
|
879
|
+
const restoredIndex = Object.create(null);
|
|
880
|
+
let cacheHits = 0;
|
|
881
|
+
let cacheMisses = 0;
|
|
882
|
+
let invalidated = false;
|
|
883
|
+
for (const filePath of Object.keys(options.index).sort((left, right) => left.localeCompare(right)).map(parseRelativeFilePath)) {
|
|
884
|
+
const metadata = options.index[filePath];
|
|
885
|
+
if (!metadata) continue;
|
|
886
|
+
const metadataHash = computeMetadataHash(metadata);
|
|
887
|
+
const cachedEntry = options.cacheEntries[filePath];
|
|
888
|
+
if (cachedEntry && cachedEntry.sourceChecksum === metadata.buildInfo.sourceChecksum && cachedEntry.metadataHash === metadataHash && cachedEntry.outputFile === createMetadataOutputFile(filePath)) {
|
|
889
|
+
const cachedMetadata = await loadCachedMetadataEntry(options.outputDir, cachedEntry);
|
|
890
|
+
if (cachedMetadata) {
|
|
891
|
+
restoredIndex[filePath] = cachedMetadata;
|
|
892
|
+
cacheHits += 1;
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
invalidated = true;
|
|
896
|
+
}
|
|
897
|
+
restoredIndex[filePath] = metadata;
|
|
898
|
+
cacheMisses += 1;
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
index: restoredIndex,
|
|
902
|
+
cacheHits,
|
|
903
|
+
cacheMisses,
|
|
904
|
+
invalidated
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
async function loadPreservedMetadataIndex(options) {
|
|
908
|
+
const preservedIndex = Object.create(null);
|
|
909
|
+
for (const filePath of Object.keys(options.cacheEntries).sort((left, right) => left.localeCompare(right)).map(parseRelativeFilePath)) {
|
|
910
|
+
if (options.excludePaths.has(filePath)) continue;
|
|
911
|
+
const cachedEntry = options.cacheEntries[filePath];
|
|
912
|
+
if (!cachedEntry) continue;
|
|
913
|
+
const cachedMetadata = await loadCachedMetadataEntry(options.outputDir, cachedEntry);
|
|
914
|
+
if (cachedMetadata) preservedIndex[filePath] = cachedMetadata;
|
|
915
|
+
}
|
|
916
|
+
return preservedIndex;
|
|
917
|
+
}
|
|
918
|
+
function createMetadataCacheEntries(index) {
|
|
919
|
+
const entries = Object.keys(index).sort((left, right) => left.localeCompare(right));
|
|
920
|
+
return Object.fromEntries(entries.map((filePath) => {
|
|
921
|
+
const parsed = parseRelativeFilePath(filePath);
|
|
922
|
+
const metadata = index[parsed];
|
|
923
|
+
if (!metadata) throw new Error(`Expected metadata for ${filePath}`);
|
|
924
|
+
return [filePath, {
|
|
925
|
+
filePath,
|
|
926
|
+
sourceChecksum: metadata.buildInfo.sourceChecksum,
|
|
927
|
+
metadataHash: computeMetadataHash(metadata),
|
|
928
|
+
outputFile: createMetadataOutputFile(parsed),
|
|
929
|
+
updatedAt: metadata.buildInfo.generatedAt,
|
|
930
|
+
crossReferenceFingerprint: computeCrossReferenceFingerprint(metadata)
|
|
931
|
+
}];
|
|
932
|
+
}));
|
|
933
|
+
}
|
|
934
|
+
function computeMetadataHash(metadata) {
|
|
935
|
+
return sha256String(JSON.stringify(normalizeMetadataForHash(metadata)));
|
|
936
|
+
}
|
|
937
|
+
async function loadCachedMetadataEntry(outputDir, cacheEntry) {
|
|
938
|
+
try {
|
|
939
|
+
const parsed = JSON.parse(await readFile(getFileMetadataPath(outputDir, cacheEntry.filePath), "utf-8"));
|
|
940
|
+
const result = FileMetadataSchema.safeParse(parsed);
|
|
941
|
+
if (!result.success) return null;
|
|
942
|
+
if (computeMetadataHash(result.data) !== cacheEntry.metadataHash) return null;
|
|
943
|
+
return result.data;
|
|
944
|
+
} catch {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function computeCrossReferenceFingerprint(metadata) {
|
|
949
|
+
const payload = {
|
|
950
|
+
workflows: metadata.definitions.workflows.map((workflow) => ({
|
|
951
|
+
exportName: workflow.exportName,
|
|
952
|
+
workflowId: workflow.workflowId,
|
|
953
|
+
description: workflow.description
|
|
954
|
+
})),
|
|
955
|
+
stepsConsumedBy: metadata.consumption.stepsConsumedBy
|
|
956
|
+
};
|
|
957
|
+
return sha256String(JSON.stringify(payload));
|
|
958
|
+
}
|
|
959
|
+
function normalizeMetadataForHash(metadata) {
|
|
960
|
+
return {
|
|
961
|
+
...metadata,
|
|
962
|
+
buildInfo: {
|
|
963
|
+
...metadata.buildInfo,
|
|
964
|
+
generatedAt: ""
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
const METADATA_FILE_GLOBS = [
|
|
969
|
+
"**/*.ts",
|
|
970
|
+
"**/*.tsx",
|
|
971
|
+
"**/*.mts"
|
|
972
|
+
];
|
|
973
|
+
const METADATA_FILE_GLOBS_WITHOUT_MTS = ["**/*.ts", "**/*.tsx"];
|
|
974
|
+
const METADATA_IGNORE = [
|
|
975
|
+
...BASE_IGNORE_PATTERNS,
|
|
976
|
+
"**/tmp/**",
|
|
977
|
+
"**/temp/**"
|
|
978
|
+
];
|
|
979
|
+
async function collectMetadataFiles(options) {
|
|
980
|
+
const resolvedProjectRoot = path.resolve(options.projectRoot);
|
|
981
|
+
const resolvedOutDir = options.outDir ? path.resolve(resolvedProjectRoot, options.outDir) : path.join(resolvedProjectRoot, BUILD_OUTPUT_DIR_NAME);
|
|
982
|
+
const relativeOutDir = path.relative(resolvedProjectRoot, resolvedOutDir).replaceAll("\\", "/");
|
|
983
|
+
const ignore = [...METADATA_IGNORE];
|
|
984
|
+
if (relativeOutDir.length > 0 && relativeOutDir !== "." && !relativeOutDir.startsWith("../") && !path.isAbsolute(relativeOutDir)) ignore.push(`${relativeOutDir}/**`);
|
|
985
|
+
const absolutePaths = await (0, import_out.default)(options.includeMts === false ? METADATA_FILE_GLOBS_WITHOUT_MTS : METADATA_FILE_GLOBS, {
|
|
986
|
+
cwd: resolvedProjectRoot,
|
|
987
|
+
absolute: true,
|
|
988
|
+
onlyFiles: true,
|
|
989
|
+
ignore
|
|
990
|
+
});
|
|
991
|
+
absolutePaths.sort((left, right) => left.localeCompare(right));
|
|
992
|
+
return absolutePaths.map((absolutePath) => ({
|
|
993
|
+
absolutePath,
|
|
994
|
+
relativePath: toRelativeFilePath(resolvedProjectRoot, absolutePath)
|
|
995
|
+
}));
|
|
996
|
+
}
|
|
997
|
+
function buildStepConsumptionIndex(options) {
|
|
998
|
+
const index = /* @__PURE__ */ new Map();
|
|
999
|
+
for (const artifact of options.artifacts) {
|
|
1000
|
+
const consumedInFile = toRelativeFilePath(options.projectRoot, artifact.workflow.resolvedFilePath);
|
|
1001
|
+
appendStepConsumptionEntries({
|
|
1002
|
+
index,
|
|
1003
|
+
flowGraph: artifact.flowGraph,
|
|
1004
|
+
workflowName: artifact.manifest.name,
|
|
1005
|
+
consumedInFile,
|
|
1006
|
+
stepNamesByFile: options.stepNamesByFile
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
for (const entries of index.values()) entries.sort((left, right) => {
|
|
1010
|
+
if (left.consumedInFile !== right.consumedInFile) return left.consumedInFile.localeCompare(right.consumedInFile);
|
|
1011
|
+
return left.nodeId.localeCompare(right.nodeId);
|
|
1012
|
+
});
|
|
1013
|
+
return index;
|
|
1014
|
+
}
|
|
1015
|
+
function appendStepConsumptionEntries(options) {
|
|
1016
|
+
for (const node of options.flowGraph.nodes) {
|
|
1017
|
+
if (node.type !== "step" || node.data.kind !== "call") continue;
|
|
1018
|
+
const stepNodeData = node.data;
|
|
1019
|
+
const resolvedPath = stepNodeData.importSource?.resolvedPath;
|
|
1020
|
+
const stepName = stepNodeData.stepName;
|
|
1021
|
+
if (!resolvedPath || stepNodeData.callKind !== "workflow-step" || !stepName) continue;
|
|
1022
|
+
let normalizedResolvedPath;
|
|
1023
|
+
try {
|
|
1024
|
+
normalizedResolvedPath = normalizeRelativeFilePath(resolvedPath);
|
|
1025
|
+
} catch {
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
const knownStepNames = options.stepNamesByFile?.get(normalizedResolvedPath);
|
|
1029
|
+
if (knownStepNames && !knownStepNames.has(stepName)) continue;
|
|
1030
|
+
const entries = options.index.get(normalizedResolvedPath) ?? [];
|
|
1031
|
+
entries.push({
|
|
1032
|
+
stepName,
|
|
1033
|
+
consumedInFile: options.consumedInFile,
|
|
1034
|
+
workflowName: options.workflowName,
|
|
1035
|
+
nodeId: node.id
|
|
1036
|
+
});
|
|
1037
|
+
options.index.set(normalizedResolvedPath, entries);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
/** Example filename suffix for each convention (used in validation messages). */
|
|
1041
|
+
const CONVENTION_TO_EXAMPLE_SUFFIX = {
|
|
1042
|
+
workflow: ".workflow.ts",
|
|
1043
|
+
operation: ".operation.ts",
|
|
1044
|
+
agent: ".agent.ts",
|
|
1045
|
+
trigger: ".trigger.ts",
|
|
1046
|
+
"credential-set": ".credential-set.ts",
|
|
1047
|
+
sandbox: ".sandbox.ts",
|
|
1048
|
+
"mcp-server": ".mcp-server.ts"
|
|
1049
|
+
};
|
|
1050
|
+
const SUFFIX_TO_CONVENTION = [
|
|
1051
|
+
[".workflow.ts", "workflow"],
|
|
1052
|
+
[".workflow.tsx", "workflow"],
|
|
1053
|
+
[".workflow.mts", "workflow"],
|
|
1054
|
+
[".operation.ts", "operation"],
|
|
1055
|
+
[".operation.tsx", "operation"],
|
|
1056
|
+
[".operation.mts", "operation"],
|
|
1057
|
+
[".step.ts", "operation"],
|
|
1058
|
+
[".step.tsx", "operation"],
|
|
1059
|
+
[".step.mts", "operation"],
|
|
1060
|
+
[".trigger.ts", "trigger"],
|
|
1061
|
+
[".trigger.tsx", "trigger"],
|
|
1062
|
+
[".trigger.mts", "trigger"],
|
|
1063
|
+
[".agent.ts", "agent"],
|
|
1064
|
+
[".agent.tsx", "agent"],
|
|
1065
|
+
[".agent.mts", "agent"],
|
|
1066
|
+
[".tool.ts", "operation"],
|
|
1067
|
+
[".tool.tsx", "operation"],
|
|
1068
|
+
[".tool.mts", "operation"],
|
|
1069
|
+
[".credential-set.ts", "credential-set"],
|
|
1070
|
+
[".credential-set.tsx", "credential-set"],
|
|
1071
|
+
[".credential-set.mts", "credential-set"],
|
|
1072
|
+
[".sandbox.ts", "sandbox"],
|
|
1073
|
+
[".sandbox.tsx", "sandbox"],
|
|
1074
|
+
[".sandbox.mts", "sandbox"],
|
|
1075
|
+
[".mcp-server.ts", "mcp-server"],
|
|
1076
|
+
[".mcp-server.tsx", "mcp-server"],
|
|
1077
|
+
[".mcp-server.mts", "mcp-server"]
|
|
1078
|
+
];
|
|
1079
|
+
function detectFileConvention(filePath) {
|
|
1080
|
+
const lower = filePath.toLowerCase();
|
|
1081
|
+
for (const [suffix, convention] of SUFFIX_TO_CONVENTION) if (lower.endsWith(suffix)) return convention;
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
/** Returns a representative typed suffix for docs and validation hints (e.g. `.credential-set.ts`). */
|
|
1085
|
+
function conventionToExampleSuffix(convention) {
|
|
1086
|
+
return CONVENTION_TO_EXAMPLE_SUFFIX[convention];
|
|
1087
|
+
}
|
|
1088
|
+
function synthesizeMetadataIndex(options) {
|
|
1089
|
+
const baseIndex = Object.create(null);
|
|
1090
|
+
const operationNamesByFile = /* @__PURE__ */ new Map();
|
|
1091
|
+
const sortedFiles = [...options.analyzedFiles].sort((left, right) => left.filePath.localeCompare(right.filePath));
|
|
1092
|
+
for (const analyzedFile of sortedFiles) {
|
|
1093
|
+
operationNamesByFile.set(analyzedFile.filePath, new Set(analyzedFile.definitions.operations.map((op) => op.name)));
|
|
1094
|
+
const capturedVars = analyzedFile.capturedVariablesByDefinition;
|
|
1095
|
+
const definitions = enrichDefinitionsWithCapturedVariables(analyzedFile.definitions, capturedVars);
|
|
1096
|
+
const metadata = FileMetadataSchema.parse({
|
|
1097
|
+
schemaVersion: FILE_METADATA_SCHEMA_VERSION,
|
|
1098
|
+
filePath: analyzedFile.filePath,
|
|
1099
|
+
fileConvention: detectFileConvention(analyzedFile.filePath),
|
|
1100
|
+
definitions,
|
|
1101
|
+
consumption: { stepsConsumedBy: [] },
|
|
1102
|
+
credentials: analyzedFile.credentials,
|
|
1103
|
+
imports: analyzedFile.imports,
|
|
1104
|
+
exports: analyzedFile.exports,
|
|
1105
|
+
buildInfo: {
|
|
1106
|
+
generatedAt: options.generatedAt,
|
|
1107
|
+
sourceChecksum: analyzedFile.sourceChecksum
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
baseIndex[analyzedFile.filePath] = metadata;
|
|
1111
|
+
}
|
|
1112
|
+
enrichWorkflowDefinitions({
|
|
1113
|
+
index: baseIndex,
|
|
1114
|
+
artifacts: options.artifacts,
|
|
1115
|
+
projectRoot: options.projectRoot
|
|
1116
|
+
});
|
|
1117
|
+
const stepConsumptionIndex = buildStepConsumptionIndex({
|
|
1118
|
+
artifacts: options.artifacts,
|
|
1119
|
+
projectRoot: options.projectRoot,
|
|
1120
|
+
stepNamesByFile: operationNamesByFile
|
|
1121
|
+
});
|
|
1122
|
+
for (const [filePath, entries] of stepConsumptionIndex.entries()) {
|
|
1123
|
+
const existingMetadata = baseIndex[filePath];
|
|
1124
|
+
if (!existingMetadata) continue;
|
|
1125
|
+
baseIndex[filePath] = FileMetadataSchema.parse({
|
|
1126
|
+
...existingMetadata,
|
|
1127
|
+
consumption: { stepsConsumedBy: entries }
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
index: baseIndex,
|
|
1132
|
+
fileCount: sortedFiles.length
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
function enrichDefinitionsWithCapturedVariables(definitions, capturedVars) {
|
|
1136
|
+
if (capturedVars.size === 0) return definitions;
|
|
1137
|
+
return {
|
|
1138
|
+
...definitions,
|
|
1139
|
+
workflows: definitions.workflows.map((wf) => {
|
|
1140
|
+
const vars = capturedVars.get(wf.exportName);
|
|
1141
|
+
return vars?.length ? {
|
|
1142
|
+
...wf,
|
|
1143
|
+
capturedVariables: vars
|
|
1144
|
+
} : wf;
|
|
1145
|
+
}),
|
|
1146
|
+
operations: definitions.operations.map((op) => {
|
|
1147
|
+
const vars = capturedVars.get(op.exportName);
|
|
1148
|
+
return vars?.length ? {
|
|
1149
|
+
...op,
|
|
1150
|
+
capturedVariables: vars
|
|
1151
|
+
} : op;
|
|
1152
|
+
}),
|
|
1153
|
+
agents: definitions.agents.map((agent) => {
|
|
1154
|
+
const vars = capturedVars.get(agent.exportName);
|
|
1155
|
+
return vars?.length ? {
|
|
1156
|
+
...agent,
|
|
1157
|
+
capturedVariables: vars
|
|
1158
|
+
} : agent;
|
|
1159
|
+
})
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
function enrichWorkflowDefinitions(options) {
|
|
1163
|
+
for (const artifact of options.artifacts) {
|
|
1164
|
+
const relativeFilePath = toRelativeFilePath(options.projectRoot, artifact.workflow.resolvedFilePath);
|
|
1165
|
+
const fileMetadata = options.index[relativeFilePath];
|
|
1166
|
+
if (!fileMetadata) continue;
|
|
1167
|
+
const enrichedWorkflows = fileMetadata.definitions.workflows.map((definition) => {
|
|
1168
|
+
if (definition.exportName !== artifact.workflow.localExportName && definition.exportName !== artifact.workflow.exportName) return definition;
|
|
1169
|
+
return {
|
|
1170
|
+
...definition,
|
|
1171
|
+
workflowId: artifact.manifest.id,
|
|
1172
|
+
description: artifact.manifest.description
|
|
1173
|
+
};
|
|
1174
|
+
});
|
|
1175
|
+
options.index[fileMetadata.filePath] = FileMetadataSchema.parse({
|
|
1176
|
+
...fileMetadata,
|
|
1177
|
+
definitions: {
|
|
1178
|
+
...fileMetadata.definitions,
|
|
1179
|
+
workflows: enrichedWorkflows
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
const PRIMITIVE_COUNT_KEYS = [
|
|
1185
|
+
{
|
|
1186
|
+
convention: "workflow",
|
|
1187
|
+
countFn: (d) => d.workflows.length,
|
|
1188
|
+
label: "Workflow"
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
convention: "operation",
|
|
1192
|
+
countFn: (d) => d.operations.length,
|
|
1193
|
+
label: "Operation"
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
convention: "agent",
|
|
1197
|
+
countFn: (d) => d.agents.length,
|
|
1198
|
+
label: "Agent"
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
convention: "trigger",
|
|
1202
|
+
countFn: (d) => d.triggers.defined.length,
|
|
1203
|
+
label: "Trigger"
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
convention: "credential-set",
|
|
1207
|
+
countFn: (d) => d.credentialSets.length,
|
|
1208
|
+
label: "CredentialSet"
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
convention: "sandbox",
|
|
1212
|
+
countFn: (d) => d.sandboxes.length,
|
|
1213
|
+
label: "Sandbox"
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
convention: "mcp-server",
|
|
1217
|
+
countFn: (d) => d.mcpServers.length,
|
|
1218
|
+
label: "McpServer"
|
|
1219
|
+
}
|
|
1220
|
+
];
|
|
1221
|
+
function totalPrimitiveCount(defs) {
|
|
1222
|
+
return PRIMITIVE_COUNT_KEYS.reduce((sum, entry) => sum + entry.countFn(defs), 0);
|
|
1223
|
+
}
|
|
1224
|
+
function countForConvention(convention, defs) {
|
|
1225
|
+
const entry = PRIMITIVE_COUNT_KEYS.find((e) => e.convention === convention);
|
|
1226
|
+
return entry ? entry.countFn(defs) : 0;
|
|
1227
|
+
}
|
|
1228
|
+
function otherPrimitiveCount(convention, defs) {
|
|
1229
|
+
return totalPrimitiveCount(defs) - countForConvention(convention, defs);
|
|
1230
|
+
}
|
|
1231
|
+
function validateFileConventions(analyzedFiles) {
|
|
1232
|
+
const violations = [];
|
|
1233
|
+
for (const file of analyzedFiles) {
|
|
1234
|
+
const convention = detectFileConvention(file.filePath);
|
|
1235
|
+
const defs = file.definitions;
|
|
1236
|
+
if (convention) {
|
|
1237
|
+
const ownCount = countForConvention(convention, defs);
|
|
1238
|
+
if (ownCount > 1) violations.push({
|
|
1239
|
+
filePath: file.filePath,
|
|
1240
|
+
level: "error",
|
|
1241
|
+
message: `File "${file.filePath}" uses .${convention}.ts suffix but exports ${ownCount} ${convention} primitives. Expected exactly 1.`
|
|
1242
|
+
});
|
|
1243
|
+
if (ownCount === 0) violations.push({
|
|
1244
|
+
filePath: file.filePath,
|
|
1245
|
+
level: "warning",
|
|
1246
|
+
message: `File "${file.filePath}" uses .${convention}.ts suffix but exports no ${convention} primitives.`
|
|
1247
|
+
});
|
|
1248
|
+
if (otherPrimitiveCount(convention, defs) > 0) violations.push({
|
|
1249
|
+
filePath: file.filePath,
|
|
1250
|
+
level: "error",
|
|
1251
|
+
message: `File "${file.filePath}" uses .${convention}.ts suffix but also exports primitives of other types. Each primitive type belongs in its own file.`
|
|
1252
|
+
});
|
|
1253
|
+
} else if (totalPrimitiveCount(defs) > 0) {
|
|
1254
|
+
const activeEntries = PRIMITIVE_COUNT_KEYS.filter((entry) => entry.countFn(defs) > 0);
|
|
1255
|
+
const typeLabels = activeEntries.map((entry) => entry.label);
|
|
1256
|
+
const firstConvention = activeEntries[0]?.convention;
|
|
1257
|
+
const exampleSuffix = firstConvention ? conventionToExampleSuffix(firstConvention) : ".workflow.ts";
|
|
1258
|
+
violations.push({
|
|
1259
|
+
filePath: file.filePath,
|
|
1260
|
+
level: "warning",
|
|
1261
|
+
message: `File "${file.filePath}" exports ${typeLabels.join(", ")} primitive(s) but does not use a typed suffix. Consider renaming to use the appropriate suffix (e.g. *${exampleSuffix}).`
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
return violations;
|
|
1266
|
+
}
|
|
1267
|
+
async function writeMetadataIndex(index, outputDir) {
|
|
1268
|
+
const outputRoot = getMetadataRoot(outputDir);
|
|
1269
|
+
const filePaths = Object.keys(index).sort((left, right) => left.localeCompare(right)).map(parseRelativeFilePath);
|
|
1270
|
+
const desiredOutputFiles = filePaths.map((filePath) => createMetadataOutputFile(filePath));
|
|
1271
|
+
let writtenCount = 0;
|
|
1272
|
+
if (filePaths.length > 0) await mkdir(outputRoot, { recursive: true });
|
|
1273
|
+
for (const filePath of filePaths) {
|
|
1274
|
+
const metadata = FileMetadataSchema.parse(index[filePath]);
|
|
1275
|
+
const outputPath = getFileMetadataPath(outputDir, filePath);
|
|
1276
|
+
const serializedMetadata = `${JSON.stringify(metadata, null, 2)}\n`;
|
|
1277
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
1278
|
+
if (await readExistingMetadata(outputPath) === serializedMetadata) continue;
|
|
1279
|
+
await writeFile(outputPath, serializedMetadata, "utf-8");
|
|
1280
|
+
writtenCount += 1;
|
|
1281
|
+
}
|
|
1282
|
+
const cleanedCount = await cleanupStaleMetadataFiles(outputDir, desiredOutputFiles);
|
|
1283
|
+
return {
|
|
1284
|
+
fileCount: filePaths.length,
|
|
1285
|
+
writtenCount,
|
|
1286
|
+
cleanedCount,
|
|
1287
|
+
outputRoot,
|
|
1288
|
+
writtenPaths: desiredOutputFiles
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
async function readExistingMetadata(filePath) {
|
|
1292
|
+
try {
|
|
1293
|
+
return await readFile(filePath, "utf-8");
|
|
1294
|
+
} catch {
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
async function cleanupStaleMetadataFiles(outputDir, desiredOutputFiles) {
|
|
1299
|
+
const metadataRoot = getMetadataRoot(outputDir);
|
|
1300
|
+
const existingMetadataFiles = await (0, import_out.default)("**/*.json", {
|
|
1301
|
+
cwd: metadataRoot,
|
|
1302
|
+
onlyFiles: true,
|
|
1303
|
+
absolute: false,
|
|
1304
|
+
suppressErrors: true
|
|
1305
|
+
}).catch(() => []);
|
|
1306
|
+
const desiredFiles = new Set(desiredOutputFiles.map((filePath) => filePath.replace(/^metadata\//u, "")));
|
|
1307
|
+
let cleanedCount = 0;
|
|
1308
|
+
for (const relativeFilePath of existingMetadataFiles) {
|
|
1309
|
+
if (desiredFiles.has(relativeFilePath.replaceAll("\\", "/"))) continue;
|
|
1310
|
+
await rm(path.join(metadataRoot, relativeFilePath), { force: true });
|
|
1311
|
+
cleanedCount += 1;
|
|
1312
|
+
}
|
|
1313
|
+
await removeEmptyMetadataDirectories(metadataRoot);
|
|
1314
|
+
return cleanedCount;
|
|
1315
|
+
}
|
|
1316
|
+
const METADATA_ANALYZE_CONCURRENCY = 8;
|
|
1317
|
+
async function buildMetadataArtifacts(options) {
|
|
1318
|
+
const rebuildDecision = options.rebuildDecision ?? {
|
|
1319
|
+
action: "full-recompute",
|
|
1320
|
+
reason: "default-build"
|
|
1321
|
+
};
|
|
1322
|
+
const metadataPhaseStartedAt = performance.now();
|
|
1323
|
+
options.emit({
|
|
1324
|
+
phase: "metadata-start",
|
|
1325
|
+
fileCount: 0
|
|
1326
|
+
});
|
|
1327
|
+
const metadataFiles = rebuildDecision.action === "targeted" ? await resolveTargetedMetadataFiles(options.projectRoot, rebuildDecision.affectedPaths) : await collectMetadataFiles({
|
|
1328
|
+
projectRoot: options.projectRoot,
|
|
1329
|
+
outDir: options.outputDir,
|
|
1330
|
+
includeMts: true
|
|
1331
|
+
});
|
|
1332
|
+
options.emit({
|
|
1333
|
+
phase: "metadata-scan",
|
|
1334
|
+
filesProcessed: 0,
|
|
1335
|
+
filesTotal: metadataFiles.length
|
|
1336
|
+
});
|
|
1337
|
+
const analyzedFacts = [];
|
|
1338
|
+
for (let batchStart = 0; batchStart < metadataFiles.length; batchStart += METADATA_ANALYZE_CONCURRENCY) {
|
|
1339
|
+
const batch = metadataFiles.slice(batchStart, batchStart + METADATA_ANALYZE_CONCURRENCY);
|
|
1340
|
+
const batchResults = await Promise.all(batch.map((metadataFile) => analyzeFile({
|
|
1341
|
+
filePath: metadataFile.absolutePath,
|
|
1342
|
+
projectRoot: options.projectRoot,
|
|
1343
|
+
relativeFilePath: metadataFile.relativePath
|
|
1344
|
+
})));
|
|
1345
|
+
analyzedFacts.push(...batchResults);
|
|
1346
|
+
options.emit({
|
|
1347
|
+
phase: "metadata-scan",
|
|
1348
|
+
filesProcessed: Math.min(batchStart + batch.length, metadataFiles.length),
|
|
1349
|
+
filesTotal: metadataFiles.length
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
analyzedFacts.sort((left, right) => left.filePath.localeCompare(right.filePath));
|
|
1353
|
+
const conventionViolations = validateFileConventions(analyzedFacts);
|
|
1354
|
+
await new Promise((resolve) => {
|
|
1355
|
+
setImmediate(resolve);
|
|
1356
|
+
});
|
|
1357
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1358
|
+
const synthesisResult = synthesizeMetadataIndex({
|
|
1359
|
+
analyzedFiles: analyzedFacts,
|
|
1360
|
+
artifacts: options.artifacts,
|
|
1361
|
+
generatedAt,
|
|
1362
|
+
projectRoot: options.projectRoot
|
|
1363
|
+
});
|
|
1364
|
+
const restoredFromCache = await restoreMetadataIndexFromCache({
|
|
1365
|
+
index: rebuildDecision.action === "targeted" ? {
|
|
1366
|
+
...await loadPreservedMetadataIndex({
|
|
1367
|
+
outputDir: options.outputDir,
|
|
1368
|
+
cacheEntries: options.cacheEntries,
|
|
1369
|
+
excludePaths: new Set(rebuildDecision.affectedPaths)
|
|
1370
|
+
}),
|
|
1371
|
+
...synthesisResult.index
|
|
1372
|
+
} : synthesisResult.index,
|
|
1373
|
+
outputDir: options.outputDir,
|
|
1374
|
+
cacheEntries: options.cacheEntries
|
|
1375
|
+
});
|
|
1376
|
+
options.emit({
|
|
1377
|
+
phase: "metadata-cache-stats",
|
|
1378
|
+
cacheHits: restoredFromCache.cacheHits,
|
|
1379
|
+
cacheMisses: restoredFromCache.cacheMisses,
|
|
1380
|
+
invalidated: options.cacheInvalidated || restoredFromCache.invalidated
|
|
1381
|
+
});
|
|
1382
|
+
const metadataWriteStartedAt = performance.now();
|
|
1383
|
+
const writeResult = await writeMetadataIndex(restoredFromCache.index, options.outputDir);
|
|
1384
|
+
options.emit({
|
|
1385
|
+
phase: "metadata-complete",
|
|
1386
|
+
fileCount: Object.keys(restoredFromCache.index).length,
|
|
1387
|
+
elapsedMs: performance.now() - metadataPhaseStartedAt
|
|
1388
|
+
});
|
|
1389
|
+
options.emit({
|
|
1390
|
+
phase: "metadata-output-complete",
|
|
1391
|
+
writtenCount: writeResult.writtenCount,
|
|
1392
|
+
cleanedCount: writeResult.cleanedCount,
|
|
1393
|
+
elapsedMs: performance.now() - metadataWriteStartedAt
|
|
1394
|
+
});
|
|
1395
|
+
return {
|
|
1396
|
+
result: {
|
|
1397
|
+
fileCount: writeResult.fileCount,
|
|
1398
|
+
writtenCount: writeResult.writtenCount,
|
|
1399
|
+
cleanedCount: writeResult.cleanedCount,
|
|
1400
|
+
outputRoot: writeResult.outputRoot
|
|
1401
|
+
},
|
|
1402
|
+
cacheEntries: createMetadataCacheEntries(restoredFromCache.index),
|
|
1403
|
+
conventionViolations,
|
|
1404
|
+
analyzedFacts
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
async function resolveTargetedMetadataFiles(projectRoot, affectedPaths) {
|
|
1408
|
+
const files = [];
|
|
1409
|
+
for (const relativePath of affectedPaths) {
|
|
1410
|
+
const absolutePath = path.resolve(projectRoot, relativePath);
|
|
1411
|
+
try {
|
|
1412
|
+
if (!(await stat(absolutePath)).isFile()) continue;
|
|
1413
|
+
files.push({
|
|
1414
|
+
absolutePath,
|
|
1415
|
+
relativePath: parseRelativeFilePath(relativePath)
|
|
1416
|
+
});
|
|
1417
|
+
} catch {}
|
|
1418
|
+
}
|
|
1419
|
+
return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
1420
|
+
}
|
|
1421
|
+
//#endregion
|
|
1422
|
+
export { buildMetadataArtifacts };
|