deepspace 0.9.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # deepspace
2
2
 
3
+ ## 0.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Keep `deepspace app migrate` as the permanent ordered app-upgrade runner and
8
+ repair obsolete `x-app-name` platform identity wiring before a canonical app
9
+ deployment, without changing retained `APP_NAME` data namespaces. Track
10
+ source-only migration commits against live release lineage so both GitHub and
11
+ DeepSpace apps are guided through the required deploy.
12
+
3
13
  ## 0.9.0
4
14
 
5
15
  ### Minor Changes
package/README.md CHANGED
@@ -90,9 +90,9 @@ npx deepspace deploy # deploy to *.app.space
90
90
  ```
91
91
 
92
92
  The [normative CLI hierarchy](../../docs/platform/cli-contract.md#public-command-hierarchy)
93
- keeps durable app identity and lifecycle operations under `deepspace app`,
93
+ keeps durable app lifecycle and version migrations under `deepspace app`,
94
94
  while checkout-oriented Git, workspace, release, and deploy operations stay
95
- top-level. In particular, legacy identity migration is
95
+ top-level. In particular, app migration is
96
96
  `deepspace app migrate`; there is no top-level `deepspace migrate` alias.
97
97
 
98
98
  Every app has one authoritative Git repository. DeepSpace source is the packaged
@@ -118,17 +118,28 @@ atomic authority change; switching back uses the same commands. Commands support
118
118
  [repository guide](https://github.com/deepdotspace/deepspace/blob/main/docs/platform/repo-store-git.md)
119
119
  for workspaces, releases, and rollback.
120
120
 
121
- Legacy GitHub apps whose `DEEPSPACE_APP_ID` is still name-shaped migrate in
122
- place with one resumable command:
121
+ `deepspace app migrate` is the permanent upgrade runner for breaking app
122
+ changes. It contains an ordered set of structural, idempotent migrations, so
123
+ agents and developers keep using the same command across releases:
123
124
 
124
125
  ```bash
125
126
  npx deepspace app migrate --dry-run
126
127
  npx deepspace app migrate
