@prisma/compute-sdk 0.31.0 → 0.33.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.
Files changed (45) hide show
  1. package/dist/artifact-stage.d.ts +19 -0
  2. package/dist/artifact-stage.d.ts.map +1 -1
  3. package/dist/artifact-stage.js +24 -10
  4. package/dist/artifact-stage.js.map +1 -1
  5. package/dist/astro-build.d.ts.map +1 -1
  6. package/dist/astro-build.js +1 -0
  7. package/dist/astro-build.js.map +1 -1
  8. package/dist/config/index.d.ts +3 -2
  9. package/dist/config/index.d.ts.map +1 -1
  10. package/dist/config/index.js +3 -2
  11. package/dist/config/index.js.map +1 -1
  12. package/dist/config/json-schema.d.ts +229 -0
  13. package/dist/config/json-schema.d.ts.map +1 -0
  14. package/dist/config/json-schema.js +128 -0
  15. package/dist/config/json-schema.js.map +1 -0
  16. package/dist/config/load.d.ts +2 -1
  17. package/dist/config/load.d.ts.map +1 -1
  18. package/dist/config/load.js +26 -4
  19. package/dist/config/load.js.map +1 -1
  20. package/dist/config/normalize.d.ts +4 -0
  21. package/dist/config/normalize.d.ts.map +1 -1
  22. package/dist/config/normalize.js +9 -4
  23. package/dist/config/normalize.js.map +1 -1
  24. package/dist/config/serialize.d.ts +15 -0
  25. package/dist/config/serialize.d.ts.map +1 -1
  26. package/dist/config/serialize.js +14 -0
  27. package/dist/config/serialize.js.map +1 -1
  28. package/dist/configured-artifact-trace.d.ts +15 -0
  29. package/dist/configured-artifact-trace.d.ts.map +1 -0
  30. package/dist/configured-artifact-trace.js +58 -0
  31. package/dist/configured-artifact-trace.js.map +1 -0
  32. package/dist/configured-artifact.d.ts +1 -0
  33. package/dist/configured-artifact.d.ts.map +1 -1
  34. package/dist/configured-artifact.js +10 -0
  35. package/dist/configured-artifact.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/artifact-stage.ts +38 -18
  38. package/src/astro-build.ts +1 -0
  39. package/src/config/index.ts +9 -1
  40. package/src/config/json-schema.ts +147 -0
  41. package/src/config/load.ts +38 -7
  42. package/src/config/normalize.ts +9 -4
  43. package/src/config/serialize.ts +27 -0
  44. package/src/configured-artifact-trace.ts +98 -0
  45. package/src/configured-artifact.ts +11 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * JSON Schema for `prisma.compute.json`, built from the same constants as the
