@prisma-next/cli 0.3.0-dev.5 → 0.3.0-dev.53
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/README.md +145 -74
- package/dist/cli.d.mts +1 -0
- package/dist/cli.js +1 -2376
- package/dist/cli.mjs +169 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client-BSZKpZTF.mjs +711 -0
- package/dist/client-BSZKpZTF.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts +7 -0
- package/dist/commands/contract-emit.d.mts.map +1 -0
- package/dist/commands/contract-emit.mjs +147 -0
- package/dist/commands/contract-emit.mjs.map +1 -0
- package/dist/commands/db-init.d.mts +7 -0
- package/dist/commands/db-init.d.mts.map +1 -0
- package/dist/commands/db-init.mjs +179 -0
- package/dist/commands/db-init.mjs.map +1 -0
- package/dist/commands/db-introspect.d.mts +7 -0
- package/dist/commands/db-introspect.d.mts.map +1 -0
- package/dist/commands/db-introspect.mjs +120 -0
- package/dist/commands/db-introspect.mjs.map +1 -0
- package/dist/commands/db-schema-verify.d.mts +7 -0
- package/dist/commands/db-schema-verify.d.mts.map +1 -0
- package/dist/commands/db-schema-verify.mjs +116 -0
- package/dist/commands/db-schema-verify.mjs.map +1 -0
- package/dist/commands/db-sign.d.mts +7 -0
- package/dist/commands/db-sign.d.mts.map +1 -0
- package/dist/commands/db-sign.mjs +138 -0
- package/dist/commands/db-sign.mjs.map +1 -0
- package/dist/commands/db-verify.d.mts +7 -0
- package/dist/commands/db-verify.d.mts.map +1 -0
- package/dist/commands/db-verify.mjs +129 -0
- package/dist/commands/db-verify.mjs.map +1 -0
- package/dist/config-loader-BJ8HsEdA.mjs +42 -0
- package/dist/config-loader-BJ8HsEdA.mjs.map +1 -0
- package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
- package/dist/config-loader.d.mts.map +1 -0
- package/dist/config-loader.mjs +3 -0
- package/dist/exports/config-types.d.mts +2 -0
- package/dist/exports/config-types.mjs +3 -0
- package/dist/exports/control-api.d.mts +433 -0
- package/dist/exports/control-api.d.mts.map +1 -0
- package/dist/exports/control-api.mjs +96 -0
- package/dist/exports/control-api.mjs.map +1 -0
- package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +10 -5
- package/dist/exports/index.d.mts.map +1 -0
- package/dist/exports/index.mjs +132 -0
- package/dist/exports/index.mjs.map +1 -0
- package/dist/result-handler-BZPY7HX4.mjs +1029 -0
- package/dist/result-handler-BZPY7HX4.mjs.map +1 -0
- package/package.json +48 -37
- package/src/commands/contract-emit.ts +205 -111
- package/src/commands/db-init.ts +258 -359
- package/src/commands/db-introspect.ts +151 -184
- package/src/commands/db-schema-verify.ts +151 -149
- package/src/commands/db-sign.ts +202 -200
- package/src/commands/db-verify.ts +181 -155
- package/src/control-api/client.ts +610 -0
- package/src/control-api/operations/contract-emit.ts +161 -0
- package/src/control-api/operations/db-init.ts +281 -0
- package/src/control-api/types.ts +475 -0
- package/src/exports/control-api.ts +48 -0
- package/src/load-ts-contract.ts +16 -11
- package/src/utils/cli-errors.ts +1 -1
- package/src/utils/framework-components.ts +11 -30
- package/src/utils/output.ts +16 -10
- package/src/utils/progress-adapter.ts +86 -0
- package/dist/chunk-464LNZCE.js +0 -134
- package/dist/chunk-464LNZCE.js.map +0 -1
- package/dist/chunk-BZMBKEEQ.js +0 -997
- package/dist/chunk-BZMBKEEQ.js.map +0 -1
- package/dist/chunk-HWYQOCAJ.js +0 -47
- package/dist/chunk-HWYQOCAJ.js.map +0 -1
- package/dist/chunk-ZKYEJROM.js +0 -94
- package/dist/chunk-ZKYEJROM.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/contract-emit.d.ts +0 -3
- package/dist/commands/contract-emit.d.ts.map +0 -1
- package/dist/commands/contract-emit.js +0 -9
- package/dist/commands/contract-emit.js.map +0 -1
- package/dist/commands/db-init.d.ts +0 -3
- package/dist/commands/db-init.d.ts.map +0 -1
- package/dist/commands/db-init.js +0 -341
- package/dist/commands/db-init.js.map +0 -1
- package/dist/commands/db-introspect.d.ts +0 -3
- package/dist/commands/db-introspect.d.ts.map +0 -1
- package/dist/commands/db-introspect.js +0 -190
- package/dist/commands/db-introspect.js.map +0 -1
- package/dist/commands/db-schema-verify.d.ts +0 -3
- package/dist/commands/db-schema-verify.d.ts.map +0 -1
- package/dist/commands/db-schema-verify.js +0 -164
- package/dist/commands/db-schema-verify.js.map +0 -1
- package/dist/commands/db-sign.d.ts +0 -3
- package/dist/commands/db-sign.d.ts.map +0 -1
- package/dist/commands/db-sign.js +0 -199
- package/dist/commands/db-sign.js.map +0 -1
- package/dist/commands/db-verify.d.ts +0 -3
- package/dist/commands/db-verify.d.ts.map +0 -1
- package/dist/commands/db-verify.js +0 -173
- package/dist/commands/db-verify.js.map +0 -1
- package/dist/config-loader.d.ts.map +0 -1
- package/dist/config-loader.js +0 -7
- package/dist/config-loader.js.map +0 -1
- package/dist/exports/config-types.d.ts +0 -3
- package/dist/exports/config-types.d.ts.map +0 -1
- package/dist/exports/config-types.js +0 -6
- package/dist/exports/config-types.js.map +0 -1
- package/dist/exports/index.d.ts +0 -4
- package/dist/exports/index.d.ts.map +0 -1
- package/dist/exports/index.js +0 -175
- package/dist/exports/index.js.map +0 -1
- package/dist/load-ts-contract.d.ts.map +0 -1
- package/dist/utils/action.d.ts +0 -16
- package/dist/utils/action.d.ts.map +0 -1
- package/dist/utils/cli-errors.d.ts +0 -7
- package/dist/utils/cli-errors.d.ts.map +0 -1
- package/dist/utils/command-helpers.d.ts +0 -12
- package/dist/utils/command-helpers.d.ts.map +0 -1
- package/dist/utils/framework-components.d.ts +0 -81
- package/dist/utils/framework-components.d.ts.map +0 -1
- package/dist/utils/global-flags.d.ts +0 -25
- package/dist/utils/global-flags.d.ts.map +0 -1
- package/dist/utils/output.d.ts +0 -142
- package/dist/utils/output.d.ts.map +0 -1
- package/dist/utils/result-handler.d.ts +0 -15
- package/dist/utils/result-handler.d.ts.map +0 -1
- package/dist/utils/spinner.d.ts +0 -29
- package/dist/utils/spinner.d.ts.map +0 -1
- package/src/utils/action.ts +0 -43
- package/src/utils/spinner.ts +0 -67
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control-api.mjs","names":["providerResult: Awaited<ReturnType<typeof contractConfig.source>>"],"sources":["../../src/control-api/operations/contract-emit.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';\nimport { abortable } from '@prisma-next/utils/abortable';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { dirname, isAbsolute, join, resolve } from 'pathe';\nimport { loadConfig } from '../../config-loader';\nimport { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';\nimport type { ContractEmitOptions, ContractEmitResult } from '../types';\n\ninterface ProviderFailureLike {\n readonly summary: string;\n readonly diagnostics: readonly unknown[];\n readonly meta?: unknown;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isAbortError(error: unknown): boolean {\n return isRecord(error) && typeof error['name'] === 'string' && error['name'] === 'AbortError';\n}\n\nfunction isProviderFailureLike(value: unknown): value is ProviderFailureLike {\n return (\n isRecord(value) && typeof value['summary'] === 'string' && Array.isArray(value['diagnostics'])\n );\n}\n\n/**\n * Executes the contract emit operation.\n *\n * This is an offline operation that:\n * 1. Loads the Prisma Next config from the specified path\n * 2. Resolves the contract source from config\n * 3. Creates a control plane stack and family instance\n * 4. Emits contract artifacts (JSON and DTS)\n * 5. Writes files to the paths specified in config\n *\n * Supports AbortSignal for cancellation, enabling \"last change wins\" behavior.\n *\n * @param options - Options including configPath and optional signal\n * @returns File paths and hashes of emitted artifacts\n * @throws If config loading fails, contract is invalid, or file I/O fails\n * @throws signal.reason if cancelled via AbortSignal (typically DOMException with name 'AbortError')\n */\nexport async function executeContractEmit(\n options: ContractEmitOptions,\n): Promise<ContractEmitResult> {\n const { configPath, signal = new AbortController().signal } = options;\n const unlessAborted = abortable(signal);\n\n // Load config using the existing config loader\n const config = await unlessAborted(loadConfig(configPath));\n\n // Validate contract config is present\n if (!config.contract) {\n throw errorContractConfigMissing({\n why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',\n });\n }\n\n const contractConfig = config.contract;\n\n // Validate output path is present and ends with .json\n if (!contractConfig.output) {\n throw errorContractConfigMissing({\n why: 'Contract config must have output path. This should not happen if defineConfig() was used.',\n });\n }\n if (!contractConfig.output.endsWith('.json')) {\n throw errorContractConfigMissing({\n why: 'Contract config output path must end with .json (e.g., \"src/prisma/contract.json\")',\n });\n }\n\n // Validate source exists and is callable\n if (typeof contractConfig.source !== 'function') {\n throw errorContractConfigMissing({\n why: 'Contract config must include a valid source provider function',\n });\n }\n\n // Normalize configPath and resolve artifact paths relative to config file directory\n const normalizedConfigPath = resolve(configPath);\n const configDir = dirname(normalizedConfigPath);\n const outputJsonPath = isAbsolute(contractConfig.output)\n ? contractConfig.output\n : join(configDir, contractConfig.output);\n // Colocate .d.ts with .json (contract.json → contract.d.ts)\n const outputDtsPath = `${outputJsonPath.slice(0, -5)}.d.ts`;\n\n let providerResult: Awaited<ReturnType<typeof contractConfig.source>>;\n try {\n providerResult = await unlessAborted(contractConfig.source());\n } catch (error) {\n if (signal.aborted || isAbortError(error)) {\n throw error;\n }\n throw errorRuntime('Failed to resolve contract source', {\n why: error instanceof Error ? error.message : String(error),\n fix: 'Ensure contract.source resolves to ok(contractIR) or returns structured diagnostics.',\n });\n }\n\n if (!isRecord(providerResult) || typeof providerResult.ok !== 'boolean') {\n throw errorRuntime('Failed to resolve contract source', {\n why: 'Contract source provider returned malformed result shape.',\n fix: 'Ensure contract.source resolves to ok(contractIR) or notOk({ summary, diagnostics }).',\n });\n }\n\n if (providerResult.ok && !('value' in providerResult)) {\n throw errorRuntime('Failed to resolve contract source', {\n why: 'Contract source provider returned malformed success result: missing value.',\n fix: 'Ensure contract.source success payload is ok(contractIR).',\n });\n }\n\n if (!providerResult.ok && !isProviderFailureLike(providerResult.failure)) {\n throw errorRuntime('Failed to resolve contract source', {\n why: 'Contract source provider returned malformed failure result: expected summary and diagnostics.',\n fix: 'Ensure contract.source failure payload is notOk({ summary, diagnostics, meta? }).',\n });\n }\n\n if (!providerResult.ok) {\n throw errorRuntime('Failed to resolve contract source', {\n why: providerResult.failure.summary,\n fix: 'Fix contract source diagnostics and return ok(contractIR).',\n meta: {\n diagnostics: providerResult.failure.diagnostics,\n ...ifDefined('providerMeta', providerResult.failure.meta),\n },\n });\n }\n\n // Create control plane stack from config\n const stack = createControlPlaneStack(config);\n const familyInstance = config.family.create(stack);\n\n // Emit contract via family instance\n const emitResult = await unlessAborted(\n familyInstance.emitContract({ contractIR: providerResult.value }),\n );\n\n // Create directory if needed and write files (both colocated)\n await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));\n await unlessAborted(writeFile(outputJsonPath, emitResult.contractJson, 'utf-8'));\n await unlessAborted(writeFile(outputDtsPath, emitResult.contractDts, 'utf-8'));\n\n return {\n storageHash: emitResult.storageHash,\n ...ifDefined('executionHash', emitResult.executionHash),\n profileHash: emitResult.profileHash,\n files: {\n json: outputJsonPath,\n dts: outputDtsPath,\n },\n };\n}\n"],"mappings":";;;;;;;;;AAeA,SAAS,SAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU;;AAGhD,SAAS,aAAa,OAAyB;AAC7C,QAAO,SAAS,MAAM,IAAI,OAAO,MAAM,YAAY,YAAY,MAAM,YAAY;;AAGnF,SAAS,sBAAsB,OAA8C;AAC3E,QACE,SAAS,MAAM,IAAI,OAAO,MAAM,eAAe,YAAY,MAAM,QAAQ,MAAM,eAAe;;;;;;;;;;;;;;;;;;;AAqBlG,eAAsB,oBACpB,SAC6B;CAC7B,MAAM,EAAE,YAAY,SAAS,IAAI,iBAAiB,CAAC,WAAW;CAC9D,MAAM,gBAAgB,UAAU,OAAO;CAGvC,MAAM,SAAS,MAAM,cAAc,WAAW,WAAW,CAAC;AAG1D,KAAI,CAAC,OAAO,SACV,OAAM,2BAA2B,EAC/B,KAAK,0GACN,CAAC;CAGJ,MAAM,iBAAiB,OAAO;AAG9B,KAAI,CAAC,eAAe,OAClB,OAAM,2BAA2B,EAC/B,KAAK,6FACN,CAAC;AAEJ,KAAI,CAAC,eAAe,OAAO,SAAS,QAAQ,CAC1C,OAAM,2BAA2B,EAC/B,KAAK,wFACN,CAAC;AAIJ,KAAI,OAAO,eAAe,WAAW,WACnC,OAAM,2BAA2B,EAC/B,KAAK,iEACN,CAAC;CAKJ,MAAM,YAAY,QADW,QAAQ,WAAW,CACD;CAC/C,MAAM,iBAAiB,WAAW,eAAe,OAAO,GACpD,eAAe,SACf,KAAK,WAAW,eAAe,OAAO;CAE1C,MAAM,gBAAgB,GAAG,eAAe,MAAM,GAAG,GAAG,CAAC;CAErD,IAAIA;AACJ,KAAI;AACF,mBAAiB,MAAM,cAAc,eAAe,QAAQ,CAAC;UACtD,OAAO;AACd,MAAI,OAAO,WAAW,aAAa,MAAM,CACvC,OAAM;AAER,QAAM,aAAa,qCAAqC;GACtD,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC3D,KAAK;GACN,CAAC;;AAGJ,KAAI,CAAC,SAAS,eAAe,IAAI,OAAO,eAAe,OAAO,UAC5D,OAAM,aAAa,qCAAqC;EACtD,KAAK;EACL,KAAK;EACN,CAAC;AAGJ,KAAI,eAAe,MAAM,EAAE,WAAW,gBACpC,OAAM,aAAa,qCAAqC;EACtD,KAAK;EACL,KAAK;EACN,CAAC;AAGJ,KAAI,CAAC,eAAe,MAAM,CAAC,sBAAsB,eAAe,QAAQ,CACtE,OAAM,aAAa,qCAAqC;EACtD,KAAK;EACL,KAAK;EACN,CAAC;AAGJ,KAAI,CAAC,eAAe,GAClB,OAAM,aAAa,qCAAqC;EACtD,KAAK,eAAe,QAAQ;EAC5B,KAAK;EACL,MAAM;GACJ,aAAa,eAAe,QAAQ;GACpC,GAAG,UAAU,gBAAgB,eAAe,QAAQ,KAAK;GAC1D;EACF,CAAC;CAIJ,MAAM,QAAQ,wBAAwB,OAAO;CAI7C,MAAM,aAAa,MAAM,cAHF,OAAO,OAAO,OAAO,MAAM,CAIjC,aAAa,EAAE,YAAY,eAAe,OAAO,CAAC,CAClE;AAGD,OAAM,cAAc,MAAM,QAAQ,eAAe,EAAE,EAAE,WAAW,MAAM,CAAC,CAAC;AACxE,OAAM,cAAc,UAAU,gBAAgB,WAAW,cAAc,QAAQ,CAAC;AAChF,OAAM,cAAc,UAAU,eAAe,WAAW,aAAa,QAAQ,CAAC;AAE9E,QAAO;EACL,aAAa,WAAW;EACxB,GAAG,UAAU,iBAAiB,WAAW,cAAc;EACvD,aAAa,WAAW;EACxB,OAAO;GACL,MAAM;GACN,KAAK;GACN;EACF"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { createContractEmitCommand } from "../commands/contract-emit.mjs";
|
|
2
|
+
import { ContractIR } from "@prisma-next/contract/ir";
|
|
3
|
+
|
|
4
|
+
//#region src/load-ts-contract.d.ts
|
|
5
|
+
interface LoadTsContractOptions {
|
|
6
|
+
readonly allowlist?: ReadonlyArray<string>;
|
|
4
7
|
}
|
|
5
8
|
/**
|
|
6
9
|
* Loads a contract from a TypeScript file and returns it as ContractIR.
|
|
@@ -17,5 +20,7 @@ export interface LoadTsContractOptions {
|
|
|
17
20
|
* @returns The contract as ContractIR (should already be normalized)
|
|
18
21
|
* @throws Error if the contract cannot be loaded or is not JSON-serializable
|
|
19
22
|
*/
|
|
20
|
-
|
|
21
|
-
//#
|
|
23
|
+
declare function loadContractFromTs(entryPath: string, options?: LoadTsContractOptions): Promise<ContractIR>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { type LoadTsContractOptions, createContractEmitCommand, loadContractFromTs };
|
|
26
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/load-ts-contract.ts"],"sourcesContent":[],"mappings":";;;;UAOiB,qBAAA;uBACM;;AADvB;AA0GA;;;;;;;;;;;;;;iBAAsB,kBAAA,8BAEV,wBACT,QAAQ"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import "../config-loader-BJ8HsEdA.mjs";
|
|
2
|
+
import "../client-BSZKpZTF.mjs";
|
|
3
|
+
import "../result-handler-BZPY7HX4.mjs";
|
|
4
|
+
import { createContractEmitCommand } from "../commands/contract-emit.mjs";
|
|
5
|
+
import { existsSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { build } from "esbuild";
|
|
9
|
+
|
|
10
|
+
//#region src/load-ts-contract.ts
|
|
11
|
+
const DEFAULT_ALLOWLIST = ["@prisma-next/*"];
|
|
12
|
+
function isAllowedImport(importPath, allowlist) {
|
|
13
|
+
for (const pattern of allowlist) if (pattern.endsWith("/*")) {
|
|
14
|
+
const prefix = pattern.slice(0, -2);
|
|
15
|
+
if (importPath === prefix || importPath.startsWith(`${prefix}/`)) return true;
|
|
16
|
+
} else if (importPath === pattern) return true;
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function validatePurity(value) {
|
|
20
|
+
if (typeof value !== "object" || value === null) return;
|
|
21
|
+
const path = /* @__PURE__ */ new WeakSet();
|
|
22
|
+
function check(value$1) {
|
|
23
|
+
if (value$1 === null || typeof value$1 !== "object") return;
|
|
24
|
+
if (path.has(value$1)) throw new Error("Contract export contains circular references");
|
|
25
|
+
path.add(value$1);
|
|
26
|
+
try {
|
|
27
|
+
for (const key in value$1) {
|
|
28
|
+
const descriptor = Object.getOwnPropertyDescriptor(value$1, key);
|
|
29
|
+
if (descriptor && (descriptor.get || descriptor.set)) throw new Error(`Contract export contains getter/setter at key "${key}"`);
|
|
30
|
+
if (descriptor && typeof descriptor.value === "function") throw new Error(`Contract export contains function at key "${key}"`);
|
|
31
|
+
check(value$1[key]);
|
|
32
|
+
}
|
|
33
|
+
} finally {
|
|
34
|
+
path.delete(value$1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
check(value);
|
|
39
|
+
JSON.stringify(value);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error instanceof Error) {
|
|
42
|
+
if (error.message.includes("getter") || error.message.includes("circular")) throw error;
|
|
43
|
+
throw new Error(`Contract export is not JSON-serializable: ${error.message}`);
|
|
44
|
+
}
|
|
45
|
+
throw new Error("Contract export is not JSON-serializable");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function createImportAllowlistPlugin(allowlist, entryPath) {
|
|
49
|
+
return {
|
|
50
|
+
name: "import-allowlist",
|
|
51
|
+
setup(build$1) {
|
|
52
|
+
build$1.onResolve({ filter: /.*/ }, (args) => {
|
|
53
|
+
if (args.kind === "entry-point") return;
|
|
54
|
+
if (args.path.startsWith(".") || args.path.startsWith("/")) return;
|
|
55
|
+
if ((args.importer === entryPath || args.importer === "<stdin>") && !isAllowedImport(args.path, allowlist)) return {
|
|
56
|
+
path: args.path,
|
|
57
|
+
external: true
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Loads a contract from a TypeScript file and returns it as ContractIR.
|
|
65
|
+
*
|
|
66
|
+
* **Responsibility: Parsing Only**
|
|
67
|
+
* This function loads and parses a TypeScript contract file. It does NOT normalize the contract.
|
|
68
|
+
* The contract should already be normalized if it was built using the contract builder.
|
|
69
|
+
*
|
|
70
|
+
* Normalization must happen in the contract builder when the contract is created.
|
|
71
|
+
* This function only validates that the contract is JSON-serializable and returns it as-is.
|
|
72
|
+
*
|
|
73
|
+
* @param entryPath - Path to the TypeScript contract file
|
|
74
|
+
* @param options - Optional configuration (import allowlist)
|
|
75
|
+
* @returns The contract as ContractIR (should already be normalized)
|
|
76
|
+
* @throws Error if the contract cannot be loaded or is not JSON-serializable
|
|
77
|
+
*/
|
|
78
|
+
async function loadContractFromTs(entryPath, options) {
|
|
79
|
+
const allowlist = options?.allowlist ?? DEFAULT_ALLOWLIST;
|
|
80
|
+
if (!existsSync(entryPath)) throw new Error(`Contract file not found: ${entryPath}`);
|
|
81
|
+
const tempFile = join(tmpdir(), `prisma-next-contract-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
|
|
82
|
+
try {
|
|
83
|
+
const result = await build({
|
|
84
|
+
entryPoints: [entryPath],
|
|
85
|
+
bundle: true,
|
|
86
|
+
format: "esm",
|
|
87
|
+
platform: "node",
|
|
88
|
+
target: "es2022",
|
|
89
|
+
outfile: tempFile,
|
|
90
|
+
write: false,
|
|
91
|
+
metafile: true,
|
|
92
|
+
plugins: [createImportAllowlistPlugin(allowlist, entryPath)],
|
|
93
|
+
logLevel: "error"
|
|
94
|
+
});
|
|
95
|
+
if (result.errors.length > 0) {
|
|
96
|
+
const errorMessages = result.errors.map((e) => e.text).join("\n");
|
|
97
|
+
throw new Error(`Failed to bundle contract file: ${errorMessages}`);
|
|
98
|
+
}
|
|
99
|
+
if (!result.outputFiles || result.outputFiles.length === 0) throw new Error("No output files generated from bundling");
|
|
100
|
+
const disallowedImports = [];
|
|
101
|
+
if (result.metafile) {
|
|
102
|
+
const inputs = result.metafile.inputs;
|
|
103
|
+
for (const [, inputData] of Object.entries(inputs)) {
|
|
104
|
+
const imports = inputData.imports || [];
|
|
105
|
+
for (const imp of imports) if (imp.external && !imp.path.startsWith(".") && !imp.path.startsWith("/") && !isAllowedImport(imp.path, allowlist)) disallowedImports.push(imp.path);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (disallowedImports.length > 0) throw new Error(`Disallowed imports detected. Only imports matching the allowlist are permitted:\n Allowlist: ${allowlist.join(", ")}\n Disallowed imports: ${disallowedImports.join(", ")}\n\nOnly @prisma-next/* packages are allowed in contract files.`);
|
|
109
|
+
const bundleContent = result.outputFiles[0]?.text;
|
|
110
|
+
if (bundleContent === void 0) throw new Error("Bundle content is undefined");
|
|
111
|
+
writeFileSync(tempFile, bundleContent, "utf-8");
|
|
112
|
+
const module = await import(`file://${tempFile}`);
|
|
113
|
+
unlinkSync(tempFile);
|
|
114
|
+
let contract;
|
|
115
|
+
if (module.default !== void 0) contract = module.default;
|
|
116
|
+
else if (module.contract !== void 0) contract = module.contract;
|
|
117
|
+
else throw new Error(`Contract file must export a contract as default export or named export 'contract'. Found exports: ${Object.keys(module).join(", ") || "none"}`);
|
|
118
|
+
if (typeof contract !== "object" || contract === null) throw new Error(`Contract export must be an object, got ${typeof contract}`);
|
|
119
|
+
validatePurity(contract);
|
|
120
|
+
return contract;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
try {
|
|
123
|
+
if (tempFile) unlinkSync(tempFile);
|
|
124
|
+
} catch {}
|
|
125
|
+
if (error instanceof Error) throw error;
|
|
126
|
+
throw new Error(`Failed to load contract from ${entryPath}: ${String(error)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
export { createContractEmitCommand, loadContractFromTs };
|
|
132
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["value","disallowedImports: string[]","contract: unknown"],"sources":["../../src/load-ts-contract.ts"],"sourcesContent":["import { existsSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport type { Plugin } from 'esbuild';\nimport { build } from 'esbuild';\n\nexport interface LoadTsContractOptions {\n readonly allowlist?: ReadonlyArray<string>;\n}\n\nconst DEFAULT_ALLOWLIST = ['@prisma-next/*'];\n\nfunction isAllowedImport(importPath: string, allowlist: ReadonlyArray<string>): boolean {\n for (const pattern of allowlist) {\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -2);\n if (importPath === prefix || importPath.startsWith(`${prefix}/`)) {\n return true;\n }\n } else if (importPath === pattern) {\n return true;\n }\n }\n return false;\n}\n\nfunction validatePurity(value: unknown): void {\n if (typeof value !== 'object' || value === null) {\n return;\n }\n\n const path = new WeakSet();\n\n function check(value: unknown): void {\n if (value === null || typeof value !== 'object') {\n return;\n }\n\n if (path.has(value)) {\n throw new Error('Contract export contains circular references');\n }\n path.add(value);\n\n try {\n for (const key in value) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (descriptor && (descriptor.get || descriptor.set)) {\n throw new Error(`Contract export contains getter/setter at key \"${key}\"`);\n }\n if (descriptor && typeof descriptor.value === 'function') {\n throw new Error(`Contract export contains function at key \"${key}\"`);\n }\n check((value as Record<string, unknown>)[key]);\n }\n } finally {\n path.delete(value);\n }\n }\n\n try {\n check(value);\n JSON.stringify(value);\n } catch (error) {\n if (error instanceof Error) {\n if (error.message.includes('getter') || error.message.includes('circular')) {\n throw error;\n }\n throw new Error(`Contract export is not JSON-serializable: ${error.message}`);\n }\n throw new Error('Contract export is not JSON-serializable');\n }\n}\n\nfunction createImportAllowlistPlugin(allowlist: ReadonlyArray<string>, entryPath: string): Plugin {\n return {\n name: 'import-allowlist',\n setup(build) {\n build.onResolve({ filter: /.*/ }, (args) => {\n if (args.kind === 'entry-point') {\n return undefined;\n }\n if (args.path.startsWith('.') || args.path.startsWith('/')) {\n return undefined;\n }\n const isFromEntryPoint = args.importer === entryPath || args.importer === '<stdin>';\n if (isFromEntryPoint && !isAllowedImport(args.path, allowlist)) {\n return {\n path: args.path,\n external: true,\n };\n }\n return undefined;\n });\n },\n };\n}\n\n/**\n * Loads a contract from a TypeScript file and returns it as ContractIR.\n *\n * **Responsibility: Parsing Only**\n * This function loads and parses a TypeScript contract file. It does NOT normalize the contract.\n * The contract should already be normalized if it was built using the contract builder.\n *\n * Normalization must happen in the contract builder when the contract is created.\n * This function only validates that the contract is JSON-serializable and returns it as-is.\n *\n * @param entryPath - Path to the TypeScript contract file\n * @param options - Optional configuration (import allowlist)\n * @returns The contract as ContractIR (should already be normalized)\n * @throws Error if the contract cannot be loaded or is not JSON-serializable\n */\nexport async function loadContractFromTs(\n entryPath: string,\n options?: LoadTsContractOptions,\n): Promise<ContractIR> {\n const allowlist = options?.allowlist ?? DEFAULT_ALLOWLIST;\n\n if (!existsSync(entryPath)) {\n throw new Error(`Contract file not found: ${entryPath}`);\n }\n\n const tempFile = join(\n tmpdir(),\n `prisma-next-contract-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`,\n );\n\n try {\n const result = await build({\n entryPoints: [entryPath],\n bundle: true,\n format: 'esm',\n platform: 'node',\n target: 'es2022',\n outfile: tempFile,\n write: false,\n metafile: true,\n plugins: [createImportAllowlistPlugin(allowlist, entryPath)],\n logLevel: 'error',\n });\n\n if (result.errors.length > 0) {\n const errorMessages = result.errors.map((e: { text: string }) => e.text).join('\\n');\n throw new Error(`Failed to bundle contract file: ${errorMessages}`);\n }\n\n if (!result.outputFiles || result.outputFiles.length === 0) {\n throw new Error('No output files generated from bundling');\n }\n\n const disallowedImports: string[] = [];\n if (result.metafile) {\n const inputs = result.metafile.inputs;\n for (const [, inputData] of Object.entries(inputs)) {\n const imports =\n (inputData as { imports?: Array<{ path: string; external?: boolean }> }).imports || [];\n for (const imp of imports) {\n if (\n imp.external &&\n !imp.path.startsWith('.') &&\n !imp.path.startsWith('/') &&\n !isAllowedImport(imp.path, allowlist)\n ) {\n disallowedImports.push(imp.path);\n }\n }\n }\n }\n\n if (disallowedImports.length > 0) {\n throw new Error(\n `Disallowed imports detected. Only imports matching the allowlist are permitted:\\n Allowlist: ${allowlist.join(', ')}\\n Disallowed imports: ${disallowedImports.join(', ')}\\n\\nOnly @prisma-next/* packages are allowed in contract files.`,\n );\n }\n\n const bundleContent = result.outputFiles[0]?.text;\n if (bundleContent === undefined) {\n throw new Error('Bundle content is undefined');\n }\n writeFileSync(tempFile, bundleContent, 'utf-8');\n\n const module = (await import(`file://${tempFile}`)) as {\n default?: unknown;\n contract?: unknown;\n };\n unlinkSync(tempFile);\n\n let contract: unknown;\n\n if (module.default !== undefined) {\n contract = module.default;\n } else if (module.contract !== undefined) {\n contract = module.contract;\n } else {\n throw new Error(\n `Contract file must export a contract as default export or named export 'contract'. Found exports: ${Object.keys(module as Record<string, unknown>).join(', ') || 'none'}`,\n );\n }\n\n if (typeof contract !== 'object' || contract === null) {\n throw new Error(`Contract export must be an object, got ${typeof contract}`);\n }\n\n validatePurity(contract);\n\n return contract as ContractIR;\n } catch (error) {\n try {\n if (tempFile) {\n unlinkSync(tempFile);\n }\n } catch {\n // Ignore cleanup errors\n }\n\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Failed to load contract from ${entryPath}: ${String(error)}`);\n }\n}\n"],"mappings":"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"}
|