127
128
  ```
128
129
 
129
- The dry-run lists the exact registry rows that will be re-keyed and the
130
- physical stores that remain at the existing resource id. The command then
131
- pauses for the normal manual GitHub commit/push and finishes with one deploy.
130
+ The dry-run lists pending source migrations without changing files. For a
131
+ legacy GitHub app whose `DEEPSPACE_APP_ID` is still name-shaped, it also lists
132
+ the exact registry rows that will be re-keyed and the physical stores that
133
+ remain at the existing resource id. The command applies only transformations
134
+ it recognizes safely, pauses for normal commit/push, and finishes with one
135
+ deploy. Source migration commits carry a machine-readable Git trailer, so the
136
+ next run can compare them with the live release and return the deploy action
137
+ without a local state file. Rerun after each returned action; when nothing is
138
+ pending it reports `up_to_date`. This is the same workflow for GitHub and
139
+ DeepSpace source; only their normal push behavior differs. `APP_NAME`-based
140
+ legacy room and storage addresses are retained; canonical `DEEPSPACE_APP_ID`
141
+ is used only for logical identity and platform authentication.
142
+
132
143
  Keep deploy, release rollback, and undeploy idle from the mutating command until
133
144
  that returned deploy begins; normal app traffic continues throughout.
134
145
  Before that deploy starts, `--cancel` reverses a prepared migration and
package/dist/cli.js CHANGED
@@ -3,8 +3,8 @@
3
3
  // src/cli/cli.ts
4
4
  import { defineCommand as defineCommand14, runMain, runCommand } from "citty";
5
5
  import { sync as spawnSync8 } from "cross-spawn";
6
- import { readFileSync as readFileSync25, existsSync as existsSync24 } from "fs";
7
- import { dirname as dirname10, join as join23 } from "path";
6
+ import { readFileSync as readFileSync26, existsSync as existsSync24 } from "fs";
7
+ import { dirname as dirname10, join as join24 } from "path";
8
8
  import { fileURLToPath as fileURLToPath3 } from "url";
9
9
 
10
10
  // src/cli/lib/spinner.ts
@@ -11783,6 +11783,177 @@ function sameRefs(left, right) {
11783
11783
  return left.every((ref) => expected.get(ref.name) === ref.oid);
11784
11784
  }
11785
11785
 
11786
+ // src/cli/lib/app-migrations.ts
11787
+ import { lstatSync, readFileSync as readFileSync25, writeFileSync as writeFileSync10 } from "fs";
11788
+ import { join as join23 } from "path";
11789
+ var CANONICAL_IDENTITY_WIRE_ID = "2026-08-canonical-app-identity-wire";
11790
+ var SOURCE_MIGRATIONS = [
11791
+ {
11792
+ id: CANONICAL_IDENTITY_WIRE_ID,
11793
+ description: "Use the immutable app id for authenticated platform requests",
11794
+ transform: migrateCanonicalIdentityWire,
11795
+ findBlockers: findLegacyIdentityWireBlockers
11796
+ }
11797
+ ];
11798
+ var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
11799
+ ({ id, description }) => ({ id, description })
11800
+ );
11801
+ function planAppMigrations(appDir) {
11802
+ const repo = repoToplevel(appDir);
11803
+ const appPrefix = gitLine(appDir, ["rev-parse", "--show-prefix"]);
11804
+ const tracked = splitNulFields(runGit(repo, ["ls-files", "-z"]).stdout);
11805
+ const perMigration = /* @__PURE__ */ new Map();
11806
+ const blockers = [];
11807
+ const edits = [];
11808
+ for (const repoPath of tracked) {
11809
+ if (!isSourcePath(repoPath, appPrefix)) continue;
11810
+ const absolutePath = join23(repo, repoPath);
11811
+ let stat;
11812
+ try {
11813
+ stat = lstatSync(absolutePath);
11814
+ } catch {
11815
+ continue;
11816
+ }
11817
+ if (!stat.isFile() || stat.isSymbolicLink()) continue;
11818
+ const before = readFileSync25(absolutePath, "utf-8");
11819
+ let after = before;
11820
+ for (const migration of SOURCE_MIGRATIONS) {
11821
+ const result = migration.transform(after);
11822
+ after = result.source;
11823
+ if (result.replacements > 0) {
11824
+ const planned = perMigration.get(migration.id) ?? {
11825
+ id: migration.id,
11826
+ description: migration.description,
11827
+ files: [],
11828
+ replacements: 0
11829
+ };
11830
+ planned.files.push(repoPath);
11831
+ planned.replacements += result.replacements;
11832
+ perMigration.set(migration.id, planned);
11833
+ }
11834
+ blockers.push(...migration.findBlockers(after, repoPath));
11835
+ }
11836
+ if (after !== before) edits.push({ path: repoPath, before, after });
11837
+ }
11838
+ return {
11839
+ pending: SOURCE_MIGRATIONS.flatMap((migration) => {
11840
+ const planned = perMigration.get(migration.id);
11841
+ return planned ? [{ ...planned, files: [...new Set(planned.files)].sort() }] : [];
11842
+ }),
11843
+ changedFiles: edits.map((edit) => edit.path).sort(),
11844
+ blockers,
11845
+ edits
11846
+ };
11847
+ }
11848
+ function applyAppMigrationPlan(appDir, plan) {
11849
+ if (plan.blockers.length > 0) {
11850
+ throw new Error("Cannot apply an app migration plan with unresolved blockers.");
11851
+ }
11852
+ const repo = repoToplevel(appDir);
11853
+ for (const edit of plan.edits) {
11854
+ const absolutePath = join23(repo, edit.path);
11855
+ if (readFileSync25(absolutePath, "utf-8") !== edit.before) {
11856
+ throw new Error(`Migration input changed after planning: ${edit.path}`);
11857
+ }
11858
+ }
11859
+ for (const edit of plan.edits) writeFileSync10(join23(repo, edit.path), edit.after);
11860
+ }
11861
+ function isSourcePath(repoPath, appPrefix) {
11862
+ if (appPrefix && !repoPath.startsWith(appPrefix)) return false;
11863
+ return /\.(?:[cm]?[jt]sx?)$/.test(repoPath);
11864
+ }
11865
+ function migrateCanonicalIdentityWire(source) {
11866
+ let replacements = 0;
11867
+ const lines = source.split("\n");
11868
+ for (let index = 0; index < lines.length; index++) {
11869
+ let line = lines[index];
11870
+ line = line.replace(
11871
+ /(\.set\(\s*)(['"])x-app-name\2(\s*,\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME(\s*\))/g,
11872
+ (_match, open, quote, separator, receiver, close) => {
11873
+ replacements++;
11874
+ return `${open}${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID${close}`;
11875
+ }
11876
+ );
11877
+ line = line.replace(
11878
+ /(['"])x-app-name\1(\s*:\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME/g,
11879
+ (_match, quote, separator, receiver) => {
11880
+ replacements++;
11881
+ return `${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID`;
11882
+ }
11883
+ );
11884
+ line = line.replace(
11885
+ /(\[\s*)(['"])x-app-name\2(\s*\]\s*=\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME/g,
11886
+ (_match, open, quote, separator, receiver) => {
11887
+ replacements++;
11888
+ return `${open}${quote}x-app-id${quote}${separator}${receiver}.DEEPSPACE_APP_ID`;
11889
+ }
11890
+ );
11891
+ line = line.replace(
11892
+ /(forwardedParams\.set\(\s*)(['"])appName\2(\s*,\s*)([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\.APP_NAME(\s*\))/g,
11893
+ (_match, open, quote, separator, receiver, close) => {
11894
+ replacements++;
11895
+ return `${open}${quote}appId${quote}${separator}${receiver}.DEEPSPACE_APP_ID${close}`;
11896
+ }
11897
+ );
11898
+ lines[index] = line;
11899
+ }
11900
+ if (replacements === 0) return { source, replacements };
11901
+ let migrated = lines.join("\n");
11902
+ if (/forwardedParams\.set\(\s*['"]appId['"]/.test(migrated)) {
11903
+ migrated = migrated.replaceAll("injectAppName", "injectAppId").replaceAll("`?appName=...`", "`?appId=...`").replaceAll("Inject appName into the query string", "Inject appId into the query string").replaceAll("caller-supplied appName", "caller-supplied appId").replaceAll(
11904
+ "/_deepspace/subscriptions/plans?appName=",
11905
+ "/_deepspace/subscriptions/plans?appId="
11906
+ );
11907
+ }
11908
+ migrated = migrated.replaceAll(
11909
+ "APP_IDENTITY_TOKEN + APP_NAME",
11910
+ "APP_IDENTITY_TOKEN + DEEPSPACE_APP_ID"
11911
+ );
11912
+ return {
11913
+ source: ensureCanonicalIdentityProperties(migrated),
11914
+ replacements
11915
+ };
11916
+ }
11917
+ function ensureCanonicalIdentityProperties(source) {
11918
+ const lines = source.split("\n");
11919
+ for (let index = lines.length - 1; index >= 0; index--) {
11920
+ const match = /^(\s*)(?:readonly\s+)?APP_NAME\??\s*:\s*string\s*([;,]?)\s*$/.exec(lines[index]);
11921
+ if (!match || blockAlreadyDeclaresCanonicalId(lines, index, match[1].length)) continue;
11922
+ const suffix = match[2] ?? "";
11923
+ lines.splice(index + 1, 0, `${match[1]}DEEPSPACE_APP_ID: string${suffix}`);
11924
+ }
11925
+ return lines.join("\n");
11926
+ }
11927
+ function blockAlreadyDeclaresCanonicalId(lines, propertyIndex, propertyIndent) {
11928
+ for (const direction of [-1, 1]) {
11929
+ for (let index = propertyIndex + direction; index >= 0 && index < lines.length; index += direction) {
11930
+ const line = lines[index];
11931
+ if (/^\s*(?:readonly\s+)?DEEPSPACE_APP_ID\??\s*:/.test(line)) return true;
11932
+ const trimmed = line.trimStart();
11933
+ if (!trimmed) continue;
11934
+ const indent = line.length - trimmed.length;
11935
+ if (indent < propertyIndent && (trimmed.includes("{") || trimmed.startsWith("}"))) break;
11936
+ }
11937
+ }
11938
+ return false;
11939
+ }
11940
+ function findLegacyIdentityWireBlockers(source, file) {
11941
+ const blockers = [];
11942
+ for (const [index, line] of source.split("\n").entries()) {
11943
+ const trimmed = line.trimStart();
11944
+ if (!/['"]x-app-name['"]/.test(line) || /\.delete\(\s*['"]x-app-name['"]/.test(line) || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
11945
+ continue;
11946
+ }
11947
+ blockers.push({
11948
+ migrationId: CANONICAL_IDENTITY_WIRE_ID,
11949
+ file,
11950
+ line: index + 1,
11951
+ message: "This app still sends the removed x-app-name identity header in a form the safe migration cannot rewrite."
11952
+ });
11953
+ }
11954
+ return blockers;
11955
+ }
11956
+
11786
11957
  // src/cli/lib/identity-migration-api.ts
11787
11958
  async function getIdentityMigration(deployUrl, token, appId) {
11788
11959
  try {
@@ -11847,7 +12018,7 @@ var STRICT_APP_ID_RE2 = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
11847
12018
  var migrate_default = defineDeepspaceCommand({
11848
12019
  meta: {
11849
12020
  name: "migrate",
11850
- description: "Upgrade a legacy GitHub app to an immutable app id without moving its data"
12021
+ description: "Apply pending DeepSpace app migrations without moving retained data"
11851
12022
  },
11852
12023
  args: {
11853
12024
  remote: {
@@ -11920,14 +12091,54 @@ var migrate_default = defineDeepspaceCommand({
11920
12091
  json: args.json
11921
12092
  });
11922
12093
  }
12094
+ const sourcePlan = planAppMigrations(appDir);
12095
+ if (sourcePlan.blockers.length > 0) {
12096
+ const first = sourcePlan.blockers[0];
12097
+ throw new Refusal(
12098
+ `Migration ${first.migrationId} needs a manual source update at ${first.file}:${first.line}. ${first.message}`,
12099
+ "source_migration_blocked",
12100
+ { extra: { blockers: sourcePlan.blockers } }
12101
+ );
12102
+ }
11923
12103
  if (STRICT_APP_ID_RE2.test(currentAppId)) {
11924
12104
  if (!existingMigration) {
11925
- reportAlreadyModern(currentAppId, args.json);
11926
- return { data: { status: "already_modern", appId: currentAppId } };
12105
+ if (args["dry-run"] && sourcePlan.pending.length > 0) {
12106
+ reportSourcePlan(sourcePlan, args.json);
12107
+ return {
12108
+ data: {
12109
+ status: "ready",
12110
+ appId: currentAppId,
12111
+ sourceMigrations: sourcePlan.pending
12112
+ }
12113
+ };
12114
+ }
12115
+ if (sourcePlan.pending.length > 0) {
12116
+ requireSourceMigrationWorktree(appDir);
12117
+ applyAppMigrationPlan(appDir, sourcePlan);
12118
+ throw sourceMigrationCommitRequired(appDir, sourcePlan);
12119
+ }
12120
+ await requireSourceMigrationDeploy(appDir, currentAppId, deployUrl, token);
12121
+ reportUpToDate(currentAppId, args.json);
12122
+ return { data: { status: "up_to_date", appId: currentAppId } };
12123
+ }
12124
+ if (sourcePlan.pending.length === 0 && existingMigration.status === "verified") {
12125
+ await requireSourceMigrationDeploy(appDir, currentAppId, deployUrl, token);
11927
12126
  }
11928
12127
  if (args["dry-run"]) {
11929
12128
  reportMigration(existingMigration, args.json);
11930
- return { data: { status: existingMigration.status, migration: existingMigration } };
12129
+ reportSourcePlan(sourcePlan, args.json);
12130
+ return {
12131
+ data: {
12132
+ status: existingMigration.status,
12133
+ migration: existingMigration,
12134
+ sourceMigrations: sourcePlan.pending
12135
+ }
12136
+ };
12137
+ }
12138
+ if (sourcePlan.pending.length > 0) {
12139
+ requireSourceMigrationWorktree(appDir, existingMigration.status === "prepared");
12140
+ applyAppMigrationPlan(appDir, sourcePlan);
12141
+ throw existingMigration.status === "prepared" ? commitRequired(appDir, existingMigration, envName, sourcePlan.changedFiles) : sourceMigrationCommitRequired(appDir, sourcePlan);
11931
12142
  }
11932
12143
  return continuePreparedMigration({
11933
12144
  appDir,
@@ -11978,6 +12189,7 @@ var migrate_default = defineDeepspaceCommand({
11978
12189
  );
11979
12190
  console.log("No changes made.");
11980
12191
  }
12192
+ reportSourcePlan(sourcePlan, args.json);
11981
12193
  return {
11982
12194
  data: {
11983
12195
  status: "ready",
@@ -11988,6 +12200,7 @@ var migrate_default = defineDeepspaceCommand({
11988
12200
  branch: head.branch,
11989
12201
  commitOid: head.oid,
11990
12202
  sourceClaimRequired: inventory.app.sourceClaimRequired,
12203
+ sourceMigrations: sourcePlan.pending,
11991
12204
  inventory
11992
12205
  }
11993
12206
  };
@@ -11997,8 +12210,9 @@ var migrate_default = defineDeepspaceCommand({
11997
12210
  repository: remote.repository,
11998
12211
  expectedSourceRevision: inventory.app.sourceRevision
11999
12212
  });
12213
+ applyAppMigrationPlan(appDir, sourcePlan);
12000
12214
  writeAppId(appDir, migration.appId, { wranglerEnv: envName, force: true });
12001
- throw commitRequired(appDir, migration, envName);
12215
+ throw commitRequired(appDir, migration, envName, sourcePlan.changedFiles);
12002
12216
  }
12003
12217
  });
12004
12218
  async function recoverMigration(input) {
@@ -12128,10 +12342,11 @@ function requirePublishedHead2(appDir, remote) {
12128
12342
  }
12129
12343
  return head;
12130
12344
  }
12131
- function commitRequired(appDir, migration, envName) {
12345
+ function commitRequired(appDir, migration, envName, additionalPaths = []) {
12132
12346
  const wranglerPath = wranglerRepoPath(appDir);
12347
+ const migrationPaths = [.../* @__PURE__ */ new Set([wranglerPath, ...additionalPaths])].sort();
12133
12348
  return new Refusal(
12134
- `Prepared ${migration.legacyAppId} \u2192 ${migration.appId}. Commit the deterministic wrangler.toml change, then rerun.`,
12349
+ `Prepared ${migration.legacyAppId} \u2192 ${migration.appId}. Commit the deterministic app migration changes, then rerun.`,
12135
12350
  "migration_commit_required",
12136
12351
  {
12137
12352
  actionRequired: true,
@@ -12144,13 +12359,102 @@ function commitRequired(appDir, migration, envName) {
12144
12359
  "--message",
12145
12360
  `Migrate DeepSpace app identity to ${migration.appId}`,
12146
12361
  "--",
12147
- wranglerPath
12362
+ ...migrationPaths
12148
12363
  ]
12149
12364
  },
12150
12365
  extra: { migration, env: envName ?? null }
12151
12366
  }
12152
12367
  );
12153
12368
  }
12369
+ function sourceMigrationCommitRequired(appDir, plan) {
12370
+ const migrationIds = plan.pending.map((migration) => migration.id);
12371
+ return new Refusal(
12372
+ `Applied ${migrationIds.join(", ")}. Commit the deterministic app migration changes, then rerun.`,
12373
+ "migration_commit_required",
12374
+ {
12375
+ actionRequired: true,
12376
+ action: {
12377
+ cwd: repoToplevel(appDir),
12378
+ argv: [
12379
+ "git",
12380
+ "commit",
12381
+ "--only",
12382
+ "--message",
12383
+ "Apply DeepSpace app migrations",
12384
+ "--message",
12385
+ `DeepSpace-Migrations: ${migrationIds.join(",")}`,
12386
+ "--",
12387
+ ...plan.changedFiles
12388
+ ]
12389
+ },
12390
+ extra: { sourceMigrations: plan.pending }
12391
+ }
12392
+ );
12393
+ }
12394
+ async function requireSourceMigrationDeploy(appDir, appId, deployUrl, token) {
12395
+ if (!isWorkTreeClean(appDir)) {
12396
+ throw new Refusal(
12397
+ "Commit or discard local changes before checking app migration deployment state.",
12398
+ "dirty_worktree"
12399
+ );
12400
+ }
12401
+ const head = resolveCommit(appDir, "HEAD");
12402
+ if (!head) return;
12403
+ const repo = repoToplevel(appDir);
12404
+ const knownIds = new Set(APP_MIGRATION_DEFINITIONS.map((migration) => migration.id));
12405
+ const migrationCommits = splitNulFields(
12406
+ runGit(repo, [
12407
+ "log",
12408
+ "--format=%H%x00",
12409
+ "--extended-regexp",
12410
+ "--grep",
12411
+ `^DeepSpace-Migrations:.*(${[...knownIds].join("|")})`
12412
+ ]).stdout
12413
+ ).map((oid) => oid.trim()).filter((oid) => /^[0-9a-f]{40}$/.test(oid));
12414
+ if (migrationCommits.length === 0) return;
12415
+ const { release: release2 } = await repoApi(deployUrl, token, appId).latestRelease();
12416
+ if (release2?.commitOid === head) return;
12417
+ const liveCommit = release2?.commitOid ?? null;
12418
+ if (liveCommit && (resolveCommit(appDir, liveCommit) === null || !isAncestor(appDir, liveCommit, head))) {
12419
+ throw new Refusal(
12420
+ `The live release commit ${liveCommit.slice(0, 10)} is not an ancestor of this checkout, so migration deployment state cannot be proven. Fetch and integrate the live source history first.`,
12421
+ "migration_lineage_unknown",
12422
+ { extra: { appId, liveCommitOid: liveCommit, commitOid: head } }
12423
+ );
12424
+ }
12425
+ const undeployedMigrationCommits = migrationCommits.filter(
12426
+ (oid) => !liveCommit || oid !== liveCommit && isAncestor(appDir, liveCommit, oid)
12427
+ );
12428
+ const pendingIds = [
12429
+ ...new Set(
12430
+ undeployedMigrationCommits.flatMap((oid) => {
12431
+ const message = runGit(repo, ["show", "-s", "--format=%B", oid]).stdout.toString("utf-8");
12432
+ return [...message.matchAll(/^DeepSpace-Migrations:\s*(.+)$/gm)].flatMap(
12433
+ (match) => (match[1] ?? "").split(",").map((id) => id.trim()).filter((id) => knownIds.has(id))
12434
+ );
12435
+ })
12436
+ )
12437
+ ];
12438
+ if (pendingIds.length === 0) return;
12439
+ throw new Refusal(
12440
+ `App migrations ${pendingIds.join(", ")} are committed but are not in the live release. Deploy them, then rerun.`,
12441
+ "migration_deploy_required",
12442
+ {
12443
+ actionRequired: true,
12444
+ action: { cwd: appDir, argv: ["deepspace", "deploy"] },
12445
+ extra: { appId, sourceMigrations: pendingIds, commitOid: head }
12446
+ }
12447
+ );
12448
+ }
12449
+ function requireSourceMigrationWorktree(appDir, allowDirtyWrangler = false) {
12450
+ if (isWorkTreeClean(appDir)) return;
12451
+ const changed = statusFiles(repoToplevel(appDir));
12452
+ if (allowDirtyWrangler && changed.every((path) => path === wranglerRepoPath(appDir))) return;
12453
+ throw new Refusal(
12454
+ "Commit or discard unrelated working-tree changes before applying app migrations.",
12455
+ "dirty_worktree"
12456
+ );
12457
+ }
12154
12458
  function restoreLegacyCommitRequired(appDir, migration, envName) {
12155
12459
  const wranglerPath = wranglerRepoPath(appDir);
12156
12460
  return new Refusal(
@@ -12200,8 +12504,16 @@ function deployRequired(appDir, migration, envName) {
12200
12504
  }
12201
12505
  );
12202
12506
  }
12203
- function reportAlreadyModern(appId, json) {
12204
- if (!json) console.log(`${appId} already uses an immutable DeepSpace app id.`);
12507
+ function reportUpToDate(appId, json) {
12508
+ if (!json) console.log(`No pending DeepSpace app migrations for ${appId}.`);
12509
+ }
12510
+ function reportSourcePlan(plan, json) {
12511
+ if (json || plan.pending.length === 0) return;
12512
+ for (const migration of plan.pending) {
12513
+ console.log(
12514
+ `Pending source migration: ${migration.id} (${migration.files.length} file${migration.files.length === 1 ? "" : "s"})`
12515
+ );
12516
+ }
12205
12517
  }
12206
12518
  function reportMigration(migration, json) {
12207
12519
  if (json) return;
@@ -12213,7 +12525,7 @@ function reportMigration(migration, json) {
12213
12525
 
12214
12526
  // src/cli/cli.ts
12215
12527
  var __dirname = dirname10(fileURLToPath3(import.meta.url));
12216
- var pkg = JSON.parse(readFileSync25(join23(__dirname, "..", "package.json"), "utf-8"));
12528
+ var pkg = JSON.parse(readFileSync26(join24(__dirname, "..", "package.json"), "utf-8"));
12217
12529
  if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync24("/etc/ssl/certs/ca-certificates.crt")) {
12218
12530
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
12219
12531
  }