@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 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 readFile10 } from "node:fs/promises";
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 resolve14 } from "node:path";
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 = resolve14(options.output);
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 readFile10(path, "utf8");
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 resolve15 } from "node:path";
2648
- import { readFile as readFile11 } from "node:fs/promises";
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 readFile8, writeFile as writeFile4 } from "node:fs/promises";
11861
- import { relative as relative11, resolve as resolve12 } from "node:path";
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(resolve12(fileName)) || fileExists(fileName);
11965
- host.readFile = (fileName) => sourceOverrides.get(resolve12(fileName)) ?? readFile(fileName);
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(resolve12(fileName));
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 = resolve12(sourceFile.fileName);
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 = resolve12(object.getSourceFile().fileName);
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(resolve12(sourceFile.fileName)) ?? [];
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(resolve12(sourceFile.fileName), replacements);
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 = resolve12(fileName);
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(), resolve12("."), new Set([absoluteFile]));
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) => resolve12(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) => resolve12(rootDir, issue.file) === file)
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 = resolve12(rootDir, issue.file);
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
- await writeFile4(temporary, content, "utf8");
12130
- await rename5(temporary, path);
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 = resolve12(options.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(resolve12(filePath), await readFile8(filePath, "utf8"));
12228
+ sourceByPath.set(resolve13(filePath), await readFile9(filePath, "utf8"));
12152
12229
  }
12153
- for (const migration of SUPACLOUD_MIGRATIONS) {
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 = resolve12(filePath);
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.push({ path: filePath, content: result.content });
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
- for (const pending of pendingWrites)
12182
- await writeAtomically(pending.path, pending.content);
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: SUPACLOUD_MIGRATIONS.map(({ id, from, to, description }) => ({ id, from, to, description })),
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 readFile9, rename as rename6, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
12196
- import { dirname as dirname9, resolve as resolve13 } from "node:path";
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 readFile9(resolve13(path), "utf8"));
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 = resolve13(outputPath);
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 readFile9(path, "utf8") === content)
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(resolve13(modulePath));
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(resolve15(process.cwd(), basePath)), await readOpenApiJson(resolve15(process.cwd(), currentPath)));
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: resolve15(process.cwd(), modulePath),
12946
- outputPath: resolve15(process.cwd(), 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 ? resolve15(process.cwd(), rootDir) : process.cwd(),
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 ? resolve15(process.cwd(), rootDir) : defaults.rootDir;
12981
- const resolvedOut = outDir ? resolve15(process.cwd(), outDir) : defaults.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 readFile11(resolve15(process.cwd(), deliveryPath), "utf8"));
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 readFile11(resolve15(process.cwd(), query), "utf8"));
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 readFile10 } from "node:fs/promises";
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 resolve14 } from "node:path";
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 = resolve14(options.output);
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 readFile10(path, "utf8");
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 readFile8, writeFile as writeFile4 } from "node:fs/promises";
11438
- import { relative as relative10, resolve as resolve11 } from "node:path";
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(resolve11(fileName)) || fileExists(fileName);
11542
- host.readFile = (fileName) => sourceOverrides.get(resolve11(fileName)) ?? readFile(fileName);
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(resolve11(fileName));
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 = resolve11(sourceFile.fileName);
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 = resolve11(object.getSourceFile().fileName);
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(resolve11(sourceFile.fileName)) ?? [];
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(resolve11(sourceFile.fileName), replacements);
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 = resolve11(fileName);
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(), resolve11("."), new Set([absoluteFile]));
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) => resolve11(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) => resolve11(rootDir, issue.file) === file)
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 = resolve11(rootDir, issue.file);
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
- await writeFile4(temporary, content, "utf8");
11707
- await rename5(temporary, path);
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 = resolve11(options.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(resolve11(filePath), await readFile8(filePath, "utf8"));
11805
+ sourceByPath.set(resolve12(filePath), await readFile9(filePath, "utf8"));
11729
11806
  }
11730
- for (const migration of SUPACLOUD_MIGRATIONS) {
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 = resolve11(filePath);
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.push({ path: filePath, content: result.content });
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
- for (const pending of pendingWrites)
11759
- await writeAtomically(pending.path, pending.content);
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: SUPACLOUD_MIGRATIONS.map(({ id, from, to, description }) => ({ id, from, to, description })),
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 readFile9, rename as rename6, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
12055
- import { dirname as dirname9, resolve as resolve12 } from "node:path";
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 readFile9(resolve12(path), "utf8"));
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 = resolve12(outputPath);
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 readFile9(path, "utf8") === content)
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(resolve12(modulePath));
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 resolve13 } from "node:path";
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: resolve13(cwd, resolved.root ?? DEFAULT_SUPACLOUD_CONFIG.root),
12647
- outDir: resolve13(cwd, resolved.outDir ?? DEFAULT_SUPACLOUD_CONFIG.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: resolve13(cwd, resolved.graphql.schema)
12767
+ schema: resolve14(cwd, resolved.graphql.schema)
12666
12768
  } : undefined
12667
12769
  };
12668
12770
  }
@@ -0,0 +1,2 @@
1
+ export declare const migrationDependencies: Readonly<Record<string, string>>;
2
+ export declare function checkMigrationDependencies(rootDir: string): Promise<string[]>;
@@ -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.19.1",
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",