@cargo-ai/cdk 1.0.6 → 1.0.8

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.
Files changed (52) hide show
  1. package/README.md +4 -44
  2. package/build/src/cli/auth.d.ts.map +1 -1
  3. package/build/src/cli/auth.js +16 -12
  4. package/build/src/cli/commands/deploy.d.ts.map +1 -1
  5. package/build/src/cli/commands/deploy.js +81 -107
  6. package/build/src/cli/commands/from.d.ts +65 -0
  7. package/build/src/cli/commands/from.d.ts.map +1 -0
  8. package/build/src/cli/commands/from.js +285 -0
  9. package/build/src/cli/commands/init.d.ts +1 -1
  10. package/build/src/cli/commands/init.d.ts.map +1 -1
  11. package/build/src/cli/commands/init.js +44 -20
  12. package/build/src/cli/commands/inputTypes.d.ts.map +1 -1
  13. package/build/src/cli/commands/inputTypes.js +77 -6
  14. package/build/src/cli/commands/types.d.ts.map +1 -1
  15. package/build/src/cli/commands/types.js +87 -9
  16. package/build/src/cli/io.js +2 -2
  17. package/build/src/cli/version.d.ts.map +1 -1
  18. package/build/src/cli/version.js +10 -6
  19. package/build/src/deploy/apply.d.ts.map +1 -1
  20. package/build/src/deploy/apply.js +37 -30
  21. package/build/src/deploy/compile.d.ts.map +1 -1
  22. package/build/src/deploy/compile.js +7 -5
  23. package/build/src/deploy/destroy.d.ts.map +1 -1
  24. package/build/src/deploy/destroy.js +24 -14
  25. package/build/src/deploy/executors.live.d.ts.map +1 -1
  26. package/build/src/deploy/executors.live.js +202 -173
  27. package/build/src/deploy/import.js +2 -2
  28. package/build/src/deploy/index.js +1 -1
  29. package/build/src/deploy/lock.d.ts.map +1 -1
  30. package/build/src/deploy/lock.js +13 -10
  31. package/build/src/deploy/readers.live.d.ts.map +1 -1
  32. package/build/src/deploy/readers.live.js +27 -15
  33. package/build/src/deploy/refresh.d.ts.map +1 -1
  34. package/build/src/deploy/refresh.js +20 -18
  35. package/build/src/index.d.ts +4 -4
  36. package/build/src/index.d.ts.map +1 -1
  37. package/build/src/index.js +2 -2
  38. package/build/src/refs.d.ts.map +1 -1
  39. package/build/src/refs.js +5 -1
  40. package/build/src/resources/actions.d.ts +1 -1
  41. package/build/src/resources/actions.d.ts.map +1 -1
  42. package/build/src/resources/actions.js +3 -2
  43. package/build/src/resources/agent.d.ts +23 -0
  44. package/build/src/resources/agent.d.ts.map +1 -1
  45. package/build/src/resources/agent.js +17 -0
  46. package/build/src/resources/capacity.d.ts +11 -0
  47. package/build/src/resources/capacity.d.ts.map +1 -1
  48. package/build/src/resources/capacity.js +81 -0
  49. package/build/src/resources/model.d.ts +11 -3
  50. package/build/src/resources/model.d.ts.map +1 -1
  51. package/build/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +2 -2
package/README.md CHANGED
@@ -51,6 +51,9 @@ no global install:
51
51
  npx @cargo-ai/cdk init my-workspace --template full # every resource type, wired up
52
52
  # or: npx @cargo-ai/cdk init my-workspace # minimal 'blank' template
53
53
  # or: npx @cargo-ai/cdk init --list-templates # see all templates
54
+ # or: npx @cargo-ai/cdk init my-tam --from getcargohq/cargo-cookbooks/signal-based-tam
55
+ # scaffold from a GitHub source (owner/repo[/folder][@ref]); a cookbook
56
+ # folder also pulls its required siblings via the repo's cargo.scaffold.json
54
57
  cd my-workspace && npm install
