@cargo-ai/cdk 1.0.2 → 1.0.4
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 +65 -36
- package/build/src/cli/auth.d.ts +9 -0
- package/build/src/cli/auth.d.ts.map +1 -0
- package/build/src/cli/auth.js +100 -0
- package/build/src/cli/bin.d.ts +3 -0
- package/build/src/cli/bin.d.ts.map +1 -0
- package/build/src/cli/bin.js +9 -0
- package/build/src/cli/commands/deploy.d.ts +4 -0
- package/build/src/cli/commands/deploy.d.ts.map +1 -0
- package/build/src/cli/commands/deploy.js +531 -0
- package/build/src/cli/commands/index.d.ts +4 -0
- package/build/src/cli/commands/index.d.ts.map +1 -0
- package/build/src/cli/commands/index.js +11 -0
- package/build/src/cli/commands/init.d.ts +3 -0
- package/build/src/cli/commands/init.d.ts.map +1 -0
- package/build/src/cli/commands/init.js +76 -0
- package/build/src/cli/commands/inputTypes.d.ts +24 -0
- package/build/src/cli/commands/inputTypes.d.ts.map +1 -0
- package/build/src/cli/commands/inputTypes.js +273 -0
- package/build/src/cli/commands/templates.d.ts +6 -0
- package/build/src/cli/commands/templates.d.ts.map +1 -0
- package/build/src/cli/commands/templates.js +33 -0
- package/build/src/cli/commands/types.d.ts +4 -0
- package/build/src/cli/commands/types.d.ts.map +1 -0
- package/build/src/cli/commands/types.js +385 -0
- package/build/src/cli/deployCommands.d.ts +4 -0
- package/build/src/cli/deployCommands.d.ts.map +1 -0
- package/build/src/cli/deployCommands.js +531 -0
- package/build/src/cli/index.d.ts +4 -0
- package/build/src/cli/index.d.ts.map +1 -0
- package/build/src/cli/index.js +20 -0
- package/build/src/cli/initCommand.d.ts +3 -0
- package/build/src/cli/initCommand.d.ts.map +1 -0
- package/build/src/cli/initCommand.js +76 -0
- package/build/src/cli/inputTypes.d.ts +24 -0
- package/build/src/cli/inputTypes.d.ts.map +1 -0
- package/build/src/cli/inputTypes.js +273 -0
- package/build/src/cli/io.d.ts +38 -0
- package/build/src/cli/io.d.ts.map +1 -0
- package/build/src/cli/io.js +226 -0
- package/build/src/cli/templates.d.ts +6 -0
- package/build/src/cli/templates.d.ts.map +1 -0
- package/build/src/cli/templates.js +34 -0
- package/build/src/cli/typesCommand.d.ts +4 -0
- package/build/src/cli/typesCommand.d.ts.map +1 -0
- package/build/src/cli/typesCommand.js +385 -0
- package/build/src/cli/version.d.ts +2 -0
- package/build/src/cli/version.d.ts.map +1 -0
- package/build/src/cli/version.js +25 -0
- package/build/src/core.d.ts +1 -1
- package/build/src/core.d.ts.map +1 -1
- package/build/src/deploy/apply.d.ts +3 -0
- package/build/src/deploy/apply.d.ts.map +1 -1
- package/build/src/deploy/apply.js +2 -0
- package/build/src/deploy/destroy.d.ts.map +1 -1
- package/build/src/deploy/destroy.js +6 -0
- package/build/src/deploy/executors.live.d.ts.map +1 -1
- package/build/src/deploy/executors.live.js +49 -3
- package/build/src/deploy/readers.live.d.ts.map +1 -1
- package/build/src/deploy/readers.live.js +16 -0
- package/build/src/index.d.ts +4 -2
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +2 -1
- package/build/src/refs.d.ts +4 -1
- package/build/src/refs.d.ts.map +1 -1
- package/build/src/refs.js +5 -0
- package/build/src/resources/bundle.d.ts +4 -2
- package/build/src/resources/bundle.d.ts.map +1 -1
- package/build/src/resources/bundle.js +11 -2
- package/build/src/resources/connector.d.ts.map +1 -1
- package/build/src/resources/connector.js +3 -2
- package/build/src/resources/segment.d.ts +24 -0
- package/build/src/resources/segment.d.ts.map +1 -0
- package/build/src/resources/segment.js +34 -0
- package/build/src/resources/worker.d.ts.map +1 -1
- package/build/src/resources/worker.js +14 -7
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -4
- package/templates/blank/package.json +3 -3
- package/templates/full/README.md +21 -20
- package/templates/full/apps/dashboard/index.html +1 -1
- package/templates/full/apps/dashboard/package.json +18 -3
- package/templates/full/apps/dashboard/src/App.tsx +57 -0
- package/templates/full/apps/dashboard/src/index.css +4 -0
- package/templates/full/apps/dashboard/src/main.tsx +13 -7
- package/templates/full/apps/dashboard/tailwind.config.ts +13 -0
- package/templates/full/apps/dashboard/tsconfig.json +10 -3
- package/templates/full/apps/dashboard/vite.config.ts +22 -0
- package/templates/full/package.json +3 -3
- package/templates/full/workers/webhook/package.json +12 -1
- package/templates/full/workers/webhook/src/index.ts +53 -0
- package/templates/full/workers/webhook/tsconfig.json +14 -0
- package/templates/full/workers/webhook.ts +9 -7
- package/templates/full/apps/dashboard/package-lock.json +0 -1080
- package/templates/full/workers/webhook/index.js +0 -12
- package/templates/full/workers/webhook/package-lock.json +0 -12
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { acquireLock, appendAudit, apply, compile, destroy, detectDrift, emptyState, formatPlan, importResource, liveExecutors, liveReaders, loadResources, newRunId, plan, readState, reconcileDrift, removeResources, restoreSnapshot, snapshotState, writeState, } from "../../deploy/index.js";
|
|
2
|
+
import { getConfig } from "../auth.js";
|
|
3
|
+
import { colors, confirm, ExitCodes, failWith, info, outputJson, startSpinner, success, } from "../io.js";
|
|
4
|
+
// `plan / deploy / refresh / import / rollback / destroy` — the reconcile
|
|
5
|
+
// commands. This layer parses args, authenticates via the injected `getApi`,
|
|
6
|
+
// confirms, and prints; the engine (load / compile / apply / state) is the
|
|
7
|
+
// package's `deploy/` module.
|
|
8
|
+
export function registerDeployCommands(parent, getApi) {
|
|
9
|
+
parent
|
|
10
|
+
.command("plan")
|
|
11
|
+
.description("Compile the resource tree and diff against cargo.state.json. Offline — no API calls.")
|
|
12
|
+
.option("--dir <path>", "Repo root containing the resource files (default: cwd)")
|
|
13
|
+
.option("--json", "Output the raw plan result as JSON")
|
|
14
|
+
.action(async (opts) => {
|
|
15
|
+
// Run from the repo root so relative code paths (defineWorker/App/File)
|
|
16
|
+
// and cargo.state.json resolve against it.
|
|
17
|
+
if (opts.dir !== undefined)
|
|
18
|
+
process.chdir(opts.dir);
|
|
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
|
+
}
|
|
29
|
+
if (opts.json === true)
|
|
30
|
+
outputJson(result);
|
|
31
|
+
else
|
|
32
|
+
process.stdout.write(`${formatPlan(result)}\n`);
|
|
33
|
+
if (result.errors.length > 0)
|
|
34
|
+
process.exitCode = ExitCodes.GenericError;
|
|
35
|
+
});
|
|
36
|
+
parent
|
|
37
|
+
.command("deploy")
|
|
38
|
+
.description("Deploy the resource tree to Cargo — create/update resources and write cargo.state.json.")
|
|
39
|
+
.option("--dir <path>", "Repo root containing the resource files (default: cwd)")
|
|
40
|
+
.option("--yes", "Skip the confirmation prompt")
|
|
41
|
+
.option("--dry-run", "Show the plan without applying")
|
|
42
|
+
.option("--prune", "Delete resources tracked in state but no longer in code")
|
|
43
|
+
.option("--refresh", "Re-read live resources first and re-apply any modified out-of-band")
|
|
44
|
+
.option("--recreate-deleted", "With --refresh, also restore resources deleted outside the CDK (off by default)")
|
|
45
|
+
.option("--force", "Steal the state lock held by another run")
|
|
46
|
+
.option("--json", "Output the deploy result as JSON")
|
|
47
|
+
.action(async (opts) => {
|
|
48
|
+
// Run from the repo root so relative code paths (defineWorker/App/File)
|
|
49
|
+
// and cargo.state.json resolve against it.
|
|
50
|
+
if (opts.dir !== undefined)
|
|
51
|
+
process.chdir(opts.dir);
|
|
52
|
+
const root = ".";
|
|
53
|
+
const workspaceUuid = await resolveWorkspaceUuid(getApi);
|
|
54
|
+
const runId = newRunId();
|
|
55
|
+
// Load the resource tree ONCE (loadResources resets the registry, so it
|
|
56
|
+
// 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
|
+
}
|
|
66
|
+
// Hold the state lock for the whole read→plan→apply sequence (not just
|
|
67
|
+
// the apply), so a concurrent run can't rewrite cargo.state.json between
|
|
68
|
+
// our read and our write. --dry-run is read-only, so it skips the lock.
|
|
69
|
+
if (opts.dryRun !== true) {
|
|
70
|
+
try {
|
|
71
|
+
acquireLock(root, "deploy", opts.force === true);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
failWith(message(error), { code: ExitCodes.GenericError });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const prior = readState(root);
|
|
78
|
+
if (prior !== undefined && prior.workspaceUuid !== workspaceUuid) {
|
|
79
|
+
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
|
+
}
|
|
81
|
+
const baseState = prior ?? emptyState(workspaceUuid);
|
|
82
|
+
// Build the authenticated client lazily so a plain `--dry-run` stays
|
|
83
|
+
// offline; `--refresh` and the apply both reuse this one instance.
|
|
84
|
+
let apiClient;
|
|
85
|
+
const api = () => {
|
|
86
|
+
if (apiClient === undefined)
|
|
87
|
+
apiClient = getApi();
|
|
88
|
+
return apiClient;
|
|
89
|
+
};
|
|
90
|
+
// With --refresh, re-read the live resources and fold out-of-band drift
|
|
91
|
+
// into the state before planning: a resource edited in the UI re-applies
|
|
92
|
+
// (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
|
+
}
|
|
134
|
+
const planResult = compile({ nodes, state });
|
|
135
|
+
info(formatPlan(planResult));
|
|
136
|
+
if (planResult.errors.length > 0) {
|
|
137
|
+
failWith("Plan has errors — fix them before deploying.", {
|
|
138
|
+
code: ExitCodes.GenericError,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const deletes = planResult.plan.filter((e) => e.change === "delete");
|
|
142
|
+
const hasApplyWork = planResult.plan.some((e) => e.change === "create" || e.change === "update");
|
|
143
|
+
const willPrune = opts.prune === true && deletes.length > 0;
|
|
144
|
+
if (opts.dryRun === true) {
|
|
145
|
+
// Make the dry run reflect the --prune decision.
|
|
146
|
+
if (deletes.length > 0) {
|
|
147
|
+
info(willPrune
|
|
148
|
+
? `Would prune ${String(deletes.length)} resource(s): ${deletes.map((e) => e.id).join(", ")}`
|
|
149
|
+
: `${String(deletes.length)} resource(s) would be left in place — add --prune to remove them.`);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!hasApplyWork && !willPrune) {
|
|
154
|
+
if (deletes.length > 0) {
|
|
155
|
+
info(`${String(deletes.length)} resource(s) in state are no longer in code. Re-run with --prune to remove them.`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
success("Nothing to deploy — everything is up to date.");
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (opts.yes !== true) {
|
|
163
|
+
const ask = willPrune
|
|
164
|
+
? `Deploy to workspace ${workspaceUuid} and prune ${String(deletes.length)} resource(s)?`
|
|
165
|
+
: `Deploy to workspace ${workspaceUuid}?`;
|
|
166
|
+
const ok = await confirm(ask);
|
|
167
|
+
if (!ok)
|
|
168
|
+
failWith("Aborted.", { code: ExitCodes.GenericError });
|
|
169
|
+
}
|
|
170
|
+
// Snapshot the pre-deploy state so `cdk rollback` can restore it.
|
|
171
|
+
snapshotState(root);
|
|
172
|
+
// Live progress: a spinner naming the resource currently being applied,
|
|
173
|
+
// leaving a ✓ line behind for each completed one. Suppressed for --json
|
|
174
|
+
// so machine output on stdout stays clean.
|
|
175
|
+
const spinner = opts.json === true ? undefined : startSpinner("Deploying…");
|
|
176
|
+
const onProgress = spinner === undefined
|
|
177
|
+
? undefined
|
|
178
|
+
: (event) => {
|
|
179
|
+
if (event.phase === "start") {
|
|
180
|
+
const verb = event.change === "create" ? "Creating" : "Updating";
|
|
181
|
+
spinner.update(`${verb} ${colors.bold(event.id)} ${colors.dim(`(${event.index}/${event.total})`)}`);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const done = event.change === "create" ? "created" : "updated";
|
|
185
|
+
spinner.log(` ${colors.green("✓")} ${event.id} ${colors.dim(done)}`);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
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();
|
|
196
|
+
appendAudit(root, {
|
|
197
|
+
runId,
|
|
198
|
+
at: new Date().toISOString(),
|
|
199
|
+
command: "deploy",
|
|
200
|
+
workspaceUuid,
|
|
201
|
+
ok: false,
|
|
202
|
+
error: message(error),
|
|
203
|
+
});
|
|
204
|
+
failWith(`Deploy failed: ${message(error)}`, {
|
|
205
|
+
code: ExitCodes.GenericError,
|
|
206
|
+
extra: {
|
|
207
|
+
runId,
|
|
208
|
+
hint: "Resources created before the failure were saved to cargo.state.json — fix the error and re-run to continue.",
|
|
209
|
+
},
|
|
210
|
+
});
|
|
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) {
|
|
222
|
+
appendAudit(root, {
|
|
223
|
+
runId,
|
|
224
|
+
at: new Date().toISOString(),
|
|
225
|
+
command: "deploy",
|
|
226
|
+
workspaceUuid,
|
|
227
|
+
ok: false,
|
|
228
|
+
error: `prune: ${message(error)}`,
|
|
229
|
+
});
|
|
230
|
+
failWith(`Prune failed: ${message(error)}`, {
|
|
231
|
+
code: ExitCodes.GenericError,
|
|
232
|
+
extra: { runId },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const created = result.applied.filter((e) => e.change === "create").length;
|
|
237
|
+
const updated = result.applied.filter((e) => e.change === "update").length;
|
|
238
|
+
appendAudit(root, {
|
|
239
|
+
runId,
|
|
240
|
+
at: new Date().toISOString(),
|
|
241
|
+
command: "deploy",
|
|
242
|
+
workspaceUuid,
|
|
243
|
+
ok: true,
|
|
244
|
+
summary: {
|
|
245
|
+
created,
|
|
246
|
+
updated,
|
|
247
|
+
unchanged: result.skipped.length,
|
|
248
|
+
pruned: pruned.removed.length,
|
|
249
|
+
released: pruned.released.length,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
if (opts.json === true) {
|
|
253
|
+
outputJson({
|
|
254
|
+
created,
|
|
255
|
+
updated,
|
|
256
|
+
skipped: result.skipped.length,
|
|
257
|
+
pruned: pruned.removed.length,
|
|
258
|
+
released: pruned.released.length,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
success(`${String(created)} created, ${String(updated)} updated, ${String(result.skipped.length)} unchanged.`);
|
|
263
|
+
if (pruned.removed.length > 0 || pruned.released.length > 0) {
|
|
264
|
+
info(`Pruned ${String(pruned.removed.length)}${pruned.released.length > 0 ? ` (released ${String(pruned.released.length)} adopted)` : ""}.`);
|
|
265
|
+
}
|
|
266
|
+
else if (deletes.length > 0) {
|
|
267
|
+
info(`${String(deletes.length)} resource(s) no longer in code — re-run with --prune to remove them.`);
|
|
268
|
+
}
|
|
269
|
+
// First deploy: nudge the user to commit state and ignore the lock.
|
|
270
|
+
if (prior === undefined) {
|
|
271
|
+
info("Wrote cargo.state.json — commit it (it links your code to deployed resources). Add cargo.state.lock to .gitignore.");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
parent
|
|
276
|
+
.command("refresh")
|
|
277
|
+
.description("Re-read live resources and report drift from the last deploy (read-only).")
|
|
278
|
+
.option("--dir <path>", "Repo root (default: cwd)")
|
|
279
|
+
.option("--json", "Output the drift report as JSON")
|
|
280
|
+
.action(async (opts) => {
|
|
281
|
+
if (opts.dir !== undefined)
|
|
282
|
+
process.chdir(opts.dir);
|
|
283
|
+
const root = ".";
|
|
284
|
+
const workspaceUuid = await resolveWorkspaceUuid(getApi);
|
|
285
|
+
const state = readState(root);
|
|
286
|
+
if (state === undefined || Object.keys(state.resources).length === 0) {
|
|
287
|
+
info("No state — nothing to refresh.");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// Reading another workspace's state against the selected one would report
|
|
291
|
+
// everything as deleted — refuse rather than print misleading drift.
|
|
292
|
+
if (state.workspaceUuid !== workspaceUuid) {
|
|
293
|
+
failWith(`cargo.state.json belongs to workspace ${state.workspaceUuid}, not the selected ${workspaceUuid}.`, { code: ExitCodes.InvalidUsage });
|
|
294
|
+
}
|
|
295
|
+
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
|
+
}
|
|
305
|
+
if (opts.json === true) {
|
|
306
|
+
outputJson(drift);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const drifted = drift.filter((d) => d.status === "modified" || d.status === "deleted");
|
|
310
|
+
if (drifted.length === 0) {
|
|
311
|
+
success(`In sync — ${String(drift.length)} resource(s) match the last deploy.`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
info(`${String(drifted.length)} resource(s) drifted from code:`);
|
|
315
|
+
for (const d of drifted) {
|
|
316
|
+
info(` ${d.status === "deleted" ? "deleted externally" : "modified externally"}: ${d.id}`);
|
|
317
|
+
}
|
|
318
|
+
info("Run 'cdk deploy --refresh' to re-apply code over the drift.");
|
|
319
|
+
});
|
|
320
|
+
parent
|
|
321
|
+
.command("import")
|
|
322
|
+
.argument("<id>", "Code resource id to bind (e.g. agent:sdr)")
|
|
323
|
+
.argument("<uuid>", "The live resource's uuid in the workspace")
|
|
324
|
+
.description("Bind an existing live resource to a code resource in cargo.state.json.")
|
|
325
|
+
.option("--dir <path>", "Repo root (default: cwd)")
|
|
326
|
+
.option("--force", "Steal the state lock held by another run")
|
|
327
|
+
.option("--json", "Output the result as JSON")
|
|
328
|
+
.action(async (id, uuid, opts) => {
|
|
329
|
+
if (opts.dir !== undefined)
|
|
330
|
+
process.chdir(opts.dir);
|
|
331
|
+
const root = ".";
|
|
332
|
+
const workspaceUuid = await resolveWorkspaceUuid(getApi);
|
|
333
|
+
const api = getApi();
|
|
334
|
+
const runId = newRunId();
|
|
335
|
+
try {
|
|
336
|
+
acquireLock(root, "import", opts.force === true);
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
failWith(message(error), { code: ExitCodes.GenericError });
|
|
340
|
+
}
|
|
341
|
+
snapshotState(root); // enable `cdk rollback`
|
|
342
|
+
let result;
|
|
343
|
+
try {
|
|
344
|
+
result = await importResource(root, api, workspaceUuid, id, uuid);
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
appendAudit(root, {
|
|
348
|
+
runId,
|
|
349
|
+
at: new Date().toISOString(),
|
|
350
|
+
command: "import",
|
|
351
|
+
workspaceUuid,
|
|
352
|
+
ok: false,
|
|
353
|
+
error: message(error),
|
|
354
|
+
});
|
|
355
|
+
failWith(`Import failed: ${message(error)}`, {
|
|
356
|
+
code: ExitCodes.GenericError,
|
|
357
|
+
extra: { runId },
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
appendAudit(root, {
|
|
361
|
+
runId,
|
|
362
|
+
at: new Date().toISOString(),
|
|
363
|
+
command: "import",
|
|
364
|
+
workspaceUuid,
|
|
365
|
+
ok: true,
|
|
366
|
+
summary: { id, uuid, kind: result.kind },
|
|
367
|
+
});
|
|
368
|
+
if (opts.json === true)
|
|
369
|
+
outputJson(result);
|
|
370
|
+
else
|
|
371
|
+
success(`Imported ${id} → ${uuid} (${result.kind}).`);
|
|
372
|
+
});
|
|
373
|
+
parent
|
|
374
|
+
.command("rollback")
|
|
375
|
+
.description("Restore cargo.state.json from the snapshot taken before the last deploy/destroy/import.")
|
|
376
|
+
.option("--dir <path>", "Repo root (default: cwd)")
|
|
377
|
+
.option("--yes", "Skip the confirmation prompt")
|
|
378
|
+
.option("--force", "Steal the state lock held by another run")
|
|
379
|
+
.action(async (opts) => {
|
|
380
|
+
if (opts.dir !== undefined)
|
|
381
|
+
process.chdir(opts.dir);
|
|
382
|
+
const root = ".";
|
|
383
|
+
const workspaceUuid = await resolveWorkspaceUuid(getApi);
|
|
384
|
+
const runId = newRunId();
|
|
385
|
+
if (opts.yes !== true) {
|
|
386
|
+
const ok = await confirm("Restore cargo.state.json from the last snapshot? (restores the state file only — not live resources)");
|
|
387
|
+
if (!ok)
|
|
388
|
+
failWith("Aborted.", { code: ExitCodes.GenericError });
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
acquireLock(root, "rollback", opts.force === true);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
failWith(message(error), { code: ExitCodes.GenericError });
|
|
395
|
+
}
|
|
396
|
+
if (!restoreSnapshot(root)) {
|
|
397
|
+
failWith("No snapshot found (cargo.state.bak.json) — nothing to roll back.", { code: ExitCodes.InvalidUsage });
|
|
398
|
+
}
|
|
399
|
+
appendAudit(root, {
|
|
400
|
+
runId,
|
|
401
|
+
at: new Date().toISOString(),
|
|
402
|
+
command: "rollback",
|
|
403
|
+
workspaceUuid,
|
|
404
|
+
ok: true,
|
|
405
|
+
});
|
|
406
|
+
success("Restored cargo.state.json from the snapshot. Run 'cargo-ai cdk deploy --refresh' to reconcile live resources to it.");
|
|
407
|
+
});
|
|
408
|
+
parent
|
|
409
|
+
.command("destroy")
|
|
410
|
+
.description("Tear down resources recorded in cargo.state.json (deletes by uuid).")
|
|
411
|
+
.option("--dir <path>", "Repo root (default: cwd)")
|
|
412
|
+
.option("--yes", "Skip the confirmation prompt")
|
|
413
|
+
.option("--target <id>", "Only destroy this resource id (e.g. play:welcome)")
|
|
414
|
+
.option("--all", "Destroy every state-tracked resource (required for blanket teardown)")
|
|
415
|
+
.option("--force", "Steal the state lock held by another run")
|
|
416
|
+
.option("--json", "Output the result as JSON")
|
|
417
|
+
.action(async (opts) => {
|
|
418
|
+
// Run from the repo root so relative code paths (defineWorker/App/File)
|
|
419
|
+
// and cargo.state.json resolve against it.
|
|
420
|
+
if (opts.dir !== undefined)
|
|
421
|
+
process.chdir(opts.dir);
|
|
422
|
+
const root = ".";
|
|
423
|
+
const api = getApi();
|
|
424
|
+
const workspaceUuid = await resolveWorkspaceUuid(getApi);
|
|
425
|
+
const runId = newRunId();
|
|
426
|
+
// Blanket teardown must be explicit — `--yes` alone can't nuke a whole
|
|
427
|
+
// workspace; you must opt in with --target <id> or --all.
|
|
428
|
+
if (opts.target === undefined && opts.all !== true) {
|
|
429
|
+
failWith("Specify --target <id> to remove one resource, or --all to remove everything in state.", { code: ExitCodes.InvalidUsage });
|
|
430
|
+
}
|
|
431
|
+
// Lock before reading state so the read→delete sequence is consistent.
|
|
432
|
+
try {
|
|
433
|
+
acquireLock(root, "destroy", opts.force === true);
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
failWith(message(error), { code: ExitCodes.GenericError });
|
|
437
|
+
}
|
|
438
|
+
const state = readState(root);
|
|
439
|
+
if (state === undefined || Object.keys(state.resources).length === 0) {
|
|
440
|
+
info("Nothing to destroy — no state.");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (state.workspaceUuid !== workspaceUuid) {
|
|
444
|
+
failWith(`cargo.state.json belongs to workspace ${state.workspaceUuid}, not the selected ${workspaceUuid}.`, { code: ExitCodes.InvalidUsage });
|
|
445
|
+
}
|
|
446
|
+
const ids = opts.target !== undefined
|
|
447
|
+
? opts.target in state.resources
|
|
448
|
+
? [opts.target]
|
|
449
|
+
: []
|
|
450
|
+
: Object.keys(state.resources);
|
|
451
|
+
if (ids.length === 0) {
|
|
452
|
+
// An explicit --target that matches nothing is a usage error, not a
|
|
453
|
+
// success — a scripted `destroy --target X && deploy` must not believe
|
|
454
|
+
// X was torn down when the id was simply mistyped.
|
|
455
|
+
failWith(`"${String(opts.target)}" is not in state — nothing to destroy. Run \`cargo-ai cdk plan\` to see the tracked ids.`, { code: ExitCodes.InvalidUsage });
|
|
456
|
+
}
|
|
457
|
+
// Always show exactly what will be deleted, even with --yes.
|
|
458
|
+
info(`About to remove ${String(ids.length)} resource(s) from workspace ${workspaceUuid}:`);
|
|
459
|
+
for (const id of ids)
|
|
460
|
+
info(` - ${id}`);
|
|
461
|
+
if (opts.yes !== true) {
|
|
462
|
+
const ok = await confirm(`Delete these ${String(ids.length)} resource(s)?`);
|
|
463
|
+
if (!ok)
|
|
464
|
+
failWith("Aborted.", { code: ExitCodes.GenericError });
|
|
465
|
+
}
|
|
466
|
+
snapshotState(root); // enable `cdk rollback`
|
|
467
|
+
let result;
|
|
468
|
+
try {
|
|
469
|
+
result = await destroy(root, api, { target: opts.target });
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
appendAudit(root, {
|
|
473
|
+
runId,
|
|
474
|
+
at: new Date().toISOString(),
|
|
475
|
+
command: "destroy",
|
|
476
|
+
workspaceUuid,
|
|
477
|
+
ok: false,
|
|
478
|
+
error: message(error),
|
|
479
|
+
});
|
|
480
|
+
failWith(`Destroy failed: ${message(error)}`, {
|
|
481
|
+
code: ExitCodes.GenericError,
|
|
482
|
+
extra: {
|
|
483
|
+
runId,
|
|
484
|
+
hint: "Resources removed before the failure are gone and dropped from state — re-run to remove the rest.",
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
appendAudit(root, {
|
|
489
|
+
runId,
|
|
490
|
+
at: new Date().toISOString(),
|
|
491
|
+
command: "destroy",
|
|
492
|
+
workspaceUuid,
|
|
493
|
+
ok: true,
|
|
494
|
+
summary: {
|
|
495
|
+
removed: result.removed.length,
|
|
496
|
+
released: result.released.length,
|
|
497
|
+
ids: result.removed,
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
if (opts.json === true)
|
|
501
|
+
outputJson(result);
|
|
502
|
+
else {
|
|
503
|
+
success(result.removed.length === 0
|
|
504
|
+
? "Nothing removed."
|
|
505
|
+
: `Removed ${String(result.removed.length)}: ${result.removed.join(", ")}`);
|
|
506
|
+
if (result.released.length > 0) {
|
|
507
|
+
info(`Released ${String(result.released.length)} adopted (left in place): ${result.released.join(", ")}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
// Resolve the workspace to operate on. Prefers an explicit `CARGO_WORKSPACE_UUID`
|
|
513
|
+
// / credentials-file workspace; otherwise falls back to the workspace the token
|
|
514
|
+
// is bound to (the same lookup `cargo-ai whoami` uses), so a workspace-scoped
|
|
515
|
+
// token "just works" without setting an env var.
|
|
516
|
+
async function resolveWorkspaceUuid(getApi) {
|
|
517
|
+
const { workspaceUuid } = getConfig();
|
|
518
|
+
if (workspaceUuid !== undefined)
|
|
519
|
+
return workspaceUuid;
|
|
520
|
+
try {
|
|
521
|
+
const { workspace } = await getApi().workspaceManagement.workspace.getCurrent();
|
|
522
|
+
return workspace.uuid;
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
failWith(`No workspace selected and couldn't resolve one from your credentials (${message(error)}). ` +
|
|
526
|
+
"Run 'cargo-ai login' or set CARGO_WORKSPACE_UUID.", { code: ExitCodes.NotAuthenticated });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function message(error) {
|
|
530
|
+
return error instanceof Error ? error.message : String(error);
|
|
531
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CAI1E"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerDeployCommands } from "./deploy.js";
|
|
2
|
+
import { registerInitCommand } from "./init.js";
|
|
3
|
+
import { registerTypesCommand } from "./types.js";
|
|
4
|
+
// Attach the cdk commands onto `program` — the root program for the standalone
|
|
5
|
+
// `cargo-cdk` bin, or a `cdk` subcommand group created by @cargo-ai/cli. Kept
|
|
6
|
+
// program-agnostic so both mount it the same way.
|
|
7
|
+
export function registerCommands(program, getApi) {
|
|
8
|
+
registerInitCommand(program);
|
|
9
|
+
registerTypesCommand(program, getApi);
|
|
10
|
+
registerDeployCommands(program, getApi);
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAezC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAyEzD"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { ExitCodes, failWith, info, success } from "../io.js";
|
|
5
|
+
import { copyDirectory, listTemplates } from "./templates.js";
|
|
6
|
+
// `cdk init <directory> [--template <slug>]` — scaffold a starter CDK project by
|
|
7
|
+
// copying one of `@cargo-ai/cdk`'s bundled templates (see `--list-templates`),
|
|
8
|
+
// substituting `__APP_NAME__`.
|
|
9
|
+
const TEMPLATE_DESCRIPTIONS = {
|
|
10
|
+
blank: "Minimal starter — one connector + model + a workflow-backed tool. Good starting point.",
|
|
11
|
+
full: "The full example — every resource type wired into a GTM growth workspace: connectors, models, plays, agents, an MCP server, context, tools, a worker, and a hosted app.",
|
|
12
|
+
};
|
|
13
|
+
export function registerInitCommand(parent) {
|
|
14
|
+
parent
|
|
15
|
+
.command("init <directory>")
|
|
16
|
+
.description("Scaffold a starter Cargo CDK project locally from a template (ready to plan/deploy).")
|
|
17
|
+
.option("--template <slug>", "Template slug (default: blank)", "blank")
|
|
18
|
+
.option("--name <name>", "Project name written into package.json")
|
|
19
|
+
.option("--list-templates", "Print available templates and exit")
|
|
20
|
+
.option("--force", "Write into a non-empty directory")
|
|
21
|
+
.action(async (directory, opts) => {
|
|
22
|
+
const templatesRoot = findTemplatesRoot();
|
|
23
|
+
if (opts.listTemplates === true) {
|
|
24
|
+
const slugs = await listTemplates(templatesRoot);
|
|
25
|
+
for (const slug of slugs) {
|
|
26
|
+
const desc = TEMPLATE_DESCRIPTIONS[slug] ?? "";
|
|
27
|
+
info(`${slug}${desc.length > 0 ? ` — ${desc}` : ""}`);
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const templateDir = join(templatesRoot, opts.template);
|
|
32
|
+
if (!existsSync(templateDir)) {
|
|
33
|
+
const slugs = await listTemplates(templatesRoot);
|
|
34
|
+
failWith(`Unknown template "${opts.template}". Available: ${slugs.join(", ")}`, { code: ExitCodes.GenericError });
|
|
35
|
+
}
|
|
36
|
+
const targetDir = resolve(process.cwd(), directory);
|
|
37
|
+
if (existsSync(targetDir) &&
|
|
38
|
+
readdirSync(targetDir).length > 0 &&
|
|
39
|
+
opts.force !== true) {
|
|
40
|
+
failWith(`Directory ${directory} is not empty. Pass --force to scaffold into it anyway.`, { code: ExitCodes.GenericError });
|
|
41
|
+
}
|
|
42
|
+
const appName = opts.name !== undefined ? opts.name : basename(targetDir);
|
|
43
|
+
await copyDirectory(templateDir, targetDir, [
|
|
44
|
+
{ from: "__APP_NAME__", to: appName },
|
|
45
|
+
]);
|
|
46
|
+
success(`Scaffolded ${directory} from the "${opts.template}" template`);
|
|
47
|
+
info(``);
|
|
48
|
+
info([
|
|
49
|
+
`Next steps:`,
|
|
50
|
+
` cd ${relative(process.cwd(), targetDir)}`,
|
|
51
|
+
` npm install`,
|
|
52
|
+
` cargo-ai login # authenticate (or: export CARGO_API_TOKEN=<token>)`,
|
|
53
|
+
` npm run types # generate typed connector/model config`,
|
|
54
|
+
` npm run plan # preview the resource tree`,
|
|
55
|
+
` npm run deploy # create it in the workspace`,
|
|
56
|
+
].join("\n"));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Templates ship inside this package at `<pkg-root>/templates`. This module runs
|
|
60
|
+
// from `<pkg-root>/build/src/cli/`, so walk up until we find a `templates/` dir
|
|
61
|
+
// that holds the `blank` template (the sentinel that distinguishes ours from any
|
|
62
|
+
// unrelated `templates/` dir along the way). Works both from the monorepo build
|
|
63
|
+
// output and an installed `node_modules/@cargo-ai/cdk`.
|
|
64
|
+
function findTemplatesRoot() {
|
|
65
|
+
let cursor = dirname(fileURLToPath(import.meta.url));
|
|
66
|
+
for (let i = 0; i < 8; i += 1) {
|
|
67
|
+
const candidate = join(cursor, "templates");
|
|
68
|
+
if (existsSync(join(candidate, "blank")))
|
|
69
|
+
return candidate;
|
|
70
|
+
const parent = dirname(cursor);
|
|
71
|
+
if (parent === cursor)
|
|
72
|
+
break;
|
|
73
|
+
cursor = parent;
|
|
74
|
+
}
|
|
75
|
+
throw new Error("Could not locate the @cargo-ai/cdk templates directory. Reinstall @cargo-ai/cdk if this persists.");
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed input shape of every agent node. Matches the SDK's `AgentInput`
|
|
3
|
+
* type and the engine's `AiUtils.agentConfig` schema.
|
|
4
|
+
*/
|
|
5
|
+
export declare const AGENT_INPUT_TYPE_SRC: string;
|
|
6
|
+
/**
|
|
7
|
+
* Print an integration action's JSON Schema config as a TS input type.
|
|
8
|
+
* Returns `"Record<string, unknown>"` when the schema is missing or not an
|
|
9
|
+
* object schema we can render.
|
|
10
|
+
*/
|
|
11
|
+
export declare function printJsonSchemaInput(schema: unknown): string;
|
|
12
|
+
/**
|
|
13
|
+
* Like {@link printJsonSchemaInput} but WITHOUT the top-level `Ref<T> | T`
|
|
14
|
+
* widening — for CDK connector/model config types, which are plain data (no
|
|
15
|
+
* workflow builder Refs). Used by `cargo-ai cdk types`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function printJsonSchemaType(schema: unknown): string;
|
|
18
|
+
/**
|
|
19
|
+
* Print a tool release's `formFields` as a TS input type. Returns
|
|
20
|
+
* `undefined` when the fields can't be interpreted (caller falls back to
|
|
21
|
+
* `Record<string, unknown>`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function printFormFieldsInput(formFields: unknown): string | undefined;
|
|
24
|
+
//# sourceMappingURL=inputTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inputTypes.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/inputTypes.ts"],"names":[],"mappings":"AAmCA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAE+D,CAAC;AAEjG;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAM5D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAQ3D;AAkMD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAU5E"}
|