@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.
- package/README.md +4 -44
- package/build/src/cli/auth.d.ts.map +1 -1
- package/build/src/cli/auth.js +16 -12
- package/build/src/cli/commands/deploy.d.ts.map +1 -1
- package/build/src/cli/commands/deploy.js +81 -107
- package/build/src/cli/commands/from.d.ts +65 -0
- package/build/src/cli/commands/from.d.ts.map +1 -0
- package/build/src/cli/commands/from.js +285 -0
- package/build/src/cli/commands/init.d.ts +1 -1
- package/build/src/cli/commands/init.d.ts.map +1 -1
- package/build/src/cli/commands/init.js +44 -20
- package/build/src/cli/commands/inputTypes.d.ts.map +1 -1
- package/build/src/cli/commands/inputTypes.js +77 -6
- package/build/src/cli/commands/types.d.ts.map +1 -1
- package/build/src/cli/commands/types.js +87 -9
- package/build/src/cli/io.js +2 -2
- package/build/src/cli/version.d.ts.map +1 -1
- package/build/src/cli/version.js +10 -6
- package/build/src/deploy/apply.d.ts.map +1 -1
- package/build/src/deploy/apply.js +37 -30
- package/build/src/deploy/compile.d.ts.map +1 -1
- package/build/src/deploy/compile.js +7 -5
- package/build/src/deploy/destroy.d.ts.map +1 -1
- package/build/src/deploy/destroy.js +24 -14
- package/build/src/deploy/executors.live.d.ts.map +1 -1
- package/build/src/deploy/executors.live.js +202 -173
- package/build/src/deploy/import.js +2 -2
- package/build/src/deploy/index.js +1 -1
- package/build/src/deploy/lock.d.ts.map +1 -1
- package/build/src/deploy/lock.js +13 -10
- package/build/src/deploy/readers.live.d.ts.map +1 -1
- package/build/src/deploy/readers.live.js +27 -15
- package/build/src/deploy/refresh.d.ts.map +1 -1
- package/build/src/deploy/refresh.js +20 -18
- package/build/src/index.d.ts +4 -4
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +2 -2
- package/build/src/refs.d.ts.map +1 -1
- package/build/src/refs.js +5 -1
- package/build/src/resources/actions.d.ts +1 -1
- package/build/src/resources/actions.d.ts.map +1 -1
- package/build/src/resources/actions.js +3 -2
- package/build/src/resources/agent.d.ts +23 -0
- package/build/src/resources/agent.d.ts.map +1 -1
- package/build/src/resources/agent.js +17 -0
- package/build/src/resources/capacity.d.ts +11 -0
- package/build/src/resources/capacity.d.ts.map +1 -1
- package/build/src/resources/capacity.js +81 -0
- package/build/src/resources/model.d.ts +11 -3
- package/build/src/resources/model.d.ts.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- 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,
|
|
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"}
|
package/build/src/cli/auth.js
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
394
|
+
if (ok === false)
|
|
464
395
|
failWith("Aborted.", { code: ExitCodes.GenericError });
|
|
465
396
|
}
|
|
466
397
|
snapshotState(root); // enable `cdk rollback`
|
|
467
|
-
|
|
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"}
|