@statelyai/sdk 0.7.0 → 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/dist/sync.mjs CHANGED
@@ -1,14 +1,772 @@
1
1
  import { createStatelyClient } from "./studio.mjs";
2
- import { d as upsertStatelyPragma, l as findStatelyPragmaAttachments, t as graphToXStateTS, u as getStatelyPragma } from "./graphToXStateTS-Gzh0ZqbN.mjs";
2
+ import { d as upsertStatelyPragma, l as findStatelyPragmaAttachments, t as graphToXStateTS, u as getStatelyPragma } from "./graphToXStateTS-moihsH_U.mjs";
3
3
  import { fromStudioMachine, toStudioMachine } from "./graph.mjs";
4
+ import * as ts$1 from "typescript";
5
+ import ts from "typescript";
4
6
  import { getDiff, isEmptyDiff } from "@statelyai/graph";
5
7
  import fs from "node:fs/promises";
6
8
  import path from "node:path";
7
9
  import { execFile } from "node:child_process";
8
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";
9
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
10
590
  //#region src/sync.ts
11
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
+ }
12
770
  function isUrl(value) {
13
771
  try {
14
772
  const url = new URL(value);
@@ -197,7 +955,7 @@ async function pushLocalMachineLinks(options) {
197
955
  if (!project.projectVersionId) throw new Error("Resolved project is missing projectVersionId.");
198
956
  const contents = await fs.readFile(sourcePath, "utf8");
199
957
  const attachments = findStatelyPragmaAttachments(contents, sourcePath);
200
- const extracted = await client.code.extractMachines(contents);
958
+ const extracted = await extractMachinesFromLocalSource(sourcePath, contents);
201
959
  if (attachments.length === 0 || extracted.machines.length === 0) return {
202
960
  sourcePath,
203
961
  project,
@@ -205,7 +963,7 @@ async function pushLocalMachineLinks(options) {
205
963
  updated: [],
206
964
  skipped: [{
207
965
  machineIndex: 0,
208
- reason: "No local machines were discovered in this file."
966
+ reason: extracted.error != null ? formatLocalMachineExtractionIssue(extracted.error) : "No local machines were discovered in this file."
209
967
  }]
210
968
  };
211
969
  if (attachments.length !== extracted.machines.length) return {
@@ -315,12 +1073,21 @@ function inferWritableTargetFormat(filePath) {
315
1073
  if (filePath.endsWith(".graph.json")) return "graph";
316
1074
  return null;
317
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
+ }
318
1081
  function serializeGraph(graph, format, options = {}) {
319
1082
  switch (format) {
320
1083
  case "digraph": return `${JSON.stringify(toStudioMachine(graph), null, 2)}\n`;
321
1084
  case "graph": return `${JSON.stringify(graph, null, 2)}\n`;
322
1085
  case "xstate": {
323
- const source = graphToXStateTS(graph);
1086
+ const targetLanguage = inferXStateTargetLanguage(options.targetPath);
1087
+ const source = graphToXStateTS({
1088
+ ...graph,
1089
+ id: resolveLocalXStateMachineId(graph)
1090
+ }, { targetLanguage });
324
1091
  if (!options.remoteMachineId) return source;
325
1092
  return upsertStatelyPragma(source, options.remoteMachineId, options.targetPath ? { fileName: options.targetPath } : {});
326
1093
  }
@@ -330,16 +1097,21 @@ function serializeGraph(graph, format, options = {}) {
330
1097
  }
331
1098
  }
332
1099
  }
1100
+ function inferXStateTargetLanguage(targetPath) {
1101
+ if (!targetPath) return "typescript";
1102
+ return /\.(?:c|m)?jsx?$/i.test(targetPath) ? "javascript" : "typescript";
1103
+ }
333
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);
334
1110
  return {
335
1111
  kind,
336
1112
  locator,
337
1113
  format: "digraph",
338
- graph: fromStudioMachine(await (options.client ?? createStatelyClient({
339
- apiKey: options.apiKey,
340
- baseUrl,
341
- fetch: options.fetch
342
- })).machines.get(machineId)),
1114
+ graph: fromStudioMachine(machineResponse && typeof machineResponse === "object" && "definition" in machineResponse && machineResponse.definition ? machineResponse.definition : machineResponse),
343
1115
  remoteMachineId: machineId
344
1116
  };
345
1117
  }
@@ -408,9 +1180,24 @@ function inferDefaultProjectName(sourcePath, repo) {
408
1180
  return path.basename(sourcePath, path.extname(sourcePath));
409
1181
  }
410
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
+ }
411
1198
  const explicitProject = options.project;
412
- if (explicitProject?.projectVersionId) return client.projects.get(explicitProject.projectVersionId);
413
- if (explicitProject?.projectId) return client.projects.get(explicitProject.projectId);
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));
414
1201
  const inferredRepo = explicitProject?.repo ?? await inferConnectedRepo(sourcePath, options.cwd);
415
1202
  const projectInput = {
416
1203
  name: explicitProject?.name ?? inferDefaultProjectName(sourcePath, inferredRepo),
@@ -419,7 +1206,7 @@ async function resolvePushProject(client, sourcePath, options) {
419
1206
  ...explicitProject?.keywords ? { keywords: explicitProject.keywords } : {},
420
1207
  ...inferredRepo ? { repo: inferredRepo } : {}
421
1208
  };
422
- return client.projects.ensure(projectInput);
1209
+ return withResolvedProjectVersionId(await client.projects.ensure(projectInput));
423
1210
  }
424
1211
  async function pushSync(options) {
425
1212
  const client = options.client ?? createStatelyClient({