@statelyai/sdk 0.5.1 → 0.6.0
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 +155 -82
- package/dist/api.d.mts +20 -0
- package/dist/api.mjs +56 -0
- package/dist/cli.d.mts +100 -2
- package/dist/cli.mjs +584 -13
- package/dist/embed.d.mts +21 -3
- package/dist/embed.mjs +52 -2
- package/dist/graph.d.mts +1 -1
- package/dist/graph.mjs +20 -7
- package/dist/graphToXStateTS-CvXM8wHL.mjs +344 -0
- package/dist/index.d.mts +29 -140
- package/dist/index.mjs +4 -3
- package/dist/{inspect-ttRIjoCu.d.mts → inspect-DIxB2Tr3.d.mts} +1 -1
- package/dist/inspect.d.mts +2 -2
- package/dist/inspect.mjs +1 -1
- package/dist/{protocol-BPuwbNCz.d.mts → protocol-CEbWQPYe.d.mts} +70 -5
- package/dist/studio.d.mts +112 -2
- package/dist/studio.mjs +73 -11
- package/dist/sync.d.mts +19 -3
- package/dist/sync.mjs +128 -6
- package/dist/{transport-DoCHBLTu.mjs → transport-C0eTgNNu.mjs} +14 -0
- package/package.json +24 -15
- package/schemas/statelyai.schema.json +128 -0
- package/dist/graphToXStateTS-CtecEESq.mjs +0 -568
- package/dist/studio-D2uQhrvX.d.mts +0 -54
- /package/dist/{graph-BfezxFKJ.d.mts → graph-CB-ALrdk.d.mts} +0 -0
package/dist/sync.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { createStatelyClient } from "./studio.mjs";
|
|
2
|
+
import { d as upsertStatelyPragma, t as graphToXStateTS } from "./graphToXStateTS-CvXM8wHL.mjs";
|
|
2
3
|
import { fromStudioMachine, toStudioMachine } from "./graph.mjs";
|
|
3
|
-
import { t as graphToXStateTS } from "./graphToXStateTS-CtecEESq.mjs";
|
|
4
4
|
import { getDiff, isEmptyDiff } from "@statelyai/graph";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { promisify } from "node:util";
|
|
7
9
|
|
|
8
10
|
//#region src/sync.ts
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
9
12
|
function isUrl(value) {
|
|
10
13
|
try {
|
|
11
14
|
const url = new URL(value);
|
|
@@ -22,6 +25,62 @@ async function fileExists(filePath) {
|
|
|
22
25
|
return false;
|
|
23
26
|
}
|
|
24
27
|
}
|
|
28
|
+
function parseGitHubRemote(remoteUrl) {
|
|
29
|
+
const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
30
|
+
if (httpsMatch) {
|
|
31
|
+
const [, owner, repo] = httpsMatch;
|
|
32
|
+
if (!owner || !repo) return null;
|
|
33
|
+
return {
|
|
34
|
+
url: `https://github.com/${owner}/${repo}`,
|
|
35
|
+
owner,
|
|
36
|
+
repo
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
40
|
+
if (sshMatch) {
|
|
41
|
+
const [, owner, repo] = sshMatch;
|
|
42
|
+
if (!owner || !repo) return null;
|
|
43
|
+
return {
|
|
44
|
+
url: `https://github.com/${owner}/${repo}`,
|
|
45
|
+
owner,
|
|
46
|
+
repo
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
async function inferConnectedRepo(filePath, cwd) {
|
|
52
|
+
const workingDir = cwd ?? path.dirname(filePath);
|
|
53
|
+
try {
|
|
54
|
+
const [{ stdout: repoRootStdout }, { stdout: remoteStdout }, { stdout: branchStdout }, { stdout: treeShaStdout }] = await Promise.all([
|
|
55
|
+
execFileAsync("git", ["rev-parse", "--show-toplevel"], { cwd: workingDir }),
|
|
56
|
+
execFileAsync("git", [
|
|
57
|
+
"remote",
|
|
58
|
+
"get-url",
|
|
59
|
+
"origin"
|
|
60
|
+
], { cwd: workingDir }),
|
|
61
|
+
execFileAsync("git", ["branch", "--show-current"], { cwd: workingDir }),
|
|
62
|
+
execFileAsync("git", ["rev-parse", "HEAD"], { cwd: workingDir })
|
|
63
|
+
]);
|
|
64
|
+
const repoRoot = repoRootStdout.trim();
|
|
65
|
+
const remote = parseGitHubRemote(remoteStdout.trim());
|
|
66
|
+
const branch = branchStdout.trim();
|
|
67
|
+
const treeSha = treeShaStdout.trim();
|
|
68
|
+
if (!remote || !branch || !treeSha) return;
|
|
69
|
+
const relativePath = path.relative(repoRoot, filePath).replace(/\\/g, "/");
|
|
70
|
+
const relativeDir = path.dirname(relativePath).replace(/\\/g, "/");
|
|
71
|
+
const selectedPaths = relativePath && relativePath !== "." ? [relativePath] : [];
|
|
72
|
+
return {
|
|
73
|
+
...remote,
|
|
74
|
+
branch,
|
|
75
|
+
treeSha,
|
|
76
|
+
autoSync: false,
|
|
77
|
+
pathForNewFiles: relativeDir && relativeDir !== "." ? relativeDir : "src/stately-studio",
|
|
78
|
+
selectedPaths
|
|
79
|
+
};
|
|
80
|
+
} catch {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
25
84
|
function normalizeActions(value) {
|
|
26
85
|
if (value == null) return [];
|
|
27
86
|
return (Array.isArray(value) ? value : [value]).map((item) => {
|
|
@@ -172,11 +231,15 @@ function inferWritableTargetFormat(filePath) {
|
|
|
172
231
|
if (filePath.endsWith(".graph.json")) return "graph";
|
|
173
232
|
return null;
|
|
174
233
|
}
|
|
175
|
-
function serializeGraph(graph, format) {
|
|
234
|
+
function serializeGraph(graph, format, options = {}) {
|
|
176
235
|
switch (format) {
|
|
177
236
|
case "digraph": return `${JSON.stringify(toStudioMachine(graph), null, 2)}\n`;
|
|
178
237
|
case "graph": return `${JSON.stringify(graph, null, 2)}\n`;
|
|
179
|
-
case "xstate":
|
|
238
|
+
case "xstate": {
|
|
239
|
+
const source = graphToXStateTS(graph);
|
|
240
|
+
if (!options.remoteMachineId) return source;
|
|
241
|
+
return upsertStatelyPragma(source, options.remoteMachineId, options.targetPath ? { fileName: options.targetPath } : {});
|
|
242
|
+
}
|
|
180
243
|
default: {
|
|
181
244
|
const exhaustive = format;
|
|
182
245
|
throw new Error(`Unsupported sync output format: ${exhaustive}`);
|
|
@@ -192,7 +255,8 @@ async function resolveRemoteMachine(machineId, baseUrl, kind, locator, options)
|
|
|
192
255
|
apiKey: options.apiKey,
|
|
193
256
|
baseUrl,
|
|
194
257
|
fetch: options.fetch
|
|
195
|
-
})).machines.get(machineId))
|
|
258
|
+
})).machines.get(machineId)),
|
|
259
|
+
remoteMachineId: machineId
|
|
196
260
|
};
|
|
197
261
|
}
|
|
198
262
|
async function resolveSyncInput(locator, options) {
|
|
@@ -237,7 +301,10 @@ async function pullSync(options) {
|
|
|
237
301
|
if (!fallbackFormat) throw error;
|
|
238
302
|
}
|
|
239
303
|
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.`);
|
|
240
|
-
const serialized = serializeGraph(source.graph, targetFormat
|
|
304
|
+
const serialized = serializeGraph(source.graph, targetFormat, {
|
|
305
|
+
remoteMachineId: source.remoteMachineId,
|
|
306
|
+
targetPath: outputPath
|
|
307
|
+
});
|
|
241
308
|
await fs.writeFile(outputPath, serialized, "utf8");
|
|
242
309
|
return {
|
|
243
310
|
source,
|
|
@@ -250,6 +317,61 @@ async function pullSync(options) {
|
|
|
250
317
|
outputPath
|
|
251
318
|
};
|
|
252
319
|
}
|
|
320
|
+
function inferDefaultProjectName(sourcePath, repo) {
|
|
321
|
+
if (repo?.repo) return repo.repo;
|
|
322
|
+
const parentDir = path.basename(path.dirname(sourcePath));
|
|
323
|
+
if (parentDir && parentDir !== ".") return parentDir;
|
|
324
|
+
return path.basename(sourcePath, path.extname(sourcePath));
|
|
325
|
+
}
|
|
326
|
+
async function resolvePushProject(client, sourcePath, options) {
|
|
327
|
+
const explicitProject = options.project;
|
|
328
|
+
if (explicitProject?.projectVersionId) return client.projects.get(explicitProject.projectVersionId);
|
|
329
|
+
if (explicitProject?.projectId) return client.projects.get(explicitProject.projectId);
|
|
330
|
+
const inferredRepo = explicitProject?.repo ?? await inferConnectedRepo(sourcePath, options.cwd);
|
|
331
|
+
const projectInput = {
|
|
332
|
+
name: explicitProject?.name ?? inferDefaultProjectName(sourcePath, inferredRepo),
|
|
333
|
+
visibility: explicitProject?.visibility ?? "Private",
|
|
334
|
+
...explicitProject?.description ? { description: explicitProject.description } : {},
|
|
335
|
+
...explicitProject?.keywords ? { keywords: explicitProject.keywords } : {},
|
|
336
|
+
...inferredRepo ? { repo: inferredRepo } : {}
|
|
337
|
+
};
|
|
338
|
+
return client.projects.ensure(projectInput);
|
|
339
|
+
}
|
|
340
|
+
async function pushSync(options) {
|
|
341
|
+
const client = options.client ?? createStatelyClient({
|
|
342
|
+
apiKey: options.apiKey,
|
|
343
|
+
baseUrl: options.baseUrl,
|
|
344
|
+
fetch: options.fetch
|
|
345
|
+
});
|
|
346
|
+
const source = await resolveSyncInput(options.source, {
|
|
347
|
+
...options,
|
|
348
|
+
target: options.source
|
|
349
|
+
});
|
|
350
|
+
if (source.kind !== "local-file") throw new Error("pushSync currently requires a local source file.");
|
|
351
|
+
const sourcePath = source.locator;
|
|
352
|
+
const project = await resolvePushProject(client, sourcePath, options);
|
|
353
|
+
if (!project.projectVersionId) throw new Error("Resolved project is missing projectVersionId.");
|
|
354
|
+
const machine = await client.machines.create({
|
|
355
|
+
projectVersionId: project.projectVersionId,
|
|
356
|
+
definition: toStudioMachine(source.graph),
|
|
357
|
+
xstateVersion: options.xstateVersion ?? 5
|
|
358
|
+
});
|
|
359
|
+
let outputPath;
|
|
360
|
+
if (source.format === "xstate") {
|
|
361
|
+
const contents = await fs.readFile(sourcePath, "utf8");
|
|
362
|
+
const nextContents = upsertStatelyPragma(contents, machine.id, { fileName: sourcePath });
|
|
363
|
+
if (nextContents !== contents) {
|
|
364
|
+
await fs.writeFile(sourcePath, nextContents, "utf8");
|
|
365
|
+
outputPath = sourcePath;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
source,
|
|
370
|
+
project,
|
|
371
|
+
machine,
|
|
372
|
+
...outputPath ? { outputPath } : {}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
253
375
|
|
|
254
376
|
//#endregion
|
|
255
|
-
export { planSync, pullSync };
|
|
377
|
+
export { planSync, pullSync, pushSync };
|
|
@@ -89,6 +89,20 @@ function createPendingExportManager(sendRetrieve) {
|
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
function toInitMessage(options) {
|
|
92
|
+
if ("machines" in options) return {
|
|
93
|
+
type: "@statelyai.project.init",
|
|
94
|
+
machines: options.machines,
|
|
95
|
+
currentMachineId: options.currentMachineId,
|
|
96
|
+
mode: options.mode,
|
|
97
|
+
theme: options.theme,
|
|
98
|
+
readOnly: options.readOnly,
|
|
99
|
+
depth: options.depth,
|
|
100
|
+
leftPanels: options.panels?.leftPanels,
|
|
101
|
+
rightPanels: options.panels?.rightPanels,
|
|
102
|
+
activePanels: options.panels?.activePanels,
|
|
103
|
+
commentsByMachineId: options.commentsByMachineId,
|
|
104
|
+
unsavedIndicator: options.unsavedIndicator
|
|
105
|
+
};
|
|
92
106
|
return {
|
|
93
107
|
type: "@statelyai.init",
|
|
94
108
|
machine: options.machine,
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"files": [
|
|
6
|
-
"dist"
|
|
7
|
-
],
|
|
8
|
-
"type": "module",
|
|
9
5
|
"bin": {
|
|
10
6
|
"statelyai": "./dist/cli.mjs"
|
|
11
7
|
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"schemas"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
12
13
|
"main": "./dist/index.mjs",
|
|
13
14
|
"types": "./dist/index.d.mts",
|
|
14
15
|
"exports": {
|
|
@@ -43,21 +44,20 @@
|
|
|
43
44
|
"./studio": {
|
|
44
45
|
"types": "./dist/studio.d.mts",
|
|
45
46
|
"import": "./dist/studio.mjs"
|
|
46
|
-
}
|
|
47
|
+
},
|
|
48
|
+
"./api": {
|
|
49
|
+
"types": "./dist/api.d.mts",
|
|
50
|
+
"import": "./dist/api.mjs"
|
|
51
|
+
},
|
|
52
|
+
"./statelyai.schema.json": "./schemas/statelyai.schema.json",
|
|
53
|
+
"./schemas/statelyai.schema.json": "./schemas/statelyai.schema.json"
|
|
47
54
|
},
|
|
48
55
|
"dependencies": {
|
|
49
56
|
"@oclif/core": "^4.10.3",
|
|
50
57
|
"@statelyai/graph": "^0.9.0",
|
|
51
58
|
"typescript": "^5.9.3",
|
|
52
|
-
"xstate": "^5.0.0"
|
|
53
|
-
|
|
54
|
-
"oclif": {
|
|
55
|
-
"bin": "statelyai",
|
|
56
|
-
"commands": {
|
|
57
|
-
"strategy": "explicit",
|
|
58
|
-
"target": "./dist/cli.mjs",
|
|
59
|
-
"identifier": "COMMANDS"
|
|
60
|
-
}
|
|
59
|
+
"xstate": "^5.0.0",
|
|
60
|
+
"@statelyai/graph-tools": "0.0.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/json-schema": "^7.0.15",
|
|
@@ -65,6 +65,15 @@
|
|
|
65
65
|
"tsdown": "0.21.0-beta.2",
|
|
66
66
|
"vitest": "^3.2.4"
|
|
67
67
|
},
|
|
68
|
+
"oclif": {
|
|
69
|
+
"bin": "statelyai",
|
|
70
|
+
"commands": {
|
|
71
|
+
"identifier": "COMMANDS",
|
|
72
|
+
"strategy": "explicit",
|
|
73
|
+
"target": "./dist/cli.mjs"
|
|
74
|
+
},
|
|
75
|
+
"topicSeparator": " "
|
|
76
|
+
},
|
|
68
77
|
"scripts": {
|
|
69
78
|
"build": "tsdown",
|
|
70
79
|
"test": "vitest run",
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://stately.ai/schemas/statelyai.json",
|
|
4
|
+
"title": "Stately CLI project configuration",
|
|
5
|
+
"description": "Configuration for mapping local machine sources to a Stately Studio project.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["version", "projectId", "sources"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"format": "uri-reference",
|
|
13
|
+
"description": "Optional self-reference to this schema."
|
|
14
|
+
},
|
|
15
|
+
"version": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$",
|
|
18
|
+
"description": "Schema version for statelyai.json, expressed as a semantic version string."
|
|
19
|
+
},
|
|
20
|
+
"projectId": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1,
|
|
23
|
+
"description": "Stable Stately Studio project identifier."
|
|
24
|
+
},
|
|
25
|
+
"studioUrl": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"format": "uri",
|
|
28
|
+
"default": "https://stately.ai",
|
|
29
|
+
"description": "Base URL of the Studio instance that owns the project."
|
|
30
|
+
},
|
|
31
|
+
"defaultXStateVersion": {
|
|
32
|
+
"type": "integer",
|
|
33
|
+
"minimum": 5,
|
|
34
|
+
"default": 5,
|
|
35
|
+
"description": "Default XState major version for sources that do not override it. Only XState 5+ is supported."
|
|
36
|
+
},
|
|
37
|
+
"sources": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"minItems": 1,
|
|
40
|
+
"description": "Source groups used to discover and classify local machine files.",
|
|
41
|
+
"items": {
|
|
42
|
+
"$ref": "#/$defs/source"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"$defs": {
|
|
47
|
+
"globPattern": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"minLength": 1,
|
|
50
|
+
"description": "A glob pattern evaluated relative to the statelyai.json location."
|
|
51
|
+
},
|
|
52
|
+
"sourceFormat": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"enum": [
|
|
55
|
+
"auto",
|
|
56
|
+
"xstate",
|
|
57
|
+
"json",
|
|
58
|
+
"xgraph",
|
|
59
|
+
"digraph",
|
|
60
|
+
"mermaid",
|
|
61
|
+
"scxml",
|
|
62
|
+
"asl-json",
|
|
63
|
+
"asl-yaml",
|
|
64
|
+
"redux",
|
|
65
|
+
"zustand"
|
|
66
|
+
],
|
|
67
|
+
"description": "Machine format for matching sources. Use xstate for JavaScript or TypeScript machine source files."
|
|
68
|
+
},
|
|
69
|
+
"source": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"additionalProperties": false,
|
|
72
|
+
"required": ["include"],
|
|
73
|
+
"properties": {
|
|
74
|
+
"name": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"minLength": 1,
|
|
77
|
+
"description": "Optional label for this source group."
|
|
78
|
+
},
|
|
79
|
+
"include": {
|
|
80
|
+
"type": "array",
|
|
81
|
+
"minItems": 1,
|
|
82
|
+
"items": {
|
|
83
|
+
"$ref": "#/$defs/globPattern"
|
|
84
|
+
},
|
|
85
|
+
"description": "Glob patterns for files to include in this source group."
|
|
86
|
+
},
|
|
87
|
+
"exclude": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"items": {
|
|
90
|
+
"$ref": "#/$defs/globPattern"
|
|
91
|
+
},
|
|
92
|
+
"description": "Glob patterns excluded from this source group."
|
|
93
|
+
},
|
|
94
|
+
"format": {
|
|
95
|
+
"$ref": "#/$defs/sourceFormat",
|
|
96
|
+
"default": "auto"
|
|
97
|
+
},
|
|
98
|
+
"xstateVersion": {
|
|
99
|
+
"type": "integer",
|
|
100
|
+
"minimum": 5,
|
|
101
|
+
"description": "Optional XState major version override for this source group. Only XState 5+ is supported."
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"examples": [
|
|
107
|
+
{
|
|
108
|
+
"$schema": "https://stately.ai/schemas/statelyai.json",
|
|
109
|
+
"version": "1.0.0",
|
|
110
|
+
"projectId": "project_123",
|
|
111
|
+
"studioUrl": "https://stately.ai",
|
|
112
|
+
"defaultXStateVersion": 5,
|
|
113
|
+
"sources": [
|
|
114
|
+
{
|
|
115
|
+
"name": "app-machines",
|
|
116
|
+
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
|
117
|
+
"exclude": ["**/*.test.ts", "**/*.spec.ts", "**/dist/**"],
|
|
118
|
+
"format": "xstate"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "docs",
|
|
122
|
+
"include": ["docs/**/*.mmd"],
|
|
123
|
+
"format": "mermaid"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|