diff --git a/packages/qwik/package.json b/packages/qwik/package.json index 9c1b1ca4d01..37084ba22a2 100644 --- a/packages/qwik/package.json +++ b/packages/qwik/package.json @@ -18,7 +18,8 @@ "kleur": "4.1.5", "prettier": "3.3.3", "vitest": "2.1.4", - "ignore": "5.3.1" + "ignore": "5.3.1", + "ts-morph": "23.0.0" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" diff --git a/packages/qwik/src/cli/migrate-v2/rename-import.ts b/packages/qwik/src/cli/migrate-v2/rename-import.ts new file mode 100644 index 00000000000..d75d1ba8367 --- /dev/null +++ b/packages/qwik/src/cli/migrate-v2/rename-import.ts @@ -0,0 +1,49 @@ +import { Project, ts } from 'ts-morph'; +import { visitNotIgnoredFiles } from './tools/visit-not-ignored-files'; +import { log } from '@clack/prompts'; + +export function replaceImportInFiles( + changes: [oldImport: string, newImport: string][], + library: string +) { + const project = new Project(); + + visitNotIgnoredFiles('.', (path) => { + if (!path.endsWith('.ts') && !path.endsWith('.tsx')) { + return; + } + project.addSourceFileAtPath(path); + }); + + project.getSourceFiles().forEach((sourceFile) => { + let hasChanges = false; + + sourceFile.getImportDeclarations().forEach((importDeclaration) => { + // startsWith is used in order to handle nested imports + if (importDeclaration.getModuleSpecifierValue().startsWith(library)) { + for (const [oldImport, newImport] of changes) { + importDeclaration.getNamedImports().forEach((namedImport) => { + if (namedImport.getName() === oldImport) { + namedImport.setName(newImport); + hasChanges = true; + } + }); + } + } + }); + + sourceFile.getDescendantsOfKind(ts.SyntaxKind.Identifier).forEach((identifier) => { + for (const [oldImport, newImport] of changes) { + if (identifier.getText() === oldImport) { + identifier.replaceWithText(newImport); + hasChanges = true; + } + } + }); + + if (hasChanges) { + sourceFile.saveSync(); + log.info(`Updated imports in ${sourceFile.getFilePath()}`); + } + }); +} diff --git a/packages/qwik/src/cli/migrate-v2/run-migration.ts b/packages/qwik/src/cli/migrate-v2/run-migration.ts index f1bbd736a38..7dcdb6c0591 100644 --- a/packages/qwik/src/cli/migrate-v2/run-migration.ts +++ b/packages/qwik/src/cli/migrate-v2/run-migration.ts @@ -3,15 +3,19 @@ import type { AppCommand } from '../utils/app-command'; import { bgMagenta, green } from 'kleur/colors'; import { bye } from '../utils/utils'; import { replacePackage } from './replace-package'; -import { updateDependencies } from './update-dependencies'; +import { + installTsMorph, + removeTsMorphFromPackageJson, + updateDependencies, +} from './update-dependencies'; import { versions } from './versions'; +import { replaceImportInFiles } from './rename-import'; export async function runV2Migration(app: AppCommand) { intro( `✨ ${bgMagenta(' This command will migrate your Qwik application from v1 to v2 \n')}` + `This includes the following: \n` + - // TODO(migrate-v2): package names - ` - "@builder.io/qwik", "@builder.io/qwik-city" packages will be rescoped to "@qwik.dev/core" and "@qwik.dev/city" \n` + + ` - "@builder.io/qwik", "@builder.io/qwik-city" and "@builder.io/qwik-react" packages will be rescoped to "@qwik.dev/core", "@qwik.dev/router" and "@qwik.dev/react" respectively \n` + ` - related dependencies will be updated \n` ); const proceed = await confirm({ @@ -24,8 +28,29 @@ export async function runV2Migration(app: AppCommand) { } try { - replacePackage('@builder.io/qwik-city', '@qwik.dev/city', versions['@qwik.dev/city']); - replacePackage('@builder.io/qwik', '@qwik.dev/qwik', versions['@qwik.dev/qwik']); + const installedTsMorph = await installTsMorph(); + + replaceImportInFiles( + [ + ['QwikCityProvider', 'QwikRouterProvider'], + ['qwikCity', 'qwikRouter'], + ['QwikCityVitePluginOptions', 'QwikRouterVitePluginOptions'], + ['QwikCityPlugin', 'QwikRouterPlugin'], + ['createQwikCity', 'createQwikRouter'], + ['QwikCityNodeRequestOptions', 'QwikRouterNodeRequestOptions'], + ], + '@builder.io/qwik-city' + ); + + replacePackage('@builder.io/qwik-city', '@qwik.dev/router', versions['@qwik.dev/router']); + replacePackage('@builder.io/qwik-react', '@qwik.dev/react', versions['@qwik.dev/react']); + // "@builder.io/qwik" should be the last one because it's name is a substring of the package names above + replacePackage('@builder.io/qwik', '@qwik.dev/core', versions['@qwik.dev/core']); + + if (installedTsMorph) { + await removeTsMorphFromPackageJson(); + } + await updateDependencies(); log.success(`${green(`Your application has been successfully migrated to v2!`)}`); } catch (error) { diff --git a/packages/qwik/src/cli/migrate-v2/update-dependencies.ts b/packages/qwik/src/cli/migrate-v2/update-dependencies.ts index 5312ccd8eb5..2f6a7be09d0 100644 --- a/packages/qwik/src/cli/migrate-v2/update-dependencies.ts +++ b/packages/qwik/src/cli/migrate-v2/update-dependencies.ts @@ -1,6 +1,6 @@ -import { readPackageJson, writePackageJson } from './../utils/utils'; +import { installDeps } from '../utils/install-deps'; +import { getPackageManager, readPackageJson, writePackageJson } from './../utils/utils'; // import { getPackageManager } from './../utils/utils'; -// import { installDeps } from '../utils/install-deps'; import { versions } from './versions'; export async function updateDependencies() { @@ -18,9 +18,29 @@ export async function updateDependencies() { await writePackageJson(process.cwd(), packageJson); // TODO(migrate-v2): not installing dependencies because we don't have correct versions set - // const { install } = installDeps(getPackageManager(), process.cwd()); - // const passed = await install; - // if (!passed) { - // throw new Error('Failed to install dependencies'); - // } + // runInstall(); +} + +export async function installTsMorph() { + const packageJson = await readPackageJson(process.cwd()); + if (packageJson.dependencies?.['ts-morph'] || packageJson.devDependencies?.['ts-morph']) { + return false; + } + (packageJson.devDependencies ??= {})['ts-morph'] = 'latest'; + await runInstall(); + return true; +} + +async function runInstall() { + const { install } = installDeps(getPackageManager(), process.cwd()); + const passed = await install; + if (!passed) { + throw new Error('Failed to install dependencies'); + } +} + +export async function removeTsMorphFromPackageJson() { + const packageJson = await readPackageJson(process.cwd()); + delete packageJson.dependencies?.['ts-morph']; + delete packageJson.devDependencies?.['ts-morph']; } diff --git a/packages/qwik/src/cli/migrate-v2/versions.ts b/packages/qwik/src/cli/migrate-v2/versions.ts index 80d758c2c5c..817643e9ec0 100644 --- a/packages/qwik/src/cli/migrate-v2/versions.ts +++ b/packages/qwik/src/cli/migrate-v2/versions.ts @@ -1,5 +1,6 @@ export const versions = { - '@qwik.dev/qwik': '2.0.0', - '@qwik.dev/city': '2.0.0', + '@qwik.dev/core': '2.0.0', + '@qwik.dev/router': '2.0.0', + '@qwik.dev/react': '2.0.0', 'eslint-plugin-qwik': '2.0.0', }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46dd8352a31..082d115acf5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -577,6 +577,9 @@ importers: prettier: specifier: 3.3.3 version: 3.3.3 + ts-morph: + specifier: 23.0.0 + version: 23.0.0 vitest: specifier: 2.1.4 version: 2.1.4(@types/node@20.14.11)(terser@5.36.0) @@ -3136,6 +3139,9 @@ packages: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} + '@ts-morph/common@0.24.0': + resolution: {integrity: sha512-c1xMmNHWpNselmpIqursHeOHHBTIsJLbB+NuovbTTRCNiTLEr/U9dbJ8qy0jd/O2x5pc3seWuOUN5R2IoOTp8A==} + '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -4187,6 +4193,9 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -7019,6 +7028,11 @@ packages: engines: {node: '>=10'} hasBin: true + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mlly@1.7.0: resolution: {integrity: sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==} @@ -8880,6 +8894,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-morph@23.0.0: + resolution: {integrity: sha512-FcvFx7a9E8TUe6T3ShihXJLiJOiqyafzFKUO4aqIHDUCIvADdGNShcbc2W5PMr3LerXRv7mafvFZ9lRENxJmug==} + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -11970,6 +11987,13 @@ snapshots: '@trysound/sax@0.2.0': {} + '@ts-morph/common@0.24.0': + dependencies: + fast-glob: 3.3.2 + minimatch: 9.0.4 + mkdirp: 3.0.1 + path-browserify: 1.0.1 + '@tsconfig/node10@1.0.11': {} '@tsconfig/node12@1.0.11': {} @@ -13245,6 +13269,8 @@ snapshots: clsx@2.1.1: {} + code-block-writer@13.0.3: {} + collapse-white-space@2.1.0: {} color-convert@1.9.3: @@ -16682,6 +16708,8 @@ snapshots: mkdirp@1.0.4: {} + mkdirp@3.0.1: {} + mlly@1.7.0: dependencies: acorn: 8.11.3 @@ -18832,6 +18860,11 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-morph@23.0.0: + dependencies: + '@ts-morph/common': 0.24.0 + code-block-writer: 13.0.3 + ts-node@10.9.2(@types/node@20.14.11)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1