@zmdb/cli 1.0.0-beta.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.
- package/LICENSE +674 -0
- package/README.md +19 -0
- package/dist/__fixtures__/http-client/src/contract.d.ts +38 -0
- package/dist/__fixtures__/http-client/src/contract.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/src/contract.js +121 -0
- package/dist/__fixtures__/http-client/src/contract.js.map +1 -0
- package/dist/__fixtures__/http-client/src/models.d.ts +11 -0
- package/dist/__fixtures__/http-client/src/models.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/src/models.js +2 -0
- package/dist/__fixtures__/http-client/src/models.js.map +1 -0
- package/dist/__fixtures__/http-client/src/schema.d.ts +4 -0
- package/dist/__fixtures__/http-client/src/schema.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/src/schema.js +2 -0
- package/dist/__fixtures__/http-client/src/schema.js.map +1 -0
- package/dist/__fixtures__/http-client/src/unrelated.d.ts +2 -0
- package/dist/__fixtures__/http-client/src/unrelated.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/src/unrelated.js +2 -0
- package/dist/__fixtures__/http-client/src/unrelated.js.map +1 -0
- package/dist/__fixtures__/http-client/zmdb.config.d.ts +16 -0
- package/dist/__fixtures__/http-client/zmdb.config.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/zmdb.config.js +12 -0
- package/dist/__fixtures__/http-client/zmdb.config.js.map +1 -0
- package/dist/__fixtures__/project/src/schema.d.ts +6 -0
- package/dist/__fixtures__/project/src/schema.d.ts.map +1 -0
- package/dist/__fixtures__/project/src/schema.js +2 -0
- package/dist/__fixtures__/project/src/schema.js.map +1 -0
- package/dist/__fixtures__/project/zmdb.config.d.ts +22 -0
- package/dist/__fixtures__/project/zmdb.config.d.ts.map +1 -0
- package/dist/__fixtures__/project/zmdb.config.js +55 -0
- package/dist/__fixtures__/project/zmdb.config.js.map +1 -0
- package/dist/application-loader.d.ts +4 -0
- package/dist/application-loader.d.ts.map +1 -0
- package/dist/application-loader.js +102 -0
- package/dist/application-loader.js.map +1 -0
- package/dist/args.d.ts +23 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +353 -0
- package/dist/args.js.map +1 -0
- package/dist/atomic.d.ts +13 -0
- package/dist/atomic.d.ts.map +1 -0
- package/dist/atomic.js +28 -0
- package/dist/atomic.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/check.d.ts +5 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +7 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/client.d.ts +38 -0
- package/dist/commands/client.d.ts.map +1 -0
- package/dist/commands/client.js +347 -0
- package/dist/commands/client.js.map +1 -0
- package/dist/commands/codegen.d.ts +9 -0
- package/dist/commands/codegen.d.ts.map +1 -0
- package/dist/commands/codegen.js +56 -0
- package/dist/commands/codegen.js.map +1 -0
- package/dist/commands/embed.d.ts +8 -0
- package/dist/commands/embed.d.ts.map +1 -0
- package/dist/commands/embed.js +17 -0
- package/dist/commands/embed.js.map +1 -0
- package/dist/commands/export.d.ts +5 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +6 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/generate.d.ts +5 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +6 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/migrate.d.ts +9 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +18 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/new.d.ts +4 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +35 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/pull.d.ts +5 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +14 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +6 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +11 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/studio.d.ts +31 -0
- package/dist/commands/studio.d.ts.map +1 -0
- package/dist/commands/studio.js +154 -0
- package/dist/commands/studio.js.map +1 -0
- package/dist/commands/upgrade.d.ts +5 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +11 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/database.d.ts +4 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +8 -0
- package/dist/database.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +20 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +626 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle.d.ts +6 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +75 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/migration-project.d.ts +14 -0
- package/dist/migration-project.d.ts.map +1 -0
- package/dist/migration-project.js +74 -0
- package/dist/migration-project.js.map +1 -0
- package/dist/output.d.ts +27 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +60 -0
- package/dist/output.js.map +1 -0
- package/dist/repl.d.ts +48 -0
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +265 -0
- package/dist/repl.js.map +1 -0
- package/dist/scaffold.d.ts +28 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +414 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/studio/index.d.ts +20 -0
- package/dist/studio/index.d.ts.map +1 -0
- package/dist/studio/index.js +607 -0
- package/dist/studio/index.js.map +1 -0
- package/dist/templates/command.d.ts +3 -0
- package/dist/templates/command.d.ts.map +1 -0
- package/dist/templates/command.js +65 -0
- package/dist/templates/command.js.map +1 -0
- package/dist/templates/controller.d.ts +3 -0
- package/dist/templates/controller.d.ts.map +1 -0
- package/dist/templates/controller.js +41 -0
- package/dist/templates/controller.js.map +1 -0
- package/dist/templates/index.d.ts +4 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +18 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/module.d.ts +3 -0
- package/dist/templates/module.d.ts.map +1 -0
- package/dist/templates/module.js +41 -0
- package/dist/templates/module.js.map +1 -0
- package/dist/templates/project.d.ts +3 -0
- package/dist/templates/project.d.ts.map +1 -0
- package/dist/templates/project.js +280 -0
- package/dist/templates/project.js.map +1 -0
- package/dist/templates/repository.d.ts +3 -0
- package/dist/templates/repository.d.ts.map +1 -0
- package/dist/templates/repository.js +66 -0
- package/dist/templates/repository.js.map +1 -0
- package/dist/templates/schema.d.ts +3 -0
- package/dist/templates/schema.d.ts.map +1 -0
- package/dist/templates/schema.js +36 -0
- package/dist/templates/schema.js.map +1 -0
- package/dist/templates/types.d.ts +25 -0
- package/dist/templates/types.d.ts.map +1 -0
- package/dist/templates/types.js +17 -0
- package/dist/templates/types.js.map +1 -0
- package/package.json +72 -0
- package/src/__fixtures__/http-client/package.json +5 -0
- package/src/__fixtures__/http-client/src/contract.ts +85 -0
- package/src/__fixtures__/http-client/src/models.ts +12 -0
- package/src/__fixtures__/http-client/src/schema.ts +3 -0
- package/src/__fixtures__/http-client/src/unrelated.ts +1 -0
- package/src/__fixtures__/http-client/tsconfig.json +17 -0
- package/src/__fixtures__/http-client/zmdb.config.ts +12 -0
- package/src/__fixtures__/project/package.json +5 -0
- package/src/__fixtures__/project/src/schema.ts +6 -0
- package/src/__fixtures__/project/tsconfig.json +24 -0
- package/src/__fixtures__/project/zmdb.config.ts +57 -0
- package/src/application-loader.ts +108 -0
- package/src/args.ts +391 -0
- package/src/atomic.ts +43 -0
- package/src/bin.ts +5 -0
- package/src/commands/check.ts +11 -0
- package/src/commands/client.ts +415 -0
- package/src/commands/codegen.ts +73 -0
- package/src/commands/embed.ts +36 -0
- package/src/commands/export.ts +10 -0
- package/src/commands/generate.ts +14 -0
- package/src/commands/migrate.ts +38 -0
- package/src/commands/new.ts +48 -0
- package/src/commands/pull.ts +32 -0
- package/src/commands/push.ts +21 -0
- package/src/commands/studio.ts +200 -0
- package/src/commands/upgrade.ts +14 -0
- package/src/database.ts +9 -0
- package/src/errors.ts +19 -0
- package/src/index.ts +710 -0
- package/src/lifecycle.ts +73 -0
- package/src/migration-project.ts +91 -0
- package/src/output.ts +77 -0
- package/src/repl.ts +328 -0
- package/src/scaffold.ts +485 -0
- package/src/studio/index.ts +674 -0
- package/src/templates/command.ts +66 -0
- package/src/templates/controller.ts +42 -0
- package/src/templates/index.ts +29 -0
- package/src/templates/module.ts +42 -0
- package/src/templates/project.ts +281 -0
- package/src/templates/repository.ts +67 -0
- package/src/templates/schema.ts +37 -0
- package/src/templates/types.ts +46 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { readFile, watch } from 'node:fs/promises';
|
|
3
|
+
import { registerHooks } from 'node:module';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
import type { ResolvedConfig, ResolvedHttpGenerationConfig } from '@zmdb/compiler/config';
|
|
8
|
+
import { ReflectSession } from '@zmdb/compiler/reflect';
|
|
9
|
+
import type { HttpContractDeclaration } from '@zmdb/web/contract';
|
|
10
|
+
import { compileHttpContracts, generateHttpClient, type HttpContractSource } from '@zmdb/web/contract/compiler';
|
|
11
|
+
import { toOpenApi, type OpenApiDocument } from '@zmdb/web/openapi';
|
|
12
|
+
import type { FormatConfig } from 'oxfmt';
|
|
13
|
+
|
|
14
|
+
import { writeTextAtomically } from '../atomic.js';
|
|
15
|
+
import { CliInvocationError } from '../errors.js';
|
|
16
|
+
|
|
17
|
+
const GENERATION_QUERY = 'zmdb-http-generation';
|
|
18
|
+
const RELATIVE_SPECIFIER = /^\.{1,2}\//u;
|
|
19
|
+
const TYPESCRIPT_SOURCE = /\.[cm]?tsx?$/u;
|
|
20
|
+
const FORMAT_OPTIONS: FormatConfig = {
|
|
21
|
+
arrowParens: 'avoid',
|
|
22
|
+
bracketSpacing: true,
|
|
23
|
+
endOfLine: 'lf',
|
|
24
|
+
insertFinalNewline: true,
|
|
25
|
+
objectWrap: 'preserve',
|
|
26
|
+
printWidth: 120,
|
|
27
|
+
quoteProps: 'as-needed',
|
|
28
|
+
semi: true,
|
|
29
|
+
singleQuote: true,
|
|
30
|
+
sortImports: true,
|
|
31
|
+
sortPackageJson: true,
|
|
32
|
+
tabWidth: 2,
|
|
33
|
+
trailingComma: 'all',
|
|
34
|
+
useTabs: false,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let contractLoader: Promise<void> | undefined;
|
|
38
|
+
let generationSequence = 0;
|
|
39
|
+
|
|
40
|
+
export interface ClientGenerateResult {
|
|
41
|
+
readonly out: {
|
|
42
|
+
readonly openApi: string;
|
|
43
|
+
readonly client: string;
|
|
44
|
+
};
|
|
45
|
+
readonly operations: readonly string[];
|
|
46
|
+
readonly changed: boolean;
|
|
47
|
+
readonly contractFormat: 1;
|
|
48
|
+
readonly generatorVersion: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface HttpArtifactGeneration {
|
|
52
|
+
readonly result: ClientGenerateResult;
|
|
53
|
+
/** Missing or byte-different outputs before this run. */
|
|
54
|
+
readonly stale: readonly string[];
|
|
55
|
+
/** Exact project-source dependency set used for watch invalidation. */
|
|
56
|
+
readonly dependencies: readonly string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface GenerateHttpArtifactsOptions {
|
|
60
|
+
readonly check?: boolean;
|
|
61
|
+
/** A caller-owned session is retained and remains open after generation. */
|
|
62
|
+
readonly session?: ReflectSession;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface WatchHttpArtifactsOptions {
|
|
66
|
+
readonly debounceMs?: number;
|
|
67
|
+
readonly until?: Promise<unknown>;
|
|
68
|
+
readonly session?: ReflectSession;
|
|
69
|
+
readonly log?: (generation: HttpArtifactGeneration) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface RenderedArtifacts {
|
|
73
|
+
readonly openApi: string;
|
|
74
|
+
readonly client: string;
|
|
75
|
+
readonly operations: readonly string[];
|
|
76
|
+
readonly contractFormat: 1;
|
|
77
|
+
readonly generatorVersion: string;
|
|
78
|
+
readonly dependencies: readonly string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Compile once, project both public artifacts, and write or check their exact bytes. */
|
|
82
|
+
export async function generateHttpArtifacts(
|
|
83
|
+
config: ResolvedConfig,
|
|
84
|
+
options: GenerateHttpArtifactsOptions = {},
|
|
85
|
+
): Promise<HttpArtifactGeneration> {
|
|
86
|
+
const borrowed = options.session;
|
|
87
|
+
const session = borrowed ?? ReflectSession.open({ project: config.project });
|
|
88
|
+
try {
|
|
89
|
+
return await generateWithSession(config, session, options.check === true);
|
|
90
|
+
} finally {
|
|
91
|
+
if (borrowed === undefined) session.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Keep one reflection session open and regenerate only after an input in the
|
|
97
|
+
* last compiled dependency set changes.
|
|
98
|
+
*/
|
|
99
|
+
export async function watchHttpArtifacts(
|
|
100
|
+
config: ResolvedConfig,
|
|
101
|
+
options: WatchHttpArtifactsOptions = {},
|
|
102
|
+
): Promise<HttpArtifactGeneration> {
|
|
103
|
+
requireHttpConfig(config);
|
|
104
|
+
const borrowed = options.session;
|
|
105
|
+
const session = borrowed ?? ReflectSession.open({ project: config.project });
|
|
106
|
+
const log = options.log ?? (() => undefined);
|
|
107
|
+
let last: HttpArtifactGeneration;
|
|
108
|
+
let dependencies = new Set<string>();
|
|
109
|
+
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
const stop = (): void => {
|
|
112
|
+
controller.abort();
|
|
113
|
+
};
|
|
114
|
+
void options.until?.then(stop, stop);
|
|
115
|
+
|
|
116
|
+
const projectRoot = dirname(resolve(config.project));
|
|
117
|
+
const pendingRelevant = new Set<string>();
|
|
118
|
+
const pendingCreated = new Set<string>();
|
|
119
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
120
|
+
let queued = Promise.resolve();
|
|
121
|
+
let failure: unknown;
|
|
122
|
+
|
|
123
|
+
const flush = (): void => {
|
|
124
|
+
timer = undefined;
|
|
125
|
+
if (controller.signal.aborted) return;
|
|
126
|
+
const relevant = [...pendingRelevant];
|
|
127
|
+
pendingRelevant.clear();
|
|
128
|
+
if (relevant.length === 0) return;
|
|
129
|
+
const observed = new Set([...relevant, ...pendingCreated]);
|
|
130
|
+
pendingCreated.clear();
|
|
131
|
+
|
|
132
|
+
queued = queued
|
|
133
|
+
.then(async () => {
|
|
134
|
+
const created: string[] = [];
|
|
135
|
+
const changed: string[] = [];
|
|
136
|
+
const deleted: string[] = [];
|
|
137
|
+
for (const path of observed) {
|
|
138
|
+
if (!existsSync(path)) {
|
|
139
|
+
if (dependencies.has(path)) deleted.push(path);
|
|
140
|
+
} else if (session.sourceFile(path) === undefined) {
|
|
141
|
+
created.push(path);
|
|
142
|
+
} else {
|
|
143
|
+
changed.push(path);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (created.length > 0) session.created(created);
|
|
147
|
+
if (changed.length > 0) session.refresh(changed);
|
|
148
|
+
if (deleted.length > 0) session.deleted(deleted);
|
|
149
|
+
|
|
150
|
+
last = await generateWithSession(config, session, false);
|
|
151
|
+
dependencies = new Set(last.dependencies);
|
|
152
|
+
log(last);
|
|
153
|
+
})
|
|
154
|
+
.catch((error: unknown) => {
|
|
155
|
+
failure = error;
|
|
156
|
+
controller.abort();
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
last = await generateWithSession(config, session, false);
|
|
162
|
+
dependencies = new Set(last.dependencies);
|
|
163
|
+
log(last);
|
|
164
|
+
try {
|
|
165
|
+
for await (const event of watch(projectRoot, { recursive: true, signal: controller.signal })) {
|
|
166
|
+
if (typeof event.filename !== 'string') continue;
|
|
167
|
+
const path = resolve(projectRoot, event.filename);
|
|
168
|
+
if (existsSync(path) && session.sourceFile(path) === undefined && TYPESCRIPT_SOURCE.test(path)) {
|
|
169
|
+
pendingCreated.add(path);
|
|
170
|
+
}
|
|
171
|
+
if (!dependencies.has(path)) continue;
|
|
172
|
+
pendingRelevant.add(path);
|
|
173
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
174
|
+
timer = setTimeout(flush, options.debounceMs ?? 60);
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (!(error instanceof Error) || error.name !== 'AbortError') throw error;
|
|
178
|
+
}
|
|
179
|
+
} finally {
|
|
180
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
181
|
+
await queued;
|
|
182
|
+
if (borrowed === undefined) session.close();
|
|
183
|
+
}
|
|
184
|
+
if (failure !== undefined) throw failure;
|
|
185
|
+
return last;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function generateWithSession(
|
|
189
|
+
config: ResolvedConfig,
|
|
190
|
+
session: ReflectSession,
|
|
191
|
+
check: boolean,
|
|
192
|
+
): Promise<HttpArtifactGeneration> {
|
|
193
|
+
const http = requireHttpConfig(config);
|
|
194
|
+
const rendered = await renderArtifacts(http, session);
|
|
195
|
+
const outputs = [
|
|
196
|
+
{ path: http.openApiOut, source: rendered.openApi },
|
|
197
|
+
{ path: http.clientOut, source: rendered.client },
|
|
198
|
+
] as const;
|
|
199
|
+
const current = await Promise.all(outputs.map(output => readOptionalText(output.path)));
|
|
200
|
+
const stale = outputs.flatMap((output, index) => (current[index] === output.source ? [] : [output.path]));
|
|
201
|
+
|
|
202
|
+
if (!check) {
|
|
203
|
+
for (const output of outputs) {
|
|
204
|
+
if (stale.includes(output.path)) await writeTextAtomically(output.path, output.source);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
result: {
|
|
210
|
+
out: { openApi: http.openApiOut, client: http.clientOut },
|
|
211
|
+
operations: rendered.operations,
|
|
212
|
+
changed: stale.length > 0,
|
|
213
|
+
contractFormat: rendered.contractFormat,
|
|
214
|
+
generatorVersion: rendered.generatorVersion,
|
|
215
|
+
},
|
|
216
|
+
stale,
|
|
217
|
+
dependencies: rendered.dependencies,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function renderArtifacts(
|
|
222
|
+
http: ResolvedHttpGenerationConfig,
|
|
223
|
+
session: ReflectSession,
|
|
224
|
+
): Promise<RenderedArtifacts> {
|
|
225
|
+
generationSequence += 1;
|
|
226
|
+
const sources = await loadContractSources(http, generationSequence);
|
|
227
|
+
const compiled = compileHttpContracts(sources, { session });
|
|
228
|
+
const document = toOpenApi(compiled.ir);
|
|
229
|
+
const client = generateHttpClient(compiled.ir);
|
|
230
|
+
const openApiOperations = openApiOperationIds(document);
|
|
231
|
+
if (!sameStrings(openApiOperations, client.operations)) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`HTTP artifact generation: OpenAPI operations [${openApiOperations.join(', ')}] ` +
|
|
234
|
+
`do not match client operations [${client.operations.join(', ')}]`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const openApi = await formatOpenApi(http.openApiOut, document);
|
|
238
|
+
return {
|
|
239
|
+
openApi,
|
|
240
|
+
client: client.source,
|
|
241
|
+
operations: client.operations,
|
|
242
|
+
contractFormat: client.contractFormat,
|
|
243
|
+
generatorVersion: client.generatorVersion,
|
|
244
|
+
dependencies: compiled.dependencies,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function formatOpenApi(path: string, document: OpenApiDocument): Promise<string> {
|
|
249
|
+
const { format } = await import('oxfmt');
|
|
250
|
+
const formatted = await format(path, `${JSON.stringify(document, null, 2)}\n`, FORMAT_OPTIONS);
|
|
251
|
+
if (formatted.errors.length > 0) {
|
|
252
|
+
throw new TypeError(`oxfmt could not format generated ${path}: ${JSON.stringify(formatted.errors)}`);
|
|
253
|
+
}
|
|
254
|
+
return formatted.code;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function loadContractSources(
|
|
258
|
+
http: ResolvedHttpGenerationConfig,
|
|
259
|
+
generation: number,
|
|
260
|
+
): Promise<readonly HttpContractSource[]> {
|
|
261
|
+
await installContractLoader();
|
|
262
|
+
const modules = new Map<string, object>();
|
|
263
|
+
const sources: HttpContractSource[] = [];
|
|
264
|
+
|
|
265
|
+
for (const configured of http.contracts) {
|
|
266
|
+
let loaded = modules.get(configured.file);
|
|
267
|
+
if (loaded === undefined) {
|
|
268
|
+
const url = pathToFileURL(configured.file);
|
|
269
|
+
url.searchParams.set(GENERATION_QUERY, String(generation));
|
|
270
|
+
const candidate: unknown = await import(url.href);
|
|
271
|
+
if (typeof candidate !== 'object' || candidate === null) {
|
|
272
|
+
throw new Error(`HTTP contract module ${configured.file} did not load as a module record`);
|
|
273
|
+
}
|
|
274
|
+
loaded = candidate;
|
|
275
|
+
modules.set(configured.file, loaded);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const contract: unknown = Reflect.get(loaded, configured.exportName);
|
|
279
|
+
if (!isHttpContractDeclaration(contract)) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
`HTTP contract module ${configured.file} has no contract declaration export ${configured.exportName}`,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
sources.push({ file: configured.file, exportName: configured.exportName, contract });
|
|
285
|
+
}
|
|
286
|
+
return sources;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function requireHttpConfig(config: ResolvedConfig): ResolvedHttpGenerationConfig {
|
|
290
|
+
if (config.http === undefined) {
|
|
291
|
+
throw new CliInvocationError(
|
|
292
|
+
`config ${config.configPath} must declare http.contracts, http.openApi.out and http.client.out`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return config.http;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function openApiOperationIds(document: OpenApiDocument): readonly string[] {
|
|
299
|
+
const operationIds: string[] = [];
|
|
300
|
+
for (const path of Object.keys(document.paths).toSorted()) {
|
|
301
|
+
const item = document.paths[path];
|
|
302
|
+
if (item === undefined) continue;
|
|
303
|
+
for (const method of Object.keys(item).toSorted()) {
|
|
304
|
+
const operation = item[method];
|
|
305
|
+
if (operation !== undefined) operationIds.push(operation.operationId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return operationIds.toSorted();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function sameStrings(left: readonly string[], right: readonly string[]): boolean {
|
|
312
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function isHttpContractDeclaration(value: unknown): value is HttpContractDeclaration {
|
|
316
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
|
|
317
|
+
const operations = Reflect.get(value, 'operations');
|
|
318
|
+
const securitySchemes = Reflect.get(value, 'securitySchemes');
|
|
319
|
+
return (
|
|
320
|
+
typeof operations === 'object' &&
|
|
321
|
+
operations !== null &&
|
|
322
|
+
!Array.isArray(operations) &&
|
|
323
|
+
typeof securitySchemes === 'object' &&
|
|
324
|
+
securitySchemes !== null &&
|
|
325
|
+
!Array.isArray(securitySchemes)
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function readOptionalText(path: string): Promise<string | undefined> {
|
|
330
|
+
try {
|
|
331
|
+
return await readFile(path, 'utf8');
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (errorCode(error) === 'ENOENT') return undefined;
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function errorCode(error: unknown): string | undefined {
|
|
339
|
+
if (typeof error !== 'object' || error === null) return undefined;
|
|
340
|
+
const code = Reflect.get(error, 'code');
|
|
341
|
+
return typeof code === 'string' ? code : undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Lower Stage-3 decorators and propagate one cache-busting generation token
|
|
346
|
+
* through relative project imports. Bare package imports keep their canonical
|
|
347
|
+
* URL, preserving framework metadata-symbol identity.
|
|
348
|
+
*/
|
|
349
|
+
async function installContractLoader(): Promise<void> {
|
|
350
|
+
contractLoader ??= import('esbuild').then(({ transformSync }) => {
|
|
351
|
+
registerHooks({
|
|
352
|
+
resolve(specifier, context, nextResolve) {
|
|
353
|
+
const parent = context.parentURL === undefined ? undefined : new URL(context.parentURL);
|
|
354
|
+
const generation = parent?.searchParams.get(GENERATION_QUERY);
|
|
355
|
+
if (generation === null || generation === undefined || !RELATIVE_SPECIFIER.test(specifier)) {
|
|
356
|
+
return nextResolve(specifier, context);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const cleanParent = new URL(context.parentURL ?? '');
|
|
360
|
+
cleanParent.search = '';
|
|
361
|
+
cleanParent.hash = '';
|
|
362
|
+
const mapped = typescriptSibling(specifier, cleanParent);
|
|
363
|
+
if (mapped !== undefined) {
|
|
364
|
+
mapped.searchParams.set(GENERATION_QUERY, generation);
|
|
365
|
+
return { url: mapped.href, shortCircuit: true };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const resolved = nextResolve(specifier, { ...context, parentURL: cleanParent.href });
|
|
369
|
+
if (!resolved.url.startsWith('file:')) return resolved;
|
|
370
|
+
const url = new URL(resolved.url);
|
|
371
|
+
url.searchParams.set(GENERATION_QUERY, generation);
|
|
372
|
+
return { ...resolved, url: url.href };
|
|
373
|
+
},
|
|
374
|
+
load(url, context, nextLoad) {
|
|
375
|
+
if (!url.startsWith('file:')) return nextLoad(url, context);
|
|
376
|
+
const sourceUrl = new URL(url);
|
|
377
|
+
sourceUrl.search = '';
|
|
378
|
+
sourceUrl.hash = '';
|
|
379
|
+
if (!TYPESCRIPT_SOURCE.test(sourceUrl.pathname)) return nextLoad(url, context);
|
|
380
|
+
|
|
381
|
+
const file = fileURLToPath(sourceUrl);
|
|
382
|
+
const transformed = transformSync(readFileSync(file, 'utf8'), {
|
|
383
|
+
loader: file.endsWith('x') ? 'tsx' : 'ts',
|
|
384
|
+
format: 'esm',
|
|
385
|
+
target: 'es2022',
|
|
386
|
+
sourcefile: file,
|
|
387
|
+
sourcemap: 'inline',
|
|
388
|
+
tsconfigRaw: {
|
|
389
|
+
compilerOptions: {
|
|
390
|
+
experimentalDecorators: false,
|
|
391
|
+
useDefineForClassFields: true,
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
return { format: 'module', source: transformed.code, shortCircuit: true };
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
await contractLoader;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function typescriptSibling(specifier: string, parent: URL): URL | undefined {
|
|
403
|
+
const replacements = specifier.endsWith('.js')
|
|
404
|
+
? [`${specifier.slice(0, -3)}.ts`, `${specifier.slice(0, -3)}.tsx`]
|
|
405
|
+
: specifier.endsWith('.mjs')
|
|
406
|
+
? [`${specifier.slice(0, -4)}.mts`]
|
|
407
|
+
: specifier.endsWith('.cjs')
|
|
408
|
+
? [`${specifier.slice(0, -4)}.cts`]
|
|
409
|
+
: [];
|
|
410
|
+
for (const replacement of replacements) {
|
|
411
|
+
const candidate = new URL(replacement, parent);
|
|
412
|
+
if (existsSync(fileURLToPath(candidate))) return candidate;
|
|
413
|
+
}
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { compileProject, watchCodegen, writeCompileResult } from '@zmdb/compiler';
|
|
5
|
+
import { loadConfig } from '@zmdb/compiler/config';
|
|
6
|
+
|
|
7
|
+
import type { ParsedCommand } from '../args.js';
|
|
8
|
+
import { errorMessage } from '../errors.js';
|
|
9
|
+
import { withSignals } from '../lifecycle.js';
|
|
10
|
+
import { CliOutput } from '../output.js';
|
|
11
|
+
|
|
12
|
+
interface CodegenEnvironment {
|
|
13
|
+
readonly cwd: string;
|
|
14
|
+
readonly stdout: (text: string) => void;
|
|
15
|
+
readonly stderr: (text: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function runCodegen(parsed: ParsedCommand, io: CodegenEnvironment): Promise<number> {
|
|
19
|
+
const output = new CliOutput('codegen', parsed.config, parsed.json, io);
|
|
20
|
+
if (parsed.values.check === true && parsed.values.watch === true) {
|
|
21
|
+
return output.failure('--check and --watch ask for opposite things', 2);
|
|
22
|
+
}
|
|
23
|
+
if (parsed.json && parsed.values.watch === true) {
|
|
24
|
+
return output.failure('--json is unavailable because a watch session is not one JSON document', 2);
|
|
25
|
+
}
|
|
26
|
+
if (typeof parsed.values.config === 'string' && !existsSync(parsed.config)) {
|
|
27
|
+
return output.failure(`config file ${parsed.config} does not exist`, 2);
|
|
28
|
+
}
|
|
29
|
+
let config: Awaited<ReturnType<typeof loadConfig>> | undefined;
|
|
30
|
+
try {
|
|
31
|
+
config =
|
|
32
|
+
typeof parsed.values.config === 'string' || existsSync(parsed.config)
|
|
33
|
+
? await loadConfig({ cwd: io.cwd, path: parsed.config })
|
|
34
|
+
: undefined;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return output.failure(errorMessage(error), 2);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const project =
|
|
40
|
+
parsed.project === undefined
|
|
41
|
+
? (config?.project ?? resolve(io.cwd, 'tsconfig.json'))
|
|
42
|
+
: resolve(io.cwd, parsed.project);
|
|
43
|
+
const options = { project, ...(config?.resolvedNaming === undefined ? {} : { naming: config.resolvedNaming }) };
|
|
44
|
+
if (parsed.values.watch === true) {
|
|
45
|
+
const result = await withSignals(until =>
|
|
46
|
+
watchCodegen({
|
|
47
|
+
...options,
|
|
48
|
+
until,
|
|
49
|
+
log: text => output.progress(`${text}\n`),
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
return result === undefined || result.problems.length === 0 ? 0 : 1;
|
|
53
|
+
}
|
|
54
|
+
const compiled = await compileProject(options);
|
|
55
|
+
if (compiled.diagnostics.length > 0) {
|
|
56
|
+
return output.failure(
|
|
57
|
+
compiled.diagnostics
|
|
58
|
+
.map(item => `${item.code}: ${item.message}${item.file === undefined ? '' : ` (${item.file})`}`)
|
|
59
|
+
.join('\n'),
|
|
60
|
+
1,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const result = await writeCompileResult(compiled, { check: parsed.values.check === true });
|
|
64
|
+
const code = parsed.values.check === true && result.stale.length > 0 ? 1 : 0;
|
|
65
|
+
return output.result(
|
|
66
|
+
result,
|
|
67
|
+
`${result.stale.length === 0 ? 'up to date' : result.stale.map(path => `${parsed.values.check === true ? 'stale' : 'generated'} ${path}`).join('\n')}\n`,
|
|
68
|
+
code,
|
|
69
|
+
);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return output.failure(errorMessage(error), 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ResolvedConfig } from '@zmdb/compiler/config';
|
|
2
|
+
import {
|
|
3
|
+
embedMigrations as embedProjectMigrations,
|
|
4
|
+
embeddedOutputPath as projectEmbeddedOutputPath,
|
|
5
|
+
renderEmbeddedModule as renderProjectEmbeddedModule,
|
|
6
|
+
type EmbeddedModuleResult,
|
|
7
|
+
type EmbedOptions,
|
|
8
|
+
type EmbedResult,
|
|
9
|
+
} from '@zmdb/migrations/files';
|
|
10
|
+
|
|
11
|
+
import { CliInvocationError } from '../errors.js';
|
|
12
|
+
import { migrationProject } from '../migration-project.js';
|
|
13
|
+
|
|
14
|
+
export type { EmbeddedModuleResult, EmbedOptions, EmbedResult };
|
|
15
|
+
|
|
16
|
+
export const EMBEDDED_WITH_DOWN_MARKER = '// Includes down sections for development tooling.';
|
|
17
|
+
|
|
18
|
+
export function embeddedOutputPath(config: ResolvedConfig, requested?: string): string {
|
|
19
|
+
return projectEmbeddedOutputPath(migrationProject(config), requested);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function renderEmbeddedModule(
|
|
23
|
+
config: ResolvedConfig,
|
|
24
|
+
options: EmbedOptions = {},
|
|
25
|
+
): Promise<EmbeddedModuleResult> {
|
|
26
|
+
return renderProjectEmbeddedModule(migrationProject(config), options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function embedMigrations(config: ResolvedConfig, options: EmbedOptions = {}): Promise<EmbedResult> {
|
|
30
|
+
if (!config.dialect.migrations.embedded) {
|
|
31
|
+
throw new CliInvocationError(
|
|
32
|
+
`embedded migrations are not supported by ${config.dialect.name} in ${config.configPath}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return embedProjectMigrations(migrationProject(config), options);
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ResolvedConfig } from '@zmdb/compiler/config';
|
|
2
|
+
import { exportSchema as exportProjectSchema, type ExportResult } from '@zmdb/migrations/files';
|
|
3
|
+
|
|
4
|
+
import { reflectedMigrationProject } from '../migration-project.js';
|
|
5
|
+
|
|
6
|
+
export type { ExportResult };
|
|
7
|
+
|
|
8
|
+
export function exportSchema(config: ResolvedConfig): ExportResult {
|
|
9
|
+
return exportProjectSchema(reflectedMigrationProject(config));
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ResolvedConfig } from '@zmdb/compiler/config';
|
|
2
|
+
import {
|
|
3
|
+
generateMigration as generateProjectMigration,
|
|
4
|
+
type GenerateOptions,
|
|
5
|
+
type GenerateResult,
|
|
6
|
+
} from '@zmdb/migrations/files';
|
|
7
|
+
|
|
8
|
+
import { reflectedMigrationProject } from '../migration-project.js';
|
|
9
|
+
|
|
10
|
+
export type { GenerateOptions, GenerateResult };
|
|
11
|
+
|
|
12
|
+
export function generateMigration(config: ResolvedConfig, options: GenerateOptions = {}): Promise<GenerateResult> {
|
|
13
|
+
return generateProjectMigration(reflectedMigrationProject(config), options);
|
|
14
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ResolvedConfig } from '@zmdb/compiler/config';
|
|
2
|
+
import type { MigrationDriver } from '@zmdb/migrations';
|
|
3
|
+
import {
|
|
4
|
+
migrate as migrateProject,
|
|
5
|
+
migrationStatus as projectMigrationStatus,
|
|
6
|
+
rollback as rollbackProject,
|
|
7
|
+
type MigrateResult,
|
|
8
|
+
type MigrationCommandOptions,
|
|
9
|
+
type RollbackResult,
|
|
10
|
+
type StatusResult,
|
|
11
|
+
} from '@zmdb/migrations/files';
|
|
12
|
+
|
|
13
|
+
import { configuredMigrationDriver, migrationProject } from '../migration-project.js';
|
|
14
|
+
|
|
15
|
+
export type { MigrateResult, MigrationCommandOptions, RollbackResult, StatusResult };
|
|
16
|
+
|
|
17
|
+
export async function migrate(config: ResolvedConfig, options: MigrationCommandOptions): Promise<MigrateResult> {
|
|
18
|
+
const driver = await configuredDriver(config);
|
|
19
|
+
return migrateProject(migrationProject(config, { driver }), options);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function rollback(
|
|
23
|
+
config: ResolvedConfig,
|
|
24
|
+
target: number | undefined,
|
|
25
|
+
options: MigrationCommandOptions,
|
|
26
|
+
): Promise<RollbackResult> {
|
|
27
|
+
const driver = await configuredDriver(config);
|
|
28
|
+
return rollbackProject(migrationProject(config, { driver }), target, options);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function migrationStatus(config: ResolvedConfig): Promise<StatusResult> {
|
|
32
|
+
const driver = await configuredDriver(config);
|
|
33
|
+
return projectMigrationStatus(migrationProject(config, { driver }));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function configuredDriver(config: ResolvedConfig): Promise<MigrationDriver> {
|
|
37
|
+
return configuredMigrationDriver(config);
|
|
38
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ParsedCommand } from '../args.js';
|
|
2
|
+
import type { CliOutput } from '../output.js';
|
|
3
|
+
import {
|
|
4
|
+
renderScaffold,
|
|
5
|
+
scaffold,
|
|
6
|
+
ScaffoldConflictError,
|
|
7
|
+
ScaffoldUsageError,
|
|
8
|
+
type ScaffoldResult,
|
|
9
|
+
} from '../scaffold.js';
|
|
10
|
+
|
|
11
|
+
export async function createNewScaffold(parsed: ParsedCommand, output: CliOutput, cwd: string): Promise<number> {
|
|
12
|
+
const kind = parsed.positionals[0];
|
|
13
|
+
const name = parsed.positionals[1];
|
|
14
|
+
if (kind === undefined || name === undefined) {
|
|
15
|
+
return output.failure('usage: zmdb new <project|schema|controller|module|repository|command> <name>', 2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = await scaffold({
|
|
20
|
+
cwd,
|
|
21
|
+
kind,
|
|
22
|
+
name,
|
|
23
|
+
...(typeof parsed.values.package === 'string' ? { package: parsed.values.package } : {}),
|
|
24
|
+
dryRun: parsed.values['dry-run'] === true,
|
|
25
|
+
});
|
|
26
|
+
return output.result(publicResult(result), renderScaffold(result));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error instanceof ScaffoldUsageError) return output.failure(error.message, 2);
|
|
29
|
+
if (error instanceof ScaffoldConflictError) return output.failure(error.message, 1);
|
|
30
|
+
return output.failure(error instanceof Error ? error.message : String(error), 1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function publicResult(result: ScaffoldResult): {
|
|
35
|
+
readonly kind: ScaffoldResult['kind'];
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly target: string;
|
|
38
|
+
readonly files: readonly string[];
|
|
39
|
+
readonly dryRun: boolean;
|
|
40
|
+
} {
|
|
41
|
+
return {
|
|
42
|
+
kind: result.kind,
|
|
43
|
+
name: result.name.input,
|
|
44
|
+
target: result.target,
|
|
45
|
+
files: result.files.map(file => file.path),
|
|
46
|
+
dryRun: result.dryRun,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ResolvedConfig } from '@zmdb/compiler/config';
|
|
2
|
+
import {
|
|
3
|
+
pullDeclarations as pullProjectDeclarations,
|
|
4
|
+
type PullExecution,
|
|
5
|
+
type PullOptions,
|
|
6
|
+
} from '@zmdb/migrations/files';
|
|
7
|
+
|
|
8
|
+
import { configuredIntrospector } from '../database.js';
|
|
9
|
+
import { configuredMigrationDriver, migrationProject } from '../migration-project.js';
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
PullExecution,
|
|
13
|
+
PullFile,
|
|
14
|
+
PullOptions,
|
|
15
|
+
PullOutputFile,
|
|
16
|
+
PullResult,
|
|
17
|
+
PullSkippedFile,
|
|
18
|
+
} from '@zmdb/migrations/files';
|
|
19
|
+
|
|
20
|
+
export async function pullDeclarations(config: ResolvedConfig, options: PullOptions = {}): Promise<PullExecution> {
|
|
21
|
+
if (config.driver === undefined) {
|
|
22
|
+
throw new TypeError('the config must declare a driver thunk before pull can connect');
|
|
23
|
+
}
|
|
24
|
+
const driver = await configuredMigrationDriver(config);
|
|
25
|
+
return pullProjectDeclarations(
|
|
26
|
+
migrationProject(config, {
|
|
27
|
+
driver,
|
|
28
|
+
introspector: configuredIntrospector(driver.dialect),
|
|
29
|
+
}),
|
|
30
|
+
options,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ResolvedConfig } from '@zmdb/compiler/config';
|
|
2
|
+
import {
|
|
3
|
+
applyPush as applyMigrationPush,
|
|
4
|
+
isDestructive,
|
|
5
|
+
planPush as planMigrationPush,
|
|
6
|
+
type PushPlan,
|
|
7
|
+
type PushResult,
|
|
8
|
+
} from '@zmdb/migrations/files';
|
|
9
|
+
|
|
10
|
+
import { configuredMigrationDriver, reflectedMigrationProject } from '../migration-project.js';
|
|
11
|
+
|
|
12
|
+
export { isDestructive, type PushPlan, type PushResult };
|
|
13
|
+
|
|
14
|
+
export async function planPush(config: ResolvedConfig): Promise<PushPlan> {
|
|
15
|
+
const driver = await configuredMigrationDriver(config);
|
|
16
|
+
return planMigrationPush(reflectedMigrationProject(config, driver));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function applyPush(plan: PushPlan, warning: (message: string) => void): Promise<PushResult> {
|
|
20
|
+
return applyMigrationPush(plan, warning);
|
|
21
|
+
}
|