statelyai 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/bin.mjs +1 -1
- package/dist/{cli-1Nrsh5Xl.mjs → cli-D4Id6WsS.mjs} +176 -65
- package/dist/index.d.mts +27 -2
- package/dist/index.mjs +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@ statelyai init
|
|
|
26
26
|
statelyai init --scan
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
If you have an older `statelyai.json` that still uses the legacy `sources` shape, the CLI will rewrite simple single-source configs automatically when it reads them.
|
|
30
|
+
|
|
29
31
|
That writes a `statelyai.json` like:
|
|
30
32
|
|
|
31
33
|
```json
|
|
@@ -37,7 +39,7 @@ That writes a `statelyai.json` like:
|
|
|
37
39
|
"defaultXStateVersion": 5,
|
|
38
40
|
"include": ["src/**/*.ts"],
|
|
39
41
|
"exclude": ["**/*.test.*", "**/*.spec.*"],
|
|
40
|
-
"
|
|
42
|
+
"newMachinesDir": "src"
|
|
41
43
|
}
|
|
42
44
|
```
|
|
43
45
|
|
|
@@ -55,13 +57,13 @@ statelyai push --dry-run
|
|
|
55
57
|
|
|
56
58
|
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.
|
|
57
59
|
|
|
58
|
-
6. Pull remote changes back into linked local files and create new local files for remote-only project machines when `
|
|
60
|
+
6. Pull remote changes back into linked local files and create new local files for remote-only project machines when `newMachinesDir` is configured:
|
|
59
61
|
|
|
60
62
|
```bash
|
|
61
63
|
statelyai pull
|
|
62
64
|
```
|
|
63
65
|
|
|
64
|
-
`pull` skips locally modified linked files unless you pass `--force`. New remote-only machines are written as `<machine-name>.machine.ts` inside `
|
|
66
|
+
`pull` skips locally modified linked files unless you pass `--force`. New remote-only machines are written as `<machine-name>.machine.ts` inside `newMachinesDir`.
|
|
65
67
|
|
|
66
68
|
Run it without installing it globally:
|
|
67
69
|
|
package/dist/bin.mjs
CHANGED
|
@@ -976,17 +976,44 @@ function normalizeProjectConfig(config) {
|
|
|
976
976
|
defaultXStateVersion: Math.max(5, config.defaultXStateVersion ?? 5),
|
|
977
977
|
include: [...config.include ?? firstSource?.include ?? []],
|
|
978
978
|
exclude: config.exclude == null ? [...firstSource?.exclude ?? DEFAULT_SOURCE_EXCLUDES] : [...config.exclude],
|
|
979
|
-
...config.
|
|
979
|
+
...config.newMachinesDir ? { newMachinesDir: config.newMachinesDir } : {}
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
function getConfigRewriteStatus(config) {
|
|
983
|
+
if (!config.sources) return { needsRewrite: false };
|
|
984
|
+
if (config.sources.length > 1) return {
|
|
985
|
+
needsRewrite: true,
|
|
986
|
+
reason: "multiple-legacy-sources"
|
|
987
|
+
};
|
|
988
|
+
return {
|
|
989
|
+
needsRewrite: true,
|
|
990
|
+
reason: "legacy-sources"
|
|
980
991
|
};
|
|
981
992
|
}
|
|
982
993
|
async function readStatelyProjectConfig(options = {}) {
|
|
983
994
|
const rootDir = path.resolve(options.cwd ?? process.cwd());
|
|
984
995
|
const configPath = path.resolve(rootDir, options.configPath ?? STATELY_CONFIG_FILE);
|
|
985
996
|
const raw = await fsPromises.readFile(configPath, "utf8");
|
|
997
|
+
const parsed = JSON.parse(raw);
|
|
998
|
+
const config = normalizeProjectConfig(parsed);
|
|
999
|
+
const rewriteStatus = getConfigRewriteStatus(parsed);
|
|
1000
|
+
if (options.rewriteIfNeeded && rewriteStatus.reason === "legacy-sources") {
|
|
1001
|
+
await fsPromises.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
1002
|
+
return {
|
|
1003
|
+
config,
|
|
1004
|
+
configPath,
|
|
1005
|
+
rootDir,
|
|
1006
|
+
rewriteStatus: {
|
|
1007
|
+
needsRewrite: false,
|
|
1008
|
+
rewritten: true
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
986
1012
|
return {
|
|
987
|
-
config
|
|
1013
|
+
config,
|
|
988
1014
|
configPath,
|
|
989
|
-
rootDir
|
|
1015
|
+
rootDir,
|
|
1016
|
+
rewriteStatus
|
|
990
1017
|
};
|
|
991
1018
|
}
|
|
992
1019
|
async function walkFiles(rootDir, currentDir = rootDir) {
|
|
@@ -1162,6 +1189,12 @@ async function discoverStatelySourceFiles(options = {}) {
|
|
|
1162
1189
|
const execFileAsync = promisify(execFile);
|
|
1163
1190
|
const STATELY_API_KEY_SETTINGS_URL = "https://stately.ai/registry/user/my-settings?tab=API+Key";
|
|
1164
1191
|
const DEFAULT_NEW_MACHINE_FILE_SUFFIX = ".machine.ts";
|
|
1192
|
+
const ANSI_RESET = "\x1B[0m";
|
|
1193
|
+
const ANSI_BOLD = "\x1B[1m";
|
|
1194
|
+
const ANSI_DIM = "\x1B[2m";
|
|
1195
|
+
const ANSI_CYAN = "\x1B[36m";
|
|
1196
|
+
const ANSI_GREEN = "\x1B[32m";
|
|
1197
|
+
const ANSI_YELLOW = "\x1B[33m";
|
|
1165
1198
|
function loadLocalEnv() {
|
|
1166
1199
|
if (typeof process.loadEnvFile !== "function") return;
|
|
1167
1200
|
const cwdEnvPath = path.join(process.cwd(), ".env.local");
|
|
@@ -1315,8 +1348,42 @@ function normalizeApiKey(value) {
|
|
|
1315
1348
|
function pluralize(count, singular, plural = `${singular}s`) {
|
|
1316
1349
|
return count === 1 ? singular : plural;
|
|
1317
1350
|
}
|
|
1351
|
+
function supportsColor() {
|
|
1352
|
+
return Boolean(process.stdout.isTTY) && process.env.NO_COLOR == null && process.env.CI !== "true";
|
|
1353
|
+
}
|
|
1354
|
+
function colorize(text, ...codes) {
|
|
1355
|
+
if (!supportsColor()) return text;
|
|
1356
|
+
return `${codes.join("")}${text}${ANSI_RESET}`;
|
|
1357
|
+
}
|
|
1358
|
+
function formatSectionLabel(label, tone = "info") {
|
|
1359
|
+
return colorize(label, ANSI_BOLD, tone === "success" ? ANSI_GREEN : tone === "warning" ? ANSI_YELLOW : ANSI_CYAN);
|
|
1360
|
+
}
|
|
1361
|
+
function formatSecondaryText(text) {
|
|
1362
|
+
return colorize(text, ANSI_DIM);
|
|
1363
|
+
}
|
|
1364
|
+
function inferSuggestedNewMachineDir(config) {
|
|
1365
|
+
const include = config.include[0];
|
|
1366
|
+
if (!include) return "src";
|
|
1367
|
+
const groupedRootMatch = include.match(/^(src|packages\/[^/]+\/src|apps\/[^/]+\/src)(?:\/|$)/);
|
|
1368
|
+
if (groupedRootMatch?.[1]) return groupedRootMatch[1];
|
|
1369
|
+
const globIndex = include.indexOf("**/");
|
|
1370
|
+
if (globIndex > 0) return include.slice(0, globIndex).replace(/\/$/, "") || "src";
|
|
1371
|
+
const wildcardIndex = include.indexOf("*");
|
|
1372
|
+
if (wildcardIndex > 0) return path.posix.dirname(include.slice(0, wildcardIndex)) || "src";
|
|
1373
|
+
return path.posix.dirname(include) === "." ? "src" : path.posix.dirname(include);
|
|
1374
|
+
}
|
|
1375
|
+
function formatMissingNewMachineDirMessage(options) {
|
|
1376
|
+
const suggestedDir = inferSuggestedNewMachineDir(options.config);
|
|
1377
|
+
const countLabel = pluralize(options.remoteMachineCount, "remote-only project machine");
|
|
1378
|
+
return [
|
|
1379
|
+
`Found ${options.remoteMachineCount} ${countLabel}, but statelyai.json does not set newMachinesDir, so they will be skipped.`,
|
|
1380
|
+
"To import new machines created in Studio, add this to statelyai.json:",
|
|
1381
|
+
` "newMachinesDir": "${suggestedDir}"`,
|
|
1382
|
+
"Then run `statelyai pull` again."
|
|
1383
|
+
].join("\n");
|
|
1384
|
+
}
|
|
1318
1385
|
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";
|
|
1386
|
+
return (typeof machineName === "string" ? machineName : "").trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "machine";
|
|
1320
1387
|
}
|
|
1321
1388
|
function getMissingApiKeyMessage() {
|
|
1322
1389
|
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}`;
|
|
@@ -1418,23 +1485,34 @@ async function discoverLinkedPullTargets(files) {
|
|
|
1418
1485
|
return linkedTargets;
|
|
1419
1486
|
}
|
|
1420
1487
|
async function discoverRemoteProjectMachineTargets(options) {
|
|
1421
|
-
const newMachineDir = options.config.
|
|
1488
|
+
const newMachineDir = options.config.newMachinesDir?.trim();
|
|
1422
1489
|
if (!newMachineDir) return [];
|
|
1423
1490
|
const targets = [];
|
|
1491
|
+
const reservedRelativePaths = /* @__PURE__ */ new Set();
|
|
1424
1492
|
for (const machine of options.project.machines) {
|
|
1425
|
-
|
|
1493
|
+
const candidate = machine;
|
|
1494
|
+
const machineId = typeof candidate.machineId === "string" ? candidate.machineId : typeof candidate.id === "string" ? candidate.id : void 0;
|
|
1495
|
+
if (!machineId || options.linkedMachineIds.has(machineId)) continue;
|
|
1426
1496
|
const baseDirectory = path.join(options.rootDir, newMachineDir);
|
|
1427
|
-
|
|
1497
|
+
let machineName = typeof candidate.name === "string" && candidate.name.trim().length > 0 ? candidate.name : typeof candidate.key === "string" && candidate.key.trim().length > 0 ? candidate.key : void 0;
|
|
1498
|
+
if ((!machineName || machineName === machineId) && options.client?.machines?.get) try {
|
|
1499
|
+
const remoteMachine = await options.client.machines.get(machineId);
|
|
1500
|
+
const fetchedName = typeof remoteMachine?.name === "string" && remoteMachine.name.trim().length > 0 ? remoteMachine.name : void 0;
|
|
1501
|
+
if (fetchedName) machineName = fetchedName;
|
|
1502
|
+
} catch {}
|
|
1503
|
+
machineName ??= machineId;
|
|
1504
|
+
const stem = toMachineFileStem(machineName);
|
|
1428
1505
|
let candidateFileName = `${stem}${DEFAULT_NEW_MACHINE_FILE_SUFFIX}`;
|
|
1429
1506
|
let attempt = 2;
|
|
1430
1507
|
for (;;) {
|
|
1431
1508
|
const filePath = path.join(baseDirectory, candidateFileName);
|
|
1432
1509
|
const relativePath = path.relative(options.rootDir, filePath).replace(/\\/g, "/");
|
|
1433
|
-
if (!isTrackedByStatelyProjectConfig(options.config, relativePath)) throw new Error(`Generated path ${relativePath} for remote machine ${
|
|
1434
|
-
if (!await fileExists(filePath)) {
|
|
1510
|
+
if (!isTrackedByStatelyProjectConfig(options.config, relativePath)) throw new Error(`Generated path ${relativePath} for remote machine ${machineName} is not covered by include/exclude globs.`);
|
|
1511
|
+
if (!reservedRelativePaths.has(relativePath) && !await fileExists(filePath)) {
|
|
1512
|
+
reservedRelativePaths.add(relativePath);
|
|
1435
1513
|
targets.push({
|
|
1436
|
-
machineId
|
|
1437
|
-
machineName
|
|
1514
|
+
machineId,
|
|
1515
|
+
machineName,
|
|
1438
1516
|
filePath,
|
|
1439
1517
|
relativePath
|
|
1440
1518
|
});
|
|
@@ -1453,7 +1531,8 @@ async function initProject(options) {
|
|
|
1453
1531
|
const defaultXStateVersion = Math.max(5, options.defaultXStateVersion ?? 5);
|
|
1454
1532
|
const existingConfig = !options.force ? await readStatelyProjectConfig({
|
|
1455
1533
|
cwd,
|
|
1456
|
-
configPath: options.configPath
|
|
1534
|
+
configPath: options.configPath,
|
|
1535
|
+
rewriteIfNeeded: true
|
|
1457
1536
|
}).catch(() => void 0) : void 0;
|
|
1458
1537
|
const client = options.client ?? createStatelyClient({
|
|
1459
1538
|
apiKey: options.apiKey,
|
|
@@ -1521,9 +1600,10 @@ function supportsMachineDiscovery(file) {
|
|
|
1521
1600
|
return file.source.format === "xstate" || file.source.format === "auto";
|
|
1522
1601
|
}
|
|
1523
1602
|
async function resolveConfiguredProject(options) {
|
|
1524
|
-
const { config, configPath, rootDir } = await readStatelyProjectConfig({
|
|
1603
|
+
const { config, configPath, rootDir, rewriteStatus } = await readStatelyProjectConfig({
|
|
1525
1604
|
cwd: options.cwd,
|
|
1526
|
-
configPath: options.configPath
|
|
1605
|
+
configPath: options.configPath,
|
|
1606
|
+
rewriteIfNeeded: true
|
|
1527
1607
|
});
|
|
1528
1608
|
const studioUrl = options.baseUrl ?? config.studioUrl;
|
|
1529
1609
|
return {
|
|
@@ -1536,7 +1616,37 @@ async function resolveConfiguredProject(options) {
|
|
|
1536
1616
|
files: await discoverStatelySourceFiles({
|
|
1537
1617
|
cwd: rootDir,
|
|
1538
1618
|
config
|
|
1539
|
-
})
|
|
1619
|
+
}),
|
|
1620
|
+
rewriteStatus
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
async function resolveConfiguredPullTargets(options) {
|
|
1624
|
+
const { client, files, config, configPath, rewriteStatus } = await resolveConfiguredProject(options);
|
|
1625
|
+
const linkedTargets = await discoverLinkedPullTargets(files);
|
|
1626
|
+
const linkedMachineIds = new Set(linkedTargets.map((linkedTarget) => linkedTarget.machineId));
|
|
1627
|
+
const project = await client.projects.get(config.projectId);
|
|
1628
|
+
const skipped = [];
|
|
1629
|
+
let remoteOnlyTargets = [];
|
|
1630
|
+
try {
|
|
1631
|
+
remoteOnlyTargets = await discoverRemoteProjectMachineTargets({
|
|
1632
|
+
rootDir: path.dirname(configPath),
|
|
1633
|
+
config,
|
|
1634
|
+
project,
|
|
1635
|
+
linkedMachineIds,
|
|
1636
|
+
client
|
|
1637
|
+
});
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
skipped.push(error instanceof Error ? error.message : String(error));
|
|
1640
|
+
}
|
|
1641
|
+
return {
|
|
1642
|
+
client,
|
|
1643
|
+
config,
|
|
1644
|
+
configPath,
|
|
1645
|
+
linkedTargets,
|
|
1646
|
+
project,
|
|
1647
|
+
remoteOnlyTargets,
|
|
1648
|
+
skipped,
|
|
1649
|
+
rewriteStatus
|
|
1540
1650
|
};
|
|
1541
1651
|
}
|
|
1542
1652
|
const sharedFlags = {
|
|
@@ -1654,37 +1764,33 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
|
|
|
1654
1764
|
const apiKey = (await resolveApiKey(flags["api-key"])).apiKey;
|
|
1655
1765
|
if (!args.source) {
|
|
1656
1766
|
if (!apiKey) this.error(getMissingApiKeyMessage());
|
|
1657
|
-
this.log("Resolving configured project and linked source files...");
|
|
1658
|
-
const {
|
|
1767
|
+
this.log(colorize("Resolving configured project and linked source files...", ANSI_CYAN));
|
|
1768
|
+
const { config, linkedTargets, project, remoteOnlyTargets, skipped, rewriteStatus } = await resolveConfiguredPullTargets({
|
|
1659
1769
|
apiKey,
|
|
1660
1770
|
baseUrl: flags["base-url"],
|
|
1661
1771
|
configPath: flags.config
|
|
1662
1772
|
});
|
|
1663
|
-
|
|
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
|
-
}
|
|
1773
|
+
if (rewriteStatus.reason === "multiple-legacy-sources") this.log("Legacy statelyai.json detected with multiple sources entries. It cannot be rewritten automatically without losing information, so please update it manually.");
|
|
1678
1774
|
if (linkedTargets.length === 0 && remoteOnlyTargets.length === 0) {
|
|
1679
|
-
if (project.machines.length > 0 && !config.
|
|
1680
|
-
this.log("
|
|
1775
|
+
if (project.machines.length > 0 && !config.newMachinesDir) {
|
|
1776
|
+
this.log(formatSectionLabel("Skipped:", "warning"));
|
|
1777
|
+
this.log(formatMissingNewMachineDirMessage({
|
|
1778
|
+
config,
|
|
1779
|
+
remoteMachineCount: project.machines.length
|
|
1780
|
+
}));
|
|
1681
1781
|
return;
|
|
1682
1782
|
}
|
|
1683
1783
|
this.log("No linked local machine files or remote-only project machines were discovered.");
|
|
1684
1784
|
return;
|
|
1685
1785
|
}
|
|
1686
|
-
if (project.machines.length > linkedTargets.length && !config.
|
|
1687
|
-
|
|
1786
|
+
if (project.machines.length > linkedTargets.length && !config.newMachinesDir) {
|
|
1787
|
+
this.log(formatSectionLabel("Skipped:", "warning"));
|
|
1788
|
+
this.log(formatMissingNewMachineDirMessage({
|
|
1789
|
+
config,
|
|
1790
|
+
remoteMachineCount: project.machines.length - linkedTargets.length
|
|
1791
|
+
}));
|
|
1792
|
+
}
|
|
1793
|
+
this.log(colorize(`Processing ${linkedTargets.length} ${pluralize(linkedTargets.length, "linked file")}${remoteOnlyTargets.length > 0 ? ` and ${remoteOnlyTargets.length} ${pluralize(remoteOnlyTargets.length, "new remote machine")}` : ""}...`, ANSI_CYAN));
|
|
1688
1794
|
const pulled = [];
|
|
1689
1795
|
const created = [];
|
|
1690
1796
|
for (const linkedTarget of linkedTargets) {
|
|
@@ -1693,7 +1799,7 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
|
|
|
1693
1799
|
skipped.push(`${linkedTarget.file.relativePath}: local git changes detected (pass --force to overwrite)`);
|
|
1694
1800
|
continue;
|
|
1695
1801
|
}
|
|
1696
|
-
this.log(`Pulling ${linkedTarget.file.relativePath}
|
|
1802
|
+
this.log(colorize(`Pulling ${linkedTarget.file.relativePath}...`, ANSI_CYAN));
|
|
1697
1803
|
try {
|
|
1698
1804
|
const result = await pullSync({
|
|
1699
1805
|
source: linkedTarget.machineId,
|
|
@@ -1701,13 +1807,13 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
|
|
|
1701
1807
|
apiKey,
|
|
1702
1808
|
baseUrl: flags["base-url"] ?? config.studioUrl
|
|
1703
1809
|
});
|
|
1704
|
-
pulled.push(`${linkedTarget.file.relativePath}: ${result.source.locator}`);
|
|
1810
|
+
pulled.push(`${linkedTarget.file.relativePath}: ${formatSecondaryText(result.source.locator)}`);
|
|
1705
1811
|
} catch (error) {
|
|
1706
1812
|
skipped.push(`${linkedTarget.file.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1707
1813
|
}
|
|
1708
1814
|
}
|
|
1709
1815
|
for (const remoteTarget of remoteOnlyTargets) {
|
|
1710
|
-
this.log(`Pulling new remote machine ${remoteTarget.machineName} into ${remoteTarget.relativePath}
|
|
1816
|
+
this.log(colorize(`Pulling new remote machine ${remoteTarget.machineName} into ${remoteTarget.relativePath}...`, ANSI_CYAN));
|
|
1711
1817
|
try {
|
|
1712
1818
|
await fsPromises.mkdir(path.dirname(remoteTarget.filePath), { recursive: true });
|
|
1713
1819
|
const result = await pullSync({
|
|
@@ -1716,14 +1822,14 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
|
|
|
1716
1822
|
apiKey,
|
|
1717
1823
|
baseUrl: flags["base-url"] ?? config.studioUrl
|
|
1718
1824
|
});
|
|
1719
|
-
created.push(`${remoteTarget.relativePath}: ${result.source.locator}`);
|
|
1825
|
+
created.push(`${remoteTarget.relativePath}: ${formatSecondaryText(result.source.locator)}`);
|
|
1720
1826
|
} catch (error) {
|
|
1721
1827
|
skipped.push(`${remoteTarget.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1722
1828
|
}
|
|
1723
1829
|
}
|
|
1724
|
-
if (pulled.length > 0) this.log(
|
|
1725
|
-
if (created.length > 0) this.log(
|
|
1726
|
-
if (skipped.length > 0) this.log(
|
|
1830
|
+
if (pulled.length > 0) this.log(`${formatSectionLabel("Pulled:", "success")}\n${pulled.join("\n")}`);
|
|
1831
|
+
if (created.length > 0) this.log(`${formatSectionLabel("Created:", "success")}\n${created.join("\n")}`);
|
|
1832
|
+
if (skipped.length > 0) this.log(`${formatSectionLabel("Skipped:", "warning")}\n${skipped.join("\n")}`);
|
|
1727
1833
|
return;
|
|
1728
1834
|
}
|
|
1729
1835
|
const localCandidate = path.resolve(process.cwd(), args.source);
|
|
@@ -1738,14 +1844,14 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
|
|
|
1738
1844
|
if (!target) this.error("Missing target path. Pass `statelyai pull <machine-id|url> <file>` or `statelyai pull <linked-file>`.");
|
|
1739
1845
|
if (!apiKey) this.error(getMissingApiKeyMessage());
|
|
1740
1846
|
if (!flags.force && await fileExists(target) && await isFileGitDirty(target)) this.error(`Refusing to overwrite locally modified file ${target}. Pass --force to overwrite it.`);
|
|
1741
|
-
this.log(`Pulling ${source} into ${target}
|
|
1847
|
+
this.log(colorize(`Pulling ${source} into ${target}...`, ANSI_CYAN));
|
|
1742
1848
|
const result = await pullSync({
|
|
1743
1849
|
source,
|
|
1744
1850
|
target,
|
|
1745
1851
|
apiKey,
|
|
1746
1852
|
baseUrl: flags["base-url"] ?? getDefaultBaseUrl()
|
|
1747
1853
|
});
|
|
1748
|
-
this.log(
|
|
1854
|
+
this.log(`${formatSectionLabel("Pulled:", "success")} ${formatSecondaryText(result.source.locator)} -> ${result.outputPath}\nTarget: ${result.target.kind} (${result.target.format})`);
|
|
1749
1855
|
}
|
|
1750
1856
|
};
|
|
1751
1857
|
var PushCommand = class PushCommand extends Command {
|
|
@@ -1770,22 +1876,27 @@ var PushCommand = class PushCommand extends Command {
|
|
|
1770
1876
|
const { args, flags } = await this.parse(PushCommand);
|
|
1771
1877
|
const resolvedApiKey = flags["dry-run"] ? { source: "missing" } : await resolveApiKey(flags["api-key"]);
|
|
1772
1878
|
if (!flags["dry-run"] && !resolvedApiKey.apiKey) this.error(getMissingApiKeyMessage());
|
|
1773
|
-
this.log("Resolving configured project and source files...");
|
|
1774
|
-
const { client, config, files } = flags["dry-run"] ? await (async () => {
|
|
1775
|
-
const { config, rootDir } = await readStatelyProjectConfig({
|
|
1879
|
+
this.log(colorize("Resolving configured project and source files...", ANSI_CYAN));
|
|
1880
|
+
const { client, config, files, rewriteStatus } = flags["dry-run"] ? await (async () => {
|
|
1881
|
+
const { config, rootDir, rewriteStatus } = await readStatelyProjectConfig({
|
|
1882
|
+
configPath: flags.config,
|
|
1883
|
+
rewriteIfNeeded: true
|
|
1884
|
+
});
|
|
1776
1885
|
return {
|
|
1777
1886
|
client: void 0,
|
|
1778
1887
|
config,
|
|
1779
1888
|
files: await discoverStatelySourceFiles({
|
|
1780
1889
|
cwd: rootDir,
|
|
1781
1890
|
config
|
|
1782
|
-
})
|
|
1891
|
+
}),
|
|
1892
|
+
rewriteStatus
|
|
1783
1893
|
};
|
|
1784
1894
|
})() : await resolveConfiguredProject({
|
|
1785
1895
|
apiKey: resolvedApiKey.apiKey,
|
|
1786
1896
|
baseUrl: flags["base-url"],
|
|
1787
1897
|
configPath: flags.config
|
|
1788
1898
|
});
|
|
1899
|
+
if (rewriteStatus.reason === "multiple-legacy-sources") this.log("Legacy statelyai.json detected with multiple sources entries. It cannot be rewritten automatically without losing information, so please update it manually.");
|
|
1789
1900
|
const candidateFiles = args.file ? [{
|
|
1790
1901
|
filePath: path.resolve(args.file),
|
|
1791
1902
|
relativePath: path.relative(process.cwd(), path.resolve(args.file)),
|
|
@@ -1797,33 +1908,33 @@ var PushCommand = class PushCommand extends Command {
|
|
|
1797
1908
|
}
|
|
1798
1909
|
}] : files.filter(supportsMachineDiscovery);
|
|
1799
1910
|
if (candidateFiles.length === 0) {
|
|
1800
|
-
this.log("No matching local machine source files were discovered.");
|
|
1911
|
+
this.log(colorize("No matching local machine source files were discovered.", ANSI_YELLOW));
|
|
1801
1912
|
return;
|
|
1802
1913
|
}
|
|
1803
1914
|
const { machineFiles } = await classifyPushCandidates(candidateFiles);
|
|
1804
1915
|
if (machineFiles.length === 0) {
|
|
1805
|
-
this.log(`Matched ${candidateFiles.length} ${pluralize(candidateFiles.length, "file")}, but no local machine sources were discovered
|
|
1916
|
+
this.log(colorize(`Matched ${candidateFiles.length} ${pluralize(candidateFiles.length, "file")}, but no local machine sources were discovered.`, ANSI_YELLOW));
|
|
1806
1917
|
return;
|
|
1807
1918
|
}
|
|
1808
|
-
this.log(`Discovered ${machineFiles.length} ${pluralize(machineFiles.length, "file with a machine")} from ${candidateFiles.length} matched ${pluralize(candidateFiles.length, "file")}
|
|
1919
|
+
this.log(colorize(`Discovered ${machineFiles.length} ${pluralize(machineFiles.length, "file with a machine")} from ${candidateFiles.length} matched ${pluralize(candidateFiles.length, "file")}.`, ANSI_CYAN));
|
|
1809
1920
|
if (flags["dry-run"]) {
|
|
1810
1921
|
const wouldLink = [];
|
|
1811
1922
|
const wouldUpdate = [];
|
|
1812
1923
|
for (const file of machineFiles) {
|
|
1813
1924
|
const pragma = getStatelyPragma(await fsPromises.readFile(file.filePath, "utf8"), file.filePath);
|
|
1814
|
-
if (pragma?.id) wouldUpdate.push(`${file.relativePath}: ${pragma.id}`);
|
|
1925
|
+
if (pragma?.id) wouldUpdate.push(`${file.relativePath}: ${formatSecondaryText(pragma.id)}`);
|
|
1815
1926
|
else wouldLink.push(file.relativePath);
|
|
1816
1927
|
}
|
|
1817
|
-
if (wouldLink.length > 0) this.log(
|
|
1818
|
-
if (wouldUpdate.length > 0) this.log(
|
|
1928
|
+
if (wouldLink.length > 0) this.log(`${formatSectionLabel("Would link:")}\n${wouldLink.join("\n")}`);
|
|
1929
|
+
if (wouldUpdate.length > 0) this.log(`${formatSectionLabel("Would update:")}\n${wouldUpdate.join("\n")}`);
|
|
1819
1930
|
return;
|
|
1820
1931
|
}
|
|
1821
|
-
this.log(`Processing ${machineFiles.length} ${pluralize(machineFiles.length, "source file")}
|
|
1932
|
+
this.log(colorize(`Processing ${machineFiles.length} ${pluralize(machineFiles.length, "source file")}...`, ANSI_CYAN));
|
|
1822
1933
|
const linked = [];
|
|
1823
1934
|
const refreshed = [];
|
|
1824
1935
|
const skipped = [];
|
|
1825
1936
|
for (const file of machineFiles) {
|
|
1826
|
-
this.log(`Pushing ${file.relativePath}
|
|
1937
|
+
this.log(colorize(`Pushing ${file.relativePath}...`, ANSI_CYAN));
|
|
1827
1938
|
if (!supportsMachineDiscovery(file)) {
|
|
1828
1939
|
skipped.push(`${file.relativePath}: unsupported format ${file.source.format}`);
|
|
1829
1940
|
continue;
|
|
@@ -1848,7 +1959,7 @@ var PushCommand = class PushCommand extends Command {
|
|
|
1848
1959
|
skipped.push(`${file.relativePath}: linked remote machine is missing or inaccessible.`);
|
|
1849
1960
|
continue;
|
|
1850
1961
|
}
|
|
1851
|
-
this.log(`Relinking ${file.relativePath} as a new remote machine
|
|
1962
|
+
this.log(colorize(`Relinking ${file.relativePath} as a new remote machine...`, ANSI_YELLOW));
|
|
1852
1963
|
result = await pushLocalMachineLinks({
|
|
1853
1964
|
source: file.filePath,
|
|
1854
1965
|
apiKey: resolvedApiKey.apiKey,
|
|
@@ -1860,13 +1971,13 @@ var PushCommand = class PushCommand extends Command {
|
|
|
1860
1971
|
});
|
|
1861
1972
|
} else throw error;
|
|
1862
1973
|
}
|
|
1863
|
-
if (result.created.length > 0) linked.push(`${file.relativePath}: ${result.created.map(({ machineIndex, machine }) => `${machine.id} [${machineIndex}] ${buildMachineEditorUrl(config.studioUrl, config.projectId, machine.id)}`).join(", ")}`);
|
|
1864
|
-
if (result.updated.length > 0) refreshed.push(`${file.relativePath}: ${result.updated.map(({ machineIndex, machine }) => `${machine.id} [${machineIndex}] ${buildMachineEditorUrl(config.studioUrl, config.projectId, machine.id)}`).join(", ")}`);
|
|
1974
|
+
if (result.created.length > 0) linked.push(`${file.relativePath}: ${result.created.map(({ machineIndex, machine }) => `${formatSecondaryText(`${machine.id} [${machineIndex}]`)} ${formatSecondaryText(buildMachineEditorUrl(config.studioUrl, config.projectId, machine.id))}`).join(", ")}`);
|
|
1975
|
+
if (result.updated.length > 0) refreshed.push(`${file.relativePath}: ${result.updated.map(({ machineIndex, machine }) => `${formatSecondaryText(`${machine.id} [${machineIndex}]`)} ${formatSecondaryText(buildMachineEditorUrl(config.studioUrl, config.projectId, machine.id))}`).join(", ")}`);
|
|
1865
1976
|
for (const entry of result.skipped) skipped.push(`${file.relativePath} [${entry.machineIndex}]: ${entry.reason}`);
|
|
1866
1977
|
}
|
|
1867
|
-
if (linked.length > 0) this.log(
|
|
1868
|
-
if (refreshed.length > 0) this.log(
|
|
1869
|
-
if (skipped.length > 0) this.log(
|
|
1978
|
+
if (linked.length > 0) this.log(`${formatSectionLabel("Linked:", "success")}\n${linked.join("\n")}`);
|
|
1979
|
+
if (refreshed.length > 0) this.log(`${formatSectionLabel("Updated:", "success")}\n${refreshed.join("\n")}`);
|
|
1980
|
+
if (skipped.length > 0) this.log(`${formatSectionLabel("Skipped:", "warning")}\n${skipped.join("\n")}`);
|
|
1870
1981
|
}
|
|
1871
1982
|
};
|
|
1872
1983
|
var OpenCommand = class OpenCommand extends Command {
|
|
@@ -1975,7 +2086,7 @@ var InitCommand = class InitCommand extends Command {
|
|
|
1975
2086
|
...result.config,
|
|
1976
2087
|
include: suggestions.flatMap((suggestion) => suggestion.include),
|
|
1977
2088
|
exclude: [...new Set(suggestions.flatMap((suggestion) => suggestion.exclude ?? []))],
|
|
1978
|
-
...inferredNewMachineDir ? {
|
|
2089
|
+
...inferredNewMachineDir ? { newMachinesDir: inferredNewMachineDir } : {}
|
|
1979
2090
|
};
|
|
1980
2091
|
await fsPromises.writeFile(result.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
1981
2092
|
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.");
|
|
@@ -2074,4 +2185,4 @@ function isDirectExecution() {
|
|
|
2074
2185
|
if (isDirectExecution()) run$1();
|
|
2075
2186
|
|
|
2076
2187
|
//#endregion
|
|
2077
|
-
export {
|
|
2188
|
+
export { formatMissingNewMachineDirMessage as a, inferInitProjectName as c, resolveApiKey as d, resolveConfiguredPullTargets as f, createStatelyProjectConfig as h, discoverRemoteProjectMachineTargets as i, initProject as l, scanProjectSources as m, classifyPushCandidates as n, formatPlanSummary as o, run$1 as p, discoverLinkedPullTargets as r, getEnvApiKey as s, COMMANDS as t, isFileGitDirty as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -18,7 +18,7 @@ interface StatelyProjectConfig {
|
|
|
18
18
|
defaultXStateVersion: number;
|
|
19
19
|
include: string[];
|
|
20
20
|
exclude?: string[];
|
|
21
|
-
|
|
21
|
+
newMachinesDir?: string;
|
|
22
22
|
}
|
|
23
23
|
interface DiscoveredSourceFile {
|
|
24
24
|
filePath: string;
|
|
@@ -66,6 +66,19 @@ interface PushCandidateClassification {
|
|
|
66
66
|
machineFiles: DiscoveredSourceFile[];
|
|
67
67
|
nonMachineFiles: DiscoveredSourceFile[];
|
|
68
68
|
}
|
|
69
|
+
interface ResolvedConfiguredPullTargets {
|
|
70
|
+
client: StudioClient;
|
|
71
|
+
config: StatelyProjectConfig;
|
|
72
|
+
configPath: string;
|
|
73
|
+
linkedTargets: LinkedPullTarget[];
|
|
74
|
+
project: ProjectData;
|
|
75
|
+
remoteOnlyTargets: RemoteProjectMachineTarget[];
|
|
76
|
+
skipped: string[];
|
|
77
|
+
rewriteStatus: {
|
|
78
|
+
needsRewrite: boolean;
|
|
79
|
+
reason?: string;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
69
82
|
type ApiKeyResolution = {
|
|
70
83
|
apiKey: string;
|
|
71
84
|
detail: string;
|
|
@@ -81,6 +94,10 @@ declare function getEnvApiKey(): {
|
|
|
81
94
|
} | undefined;
|
|
82
95
|
declare function resolveApiKey(explicitApiKey?: string): Promise<ApiKeyResolution>;
|
|
83
96
|
declare function inferInitProjectName(cwd: string, repo?: ConnectedRepo): string;
|
|
97
|
+
declare function formatMissingNewMachineDirMessage(options: {
|
|
98
|
+
config: Pick<StatelyProjectConfig, 'include' | 'newMachinesDir'>;
|
|
99
|
+
remoteMachineCount: number;
|
|
100
|
+
}): string;
|
|
84
101
|
declare function isFileGitDirty(filePath: string): Promise<boolean>;
|
|
85
102
|
declare function discoverLinkedPullTargets(files: DiscoveredSourceFile[]): Promise<LinkedPullTarget[]>;
|
|
86
103
|
declare function discoverRemoteProjectMachineTargets(options: {
|
|
@@ -88,10 +105,18 @@ declare function discoverRemoteProjectMachineTargets(options: {
|
|
|
88
105
|
config: StatelyProjectConfig;
|
|
89
106
|
project: ProjectData;
|
|
90
107
|
linkedMachineIds: Set<string>;
|
|
108
|
+
client?: Pick<StudioClient, 'machines'>;
|
|
91
109
|
}): Promise<RemoteProjectMachineTarget[]>;
|
|
92
110
|
declare function initProject(options: InitProjectOptions): Promise<InitProjectResult>;
|
|
93
111
|
declare function scanProjectSources(options: ScanProjectSourcesOptions): Promise<Array<Pick<StatelyProjectConfig, 'include' | 'exclude'>>>;
|
|
94
112
|
declare function classifyPushCandidates(files: DiscoveredSourceFile[]): Promise<PushCandidateClassification>;
|
|
113
|
+
declare function resolveConfiguredPullTargets(options: {
|
|
114
|
+
cwd?: string;
|
|
115
|
+
configPath?: string;
|
|
116
|
+
apiKey: string;
|
|
117
|
+
baseUrl?: string;
|
|
118
|
+
client?: StudioClient;
|
|
119
|
+
}): Promise<ResolvedConfiguredPullTargets>;
|
|
95
120
|
declare function formatPlanSummary(plan: SyncPlan): string;
|
|
96
121
|
declare abstract class BaseSyncCommand extends Command {
|
|
97
122
|
static enableJsonFlag: boolean;
|
|
@@ -240,4 +265,4 @@ declare const COMMANDS: {
|
|
|
240
265
|
};
|
|
241
266
|
declare function run(argv?: any, entryUrl?: string): Promise<void>;
|
|
242
267
|
//#endregion
|
|
243
|
-
export { ApiKeyResolution, COMMANDS, InitProjectOptions, InitProjectResult, LinkedPullTarget, PushCandidateClassification, RemoteProjectMachineTarget, ScanProjectSourcesOptions, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverRemoteProjectMachineTargets, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, run, scanProjectSources };
|
|
268
|
+
export { ApiKeyResolution, COMMANDS, InitProjectOptions, InitProjectResult, LinkedPullTarget, PushCandidateClassification, RemoteProjectMachineTarget, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, run, scanProjectSources };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as formatMissingNewMachineDirMessage, c as inferInitProjectName, d as resolveApiKey, f as resolveConfiguredPullTargets, h as createStatelyProjectConfig, i as discoverRemoteProjectMachineTargets, l as initProject, m as scanProjectSources, n as classifyPushCandidates, o as formatPlanSummary, p as run, r as discoverLinkedPullTargets, s as getEnvApiKey, t as COMMANDS, u as isFileGitDirty } from "./cli-D4Id6WsS.mjs";
|
|
2
2
|
|
|
3
|
-
export { COMMANDS, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverRemoteProjectMachineTargets, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, run, scanProjectSources };
|
|
3
|
+
export { COMMANDS, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, run, scanProjectSources };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "statelyai",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
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.10.
|
|
24
|
+
"@statelyai/sdk": "0.10.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"tsdown": "0.21.0-beta.2",
|