@prisma/cli 3.0.0-dev.87.1 → 3.0.0-dev.90.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.
@@ -0,0 +1,80 @@
1
+ import "./build-settings.js";
2
+ import "node:fs/promises";
3
+ import "node:path";
4
+ import { normalizeArtifactSymlinks, resolveBuildStrategy } from "@prisma/compute-sdk";
5
+ //#region src/lib/app/build.ts
6
+ const APP_BUILD_TYPES = [
7
+ "auto",
8
+ "bun",
9
+ "nextjs",
10
+ "nuxt",
11
+ "astro",
12
+ "nestjs",
13
+ "tanstack-start"
14
+ ];
15
+ const RESOLVED_APP_BUILD_TYPES = APP_BUILD_TYPES.filter((buildType) => buildType !== "auto");
16
+ var AppBuildStrategy = class {
17
+ #appPath;
18
+ #entrypoint;
19
+ #buildType;
20
+ #signal;
21
+ #buildSettings;
22
+ constructor(options) {
23
+ this.#appPath = options.appPath;
24
+ this.#entrypoint = options.entrypoint;
25
+ this.#buildType = options.buildType ?? "auto";
26
+ this.#signal = options.signal;
27
+ this.#buildSettings = options.buildSettings;
28
+ }
29
+ async canBuild(signal = this.#signal) {
30
+ const { strategy } = await resolveAppBuildStrategy({
31
+ appPath: this.#appPath,
32
+ entrypoint: this.#entrypoint,
33
+ buildType: this.#buildType,
34
+ signal,
35
+ buildSettings: this.#buildSettings
36
+ });
37
+ return strategy.canBuild(signal);
38
+ }
39
+ async execute(signal = this.#signal) {
40
+ const { artifact } = await executeAppBuild({
41
+ appPath: this.#appPath,
42
+ entrypoint: this.#entrypoint,
43
+ buildType: this.#buildType,
44
+ signal,
45
+ buildSettings: this.#buildSettings
46
+ });
47
+ return artifact;
48
+ }
49
+ };
50
+ async function executeAppBuild(options) {
51
+ const { strategy, buildType } = await resolveAppBuildStrategy({
52
+ appPath: options.appPath,
53
+ entrypoint: options.entrypoint,
54
+ buildType: options.buildType ?? "auto",
55
+ signal: options.signal,
56
+ buildSettings: options.buildSettings
57
+ });
58
+ const artifact = await strategy.execute(options.signal);
59
+ try {
60
+ await normalizeArtifactSymlinks(artifact.directory, options.appPath, options.signal);
61
+ return {
62
+ artifact,
63
+ buildType
64
+ };
65
+ } catch (error) {
66
+ await artifact.cleanup?.().catch(() => void 0);
67
+ throw error;
68
+ }
69
+ }
70
+ async function resolveAppBuildStrategy(options) {
71
+ return resolveBuildStrategy({
72
+ appPath: options.appPath,
73
+ buildType: options.buildType,
74
+ entrypoint: options.entrypoint,
75
+ buildSettings: options.buildSettings,
76
+ signal: options.signal
77
+ });
78
+ }
79
+ //#endregion
80
+ export { APP_BUILD_TYPES, AppBuildStrategy, RESOLVED_APP_BUILD_TYPES, executeAppBuild };
@@ -21,13 +21,12 @@ async function readBunPackageJson(appPath, signal) {
21
21
  }
22
22
  }
23
23
  function readBunPackageEntrypoint(packageJson) {
24
- if (typeof packageJson?.main === "string") return packageJson.main;
25
- if (typeof packageJson?.module === "string") return packageJson.module;
24
+ return typeof packageJson?.main === "string" ? packageJson.main : void 0;
26
25
  }
