@supacloud/compiler 0.19.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +168 -50
- package/dist/index.js +143 -41
- package/dist/migration-policy.d.ts +2 -0
- package/dist/migrations.d.ts +3 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2575,9 +2575,9 @@ var init_graphql = __esm(() => {
|
|
|
2575
2575
|
});
|
|
2576
2576
|
|
|
2577
2577
|
// src/graphql-schema.ts
|
|
2578
|
-
import { mkdir as mkdir5, readFile as
|
|
2578
|
+
import { mkdir as mkdir5, readFile as readFile11 } from "node:fs/promises";
|
|
2579
2579
|
import { createHash as createHash9 } from "node:crypto";
|
|
2580
|
-
import { dirname as dirname10, resolve as
|
|
2580
|
+
import { dirname as dirname10, resolve as resolve15 } from "node:path";
|
|
2581
2581
|
async function pullGraphqlSchema(options) {
|
|
2582
2582
|
assertGraphqlOptions({ schema: options.output });
|
|
2583
2583
|
const endpoint = new URL(options.url);
|
|
@@ -2617,7 +2617,7 @@ async function pullGraphqlSchema(options) {
|
|
|
2617
2617
|
throw new Error("GraphQL schema export failed. Verify caller grants and enable introspection only in the intended development environment.");
|
|
2618
2618
|
}
|
|
2619
2619
|
const schema = lexicographicSortSchema(buildClientSchema(data));
|
|
2620
|
-
const path =
|
|
2620
|
+
const path = resolve15(options.output);
|
|
2621
2621
|
const content = path.endsWith(".json") ? JSON.stringify(introspectionFromSchema(schema), null, 2) + `
|
|
2622
2622
|
` : `# GENERATED BY supacloud-compiler graphql-schema. DO NOT EDIT.
|
|
2623
2623
|
# Database First: change database declarations, apply migrations, then re-export for the intended role.
|
|
@@ -2625,7 +2625,7 @@ async function pullGraphqlSchema(options) {
|
|
|
2625
2625
|
`;
|
|
2626
2626
|
let previous;
|
|
2627
2627
|
try {
|
|
2628
|
-
previous = await
|
|
2628
|
+
previous = await readFile11(path, "utf8");
|
|
2629
2629
|
} catch (error) {
|
|
2630
2630
|
if (!(error instanceof Error && ("code" in error) && error.code === "ENOENT"))
|
|
2631
2631
|
throw error;
|
|
@@ -2644,8 +2644,8 @@ var init_graphql_schema = __esm(() => {
|
|
|
2644
2644
|
});
|
|
2645
2645
|
|
|
2646
2646
|
// src/cli.ts
|
|
2647
|
-
import { resolve as
|
|
2648
|
-
import { readFile as
|
|
2647
|
+
import { resolve as resolve16 } from "node:path";
|
|
2648
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
2649
2649
|
|
|
2650
2650
|
// src/analyze.ts
|
|
2651
2651
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -11857,9 +11857,44 @@ async function buildDeliveryProject(options, delivery) {
|
|
|
11857
11857
|
}
|
|
11858
11858
|
|
|
11859
11859
|
// src/migrations.ts
|
|
11860
|
-
import { rename as rename5, readFile as
|
|
11861
|
-
import { relative as relative11, resolve as
|
|
11860
|
+
import { rename as rename5, readFile as readFile9, writeFile as writeFile4, rm as rm3 } from "node:fs/promises";
|
|
11861
|
+
import { relative as relative11, resolve as resolve13 } from "node:path";
|
|
11862
11862
|
import * as ts10 from "@typescript/typescript6";
|
|
11863
|
+
|
|
11864
|
+
// src/migration-policy.ts
|
|
11865
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
11866
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
11867
|
+
import { resolve as resolve12 } from "node:path";
|
|
11868
|
+
function compilerVersion() {
|
|
11869
|
+
const manifest = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
|
|
11870
|
+
if (!manifest || typeof manifest !== "object" || !("version" in manifest) || typeof manifest.version !== "string") {
|
|
11871
|
+
throw new Error("Cannot determine executing compiler version");
|
|
11872
|
+
}
|
|
11873
|
+
return manifest.version;
|
|
11874
|
+
}
|
|
11875
|
+
var migrationDependencies = {
|
|
11876
|
+
"@supacloud/app": "0.14.0",
|
|
11877
|
+
"@supacloud/compiler": compilerVersion(),
|
|
11878
|
+
"@supacloud/elysia": "0.16.0",
|
|
11879
|
+
elysia: "1.4.30",
|
|
11880
|
+
typescript: "7.0.2"
|
|
11881
|
+
};
|
|
11882
|
+
async function checkMigrationDependencies(rootDir) {
|
|
11883
|
+
const problems = [];
|
|
11884
|
+
for (const [name, expected] of Object.entries(migrationDependencies)) {
|
|
11885
|
+
try {
|
|
11886
|
+
const manifest = JSON.parse(await readFile8(resolve12(rootDir, "node_modules", name, "package.json"), "utf8"));
|
|
11887
|
+
if (!manifest || typeof manifest !== "object" || !("name" in manifest) || manifest.name !== name || !("version" in manifest) || manifest.version !== expected) {
|
|
11888
|
+
problems.push(`${name}: requires tested installed version ${expected}`);
|
|
11889
|
+
}
|
|
11890
|
+
} catch {
|
|
11891
|
+
problems.push(`${name}: install tested version ${expected} in the project node_modules first`);
|
|
11892
|
+
}
|
|
11893
|
+
}
|
|
11894
|
+
return problems;
|
|
11895
|
+
}
|
|
11896
|
+
|
|
11897
|
+
// src/migrations.ts
|
|
11863
11898
|
var ROUTE_DECORATORS2 = new Set(["Get", "Post", "Put", "Patch", "Delete", "Head", "Options"]);
|
|
11864
11899
|
var MIGRATION_COMPILER_OPTIONS = {
|
|
11865
11900
|
target: ts10.ScriptTarget.ES2022,
|
|
@@ -11961,10 +11996,10 @@ function createMigrationProgram(fileNames, sourceOverrides, rootDir) {
|
|
|
11961
11996
|
const fileExists = host.fileExists.bind(host);
|
|
11962
11997
|
const readFile = host.readFile.bind(host);
|
|
11963
11998
|
const currentDirectory = host.getCurrentDirectory.bind(host);
|
|
11964
|
-
host.fileExists = (fileName) => sourceOverrides.has(
|
|
11965
|
-
host.readFile = (fileName) => sourceOverrides.get(
|
|
11999
|
+
host.fileExists = (fileName) => sourceOverrides.has(resolve13(fileName)) || fileExists(fileName);
|
|
12000
|
+
host.readFile = (fileName) => sourceOverrides.get(resolve13(fileName)) ?? readFile(fileName);
|
|
11966
12001
|
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
11967
|
-
const source = sourceOverrides.get(
|
|
12002
|
+
const source = sourceOverrides.get(resolve13(fileName));
|
|
11968
12003
|
return source === undefined ? getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile) : ts10.createSourceFile(fileName, source, languageVersion, true);
|
|
11969
12004
|
};
|
|
11970
12005
|
host.getCurrentDirectory = () => rootDir ?? currentDirectory();
|
|
@@ -11990,7 +12025,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11990
12025
|
issues.push(issue);
|
|
11991
12026
|
};
|
|
11992
12027
|
for (const sourceFile of sourceFiles) {
|
|
11993
|
-
const sourcePath =
|
|
12028
|
+
const sourcePath = resolve13(sourceFile.fileName);
|
|
11994
12029
|
if (!includedFiles.has(sourcePath))
|
|
11995
12030
|
continue;
|
|
11996
12031
|
const visit = (node) => {
|
|
@@ -11998,7 +12033,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11998
12033
|
const options = node.arguments[1];
|
|
11999
12034
|
const object = options && resolveStaticObjectLiteral2(options, checker);
|
|
12000
12035
|
if (object) {
|
|
12001
|
-
const objectPath =
|
|
12036
|
+
const objectPath = resolve13(object.getSourceFile().fileName);
|
|
12002
12037
|
const properties = routeResponseProperties(object);
|
|
12003
12038
|
if (properties.response) {
|
|
12004
12039
|
if (!includedFiles.has(objectPath)) {
|
|
@@ -12041,13 +12076,13 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
12041
12076
|
line: lineOf2(sourceFile, response)
|
|
12042
12077
|
});
|
|
12043
12078
|
} else {
|
|
12044
|
-
const replacements = replacementsByFile.get(
|
|
12079
|
+
const replacements = replacementsByFile.get(resolve13(sourceFile.fileName)) ?? [];
|
|
12045
12080
|
replacements.push({
|
|
12046
12081
|
start: response.getStart(sourceFile),
|
|
12047
12082
|
end: response.getEnd(),
|
|
12048
12083
|
text: `responses: { 200: ${response.initializer.getText(sourceFile)} }`
|
|
12049
12084
|
});
|
|
12050
|
-
replacementsByFile.set(
|
|
12085
|
+
replacementsByFile.set(resolve13(sourceFile.fileName), replacements);
|
|
12051
12086
|
}
|
|
12052
12087
|
}
|
|
12053
12088
|
return { replacementsByFile, issues };
|
|
@@ -12060,7 +12095,7 @@ function applyReplacements(source, replacements) {
|
|
|
12060
12095
|
return content;
|
|
12061
12096
|
}
|
|
12062
12097
|
function migrateRouteResponse(source, fileName) {
|
|
12063
|
-
const absoluteFile =
|
|
12098
|
+
const absoluteFile = resolve13(fileName);
|
|
12064
12099
|
const sourceOverrides = new Map([[absoluteFile, source]]);
|
|
12065
12100
|
const program = createMigrationProgram([absoluteFile], sourceOverrides);
|
|
12066
12101
|
const sourceFile = program.getSourceFile(absoluteFile);
|
|
@@ -12076,7 +12111,7 @@ function migrateRouteResponse(source, fileName) {
|
|
|
12076
12111
|
}]
|
|
12077
12112
|
};
|
|
12078
12113
|
}
|
|
12079
|
-
const plan = planRouteResponseMigration([sourceFile], program.getTypeChecker(),
|
|
12114
|
+
const plan = planRouteResponseMigration([sourceFile], program.getTypeChecker(), resolve13("."), new Set([absoluteFile]));
|
|
12080
12115
|
const replacements = plan.replacementsByFile.get(absoluteFile) ?? [];
|
|
12081
12116
|
return {
|
|
12082
12117
|
changed: replacements.length > 0,
|
|
@@ -12086,7 +12121,7 @@ function migrateRouteResponse(source, fileName) {
|
|
|
12086
12121
|
};
|
|
12087
12122
|
}
|
|
12088
12123
|
function migrateRouteResponseProject(files, rootDir, sourceByPath) {
|
|
12089
|
-
const absoluteFiles = files.map((file) =>
|
|
12124
|
+
const absoluteFiles = files.map((file) => resolve13(file));
|
|
12090
12125
|
const program = createMigrationProgram(absoluteFiles, sourceByPath, rootDir);
|
|
12091
12126
|
const sourceFiles = absoluteFiles.map((file) => program.getSourceFile(file)).filter((file) => file !== undefined);
|
|
12092
12127
|
const plan = planRouteResponseMigration(sourceFiles, program.getTypeChecker(), rootDir, new Set(absoluteFiles));
|
|
@@ -12099,11 +12134,11 @@ function migrateRouteResponseProject(files, rootDir, sourceByPath) {
|
|
|
12099
12134
|
changed: replacements.length > 0,
|
|
12100
12135
|
content: applyReplacements(source, replacements),
|
|
12101
12136
|
replacements: replacements.length,
|
|
12102
|
-
issues: plan.issues.filter((issue) =>
|
|
12137
|
+
issues: plan.issues.filter((issue) => resolve13(rootDir, issue.file) === file)
|
|
12103
12138
|
});
|
|
12104
12139
|
}
|
|
12105
12140
|
for (const issue of plan.issues) {
|
|
12106
|
-
const path =
|
|
12141
|
+
const path = resolve13(rootDir, issue.file);
|
|
12107
12142
|
if (!results.has(path) && sourceByPath.has(path)) {
|
|
12108
12143
|
results.set(path, {
|
|
12109
12144
|
changed: false,
|
|
@@ -12125,17 +12160,59 @@ var SUPACLOUD_MIGRATIONS = [
|
|
|
12125
12160
|
}
|
|
12126
12161
|
];
|
|
12127
12162
|
async function writeAtomically(path, content) {
|
|
12128
|
-
const temporary = `${path}.supacloud-migrate-${process.pid}`;
|
|
12129
|
-
|
|
12130
|
-
|
|
12163
|
+
const temporary = `${path}.supacloud-migrate-${process.pid}-${crypto.randomUUID()}`;
|
|
12164
|
+
try {
|
|
12165
|
+
await writeFile4(temporary, content, "utf8");
|
|
12166
|
+
await rename5(temporary, path);
|
|
12167
|
+
} finally {
|
|
12168
|
+
await rm3(temporary, { force: true });
|
|
12169
|
+
}
|
|
12131
12170
|
}
|
|
12132
12171
|
async function migrateProject(options) {
|
|
12133
|
-
const rootDir =
|
|
12172
|
+
const rootDir = resolve13(options.rootDir);
|
|
12173
|
+
let migrations = SUPACLOUD_MIGRATIONS;
|
|
12174
|
+
const preflightIssues = [];
|
|
12175
|
+
if (options.fromVersion !== undefined || options.toVersion !== undefined) {
|
|
12176
|
+
migrations = [];
|
|
12177
|
+
const checkpoints = new Set(SUPACLOUD_MIGRATIONS.flatMap(({ from, to }) => [from, to]));
|
|
12178
|
+
let current = options.fromVersion;
|
|
12179
|
+
if (!current || !options.toVersion || !checkpoints.has(current) || !checkpoints.has(options.toVersion)) {
|
|
12180
|
+
preflightIssues.push({
|
|
12181
|
+
code: "migration-version-unsupported",
|
|
12182
|
+
file: "package.json",
|
|
12183
|
+
message: `Supply both supported source-format checkpoints: ${[...checkpoints].join(", ")}`
|
|
12184
|
+
});
|
|
12185
|
+
} else {
|
|
12186
|
+
const visited = new Set;
|
|
12187
|
+
while (current !== options.toVersion) {
|
|
12188
|
+
const next = SUPACLOUD_MIGRATIONS.filter((migration) => migration.from === current);
|
|
12189
|
+
if (visited.has(current) || next.length !== 1 || !next[0]) {
|
|
12190
|
+
preflightIssues.push({
|
|
12191
|
+
code: "migration-path-unavailable",
|
|
12192
|
+
file: "package.json",
|
|
12193
|
+
message: `No unambiguous forward migration from ${current} to ${options.toVersion}`
|
|
12194
|
+
});
|
|
12195
|
+
break;
|
|
12196
|
+
}
|
|
12197
|
+
visited.add(current);
|
|
12198
|
+
migrations.push(next[0]);
|
|
12199
|
+
current = next[0].to;
|
|
12200
|
+
}
|
|
12201
|
+
}
|
|
12202
|
+
if (preflightIssues.length === 0) {
|
|
12203
|
+
for (const message of await checkMigrationDependencies(rootDir)) {
|
|
12204
|
+
preflightIssues.push({ code: "migration-dependency-incompatible", file: "package.json", message });
|
|
12205
|
+
}
|
|
12206
|
+
}
|
|
12207
|
+
if (preflightIssues.length > 0) {
|
|
12208
|
+
return { write: options.write === true, migrations: [], files: [], changedFiles: [], issues: preflightIssues };
|
|
12209
|
+
}
|
|
12210
|
+
}
|
|
12134
12211
|
const include = options.include ?? ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"];
|
|
12135
12212
|
const files = ts10.sys.readDirectory(rootDir, [".ts", ".tsx", ".mts", ".cts"], ["node_modules", "dist", "generated"], include).sort();
|
|
12136
12213
|
const results = [];
|
|
12137
12214
|
const issues = [];
|
|
12138
|
-
const pendingWrites =
|
|
12215
|
+
const pendingWrites = new Map;
|
|
12139
12216
|
const sourceByPath = new Map;
|
|
12140
12217
|
const issueKeys = new Set;
|
|
12141
12218
|
const appendIssues = (items) => {
|
|
@@ -12148,14 +12225,16 @@ async function migrateProject(options) {
|
|
|
12148
12225
|
}
|
|
12149
12226
|
};
|
|
12150
12227
|
for (const filePath of files) {
|
|
12151
|
-
sourceByPath.set(
|
|
12228
|
+
sourceByPath.set(resolve13(filePath), await readFile9(filePath, "utf8"));
|
|
12152
12229
|
}
|
|
12153
|
-
|
|
12230
|
+
const originalSources = new Map(sourceByPath);
|
|
12231
|
+
const writtenFiles = new Set;
|
|
12232
|
+
for (const migration of migrations) {
|
|
12154
12233
|
const projectResults = migration.id === "route-response-to-responses" ? migrateRouteResponseProject(files, rootDir, sourceByPath) : undefined;
|
|
12155
12234
|
if (projectResults)
|
|
12156
12235
|
appendIssues(projectResults.issues);
|
|
12157
12236
|
for (const filePath of files) {
|
|
12158
|
-
const absoluteFile =
|
|
12237
|
+
const absoluteFile = resolve13(filePath);
|
|
12159
12238
|
const file = relative11(rootDir, absoluteFile) || absoluteFile;
|
|
12160
12239
|
const before = sourceByPath.get(absoluteFile);
|
|
12161
12240
|
if (before === undefined)
|
|
@@ -12163,7 +12242,7 @@ async function migrateProject(options) {
|
|
|
12163
12242
|
const result = projectResults?.results.get(absoluteFile) ?? migration.apply(before, file);
|
|
12164
12243
|
sourceByPath.set(absoluteFile, result.content);
|
|
12165
12244
|
if (result.changed && result.issues.length === 0) {
|
|
12166
|
-
pendingWrites.
|
|
12245
|
+
pendingWrites.set(absoluteFile, result.content);
|
|
12167
12246
|
}
|
|
12168
12247
|
if (!projectResults)
|
|
12169
12248
|
appendIssues(result.issues);
|
|
@@ -12178,13 +12257,36 @@ async function migrateProject(options) {
|
|
|
12178
12257
|
}
|
|
12179
12258
|
}
|
|
12180
12259
|
if (options.write && issues.length === 0) {
|
|
12181
|
-
|
|
12182
|
-
|
|
12260
|
+
const written = [];
|
|
12261
|
+
try {
|
|
12262
|
+
for (const [path, content] of pendingWrites) {
|
|
12263
|
+
if (await readFile9(path, "utf8") !== originalSources.get(path)) {
|
|
12264
|
+
throw new Error(`Source changed during migration: ${path}`);
|
|
12265
|
+
}
|
|
12266
|
+
await writeAtomically(path, content);
|
|
12267
|
+
written.push(path);
|
|
12268
|
+
writtenFiles.add(path);
|
|
12269
|
+
}
|
|
12270
|
+
} catch (error) {
|
|
12271
|
+
appendIssues([{ code: "migration-write-failed", file: rootDir, message: String(error) }]);
|
|
12272
|
+
for (const path of written.reverse()) {
|
|
12273
|
+
try {
|
|
12274
|
+
const original = originalSources.get(path);
|
|
12275
|
+
if (original === undefined || await readFile9(path, "utf8") !== pendingWrites.get(path)) {
|
|
12276
|
+
throw new Error("File changed after migration; refusing to overwrite concurrent edits");
|
|
12277
|
+
}
|
|
12278
|
+
await writeAtomically(path, original);
|
|
12279
|
+
writtenFiles.delete(path);
|
|
12280
|
+
} catch (rollbackError) {
|
|
12281
|
+
appendIssues([{ code: "migration-rollback-failed", file: path, message: String(rollbackError) }]);
|
|
12282
|
+
}
|
|
12283
|
+
}
|
|
12284
|
+
}
|
|
12183
12285
|
}
|
|
12184
|
-
const changedFiles = options.write && issues.length > 0 ? [] : results.filter((result) => result.changed && result.issues.length === 0).map((result) => result.file);
|
|
12286
|
+
const changedFiles = options.write && issues.length > 0 ? [...writtenFiles].map((file) => relative11(rootDir, file)) : [...new Set(results.filter((result) => result.changed && result.issues.length === 0).map((result) => result.file))];
|
|
12185
12287
|
return {
|
|
12186
12288
|
write: options.write === true,
|
|
12187
|
-
migrations:
|
|
12289
|
+
migrations: migrations.map(({ id, from, to, description }) => ({ id, from, to, description })),
|
|
12188
12290
|
files: results,
|
|
12189
12291
|
changedFiles,
|
|
12190
12292
|
issues
|
|
@@ -12192,8 +12294,8 @@ async function migrateProject(options) {
|
|
|
12192
12294
|
}
|
|
12193
12295
|
|
|
12194
12296
|
// src/openapi-tools.ts
|
|
12195
|
-
import { mkdir as mkdir4, readFile as
|
|
12196
|
-
import { dirname as dirname9, resolve as
|
|
12297
|
+
import { mkdir as mkdir4, readFile as readFile10, rename as rename6, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
|
|
12298
|
+
import { dirname as dirname9, resolve as resolve14 } from "node:path";
|
|
12197
12299
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
12198
12300
|
|
|
12199
12301
|
class OpenApiDocumentError extends Error {
|
|
@@ -12584,7 +12686,7 @@ function serializeOpenApiJson(document, space = 2) {
|
|
|
12584
12686
|
}
|
|
12585
12687
|
async function readOpenApiJson(path) {
|
|
12586
12688
|
try {
|
|
12587
|
-
const value = JSON.parse(await
|
|
12689
|
+
const value = JSON.parse(await readFile10(resolve14(path), "utf8"));
|
|
12588
12690
|
return parseOpenApiDocument(value);
|
|
12589
12691
|
} catch (error) {
|
|
12590
12692
|
if (error instanceof OpenApiDocumentError)
|
|
@@ -12593,11 +12695,11 @@ async function readOpenApiJson(path) {
|
|
|
12593
12695
|
}
|
|
12594
12696
|
}
|
|
12595
12697
|
async function writeOpenApiJson(document, outputPath, space = 2) {
|
|
12596
|
-
const path =
|
|
12698
|
+
const path = resolve14(outputPath);
|
|
12597
12699
|
const content = serializeOpenApiJson(document, space);
|
|
12598
12700
|
await mkdir4(dirname9(path), { recursive: true });
|
|
12599
12701
|
try {
|
|
12600
|
-
if (await
|
|
12702
|
+
if (await readFile10(path, "utf8") === content)
|
|
12601
12703
|
return { path, written: false };
|
|
12602
12704
|
} catch {}
|
|
12603
12705
|
const temporaryPath = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -12614,7 +12716,7 @@ async function writeOpenApiJson(document, outputPath, space = 2) {
|
|
|
12614
12716
|
}
|
|
12615
12717
|
async function loadGeneratedOpenApiDocument(modulePath) {
|
|
12616
12718
|
try {
|
|
12617
|
-
const moduleUrl = pathToFileURL2(
|
|
12719
|
+
const moduleUrl = pathToFileURL2(resolve14(modulePath));
|
|
12618
12720
|
moduleUrl.searchParams.set("supacloud-openapi-export", "1");
|
|
12619
12721
|
const loaded = await import(moduleUrl.href);
|
|
12620
12722
|
if (!isRecord(loaded))
|
|
@@ -12757,6 +12859,8 @@ Options:
|
|
|
12757
12859
|
--delivery <file> plan/build-delivery: validated JSON configuration (overrides config.delivery)
|
|
12758
12860
|
--dry-run Preview a fix without writing the target file
|
|
12759
12861
|
--write Apply a fix or migration to disk (preview-only by default)
|
|
12862
|
+
--from-version Migration source-format checkpoint (requires --to-version)
|
|
12863
|
+
--to-version Migration target checkpoint; verifies installed dependencies
|
|
12760
12864
|
--preset, -p <name> Architecture preset ('modular-monolith' | 'angular-enterprise' | 'clean-architecture')
|
|
12761
12865
|
--help, -h Show this help
|
|
12762
12866
|
`);
|
|
@@ -12784,6 +12888,8 @@ async function run() {
|
|
|
12784
12888
|
let query;
|
|
12785
12889
|
let json = false;
|
|
12786
12890
|
let dryRun = true;
|
|
12891
|
+
let fromVersion;
|
|
12892
|
+
let toVersion;
|
|
12787
12893
|
let noGraphql = false;
|
|
12788
12894
|
let projectUrl;
|
|
12789
12895
|
let keyEnv;
|
|
@@ -12897,6 +13003,16 @@ async function run() {
|
|
|
12897
13003
|
if (command === "plan")
|
|
12898
13004
|
throw new Error("plan is read-only; --write is not supported");
|
|
12899
13005
|
dryRun = false;
|
|
13006
|
+
} else if (arg === "--from-version" || arg === "--to-version") {
|
|
13007
|
+
if (command !== "migrate")
|
|
13008
|
+
throw new Error(`${arg} is only supported by migrate`);
|
|
13009
|
+
const value = args[++i];
|
|
13010
|
+
if (!value || value.startsWith("-"))
|
|
13011
|
+
throw new Error(`${arg} requires a version`);
|
|
13012
|
+
if (arg === "--from-version")
|
|
13013
|
+
fromVersion = value;
|
|
13014
|
+
else
|
|
13015
|
+
toVersion = value;
|
|
12900
13016
|
} else if (arg === "--preset" || arg === "-p") {
|
|
12901
13017
|
const presetArg = args[++i];
|
|
12902
13018
|
if (!isModuleBoundaryPresetName(presetArg)) {
|
|
@@ -12927,7 +13043,7 @@ async function run() {
|
|
|
12927
13043
|
const currentPath = openApiDiffPaths[1];
|
|
12928
13044
|
if (!basePath || !currentPath)
|
|
12929
13045
|
throw new Error("openapi-diff requires two JSON file paths");
|
|
12930
|
-
const result = diffOpenApiDocuments(await readOpenApiJson(
|
|
13046
|
+
const result = diffOpenApiDocuments(await readOpenApiJson(resolve16(process.cwd(), basePath)), await readOpenApiJson(resolve16(process.cwd(), currentPath)));
|
|
12931
13047
|
console.log(json ? JSON.stringify(result, null, 2) : formatOpenApiDiff(result));
|
|
12932
13048
|
if (!result.ok)
|
|
12933
13049
|
process.exitCode = 1;
|
|
@@ -12942,8 +13058,8 @@ async function run() {
|
|
|
12942
13058
|
if (!modulePath || !outputPath)
|
|
12943
13059
|
throw new Error("openapi-export requires an OpenAPI module and output path");
|
|
12944
13060
|
const result = await exportGeneratedOpenApiJson({
|
|
12945
|
-
modulePath:
|
|
12946
|
-
outputPath:
|
|
13061
|
+
modulePath: resolve16(process.cwd(), modulePath),
|
|
13062
|
+
outputPath: resolve16(process.cwd(), outputPath),
|
|
12947
13063
|
...openApiExportSpace === undefined ? {} : { space: openApiExportSpace }
|
|
12948
13064
|
});
|
|
12949
13065
|
console.log(json ? JSON.stringify({ ok: true, ...result }, null, 2) : result.written ? `OpenAPI JSON written: ${result.path}` : `OpenAPI JSON matches: ${result.path}`);
|
|
@@ -12951,8 +13067,10 @@ async function run() {
|
|
|
12951
13067
|
}
|
|
12952
13068
|
if (command === "migrate") {
|
|
12953
13069
|
const result = await migrateProject({
|
|
12954
|
-
rootDir: rootDir ?
|
|
12955
|
-
write: !dryRun
|
|
13070
|
+
rootDir: rootDir ? resolve16(process.cwd(), rootDir) : process.cwd(),
|
|
13071
|
+
write: !dryRun,
|
|
13072
|
+
...fromVersion === undefined ? {} : { fromVersion },
|
|
13073
|
+
...toVersion === undefined ? {} : { toVersion }
|
|
12956
13074
|
});
|
|
12957
13075
|
if (json) {
|
|
12958
13076
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -12961,9 +13079,9 @@ async function run() {
|
|
|
12961
13079
|
const lines = [`${action} ${result.changedFiles.length} file(s)`];
|
|
12962
13080
|
for (const file of result.files) {
|
|
12963
13081
|
lines.push(` ${file.file}: ${file.replacements} replacement(s)`);
|
|
12964
|
-
for (const issue of file.issues)
|
|
12965
|
-
lines.push(` ${issue.file}:${issue.line ?? 0} ${issue.code}: ${issue.message}`);
|
|
12966
13082
|
}
|
|
13083
|
+
for (const issue of result.issues)
|
|
13084
|
+
lines.push(` ${issue.file}:${issue.line ?? 0} ${issue.code}: ${issue.message}`);
|
|
12967
13085
|
if (result.changedFiles.length === 0 && result.issues.length === 0)
|
|
12968
13086
|
lines.push(" no migrations required");
|
|
12969
13087
|
console.log(lines.join(`
|
|
@@ -12977,8 +13095,8 @@ async function run() {
|
|
|
12977
13095
|
if (checkSchema && command !== "graphql-schema")
|
|
12978
13096
|
throw new Error("--check is only supported by graphql-schema");
|
|
12979
13097
|
const defaults = resolveSupacloudConfig(loadedConfig, process.cwd());
|
|
12980
|
-
const resolvedRoot = rootDir ?
|
|
12981
|
-
const resolvedOut = outDir ?
|
|
13098
|
+
const resolvedRoot = rootDir ? resolve16(process.cwd(), rootDir) : defaults.rootDir;
|
|
13099
|
+
const resolvedOut = outDir ? resolve16(process.cwd(), outDir) : defaults.outDir;
|
|
12982
13100
|
const configured = compileOptionsFromConfig({
|
|
12983
13101
|
...loadedConfig,
|
|
12984
13102
|
root: resolvedRoot,
|
|
@@ -12997,7 +13115,7 @@ async function run() {
|
|
|
12997
13115
|
let delivery = loadedConfig.delivery;
|
|
12998
13116
|
if (deliveryPath !== undefined) {
|
|
12999
13117
|
try {
|
|
13000
|
-
delivery = JSON.parse(await
|
|
13118
|
+
delivery = JSON.parse(await readFile12(resolve16(process.cwd(), deliveryPath), "utf8"));
|
|
13001
13119
|
} catch {
|
|
13002
13120
|
throw new DeliveryConfigurationError;
|
|
13003
13121
|
}
|
|
@@ -13044,7 +13162,7 @@ ${item.suggestion ?? ""}`).join(`
|
|
|
13044
13162
|
} else if (command === "fix") {
|
|
13045
13163
|
if (!query)
|
|
13046
13164
|
throw new Error("fix requires a JSON file containing one DiagnosticFix");
|
|
13047
|
-
const fix = JSON.parse(await
|
|
13165
|
+
const fix = JSON.parse(await readFile12(resolve16(process.cwd(), query), "utf8"));
|
|
13048
13166
|
const result = await applyDiagnosticFix(fix, { rootDir: resolvedRoot, dryRun });
|
|
13049
13167
|
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
13050
13168
|
} else if (command === "compile") {
|
package/dist/index.js
CHANGED
|
@@ -2574,9 +2574,9 @@ var init_graphql = __esm(() => {
|
|
|
2574
2574
|
});
|
|
2575
2575
|
|
|
2576
2576
|
// src/graphql-schema.ts
|
|
2577
|
-
import { mkdir as mkdir5, readFile as
|
|
2577
|
+
import { mkdir as mkdir5, readFile as readFile11 } from "node:fs/promises";
|
|
2578
2578
|
import { createHash as createHash9 } from "node:crypto";
|
|
2579
|
-
import { dirname as dirname10, resolve as
|
|
2579
|
+
import { dirname as dirname10, resolve as resolve15 } from "node:path";
|
|
2580
2580
|
async function pullGraphqlSchema(options) {
|
|
2581
2581
|
assertGraphqlOptions({ schema: options.output });
|
|
2582
2582
|
const endpoint = new URL(options.url);
|
|
@@ -2616,7 +2616,7 @@ async function pullGraphqlSchema(options) {
|
|
|
2616
2616
|
throw new Error("GraphQL schema export failed. Verify caller grants and enable introspection only in the intended development environment.");
|
|
2617
2617
|
}
|
|
2618
2618
|
const schema = lexicographicSortSchema(buildClientSchema(data));
|
|
2619
|
-
const path =
|
|
2619
|
+
const path = resolve15(options.output);
|
|
2620
2620
|
const content = path.endsWith(".json") ? JSON.stringify(introspectionFromSchema(schema), null, 2) + `
|
|
2621
2621
|
` : `# GENERATED BY supacloud-compiler graphql-schema. DO NOT EDIT.
|
|
2622
2622
|
# Database First: change database declarations, apply migrations, then re-export for the intended role.
|
|
@@ -2624,7 +2624,7 @@ async function pullGraphqlSchema(options) {
|
|
|
2624
2624
|
`;
|
|
2625
2625
|
let previous;
|
|
2626
2626
|
try {
|
|
2627
|
-
previous = await
|
|
2627
|
+
previous = await readFile11(path, "utf8");
|
|
2628
2628
|
} catch (error) {
|
|
2629
2629
|
if (!(error instanceof Error && ("code" in error) && error.code === "ENOENT"))
|
|
2630
2630
|
throw error;
|
|
@@ -11434,9 +11434,44 @@ function watchProject(options) {
|
|
|
11434
11434
|
};
|
|
11435
11435
|
}
|
|
11436
11436
|
// src/migrations.ts
|
|
11437
|
-
import { rename as rename5, readFile as
|
|
11438
|
-
import { relative as relative10, resolve as
|
|
11437
|
+
import { rename as rename5, readFile as readFile9, writeFile as writeFile4, rm as rm3 } from "node:fs/promises";
|
|
11438
|
+
import { relative as relative10, resolve as resolve12 } from "node:path";
|
|
11439
11439
|
import * as ts10 from "@typescript/typescript6";
|
|
11440
|
+
|
|
11441
|
+
// src/migration-policy.ts
|
|
11442
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
11443
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
11444
|
+
import { resolve as resolve11 } from "node:path";
|
|
11445
|
+
function compilerVersion() {
|
|
11446
|
+
const manifest = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
|
|
11447
|
+
if (!manifest || typeof manifest !== "object" || !("version" in manifest) || typeof manifest.version !== "string") {
|
|
11448
|
+
throw new Error("Cannot determine executing compiler version");
|
|
11449
|
+
}
|
|
11450
|
+
return manifest.version;
|
|
11451
|
+
}
|
|
11452
|
+
var migrationDependencies = {
|
|
11453
|
+
"@supacloud/app": "0.14.0",
|
|
11454
|
+
"@supacloud/compiler": compilerVersion(),
|
|
11455
|
+
"@supacloud/elysia": "0.16.0",
|
|
11456
|
+
elysia: "1.4.30",
|
|
11457
|
+
typescript: "7.0.2"
|
|
11458
|
+
};
|
|
11459
|
+
async function checkMigrationDependencies(rootDir) {
|
|
11460
|
+
const problems = [];
|
|
11461
|
+
for (const [name, expected] of Object.entries(migrationDependencies)) {
|
|
11462
|
+
try {
|
|
11463
|
+
const manifest = JSON.parse(await readFile8(resolve11(rootDir, "node_modules", name, "package.json"), "utf8"));
|
|
11464
|
+
if (!manifest || typeof manifest !== "object" || !("name" in manifest) || manifest.name !== name || !("version" in manifest) || manifest.version !== expected) {
|
|
11465
|
+
problems.push(`${name}: requires tested installed version ${expected}`);
|
|
11466
|
+
}
|
|
11467
|
+
} catch {
|
|
11468
|
+
problems.push(`${name}: install tested version ${expected} in the project node_modules first`);
|
|
11469
|
+
}
|
|
11470
|
+
}
|
|
11471
|
+
return problems;
|
|
11472
|
+
}
|
|
11473
|
+
|
|
11474
|
+
// src/migrations.ts
|
|
11440
11475
|
var ROUTE_DECORATORS2 = new Set(["Get", "Post", "Put", "Patch", "Delete", "Head", "Options"]);
|
|
11441
11476
|
var MIGRATION_COMPILER_OPTIONS = {
|
|
11442
11477
|
target: ts10.ScriptTarget.ES2022,
|
|
@@ -11538,10 +11573,10 @@ function createMigrationProgram(fileNames, sourceOverrides, rootDir) {
|
|
|
11538
11573
|
const fileExists = host.fileExists.bind(host);
|
|
11539
11574
|
const readFile = host.readFile.bind(host);
|
|
11540
11575
|
const currentDirectory = host.getCurrentDirectory.bind(host);
|
|
11541
|
-
host.fileExists = (fileName) => sourceOverrides.has(
|
|
11542
|
-
host.readFile = (fileName) => sourceOverrides.get(
|
|
11576
|
+
host.fileExists = (fileName) => sourceOverrides.has(resolve12(fileName)) || fileExists(fileName);
|
|
11577
|
+
host.readFile = (fileName) => sourceOverrides.get(resolve12(fileName)) ?? readFile(fileName);
|
|
11543
11578
|
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
11544
|
-
const source = sourceOverrides.get(
|
|
11579
|
+
const source = sourceOverrides.get(resolve12(fileName));
|
|
11545
11580
|
return source === undefined ? getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile) : ts10.createSourceFile(fileName, source, languageVersion, true);
|
|
11546
11581
|
};
|
|
11547
11582
|
host.getCurrentDirectory = () => rootDir ?? currentDirectory();
|
|
@@ -11567,7 +11602,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11567
11602
|
issues.push(issue);
|
|
11568
11603
|
};
|
|
11569
11604
|
for (const sourceFile of sourceFiles) {
|
|
11570
|
-
const sourcePath =
|
|
11605
|
+
const sourcePath = resolve12(sourceFile.fileName);
|
|
11571
11606
|
if (!includedFiles.has(sourcePath))
|
|
11572
11607
|
continue;
|
|
11573
11608
|
const visit = (node) => {
|
|
@@ -11575,7 +11610,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11575
11610
|
const options = node.arguments[1];
|
|
11576
11611
|
const object = options && resolveStaticObjectLiteral2(options, checker);
|
|
11577
11612
|
if (object) {
|
|
11578
|
-
const objectPath =
|
|
11613
|
+
const objectPath = resolve12(object.getSourceFile().fileName);
|
|
11579
11614
|
const properties = routeResponseProperties(object);
|
|
11580
11615
|
if (properties.response) {
|
|
11581
11616
|
if (!includedFiles.has(objectPath)) {
|
|
@@ -11618,13 +11653,13 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11618
11653
|
line: lineOf2(sourceFile, response)
|
|
11619
11654
|
});
|
|
11620
11655
|
} else {
|
|
11621
|
-
const replacements = replacementsByFile.get(
|
|
11656
|
+
const replacements = replacementsByFile.get(resolve12(sourceFile.fileName)) ?? [];
|
|
11622
11657
|
replacements.push({
|
|
11623
11658
|
start: response.getStart(sourceFile),
|
|
11624
11659
|
end: response.getEnd(),
|
|
11625
11660
|
text: `responses: { 200: ${response.initializer.getText(sourceFile)} }`
|
|
11626
11661
|
});
|
|
11627
|
-
replacementsByFile.set(
|
|
11662
|
+
replacementsByFile.set(resolve12(sourceFile.fileName), replacements);
|
|
11628
11663
|
}
|
|
11629
11664
|
}
|
|
11630
11665
|
return { replacementsByFile, issues };
|
|
@@ -11637,7 +11672,7 @@ function applyReplacements(source, replacements) {
|
|
|
11637
11672
|
return content;
|
|
11638
11673
|
}
|
|
11639
11674
|
function migrateRouteResponse(source, fileName) {
|
|
11640
|
-
const absoluteFile =
|
|
11675
|
+
const absoluteFile = resolve12(fileName);
|
|
11641
11676
|
const sourceOverrides = new Map([[absoluteFile, source]]);
|
|
11642
11677
|
const program = createMigrationProgram([absoluteFile], sourceOverrides);
|
|
11643
11678
|
const sourceFile = program.getSourceFile(absoluteFile);
|
|
@@ -11653,7 +11688,7 @@ function migrateRouteResponse(source, fileName) {
|
|
|
11653
11688
|
}]
|
|
11654
11689
|
};
|
|
11655
11690
|
}
|
|
11656
|
-
const plan = planRouteResponseMigration([sourceFile], program.getTypeChecker(),
|
|
11691
|
+
const plan = planRouteResponseMigration([sourceFile], program.getTypeChecker(), resolve12("."), new Set([absoluteFile]));
|
|
11657
11692
|
const replacements = plan.replacementsByFile.get(absoluteFile) ?? [];
|
|
11658
11693
|
return {
|
|
11659
11694
|
changed: replacements.length > 0,
|
|
@@ -11663,7 +11698,7 @@ function migrateRouteResponse(source, fileName) {
|
|
|
11663
11698
|
};
|
|
11664
11699
|
}
|
|
11665
11700
|
function migrateRouteResponseProject(files, rootDir, sourceByPath) {
|
|
11666
|
-
const absoluteFiles = files.map((file) =>
|
|
11701
|
+
const absoluteFiles = files.map((file) => resolve12(file));
|
|
11667
11702
|
const program = createMigrationProgram(absoluteFiles, sourceByPath, rootDir);
|
|
11668
11703
|
const sourceFiles = absoluteFiles.map((file) => program.getSourceFile(file)).filter((file) => file !== undefined);
|
|
11669
11704
|
const plan = planRouteResponseMigration(sourceFiles, program.getTypeChecker(), rootDir, new Set(absoluteFiles));
|
|
@@ -11676,11 +11711,11 @@ function migrateRouteResponseProject(files, rootDir, sourceByPath) {
|
|
|
11676
11711
|
changed: replacements.length > 0,
|
|
11677
11712
|
content: applyReplacements(source, replacements),
|
|
11678
11713
|
replacements: replacements.length,
|
|
11679
|
-
issues: plan.issues.filter((issue) =>
|
|
11714
|
+
issues: plan.issues.filter((issue) => resolve12(rootDir, issue.file) === file)
|
|
11680
11715
|
});
|
|
11681
11716
|
}
|
|
11682
11717
|
for (const issue of plan.issues) {
|
|
11683
|
-
const path =
|
|
11718
|
+
const path = resolve12(rootDir, issue.file);
|
|
11684
11719
|
if (!results.has(path) && sourceByPath.has(path)) {
|
|
11685
11720
|
results.set(path, {
|
|
11686
11721
|
changed: false,
|
|
@@ -11702,17 +11737,59 @@ var SUPACLOUD_MIGRATIONS = [
|
|
|
11702
11737
|
}
|
|
11703
11738
|
];
|
|
11704
11739
|
async function writeAtomically(path, content) {
|
|
11705
|
-
const temporary = `${path}.supacloud-migrate-${process.pid}`;
|
|
11706
|
-
|
|
11707
|
-
|
|
11740
|
+
const temporary = `${path}.supacloud-migrate-${process.pid}-${crypto.randomUUID()}`;
|
|
11741
|
+
try {
|
|
11742
|
+
await writeFile4(temporary, content, "utf8");
|
|
11743
|
+
await rename5(temporary, path);
|
|
11744
|
+
} finally {
|
|
11745
|
+
await rm3(temporary, { force: true });
|
|
11746
|
+
}
|
|
11708
11747
|
}
|
|
11709
11748
|
async function migrateProject(options) {
|
|
11710
|
-
const rootDir =
|
|
11749
|
+
const rootDir = resolve12(options.rootDir);
|
|
11750
|
+
let migrations = SUPACLOUD_MIGRATIONS;
|
|
11751
|
+
const preflightIssues = [];
|
|
11752
|
+
if (options.fromVersion !== undefined || options.toVersion !== undefined) {
|
|
11753
|
+
migrations = [];
|
|
11754
|
+
const checkpoints = new Set(SUPACLOUD_MIGRATIONS.flatMap(({ from, to }) => [from, to]));
|
|
11755
|
+
let current = options.fromVersion;
|
|
11756
|
+
if (!current || !options.toVersion || !checkpoints.has(current) || !checkpoints.has(options.toVersion)) {
|
|
11757
|
+
preflightIssues.push({
|
|
11758
|
+
code: "migration-version-unsupported",
|
|
11759
|
+
file: "package.json",
|
|
11760
|
+
message: `Supply both supported source-format checkpoints: ${[...checkpoints].join(", ")}`
|
|
11761
|
+
});
|
|
11762
|
+
} else {
|
|
11763
|
+
const visited = new Set;
|
|
11764
|
+
while (current !== options.toVersion) {
|
|
11765
|
+
const next = SUPACLOUD_MIGRATIONS.filter((migration) => migration.from === current);
|
|
11766
|
+
if (visited.has(current) || next.length !== 1 || !next[0]) {
|
|
11767
|
+
preflightIssues.push({
|
|
11768
|
+
code: "migration-path-unavailable",
|
|
11769
|
+
file: "package.json",
|
|
11770
|
+
message: `No unambiguous forward migration from ${current} to ${options.toVersion}`
|
|
11771
|
+
});
|
|
11772
|
+
break;
|
|
11773
|
+
}
|
|
11774
|
+
visited.add(current);
|
|
11775
|
+
migrations.push(next[0]);
|
|
11776
|
+
current = next[0].to;
|
|
11777
|
+
}
|
|
11778
|
+
}
|
|
11779
|
+
if (preflightIssues.length === 0) {
|
|
11780
|
+
for (const message of await checkMigrationDependencies(rootDir)) {
|
|
11781
|
+
preflightIssues.push({ code: "migration-dependency-incompatible", file: "package.json", message });
|
|
11782
|
+
}
|
|
11783
|
+
}
|
|
11784
|
+
if (preflightIssues.length > 0) {
|
|
11785
|
+
return { write: options.write === true, migrations: [], files: [], changedFiles: [], issues: preflightIssues };
|
|
11786
|
+
}
|
|
11787
|
+
}
|
|
11711
11788
|
const include = options.include ?? ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"];
|
|
11712
11789
|
const files = ts10.sys.readDirectory(rootDir, [".ts", ".tsx", ".mts", ".cts"], ["node_modules", "dist", "generated"], include).sort();
|
|
11713
11790
|
const results = [];
|
|
11714
11791
|
const issues = [];
|
|
11715
|
-
const pendingWrites =
|
|
11792
|
+
const pendingWrites = new Map;
|
|
11716
11793
|
const sourceByPath = new Map;
|
|
11717
11794
|
const issueKeys = new Set;
|
|
11718
11795
|
const appendIssues = (items) => {
|
|
@@ -11725,14 +11802,16 @@ async function migrateProject(options) {
|
|
|
11725
11802
|
}
|
|
11726
11803
|
};
|
|
11727
11804
|
for (const filePath of files) {
|
|
11728
|
-
sourceByPath.set(
|
|
11805
|
+
sourceByPath.set(resolve12(filePath), await readFile9(filePath, "utf8"));
|
|
11729
11806
|
}
|
|
11730
|
-
|
|
11807
|
+
const originalSources = new Map(sourceByPath);
|
|
11808
|
+
const writtenFiles = new Set;
|
|
11809
|
+
for (const migration of migrations) {
|
|
11731
11810
|
const projectResults = migration.id === "route-response-to-responses" ? migrateRouteResponseProject(files, rootDir, sourceByPath) : undefined;
|
|
11732
11811
|
if (projectResults)
|
|
11733
11812
|
appendIssues(projectResults.issues);
|
|
11734
11813
|
for (const filePath of files) {
|
|
11735
|
-
const absoluteFile =
|
|
11814
|
+
const absoluteFile = resolve12(filePath);
|
|
11736
11815
|
const file = relative10(rootDir, absoluteFile) || absoluteFile;
|
|
11737
11816
|
const before = sourceByPath.get(absoluteFile);
|
|
11738
11817
|
if (before === undefined)
|
|
@@ -11740,7 +11819,7 @@ async function migrateProject(options) {
|
|
|
11740
11819
|
const result = projectResults?.results.get(absoluteFile) ?? migration.apply(before, file);
|
|
11741
11820
|
sourceByPath.set(absoluteFile, result.content);
|
|
11742
11821
|
if (result.changed && result.issues.length === 0) {
|
|
11743
|
-
pendingWrites.
|
|
11822
|
+
pendingWrites.set(absoluteFile, result.content);
|
|
11744
11823
|
}
|
|
11745
11824
|
if (!projectResults)
|
|
11746
11825
|
appendIssues(result.issues);
|
|
@@ -11755,13 +11834,36 @@ async function migrateProject(options) {
|
|
|
11755
11834
|
}
|
|
11756
11835
|
}
|
|
11757
11836
|
if (options.write && issues.length === 0) {
|
|
11758
|
-
|
|
11759
|
-
|
|
11837
|
+
const written = [];
|
|
11838
|
+
try {
|
|
11839
|
+
for (const [path, content] of pendingWrites) {
|
|
11840
|
+
if (await readFile9(path, "utf8") !== originalSources.get(path)) {
|
|
11841
|
+
throw new Error(`Source changed during migration: ${path}`);
|
|
11842
|
+
}
|
|
11843
|
+
await writeAtomically(path, content);
|
|
11844
|
+
written.push(path);
|
|
11845
|
+
writtenFiles.add(path);
|
|
11846
|
+
}
|
|
11847
|
+
} catch (error) {
|
|
11848
|
+
appendIssues([{ code: "migration-write-failed", file: rootDir, message: String(error) }]);
|
|
11849
|
+
for (const path of written.reverse()) {
|
|
11850
|
+
try {
|
|
11851
|
+
const original = originalSources.get(path);
|
|
11852
|
+
if (original === undefined || await readFile9(path, "utf8") !== pendingWrites.get(path)) {
|
|
11853
|
+
throw new Error("File changed after migration; refusing to overwrite concurrent edits");
|
|
11854
|
+
}
|
|
11855
|
+
await writeAtomically(path, original);
|
|
11856
|
+
writtenFiles.delete(path);
|
|
11857
|
+
} catch (rollbackError) {
|
|
11858
|
+
appendIssues([{ code: "migration-rollback-failed", file: path, message: String(rollbackError) }]);
|
|
11859
|
+
}
|
|
11860
|
+
}
|
|
11861
|
+
}
|
|
11760
11862
|
}
|
|
11761
|
-
const changedFiles = options.write && issues.length > 0 ? [] : results.filter((result) => result.changed && result.issues.length === 0).map((result) => result.file);
|
|
11863
|
+
const changedFiles = options.write && issues.length > 0 ? [...writtenFiles].map((file) => relative10(rootDir, file)) : [...new Set(results.filter((result) => result.changed && result.issues.length === 0).map((result) => result.file))];
|
|
11762
11864
|
return {
|
|
11763
11865
|
write: options.write === true,
|
|
11764
|
-
migrations:
|
|
11866
|
+
migrations: migrations.map(({ id, from, to, description }) => ({ id, from, to, description })),
|
|
11765
11867
|
files: results,
|
|
11766
11868
|
changedFiles,
|
|
11767
11869
|
issues
|
|
@@ -12051,8 +12153,8 @@ function exportGraphDot(graph) {
|
|
|
12051
12153
|
init_generate();
|
|
12052
12154
|
|
|
12053
12155
|
// src/openapi-tools.ts
|
|
12054
|
-
import { mkdir as mkdir4, readFile as
|
|
12055
|
-
import { dirname as dirname9, resolve as
|
|
12156
|
+
import { mkdir as mkdir4, readFile as readFile10, rename as rename6, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
|
|
12157
|
+
import { dirname as dirname9, resolve as resolve13 } from "node:path";
|
|
12056
12158
|
import { pathToFileURL } from "node:url";
|
|
12057
12159
|
|
|
12058
12160
|
class OpenApiDocumentError extends Error {
|
|
@@ -12443,7 +12545,7 @@ function serializeOpenApiJson(document, space = 2) {
|
|
|
12443
12545
|
}
|
|
12444
12546
|
async function readOpenApiJson(path) {
|
|
12445
12547
|
try {
|
|
12446
|
-
const value = JSON.parse(await
|
|
12548
|
+
const value = JSON.parse(await readFile10(resolve13(path), "utf8"));
|
|
12447
12549
|
return parseOpenApiDocument(value);
|
|
12448
12550
|
} catch (error) {
|
|
12449
12551
|
if (error instanceof OpenApiDocumentError)
|
|
@@ -12452,11 +12554,11 @@ async function readOpenApiJson(path) {
|
|
|
12452
12554
|
}
|
|
12453
12555
|
}
|
|
12454
12556
|
async function writeOpenApiJson(document, outputPath, space = 2) {
|
|
12455
|
-
const path =
|
|
12557
|
+
const path = resolve13(outputPath);
|
|
12456
12558
|
const content = serializeOpenApiJson(document, space);
|
|
12457
12559
|
await mkdir4(dirname9(path), { recursive: true });
|
|
12458
12560
|
try {
|
|
12459
|
-
if (await
|
|
12561
|
+
if (await readFile10(path, "utf8") === content)
|
|
12460
12562
|
return { path, written: false };
|
|
12461
12563
|
} catch {}
|
|
12462
12564
|
const temporaryPath = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -12473,7 +12575,7 @@ async function writeOpenApiJson(document, outputPath, space = 2) {
|
|
|
12473
12575
|
}
|
|
12474
12576
|
async function loadGeneratedOpenApiDocument(modulePath) {
|
|
12475
12577
|
try {
|
|
12476
|
-
const moduleUrl = pathToFileURL(
|
|
12578
|
+
const moduleUrl = pathToFileURL(resolve13(modulePath));
|
|
12477
12579
|
moduleUrl.searchParams.set("supacloud-openapi-export", "1");
|
|
12478
12580
|
const loaded = await import(moduleUrl.href);
|
|
12479
12581
|
if (!isRecord(loaded))
|
|
@@ -12557,7 +12659,7 @@ function formatOpenApiDiff(result) {
|
|
|
12557
12659
|
// src/config.ts
|
|
12558
12660
|
init_graphql_options();
|
|
12559
12661
|
import { existsSync as existsSync5 } from "node:fs";
|
|
12560
|
-
import { join as join8, resolve as
|
|
12662
|
+
import { join as join8, resolve as resolve14 } from "node:path";
|
|
12561
12663
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
12562
12664
|
var DEFAULT_SUPACLOUD_CONFIG = {
|
|
12563
12665
|
graphql: false,
|
|
@@ -12643,8 +12745,8 @@ function validateGovernanceConfig(config) {
|
|
|
12643
12745
|
function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
12644
12746
|
const resolved = defineSupacloudConfig(config);
|
|
12645
12747
|
return {
|
|
12646
|
-
rootDir:
|
|
12647
|
-
outDir:
|
|
12748
|
+
rootDir: resolve14(cwd, resolved.root ?? DEFAULT_SUPACLOUD_CONFIG.root),
|
|
12749
|
+
outDir: resolve14(cwd, resolved.outDir ?? DEFAULT_SUPACLOUD_CONFIG.outDir),
|
|
12648
12750
|
include: resolved.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include],
|
|
12649
12751
|
strict: resolved.strict ?? DEFAULT_SUPACLOUD_CONFIG.strict,
|
|
12650
12752
|
requireRouteContracts: resolved.requireRouteContracts ?? DEFAULT_SUPACLOUD_CONFIG.requireRouteContracts,
|
|
@@ -12662,7 +12764,7 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
|
12662
12764
|
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders,
|
|
12663
12765
|
graphql: resolved.graphql ? {
|
|
12664
12766
|
...resolved.graphql,
|
|
12665
|
-
schema:
|
|
12767
|
+
schema: resolve14(cwd, resolved.graphql.schema)
|
|
12666
12768
|
} : undefined
|
|
12667
12769
|
};
|
|
12668
12770
|
}
|
package/dist/migrations.d.ts
CHANGED
|
@@ -21,6 +21,9 @@ export interface MigrateProjectOptions {
|
|
|
21
21
|
rootDir: string;
|
|
22
22
|
include?: string[];
|
|
23
23
|
write?: boolean;
|
|
24
|
+
/** Source-format checkpoints, not npm package versions. Both are required together. */
|
|
25
|
+
fromVersion?: string;
|
|
26
|
+
toVersion?: string;
|
|
24
27
|
}
|
|
25
28
|
export interface MigrateFileResult {
|
|
26
29
|
file: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|