3
+ * runtime validator so enums and key sets cannot drift.
4
+ *
5
+ * The schema exists for editor assistance ($schema autocomplete/validation)
6
+ * and static tooling. It encodes structure, enums, and ranges; cross-field
7
+ * rules (e.g. the custom framework requiring build output settings) stay in
8
+ * `normalizeComputeConfig`, which remains the authority for every format.
9
+ */
10
+
11
+ import { COMPUTE_FRAMEWORKS, COMPUTE_REGIONS } from "./types.ts";
12
+
13
+ /**
14
+ * Intended publish location of the compute config JSON Schema. The URL does
15
+ * not resolve yet, so serializers omit `$schema` by default; once the schema
16
+ * is hosted here, pass this constant to `serializeComputeConfigJson` to emit
17
+ * it in generated configs.
18
+ */
19
+ export const COMPUTE_CONFIG_JSON_SCHEMA_URL =
20
+ "https://schemas.prisma.io/compute.schema.json";
21
+
22
+ const ENV_SCHEMA = {
23
+ description:
24
+ "Environment variables for the deploy. A string is shorthand for a dotenv file path.",
25
+ oneOf: [
26
+ { type: "string", minLength: 1 },
27
+ {
28
+ type: "object",
29
+ additionalProperties: false,
30
+ properties: {
31
+ file: {
32
+ description:
33
+ "Dotenv file path(s) resolved relative to the config file directory.",
34
+ oneOf: [
35
+ { type: "string", minLength: 1 },
36
+ { type: "array", items: { type: "string", minLength: 1 } },
37
+ ],
38
+ },
39
+ vars: {
40
+ description:
41
+ "Inline environment variable assignments. This file is committed — keep secrets in platform branch config.",
42
+ type: "object",
43
+ additionalProperties: { type: "string", minLength: 1 },
44
+ },
45
+ },
46
+ },
47
+ ],
48
+ } as const;
49
+
50
+ const BUILD_SCHEMA = {
51
+ description:
52
+ "Build settings. When present, these own the app's build configuration.",
53
+ type: "object",
54
+ additionalProperties: false,
55
+ properties: {
56
+ command: {
57
+ description:
58
+ "Build command run in the app root. null skips the build step.",
59
+ type: ["string", "null"],
60
+ },
61
+ outputDirectory: {
62
+ description:
63
+ 'Framework output path relative to the app root, e.g. ".next/standalone".',
64
+ type: "string",
65
+ minLength: 1,
66
+ },
67
+ entrypoint: {
68
+ description:
69
+ "Entrypoint for the built artifact, relative to outputDirectory when one is set.",
70
+ type: "string",
71
+ minLength: 1,
72
+ },
73
+ },
74
+ } as const;
75
+
76
+ const APP_SCHEMA = {
77
+ type: "object",
78
+ additionalProperties: false,
79
+ properties: {
80
+ name: {
81
+ description: "Deployed app name. Defaults to inference.",
82
+ type: "string",
83
+ minLength: 1,
84
+ },
85
+ region: {
86
+ description: "App region override. Defaults to the project region.",
87
+ enum: COMPUTE_REGIONS,
88
+ },
89
+ root: {
90
+ description:
91
+ "App directory relative to the config file. Defaults to the config file directory.",
92
+ type: "string",
93
+ minLength: 1,
94
+ },
95
+ framework: {
96
+ description:
97
+ "Framework to deploy. Defaults to detection from the app directory.",
98
+ enum: COMPUTE_FRAMEWORKS,
99
+ },
100
+ entry: {
101
+ description:
102
+ "Entrypoint path for Bun (and Hono) deploys, relative to the app root.",
103
+ type: "string",
104
+ minLength: 1,
105
+ },
106
+ httpPort: {
107
+ description:
108
+ "HTTP port the deployed app listens on. Defaults to the framework default.",
109
+ type: "integer",
110
+ minimum: 1,
111
+ maximum: 65535,
112
+ },
113
+ env: ENV_SCHEMA,
114
+ build: BUILD_SCHEMA,
115
+ },
116
+ } as const;
117
+
118
+ /**
119
+ * The compute config JSON Schema document (draft-07 for the broadest editor
120
+ * support). Publish this at `COMPUTE_CONFIG_JSON_SCHEMA_URL`.
121
+ */
122
+ export const COMPUTE_CONFIG_JSON_SCHEMA = {
123
+ $schema: "http://json-schema.org/draft-07/schema#",
124
+ $id: COMPUTE_CONFIG_JSON_SCHEMA_URL,
125
+ title: "Prisma Compute config",
126
+ description:
127
+ "Static serialization of the Prisma Compute config. Define app for a single-app repository or apps for a multi-app repository.",
128
+ type: "object",
129
+ additionalProperties: false,
130
+ properties: {
131
+ $schema: { type: "string" },
132
+ region: {
133
+ description:
134
+ "Default region used when creating new apps. Existing apps keep their current region.",
135
+ enum: COMPUTE_REGIONS,
136
+ },
137
+ app: APP_SCHEMA,
138
+ apps: {
139
+ description: "Apps keyed by deploy target name.",
140
+ type: "object",
141
+ minProperties: 1,
142
+ additionalProperties: APP_SCHEMA,
143
+ propertyNames: { minLength: 1 },
144
+ },
145
+ },
146
+ oneOf: [{ required: ["app"] }, { required: ["apps"] }],
147
+ } as const;
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
- import { access } from "node:fs/promises";
2
+ import { access, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
@@ -14,15 +14,18 @@ import {
14
14
  import { sourceRootLineage } from "./source-root.ts";
15
15
 
16
16
  export const COMPUTE_CONFIG_FILENAME = "prisma.compute.ts";
17
+ export const COMPUTE_CONFIG_JSON_FILENAME = "prisma.compute.json";
17
18
 
18
- // Highest priority first. TypeScript is the canonical format; the rest exist
19
- // so plain JavaScript projects are not forced into TypeScript.
19
+ // One config schema, two serializations: TypeScript (and its JS variants) for
20
+ // programmatic configs, JSON for static dependency-free configs that any tool
21
+ // can read and write without executing code. Exactly one file may exist.
20
22
  export const COMPUTE_CONFIG_FILENAMES = [
21
23
  "prisma.compute.ts",
22
24
  "prisma.compute.mts",
23
25
  "prisma.compute.js",
24
26
  "prisma.compute.mjs",
25
27
  "prisma.compute.cjs",
28
+ "prisma.compute.json",
26
29
  ] as const;
27
30
 
28
31
  // Config files import the typed helper through this specifier; it aliases
@@ -137,10 +140,13 @@ export async function loadComputeConfig(
137
140
 
138
141
  const configPath = candidates[0] as string;
139
142
  signal?.throwIfAborted();
140
- const imported = await importComputeConfigModule(
141
- configPath,
142
- options?.configModuleAlias,
143
- );
143
+ const imported =
144
+ path.extname(configPath) === ".json"
145
+ ? await readComputeConfigJson(configPath)
146
+ : await importComputeConfigModule(
147
+ configPath,
148
+ options?.configModuleAlias,
149
+ );
144
150
  if (imported.isErr()) {
145
151
  return Result.err(imported.error);
146
152
  }
@@ -150,6 +156,31 @@ export async function loadComputeConfig(
150
156
  return Result.ok(null);
151
157
  }
152
158
 
159
+ async function readComputeConfigJson(
160
+ configPath: string,
161
+ ): Promise<Result<unknown, ComputeConfigLoadError>> {
162
+ return Result.tryPromise({
163
+ try: async () => {
164
+ const parsed: unknown = JSON.parse(await readFile(configPath, "utf8"));
165
+ // "$schema" is editor tooling metadata, not config; drop it before
166
+ // validation so JSON and TS configs share one schema exactly.
167
+ if (
168
+ parsed !== null &&
169
+ typeof parsed === "object" &&
170
+ !Array.isArray(parsed)
171
+ ) {
172
+ const { $schema: _schema, ...config } = parsed as Record<
173
+ string,
174
+ unknown
175
+ >;
176
+ return config;
177
+ }
178
+ return parsed;
179
+ },
180
+ catch: (cause) => new ComputeConfigLoadError(configPath, cause),
181
+ });
182
+ }
183
+
153
184
  async function importComputeConfigModule(
154
185
  configPath: string,
155
186
  configModuleAlias: string | undefined,
@@ -107,7 +107,8 @@ export type ComputeConfigTargetError =
107
107
  | ComputeConfigTargetRequiredError
108
108
  | ComputeConfigTargetUnknownError;
109
109
 
110
- const KNOWN_APP_KEYS = [
110
+ // Exported so the JSON Schema (and its tests) provably cover the same keys.
111
+ export const KNOWN_APP_KEYS = [
111
112
  "name",
112
113
  "region",
113
114
  "root",
@@ -117,9 +118,13 @@ const KNOWN_APP_KEYS = [
117
118
  "env",
118
119
  "build",
119
120
  ] as const;
120
- const KNOWN_TOP_LEVEL_KEYS = ["app", "apps", "region"] as const;
121
- const KNOWN_ENV_KEYS = ["file", "vars"] as const;
122
- const KNOWN_BUILD_KEYS = ["command", "outputDirectory", "entrypoint"] as const;
121
+ export const KNOWN_TOP_LEVEL_KEYS = ["app", "apps", "region"] as const;
122
+ export const KNOWN_ENV_KEYS = ["file", "vars"] as const;
123
+ export const KNOWN_BUILD_KEYS = [
124
+ "command",
125
+ "outputDirectory",
126
+ "entrypoint",
127
+ ] as const;
123
128
 
124
129
  /**
125
130
  * Validates and normalizes a config module's default export. Reports every
@@ -29,6 +29,33 @@ export function serializeComputeConfig(
29
29
  ].join("\n");
30
30
  }
31
31
 
32
+ /**
33
+ * Renders a `prisma.compute.json` source file for a config — the same
34
+ * serialization as `serializeComputeConfig` in the dependency-free static
35
+ * format. Output round-trips through `normalizeComputeConfig` and is stable
36
+ * for committing to a repository.
37
+ */
38
+ export function serializeComputeConfigJson(
39
+ config: ComputeConfig,
40
+ options?: {
41
+ /**
42
+ * `$schema` URL emitted for editor validation. Omitted by default:
43
+ * generated configs must not point editors at a URL that does not
44
+ * resolve, so pass `COMPUTE_CONFIG_JSON_SCHEMA_URL` explicitly once the
45
+ * schema is actually published there.
46
+ */
47
+ schemaUrl?: string | null;
48
+ },
49
+ ): string {
50
+ const schemaUrl = options?.schemaUrl ?? null;
51
+ const serializable = {
52
+ ...(schemaUrl === null ? {} : { $schema: schemaUrl }),
53
+ ...serializableComputeConfig(config),
54
+ };
55
+
56
+ return `${JSON.stringify(serializable, null, 2)}\n`;
57
+ }
58
+
32
59
  function serializableComputeConfig(
33
60
  config: ComputeConfig,
34
61
  ): SerializableComputeConfig {
@@ -0,0 +1,98 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { nodeFileTrace } from "@vercel/nft";
5
+
6
+ import {
7
+ copyPathMaterializingSymlinks,
8
+ hoistIsolatedStoreDependencies,
9
+ isPathWithin,
10
+ } from "./artifact-stage.ts";
11
+ import { resolveSourceRoot } from "./config/source-root.ts";
12
+
13
+ /**
14
+ * Configured artifacts copy an output directory's contents to the artifact
15
+ * root. Runtime dependencies traced from that output need the same relocation:
16
+ * app-local node_modules become artifact-root node_modules, while hoisted
17
+ * workspace deps and workspace packages keep enough source-root layout for
18
+ * bare imports and package symlinks to resolve after unpacking.
19
+ */
20
+ export async function stageConfiguredArtifactDependencies(options: {
21
+ appPath: string;
22
+ artifactDir: string;
23
+ outputDirectory: string;
24
+ entrypoint: string;
25
+ signal?: AbortSignal;
26
+ }): Promise<void> {
27
+ const [appRoot, outputRoot] = await Promise.all([
28
+ realpath(path.resolve(options.appPath)),
29
+ realpath(path.resolve(options.outputDirectory)),
30
+ ]);
31
+ const sourceRoot = await resolveSourceRoot(appRoot, options.signal);
32
+ const entrypointPath = path.join(outputRoot, options.entrypoint);
33
+
34
+ options.signal?.throwIfAborted();
35
+ const { fileList } = await nodeFileTrace([entrypointPath], {
36
+ base: sourceRoot,
37
+ });
38
+
39
+ for (const relativePath of fileList) {
40
+ options.signal?.throwIfAborted();
41
+ const sourcePath = path.join(sourceRoot, relativePath);
42
+ if (isPathWithin(outputRoot, sourcePath)) {
43
+ continue;
44
+ }
45
+
46
+ await copyPathMaterializingSymlinks(
47
+ sourcePath,
48
+ path.join(
49
+ options.artifactDir,
50
+ artifactRelativePathForTrace(sourcePath, {
51
+ appRoot,
52
+ sourceRoot,
53
+ }),
54
+ ),
55
+ {
56
+ appRoot,
57
+ sourceRoot,
58
+ allowedSymlinkTargetRoots: [appRoot, sourceRoot],
59
+ ignoreMissingSource: true,
60
+ signal: options.signal,
61
+ },
62
+ );
63
+ }
64
+
65
+ await hoistIsolatedStoreDependencies(
66
+ path.join(options.artifactDir, "node_modules"),
67
+ options.signal,
68
+ );
69
+ }
70
+
71
+ function artifactRelativePathForTrace(
72
+ sourcePath: string,
73
+ options: { appRoot: string; sourceRoot: string },
74
+ ): string {
75
+ const resolvedSource = path.resolve(sourcePath);
76
+ const appNodeModules = path.join(options.appRoot, "node_modules");
77
+ const sourceNodeModules = path.join(options.sourceRoot, "node_modules");
78
+
79
+ if (isPathWithin(appNodeModules, resolvedSource)) {
80
+ return path.join(
81
+ "node_modules",
82
+ path.relative(appNodeModules, resolvedSource),
83
+ );
84
+ }
85
+
86
+ if (isPathWithin(options.appRoot, resolvedSource)) {
87
+ return path.relative(options.appRoot, resolvedSource);
88
+ }
89
+
90
+ if (isPathWithin(sourceNodeModules, resolvedSource)) {
91
+ return path.join(
92
+ "node_modules",
93
+ path.relative(sourceNodeModules, resolvedSource),
94
+ );
95
+ }
96
+
97
+ return path.relative(options.sourceRoot, resolvedSource);
98
+ }
@@ -8,6 +8,7 @@ import {
8
8
  defaultHttpPortForBuildType,
9
9
  type FrameworkBuildType,
10
10
  } from "./config/frameworks.ts";
11
+ import { stageConfiguredArtifactDependencies } from "./configured-artifact-trace.ts";
11
12
 
12
13
  export async function stageConfiguredArtifact(options: {
13
14
  appPath: string;
@@ -16,6 +17,7 @@ export async function stageConfiguredArtifact(options: {
16
17
  buildType: FrameworkBuildType;
17
18
  label: string;
18
19
  missingEntrypointMessage?: string;
20
+ traceDependencies?: boolean;
19
21
  signal?: AbortSignal;
20
22
  }): Promise<BuildArtifact> {
21
23
  options.signal?.throwIfAborted();
@@ -61,6 +63,15 @@ export async function stageConfiguredArtifact(options: {
61
63
  options.appPath,
62
64
  options.signal,
63
65
  );
66
+ if (options.traceDependencies) {
67
+ await stageConfiguredArtifactDependencies({
68
+ appPath: realAppPath,
69
+ artifactDir,
70
+ outputDirectory: realOutputDir,
71
+ entrypoint,
72
+ signal: options.signal,
73
+ });
74
+ }
64
75
 
65
76
  return {
66
77
  directory: artifactDir,