27
26
  async function resolveBunEntrypoint(appPath, explicitEntrypoint, signal) {
28
27
  const packageJson = await readBunPackageJson(appPath, signal);
29
28
  const candidate = explicitEntrypoint ?? readBunPackageEntrypoint(packageJson);
30
- if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main or module.");
29
+ if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main.");
31
30
  if (path.isAbsolute(candidate)) throw new Error("Entrypoint must be a relative path.");
32
31
  const normalized = path.normalize(candidate);
33
32
  if (normalized.startsWith("..") || path.isAbsolute(normalized) || normalized.includes(`${path.sep}..${path.sep}`)) throw new Error("Entrypoint must not escape the app directory.");
@@ -0,0 +1,147 @@
1
+ import { CliError } from "../../shell/errors.js";
2
+ import { COMPUTE_CONFIG_FILENAME, COMPUTE_CONFIG_FILENAME as COMPUTE_CONFIG_FILENAME$1, ComputeConfigTargetRequiredError, computeTargetAppDir, frameworkByKey, inferComputeTargetFromCwd, loadComputeConfig, selectComputeDeployTarget } from "@prisma/compute-sdk/config";
3
+ import path from "node:path";
4
+ import { matchError } from "better-result";
5
+ //#region src/lib/app/compute-config.ts
6
+ /**
7
+ * Loads the nearest compute config, searching from `cwd` up to the source
8
+ * root (repository or workspace boundary). Thin adapter over the SDK loader
9
+ * keeping the CLI's positional-signal call shape.
10
+ */
11
+ async function loadComputeConfig$1(cwd, signal) {
12
+ return loadComputeConfig(cwd, { signal });
13
+ }
14
+ /** Local build/run strategy implied by a configured framework. */
15
+ function computeFrameworkToBuildType(framework) {
16
+ return frameworkByKey(framework).buildType;
17
+ }
18
+ function mergeComputeDeployInputs(options) {
19
+ const { cli, target, configFilename } = options;
20
+ const configAnnotation = `set by ${configFilename}`;
21
+ const framework = cli.framework ? {
22
+ value: cli.framework,
23
+ annotation: "set by --framework"
24
+ } : target?.framework ? {
25
+ value: target.framework,
26
+ annotation: configAnnotation
27
+ } : void 0;
28
+ const entrypoint = cli.entrypoint ? {
29
+ value: cli.entrypoint,
30
+ annotation: "set by --entry"
31
+ } : target?.entry ? {
32
+ value: target.entry,
33
+ annotation: configAnnotation
34
+ } : void 0;
35
+ const httpPort = cli.httpPort ? {
36
+ value: cli.httpPort,
37
+ annotation: "set by --http-port"
38
+ } : target?.httpPort ? {
39
+ value: String(target.httpPort),
40
+ annotation: configAnnotation
41
+ } : void 0;
42
+ const cliEnvInputs = cli.envInputs && cli.envInputs.length > 0 ? cli.envInputs : void 0;
43
+ const configEnvInputs = target && target.envInputs.length > 0 ? target.envInputs : void 0;
44
+ const envInputs = cliEnvInputs ?? configEnvInputs;
45
+ const configAppName = target?.name ? {
46
+ value: target.name,
47
+ annotation: configAnnotation
48
+ } : target?.key ? {
49
+ value: target.key,
50
+ annotation: configAnnotation
51
+ } : void 0;
52
+ return {
53
+ framework,
54
+ entrypoint,
55
+ httpPort,
56
+ envInputs,
57
+ envInputsFromConfig: !cliEnvInputs && configEnvInputs !== void 0,
58
+ configAppName,
59
+ appRoot: target?.root ?? void 0
60
+ };
61
+ }
62
+ /**
63
+ * Merges CLI inputs for the local `app build` and `app run` commands with a
64
+ * selected config target. Explicit flags win; `--build-type auto` is the
65
+ * flag default and defers to the configured framework.
66
+ */
67
+ function mergeComputeLocalInputs(options) {
68
+ const { cli, target } = options;
69
+ const cliBuildType = cli.buildType && cli.buildType !== "auto" ? cli.buildType : void 0;
70
+ const configBuildType = target?.framework ? computeFrameworkToBuildType(target.framework) : void 0;
71
+ return {
72
+ entrypoint: cli.entrypoint ?? target?.entry ?? void 0,
73
+ buildType: cliBuildType ?? configBuildType,
74
+ buildTypeFromConfig: !cliBuildType && configBuildType !== void 0,
75
+ port: cli.port ?? (target?.httpPort ? String(target.httpPort) : void 0),
76
+ appRoot: target?.root ?? void 0
77
+ };
78
+ }
79
+ function computeConfigErrorToCliError(error, commandName = "deploy") {
80
+ const command = `prisma-cli app ${commandName}`;
81
+ return matchError(error, {
82
+ ComputeConfigAmbiguousError: (ambiguous) => new CliError({
83
+ code: "COMPUTE_CONFIG_INVALID",
84
+ domain: "app",
85
+ summary: "Multiple compute config files found",
86
+ why: ambiguous.message,
87
+ fix: `Keep exactly one compute config file, preferably ${COMPUTE_CONFIG_FILENAME}.`,
88
+ meta: { configPaths: ambiguous.configPaths },
89
+ exitCode: 2,
90
+ nextSteps: [command]
91
+ }),
92
+ ComputeConfigLoadError: (load) => new CliError({
93
+ code: "COMPUTE_CONFIG_INVALID",
94
+ domain: "app",
95
+ summary: `Could not load ${path.basename(load.configPath)}`,
96
+ why: load.message,
97
+ fix: `Fix the error in ${path.basename(load.configPath)} and rerun the command.`,
98
+ where: load.configPath,
99
+ meta: { configPath: load.configPath },
100
+ exitCode: 2,
101
+ nextSteps: [command]
102
+ }),
103
+ ComputeConfigInvalidError: (invalid) => new CliError({
104
+ code: "COMPUTE_CONFIG_INVALID",
105
+ domain: "app",
106
+ summary: `Invalid ${path.basename(invalid.configPath)}`,
107
+ why: invalid.issues.join(" "),
108
+ fix: `Edit ${path.basename(invalid.configPath)} so it default-exports defineComputeConfig({ app }) or defineComputeConfig({ apps }).`,
109
+ where: invalid.configPath,
110
+ meta: {
111
+ configPath: invalid.configPath,
112
+ issues: invalid.issues
113
+ },
114
+ exitCode: 2,
115
+ nextSteps: [command]
116
+ }),
117
+ ComputeConfigTargetRequiredError: (required) => new CliError({
118
+ code: "COMPUTE_CONFIG_TARGET_REQUIRED",
119
+ domain: "app",
120
+ summary: "App target required",
121
+ why: required.message,
122
+ fix: `Pass the app target, for example ${command} <target>.`,
123
+ meta: {
124
+ configPath: required.configPath,
125
+ availableTargets: required.availableTargets
126
+ },
127
+ exitCode: 2,
128
+ nextSteps: required.availableTargets.map((target) => `${command} ${target}`)
129
+ }),
130
+ ComputeConfigTargetUnknownError: (unknown) => new CliError({
131
+ code: "COMPUTE_CONFIG_TARGET_UNKNOWN",
132
+ domain: "app",
133
+ summary: `Unknown app target "${unknown.requestedTarget}"`,
134
+ why: unknown.message,
135
+ fix: unknown.availableTargets.length > 0 ? `Pass one of the configured targets: ${unknown.availableTargets.join(", ")}.` : "Remove the target argument; this config defines a single app.",
136
+ meta: {
137
+ configPath: unknown.configPath,
138
+ requestedTarget: unknown.requestedTarget,
139
+ availableTargets: unknown.availableTargets
140
+ },
141
+ exitCode: 2,
142
+ nextSteps: unknown.availableTargets.length > 0 ? unknown.availableTargets.map((target) => `${command} ${target}`) : [command]
143
+ })
144
+ });
145
+ }
146
+ //#endregion
147
+ export { COMPUTE_CONFIG_FILENAME$1 as COMPUTE_CONFIG_FILENAME, ComputeConfigTargetRequiredError, computeConfigErrorToCliError, computeFrameworkToBuildType, computeTargetAppDir, inferComputeTargetFromCwd, loadComputeConfig$1 as loadComputeConfig, mergeComputeDeployInputs, mergeComputeLocalInputs, selectComputeDeployTarget };
@@ -0,0 +1,58 @@
1
+ //#region src/lib/app/deploy-plan.ts
2
+ /**
3
+ * Decides whether a deploy covers one app or the whole system. A multi-app
4
+ * config with no target named or inferred deploys every target in declaration
5
+ * order; everything else (single-app config, an explicit/inferred target, or
6
+ * no config at all) is a single deploy.
7
+ */
8
+ function planAppDeploy(options) {
9
+ const { config, requestedTarget, hasCreateProject } = options;
10
+ if (!(config !== null && config.kind === "multi" && !requestedTarget && config.targets.length > 1)) return { mode: "single" };
11
+ const total = config.targets.length;
12
+ return {
13
+ mode: "all",
14
+ targets: config.targets.map((target, index) => ({
15
+ targetKey: target.key,
16
+ index,
17
+ total,
18
+ bindsCreateProject: index === 0 && hasCreateProject
19
+ }))
20
+ };
21
+ }
22
+ /**
23
+ * Names the per-app inputs present in a deploy-all invocation, in the order
24
+ * they should be reported. An empty list means the run is unambiguous; the
25
+ * caller renders the usage error when it is not.
26
+ */
27
+ function perAppInputsForDeployAll(inputs) {
28
+ return [
29
+ ["--app", inputs.appName],
30
+ ["--framework", inputs.framework],
31
+ ["--entry", inputs.entrypoint],
32
+ ["--http-port", inputs.httpPort],
33
+ ["--env", inputs.envAssignments?.length ? inputs.envAssignments : void 0],
34
+ [inputs.appIdEnvVar.name, inputs.appIdEnvVar.value]
35
+ ].filter(([, value]) => value !== void 0).map(([flag]) => flag);
36
+ }
37
+ /**
38
+ * Describes a deploy-all run that stopped at the first failing target: which
39
+ * targets already went live and which were never attempted, so the caller can
40
+ * surface a converge path. Pure; the caller folds this into the CliError.
41
+ */
42
+ function describeDeployAllFailure(options) {
43
+ const { targetKeys, failedIndex, completed } = options;
44
+ const failedTarget = targetKeys[failedIndex];
45
+ const notAttempted = targetKeys.slice(failedIndex + 1);
46
+ return {
47
+ failedTarget,
48
+ completed,
49
+ notAttempted,
50
+ contextLines: [
51
+ `Deploying all apps stopped at "${failedTarget}" (${failedIndex + 1}/${targetKeys.length}).`,
52
+ ...completed.length > 0 ? [`Already live: ${completed.map((entry) => entry.target).join(", ")}.`] : [],
53
+ ...notAttempted.length > 0 ? [`Not attempted: ${notAttempted.join(", ")}.`] : []
54
+ ]
55
+ };
56
+ }
57
+ //#endregion
58
+ export { describeDeployAllFailure, perAppInputsForDeployAll, planAppDeploy };
@@ -1,6 +1,6 @@
1
1
  import { renderDeployOutputRows } from "./deploy-output.js";
2
- //#region src/lib/app/preview-progress.ts
3
- function createPreviewDeployProgressState() {
2
+ //#region src/lib/app/deploy-progress.ts
3
+ function createDeployProgressState() {
4
4
  return {
5
5
  buildStarted: false,
6
6
  buildCompleted: false,
@@ -13,7 +13,7 @@ function createPreviewDeployProgressState() {
13
13
  promotedUrl: null
14
14
  };
15
15
  }
16
- function createPreviewDeployProgress(output, ui, enabled, state = createPreviewDeployProgressState()) {
16
+ function createDeployProgress(output, ui, enabled, state = createDeployProgressState()) {
17
17
  const write = (line) => {
18
18
  if (!enabled) return;
19
19
  output.write(`${line}\n`);
@@ -63,7 +63,7 @@ function createPreviewDeployProgress(output, ui, enabled, state = createPreviewD
63
63
  function formatArtifactSize(byteLength) {
64
64
  return `${(byteLength / 1024 / 1024).toFixed(1)} MB`;
65
65
  }
66
- function createPreviewPromoteProgress(output, enabled) {
66
+ function createPromoteProgress(output, enabled) {
67
67
  if (!enabled) return;
68
68
  const write = (line) => {
69
69
  output.write(`${line}\n`);
@@ -97,4 +97,4 @@ function createPreviewPromoteProgress(output, enabled) {
97
97
  };
98
98
  }
99
99
  //#endregion
100
- export { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress };
100
+ export { createDeployProgress, createDeployProgressState, createPromoteProgress };
@@ -1,25 +1,9 @@
1
- import { readBunPackageEntrypoint, readBunPackageJson, resolveBunEntrypoint } from "./bun-project.js";
2
- import { access } from "node:fs/promises";
1
+ import { resolveBunEntrypoint } from "./bun-project.js";
2
+ import "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { spawn } from "node:child_process";
5
5
  //#region src/lib/app/local-dev.ts
6
- const NEXT_CONFIG_FILENAMES = [
7
- "next.config.js",
8
- "next.config.mjs",
9
- "next.config.ts",
10
- "next.config.mts"
11
- ];
12
6
  const DEFAULT_LOCAL_DEV_PORT = 3e3;
13
- async function resolveLocalBuildType(appPath, buildType, signal) {
14
- if (buildType === "bun" || buildType === "nextjs") return buildType;
15
- if (buildType !== "auto") return null;
16
- return detectLocalBuildType(appPath, signal);
17
- }
18
- async function detectLocalBuildType(appPath, signal) {
19
- if (await isNextProject(appPath, signal)) return "nextjs";
20
- if (await isBunProject(appPath, signal)) return "bun";
21
- return null;
22
- }
23
7
  async function runLocalApp(options) {
24
8
  const spawnImpl = options.spawnImpl ?? spawn;
25
9
  if (options.buildType === "nextjs") {
@@ -92,47 +76,6 @@ async function runLocalApp(options) {
92
76
  signal: command.signal
93
77
  };
94
78
  }
95
- async function isNextProject(appPath, signal) {
96
- for (const fileName of NEXT_CONFIG_FILENAMES) {
97
- signal?.throwIfAborted();
98
- try {
99
- await access(path.join(appPath, fileName));
100
- signal?.throwIfAborted();
101
- return true;
102
- } catch (error) {
103
- if (signal?.aborted) throw error;
104
- }
105
- }
106
- return hasDependency(await readBunPackageJson(appPath, signal), "next");
107
- }
108
- async function isBunProject(appPath, signal) {
109
- signal?.throwIfAborted();
110
- try {
111
- await access(path.join(appPath, "bun.lock"));
112
- signal?.throwIfAborted();
113
- return true;
114
- } catch (error) {
115
- if (signal?.aborted) throw error;
116
- }
117
- signal?.throwIfAborted();
118
- try {
119
- await access(path.join(appPath, "bun.lockb"));
120
- signal?.throwIfAborted();
121
- return true;
122
- } catch (error) {
123
- if (signal?.aborted) throw error;
124
- }
125
- const packageJson = await readBunPackageJson(appPath, signal);
126
- if (!packageJson) return false;
127
- const hasEntrypoint = typeof readBunPackageEntrypoint(packageJson) === "string";
128
- const hasBunDependency = hasDependency(packageJson, "@types/bun") || hasDependency(packageJson, "bun");
129
- const usesBunScripts = (typeof packageJson.scripts === "object" && packageJson.scripts !== null ? Object.values(packageJson.scripts) : []).some((value) => typeof value === "string" && /\bbun\b/.test(value));
130
- return hasEntrypoint && (hasBunDependency || usesBunScripts);
131
- }
132
- function hasDependency(packageJson, dependencyName) {
133
- if (!packageJson) return false;
134
- return [packageJson.dependencies, packageJson.devDependencies].some((group) => typeof group === "object" && group !== null && dependencyName in group);
135
- }
136
79
  async function runWithFallback(candidates, options, spawnImpl, missingCommandMessage) {
137
80
  for (const candidate of candidates) try {
138
81
  const result = await spawnCommand(candidate, options, spawnImpl);
@@ -163,4 +106,4 @@ function spawnCommand(candidate, options, spawnImpl) {
163
106
  });
164
107
  }
165
108
  //#endregion
166
- export { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp };
109
+ export { DEFAULT_LOCAL_DEV_PORT, runLocalApp };
@@ -104,12 +104,12 @@ function productionDeployRequiresFlagError() {
104
104
  "",
105
105
  "Production deploys require explicit intent. Re-run with:",
106
106
  "",
107
- " prisma app deploy --prod",
107
+ " prisma-cli app deploy --prod",
108
108
  "",
109
109
  "Or deploy a preview from a feature branch:",
110
110
  "",
111
111
  " git checkout -b <branch-name>",
112
- " prisma app deploy"
112
+ " prisma-cli app deploy"
113
113
  ]
114
114
  });
115
115
  }
@@ -3,7 +3,7 @@ import { resolveStateDir } from "../shell/runtime.js";
3
3
  import { readLocalGitState } from "./git/local-status.js";
4
4
  //#region src/lib/diagnostics.ts
5
5
  async function collectCommandDiagnostics(context, options = {}) {
6
- const stateDir = resolveStateDir(context.runtime);
6
+ const stateDir = await resolveStateDir(context.runtime);
7
7
  return {
8
8
  cwd: context.runtime.cwd,
9
9
  stateFilePath: resolveLocalStateFilePath(stateDir),
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ //#region src/lib/fs/home-path.ts
3
+ /**
4
+ * Shortens a path under the user's home directory to `~/...` for display,
5
+ * posix-style on every platform. Falls back to the Windows home variables
6
+ * when `HOME` is unset (native cmd/PowerShell sessions).
7
+ */
8
+ function shortenHomePath(value, env) {
9
+ const resolved = path.resolve(value);
10
+ const home = resolveHomeDirectory(env);
11
+ if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
12
+ const relative = path.relative(home, resolved).split(path.sep).join("/");
13
+ return relative ? `~/${relative}` : "~";
14
+ }
15
+ return resolved;
16
+ }
17
+ function resolveHomeDirectory(env) {
18
+ if (env.HOME) return path.resolve(env.HOME);
19
+ if (env.USERPROFILE) return path.resolve(env.USERPROFILE);
20
+ if (env.HOMEDRIVE && env.HOMEPATH) return path.resolve(`${env.HOMEDRIVE}${env.HOMEPATH}`);
21
+ return null;
22
+ }
23
+ //#endregion
24
+ export { shortenHomePath };
@@ -1,9 +1,22 @@
1
1
  import { access, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  //#region src/lib/git/local-branch.ts
4
+ /**
5
+ * Resolves the checked-out branch the way git does: the nearest `.git`
6
+ * (directory or worktree file) from `cwd` upward owns the answer, so
7
+ * monorepo commands run from inside a package see the repository branch.
8
+ * Returns null for detached HEAD or when no repository contains `cwd`.
9
+ */
4
10
  async function readLocalGitBranch(cwd, signal) {
5
- const headPath = await resolveGitHeadPath(path.join(cwd, ".git"), signal);
6
- if (!headPath) return null;
11
+ for (let directory = path.resolve(cwd);;) {
12
+ const headPath = await resolveGitHeadPath(path.join(directory, ".git"), signal);
13
+ if (headPath) return readBranchFromHead(headPath, signal);
14
+ const parent = path.dirname(directory);
15
+ if (parent === directory) return null;
16
+ directory = parent;
17
+ }
18
+ }
19
+ async function readBranchFromHead(headPath, signal) {
7
20
  try {
8
21
  const head = (await readFile(headPath, {
9
22
  encoding: "utf8",
@@ -12,7 +25,6 @@ async function readLocalGitBranch(cwd, signal) {
12
25
  if (head.startsWith("ref: refs/heads/")) return head.slice(16);
13
26
  } catch (error) {
14
27
  if (signal.aborted) throw error;
15
- return null;
16
28
  }
17
29
  return null;
18
30
  }
@@ -339,7 +339,7 @@ async function resolveBoundProjectTarget(options, projects, settings) {
339
339
  }
340
340
  async function readImplicitLocalPin(options, settings) {
341
341
  if (options.explicitProject || settings.allowEnvProjectId && options.envProjectId) return Result.ok(null);
342
- const localPinResult = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
342
+ const localPinResult = await readLocalResolutionPin(options.projectDir ?? options.context.runtime.cwd, options.context.runtime.signal);
343
343
  if (localPinResult.isErr()) return Result.err(localPinReadErrorToProjectError(localPinResult.error));
344
344
  const localPin = localPinResult.value;
345
345
  if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id) return Result.err(new LocalProjectWorkspaceMismatchError({
@@ -1,7 +1,9 @@
1
1
  import { CliError, usageError } from "../../shell/errors.js";
2
+ import { shortenHomePath } from "../fs/home-path.js";
2
3
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
3
4
  import "../../shell/command-arguments.js";
4
5
  import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
6
+ import path from "node:path";
5
7
  import { Result, matchError } from "better-result";
6
8
  //#region src/lib/project/setup.ts
7
9
  function isValidProjectSetupName(projectName) {
@@ -13,22 +15,21 @@ function validateProjectSetupNameText(value, fallback) {
13
15
  }
14
16
  function resolveProjectForSetup(projectRef, projects, workspace) {
15
17
  const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
16
- const match = matches[0];
17
- if (matches.length === 1 && match) return match;
18
+ if (matches.length === 1) return matches[0];
18
19
  if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
19
20
  throw projectNotFoundError(projectRef, workspace);
20
21
  }
21
- async function bindProjectToDirectory(context, workspace, project, action) {
22
+ async function bindProjectToDirectory(context, workspace, project, action, directory = context.runtime.cwd) {
22
23
  return Result.gen(async function* () {
23
- yield* Result.await(writeLocalResolutionPin(context.runtime.cwd, {
24
+ yield* Result.await(writeLocalResolutionPin(directory, {
24
25
  workspaceId: workspace.id,
25
26
  projectId: project.id
26
27
  }, context.runtime.signal));
27
- yield* Result.await(ensureLocalResolutionPinGitignore(context.runtime.cwd, context.runtime.signal));
28
+ yield* Result.await(ensureLocalResolutionPinGitignore(directory, context.runtime.signal));
28
29
  return Result.ok({
29
30
  workspace,
30
31
  project,
31
- directory: formatSetupDirectory(context.runtime.cwd),
32
+ directory: formatSetupDirectory(directory, context),
32
33
  localPin: {
33
34
  path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
34
35
  written: true
@@ -110,8 +111,9 @@ function projectCreateFailedError(error, projectName, workspace, options) {
110
111
  nextSteps: options.nextSteps
111
112
  });
112
113
  }
113
- function formatSetupDirectory(cwd) {
114
- const basename = cwd.split(/[\\/]/).filter(Boolean).pop();
114
+ function formatSetupDirectory(directory, context) {
115
+ if (path.resolve(directory) !== path.resolve(context.runtime.cwd)) return shortenHomePath(directory, context.runtime.env);
116
+ const basename = directory.split(/[\\/]/).filter(Boolean).pop();
115
117
  return basename ? `./${basename}` : ".";
116
118
  }
117
119
  function extractHttpStatus(error) {