run402 4.81.0 → 4.83.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.
@@ -191,8 +191,17 @@ export class NodeActions {
191
191
  }
192
192
  }
193
193
  async #runUpFromWorkspace(input, run, source, startedAt) {
194
- const workspaceDir = source.workspaceDir;
194
+ let workspaceDir = source.workspaceDir;
195
195
  const manifest = await this.#discoverAndValidateManifest(input, workspaceDir, source.metadata, run);
196
+ workspaceDir = dirname(manifest.manifestPath);
197
+ if (!input.verifyOnly) {
198
+ await resolveDeploymentTarget({
199
+ appRoot: workspaceDir, manifestPath: manifest.manifestPath, projectId: input.projectId,
200
+ manifestProjectId: manifest.manifestProjectId ?? manifest.appSpec?.project.id, name: input.name,
201
+ targetKind: this.#targetKind(), apiBase: this.sdk.apiBase,
202
+ allowUnresolved: run.executionMode === "check" || run.executionMode === "printSpec" || run.dryRun,
203
+ });
204
+ }
196
205
  if (manifest.manifestKind === "app") {
197
206
  if (input.verifyOnly) {
198
207
  return this.#verifyAppManifestOnly(input, manifest, workspaceDir, run, startedAt);
@@ -272,7 +281,7 @@ export class NodeActions {
272
281
  schema_version: "run402.workspace-project.v1",
273
282
  project_id: resolved.projectId,
274
283
  ...(input.name ? { name: input.name } : {}),
275
- target: { kind: this.#targetKind() },
284
+ target: { kind: this.#targetKind(), api_base: this.sdk.apiBase },
276
285
  created_at: resolved.link?.created_at ?? new Date().toISOString(),
277
286
  updated_at: new Date().toISOString(),
278
287
  }, resolved.link, run);
@@ -588,14 +597,26 @@ export class NodeActions {
588
597
  // level down) usually is one `--dir` away from the manifest it meant.
589
598
  // Name what is one directory down so the hop is executable.
590
599
  const nearby = await findNearbyManifests(workspaceDir);
591
- const nearbyActions = nearby.map((entry) => ({
600
+ const selectorArgs = [...(input.projectId ? ["--project", input.projectId] : []), ...(input.name ? ["--name", input.name] : [])];
601
+ const candidateActions = await Promise.all(nearby.map(async (entry) => ({
602
+ binding: await nearbyBinding(entry.path),
603
+ cwd: workspaceDir,
604
+ env: { RUN402_API_BASE: this.sdk.apiBase, ...(this.opts.profile ? { RUN402_WALLET: this.opts.profile } : {}) },
592
605
  type: "run_in_directory",
593
- command: `run402 up --check --dir ${shellArg(entry.relative_dir)}`,
594
- argv: ["run402", "up", "--check", "--dir", entry.relative_dir],
606
+ command: ["run402", "up", "--check", "--dir", entry.relative_dir, ...selectorArgs].map(shellArg).join(" "),
607
+ argv: ["run402", "up", "--check", "--dir", entry.relative_dir, ...selectorArgs],
608
+ app_root: dirname(entry.path),
609
+ manifest_path: entry.path,
610
+ safe_to_auto_execute: true,
595
611
  path: entry.path,
596
612
  dir: entry.relative_dir,
597
613
  why: "A manifest exists one directory down; up does not walk into subdirectories.",
598
- }));
614
+ })));
615
+ const nearbyActions = nearby.length > 1 ? [{
616
+ type: "select_application", safe_to_auto_execute: false,
617
+ why: "Choose the intended application; directory order does not indicate a recommendation.",
618
+ candidates: candidateActions.map((entry) => ({ ...entry, recommended: false })),
619
+ }] : candidateActions;
599
620
  throw run.error(nearby.length > 0
600
621
  ? `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
622
  : "No deploy manifest found. Add run402.json, run402.deploy.json or app.json, or pass --manifest for executable configs.", "UP_MANIFEST_REQUIRED", {
@@ -603,7 +624,7 @@ export class NodeActions {
603
624
  candidates: MANIFEST_CANDIDATES,
604
625
  executable_candidates: EXECUTABLE_MANIFEST_CANDIDATES,
605
626
  nearby_manifests: nearby,
606
- next_actions: [
627
+ next_actions: nearby.length > 1 ? nearbyActions : [
607
628
  ...nearbyActions,
608
629
  {
609
630
  type: "create_manifest",
@@ -684,6 +705,7 @@ export class NodeActions {
684
705
  });
685
706
  return {
686
707
  manifestKind: "app",
708
+ manifestProjectId: appSpec.project.id,
687
709
  manifestPath,
688
710
  releaseSpec: null,
689
711
  appGraph,
@@ -693,9 +715,7 @@ export class NodeActions {
693
715
  }
694
716
  }
695
717
  const loaded = await loadDeployManifest(manifestPath, {
696
- ...(input.projectId
697
- ? { project: input.projectId }
698
- : { defaultProject: "prj_up_preflight_placeholder" }),
718
+ defaultProject: "prj_up_preflight_placeholder",
699
719
  });
700
720
  if (!hasDeployableContent(loaded.spec)) {
701
721
  throw run.error("Deploy manifest contains no deployable sections.", "MANIFEST_EMPTY", { manifest_path: manifestPath });
@@ -1060,7 +1080,7 @@ export class NodeActions {
1060
1080
  schema_version: "run402.workspace-project.v1",
1061
1081
  project_id: resolved.projectId,
1062
1082
  ...(input.name ? { name: input.name } : {}),
1063
- target: { kind: this.#targetKind() },
1083
+ target: { kind: this.#targetKind(), api_base: this.sdk.apiBase },
1064
1084
  created_at: resolved.link?.created_at ?? new Date().toISOString(),
1065
1085
  updated_at: new Date().toISOString(),
1066
1086
  }, resolved.link, run);
@@ -1793,7 +1813,7 @@ export class NodeActions {
1793
1813
  });
1794
1814
  run.setState(step, "running");
1795
1815
  const linkConflict = workspaceLinkConflict(link, input, this.#targetKind(), linkPath);
1796
- if (linkConflict && !input.projectId) {
1816
+ if (linkConflict) {
1797
1817
  throw run.error(linkConflict.message, "RUN402_WORKSPACE_LINK_CONFLICT", linkConflict.details);
1798
1818
  }
1799
1819
  let projectId = null;
@@ -1898,34 +1918,7 @@ export class NodeActions {
1898
1918
  shouldWriteLink: true,
1899
1919
  };
1900
1920
  }
1901
- const active = await this.sdk.projects.active();
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 });
1921
+ throw projectSelectionRequired(workspaceDir, manifest.manifestPath);
1929
1922
  }
1930
1923
  async #resolveProjectForVerify(input, manifest, workspaceDir, run) {
1931
1924
  const linkPath = join(workspaceDir, ".run402", "project.json");
@@ -2732,7 +2725,9 @@ async function readWorkspaceProjectLink(path) {
2732
2725
  try {
2733
2726
  raw = await readFile(path, "utf-8");
2734
2727
  }
2735
- catch {
2728
+ catch (err) {
2729
+ if (err.code !== "ENOENT")
2730
+ throw err;
2736
2731
  return null;
2737
2732
  }
2738
2733
  let parsed;
@@ -2913,4 +2908,43 @@ function actionErrorDetails(err) {
2913
2908
  function assertNever(value) {
2914
2909
  throw new LocalError(`Unknown Run402 action ${value.type}`, "running Run402 action", { code: "RUN402_ACTION_UNKNOWN" });
2915
2910
  }
2911
+ /** Resolve only local destination intent. Approval and global active state are never selectors. */
2912
+ export async function resolveDeploymentTarget(input) {
2913
+ const linkPath = join(input.appRoot, ".run402", "project.json");
2914
+ const link = await readWorkspaceProjectLink(linkPath);
2915
+ const conflict = workspaceLinkConflict(link, input, input.targetKind ?? "unknown", linkPath);
2916
+ if (conflict)
2917
+ throw new LocalError(conflict.message, "resolving deployment target", { code: "RUN402_WORKSPACE_LINK_CONFLICT", details: conflict.details });
2918
+ if (link?.target?.api_base && input.apiBase && link.target.api_base.replace(/\/$/, "") !== input.apiBase.replace(/\/$/, "")) {
2919
+ 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 } });
2920
+ }
2921
+ const selectors = [input.projectId, input.environmentProjectId, link?.project_id, input.manifestProjectId].filter(Boolean);
2922
+ if (new Set(selectors).size > 1) {
2923
+ 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 } });
2924
+ }
2925
+ const projectId = input.projectId ?? input.environmentProjectId ?? link?.project_id ?? input.manifestProjectId ?? null;
2926
+ if (projectId && input.name && (!link || link.name !== input.name)) {
2927
+ 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 } });
2928
+ }
2929
+ if (!projectId && !input.name && !input.allowUnresolved)
2930
+ throw projectSelectionRequired(input.appRoot, input.manifestPath);
2931
+ return { project_id: projectId, project_name: input.name ?? link?.name ?? null,
2932
+ source: input.projectId || input.environmentProjectId ? "explicit" : link ? "workspace_link" : input.manifestProjectId ? "manifest" : input.name ? "create" : "unresolved" };
2933
+ }
2934
+ function projectSelectionRequired(appRoot, manifestPath) {
2935
+ 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", {
2936
+ code: "UP_PROJECT_REQUIRED", details: { app_root: appRoot, manifest_path: manifestPath ?? null, mutation_state: "not_started" },
2937
+ 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 }],
2938
+ });
2939
+ }
2940
+ async function nearbyBinding(manifestPath) {
2941
+ try {
2942
+ const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
2943
+ const link = await readWorkspaceProjectLink(join(dirname(manifestPath), ".run402", "project.json"));
2944
+ 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 };
2945
+ }
2946
+ catch {
2947
+ return { status: "unknown", reason: "Local binding could not be read; select the application and run its check." };
2948
+ }
2949
+ }
2916
2950
  //# sourceMappingURL=actions-node.js.map