statelyai 0.8.23 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,9 +88,14 @@ statelyai pull
88
88
  ```
89
89
 
90
90
  `push` creates remote machines for unlinked source machines, updates already
91
- linked machines, and writes returned IDs into source comments. `pull` updates
92
- linked files. If `newMachinesDir` is configured, it also creates local files
93
- for machines that exist only in the Studio project.
91
+ linked machines, and writes returned IDs into source comments. Updating a
92
+ linked machine preserves Studio layout, colors, and annotations for unchanged
93
+ states while applying local structural changes. New states use default layout.
94
+ `pull` updates linked files. If `newMachinesDir` is configured, it also creates
95
+ local files for machines that exist only in the Studio project.
96
+ Push re-reads and retries when it observes a concurrent Studio edit. Registry
97
+ updates do not yet expose an atomic revision precondition, so avoid editing the
98
+ same machine in Studio during a push.
94
99
 
95
100
  ## Authentication
96
101
 
@@ -296,9 +301,10 @@ Locators may be:
296
301
  - a Studio machine ID
297
302
  - a Studio machine URL
298
303
 
299
- `--fail-on-changes` exits with status `1` when the normalized graphs differ.
300
- Use `--base-url` for remote IDs at another Studio origin. `plan` remains a
301
- hidden compatibility alias for `diff`.
304
+ `--fail-on-changes` exits with status `1` when machine semantics differ.
305
+ Entity IDs and presentation-only layout or color fields do not count as
306
+ changes. Use `--base-url` for remote IDs at another Studio origin. `plan`
307
+ remains a hidden compatibility alias for `diff`.
302
308
 
303
309
  ### `push`
304
310
 
@@ -323,6 +329,13 @@ statelyai push --dry-run
323
329
  `--dry-run` does not create or update machines, write `@statelyai` IDs, or
324
330
  migrate legacy configuration.
325
331
 
332
+ For linked machines, `push` reads the current remote definition before updating
333
+ it. Local source remains authoritative for structure; Studio presentation for
334
+ unchanged states and transitions is preserved.
335
+ Push re-reads before writing and retries if it observes a remote change.
336
+ Registry updates do not yet expose an atomic revision precondition, so avoid
337
+ editing the same machine in Studio during a push.
338
+
326
339
  Flags: `--config <path>`, `--base-url <url>`, `--dry-run`.
327
340
 
328
341
  ### `pull`
@@ -347,9 +360,17 @@ statelyai pull machine_123 src/checkout.machine.ts
347
360
 
348
361
  New targets support JavaScript/TypeScript, `.digraph.json`, and `.graph.json`.
349
362
  For existing JSON targets, the current file shape determines the output format.
363
+ Existing JavaScript and TypeScript targets are updated through surgical graph
364
+ patches so surrounding comments, imports, helpers, and implementation bodies
365
+ survive. New source files and JSON targets are generated as complete files.
350
366
 
351
367
  Project-wide pull skips linked files with uncommitted Git changes. Pass
352
- `--force` to overwrite them. Remote-only machines are skipped until
368
+ `--force` to overwrite them. If an existing source file cannot be updated
369
+ safely, pull leaves it unchanged and exits nonzero; `--force` explicitly allows
370
+ complete regeneration, which discards comments, imports, and helper code around
371
+ the machine. `--force` enables both behaviors together, so use it only when full
372
+ regeneration is acceptable. Every linked machine in a multi-machine source file
373
+ is pulled independently. Remote-only machines are skipped until
353
374
  `newMachinesDir` is set.
354
375
 
355
376
  Flags: `--config <path>`, `--base-url <url>`, `--force`.
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { S as run } from "./cli-BiA4S5aG.mjs";
2
+ import { S as run } from "./cli-uNjqtpTz.mjs";
3
3
 
4
4
  //#region src/bin.ts
5
5
  run();
@@ -14,10 +14,10 @@ import * as ts from "typescript";
14
14
  import { EditorHostSession, StudioApiError, buildAuthorizeUrl, createPkcePair, createRemoteEditorSyncBackend, createStatelyClient, discoverAuthorizationServer, discoverProtectedResource, exchangeCodeForToken, findStatelyPragmaAttachments, getStatelyPragma, refreshToken, registerOAuthClient } from "@statelyai/sdk";
15
15
  import { WebSocket, WebSocketServer } from "ws";
16
16
  import os from "node:os";
17
- import { planSync, pullSync, pushLocalMachineLinks } from "@statelyai/sdk/sync";
17
+ import { PullSourceReconciliationError, planSync, pullSync, pushLocalMachineLinks } from "@statelyai/sdk/sync";
18
18
 
19
19
  //#region src/version.ts
20
- const CLI_VERSION = "0.8.23";
20
+ const CLI_VERSION = "0.9.0";
21
21
 
22
22
  //#endregion
23
23
  //#region src/cliHost.ts
@@ -1601,11 +1601,12 @@ async function discoverLinkedPullTargets(files) {
1601
1601
  const linkedTargets = [];
1602
1602
  for (const file of files) {
1603
1603
  if (!supportsMachineDiscovery(file)) continue;
1604
- const pragma = getStatelyPragma(await fsPromises.readFile(file.filePath, "utf8"), file.filePath);
1605
- if (!pragma?.id) continue;
1606
- linkedTargets.push({
1607
- file,
1608
- machineId: pragma.id
1604
+ findStatelyPragmaAttachments(await fsPromises.readFile(file.filePath, "utf8"), file.filePath).forEach((attachment, machineIndex) => {
1605
+ if (attachment.pragma?.id) linkedTargets.push({
1606
+ file,
1607
+ machineId: attachment.pragma.id,
1608
+ machineIndex
1609
+ });
1609
1610
  });
1610
1611
  }
1611
1612
  return linkedTargets;
@@ -2125,9 +2126,14 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
2125
2126
  this.log(colorize(`Processing ${linkedTargets.length} ${pluralize(linkedTargets.length, "linked file")}${remoteOnlyTargets.length > 0 ? ` and ${remoteOnlyTargets.length} ${pluralize(remoteOnlyTargets.length, "new remote machine")}` : ""}...`, ANSI_CYAN));
2126
2127
  const pulled = [];
2127
2128
  const created = [];
2129
+ let hadSourceReconciliationFailure = false;
2130
+ const dirtyLinkedFiles = /* @__PURE__ */ new Map();
2131
+ if (!flags.force) await Promise.all([...new Set(linkedTargets.map((target) => target.file.filePath))].map(async (filePath) => {
2132
+ dirtyLinkedFiles.set(filePath, await isFileGitDirty(filePath));
2133
+ }));
2128
2134
  for (const linkedTarget of linkedTargets) {
2129
2135
  const targetPath = linkedTarget.file.filePath;
2130
- if (!flags.force && await isFileGitDirty(targetPath)) {
2136
+ if (!flags.force && dirtyLinkedFiles.get(targetPath)) {
2131
2137
  skipped.push(`${linkedTarget.file.relativePath}: local git changes detected (pass --force to overwrite)`);
2132
2138
  continue;
2133
2139
  }
@@ -2137,10 +2143,13 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
2137
2143
  source: linkedTarget.machineId,
2138
2144
  target: targetPath,
2139
2145
  apiKey,
2140
- baseUrl: resolveApiBaseUrl(flags["base-url"] ?? config.studioUrl)
2146
+ baseUrl: resolveApiBaseUrl(flags["base-url"] ?? config.studioUrl),
2147
+ force: flags.force,
2148
+ machineIndex: linkedTarget.machineIndex
2141
2149
  });
2142
2150
  pulled.push(`${linkedTarget.file.relativePath}: ${formatSecondaryText(result.source.locator)}`);
2143
2151
  } catch (error) {
2152
+ if (error instanceof PullSourceReconciliationError) hadSourceReconciliationFailure = true;
2144
2153
  skipped.push(`${linkedTarget.file.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
2145
2154
  }
