run402 4.82.0 → 4.84.0
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/cli.mjs +1 -1
- package/gitvault-surface.json +1 -1
- package/lib/deploy-v2.mjs +41 -71
- package/lib/doctor-source-scan.mjs +2 -547
- package/lib/doctor.mjs +28 -8
- package/lib/sdk-errors.mjs +7 -0
- package/lib/status.mjs +1 -1
- package/lib/up.mjs +62 -21
- package/lib/up.test.mjs +16 -0
- package/lib/update-check.mjs +11 -6
- package/lib/update-check.test.mjs +18 -5
- package/package.json +1 -1
- package/sdk/dist/actions.d.ts +16 -3
- package/sdk/dist/actions.d.ts.map +1 -1
- package/sdk/dist/config.d.ts +1 -1
- package/sdk/dist/config.d.ts.map +1 -1
- package/sdk/dist/config.js.map +1 -1
- package/sdk/dist/index.d.ts +2 -0
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +2 -0
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +2 -1
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +6 -0
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/edge-evidence.d.ts +22 -0
- package/sdk/dist/namespaces/edge-evidence.d.ts.map +1 -0
- package/sdk/dist/namespaces/edge-evidence.js +38 -0
- package/sdk/dist/namespaces/edge-evidence.js.map +1 -0
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +9 -7
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/node/actions-node.d.ts +18 -0
- package/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +139 -91
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/app-scope.d.ts +24 -0
- package/sdk/dist/node/app-scope.d.ts.map +1 -0
- package/sdk/dist/node/app-scope.js +47 -0
- package/sdk/dist/node/app-scope.js.map +1 -0
- package/sdk/dist/node/deploy-manifest.d.ts +6 -0
- package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +61 -0
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/index.d.ts +6 -1
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +8 -2
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/manifest-export.d.ts +5 -0
- package/sdk/dist/node/manifest-export.d.ts.map +1 -0
- package/sdk/dist/node/manifest-export.js +78 -0
- package/sdk/dist/node/manifest-export.js.map +1 -0
- package/sdk/dist/node/preflight.d.ts +65 -0
- package/sdk/dist/node/preflight.d.ts.map +1 -0
- package/sdk/dist/node/preflight.js +23 -0
- package/sdk/dist/node/preflight.js.map +1 -0
- package/sdk/dist/node/source-scan.d.ts +73 -0
- package/sdk/dist/node/source-scan.d.ts.map +1 -0
- package/sdk/dist/node/source-scan.js +552 -0
- package/sdk/dist/node/source-scan.js.map +1 -0
- package/sdk/dist/rest-diagnostics.d.ts +15 -0
- package/sdk/dist/rest-diagnostics.d.ts.map +1 -0
- package/sdk/dist/rest-diagnostics.js +19 -0
- package/sdk/dist/rest-diagnostics.js.map +1 -0
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { serializeDeployManifest, manifestExportUnsupported } from "./manifest-export.js";
|
|
2
|
+
import { describeLocalPreflight } from "./preflight.js";
|
|
3
|
+
import { resolveApplicationScope, materializeTemplates } from "./app-scope.js";
|
|
4
|
+
import { scanDeploymentSources } from "./source-scan.js";
|
|
1
5
|
import { createHash } from "node:crypto";
|
|
2
6
|
import { execFile } from "node:child_process";
|
|
3
7
|
import { lstat, mkdir, mkdtemp, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
@@ -191,9 +195,23 @@ export class NodeActions {
|
|
|
191
195
|
}
|
|
192
196
|
}
|
|
193
197
|
async #runUpFromWorkspace(input, run, source, startedAt) {
|
|
194
|
-
|
|
198
|
+
let workspaceDir = source.workspaceDir;
|
|
195
199
|
const manifest = await this.#discoverAndValidateManifest(input, workspaceDir, source.metadata, run);
|
|
200
|
+
workspaceDir = dirname(manifest.manifestPath);
|
|
201
|
+
const target = !input.verifyOnly ? await resolveDeploymentTarget({
|
|
202
|
+
appRoot: workspaceDir, manifestPath: manifest.manifestPath, projectId: input.projectId,
|
|
203
|
+
manifestProjectId: manifest.manifestProjectId ?? manifest.appSpec?.project.id, name: input.name,
|
|
204
|
+
targetKind: this.#targetKind(), apiBase: this.sdk.apiBase,
|
|
205
|
+
allowUnresolved: run.executionMode === "check" || (run.executionMode === "printSpec" || run.executionMode === "printManifest") || run.dryRun,
|
|
206
|
+
}) : null;
|
|
207
|
+
if (!input.verifyOnly && process.env.RUN402_DEPLOY_SKIP_SCAN !== "1") {
|
|
208
|
+
const scan = scanDeploymentSources(manifest.releaseSpec ?? {}, workspaceDir);
|
|
209
|
+
if (scan.errors.length)
|
|
210
|
+
throw run.error("Source scan blocked this application.", "R402_AUTH_PREFLIGHT_FAILED", scan);
|
|
211
|
+
}
|
|
196
212
|
if (manifest.manifestKind === "app") {
|
|
213
|
+
if (run.executionMode === "printManifest")
|
|
214
|
+
manifestExportUnsupported("app", "app resources and build intent cannot be faithfully represented as a release manifest");
|
|
197
215
|
if (input.verifyOnly) {
|
|
198
216
|
return this.#verifyAppManifestOnly(input, manifest, workspaceDir, run, startedAt);
|
|
199
217
|
}
|
|
@@ -202,22 +220,23 @@ export class NodeActions {
|
|
|
202
220
|
startedAt,
|
|
203
221
|
status: block ? "blocked" : "planned",
|
|
204
222
|
dryRun: run.dryRun || run.executionMode === "check" || run.executionMode === "printSpec",
|
|
205
|
-
projectId:
|
|
223
|
+
projectId: target?.project_id ?? null,
|
|
206
224
|
diagnostics: block?.diagnostics,
|
|
207
225
|
nextActions: block?.nextActions,
|
|
208
226
|
blockedNodeId: block?.nodeId,
|
|
209
227
|
});
|
|
210
228
|
if (run.executionMode === "check" || run.executionMode === "printSpec" || run.dryRun) {
|
|
211
229
|
return run.result({
|
|
212
|
-
project_id:
|
|
230
|
+
project_id: target?.project_id ?? null,
|
|
213
231
|
manifest_path: manifest.manifestPath,
|
|
214
232
|
app_graph: manifest.appGraph,
|
|
215
233
|
app_result: appResult,
|
|
234
|
+
preflight: describeLocalPreflight({ appRoot: workspaceDir, manifestPath: manifest.manifestPath, target: target, authoring: manifest.appSpec?.release, spec: manifest.releaseSpec ?? undefined, buildDeferred: !!manifest.appSpec?.build?.commands?.length, apiBase: this.sdk.apiBase, profile: this.opts.profile }),
|
|
216
235
|
});
|
|
217
236
|
}
|
|
218
237
|
if (block) {
|
|
219
238
|
return run.result({
|
|
220
|
-
project_id:
|
|
239
|
+
project_id: target?.project_id ?? null,
|
|
221
240
|
manifest_path: manifest.manifestPath,
|
|
222
241
|
app_graph: manifest.appGraph,
|
|
223
242
|
app_result: appResult,
|
|
@@ -228,14 +247,16 @@ export class NodeActions {
|
|
|
228
247
|
if (input.verifyOnly) {
|
|
229
248
|
return this.#verifyDeployManifestOnly(input, manifest, workspaceDir, run);
|
|
230
249
|
}
|
|
231
|
-
if (run.executionMode === "check" || run.executionMode === "printSpec") {
|
|
250
|
+
if (run.executionMode === "check" || (run.executionMode === "printSpec" || run.executionMode === "printManifest")) {
|
|
232
251
|
if (!manifest.releaseSpec) {
|
|
233
252
|
throw run.error("Internal error: release manifest did not produce a ReleaseSpec.", "RUN402_ACTION_INTERNAL");
|
|
234
253
|
}
|
|
235
254
|
return run.result({
|
|
236
|
-
project_id:
|
|
255
|
+
project_id: target?.project_id ?? null,
|
|
237
256
|
manifest_path: manifest.manifestPath,
|
|
238
|
-
|
|
257
|
+
preflight: describeLocalPreflight({ appRoot: workspaceDir, manifestPath: manifest.manifestPath, target: target, authoring: manifest.loaded?.manifest, spec: manifest.releaseSpec, apiBase: this.sdk.apiBase, profile: this.opts.profile }),
|
|
258
|
+
...(run.executionMode === "printManifest" ? { manifest: serializeDeployManifest(manifest.loaded, workspaceDir, input.projectId) } : {}),
|
|
259
|
+
...(run.executionMode === "printSpec" ? { spec: { ...manifest.releaseSpec, project: target?.project_id ?? undefined } } : {}),
|
|
239
260
|
});
|
|
240
261
|
}
|
|
241
262
|
if (run.executionMode === "plan") {
|
|
@@ -272,7 +293,7 @@ export class NodeActions {
|
|
|
272
293
|
schema_version: "run402.workspace-project.v1",
|
|
273
294
|
project_id: resolved.projectId,
|
|
274
295
|
...(input.name ? { name: input.name } : {}),
|
|
275
|
-
target: { kind: this.#targetKind() },
|
|
296
|
+
target: { kind: this.#targetKind(), api_base: this.sdk.apiBase },
|
|
276
297
|
created_at: resolved.link?.created_at ?? new Date().toISOString(),
|
|
277
298
|
updated_at: new Date().toISOString(),
|
|
278
299
|
}, resolved.link, run);
|
|
@@ -283,14 +304,14 @@ export class NodeActions {
|
|
|
283
304
|
mutation: run.executionMode !== "plan",
|
|
284
305
|
auto: false,
|
|
285
306
|
details: {
|
|
286
|
-
project_id: resolved.projectId,
|
|
307
|
+
project_id: resolved.projectId === "prj_planned" ? null : resolved.projectId,
|
|
287
308
|
manifest_path: manifest.manifestPath,
|
|
288
309
|
},
|
|
289
310
|
});
|
|
290
311
|
if (run.dryRun) {
|
|
291
312
|
run.setState(deployStep, "planned");
|
|
292
313
|
return run.result({
|
|
293
|
-
project_id: resolved.projectId,
|
|
314
|
+
project_id: resolved.projectId === "prj_planned" ? null : resolved.projectId,
|
|
294
315
|
manifest_path: manifest.manifestPath,
|
|
295
316
|
...(resolved.shouldWriteLink ? { workspace_link_path: resolved.linkPath } : {}),
|
|
296
317
|
});
|
|
@@ -401,7 +422,7 @@ export class NodeActions {
|
|
|
401
422
|
* principal has a display name before the deploy that will be credited to
|
|
402
423
|
* it, then join the project's room under that name. Explicit
|
|
403
424
|
* `identityName` / `RUN402_AGENT_NAME` wins and may rename an already-named
|
|
404
|
-
* principal;
|
|
425
|
+
* agent principal; human and unknown principals are preserved. Otherwise a detected client name is set only when no name
|
|
405
426
|
* exists yet, and reported as `detected`. The detected client is always
|
|
406
427
|
* reported (`detected` + `detection`), even when it was not applied, so an
|
|
407
428
|
* agent whose principal is already named sees why. Best-effort end to
|
|
@@ -412,15 +433,23 @@ export class NodeActions {
|
|
|
412
433
|
if (this.#targetKind() === "core") {
|
|
413
434
|
return { display_name: null, source: "unavailable" };
|
|
414
435
|
}
|
|
436
|
+
const explicitName = input.identityName?.trim() || declaredAgentName() || null;
|
|
437
|
+
const detectedName = detectClientName();
|
|
415
438
|
let current = null;
|
|
439
|
+
let principalId = null;
|
|
440
|
+
let principalType = null;
|
|
416
441
|
try {
|
|
417
442
|
const me = await this.sdk.orgs.whoami();
|
|
443
|
+
principalId = me.principal?.id ?? null;
|
|
444
|
+
principalType = me.principal?.type ?? null;
|
|
418
445
|
// The gateway never seeds display_name from a wallet subject (and
|
|
419
446
|
// nulled the ones it once did), so null here means exactly "unset".
|
|
420
447
|
current = me.principal?.display_name ?? null;
|
|
421
448
|
}
|
|
422
449
|
catch {
|
|
423
|
-
return { display_name: null, source: "unavailable"
|
|
450
|
+
return { display_name: null, source: "unavailable", detected: detectedName,
|
|
451
|
+
principal: { id: null, type: null, display_name: null },
|
|
452
|
+
client: { detected: detectedName, declared_name: explicitName } };
|
|
424
453
|
}
|
|
425
454
|
let displayName = current;
|
|
426
455
|
let source = "existing";
|
|
@@ -428,8 +457,13 @@ export class NodeActions {
|
|
|
428
457
|
// (RUN402_AGENT_NAME), then a specifically detected client. A generic
|
|
429
458
|
// guess is never persisted as a name. Detection always runs so the
|
|
430
459
|
// result can say what was seen even when it was not applied.
|
|
431
|
-
|
|
432
|
-
|
|
460
|
+
if (principalType !== "agent") {
|
|
461
|
+
run.skipStep({ action: "identity.name.set", description: "Preserve authenticated human or unknown principal identity; client detection does not rename it.", mutation: false, auto: true, details: { principal_type: principalType } });
|
|
462
|
+
return { display_name: current, source: "existing",
|
|
463
|
+
principal: { id: principalId, type: principalType, display_name: current },
|
|
464
|
+
client: { detected: detectedName, declared_name: explicitName },
|
|
465
|
+
detected: detectedName, detection: { applied: false, reason: "principal_identity_preserved" } };
|
|
466
|
+
}
|
|
433
467
|
const detectionReport = (applied) => {
|
|
434
468
|
const reason = applied
|
|
435
469
|
? "applied"
|
|
@@ -438,7 +472,9 @@ export class NodeActions {
|
|
|
438
472
|
: explicitName
|
|
439
473
|
? "explicit_name_wins"
|
|
440
474
|
: "name_already_set";
|
|
441
|
-
return { detected: detectedName, detection: { applied, reason }
|
|
475
|
+
return { detected: detectedName, detection: { applied, reason },
|
|
476
|
+
principal: { id: principalId, type: principalType, display_name: displayName },
|
|
477
|
+
client: { detected: detectedName, declared_name: explicitName } };
|
|
442
478
|
};
|
|
443
479
|
if (!displayName || (explicitName && explicitName !== displayName)) {
|
|
444
480
|
const desired = explicitName ?? detectedName;
|
|
@@ -588,14 +624,26 @@ export class NodeActions {
|
|
|
588
624
|
// level down) usually is one `--dir` away from the manifest it meant.
|
|
589
625
|
// Name what is one directory down so the hop is executable.
|
|
590
626
|
const nearby = await findNearbyManifests(workspaceDir);
|
|
591
|
-
const
|
|
627
|
+
const selectorArgs = [...(input.projectId ? ["--project", input.projectId] : []), ...(input.name ? ["--name", input.name] : [])];
|
|
628
|
+
const candidateActions = await Promise.all(nearby.map(async (entry) => ({
|
|
629
|
+
binding: await nearbyBinding(entry.path),
|
|
630
|
+
cwd: workspaceDir,
|
|
631
|
+
env: { RUN402_API_BASE: this.sdk.apiBase, ...(this.opts.profile ? { RUN402_WALLET: this.opts.profile } : {}) },
|
|
592
632
|
type: "run_in_directory",
|
|
593
|
-
command:
|
|
594
|
-
argv: ["run402", "up", "--check", "--dir", entry.relative_dir],
|
|
633
|
+
command: ["run402", "up", "--check", "--dir", entry.relative_dir, ...selectorArgs].map(shellArg).join(" "),
|
|
634
|
+
argv: ["run402", "up", "--check", "--dir", entry.relative_dir, ...selectorArgs],
|
|
635
|
+
app_root: dirname(entry.path),
|
|
636
|
+
manifest_path: entry.path,
|
|
637
|
+
safe_to_auto_execute: true,
|
|
595
638
|
path: entry.path,
|
|
596
639
|
dir: entry.relative_dir,
|
|
597
640
|
why: "A manifest exists one directory down; up does not walk into subdirectories.",
|
|
598
|
-
}));
|
|
641
|
+
})));
|
|
642
|
+
const nearbyActions = nearby.length > 1 ? [{
|
|
643
|
+
type: "select_application", safe_to_auto_execute: false,
|
|
644
|
+
why: "Choose the intended application; directory order does not indicate a recommendation.",
|
|
645
|
+
candidates: candidateActions.map((entry) => ({ ...entry, recommended: false })),
|
|
646
|
+
}] : candidateActions;
|
|
599
647
|
throw run.error(nearby.length > 0
|
|
600
648
|
? `No deploy manifest found in ${workspaceDir}, but ${nearby.length === 1 ? "one exists" : `${nearby.length} exist`} one directory down (${nearby.map((entry) => entry.relative_dir).join(", ")}). Re-run with --dir <that directory>, or add run402.json, run402.deploy.json or app.json here.`
|
|
601
649
|
: "No deploy manifest found. Add run402.json, run402.deploy.json or app.json, or pass --manifest for executable configs.", "UP_MANIFEST_REQUIRED", {
|
|
@@ -603,7 +651,7 @@ export class NodeActions {
|
|
|
603
651
|
candidates: MANIFEST_CANDIDATES,
|
|
604
652
|
executable_candidates: EXECUTABLE_MANIFEST_CANDIDATES,
|
|
605
653
|
nearby_manifests: nearby,
|
|
606
|
-
next_actions: [
|
|
654
|
+
next_actions: nearby.length > 1 ? nearbyActions : [
|
|
607
655
|
...nearbyActions,
|
|
608
656
|
{
|
|
609
657
|
type: "create_manifest",
|
|
@@ -672,9 +720,7 @@ export class NodeActions {
|
|
|
672
720
|
// output-dir field to narrow by), so the pre-build check is skipped
|
|
673
721
|
// then and the post-build normalization performs it instead.
|
|
674
722
|
const buildProducesFiles = (appSpec.build?.commands ?? []).length > 0;
|
|
675
|
-
|
|
676
|
-
await this.#assertAppReleaseFileReferences(appSpec, manifestPath, run);
|
|
677
|
-
}
|
|
723
|
+
const checkedRelease = !buildProducesFiles ? await this.#assertAppReleaseFileReferences(appSpec, manifestPath, run) : undefined;
|
|
678
724
|
run.setState(step, "succeeded", {
|
|
679
725
|
manifest_kind: "app",
|
|
680
726
|
manifest_path: manifestPath,
|
|
@@ -684,19 +730,19 @@ export class NodeActions {
|
|
|
684
730
|
});
|
|
685
731
|
return {
|
|
686
732
|
manifestKind: "app",
|
|
733
|
+
manifestProjectId: appSpec.project.id,
|
|
687
734
|
manifestPath,
|
|
688
|
-
releaseSpec: null,
|
|
735
|
+
releaseSpec: checkedRelease ?? null,
|
|
689
736
|
appGraph,
|
|
690
737
|
appSpec,
|
|
691
738
|
source,
|
|
692
739
|
};
|
|
693
740
|
}
|
|
694
741
|
}
|
|
695
|
-
const loaded = await loadDeployManifest(manifestPath, {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
});
|
|
742
|
+
const loaded = await this.#normalizeWithTypedFileErrors(() => loadDeployManifest(manifestPath, {
|
|
743
|
+
defaultProject: "prj_up_preflight_placeholder",
|
|
744
|
+
validateFiles: true,
|
|
745
|
+
}), run);
|
|
700
746
|
if (!hasDeployableContent(loaded.spec)) {
|
|
701
747
|
throw run.error("Deploy manifest contains no deployable sections.", "MANIFEST_EMPTY", { manifest_path: manifestPath });
|
|
702
748
|
}
|
|
@@ -704,13 +750,14 @@ export class NodeActions {
|
|
|
704
750
|
run.setState(step, "succeeded", {
|
|
705
751
|
manifest_kind: "release",
|
|
706
752
|
manifest_path: manifestPath,
|
|
707
|
-
project_id: loaded.spec.project,
|
|
753
|
+
project_id: loaded.spec.project === "prj_up_preflight_placeholder" ? null : loaded.spec.project,
|
|
708
754
|
idempotency_key: loaded.idempotencyKey ?? null,
|
|
709
755
|
});
|
|
710
756
|
return {
|
|
711
757
|
manifestKind: "release",
|
|
712
758
|
manifestPath,
|
|
713
759
|
releaseSpec: loaded.spec,
|
|
760
|
+
loaded,
|
|
714
761
|
idempotencyKey: loaded.idempotencyKey,
|
|
715
762
|
manifestProjectId: loaded.spec.project === "prj_up_preflight_placeholder"
|
|
716
763
|
? undefined
|
|
@@ -787,6 +834,11 @@ export class NodeActions {
|
|
|
787
834
|
async #assertManifestFileReferences(spec, manifestPath, run) {
|
|
788
835
|
try {
|
|
789
836
|
await assertLocalFileReferencesExist(spec, { manifestPath });
|
|
837
|
+
if (process.env.RUN402_DEPLOY_SKIP_SCAN !== "1") {
|
|
838
|
+
const scan = scanDeploymentSources(spec, dirname(manifestPath));
|
|
839
|
+
if (scan.errors.length)
|
|
840
|
+
throw run.error("Source scan blocked this application. Fix the reported findings, or explicitly override with RUN402_DEPLOY_SKIP_SCAN=1.", "R402_AUTH_PREFLIGHT_FAILED", scan);
|
|
841
|
+
}
|
|
790
842
|
}
|
|
791
843
|
catch (err) {
|
|
792
844
|
throw this.#retypeManifestFileError(err, run);
|
|
@@ -834,6 +886,8 @@ export class NodeActions {
|
|
|
834
886
|
try {
|
|
835
887
|
normalized = await normalizeDeployManifest(releaseInput, {
|
|
836
888
|
project: "prj_up_preflight_placeholder",
|
|
889
|
+
validateFiles: true,
|
|
890
|
+
manifestPath,
|
|
837
891
|
baseDir: dirname(manifestPath),
|
|
838
892
|
});
|
|
839
893
|
}
|
|
@@ -844,6 +898,7 @@ export class NodeActions {
|
|
|
844
898
|
return;
|
|
845
899
|
}
|
|
846
900
|
await this.#assertManifestFileReferences(normalized.spec, manifestPath, run);
|
|
901
|
+
return normalized.spec;
|
|
847
902
|
}
|
|
848
903
|
#planAppUpResult(input, manifest, run, opts) {
|
|
849
904
|
if (!manifest.appGraph) {
|
|
@@ -987,7 +1042,7 @@ export class NodeActions {
|
|
|
987
1042
|
})),
|
|
988
1043
|
};
|
|
989
1044
|
}
|
|
990
|
-
if (run.executionMode !== "check" && run.executionMode !== "printSpec" && !run.dryRun) {
|
|
1045
|
+
if (run.executionMode !== "check" && run.executionMode !== "printSpec" && run.executionMode !== "printManifest" && !run.dryRun) {
|
|
991
1046
|
const spendBlock = spendApprovalBlock(input, run);
|
|
992
1047
|
if (spendBlock)
|
|
993
1048
|
return spendBlock;
|
|
@@ -1013,7 +1068,7 @@ export class NodeActions {
|
|
|
1013
1068
|
blockedNodeId: block.nodeId,
|
|
1014
1069
|
});
|
|
1015
1070
|
return run.result({
|
|
1016
|
-
project_id: input.projectId ??
|
|
1071
|
+
project_id: input.projectId ?? null,
|
|
1017
1072
|
manifest_path: manifest.manifestPath,
|
|
1018
1073
|
app_graph: manifest.appGraph,
|
|
1019
1074
|
app_result: blocked,
|
|
@@ -1041,7 +1096,7 @@ export class NodeActions {
|
|
|
1041
1096
|
blockedNodeId: "project.ensure",
|
|
1042
1097
|
});
|
|
1043
1098
|
return run.result({
|
|
1044
|
-
project_id: input.projectId ??
|
|
1099
|
+
project_id: input.projectId ?? null,
|
|
1045
1100
|
manifest_path: manifest.manifestPath,
|
|
1046
1101
|
app_graph: manifest.appGraph,
|
|
1047
1102
|
app_result: blocked,
|
|
@@ -1060,7 +1115,7 @@ export class NodeActions {
|
|
|
1060
1115
|
schema_version: "run402.workspace-project.v1",
|
|
1061
1116
|
project_id: resolved.projectId,
|
|
1062
1117
|
...(input.name ? { name: input.name } : {}),
|
|
1063
|
-
target: { kind: this.#targetKind() },
|
|
1118
|
+
target: { kind: this.#targetKind(), api_base: this.sdk.apiBase },
|
|
1064
1119
|
created_at: resolved.link?.created_at ?? new Date().toISOString(),
|
|
1065
1120
|
updated_at: new Date().toISOString(),
|
|
1066
1121
|
}, resolved.link, run);
|
|
@@ -1793,7 +1848,7 @@ export class NodeActions {
|
|
|
1793
1848
|
});
|
|
1794
1849
|
run.setState(step, "running");
|
|
1795
1850
|
const linkConflict = workspaceLinkConflict(link, input, this.#targetKind(), linkPath);
|
|
1796
|
-
if (linkConflict
|
|
1851
|
+
if (linkConflict) {
|
|
1797
1852
|
throw run.error(linkConflict.message, "RUN402_WORKSPACE_LINK_CONFLICT", linkConflict.details);
|
|
1798
1853
|
}
|
|
1799
1854
|
let projectId = null;
|
|
@@ -1898,34 +1953,7 @@ export class NodeActions {
|
|
|
1898
1953
|
shouldWriteLink: true,
|
|
1899
1954
|
};
|
|
1900
1955
|
}
|
|
1901
|
-
|
|
1902
|
-
if (active) {
|
|
1903
|
-
const activeStep = run.addStep({
|
|
1904
|
-
action: "project.resolve",
|
|
1905
|
-
description: "Use active project for this workspace",
|
|
1906
|
-
mutation: false,
|
|
1907
|
-
auto: true,
|
|
1908
|
-
details: { active_project_id: active },
|
|
1909
|
-
});
|
|
1910
|
-
await run.approve(activeStep, [], `Use active project ${active} and link this workspace to it.`);
|
|
1911
|
-
run.setState(activeStep, run.dryRun ? "planned" : "succeeded", {
|
|
1912
|
-
project_id: active,
|
|
1913
|
-
source: "active",
|
|
1914
|
-
});
|
|
1915
|
-
run.setState(step, run.dryRun ? "planned" : "succeeded", {
|
|
1916
|
-
project_id: active,
|
|
1917
|
-
source: "active",
|
|
1918
|
-
link_path: linkPath,
|
|
1919
|
-
});
|
|
1920
|
-
return {
|
|
1921
|
-
projectId: active,
|
|
1922
|
-
source: "active",
|
|
1923
|
-
link,
|
|
1924
|
-
linkPath,
|
|
1925
|
-
shouldWriteLink: true,
|
|
1926
|
-
};
|
|
1927
|
-
}
|
|
1928
|
-
throw run.error("No project is configured for this workspace. Pass --project, add project_id to the manifest, or pass --name to create one.", "RUN402_PROJECT_REQUIRED", { link_path: linkPath, manifest_path: manifest.manifestPath });
|
|
1956
|
+
throw projectSelectionRequired(workspaceDir, manifest.manifestPath);
|
|
1929
1957
|
}
|
|
1930
1958
|
async #resolveProjectForVerify(input, manifest, workspaceDir, run) {
|
|
1931
1959
|
const linkPath = join(workspaceDir, ".run402", "project.json");
|
|
@@ -1988,7 +2016,7 @@ export class NodeActions {
|
|
|
1988
2016
|
description: "Write workspace project link",
|
|
1989
2017
|
mutation: true,
|
|
1990
2018
|
auto: true,
|
|
1991
|
-
details: { path, project_id: link.project_id, name: link.name ?? null },
|
|
2019
|
+
details: { path, project_id: run.dryRun && link.project_id === "prj_planned" ? null : link.project_id, name: link.name ?? null },
|
|
1992
2020
|
});
|
|
1993
2021
|
await run.approve(step, ["workspace.link.write"], `Write ${shortPath(path)}.`);
|
|
1994
2022
|
if (run.dryRun) {
|
|
@@ -2124,7 +2152,7 @@ class ActionRun {
|
|
|
2124
2152
|
return {
|
|
2125
2153
|
action: this.input.type,
|
|
2126
2154
|
mode: this.executionMode,
|
|
2127
|
-
dry_run: this.dryRun || this.executionMode === "check" || this.executionMode === "printSpec" || this.executionMode === "plan",
|
|
2155
|
+
dry_run: this.dryRun || this.executionMode === "check" || (this.executionMode === "printSpec" || this.executionMode === "printManifest") || this.executionMode === "plan",
|
|
2128
2156
|
target: this.target,
|
|
2129
2157
|
steps: this.steps,
|
|
2130
2158
|
result,
|
|
@@ -2182,20 +2210,6 @@ function appPublicOrigin(input, spec) {
|
|
|
2182
2210
|
return null;
|
|
2183
2211
|
return `https://${subdomain}.run402.com`;
|
|
2184
2212
|
}
|
|
2185
|
-
function materializeTemplates(value, values) {
|
|
2186
|
-
if (typeof value === "string") {
|
|
2187
|
-
return value.replace(/\$\{([^}]+)\}/g, (_match, key) => values[key] ?? "");
|
|
2188
|
-
}
|
|
2189
|
-
if (Array.isArray(value))
|
|
2190
|
-
return value.map((item) => materializeTemplates(item, values));
|
|
2191
|
-
if (value && typeof value === "object") {
|
|
2192
|
-
return Object.fromEntries(Object.entries(value).map(([key, nested]) => [
|
|
2193
|
-
key,
|
|
2194
|
-
materializeTemplates(nested, values),
|
|
2195
|
-
]));
|
|
2196
|
-
}
|
|
2197
|
-
return value;
|
|
2198
|
-
}
|
|
2199
2213
|
function appMailboxSlug(logicalName, spec) {
|
|
2200
2214
|
return spec.slug ?? logicalName.replace(/_/g, "-");
|
|
2201
2215
|
}
|
|
@@ -2317,6 +2331,9 @@ async function classifyAppVerifyAttempt(input) {
|
|
|
2317
2331
|
if (input.attempt.status === input.check.expect.status) {
|
|
2318
2332
|
return { kind: "success" };
|
|
2319
2333
|
}
|
|
2334
|
+
if ((input.attempt.status === 400 || input.attempt.status === 429) && input.attempt.edgeCode === null && !input.attempt.edgeHeader) {
|
|
2335
|
+
return { kind: "failed" };
|
|
2336
|
+
}
|
|
2320
2337
|
const host = hostnameFromUrl(input.url);
|
|
2321
2338
|
const freshness = host ? freshnessForHost(input.context, host) : { fresh: false, ageSeconds: null };
|
|
2322
2339
|
if (freshness.fresh &&
|
|
@@ -2611,17 +2628,7 @@ function appBuildBlock(input, manifest) {
|
|
|
2611
2628
|
return null;
|
|
2612
2629
|
}
|
|
2613
2630
|
async function findManifest(workspaceDir) {
|
|
2614
|
-
|
|
2615
|
-
const path = join(workspaceDir, candidate);
|
|
2616
|
-
try {
|
|
2617
|
-
await lstat(path);
|
|
2618
|
-
return path;
|
|
2619
|
-
}
|
|
2620
|
-
catch {
|
|
2621
|
-
// try next candidate
|
|
2622
|
-
}
|
|
2623
|
-
}
|
|
2624
|
-
return null;
|
|
2631
|
+
return (await resolveApplicationScope({ dir: workspaceDir })).manifest_path;
|
|
2625
2632
|
}
|
|
2626
2633
|
const NEARBY_MANIFEST_SCAN_LIMIT = 200;
|
|
2627
2634
|
/**
|
|
@@ -2732,7 +2739,9 @@ async function readWorkspaceProjectLink(path) {
|
|
|
2732
2739
|
try {
|
|
2733
2740
|
raw = await readFile(path, "utf-8");
|
|
2734
2741
|
}
|
|
2735
|
-
catch {
|
|
2742
|
+
catch (err) {
|
|
2743
|
+
if (err.code !== "ENOENT")
|
|
2744
|
+
throw err;
|
|
2736
2745
|
return null;
|
|
2737
2746
|
}
|
|
2738
2747
|
let parsed;
|
|
@@ -2913,4 +2922,43 @@ function actionErrorDetails(err) {
|
|
|
2913
2922
|
function assertNever(value) {
|
|
2914
2923
|
throw new LocalError(`Unknown Run402 action ${value.type}`, "running Run402 action", { code: "RUN402_ACTION_UNKNOWN" });
|
|
2915
2924
|
}
|
|
2925
|
+
/** Resolve only local destination intent. Approval and global active state are never selectors. */
|
|
2926
|
+
export async function resolveDeploymentTarget(input) {
|
|
2927
|
+
const linkPath = join(input.appRoot, ".run402", "project.json");
|
|
2928
|
+
const link = await readWorkspaceProjectLink(linkPath);
|
|
2929
|
+
const conflict = workspaceLinkConflict(link, input, input.targetKind ?? "unknown", linkPath);
|
|
2930
|
+
if (conflict)
|
|
2931
|
+
throw new LocalError(conflict.message, "resolving deployment target", { code: "RUN402_WORKSPACE_LINK_CONFLICT", details: conflict.details });
|
|
2932
|
+
if (link?.target?.api_base && input.apiBase && link.target.api_base.replace(/\/$/, "") !== input.apiBase.replace(/\/$/, "")) {
|
|
2933
|
+
throw new LocalError("Workspace link belongs to a different API target.", "resolving deployment target", { code: "RUN402_WORKSPACE_LINK_CONFLICT", details: { reason: "api_base_mismatch", link_path: linkPath } });
|
|
2934
|
+
}
|
|
2935
|
+
const selectors = [input.projectId, input.environmentProjectId, link?.project_id, input.manifestProjectId].filter(Boolean);
|
|
2936
|
+
if (new Set(selectors).size > 1) {
|
|
2937
|
+
throw new LocalError("Deployment project selectors disagree.", "resolving deployment target", { code: "RUN402_PROJECT_CONFLICT", details: { explicit_project_id: input.projectId ?? null, linked_project_id: link?.project_id ?? null, manifest_project_id: input.manifestProjectId ?? null } });
|
|
2938
|
+
}
|
|
2939
|
+
const projectId = input.projectId ?? input.environmentProjectId ?? link?.project_id ?? input.manifestProjectId ?? null;
|
|
2940
|
+
if (projectId && input.name && (!link || link.name !== input.name)) {
|
|
2941
|
+
throw new LocalError("--name is creation intent and cannot select or rename an existing project. Pass --project alone.", "resolving deployment target", { code: "RUN402_PROJECT_CONFLICT", details: { project_id: projectId, name: input.name } });
|
|
2942
|
+
}
|
|
2943
|
+
if (!projectId && !input.name && !input.allowUnresolved)
|
|
2944
|
+
throw projectSelectionRequired(input.appRoot, input.manifestPath);
|
|
2945
|
+
return { project_id: projectId, project_name: input.name ?? link?.name ?? null,
|
|
2946
|
+
source: input.projectId || input.environmentProjectId ? "explicit" : link ? "workspace_link" : input.manifestProjectId ? "manifest" : input.name ? "create" : "unresolved" };
|
|
2947
|
+
}
|
|
2948
|
+
function projectSelectionRequired(appRoot, manifestPath) {
|
|
2949
|
+
return new LocalError("No deployment destination is selected. Pass --project for an existing project or --name to create one; -y never selects the global active project.", "resolving deployment target", {
|
|
2950
|
+
code: "UP_PROJECT_REQUIRED", details: { app_root: appRoot, manifest_path: manifestPath ?? null, mutation_state: "not_started" },
|
|
2951
|
+
next_actions: [{ type: "select_project", safe_to_auto_execute: false, why: "Select the intended project explicitly, or name a new project, then retry in this application directory.", app_root: appRoot }],
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
async function nearbyBinding(manifestPath) {
|
|
2955
|
+
try {
|
|
2956
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
2957
|
+
const link = await readWorkspaceProjectLink(join(dirname(manifestPath), ".run402", "project.json"));
|
|
2958
|
+
return { app_name: manifest.app?.name ?? manifest.app?.id ?? null, manifest_project_id: typeof manifest.project === "string" ? manifest.project : manifest.project_id ?? manifest.project?.id ?? null, linked_project_id: link?.project_id ?? null, target: link?.target ?? null };
|
|
2959
|
+
}
|
|
2960
|
+
catch {
|
|
2961
|
+
return { status: "unknown", reason: "Local binding could not be read; select the application and run its check." };
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2916
2964
|
//# sourceMappingURL=actions-node.js.map
|