statelyai 0.7.5 → 0.7.7
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 +52 -10
- package/dist/bin.mjs +1 -1
- package/dist/{cli-DRKY520A.mjs → cli-DicCA4VI.mjs} +301 -68
- package/dist/index.d.mts +46 -7
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# statelyai
|
|
2
2
|
|
|
3
|
-
`statelyai`
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`statelyai` discovers local machine source files and can connect them to
|
|
4
|
+
Stately Studio. Use it locally to inventory machines, or connect a project to
|
|
5
|
+
open, compare, push, and pull machines.
|
|
6
6
|
|
|
7
7
|
## Run the CLI
|
|
8
8
|
|
|
@@ -26,8 +26,10 @@ npm install --global statelyai
|
|
|
26
26
|
|
|
27
27
|
| Goal | Command | Writes |
|
|
28
28
|
| ---------------------------- | ---------------------------------------------------- | ------------------------------------------------------- |
|
|
29
|
+
| List local machines | `statelyai scan` | Nothing |
|
|
29
30
|
| Edit one local file visually | `statelyai open <file>` | Local file, when you save in the editor |
|
|
30
|
-
| Set up
|
|
31
|
+
| Set up local discovery | `statelyai init --local --scan` | Local `statelyai.json` |
|
|
32
|
+
| Set up project sync | `statelyai init --scan` | Remote project and local `statelyai.json` |
|
|
31
33
|
| Inspect project machines | `statelyai status` | Nothing |
|
|
32
34
|
| Preview project uploads | `statelyai push --dry-run` | Nothing |
|
|
33
35
|
| Upload local machines | `statelyai push [file]` | Remote machines and local `@statelyai` IDs |
|
|
@@ -53,6 +55,24 @@ statelyai init --scan
|
|
|
53
55
|
|
|
54
56
|
`--scan` suggests `include` globs and asks before saving them. Without
|
|
55
57
|
`--scan`, `init` creates `statelyai.json` with an empty `include` list.
|
|
58
|
+
It also lists every discovered machine with its symbol, file, line, and linked
|
|
59
|
+
Stately ID when present.
|
|
60
|
+
|
|
61
|
+
For local discovery without authentication or a Studio project:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
statelyai init --local --scan
|
|
65
|
+
statelyai status
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Run `statelyai init` later to connect the local config to a Studio project
|
|
69
|
+
without losing its discovery globs.
|
|
70
|
+
|
|
71
|
+
To list machines without creating any config:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
statelyai scan
|
|
75
|
+
```
|
|
56
76
|
|
|
57
77
|
Preview the files and remote operations:
|
|
58
78
|
|
|
@@ -114,6 +134,10 @@ environment variables.
|
|
|
114
134
|
Stored credentials use macOS Keychain or Linux Secret Service when available,
|
|
115
135
|
with a private config file as fallback. Set `STATELYAI_CREDENTIALS_BACKEND=file`
|
|
116
136
|
to force file storage, or `STATELYAI_CONFIG_DIR` to choose its directory.
|
|
137
|
+
Stored OAuth sessions refresh automatically before expiration. If the OAuth
|
|
138
|
+
server rejects the refresh token, the CLI removes the unusable credential and
|
|
139
|
+
directs you to run `npx statelyai login`. Transient refresh failures preserve
|
|
140
|
+
the credential so a later command can retry.
|
|
117
141
|
|
|
118
142
|
## `statelyai.json`
|
|
119
143
|
|
|
@@ -139,16 +163,16 @@ to force file storage, or `STATELYAI_CONFIG_DIR` to choose its directory.
|
|
|
139
163
|
| ---------------------- | --------------------------------------------------------------- |
|
|
140
164
|
| `$schema` | Published JSON Schema URL. |
|
|
141
165
|
| `version` | Config format version. Currently `1.0.0`. |
|
|
142
|
-
| `projectId` | Remote Studio project ID.
|
|
143
|
-
| `studioUrl` | Studio API origin.
|
|
166
|
+
| `projectId` | Remote Studio project ID. Omitted for local-only projects. |
|
|
167
|
+
| `studioUrl` | Studio API origin. Omitted for local-only projects. |
|
|
144
168
|
| `defaultXStateVersion` | XState version used when creating remote machines. Minimum `5`. |
|
|
145
169
|
| `include` | Source globs used by project-wide `push` and `pull`. |
|
|
146
170
|
| `exclude` | Globs removed from discovery. Defaults to tests and specs. |
|
|
147
171
|
| `newMachinesDir` | Destination for remote-only machines created by `pull`. |
|
|
148
172
|
|
|
149
|
-
Project-wide
|
|
150
|
-
|
|
151
|
-
|
|
173
|
+
Project-wide discovery identifies every `createMachine(...)` and
|
|
174
|
+
`setup(...).createMachine(...)` call in configured JavaScript and TypeScript
|
|
175
|
+
files. `push` currently supports files configured as `xstate` or `auto`.
|
|
152
176
|
|
|
153
177
|
Mutating project commands rewrite legacy configs with one `sources` entry to
|
|
154
178
|
the current top-level shape. `status` and `push --dry-run` normalize legacy
|
|
@@ -187,6 +211,7 @@ statelyai status
|
|
|
187
211
|
```
|
|
188
212
|
|
|
189
213
|
Statuses are `linked`, `local-only`, `remote-only`, and `missing-remote`.
|
|
214
|
+
Local-only configs require no credentials and never contact Studio.
|
|
190
215
|
Use `--json` for automation or `--auth` to show only credential resolution.
|
|
191
216
|
`status` is read-only and never migrates `statelyai.json`.
|
|
192
217
|
|
|
@@ -194,10 +219,11 @@ Flags: `--config <path>`, `--base-url <url>`, `--json`, `--auth`.
|
|
|
194
219
|
|
|
195
220
|
### `init`
|
|
196
221
|
|
|
197
|
-
Create or reuse a Studio project and write `statelyai.json`.
|
|
222
|
+
Create a local config or reuse a Studio project and write `statelyai.json`.
|
|
198
223
|
|
|
199
224
|
```bash
|
|
200
225
|
statelyai init --name "Checkout" --visibility Private --scan
|
|
226
|
+
statelyai init --local --scan
|
|
201
227
|
```
|
|
202
228
|
|
|
203
229
|
Flags:
|
|
@@ -205,9 +231,22 @@ Flags:
|
|
|
205
231
|
- `--name <name>` sets the remote project name.
|
|
206
232
|
- `--visibility Private|Public|Unlisted` defaults to `Private`.
|
|
207
233
|
- `--scan` proposes source globs interactively.
|
|
234
|
+
- `--local` skips authentication and remote project creation.
|
|
208
235
|
- `--force` replaces an existing config.
|
|
209
236
|
- `--base-url <url>` overrides the Studio API origin.
|
|
210
237
|
|
|
238
|
+
### `scan`
|
|
239
|
+
|
|
240
|
+
List every local XState machine without authentication or `statelyai.json`:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
statelyai scan
|
|
244
|
+
statelyai scan --json
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The output includes the machine symbol, source file, line, and linked Stately
|
|
248
|
+
ID when present.
|
|
249
|
+
|
|
211
250
|
### `open`
|
|
212
251
|
|
|
213
252
|
Start a local bridge and open one source file in the browser editor:
|
|
@@ -317,6 +356,7 @@ secret environment.
|
|
|
317
356
|
Check project discovery without network credentials or writes:
|
|
318
357
|
|
|
319
358
|
```bash
|
|
359
|
+
npx statelyai scan
|
|
320
360
|
npx statelyai push --dry-run
|
|
321
361
|
```
|
|
322
362
|
|
|
@@ -330,6 +370,8 @@ Set `NO_COLOR=1` or `CI=true` for plain output.
|
|
|
330
370
|
`STATELY_ACCESS_TOKEN` or `STATELY_API_KEY`.
|
|
331
371
|
- **`push` finds no files:** check `include` and `exclude`; `init` without
|
|
332
372
|
`--scan` intentionally leaves `include` empty.
|
|
373
|
+
- **You only need local inventory:** run `statelyai scan` or initialize with
|
|
374
|
+
`statelyai init --local --scan`; neither requires authentication.
|
|
333
375
|
- **`push` matches files but finds no machines:** project discovery currently
|
|
334
376
|
requires XState imports plus `createMachine(...)` or
|
|
335
377
|
`setup(...).createMachine(...)`.
|
package/dist/bin.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { promisify } from "node:util";
|
|
|
11
11
|
import { Args, Command, Flags, flush, handle, run } from "@oclif/core";
|
|
12
12
|
import * as http from "node:http";
|
|
13
13
|
import os from "node:os";
|
|
14
|
-
import { StudioApiError, buildAuthorizeUrl, createPkcePair, createStatelyClient, discoverAuthorizationServer, discoverProtectedResource, exchangeCodeForToken, getStatelyPragma, registerOAuthClient } from "@statelyai/sdk";
|
|
14
|
+
import { StudioApiError, buildAuthorizeUrl, createPkcePair, createStatelyClient, discoverAuthorizationServer, discoverProtectedResource, exchangeCodeForToken, findStatelyPragmaAttachments, getStatelyPragma, refreshToken, registerOAuthClient } from "@statelyai/sdk";
|
|
15
15
|
import { planSync, pullSync, pushLocalMachineLinks } from "@statelyai/sdk/sync";
|
|
16
16
|
|
|
17
17
|
//#region src/cliHost.ts
|
|
@@ -168,7 +168,13 @@ var RemoteEditorSession = class {
|
|
|
168
168
|
activePanels: []
|
|
169
169
|
});
|
|
170
170
|
} catch (error) {
|
|
171
|
-
|
|
171
|
+
const message = formatError(error, "Failed to initialize the visual editor.");
|
|
172
|
+
this.postMessage({
|
|
173
|
+
type: "@statelyai.error",
|
|
174
|
+
code: "initialization_failed",
|
|
175
|
+
message
|
|
176
|
+
});
|
|
177
|
+
this.showError(message);
|
|
172
178
|
return;
|
|
173
179
|
}
|
|
174
180
|
for (const pending of this.pendingMessages) this.sendMessage(pending);
|
|
@@ -530,6 +536,8 @@ function watchWorkspace(rootDir, listener) {
|
|
|
530
536
|
}
|
|
531
537
|
function getCliWebviewContent(options) {
|
|
532
538
|
const baseUrl = normalizedBaseUrl(options.editorUrl);
|
|
539
|
+
const url = new URL(`${baseUrl}/embed`);
|
|
540
|
+
url.searchParams.set("auth", "message");
|
|
533
541
|
return `<!DOCTYPE html>
|
|
534
542
|
<html lang="en" style="height:100%;margin:0">
|
|
535
543
|
<head>
|
|
@@ -543,7 +551,7 @@ function getCliWebviewContent(options) {
|
|
|
543
551
|
<body>
|
|
544
552
|
<iframe
|
|
545
553
|
id="stately-editor"
|
|
546
|
-
src="${escapeAttribute(
|
|
554
|
+
src="${escapeAttribute(url.toString())}"
|
|
547
555
|
allow="clipboard-read; clipboard-write"
|
|
548
556
|
></iframe>
|
|
549
557
|
<script>
|
|
@@ -793,7 +801,9 @@ function parseStoredCredential(raw, backend, location) {
|
|
|
793
801
|
type: "oauth",
|
|
794
802
|
accessToken: credential.accessToken,
|
|
795
803
|
...typeof credential.refreshToken === "string" ? { refreshToken: credential.refreshToken } : {},
|
|
796
|
-
...typeof credential.expiresAt === "number" ? { expiresAt: credential.expiresAt } : {}
|
|
804
|
+
...typeof credential.expiresAt === "number" ? { expiresAt: credential.expiresAt } : {},
|
|
805
|
+
...typeof credential.clientId === "string" ? { clientId: credential.clientId } : {},
|
|
806
|
+
...typeof credential.tokenEndpoint === "string" ? { tokenEndpoint: credential.tokenEndpoint } : {}
|
|
797
807
|
},
|
|
798
808
|
backend,
|
|
799
809
|
location
|
|
@@ -1064,13 +1074,21 @@ function createStatelyProjectConfig(options) {
|
|
|
1064
1074
|
return {
|
|
1065
1075
|
$schema: STATELY_CONFIG_SCHEMA_URL,
|
|
1066
1076
|
version: STATELY_CONFIG_VERSION,
|
|
1067
|
-
projectId: options.projectId,
|
|
1068
|
-
studioUrl: options.studioUrl,
|
|
1077
|
+
projectId: options.projectId ?? "",
|
|
1078
|
+
studioUrl: options.studioUrl ?? "https://stately.ai",
|
|
1069
1079
|
defaultXStateVersion,
|
|
1070
1080
|
include: [],
|
|
1071
1081
|
exclude: [...DEFAULT_SOURCE_EXCLUDES]
|
|
1072
1082
|
};
|
|
1073
1083
|
}
|
|
1084
|
+
async function writeStatelyProjectConfig(configPath, config) {
|
|
1085
|
+
const serialized = { ...config };
|
|
1086
|
+
if (!config.projectId) {
|
|
1087
|
+
delete serialized.projectId;
|
|
1088
|
+
delete serialized.studioUrl;
|
|
1089
|
+
}
|
|
1090
|
+
await fsPromises.writeFile(configPath, `${JSON.stringify(serialized, null, 2)}\n`, "utf8");
|
|
1091
|
+
}
|
|
1074
1092
|
function normalizeProjectConfig(config) {
|
|
1075
1093
|
const firstSource = config.sources?.[0];
|
|
1076
1094
|
return {
|
|
@@ -1103,7 +1121,7 @@ async function readStatelyProjectConfig(options = {}) {
|
|
|
1103
1121
|
const config = normalizeProjectConfig(parsed);
|
|
1104
1122
|
const rewriteStatus = getConfigRewriteStatus(parsed);
|
|
1105
1123
|
if (options.rewriteIfNeeded && rewriteStatus.reason === "legacy-sources") {
|
|
1106
|
-
await
|
|
1124
|
+
await writeStatelyProjectConfig(configPath, config);
|
|
1107
1125
|
return {
|
|
1108
1126
|
config,
|
|
1109
1127
|
configPath,
|
|
@@ -1351,7 +1369,61 @@ function getEnvCredential() {
|
|
|
1351
1369
|
variable: "NEXT_PUBLIC_STATELY_API_KEY"
|
|
1352
1370
|
};
|
|
1353
1371
|
}
|
|
1354
|
-
|
|
1372
|
+
const OAUTH_EXPIRY_SKEW_MS = 6e4;
|
|
1373
|
+
function decodeJwtPayload(token) {
|
|
1374
|
+
const [, payload] = token.split(".");
|
|
1375
|
+
if (!payload) return;
|
|
1376
|
+
try {
|
|
1377
|
+
const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
1378
|
+
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
1379
|
+
} catch {
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
function getOAuthExpiresAt(credential) {
|
|
1384
|
+
if (credential.expiresAt !== void 0) return credential.expiresAt;
|
|
1385
|
+
const exp = decodeJwtPayload(credential.accessToken)?.exp;
|
|
1386
|
+
return typeof exp === "number" ? exp * 1e3 : void 0;
|
|
1387
|
+
}
|
|
1388
|
+
function isTerminalOAuthRefreshError(error) {
|
|
1389
|
+
return error instanceof Error && /Token refresh failed with HTTP (?:400|401|403)\b/.test(error.message);
|
|
1390
|
+
}
|
|
1391
|
+
async function invalidateExpiredOAuthCredential() {
|
|
1392
|
+
await deleteStoredApiKey();
|
|
1393
|
+
throw new Error("Your Stately session expired. Run `npx statelyai login` to continue.");
|
|
1394
|
+
}
|
|
1395
|
+
async function refreshStoredOAuthCredential(credential, options) {
|
|
1396
|
+
if (!credential.refreshToken) return invalidateExpiredOAuthCredential();
|
|
1397
|
+
const claims = decodeJwtPayload(credential.accessToken);
|
|
1398
|
+
const clientId = credential.clientId ?? (typeof claims?.client_id === "string" ? claims.client_id : void 0) ?? getConfiguredOauthClientId();
|
|
1399
|
+
if (!clientId) return invalidateExpiredOAuthCredential();
|
|
1400
|
+
let tokenEndpoint = credential.tokenEndpoint;
|
|
1401
|
+
try {
|
|
1402
|
+
if (!tokenEndpoint) tokenEndpoint = (await discoverAuthorizationServer(typeof claims?.iss === "string" ? claims.iss : getDefaultOAuthIssuer(), { fetch: options.fetch })).token_endpoint;
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
throw new Error("Could not refresh your expired Stately session. Retry, or run `npx statelyai login`.", { cause: error });
|
|
1405
|
+
}
|
|
1406
|
+
if (!tokenEndpoint) return invalidateExpiredOAuthCredential();
|
|
1407
|
+
try {
|
|
1408
|
+
const refreshedCredential = createOAuthCredential(await refreshToken({
|
|
1409
|
+
clientId,
|
|
1410
|
+
fetch: options.fetch,
|
|
1411
|
+
refreshToken: credential.refreshToken,
|
|
1412
|
+
tokenEndpoint
|
|
1413
|
+
}), {
|
|
1414
|
+
clientId,
|
|
1415
|
+
fallbackRefreshToken: credential.refreshToken,
|
|
1416
|
+
now: options.now,
|
|
1417
|
+
tokenEndpoint
|
|
1418
|
+
});
|
|
1419
|
+
await setStoredCredential(refreshedCredential);
|
|
1420
|
+
return refreshedCredential;
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
if (isTerminalOAuthRefreshError(error)) return invalidateExpiredOAuthCredential();
|
|
1423
|
+
throw new Error("Could not refresh your expired Stately session. Retry, or run `npx statelyai login`.", { cause: error });
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
async function resolveApiKey(options = {}) {
|
|
1355
1427
|
const envApiKey = getEnvApiKey();
|
|
1356
1428
|
if (envApiKey) return {
|
|
1357
1429
|
apiKey: envApiKey.apiKey,
|
|
@@ -1359,11 +1431,19 @@ async function resolveApiKey() {
|
|
|
1359
1431
|
detail: envApiKey.variable
|
|
1360
1432
|
};
|
|
1361
1433
|
const storedCredential = await getStoredCredential();
|
|
1362
|
-
if (storedCredential?.credential)
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1434
|
+
if (storedCredential?.credential) {
|
|
1435
|
+
let credential = storedCredential.credential;
|
|
1436
|
+
const now = options.now ?? Date.now();
|
|
1437
|
+
if (credential.type === "oauth" && (getOAuthExpiresAt(credential) ?? Infinity) <= now + OAUTH_EXPIRY_SKEW_MS) credential = await refreshStoredOAuthCredential(credential, {
|
|
1438
|
+
...options,
|
|
1439
|
+
now
|
|
1440
|
+
});
|
|
1441
|
+
return {
|
|
1442
|
+
apiKey: credential.type === "oauth" ? credential.accessToken : credential.token,
|
|
1443
|
+
source: "stored",
|
|
1444
|
+
detail: describeCredentialBackend(storedCredential.backend, storedCredential.location)
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1367
1447
|
return { source: "missing" };
|
|
1368
1448
|
}
|
|
1369
1449
|
function getDefaultBaseUrl() {
|
|
@@ -1530,14 +1610,17 @@ function parseOAuthCallback(value) {
|
|
|
1530
1610
|
return { code: value };
|
|
1531
1611
|
}
|
|
1532
1612
|
}
|
|
1533
|
-
function createOAuthCredential(tokenResponse,
|
|
1613
|
+
function createOAuthCredential(tokenResponse, options = {}) {
|
|
1534
1614
|
if (!tokenResponse.access_token) throw new Error("Token response did not include an access_token.");
|
|
1615
|
+
const now = options.now ?? Date.now();
|
|
1535
1616
|
const expiresAt = typeof tokenResponse.expires_at === "number" ? tokenResponse.expires_at * 1e3 : typeof tokenResponse.expires_in === "number" ? now + tokenResponse.expires_in * 1e3 : void 0;
|
|
1536
1617
|
return {
|
|
1537
1618
|
type: "oauth",
|
|
1538
1619
|
accessToken: tokenResponse.access_token,
|
|
1539
|
-
...tokenResponse.refresh_token ? { refreshToken: tokenResponse.refresh_token } : {},
|
|
1540
|
-
...expiresAt ? { expiresAt } : {}
|
|
1620
|
+
...tokenResponse.refresh_token ?? options.fallbackRefreshToken ? { refreshToken: tokenResponse.refresh_token ?? options.fallbackRefreshToken } : {},
|
|
1621
|
+
...expiresAt ? { expiresAt } : {},
|
|
1622
|
+
...options.clientId ? { clientId: options.clientId } : {},
|
|
1623
|
+
...options.tokenEndpoint ? { tokenEndpoint: options.tokenEndpoint } : {}
|
|
1541
1624
|
};
|
|
1542
1625
|
}
|
|
1543
1626
|
async function buildOAuthLoginStart(options) {
|
|
@@ -1597,7 +1680,10 @@ async function runOAuthBrowserLogin(options) {
|
|
|
1597
1680
|
codeVerifier,
|
|
1598
1681
|
redirectUri: callbackServer.redirectUri,
|
|
1599
1682
|
clientId: discovery.clientId
|
|
1600
|
-
})
|
|
1683
|
+
}), {
|
|
1684
|
+
clientId: discovery.clientId,
|
|
1685
|
+
tokenEndpoint: discovery.tokenEndpoint
|
|
1686
|
+
}));
|
|
1601
1687
|
} finally {
|
|
1602
1688
|
await callbackServer.close();
|
|
1603
1689
|
}
|
|
@@ -1808,16 +1894,38 @@ async function initProject(options) {
|
|
|
1808
1894
|
configPath: options.configPath,
|
|
1809
1895
|
rewriteIfNeeded: true
|
|
1810
1896
|
}).catch(() => void 0) : void 0;
|
|
1897
|
+
if (existingConfig) {
|
|
1898
|
+
if (options.local) return {
|
|
1899
|
+
config: existingConfig.config,
|
|
1900
|
+
configPath: existingConfig.configPath,
|
|
1901
|
+
reusedExistingConfig: true
|
|
1902
|
+
};
|
|
1903
|
+
if (existingConfig.config.projectId) {
|
|
1904
|
+
const client = options.client ?? createStatelyClient({
|
|
1905
|
+
apiKey: options.apiKey,
|
|
1906
|
+
baseUrl: studioUrl
|
|
1907
|
+
});
|
|
1908
|
+
return {
|
|
1909
|
+
config: existingConfig.config,
|
|
1910
|
+
configPath: existingConfig.configPath,
|
|
1911
|
+
project: await client.projects.get(existingConfig.config.projectId),
|
|
1912
|
+
reusedExistingConfig: true
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
if (options.local) {
|
|
1917
|
+
const config = createStatelyProjectConfig({ defaultXStateVersion });
|
|
1918
|
+
await writeStatelyProjectConfig(configPath, config);
|
|
1919
|
+
return {
|
|
1920
|
+
config,
|
|
1921
|
+
configPath,
|
|
1922
|
+
reusedExistingConfig: false
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1811
1925
|
const client = options.client ?? createStatelyClient({
|
|
1812
1926
|
apiKey: options.apiKey,
|
|
1813
1927
|
baseUrl: studioUrl
|
|
1814
1928
|
});
|
|
1815
|
-
if (existingConfig) return {
|
|
1816
|
-
config: existingConfig.config,
|
|
1817
|
-
configPath: existingConfig.configPath,
|
|
1818
|
-
project: await client.projects.get(existingConfig.config.projectId),
|
|
1819
|
-
reusedExistingConfig: true
|
|
1820
|
-
};
|
|
1821
1929
|
const inferredRepo = options.project?.repo ?? await inferConnectedRepoFromCwd(cwd);
|
|
1822
1930
|
const projectInput = {
|
|
1823
1931
|
name: options.project?.name ?? inferInitProjectName(cwd, inferredRepo),
|
|
@@ -1827,12 +1935,16 @@ async function initProject(options) {
|
|
|
1827
1935
|
...inferredRepo ? { repo: inferredRepo } : {}
|
|
1828
1936
|
};
|
|
1829
1937
|
const project = await client.projects.ensure(projectInput);
|
|
1830
|
-
const config =
|
|
1938
|
+
const config = existingConfig ? {
|
|
1939
|
+
...existingConfig.config,
|
|
1940
|
+
projectId: project.projectId,
|
|
1941
|
+
studioUrl
|
|
1942
|
+
} : createStatelyProjectConfig({
|
|
1831
1943
|
projectId: project.projectId,
|
|
1832
1944
|
studioUrl,
|
|
1833
1945
|
defaultXStateVersion
|
|
1834
1946
|
});
|
|
1835
|
-
await
|
|
1947
|
+
await writeStatelyProjectConfig(configPath, config);
|
|
1836
1948
|
return {
|
|
1837
1949
|
config,
|
|
1838
1950
|
configPath,
|
|
@@ -1840,15 +1952,66 @@ async function initProject(options) {
|
|
|
1840
1952
|
reusedExistingConfig: false
|
|
1841
1953
|
};
|
|
1842
1954
|
}
|
|
1955
|
+
function inferDiscoveredMachineName(options) {
|
|
1956
|
+
const declaration = options.source.slice(options.attachStart, options.machineStart);
|
|
1957
|
+
const variableName = [...declaration.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/g)].at(-1)?.[1];
|
|
1958
|
+
if (variableName) return variableName;
|
|
1959
|
+
if (/\bexport\s+default\b/.test(declaration)) return "default";
|
|
1960
|
+
const configuredId = options.source.slice(options.machineStart, options.machineStart + 500).match(/\bid\s*:\s*['"]([^'"]+)['"]/)?.[1];
|
|
1961
|
+
if (configuredId) return configuredId;
|
|
1962
|
+
return `${path.basename(options.relativePath, path.extname(options.relativePath))}#${options.machineIndex + 1}`;
|
|
1963
|
+
}
|
|
1964
|
+
function discoverMachinesInSource(options) {
|
|
1965
|
+
if (!looksLikeXStateMachineSource(options.source)) return [];
|
|
1966
|
+
return findStatelyPragmaAttachments(options.source, options.filePath).map((attachment, machineIndex) => ({
|
|
1967
|
+
filePath: options.filePath,
|
|
1968
|
+
...attachment.pragma?.id ? { id: attachment.pragma.id } : {},
|
|
1969
|
+
line: options.source.slice(0, attachment.machineStart).split(/\r?\n/).length,
|
|
1970
|
+
machineIndex,
|
|
1971
|
+
name: inferDiscoveredMachineName({
|
|
1972
|
+
attachStart: attachment.attachStart,
|
|
1973
|
+
machineIndex,
|
|
1974
|
+
machineStart: attachment.machineStart,
|
|
1975
|
+
relativePath: options.relativePath,
|
|
1976
|
+
source: options.source
|
|
1977
|
+
}),
|
|
1978
|
+
relativePath: options.relativePath
|
|
1979
|
+
}));
|
|
1980
|
+
}
|
|
1981
|
+
async function discoverMachinesInFiles(files) {
|
|
1982
|
+
const machines = [];
|
|
1983
|
+
for (const file of files) {
|
|
1984
|
+
const source = await fsPromises.readFile(file.filePath, "utf8");
|
|
1985
|
+
machines.push(...discoverMachinesInSource({
|
|
1986
|
+
filePath: file.filePath,
|
|
1987
|
+
relativePath: file.relativePath,
|
|
1988
|
+
source
|
|
1989
|
+
}));
|
|
1990
|
+
}
|
|
1991
|
+
return machines;
|
|
1992
|
+
}
|
|
1993
|
+
async function scanProjectMachines(options = {}) {
|
|
1994
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1995
|
+
return discoverMachinesInFiles((await discoverCodeSourceFiles({ cwd })).map((relativePath) => ({
|
|
1996
|
+
filePath: path.join(cwd, relativePath),
|
|
1997
|
+
relativePath,
|
|
1998
|
+
source: {
|
|
1999
|
+
include: [relativePath],
|
|
2000
|
+
exclude: [],
|
|
2001
|
+
format: "xstate",
|
|
2002
|
+
xstateVersion: 5
|
|
2003
|
+
}
|
|
2004
|
+
})));
|
|
2005
|
+
}
|
|
2006
|
+
function formatDiscoveredMachines(machines) {
|
|
2007
|
+
const fileCount = new Set(machines.map((machine) => machine.relativePath)).size;
|
|
2008
|
+
const lines = [`Found ${machines.length} ${pluralize(machines.length, "machine")} in ${fileCount} ${pluralize(fileCount, "file")}:`];
|
|
2009
|
+
for (const machine of machines) lines.push(` ${machine.name} ${machine.relativePath}:${machine.line}${machine.id ? ` ${machine.id}` : ""}`);
|
|
2010
|
+
return lines.join("\n");
|
|
2011
|
+
}
|
|
1843
2012
|
async function scanProjectSources(options) {
|
|
1844
2013
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1845
|
-
|
|
1846
|
-
const machineRelativePaths = [];
|
|
1847
|
-
for (const relativePath of candidateRelativePaths) {
|
|
1848
|
-
const filePath = path.join(cwd, relativePath);
|
|
1849
|
-
if (looksLikeXStateMachineSource(await fsPromises.readFile(filePath, "utf8"))) machineRelativePaths.push(relativePath);
|
|
1850
|
-
}
|
|
1851
|
-
return suggestStatelySourceConfigs(machineRelativePaths);
|
|
2014
|
+
return suggestStatelySourceConfigs([...new Set((await scanProjectMachines({ cwd })).map((machine) => machine.relativePath))]);
|
|
1852
2015
|
}
|
|
1853
2016
|
function looksLikeXStateMachineSource(source) {
|
|
1854
2017
|
const hasXStateImport = /from\s+['"]xstate(?:\/[^'"]+)?['"]/.test(source) || /require\(\s*['"]xstate(?:\/[^'"]+)?['"]\s*\)/.test(source);
|
|
@@ -1859,7 +2022,12 @@ async function classifyPushCandidates(files) {
|
|
|
1859
2022
|
const machineFiles = [];
|
|
1860
2023
|
const nonMachineFiles = [];
|
|
1861
2024
|
for (const file of files) {
|
|
1862
|
-
|
|
2025
|
+
const contents = await fsPromises.readFile(file.filePath, "utf8");
|
|
2026
|
+
if (discoverMachinesInSource({
|
|
2027
|
+
filePath: file.filePath,
|
|
2028
|
+
relativePath: file.relativePath,
|
|
2029
|
+
source: contents
|
|
2030
|
+
}).length > 0) {
|
|
1863
2031
|
machineFiles.push(file);
|
|
1864
2032
|
continue;
|
|
1865
2033
|
}
|
|
@@ -1879,6 +2047,7 @@ async function resolveConfiguredProject(options) {
|
|
|
1879
2047
|
configPath: options.configPath,
|
|
1880
2048
|
rewriteIfNeeded: options.rewriteIfNeeded ?? true
|
|
1881
2049
|
});
|
|
2050
|
+
if (!config.projectId) throw new Error("This is a local-only project. Run `statelyai init` to connect it to Stately Studio.");
|
|
1882
2051
|
const studioUrl = options.baseUrl ?? config.studioUrl;
|
|
1883
2052
|
return {
|
|
1884
2053
|
client: options.client ?? createStatelyClient({
|
|
@@ -1950,34 +2119,68 @@ function formatCredentialStatus(status) {
|
|
|
1950
2119
|
return status.type === "none" ? `Auth: none (${status.detail})` : `Auth: ${status.type} from ${status.source} (${status.detail})`;
|
|
1951
2120
|
}
|
|
1952
2121
|
async function resolveProjectStatus(options = {}) {
|
|
1953
|
-
const {
|
|
1954
|
-
|
|
2122
|
+
const { config, configPath, rootDir } = await readStatelyProjectConfig({
|
|
2123
|
+
cwd: options.cwd,
|
|
2124
|
+
configPath: options.configPath,
|
|
1955
2125
|
rewriteIfNeeded: false
|
|
1956
2126
|
});
|
|
1957
|
-
const
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2127
|
+
const localMachines = await discoverMachinesInFiles((await discoverStatelySourceFiles({
|
|
2128
|
+
cwd: rootDir,
|
|
2129
|
+
config
|
|
2130
|
+
})).filter(supportsMachineDiscovery));
|
|
2131
|
+
const studioUrl = options.baseUrl ?? config.studioUrl;
|
|
2132
|
+
if (!config.projectId) {
|
|
2133
|
+
const machines = localMachines.map((machine) => ({
|
|
2134
|
+
line: machine.line,
|
|
2135
|
+
machineIndex: machine.machineIndex,
|
|
2136
|
+
name: machine.name,
|
|
2137
|
+
path: machine.relativePath,
|
|
2138
|
+
status: "local-only"
|
|
2139
|
+
}));
|
|
2140
|
+
return {
|
|
2141
|
+
auth: {
|
|
2142
|
+
detail: "not required for local projects",
|
|
2143
|
+
source: "none",
|
|
2144
|
+
type: "none"
|
|
2145
|
+
},
|
|
2146
|
+
configPath,
|
|
2147
|
+
counts: {
|
|
2148
|
+
linked: 0,
|
|
2149
|
+
"local-only": machines.length,
|
|
2150
|
+
"missing-remote": 0,
|
|
2151
|
+
"remote-only": 0
|
|
2152
|
+
},
|
|
2153
|
+
machines,
|
|
2154
|
+
mode: "local"
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
const apiKey = options.apiKey ?? (options.client ? void 0 : (await resolveApiKey()).apiKey);
|
|
2158
|
+
const project = await (options.client ?? createStatelyClient({
|
|
2159
|
+
apiKey,
|
|
2160
|
+
baseUrl: studioUrl
|
|
2161
|
+
})).projects.get(config.projectId);
|
|
1961
2162
|
const remoteById = new Map(project.machines.flatMap((machine) => {
|
|
1962
2163
|
const id = getProjectMachineId(machine);
|
|
1963
2164
|
return id ? [[id, machine]] : [];
|
|
1964
2165
|
}));
|
|
1965
|
-
const linkedIds = new Set(
|
|
1966
|
-
const
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
name:
|
|
1971
|
-
path:
|
|
2166
|
+
const linkedIds = new Set(localMachines.flatMap((machine) => machine.id ? [machine.id] : []));
|
|
2167
|
+
const machines = localMachines.map((machine) => {
|
|
2168
|
+
if (!machine.id) return {
|
|
2169
|
+
line: machine.line,
|
|
2170
|
+
machineIndex: machine.machineIndex,
|
|
2171
|
+
name: machine.name,
|
|
2172
|
+
path: machine.relativePath,
|
|
1972
2173
|
status: "local-only"
|
|
1973
2174
|
};
|
|
1974
|
-
const remote = remoteById.get(
|
|
2175
|
+
const remote = remoteById.get(machine.id);
|
|
1975
2176
|
return {
|
|
1976
|
-
id:
|
|
1977
|
-
|
|
1978
|
-
|
|
2177
|
+
id: machine.id,
|
|
2178
|
+
line: machine.line,
|
|
2179
|
+
machineIndex: machine.machineIndex,
|
|
2180
|
+
name: remote?.name || machine.name,
|
|
2181
|
+
path: machine.relativePath,
|
|
1979
2182
|
status: remote ? "linked" : "missing-remote",
|
|
1980
|
-
url: buildMachineEditorUrl(studioUrl, config.projectId,
|
|
2183
|
+
url: buildMachineEditorUrl(studioUrl, config.projectId, machine.id)
|
|
1981
2184
|
};
|
|
1982
2185
|
});
|
|
1983
2186
|
for (const remote of project.machines) {
|
|
@@ -2001,6 +2204,7 @@ async function resolveProjectStatus(options = {}) {
|
|
|
2001
2204
|
"remote-only": machines.filter((machine) => machine.status === "remote-only").length
|
|
2002
2205
|
},
|
|
2003
2206
|
machines,
|
|
2207
|
+
mode: "studio",
|
|
2004
2208
|
project: {
|
|
2005
2209
|
id: config.projectId,
|
|
2006
2210
|
...project.name ? { name: project.name } : {},
|
|
@@ -2275,10 +2479,10 @@ var PushCommand = class PushCommand extends Command {
|
|
|
2275
2479
|
if (flags["dry-run"]) {
|
|
2276
2480
|
const wouldLink = [];
|
|
2277
2481
|
const wouldUpdate = [];
|
|
2278
|
-
for (const
|
|
2279
|
-
const
|
|
2280
|
-
if (
|
|
2281
|
-
else wouldLink.push(
|
|
2482
|
+
for (const machine of await discoverMachinesInFiles(machineFiles)) {
|
|
2483
|
+
const location = `${machine.relativePath}:${machine.line}`;
|
|
2484
|
+
if (machine.id) wouldUpdate.push(`${machine.name} ${location} ${formatSecondaryText(machine.id)}`);
|
|
2485
|
+
else wouldLink.push(`${machine.name} ${location}`);
|
|
2282
2486
|
}
|
|
2283
2487
|
if (wouldLink.length > 0) this.log(`${formatSectionLabel("Would link:")}\n${wouldLink.join("\n")}`);
|
|
2284
2488
|
if (wouldUpdate.length > 0) this.log(`${formatSectionLabel("Would update:")}\n${wouldUpdate.join("\n")}`);
|
|
@@ -2381,8 +2585,8 @@ var OpenCommand = class OpenCommand extends Command {
|
|
|
2381
2585
|
};
|
|
2382
2586
|
var InitCommand = class InitCommand extends Command {
|
|
2383
2587
|
static enableJsonFlag = false;
|
|
2384
|
-
static summary = "Create a
|
|
2385
|
-
static description = "Creates or reuses a remote Studio project for the current working directory
|
|
2588
|
+
static summary = "Create a local or Studio-connected statelyai.json.";
|
|
2589
|
+
static description = "Creates a local configuration or reuses a remote Studio project for the current working directory.";
|
|
2386
2590
|
static flags = {
|
|
2387
2591
|
help: Flags.help({ char: "h" }),
|
|
2388
2592
|
"base-url": Flags.string({ description: "Base URL for Stately Studio or a self-hosted deployment" }),
|
|
@@ -2400,6 +2604,10 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2400
2604
|
description: "Overwrite an existing statelyai.json file",
|
|
2401
2605
|
default: false
|
|
2402
2606
|
}),
|
|
2607
|
+
local: Flags.boolean({
|
|
2608
|
+
description: "Create a local-only config without authentication or Studio access",
|
|
2609
|
+
default: false
|
|
2610
|
+
}),
|
|
2403
2611
|
scan: Flags.boolean({
|
|
2404
2612
|
description: "Scan the repo for machine-bearing files and suggest source globs to save into statelyai.json",
|
|
2405
2613
|
default: false
|
|
@@ -2407,21 +2615,25 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2407
2615
|
};
|
|
2408
2616
|
async run() {
|
|
2409
2617
|
const { flags } = await this.parse(InitCommand);
|
|
2410
|
-
const resolvedApiKey = await resolveApiKey();
|
|
2411
|
-
this.log("Creating or reusing remote project...");
|
|
2618
|
+
const resolvedApiKey = flags.local ? { source: "missing" } : await resolveApiKey();
|
|
2619
|
+
this.log(flags.local ? "Creating local project config..." : "Creating or reusing remote project...");
|
|
2412
2620
|
const result = await initProject({
|
|
2413
2621
|
apiKey: resolvedApiKey.apiKey,
|
|
2414
2622
|
baseUrl: flags["base-url"],
|
|
2415
2623
|
force: flags.force,
|
|
2624
|
+
local: flags.local,
|
|
2416
2625
|
project: {
|
|
2417
2626
|
...flags.name ? { name: flags.name } : {},
|
|
2418
2627
|
visibility: flags.visibility
|
|
2419
2628
|
}
|
|
2420
2629
|
});
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2630
|
+
const initMessage = result.project ? (() => {
|
|
2631
|
+
const projectEditorUrl = buildProjectEditorUrl(result.config.studioUrl, result.project.projectId);
|
|
2632
|
+
return result.reusedExistingConfig ? `Using existing statelyai.json at ${result.configPath} for project ${result.project.projectId}.\nEditor: ${projectEditorUrl}` : `Initialized project ${result.project.projectId} and wrote ${result.configPath}.\nEditor: ${projectEditorUrl}`;
|
|
2633
|
+
})() : result.reusedExistingConfig ? `Using existing local statelyai.json at ${result.configPath}.` : `Initialized local project and wrote ${result.configPath}.`;
|
|
2423
2634
|
if (flags.scan) {
|
|
2424
2635
|
this.log("Scanning local source files...");
|
|
2636
|
+
const machines = await scanProjectMachines({ cwd: path.dirname(result.configPath) });
|
|
2425
2637
|
const suggestions = await scanProjectSources({
|
|
2426
2638
|
cwd: path.dirname(result.configPath),
|
|
2427
2639
|
defaultXStateVersion: result.config.defaultXStateVersion
|
|
@@ -2431,6 +2643,7 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2431
2643
|
return;
|
|
2432
2644
|
}
|
|
2433
2645
|
this.log(initMessage);
|
|
2646
|
+
this.log(formatDiscoveredMachines(machines));
|
|
2434
2647
|
this.log(`Suggested source globs:\n${suggestions.map((source) => `- ${source.include.join(", ")}`).join("\n")}`);
|
|
2435
2648
|
if (await promptYesNo("Save these source globs to statelyai.json?", true)) {
|
|
2436
2649
|
const inferredNewMachineDir = inferNewMachineDirFromSuggestions(suggestions);
|
|
@@ -2440,7 +2653,7 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2440
2653
|
exclude: [...new Set(suggestions.flatMap((suggestion) => suggestion.exclude ?? []))],
|
|
2441
2654
|
...inferredNewMachineDir ? { newMachinesDir: inferredNewMachineDir } : {}
|
|
2442
2655
|
};
|
|
2443
|
-
await
|
|
2656
|
+
await writeStatelyProjectConfig(result.configPath, nextConfig);
|
|
2444
2657
|
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.");
|
|
2445
2658
|
} else this.log("Left statelyai.json unchanged. Edit it before running statelyai push.");
|
|
2446
2659
|
return;
|
|
@@ -2448,6 +2661,23 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2448
2661
|
this.log(`${initMessage}\nNo include globs configured yet. Edit statelyai.json before running statelyai push, or rerun with --scan.`);
|
|
2449
2662
|
}
|
|
2450
2663
|
};
|
|
2664
|
+
var ScanCommand = class ScanCommand extends Command {
|
|
2665
|
+
static enableJsonFlag = false;
|
|
2666
|
+
static summary = "List local state machines without authentication or config.";
|
|
2667
|
+
static description = "Scans source files in the current repository and reports every discovered XState machine.";
|
|
2668
|
+
static flags = {
|
|
2669
|
+
help: Flags.help({ char: "h" }),
|
|
2670
|
+
json: Flags.boolean({
|
|
2671
|
+
description: "Print machine-readable JSON",
|
|
2672
|
+
default: false
|
|
2673
|
+
})
|
|
2674
|
+
};
|
|
2675
|
+
async run() {
|
|
2676
|
+
const { flags } = await this.parse(ScanCommand);
|
|
2677
|
+
const machines = await scanProjectMachines();
|
|
2678
|
+
this.log(flags.json ? JSON.stringify({ machines }, null, 2) : formatDiscoveredMachines(machines));
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2451
2681
|
var LoginCommand = class LoginCommand extends Command {
|
|
2452
2682
|
static enableJsonFlag = false;
|
|
2453
2683
|
static summary = "Store Stately credentials for future CLI use.";
|
|
@@ -2495,16 +2725,20 @@ var LogoutCommand = class extends Command {
|
|
|
2495
2725
|
function formatProjectStatus(result) {
|
|
2496
2726
|
const lines = [
|
|
2497
2727
|
formatCredentialStatus(result.auth),
|
|
2498
|
-
`
|
|
2499
|
-
`Studio: ${result.project.url}
|
|
2728
|
+
`Mode: ${result.mode}`,
|
|
2729
|
+
...result.project ? [`Project: ${result.project.name ? `${result.project.name} ` : ""}(${result.project.id})`, `Studio: ${result.project.url}`] : [],
|
|
2500
2730
|
`Config: ${result.configPath}`,
|
|
2501
2731
|
`Machines: ${result.machines.length} (${result.counts.linked} linked, ${result.counts["local-only"]} local-only, ${result.counts["remote-only"]} remote-only, ${result.counts["missing-remote"]} missing-remote)`
|
|
2502
2732
|
];
|
|
2503
2733
|
for (const machine of result.machines) {
|
|
2504
|
-
const details = [machine.
|
|
2734
|
+
const details = [machine.path ? `${machine.path}${machine.line ? `:${machine.line}` : ""}` : void 0, machine.id].filter(Boolean).join(" ");
|
|
2505
2735
|
lines.push(` [${machine.status}] ${machine.name}${details ? ` ${details}` : ""}`);
|
|
2506
2736
|
}
|
|
2507
2737
|
const next = [];
|
|
2738
|
+
if (result.mode === "local") {
|
|
2739
|
+
lines.push("Next: statelyai init (connect to Studio)");
|
|
2740
|
+
return lines.join("\n");
|
|
2741
|
+
}
|
|
2508
2742
|
if (result.counts["local-only"] > 0 || result.counts["missing-remote"] > 0) next.push("statelyai push");
|
|
2509
2743
|
if (result.counts["remote-only"] > 0) next.push("statelyai pull");
|
|
2510
2744
|
if (next.length > 0) lines.push(`Next: ${next.join(", ")}`);
|
|
@@ -2533,9 +2767,7 @@ var StatusCommand = class StatusCommand extends Command {
|
|
|
2533
2767
|
this.log(flags.json ? JSON.stringify({ auth }, null, 2) : formatCredentialStatus(auth));
|
|
2534
2768
|
return;
|
|
2535
2769
|
}
|
|
2536
|
-
const apiKey = (await resolveApiKey()).apiKey;
|
|
2537
2770
|
const result = await resolveProjectStatus({
|
|
2538
|
-
apiKey,
|
|
2539
2771
|
baseUrl: flags["base-url"],
|
|
2540
2772
|
configPath: flags.config
|
|
2541
2773
|
});
|
|
@@ -2558,6 +2790,7 @@ const COMMANDS = {
|
|
|
2558
2790
|
pull: PullCommand,
|
|
2559
2791
|
push: PushCommand,
|
|
2560
2792
|
open: OpenCommand,
|
|
2793
|
+
scan: ScanCommand,
|
|
2561
2794
|
status: StatusCommand,
|
|
2562
2795
|
init: InitCommand,
|
|
2563
2796
|
login: LoginCommand,
|
|
@@ -2586,4 +2819,4 @@ function isDirectExecution() {
|
|
|
2586
2819
|
if (isDirectExecution()) run$1();
|
|
2587
2820
|
|
|
2588
2821
|
//#endregion
|
|
2589
|
-
export {
|
|
2822
|
+
export { scanProjectMachines as C, run$1 as S, createStatelyProjectConfig as T, isFileGitDirty as _, discoverMachinesInSource as a, resolveCredentialStatus as b, formatDiscoveredMachines as c, formatProjectStatus as d, formatStoredCredentialSource as f, initProject as g, inferInitProjectName as h, discoverLinkedPullTargets as i, formatMissingNewMachineDirMessage as l, getEnvCredential as m, buildOAuthLoginStart as n, discoverOAuthLogin as o, getEnvApiKey as p, classifyPushCandidates as r, discoverRemoteProjectMachineTargets as s, COMMANDS as t, formatPlanSummary as u, resolveApiKey as v, scanProjectSources as w, resolveProjectStatus as x, resolveConfiguredPullTargets as y };
|
package/dist/index.d.mts
CHANGED
|
@@ -12,6 +12,8 @@ type StatelyCredential = {
|
|
|
12
12
|
accessToken: string;
|
|
13
13
|
refreshToken?: string;
|
|
14
14
|
expiresAt?: number;
|
|
15
|
+
clientId?: string;
|
|
16
|
+
tokenEndpoint?: string;
|
|
15
17
|
};
|
|
16
18
|
type CredentialBackend = 'file' | 'linux-secret-tool' | 'macos-keychain';
|
|
17
19
|
type StoredCredential = {
|
|
@@ -44,8 +46,8 @@ interface DiscoveredSourceFile {
|
|
|
44
46
|
source: StatelySourceConfig;
|
|
45
47
|
}
|
|
46
48
|
declare function createStatelyProjectConfig(options: {
|
|
47
|
-
projectId
|
|
48
|
-
studioUrl
|
|
49
|
+
projectId?: string;
|
|
50
|
+
studioUrl?: string;
|
|
49
51
|
defaultXStateVersion?: number;
|
|
50
52
|
}): StatelyProjectConfig;
|
|
51
53
|
//#endregion
|
|
@@ -58,18 +60,27 @@ interface InitProjectOptions {
|
|
|
58
60
|
configPath?: string;
|
|
59
61
|
defaultXStateVersion?: number;
|
|
60
62
|
force?: boolean;
|
|
63
|
+
local?: boolean;
|
|
61
64
|
project?: Partial<CreateProjectInput>;
|
|
62
65
|
}
|
|
63
66
|
interface InitProjectResult {
|
|
64
67
|
config: StatelyProjectConfig;
|
|
65
68
|
configPath: string;
|
|
66
|
-
project
|
|
69
|
+
project?: ProjectData;
|
|
67
70
|
reusedExistingConfig?: boolean;
|
|
68
71
|
}
|
|
69
72
|
interface ScanProjectSourcesOptions {
|
|
70
73
|
cwd?: string;
|
|
71
74
|
defaultXStateVersion?: number;
|
|
72
75
|
}
|
|
76
|
+
interface DiscoveredMachine {
|
|
77
|
+
filePath: string;
|
|
78
|
+
id?: string;
|
|
79
|
+
line: number;
|
|
80
|
+
machineIndex: number;
|
|
81
|
+
name: string;
|
|
82
|
+
relativePath: string;
|
|
83
|
+
}
|
|
73
84
|
interface LinkedPullTarget {
|
|
74
85
|
file: DiscoveredSourceFile;
|
|
75
86
|
machineId: string;
|
|
@@ -100,6 +111,8 @@ interface ResolvedConfiguredPullTargets {
|
|
|
100
111
|
type ProjectMachineStatus = 'linked' | 'local-only' | 'missing-remote' | 'remote-only';
|
|
101
112
|
interface ProjectStatusMachine {
|
|
102
113
|
id?: string;
|
|
114
|
+
line?: number;
|
|
115
|
+
machineIndex?: number;
|
|
103
116
|
name: string;
|
|
104
117
|
path?: string;
|
|
105
118
|
status: ProjectMachineStatus;
|
|
@@ -110,17 +123,22 @@ interface CredentialStatus {
|
|
|
110
123
|
source: 'environment' | 'none' | 'stored';
|
|
111
124
|
type: 'api_key' | 'none' | 'oauth';
|
|
112
125
|
}
|
|
126
|
+
interface ResolveApiKeyOptions {
|
|
127
|
+
fetch?: typeof fetch;
|
|
128
|
+
now?: number;
|
|
129
|
+
}
|
|
113
130
|
interface ProjectStatusResult {
|
|
114
131
|
auth: CredentialStatus;
|
|
115
132
|
configPath: string;
|
|
116
133
|
counts: Record<ProjectMachineStatus, number>;
|
|
117
134
|
machines: ProjectStatusMachine[];
|
|
118
|
-
|
|
135
|
+
mode: 'local' | 'studio';
|
|
136
|
+
project?: {
|
|
119
137
|
id: string;
|
|
120
138
|
name?: string;
|
|
121
139
|
url: string;
|
|
122
140
|
};
|
|
123
|
-
studioUrl
|
|
141
|
+
studioUrl?: string;
|
|
124
142
|
}
|
|
125
143
|
type ApiKeyResolution = {
|
|
126
144
|
apiKey: string;
|
|
@@ -139,7 +157,7 @@ declare function getEnvCredential(): {
|
|
|
139
157
|
credential: StatelyCredential;
|
|
140
158
|
variable: 'NEXT_PUBLIC_STATELY_API_KEY' | 'STATELY_API_KEY' | 'STATELY_ACCESS_TOKEN';
|
|
141
159
|
} | undefined;
|
|
142
|
-
declare function resolveApiKey(): Promise<ApiKeyResolution>;
|
|
160
|
+
declare function resolveApiKey(options?: ResolveApiKeyOptions): Promise<ApiKeyResolution>;
|
|
143
161
|
interface OAuthLoginDiscovery {
|
|
144
162
|
authorizationServer: OAuthAuthorizationServerMetadata;
|
|
145
163
|
authorizationEndpoint: string;
|
|
@@ -182,6 +200,15 @@ declare function discoverRemoteProjectMachineTargets(options: {
|
|
|
182
200
|
client?: Pick<StudioClient, 'machines'>;
|
|
183
201
|
}): Promise<RemoteProjectMachineTarget[]>;
|
|
184
202
|
declare function initProject(options: InitProjectOptions): Promise<InitProjectResult>;
|
|
203
|
+
declare function discoverMachinesInSource(options: {
|
|
204
|
+
filePath: string;
|
|
205
|
+
relativePath: string;
|
|
206
|
+
source: string;
|
|
207
|
+
}): DiscoveredMachine[];
|
|
208
|
+
declare function scanProjectMachines(options?: {
|
|
209
|
+
cwd?: string;
|
|
210
|
+
}): Promise<DiscoveredMachine[]>;
|
|
211
|
+
declare function formatDiscoveredMachines(machines: DiscoveredMachine[]): string;
|
|
185
212
|
declare function scanProjectSources(options: ScanProjectSourcesOptions): Promise<Array<Pick<StatelyProjectConfig, 'include' | 'exclude'>>>;
|
|
186
213
|
declare function classifyPushCandidates(files: DiscoveredSourceFile[]): Promise<PushCandidateClassification>;
|
|
187
214
|
declare function resolveConfiguredPullTargets(options: {
|
|
@@ -295,10 +322,21 @@ declare class InitCommand extends Command {
|
|
|
295
322
|
name: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
296
323
|
visibility: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
|
|
297
324
|
force: _oclif_core_interfaces0.BooleanFlag<boolean>;
|
|
325
|
+
local: _oclif_core_interfaces0.BooleanFlag<boolean>;
|
|
298
326
|
scan: _oclif_core_interfaces0.BooleanFlag<boolean>;
|
|
299
327
|
};
|
|
300
328
|
run(): Promise<void>;
|
|
301
329
|
}
|
|
330
|
+
declare class ScanCommand extends Command {
|
|
331
|
+
static enableJsonFlag: boolean;
|
|
332
|
+
static summary: string;
|
|
333
|
+
static description: string;
|
|
334
|
+
static flags: {
|
|
335
|
+
help: _oclif_core_interfaces0.BooleanFlag<void>;
|
|
336
|
+
json: _oclif_core_interfaces0.BooleanFlag<boolean>;
|
|
337
|
+
};
|
|
338
|
+
run(): Promise<void>;
|
|
339
|
+
}
|
|
302
340
|
declare class LoginCommand extends Command {
|
|
303
341
|
static enableJsonFlag: boolean;
|
|
304
342
|
static summary: string;
|
|
@@ -350,6 +388,7 @@ declare const COMMANDS: {
|
|
|
350
388
|
pull: typeof PullCommand;
|
|
351
389
|
push: typeof PushCommand;
|
|
352
390
|
open: typeof OpenCommand;
|
|
391
|
+
scan: typeof ScanCommand;
|
|
353
392
|
status: typeof StatusCommand;
|
|
354
393
|
init: typeof InitCommand;
|
|
355
394
|
login: typeof LoginCommand;
|
|
@@ -358,4 +397,4 @@ declare const COMMANDS: {
|
|
|
358
397
|
};
|
|
359
398
|
declare function run(argv?: any, entryUrl?: string): Promise<void>;
|
|
360
399
|
//#endregion
|
|
361
|
-
export { ApiKeyResolution, COMMANDS, CredentialStatus, InitProjectOptions, InitProjectResult, LinkedPullTarget, OAuthLoginDiscovery, ProjectMachineStatus, ProjectStatusMachine, ProjectStatusResult, PushCandidateClassification, RemoteProjectMachineTarget, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveProjectStatus, run, scanProjectSources };
|
|
400
|
+
export { ApiKeyResolution, COMMANDS, CredentialStatus, DiscoveredMachine, InitProjectOptions, InitProjectResult, LinkedPullTarget, OAuthLoginDiscovery, ProjectMachineStatus, ProjectStatusMachine, ProjectStatusResult, PushCandidateClassification, RemoteProjectMachineTarget, ResolveApiKeyOptions, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveProjectStatus, run, scanProjectMachines, scanProjectSources };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { S as createStatelyProjectConfig, _ as
|
|
1
|
+
import { C as scanProjectMachines, S as run, T as createStatelyProjectConfig, _ as isFileGitDirty, a as discoverMachinesInSource, b as resolveCredentialStatus, c as formatDiscoveredMachines, d as formatProjectStatus, f as formatStoredCredentialSource, g as initProject, h as inferInitProjectName, i as discoverLinkedPullTargets, l as formatMissingNewMachineDirMessage, m as getEnvCredential, n as buildOAuthLoginStart, o as discoverOAuthLogin, p as getEnvApiKey, r as classifyPushCandidates, s as discoverRemoteProjectMachineTargets, t as COMMANDS, u as formatPlanSummary, v as resolveApiKey, w as scanProjectSources, x as resolveProjectStatus, y as resolveConfiguredPullTargets } from "./cli-DicCA4VI.mjs";
|
|
2
2
|
|
|
3
|
-
export { COMMANDS, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveProjectStatus, run, scanProjectSources };
|
|
3
|
+
export { COMMANDS, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveProjectStatus, run, scanProjectMachines, scanProjectSources };
|