statelyai 0.4.1 → 0.5.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 +4 -3
- package/dist/bin.mjs +1 -1
- package/dist/{cli-DkWxDq0T.mjs → cli-1Nrsh5Xl.mjs} +92 -9
- package/dist/index.d.mts +14 -1
- package/dist/index.mjs +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,7 +36,8 @@ That writes a `statelyai.json` like:
|
|
|
36
36
|
"studioUrl": "https://stately.ai",
|
|
37
37
|
"defaultXStateVersion": 5,
|
|
38
38
|
"include": ["src/**/*.ts"],
|
|
39
|
-
"exclude": ["**/*.test.*", "**/*.spec.*"]
|
|
39
|
+
"exclude": ["**/*.test.*", "**/*.spec.*"],
|
|
40
|
+
"newMachineDir": "src"
|
|
40
41
|
}
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -54,13 +55,13 @@ statelyai push --dry-run
|
|
|
54
55
|
|
|
55
56
|
If a saved `// @statelyai id=...` points to a deleted or inaccessible remote machine, `push` will prompt to relink the file as a new remote machine and replace the local id.
|
|
56
57
|
|
|
57
|
-
6. Pull remote changes back into linked local files:
|
|
58
|
+
6. Pull remote changes back into linked local files and create new local files for remote-only project machines when `newMachineDir` is configured:
|
|
58
59
|
|
|
59
60
|
```bash
|
|
60
61
|
statelyai pull
|
|
61
62
|
```
|
|
62
63
|
|
|
63
|
-
`pull` skips locally modified files unless you pass `--force`.
|
|
64
|
+
`pull` skips locally modified linked files unless you pass `--force`. New remote-only machines are written as `<machine-name>.machine.ts` inside `newMachineDir`.
|
|
64
65
|
|
|
65
66
|
Run it without installing it globally:
|
|
66
67
|
|
package/dist/bin.mjs
CHANGED
|
@@ -975,7 +975,8 @@ function normalizeProjectConfig(config) {
|
|
|
975
975
|
studioUrl: config.studioUrl ?? "https://stately.ai",
|
|
976
976
|
defaultXStateVersion: Math.max(5, config.defaultXStateVersion ?? 5),
|
|
977
977
|
include: [...config.include ?? firstSource?.include ?? []],
|
|
978
|
-
exclude: config.exclude == null ? [...firstSource?.exclude ?? DEFAULT_SOURCE_EXCLUDES] : [...config.exclude]
|
|
978
|
+
exclude: config.exclude == null ? [...firstSource?.exclude ?? DEFAULT_SOURCE_EXCLUDES] : [...config.exclude],
|
|
979
|
+
...config.newMachineDir ? { newMachineDir: config.newMachineDir } : {}
|
|
979
980
|
};
|
|
980
981
|
}
|
|
981
982
|
async function readStatelyProjectConfig(options = {}) {
|
|
@@ -1060,6 +1061,9 @@ function matchesGlob(relativePath, pattern) {
|
|
|
1060
1061
|
function matchesAny(patterns, relativePath) {
|
|
1061
1062
|
return (patterns ?? []).some((pattern) => matchesGlob(relativePath, pattern));
|
|
1062
1063
|
}
|
|
1064
|
+
function isTrackedByStatelyProjectConfig(config, relativePath) {
|
|
1065
|
+
return matchesAny(config.include, relativePath) && !matchesAny(config.exclude ?? DEFAULT_SOURCE_EXCLUDES, relativePath);
|
|
1066
|
+
}
|
|
1063
1067
|
async function discoverCodeSourceFiles(options = {}) {
|
|
1064
1068
|
const rootDir = path.resolve(options.cwd ?? process.cwd());
|
|
1065
1069
|
return (await filterGitIgnoredPaths(rootDir, await walkFiles(rootDir))).filter((relativePath) => isCodeSourceFile(relativePath)).filter((relativePath) => !matchesAny(DEFAULT_SOURCE_EXCLUDES, relativePath)).sort((left, right) => left.localeCompare(right));
|
|
@@ -1157,6 +1161,7 @@ async function discoverStatelySourceFiles(options = {}) {
|
|
|
1157
1161
|
//#region src/cli.ts
|
|
1158
1162
|
const execFileAsync = promisify(execFile);
|
|
1159
1163
|
const STATELY_API_KEY_SETTINGS_URL = "https://stately.ai/registry/user/my-settings?tab=API+Key";
|
|
1164
|
+
const DEFAULT_NEW_MACHINE_FILE_SUFFIX = ".machine.ts";
|
|
1160
1165
|
function loadLocalEnv() {
|
|
1161
1166
|
if (typeof process.loadEnvFile !== "function") return;
|
|
1162
1167
|
const cwdEnvPath = path.join(process.cwd(), ".env.local");
|
|
@@ -1310,6 +1315,9 @@ function normalizeApiKey(value) {
|
|
|
1310
1315
|
function pluralize(count, singular, plural = `${singular}s`) {
|
|
1311
1316
|
return count === 1 ? singular : plural;
|
|
1312
1317
|
}
|
|
1318
|
+
function toMachineFileStem(machineName) {
|
|
1319
|
+
return machineName.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "machine";
|
|
1320
|
+
}
|
|
1313
1321
|
function getMissingApiKeyMessage() {
|
|
1314
1322
|
return `No API key configured. Use \`statelyai login\`, set \`STATELY_API_KEY\`, or pass \`--api-key\`.\nGet or create an API key at ${STATELY_API_KEY_SETTINGS_URL}`;
|
|
1315
1323
|
}
|
|
@@ -1321,6 +1329,15 @@ function buildMachineEditorUrl(studioUrl, projectId, machineId) {
|
|
|
1321
1329
|
url.searchParams.set("machineId", machineId);
|
|
1322
1330
|
return url.toString();
|
|
1323
1331
|
}
|
|
1332
|
+
function inferNewMachineDirFromSuggestions(suggestions) {
|
|
1333
|
+
if (suggestions.length !== 1) return;
|
|
1334
|
+
const include = suggestions[0]?.include[0];
|
|
1335
|
+
if (!include) return;
|
|
1336
|
+
const globIndex = include.indexOf("**/");
|
|
1337
|
+
if (globIndex >= 0) return include.slice(0, globIndex).replace(/\/$/, "") || void 0;
|
|
1338
|
+
const directory = path.posix.dirname(include);
|
|
1339
|
+
return directory === "." ? void 0 : directory;
|
|
1340
|
+
}
|
|
1324
1341
|
function isRelinkableLinkedMachineError(error) {
|
|
1325
1342
|
return error instanceof StudioApiError && (error.status === 403 || error.status === 404);
|
|
1326
1343
|
}
|
|
@@ -1400,6 +1417,35 @@ async function discoverLinkedPullTargets(files) {
|
|
|
1400
1417
|
}
|
|
1401
1418
|
return linkedTargets;
|
|
1402
1419
|
}
|
|
1420
|
+
async function discoverRemoteProjectMachineTargets(options) {
|
|
1421
|
+
const newMachineDir = options.config.newMachineDir?.trim();
|
|
1422
|
+
if (!newMachineDir) return [];
|
|
1423
|
+
const targets = [];
|
|
1424
|
+
for (const machine of options.project.machines) {
|
|
1425
|
+
if (options.linkedMachineIds.has(machine.machineId)) continue;
|
|
1426
|
+
const baseDirectory = path.join(options.rootDir, newMachineDir);
|
|
1427
|
+
const stem = toMachineFileStem(machine.name);
|
|
1428
|
+
let candidateFileName = `${stem}${DEFAULT_NEW_MACHINE_FILE_SUFFIX}`;
|
|
1429
|
+
let attempt = 2;
|
|
1430
|
+
for (;;) {
|
|
1431
|
+
const filePath = path.join(baseDirectory, candidateFileName);
|
|
1432
|
+
const relativePath = path.relative(options.rootDir, filePath).replace(/\\/g, "/");
|
|
1433
|
+
if (!isTrackedByStatelyProjectConfig(options.config, relativePath)) throw new Error(`Generated path ${relativePath} for remote machine ${machine.name} is not covered by include/exclude globs.`);
|
|
1434
|
+
if (!await fileExists(filePath)) {
|
|
1435
|
+
targets.push({
|
|
1436
|
+
machineId: machine.machineId,
|
|
1437
|
+
machineName: machine.name,
|
|
1438
|
+
filePath,
|
|
1439
|
+
relativePath
|
|
1440
|
+
});
|
|
1441
|
+
break;
|
|
1442
|
+
}
|
|
1443
|
+
candidateFileName = `${stem}-${attempt}${DEFAULT_NEW_MACHINE_FILE_SUFFIX}`;
|
|
1444
|
+
attempt += 1;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return targets;
|
|
1448
|
+
}
|
|
1403
1449
|
async function initProject(options) {
|
|
1404
1450
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1405
1451
|
const studioUrl = getResolvedStudioUrl(options.baseUrl);
|
|
@@ -1609,19 +1655,38 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
|
|
|
1609
1655
|
if (!args.source) {
|
|
1610
1656
|
if (!apiKey) this.error(getMissingApiKeyMessage());
|
|
1611
1657
|
this.log("Resolving configured project and linked source files...");
|
|
1612
|
-
const { files, config } = await resolveConfiguredProject({
|
|
1658
|
+
const { files, config, configPath } = await resolveConfiguredProject({
|
|
1613
1659
|
apiKey,
|
|
1614
1660
|
baseUrl: flags["base-url"],
|
|
1615
1661
|
configPath: flags.config
|
|
1616
1662
|
});
|
|
1617
1663
|
const linkedTargets = await discoverLinkedPullTargets(files);
|
|
1618
|
-
|
|
1619
|
-
|
|
1664
|
+
const linkedMachineIds = new Set(linkedTargets.map((linkedTarget) => linkedTarget.machineId));
|
|
1665
|
+
const project = await client.projects.get(config.projectId);
|
|
1666
|
+
const skipped = [];
|
|
1667
|
+
let remoteOnlyTargets = [];
|
|
1668
|
+
try {
|
|
1669
|
+
remoteOnlyTargets = await discoverRemoteProjectMachineTargets({
|
|
1670
|
+
rootDir: path.dirname(configPath),
|
|
1671
|
+
config,
|
|
1672
|
+
project,
|
|
1673
|
+
linkedMachineIds
|
|
1674
|
+
});
|
|
1675
|
+
} catch (error) {
|
|
1676
|
+
skipped.push(error instanceof Error ? error.message : String(error));
|
|
1677
|
+
}
|
|
1678
|
+
if (linkedTargets.length === 0 && remoteOnlyTargets.length === 0) {
|
|
1679
|
+
if (project.machines.length > 0 && !config.newMachineDir) {
|
|
1680
|
+
this.log("No linked local machine files were discovered, and statelyai.json does not set newMachineDir for importing remote-only machines.");
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
this.log("No linked local machine files or remote-only project machines were discovered.");
|
|
1620
1684
|
return;
|
|
1621
1685
|
}
|
|
1622
|
-
|
|
1686
|
+
if (project.machines.length > linkedTargets.length && !config.newMachineDir) this.log("Remote-only project machines were found, but statelyai.json does not set newMachineDir, so they will be skipped.");
|
|
1687
|
+
this.log(`Processing ${linkedTargets.length} ${pluralize(linkedTargets.length, "linked file")}${remoteOnlyTargets.length > 0 ? ` and ${remoteOnlyTargets.length} ${pluralize(remoteOnlyTargets.length, "new remote machine")}` : ""}...`);
|
|
1623
1688
|
const pulled = [];
|
|
1624
|
-
const
|
|
1689
|
+
const created = [];
|
|
1625
1690
|
for (const linkedTarget of linkedTargets) {
|
|
1626
1691
|
const targetPath = linkedTarget.file.filePath;
|
|
1627
1692
|
if (!flags.force && await isFileGitDirty(targetPath)) {
|
|
@@ -1641,7 +1706,23 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
|
|
|
1641
1706
|
skipped.push(`${linkedTarget.file.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1642
1707
|
}
|
|
1643
1708
|
}
|
|
1709
|
+
for (const remoteTarget of remoteOnlyTargets) {
|
|
1710
|
+
this.log(`Pulling new remote machine ${remoteTarget.machineName} into ${remoteTarget.relativePath}...`);
|
|
1711
|
+
try {
|
|
1712
|
+
await fsPromises.mkdir(path.dirname(remoteTarget.filePath), { recursive: true });
|
|
1713
|
+
const result = await pullSync({
|
|
1714
|
+
source: remoteTarget.machineId,
|
|
1715
|
+
target: remoteTarget.filePath,
|
|
1716
|
+
apiKey,
|
|
1717
|
+
baseUrl: flags["base-url"] ?? config.studioUrl
|
|
1718
|
+
});
|
|
1719
|
+
created.push(`${remoteTarget.relativePath}: ${result.source.locator}`);
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
skipped.push(`${remoteTarget.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1644
1724
|
if (pulled.length > 0) this.log(`Pulled:\n${pulled.join("\n")}`);
|
|
1725
|
+
if (created.length > 0) this.log(`Created:\n${created.join("\n")}`);
|
|
1645
1726
|
if (skipped.length > 0) this.log(`Skipped:\n${skipped.join("\n")}`);
|
|
1646
1727
|
return;
|
|
1647
1728
|
}
|
|
@@ -1889,13 +1970,15 @@ var InitCommand = class InitCommand extends Command {
|
|
|
1889
1970
|
this.log(initMessage);
|
|
1890
1971
|
this.log(`Suggested source globs:\n${suggestions.map((source) => `- ${source.include.join(", ")}`).join("\n")}`);
|
|
1891
1972
|
if (await promptYesNo("Save these source globs to statelyai.json?", true)) {
|
|
1973
|
+
const inferredNewMachineDir = inferNewMachineDirFromSuggestions(suggestions);
|
|
1892
1974
|
const nextConfig = {
|
|
1893
1975
|
...result.config,
|
|
1894
1976
|
include: suggestions.flatMap((suggestion) => suggestion.include),
|
|
1895
|
-
exclude: [...new Set(suggestions.flatMap((suggestion) => suggestion.exclude ?? []))]
|
|
1977
|
+
exclude: [...new Set(suggestions.flatMap((suggestion) => suggestion.exclude ?? []))],
|
|
1978
|
+
...inferredNewMachineDir ? { newMachineDir: inferredNewMachineDir } : {}
|
|
1896
1979
|
};
|
|
1897
1980
|
await fsPromises.writeFile(result.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
1898
|
-
this.log("Saved scanned source globs to statelyai.json.");
|
|
1981
|
+
this.log(inferredNewMachineDir ? `Saved scanned source globs to statelyai.json.\nNew remote machines will be created under ${inferredNewMachineDir}/ by default.` : "Saved scanned source globs to statelyai.json.");
|
|
1899
1982
|
} else this.log("Left statelyai.json unchanged. Edit it before running statelyai push.");
|
|
1900
1983
|
return;
|
|
1901
1984
|
}
|
|
@@ -1991,4 +2074,4 @@ function isDirectExecution() {
|
|
|
1991
2074
|
if (isDirectExecution()) run$1();
|
|
1992
2075
|
|
|
1993
2076
|
//#endregion
|
|
1994
|
-
export {
|
|
2077
|
+
export { formatPlanSummary as a, initProject as c, run$1 as d, scanProjectSources as f, discoverRemoteProjectMachineTargets as i, isFileGitDirty as l, classifyPushCandidates as n, getEnvApiKey as o, createStatelyProjectConfig as p, discoverLinkedPullTargets as r, inferInitProjectName as s, COMMANDS as t, resolveApiKey as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -18,6 +18,7 @@ interface StatelyProjectConfig {
|
|
|
18
18
|
defaultXStateVersion: number;
|
|
19
19
|
include: string[];
|
|
20
20
|
exclude?: string[];
|
|
21
|
+
newMachineDir?: string;
|
|
21
22
|
}
|
|
22
23
|
interface DiscoveredSourceFile {
|
|
23
24
|
filePath: string;
|
|
@@ -55,6 +56,12 @@ interface LinkedPullTarget {
|
|
|
55
56
|
file: DiscoveredSourceFile;
|
|
56
57
|
machineId: string;
|
|
57
58
|
}
|
|
59
|
+
interface RemoteProjectMachineTarget {
|
|
60
|
+
machineId: string;
|
|
61
|
+
machineName: string;
|
|
62
|
+
filePath: string;
|
|
63
|
+
relativePath: string;
|
|
64
|
+
}
|
|
58
65
|
interface PushCandidateClassification {
|
|
59
66
|
machineFiles: DiscoveredSourceFile[];
|
|
60
67
|
nonMachineFiles: DiscoveredSourceFile[];
|
|
@@ -76,6 +83,12 @@ declare function resolveApiKey(explicitApiKey?: string): Promise<ApiKeyResolutio
|
|
|
76
83
|
declare function inferInitProjectName(cwd: string, repo?: ConnectedRepo): string;
|
|
77
84
|
declare function isFileGitDirty(filePath: string): Promise<boolean>;
|
|
78
85
|
declare function discoverLinkedPullTargets(files: DiscoveredSourceFile[]): Promise<LinkedPullTarget[]>;
|
|
86
|
+
declare function discoverRemoteProjectMachineTargets(options: {
|
|
87
|
+
rootDir: string;
|
|
88
|
+
config: StatelyProjectConfig;
|
|
89
|
+
project: ProjectData;
|
|
90
|
+
linkedMachineIds: Set<string>;
|
|
91
|
+
}): Promise<RemoteProjectMachineTarget[]>;
|
|
79
92
|
declare function initProject(options: InitProjectOptions): Promise<InitProjectResult>;
|
|
80
93
|
declare function scanProjectSources(options: ScanProjectSourcesOptions): Promise<Array<Pick<StatelyProjectConfig, 'include' | 'exclude'>>>;
|
|
81
94
|
declare function classifyPushCandidates(files: DiscoveredSourceFile[]): Promise<PushCandidateClassification>;
|
|
@@ -227,4 +240,4 @@ declare const COMMANDS: {
|
|
|
227
240
|
};
|
|
228
241
|
declare function run(argv?: any, entryUrl?: string): Promise<void>;
|
|
229
242
|
//#endregion
|
|
230
|
-
export { ApiKeyResolution, COMMANDS, InitProjectOptions, InitProjectResult, LinkedPullTarget, PushCandidateClassification, ScanProjectSourcesOptions, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, run, scanProjectSources };
|
|
243
|
+
export { ApiKeyResolution, COMMANDS, InitProjectOptions, InitProjectResult, LinkedPullTarget, PushCandidateClassification, RemoteProjectMachineTarget, ScanProjectSourcesOptions, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverRemoteProjectMachineTargets, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, run, scanProjectSources };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as formatPlanSummary, c as initProject, d as run, f as scanProjectSources, i as discoverRemoteProjectMachineTargets, l as isFileGitDirty, n as classifyPushCandidates, o as getEnvApiKey, p as createStatelyProjectConfig, r as discoverLinkedPullTargets, s as inferInitProjectName, t as COMMANDS, u as resolveApiKey } from "./cli-1Nrsh5Xl.mjs";
|
|
2
2
|
|
|
3
|
-
export { COMMANDS, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, run, scanProjectSources };
|
|
3
|
+
export { COMMANDS, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverRemoteProjectMachineTargets, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, run, scanProjectSources };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "statelyai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Command-line tools for Stately",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@oclif/core": "^4.10.3",
|
|
24
|
-
"@statelyai/sdk": "0.
|
|
24
|
+
"@statelyai/sdk": "0.10.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"tsdown": "0.21.0-beta.2",
|