@prisma/orm-mongo 8.0.0-rc.5 → 8.0.0-rc.5-dev.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.
- package/package.json +11 -10
- package/skills/prisma-8/SKILL.md +84 -0
- package/skills/prisma-8/references/build.md +142 -0
- package/skills/prisma-8/references/contract.md +417 -0
- package/skills/prisma-8/references/debug.md +141 -0
- package/skills/prisma-8/references/feedback.md +251 -0
- package/skills/prisma-8/references/migration-review.md +224 -0
- package/skills/prisma-8/references/migrations.md +519 -0
- package/skills/prisma-8/references/queries-mongo.md +236 -0
- package/skills/prisma-8/references/queries-postgres.md +415 -0
- package/skills/prisma-8/references/queries.md +168 -0
- package/skills/prisma-8/references/quickstart.md +326 -0
- package/skills/prisma-8/references/runtime.md +344 -0
- package/skills/prisma-8/references/supabase.md +244 -0
- package/skills/prisma-8/references/upgrade-app.md +101 -0
- package/skills/prisma-8/references/upgrade-extension.md +105 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.10-to-0.11/instructions.md +56 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/instructions.md +381 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts +202 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts +201 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts +198 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/instructions.md +339 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts +229 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/instructions.md +543 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.14-to-0.15/instructions.md +359 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.15-to-0.16/instructions.md +173 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/instructions.md +805 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md +72 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md +8 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md +36 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/instructions.md +86 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +588 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +158 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +42 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.10-to-0.11/instructions.md +276 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/instructions.md +738 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts +97 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts +223 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.12-to-0.13/instructions.md +266 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/instructions.md +522 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.14-to-0.15/instructions.md +803 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.15-to-0.16/instructions.md +219 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/instructions.md +731 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md +194 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md +8 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md +57 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/instructions.md +150 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +746 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +137 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +129 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-emits every Mongo contract in the consumer project so emitted
|
|
3
|
+
* `contract.json` / `contract.d.ts` pick up closed `$jsonSchema`
|
|
4
|
+
* validators (`additionalProperties: false` at every level, including
|
|
5
|
+
* polymorphic `oneOf` branches).
|
|
6
|
+
*
|
|
7
|
+
* Background: starting at the 0.12 release, MongoDB emits closed
|
|
8
|
+
* `$jsonSchema` validators by default. The contract canonicalizer also
|
|
9
|
+
* preserves `additionalProperties` through emission, so re-emitting is
|
|
10
|
+
* the consumer-facing migration for on-disk contract artefacts. A
|
|
11
|
+
* non-variant Mongo model must resolve to an `objectId` `_id`; otherwise
|
|
12
|
+
* interpret fails with `PSL_MONGO_ID_REQUIRED` — fix the PSL/TS source
|
|
13
|
+
* before re-emitting.
|
|
14
|
+
*
|
|
15
|
+
* After re-emitting, apply the resulting open→closed validator migration
|
|
16
|
+
* with `prisma-next db update -y` (or `pnpm db:update -y` if your
|
|
17
|
+
* project wraps it). The planner classifies the validator tightening as
|
|
18
|
+
* `destructive`; without `-y` the apply step refuses to run.
|
|
19
|
+
*
|
|
20
|
+
* Dispatch: walks the project root for directories that contain both
|
|
21
|
+
* `prisma.config.ts` and a committed `contract.json` whose storage
|
|
22
|
+
* tree includes `"kind": "mongo-database"`. In each match, runs
|
|
23
|
+
* `pnpm emit` when a `package.json` scripts.emit entry exists, otherwise
|
|
24
|
+
* `pnpm exec prisma-next contract emit`.
|
|
25
|
+
*
|
|
26
|
+
* Flags:
|
|
27
|
+
* --check dry-run; lists directories that would be re-emitted and
|
|
28
|
+
* exits 1 if any contract.json still lacks closed validators.
|
|
29
|
+
*/
|
|
30
|
+
import { execFile } from 'node:child_process';
|
|
31
|
+
import { access, readdir, readFile } from 'node:fs/promises';
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
import { promisify } from 'node:util';
|
|
34
|
+
|
|
35
|
+
const execFileAsync = promisify(execFile);
|
|
36
|
+
|
|
37
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build']);
|
|
38
|
+
|
|
39
|
+
const dryRun = process.argv.includes('--check');
|
|
40
|
+
const projectRoot = process.cwd();
|
|
41
|
+
|
|
42
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
43
|
+
try {
|
|
44
|
+
await access(path);
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function findPrismaNextConfigDirs(root: string): Promise<string[]> {
|
|
52
|
+
const out: string[] = [];
|
|
53
|
+
|
|
54
|
+
async function walk(dir: string): Promise<void> {
|
|
55
|
+
let entries: Awaited<ReturnType<typeof readdir>>;
|
|
56
|
+
try {
|
|
57
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
58
|
+
} catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
64
|
+
await walk(join(dir, entry.name));
|
|
65
|
+
} else if (entry.isFile() && entry.name === 'prisma.config.ts') {
|
|
66
|
+
out.push(dir);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await walk(root);
|
|
72
|
+
return out.sort();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function contractJsonCandidates(configDir: string): string[] {
|
|
76
|
+
return [
|
|
77
|
+
join(configDir, 'src', 'contract.json'),
|
|
78
|
+
join(configDir, 'src', 'prisma', 'contract.json'),
|
|
79
|
+
join(configDir, 'prisma', 'contract.json'),
|
|
80
|
+
join(configDir, 'contract.json'),
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function resolveContractJson(configDir: string): Promise<string | null> {
|
|
85
|
+
for (const candidate of contractJsonCandidates(configDir)) {
|
|
86
|
+
if (await pathExists(candidate)) return candidate;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function isMongoContract(contractPath: string): Promise<boolean> {
|
|
92
|
+
const raw = await readFile(contractPath, 'utf-8');
|
|
93
|
+
return raw.includes('"kind": "mongo-database"') || raw.includes('"kind":"mongo-database"');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A contract is in the closed-validator (post-0.12) format when every object
|
|
98
|
+
* schema that declares a `properties` map also carries `additionalProperties:
|
|
99
|
+
* false`. That covers collection validators, nested value objects, and each
|
|
100
|
+
* polymorphic `oneOf` branch — all of which expose `properties`.
|
|
101
|
+
*
|
|
102
|
+
* The one exception is a polymorphic schema's top-level node: it carries both
|
|
103
|
+
* base `properties` and a `oneOf`, and is deliberately left open because
|
|
104
|
+
* closure is enforced on each branch (a document must match exactly one closed
|
|
105
|
+
* branch). Such a node is exempt from the `additionalProperties: false`
|
|
106
|
+
* requirement, but its branches are still walked and checked.
|
|
107
|
+
*
|
|
108
|
+
* A substring scan is unsafe here: a single closed branch would mask a sibling
|
|
109
|
+
* that still needs re-emitting.
|
|
110
|
+
*/
|
|
111
|
+
/** Narrows an arbitrary JSON-parsed value to a plain object (non-null, non-array). */
|
|
112
|
+
function isJsonObject(value: unknown): value is Record<string, unknown> {
|
|
113
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function contractLooksClosed(raw: string): boolean {
|
|
117
|
+
let parsed: unknown;
|
|
118
|
+
try {
|
|
119
|
+
parsed = JSON.parse(raw);
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isClosed(node: unknown): boolean {
|
|
125
|
+
if (Array.isArray(node)) return node.every(isClosed);
|
|
126
|
+
if (!isJsonObject(node)) return true;
|
|
127
|
+
|
|
128
|
+
const hasProperties = isJsonObject(node['properties']);
|
|
129
|
+
const isPolymorphicTopLevel = Array.isArray(node['oneOf']);
|
|
130
|
+
if (hasProperties && !isPolymorphicTopLevel && node['additionalProperties'] !== false) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return Object.values(node).every(isClosed);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return isClosed(parsed);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function packageJsonHasEmitScript(configDir: string): Promise<boolean> {
|
|
141
|
+
const pkgPath = join(configDir, 'package.json');
|
|
142
|
+
if (!(await pathExists(pkgPath))) return false;
|
|
143
|
+
const raw = await readFile(pkgPath, 'utf-8');
|
|
144
|
+
try {
|
|
145
|
+
const parsed: unknown = JSON.parse(raw);
|
|
146
|
+
if (!isJsonObject(parsed)) return false;
|
|
147
|
+
const scripts = parsed['scripts'];
|
|
148
|
+
if (!isJsonObject(scripts)) return false;
|
|
149
|
+
return typeof scripts['emit'] === 'string' && scripts['emit'].length > 0;
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function runEmit(configDir: string): Promise<void> {
|
|
156
|
+
const hasEmitScript = await packageJsonHasEmitScript(configDir);
|
|
157
|
+
const cmd = hasEmitScript ? 'pnpm' : 'pnpm';
|
|
158
|
+
const args = hasEmitScript ? ['emit'] : ['exec', 'prisma-next', 'contract', 'emit'];
|
|
159
|
+
await execFileAsync(cmd, args, { cwd: configDir, env: process.env });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const configDirs = await findPrismaNextConfigDirs(projectRoot);
|
|
163
|
+
const mongoDirs: Array<{ dir: string; contractPath: string }> = [];
|
|
164
|
+
|
|
165
|
+
for (const dir of configDirs) {
|
|
166
|
+
const contractPath = await resolveContractJson(dir);
|
|
167
|
+
if (contractPath === null) continue;
|
|
168
|
+
if (!(await isMongoContract(contractPath))) continue;
|
|
169
|
+
mongoDirs.push({ dir, contractPath });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (mongoDirs.length === 0) {
|
|
173
|
+
console.error(`No Mongo contract directories found under ${projectRoot}.`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let needsFix = 0;
|
|
178
|
+
let alreadyClean = 0;
|
|
179
|
+
|
|
180
|
+
for (const { dir, contractPath } of mongoDirs) {
|
|
181
|
+
const rel = dir.slice(projectRoot.length + 1) || '.';
|
|
182
|
+
const raw = await readFile(contractPath, 'utf-8');
|
|
183
|
+
if (contractLooksClosed(raw)) {
|
|
184
|
+
alreadyClean += 1;
|
|
185
|
+
console.log(`OK ${rel}`);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
needsFix += 1;
|
|
189
|
+
if (dryRun) {
|
|
190
|
+
console.log(`WOULD RE-EMIT ${rel}`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
console.log(`EMIT ${rel}`);
|
|
194
|
+
await runEmit(dir);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log();
|
|
198
|
+
console.log(
|
|
199
|
+
`${mongoDirs.length} Mongo contract(s): ${needsFix} ${dryRun ? 'needing re-emit' : 're-emitted'}, ${alreadyClean} already closed.`,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
if (dryRun && needsFix > 0) process.exit(1);
|
package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-emits every contract artefact still on the pre-0.12 flat domain plane
|
|
3
|
+
* (`contract.models` / `contract.valueObjects` at the contract root) so
|
|
4
|
+
* emitted JSON and `contract.d.ts` pick up `contract.domain.namespaces.<ns>`.
|
|
5
|
+
*
|
|
6
|
+
* Background: starting at 0.12 (symmetric domain plane, ADR 221), models
|
|
7
|
+
* and value objects live under `domain.namespaces`. The supported read
|
|
8
|
+
* paths are `contract.domain.namespaces` plus helpers such as
|
|
9
|
+
* `contractModels()` / `ContractModelsMap`.
|
|
10
|
+
*
|
|
11
|
+
* Dispatch: walks the project root for `prisma.config.ts` directories,
|
|
12
|
+
* resolves each space's committed `contract.json` / `contract.d.ts`, and
|
|
13
|
+
* re-emits when the flat domain shape remains. Uses the nearest ancestor
|
|
14
|
+
* `package.json` `scripts.emit` when present; otherwise runs
|
|
15
|
+
* `prisma-next contract emit --config <path>`.
|
|
16
|
+
*
|
|
17
|
+
* Flags:
|
|
18
|
+
* --check dry-run; lists contract-spaces that still need re-emitting
|
|
19
|
+
* and exits 1 if any remain.
|
|
20
|
+
*/
|
|
21
|
+
import { execFile } from 'node:child_process';
|
|
22
|
+
import { access, readdir, readFile } from 'node:fs/promises';
|
|
23
|
+
import { dirname, join } from 'node:path';
|
|
24
|
+
import { promisify } from 'node:util';
|
|
25
|
+
|
|
26
|
+
const execFileAsync = promisify(execFile);
|
|
27
|
+
|
|
28
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build']);
|
|
29
|
+
|
|
30
|
+
const dryRun = process.argv.includes('--check');
|
|
31
|
+
const projectRoot = process.cwd();
|
|
32
|
+
|
|
33
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
await access(path);
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isJsonObject(value: unknown): value is Record<string, unknown> {
|
|
43
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function findPrismaNextConfigDirs(root: string): Promise<string[]> {
|
|
47
|
+
const out: string[] = [];
|
|
48
|
+
|
|
49
|
+
async function walk(dir: string): Promise<void> {
|
|
50
|
+
let entries: Awaited<ReturnType<typeof readdir>>;
|
|
51
|
+
try {
|
|
52
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
53
|
+
} catch {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
59
|
+
await walk(join(dir, entry.name));
|
|
60
|
+
} else if (entry.isFile() && entry.name === 'prisma.config.ts') {
|
|
61
|
+
out.push(dir);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await walk(root);
|
|
67
|
+
return out.sort();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function contractJsonCandidates(configDir: string): string[] {
|
|
71
|
+
return [
|
|
72
|
+
join(configDir, 'src', 'contract.json'),
|
|
73
|
+
join(configDir, 'src', 'prisma', 'contract.json'),
|
|
74
|
+
join(configDir, 'prisma', 'contract.json'),
|
|
75
|
+
join(configDir, 'contract.json'),
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function contractDtsCandidates(configDir: string): string[] {
|
|
80
|
+
return [
|
|
81
|
+
join(configDir, 'src', 'contract.d.ts'),
|
|
82
|
+
join(configDir, 'src', 'prisma', 'contract.d.ts'),
|
|
83
|
+
join(configDir, 'prisma', 'contract.d.ts'),
|
|
84
|
+
join(configDir, 'contract.d.ts'),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function contractJsonNeedsDomainPlaneMigration(raw: string): boolean {
|
|
89
|
+
let parsed: unknown;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(raw);
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (!isJsonObject(parsed)) return false;
|
|
96
|
+
const domain = parsed['domain'];
|
|
97
|
+
if (!isJsonObject(domain)) return true;
|
|
98
|
+
const namespaces = domain['namespaces'];
|
|
99
|
+
return !isJsonObject(namespaces) || Object.keys(namespaces).length === 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function contractDtsNeedsDomainPlaneMigration(raw: string): boolean {
|
|
103
|
+
return raw.includes("Contract['models']");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function packageJsonHasEmitScript(dir: string): Promise<boolean> {
|
|
107
|
+
const pkgPath = join(dir, 'package.json');
|
|
108
|
+
if (!(await pathExists(pkgPath))) return false;
|
|
109
|
+
const raw = await readFile(pkgPath, 'utf-8');
|
|
110
|
+
try {
|
|
111
|
+
const parsed: unknown = JSON.parse(raw);
|
|
112
|
+
if (!isJsonObject(parsed)) return false;
|
|
113
|
+
const scripts = parsed['scripts'];
|
|
114
|
+
if (!isJsonObject(scripts)) return false;
|
|
115
|
+
return typeof scripts['emit'] === 'string' && scripts['emit'].length > 0;
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function resolveEmitInvocation(configDir: string): Promise<{
|
|
122
|
+
readonly cwd: string;
|
|
123
|
+
readonly args: string[];
|
|
124
|
+
readonly key: string;
|
|
125
|
+
}> {
|
|
126
|
+
let dir = configDir;
|
|
127
|
+
while (dir.startsWith(projectRoot)) {
|
|
128
|
+
if (await packageJsonHasEmitScript(dir)) {
|
|
129
|
+
return { cwd: dir, args: ['emit'], key: `script:${dir}` };
|
|
130
|
+
}
|
|
131
|
+
const parent = dirname(dir);
|
|
132
|
+
if (parent === dir) break;
|
|
133
|
+
dir = parent;
|
|
134
|
+
}
|
|
135
|
+
const configPath = join(configDir, 'prisma.config.ts');
|
|
136
|
+
return {
|
|
137
|
+
cwd: projectRoot,
|
|
138
|
+
args: ['exec', 'prisma-next', 'contract', 'emit', '--config', configPath],
|
|
139
|
+
key: `config:${configPath}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function runEmit(configDir: string): Promise<void> {
|
|
144
|
+
const { cwd, args } = await resolveEmitInvocation(configDir);
|
|
145
|
+
await execFileAsync('pnpm', args, { cwd, env: process.env });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function configDirNeedsDomainPlaneMigration(configDir: string): Promise<boolean> {
|
|
149
|
+
for (const candidate of contractJsonCandidates(configDir)) {
|
|
150
|
+
if (!(await pathExists(candidate))) continue;
|
|
151
|
+
const raw = await readFile(candidate, 'utf-8');
|
|
152
|
+
if (contractJsonNeedsDomainPlaneMigration(raw)) return true;
|
|
153
|
+
}
|
|
154
|
+
for (const candidate of contractDtsCandidates(configDir)) {
|
|
155
|
+
if (!(await pathExists(candidate))) continue;
|
|
156
|
+
const raw = await readFile(candidate, 'utf-8');
|
|
157
|
+
if (contractDtsNeedsDomainPlaneMigration(raw)) return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const configDirs = await findPrismaNextConfigDirs(projectRoot);
|
|
163
|
+
const emitKeys = new Set<string>();
|
|
164
|
+
const targets: string[] = [];
|
|
165
|
+
|
|
166
|
+
for (const configDir of configDirs) {
|
|
167
|
+
if (!(await configDirNeedsDomainPlaneMigration(configDir))) continue;
|
|
168
|
+
const { key } = await resolveEmitInvocation(configDir);
|
|
169
|
+
if (emitKeys.has(key)) continue;
|
|
170
|
+
emitKeys.add(key);
|
|
171
|
+
targets.push(configDir);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (targets.length === 0) {
|
|
175
|
+
console.error(`No flat-domain contract candidates under ${projectRoot}.`);
|
|
176
|
+
process.exit(dryRun ? 0 : 1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let needsFix = 0;
|
|
180
|
+
|
|
181
|
+
for (const configDir of targets) {
|
|
182
|
+
const rel = configDir.slice(projectRoot.length + 1) || '.';
|
|
183
|
+
if (!(await configDirNeedsDomainPlaneMigration(configDir))) {
|
|
184
|
+
console.log(`OK ${rel}`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
needsFix += 1;
|
|
188
|
+
if (dryRun) {
|
|
189
|
+
console.log(`WOULD RE-EMIT ${rel}`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
console.log(`EMIT ${rel}`);
|
|
193
|
+
await runEmit(configDir);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log();
|
|
197
|
+
console.log(
|
|
198
|
+
`${targets.length} contract-space(s): ${needsFix} ${dryRun ? 'needing re-emit' : 're-emitted'}.`,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (dryRun && needsFix > 0) process.exit(1);
|
package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-emits every Postgres contract whose default namespace still uses the
|
|
3
|
+
* pre-0.12 `__unbound__` / `postgres-unbound-schema` sentinel so emitted
|
|
4
|
+
* `contract.json` / `contract.d.ts` pick up the `public` / `postgres-schema`
|
|
5
|
+
* default (public-by-default).
|
|
6
|
+
*
|
|
7
|
+
* Starting at 0.12, un-namespaced Postgres models resolve to the `public`
|
|
8
|
+
* namespace id. Explicit `namespace unbound { … }` in PSL still round-trips
|
|
9
|
+
* to `__unbound__`; this script targets only contracts whose *default*
|
|
10
|
+
* namespace is still the old sentinel shape.
|
|
11
|
+
*
|
|
12
|
+
* Dispatch: walks the project root for `prisma.config.ts` directories,
|
|
13
|
+
* resolves each space's committed `contract.json`, and re-emits when the
|
|
14
|
+
* storage tree still includes `"kind": "postgres-unbound-schema"`. Uses
|
|
15
|
+
* the nearest ancestor `package.json` `scripts.emit` when present; otherwise
|
|
16
|
+
* runs `prisma-next contract emit --config <path>`.
|
|
17
|
+
*
|
|
18
|
+
* Flags:
|
|
19
|
+
* --check dry-run; lists contract-spaces that still need re-emitting and
|
|
20
|
+
* exits 1 if any remain.
|
|
21
|
+
*/
|
|
22
|
+
import { execFile } from 'node:child_process';
|
|
23
|
+
import { access, readdir, readFile } from 'node:fs/promises';
|
|
24
|
+
import { dirname, join } from 'node:path';
|
|
25
|
+
import { promisify } from 'node:util';
|
|
26
|
+
|
|
27
|
+
const execFileAsync = promisify(execFile);
|
|
28
|
+
|
|
29
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build']);
|
|
30
|
+
|
|
31
|
+
const dryRun = process.argv.includes('--check');
|
|
32
|
+
const projectRoot = process.cwd();
|
|
33
|
+
|
|
34
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
35
|
+
try {
|
|
36
|
+
await access(path);
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isJsonObject(value: unknown): value is Record<string, unknown> {
|
|
44
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function findPrismaNextConfigDirs(root: string): Promise<string[]> {
|
|
48
|
+
const out: string[] = [];
|
|
49
|
+
|
|
50
|
+
async function walk(dir: string): Promise<void> {
|
|
51
|
+
let entries: Awaited<ReturnType<typeof readdir>>;
|
|
52
|
+
try {
|
|
53
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
54
|
+
} catch {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
if (entry.isDirectory()) {
|
|
59
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
60
|
+
await walk(join(dir, entry.name));
|
|
61
|
+
} else if (entry.isFile() && entry.name === 'prisma.config.ts') {
|
|
62
|
+
out.push(dir);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await walk(root);
|
|
68
|
+
return out.sort();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function contractJsonCandidates(configDir: string): string[] {
|
|
72
|
+
return [
|
|
73
|
+
join(configDir, 'src', 'contract.json'),
|
|
74
|
+
join(configDir, 'src', 'prisma', 'contract.json'),
|
|
75
|
+
join(configDir, 'prisma', 'contract.json'),
|
|
76
|
+
join(configDir, 'contract.json'),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function resolveContractJson(configDir: string): Promise<string | null> {
|
|
81
|
+
for (const candidate of contractJsonCandidates(configDir)) {
|
|
82
|
+
if (await pathExists(candidate)) return candidate;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function contractNeedsPublicDefaultMigration(raw: string): boolean {
|
|
88
|
+
return (
|
|
89
|
+
raw.includes('"kind": "postgres-unbound-schema"') ||
|
|
90
|
+
raw.includes('"kind":"postgres-unbound-schema"')
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function packageJsonHasEmitScript(dir: string): Promise<boolean> {
|
|
95
|
+
const pkgPath = join(dir, 'package.json');
|
|
96
|
+
if (!(await pathExists(pkgPath))) return false;
|
|
97
|
+
const raw = await readFile(pkgPath, 'utf-8');
|
|
98
|
+
try {
|
|
99
|
+
const parsed: unknown = JSON.parse(raw);
|
|
100
|
+
if (!isJsonObject(parsed)) return false;
|
|
101
|
+
const scripts = parsed['scripts'];
|
|
102
|
+
if (!isJsonObject(scripts)) return false;
|
|
103
|
+
return typeof scripts['emit'] === 'string' && scripts['emit'].length > 0;
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function packageJsonHasBuildContractSpaceScript(dir: string): Promise<boolean> {
|
|
110
|
+
const pkgPath = join(dir, 'package.json');
|
|
111
|
+
if (!(await pathExists(pkgPath))) return false;
|
|
112
|
+
const raw = await readFile(pkgPath, 'utf-8');
|
|
113
|
+
try {
|
|
114
|
+
const parsed: unknown = JSON.parse(raw);
|
|
115
|
+
if (!isJsonObject(parsed)) return false;
|
|
116
|
+
const scripts = parsed['scripts'];
|
|
117
|
+
if (!isJsonObject(scripts)) return false;
|
|
118
|
+
return (
|
|
119
|
+
typeof scripts['build:contract-space'] === 'string' &&
|
|
120
|
+
scripts['build:contract-space'].length > 0
|
|
121
|
+
);
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function resolveEmitInvocation(configDir: string): Promise<{
|
|
128
|
+
readonly cwd: string;
|
|
129
|
+
readonly args: string[];
|
|
130
|
+
readonly key: string;
|
|
131
|
+
}> {
|
|
132
|
+
let dir = configDir;
|
|
133
|
+
while (dir.startsWith(projectRoot)) {
|
|
134
|
+
if (await packageJsonHasEmitScript(dir)) {
|
|
135
|
+
return { cwd: dir, args: ['emit'], key: `script:${dir}` };
|
|
136
|
+
}
|
|
137
|
+
const parent = dirname(dir);
|
|
138
|
+
if (parent === dir) break;
|
|
139
|
+
dir = parent;
|
|
140
|
+
}
|
|
141
|
+
const configPath = join(configDir, 'prisma.config.ts');
|
|
142
|
+
return {
|
|
143
|
+
cwd: projectRoot,
|
|
144
|
+
args: ['exec', 'prisma-next', 'contract', 'emit', '--config', configPath],
|
|
145
|
+
key: `config:${configPath}`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function runEmit(configDir: string): Promise<void> {
|
|
150
|
+
const { cwd, args } = await resolveEmitInvocation(configDir);
|
|
151
|
+
await execFileAsync('pnpm', args, { cwd, env: process.env });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const configDirs = await findPrismaNextConfigDirs(projectRoot);
|
|
155
|
+
const emitKeys = new Set<string>();
|
|
156
|
+
const targets: Array<{ configDir: string; contractPath: string }> = [];
|
|
157
|
+
|
|
158
|
+
for (const configDir of configDirs) {
|
|
159
|
+
if (await packageJsonHasBuildContractSpaceScript(configDir)) continue;
|
|
160
|
+
const contractPath = await resolveContractJson(configDir);
|
|
161
|
+
if (contractPath === null) continue;
|
|
162
|
+
const raw = await readFile(contractPath, 'utf-8');
|
|
163
|
+
if (!contractNeedsPublicDefaultMigration(raw)) continue;
|
|
164
|
+
const { key } = await resolveEmitInvocation(configDir);
|
|
165
|
+
if (emitKeys.has(key)) continue;
|
|
166
|
+
emitKeys.add(key);
|
|
167
|
+
targets.push({ configDir, contractPath });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (targets.length === 0) {
|
|
171
|
+
console.error(`No Postgres public-default migration candidates under ${projectRoot}.`);
|
|
172
|
+
process.exit(dryRun ? 0 : 1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let needsFix = 0;
|
|
176
|
+
|
|
177
|
+
for (const { configDir, contractPath } of targets) {
|
|
178
|
+
const rel = configDir.slice(projectRoot.length + 1) || '.';
|
|
179
|
+
const raw = await readFile(contractPath, 'utf-8');
|
|
180
|
+
if (!contractNeedsPublicDefaultMigration(raw)) {
|
|
181
|
+
console.log(`OK ${rel}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
needsFix += 1;
|
|
185
|
+
if (dryRun) {
|
|
186
|
+
console.log(`WOULD RE-EMIT ${rel}`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
console.log(`EMIT ${rel}`);
|
|
190
|
+
await runEmit(configDir);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log();
|
|
194
|
+
console.log(
|
|
195
|
+
`${targets.length} contract-space(s): ${needsFix} ${dryRun ? 'needing re-emit' : 're-emitted'}.`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (dryRun && needsFix > 0) process.exit(1);
|