@looma/prisma-cli 0.1.1 → 0.1.2

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.
@@ -591,6 +591,7 @@ function deployFailedError(summary, error, nextSteps) {
591
591
  summary,
592
592
  why: error instanceof Error ? error.message : String(error),
593
593
  fix: "Retry the command, or rerun with --trace for more detailed diagnostics.",
594
+ debug: formatDebugDetails(error),
594
595
  exitCode: 1,
595
596
  nextSteps
596
597
  });
@@ -613,6 +614,7 @@ function buildFailedError(summary, error) {
613
614
  summary,
614
615
  why: error instanceof Error ? error.message : String(error),
615
616
  fix: "Inspect the framework output, fix the build issue, and rerun prisma app build.",
617
+ debug: formatDebugDetails(error),
616
618
  exitCode: 1,
617
619
  nextSteps: ["prisma app build", "prisma app deploy"]
618
620
  });
@@ -638,6 +640,7 @@ function removeFailedError(summary, error, nextSteps) {
638
640
  summary,
639
641
  why: error instanceof Error ? error.message : String(error),
640
642
  fix: "Retry the command, or rerun with --trace for more detailed diagnostics.",
643
+ debug: formatDebugDetails(error),
641
644
  exitCode: 1,
642
645
  nextSteps
643
646
  });