55
58
  ```
56
59
 
@@ -165,49 +168,6 @@ There is one context repo per workspace, so this is a singleton. On deploy it
165
168
  writes each changed file into the repo via the runtime — **additive**: files
166
169
  added elsewhere (the UI, other tooling) are left in place.
167
170
 
168
- ### Unification
169
-
170
- A model can declare how it feeds Cargo's unified `Account` / `Contact` /
171
- `AccountEvent` / `ContactEvent` models. A connector model defaults to its
172
- integration's built-in mapping (`{ source: "integration" }`), so you only pass
173
- `unification` to override it or to unify a model the integration doesn't map on
174
- its own. A `custom` unification maps columns to shared references (`domain`,
175
- `email`, `linkedinId`, …) and can link a child to its parent by pointing at
176
- another model handle:
177
-
178
- ```ts
179
- import { defineModel } from "@cargo-ai/cdk";
180
-
181
- export const accounts = defineModel("accounts", {
182
- dataset: warehouse,
183
- extractSlug: "runQuery",
184
- unification: {
185
- source: "custom",
186
- type: "account",
187
- uniqueColumns: [{ slug: "website", reference: "domain" }],
188
- },
189
- });
190
-
191
- export const contacts = defineModel("contacts", {
192
- dataset: warehouse,
193
- extractSlug: "runQuery",
194
- unification: {
195
- source: "custom",
196
- type: "contact",
197
- uniqueColumns: [{ slug: "work_email", reference: "workEmail" }],
198
- // link each contact to its unified account — pass the model handle, not a uuid
199
- parent: { kind: "model", columnSlug: "account_id", parent: accounts },
200
- },
201
- });
202
- ```
203
-
204
- Unification isn't part of the create call, so the CDK applies it with a
205
- follow-up update once the model exists — including models the deploy adopts.
206
- Passing a parent handle makes the child depend on the parent, so the parent is
207
- created first and its uuid is injected. See
208
- [Unification](https://docs.getcargo.ai/models/unification) for the reference and
209
- priority rules.
210
-
211
171
  ## Commands
212
172
 
213
173
  In a scaffolded project, the everyday commands are `npm run` scripts:
@@ -221,7 +181,7 @@ npm run deploy # create/update resources, write cargo.state.json
221
181
  Run any command — including the ones without a script — with `npx @cargo-ai/cdk`:
222
182
 
223
183
  ```bash
224
- npx @cargo-ai/cdk init <directory> # scaffold from a template (--template, --list-templates)
184
+ npx @cargo-ai/cdk init <directory> # scaffold from a template (--template, --list-templates) or GitHub (--from owner/repo[/folder][@ref])
225
185
  npx @cargo-ai/cdk deploy --prune # also delete resources removed from code
226
186
  npx @cargo-ai/cdk deploy --refresh # re-read live resources, re-apply out-of-band changes
227
187
  npx @cargo-ai/cdk refresh # read-only: report resources that drifted from code
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/cli/auth.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,GAAG,EAAkC,MAAM,eAAe,CAAC;AAuBzE,MAAM,MAAM,MAAM,GAAG;IACnB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC,CAAC;AAYF,wBAAgB,SAAS,IAAI,MAAM,CAoBlC;AA4DD,wBAAgB,MAAM,IAAI,GAAG,CAS5B"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/cli/auth.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,GAAG,EAAkC,MAAM,eAAe,CAAC;AAuBzE,MAAM,MAAM,MAAM,GAAG;IACnB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC,CAAC;AAYF,wBAAgB,SAAS,IAAI,MAAM,CAqBlC;AAgED,wBAAgB,MAAM,IAAI,GAAG,CAS5B"}
@@ -13,7 +13,7 @@ const ORIGIN = { name: "cargo-cdk", version: packageVersion() };
13
13
  const DEFAULT_BASE_URL = "https://api.getcargo.io";
14
14
  const CREDENTIALS_FILE = join(homedir(), ".config", "cargo-ai", "credentials.json");
