@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
|
@@ -27,6 +27,161 @@ async function waitForDeployment(api, uuid, attemptsLeft = 150) {
|
|
|
27
27
|
await sleep(2000);
|
|
28
28
|
return waitForDeployment(api, uuid, attemptsLeft - 1);
|
|
29
29
|
}
|
|
30
|
+
// Upsert the play row (create or update), returning its uuid + backing workflow
|
|
31
|
+
// uuid. Builds the create/update payload internally (used only here).
|
|
32
|
+
async function upsertPlay(api, spec, prior) {
|
|
33
|
+
// Play has no slug — identity is purely the prior state uuid.
|
|
34
|
+
const base = {
|
|
35
|
+
name: str(spec["name"]),
|
|
36
|
+
description: optStr(spec["description"]),
|
|
37
|
+
modelUuid: str(spec["modelUuid"]),
|
|
38
|
+
changeKinds: spec["changeKinds"],
|
|
39
|
+
runCreationRule: spec["runCreationRule"],
|
|
40
|
+
filter: spec["filter"],
|
|
41
|
+
sort: spec["sort"],
|
|
42
|
+
limit: spec["limit"],
|
|
43
|
+
trackingColumnSlugs: spec["trackingColumnSlugs"],
|
|
44
|
+
schedule: spec["schedule"],
|
|
45
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
46
|
+
};
|
|
47
|
+
if (prior !== undefined) {
|
|
48
|
+
// play.update rejects modelUuid (a play's model is fixed at
|
|
49
|
+
// creation) — strip it from the update payload.
|
|
50
|
+
const { modelUuid: _modelUuid, ...updateBase } = base;
|
|
51
|
+
await api.orchestration.play.update({
|
|
52
|
+
uuid: prior.uuid,
|
|
53
|
+
...updateBase,
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
uuid: prior.uuid,
|
|
57
|
+
workflowUuid: prior.outputs["workflowUuid"],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const created = await api.orchestration.play.create(base);
|
|
61
|
+
return {
|
|
62
|
+
uuid: created.play.uuid,
|
|
63
|
+
workflowUuid: created.play.workflowUuid,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Ensure the agent row exists (create or update) and return its uuid. The
|
|
67
|
+
// config release (full desired state) is deployed separately by the caller.
|
|
68
|
+
async function ensureAgentRow(api, spec, prior) {
|
|
69
|
+
const description = optStr(spec["description"]);
|
|
70
|
+
if (prior !== undefined) {
|
|
71
|
+
await api.ai.agent.update({
|
|
72
|
+
uuid: prior.uuid,
|
|
73
|
+
name: str(spec["name"]),
|
|
74
|
+
icon: spec["icon"],
|
|
75
|
+
description: description === undefined ? null : description,
|
|
76
|
+
triggers: mapAgentTriggers(spec["triggers"]),
|
|
77
|
+
});
|
|
78
|
+
return prior.uuid;
|
|
79
|
+
}
|
|
80
|
+
const created = await api.ai.agent.create({
|
|
81
|
+
name: str(spec["name"]),
|
|
82
|
+
icon: spec["icon"],
|
|
83
|
+
description,
|
|
84
|
+
triggers: mapAgentTriggers(spec["triggers"]),
|
|
85
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
86
|
+
});
|
|
87
|
+
return created.agent.uuid;
|
|
88
|
+
}
|
|
89
|
+
// Upsert the tool row (create or update), returning its uuid + backing workflow
|
|
90
|
+
// uuid. Builds the create/update payload internally (used only here).
|
|
91
|
+
async function upsertTool(api, spec, prior) {
|
|
92
|
+
const triggers = mapToolTriggers(spec["triggers"]);
|
|
93
|
+
const publicForm = spec["publicForm"];
|
|
94
|
+
const description = optStr(spec["description"]);
|
|
95
|
+
if (prior !== undefined) {
|
|
96
|
+
await api.orchestration.tool.update({
|
|
97
|
+
uuid: prior.uuid,
|
|
98
|
+
name: str(spec["name"]),
|
|
99
|
+
icon: spec["icon"],
|
|
100
|
+
description: description === undefined ? null : description,
|
|
101
|
+
triggers,
|
|
102
|
+
publicForm,
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
toolUuid: prior.uuid,
|
|
106
|
+
workflowUuid: prior.outputs["workflowUuid"],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const created = await api.orchestration.tool.create({
|
|
110
|
+
name: str(spec["name"]),
|
|
111
|
+
icon: spec["icon"],
|
|
112
|
+
description,
|
|
113
|
+
triggers,
|
|
114
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
115
|
+
});
|
|
116
|
+
const toolUuid = created.tool.uuid;
|
|
117
|
+
const workflowUuid = created.tool.workflowUuid;
|
|
118
|
+
// publicForm isn't a create param — apply it via update when set.
|
|
119
|
+
if (publicForm !== null && publicForm !== undefined) {
|
|
120
|
+
await api.orchestration.tool.update({ uuid: toolUuid, publicForm });
|
|
121
|
+
}
|
|
122
|
+
return { toolUuid, workflowUuid };
|
|
123
|
+
}
|
|
124
|
+
// Upsert the worker row (create, update, or recover a same-slug slot),
|
|
125
|
+
// returning its uuid and whether an existing slot was adopted.
|
|
126
|
+
async function upsertWorker(api, spec, prior, slug) {
|
|
127
|
+
const triggers = mapWorkerTriggers(spec["triggers"]);
|
|
128
|
+
const description = optStr(spec["description"]);
|
|
129
|
+
if (prior !== undefined) {
|
|
130
|
+
await api.hosting.worker.update({ uuid: prior.uuid, triggers });
|
|
131
|
+
return { uuid: prior.uuid, adopted: false };
|
|
132
|
+
}
|
|
133
|
+
// Recover our own slot if a prior deploy created it but crashed before
|
|
134
|
+
// persisting (e.g. a failed build). The slug is CDK-chosen, so a worker
|
|
135
|
+
// with the same slug is *probably* ours — reuse it instead of
|
|
136
|
+
// conflicting. We can't be certain it isn't a foreign same-slug worker,
|
|
137
|
+
// so mark it adopted: destroy will release (not delete) it.
|
|
138
|
+
const { workers } = await api.hosting.worker.list();
|
|
139
|
+
const existing = workers.find((w) => w.slug === slug);
|
|
140
|
+
if (existing !== undefined) {
|
|
141
|
+
await api.hosting.worker.update({ uuid: existing.uuid, triggers });
|
|
142
|
+
return { uuid: existing.uuid, adopted: true };
|
|
143
|
+
}
|
|
144
|
+
const created = await api.hosting.worker.create({
|
|
145
|
+
name: str(spec["name"]),
|
|
146
|
+
slug,
|
|
147
|
+
description: description === undefined ? null : description,
|
|
148
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
149
|
+
triggers,
|
|
150
|
+
});
|
|
151
|
+
return { uuid: created.worker.uuid, adopted: false };
|
|
152
|
+
}
|
|
153
|
+
// Resolve the app's slot: reuse the prior uuid, recover a same-slug slot, or
|
|
154
|
+
// create it. Returns the uuid and whether an existing slot was adopted.
|
|
155
|
+
async function resolveAppSlot(api, spec, prior, slug) {
|
|
156
|
+
if (prior !== undefined)
|
|
157
|
+
return { uuid: prior.uuid, adopted: false };
|
|
158
|
+
// Recover our own slot if a prior deploy created it but crashed before
|
|
159
|
+
// persisting — same-slug app is *probably* ours (the slug is
|
|
160
|
+
// CDK-chosen). We can't be certain, so mark it adopted: destroy will
|
|
161
|
+
// release (not delete) it.
|
|
162
|
+
const { apps } = await api.hosting.app.list();
|
|
163
|
+
const existing = apps.find((a) => a.slug === slug);
|
|
164
|
+
if (existing !== undefined) {
|
|
165
|
+
return { uuid: existing.uuid, adopted: true };
|
|
166
|
+
}
|
|
167
|
+
const created = await api.hosting.app.create({
|
|
168
|
+
name: str(spec["name"]),
|
|
169
|
+
slug,
|
|
170
|
+
description: optStr(spec["description"]),
|
|
171
|
+
folderUuid: optStr(spec["folderUuid"]),
|
|
172
|
+
});
|
|
173
|
+
return { uuid: created.app.uuid, adopted: false };
|
|
174
|
+
}
|
|
175
|
+
// Read a context file's current content through the runtime; undefined when it
|
|
176
|
+
// doesn't exist yet (read 404s).
|
|
177
|
+
async function readContextFile(api, path) {
|
|
178
|
+
try {
|
|
179
|
+
return (await api.context.runtime.read({ path })).content;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return undefined; // new file — read 404s
|
|
183
|
+
}
|
|
184
|
+
}
|
|
30
185
|
export function liveExecutors(api) {
|
|
31
186
|
// Resolve the dataset uuid auto-created alongside a connector — needed when a
|
|
32
187
|
// connector is ADOPTED (create returns it directly; list/adopt does not).
|
|
@@ -49,9 +204,9 @@ export function liveExecutors(api) {
|
|
|
49
204
|
await api.connection.connector.update({
|
|
50
205
|
uuid: prior.uuid,
|
|
51
206
|
name: str(spec["name"]),
|
|
52
|
-
config: config
|
|
53
|
-
rateLimit: rateLimit
|
|
54
|
-
cacheTtlMilliseconds: cacheTtlMilliseconds
|
|
207
|
+
config: config === undefined ? null : config,
|
|
208
|
+
rateLimit: rateLimit === undefined ? null : rateLimit,
|
|
209
|
+
cacheTtlMilliseconds: cacheTtlMilliseconds === undefined ? null : cacheTtlMilliseconds,
|
|
55
210
|
});
|
|
56
211
|
// Preserve the adopted marker so destroy keeps releasing (not deleting).
|
|
57
212
|
const out = {
|
|
@@ -64,9 +219,9 @@ export function liveExecutors(api) {
|
|
|
64
219
|
return out;
|
|
65
220
|
}
|
|
66
221
|
const explicitAdopt = spec["adopt"] === true;
|
|
67
|
-
const adopt = explicitAdopt ||
|
|
222
|
+
const adopt = explicitAdopt === true ||
|
|
68
223
|
(await api.connection.connector.existsBySlug({ slug })).exists;
|
|
69
|
-
if (adopt) {
|
|
224
|
+
if (adopt === true) {
|
|
70
225
|
const { connectors } = await api.connection.connector.list({
|
|
71
226
|
integrationSlug,
|
|
72
227
|
});
|
|
@@ -74,10 +229,11 @@ export function liveExecutors(api) {
|
|
|
74
229
|
// workspace already treats as default for this integration, falling back
|
|
75
230
|
// to a slug match. Implicit adopt (a same-slug connector already exists)
|
|
76
231
|
// recovers that exact slot, so it stays a strict slug match.
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
232
|
+
const defaultConnector = connectors.find((c) => c.isDefault === true);
|
|
233
|
+
const slugMatch = connectors.find((c) => c.slug === slug);
|
|
234
|
+
const found = explicitAdopt === true && defaultConnector !== undefined
|
|
235
|
+
? defaultConnector
|
|
236
|
+
: slugMatch;
|
|
81
237
|
if (found === undefined) {
|
|
82
238
|
throw new Error(explicitAdopt
|
|
83
239
|
? `no connector found to adopt for integration "${integrationSlug}"`
|
|
@@ -107,6 +263,7 @@ export function liveExecutors(api) {
|
|
|
107
263
|
async model(spec, prior) {
|
|
108
264
|
const slug = str(spec["slug"]);
|
|
109
265
|
const datasetUuid = str(spec["datasetUuid"]);
|
|
266
|
+
const description = optStr(spec["description"]);
|
|
110
267
|
const schedule = spec["schedule"];
|
|
111
268
|
// Unification is not part of CreateModelPayload — it's set through an
|
|
112
269
|
// update, both when the model is first created and on later syncs. Left
|
|
@@ -119,9 +276,9 @@ export function liveExecutors(api) {
|
|
|
119
276
|
await api.storage.model.update({
|
|
120
277
|
uuid: prior.uuid,
|
|
121
278
|
name: str(spec["name"]),
|
|
122
|
-
description:
|
|
279
|
+
description: description === undefined ? null : description,
|
|
123
280
|
config: spec["config"] ?? {},
|
|
124
|
-
schedule: schedule
|
|
281
|
+
schedule: schedule === undefined ? null : schedule,
|
|
125
282
|
unification,
|
|
126
283
|
});
|
|
127
284
|
const out = { uuid: prior.uuid };
|
|
@@ -147,7 +304,7 @@ export function liveExecutors(api) {
|
|
|
147
304
|
const created = await api.storage.model.create({
|
|
148
305
|
slug,
|
|
149
306
|
name: str(spec["name"]),
|
|
150
|
-
description
|
|
307
|
+
description,
|
|
151
308
|
datasetUuid,
|
|
152
309
|
extractorSlug: str(spec["extractorSlug"]),
|
|
153
310
|
config: spec["config"] ?? {},
|
|
@@ -165,45 +322,16 @@ export function liveExecutors(api) {
|
|
|
165
322
|
return { uuid: created.model.uuid };
|
|
166
323
|
},
|
|
167
324
|
async play(spec, prior) {
|
|
168
|
-
|
|
169
|
-
const base = {
|
|
170
|
-
name: str(spec["name"]),
|
|
171
|
-
description: optStr(spec["description"]),
|
|
172
|
-
modelUuid: str(spec["modelUuid"]),
|
|
173
|
-
changeKinds: spec["changeKinds"],
|
|
174
|
-
runCreationRule: spec["runCreationRule"],
|
|
175
|
-
filter: spec["filter"],
|
|
176
|
-
sort: spec["sort"],
|
|
177
|
-
limit: spec["limit"],
|
|
178
|
-
trackingColumnSlugs: spec["trackingColumnSlugs"],
|
|
179
|
-
schedule: spec["schedule"],
|
|
180
|
-
folderUuid: optStr(spec["folderUuid"]),
|
|
181
|
-
};
|
|
182
|
-
let uuid;
|
|
183
|
-
let workflowUuid;
|
|
184
|
-
if (prior !== undefined) {
|
|
185
|
-
// play.update rejects modelUuid (a play's model is fixed at creation) —
|
|
186
|
-
// strip it from the update payload.
|
|
187
|
-
const { modelUuid: _modelUuid, ...updateBase } = base;
|
|
188
|
-
await api.orchestration.play.update({
|
|
189
|
-
uuid: prior.uuid,
|
|
190
|
-
...updateBase,
|
|
191
|
-
});
|
|
192
|
-
uuid = prior.uuid;
|
|
193
|
-
workflowUuid = prior.outputs["workflowUuid"];
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
const created = await api.orchestration.play.create(base);
|
|
197
|
-
uuid = created.play.uuid;
|
|
198
|
-
workflowUuid = created.play.workflowUuid;
|
|
199
|
-
}
|
|
325
|
+
const { uuid, workflowUuid } = await upsertPlay(api, spec, prior);
|
|
200
326
|
// Deploy the play's backing workflow graph as its release, if provided.
|
|
201
327
|
const nodes = spec["nodes"];
|
|
202
328
|
if (nodes !== undefined && workflowUuid !== undefined) {
|
|
203
329
|
await api.orchestration.draftRelease.deploy({
|
|
204
330
|
workflowUuid,
|
|
205
331
|
nodes: nodes,
|
|
206
|
-
formFields: (spec["formFields"]
|
|
332
|
+
formFields: (spec["formFields"] === undefined
|
|
333
|
+
? null
|
|
334
|
+
: spec["formFields"]),
|
|
207
335
|
});
|
|
208
336
|
}
|
|
209
337
|
const result = { uuid };
|
|
@@ -223,27 +351,7 @@ export function liveExecutors(api) {
|
|
|
223
351
|
// NOTE: agent + release are two calls. A crash between them orphans the
|
|
224
352
|
// agent (no slug to re-find it). Acceptable for v1; a partial-persist hook
|
|
225
353
|
// would close the window.
|
|
226
|
-
|
|
227
|
-
if (prior !== undefined) {
|
|
228
|
-
agentUuid = prior.uuid;
|
|
229
|
-
await api.ai.agent.update({
|
|
230
|
-
uuid: agentUuid,
|
|
231
|
-
name: str(spec["name"]),
|
|
232
|
-
icon: spec["icon"],
|
|
233
|
-
description: optStr(spec["description"]) ?? null,
|
|
234
|
-
triggers: mapAgentTriggers(spec["triggers"]),
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
const created = await api.ai.agent.create({
|
|
239
|
-
name: str(spec["name"]),
|
|
240
|
-
icon: spec["icon"],
|
|
241
|
-
description: optStr(spec["description"]),
|
|
242
|
-
triggers: mapAgentTriggers(spec["triggers"]),
|
|
243
|
-
folderUuid: optStr(spec["folderUuid"]),
|
|
244
|
-
});
|
|
245
|
-
agentUuid = created.agent.uuid;
|
|
246
|
-
}
|
|
354
|
+
const agentUuid = await ensureAgentRow(api, spec, prior);
|
|
247
355
|
// ── phase B: deploy the config release (full desired state) ──
|
|
248
356
|
// No explicit version — the backend auto-bumps (an explicit wrong value
|
|
249
357
|
// throws invalidReleaseVersion).
|
|
@@ -308,37 +416,7 @@ export function liveExecutors(api) {
|
|
|
308
416
|
async tool(spec, prior) {
|
|
309
417
|
const nodes = spec["nodes"];
|
|
310
418
|
const formFields = spec["formFields"];
|
|
311
|
-
const
|
|
312
|
-
const publicForm = spec["publicForm"];
|
|
313
|
-
let toolUuid;
|
|
314
|
-
let workflowUuid;
|
|
315
|
-
if (prior !== undefined) {
|
|
316
|
-
toolUuid = prior.uuid;
|
|
317
|
-
workflowUuid = prior.outputs["workflowUuid"];
|
|
318
|
-
await api.orchestration.tool.update({
|
|
319
|
-
uuid: toolUuid,
|
|
320
|
-
name: str(spec["name"]),
|
|
321
|
-
icon: spec["icon"],
|
|
322
|
-
description: optStr(spec["description"]) ?? null,
|
|
323
|
-
triggers,
|
|
324
|
-
publicForm,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
const created = await api.orchestration.tool.create({
|
|
329
|
-
name: str(spec["name"]),
|
|
330
|
-
icon: spec["icon"],
|
|
331
|
-
description: optStr(spec["description"]),
|
|
332
|
-
triggers,
|
|
333
|
-
folderUuid: optStr(spec["folderUuid"]),
|
|
334
|
-
});
|
|
335
|
-
toolUuid = created.tool.uuid;
|
|
336
|
-
workflowUuid = created.tool.workflowUuid;
|
|
337
|
-
// publicForm isn't a create param — apply it via update when set.
|
|
338
|
-
if (publicForm !== null && publicForm !== undefined) {
|
|
339
|
-
await api.orchestration.tool.update({ uuid: toolUuid, publicForm });
|
|
340
|
-
}
|
|
341
|
-
}
|
|
419
|
+
const { toolUuid, workflowUuid } = await upsertTool(api, spec, prior);
|
|
342
420
|
// Deploy the backing workflow's graph as the tool's release.
|
|
343
421
|
await api.orchestration.draftRelease.deploy({
|
|
344
422
|
workflowUuid,
|
|
@@ -384,36 +462,7 @@ export function liveExecutors(api) {
|
|
|
384
462
|
async worker(spec, prior) {
|
|
385
463
|
const files = readBundle(str(spec["path"]));
|
|
386
464
|
const slug = str(spec["slug"]);
|
|
387
|
-
const
|
|
388
|
-
let uuid;
|
|
389
|
-
let adopted = false;
|
|
390
|
-
if (prior !== undefined) {
|
|
391
|
-
uuid = prior.uuid;
|
|
392
|
-
await api.hosting.worker.update({ uuid, triggers });
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
// Recover our own slot if a prior deploy created it but crashed before
|
|
396
|
-
// persisting (e.g. a failed build). The slug is CDK-chosen, so a worker
|
|
397
|
-
// with the same slug is *probably* ours — reuse it instead of
|
|
398
|
-
// conflicting. We can't be certain it isn't a foreign same-slug worker,
|
|
399
|
-
// so mark it adopted: destroy will release (not delete) it.
|
|
400
|
-
const { workers } = await api.hosting.worker.list();
|
|
401
|
-
const existing = workers.find((w) => w.slug === slug);
|
|
402
|
-
if (existing !== undefined) {
|
|
403
|
-
uuid = existing.uuid;
|
|
404
|
-
adopted = true;
|
|
405
|
-
await api.hosting.worker.update({ uuid, triggers });
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
uuid = (await api.hosting.worker.create({
|
|
409
|
-
name: str(spec["name"]),
|
|
410
|
-
slug,
|
|
411
|
-
description: optStr(spec["description"]) ?? null,
|
|
412
|
-
folderUuid: optStr(spec["folderUuid"]),
|
|
413
|
-
triggers,
|
|
414
|
-
})).worker.uuid;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
465
|
+
const { uuid, adopted } = await upsertWorker(api, spec, prior, slug);
|
|
417
466
|
await syncEnvVars(api, { kind: "worker", workerUuid: uuid }, spec["env"]);
|
|
418
467
|
const { deployment } = await api.hosting.deployment.create({
|
|
419
468
|
kind: "worker",
|
|
@@ -430,38 +479,14 @@ export function liveExecutors(api) {
|
|
|
430
479
|
uuid,
|
|
431
480
|
url: promoted.url,
|
|
432
481
|
};
|
|
433
|
-
if (adopted)
|
|
482
|
+
if (adopted === true)
|
|
434
483
|
out["adopted"] = "true";
|
|
435
484
|
return out;
|
|
436
485
|
},
|
|
437
486
|
async app(spec, prior) {
|
|
438
487
|
const files = readBundle(str(spec["path"]));
|
|
439
488
|
const slug = str(spec["slug"]);
|
|
440
|
-
|
|
441
|
-
let adopted = false;
|
|
442
|
-
if (prior !== undefined) {
|
|
443
|
-
uuid = prior.uuid;
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
// Recover our own slot if a prior deploy created it but crashed before
|
|
447
|
-
// persisting — same-slug app is *probably* ours (the slug is
|
|
448
|
-
// CDK-chosen). We can't be certain, so mark it adopted: destroy will
|
|
449
|
-
// release (not delete) it.
|
|
450
|
-
const { apps } = await api.hosting.app.list();
|
|
451
|
-
const existing = apps.find((a) => a.slug === slug);
|
|
452
|
-
if (existing !== undefined) {
|
|
453
|
-
uuid = existing.uuid;
|
|
454
|
-
adopted = true;
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
uuid = (await api.hosting.app.create({
|
|
458
|
-
name: str(spec["name"]),
|
|
459
|
-
slug,
|
|
460
|
-
description: optStr(spec["description"]),
|
|
461
|
-
folderUuid: optStr(spec["folderUuid"]),
|
|
462
|
-
})).app.uuid;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
489
|
+
const { uuid, adopted } = await resolveAppSlot(api, spec, prior, slug);
|
|
465
490
|
await syncEnvVars(api, { kind: "app", appUuid: uuid }, spec["env"]);
|
|
466
491
|
const { deployment } = await api.hosting.deployment.create({
|
|
467
492
|
kind: "app",
|
|
@@ -478,7 +503,7 @@ export function liveExecutors(api) {
|
|
|
478
503
|
uuid,
|
|
479
504
|
url: promoted.url,
|
|
480
505
|
};
|
|
481
|
-
if (adopted)
|
|
506
|
+
if (adopted === true)
|
|
482
507
|
out["adopted"] = "true";
|
|
483
508
|
return out;
|
|
484
509
|
},
|
|
@@ -549,13 +574,7 @@ export function liveExecutors(api) {
|
|
|
549
574
|
const files = (spec["files"] ?? {});
|
|
550
575
|
const { repository } = await api.context.repository.get();
|
|
551
576
|
for (const [path, content] of Object.entries(files)) {
|
|
552
|
-
|
|
553
|
-
try {
|
|
554
|
-
current = (await api.context.runtime.read({ path })).content;
|
|
555
|
-
}
|
|
556
|
-
catch {
|
|
557
|
-
current = undefined; // new file — read 404s
|
|
558
|
-
}
|
|
577
|
+
const current = await readContextFile(api, path);
|
|
559
578
|
if (current !== content) {
|
|
560
579
|
await api.context.runtime.write({
|
|
561
580
|
path,
|
|
@@ -586,7 +605,9 @@ export function liveExecutors(api) {
|
|
|
586
605
|
await api.revenueOrganization.capacity.update({
|
|
587
606
|
uuid: prior.uuid,
|
|
588
607
|
...base,
|
|
589
|
-
filter: (spec["filter"]
|
|
608
|
+
filter: (spec["filter"] === undefined
|
|
609
|
+
? null
|
|
610
|
+
: spec["filter"]),
|
|
590
611
|
});
|
|
591
612
|
return carryAdopted({ uuid: prior.uuid }, prior);
|
|
592
613
|
}
|
|
@@ -635,10 +656,14 @@ export function liveExecutors(api) {
|
|
|
635
656
|
uuid: prior.uuid,
|
|
636
657
|
name,
|
|
637
658
|
filter: spec["filter"],
|
|
638
|
-
sort: (spec["sort"]
|
|
639
|
-
limit: (spec["limit"]
|
|
640
|
-
trackingColumnSlugs: (spec["trackingColumnSlugs"]
|
|
641
|
-
|
|
659
|
+
sort: (spec["sort"] === undefined ? null : spec["sort"]),
|
|
660
|
+
limit: (spec["limit"] === undefined ? null : spec["limit"]),
|
|
661
|
+
trackingColumnSlugs: (spec["trackingColumnSlugs"] === undefined
|
|
662
|
+
? null
|
|
663
|
+
: spec["trackingColumnSlugs"]),
|
|
664
|
+
columnSlugs: (spec["columnSlugs"] === undefined
|
|
665
|
+
? null
|
|
666
|
+
: spec["columnSlugs"]),
|
|
642
667
|
});
|
|
643
668
|
return carryAdopted({ uuid: prior.uuid }, prior);
|
|
644
669
|
}
|
|
@@ -670,22 +695,25 @@ export function liveExecutors(api) {
|
|
|
670
695
|
const fromModelUuid = str(spec["fromModelUuid"]);
|
|
671
696
|
const { model } = await api.storage.model.get(fromModelUuid);
|
|
672
697
|
const datasetUuid = model.datasetUuid;
|
|
698
|
+
const fromPropertySlug = optStr(spec["fromPropertySlug"]);
|
|
699
|
+
const toPropertySlug = optStr(spec["toPropertySlug"]);
|
|
673
700
|
const row = {
|
|
674
701
|
fromModelUuid,
|
|
675
702
|
fromColumnSlug: str(spec["fromColumnSlug"]),
|
|
676
|
-
fromPropertySlug:
|
|
703
|
+
fromPropertySlug: fromPropertySlug === undefined ? null : fromPropertySlug,
|
|
677
704
|
toModelUuid: str(spec["toModelUuid"]),
|
|
678
705
|
toColumnSlug: str(spec["toColumnSlug"]),
|
|
679
|
-
toPropertySlug:
|
|
706
|
+
toPropertySlug: toPropertySlug === undefined ? null : toPropertySlug,
|
|
680
707
|
fromSelectedColumnSlugs: null,
|
|
681
708
|
toSelectedColumnSlugs: null,
|
|
682
709
|
relation: spec["relation"],
|
|
683
710
|
};
|
|
684
711
|
const { relationships } = await api.storage.relationship.all();
|
|
712
|
+
const priorUuid = prior === undefined ? undefined : prior.uuid;
|
|
685
713
|
// Every row in this dataset except the one we manage — kept verbatim (with
|
|
686
714
|
// its uuid) so `set` updates it in place rather than deleting it.
|
|
687
715
|
const others = relationships
|
|
688
|
-
.filter((r) => r.fromDatasetUuid === datasetUuid && r.uuid !==
|
|
716
|
+
.filter((r) => r.fromDatasetUuid === datasetUuid && r.uuid !== priorUuid)
|
|
689
717
|
.map(toSetInput);
|
|
690
718
|
const ours = prior === undefined ? row : { ...row, uuid: prior.uuid };
|
|
691
719
|
const { relationships: after } = await api.storage.relationship.set({
|
|
@@ -713,16 +741,17 @@ export function liveExecutors(api) {
|
|
|
713
741
|
// so `set` updates it in place instead of deleting it. `all()` returns nullable
|
|
714
742
|
// fields as `undefined` while `set()` expects `null`, so normalize each here.
|
|
715
743
|
function toSetInput(relationship) {
|
|
744
|
+
const { fromPropertySlug = null, toPropertySlug = null, fromSelectedColumnSlugs = null, toSelectedColumnSlugs = null, } = relationship;
|
|
716
745
|
return {
|
|
717
746
|
uuid: relationship.uuid,
|
|
718
747
|
fromModelUuid: relationship.fromModelUuid,
|
|
719
748
|
fromColumnSlug: relationship.fromColumnSlug,
|
|
720
|
-
fromPropertySlug
|
|
749
|
+
fromPropertySlug,
|
|
721
750
|
toModelUuid: relationship.toModelUuid,
|
|
722
751
|
toColumnSlug: relationship.toColumnSlug,
|
|
723
|
-
toPropertySlug
|
|
724
|
-
fromSelectedColumnSlugs
|
|
725
|
-
toSelectedColumnSlugs
|
|
752
|
+
toPropertySlug,
|
|
753
|
+
fromSelectedColumnSlugs,
|
|
754
|
+
toSelectedColumnSlugs,
|
|
726
755
|
relation: relationship.relation,
|
|
727
756
|
};
|
|
728
757
|
}
|
|
@@ -58,7 +58,7 @@ export async function importResource(root, api, workspaceUuid, id, uuid,
|
|
|
58
58
|
// The repo's resources. Defaults to loading them from `root`; injectable so
|
|
59
59
|
// the import logic is testable without the tsx file loader.
|
|
60
60
|
nodes) {
|
|
61
|
-
const resolved = nodes
|
|
61
|
+
const resolved = nodes !== undefined ? nodes : await loadResources(root);
|
|
62
62
|
const entry = compile({ nodes: resolved }).plan.find((p) => p.id === id);
|
|
63
63
|
if (entry === undefined) {
|
|
64
64
|
throw new Error(`no resource "${id}" found in code — import targets a code resource id like "agent:sdr".`);
|
|
@@ -71,7 +71,7 @@ nodes) {
|
|
|
71
71
|
const stateEntry = { hash: entry.hash, uuid, outputs };
|
|
72
72
|
// Confirm it's actually there (and capture a drift baseline).
|
|
73
73
|
const live = await liveReaders(api)[kind](stateEntry);
|
|
74
|
-
if (
|
|
74
|
+
if (live.exists === false) {
|
|
75
75
|
throw new Error(`${kind} ${uuid} was not found in the workspace — nothing to import.`);
|
|
76
76
|
}
|
|
77
77
|
if (live.fingerprint !== undefined)
|
|
@@ -40,7 +40,7 @@ export async function deploy(root, workspaceUuid, executors) {
|
|
|
40
40
|
`selected ${workspaceUuid} — refusing to deploy (would orphan resources).`);
|
|
41
41
|
}
|
|
42
42
|
const nodes = await loadResources(root);
|
|
43
|
-
const state = prior
|
|
43
|
+
const state = prior !== undefined ? prior : emptyState(workspaceUuid);
|
|
44
44
|
const result = await apply({ nodes, state }, executors, (next) => writeState(root, next));
|
|
45
45
|
return {
|
|
46
46
|
state: result.state,
|
|
@@ -1 +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,
|
|
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,CAgEN"}
|
package/build/src/deploy/lock.js
CHANGED
|
@@ -51,11 +51,13 @@ export function acquireLock(root, command, force = false) {
|
|
|
51
51
|
command,
|
|
52
52
|
};
|
|
53
53
|
const payload = `${JSON.stringify(me, null, 2)}\n`;
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// Create the lock file, stealing a stale/forced one and retrying per steal;
|
|
55
|
+
// gives up after 6 attempts. A successful write returns; the original loop's
|
|
56
|
+
// `break` maps to this `return`.
|
|
57
|
+
const acquire = (attempts) => {
|
|
56
58
|
try {
|
|
57
59
|
writeFileSync(path, payload, { flag: "wx" }); // atomic create-or-fail
|
|
58
|
-
|
|
60
|
+
return;
|
|
59
61
|
}
|
|
60
62
|
catch (error) {
|
|
61
63
|
if (error.code !== "EEXIST")
|
|
@@ -63,7 +65,7 @@ export function acquireLock(root, command, force = false) {
|
|
|
63
65
|
// The lock file exists (EEXIST). Only steal it when we can positively
|
|
64
66
|
// confirm it's free, or the caller forced it.
|
|
65
67
|
const existing = readLock(path);
|
|
66
|
-
if (
|
|
68
|
+
if (force === false) {
|
|
67
69
|
if (existing === undefined) {
|
|
68
70
|
// Present but unreadable — a live holder mid-write, or a corrupt
|
|
69
71
|
// file. Fail closed: never steal a lock we can't inspect.
|
|
@@ -77,17 +79,18 @@ export function acquireLock(root, command, force = false) {
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
rmSync(path, { force: true }); // forced, or readable + confirmed stale
|
|
80
|
-
attempts
|
|
81
|
-
if (attempts > 5) {
|
|
82
|
+
if (attempts + 1 > 5) {
|
|
82
83
|
throw new Error("could not acquire the cargo.state lock");
|
|
83
84
|
}
|
|
85
|
+
acquire(attempts + 1);
|
|
84
86
|
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
+
};
|
|
88
|
+
acquire(0);
|
|
89
|
+
const releaseState = { released: false };
|
|
87
90
|
const release = () => {
|
|
88
|
-
if (released)
|
|
91
|
+
if (releaseState.released)
|
|
89
92
|
return;
|
|
90
|
-
released = true;
|
|
93
|
+
releaseState.released = true;
|
|
91
94
|
const current = readLock(path);
|
|
92
95
|
if (current !== undefined &&
|
|
93
96
|
current.pid === me.pid &&
|
|
@@ -1 +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,
|
|
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,CAoUjD"}
|