@@ -645,6 +648,10 @@ function removeFailedError(summary, error, nextSteps) {
645
648
  function localStateCleanupWarning(target, error) {
646
649
  return `The app was removed remotely, but the local ${target} state could not be cleared: ${error instanceof Error ? error.message : String(error)}`;
647
650
  }
651
+ function formatDebugDetails(error) {
652
+ if (error instanceof Error) return error.stack ?? error.message;
653
+ return typeof error === "string" ? error : null;
654
+ }
648
655
  function isMissingProjectError(error) {
649
656
  return error instanceof Error && error.message === "Resource Not Found";
650
657
  }
@@ -1,7 +1,15 @@
1
1
  import path from "node:path";
2
- import { cp, readdir, readlink, rm, stat } from "node:fs/promises";
3
- import { BunBuild, NextjsBuild } from "@prisma/compute-sdk";
2
+ import { chmod, copyFile, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, rm, stat } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import { execFile } from "node:child_process";
5
+ import { BunBuild } from "@prisma/compute-sdk";
4
6
  //#region src/lib/app/prototype-build.ts
7
+ const NEXT_CONFIG_FILENAMES = [
8
+ "next.config.js",
9
+ "next.config.mjs",
10
+ "next.config.ts",
11
+ "next.config.mts"
12
+ ];
5
13
  var PrototypeBuildStrategy = class {
6
14
  #appPath;
7
15
  #entrypoint;
@@ -49,7 +57,7 @@ async function executePrototypeBuild(options) {
49
57
  async function resolvePrototypeBuildStrategy(options) {
50
58
  if (options.buildType === "nextjs") return {
51
59
  buildType: "nextjs",
52
- strategy: new NextjsBuild({ appPath: options.appPath })
60
+ strategy: new PrototypeNextjsBuild({ appPath: options.appPath })
53
61
  };
54
62
  if (options.buildType === "bun") return {
55
63
  buildType: "bun",
@@ -58,7 +66,7 @@ async function resolvePrototypeBuildStrategy(options) {
58
66
  entrypoint: options.entrypoint
59
67
  })
60
68
  };
61
- const nextjsStrategy = new NextjsBuild({ appPath: options.appPath });
69
+ const nextjsStrategy = new PrototypeNextjsBuild({ appPath: options.appPath });
62
70
  if (await nextjsStrategy.canBuild()) return {
63
71
  buildType: "nextjs",
64
72
  strategy: nextjsStrategy
@@ -71,6 +79,106 @@ async function resolvePrototypeBuildStrategy(options) {
71
79
  })
72
80
  };
73
81
  }
82
+ var PrototypeNextjsBuild = class {
83
+ #appPath;
84
+ constructor(options) {
85
+ this.#appPath = options.appPath;
86
+ }
87
+ async canBuild() {
88
+ return await this.#hasNextConfig() || await this.#hasNextDependency();
89
+ }
90
+ async execute() {
91
+ await this.#runBuild();
92
+ const standaloneDir = path.join(this.#appPath, ".next", "standalone");
93
+ if (!(await stat(standaloneDir).catch(() => null))?.isDirectory()) throw new Error("Next.js build did not produce standalone output. Add output: \"standalone\" to your next.config file.");
94
+ const outDir = await mkdtemp(path.join(os.tmpdir(), "compute-build-"));
95
+ try {
96
+ const artifactDir = path.join(outDir, "app");
97
+ await stageNextjsStandaloneArtifact({
98
+ standaloneDir,
99
+ artifactDir,
100
+ appPath: this.#appPath
101
+ });
102
+ const publicDir = path.join(this.#appPath, "public");
103
+ if (await directoryExists(publicDir)) await cp(publicDir, path.join(artifactDir, "public"), { recursive: true });
104
+ const staticDir = path.join(this.#appPath, ".next", "static");
105
+ if (await directoryExists(staticDir)) await cp(staticDir, path.join(artifactDir, ".next", "static"), { recursive: true });
106
+ return {
107
+ directory: artifactDir,
108
+ entrypoint: "server.js",
109
+ defaultPortMapping: { http: 3e3 },
110
+ cleanup: () => rm(outDir, {
111
+ recursive: true,
112
+ force: true
113
+ })
114
+ };
115
+ } catch (error) {
116
+ await rm(outDir, {
117
+ recursive: true,
118
+ force: true
119
+ });
120
+ throw error;
121
+ }
122
+ }
123
+ async #hasNextConfig() {
124
+ let entries;
125
+ try {
126
+ entries = await readdir(this.#appPath);
127
+ } catch {
128
+ return false;
129
+ }
130
+ return entries.some((entry) => NEXT_CONFIG_FILENAMES.includes(entry));
131
+ }
132
+ async #hasNextDependency() {
133
+ const packageJsonPath = path.join(this.#appPath, "package.json");
134
+ let content;
135
+ try {
136
+ content = await readFile(packageJsonPath, "utf8");
137
+ } catch {
138
+ return false;
139
+ }
140
+ let parsed;
141
+ try {
142
+ parsed = JSON.parse(content);
143
+ } catch {
144
+ return false;
145
+ }
146
+ const deps = isRecord(parsed.dependencies) ? parsed.dependencies : {};
147
+ const devDeps = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
148
+ return "next" in deps || "next" in devDeps;
149
+ }
150
+ async #runBuild() {
151
+ const candidates = [
152
+ {
153
+ command: path.join(this.#appPath, "node_modules", ".bin", "next"),
154
+ args: ["build"]
155
+ },
156
+ {
157
+ command: "npx",
158
+ args: ["next", "build"]
159
+ },
160
+ {
161
+ command: "bunx",
162
+ args: ["next", "build"]
163
+ }
164
+ ];
165
+ for (const { command, args } of candidates) try {
166
+ await exec(command, args, this.#appPath);
167
+ return;
168
+ } catch (error) {
169
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
170
+ throw error;
171
+ }
172
+ throw new Error("Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.");
173
+ }
174
+ };
175
+ async function stageNextjsStandaloneArtifact(options) {
176
+ const standaloneRoot = path.resolve(options.standaloneDir);
177
+ await copyPathMaterializingSymlinks(standaloneRoot, path.resolve(options.artifactDir), {
178
+ standaloneRoot,
179
+ appRoot: path.resolve(options.appPath)
180
+ });
181
+ }
74
182
  async function normalizeArtifactSymlinks(artifactDir, appPath) {
75
183
  const normalizedArtifactDir = path.resolve(artifactDir);
76
184
  const normalizedAppPath = path.resolve(appPath);
@@ -105,5 +213,66 @@ function isPathWithin(rootPath, candidatePath) {
105
213
  const relativePath = path.relative(rootPath, candidatePath);
106
214
  return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
107
215
  }
216
+ async function copyPathMaterializingSymlinks(sourcePath, destinationPath, options) {
217
+ const sourceStat = await lstat(sourcePath);
218
+ if (sourceStat.isSymbolicLink()) {
219
+ await copyPathMaterializingSymlinks(await resolveSymlinkTarget(sourcePath, options), destinationPath, options);
220
+ return;
221
+ }
222
+ if (sourceStat.isDirectory()) {
223
+ await mkdir(destinationPath, { recursive: true });
224
+ const entries = await readdir(sourcePath, { withFileTypes: true });
225
+ for (const entry of entries) await copyPathMaterializingSymlinks(path.join(sourcePath, entry.name), path.join(destinationPath, entry.name), options);
226
+ return;
227
+ }
228
+ if (sourceStat.isFile()) {
229
+ await mkdir(path.dirname(destinationPath), { recursive: true });
230
+ await copyFile(sourcePath, destinationPath);
231
+ await chmod(destinationPath, sourceStat.mode);
232
+ }
233
+ }
234
+ async function resolveSymlinkTarget(symlinkPath, options) {
235
+ const linkTarget = await readlink(symlinkPath);
236
+ const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget);
237
+ if (await pathExists(resolvedTarget)) {
238
+ if (!isPathWithin(options.appRoot, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
239
+ return resolvedTarget;
240
+ }
241
+ if (isPathWithin(options.standaloneRoot, resolvedTarget)) {
242
+ const fallbackTarget = path.join(options.appRoot, path.relative(options.standaloneRoot, resolvedTarget));
243
+ if (await pathExists(fallbackTarget)) return fallbackTarget;
244
+ }
245
+ throw new Error(`Next.js standalone symlink target is missing: ${symlinkPath} -> ${linkTarget} (resolved to ${resolvedTarget})`);
246
+ }
247
+ async function directoryExists(dirPath) {
248
+ return (await stat(dirPath).catch(() => null))?.isDirectory() ?? false;
249
+ }
250
+ function exec(command, args, cwd) {
251
+ return new Promise((resolve, reject) => {
252
+ execFile(command, args, { cwd }, (error, _stdout, stderr) => {
253
+ if (error) {
254
+ if ("code" in error && error.code === "ENOENT") {
255
+ reject(Object.assign(/* @__PURE__ */ new Error(`${command} not found`), { code: "ENOENT" }));
256
+ return;
257
+ }
258
+ const message = stderr.trim() || error.message;
259
+ reject(/* @__PURE__ */ new Error(`Next.js build failed:\n${message}`));
260
+ return;
261
+ }
262
+ resolve();
263
+ });
264
+ });
265
+ }
266
+ function isRecord(value) {
267
+ return typeof value === "object" && value !== null;
268
+ }
269
+ async function pathExists(targetPath) {
270
+ try {
271
+ await stat(targetPath);
272
+ return true;
273
+ } catch {
274
+ return false;
275
+ }
276
+ }
108
277
  //#endregion
109
278
  export { PrototypeBuildStrategy, executePrototypeBuild };
@@ -6,6 +6,7 @@ var CliError = class extends Error {
6
6
  summary;
7
7
  why;
8
8
  fix;
9
+ debug;
9
10
  where;
10
11
  meta;
11
12
  docsUrl;
@@ -20,6 +21,7 @@ var CliError = class extends Error {
20
21
  this.summary = options.summary;
21
22
  this.why = options.why;
22
23
  this.fix = options.fix;
24
+ this.debug = options.debug ?? null;
23
25
  this.where = options.where ?? null;
24
26
  this.meta = options.meta ?? {};
25
27
  this.docsUrl = options.docsUrl ?? null;
@@ -37,7 +37,13 @@ function writeHumanError(output, ui, error, options) {
37
37
  lines.push(`Why: ${error.why}`);
38
38
  }
39
39
  if (error.fix) lines.push(`Fix: ${error.fix}`);
40
- if (!options.trace) {
40
+ if (options.trace) {
41
+ if (error.debug) {
42
+ lines.push("");
43
+ lines.push("Trace:");
44
+ lines.push(...error.debug.trimEnd().split("\n"));
45
+ }
46
+ } else {
41
47
  lines.push("");
42
48
  lines.push("More: Re-run with --trace for deeper diagnostics");
43
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@looma/prisma-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Prototype Prisma Compute CLI.",
5
5
  "type": "module",
6
6
  "bin": {