@statelyai/sdk 0.7.1 → 0.8.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/README.md +11 -638
- package/dist/assetStorage.d.mts +1 -1
- package/dist/embed.d.mts +1 -1
- package/dist/embed.mjs +1 -1
- package/dist/graph.d.mts +1 -1
- package/dist/{graphToXStateTS-Gzh0ZqbN.mjs → graphToXStateTS-moihsH_U.mjs} +17 -16
- package/dist/index.d.mts +144 -5
- package/dist/index.mjs +2 -2
- package/dist/{inspect-YoEwfiKb.d.mts → inspect-BLlM3qKf.d.mts} +1 -1
- package/dist/inspect.d.mts +2 -2
- package/dist/inspect.mjs +1 -1
- package/dist/{protocol-s9zwsiCW.d.mts → protocol.d.mts} +2 -1
- package/dist/protocol.mjs +5 -0
- package/dist/sync.d.mts +1 -1
- package/dist/sync.mjs +1254 -3
- package/dist/{transport-C8UTS3Fa.mjs → transport-CVZGF0w9.mjs} +2 -4
- package/package.json +5 -18
- package/dist/cli.d.mts +0 -203
- package/dist/cli.mjs +0 -1760
- package/dist/sync-DLkTmSyA.mjs +0 -2513
- /package/dist/{graph-zuNj3kfa.d.mts → graph-CJ3N2r43.d.mts} +0 -0
package/dist/sync.mjs
CHANGED
|
@@ -1,5 +1,1256 @@
|
|
|
1
|
-
import "./studio.mjs";
|
|
2
|
-
import "./graphToXStateTS-
|
|
3
|
-
import {
|
|
1
|
+
import { createStatelyClient } from "./studio.mjs";
|
|
2
|
+
import { d as upsertStatelyPragma, l as findStatelyPragmaAttachments, t as graphToXStateTS, u as getStatelyPragma } from "./graphToXStateTS-moihsH_U.mjs";
|
|
3
|
+
import { fromStudioMachine, toStudioMachine } from "./graph.mjs";
|
|
4
|
+
import * as ts$1 from "typescript";
|
|
5
|
+
import ts from "typescript";
|
|
6
|
+
import { getDiff, isEmptyDiff } from "@statelyai/graph";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import * as fs$1 from "fs";
|
|
12
|
+
import * as path$1 from "path";
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
4
14
|
|
|
15
|
+
//#region ../editor-sync/src/fs.ts
|
|
16
|
+
function createTextDocument(fileName, text, uri = path$1.isAbsolute(fileName) ? pathToFileURL(fileName).toString() : fileName) {
|
|
17
|
+
const lineStarts = getLineStarts(text);
|
|
18
|
+
return {
|
|
19
|
+
fileName,
|
|
20
|
+
uri,
|
|
21
|
+
getText() {
|
|
22
|
+
return text;
|
|
23
|
+
},
|
|
24
|
+
positionAt(offset) {
|
|
25
|
+
const clampedOffset = Math.max(0, Math.min(offset, text.length));
|
|
26
|
+
let low = 0;
|
|
27
|
+
let high = lineStarts.length - 1;
|
|
28
|
+
while (low <= high) {
|
|
29
|
+
const mid = Math.floor((low + high) / 2);
|
|
30
|
+
const start = lineStarts[mid];
|
|
31
|
+
const nextStart = mid + 1 < lineStarts.length ? lineStarts[mid + 1] : text.length + 1;
|
|
32
|
+
if (clampedOffset < start) {
|
|
33
|
+
high = mid - 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (clampedOffset >= nextStart) {
|
|
37
|
+
low = mid + 1;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
line: mid,
|
|
42
|
+
character: clampedOffset - start
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
line: lineStarts.length - 1,
|
|
47
|
+
character: clampedOffset - lineStarts[lineStarts.length - 1]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function sourceUriToFileName(uriOrPath) {
|
|
53
|
+
if (uriOrPath.startsWith("file://")) return fileURLToPath(uriOrPath);
|
|
54
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(uriOrPath)) throw new Error(`Unsupported non-file URI: ${uriOrPath}`);
|
|
55
|
+
return path$1.resolve(uriOrPath);
|
|
56
|
+
}
|
|
57
|
+
function fileNameToUri(fileName) {
|
|
58
|
+
return pathToFileURL(path$1.resolve(fileName)).toString();
|
|
59
|
+
}
|
|
60
|
+
function createFileSourceDocumentResolver() {
|
|
61
|
+
return {
|
|
62
|
+
read(uri) {
|
|
63
|
+
return readFileSnapshot(sourceUriToFileName(uri));
|
|
64
|
+
},
|
|
65
|
+
resolveImport(fromDocument, specifier) {
|
|
66
|
+
const importedFileName = resolveImportPath(fromDocument.fileName, specifier);
|
|
67
|
+
return importedFileName ? readFileSnapshot(importedFileName) : null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function resolveImportPath(fromFileName, specifier) {
|
|
72
|
+
if (!specifier.startsWith(".")) return null;
|
|
73
|
+
const basePath = path$1.resolve(path$1.dirname(fromFileName), specifier);
|
|
74
|
+
return [
|
|
75
|
+
basePath,
|
|
76
|
+
`${basePath}.ts`,
|
|
77
|
+
`${basePath}.tsx`,
|
|
78
|
+
`${basePath}.js`,
|
|
79
|
+
`${basePath}.jsx`,
|
|
80
|
+
path$1.join(basePath, "index.ts"),
|
|
81
|
+
path$1.join(basePath, "index.tsx"),
|
|
82
|
+
path$1.join(basePath, "index.js"),
|
|
83
|
+
path$1.join(basePath, "index.jsx")
|
|
84
|
+
].find((candidate) => isReadableFile(candidate)) ?? null;
|
|
85
|
+
}
|
|
86
|
+
function isReadableFile(fileName) {
|
|
87
|
+
try {
|
|
88
|
+
return fs$1.statSync(fileName).isFile();
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function readFileSnapshot(fileName) {
|
|
94
|
+
try {
|
|
95
|
+
return {
|
|
96
|
+
fileName,
|
|
97
|
+
uri: fileNameToUri(fileName),
|
|
98
|
+
text: fs$1.readFileSync(fileName, "utf8")
|
|
99
|
+
};
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function getLineStarts(text) {
|
|
105
|
+
const lineStarts = [0];
|
|
106
|
+
for (let index = 0; index < text.length; index += 1) if (text.charCodeAt(index) === 10) lineStarts.push(index + 1);
|
|
107
|
+
return lineStarts;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region ../editor-sync/src/internal/sourceLocations.ts
|
|
112
|
+
const sourceFileUris = /* @__PURE__ */ new WeakMap();
|
|
113
|
+
function collectXStateSourceLocations(sourceText, document, options = {}) {
|
|
114
|
+
const sourceFile = ts$1.createSourceFile(document.fileName, sourceText, ts$1.ScriptTarget.Latest, true, getScriptKind$1(document.fileName));
|
|
115
|
+
const uri = document.uri?.toString() ?? document.fileName;
|
|
116
|
+
sourceFileUris.set(sourceFile, uri);
|
|
117
|
+
const bindings = collectBindings(sourceFile, uri, document.fileName, options);
|
|
118
|
+
const staticValues = collectStaticValueBindings(sourceFile);
|
|
119
|
+
const root = findMachineConfigObjectLiteral(sourceFile, bindings);
|
|
120
|
+
if (!root) return;
|
|
121
|
+
const states = [];
|
|
122
|
+
collectStateLocations(root, [], states, bindings, staticValues, root);
|
|
123
|
+
return {
|
|
124
|
+
version: 1,
|
|
125
|
+
root: {
|
|
126
|
+
uri,
|
|
127
|
+
range: toSourceRange(sourceFile, root)
|
|
128
|
+
},
|
|
129
|
+
states,
|
|
130
|
+
staticValues: collectStaticValueReferences(sourceFile, uri, root, staticValues)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function collectStaticValueBindings(sourceFile) {
|
|
134
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
135
|
+
for (const statement of sourceFile.statements) {
|
|
136
|
+
if (ts$1.isVariableStatement(statement)) {
|
|
137
|
+
if (!((statement.declarationList.flags & ts$1.NodeFlags.Const) === ts$1.NodeFlags.Const)) continue;
|
|
138
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
139
|
+
if (!ts$1.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
140
|
+
const value = getStaticStringValue(declaration.initializer);
|
|
141
|
+
if (value === null) continue;
|
|
142
|
+
bindings.set(declaration.name.text, {
|
|
143
|
+
kind: "const",
|
|
144
|
+
symbol: declaration.name.text,
|
|
145
|
+
value,
|
|
146
|
+
valueRange: toSourceRange(sourceFile, declaration.initializer)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (ts$1.isEnumDeclaration(statement)) for (const member of statement.members) {
|
|
152
|
+
const memberName = getEnumMemberName(member.name);
|
|
153
|
+
if (!memberName || !member.initializer) continue;
|
|
154
|
+
const value = getStaticStringValue(member.initializer);
|
|
155
|
+
if (value === null) continue;
|
|
156
|
+
const symbol = `${statement.name.text}.${memberName}`;
|
|
157
|
+
bindings.set(symbol, {
|
|
158
|
+
kind: "enumMember",
|
|
159
|
+
symbol,
|
|
160
|
+
value,
|
|
161
|
+
valueRange: toSourceRange(sourceFile, member.initializer)
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return bindings;
|
|
166
|
+
}
|
|
167
|
+
function collectBindings(sourceFile, uri, fileName, options) {
|
|
168
|
+
const bindings = collectTopLevelBindings(sourceFile, uri);
|
|
169
|
+
addImportedBindings(bindings, sourceFile, uri, fileName, options);
|
|
170
|
+
return bindings;
|
|
171
|
+
}
|
|
172
|
+
function collectTopLevelBindings(sourceFile, uri) {
|
|
173
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
174
|
+
for (const statement of sourceFile.statements) {
|
|
175
|
+
if (!ts$1.isVariableStatement(statement)) continue;
|
|
176
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
177
|
+
if (!ts$1.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
178
|
+
const objectLiteral = unwrapObjectLiteralExpression(declaration.initializer);
|
|
179
|
+
if (objectLiteral) {
|
|
180
|
+
bindings.set(declaration.name.text, {
|
|
181
|
+
kind: "objectReference",
|
|
182
|
+
node: objectLiteral,
|
|
183
|
+
sourceFile,
|
|
184
|
+
uri,
|
|
185
|
+
bindings
|
|
186
|
+
});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const stateConfig = unwrapCreateConfigArgument(declaration.initializer);
|
|
190
|
+
if (stateConfig) bindings.set(declaration.name.text, {
|
|
191
|
+
kind: "createStateConfig",
|
|
192
|
+
node: stateConfig,
|
|
193
|
+
sourceFile,
|
|
194
|
+
uri,
|
|
195
|
+
bindings
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return bindings;
|
|
200
|
+
}
|
|
201
|
+
function addImportedBindings(bindings, sourceFile, uri, fileName, options) {
|
|
202
|
+
const resolver = options.resolver;
|
|
203
|
+
if (!resolver) return;
|
|
204
|
+
for (const statement of sourceFile.statements) {
|
|
205
|
+
if (!ts$1.isImportDeclaration(statement) || !ts$1.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
206
|
+
const namedBindings = statement.importClause?.namedBindings;
|
|
207
|
+
if (!namedBindings || !ts$1.isNamedImports(namedBindings)) continue;
|
|
208
|
+
const importedDocument = resolver.resolveImport({
|
|
209
|
+
fileName,
|
|
210
|
+
uri
|
|
211
|
+
}, statement.moduleSpecifier.text);
|
|
212
|
+
if (!importedDocument) continue;
|
|
213
|
+
const importedSourceFile = ts$1.createSourceFile(importedDocument.fileName, importedDocument.text, ts$1.ScriptTarget.Latest, true, getScriptKind$1(importedDocument.fileName));
|
|
214
|
+
sourceFileUris.set(importedSourceFile, importedDocument.uri);
|
|
215
|
+
const importedBindings = collectBindings(importedSourceFile, importedDocument.uri, importedDocument.fileName, options);
|
|
216
|
+
for (const element of namedBindings.elements) {
|
|
217
|
+
const importedName = element.propertyName?.text ?? element.name.text;
|
|
218
|
+
const binding = importedBindings.get(importedName);
|
|
219
|
+
if (binding) bindings.set(element.name.text, binding);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function findMachineConfigObjectLiteral(sourceFile, bindings) {
|
|
224
|
+
let found = null;
|
|
225
|
+
const visit = (node) => {
|
|
226
|
+
if (found) return;
|
|
227
|
+
if (ts$1.isCallExpression(node) && isCreateMachineExpression$1(node.expression)) {
|
|
228
|
+
const [firstArgument] = node.arguments;
|
|
229
|
+
if (!firstArgument || !ts$1.isExpression(firstArgument)) return;
|
|
230
|
+
found = unwrapObjectLiteralExpression(firstArgument) ?? resolveIdentifierObject(firstArgument, bindings);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
ts$1.forEachChild(node, visit);
|
|
234
|
+
};
|
|
235
|
+
visit(sourceFile);
|
|
236
|
+
return found;
|
|
237
|
+
}
|
|
238
|
+
function collectStateLocations(stateObject, parentPath, locations, bindings, staticValues, root) {
|
|
239
|
+
const statesObject = getObjectLiteralProperty(stateObject, "states");
|
|
240
|
+
if (!statesObject) return;
|
|
241
|
+
for (const property of statesObject.properties) {
|
|
242
|
+
const resolved = resolveStateProperty(property, bindings, staticValues, root);
|
|
243
|
+
if (!resolved) continue;
|
|
244
|
+
const path = [...parentPath, resolved.key];
|
|
245
|
+
locations.push({
|
|
246
|
+
uri: resolved.uri,
|
|
247
|
+
path,
|
|
248
|
+
kind: resolved.kind,
|
|
249
|
+
symbol: resolved.symbol,
|
|
250
|
+
keyRange: resolved.keyRange,
|
|
251
|
+
referenceRange: resolved.referenceRange,
|
|
252
|
+
keySource: resolved.keySource,
|
|
253
|
+
keyReplacementText: resolved.keyReplacementText,
|
|
254
|
+
range: toSourceRange(resolved.sourceFile, resolved.stateObject)
|
|
255
|
+
});
|
|
256
|
+
collectStateLocations(resolved.stateObject, path, locations, resolved.bindings, staticValues, root);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function resolveStateProperty(property, bindings, staticValues, root) {
|
|
260
|
+
if (ts$1.isShorthandPropertyAssignment(property)) {
|
|
261
|
+
const binding = bindings.get(property.name.text);
|
|
262
|
+
if (!binding) return null;
|
|
263
|
+
return {
|
|
264
|
+
key: property.name.text,
|
|
265
|
+
kind: binding.kind,
|
|
266
|
+
symbol: property.name.text,
|
|
267
|
+
keyRange: toSourceRange(property.getSourceFile(), property.name),
|
|
268
|
+
referenceRange: toSourceRange(property.getSourceFile(), property.name),
|
|
269
|
+
stateObject: binding.node,
|
|
270
|
+
sourceFile: binding.sourceFile,
|
|
271
|
+
uri: binding.uri,
|
|
272
|
+
bindings: binding.bindings
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (!ts$1.isPropertyAssignment(property)) return null;
|
|
276
|
+
const resolvedKey = resolvePropertyKey(property.name, staticValues, root);
|
|
277
|
+
if (!resolvedKey) return null;
|
|
278
|
+
const inlineObject = unwrapObjectLiteralExpression(property.initializer);
|
|
279
|
+
if (inlineObject) return {
|
|
280
|
+
key: resolvedKey.key,
|
|
281
|
+
kind: "inline",
|
|
282
|
+
keyRange: toSourceRange(property.getSourceFile(), property.name),
|
|
283
|
+
keySource: resolvedKey.keySource,
|
|
284
|
+
keyReplacementText: resolvedKey.keyReplacementText,
|
|
285
|
+
stateObject: inlineObject,
|
|
286
|
+
sourceFile: property.getSourceFile(),
|
|
287
|
+
uri: getSourceFileUri(property.getSourceFile()),
|
|
288
|
+
bindings
|
|
289
|
+
};
|
|
290
|
+
if (ts$1.isIdentifier(property.initializer)) {
|
|
291
|
+
const binding = bindings.get(property.initializer.text);
|
|
292
|
+
if (!binding) return null;
|
|
293
|
+
return {
|
|
294
|
+
key: resolvedKey.key,
|
|
295
|
+
kind: binding.kind,
|
|
296
|
+
symbol: property.initializer.text,
|
|
297
|
+
keyRange: toSourceRange(property.getSourceFile(), property.name),
|
|
298
|
+
referenceRange: toSourceRange(property.getSourceFile(), property.initializer),
|
|
299
|
+
keySource: resolvedKey.keySource,
|
|
300
|
+
keyReplacementText: resolvedKey.keyReplacementText,
|
|
301
|
+
stateObject: binding.node,
|
|
302
|
+
sourceFile: binding.sourceFile,
|
|
303
|
+
uri: binding.uri,
|
|
304
|
+
bindings: binding.bindings
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
function getSourceFileUri(sourceFile) {
|
|
310
|
+
return sourceFileUris.get(sourceFile) ?? sourceFile.fileName;
|
|
311
|
+
}
|
|
312
|
+
function unwrapCreateConfigArgument(expression) {
|
|
313
|
+
const unwrapped = unwrapExpression$1(expression);
|
|
314
|
+
if (!ts$1.isCallExpression(unwrapped)) return null;
|
|
315
|
+
const callee = unwrapped.expression;
|
|
316
|
+
if (!ts$1.isPropertyAccessExpression(callee) || !isSetupCreateConfigName(callee.name.text)) return null;
|
|
317
|
+
const [firstArgument] = unwrapped.arguments;
|
|
318
|
+
if (!firstArgument || !ts$1.isExpression(firstArgument)) return null;
|
|
319
|
+
return unwrapObjectLiteralExpression(firstArgument);
|
|
320
|
+
}
|
|
321
|
+
function isSetupCreateConfigName(name) {
|
|
322
|
+
return /^create[A-Z_$][\w$]*Config$/.test(name);
|
|
323
|
+
}
|
|
324
|
+
function resolveIdentifierObject(expression, bindings) {
|
|
325
|
+
const unwrapped = unwrapExpression$1(expression);
|
|
326
|
+
if (!ts$1.isIdentifier(unwrapped)) return null;
|
|
327
|
+
const binding = bindings.get(unwrapped.text);
|
|
328
|
+
return binding?.kind === "objectReference" ? binding.node : null;
|
|
329
|
+
}
|
|
330
|
+
function getObjectLiteralProperty(objectLiteral, propertyName) {
|
|
331
|
+
for (const property of objectLiteral.properties) {
|
|
332
|
+
if (!ts$1.isPropertyAssignment(property)) continue;
|
|
333
|
+
if (getPropertyNameText$1(property.name) !== propertyName) continue;
|
|
334
|
+
return unwrapObjectLiteralExpression(property.initializer);
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
function getPropertyNameText$1(name) {
|
|
339
|
+
if (ts$1.isIdentifier(name) || ts$1.isStringLiteral(name) || ts$1.isNumericLiteral(name)) return name.text;
|
|
340
|
+
if (ts$1.isComputedPropertyName(name)) {
|
|
341
|
+
const expression = name.expression;
|
|
342
|
+
if (ts$1.isStringLiteral(expression) || ts$1.isNoSubstitutionTemplateLiteral(expression) || ts$1.isNumericLiteral(expression)) return expression.text;
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
function resolvePropertyKey(name, staticValues, root) {
|
|
347
|
+
const literalKey = getPropertyNameText$1(name);
|
|
348
|
+
if (literalKey !== null) {
|
|
349
|
+
if (ts$1.isComputedPropertyName(name)) return {
|
|
350
|
+
key: literalKey,
|
|
351
|
+
keySource: { kind: "computedLiteral" },
|
|
352
|
+
keyReplacementText: JSON.stringify(literalKey)
|
|
353
|
+
};
|
|
354
|
+
return { key: literalKey };
|
|
355
|
+
}
|
|
356
|
+
if (!ts$1.isComputedPropertyName(name)) return null;
|
|
357
|
+
const binding = getStaticValueBindingForExpression(name.expression, staticValues);
|
|
358
|
+
if (!binding) return null;
|
|
359
|
+
return {
|
|
360
|
+
key: binding.value,
|
|
361
|
+
keySource: {
|
|
362
|
+
kind: binding.kind,
|
|
363
|
+
symbol: binding.symbol,
|
|
364
|
+
valueRange: binding.valueRange,
|
|
365
|
+
machineLocal: isStaticValueMachineLocal(name.getSourceFile(), binding, root)
|
|
366
|
+
},
|
|
367
|
+
keyReplacementText: JSON.stringify(binding.value)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function getStaticValueBindingForExpression(expression, staticValues) {
|
|
371
|
+
const unwrapped = unwrapExpression$1(expression);
|
|
372
|
+
if (ts$1.isIdentifier(unwrapped)) return staticValues.get(unwrapped.text) ?? null;
|
|
373
|
+
if (ts$1.isPropertyAccessExpression(unwrapped) && ts$1.isIdentifier(unwrapped.expression)) return staticValues.get(`${unwrapped.expression.text}.${unwrapped.name.text}`) ?? null;
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
function collectStaticValueReferences(sourceFile, uri, root, staticValues) {
|
|
377
|
+
const references = [];
|
|
378
|
+
if (staticValues.size === 0) return references;
|
|
379
|
+
const rootStart = root.getStart(sourceFile);
|
|
380
|
+
const rootEnd = root.getEnd();
|
|
381
|
+
const visit = (node) => {
|
|
382
|
+
if (ts$1.isComputedPropertyName(node)) {
|
|
383
|
+
const binding = getStaticValueBindingForExpression(node.expression, staticValues);
|
|
384
|
+
if (binding && !isStateKeyComputedPropertyName(node)) references.push({
|
|
385
|
+
uri,
|
|
386
|
+
range: toSourceRange(sourceFile, node),
|
|
387
|
+
value: binding.value
|
|
388
|
+
});
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (!(node.getStart(sourceFile) >= rootStart && node.getEnd() <= rootEnd)) {
|
|
392
|
+
ts$1.forEachChild(node, visit);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const binding = getStaticValueReferenceBinding(node, staticValues);
|
|
396
|
+
if (binding) {
|
|
397
|
+
references.push({
|
|
398
|
+
uri,
|
|
399
|
+
range: toSourceRange(sourceFile, node),
|
|
400
|
+
value: binding.value
|
|
401
|
+
});
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
ts$1.forEachChild(node, visit);
|
|
405
|
+
};
|
|
406
|
+
visit(root);
|
|
407
|
+
return references;
|
|
408
|
+
}
|
|
409
|
+
function getStaticValueReferenceBinding(node, staticValues) {
|
|
410
|
+
if (ts$1.isIdentifier(node)) {
|
|
411
|
+
if (ts$1.isVariableDeclaration(node.parent) && node.parent.name === node) return null;
|
|
412
|
+
if (ts$1.isPropertyAssignment(node.parent) && node.parent.name === node) return null;
|
|
413
|
+
if (ts$1.isPropertyAccessExpression(node.parent)) return null;
|
|
414
|
+
return staticValues.get(node.text) ?? null;
|
|
415
|
+
}
|
|
416
|
+
if (ts$1.isPropertyAccessExpression(node) && ts$1.isIdentifier(node.expression)) {
|
|
417
|
+
if (ts$1.isComputedPropertyName(node.parent) && ts$1.isEnumMember(node.parent.parent)) return null;
|
|
418
|
+
return staticValues.get(`${node.expression.text}.${node.name.text}`) ?? null;
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
function isStaticValueMachineLocal(sourceFile, binding, root) {
|
|
423
|
+
const rootStart = root.getStart(sourceFile);
|
|
424
|
+
const rootEnd = root.getEnd();
|
|
425
|
+
let referenceCount = 0;
|
|
426
|
+
let hasOutsideReference = false;
|
|
427
|
+
const visit = (node) => {
|
|
428
|
+
if (getStaticValueReferenceBinding(node, new Map([[binding.symbol, binding]]))) {
|
|
429
|
+
referenceCount += 1;
|
|
430
|
+
if (node.getStart(sourceFile) < rootStart || node.getEnd() > rootEnd) hasOutsideReference = true;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
ts$1.forEachChild(node, visit);
|
|
434
|
+
};
|
|
435
|
+
visit(sourceFile);
|
|
436
|
+
return referenceCount > 0 && !hasOutsideReference;
|
|
437
|
+
}
|
|
438
|
+
function isStateKeyComputedPropertyName(name) {
|
|
439
|
+
const property = name.parent;
|
|
440
|
+
if (!ts$1.isPropertyAssignment(property)) return false;
|
|
441
|
+
const objectLiteral = property.parent;
|
|
442
|
+
if (!ts$1.isObjectLiteralExpression(objectLiteral)) return false;
|
|
443
|
+
const parentProperty = objectLiteral.parent;
|
|
444
|
+
if (!ts$1.isPropertyAssignment(parentProperty)) return false;
|
|
445
|
+
return getPropertyNameText$1(parentProperty.name) === "states";
|
|
446
|
+
}
|
|
447
|
+
function getStaticStringValue(expression) {
|
|
448
|
+
const unwrapped = unwrapExpression$1(expression);
|
|
449
|
+
if (ts$1.isStringLiteral(unwrapped) || ts$1.isNoSubstitutionTemplateLiteral(unwrapped) || ts$1.isNumericLiteral(unwrapped)) return unwrapped.text;
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
function getEnumMemberName(name) {
|
|
453
|
+
if (ts$1.isIdentifier(name) || ts$1.isStringLiteral(name) || ts$1.isNumericLiteral(name)) return name.text;
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
function isCreateMachineExpression$1(expression) {
|
|
457
|
+
return ts$1.isIdentifier(expression) && expression.text === "createMachine" || ts$1.isPropertyAccessExpression(expression) && expression.name.text === "createMachine";
|
|
458
|
+
}
|
|
459
|
+
function unwrapObjectLiteralExpression(expression) {
|
|
460
|
+
const unwrapped = unwrapExpression$1(expression);
|
|
461
|
+
return ts$1.isObjectLiteralExpression(unwrapped) ? unwrapped : null;
|
|
462
|
+
}
|
|
463
|
+
function unwrapExpression$1(expression) {
|
|
464
|
+
let current = expression;
|
|
465
|
+
for (;;) {
|
|
466
|
+
if (ts$1.isParenthesizedExpression(current)) {
|
|
467
|
+
current = current.expression;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (ts$1.isAsExpression(current) || ts$1.isSatisfiesExpression(current)) {
|
|
471
|
+
current = current.expression;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (ts$1.isTypeAssertionExpression(current) || ts$1.isNonNullExpression(current)) {
|
|
475
|
+
current = current.expression;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
return current;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function toSourceRange(sourceFile, node) {
|
|
482
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
483
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
484
|
+
return {
|
|
485
|
+
startLine: start.line,
|
|
486
|
+
startChar: start.character,
|
|
487
|
+
endLine: end.line,
|
|
488
|
+
endChar: end.character
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function getScriptKind$1(fileName) {
|
|
492
|
+
if (fileName.endsWith(".tsx")) return ts$1.ScriptKind.TSX;
|
|
493
|
+
if (fileName.endsWith(".jsx")) return ts$1.ScriptKind.JSX;
|
|
494
|
+
if (fileName.endsWith(".js")) return ts$1.ScriptKind.JS;
|
|
495
|
+
return ts$1.ScriptKind.TS;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region ../editor-sync/src/internal/codeToGraph.ts
|
|
500
|
+
/**
|
|
501
|
+
* Returns source text for the embed to parse.
|
|
502
|
+
*
|
|
503
|
+
* The returned text is still a temporary parse payload, not the source we write
|
|
504
|
+
* back to. Directly imported modular states are hydrated into the root
|
|
505
|
+
* `states` object so Viz can see their transition bodies even though it only
|
|
506
|
+
* receives one string.
|
|
507
|
+
*/
|
|
508
|
+
function parseSourceToMachine(text, document, options = {}) {
|
|
509
|
+
const sourceLocations = collectXStateSourceLocations(text, document, options);
|
|
510
|
+
if (!sourceLocations) return text;
|
|
511
|
+
const staticValueReplacements = (sourceLocations.staticValues ?? []).map((location) => ({
|
|
512
|
+
range: location.range,
|
|
513
|
+
newText: JSON.stringify(location.value)
|
|
514
|
+
}));
|
|
515
|
+
const stateReplacements = sourceLocations.states.filter((location) => location.path.length === 1 && location.keyRange).flatMap((location) => {
|
|
516
|
+
const keyRange = location.keyRange;
|
|
517
|
+
const keyReplacement = location.keyReplacementText ? [{
|
|
518
|
+
range: keyRange,
|
|
519
|
+
newText: location.keyReplacementText
|
|
520
|
+
}] : [];
|
|
521
|
+
const stateText = readSourceRange(location.uri, location.range, options.resolver);
|
|
522
|
+
if (!stateText) return keyReplacement;
|
|
523
|
+
if (location.referenceRange && !areSourceRangesEqual(location.referenceRange, keyRange)) return [...keyReplacement, {
|
|
524
|
+
range: location.referenceRange,
|
|
525
|
+
newText: stateText
|
|
526
|
+
}];
|
|
527
|
+
if (!canHydrateShorthandState(text, keyRange)) return keyReplacement;
|
|
528
|
+
return [...keyReplacement, {
|
|
529
|
+
range: keyRange,
|
|
530
|
+
newText: `${location.path[0]}: ${stateText}`
|
|
531
|
+
}];
|
|
532
|
+
});
|
|
533
|
+
return applySourceRangeReplacements(text, [...staticValueReplacements, ...stateReplacements].filter(removeOverlappingRanges).sort(compareSourceRangesDescending));
|
|
534
|
+
}
|
|
535
|
+
function removeOverlappingRanges(replacement, index, replacements) {
|
|
536
|
+
return !replacements.some((candidate, candidateIndex) => {
|
|
537
|
+
if (candidateIndex === index) return false;
|
|
538
|
+
return compareSourceRangesDescending(candidate, replacement) < 0 && rangesOverlap(candidate.range, replacement.range);
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
function rangesOverlap(left, right) {
|
|
542
|
+
const leftStart = rangePositionKey(left.startLine, left.startChar);
|
|
543
|
+
const leftEnd = rangePositionKey(left.endLine, left.endChar);
|
|
544
|
+
const rightStart = rangePositionKey(right.startLine, right.startChar);
|
|
545
|
+
return leftStart < rangePositionKey(right.endLine, right.endChar) && rightStart < leftEnd;
|
|
546
|
+
}
|
|
547
|
+
function rangePositionKey(line, character) {
|
|
548
|
+
return line * 1e6 + character;
|
|
549
|
+
}
|
|
550
|
+
function areSourceRangesEqual(left, right) {
|
|
551
|
+
return left.startLine === right.startLine && left.startChar === right.startChar && left.endLine === right.endLine && left.endChar === right.endChar;
|
|
552
|
+
}
|
|
553
|
+
function canHydrateShorthandState(text, range) {
|
|
554
|
+
const end = offsetAtRangePosition(text, range.endLine, range.endChar);
|
|
555
|
+
return text.slice(end).match(/\S/)?.[0] !== ":";
|
|
556
|
+
}
|
|
557
|
+
function readSourceRange(uri, range, resolver) {
|
|
558
|
+
if (!resolver) return null;
|
|
559
|
+
const document = resolver.read(uri);
|
|
560
|
+
if (!document) return null;
|
|
561
|
+
const start = offsetAtRangePosition(document.text, range.startLine, range.startChar);
|
|
562
|
+
const end = offsetAtRangePosition(document.text, range.endLine, range.endChar);
|
|
563
|
+
return document.text.slice(start, end);
|
|
564
|
+
}
|
|
565
|
+
function applySourceRangeReplacements(text, replacements) {
|
|
566
|
+
let updated = text;
|
|
567
|
+
for (const replacement of replacements) {
|
|
568
|
+
const start = offsetAtRangePosition(updated, replacement.range.startLine, replacement.range.startChar);
|
|
569
|
+
const end = offsetAtRangePosition(updated, replacement.range.endLine, replacement.range.endChar);
|
|
570
|
+
updated = `${updated.slice(0, start)}${replacement.newText}${updated.slice(end)}`;
|
|
571
|
+
}
|
|
572
|
+
return updated;
|
|
573
|
+
}
|
|
574
|
+
function compareSourceRangesDescending(left, right) {
|
|
575
|
+
if (left.range.startLine !== right.range.startLine) return right.range.startLine - left.range.startLine;
|
|
576
|
+
return right.range.startChar - left.range.startChar;
|
|
577
|
+
}
|
|
578
|
+
function offsetAtRangePosition(text, line, character) {
|
|
579
|
+
if (line <= 0) return character;
|
|
580
|
+
let currentLine = 0;
|
|
581
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
582
|
+
if (text.charCodeAt(index) !== 10) continue;
|
|
583
|
+
currentLine += 1;
|
|
584
|
+
if (currentLine === line) return index + 1 + character;
|
|
585
|
+
}
|
|
586
|
+
return text.length;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/sync.ts
|
|
591
|
+
const execFileAsync = promisify(execFile);
|
|
592
|
+
const MAX_CAPTURED_LOCAL_MACHINES = 100;
|
|
593
|
+
async function extractMachinesFromLocalSource(sourcePath, contents) {
|
|
594
|
+
try {
|
|
595
|
+
return { machines: extractMachineConfigsFromAst(sourcePath, parseSourceToMachine(contents, createTextDocument(sourcePath, contents), { resolver: createFileSourceDocumentResolver() })) };
|
|
596
|
+
} catch (error) {
|
|
597
|
+
return {
|
|
598
|
+
machines: [],
|
|
599
|
+
error: error instanceof Error ? error.message : String(error)
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function isInvalidLocalMachineDefinitionError(message) {
|
|
604
|
+
return /No initial state specified for compound state node/.test(message) || /Try adding \{\s*initial:\s*['"`]/.test(message);
|
|
605
|
+
}
|
|
606
|
+
function formatLocalMachineExtractionIssue(message) {
|
|
607
|
+
if (isInvalidLocalMachineDefinitionError(message)) return `Invalid local machine definition: ${message}`;
|
|
608
|
+
return `Could not extract a local machine from this file: ${message}`;
|
|
609
|
+
}
|
|
610
|
+
function extractMachineConfigsFromAst(fileName, sourceText) {
|
|
611
|
+
const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(fileName));
|
|
612
|
+
const bindings = collectLocalBindings(sourceFile);
|
|
613
|
+
const machines = [];
|
|
614
|
+
const visit = (node) => {
|
|
615
|
+
if (machines.length >= MAX_CAPTURED_LOCAL_MACHINES) return;
|
|
616
|
+
if (!ts.isCallExpression(node) || !isCreateMachineExpression(node.expression)) {
|
|
617
|
+
ts.forEachChild(node, visit);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const [firstArgument] = node.arguments;
|
|
621
|
+
if (!firstArgument || !ts.isExpression(firstArgument)) return;
|
|
622
|
+
const config = evaluateExpression(firstArgument, sourceFile, bindings, /* @__PURE__ */ new Set());
|
|
623
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) return;
|
|
624
|
+
machines.push({
|
|
625
|
+
config,
|
|
626
|
+
_type: ts.isPropertyAccessExpression(unwrapExpression(node.expression)) ? "setup.createMachine" : "createMachine"
|
|
627
|
+
});
|
|
628
|
+
};
|
|
629
|
+
ts.forEachChild(sourceFile, visit);
|
|
630
|
+
return machines;
|
|
631
|
+
}
|
|
632
|
+
function collectLocalBindings(sourceFile) {
|
|
633
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
634
|
+
for (const statement of sourceFile.statements) {
|
|
635
|
+
if (ts.isVariableStatement(statement)) {
|
|
636
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
637
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
638
|
+
bindings.set(declaration.name.text, {
|
|
639
|
+
kind: "value",
|
|
640
|
+
expression: declaration.initializer
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
if (!ts.isEnumDeclaration(statement)) continue;
|
|
646
|
+
let nextNumericValue = 0;
|
|
647
|
+
for (const member of statement.members) {
|
|
648
|
+
const memberName = getPropertyNameText(member.name);
|
|
649
|
+
if (!memberName) continue;
|
|
650
|
+
const symbol = `${statement.name.text}.${memberName}`;
|
|
651
|
+
if (!member.initializer) {
|
|
652
|
+
bindings.set(symbol, {
|
|
653
|
+
kind: "enumMember",
|
|
654
|
+
value: nextNumericValue
|
|
655
|
+
});
|
|
656
|
+
nextNumericValue += 1;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
const value = evaluateExpression(member.initializer, sourceFile, bindings, /* @__PURE__ */ new Set());
|
|
660
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
661
|
+
bindings.set(symbol, {
|
|
662
|
+
kind: "enumMember",
|
|
663
|
+
value
|
|
664
|
+
});
|
|
665
|
+
if (typeof value === "number") nextNumericValue = value + 1;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return bindings;
|
|
670
|
+
}
|
|
671
|
+
function evaluateExpression(expression, sourceFile, bindings, seenBindings) {
|
|
672
|
+
const unwrapped = unwrapExpression(expression);
|
|
673
|
+
if (ts.isStringLiteral(unwrapped) || ts.isNoSubstitutionTemplateLiteral(unwrapped)) return unwrapped.text;
|
|
674
|
+
if (ts.isNumericLiteral(unwrapped)) return Number(unwrapped.text);
|
|
675
|
+
if (unwrapped.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
676
|
+
if (unwrapped.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
677
|
+
if (unwrapped.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
678
|
+
if (ts.isIdentifier(unwrapped)) {
|
|
679
|
+
if (unwrapped.text === "undefined") return;
|
|
680
|
+
const binding = bindings.get(unwrapped.text);
|
|
681
|
+
if (!binding || binding.kind !== "value" || seenBindings.has(unwrapped.text)) return unwrapped.getText(sourceFile);
|
|
682
|
+
const nextSeen = new Set(seenBindings);
|
|
683
|
+
nextSeen.add(unwrapped.text);
|
|
684
|
+
return evaluateExpression(binding.expression, sourceFile, bindings, nextSeen);
|
|
685
|
+
}
|
|
686
|
+
if (ts.isPropertyAccessExpression(unwrapped) && ts.isIdentifier(unwrapped.expression)) {
|
|
687
|
+
const binding = bindings.get(`${unwrapped.expression.text}.${unwrapped.name.text}`);
|
|
688
|
+
if (binding?.kind === "enumMember") return binding.value;
|
|
689
|
+
return unwrapped.getText(sourceFile);
|
|
690
|
+
}
|
|
691
|
+
if (ts.isArrayLiteralExpression(unwrapped)) return unwrapped.elements.map((element) => ts.isSpreadElement(element) ? evaluateExpression(element.expression, sourceFile, bindings, seenBindings) : evaluateExpression(element, sourceFile, bindings, seenBindings));
|
|
692
|
+
if (ts.isObjectLiteralExpression(unwrapped)) {
|
|
693
|
+
const result = {};
|
|
694
|
+
for (const property of unwrapped.properties) {
|
|
695
|
+
if (ts.isSpreadAssignment(property)) {
|
|
696
|
+
const spreadValue = evaluateExpression(property.expression, sourceFile, bindings, seenBindings);
|
|
697
|
+
if (spreadValue && typeof spreadValue === "object" && !Array.isArray(spreadValue)) Object.assign(result, spreadValue);
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
701
|
+
result[property.name.text] = evaluateExpression(property.name, sourceFile, bindings, seenBindings);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
705
|
+
const key = resolvePropertyNameValue(property.name, sourceFile, bindings, seenBindings);
|
|
706
|
+
if (key == null) continue;
|
|
707
|
+
result[String(key)] = evaluateExpression(property.initializer, sourceFile, bindings, seenBindings);
|
|
708
|
+
}
|
|
709
|
+
return result;
|
|
710
|
+
}
|
|
711
|
+
if (ts.isPrefixUnaryExpression(unwrapped)) {
|
|
712
|
+
const operand = evaluateExpression(unwrapped.operand, sourceFile, bindings, seenBindings);
|
|
713
|
+
if (typeof operand !== "number") return unwrapped.getText(sourceFile);
|
|
714
|
+
switch (unwrapped.operator) {
|
|
715
|
+
case ts.SyntaxKind.MinusToken: return -operand;
|
|
716
|
+
case ts.SyntaxKind.PlusToken: return operand;
|
|
717
|
+
default: return unwrapped.getText(sourceFile);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (ts.isTemplateExpression(unwrapped)) {
|
|
721
|
+
let value = unwrapped.head.text;
|
|
722
|
+
for (const span of unwrapped.templateSpans) {
|
|
723
|
+
const expressionValue = evaluateExpression(span.expression, sourceFile, bindings, seenBindings);
|
|
724
|
+
value += String(expressionValue ?? "");
|
|
725
|
+
value += span.literal.text;
|
|
726
|
+
}
|
|
727
|
+
return value;
|
|
728
|
+
}
|
|
729
|
+
return unwrapped.getText(sourceFile);
|
|
730
|
+
}
|
|
731
|
+
function resolvePropertyNameValue(name, sourceFile, bindings, seenBindings) {
|
|
732
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
733
|
+
if (!ts.isComputedPropertyName(name)) return null;
|
|
734
|
+
const value = evaluateExpression(name.expression, sourceFile, bindings, seenBindings);
|
|
735
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
736
|
+
return name.getText(sourceFile);
|
|
737
|
+
}
|
|
738
|
+
function isCreateMachineExpression(expression) {
|
|
739
|
+
const unwrapped = unwrapExpression(expression);
|
|
740
|
+
return ts.isIdentifier(unwrapped) && unwrapped.text === "createMachine" || ts.isPropertyAccessExpression(unwrapped) && unwrapped.name.text === "createMachine";
|
|
741
|
+
}
|
|
742
|
+
function unwrapExpression(expression) {
|
|
743
|
+
let current = expression;
|
|
744
|
+
for (;;) {
|
|
745
|
+
if (ts.isParenthesizedExpression(current)) {
|
|
746
|
+
current = current.expression;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (ts.isAsExpression(current) || ts.isSatisfiesExpression(current)) {
|
|
750
|
+
current = current.expression;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
if (ts.isTypeAssertionExpression(current) || ts.isNonNullExpression(current)) {
|
|
754
|
+
current = current.expression;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
return current;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function getPropertyNameText(name) {
|
|
761
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
function getScriptKind(fileName) {
|
|
765
|
+
if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
766
|
+
if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
767
|
+
if (fileName.endsWith(".js")) return ts.ScriptKind.JS;
|
|
768
|
+
return ts.ScriptKind.TS;
|
|
769
|
+
}
|
|
770
|
+
function isUrl(value) {
|
|
771
|
+
try {
|
|
772
|
+
const url = new URL(value);
|
|
773
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
774
|
+
} catch {
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
async function fileExists(filePath) {
|
|
779
|
+
try {
|
|
780
|
+
await fs.access(filePath);
|
|
781
|
+
return true;
|
|
782
|
+
} catch {
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function parseGitHubRemote(remoteUrl) {
|
|
787
|
+
const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
788
|
+
if (httpsMatch) {
|
|
789
|
+
const [, owner, repo] = httpsMatch;
|
|
790
|
+
if (!owner || !repo) return null;
|
|
791
|
+
return {
|
|
792
|
+
url: `https://github.com/${owner}/${repo}`,
|
|
793
|
+
owner,
|
|
794
|
+
repo
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
798
|
+
if (sshMatch) {
|
|
799
|
+
const [, owner, repo] = sshMatch;
|
|
800
|
+
if (!owner || !repo) return null;
|
|
801
|
+
return {
|
|
802
|
+
url: `https://github.com/${owner}/${repo}`,
|
|
803
|
+
owner,
|
|
804
|
+
repo
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
async function inferConnectedRepo(filePath, cwd) {
|
|
810
|
+
const workingDir = cwd ?? path.dirname(filePath);
|
|
811
|
+
try {
|
|
812
|
+
const [{ stdout: repoRootStdout }, { stdout: remoteStdout }, { stdout: branchStdout }, { stdout: treeShaStdout }] = await Promise.all([
|
|
813
|
+
execFileAsync("git", ["rev-parse", "--show-toplevel"], { cwd: workingDir }),
|
|
814
|
+
execFileAsync("git", [
|
|
815
|
+
"remote",
|
|
816
|
+
"get-url",
|
|
817
|
+
"origin"
|
|
818
|
+
], { cwd: workingDir }),
|
|
819
|
+
execFileAsync("git", ["branch", "--show-current"], { cwd: workingDir }),
|
|
820
|
+
execFileAsync("git", ["rev-parse", "HEAD"], { cwd: workingDir })
|
|
821
|
+
]);
|
|
822
|
+
const repoRoot = repoRootStdout.trim();
|
|
823
|
+
const remote = parseGitHubRemote(remoteStdout.trim());
|
|
824
|
+
const branch = branchStdout.trim();
|
|
825
|
+
const treeSha = treeShaStdout.trim();
|
|
826
|
+
if (!remote || !branch || !treeSha) return;
|
|
827
|
+
const relativePath = path.relative(repoRoot, filePath).replace(/\\/g, "/");
|
|
828
|
+
const relativeDir = path.dirname(relativePath).replace(/\\/g, "/");
|
|
829
|
+
const selectedPaths = relativePath && relativePath !== "." ? [relativePath] : [];
|
|
830
|
+
return {
|
|
831
|
+
...remote,
|
|
832
|
+
branch,
|
|
833
|
+
treeSha,
|
|
834
|
+
autoSync: false,
|
|
835
|
+
pathForNewFiles: relativeDir && relativeDir !== "." ? relativeDir : "src/stately-studio",
|
|
836
|
+
selectedPaths
|
|
837
|
+
};
|
|
838
|
+
} catch {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function normalizeActions(value) {
|
|
843
|
+
if (value == null) return [];
|
|
844
|
+
return (Array.isArray(value) ? value : [value]).map((item) => {
|
|
845
|
+
if (typeof item === "string") return { type: item };
|
|
846
|
+
if (typeof item === "object" && item !== null && "type" in item) {
|
|
847
|
+
const type = item.type;
|
|
848
|
+
const params = "params" in item && typeof item.params === "object" && item.params ? item.params : void 0;
|
|
849
|
+
return {
|
|
850
|
+
type,
|
|
851
|
+
...params ? { params } : {}
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
return { type: String(item) };
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
function normalizeTags(value) {
|
|
858
|
+
if (!value) return [];
|
|
859
|
+
if (Array.isArray(value)) return value.map((tag) => String(tag));
|
|
860
|
+
return [String(value)];
|
|
861
|
+
}
|
|
862
|
+
function normalizeInvoke(value) {
|
|
863
|
+
if (!value) return [];
|
|
864
|
+
return (Array.isArray(value) ? value : [value]).map((invoke, index) => ({
|
|
865
|
+
src: invoke.src,
|
|
866
|
+
id: invoke.id ?? `invoke[${index}]`,
|
|
867
|
+
...invoke.input ? { input: invoke.input } : {}
|
|
868
|
+
}));
|
|
869
|
+
}
|
|
870
|
+
function resolveTargetId(sourceParentPath, target) {
|
|
871
|
+
if (!target) return;
|
|
872
|
+
if (target.startsWith("#")) {
|
|
873
|
+
const stripped = target.slice(1);
|
|
874
|
+
const machineIndex = stripped.indexOf(".");
|
|
875
|
+
if (machineIndex >= 0) return `(machine).${stripped.slice(machineIndex + 1)}`;
|
|
876
|
+
return `(machine).${stripped}`;
|
|
877
|
+
}
|
|
878
|
+
if (target.startsWith("(machine)")) return target;
|
|
879
|
+
if (target.includes(".")) return `(machine).${target}`;
|
|
880
|
+
return `${sourceParentPath}.${target}`;
|
|
881
|
+
}
|
|
882
|
+
function appendTransitionEdges(edges, sourceId, sourceParentPath, eventType, transition, edgeCounts) {
|
|
883
|
+
const transitions = Array.isArray(transition) ? transition : [transition];
|
|
884
|
+
for (const item of transitions) {
|
|
885
|
+
const normalized = typeof item === "string" ? { target: item } : item;
|
|
886
|
+
const targetId = resolveTargetId(sourceParentPath, Array.isArray(normalized.target) ? normalized.target[0] : normalized.target);
|
|
887
|
+
const groupKey = `${sourceId}:${eventType}`;
|
|
888
|
+
const index = edgeCounts.get(groupKey) ?? 0;
|
|
889
|
+
edgeCounts.set(groupKey, index + 1);
|
|
890
|
+
edges.push({
|
|
891
|
+
type: "edge",
|
|
892
|
+
id: `${sourceId}#${eventType}[${index}]`,
|
|
893
|
+
sourceId,
|
|
894
|
+
targetId: targetId ?? sourceId,
|
|
895
|
+
label: eventType,
|
|
896
|
+
data: {
|
|
897
|
+
eventType,
|
|
898
|
+
transitionType: normalized.reenter === true ? "reenter" : normalized.internal === true || !targetId ? "targetless" : "normal",
|
|
899
|
+
...normalized.guard ? { guard: typeof normalized.guard === "string" ? { type: normalized.guard } : {
|
|
900
|
+
type: normalized.guard.type,
|
|
901
|
+
...normalized.guard.params ? { params: normalized.guard.params } : {}
|
|
902
|
+
} } : {},
|
|
903
|
+
actions: normalizeActions(normalized.actions),
|
|
904
|
+
...normalized.description ? { description: normalized.description } : {}
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function fromXStateConfig(config) {
|
|
910
|
+
const nodes = [];
|
|
911
|
+
const edges = [];
|
|
912
|
+
const edgeCounts = /* @__PURE__ */ new Map();
|
|
913
|
+
function visitNode(key, nodeConfig, parentId, parentPath) {
|
|
914
|
+
const nodeId = parentPath ? `${parentPath}.${key}` : "(machine)";
|
|
915
|
+
const currentPath = parentPath ? nodeId : "(machine)";
|
|
916
|
+
const initialId = nodeConfig.initial ? `${currentPath}.${nodeConfig.initial}` : void 0;
|
|
917
|
+
nodes.push({
|
|
918
|
+
type: "node",
|
|
919
|
+
id: nodeId,
|
|
920
|
+
parentId,
|
|
921
|
+
label: key,
|
|
922
|
+
...initialId ? { initialNodeId: initialId } : {},
|
|
923
|
+
data: {
|
|
924
|
+
key,
|
|
925
|
+
...nodeConfig.type ? { type: nodeConfig.type } : {},
|
|
926
|
+
...nodeConfig.history ? { history: nodeConfig.history } : {},
|
|
927
|
+
...initialId ? { initialId } : {},
|
|
928
|
+
entry: normalizeActions(nodeConfig.entry),
|
|
929
|
+
exit: normalizeActions(nodeConfig.exit),
|
|
930
|
+
invokes: normalizeInvoke(nodeConfig.invoke),
|
|
931
|
+
tags: normalizeTags(nodeConfig.tags),
|
|
932
|
+
...nodeConfig.description ? { description: nodeConfig.description } : {}
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
for (const [eventType, transition] of Object.entries(nodeConfig.on ?? {})) appendTransitionEdges(edges, nodeId, parentPath ?? "(machine)", eventType, transition, edgeCounts);
|
|
936
|
+
if (nodeConfig.always) appendTransitionEdges(edges, nodeId, parentPath ?? "(machine)", "", nodeConfig.always, edgeCounts);
|
|
937
|
+
for (const [childKey, childConfig] of Object.entries(nodeConfig.states ?? {})) visitNode(childKey, childConfig, nodeId, currentPath);
|
|
938
|
+
}
|
|
939
|
+
visitNode("(machine)", config, null, null);
|
|
940
|
+
return {
|
|
941
|
+
id: config.id ?? "machine",
|
|
942
|
+
nodes,
|
|
943
|
+
edges,
|
|
944
|
+
data: {}
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
async function pushLocalMachineLinks(options) {
|
|
948
|
+
const client = options.client ?? createStatelyClient({
|
|
949
|
+
apiKey: options.apiKey,
|
|
950
|
+
baseUrl: options.baseUrl,
|
|
951
|
+
fetch: options.fetch
|
|
952
|
+
});
|
|
953
|
+
const sourcePath = path.resolve(options.cwd ?? process.cwd(), options.source);
|
|
954
|
+
const project = await resolvePushProject(client, sourcePath, options);
|
|
955
|
+
if (!project.projectVersionId) throw new Error("Resolved project is missing projectVersionId.");
|
|
956
|
+
const contents = await fs.readFile(sourcePath, "utf8");
|
|
957
|
+
const attachments = findStatelyPragmaAttachments(contents, sourcePath);
|
|
958
|
+
const extracted = await extractMachinesFromLocalSource(sourcePath, contents);
|
|
959
|
+
if (attachments.length === 0 || extracted.machines.length === 0) return {
|
|
960
|
+
sourcePath,
|
|
961
|
+
project,
|
|
962
|
+
created: [],
|
|
963
|
+
updated: [],
|
|
964
|
+
skipped: [{
|
|
965
|
+
machineIndex: 0,
|
|
966
|
+
reason: extracted.error != null ? formatLocalMachineExtractionIssue(extracted.error) : "No local machines were discovered in this file."
|
|
967
|
+
}]
|
|
968
|
+
};
|
|
969
|
+
if (attachments.length !== extracted.machines.length) return {
|
|
970
|
+
sourcePath,
|
|
971
|
+
project,
|
|
972
|
+
created: [],
|
|
973
|
+
updated: [],
|
|
974
|
+
skipped: [{
|
|
975
|
+
machineIndex: 0,
|
|
976
|
+
reason: "The local machine extractor did not align with the source attachments for this file."
|
|
977
|
+
}]
|
|
978
|
+
};
|
|
979
|
+
let nextContents = contents;
|
|
980
|
+
let outputPath;
|
|
981
|
+
const created = [];
|
|
982
|
+
const updated = [];
|
|
983
|
+
const skipped = [];
|
|
984
|
+
for (const [machineIndex, attachment] of attachments.entries()) {
|
|
985
|
+
const extractedMachine = extracted.machines[machineIndex];
|
|
986
|
+
if (!extractedMachine?.config) {
|
|
987
|
+
skipped.push({
|
|
988
|
+
machineIndex,
|
|
989
|
+
reason: "No extracted machine config was available for this source."
|
|
990
|
+
});
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
if (attachment.pragma?.id) {
|
|
994
|
+
const updatedMachine = await client.machines.update({
|
|
995
|
+
id: attachment.pragma.id,
|
|
996
|
+
definition: toStudioMachine(fromXStateConfig(extractedMachine.config))
|
|
997
|
+
});
|
|
998
|
+
updated.push({
|
|
999
|
+
machineIndex,
|
|
1000
|
+
machine: updatedMachine
|
|
1001
|
+
});
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
const machine = await client.machines.create({
|
|
1005
|
+
projectVersionId: project.projectVersionId,
|
|
1006
|
+
definition: toStudioMachine(fromXStateConfig(extractedMachine.config)),
|
|
1007
|
+
xstateVersion: Math.max(5, options.xstateVersion ?? 5)
|
|
1008
|
+
});
|
|
1009
|
+
nextContents = upsertStatelyPragma(nextContents, machine.id, {
|
|
1010
|
+
fileName: sourcePath,
|
|
1011
|
+
machineIndex
|
|
1012
|
+
});
|
|
1013
|
+
created.push({
|
|
1014
|
+
machineIndex,
|
|
1015
|
+
machine
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
if (nextContents !== contents) {
|
|
1019
|
+
await fs.writeFile(sourcePath, nextContents, "utf8");
|
|
1020
|
+
outputPath = sourcePath;
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
sourcePath,
|
|
1024
|
+
project,
|
|
1025
|
+
created,
|
|
1026
|
+
updated,
|
|
1027
|
+
skipped,
|
|
1028
|
+
...outputPath ? { outputPath } : {}
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
async function resolveLocalFile(locator, options) {
|
|
1032
|
+
const filePath = path.resolve(options.cwd ?? process.cwd(), locator);
|
|
1033
|
+
const contents = await fs.readFile(filePath, "utf8");
|
|
1034
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
1035
|
+
if (extension === ".ts" || extension === ".tsx" || extension === ".js" || extension === ".jsx") {
|
|
1036
|
+
const config = (await (options.client ?? createStatelyClient({
|
|
1037
|
+
apiKey: options.apiKey,
|
|
1038
|
+
baseUrl: options.baseUrl,
|
|
1039
|
+
fetch: options.fetch
|
|
1040
|
+
})).code.extractMachines(contents)).machines[0]?.config;
|
|
1041
|
+
if (!config) throw new Error(`No machines extracted from ${filePath}`);
|
|
1042
|
+
return {
|
|
1043
|
+
kind: "local-file",
|
|
1044
|
+
locator: filePath,
|
|
1045
|
+
format: "xstate",
|
|
1046
|
+
graph: fromXStateConfig(config)
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
const parsed = JSON.parse(contents);
|
|
1050
|
+
if ("rootNode" in parsed && "edges" in parsed) return {
|
|
1051
|
+
kind: "local-file",
|
|
1052
|
+
locator: filePath,
|
|
1053
|
+
format: "digraph",
|
|
1054
|
+
graph: fromStudioMachine(parsed)
|
|
1055
|
+
};
|
|
1056
|
+
if ("nodes" in parsed && "edges" in parsed) return {
|
|
1057
|
+
kind: "local-file",
|
|
1058
|
+
locator: filePath,
|
|
1059
|
+
format: "graph",
|
|
1060
|
+
graph: parsed
|
|
1061
|
+
};
|
|
1062
|
+
if ("states" in parsed) return {
|
|
1063
|
+
kind: "local-file",
|
|
1064
|
+
locator: filePath,
|
|
1065
|
+
format: "xstate",
|
|
1066
|
+
graph: fromXStateConfig(parsed)
|
|
1067
|
+
};
|
|
1068
|
+
throw new Error(`Unsupported local sync input: ${filePath}`);
|
|
1069
|
+
}
|
|
1070
|
+
function inferWritableTargetFormat(filePath) {
|
|
1071
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".js") || filePath.endsWith(".jsx")) return "xstate";
|
|
1072
|
+
if (filePath.endsWith(".digraph.json")) return "digraph";
|
|
1073
|
+
if (filePath.endsWith(".graph.json")) return "graph";
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
function resolveLocalXStateMachineId(graph) {
|
|
1077
|
+
const rootKey = (graph.nodes.find((node) => node.parentId == null) ?? null)?.data?.key;
|
|
1078
|
+
if (rootKey && rootKey !== "(machine)") return rootKey;
|
|
1079
|
+
return graph.id;
|
|
1080
|
+
}
|
|
1081
|
+
function serializeGraph(graph, format, options = {}) {
|
|
1082
|
+
switch (format) {
|
|
1083
|
+
case "digraph": return `${JSON.stringify(toStudioMachine(graph), null, 2)}\n`;
|
|
1084
|
+
case "graph": return `${JSON.stringify(graph, null, 2)}\n`;
|
|
1085
|
+
case "xstate": {
|
|
1086
|
+
const targetLanguage = inferXStateTargetLanguage(options.targetPath);
|
|
1087
|
+
const source = graphToXStateTS({
|
|
1088
|
+
...graph,
|
|
1089
|
+
id: resolveLocalXStateMachineId(graph)
|
|
1090
|
+
}, { targetLanguage });
|
|
1091
|
+
if (!options.remoteMachineId) return source;
|
|
1092
|
+
return upsertStatelyPragma(source, options.remoteMachineId, options.targetPath ? { fileName: options.targetPath } : {});
|
|
1093
|
+
}
|
|
1094
|
+
default: {
|
|
1095
|
+
const exhaustive = format;
|
|
1096
|
+
throw new Error(`Unsupported sync output format: ${exhaustive}`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
function inferXStateTargetLanguage(targetPath) {
|
|
1101
|
+
if (!targetPath) return "typescript";
|
|
1102
|
+
return /\.(?:c|m)?jsx?$/i.test(targetPath) ? "javascript" : "typescript";
|
|
1103
|
+
}
|
|
1104
|
+
async function resolveRemoteMachine(machineId, baseUrl, kind, locator, options) {
|
|
1105
|
+
const machineResponse = await (options.client ?? createStatelyClient({
|
|
1106
|
+
apiKey: options.apiKey,
|
|
1107
|
+
baseUrl,
|
|
1108
|
+
fetch: options.fetch
|
|
1109
|
+
})).machines.get(machineId);
|
|
1110
|
+
return {
|
|
1111
|
+
kind,
|
|
1112
|
+
locator,
|
|
1113
|
+
format: "digraph",
|
|
1114
|
+
graph: fromStudioMachine(machineResponse && typeof machineResponse === "object" && "definition" in machineResponse && machineResponse.definition ? machineResponse.definition : machineResponse),
|
|
1115
|
+
remoteMachineId: machineId
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
async function resolveSyncInput(locator, options) {
|
|
1119
|
+
if (await fileExists(path.resolve(options.cwd ?? process.cwd(), locator))) return resolveLocalFile(locator, options);
|
|
1120
|
+
if (isUrl(locator)) {
|
|
1121
|
+
const url = new URL(locator);
|
|
1122
|
+
const machineId = url.pathname.split("/").filter(Boolean).at(-1);
|
|
1123
|
+
if (!machineId) throw new Error(`Could not resolve machine ID from URL: ${locator}`);
|
|
1124
|
+
return resolveRemoteMachine(machineId, url.origin, "studio-url", locator, options);
|
|
1125
|
+
}
|
|
1126
|
+
return resolveRemoteMachine(locator, options.baseUrl, "studio-machine-id", locator, options);
|
|
1127
|
+
}
|
|
1128
|
+
function summarizeDiff(diff) {
|
|
1129
|
+
const nodeChanges = diff.nodes.added.length + diff.nodes.removed.length + diff.nodes.updated.length;
|
|
1130
|
+
const edgeChanges = diff.edges.added.length + diff.edges.removed.length + diff.edges.updated.length;
|
|
1131
|
+
return {
|
|
1132
|
+
hasChanges: !isEmptyDiff(diff),
|
|
1133
|
+
nodeChanges,
|
|
1134
|
+
edgeChanges
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
async function planSync(options) {
|
|
1138
|
+
const source = await resolveSyncInput(options.source, options);
|
|
1139
|
+
const target = await resolveSyncInput(options.target, options);
|
|
1140
|
+
const diff = getDiff(source.graph, target.graph);
|
|
1141
|
+
return {
|
|
1142
|
+
source,
|
|
1143
|
+
target,
|
|
1144
|
+
diff,
|
|
1145
|
+
summary: summarizeDiff(diff),
|
|
1146
|
+
warnings: []
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
async function pullSync(options) {
|
|
1150
|
+
const source = await resolveSyncInput(options.source, options);
|
|
1151
|
+
const outputPath = path.resolve(options.cwd ?? process.cwd(), options.target);
|
|
1152
|
+
const fallbackFormat = inferWritableTargetFormat(outputPath);
|
|
1153
|
+
let targetFormat = fallbackFormat;
|
|
1154
|
+
if (await fileExists(outputPath)) try {
|
|
1155
|
+
targetFormat = (await resolveLocalFile(options.target, options)).format;
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
if (!fallbackFormat) throw error;
|
|
1158
|
+
}
|
|
1159
|
+
if (!targetFormat) throw new Error(`Could not infer a writable target format from ${outputPath}. Use an existing digraph/graph file or a .digraph.json/.graph.json target.`);
|
|
1160
|
+
const serialized = serializeGraph(source.graph, targetFormat, {
|
|
1161
|
+
remoteMachineId: source.remoteMachineId,
|
|
1162
|
+
targetPath: outputPath
|
|
1163
|
+
});
|
|
1164
|
+
await fs.writeFile(outputPath, serialized, "utf8");
|
|
1165
|
+
return {
|
|
1166
|
+
source,
|
|
1167
|
+
target: {
|
|
1168
|
+
kind: "local-file",
|
|
1169
|
+
locator: outputPath,
|
|
1170
|
+
format: targetFormat,
|
|
1171
|
+
graph: source.graph
|
|
1172
|
+
},
|
|
1173
|
+
outputPath
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function inferDefaultProjectName(sourcePath, repo) {
|
|
1177
|
+
if (repo?.repo) return repo.repo;
|
|
1178
|
+
const parentDir = path.basename(path.dirname(sourcePath));
|
|
1179
|
+
if (parentDir && parentDir !== ".") return parentDir;
|
|
1180
|
+
return path.basename(sourcePath, path.extname(sourcePath));
|
|
1181
|
+
}
|
|
1182
|
+
async function resolvePushProject(client, sourcePath, options) {
|
|
1183
|
+
async function withResolvedProjectVersionId(project, explicitProjectVersionId) {
|
|
1184
|
+
if (project.projectVersionId) return project;
|
|
1185
|
+
if (explicitProjectVersionId) return {
|
|
1186
|
+
...project,
|
|
1187
|
+
projectVersionId: explicitProjectVersionId
|
|
1188
|
+
};
|
|
1189
|
+
const matchedProject = (await client.projects.list()).find((candidate) => {
|
|
1190
|
+
return candidate.projectId === project.projectId || candidate.id != null && project.id != null && candidate.id === project.id;
|
|
1191
|
+
});
|
|
1192
|
+
if (matchedProject?.projectVersionId) return {
|
|
1193
|
+
...project,
|
|
1194
|
+
projectVersionId: matchedProject.projectVersionId
|
|
1195
|
+
};
|
|
1196
|
+
return project;
|
|
1197
|
+
}
|
|
1198
|
+
const explicitProject = options.project;
|
|
1199
|
+
if (explicitProject?.projectVersionId) return withResolvedProjectVersionId(await client.projects.get(explicitProject.projectVersionId), explicitProject.projectVersionId);
|
|
1200
|
+
if (explicitProject?.projectId) return withResolvedProjectVersionId(await client.projects.get(explicitProject.projectId));
|
|
1201
|
+
const inferredRepo = explicitProject?.repo ?? await inferConnectedRepo(sourcePath, options.cwd);
|
|
1202
|
+
const projectInput = {
|
|
1203
|
+
name: explicitProject?.name ?? inferDefaultProjectName(sourcePath, inferredRepo),
|
|
1204
|
+
visibility: explicitProject?.visibility ?? "Private",
|
|
1205
|
+
...explicitProject?.description ? { description: explicitProject.description } : {},
|
|
1206
|
+
...explicitProject?.keywords ? { keywords: explicitProject.keywords } : {},
|
|
1207
|
+
...inferredRepo ? { repo: inferredRepo } : {}
|
|
1208
|
+
};
|
|
1209
|
+
return withResolvedProjectVersionId(await client.projects.ensure(projectInput));
|
|
1210
|
+
}
|
|
1211
|
+
async function pushSync(options) {
|
|
1212
|
+
const client = options.client ?? createStatelyClient({
|
|
1213
|
+
apiKey: options.apiKey,
|
|
1214
|
+
baseUrl: options.baseUrl,
|
|
1215
|
+
fetch: options.fetch
|
|
1216
|
+
});
|
|
1217
|
+
const source = await resolveSyncInput(options.source, {
|
|
1218
|
+
...options,
|
|
1219
|
+
target: options.source
|
|
1220
|
+
});
|
|
1221
|
+
if (source.kind !== "local-file") throw new Error("pushSync currently requires a local source file.");
|
|
1222
|
+
const sourcePath = source.locator;
|
|
1223
|
+
const project = await resolvePushProject(client, sourcePath, options);
|
|
1224
|
+
const existingMachineId = source.format === "xstate" ? getStatelyPragma(await fs.readFile(sourcePath, "utf8"), sourcePath)?.id : void 0;
|
|
1225
|
+
let machine;
|
|
1226
|
+
if (existingMachineId) machine = await client.machines.update({
|
|
1227
|
+
id: existingMachineId,
|
|
1228
|
+
definition: toStudioMachine(source.graph)
|
|
1229
|
+
});
|
|
1230
|
+
else {
|
|
1231
|
+
if (!project.projectVersionId) throw new Error("Resolved project is missing projectVersionId.");
|
|
1232
|
+
machine = await client.machines.create({
|
|
1233
|
+
projectVersionId: project.projectVersionId,
|
|
1234
|
+
definition: toStudioMachine(source.graph),
|
|
1235
|
+
xstateVersion: options.xstateVersion ?? 5
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
let outputPath;
|
|
1239
|
+
if (source.format === "xstate") {
|
|
1240
|
+
const contents = await fs.readFile(sourcePath, "utf8");
|
|
1241
|
+
const nextContents = upsertStatelyPragma(contents, machine.id, { fileName: sourcePath });
|
|
1242
|
+
if (nextContents !== contents) {
|
|
1243
|
+
await fs.writeFile(sourcePath, nextContents, "utf8");
|
|
1244
|
+
outputPath = sourcePath;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return {
|
|
1248
|
+
source,
|
|
1249
|
+
project,
|
|
1250
|
+
machine,
|
|
1251
|
+
...outputPath ? { outputPath } : {}
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
//#endregion
|
|
5
1256
|
export { planSync, pullSync, pushLocalMachineLinks, pushSync };
|