statelyai 0.7.6 → 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-D0TeuFzR.mjs → cli-DicCA4VI.mjs} +291 -66
- 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
|
|
@@ -801,7 +801,9 @@ function parseStoredCredential(raw, backend, location) {
|
|
|
801
801
|
type: "oauth",
|
|
802
802
|
accessToken: credential.accessToken,
|
|
803
803
|
...typeof credential.refreshToken === "string" ? { refreshToken: credential.refreshToken } : {},
|
|
804
|
-
...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 } : {}
|
|
805
807
|
},
|
|
806
808
|
backend,
|
|
807
809
|
location
|
|
@@ -1072,13 +1074,21 @@ function createStatelyProjectConfig(options) {
|
|
|
1072
1074
|
return {
|
|
1073
1075
|
$schema: STATELY_CONFIG_SCHEMA_URL,
|
|
1074
1076
|
version: STATELY_CONFIG_VERSION,
|
|
1075
|
-
projectId: options.projectId,
|
|
1076
|
-
studioUrl: options.studioUrl,
|
|
1077
|
+
projectId: options.projectId ?? "",
|
|
1078
|
+
studioUrl: options.studioUrl ?? "https://stately.ai",
|
|
1077
1079
|
defaultXStateVersion,
|
|
1078
1080
|
include: [],
|
|
1079
1081
|
exclude: [...DEFAULT_SOURCE_EXCLUDES]
|
|
1080
1082
|
};
|
|
1081
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
|
+
}
|
|
1082
1092
|
function normalizeProjectConfig(config) {
|
|
1083
1093
|
const firstSource = config.sources?.[0];
|
|
1084
1094
|
return {
|
|
@@ -1111,7 +1121,7 @@ async function readStatelyProjectConfig(options = {}) {
|
|
|
1111
1121
|
const config = normalizeProjectConfig(parsed);
|
|
1112
1122
|
const rewriteStatus = getConfigRewriteStatus(parsed);
|
|
1113
1123
|
if (options.rewriteIfNeeded && rewriteStatus.reason === "legacy-sources") {
|
|
1114
|
-
await
|
|
1124
|
+
await writeStatelyProjectConfig(configPath, config);
|
|
1115
1125
|
return {
|
|
1116
1126
|
config,
|
|
1117
1127
|
configPath,
|
|
@@ -1359,7 +1369,61 @@ function getEnvCredential() {
|
|
|
1359
1369
|
variable: "NEXT_PUBLIC_STATELY_API_KEY"
|
|
1360
1370
|
};
|
|
1361
1371
|
}
|
|
1362
|
-
|
|
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 = {}) {
|
|
1363
1427
|
const envApiKey = getEnvApiKey();
|
|
1364
1428
|
if (envApiKey) return {
|
|
1365
1429
|
apiKey: envApiKey.apiKey,
|
|
@@ -1367,11 +1431,19 @@ async function resolveApiKey() {
|
|
|
1367
1431
|
detail: envApiKey.variable
|
|
1368
1432
|
};
|
|
1369
1433
|
const storedCredential = await getStoredCredential();
|
|
1370
|
-
if (storedCredential?.credential)
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
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
|
+
}
|
|
1375
1447
|
return { source: "missing" };
|
|
1376
1448
|
}
|
|
1377
1449
|
function getDefaultBaseUrl() {
|
|
@@ -1538,14 +1610,17 @@ function parseOAuthCallback(value) {
|
|
|
1538
1610
|
return { code: value };
|
|
1539
1611
|
}
|
|
1540
1612
|
}
|
|
1541
|
-
function createOAuthCredential(tokenResponse,
|
|
1613
|
+
function createOAuthCredential(tokenResponse, options = {}) {
|
|
1542
1614
|
if (!tokenResponse.access_token) throw new Error("Token response did not include an access_token.");
|
|
1615
|
+
const now = options.now ?? Date.now();
|
|
1543
1616
|
const expiresAt = typeof tokenResponse.expires_at === "number" ? tokenResponse.expires_at * 1e3 : typeof tokenResponse.expires_in === "number" ? now + tokenResponse.expires_in * 1e3 : void 0;
|
|
1544
1617
|
return {
|
|
1545
1618
|
type: "oauth",
|
|
1546
1619
|
accessToken: tokenResponse.access_token,
|
|
1547
|
-
...tokenResponse.refresh_token ? { refreshToken: tokenResponse.refresh_token } : {},
|
|
1548
|
-
...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 } : {}
|
|
1549
1624
|
};
|
|
1550
1625
|
}
|
|
1551
1626
|
async function buildOAuthLoginStart(options) {
|
|
@@ -1605,7 +1680,10 @@ async function runOAuthBrowserLogin(options) {
|
|
|
1605
1680
|
codeVerifier,
|
|
1606
1681
|
redirectUri: callbackServer.redirectUri,
|
|
1607
1682
|
clientId: discovery.clientId
|
|
1608
|
-
})
|
|
1683
|
+
}), {
|
|
1684
|
+
clientId: discovery.clientId,
|
|
1685
|
+
tokenEndpoint: discovery.tokenEndpoint
|
|
1686
|
+
}));
|
|
1609
1687
|
} finally {
|
|
1610
1688
|
await callbackServer.close();
|
|
1611
1689
|
}
|
|
@@ -1816,16 +1894,38 @@ async function initProject(options) {
|
|
|
1816
1894
|
configPath: options.configPath,
|
|
1817
1895
|
rewriteIfNeeded: true
|
|
1818
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
|
+
}
|
|
1819
1925
|
const client = options.client ?? createStatelyClient({
|
|
1820
1926
|
apiKey: options.apiKey,
|
|
1821
1927
|
baseUrl: studioUrl
|
|
1822
1928
|
});
|
|
1823
|
-
if (existingConfig) return {
|
|
1824
|
-
config: existingConfig.config,
|
|
1825
|
-
configPath: existingConfig.configPath,
|
|
1826
|
-
project: await client.projects.get(existingConfig.config.projectId),
|
|
1827
|
-
reusedExistingConfig: true
|
|
1828
|
-
};
|
|
1829
1929
|
const inferredRepo = options.project?.repo ?? await inferConnectedRepoFromCwd(cwd);
|
|
1830
1930
|
const projectInput = {
|
|
1831
1931
|
name: options.project?.name ?? inferInitProjectName(cwd, inferredRepo),
|
|
@@ -1835,12 +1935,16 @@ async function initProject(options) {
|
|
|
1835
1935
|
...inferredRepo ? { repo: inferredRepo } : {}
|
|
1836
1936
|
};
|
|
1837
1937
|
const project = await client.projects.ensure(projectInput);
|
|
1838
|
-
const config =
|
|
1938
|
+
const config = existingConfig ? {
|
|
1939
|
+
...existingConfig.config,
|
|
1940
|
+
projectId: project.projectId,
|
|
1941
|
+
studioUrl
|
|
1942
|
+
} : createStatelyProjectConfig({
|
|
1839
1943
|
projectId: project.projectId,
|
|
1840
1944
|
studioUrl,
|
|
1841
1945
|
defaultXStateVersion
|
|
1842
1946
|
});
|
|
1843
|
-
await
|
|
1947
|
+
await writeStatelyProjectConfig(configPath, config);
|
|
1844
1948
|
return {
|
|
1845
1949
|
config,
|
|
1846
1950
|
configPath,
|
|
@@ -1848,15 +1952,66 @@ async function initProject(options) {
|
|
|
1848
1952
|
reusedExistingConfig: false
|
|
1849
1953
|
};
|
|
1850
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
|
+
}
|
|
1851
2012
|
async function scanProjectSources(options) {
|
|
1852
2013
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1853
|
-
|
|
1854
|
-
const machineRelativePaths = [];
|
|
1855
|
-
for (const relativePath of candidateRelativePaths) {
|
|
1856
|
-
const filePath = path.join(cwd, relativePath);
|
|
1857
|
-
if (looksLikeXStateMachineSource(await fsPromises.readFile(filePath, "utf8"))) machineRelativePaths.push(relativePath);
|
|
1858
|
-
}
|
|
1859
|
-
return suggestStatelySourceConfigs(machineRelativePaths);
|
|
2014
|
+
return suggestStatelySourceConfigs([...new Set((await scanProjectMachines({ cwd })).map((machine) => machine.relativePath))]);
|
|
1860
2015
|
}
|
|
1861
2016
|
function looksLikeXStateMachineSource(source) {
|
|
1862
2017
|
const hasXStateImport = /from\s+['"]xstate(?:\/[^'"]+)?['"]/.test(source) || /require\(\s*['"]xstate(?:\/[^'"]+)?['"]\s*\)/.test(source);
|
|
@@ -1867,7 +2022,12 @@ async function classifyPushCandidates(files) {
|
|
|
1867
2022
|
const machineFiles = [];
|
|
1868
2023
|
const nonMachineFiles = [];
|
|
1869
2024
|
for (const file of files) {
|
|
1870
|
-
|
|
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) {
|
|
1871
2031
|
machineFiles.push(file);
|
|
1872
2032
|
continue;
|
|
1873
2033
|
}
|
|
@@ -1887,6 +2047,7 @@ async function resolveConfiguredProject(options) {
|
|
|
1887
2047
|
configPath: options.configPath,
|
|
1888
2048
|
rewriteIfNeeded: options.rewriteIfNeeded ?? true
|
|
1889
2049
|
});
|
|
2050
|
+
if (!config.projectId) throw new Error("This is a local-only project. Run `statelyai init` to connect it to Stately Studio.");
|
|
1890
2051
|
const studioUrl = options.baseUrl ?? config.studioUrl;
|
|
1891
2052
|
return {
|
|
1892
2053
|
client: options.client ?? createStatelyClient({
|
|
@@ -1958,34 +2119,68 @@ function formatCredentialStatus(status) {
|
|
|
1958
2119
|
return status.type === "none" ? `Auth: none (${status.detail})` : `Auth: ${status.type} from ${status.source} (${status.detail})`;
|
|
1959
2120
|
}
|
|
1960
2121
|
async function resolveProjectStatus(options = {}) {
|
|
1961
|
-
const {
|
|
1962
|
-
|
|
2122
|
+
const { config, configPath, rootDir } = await readStatelyProjectConfig({
|
|
2123
|
+
cwd: options.cwd,
|
|
2124
|
+
configPath: options.configPath,
|
|
1963
2125
|
rewriteIfNeeded: false
|
|
1964
2126
|
});
|
|
1965
|
-
const
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
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);
|
|
1969
2162
|
const remoteById = new Map(project.machines.flatMap((machine) => {
|
|
1970
2163
|
const id = getProjectMachineId(machine);
|
|
1971
2164
|
return id ? [[id, machine]] : [];
|
|
1972
2165
|
}));
|
|
1973
|
-
const linkedIds = new Set(
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
name:
|
|
1979
|
-
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,
|
|
1980
2173
|
status: "local-only"
|
|
1981
2174
|
};
|
|
1982
|
-
const remote = remoteById.get(
|
|
2175
|
+
const remote = remoteById.get(machine.id);
|
|
1983
2176
|
return {
|
|
1984
|
-
id:
|
|
1985
|
-
|
|
1986
|
-
|
|
2177
|
+
id: machine.id,
|
|
2178
|
+
line: machine.line,
|
|
2179
|
+
machineIndex: machine.machineIndex,
|
|
2180
|
+
name: remote?.name || machine.name,
|
|
2181
|
+
path: machine.relativePath,
|
|
1987
2182
|
status: remote ? "linked" : "missing-remote",
|
|
1988
|
-
url: buildMachineEditorUrl(studioUrl, config.projectId,
|
|
2183
|
+
url: buildMachineEditorUrl(studioUrl, config.projectId, machine.id)
|
|
1989
2184
|
};
|
|
1990
2185
|
});
|
|
1991
2186
|
for (const remote of project.machines) {
|
|
@@ -2009,6 +2204,7 @@ async function resolveProjectStatus(options = {}) {
|
|
|
2009
2204
|
"remote-only": machines.filter((machine) => machine.status === "remote-only").length
|
|
2010
2205
|
},
|
|
2011
2206
|
machines,
|
|
2207
|
+
mode: "studio",
|
|
2012
2208
|
project: {
|
|
2013
2209
|
id: config.projectId,
|
|
2014
2210
|
...project.name ? { name: project.name } : {},
|
|
@@ -2283,10 +2479,10 @@ var PushCommand = class PushCommand extends Command {
|
|
|
2283
2479
|
if (flags["dry-run"]) {
|
|
2284
2480
|
const wouldLink = [];
|
|
2285
2481
|
const wouldUpdate = [];
|
|
2286
|
-
for (const
|
|
2287
|
-
const
|
|
2288
|
-
if (
|
|
2289
|
-
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}`);
|
|
2290
2486
|
}
|
|
2291
2487
|
if (wouldLink.length > 0) this.log(`${formatSectionLabel("Would link:")}\n${wouldLink.join("\n")}`);
|
|
2292
2488
|
if (wouldUpdate.length > 0) this.log(`${formatSectionLabel("Would update:")}\n${wouldUpdate.join("\n")}`);
|
|
@@ -2389,8 +2585,8 @@ var OpenCommand = class OpenCommand extends Command {
|
|
|
2389
2585
|
};
|
|
2390
2586
|
var InitCommand = class InitCommand extends Command {
|
|
2391
2587
|
static enableJsonFlag = false;
|
|
2392
|
-
static summary = "Create a
|
|
2393
|
-
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.";
|
|
2394
2590
|
static flags = {
|
|
2395
2591
|
help: Flags.help({ char: "h" }),
|
|
2396
2592
|
"base-url": Flags.string({ description: "Base URL for Stately Studio or a self-hosted deployment" }),
|
|
@@ -2408,6 +2604,10 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2408
2604
|
description: "Overwrite an existing statelyai.json file",
|
|
2409
2605
|
default: false
|
|
2410
2606
|
}),
|
|
2607
|
+
local: Flags.boolean({
|
|
2608
|
+
description: "Create a local-only config without authentication or Studio access",
|
|
2609
|
+
default: false
|
|
2610
|
+
}),
|
|
2411
2611
|
scan: Flags.boolean({
|
|
2412
2612
|
description: "Scan the repo for machine-bearing files and suggest source globs to save into statelyai.json",
|
|
2413
2613
|
default: false
|
|
@@ -2415,21 +2615,25 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2415
2615
|
};
|
|
2416
2616
|
async run() {
|
|
2417
2617
|
const { flags } = await this.parse(InitCommand);
|
|
2418
|
-
const resolvedApiKey = await resolveApiKey();
|
|
2419
|
-
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...");
|
|
2420
2620
|
const result = await initProject({
|
|
2421
2621
|
apiKey: resolvedApiKey.apiKey,
|
|
2422
2622
|
baseUrl: flags["base-url"],
|
|
2423
2623
|
force: flags.force,
|
|
2624
|
+
local: flags.local,
|
|
2424
2625
|
project: {
|
|
2425
2626
|
...flags.name ? { name: flags.name } : {},
|
|
2426
2627
|
visibility: flags.visibility
|
|
2427
2628
|
}
|
|
2428
2629
|
});
|
|
2429
|
-
const
|
|
2430
|
-
|
|
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}.`;
|
|
2431
2634
|
if (flags.scan) {
|
|
2432
2635
|
this.log("Scanning local source files...");
|
|
2636
|
+
const machines = await scanProjectMachines({ cwd: path.dirname(result.configPath) });
|
|
2433
2637
|
const suggestions = await scanProjectSources({
|
|
2434
2638
|
cwd: path.dirname(result.configPath),
|
|
2435
2639
|
defaultXStateVersion: result.config.defaultXStateVersion
|
|
@@ -2439,6 +2643,7 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2439
2643
|
return;
|
|
2440
2644
|
}
|
|
2441
2645
|
this.log(initMessage);
|
|
2646
|
+
this.log(formatDiscoveredMachines(machines));
|
|
2442
2647
|
this.log(`Suggested source globs:\n${suggestions.map((source) => `- ${source.include.join(", ")}`).join("\n")}`);
|
|
2443
2648
|
if (await promptYesNo("Save these source globs to statelyai.json?", true)) {
|
|
2444
2649
|
const inferredNewMachineDir = inferNewMachineDirFromSuggestions(suggestions);
|
|
@@ -2448,7 +2653,7 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2448
2653
|
exclude: [...new Set(suggestions.flatMap((suggestion) => suggestion.exclude ?? []))],
|
|
2449
2654
|
...inferredNewMachineDir ? { newMachinesDir: inferredNewMachineDir } : {}
|
|
2450
2655
|
};
|
|
2451
|
-
await
|
|
2656
|
+
await writeStatelyProjectConfig(result.configPath, nextConfig);
|
|
2452
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.");
|
|
2453
2658
|
} else this.log("Left statelyai.json unchanged. Edit it before running statelyai push.");
|
|
2454
2659
|
return;
|
|
@@ -2456,6 +2661,23 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2456
2661
|
this.log(`${initMessage}\nNo include globs configured yet. Edit statelyai.json before running statelyai push, or rerun with --scan.`);
|
|
2457
2662
|
}
|
|
2458
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
|
+
};
|
|
2459
2681
|
var LoginCommand = class LoginCommand extends Command {
|
|
2460
2682
|
static enableJsonFlag = false;
|
|
2461
2683
|
static summary = "Store Stately credentials for future CLI use.";
|
|
@@ -2503,16 +2725,20 @@ var LogoutCommand = class extends Command {
|
|
|
2503
2725
|
function formatProjectStatus(result) {
|
|
2504
2726
|
const lines = [
|
|
2505
2727
|
formatCredentialStatus(result.auth),
|
|
2506
|
-
`
|
|
2507
|
-
`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}`] : [],
|
|
2508
2730
|
`Config: ${result.configPath}`,
|
|
2509
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)`
|
|
2510
2732
|
];
|
|
2511
2733
|
for (const machine of result.machines) {
|
|
2512
|
-
const details = [machine.
|
|
2734
|
+
const details = [machine.path ? `${machine.path}${machine.line ? `:${machine.line}` : ""}` : void 0, machine.id].filter(Boolean).join(" ");
|
|
2513
2735
|
lines.push(` [${machine.status}] ${machine.name}${details ? ` ${details}` : ""}`);
|
|
2514
2736
|
}
|
|
2515
2737
|
const next = [];
|
|
2738
|
+
if (result.mode === "local") {
|
|
2739
|
+
lines.push("Next: statelyai init (connect to Studio)");
|
|
2740
|
+
return lines.join("\n");
|
|
2741
|
+
}
|
|
2516
2742
|
if (result.counts["local-only"] > 0 || result.counts["missing-remote"] > 0) next.push("statelyai push");
|
|
2517
2743
|
if (result.counts["remote-only"] > 0) next.push("statelyai pull");
|
|
2518
2744
|
if (next.length > 0) lines.push(`Next: ${next.join(", ")}`);
|
|
@@ -2541,9 +2767,7 @@ var StatusCommand = class StatusCommand extends Command {
|
|
|
2541
2767
|
this.log(flags.json ? JSON.stringify({ auth }, null, 2) : formatCredentialStatus(auth));
|
|
2542
2768
|
return;
|
|
2543
2769
|
}
|
|
2544
|
-
const apiKey = (await resolveApiKey()).apiKey;
|
|
2545
2770
|
const result = await resolveProjectStatus({
|
|
2546
|
-
apiKey,
|
|
2547
2771
|
baseUrl: flags["base-url"],
|
|
2548
2772
|
configPath: flags.config
|
|
2549
2773
|
});
|
|
@@ -2566,6 +2790,7 @@ const COMMANDS = {
|
|
|
2566
2790
|
pull: PullCommand,
|
|
2567
2791
|
push: PushCommand,
|
|
2568
2792
|
open: OpenCommand,
|
|
2793
|
+
scan: ScanCommand,
|
|
2569
2794
|
status: StatusCommand,
|
|
2570
2795
|
init: InitCommand,
|
|
2571
2796
|
login: LoginCommand,
|
|
@@ -2594,4 +2819,4 @@ function isDirectExecution() {
|
|
|
2594
2819
|
if (isDirectExecution()) run$1();
|
|
2595
2820
|
|
|
2596
2821
|
//#endregion
|
|
2597
|
-
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 };
|