@statelyai/sdk 0.6.0 → 0.7.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 +68 -16
- package/dist/assetStorage.d.mts +73 -0
- package/dist/assetStorage.mjs +99 -0
- package/dist/cli.d.mts +34 -9
- package/dist/cli.mjs +327 -23
- package/dist/embed.d.mts +9 -2
- package/dist/embed.mjs +14 -8
- package/dist/graph.d.mts +1 -1
- package/dist/graph.mjs +9 -5
- package/dist/graphToXStateTS-Gzh0ZqbN.mjs +709 -0
- package/dist/index.d.mts +142 -7
- package/dist/index.mjs +4 -3
- package/dist/{inspect-DIxB2Tr3.d.mts → inspect-Bg9FTvb3.d.mts} +1 -1
- package/dist/inspect.d.mts +2 -2
- package/dist/inspect.mjs +1 -1
- package/dist/{protocol-CEbWQPYe.d.mts → protocol-DN4mH4jR.d.mts} +1 -2
- package/dist/studio.d.mts +7 -1
- package/dist/studio.mjs +7 -0
- package/dist/sync.d.mts +20 -2
- package/dist/sync.mjs +99 -7
- package/dist/{transport-C0eTgNNu.mjs → transport-C8UTS3Fa.mjs} +1 -1
- package/package.json +9 -4
- package/schemas/statelyai.schema.json +1 -1
- package/schemas/xstate-json.schema.json +214 -0
- package/dist/graphToXStateTS-CvXM8wHL.mjs +0 -344
- /package/dist/{graph-CB-ALrdk.d.mts → graph-DpBGHZwl.d.mts} +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createStatelyClient } from "./studio.mjs";
|
|
3
|
-
import "./graphToXStateTS-
|
|
4
|
-
import { planSync, pullSync } from "./sync.mjs";
|
|
3
|
+
import { u as getStatelyPragma } from "./graphToXStateTS-Gzh0ZqbN.mjs";
|
|
4
|
+
import { planSync, pullSync, pushLocalMachineLinks } from "./sync.mjs";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import * as path$1 from "node:path";
|
|
7
7
|
import path from "node:path";
|
|
@@ -931,24 +931,26 @@ function describeCredentialBackend(backend, location) {
|
|
|
931
931
|
}
|
|
932
932
|
|
|
933
933
|
//#endregion
|
|
934
|
-
//#region src/
|
|
935
|
-
const execFileAsync = promisify(execFile);
|
|
934
|
+
//#region src/projectConfig.ts
|
|
936
935
|
const STATELY_CONFIG_FILE = "statelyai.json";
|
|
937
936
|
const STATELY_CONFIG_SCHEMA_URL = "https://stately.ai/schemas/statelyai.json";
|
|
938
937
|
const STATELY_CONFIG_VERSION = "1.0.0";
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
938
|
+
const DEFAULT_SOURCE_EXCLUDES = [
|
|
939
|
+
"**/*.test.*",
|
|
940
|
+
"**/*.spec.*",
|
|
941
|
+
"**/dist/**",
|
|
942
|
+
"**/node_modules/**"
|
|
943
|
+
];
|
|
944
|
+
const CODE_SOURCE_EXTENSIONS = new Set([
|
|
945
|
+
".ts",
|
|
946
|
+
".tsx",
|
|
947
|
+
".js",
|
|
948
|
+
".jsx",
|
|
949
|
+
".mts",
|
|
950
|
+
".cts",
|
|
951
|
+
".mjs",
|
|
952
|
+
".cjs"
|
|
953
|
+
]);
|
|
952
954
|
function createStatelyProjectConfig(options) {
|
|
953
955
|
const defaultXStateVersion = options.defaultXStateVersion ?? 5;
|
|
954
956
|
return {
|
|
@@ -957,9 +959,145 @@ function createStatelyProjectConfig(options) {
|
|
|
957
959
|
projectId: options.projectId,
|
|
958
960
|
studioUrl: options.studioUrl,
|
|
959
961
|
defaultXStateVersion,
|
|
960
|
-
sources:
|
|
962
|
+
sources: []
|
|
961
963
|
};
|
|
962
964
|
}
|
|
965
|
+
async function readStatelyProjectConfig(options = {}) {
|
|
966
|
+
const rootDir = path.resolve(options.cwd ?? process.cwd());
|
|
967
|
+
const configPath = path.resolve(rootDir, options.configPath ?? STATELY_CONFIG_FILE);
|
|
968
|
+
const raw = await fs.readFile(configPath, "utf8");
|
|
969
|
+
return {
|
|
970
|
+
config: JSON.parse(raw),
|
|
971
|
+
configPath,
|
|
972
|
+
rootDir
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
async function walkFiles(rootDir, currentDir = rootDir) {
|
|
976
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
977
|
+
const files = [];
|
|
978
|
+
for (const entry of entries) {
|
|
979
|
+
if (entry.name === ".git") continue;
|
|
980
|
+
const absolutePath = path.join(currentDir, entry.name);
|
|
981
|
+
if (entry.isDirectory()) {
|
|
982
|
+
files.push(...await walkFiles(rootDir, absolutePath));
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
if (!entry.isFile()) continue;
|
|
986
|
+
files.push(path.relative(rootDir, absolutePath).replace(/\\/g, "/"));
|
|
987
|
+
}
|
|
988
|
+
return files;
|
|
989
|
+
}
|
|
990
|
+
function isCodeSourceFile(relativePath) {
|
|
991
|
+
return CODE_SOURCE_EXTENSIONS.has(path.extname(relativePath).toLowerCase());
|
|
992
|
+
}
|
|
993
|
+
function expandBraces(pattern) {
|
|
994
|
+
const match = pattern.match(/\{([^{}]+)\}/);
|
|
995
|
+
if (!match || match.index == null) return [pattern];
|
|
996
|
+
const [token, inner] = match;
|
|
997
|
+
return inner.split(",").flatMap((variant) => expandBraces(`${pattern.slice(0, match.index)}${variant}${pattern.slice(match.index + token.length)}`));
|
|
998
|
+
}
|
|
999
|
+
function globToRegExp(pattern) {
|
|
1000
|
+
let regex = "^";
|
|
1001
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
1002
|
+
const char = pattern[index];
|
|
1003
|
+
const next = pattern[index + 1];
|
|
1004
|
+
if (char === "*") {
|
|
1005
|
+
if (next === "*") {
|
|
1006
|
+
const slashAfterGlobstar = pattern[index + 2] === "/";
|
|
1007
|
+
regex += slashAfterGlobstar ? "(?:.*/)?" : ".*";
|
|
1008
|
+
index += slashAfterGlobstar ? 2 : 1;
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
regex += "[^/]*";
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (char === "?") {
|
|
1015
|
+
regex += "[^/]";
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
if ("\\.[]{}()+-^$|".includes(char)) {
|
|
1019
|
+
regex += `\\${char}`;
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
regex += char;
|
|
1023
|
+
}
|
|
1024
|
+
regex += "$";
|
|
1025
|
+
return new RegExp(regex);
|
|
1026
|
+
}
|
|
1027
|
+
function matchesGlob(relativePath, pattern) {
|
|
1028
|
+
return expandBraces(pattern).some((expanded) => globToRegExp(expanded).test(relativePath));
|
|
1029
|
+
}
|
|
1030
|
+
function matchesAny(patterns, relativePath) {
|
|
1031
|
+
return (patterns ?? []).some((pattern) => matchesGlob(relativePath, pattern));
|
|
1032
|
+
}
|
|
1033
|
+
async function discoverCodeSourceFiles(options = {}) {
|
|
1034
|
+
return (await walkFiles(path.resolve(options.cwd ?? process.cwd()))).filter((relativePath) => isCodeSourceFile(relativePath)).filter((relativePath) => !matchesAny(DEFAULT_SOURCE_EXCLUDES, relativePath)).sort((left, right) => left.localeCompare(right));
|
|
1035
|
+
}
|
|
1036
|
+
function createSuggestedSource(include) {
|
|
1037
|
+
return {
|
|
1038
|
+
include: [include],
|
|
1039
|
+
exclude: [...DEFAULT_SOURCE_EXCLUDES],
|
|
1040
|
+
format: "xstate",
|
|
1041
|
+
xstateVersion: 5
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
function suggestStatelySourceConfigs(relativePaths, defaultXStateVersion = 5) {
|
|
1045
|
+
const pending = new Set(relativePaths.map((relativePath) => relativePath.replace(/\\/g, "/")).filter(Boolean));
|
|
1046
|
+
const suggestions = [];
|
|
1047
|
+
const addSuggestion = (include, predicate) => {
|
|
1048
|
+
const matched = [...pending].filter(predicate);
|
|
1049
|
+
if (matched.length === 0) return;
|
|
1050
|
+
suggestions.push({
|
|
1051
|
+
...createSuggestedSource(include),
|
|
1052
|
+
xstateVersion: defaultXStateVersion
|
|
1053
|
+
});
|
|
1054
|
+
for (const matchedPath of matched) pending.delete(matchedPath);
|
|
1055
|
+
};
|
|
1056
|
+
addSuggestion("**/*.machine.ts", (relativePath) => relativePath.endsWith(".machine.ts"));
|
|
1057
|
+
addSuggestion("src/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}", (relativePath) => relativePath.startsWith("src/"));
|
|
1058
|
+
addSuggestion("packages/*/src/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}", (relativePath) => /^packages\/[^/]+\/src\//.test(relativePath));
|
|
1059
|
+
addSuggestion("apps/*/src/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}", (relativePath) => /^apps\/[^/]+\/src\//.test(relativePath));
|
|
1060
|
+
const byDirectory = /* @__PURE__ */ new Map();
|
|
1061
|
+
for (const relativePath of pending) {
|
|
1062
|
+
const directory = path.posix.dirname(relativePath);
|
|
1063
|
+
const key = directory === "." ? relativePath : directory;
|
|
1064
|
+
const bucket = byDirectory.get(key) ?? [];
|
|
1065
|
+
bucket.push(relativePath);
|
|
1066
|
+
byDirectory.set(key, bucket);
|
|
1067
|
+
}
|
|
1068
|
+
for (const [key, matchedPaths] of [...byDirectory.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
1069
|
+
if (matchedPaths.length > 1 && key !== "." && key !== matchedPaths[0]) {
|
|
1070
|
+
addSuggestion(`${key}/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}`, (relativePath) => relativePath.startsWith(`${key}/`));
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
const exactPath = matchedPaths[0];
|
|
1074
|
+
addSuggestion(exactPath, (relativePath) => relativePath === exactPath);
|
|
1075
|
+
}
|
|
1076
|
+
return suggestions;
|
|
1077
|
+
}
|
|
1078
|
+
async function discoverStatelySourceFiles(options = {}) {
|
|
1079
|
+
const { config, rootDir } = options.config ? {
|
|
1080
|
+
config: options.config,
|
|
1081
|
+
rootDir: path.resolve(options.cwd ?? process.cwd())
|
|
1082
|
+
} : await readStatelyProjectConfig(options);
|
|
1083
|
+
const relativeFiles = await walkFiles(rootDir);
|
|
1084
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
1085
|
+
for (const source of config.sources) for (const relativePath of relativeFiles) {
|
|
1086
|
+
if (!matchesAny(source.include, relativePath)) continue;
|
|
1087
|
+
if (matchesAny(source.exclude, relativePath)) continue;
|
|
1088
|
+
const filePath = path.join(rootDir, relativePath);
|
|
1089
|
+
if (!discovered.has(filePath)) discovered.set(filePath, {
|
|
1090
|
+
filePath,
|
|
1091
|
+
relativePath,
|
|
1092
|
+
source
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
return [...discovered.values()].sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
//#endregion
|
|
1099
|
+
//#region src/cli.ts
|
|
1100
|
+
const execFileAsync = promisify(execFile);
|
|
963
1101
|
function loadLocalEnv() {
|
|
964
1102
|
if (typeof process.loadEnvFile !== "function") return;
|
|
965
1103
|
const cwdEnvPath = path.join(process.cwd(), ".env.local");
|
|
@@ -1090,6 +1228,22 @@ async function promptForApiKey() {
|
|
|
1090
1228
|
rl.close();
|
|
1091
1229
|
}
|
|
1092
1230
|
}
|
|
1231
|
+
async function promptYesNo(question, defaultValue = true) {
|
|
1232
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("No interactive terminal available.");
|
|
1233
|
+
const rl = createInterface({
|
|
1234
|
+
input: process.stdin,
|
|
1235
|
+
output: process.stdout,
|
|
1236
|
+
terminal: true
|
|
1237
|
+
});
|
|
1238
|
+
try {
|
|
1239
|
+
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
1240
|
+
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
|
1241
|
+
if (!answer) return defaultValue;
|
|
1242
|
+
return answer === "y" || answer === "yes";
|
|
1243
|
+
} finally {
|
|
1244
|
+
rl.close();
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1093
1247
|
function normalizeApiKey(value) {
|
|
1094
1248
|
const trimmed = value?.trim();
|
|
1095
1249
|
return trimmed ? trimmed : void 0;
|
|
@@ -1133,6 +1287,45 @@ async function initProject(options) {
|
|
|
1133
1287
|
project
|
|
1134
1288
|
};
|
|
1135
1289
|
}
|
|
1290
|
+
async function scanProjectSources(options) {
|
|
1291
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1292
|
+
const candidateRelativePaths = await discoverCodeSourceFiles({ cwd });
|
|
1293
|
+
const machineRelativePaths = [];
|
|
1294
|
+
for (const relativePath of candidateRelativePaths) {
|
|
1295
|
+
const filePath = path.join(cwd, relativePath);
|
|
1296
|
+
const contents = await fs.readFile(filePath, "utf8");
|
|
1297
|
+
let extracted;
|
|
1298
|
+
try {
|
|
1299
|
+
extracted = await options.client.code.extractMachines(contents);
|
|
1300
|
+
} catch {
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1303
|
+
if (extracted.machines.length > 0) machineRelativePaths.push(relativePath);
|
|
1304
|
+
}
|
|
1305
|
+
return suggestStatelySourceConfigs(machineRelativePaths, Math.max(5, options.defaultXStateVersion ?? 5));
|
|
1306
|
+
}
|
|
1307
|
+
function supportsMachineDiscovery(file) {
|
|
1308
|
+
return file.source.format === "xstate" || file.source.format === "auto";
|
|
1309
|
+
}
|
|
1310
|
+
async function resolveConfiguredProject(options) {
|
|
1311
|
+
const { config, configPath, rootDir } = await readStatelyProjectConfig({
|
|
1312
|
+
cwd: options.cwd,
|
|
1313
|
+
configPath: options.configPath
|
|
1314
|
+
});
|
|
1315
|
+
const studioUrl = options.baseUrl ?? config.studioUrl;
|
|
1316
|
+
return {
|
|
1317
|
+
client: options.client ?? createStatelyClient({
|
|
1318
|
+
apiKey: options.apiKey,
|
|
1319
|
+
baseUrl: studioUrl
|
|
1320
|
+
}),
|
|
1321
|
+
config,
|
|
1322
|
+
configPath,
|
|
1323
|
+
files: await discoverStatelySourceFiles({
|
|
1324
|
+
cwd: rootDir,
|
|
1325
|
+
config
|
|
1326
|
+
})
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1136
1329
|
const sharedFlags = {
|
|
1137
1330
|
help: Flags.help({ char: "h" }),
|
|
1138
1331
|
"fail-on-changes": Flags.boolean({
|
|
@@ -1225,19 +1418,99 @@ var DiffCommand = class DiffCommand extends ParsedSyncCommand {
|
|
|
1225
1418
|
var PullCommand = class PullCommand extends ParsedSyncCommand {
|
|
1226
1419
|
static summary = "Pull a source locator into a local target file.";
|
|
1227
1420
|
static description = "Resolves the source, materializes it in the target format inferred from the target file, and writes the result locally.";
|
|
1228
|
-
static args =
|
|
1421
|
+
static args = {
|
|
1422
|
+
source: Args.string({
|
|
1423
|
+
required: true,
|
|
1424
|
+
description: "Source locator. Supports a local linked file, machine ID, URL, or local file path."
|
|
1425
|
+
}),
|
|
1426
|
+
target: Args.string({
|
|
1427
|
+
required: false,
|
|
1428
|
+
description: "Optional local target path. Defaults to the source file when pulling a linked local file."
|
|
1429
|
+
})
|
|
1430
|
+
};
|
|
1229
1431
|
async run() {
|
|
1230
1432
|
const { args, flags } = await this.parseSync(PullCommand);
|
|
1231
1433
|
const apiKey = (await resolveApiKey(flags["api-key"])).apiKey;
|
|
1434
|
+
const localCandidate = path.resolve(process.cwd(), args.source);
|
|
1435
|
+
let source = args.source;
|
|
1436
|
+
let target = args.target;
|
|
1437
|
+
if (!target && await fileExists(localCandidate)) {
|
|
1438
|
+
const pragma = getStatelyPragma(await fs.readFile(localCandidate, "utf8"), localCandidate);
|
|
1439
|
+
if (!pragma?.id) this.error(`No @statelyai id found in ${localCandidate}. Pass an explicit source machine ID or URL and target path.`);
|
|
1440
|
+
source = pragma.id;
|
|
1441
|
+
target = localCandidate;
|
|
1442
|
+
}
|
|
1443
|
+
if (!target) this.error("Missing target path. Pass `statelyai pull <machine-id|url> <file>` or `statelyai pull <linked-file>`.");
|
|
1232
1444
|
const result = await pullSync({
|
|
1233
|
-
source
|
|
1234
|
-
target
|
|
1445
|
+
source,
|
|
1446
|
+
target,
|
|
1235
1447
|
apiKey,
|
|
1236
1448
|
baseUrl: flags["base-url"] ?? getDefaultBaseUrl()
|
|
1237
1449
|
});
|
|
1238
1450
|
this.log(`Pulled: ${result.source.locator} -> ${result.outputPath}\nTarget: ${result.target.kind} (${result.target.format})`);
|
|
1239
1451
|
}
|
|
1240
1452
|
};
|
|
1453
|
+
var PushCommand = class PushCommand extends Command {
|
|
1454
|
+
static enableJsonFlag = false;
|
|
1455
|
+
static summary = "Create or update remote machines for local source files.";
|
|
1456
|
+
static description = "Uses statelyai.json source discovery by default, creates remote machines for unlabeled local machine sources, updates already-linked machines, and writes returned @statelyai ids back into source files.";
|
|
1457
|
+
static args = { file: Args.string({
|
|
1458
|
+
required: false,
|
|
1459
|
+
description: "Optional local source file to push instead of scanning statelyai.json."
|
|
1460
|
+
}) };
|
|
1461
|
+
static flags = {
|
|
1462
|
+
help: Flags.help({ char: "h" }),
|
|
1463
|
+
"api-key": Flags.string({ description: "Stately API key used to create or update remote machines" }),
|
|
1464
|
+
"base-url": Flags.string({ description: "Base URL for Stately Studio or a self-hosted deployment" }),
|
|
1465
|
+
config: Flags.string({ description: "Path to statelyai.json" })
|
|
1466
|
+
};
|
|
1467
|
+
async run() {
|
|
1468
|
+
const { args, flags } = await this.parse(PushCommand);
|
|
1469
|
+
const resolvedApiKey = await resolveApiKey(flags["api-key"]);
|
|
1470
|
+
if (!resolvedApiKey.apiKey) this.error("No API key configured. Use `statelyai login`, set `STATELY_API_KEY`, or pass `--api-key`.");
|
|
1471
|
+
const { client, config, files } = await resolveConfiguredProject({
|
|
1472
|
+
apiKey: resolvedApiKey.apiKey,
|
|
1473
|
+
baseUrl: flags["base-url"],
|
|
1474
|
+
configPath: flags.config
|
|
1475
|
+
});
|
|
1476
|
+
const candidateFiles = args.file ? [{
|
|
1477
|
+
filePath: path.resolve(args.file),
|
|
1478
|
+
relativePath: path.relative(process.cwd(), path.resolve(args.file)),
|
|
1479
|
+
source: {
|
|
1480
|
+
include: [args.file],
|
|
1481
|
+
format: "xstate",
|
|
1482
|
+
xstateVersion: config.defaultXStateVersion
|
|
1483
|
+
}
|
|
1484
|
+
}] : files.filter(supportsMachineDiscovery);
|
|
1485
|
+
if (candidateFiles.length === 0) {
|
|
1486
|
+
this.log("No matching local machine source files were discovered.");
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
const linked = [];
|
|
1490
|
+
const refreshed = [];
|
|
1491
|
+
const skipped = [];
|
|
1492
|
+
for (const file of candidateFiles) {
|
|
1493
|
+
if (!supportsMachineDiscovery(file)) {
|
|
1494
|
+
skipped.push(`${file.relativePath}: unsupported format ${file.source.format}`);
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
const result = await pushLocalMachineLinks({
|
|
1498
|
+
source: file.filePath,
|
|
1499
|
+
apiKey: resolvedApiKey.apiKey,
|
|
1500
|
+
baseUrl: flags["base-url"] ?? config.studioUrl,
|
|
1501
|
+
client,
|
|
1502
|
+
project: { projectId: config.projectId },
|
|
1503
|
+
xstateVersion: Math.max(5, file.source.xstateVersion ?? config.defaultXStateVersion)
|
|
1504
|
+
});
|
|
1505
|
+
if (result.created.length > 0) linked.push(`${file.relativePath}: ${result.created.map(({ machineIndex, machine }) => `${machine.id} [${machineIndex}]`).join(", ")}`);
|
|
1506
|
+
if (result.updated.length > 0) refreshed.push(`${file.relativePath}: ${result.updated.map(({ machineIndex, machine }) => `${machine.id} [${machineIndex}]`).join(", ")}`);
|
|
1507
|
+
for (const entry of result.skipped) skipped.push(`${file.relativePath} [${entry.machineIndex}]: ${entry.reason}`);
|
|
1508
|
+
}
|
|
1509
|
+
if (linked.length > 0) this.log(`Linked:\n${linked.join("\n")}`);
|
|
1510
|
+
if (refreshed.length > 0) this.log(`Updated:\n${refreshed.join("\n")}`);
|
|
1511
|
+
if (skipped.length > 0) this.log(`Skipped:\n${skipped.join("\n")}`);
|
|
1512
|
+
}
|
|
1513
|
+
};
|
|
1241
1514
|
var OpenCommand = class OpenCommand extends Command {
|
|
1242
1515
|
static enableJsonFlag = false;
|
|
1243
1516
|
static summary = "Open a local file in the Stately visual editor.";
|
|
@@ -1304,6 +1577,10 @@ var InitCommand = class InitCommand extends Command {
|
|
|
1304
1577
|
force: Flags.boolean({
|
|
1305
1578
|
description: "Overwrite an existing statelyai.json file",
|
|
1306
1579
|
default: false
|
|
1580
|
+
}),
|
|
1581
|
+
scan: Flags.boolean({
|
|
1582
|
+
description: "Scan the repo for machine-bearing files and suggest source globs to save into statelyai.json",
|
|
1583
|
+
default: false
|
|
1307
1584
|
})
|
|
1308
1585
|
};
|
|
1309
1586
|
async run() {
|
|
@@ -1319,7 +1596,33 @@ var InitCommand = class InitCommand extends Command {
|
|
|
1319
1596
|
visibility: flags.visibility
|
|
1320
1597
|
}
|
|
1321
1598
|
});
|
|
1322
|
-
|
|
1599
|
+
if (flags.scan) {
|
|
1600
|
+
const scanClient = createStatelyClient({
|
|
1601
|
+
apiKey: resolvedApiKey.apiKey,
|
|
1602
|
+
baseUrl: flags["base-url"] ?? result.config.studioUrl
|
|
1603
|
+
});
|
|
1604
|
+
const suggestions = await scanProjectSources({
|
|
1605
|
+
cwd: path.dirname(result.configPath),
|
|
1606
|
+
client: scanClient,
|
|
1607
|
+
defaultXStateVersion: result.config.defaultXStateVersion
|
|
1608
|
+
});
|
|
1609
|
+
if (suggestions.length === 0) {
|
|
1610
|
+
this.log(`Initialized project ${result.project.projectId} and wrote ${result.configPath}.\nNo machine source globs were suggested. Edit statelyai.json before running statelyai push.`);
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
this.log(`Initialized project ${result.project.projectId} and wrote ${result.configPath}.`);
|
|
1614
|
+
this.log(`Suggested source globs:\n${suggestions.map((source) => `- ${source.include.join(", ")}`).join("\n")}`);
|
|
1615
|
+
if (await promptYesNo("Save these source globs to statelyai.json?", true)) {
|
|
1616
|
+
const nextConfig = {
|
|
1617
|
+
...result.config,
|
|
1618
|
+
sources: suggestions
|
|
1619
|
+
};
|
|
1620
|
+
await fs.writeFile(result.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
1621
|
+
this.log("Saved scanned source globs to statelyai.json.");
|
|
1622
|
+
} else this.log("Left statelyai.json with an empty sources array. Edit it before running statelyai push.");
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
this.log(`Initialized project ${result.project.projectId} and wrote ${result.configPath}.\nNo source globs configured yet. Edit statelyai.json before running statelyai push, or rerun with --scan.`);
|
|
1323
1626
|
}
|
|
1324
1627
|
};
|
|
1325
1628
|
var LoginCommand = class LoginCommand extends Command {
|
|
@@ -1381,6 +1684,7 @@ const COMMANDS = {
|
|
|
1381
1684
|
plan: PlanCommand,
|
|
1382
1685
|
diff: DiffCommand,
|
|
1383
1686
|
pull: PullCommand,
|
|
1687
|
+
push: PushCommand,
|
|
1384
1688
|
open: OpenCommand,
|
|
1385
1689
|
init: InitCommand,
|
|
1386
1690
|
login: LoginCommand,
|
|
@@ -1409,4 +1713,4 @@ function isDirectExecution() {
|
|
|
1409
1713
|
if (isDirectExecution()) run();
|
|
1410
1714
|
|
|
1411
1715
|
//#endregion
|
|
1412
|
-
export { COMMANDS, createStatelyProjectConfig, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, resolveApiKey, run };
|
|
1716
|
+
export { COMMANDS, createStatelyProjectConfig, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, resolveApiKey, run, scanProjectSources };
|
package/dist/embed.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as EmbedMode, c as ExportFormatMap, d as MachineSourceLocations, f as ProjectEmbedMachine, i as EmbedEventName, l as InitOptions, m as UploadResult, n as EmbedEventHandler, o as ExportCallOptions, r as EmbedEventMap, s as ExportFormat, t as CommentsConfig, u as MachineInitOptions } from "./protocol-
|
|
1
|
+
import { a as EmbedMode, c as ExportFormatMap, d as MachineSourceLocations, f as ProjectEmbedMachine, i as EmbedEventName, l as InitOptions, m as UploadResult, n as EmbedEventHandler, o as ExportCallOptions, r as EmbedEventMap, s as ExportFormat, t as CommentsConfig, u as MachineInitOptions } from "./protocol-DN4mH4jR.mjs";
|
|
2
|
+
import { AssetUploadAdapter } from "./assetStorage.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/embed.d.ts
|
|
4
5
|
interface AssetConfig {
|
|
@@ -7,9 +8,15 @@ interface AssetConfig {
|
|
|
7
8
|
* Receives a real File object (reconstructed from serialized data).
|
|
8
9
|
* Throwing or rejecting will show an error toast in the embed.
|
|
9
10
|
*/
|
|
10
|
-
onUploadRequest
|
|
11
|
+
onUploadRequest?: (file: File, context: {
|
|
11
12
|
stateNodeId: string;
|
|
12
13
|
}) => Promise<UploadResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Storage adapter used by the embed upload bridge. This keeps the editor
|
|
16
|
+
* protocol storage-neutral while allowing integrations to plug in S3, R2,
|
|
17
|
+
* Supabase, or any other backing store.
|
|
18
|
+
*/
|
|
19
|
+
adapter?: AssetUploadAdapter;
|
|
13
20
|
/**
|
|
14
21
|
* Accepted MIME types. Supports wildcards (e.g. 'image/*').
|
|
15
22
|
* @default ['image/*']
|
package/dist/embed.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as toInitMessage, i as createPendingExportManager, r as createEventRegistry, t as createPostMessageTransport } from "./transport-
|
|
1
|
+
import { a as toInitMessage, i as createPendingExportManager, r as createEventRegistry, t as createPostMessageTransport } from "./transport-C8UTS3Fa.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/embed.ts
|
|
4
4
|
function buildEmbedUrl(options) {
|
|
@@ -43,6 +43,13 @@ function createStatelyEmbed(options) {
|
|
|
43
43
|
const pendingMessages = [];
|
|
44
44
|
const events = createEventRegistry();
|
|
45
45
|
const exportManager = createPendingExportManager((message) => send(message));
|
|
46
|
+
const assetUploadAdapter = options.assets?.adapter;
|
|
47
|
+
const uploadAsset = options.assets?.onUploadRequest ? (file, context) => options.assets.onUploadRequest(file, context) : assetUploadAdapter ? (file, context) => assetUploadAdapter.upload({
|
|
48
|
+
file,
|
|
49
|
+
...context
|
|
50
|
+
}) : void 0;
|
|
51
|
+
const assetAccept = options.assets?.accept ?? assetUploadAdapter?.accept;
|
|
52
|
+
const assetMaxFileSize = options.assets?.maxFileSize ?? assetUploadAdapter?.maxFileSize;
|
|
46
53
|
function send(msg) {
|
|
47
54
|
if (!transport?.ready) {
|
|
48
55
|
pendingMessages.push(msg);
|
|
@@ -63,11 +70,11 @@ function createStatelyEmbed(options) {
|
|
|
63
70
|
case "@statelyai.ready": {
|
|
64
71
|
const ready = data;
|
|
65
72
|
flush();
|
|
66
|
-
if (
|
|
73
|
+
if (uploadAsset) send({
|
|
67
74
|
type: "@statelyai.uploadCapabilities",
|
|
68
75
|
enabled: true,
|
|
69
|
-
accept:
|
|
70
|
-
maxFileSize:
|
|
76
|
+
accept: assetAccept,
|
|
77
|
+
maxFileSize: assetMaxFileSize
|
|
71
78
|
});
|
|
72
79
|
options.onReady?.();
|
|
73
80
|
events.emit("ready", { version: ready.version });
|
|
@@ -129,7 +136,7 @@ function createStatelyEmbed(options) {
|
|
|
129
136
|
}
|
|
130
137
|
case "@statelyai.uploadRequest": {
|
|
131
138
|
const req = data;
|
|
132
|
-
if (!
|
|
139
|
+
if (!uploadAsset) {
|
|
133
140
|
send({
|
|
134
141
|
type: "@statelyai.error",
|
|
135
142
|
requestId: req.requestId,
|
|
@@ -138,7 +145,7 @@ function createStatelyEmbed(options) {
|
|
|
138
145
|
});
|
|
139
146
|
break;
|
|
140
147
|
}
|
|
141
|
-
const maxSize =
|
|
148
|
+
const maxSize = assetMaxFileSize ?? 10485760;
|
|
142
149
|
if (req.file.size > maxSize) {
|
|
143
150
|
send({
|
|
144
151
|
type: "@statelyai.error",
|
|
@@ -151,8 +158,7 @@ function createStatelyEmbed(options) {
|
|
|
151
158
|
const binary = atob(req.file.data);
|
|
152
159
|
const bytes = new Uint8Array(binary.length);
|
|
153
160
|
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
154
|
-
|
|
155
|
-
options.assets.onUploadRequest(file, { stateNodeId: req.stateNodeId }).then((result) => {
|
|
161
|
+
uploadAsset(new File([bytes], req.file.name, { type: req.file.mimeType }), { stateNodeId: req.stateNodeId }).then((result) => {
|
|
156
162
|
send({
|
|
157
163
|
type: "@statelyai.uploadResponse",
|
|
158
164
|
requestId: req.requestId,
|
package/dist/graph.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as studioMachineConverter, a as StatelyGraphData, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, n as StatelyActorImplementation, o as StatelyGuard, p as StudioEventTypeData, r as StatelyEdgeData, s as StatelyImplementation, t as StatelyAction, u as StatelyTagImplementation, v as toStudioMachine } from "./graph-
|
|
1
|
+
import { _ as studioMachineConverter, a as StatelyGraphData, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, n as StatelyActorImplementation, o as StatelyGuard, p as StudioEventTypeData, r as StatelyEdgeData, s as StatelyImplementation, t as StatelyAction, u as StatelyTagImplementation, v as toStudioMachine } from "./graph-DpBGHZwl.mjs";
|
|
2
2
|
export { StatelyAction, StatelyActorImplementation, StatelyEdgeData, StatelyGraph, StatelyGraphData, StatelyGuard, StatelyImplementation, StatelyInvoke, StatelyNodeData, StatelyTagImplementation, StudioAction, StudioEdge, StudioEventTypeData, StudioMachine, StudioNode, fromStudioMachine, studioMachineConverter, toStudioMachine };
|
package/dist/graph.mjs
CHANGED
|
@@ -2,8 +2,12 @@ import { createFormatConverter, createGraph } from "@statelyai/graph";
|
|
|
2
2
|
|
|
3
3
|
//#region src/graph.ts
|
|
4
4
|
const EXPR_ACTION_TYPE = "xstate.expr";
|
|
5
|
+
function stripMarkdownLinks(code) {
|
|
6
|
+
return code.replace(/\[([^\]\n]+)\]\(([^)\n]+)\)/g, "$1");
|
|
7
|
+
}
|
|
5
8
|
function stripExportDefault(code) {
|
|
6
|
-
|
|
9
|
+
const normalized = stripMarkdownLinks(code).trim();
|
|
10
|
+
return normalized.match(/^export\s+default\s+(.+)/s)?.[1]?.trim() ?? normalized;
|
|
7
11
|
}
|
|
8
12
|
function toJsonObject(value) {
|
|
9
13
|
return value;
|
|
@@ -332,7 +336,7 @@ function fromStudioMachine(studioMachine) {
|
|
|
332
336
|
...edge.data.guard ? { guard: {
|
|
333
337
|
type: edge.data.guard.type,
|
|
334
338
|
params: toUnknownRecord(edge.data.guard.params),
|
|
335
|
-
...edge.data.guard.kind === "inline" ? { code: edge.data.guard.type } : {}
|
|
339
|
+
...edge.data.guard.kind === "inline" ? { code: stripExportDefault(studioMachine.implementations?.guards?.[edge.data.guard.type]?.code ?? edge.data.guard.type) } : {}
|
|
336
340
|
} } : {},
|
|
337
341
|
actions: edge.data.actions.map((action) => fromStudioAction(action, studioMachine.implementations?.actions)),
|
|
338
342
|
...edge.data.description ? { description: edge.data.description } : {},
|
|
@@ -350,9 +354,9 @@ function fromStudioMachine(studioMachine) {
|
|
|
350
354
|
data: {
|
|
351
355
|
...studioMachine.schemas ? { schemas: studioMachine.schemas } : {},
|
|
352
356
|
...studioMachine.implementations ? { implementations: {
|
|
353
|
-
actions: Object.
|
|
354
|
-
guards: Object.
|
|
355
|
-
actors: Object.
|
|
357
|
+
actions: Object.entries(studioMachine.implementations.actions).filter(([key]) => !key.startsWith("inline:")).map(([, source]) => fromActionSource(source)),
|
|
358
|
+
guards: Object.entries(studioMachine.implementations.guards).filter(([key]) => !key.startsWith("inline:")).map(([, guard]) => fromGuardSource(guard)),
|
|
359
|
+
actors: Object.entries(studioMachine.implementations.actors).filter(([key]) => !key.startsWith("inline:") && !/:invocation\[\d+\]$/.test(key)).map(([, actor]) => fromActorSource(actor)),
|
|
356
360
|
delays: [],
|
|
357
361
|
tags: []
|
|
358
362
|
} } : {}
|