@cargo-ai/cdk 0.1.1
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 +187 -0
- package/build/src/core.d.ts +83 -0
- package/build/src/core.d.ts.map +1 -0
- package/build/src/core.js +205 -0
- package/build/src/deploy/apply.d.ts +55 -0
- package/build/src/deploy/apply.d.ts.map +1 -0
- package/build/src/deploy/apply.js +219 -0
- package/build/src/deploy/audit.d.ts +14 -0
- package/build/src/deploy/audit.d.ts.map +1 -0
- package/build/src/deploy/audit.js +25 -0
- package/build/src/deploy/compile.d.ts +36 -0
- package/build/src/deploy/compile.d.ts.map +1 -0
- package/build/src/deploy/compile.js +99 -0
- package/build/src/deploy/destroy.d.ts +20 -0
- package/build/src/deploy/destroy.d.ts.map +1 -0
- package/build/src/deploy/destroy.js +153 -0
- package/build/src/deploy/executors.live.d.ts +4 -0
- package/build/src/deploy/executors.live.d.ts.map +1 -0
- package/build/src/deploy/executors.live.js +623 -0
- package/build/src/deploy/import.d.ts +13 -0
- package/build/src/deploy/import.d.ts.map +1 -0
- package/build/src/deploy/import.js +87 -0
- package/build/src/deploy/index.d.ts +39 -0
- package/build/src/deploy/index.d.ts.map +1 -0
- package/build/src/deploy/index.js +51 -0
- package/build/src/deploy/load.d.ts +7 -0
- package/build/src/deploy/load.d.ts.map +1 -0
- package/build/src/deploy/load.js +58 -0
- package/build/src/deploy/lock.d.ts +10 -0
- package/build/src/deploy/lock.d.ts.map +1 -0
- package/build/src/deploy/lock.js +101 -0
- package/build/src/deploy/plan.d.ts +3 -0
- package/build/src/deploy/plan.d.ts.map +1 -0
- package/build/src/deploy/plan.js +39 -0
- package/build/src/deploy/readers.live.d.ts +4 -0
- package/build/src/deploy/readers.live.d.ts.map +1 -0
- package/build/src/deploy/readers.live.js +273 -0
- package/build/src/deploy/refresh.d.ts +32 -0
- package/build/src/deploy/refresh.d.ts.map +1 -0
- package/build/src/deploy/refresh.js +76 -0
- package/build/src/deploy/state.d.ts +23 -0
- package/build/src/deploy/state.d.ts.map +1 -0
- package/build/src/deploy/state.js +107 -0
- package/build/src/index.d.ts +30 -0
- package/build/src/index.d.ts.map +1 -0
- package/build/src/index.js +30 -0
- package/build/src/refs.d.ts +28 -0
- package/build/src/refs.d.ts.map +1 -0
- package/build/src/refs.js +34 -0
- package/build/src/resources/actions.d.ts +31 -0
- package/build/src/resources/actions.d.ts.map +1 -0
- package/build/src/resources/actions.js +44 -0
- package/build/src/resources/agent.d.ts +71 -0
- package/build/src/resources/agent.d.ts.map +1 -0
- package/build/src/resources/agent.js +96 -0
- package/build/src/resources/app.d.ts +18 -0
- package/build/src/resources/app.d.ts.map +1 -0
- package/build/src/resources/app.js +33 -0
- package/build/src/resources/bundle.d.ts +15 -0
- package/build/src/resources/bundle.d.ts.map +1 -0
- package/build/src/resources/bundle.js +61 -0
- package/build/src/resources/connector.d.ts +30 -0
- package/build/src/resources/connector.d.ts.map +1 -0
- package/build/src/resources/connector.js +41 -0
- package/build/src/resources/context.d.ts +15 -0
- package/build/src/resources/context.d.ts.map +1 -0
- package/build/src/resources/context.js +58 -0
- package/build/src/resources/file.d.ts +15 -0
- package/build/src/resources/file.d.ts.map +1 -0
- package/build/src/resources/file.js +35 -0
- package/build/src/resources/folder.d.ts +20 -0
- package/build/src/resources/folder.d.ts.map +1 -0
- package/build/src/resources/folder.js +28 -0
- package/build/src/resources/mcpServer.d.ts +25 -0
- package/build/src/resources/mcpServer.d.ts.map +1 -0
- package/build/src/resources/mcpServer.js +34 -0
- package/build/src/resources/model.d.ts +34 -0
- package/build/src/resources/model.d.ts.map +1 -0
- package/build/src/resources/model.js +43 -0
- package/build/src/resources/play.d.ts +35 -0
- package/build/src/resources/play.d.ts.map +1 -0
- package/build/src/resources/play.js +49 -0
- package/build/src/resources/tool.d.ts +40 -0
- package/build/src/resources/tool.d.ts.map +1 -0
- package/build/src/resources/tool.js +36 -0
- package/build/src/resources/worker.d.ts +18 -0
- package/build/src/resources/worker.d.ts.map +1 -0
- package/build/src/resources/worker.js +40 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +71 -0
- package/templates/blank/README.md +24 -0
- package/templates/blank/cargo.ts +44 -0
- package/templates/blank/package.json +17 -0
- package/templates/blank/tsconfig.json +14 -0
- package/templates/full/README.md +69 -0
- package/templates/full/agents/enricher.ts +13 -0
- package/templates/full/agents/sdr.ts +28 -0
- package/templates/full/apps/dashboard/index.html +12 -0
- package/templates/full/apps/dashboard/package-lock.json +1080 -0
- package/templates/full/apps/dashboard/package.json +15 -0
- package/templates/full/apps/dashboard/src/main.tsx +12 -0
- package/templates/full/apps/dashboard/tsconfig.json +13 -0
- package/templates/full/apps/dashboard.ts +9 -0
- package/templates/full/connectors/hubspot.ts +12 -0
- package/templates/full/connectors/openai.ts +9 -0
- package/templates/full/context/context.ts +7 -0
- package/templates/full/context/icp.md +22 -0
- package/templates/full/context/personas/head-of-growth.md +26 -0
- package/templates/full/files/playbook.md +19 -0
- package/templates/full/files/playbook.ts +8 -0
- package/templates/full/folders/crm.ts +14 -0
- package/templates/full/mcp/crm.ts +15 -0
- package/templates/full/models/contacts.ts +15 -0
- package/templates/full/package.json +17 -0
- package/templates/full/plays/onboarding.ts +41 -0
- package/templates/full/tools/enrich.ts +34 -0
- package/templates/full/tsconfig.json +17 -0
- package/templates/full/workers/webhook/index.js +12 -0
- package/templates/full/workers/webhook/manifest.json +1 -0
- package/templates/full/workers/webhook/package-lock.json +12 -0
- package/templates/full/workers/webhook/package.json +6 -0
- package/templates/full/workers/webhook.ts +13 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// `cdk import` — bind an existing live resource to a code resource by writing a
|
|
2
|
+
// state entry (kind:slug → uuid + outputs + live fingerprint). Recovers the
|
|
3
|
+
// link to uuid-only resources (play/agent/tool/file/mcpServer) when the state
|
|
4
|
+
// file is lost, or brings already-existing infra under CDK management.
|
|
5
|
+
//
|
|
6
|
+
// The entry's hash is the resource's CURRENT code hash, so a subsequent plain
|
|
7
|
+
// deploy treats it as up to date (no rebuild). Future edits — in code or out of
|
|
8
|
+
// band (via `refresh`) — diff against it normally.
|
|
9
|
+
import { compile } from "./compile.js";
|
|
10
|
+
import { loadResources } from "./load.js";
|
|
11
|
+
import { liveReaders } from "./readers.live.js";
|
|
12
|
+
import { emptyState, readState, writeState } from "./state.js";
|
|
13
|
+
// Reconstruct the outputs an apply would have produced for this kind, so tokens
|
|
14
|
+
// its dependents reference (connector.datasetUuid, tool/play.workflowUuid,
|
|
15
|
+
// worker/app.url) still resolve.
|
|
16
|
+
async function importOutputs(api, kind, uuid) {
|
|
17
|
+
const outputs = { uuid };
|
|
18
|
+
if (kind === "connector") {
|
|
19
|
+
const { datasets } = await api.storage.dataset.all();
|
|
20
|
+
const match = datasets.find((d) => d["kind"] === "connector" && d["connectorUuid"] === uuid);
|
|
21
|
+
if (match !== undefined)
|
|
22
|
+
outputs["datasetUuid"] = match["uuid"];
|
|
23
|
+
}
|
|
24
|
+
else if (kind === "play") {
|
|
25
|
+
const { plays } = await api.orchestration.play.list();
|
|
26
|
+
const play = plays.find((p) => p["uuid"] === uuid);
|
|
27
|
+
const wf = play === undefined ? undefined : play["workflowUuid"];
|
|
28
|
+
if (typeof wf === "string")
|
|
29
|
+
outputs["workflowUuid"] = wf;
|
|
30
|
+
}
|
|
31
|
+
else if (kind === "tool") {
|
|
32
|
+
const { tools } = await api.orchestration.tool.all();
|
|
33
|
+
const tool = tools.find((t) => t["uuid"] === uuid);
|
|
34
|
+
const wf = tool === undefined ? undefined : tool["workflowUuid"];
|
|
35
|
+
if (typeof wf === "string")
|
|
36
|
+
outputs["workflowUuid"] = wf;
|
|
37
|
+
}
|
|
38
|
+
else if (kind === "worker") {
|
|
39
|
+
const { worker } = await api.hosting.worker.get(uuid);
|
|
40
|
+
const url = worker["url"];
|
|
41
|
+
if (typeof url === "string" && url !== "")
|
|
42
|
+
outputs["url"] = url;
|
|
43
|
+
}
|
|
44
|
+
else if (kind === "app") {
|
|
45
|
+
const { app } = await api.hosting.app.get(uuid);
|
|
46
|
+
const url = app["url"];
|
|
47
|
+
if (typeof url === "string" && url !== "")
|
|
48
|
+
outputs["url"] = url;
|
|
49
|
+
}
|
|
50
|
+
return outputs;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Import the live resource `uuid` as the code resource `id` (e.g. "agent:sdr").
|
|
54
|
+
* Loads the repo to confirm the id exists and capture its current hash, verifies
|
|
55
|
+
* the live resource is reachable, then writes/overwrites its state entry.
|
|
56
|
+
*/
|
|
57
|
+
export async function importResource(root, api, workspaceUuid, id, uuid,
|
|
58
|
+
// The repo's resources. Defaults to loading them from `root`; injectable so
|
|
59
|
+
// the import logic is testable without the tsx file loader.
|
|
60
|
+
nodes) {
|
|
61
|
+
const resolved = nodes ?? (await loadResources(root));
|
|
62
|
+
const entry = compile({ nodes: resolved }).plan.find((p) => p.id === id);
|
|
63
|
+
if (entry === undefined) {
|
|
64
|
+
throw new Error(`no resource "${id}" found in code — import targets a code resource id like "agent:sdr".`);
|
|
65
|
+
}
|
|
66
|
+
const kind = entry.kind;
|
|
67
|
+
const outputs = await importOutputs(api, kind, uuid);
|
|
68
|
+
// Imported resources are treated as adopted: a later `destroy` releases them
|
|
69
|
+
// (drops from state) rather than deleting infra the CDK didn't create.
|
|
70
|
+
outputs["adopted"] = "true";
|
|
71
|
+
const stateEntry = { hash: entry.hash, uuid, outputs };
|
|
72
|
+
// Confirm it's actually there (and capture a drift baseline).
|
|
73
|
+
const live = await liveReaders(api)[kind](stateEntry);
|
|
74
|
+
if (!live.exists) {
|
|
75
|
+
throw new Error(`${kind} ${uuid} was not found in the workspace — nothing to import.`);
|
|
76
|
+
}
|
|
77
|
+
if (live.fingerprint !== undefined)
|
|
78
|
+
stateEntry.live = live.fingerprint;
|
|
79
|
+
const prior = readState(root);
|
|
80
|
+
const state = prior !== undefined ? prior : emptyState(workspaceUuid);
|
|
81
|
+
if (prior !== undefined && prior.workspaceUuid !== workspaceUuid) {
|
|
82
|
+
throw new Error(`cargo.state.json belongs to workspace ${prior.workspaceUuid}, not the selected ${workspaceUuid}.`);
|
|
83
|
+
}
|
|
84
|
+
state.resources[id] = stateEntry;
|
|
85
|
+
writeState(root, state);
|
|
86
|
+
return { kind, outputs };
|
|
87
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type Executors } from "./apply.js";
|
|
2
|
+
import { type CompileResult, type State } from "./compile.js";
|
|
3
|
+
export type { ApplyProgress, ApplyResult, Executors, Outputs, } from "./apply.js";
|
|
4
|
+
export { apply } from "./apply.js";
|
|
5
|
+
export type { AuditEntry } from "./audit.js";
|
|
6
|
+
export { appendAudit, newRunId } from "./audit.js";
|
|
7
|
+
export type { ChangeType, CompileInput, CompileResult, PlanEntry, State, StateEntry, ValidationError, } from "./compile.js";
|
|
8
|
+
export { compile } from "./compile.js";
|
|
9
|
+
export type { DestroyResult } from "./destroy.js";
|
|
10
|
+
export { destroy, removeResources } from "./destroy.js";
|
|
11
|
+
export { liveExecutors } from "./executors.live.js";
|
|
12
|
+
export type { ImportResult } from "./import.js";
|
|
13
|
+
export { importResource } from "./import.js";
|
|
14
|
+
export { loadResources } from "./load.js";
|
|
15
|
+
export type { Lock } from "./lock.js";
|
|
16
|
+
export { acquireLock } from "./lock.js";
|
|
17
|
+
export { formatPlan } from "./plan.js";
|
|
18
|
+
export { liveReaders } from "./readers.live.js";
|
|
19
|
+
export type { DriftEntry, DriftStatus, LiveRead, LiveReader, LiveReaders, } from "./refresh.js";
|
|
20
|
+
export { detectDrift, fingerprint, reconcileDrift } from "./refresh.js";
|
|
21
|
+
export { emptyState, readState, restoreSnapshot, snapshotState, writeState, } from "./state.js";
|
|
22
|
+
/**
|
|
23
|
+
* Load a repo's resource definitions, read its cargo.state.json, and compile a
|
|
24
|
+
* plan — the offline diff. No side effects on the workspace.
|
|
25
|
+
*/
|
|
26
|
+
export declare function plan(root: string): Promise<CompileResult>;
|
|
27
|
+
export type DeployResult = {
|
|
28
|
+
state: State;
|
|
29
|
+
applied: number;
|
|
30
|
+
updated: number;
|
|
31
|
+
skipped: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Load → compile → apply against `executors`, persisting cargo.state.json after
|
|
35
|
+
* every resource. Seeds from the existing state, or an empty state bound to
|
|
36
|
+
* `workspaceUuid` on the first deploy. Throws on validation errors.
|
|
37
|
+
*/
|
|
38
|
+
export declare function deploy(root: string, workspaceUuid: string, executors: Executors): Promise<DeployResult>;
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/deploy/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAS,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAW,KAAK,aAAa,EAAE,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAIvE,YAAY,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,OAAO,GACR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACnD,YAAY,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,SAAS,EACT,KAAK,EACL,UAAU,EACV,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,UAAU,EACV,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EACL,UAAU,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,UAAU,GACX,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAG/D;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,MAAM,CAC1B,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,YAAY,CAAC,CAyBvB"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// The deploy engine — the reconciler. Programmatic `plan` + `apply` + `deploy`
|
|
2
|
+
// over a repo of resource definitions, plus the lower-level pieces for hosts.
|
|
3
|
+
import { apply } from "./apply.js";
|
|
4
|
+
import { compile } from "./compile.js";
|
|
5
|
+
import { loadResources } from "./load.js";
|
|
6
|
+
import { emptyState, readState, writeState } from "./state.js";
|
|
7
|
+
export { apply } from "./apply.js";
|
|
8
|
+
export { appendAudit, newRunId } from "./audit.js";
|
|
9
|
+
export { compile } from "./compile.js";
|
|
10
|
+
export { destroy, removeResources } from "./destroy.js";
|
|
11
|
+
export { liveExecutors } from "./executors.live.js";
|
|
12
|
+
export { importResource } from "./import.js";
|
|
13
|
+
export { loadResources } from "./load.js";
|
|
14
|
+
export { acquireLock } from "./lock.js";
|
|
15
|
+
export { formatPlan } from "./plan.js";
|
|
16
|
+
export { liveReaders } from "./readers.live.js";
|
|
17
|
+
export { detectDrift, fingerprint, reconcileDrift } from "./refresh.js";
|
|
18
|
+
export { emptyState, readState, restoreSnapshot, snapshotState, writeState, } from "./state.js";
|
|
19
|
+
/**
|
|
20
|
+
* Load a repo's resource definitions, read its cargo.state.json, and compile a
|
|
21
|
+
* plan — the offline diff. No side effects on the workspace.
|
|
22
|
+
*/
|
|
23
|
+
export async function plan(root) {
|
|
24
|
+
const nodes = await loadResources(root);
|
|
25
|
+
return compile({ nodes, state: readState(root) });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load → compile → apply against `executors`, persisting cargo.state.json after
|
|
29
|
+
* every resource. Seeds from the existing state, or an empty state bound to
|
|
30
|
+
* `workspaceUuid` on the first deploy. Throws on validation errors.
|
|
31
|
+
*/
|
|
32
|
+
export async function deploy(root, workspaceUuid, executors) {
|
|
33
|
+
const prior = readState(root);
|
|
34
|
+
// Refuse a state bound to a different workspace — deploying it would create
|
|
35
|
+
// and orphan resources across workspaces. (The CLI enforces this too; keeping
|
|
36
|
+
// it in the engine protects programmatic/host callers.) Checked before the
|
|
37
|
+
// resource files load, so it fails fast.
|
|
38
|
+
if (prior !== undefined && prior.workspaceUuid !== workspaceUuid) {
|
|
39
|
+
throw new Error(`cargo.state.json belongs to workspace ${prior.workspaceUuid}, not the ` +
|
|
40
|
+
`selected ${workspaceUuid} — refusing to deploy (would orphan resources).`);
|
|
41
|
+
}
|
|
42
|
+
const nodes = await loadResources(root);
|
|
43
|
+
const state = prior ?? emptyState(workspaceUuid);
|
|
44
|
+
const result = await apply({ nodes, state }, executors, (next) => writeState(root, next));
|
|
45
|
+
return {
|
|
46
|
+
state: result.state,
|
|
47
|
+
applied: result.applied.filter((e) => e.change === "create").length,
|
|
48
|
+
updated: result.applied.filter((e) => e.change === "update").length,
|
|
49
|
+
skipped: result.skipped.length,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ResourceNode } from "../core.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reset the registry, import every resource module under `root` (which registers
|
|
4
|
+
* each define*'d resource), and return the registered nodes.
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadResources(root: string): Promise<ResourceNode[]>;
|
|
7
|
+
//# sourceMappingURL=load.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../../src/deploy/load.ts"],"names":[],"mappings":"AASA,OAAO,EAAiB,KAAK,YAAY,EAAa,MAAM,YAAY,CAAC;AA+BzE;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAmBzE"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Loads a Cargo repo: imports every resource `.ts` file under a root directory,
|
|
2
|
+
// transpiled on the fly via the tsx ESM loader. Each `define*` call registers
|
|
3
|
+
// its resource into the core module's registry as a side effect of being
|
|
4
|
+
// imported, so loading IS discovery — there is no manifest to read.
|
|
5
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
6
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { resetRegistry, resources } from "../core.js";
|
|
9
|
+
function tsFilesIn(dir) {
|
|
10
|
+
if (!existsSync(dir))
|
|
11
|
+
return [];
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const entry of readdirSync(dir)) {
|
|
14
|
+
if (entry.startsWith(".") ||
|
|
15
|
+
entry === "node_modules" ||
|
|
16
|
+
entry === "build") {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const full = join(dir, entry);
|
|
20
|
+
if (statSync(full).isDirectory()) {
|
|
21
|
+
// A nested package.json marks a sub-bundle (worker/app runtime code,
|
|
22
|
+
// referenced by `defineWorker`/`defineApp` via its path). Its files are
|
|
23
|
+
// deployed by upload, not imported as resource definitions — skip them.
|
|
24
|
+
if (existsSync(join(full, "package.json")))
|
|
25
|
+
continue;
|
|
26
|
+
out.push(...tsFilesIn(full));
|
|
27
|
+
}
|
|
28
|
+
else if (entry.endsWith(".ts") &&
|
|
29
|
+
!entry.endsWith(".test.ts") &&
|
|
30
|
+
!entry.endsWith(".d.ts")) {
|
|
31
|
+
out.push(full);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Reset the registry, import every resource module under `root` (which registers
|
|
38
|
+
* each define*'d resource), and return the registered nodes.
|
|
39
|
+
*/
|
|
40
|
+
export async function loadResources(root) {
|
|
41
|
+
const rootDir = isAbsolute(root) ? root : resolve(process.cwd(), root);
|
|
42
|
+
if (!existsSync(rootDir)) {
|
|
43
|
+
throw new Error(`Resource directory not found: ${rootDir}`);
|
|
44
|
+
}
|
|
45
|
+
resetRegistry();
|
|
46
|
+
// Load each resource file through tsx's `tsImport`, which transpiles .ts on the
|
|
47
|
+
// fly and works whether @cargo-ai/cdk runs from source (monorepo) or as a built,
|
|
48
|
+
// installed package — unlike a process-wide `register()` + plain `import()`,
|
|
49
|
+
// which trips Node's require(esm)-cycle guard when a resource file imports the
|
|
50
|
+
// already-loaded built package. tsImport cache-busts, so a file imported both
|
|
51
|
+
// directly and transitively evaluates twice; `register()` dedupes that by hash,
|
|
52
|
+
// so each resource still lands once.
|
|
53
|
+
const { tsImport } = await import("tsx/esm/api");
|
|
54
|
+
for (const file of tsFilesIn(rootDir).sort()) {
|
|
55
|
+
await tsImport(pathToFileURL(file).href, import.meta.url);
|
|
56
|
+
}
|
|
57
|
+
return resources();
|
|
58
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Lock = {
|
|
2
|
+
release: () => void;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Acquire the state lock for `root`. Throws if another live run holds it (unless
|
|
6
|
+
* `force`). Releases automatically on process exit; the returned `release` lets
|
|
7
|
+
* callers free it early. Stale locks (dead holder) are stolen.
|
|
8
|
+
*/
|
|
9
|
+
export declare function acquireLock(root: string, command: string, force?: boolean): Lock;
|
|
10
|
+
//# sourceMappingURL=lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../../src/deploy/lock.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,IAAI,GAAG;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC;AAsC3C;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,UAAQ,GACZ,IAAI,CA6DN"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// A coarse advisory lock around the committed cargo.state.json so two concurrent
|
|
2
|
+
// deploy/destroy/import runs can't interleave writes and corrupt it. The lock is
|
|
3
|
+
// a sibling `cargo.state.lock` file holding the holder's pid/host/time; it is
|
|
4
|
+
// created atomically (O_EXCL) and released on process exit. A lock whose holder
|
|
5
|
+
// is no longer alive (same host) or older than STALE_MS (cross host) is stolen,
|
|
6
|
+
// so a crashed run never wedges the repo.
|
|
7
|
+
import { readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { hostname } from "node:os";
|
|
9
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
10
|
+
const LOCK_FILE = "cargo.state.lock";
|
|
11
|
+
const STALE_MS = 60 * 60 * 1000; // 1h
|
|
12
|
+
function lockPath(root) {
|
|
13
|
+
const dir = isAbsolute(root) ? root : resolve(process.cwd(), root);
|
|
14
|
+
return join(dir, LOCK_FILE);
|
|
15
|
+
}
|
|
16
|
+
function readLock(path) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Is the lock still held by a live process? Same-host: probe the pid. Cross-host:
|
|
25
|
+
// we can't, so fall back to age.
|
|
26
|
+
function isHeld(info) {
|
|
27
|
+
if (info.host === hostname()) {
|
|
28
|
+
try {
|
|
29
|
+
process.kill(info.pid, 0);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// EPERM → pid exists but isn't ours (still held); ESRCH → gone.
|
|
34
|
+
return error.code === "EPERM";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const age = Date.now() - Date.parse(info.startedAt);
|
|
38
|
+
return Number.isFinite(age) && age < STALE_MS;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Acquire the state lock for `root`. Throws if another live run holds it (unless
|
|
42
|
+
* `force`). Releases automatically on process exit; the returned `release` lets
|
|
43
|
+
* callers free it early. Stale locks (dead holder) are stolen.
|
|
44
|
+
*/
|
|
45
|
+
export function acquireLock(root, command, force = false) {
|
|
46
|
+
const path = lockPath(root);
|
|
47
|
+
const me = {
|
|
48
|
+
pid: process.pid,
|
|
49
|
+
host: hostname(),
|
|
50
|
+
startedAt: new Date().toISOString(),
|
|
51
|
+
command,
|
|
52
|
+
};
|
|
53
|
+
const payload = `${JSON.stringify(me, null, 2)}\n`;
|
|
54
|
+
let attempts = 0;
|
|
55
|
+
for (;;) {
|
|
56
|
+
try {
|
|
57
|
+
writeFileSync(path, payload, { flag: "wx" }); // atomic create-or-fail
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error.code !== "EEXIST")
|
|
62
|
+
throw error;
|
|
63
|
+
// The lock file exists (EEXIST). Only steal it when we can positively
|
|
64
|
+
// confirm it's free, or the caller forced it.
|
|
65
|
+
const existing = readLock(path);
|
|
66
|
+
if (!force) {
|
|
67
|
+
if (existing === undefined) {
|
|
68
|
+
// Present but unreadable — a live holder mid-write, or a corrupt
|
|
69
|
+
// file. Fail closed: never steal a lock we can't inspect.
|
|
70
|
+
throw new Error(`cargo.state is locked — cargo.state.lock exists but couldn't be read ` +
|
|
71
|
+
`(another run may be writing it, or it's corrupt). If no run is ` +
|
|
72
|
+
`active, re-run with --force.`);
|
|
73
|
+
}
|
|
74
|
+
if (isHeld(existing)) {
|
|
75
|
+
throw new Error(`cargo.state is locked by '${existing.command}' (pid ${existing.pid} on ${existing.host}) since ${existing.startedAt}. ` +
|
|
76
|
+
`If that run is no longer active, re-run with --force.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
rmSync(path, { force: true }); // forced, or readable + confirmed stale
|
|
80
|
+
attempts += 1;
|
|
81
|
+
if (attempts > 5) {
|
|
82
|
+
throw new Error("could not acquire the cargo.state lock");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
let released = false;
|
|
87
|
+
const release = () => {
|
|
88
|
+
if (released)
|
|
89
|
+
return;
|
|
90
|
+
released = true;
|
|
91
|
+
const current = readLock(path);
|
|
92
|
+
if (current !== undefined &&
|
|
93
|
+
current.pid === me.pid &&
|
|
94
|
+
current.host === me.host) {
|
|
95
|
+
rmSync(path, { force: true });
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
// Covers both normal completion and the CLI's process.exit-based failures.
|
|
99
|
+
process.on("exit", release);
|
|
100
|
+
return { release };
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/deploy/plan.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAc,aAAa,EAAa,MAAM,cAAc,CAAC;AAazE,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAoCxD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Human-readable formatting of a CompileResult — the `cargo-ai framework plan`
|
|
2
|
+
// output. Pure (string in / string out) so it is unit-testable.
|
|
3
|
+
const SYMBOL = {
|
|
4
|
+
create: "+",
|
|
5
|
+
update: "~",
|
|
6
|
+
noop: "=",
|
|
7
|
+
delete: "-",
|
|
8
|
+
};
|
|
9
|
+
function line(entry) {
|
|
10
|
+
return ` ${SYMBOL[entry.change]} ${entry.change.padEnd(6)} ${entry.id}`;
|
|
11
|
+
}
|
|
12
|
+
export function formatPlan(result) {
|
|
13
|
+
const out = [];
|
|
14
|
+
if (result.errors.length > 0) {
|
|
15
|
+
out.push("Validation errors:");
|
|
16
|
+
for (const error of result.errors) {
|
|
17
|
+
out.push(` ✗ ${error.id}: ${error.message}`);
|
|
18
|
+
}
|
|
19
|
+
out.push("");
|
|
20
|
+
out.push("Plan aborted — fix the errors above.");
|
|
21
|
+
return out.join("\n");
|
|
22
|
+
}
|
|
23
|
+
if (result.plan.length === 0) {
|
|
24
|
+
return "No resources found.";
|
|
25
|
+
}
|
|
26
|
+
out.push("Plan (apply order):");
|
|
27
|
+
for (const entry of result.plan)
|
|
28
|
+
out.push(line(entry));
|
|
29
|
+
const counts = result.plan.reduce((acc, entry) => {
|
|
30
|
+
acc[entry.change] += 1;
|
|
31
|
+
return acc;
|
|
32
|
+
}, { create: 0, update: 0, noop: 0, delete: 0 });
|
|
33
|
+
out.push("");
|
|
34
|
+
const summary = `${String(counts.create)} to create, ${String(counts.update)} to update, ${String(counts.noop)} unchanged`;
|
|
35
|
+
out.push(counts.delete > 0
|
|
36
|
+
? `${summary}, ${String(counts.delete)} to prune.`
|
|
37
|
+
: `${summary}.`);
|
|
38
|
+
return out.join("\n");
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readers.live.d.ts","sourceRoot":"","sources":["../../../src/deploy/readers.live.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAA8B,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAoD5E,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,CAoOjD"}
|