2146
2155
  }
@@ -2152,16 +2161,19 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
2152
2161
  source: remoteTarget.machineId,
2153
2162
  target: remoteTarget.filePath,
2154
2163
  apiKey,
2155
- baseUrl: resolveApiBaseUrl(flags["base-url"] ?? config.studioUrl)
2164
+ baseUrl: resolveApiBaseUrl(flags["base-url"] ?? config.studioUrl),
2165
+ force: flags.force
2156
2166
  });
2157
2167
  created.push(`${remoteTarget.relativePath}: ${formatSecondaryText(result.source.locator)}`);
2158
2168
  } catch (error) {
2169
+ if (error instanceof PullSourceReconciliationError) hadSourceReconciliationFailure = true;
2159
2170
  skipped.push(`${remoteTarget.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
2160
2171
  }
2161
2172
  }
2162
2173
  if (pulled.length > 0) this.log(`${formatSectionLabel("Pulled:", "success")}\n${pulled.join("\n")}`);
2163
2174
  if (created.length > 0) this.log(`${formatSectionLabel("Created:", "success")}\n${created.join("\n")}`);
2164
2175
  if (skipped.length > 0) this.log(`${formatSectionLabel("Skipped:", "warning")}\n${skipped.join("\n")}`);
2176
+ if (hadSourceReconciliationFailure) this.exit(1);
2165
2177
  return;
2166
2178
  }
2167
2179
  const localCandidate = path.resolve(process.cwd(), args.source);
@@ -2180,7 +2192,8 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
2180
2192
  source,
2181
2193
  target,
2182
2194
  apiKey,
2183
- baseUrl: resolveApiBaseUrl(flags["base-url"] ?? getDefaultBaseUrl())
2195
+ baseUrl: resolveApiBaseUrl(flags["base-url"] ?? getDefaultBaseUrl()),
2196
+ force: flags.force
2184
2197
  });
2185
2198
  this.log(`${formatSectionLabel("Pulled:", "success")} ${formatSecondaryText(result.source.locator)} -> ${result.outputPath}\nTarget: ${result.target.kind} (${result.target.format})`);
2186
2199
  }
package/dist/index.d.mts CHANGED
@@ -84,6 +84,7 @@ interface DiscoveredMachine {
84
84
  interface LinkedPullTarget {
85
85
  file: DiscoveredSourceFile;
86
86
  machineId: string;
87
+ machineIndex: number;
87
88
  }
88
89
  interface RemoteProjectMachineTarget {
89
90
  machineId: string;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
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-BiA4S5aG.mjs";
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-uNjqtpTz.mjs";
2
2
 
3
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statelyai",
3
- "version": "0.8.23",
3
+ "version": "0.9.0",
4
4
  "description": "Command-line tools for Stately",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,7 +23,7 @@
23
23
  "@oclif/core": "^4.10.3",
24
24
  "typescript": "^5.9.3",
25
25
  "ws": "^8.21.0",
26
- "@statelyai/sdk": "0.26.3"
26
+ "@statelyai/sdk": "0.27.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/ws": "^8.18.1",