Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions packages/migrate/src/ast/deps-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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);
}

/**
Expand Down
9 changes: 0 additions & 9 deletions packages/migrate/src/detect.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions packages/migrate/src/migrations/v9/angular-deps.ts
Original file line number Diff line number Diff line change
@@ -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
*/
Expand All @@ -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],
],
});
5 changes: 2 additions & 3 deletions packages/migrate/src/migrations/v9/react-deps.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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],
],
Expand Down
5 changes: 2 additions & 3 deletions packages/migrate/src/migrations/v9/vue-deps.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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],
],
Expand Down
14 changes: 0 additions & 14 deletions packages/migrate/src/versions.ts

This file was deleted.

9 changes: 4 additions & 5 deletions packages/migrate/test/engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 }]);
Expand Down
11 changes: 5 additions & 6 deletions packages/migrate/test/migrations.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();
Expand All @@ -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
),
Expand All @@ -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');
});
});

Expand All @@ -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');
Expand All @@ -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
),
Expand Down
Loading