Skip to content

Commit

Permalink
Merge pull request #7054 from QwikDev/ds/cli-migrate-v2-cmd-for-v1
Browse files Browse the repository at this point in the history
feat: add migrate-v2 command
  • Loading branch information
shairez authored Nov 17, 2024
2 parents cda8c72 + 46a8303 commit cbc2dd5
Show file tree
Hide file tree
Showing 10 changed files with 618 additions and 41 deletions.
4 changes: 3 additions & 1 deletion packages/qwik/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"@builder.io/qwik": "workspace:^",
"@builder.io/qwik-dom": "workspace:^",
"image-size": "1.1.1",
"kleur": "4.1.5"
"kleur": "4.1.5",
"ignore": "5.3.1",
"ts-morph": "23.0.0"
},
"engines": {
"node": ">=16.8.0 <18.0.0 || >=18.11"
Expand Down
49 changes: 49 additions & 0 deletions packages/qwik/src/cli/migrate-v2/rename-import.ts
Original file line number Diff line number Diff line change
@@ -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()}`);
}
});
}
85 changes: 85 additions & 0 deletions packages/qwik/src/cli/migrate-v2/replace-package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { basename } from 'path';
import { isBinaryPath } from './tools/binary-extensions';
import { visitNotIgnoredFiles } from './tools/visit-not-ignored-files';
import { readFileSync, writeFileSync } from 'fs';
import { log } from '@clack/prompts';

function updateFileContent(path: string, content: string) {
writeFileSync(path, content);
log.info(`"${path}" has been updated`);
}

export function replacePackage(
oldPackageName: string,
newPackageName: string,
newPackageVersion: string
): void {
replacePackageInDependencies(oldPackageName, newPackageName, newPackageVersion);

replaceMentions(oldPackageName, newPackageName);
}

function replacePackageInDependencies(
oldPackageName: string,
newPackageName: string,
newPackageVersion: string
) {
visitNotIgnoredFiles('.', (path) => {
if (basename(path) !== 'package.json') {
return;
}

try {
const packageJson = JSON.parse(readFileSync(path, 'utf-8'));
for (const deps of [
packageJson.dependencies ?? {},
packageJson.devDependencies ?? {},
packageJson.peerDependencies ?? {},
packageJson.optionalDependencies ?? {},
]) {
if (oldPackageName in deps) {
deps[newPackageName] = newPackageVersion;
delete deps[oldPackageName];
}
}
updateFileContent(path, JSON.stringify(packageJson, null, 2));
} catch (e) {
console.warn(`Could not replace ${oldPackageName} with ${newPackageName} in ${path}.`);
}
});
}

function replaceMentions(oldPackageName: string, newPackageName: string) {
visitNotIgnoredFiles('.', (path) => {
if (isBinaryPath(path)) {
return;
}

const ignoredFiles = [
'yarn.lock',
'package-lock.json',
'pnpm-lock.yaml',
'bun.lockb',
'CHANGELOG.md',
];
if (ignoredFiles.includes(basename(path))) {
return;
}

try {
const contents = readFileSync(path, 'utf-8');

if (!contents.includes(oldPackageName)) {
return;
}

updateFileContent(path, contents.replace(new RegExp(oldPackageName, 'g'), newPackageName));
} catch {
// Its **probably** ok, contents can be null if the file is too large or
// there was an access exception.
log.warn(
`An error was thrown when trying to update ${path}. If you believe the migration should have updated it, be sure to review the file and open an issue.`
);
}
});
}
60 changes: 60 additions & 0 deletions packages/qwik/src/cli/migrate-v2/run-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { confirm, intro, isCancel, log } from '@clack/prompts';
import type { AppCommand } from '../utils/app-command';
import { bgMagenta, green } from 'kleur/colors';
import { bye } from '../utils/utils';
import { replacePackage } from './replace-package';
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` +
` - "@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({
message: 'Do you want to proceed?',
initialValue: true,
});

if (isCancel(proceed) || !proceed) {
bye();
}

try {
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) {
console.error(error);
throw error;
}
}
Loading

0 comments on commit cbc2dd5

Please sign in to comment.