From 8b11af6a11105599e1822d9b42df73f7b88d0dc6 Mon Sep 17 00:00:00 2001 From: ShaneK Date: Fri, 14 Aug 2026 09:00:25 -0700 Subject: [PATCH] chore(migrate): target v9 by major instead of a dev build pin --- packages/migrate/src/ast/deps-migration.ts | 24 +++++++------------ packages/migrate/src/detect.ts | 9 ------- .../migrate/src/migrations/v9/angular-deps.ts | 9 ++++--- .../migrate/src/migrations/v9/react-deps.ts | 5 ++-- .../migrate/src/migrations/v9/vue-deps.ts | 5 ++-- packages/migrate/src/versions.ts | 14 ----------- packages/migrate/test/engine.test.ts | 9 ++++--- packages/migrate/test/migrations.test.ts | 11 ++++----- 8 files changed, 26 insertions(+), 60 deletions(-) delete mode 100644 packages/migrate/src/versions.ts diff --git a/packages/migrate/src/ast/deps-migration.ts b/packages/migrate/src/ast/deps-migration.ts index f16e5927b5d..1042c47fe58 100644 --- a/packages/migrate/src/ast/deps-migration.ts +++ b/packages/migrate/src/ast/deps-migration.ts @@ -4,9 +4,8 @@ import type { Finding, Framework, Migration } from '../types.js'; import { findDependency, readPackageJson, setRange, writePackageJson } from './package-json.js'; /** - * A bump target: either a major number (raised to `^{major}.0.0`) for normal - * published packages, or an explicit version string (used verbatim) for - * unpublished `@ionic/*` dev builds. + * A bump target: either a major number (raised to `^{major}.0.0`) or an + * explicit range used verbatim (e.g. `^3.5.0`). */ export type BumpTarget = number | string; @@ -46,14 +45,11 @@ function isBelow(a: [number, number, number], b: [number, number, number]): bool } /** - * Whether a dependency needs changing: + * Whether a dependency needs changing. Both target shapes compare a floor, so a + * range already at or above the target is never rewritten or downgraded: * - numeric target: the installed major must be below it. - * - caret/tilde range target (a minimum floor, e.g. `^3.5.0`): bump only when - * the installed version is below the floor, so a higher pin is never - * downgraded. - * - explicit version string (an unpublished dev build): the range must simply - * differ - a major comparison won't work because a `8.8.x-dev` build of v9 - * still reads as major 8. + * - range target (a minimum floor, e.g. `^3.5.0`): the installed version must + * be below the floor. */ function needsChange(pkg: PackageJson, name: string, target: BumpTarget): boolean { const dep = findDependency(pkg, name); @@ -63,11 +59,9 @@ function needsChange(pkg: PackageJson, name: string, target: BumpTarget): boolea if (typeof target === 'number') { return (parseMajor(dep.range) ?? Infinity) < target; } - if (target.startsWith('^') || target.startsWith('~')) { - const current = parseVersion(dep.range); - return current !== undefined && isBelow(current, parseVersion(target)!); - } - return dep.range !== target; + const floor = parseVersion(target); + const current = parseVersion(dep.range); + return floor !== undefined && current !== undefined && isBelow(current, floor); } /** diff --git a/packages/migrate/src/detect.ts b/packages/migrate/src/detect.ts index b84cda50315..ff4dcfaa398 100644 --- a/packages/migrate/src/detect.ts +++ b/packages/migrate/src/detect.ts @@ -1,6 +1,5 @@ import type { MigrationContext } from './context.js'; import type { Framework } from './types.js'; -import { IONIC_V9_VERSION } from './versions.js'; /** An Ionic framework binding found in the project, with its installed major. */ export interface DetectedFramework { @@ -61,14 +60,6 @@ export function detectFrameworks(ctx: MigrationContext): DetectedFramework[] { ][]) { const range = deps[pkgName]; if (range === undefined) continue; - // A project already pinned to the v9 dev build reads as major 8 via semver - // (the pin is versioned `8.8.x-dev`), so recognize it explicitly as v9. - // This closes the re-run gate: a migrated project detects as v9 and selects - // no v8->v9 migrations. Remove once the pin becomes `^9.0.0` at GA. - if (range === IONIC_V9_VERSION) { - detected.push({ framework, major: 9 }); - continue; - } // Only a plain, bumpable semver range gates re-runs correctly. angular-deps // won't rewrite a protocol/alias range (npm:, git+, workspace:, ...), so if // we migrated one the version gate would never close and single-shot diff --git a/packages/migrate/src/migrations/v9/angular-deps.ts b/packages/migrate/src/migrations/v9/angular-deps.ts index 62b56147116..d459a9f03c5 100644 --- a/packages/migrate/src/migrations/v9/angular-deps.ts +++ b/packages/migrate/src/migrations/v9/angular-deps.ts @@ -1,12 +1,11 @@ import { createDepsMigration } from '../../ast/deps-migration.js'; -import { IONIC_V9_VERSION } from '../../versions.js'; /** * Raise the `@ionic/angular` packages to v9. (The Angular framework version * itself is the developer's choice via `ng update`, so it is left untouched.) * Bumping the Ionic packages here also closes the engine's version gate: once - * `package.json` pins the v9 build, `detectFrameworks` reports v9 on a re-run - * and applies nothing. + * `package.json` asks for v9, `detectFrameworks` reports v9 on a re-run and + * applies nothing. * * See https://ionicframework.com/docs/updating/9-0#angular */ @@ -15,7 +14,7 @@ export const angularDeps = createDepsMigration({ framework: 'angular', docsUrl: 'https://ionicframework.com/docs/updating/9-0#angular', bumps: [ - ['@ionic/angular', IONIC_V9_VERSION], - ['@ionic/angular-server', IONIC_V9_VERSION], + ['@ionic/angular', 9], + ['@ionic/angular-server', 9], ], }); diff --git a/packages/migrate/src/migrations/v9/react-deps.ts b/packages/migrate/src/migrations/v9/react-deps.ts index b52ab493c08..0a17a4b2e18 100644 --- a/packages/migrate/src/migrations/v9/react-deps.ts +++ b/packages/migrate/src/migrations/v9/react-deps.ts @@ -1,5 +1,4 @@ import { createDepsMigration } from '../../ast/deps-migration.js'; -import { IONIC_V9_VERSION } from '../../versions.js'; /** * Raise the React Ionic packages to v9 and React Router to v6, and drop the @@ -13,8 +12,8 @@ export const reactDeps = createDepsMigration({ framework: 'react', docsUrl: 'https://ionicframework.com/docs/updating/9-0#react', bumps: [ - ['@ionic/react', IONIC_V9_VERSION], - ['@ionic/react-router', IONIC_V9_VERSION], + ['@ionic/react', 9], + ['@ionic/react-router', 9], ['react-router', 6], ['react-router-dom', 6], ], diff --git a/packages/migrate/src/migrations/v9/vue-deps.ts b/packages/migrate/src/migrations/v9/vue-deps.ts index cfb6d183d3f..9973c006f39 100644 --- a/packages/migrate/src/migrations/v9/vue-deps.ts +++ b/packages/migrate/src/migrations/v9/vue-deps.ts @@ -1,5 +1,4 @@ import { createDepsMigration } from '../../ast/deps-migration.js'; -import { IONIC_V9_VERSION } from '../../versions.js'; /** * Raise the Vue Ionic packages to v9, Vue Router to v5, and Vue to 3.5+ (the @@ -14,8 +13,8 @@ export const vueDeps = createDepsMigration({ framework: 'vue', docsUrl: 'https://ionicframework.com/docs/updating/9-0#vue', bumps: [ - ['@ionic/vue', IONIC_V9_VERSION], - ['@ionic/vue-router', IONIC_V9_VERSION], + ['@ionic/vue', 9], + ['@ionic/vue-router', 9], ['vue', '^3.5.0'], ['vue-router', 5], ], diff --git a/packages/migrate/src/versions.ts b/packages/migrate/src/versions.ts deleted file mode 100644 index 9e0edbbb502..00000000000 --- a/packages/migrate/src/versions.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * The version the `@ionic/*` packages are pinned to when migrating to v9. - * - * v9 is not yet published under the `latest` dist-tag, so a caret range like - * `^9.0.0` will not resolve. Until v9 ships, this pins a specific `major-9.0` - * dev build. It is versioned `8.8.x-dev` (not `9.0.0-dev`) because the - * `major-9.0` branch has not bumped its base version yet, but the code is v9. - * - * We pin the exact version rather than the `dev` dist-tag because that tag is - * shared with `main`'s dev builds and moves whenever any dev build publishes. - * - * TODO(FW-7579): Swap this for `^9.0.0` at GA. - */ -export const IONIC_V9_VERSION = '8.8.14-dev.11784563563.137a903a'; diff --git a/packages/migrate/test/engine.test.ts b/packages/migrate/test/engine.test.ts index dd776023c8c..df9fcd89ab1 100644 --- a/packages/migrate/test/engine.test.ts +++ b/packages/migrate/test/engine.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest'; import { createInMemoryContext } from '../src/context.js'; import { detectFrameworks, parseMajor } from '../src/detect.js'; -import { IONIC_V9_VERSION } from '../src/versions.js'; import { resolveTarget, selectMigrations } from '../src/registry.js'; import { allMigrations } from '../src/migrations/index.js'; import { run } from '../src/runner.js'; @@ -51,11 +50,11 @@ describe('detectFrameworks', () => { expect(parseMajor(undefined)).toBeUndefined(); }); - it('treats the v9 dev pin as major 9, closing the re-run gate', () => { - // The pin is versioned `8.8.x-dev`, so a naive semver read reports major 8 - // and would re-select every v8->v9 migration on a second run. + it('treats a project already on v9 as major 9, closing the re-run gate', () => { + // Single-shot transforms corrupt already-migrated code, so a second run + // must select nothing. const ctx = createInMemoryContext({ - 'package.json': JSON.stringify({ dependencies: { '@ionic/angular': IONIC_V9_VERSION } }), + 'package.json': JSON.stringify({ dependencies: { '@ionic/angular': '^9.0.0' } }), }); expect(detectFrameworks(ctx)).toEqual([{ framework: 'angular', major: 9 }]); diff --git a/packages/migrate/test/migrations.test.ts b/packages/migrate/test/migrations.test.ts index dad0c078377..4174a5306c6 100644 --- a/packages/migrate/test/migrations.test.ts +++ b/packages/migrate/test/migrations.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createInMemoryContext } from '../src/context.js'; -import { IONIC_V9_VERSION } from '../src/versions.js'; import { angularDeps } from '../src/migrations/v9/angular-deps.js'; import { reactDeps } from '../src/migrations/v9/react-deps.js'; import { reactRouter6Code } from '../src/migrations/v9/react-router-6-code.js'; @@ -33,7 +32,7 @@ describe('react-deps', () => { reactDeps.fix!(ctx); const pkg = JSON.parse(ctx.readFile('package.json')!); - expect(pkg.dependencies['@ionic/react']).toBe(IONIC_V9_VERSION); + expect(pkg.dependencies['@ionic/react']).toBe('^9.0.0'); expect(pkg.dependencies['react-router']).toBe('^6.0.0'); expect(pkg.dependencies['react-router-dom']).toBe('^6.0.0'); expect(pkg.devDependencies['@types/react-router-dom']).toBeUndefined(); @@ -42,7 +41,7 @@ describe('react-deps', () => { it('does nothing when already on v9/v6 (version gate is closed)', () => { const ctx = createInMemoryContext({ 'package.json': JSON.stringify( - { dependencies: { '@ionic/react': IONIC_V9_VERSION, 'react-router-dom': '^6.4.0' } }, + { dependencies: { '@ionic/react': '^9.0.0', 'react-router-dom': '^6.4.0' } }, null, 2 ), @@ -61,7 +60,7 @@ describe('angular-deps', () => { angularDeps.fix!(ctx); const pkg = JSON.parse(ctx.readFile('package.json')!); - expect(pkg.dependencies['@ionic/angular']).toBe(IONIC_V9_VERSION); + expect(pkg.dependencies['@ionic/angular']).toBe('^9.0.0'); }); }); @@ -78,7 +77,7 @@ describe('vue-deps', () => { vueDeps.fix!(ctx); const pkg = JSON.parse(ctx.readFile('package.json')!); - expect(pkg.dependencies['@ionic/vue']).toBe(IONIC_V9_VERSION); + expect(pkg.dependencies['@ionic/vue']).toBe('^9.0.0'); expect(pkg.dependencies['vue-router']).toBe('^5.0.0'); // Vue must be raised to the 3.5+ floor v9 requires; a 3.4 pin is below it. expect(pkg.dependencies['vue']).toBe('^3.5.0'); @@ -87,7 +86,7 @@ describe('vue-deps', () => { it('does not downgrade a Vue pin already at or above the 3.5 floor', () => { const ctx = createInMemoryContext({ 'package.json': JSON.stringify( - { dependencies: { '@ionic/vue': IONIC_V9_VERSION, vue: '^3.6.0', 'vue-router': '^5.0.0' } }, + { dependencies: { '@ionic/vue': '^9.0.0', vue: '^3.6.0', 'vue-router': '^5.0.0' } }, null, 2 ),