@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 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,50 @@ 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
+ 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(resolve12(fileName)) || fileExists(fileName);
11965
- host.readFile = (fileName) => sourceOverrides.get(resolve12(fileName)) ?? readFile(fileName);
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(resolve12(fileName));
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 = resolve12(sourceFile.fileName);
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 = resolve12(object.getSourceFile().fileName);
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(resolve12(sourceFile.fileName)) ?? [];
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(resolve12(sourceFile.fileName), replacements);
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 = resolve12(fileName);
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(), resolve12("."), new Set([absoluteFile]));
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) => resolve12(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) => resolve12(rootDir, issue.file) === file)
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 = resolve12(rootDir, issue.file);
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
- await writeFile4(temporary, content, "utf8");
12130
- await rename5(temporary, path);
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 = resolve12(options.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(resolve12(filePath), await readFile8(filePath, "utf8"));
12234
+ sourceByPath.set(resolve13(filePath), await readFile9(filePath, "utf8"));
12152
12235
  }
12153
- for (const migration of SUPACLOUD_MIGRATIONS) {
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 = resolve12(filePath);
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.push({ path: filePath, content: result.content });
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
- for (const pending of pendingWrites)
12182
- await writeAtomically(pending.path, pending.content);
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: SUPACLOUD_MIGRATIONS.map(({ id, from, to, description }) => ({ id, from, to, description })),
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 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";
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 readFile9(resolve13(path), "utf8"));
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 = resolve13(outputPath);
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 readFile9(path, "utf8") === content)
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(resolve13(modulePath));
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(resolve15(process.cwd(), basePath)), await readOpenApiJson(resolve15(process.cwd(), currentPath)));
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: resolve15(process.cwd(), modulePath),
12946
- outputPath: resolve15(process.cwd(), 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 ? resolve15(process.cwd(), rootDir) : process.cwd(),
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 ? resolve15(process.cwd(), rootDir) : defaults.rootDir;
12981
- const resolvedOut = outDir ? resolve15(process.cwd(), outDir) : defaults.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 readFile11(resolve15(process.cwd(), deliveryPath), "utf8"));
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 readFile11(resolve15(process.cwd(), query), "utf8"));
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 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,50 @@ 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
+ 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(resolve11(fileName)) || fileExists(fileName);
11542
- host.readFile = (fileName) => sourceOverrides.get(resolve11(fileName)) ?? readFile(fileName);
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(resolve11(fileName));
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 = resolve11(sourceFile.fileName);
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 = resolve11(object.getSourceFile().fileName);
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(resolve11(sourceFile.fileName)) ?? [];
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(resolve11(sourceFile.fileName), replacements);
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 = resolve11(fileName);
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(), resolve11("."), new Set([absoluteFile]));
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) => resolve11(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) => resolve11(rootDir, issue.file) === file)
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 = resolve11(rootDir, issue.file);
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
- await writeFile4(temporary, content, "utf8");
11707
- await rename5(temporary, path);
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 = resolve11(options.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(resolve11(filePath), await readFile8(filePath, "utf8"));
11811
+ sourceByPath.set(resolve12(filePath), await readFile9(filePath, "utf8"));
11729
11812
  }
11730
- for (const migration of SUPACLOUD_MIGRATIONS) {
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 = resolve11(filePath);
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.push({ path: filePath, content: result.content });
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
- for (const pending of pendingWrites)
11759
- await writeAtomically(pending.path, pending.content);
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: SUPACLOUD_MIGRATIONS.map(({ id, from, to, description }) => ({ id, from, to, description })),
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 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";
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 readFile9(resolve12(path), "utf8"));
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 = resolve12(outputPath);
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 readFile9(path, "utf8") === content)
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(resolve12(modulePath));
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 resolve13 } from "node:path";
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: resolve13(cwd, resolved.root ?? DEFAULT_SUPACLOUD_CONFIG.root),
12647
- outDir: resolve13(cwd, resolved.outDir ?? DEFAULT_SUPACLOUD_CONFIG.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: resolve13(cwd, resolved.graphql.schema)
12773
+ schema: resolve14(cwd, resolved.graphql.schema)
12666
12774
  } : undefined
12667
12775
  };
12668
12776
  }
@@ -0,0 +1,2 @@
1
+ export declare function 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.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",