From 67260743de9750471990c33f9dcebe9d4c065ef8 Mon Sep 17 00:00:00 2001 From: Dwayne Charrington Date: Fri, 23 Jan 2026 17:25:59 +1000 Subject: [PATCH 1/3] feat(pnpm): generate .npmrc when using pnpm Adds automatic creation/merging of a project-level .npmrc when pnpm is selected, ensuring required settings are present. Introduces a helper to write default pnpm config and safely append it if missing or incomplete. Preserves existing content and supports in-project setups. Updates tests to verify .npmrc creation in the project root during setup. Removes default in-repo .npmrc in common. --- __test__/after-task.spec.js | 45 +++++++++++++++++++++++++++++++++++++ after.js | 27 ++++++++++++++++++++++ common/.npmrc | 3 --- 3 files changed, 72 insertions(+), 3 deletions(-) delete mode 100644 common/.npmrc diff --git a/__test__/after-task.spec.js b/__test__/after-task.spec.js index f549d21..598f32b 100644 --- a/__test__/after-task.spec.js +++ b/__test__/after-task.spec.js @@ -1,4 +1,7 @@ const test = require('ava'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); const after = require('../after'); const ansiColors = (m) => m; @@ -196,6 +199,48 @@ test('"after" task installs deps with pnpm, and prints summary', async t => { ); }); +test.serial('"after" task writes pnpm .npmrc when pnpm is selected', async t => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aurelia-pnpm-')); + const cwd = process.cwd(); + t.teardown(() => { + process.chdir(cwd); + fs.rmSync(tempDir, {recursive: true, force: true}); + }); + process.chdir(tempDir); + + const prompts = { + select(opts) { + t.deepEqual(opts.choices.map(c => c.value), ['npm', 'yarn', 'pnpm', undefined]); + return 'pnpm'; + } + }; + + function run(cmd, args) { + t.is(cmd, 'pnpm'); + t.deepEqual(args, ['install']); + } + + await after({ + unattended: false, + here: true, + prompts, + run, + properties: {name: 'my-app'}, + features: [], + notDefaultFeatures: [], + ansiColors + }, { + _isAvailable: isAvailable, + _log() {} + }); + + const npmrcPath = path.join(tempDir, '.npmrc'); + t.true(fs.existsSync(npmrcPath)); + const content = fs.readFileSync(npmrcPath, 'utf8'); + t.true(content.includes('shamefully-hoist=true')); + t.true(content.includes('auto-install-peers=true')); +}); + test('"after" task installs deps, and prints summary in here mode', async t => { const prompts = { select(opts) { diff --git a/after.js b/after.js index c1db531..f1f3a29 100644 --- a/after.js +++ b/after.js @@ -4,6 +4,13 @@ const {execSync} = require('child_process'); const fs = require('fs'); const path = require('path'); +const PNPM_NPMRC = [ + '# for pnpm, use flat node_modules', + 'shamefully-hoist=true', + 'auto-install-peers=true', + '' +].join('\n'); + function isAvailable(bin) { try { execSync(bin + ' -v', {stdio: 'ignore'}); @@ -23,6 +30,7 @@ module.exports = async function({ const c = ansiColors; let depsInstalled = false; let packageManager = undefined; + const projectDir = here ? '.' : properties.name; if (!unattended) { const choices = [ {value: 'npm', title: 'Yes, use npm'} ]; @@ -43,6 +51,9 @@ module.exports = async function({ }); if (packageManager) { + if (packageManager === 'pnpm') { + ensurePnpmrc(projectDir); + } await run(packageManager, ['install']); if (features.includes('playwright')) { @@ -107,3 +118,19 @@ module.exports = async function({ } _log((packageManager ?? 'npm') + ' start\n'); }; + +function ensurePnpmrc(projectDir) { + if (!projectDir || !fs.existsSync(projectDir)) return; + + const npmrcPath = path.join(projectDir, '.npmrc'); + if (!fs.existsSync(npmrcPath)) { + fs.writeFileSync(npmrcPath, PNPM_NPMRC); + return; + } + + const existing = fs.readFileSync(npmrcPath, 'utf8'); + if (existing.includes('shamefully-hoist=') && existing.includes('auto-install-peers=')) return; + + const spacer = existing.endsWith('\n') ? '' : '\n'; + fs.writeFileSync(npmrcPath, existing + spacer + PNPM_NPMRC); +} diff --git a/common/.npmrc b/common/.npmrc deleted file mode 100644 index f8f5435..0000000 --- a/common/.npmrc +++ /dev/null @@ -1,3 +0,0 @@ -# for pnpm, use flat node_modules -shamefully-hoist=true -auto-install-peers=true From 82ad753fc184b5ed7894478f66286cf770e02bc5 Mon Sep 17 00:00:00 2001 From: Dwayne Charrington Date: Sat, 24 Jan 2026 09:47:01 +1000 Subject: [PATCH 2/3] feat(cli): introduce new presets and vite support Adds new lean presets and minimal app presets powered by Vite, updating existing presets to use Vite/Vitest instead of Webpack. Introduces Vite-based plugin support with a CSS-injection mechanism to embed CSS into JS for Shadow DOM builds. Extends CLI prompts and samples to reflect the new presets, adds blank/minimal/router scaffolds, and updates router-tailwind samples accordingly. Aligns tests, lint rules, and docs with the new tooling and bump Storybook integration to compatible versions. --- README.md | 8 +++ __test__/before-task.spec.js | 49 +++++++++++++++++-- app-blank/src/my-app.ext | 2 + app-blank/src/my-app.html | 1 + .../src/my-app.stories.ext__if_storybook | 29 +++++++++++ .../my-app.spec.ext | 20 ++++++++ app-min/src/my-app.html | 12 ++--- app-with-router/src/about-page.html | 20 ++------ app-with-router/src/missing-page.ext | 3 -- app-with-router/src/missing-page.html | 12 ----- app-with-router/src/my-app.ext | 1 - app-with-router/src/my-app.html | 20 +++----- app-with-router/src/welcome-page.html | 16 ++---- before.js | 32 +++++++----- common/README.md__skip-if-exists | 29 +++++++++++ common/package.json | 2 +- plugin-min/dev-app/main.ext | 4 ++ questions.js | 5 +- vite/package.json | 2 +- vite/vite.config.ext | 42 ++++++++++++++++ webpack/package.json | 2 +- 21 files changed, 225 insertions(+), 86 deletions(-) create mode 100644 app-blank/src/my-app.ext create mode 100644 app-blank/src/my-app.html create mode 100644 app-blank/src/my-app.stories.ext__if_storybook create mode 100644 app-blank/test__if_not_no-unit-tests/my-app.spec.ext delete mode 100644 app-with-router/src/missing-page.ext delete mode 100644 app-with-router/src/missing-page.html diff --git a/README.md b/README.md index 8e526b0..c69735e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,14 @@ npx makes aurelia This will cause `npx` to download the `makes` tool, along with the `aurelia` scaffold from this repo, which it will use to guide you through creating your project. +## Presets and samples + +Use the preset picker to quickly choose a profile, including **Lean Modern Frontend** (TypeScript + Vite + Tailwind + Vitest + Storybook). When picking sample code, you can select **Blank app** for a clean, empty app shell (no demo markup) or use the minimal/router samples as before. + +## Plugin projects (Vite + Webpack) + +Plugin templates support Vite or Webpack. The Vite plugin build uses Vite's library mode (Rollup under the hood) and injects component CSS into the JS bundle so consumers don't need to import a separate CSS file. The dev-app still runs on the selected bundler for local testing. + ## Development There are some tests for this skeleton, setup in package.json. (totally not required by makes) diff --git a/__test__/before-task.spec.js b/__test__/before-task.spec.js index 19576d2..4f1ba5f 100644 --- a/__test__/before-task.spec.js +++ b/__test__/before-task.spec.js @@ -44,6 +44,50 @@ test('"before" task can select default-typescript preset', async t => { }); }); +test('"before" task can select minimal-esnext preset', async t => { + const prompts = { + select(opts) { + t.truthy(opts.choices.find(c => c.value === 'minimal-esnext')); + return 'minimal-esnext'; + } + }; + + const result = await before({unattended: false, prompts}); + t.deepEqual(result, { + silentQuestions: true, + preselectedFeatures: ['app', 'vite', 'babel', 'no-unit-tests', 'app-blank', 'css'] + }); +}); + +test('"before" task can select minimal-typescript preset', async t => { + const prompts = { + select(opts) { + t.truthy(opts.choices.find(c => c.value === 'minimal-typescript')); + return 'minimal-typescript'; + } + }; + + const result = await before({unattended: false, prompts}); + t.deepEqual(result, { + silentQuestions: true, + preselectedFeatures: ['app', 'vite', 'typescript', 'no-unit-tests', 'app-blank', 'css'] + }); +}); + +test('"before" task can select lean-modern-frontend preset', async t => { + const prompts = { + select(opts) { + t.truthy(opts.choices.find(c => c.value === 'lean-modern-frontend')); + return 'lean-modern-frontend'; + } + }; + + const result = await before({unattended: false, prompts}); + t.deepEqual(result, { + silentQuestions: true, + preselectedFeatures: ['app', 'vite', 'typescript', 'vitest', 'tailwindcss', 'storybook', 'app-min'] + }); +}); test('"before" task can select default-esnext-plugin preset', async t => { const prompts = { select(opts) { @@ -55,7 +99,7 @@ test('"before" task can select default-esnext-plugin preset', async t => { const result = await before({ unattended: false, prompts }); t.deepEqual(result, { silentQuestions: true, - preselectedFeatures: ['plugin', 'webpack', 'babel', 'shadow-dom', 'jest'] + preselectedFeatures: ['plugin', 'vite', 'babel', 'shadow-dom', 'vitest'] }); }); @@ -72,7 +116,7 @@ test('"before" task can select default-typescript-plugin preset', async t => { const result = await before({ unattended: false, prompts }); t.deepEqual(result, { silentQuestions: true, - preselectedFeatures: ['plugin', 'webpack', 'typescript', 'shadow-dom', 'jest'] + preselectedFeatures: ['plugin', 'vite', 'typescript', 'shadow-dom', 'vitest'] }); }); test('"before" task can select no preset', async t => { @@ -88,4 +132,3 @@ test('"before" task can select no preset', async t => { const result = await before({unattended: false, prompts}); t.is(result, undefined); }); - diff --git a/app-blank/src/my-app.ext b/app-blank/src/my-app.ext new file mode 100644 index 0000000..c6fa3a3 --- /dev/null +++ b/app-blank/src/my-app.ext @@ -0,0 +1,2 @@ +export class MyApp { +} diff --git a/app-blank/src/my-app.html b/app-blank/src/my-app.html new file mode 100644 index 0000000..c7423b4 --- /dev/null +++ b/app-blank/src/my-app.html @@ -0,0 +1 @@ +
Delete me
> \ No newline at end of file diff --git a/app-blank/src/my-app.stories.ext__if_storybook b/app-blank/src/my-app.stories.ext__if_storybook new file mode 100644 index 0000000..0d63baa --- /dev/null +++ b/app-blank/src/my-app.stories.ext__if_storybook @@ -0,0 +1,29 @@ +/* @if vite */ +import { MyApp } from './my-app'; + +const meta = { + title: 'Example/MyApp', + component: MyApp, + render: () => ({ + template: ``, + }) +}; + +export default meta; + +export const Default = {}; +/* @endif */ +/* @if webpack */ +import { MyApp } from './my-app'; + +export default { + title: 'MyApp', + component: MyApp, +}; + +export const Default = () => ({ + Component: MyApp, + template: '', + props: {} +}); +/* @endif */ diff --git a/app-blank/test__if_not_no-unit-tests/my-app.spec.ext b/app-blank/test__if_not_no-unit-tests/my-app.spec.ext new file mode 100644 index 0000000..5846bfe --- /dev/null +++ b/app-blank/test__if_not_no-unit-tests/my-app.spec.ext @@ -0,0 +1,20 @@ +// @if vitest +import { describe, it } from 'vitest'; +// @endif +import { MyApp } from '../src/my-app'; +import { createFixture } from '@aurelia/testing'; + +describe('my-app', () => { + it('should render', async () => { + const { appHost } = await createFixture( + '', + {}, + [MyApp], + ).started; + + const element = appHost.querySelector('my-app'); + if (element === null) { + throw new Error('Expected to find my-app element in host'); + } + }); +}); diff --git a/app-min/src/my-app.html b/app-min/src/my-app.html index 1ac1297..77edb08 100644 --- a/app-min/src/my-app.html +++ b/app-min/src/my-app.html @@ -1,12 +1,10 @@ -
-
-
Welcome to Aurelia 2!
-
You're now running ${message}
-
Experience the power of Aurelia 2 with TailwindCSS
-
Aurelia 2
+
+
+

${message}

+

Aurelia 2 + TailwindCSS

-
+
This content uses shared Shadow DOM styles!
diff --git a/app-with-router/src/about-page.html b/app-with-router/src/about-page.html index 371f79b..af63cfe 100644 --- a/app-with-router/src/about-page.html +++ b/app-with-router/src/about-page.html @@ -1,21 +1,7 @@ -
-

About

-
-

- This is a sample Aurelia application showcasing the integration of TailwindCSS with the Aurelia framework. - The combination provides a powerful development experience with utility-first CSS styling. -

-
-

Features

-
    -
  • • Aurelia 2 with modern web standards
  • -
  • • TailwindCSS for utility-first styling
  • -
  • • Responsive design out of the box
  • -
  • • Fast development workflow
  • -
-
-
+
+

About

+

This page is routed via @aurelia/router.

diff --git a/app-with-router/src/missing-page.ext b/app-with-router/src/missing-page.ext deleted file mode 100644 index ced926e..0000000 --- a/app-with-router/src/missing-page.ext +++ /dev/null @@ -1,3 +0,0 @@ -export class MissingPage { - /* @if typescript */public /* @endif */missingComponent/* @if typescript */: string /* @endif */ = 'Unknown page'; -} diff --git a/app-with-router/src/missing-page.html b/app-with-router/src/missing-page.html deleted file mode 100644 index 823482d..0000000 --- a/app-with-router/src/missing-page.html +++ /dev/null @@ -1,12 +0,0 @@ - -
-

404 - Page Not Found

-

Sorry, the page you're looking for doesn't exist.

- Go Home -
- - -

404 - Page Not Found

-

Sorry, the page you're looking for doesn't exist.

-Go Home - diff --git a/app-with-router/src/my-app.ext b/app-with-router/src/my-app.ext index 7fc5da3..39d8778 100644 --- a/app-with-router/src/my-app.ext +++ b/app-with-router/src/my-app.ext @@ -13,7 +13,6 @@ import { route } from '@aurelia/router'; title: 'About', }, ], - fallback: import('./missing-page'), }) export class MyApp { } diff --git a/app-with-router/src/my-app.html b/app-with-router/src/my-app.html index 904d578..b3f4d21 100644 --- a/app-with-router/src/my-app.html +++ b/app-with-router/src/my-app.html @@ -1,22 +1,14 @@
-