@supacloud/compiler 0.19.1 → 0.21.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/dist/cli.js +174 -50
- package/dist/index.js +149 -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,50 @@ 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
|
+
var cachedCompilerVersion;
|
|
11869
|
+
function compilerVersion() {
|
|
11870
|
+
if (cachedCompilerVersion !== undefined)
|
|
11871
|
+
return cachedCompilerVersion;
|
|
11872
|
+
const manifest = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
|
|
11873
|
+
if (!manifest || typeof manifest !== "object" || !("version" in manifest) || typeof manifest.version !== "string") {
|
|
11874
|
+
throw new Error("Cannot determine executing compiler version");
|
|
11875
|
+
}
|
|
11876
|
+
cachedCompilerVersion = manifest.version;
|
|
11877
|
+
return cachedCompilerVersion;
|
|
11878
|
+
}
|
|
11879
|
+
function migrationDependencies() {
|
|
11880
|
+
return {
|
|
11881
|
+
"@supacloud/app": "0.14.0",
|
|
11882
|
+
"@supacloud/compiler": compilerVersion(),
|
|
11883
|
+
"@supacloud/elysia": "0.16.0",
|
|
11884
|
+
elysia: "1.4.30",
|
|
11885
|
+
typescript: "7.0.2"
|
|
11886
|
+
};
|
|
11887
|
+
}
|
|
11888
|
+
async function checkMigrationDependencies(rootDir) {
|
|
11889
|
+
const problems = [];
|
|
11890
|
+
for (const [name, expected] of Object.entries(migrationDependencies())) {
|
|
11891
|
+
try {
|
|
11892
|
+
const manifest = JSON.parse(await readFile8(resolve12(rootDir, "node_modules", name, "package.json"), "utf8"));
|
|
11893
|
+
if (!manifest || typeof manifest !== "object" || !("name" in manifest) || manifest.name !== name || !("version" in manifest) || manifest.version !== expected) {
|
|
11894
|
+
problems.push(`${name}: requires tested installed version ${expected}`);
|
|
11895
|
+
}
|
|
11896
|
+
} catch {
|
|
11897
|
+
problems.push(`${name}: install tested version ${expected} in the project node_modules first`);
|
|
11898
|
+
}
|
|
11899
|
+
}
|
|
11900
|
+
return problems;
|
|
11901
|
+
}
|
|
11902
|
+
|
|
11903
|
+
// src/migrations.ts
|
|
11863
11904
|
var ROUTE_DECORATORS2 = new Set(["Get", "Post", "Put", "Patch", "Delete", "Head", "Options"]);
|
|
11864
11905
|
var MIGRATION_COMPILER_OPTIONS = {
|
|
11865
11906
|
target: ts10.ScriptTarget.ES2022,
|
|
@@ -11961,10 +12002,10 @@ function createMigrationProgram(fileNames, sourceOverrides, rootDir) {
|
|
|
11961
12002
|
const fileExists = host.fileExists.bind(host);
|
|
11962
12003
|
const readFile = host.readFile.bind(host);
|
|
11963
12004
|
const currentDirectory = host.getCurrentDirectory.bind(host);
|
|
11964
|
-
host.fileExists = (fileName) => sourceOverrides.has(
|
|
11965
|
-
host.readFile = (fileName) => sourceOverrides.get(
|
|
12005
|
+
host.fileExists = (fileName) => sourceOverrides.has(resolve13(fileName)) || fileExists(fileName);
|
|
12006
|
+
host.readFile = (fileName) => sourceOverrides.get(resolve13(fileName)) ?? readFile(fileName);
|
|
11966
12007
|
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
11967
|
-
const source = sourceOverrides.get(
|
|
12008
|
+
const source = sourceOverrides.get(resolve13(fileName));
|
|
11968
12009
|
return source === undefined ? getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile) : ts10.createSourceFile(fileName, source, languageVersion, true);
|
|
11969
12010
|
};
|
|
11970
12011
|
host.getCurrentDirectory = () => rootDir ?? currentDirectory();
|
|
@@ -11990,7 +12031,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11990
12031
|
issues.push(issue);
|
|
11991
12032
|
};
|
|
11992
12033
|
for (const sourceFile of sourceFiles) {
|
|
11993
|
-
const sourcePath =
|
|
12034
|
+
const sourcePath = resolve13(sourceFile.fileName);
|
|
11994
12035
|
if (!includedFiles.has(sourcePath))
|
|
11995
12036
|
continue;
|
|
11996
12037
|
const visit = (node) => {
|
|
@@ -11998,7 +12039,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11998
12039
|
const options = node.arguments[1];
|
|
11999
12040
|
const object = options && resolveStaticObjectLiteral2(options, checker);
|
|
12000
12041
|
if (object) {
|
|
12001
|
-
const objectPath =
|
|
12042
|
+
const objectPath = resolve13(object.getSourceFile().fileName);
|
|
12002
12043
|
const properties = routeResponseProperties(object);
|
|
12003
12044
|
if (properties.response) {
|
|
12004
12045
|
if (!includedFiles.has(objectPath)) {
|
|
@@ -12041,13 +12082,13 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
12041
12082
|
line: lineOf2(sourceFile, response)
|
|
12042
12083
|
});
|
|
12043
12084
|
} else {
|
|
12044
|
-
const replacements = replacementsByFile.get(
|
|
12085
|
+
const replacements = replacementsByFile.get(resolve13(sourceFile.fileName)) ?? [];
|
|
12045
12086
|
replacements.push({
|
|
12046
12087
|
start: response.getStart(sourceFile),
|
|
12047
12088
|
end: response.getEnd(),
|
|
12048
12089
|
text: `responses: { 200: ${response.initializer.getText(sourceFile)} }`
|
|
12049
12090
|
});
|
|
12050
|
-
replacementsByFile.set(
|
|
12091
|
+
replacementsByFile.set(resolve13(sourceFile.fileName), replacements);
|
|
12051
12092
|
}
|
|
12052
12093
|
}
|
|
12053
12094
|
return { replacementsByFile, issues };
|
|
@@ -12060,7 +12101,7 @@ function applyReplacements(source, replacements) {
|
|
|
12060
12101
|
return content;
|
|
12061
12102
|
}
|
|
12062
12103
|
function migrateRouteResponse(source, fileName) {
|
|
12063
|
-
const absoluteFile =
|
|
12104
|
+
const absoluteFile = resolve13(fileName);
|
|
12064
12105
|
const sourceOverrides = new Map([[absoluteFile, source]]);
|
|
12065
12106
|
const program = createMigrationProgram([absoluteFile], sourceOverrides);
|
|
12066
12107
|
const sourceFile = program.getSourceFile(absoluteFile);
|
|
@@ -12076,7 +12117,7 @@ function migrateRouteResponse(source, fileName) {
|
|
|
12076
12117
|
}]
|
|
12077
12118
|
};
|
|
12078
12119
|
}
|
|
12079
|
-
const plan = planRouteResponseMigration([sourceFile], program.getTypeChecker(),
|
|
12120
|
+
const plan = planRouteResponseMigration([sourceFile], program.getTypeChecker(), resolve13("."), new Set([absoluteFile]));
|
|
12080
12121
|
const replacements = plan.replacementsByFile.get(absoluteFile) ?? [];
|
|
12081
12122
|
return {
|
|
12082
12123
|
changed: replacements.length > 0,
|
|
@@ -12086,7 +12127,7 @@ function migrateRouteResponse(source, fileName) {
|
|
|
12086
12127
|
};
|
|
12087
12128
|
}
|
|
12088
12129
|
function migrateRouteResponseProject(files, rootDir, sourceByPath) {
|
|
12089
|
-
const absoluteFiles = files.map((file) =>
|
|
12130
|
+
const absoluteFiles = files.map((file) => resolve13(file));
|
|
12090
12131
|
const program = createMigrationProgram(absoluteFiles, sourceByPath, rootDir);
|
|
12091
12132
|
const sourceFiles = absoluteFiles.map((file) => program.getSourceFile(file)).filter((file) => file !== undefined);
|
|
12092
12133
|
const plan = planRouteResponseMigration(sourceFiles, program.getTypeChecker(), rootDir, new Set(absoluteFiles));
|
|
@@ -12099,11 +12140,11 @@ function migrateRouteResponseProject(files, rootDir, sourceByPath) {
|
|
|
12099
12140
|
changed: replacements.length > 0,
|
|
12100
12141
|
content: applyReplacements(source, replacements),
|
|
12101
12142
|
replacements: replacements.length,
|
|
12102
|
-
issues: plan.issues.filter((issue) =>
|
|
12143
|
+
issues: plan.issues.filter((issue) => resolve13(rootDir, issue.file) === file)
|
|
12103
12144
|
});
|
|
12104
12145
|
}
|
|
12105
12146
|
for (const issue of plan.issues) {
|
|
12106
|
-
const path =
|
|
12147
|
+
const path = resolve13(rootDir, issue.file);
|
|
12107
12148
|
if (!results.has(path) && sourceByPath.has(path)) {
|
|
12108
12149
|
results.set(path, {
|
|
12109
12150
|
changed: false,
|
|
@@ -12125,17 +12166,59 @@ var SUPACLOUD_MIGRATIONS = [
|
|
|
12125
12166
|
}
|
|
12126
12167
|
];
|
|
12127
12168
|
async function writeAtomically(path, content) {
|
|
12128
|
-
const temporary = `${path}.supacloud-migrate-${process.pid}`;
|
|
12129
|
-
|
|
12130
|
-
|
|
12169
|
+
const temporary = `${path}.supacloud-migrate-${process.pid}-${crypto.randomUUID()}`;
|
|
12170
|
+
try {
|
|
12171
|
+
await writeFile4(temporary, content, "utf8");
|
|
12172
|
+
await rename5(temporary, path);
|
|
12173
|
+
} finally {
|
|
12174
|
+
await rm3(temporary, { force: true });
|
|
12175
|
+
}
|
|
12131
12176
|
}
|
|
12132
12177
|
async function migrateProject(options) {
|
|
12133
|
-
const rootDir =
|
|
12178
|
+
const rootDir = resolve13(options.rootDir);
|
|
12179
|
+
let migrations = SUPACLOUD_MIGRATIONS;
|
|
12180
|
+
const preflightIssues = [];
|
|
12181
|
+
if (options.fromVersion !== undefined || options.toVersion !== undefined) {
|
|
12182
|
+
migrations = [];
|
|
12183
|
+
const checkpoints = new Set(SUPACLOUD_MIGRATIONS.flatMap(({ from, to }) => [from, to]));
|
|
12184
|
+
let current = options.fromVersion;
|
|
12185
|
+
if (!current || !options.toVersion || !checkpoints.has(current) || !checkpoints.has(options.toVersion)) {
|
|
12186
|
+
preflightIssues.push({
|
|
12187
|
+
code: "migration-version-unsupported",
|
|
12188
|
+
file: "package.json",
|
|
12189
|
+
message: `Supply both supported source-format checkpoints: ${[...checkpoints].join(", ")}`
|
|
12190
|
+
});
|
|
12191
|
+
} else {
|
|
12192
|
+
const visited = new Set;
|
|
12193
|
+
while (current !== options.toVersion) {
|
|
12194
|
+
const next = SUPACLOUD_MIGRATIONS.filter((migration) => migration.from === current);
|
|
12195
|
+
if (visited.has(current) || next.length !== 1 || !next[0]) {
|
|
12196
|
+
preflightIssues.push({
|
|
12197
|
+
code: "migration-path-unavailable",
|
|
12198
|
+
file: "package.json",
|
|
12199
|
+
message: `No unambiguous forward migration from ${current} to ${options.toVersion}`
|
|
12200
|
+
});
|
|
12201
|
+
break;
|
|
12202
|
+
}
|
|
12203
|
+
visited.add(current);
|
|
12204
|
+
migrations.push(next[0]);
|
|
12205
|
+
current = next[0].to;
|
|
12206
|
+
}
|
|
12207
|
+
}
|
|
12208
|
+
if (preflightIssues.length === 0) {
|
|
12209
|
+
for (const message of await checkMigrationDependencies(rootDir)) {
|
|
12210
|
+
preflightIssues.push({ code: "migration-dependency-incompatible", file: "package.json", message });
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12213
|
+
if (preflightIssues.length > 0) {
|
|
12214
|
+
return { write: options.write === true, migrations: [], files: [], changedFiles: [], issues: preflightIssues };
|
|
12215
|
+
}
|
|
12216
|
+
}
|
|
12134
12217
|
const include = options.include ?? ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"];
|
|
12135
12218
|
const files = ts10.sys.readDirectory(rootDir, [".ts", ".tsx", ".mts", ".cts"], ["node_modules", "dist", "generated"], include).sort();
|
|
12136
12219
|
const results = [];
|
|
12137
12220
|
const issues = [];
|
|
12138
|
-
const pendingWrites =
|
|
12221
|
+
const pendingWrites = new Map;
|
|
12139
12222
|
const sourceByPath = new Map;
|
|
12140
12223
|
const issueKeys = new Set;
|
|
12141
12224
|
const appendIssues = (items) => {
|
|
@@ -12148,14 +12231,16 @@ async function migrateProject(options) {
|
|
|
12148
12231
|
}
|
|
12149
12232
|
};
|
|
12150
12233
|
for (const filePath of files) {
|
|
12151
|
-
sourceByPath.set(
|
|
12234
|
+
sourceByPath.set(resolve13(filePath), await readFile9(filePath, "utf8"));
|
|
12152
12235
|
}
|
|
12153
|
-
|
|
12236
|
+
const originalSources = new Map(sourceByPath);
|
|
12237
|
+
const writtenFiles = new Set;
|
|
12238
|
+
for (const migration of migrations) {
|
|
12154
12239
|
const projectResults = migration.id === "route-response-to-responses" ? migrateRouteResponseProject(files, rootDir, sourceByPath) : undefined;
|
|
12155
12240
|
if (projectResults)
|
|
12156
12241
|
appendIssues(projectResults.issues);
|
|
12157
12242
|
for (const filePath of files) {
|
|
12158
|
-
const absoluteFile =
|
|
12243
|
+
const absoluteFile = resolve13(filePath);
|
|
12159
12244
|
const file = relative11(rootDir, absoluteFile) || absoluteFile;
|
|
12160
12245
|
const before = sourceByPath.get(absoluteFile);
|
|
12161
12246
|
if (before === undefined)
|
|
@@ -12163,7 +12248,7 @@ async function migrateProject(options) {
|
|
|
12163
12248
|
const result = projectResults?.results.get(absoluteFile) ?? migration.apply(before, file);
|
|
12164
12249
|
sourceByPath.set(absoluteFile, result.content);
|
|
12165
12250
|
if (result.changed && result.issues.length === 0) {
|
|
12166
|
-
pendingWrites.
|
|
12251
|
+
pendingWrites.set(absoluteFile, result.content);
|
|
12167
12252
|
}
|
|
12168
12253
|
if (!projectResults)
|
|
12169
12254
|
appendIssues(result.issues);
|
|
@@ -12178,13 +12263,36 @@ async function migrateProject(options) {
|
|
|
12178
12263
|
}
|
|
12179
12264
|
}
|
|
12180
12265
|
if (options.write && issues.length === 0) {
|
|
12181
|
-
|
|
12182
|
-
|
|
12266
|
+
const written = [];
|
|
12267
|
+
try {
|
|
12268
|
+
for (const [path, content] of pendingWrites) {
|
|
12269
|
+
if (await readFile9(path, "utf8") !== originalSources.get(path)) {
|
|
12270
|
+
throw new Error(`Source changed during migration: ${path}`);
|
|
12271
|
+
}
|
|
12272
|
+
await writeAtomically(path, content);
|
|
12273
|
+
written.push(path);
|
|
12274
|
+
writtenFiles.add(path);
|
|
12275
|
+
}
|
|
12276
|
+
} catch (error) {
|
|
12277
|
+
appendIssues([{ code: "migration-write-failed", file: rootDir, message: String(error) }]);
|
|
12278
|
+
for (const path of written.reverse()) {
|
|
12279
|
+
try {
|
|
12280
|
+
const original = originalSources.get(path);
|
|
12281
|
+
if (original === undefined || await readFile9(path, "utf8") !== pendingWrites.get(path)) {
|
|
12282
|
+
throw new Error("File changed after migration; refusing to overwrite concurrent edits");
|
|
12283
|
+
}
|
|
12284
|
+
await writeAtomically(path, original);
|
|
12285
|
+
writtenFiles.delete(path);
|
|
12286
|
+
} catch (rollbackError) {
|
|
12287
|
+
appendIssues([{ code: "migration-rollback-failed", file: path, message: String(rollbackError) }]);
|
|
12288
|
+
}
|
|
12289
|
+
}
|
|
12290
|
+
}
|
|
12183
12291
|
}
|
|
12184
|
-
const changedFiles = options.write && issues.length > 0 ? [] : results.filter((result) => result.changed && result.issues.length === 0).map((result) => result.file);
|
|
12292
|
+
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
12293
|
return {
|
|
12186
12294
|
write: options.write === true,
|
|
12187
|
-
migrations:
|
|
12295
|
+
migrations: migrations.map(({ id, from, to, description }) => ({ id, from, to, description })),
|
|
12188
12296
|
files: results,
|
|
12189
12297
|
changedFiles,
|
|
12190
12298
|
issues
|
|
@@ -12192,8 +12300,8 @@ async function migrateProject(options) {
|
|
|
12192
12300
|
}
|
|
12193
12301
|
|
|
12194
12302
|
// src/openapi-tools.ts
|
|
12195
|
-
import { mkdir as mkdir4, readFile as
|
|
12196
|
-
import { dirname as dirname9, resolve as
|
|
12303
|
+
import { mkdir as mkdir4, readFile as readFile10, rename as rename6, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
|
|
12304
|
+
import { dirname as dirname9, resolve as resolve14 } from "node:path";
|
|
12197
12305
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
12198
12306
|
|
|
12199
12307
|
class OpenApiDocumentError extends Error {
|
|
@@ -12584,7 +12692,7 @@ function serializeOpenApiJson(document, space = 2) {
|
|
|
12584
12692
|
}
|
|
12585
12693
|
async function readOpenApiJson(path) {
|
|
12586
12694
|
try {
|
|
12587
|
-
const value = JSON.parse(await
|
|
12695
|
+
const value = JSON.parse(await readFile10(resolve14(path), "utf8"));
|
|
12588
12696
|
return parseOpenApiDocument(value);
|
|
12589
12697
|
} catch (error) {
|
|
12590
12698
|
if (error instanceof OpenApiDocumentError)
|
|
@@ -12593,11 +12701,11 @@ async function readOpenApiJson(path) {
|
|
|
12593
12701
|
}
|
|
12594
12702
|
}
|
|
12595
12703
|
async function writeOpenApiJson(document, outputPath, space = 2) {
|
|
12596
|
-
const path =
|
|
12704
|
+
const path = resolve14(outputPath);
|
|
12597
12705
|
const content = serializeOpenApiJson(document, space);
|
|
12598
12706
|
await mkdir4(dirname9(path), { recursive: true });
|
|
12599
12707
|
try {
|
|
12600
|
-
if (await
|
|
12708
|
+
if (await readFile10(path, "utf8") === content)
|
|
12601
12709
|
return { path, written: false };
|
|
12602
12710
|
} catch {}
|
|
12603
12711
|
const temporaryPath = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -12614,7 +12722,7 @@ async function writeOpenApiJson(document, outputPath, space = 2) {
|
|
|
12614
12722
|
}
|
|
12615
12723
|
async function loadGeneratedOpenApiDocument(modulePath) {
|
|
12616
12724
|
try {
|
|
12617
|
-
const moduleUrl = pathToFileURL2(
|
|
12725
|
+
const moduleUrl = pathToFileURL2(resolve14(modulePath));
|
|
12618
12726
|
moduleUrl.searchParams.set("supacloud-openapi-export", "1");
|
|
12619
12727
|
const loaded = await import(moduleUrl.href);
|
|
12620
12728
|
if (!isRecord(loaded))
|
|
@@ -12757,6 +12865,8 @@ Options:
|
|
|
12757
12865
|
--delivery <file> plan/build-delivery: validated JSON configuration (overrides config.delivery)
|
|
12758
12866
|
--dry-run Preview a fix without writing the target file
|
|
12759
12867
|
--write Apply a fix or migration to disk (preview-only by default)
|
|
12868
|
+
--from-version Migration source-format checkpoint (requires --to-version)
|
|
12869
|
+
--to-version Migration target checkpoint; verifies installed dependencies
|
|
12760
12870
|
--preset, -p <name> Architecture preset ('modular-monolith' | 'angular-enterprise' | 'clean-architecture')
|
|
12761
12871
|
--help, -h Show this help
|
|
12762
12872
|
`);
|
|
@@ -12784,6 +12894,8 @@ async function run() {
|
|
|
12784
12894
|
let query;
|
|
12785
12895
|
let json = false;
|
|
12786
12896
|
let dryRun = true;
|
|
12897
|
+
let fromVersion;
|
|
12898
|
+
let toVersion;
|
|
12787
12899
|
let noGraphql = false;
|
|
12788
12900
|
let projectUrl;
|
|
12789
12901
|
let keyEnv;
|
|
@@ -12897,6 +13009,16 @@ async function run() {
|
|
|
12897
13009
|
if (command === "plan")
|
|
12898
13010
|
throw new Error("plan is read-only; --write is not supported");
|
|
12899
13011
|
dryRun = false;
|
|
13012
|
+
} else if (arg === "--from-version" || arg === "--to-version") {
|
|
13013
|
+
if (command !== "migrate")
|
|
13014
|
+
throw new Error(`${arg} is only supported by migrate`);
|
|
13015
|
+
const value = args[++i];
|
|
13016
|
+
if (!value || value.startsWith("-"))
|
|
13017
|
+
throw new Error(`${arg} requires a version`);
|
|
13018
|
+
if (arg === "--from-version")
|
|
13019
|
+
fromVersion = value;
|
|
13020
|
+
else
|
|
13021
|
+
toVersion = value;
|
|
12900
13022
|
} else if (arg === "--preset" || arg === "-p") {
|
|
12901
13023
|
const presetArg = args[++i];
|
|
12902
13024
|
if (!isModuleBoundaryPresetName(presetArg)) {
|
|
@@ -12927,7 +13049,7 @@ async function run() {
|
|
|
12927
13049
|
const currentPath = openApiDiffPaths[1];
|
|
12928
13050
|
if (!basePath || !currentPath)
|
|
12929
13051
|
throw new Error("openapi-diff requires two JSON file paths");
|
|
12930
|
-
const result = diffOpenApiDocuments(await readOpenApiJson(
|
|
13052
|
+
const result = diffOpenApiDocuments(await readOpenApiJson(resolve16(process.cwd(), basePath)), await readOpenApiJson(resolve16(process.cwd(), currentPath)));
|
|
12931
13053
|
console.log(json ? JSON.stringify(result, null, 2) : formatOpenApiDiff(result));
|
|
12932
13054
|
if (!result.ok)
|
|
12933
13055
|
process.exitCode = 1;
|
|
@@ -12942,8 +13064,8 @@ async function run() {
|
|
|
12942
13064
|
if (!modulePath || !outputPath)
|
|
12943
13065
|
throw new Error("openapi-export requires an OpenAPI module and output path");
|
|
12944
13066
|
const result = await exportGeneratedOpenApiJson({
|
|
12945
|
-
modulePath:
|
|
12946
|
-
outputPath:
|
|
13067
|
+
modulePath: resolve16(process.cwd(), modulePath),
|
|
13068
|
+
outputPath: resolve16(process.cwd(), outputPath),
|
|
12947
13069
|
...openApiExportSpace === undefined ? {} : { space: openApiExportSpace }
|
|
12948
13070
|
});
|
|
12949
13071
|
console.log(json ? JSON.stringify({ ok: true, ...result }, null, 2) : result.written ? `OpenAPI JSON written: ${result.path}` : `OpenAPI JSON matches: ${result.path}`);
|
|
@@ -12951,8 +13073,10 @@ async function run() {
|
|
|
12951
13073
|
}
|
|
12952
13074
|
if (command === "migrate") {
|
|
12953
13075
|
const result = await migrateProject({
|
|
12954
|
-
rootDir: rootDir ?
|
|
12955
|
-
write: !dryRun
|
|
13076
|
+
rootDir: rootDir ? resolve16(process.cwd(), rootDir) : process.cwd(),
|
|
13077
|
+
write: !dryRun,
|
|
13078
|
+
...fromVersion === undefined ? {} : { fromVersion },
|
|
13079
|
+
...toVersion === undefined ? {} : { toVersion }
|
|
12956
13080
|
});
|
|
12957
13081
|
if (json) {
|
|
12958
13082
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -12961,9 +13085,9 @@ async function run() {
|
|
|
12961
13085
|
const lines = [`${action} ${result.changedFiles.length} file(s)`];
|
|
12962
13086
|
for (const file of result.files) {
|
|
12963
13087
|
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
13088
|
}
|
|
13089
|
+
for (const issue of result.issues)
|
|
13090
|
+
lines.push(` ${issue.file}:${issue.line ?? 0} ${issue.code}: ${issue.message}`);
|
|
12967
13091
|
if (result.changedFiles.length === 0 && result.issues.length === 0)
|
|
12968
13092
|
lines.push(" no migrations required");
|
|
12969
13093
|
console.log(lines.join(`
|
|
@@ -12977,8 +13101,8 @@ async function run() {
|
|
|
12977
13101
|
if (checkSchema && command !== "graphql-schema")
|
|
12978
13102
|
throw new Error("--check is only supported by graphql-schema");
|
|
12979
13103
|
const defaults = resolveSupacloudConfig(loadedConfig, process.cwd());
|
|
12980
|
-
const resolvedRoot = rootDir ?
|
|
12981
|
-
const resolvedOut = outDir ?
|
|
13104
|
+
const resolvedRoot = rootDir ? resolve16(process.cwd(), rootDir) : defaults.rootDir;
|
|
13105
|
+
const resolvedOut = outDir ? resolve16(process.cwd(), outDir) : defaults.outDir;
|
|
12982
13106
|
const configured = compileOptionsFromConfig({
|
|
12983
13107
|
...loadedConfig,
|
|
12984
13108
|
root: resolvedRoot,
|
|
@@ -12997,7 +13121,7 @@ async function run() {
|
|
|
12997
13121
|
let delivery = loadedConfig.delivery;
|
|
12998
13122
|
if (deliveryPath !== undefined) {
|
|
12999
13123
|
try {
|
|
13000
|
-
delivery = JSON.parse(await
|
|
13124
|
+
delivery = JSON.parse(await readFile12(resolve16(process.cwd(), deliveryPath), "utf8"));
|
|
13001
13125
|
} catch {
|
|
13002
13126
|
throw new DeliveryConfigurationError;
|
|
13003
13127
|
}
|
|
@@ -13044,7 +13168,7 @@ ${item.suggestion ?? ""}`).join(`
|
|
|
13044
13168
|
} else if (command === "fix") {
|
|
13045
13169
|
if (!query)
|
|
13046
13170
|
throw new Error("fix requires a JSON file containing one DiagnosticFix");
|
|
13047
|
-
const fix = JSON.parse(await
|
|
13171
|
+
const fix = JSON.parse(await readFile12(resolve16(process.cwd(), query), "utf8"));
|
|
13048
13172
|
const result = await applyDiagnosticFix(fix, { rootDir: resolvedRoot, dryRun });
|
|
13049
13173
|
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
13050
13174
|
} 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,50 @@ 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
|
+
var cachedCompilerVersion;
|
|
11446
|
+
function compilerVersion() {
|
|
11447
|
+
if (cachedCompilerVersion !== undefined)
|
|
11448
|
+
return cachedCompilerVersion;
|
|
11449
|
+
const manifest = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
|
|
11450
|
+
if (!manifest || typeof manifest !== "object" || !("version" in manifest) || typeof manifest.version !== "string") {
|
|
11451
|
+
throw new Error("Cannot determine executing compiler version");
|
|
11452
|
+
}
|
|
11453
|
+
cachedCompilerVersion = manifest.version;
|
|
11454
|
+
return cachedCompilerVersion;
|
|
11455
|
+
}
|
|
11456
|
+
function migrationDependencies() {
|
|
11457
|
+
return {
|
|
11458
|
+
"@supacloud/app": "0.14.0",
|
|
11459
|
+
"@supacloud/compiler": compilerVersion(),
|
|
11460
|
+
"@supacloud/elysia": "0.16.0",
|
|
11461
|
+
elysia: "1.4.30",
|
|
11462
|
+
typescript: "7.0.2"
|
|
11463
|
+
};
|
|
11464
|
+
}
|
|
11465
|
+
async function checkMigrationDependencies(rootDir) {
|
|
11466
|
+
const problems = [];
|
|
11467
|
+
for (const [name, expected] of Object.entries(migrationDependencies())) {
|
|
11468
|
+
try {
|
|
11469
|
+
const manifest = JSON.parse(await readFile8(resolve11(rootDir, "node_modules", name, "package.json"), "utf8"));
|
|
11470
|
+
if (!manifest || typeof manifest !== "object" || !("name" in manifest) || manifest.name !== name || !("version" in manifest) || manifest.version !== expected) {
|
|
11471
|
+
problems.push(`${name}: requires tested installed version ${expected}`);
|
|
11472
|
+
}
|
|
11473
|
+
} catch {
|
|
11474
|
+
problems.push(`${name}: install tested version ${expected} in the project node_modules first`);
|
|
11475
|
+
}
|
|
11476
|
+
}
|
|
11477
|
+
return problems;
|
|
11478
|
+
}
|
|
11479
|
+
|
|
11480
|
+
// src/migrations.ts
|
|
11440
11481
|
var ROUTE_DECORATORS2 = new Set(["Get", "Post", "Put", "Patch", "Delete", "Head", "Options"]);
|
|
11441
11482
|
var MIGRATION_COMPILER_OPTIONS = {
|
|
11442
11483
|
target: ts10.ScriptTarget.ES2022,
|
|
@@ -11538,10 +11579,10 @@ function createMigrationProgram(fileNames, sourceOverrides, rootDir) {
|
|
|
11538
11579
|
const fileExists = host.fileExists.bind(host);
|
|
11539
11580
|
const readFile = host.readFile.bind(host);
|
|
11540
11581
|
const currentDirectory = host.getCurrentDirectory.bind(host);
|
|
11541
|
-
host.fileExists = (fileName) => sourceOverrides.has(
|
|
11542
|
-
host.readFile = (fileName) => sourceOverrides.get(
|
|
11582
|
+
host.fileExists = (fileName) => sourceOverrides.has(resolve12(fileName)) || fileExists(fileName);
|
|
11583
|
+
host.readFile = (fileName) => sourceOverrides.get(resolve12(fileName)) ?? readFile(fileName);
|
|
11543
11584
|
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
11544
|
-
const source = sourceOverrides.get(
|
|
11585
|
+
const source = sourceOverrides.get(resolve12(fileName));
|
|
11545
11586
|
return source === undefined ? getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile) : ts10.createSourceFile(fileName, source, languageVersion, true);
|
|
11546
11587
|
};
|
|
11547
11588
|
host.getCurrentDirectory = () => rootDir ?? currentDirectory();
|
|
@@ -11567,7 +11608,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11567
11608
|
issues.push(issue);
|
|
11568
11609
|
};
|
|
11569
11610
|
for (const sourceFile of sourceFiles) {
|
|
11570
|
-
const sourcePath =
|
|
11611
|
+
const sourcePath = resolve12(sourceFile.fileName);
|
|
11571
11612
|
if (!includedFiles.has(sourcePath))
|
|
11572
11613
|
continue;
|
|
11573
11614
|
const visit = (node) => {
|
|
@@ -11575,7 +11616,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11575
11616
|
const options = node.arguments[1];
|
|
11576
11617
|
const object = options && resolveStaticObjectLiteral2(options, checker);
|
|
11577
11618
|
if (object) {
|
|
11578
|
-
const objectPath =
|
|
11619
|
+
const objectPath = resolve12(object.getSourceFile().fileName);
|
|
11579
11620
|
const properties = routeResponseProperties(object);
|
|
11580
11621
|
if (properties.response) {
|
|
11581
11622
|
if (!includedFiles.has(objectPath)) {
|
|
@@ -11618,13 +11659,13 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
11618
11659
|
line: lineOf2(sourceFile, response)
|
|
11619
11660
|
});
|
|
11620
11661
|
} else {
|
|
11621
|
-
const replacements = replacementsByFile.get(
|
|
11662
|
+
const replacements = replacementsByFile.get(resolve12(sourceFile.fileName)) ?? [];
|
|
11622
11663
|
replacements.push({
|
|
11623
11664
|
start: response.getStart(sourceFile),
|
|
11624
11665
|
end: response.getEnd(),
|
|
11625
11666
|
text: `responses: { 200: ${response.initializer.getText(sourceFile)} }`
|
|
11626
11667
|
});
|
|
11627
|
-
replacementsByFile.set(
|
|
11668
|
+
replacementsByFile.set(resolve12(sourceFile.fileName), replacements);
|
|
11628
11669
|
}
|
|
11629
11670
|
}
|
|
11630
11671
|
return { replacementsByFile, issues };
|
|
@@ -11637,7 +11678,7 @@ function applyReplacements(source, replacements) {
|
|
|
11637
11678
|
return content;
|
|
11638
11679
|
}
|
|
11639
11680
|
function migrateRouteResponse(source, fileName) {
|
|
11640
|
-
const absoluteFile =
|
|
11681
|
+
const absoluteFile = resolve12(fileName);
|
|
11641
11682
|
const sourceOverrides = new Map([[absoluteFile, source]]);
|
|
11642
11683
|
const program = createMigrationProgram([absoluteFile], sourceOverrides);
|
|
11643
11684
|
const sourceFile = program.getSourceFile(absoluteFile);
|
|
@@ -11653,7 +11694,7 @@ function migrateRouteResponse(source, fileName) {
|
|
|
11653
11694
|
}]
|
|
11654
11695
|
};
|
|
11655
11696
|
}
|
|
11656
|
-
const plan = planRouteResponseMigration([sourceFile], program.getTypeChecker(),
|
|
11697
|
+
const plan = planRouteResponseMigration([sourceFile], program.getTypeChecker(), resolve12("."), new Set([absoluteFile]));
|
|
11657
11698
|
const replacements = plan.replacementsByFile.get(absoluteFile) ?? [];
|
|
11658
11699
|
return {
|
|
11659
11700
|
changed: replacements.length > 0,
|
|
@@ -11663,7 +11704,7 @@ function migrateRouteResponse(source, fileName) {
|
|
|
11663
11704
|
};
|
|
11664
11705
|
}
|
|
11665
11706
|
function migrateRouteResponseProject(files, rootDir, sourceByPath) {
|
|
11666
|
-
const absoluteFiles = files.map((file) =>
|
|
11707
|
+
const absoluteFiles = files.map((file) => resolve12(file));
|
|
11667
11708
|
const program = createMigrationProgram(absoluteFiles, sourceByPath, rootDir);
|
|
11668
11709
|
const sourceFiles = absoluteFiles.map((file) => program.getSourceFile(file)).filter((file) => file !== undefined);
|
|
11669
11710
|
const plan = planRouteResponseMigration(sourceFiles, program.getTypeChecker(), rootDir, new Set(absoluteFiles));
|
|
@@ -11676,11 +11717,11 @@ function migrateRouteResponseProject(files, rootDir, sourceByPath) {
|
|
|
11676
11717
|
changed: replacements.length > 0,
|
|
11677
11718
|
content: applyReplacements(source, replacements),
|
|
11678
11719
|
replacements: replacements.length,
|
|
11679
|
-
issues: plan.issues.filter((issue) =>
|
|
11720
|
+
issues: plan.issues.filter((issue) => resolve12(rootDir, issue.file) === file)
|
|
11680
11721
|
});
|
|
11681
11722
|
}
|
|
11682
11723
|
for (const issue of plan.issues) {
|
|
11683
|
-
const path =
|
|
11724
|
+
const path = resolve12(rootDir, issue.file);
|
|
11684
11725
|
if (!results.has(path) && sourceByPath.has(path)) {
|
|
11685
11726
|
results.set(path, {
|
|
11686
11727
|
changed: false,
|
|
@@ -11702,17 +11743,59 @@ var SUPACLOUD_MIGRATIONS = [
|
|
|
11702
11743
|
}
|
|
11703
11744
|
];
|
|
11704
11745
|
async function writeAtomically(path, content) {
|
|
11705
|
-
const temporary = `${path}.supacloud-migrate-${process.pid}`;
|
|
11706
|
-
|
|
11707
|
-
|
|
11746
|
+
const temporary = `${path}.supacloud-migrate-${process.pid}-${crypto.randomUUID()}`;
|
|
11747
|
+
try {
|
|
11748
|
+
await writeFile4(temporary, content, "utf8");
|
|
11749
|
+
await rename5(temporary, path);
|
|
11750
|
+
} finally {
|
|
11751
|
+
await rm3(temporary, { force: true });
|
|
11752
|
+
}
|
|
11708
11753
|
}
|
|
11709
11754
|
async function migrateProject(options) {
|
|
11710
|
-
const rootDir =
|
|
11755
|
+
const rootDir = resolve12(options.rootDir);
|
|
11756
|
+
let migrations = SUPACLOUD_MIGRATIONS;
|
|
11757
|
+
const preflightIssues = [];
|
|
11758
|
+
if (options.fromVersion !== undefined || options.toVersion !== undefined) {
|
|
11759
|
+
migrations = [];
|
|
11760
|
+
const checkpoints = new Set(SUPACLOUD_MIGRATIONS.flatMap(({ from, to }) => [from, to]));
|
|
11761
|
+
let current = options.fromVersion;
|
|
11762
|
+
if (!current || !options.toVersion || !checkpoints.has(current) || !checkpoints.has(options.toVersion)) {
|
|
11763
|
+
preflightIssues.push({
|
|
11764
|
+
code: "migration-version-unsupported",
|
|
11765
|
+
file: "package.json",
|
|
11766
|
+
message: `Supply both supported source-format checkpoints: ${[...checkpoints].join(", ")}`
|
|
11767
|
+
});
|
|
11768
|
+
} else {
|
|
11769
|
+
const visited = new Set;
|
|
11770
|
+
while (current !== options.toVersion) {
|
|
11771
|
+
const next = SUPACLOUD_MIGRATIONS.filter((migration) => migration.from === current);
|
|
11772
|
+
if (visited.has(current) || next.length !== 1 || !next[0]) {
|
|
11773
|
+
preflightIssues.push({
|
|
11774
|
+
code: "migration-path-unavailable",
|
|
11775
|
+
file: "package.json",
|
|
11776
|
+
message: `No unambiguous forward migration from ${current} to ${options.toVersion}`
|
|
11777
|
+
});
|
|
11778
|
+
break;
|
|
11779
|
+
}
|
|
11780
|
+
visited.add(current);
|
|
11781
|
+
migrations.push(next[0]);
|
|
11782
|
+
current = next[0].to;
|
|
11783
|
+
}
|
|
11784
|
+
}
|
|
11785
|
+
if (preflightIssues.length === 0) {
|
|
11786
|
+
for (const message of await checkMigrationDependencies(rootDir)) {
|
|
11787
|
+
preflightIssues.push({ code: "migration-dependency-incompatible", file: "package.json", message });
|
|
11788
|
+
}
|
|
11789
|
+
}
|
|
11790
|
+
if (preflightIssues.length > 0) {
|
|
11791
|
+
return { write: options.write === true, migrations: [], files: [], changedFiles: [], issues: preflightIssues };
|
|
11792
|
+
}
|
|
11793
|
+
}
|
|
11711
11794
|
const include = options.include ?? ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"];
|
|
11712
11795
|
const files = ts10.sys.readDirectory(rootDir, [".ts", ".tsx", ".mts", ".cts"], ["node_modules", "dist", "generated"], include).sort();
|
|
11713
11796
|
const results = [];
|
|
11714
11797
|
const issues = [];
|
|
11715
|
-
const pendingWrites =
|
|
11798
|
+
const pendingWrites = new Map;
|
|
11716
11799
|
const sourceByPath = new Map;
|
|
11717
11800
|
const issueKeys = new Set;
|
|
11718
11801
|
const appendIssues = (items) => {
|
|
@@ -11725,14 +11808,16 @@ async function migrateProject(options) {
|
|
|
11725
11808
|
}
|
|
11726
11809
|
};
|
|
11727
11810
|
for (const filePath of files) {
|
|
11728
|
-
sourceByPath.set(
|
|
11811
|
+
sourceByPath.set(resolve12(filePath), await readFile9(filePath, "utf8"));
|
|
11729
11812
|
}
|
|
11730
|
-
|
|
11813
|
+
const originalSources = new Map(sourceByPath);
|
|
11814
|
+
const writtenFiles = new Set;
|
|
11815
|
+
for (const migration of migrations) {
|
|
11731
11816
|
const projectResults = migration.id === "route-response-to-responses" ? migrateRouteResponseProject(files, rootDir, sourceByPath) : undefined;
|
|
11732
11817
|
if (projectResults)
|
|
11733
11818
|
appendIssues(projectResults.issues);
|
|
11734
11819
|
for (const filePath of files) {
|
|
11735
|
-
const absoluteFile =
|
|
11820
|
+
const absoluteFile = resolve12(filePath);
|
|
11736
11821
|
const file = relative10(rootDir, absoluteFile) || absoluteFile;
|
|
11737
11822
|
const before = sourceByPath.get(absoluteFile);
|
|
11738
11823
|
if (before === undefined)
|
|
@@ -11740,7 +11825,7 @@ async function migrateProject(options) {
|
|
|
11740
11825
|
const result = projectResults?.results.get(absoluteFile) ?? migration.apply(before, file);
|
|
11741
11826
|
sourceByPath.set(absoluteFile, result.content);
|
|
11742
11827
|
if (result.changed && result.issues.length === 0) {
|
|
11743
|
-
pendingWrites.
|
|
11828
|
+
pendingWrites.set(absoluteFile, result.content);
|
|
11744
11829
|
}
|
|
11745
11830
|
if (!projectResults)
|
|
11746
11831
|
appendIssues(result.issues);
|
|
@@ -11755,13 +11840,36 @@ async function migrateProject(options) {
|
|
|
11755
11840
|
}
|
|
11756
11841
|
}
|
|
11757
11842
|
if (options.write && issues.length === 0) {
|
|
11758
|
-
|
|
11759
|
-
|
|
11843
|
+
const written = [];
|
|
11844
|
+
try {
|
|
11845
|
+
for (const [path, content] of pendingWrites) {
|
|
11846
|
+
if (await readFile9(path, "utf8") !== originalSources.get(path)) {
|
|
11847
|
+
throw new Error(`Source changed during migration: ${path}`);
|
|
11848
|
+
}
|
|
11849
|
+
await writeAtomically(path, content);
|
|
11850
|
+
written.push(path);
|
|
11851
|
+
writtenFiles.add(path);
|
|
11852
|
+
}
|
|
11853
|
+
} catch (error) {
|
|
11854
|
+
appendIssues([{ code: "migration-write-failed", file: rootDir, message: String(error) }]);
|
|
11855
|
+
for (const path of written.reverse()) {
|
|
11856
|
+
try {
|
|
11857
|
+
const original = originalSources.get(path);
|
|
11858
|
+
if (original === undefined || await readFile9(path, "utf8") !== pendingWrites.get(path)) {
|
|
11859
|
+
throw new Error("File changed after migration; refusing to overwrite concurrent edits");
|
|
11860
|
+
}
|
|
11861
|
+
await writeAtomically(path, original);
|
|
11862
|
+
writtenFiles.delete(path);
|
|
11863
|
+
} catch (rollbackError) {
|
|
11864
|
+
appendIssues([{ code: "migration-rollback-failed", file: path, message: String(rollbackError) }]);
|
|
11865
|
+
}
|
|
11866
|
+
}
|
|
11867
|
+
}
|
|
11760
11868
|
}
|
|
11761
|
-
const changedFiles = options.write && issues.length > 0 ? [] : results.filter((result) => result.changed && result.issues.length === 0).map((result) => result.file);
|
|
11869
|
+
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
11870
|
return {
|
|
11763
11871
|
write: options.write === true,
|
|
11764
|
-
migrations:
|
|
11872
|
+
migrations: migrations.map(({ id, from, to, description }) => ({ id, from, to, description })),
|
|
11765
11873
|
files: results,
|
|
11766
11874
|
changedFiles,
|
|
11767
11875
|
issues
|
|
@@ -12051,8 +12159,8 @@ function exportGraphDot(graph) {
|
|
|
12051
12159
|
init_generate();
|
|
12052
12160
|
|
|
12053
12161
|
// src/openapi-tools.ts
|
|
12054
|
-
import { mkdir as mkdir4, readFile as
|
|
12055
|
-
import { dirname as dirname9, resolve as
|
|
12162
|
+
import { mkdir as mkdir4, readFile as readFile10, rename as rename6, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
|
|
12163
|
+
import { dirname as dirname9, resolve as resolve13 } from "node:path";
|
|
12056
12164
|
import { pathToFileURL } from "node:url";
|
|
12057
12165
|
|
|
12058
12166
|
class OpenApiDocumentError extends Error {
|
|
@@ -12443,7 +12551,7 @@ function serializeOpenApiJson(document, space = 2) {
|
|
|
12443
12551
|
}
|
|
12444
12552
|
async function readOpenApiJson(path) {
|
|
12445
12553
|
try {
|
|
12446
|
-
const value = JSON.parse(await
|
|
12554
|
+
const value = JSON.parse(await readFile10(resolve13(path), "utf8"));
|
|
12447
12555
|
return parseOpenApiDocument(value);
|
|
12448
12556
|
} catch (error) {
|
|
12449
12557
|
if (error instanceof OpenApiDocumentError)
|
|
@@ -12452,11 +12560,11 @@ async function readOpenApiJson(path) {
|
|
|
12452
12560
|
}
|
|
12453
12561
|
}
|
|
12454
12562
|
async function writeOpenApiJson(document, outputPath, space = 2) {
|
|
12455
|
-
const path =
|
|
12563
|
+
const path = resolve13(outputPath);
|
|
12456
12564
|
const content = serializeOpenApiJson(document, space);
|
|
12457
12565
|
await mkdir4(dirname9(path), { recursive: true });
|
|
12458
12566
|
try {
|
|
12459
|
-
if (await
|
|
12567
|
+
if (await readFile10(path, "utf8") === content)
|
|
12460
12568
|
return { path, written: false };
|
|
12461
12569
|
} catch {}
|
|
12462
12570
|
const temporaryPath = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -12473,7 +12581,7 @@ async function writeOpenApiJson(document, outputPath, space = 2) {
|
|
|
12473
12581
|
}
|
|
12474
12582
|
async function loadGeneratedOpenApiDocument(modulePath) {
|
|
12475
12583
|
try {
|
|
12476
|
-
const moduleUrl = pathToFileURL(
|
|
12584
|
+
const moduleUrl = pathToFileURL(resolve13(modulePath));
|
|
12477
12585
|
moduleUrl.searchParams.set("supacloud-openapi-export", "1");
|
|
12478
12586
|
const loaded = await import(moduleUrl.href);
|
|
12479
12587
|
if (!isRecord(loaded))
|
|
@@ -12557,7 +12665,7 @@ function formatOpenApiDiff(result) {
|
|
|
12557
12665
|
// src/config.ts
|
|
12558
12666
|
init_graphql_options();
|
|
12559
12667
|
import { existsSync as existsSync5 } from "node:fs";
|
|
12560
|
-
import { join as join8, resolve as
|
|
12668
|
+
import { join as join8, resolve as resolve14 } from "node:path";
|
|
12561
12669
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
12562
12670
|
var DEFAULT_SUPACLOUD_CONFIG = {
|
|
12563
12671
|
graphql: false,
|
|
@@ -12643,8 +12751,8 @@ function validateGovernanceConfig(config) {
|
|
|
12643
12751
|
function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
12644
12752
|
const resolved = defineSupacloudConfig(config);
|
|
12645
12753
|
return {
|
|
12646
|
-
rootDir:
|
|
12647
|
-
outDir:
|
|
12754
|
+
rootDir: resolve14(cwd, resolved.root ?? DEFAULT_SUPACLOUD_CONFIG.root),
|
|
12755
|
+
outDir: resolve14(cwd, resolved.outDir ?? DEFAULT_SUPACLOUD_CONFIG.outDir),
|
|
12648
12756
|
include: resolved.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include],
|
|
12649
12757
|
strict: resolved.strict ?? DEFAULT_SUPACLOUD_CONFIG.strict,
|
|
12650
12758
|
requireRouteContracts: resolved.requireRouteContracts ?? DEFAULT_SUPACLOUD_CONFIG.requireRouteContracts,
|
|
@@ -12662,7 +12770,7 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
|
12662
12770
|
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders,
|
|
12663
12771
|
graphql: resolved.graphql ? {
|
|
12664
12772
|
...resolved.graphql,
|
|
12665
|
-
schema:
|
|
12773
|
+
schema: resolve14(cwd, resolved.graphql.schema)
|
|
12666
12774
|
} : undefined
|
|
12667
12775
|
};
|
|
12668
12776
|
}
|
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.1",
|
|
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",
|