Skip to content

Commit

Permalink
Feature: app.html is now transformed.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugos68 committed Jan 14, 2025
1 parent 844ef63 commit 023081d
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 108 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-news-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@skeletonlabs/skeleton-cli': patch
---

Feature: `app.html` is now transformed.
3 changes: 3 additions & 0 deletions packages/skeleton-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"test:watch": "vitest watch"
},
"devDependencies": {
"@types/jsdom": "^21.1.7",
"@types/node": "^22.1.0",
"@types/semver": "^7.5.8",
"type-fest": "^4.27.0",
Expand All @@ -42,9 +43,11 @@
"detect-indent": "^7.0.1",
"estree-walker": "^3.0.3",
"fast-glob": "^3.3.1",
"jsdom": "^25.0.1",
"latest-version": "^9.0.0",
"magic-string": "^0.30.17",
"package-manager-detector": "^0.2.8",
"parse5": "^7.2.1",
"semver": "^7.6.3",
"svelte": "^5.17.3",
"ts-morph": "^25.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { cli } from '../../../../index.js';
import { extname } from 'node:path';
import { transformSvelte } from './transformers/transform-svelte';
import { transformModule } from './transformers/transform-module';
import { transformApp } from './transformers/transform-app';
import { readFile, writeFile } from 'node:fs/promises';

export default async function (options: MigrateOptions) {
const cwd = options.cwd ?? process.cwd();
Expand All @@ -19,8 +21,12 @@ export default async function (options: MigrateOptions) {
matcher: 'tailwind.config.{js,mjs,ts,mts}',
paths: await fg('tailwind.config.{js,mjs,ts,mts}', { cwd })
};
const app = {
matcher: 'src/app.html',
paths: await fg('src/app.html', { cwd })
};

for (const file of [pkg, tailwindConfig]) {
for (const file of [pkg, tailwindConfig, app]) {
if (file.paths.length === 0) {
cli.error(`"${file.matcher}" not found in directory "${cwd}".`);
}
Expand All @@ -43,13 +49,26 @@ export default async function (options: MigrateOptions) {

const packageSpinner = spinner();
packageSpinner.start(`Migrating ${pkg.matcher}...`);
await transformPackage(pkg.paths[0]);
const pkgCode = await readFile(pkg.paths[0], 'utf-8');
const transformedPkg = await transformPackage(pkgCode);
await writeFile(pkg.paths[0], transformedPkg.code);
packageSpinner.stop(`Successfully migrated ${pkg.matcher}`);

const tailwindConfigSpinner = spinner();
tailwindConfigSpinner.start(`Migrating ${tailwindConfig.matcher}...`);
await transformTailwindConfig(tailwindConfig.paths[0]);
tailwindConfigSpinner.stop(`Successfully migrated ${tailwindConfig.matcher}`);
const tailwindSpinner = spinner();
tailwindSpinner.start(`Migrating ${tailwindConfig.matcher}...`);
const tailwindCode = await readFile(tailwindConfig.paths[0], 'utf-8');
const transformedTailwind = transformTailwindConfig(tailwindCode);
await writeFile(tailwindConfig.paths[0], transformedTailwind.code);
tailwindSpinner.stop(`Successfully migrated ${tailwindConfig.matcher}`);

const theme = transformedTailwind.meta.themes.find((theme) => theme.type === 'preset');

const appSpinner = spinner();
appSpinner.start(`Migrating ${app.matcher}...`);
const appCode = await readFile(app.paths[0], 'utf-8');
const transformedApp = transformApp(appCode, theme?.name ?? 'cerberus');
await writeFile(app.paths[0], transformedApp.code);
appSpinner.stop(`Successfully migrated ${app.matcher}`);

const sourceFileMatcher = `{${sourceFolders.join(',')}}/**/*.{js,mjs,ts,mts,svelte}`;
const sourceFiles = await fg(sourceFileMatcher, { cwd, ignore: ['node_modules', 'dist', 'build', 'public'] });
Expand All @@ -60,9 +79,13 @@ export default async function (options: MigrateOptions) {
sourceFilesSpinner.message(`Migrating ${sourceFile}...`);
const extension = extname(sourceFile);
if (extension === '.svelte') {
await transformSvelte(sourceFile);
const svelteCode = await readFile(sourceFile, 'utf-8');
const transformedSvelte = transformSvelte(svelteCode);
await writeFile(sourceFile, transformedSvelte.code);
} else {
await transformModule(sourceFile);
const moduleCode = await readFile(sourceFile, 'utf-8');
const transformedModule = transformModule(moduleCode);
await writeFile(sourceFile, transformedModule.code);
}
sourceFilesSpinner.message(`Successfully migrated ${sourceFile}`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest';
import { transformApp } from './transform-app.js';

describe('transformsApp', () => {
it('edits the `data-them` attribute when already present', () => {
expect(
transformApp(
`
<html lang="en">
<head><title>foo</title></head>
<body data-theme="skeleton"></body>
</html>
`,
'cerberus'
)
.code.trim()
.replace(/\r\n|\r|\n/g, '\n')
).toBe(
`
<html lang="en">
<head><title>foo</title></head>
<body data-theme="cerberus"></body>
</html>
`
.trim()
.replace(/\r\n|\r|\n/g, '\n')
);
});
it('adds the `data-theme` attribute ', () => {
expect(
transformApp(
`
<html lang="en">
<head><title>foo</title></head>
<body></body>
</html>
`,
'cerberus'
)
.code.trim()
.replace(/\r\n|\r|\n/g, '\n')
).toBe(
`
<html lang="en">
<head><title>foo</title></head>
<body data-theme="cerberus"></body>
</html>
`
.trim()
.replace(/\r\n|\r|\n/g, '\n')
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { AST, parse } from 'svelte/compiler';
import { walk, type Node } from 'estree-walker';
import MagicString from 'magic-string';

function transformApp(code: string, theme: string) {
const s = new MagicString(code);
const ast = parse(code, {
modern: true
});
walk(ast.fragment as unknown as Node, {
enter(node: AST.SvelteNode) {
if (node.type === 'RegularElement' && node.name === 'body') {
const dataThemeAttribute = node.attributes.find((attribute) => {
return attribute.type === 'Attribute' && attribute.name === 'data-theme';
});
if (dataThemeAttribute) {
s.update(dataThemeAttribute.start, dataThemeAttribute.end, `data-theme="${theme}"`);
} else {
s.appendRight(node.start + '<body'.length, ` data-theme="${theme}"`);
}
}
}
});

return {
code: s.toString()
};
}

export { transformApp };
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { describe, expect, it } from 'vitest';
import { transformModuleContent } from './transform-module.js';
import { transformModule } from './transform-module.js';

describe('transformModuleContent', () => {
it('transforms imports', () => {
expect(
transformModuleContent(`
transformModule(`
import { Avatar } from "@skeletonlabs/skeleton";
Avatar;
`)
.trim()
.code.trim()
.replace(/\r\n|\r|\n/g, '\n')
).toBe(
`
Expand All @@ -23,10 +23,10 @@ Avatar;
});
it('transforms classes in strings', () => {
expect(
transformModuleContent(`
transformModule(`
const foo = "rounded-token";
`)
.trim()
.code.trim()
.replace(/\r\n|\r|\n/g, '\n')
).toBe(
`
Expand All @@ -38,12 +38,12 @@ const foo = "rounded";
});
it('does not transform classes in imports', () => {
expect(
transformModuleContent(`
transformModule(`
import foo from "rounded-token";
foo;
`)
.trim()
.code.trim()
.replace(/\r\n|\r|\n/g, '\n')
).toBe(
`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { readFile, writeFile } from 'node:fs/promises';
import { Node } from 'ts-morph';
import { transformClasses } from './transform-classes.js';
import { createSourceFile } from '../../../../../utility/create-source-file.js';

function transformModuleContent(code: string) {
function transformModule(code: string) {
const file = createSourceFile(code);
for (const importDeclaration of file.getImportDeclarations()) {
const moduleSpecifier = importDeclaration.getModuleSpecifier();
Expand All @@ -18,13 +17,9 @@ function transformModuleContent(code: string) {
node.setLiteralValue(transformClasses(node.getLiteralValue()));
});
file.fixUnusedIdentifiers();
return file.getFullText();
return {
code: file.getFullText()
};
}

async function transformModule(path: string) {
const code = await readFile(path, 'utf-8');
const transformed = transformModuleContent(code);
await writeFile(path, transformed);
}

export { transformModuleContent, transformModule };
export { transformModule };
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { transformPackageContent } from './transform-package.js';
import { transformPackage } from './transform-package.js';
import getLatestVersion from 'latest-version';
import { describe, it, expect, vi } from 'vitest';

Expand All @@ -8,19 +8,19 @@ vi.mock('latest-version', () => {
};
});

describe('transformPackageContent', () => {
describe('transformPackage', () => {
it('updates the "@skeletonlabs/tw-plugin" dependency', async () => {
vi.mocked(getLatestVersion).mockReturnValue(Promise.resolve('3.0.0'));
expect(
(
await transformPackageContent(`
await transformPackage(`
{
"dependencies": {
"@skeletonlabs/tw-plugin": "^1.0.0"
}
}
`)
)
).code
.trim()
.replace(/\r\n|\r|\n/g, '\n')
).toBe(
Expand All @@ -40,14 +40,14 @@ describe('transformPackageContent', () => {

expect(
(
await transformPackageContent(`
await transformPackage(`
{
"dependencies": {
"@skeletonlabs/skeleton": "^2.0.0"
}
}
`)
)
).code
.trim()
.replace(/\r\n|\r|\n/g, '\n')
).toBe(
Expand All @@ -74,15 +74,15 @@ describe('transformPackageContent', () => {
});
expect(
(
await transformPackageContent(`
await transformPackage(`
{
"dependencies": {
"@skeletonlabs/tw-plugin": "^1.0.0",
"@skeletonlabs/skeleton": "^2.0.0"
}
}
`)
)
).code
.trim()
.replace(/\r\n|\r|\n/g, '\n')
).toBe(
Expand All @@ -102,14 +102,14 @@ describe('transformPackageContent', () => {
vi.mocked(getLatestVersion).mockReturnValue(Promise.resolve('3.0.0'));
expect(
(
await transformPackageContent(`
await transformPackage(`
{
"dependencies": {
"@skeletonlabs/skeleton": "^3.0.0"
}
}
`)
)
).code
.trim()
.replace(/\r\n|\r|\n/g, '\n')
).toBe(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { readFile, writeFile } from 'fs/promises';
import type { PackageJson } from 'type-fest';
import getLatestVersion from 'latest-version';
import { coerce, lt } from 'semver';
import { sortPropertiesAlphabetically } from '../../../../../utility/sort-properties-alphabetically';
import detectIndent from 'detect-indent';

async function transformPackageContent(code: string) {
async function transformPackage(code: string) {
const pkg = JSON.parse(code) as PackageJson;
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' });
Expand All @@ -24,13 +23,9 @@ async function transformPackageContent(code: string) {
}
pkg[field] = sortPropertiesAlphabetically(pkg[field] as Record<string, string>);
}
return JSON.stringify(pkg, null, detectIndent(code).indent || '\t');
return {
code: JSON.stringify(pkg, null, detectIndent(code).indent || '\t')
};
}

async function transformPackage(path: string) {
const code = await readFile(path, 'utf-8');
const transformed = await transformPackageContent(code);
await writeFile(path, transformed);
}

export { transformPackageContent, transformPackage };
export { transformPackage };
Loading

0 comments on commit 023081d

Please sign in to comment.