15
15
  function loadCredentials() {
16
- if (!existsSync(CREDENTIALS_FILE))
16
+ if (existsSync(CREDENTIALS_FILE) === false)
17
17
  return undefined;
18
18
  try {
19
19
  return JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
@@ -24,21 +24,31 @@ function loadCredentials() {
24
24
  }
25
25
  // Resolve token / baseUrl / workspace, preferring env over the credentials file.
26
26
  export function getConfig() {
27
- const credentials = loadCredentials();
27
+ const loaded = loadCredentials();
28
+ const credentials = loaded === undefined ? {} : loaded;
28
29
  const envToken = process.env["CARGO_API_TOKEN"];
29
- const accessToken = envToken !== undefined ? envToken : credentials?.accessToken;
30
+ const accessToken = envToken !== undefined ? envToken : credentials.accessToken;
30
31
  const envUrl = process.env["CARGO_BASE_URL"];
31
32
  const baseUrl = envUrl !== undefined
32
33
  ? envUrl
33
- : credentials?.baseUrl !== undefined
34
+ : credentials.baseUrl !== undefined
34
35
  ? credentials.baseUrl
35
36
  : DEFAULT_BASE_URL;
36
37
  const envUuid = process.env["CARGO_WORKSPACE_UUID"];
37
- const workspaceUuid = envUuid !== undefined ? envUuid : credentials?.workspaceUuid;
38
+ const workspaceUuid = envUuid !== undefined ? envUuid : credentials.workspaceUuid;
38
39
  return { accessToken, baseUrl, workspaceUuid };
39
40
  }
40
41
  // Tunnel through an HTTP/HTTPS proxy when one is configured via the standard env
41
42
  // vars (honoring NO_PROXY); returns undefined otherwise so egress stays direct.
43
+ // The URL's hostname, or "" if it doesn't parse (used only for NO_PROXY matching).
44
+ function safeHostname(url) {
45
+ try {
46
+ return new URL(url).hostname;
47
+ }
48
+ catch {
49
+ return "";
50
+ }
51
+ }
42
52
  function getProxyTransport(targetUrl) {
43
53
  const getEnv = (...names) => {
44
54
  for (const name of names) {
@@ -52,13 +62,7 @@ function getProxyTransport(targetUrl) {
52
62
  if (noProxy === "*")
53
63
  return undefined;
54
64
  if (noProxy !== undefined) {
55
- let hostname = "";
56
- try {
57
- hostname = new URL(targetUrl).hostname;
58
- }
59
- catch {
60
- hostname = "";
61
- }
65
+ const hostname = safeHostname(targetUrl);
62
66
  const bypass = noProxy
63
67
  .split(",")
64
68
  .map((e) => e.trim())
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0CzC,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA0oBN"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2CzC,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA2lBN"}
@@ -17,15 +17,9 @@ export function registerDeployCommands(parent, getApi) {
17
17
  if (opts.dir !== undefined)
18
18
  process.chdir(opts.dir);
19
19
  const root = ".";
20
- let result;
21
- try {
22
- result = await plan(root);
23
- }
24
- catch (error) {
25
- failWith(`Failed to load cdk repo: ${message(error)}`, {
26
- code: ExitCodes.InvalidUsage,
27
- });
28
- }
20
+ const result = await runOrFail(() => plan(root), (error) => failWith(`Failed to load cdk repo: ${message(error)}`, {
21
+ code: ExitCodes.InvalidUsage,
22
+ }));
29
23
  if (opts.json === true)
30
24
  outputJson(result);
31
25
  else
@@ -54,15 +48,9 @@ export function registerDeployCommands(parent, getApi) {
54
48
  const runId = newRunId();
55
49
  // Load the resource tree ONCE (loadResources resets the registry, so it
56
50
  // must not run twice in a process), then reuse the nodes for plan + apply.
57
- let nodes;
58
- try {
59
- nodes = await loadResources(root);
60
- }
61
- catch (error) {
62
- failWith(`Failed to load cdk repo: ${message(error)}`, {
63
- code: ExitCodes.InvalidUsage,
64
- });
65
- }
51
+ const nodes = await runOrFail(() => loadResources(root), (error) => failWith(`Failed to load cdk repo: ${message(error)}`, {
52
+ code: ExitCodes.InvalidUsage,
53
+ }));
66
54
  // Hold the state lock for the whole read→plan→apply sequence (not just
67
55
  // the apply), so a concurrent run can't rewrite cargo.state.json between
68
56
  // our read and our write. --dry-run is read-only, so it skips the lock.
@@ -78,7 +66,7 @@ export function registerDeployCommands(parent, getApi) {
78
66
  if (prior !== undefined && prior.workspaceUuid !== workspaceUuid) {
79
67
  failWith(`cargo.state.json belongs to workspace ${prior.workspaceUuid}, but the selected workspace is ${workspaceUuid}. Refusing to deploy (would orphan resources).`, { code: ExitCodes.InvalidUsage });
80
68
  }
81
- const baseState = prior ?? emptyState(workspaceUuid);
69
+ const baseState = prior !== undefined ? prior : emptyState(workspaceUuid);
82
70
  // Build the authenticated client lazily so a plain `--dry-run` stays
83
71
  // offline; `--refresh` and the apply both reuse this one instance.
84
72
  let apiClient;
@@ -90,47 +78,9 @@ export function registerDeployCommands(parent, getApi) {
90
78
  // With --refresh, re-read the live resources and fold out-of-band drift
91
79
  // into the state before planning: a resource edited in the UI re-applies
92
80
  // (its code-hash is invalidated) and one deleted externally is recreated.
93
- let state = baseState;
94
- if (opts.refresh === true &&
95
- Object.keys(baseState.resources).length > 0) {
96
- let drift;
97
- try {
98
- drift = await detectDrift(baseState, liveReaders(api()));
99
- }
100
- catch (error) {
101
- failWith(`Refresh failed: ${message(error)}`, {
102
- code: ExitCodes.GenericError,
103
- });
104
- }
105
- const modifiedExternally = drift.filter((d) => d.status === "modified");
106
- const deletedExternally = drift.filter((d) => d.status === "deleted");
107
- const changed = [...modifiedExternally, ...deletedExternally];
108
- if (changed.length === 0) {
109
- info("Refresh: no out-of-band drift.");
110
- }
111
- else {
112
- info(`Refresh found ${String(changed.length)} drifted resource(s):`);
113
- for (const d of modifiedExternally) {
114
- info(` modified externally: ${d.id}`);
115
- }
116
- for (const d of deletedExternally) {
117
- info(` deleted externally: ${d.id}`);
118
- }
119
- // A resource present in code but deleted in the UI is ambiguous —
120
- // an accident to restore, or an intentional removal. Don't silently
121
- // recreate it; force the decision unless --recreate-deleted opts in.
122
- if (deletedExternally.length > 0 && opts.recreateDeleted !== true) {
123
- failWith(`${String(deletedExternally.length)} resource(s) were deleted outside the CDK ` +
124
- `(${deletedExternally.map((d) => d.id).join(", ")}). Re-run with ` +
125
- `--recreate-deleted to restore them, or remove them from your code if ` +
126
- `the deletion was intentional.`, { code: ExitCodes.InvalidUsage });
127
- }
128
- // Modified always re-applies; deleted only reconciles (→ recreate)
129
- // once the user has opted in above.
130
- const effectiveDrift = opts.recreateDeleted === true ? drift : modifiedExternally;
131
- state = reconcileDrift(baseState, effectiveDrift);
132
- }
133
- }
81
+ const state = opts.refresh === true && Object.keys(baseState.resources).length > 0
82
+ ? await refreshedState(baseState, opts, api)
83
+ : baseState;
134
84
  const planResult = compile({ nodes, state });
135
85
  info(formatPlan(planResult));
136
86
  if (planResult.errors.length > 0) {
@@ -150,7 +100,7 @@ export function registerDeployCommands(parent, getApi) {
150
100
  }
151
101
  return;
152
102
  }
153
- if (!hasApplyWork && !willPrune) {
103
+ if (hasApplyWork === false && willPrune === false) {
154
104
  if (deletes.length > 0) {
155
105
  info(`${String(deletes.length)} resource(s) in state are no longer in code. Re-run with --prune to remove them.`);
156
106
  }
@@ -164,7 +114,7 @@ export function registerDeployCommands(parent, getApi) {
164
114
  ? `Deploy to workspace ${workspaceUuid} and prune ${String(deletes.length)} resource(s)?`
165
115
  : `Deploy to workspace ${workspaceUuid}?`;
166
116
  const ok = await confirm(ask);
167
- if (!ok)
117
+ if (ok === false)
168
118
  failWith("Aborted.", { code: ExitCodes.GenericError });
169
119
  }
170
120
  // Snapshot the pre-deploy state so `cdk rollback` can restore it.
@@ -185,14 +135,11 @@ export function registerDeployCommands(parent, getApi) {
185
135
  spinner.log(` ${colors.green("✓")} ${event.id} ${colors.dim(done)}`);
186
136
  }
187
137
  };
188
- let result;
189
- try {
190
- result = await apply({ nodes, state }, liveExecutors(api()), // throws NotAuthenticated if not logged in
191
- (next) => writeState(root, next), liveReaders(api()), // capture a live fingerprint for drift detection
192
- onProgress);
193
- }
194
- catch (error) {
195
- spinner?.stop();
138
+ const result = await runOrFail(() => apply({ nodes, state }, liveExecutors(api()), // throws NotAuthenticated if not logged in
139
+ (next) => writeState(root, next), liveReaders(api()), // capture a live fingerprint for drift detection
140
+ onProgress), (error) => {
141
+ if (spinner !== undefined)
142
+ spinner.stop();
196
143
  appendAudit(root, {
197
144
  runId,
198
145
  at: new Date().toISOString(),
@@ -208,17 +155,11 @@ export function registerDeployCommands(parent, getApi) {
208
155
  hint: "Resources created before the failure were saved to cargo.state.json — fix the error and re-run to continue.",
209
156
  },
210
157
  });
211
- }
212
- spinner?.stop();
213
- let pruned = {
214
- removed: [],
215
- released: [],
216
- };
217
- if (willPrune) {
218
- try {
219
- pruned = await removeResources(root, api(), deletes.map((e) => e.id));
220
- }
221
- catch (error) {
158
+ });
159
+ if (spinner !== undefined)
160
+ spinner.stop();
161
+ const pruned = willPrune
162
+ ? await runOrFail(() => removeResources(root, api(), deletes.map((e) => e.id)), (error) => {
222
163
  appendAudit(root, {
223
164
  runId,
224
165
  at: new Date().toISOString(),
@@ -231,8 +172,8 @@ export function registerDeployCommands(parent, getApi) {
231
172
  code: ExitCodes.GenericError,
232
173
  extra: { runId },
233
174
  });
234
- }
235
- }
175
+ })
176
+ : { removed: [], released: [] };
236
177
  const created = result.applied.filter((e) => e.change === "create").length;
237
178
  const updated = result.applied.filter((e) => e.change === "update").length;
238
179
  appendAudit(root, {
@@ -293,15 +234,9 @@ export function registerDeployCommands(parent, getApi) {
293
234
  failWith(`cargo.state.json belongs to workspace ${state.workspaceUuid}, not the selected ${workspaceUuid}.`, { code: ExitCodes.InvalidUsage });
294
235
  }
295
236
  const api = getApi();
296
- let drift;
297
- try {
298
- drift = await detectDrift(state, liveReaders(api));
299
- }
300
- catch (error) {
301
- failWith(`Refresh failed: ${message(error)}`, {
302
- code: ExitCodes.GenericError,
303
- });
304
- }
237
+ const drift = await runOrFail(() => detectDrift(state, liveReaders(api)), (error) => failWith(`Refresh failed: ${message(error)}`, {
238
+ code: ExitCodes.GenericError,
239
+ }));
305
240
  if (opts.json === true) {
306
241
  outputJson(drift);
307
242
  return;
@@ -339,11 +274,7 @@ export function registerDeployCommands(parent, getApi) {
339
274
  failWith(message(error), { code: ExitCodes.GenericError });
340
275
  }
341
276
  snapshotState(root); // enable `cdk rollback`
342
- let result;
343
- try {
344
- result = await importResource(root, api, workspaceUuid, id, uuid);
345
- }
346
- catch (error) {
277
+ const result = await runOrFail(() => importResource(root, api, workspaceUuid, id, uuid), (error) => {
347
278
  appendAudit(root, {
348
279
  runId,
349
280
  at: new Date().toISOString(),
@@ -356,7 +287,7 @@ export function registerDeployCommands(parent, getApi) {
356
287
  code: ExitCodes.GenericError,
357
288
  extra: { runId },
358
289
  });
359
- }
290
+ });
360
291
  appendAudit(root, {
361
292
  runId,
362
293
  at: new Date().toISOString(),
@@ -384,7 +315,7 @@ export function registerDeployCommands(parent, getApi) {
384
315
  const runId = newRunId();
385
316
  if (opts.yes !== true) {
386
317
  const ok = await confirm("Restore cargo.state.json from the last snapshot? (restores the state file only — not live resources)");
387
- if (!ok)
318
+ if (ok === false)
388
319
  failWith("Aborted.", { code: ExitCodes.GenericError });
389
320
  }
390
321
  try {
@@ -393,7 +324,7 @@ export function registerDeployCommands(parent, getApi) {
393
324
  catch (error) {
394
325
  failWith(message(error), { code: ExitCodes.GenericError });
395
326
  }
396
- if (!restoreSnapshot(root)) {
327
+ if (restoreSnapshot(root) === false) {
397
328
  failWith("No snapshot found (cargo.state.bak.json) — nothing to roll back.", { code: ExitCodes.InvalidUsage });
398
329
  }
399
330
  appendAudit(root, {
@@ -460,15 +391,11 @@ export function registerDeployCommands(parent, getApi) {
460
391
  info(` - ${id}`);
461
392
  if (opts.yes !== true) {
462
393
  const ok = await confirm(`Delete these ${String(ids.length)} resource(s)?`);
463
- if (!ok)
394
+ if (ok === false)
464
395
  failWith("Aborted.", { code: ExitCodes.GenericError });
465
396
  }
466
397
  snapshotState(root); // enable `cdk rollback`
467
- let result;
468
- try {
469
- result = await destroy(root, api, { target: opts.target });
470
- }
471
- catch (error) {
398
+ const result = await runOrFail(() => destroy(root, api, { target: opts.target }), (error) => {
472
399
  appendAudit(root, {
473
400
  runId,
474
401
  at: new Date().toISOString(),
@@ -484,7 +411,7 @@ export function registerDeployCommands(parent, getApi) {
484
411
  hint: "Resources removed before the failure are gone and dropped from state — re-run to remove the rest.",
485
412
  },
486
413
  });
487
- }
414
+ });
488
415
  appendAudit(root, {
489
416
  runId,
490
417
  at: new Date().toISOString(),
@@ -526,6 +453,53 @@ async function resolveWorkspaceUuid(getApi) {
526
453
  "Run 'cargo-ai login' or set CARGO_WORKSPACE_UUID.", { code: ExitCodes.NotAuthenticated });
527
454
  }
528
455
  }
456
+ // Await `op`, routing any failure through `onError` (which never returns —
457
+ // it exits via `failWith`) so the successful result can bind to a `const`
458
+ // instead of a reassigned `let` captured out of a try/catch.
459
+ async function runOrFail(op, onError) {
460
+ try {
461
+ return await op();
462
+ }
463
+ catch (error) {
464
+ onError(error);
465
+ }
466
+ }
467
+ // --refresh: re-read the live resources and fold out-of-band drift into the
468
+ // state before planning. Returns the state to plan against — `baseState`
469
+ // unchanged when there is no actionable drift. Callers gate this behind the
470
+ // refresh flag and a non-empty state.
471
+ async function refreshedState(baseState, opts, api) {
472
+ const drift = await runOrFail(() => detectDrift(baseState, liveReaders(api())), (error) => failWith(`Refresh failed: ${message(error)}`, {
473
+ code: ExitCodes.GenericError,
474
+ }));
475
+ const modifiedExternally = drift.filter((d) => d.status === "modified");
476
+ const deletedExternally = drift.filter((d) => d.status === "deleted");
477
+ const changed = [...modifiedExternally, ...deletedExternally];
478
+ if (changed.length === 0) {
479
+ info("Refresh: no out-of-band drift.");
480
+ return baseState;
481
+ }
482
+ info(`Refresh found ${String(changed.length)} drifted resource(s):`);
483
+ for (const d of modifiedExternally) {
484
+ info(` modified externally: ${d.id}`);
485
+ }
486
+ for (const d of deletedExternally) {
487
+ info(` deleted externally: ${d.id}`);
488
+ }
489
+ // A resource present in code but deleted in the UI is ambiguous — an accident
490
+ // to restore, or an intentional removal. Don't silently recreate it; force the
491
+ // decision unless --recreate-deleted opts in.
492
+ if (deletedExternally.length > 0 && opts.recreateDeleted !== true) {
493
+ failWith(`${String(deletedExternally.length)} resource(s) were deleted outside the CDK ` +
494
+ `(${deletedExternally.map((d) => d.id).join(", ")}). Re-run with ` +
495
+ `--recreate-deleted to restore them, or remove them from your code if ` +
496
+ `the deletion was intentional.`, { code: ExitCodes.InvalidUsage });
497
+ }
498
+ // Modified always re-applies; deleted only reconciles (→ recreate) once the
499
+ // user has opted in above.
500
+ const effectiveDrift = opts.recreateDeleted === true ? drift : modifiedExternally;
501
+ return reconcileDrift(baseState, effectiveDrift);
502
+ }
529
503
  function message(error) {
530
504
  return error instanceof Error ? error.message : String(error);
531
505
  }
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ export type FromRef = {
3
+ owner: string;
4
+ repo: string;
5
+ /** Repo-relative folder to scaffold ("" = the whole repo). */
6
+ subPath: string;
7
+ /** Branch or tag ("" = the repo's default branch). */
8
+ gitRef: string;
9
+ };
10
+ /** Reject absolute paths, `..`, `.`, and empty segments in repo-relative paths. */
11
+ export declare function assertSafeRepoRelativePath(repoRelativePath: string, label: string): void;
12
+ /** Join `relativePath` under `baseDir` and ensure the result cannot escape it. */
13
+ export declare function resolveWithin(baseDir: string, relativePath: string): string;
14
+ /**
15
+ * Parse a `--from` source. Accepts `owner/repo[/sub/path][@ref]`, the same
16
+ * with a `github.com` / `git@github.com:` prefix, and GitHub tree URLs
17
+ * (`github.com/owner/repo/tree/<ref>/<sub/path>`). Returns `undefined` when
18
+ * the source doesn't name a GitHub repo.
19
+ */
20
+ export declare function parseFromRef(raw: string): FromRef | undefined;
21
+ declare const manifestSchema: z.ZodObject<{
22
+ shared: z.ZodDefault<z.ZodArray<z.ZodString>>;
23
+ folders: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
24
+ requires: z.ZodDefault<z.ZodArray<z.ZodString>>;
25
+ }, z.core.$strip>>>;
26
+ }, z.core.$strip>;
27
+ export type ScaffoldManifest = z.infer<typeof manifestSchema>;
28
+ export declare const MANIFEST_FILENAME = "cargo.scaffold.json";
29
+ /** Read and validate `cargo.scaffold.json` at the repo root, if present. */
30
+ export declare function readManifest(repoDir: string): Promise<ScaffoldManifest | undefined>;
31
+ export type CopyPlan = {
32
+ /** Repo-relative folders to copy, keeping their path under the target. */
33
+ folders: string[];
34
+ /** Repo-relative files to copy, keeping their path under the target. */
35
+ files: string[];
36
+ /** When true, the single folder's CONTENTS land at the target root. */
37
+ flatten: boolean;
38
+ };
39
+ /**
40
+ * Decide what to copy. A manifest-declared folder keeps the repo layout and
41
+ * brings its transitive `requires` plus the shared root files; anything else
42
+ * is a flat copy of the requested folder (or the whole repo).
43
+ */
44
+ export declare function planCopies(manifest: ScaffoldManifest | undefined, subPath: string): CopyPlan;
45
+ /**
46
+ * Shallow-clone the repo into a temp dir. Returns the dir and its cleanup.
47
+ * `remoteUrl` overrides the GitHub URL (tests clone from a local repo).
48
+ */
49
+ export declare function cloneGitHubRepo(ref: FromRef, remoteUrl?: string): Promise<{
50
+ dir: string;
51
+ cleanup: () => Promise<void>;
52
+ }>;
53
+ /**
54
+ * Fetch the source and copy the planned folders/files into `targetDir`,
55
+ * substituting `__APP_NAME__`. Returns the copied repo-relative paths.
56
+ */
57
+ export declare function scaffoldFromGitHub(payload: {
58
+ ref: FromRef;
59
+ targetDir: string;
60
+ appName: string;
61
+ /** Test-only override — clone from this URL/path instead of github.com. */
62
+ remoteUrl?: string;
63
+ }): Promise<string[]>;
64
+ export {};
65
+ //# sourceMappingURL=from.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/from.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAIF,mFAAmF;AACnF,wBAAgB,0BAA0B,CACxC,gBAAgB,EAAE,MAAM,EACxB,KAAK,EAAE,MAAM,GACZ,IAAI,CAeN;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAW3E;AA0BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAwD7D;AAED,QAAA,MAAM,cAAc;;;;;iBAKlB,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE9D,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAqCvD,4EAA4E;AAC5E,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAavC;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wEAAwE;IACxE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EACtC,OAAO,EAAE,MAAM,GACd,QAAQ,CAkBV;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,OAAO,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CA8BxD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsDpB"}