Skip to content

Commit

Permalink
Feature/cli improvements 4 (#3110)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugos68 authored Jan 16, 2025
1 parent f465fe6 commit 11edc8d
Show file tree
Hide file tree
Showing 21 changed files with 213 additions and 175 deletions.
5 changes: 5 additions & 0 deletions .changeset/little-wolves-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@skeletonlabs/skeleton-cli': patch
---

Bugfix (migrate/skeleton-3): Renaming components no longer includes a bug that could cause invalid components to be produced in bigger components
5 changes: 5 additions & 0 deletions .changeset/quiet-rabbits-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@skeletonlabs/skeleton-cli': patch
---

Bugfix (migrate/skeleton-3): Empty strings no longer crash the `transformSvelte` function
5 changes: 5 additions & 0 deletions .changeset/tall-toes-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@skeletonlabs/skeleton-cli': patch
---

Bugfix: Fixed longstanding bug of svelte files not being transformed properly.
5 changes: 5 additions & 0 deletions .changeset/ten-falcons-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@skeletonlabs/skeleton-cli': patch
---

Feature (migrate/skeleton-3): `TabGroup` is now renamed to `Tabs`
5 changes: 5 additions & 0 deletions .changeset/three-mirrors-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@skeletonlabs/skeleton-cli': patch
---

Feature (migrate/skeleton-3): All file transformations are batched to write to disk at the end of the migration, this will prevent any files being written to disk if any of the file transformations fail.
2 changes: 1 addition & 1 deletion packages/skeleton-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@
"colorette": "^2.0.20",
"commander": "^13.0.0",
"detect-indent": "^7.0.1",
"esrap": "^1.4.3",
"fast-glob": "^3.3.1",
"latest-version": "^9.0.0",
"magic-string": "^0.30.17",
"nanoid": "^5.0.9",
"package-manager-detector": "^0.2.8",
"semver": "^7.6.3",
"svelte": "^5.17.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fg from 'fast-glob';
import { transformTailwindConfig } from './transformers/transform-tailwind-config.js';
import { transformPackage } from './transformers/transform-package.js';
import type { MigrateOptions } from '../../index.js';
import { isCancel, multiselect, spinner } from '@clack/prompts';
import { isCancel, log, multiselect, spinner } from '@clack/prompts';
import { cli } from '../../../../index.js';
import { extname } from 'node:path';
import { transformSvelte } from './transformers/transform-svelte.js';
Expand All @@ -13,94 +13,101 @@ import { installDependencies } from '../../../../utility/install-dependencies.js
import { FALLBACK_THEME } from './utility/constants';
import getLatestVersion from 'latest-version';

interface FileMigration {
path: string;
content: string;
}

export default async function (options: MigrateOptions) {
const cwd = options.cwd ?? process.cwd();
const migrations: FileMigration[] = [];

// Find all required files
const pkg = {
matcher: 'package.json',
name: 'package.json',
paths: await fg('package.json', { cwd })
};
const tailwindConfig = {
matcher: 'tailwind.config.{js,mjs,ts,mts}',
name: 'tailwind.config',
paths: await fg('tailwind.config.{js,mjs,ts,mts}', { cwd })
};
const app = {
matcher: 'src/app.html',
name: 'app.html',
paths: await fg('src/app.html', { cwd })
};

// Validate file existence
for (const file of [pkg, tailwindConfig, app]) {
if (file.paths.length === 0) {
cli.error(`"${file.matcher}" not found in directory "${cwd}".`);
cli.error(`"${file.name}" not found in directory "${cwd}".`);
}
if (file.paths.length > 1) {
cli.error(`Multiple "${file.matcher}" entries found in directory: "${cwd}", please ensure there is only one`);
cli.error(`Multiple "${file.name}" entries found in directory: "${cwd}", please ensure there is only one`);
}
}

// Get source folders
const availableSourceFolders = await fg('*', {
cwd: cwd,
onlyDirectories: true,
ignore: ['node_modules']
});
const sourceFolders = await multiselect({
message: 'What folders contain usage of Skeleton? (classes, imports, etc.)',
message: 'What folders make use of Skeleton? (classes, imports, etc.)',
options: availableSourceFolders.map((folder) => ({ label: folder, value: folder })),
initialValues: availableSourceFolders
});

if (isCancel(sourceFolders)) {
cli.error('Migration cancelled by user.');
cli.error('Migration canceled, nothing written to disk');
return;
}

let theme: string | null = null;

// Migrate package.json
const packageSpinner = spinner();
packageSpinner.start(`Migrating ${pkg.matcher}...`);
packageSpinner.start(`Migrating ${pkg.name}...`);
try {
const pkgCode = await readFile(pkg.paths[0], 'utf-8');
const skeletonVersion = await getLatestVersion('@skeletonlabs/skeleton', { version: '>=3.0.0-0 <4.0.0' });
const skeletonSvelteVersion = await getLatestVersion('@skeletonlabs/skeleton-svelte', { version: '>=1.0.0-0 <2.0.0' });
const transformedPkg = transformPackage(pkgCode, skeletonVersion, skeletonSvelteVersion);
await writeFile(pkg.paths[0], transformedPkg.code);
packageSpinner.stop(`Successfully migrated ${pkg.matcher}`);
migrations.push({ path: pkg.paths[0], content: transformedPkg.code });
packageSpinner.stop(`Successfully migrated ${pkg.name}!`);
} catch (e) {
if (e instanceof Error) {
packageSpinner.stop(`Failed to migrate ${pkg.matcher}: ${e.message}`);
}
packageSpinner.stop(`Failed to migrate ${pkg.matcher}`);
packageSpinner.stop(`Failed to migrate ${pkg.name}: ${e instanceof Error ? e.message : 'Unknown error'}`, 1);
cli.error('Migration canceled, nothing written to disk');
}

let theme: string | null = null;

// Migrate tailwind config
const tailwindSpinner = spinner();
tailwindSpinner.start(`Migrating ${tailwindConfig.matcher}...`);
tailwindSpinner.start(`Migrating ${tailwindConfig.name}...`);
try {
const tailwindCode = await readFile(tailwindConfig.paths[0], 'utf-8');
const transformedTailwind = transformTailwindConfig(tailwindCode);
theme = transformedTailwind.meta.themes.preset.at(0) ?? null;
await writeFile(tailwindConfig.paths[0], transformedTailwind.code);
tailwindSpinner.stop(`Successfully migrated ${tailwindConfig.matcher}`);
migrations.push({ path: tailwindConfig.paths[0], content: transformedTailwind.code });
tailwindSpinner.stop(`Successfully migrated ${tailwindConfig.name}!`);
} catch (e) {
if (e instanceof Error) {
tailwindSpinner.stop(`Failed to migrate ${tailwindConfig.matcher}: ${e.message}`);
}
tailwindSpinner.stop(`Failed to migrate ${tailwindConfig.matcher}`);
tailwindSpinner.stop(`Failed to migrate ${tailwindConfig.name}: ${e instanceof Error ? e.message : 'Unknown error'}`, 1);
cli.error('Migration canceled, nothing written to disk');
}

// Migrate app.html
const appSpinner = spinner();
appSpinner.start(`Migrating ${app.matcher}...`);
appSpinner.start(`Migrating ${app.name}...`);
try {
const appCode = await readFile(app.paths[0], 'utf-8');
const transformedApp = transformApp(appCode, theme ?? FALLBACK_THEME);
await writeFile(app.paths[0], transformedApp.code);
appSpinner.stop(`Successfully migrated ${app.matcher}!`);
migrations.push({ path: app.paths[0], content: transformedApp.code });
appSpinner.stop(`Successfully migrated ${app.name}!`);
} catch (e) {
if (e instanceof Error) {
appSpinner.stop(`Failed to migrate ${app.matcher}: ${e.message}`);
}
appSpinner.stop(`Failed to migrate ${app.matcher}.`);
appSpinner.stop(`Failed to migrate ${app.name}: ${e instanceof Error ? e.message : 'Unknown error'}`, 1);
cli.error('Migration canceled, nothing written to disk');
}

// Migrate source files
const sourceFileMatcher = `{${sourceFolders.join(',')}}/**/*.{js,mjs,ts,mts,svelte}`;
const sourceFiles = await fg(sourceFileMatcher, {
cwd: cwd,
Expand All @@ -109,38 +116,47 @@ export default async function (options: MigrateOptions) {

const sourceFilesSpinner = spinner();
sourceFilesSpinner.start(`Migrating source files...`);
try {
for (const sourceFile of sourceFiles) {
sourceFilesSpinner.message(`Migrating ${sourceFile}...`);
const extension = extname(sourceFile);
for (const sourceFile of sourceFiles) {
sourceFilesSpinner.message(`Migrating ${sourceFile}...`);
const extension = extname(sourceFile);
try {
const code = await readFile(sourceFile, 'utf-8');
if (extension === '.svelte') {
const svelteCode = await readFile(sourceFile, 'utf-8');
const transformedSvelte = transformSvelte(svelteCode);
await writeFile(sourceFile, transformedSvelte.code);
const transformedSvelte = transformSvelte(code);
migrations.push({ path: sourceFile, content: transformedSvelte.code });
} else {
const moduleCode = await readFile(sourceFile, 'utf-8');
const transformedModule = transformModule(moduleCode);
await writeFile(sourceFile, transformedModule.code);
const transformedModule = transformModule(code);
migrations.push({ path: sourceFile, content: transformedModule.code });
}
sourceFilesSpinner.message(`Successfully migrated ${sourceFile}!`);
} catch (e) {
sourceFilesSpinner.stop(`Failed to migrate ${sourceFile}: ${e instanceof Error ? e.message : 'Unknown error'}`, 1);
cli.error('Migration canceled, nothing written to disk');
}
sourceFilesSpinner.stop('Successfully migrated all source files!');
} catch (error) {
if (error instanceof Error) {
sourceFilesSpinner.stop(`Failed to migrate source files: ${error.message}`);
}
sourceFilesSpinner.stop('Failed to migrate source files.');
}
sourceFilesSpinner.stop('Successfully migrated all source files!');

// Write all migrations to disk
const writeSpinner = spinner();
writeSpinner.start('Applying all migrations...');
try {
await Promise.all(migrations.map(({ path, content }) => writeFile(path, content)));
writeSpinner.stop('Successfully applied all migrations!');
} catch (e) {
writeSpinner.stop(`Failed to apply migrations: ${e instanceof Error ? e.message.replace('\n', ' ') : 'Unknown error'}`, 1);
cli.error('Migration canceled');
}

// Install dependencies
const installDependenciesSpinner = spinner();
installDependenciesSpinner.start('Installing dependencies...');
installDependenciesSpinner.start('Updating dependencies...');
try {
await installDependencies(cwd);
installDependenciesSpinner.stop('Successfully installed dependencies!');
installDependenciesSpinner.stop('Successfully updated dependencies!');
} catch (e) {
if (e instanceof Error) {
installDependenciesSpinner.stop(`Failed to install dependencies: ${e.message}`);
}
installDependenciesSpinner.stop('Failed to install dependencies.');
installDependenciesSpinner.stop(`Failed to update dependencies: ${e instanceof Error ? e.message : 'Unknown error'}`, 1);
cli.error('Migration canceled');
return;
}
log.info('Migration complete! Visit https://skeleton.dev for more information.');
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import { Node } from 'ts-morph';
import { addNamedImport } from '../../../../../utility/ts-morph/add-named-import';
import { parseSourceFile } from '../../../../../utility/ts-morph/parse-source-file';

function transformModule(code: string) {
interface TransformModuleOptions {
fixUnusedIdentifiers?: boolean;
}

function transformModule(code: string, options: TransformModuleOptions = {}) {
const file = parseSourceFile(code);
const fixUnusedIdentifiers = options.fixUnusedIdentifiers ?? true;
file.forEachDescendant((node) => {
if (Node.isImportDeclaration(node)) {
const moduleSpecifier = node.getModuleSpecifier();
Expand Down Expand Up @@ -44,7 +49,9 @@ function transformModule(code: string) {
node.setLiteralValue(transformClasses(node.getLiteralValue()).code);
}
});
file.fixUnusedIdentifiers();
if (fixUnusedIdentifiers) {
file.fixUnusedIdentifiers();
}
return {
code: file.getFullText()
};
Expand Down
Loading

0 comments on commit 11edc8d

Please sign in to comment.