ember.nvp 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.github/workflows/plan-release.yml +61 -0
  2. package/.github/workflows/publish.yml +41 -0
  3. package/.nvmrc +1 -0
  4. package/.prettierignore +2 -0
  5. package/.release-plan.json +22 -0
  6. package/CHANGELOG.md +17 -0
  7. package/README.md +222 -1
  8. package/RELEASE.md +27 -0
  9. package/package.json +28 -3
  10. package/src/cli/generator.js +19 -0
  11. package/src/cli/index.js +48 -0
  12. package/src/cli/questions/layers.js +75 -0
  13. package/src/cli/questions/name.js +22 -0
  14. package/src/cli/questions/package-manager.js +21 -0
  15. package/src/layers/eslint/files/eslint.config.mjs +37 -0
  16. package/src/layers/eslint/index.js +42 -0
  17. package/src/layers/minimal/files/app/app.ts +10 -0
  18. package/src/layers/minimal/files/app/config.ts +18 -0
  19. package/src/layers/minimal/files/app/router.ts +9 -0
  20. package/src/layers/minimal/files/app/routes/.gitkeep +0 -0
  21. package/src/layers/minimal/files/app/templates/application.gts +7 -0
  22. package/src/layers/minimal/files/index.html +19 -0
  23. package/src/layers/minimal/files/tsconfig.json +18 -0
  24. package/src/layers/minimal/files/vite.config.mjs +13 -0
  25. package/src/layers/minimal/files-js/babel.config.js +38 -0
  26. package/src/layers/minimal/files-ts/babel.config.js +46 -0
  27. package/src/layers/minimal/index.js +89 -0
  28. package/src/layers/prettier/files/.prettierignore +18 -0
  29. package/src/layers/prettier/files/.prettierrc.cjs +15 -0
  30. package/src/layers/prettier/index.js +38 -0
  31. package/src/layers/qunit/files/run-tests.mjs +30 -0
  32. package/src/layers/qunit/files/tests/index.html +21 -0
  33. package/src/layers/qunit/files/tests/integration/.gitkeep +0 -0
  34. package/src/layers/qunit/files/tests/test-helper.ts +12 -0
  35. package/src/layers/qunit/files/tests/unit/.gitkeep +0 -0
  36. package/src/layers/qunit/index.js +53 -0
  37. package/src/layers/typescript/index.js +0 -0
  38. package/src/layers/vitest/files/tests/integration/.gitkeep +0 -0
  39. package/src/layers/vitest/files/tests/test-helper.ts +5 -0
  40. package/src/layers/vitest/files/tests/unit/.gitkeep +0 -0
  41. package/src/layers/vitest/files/vitest.config.mjs +22 -0
  42. package/src/layers/vitest/index.js +49 -0
  43. package/tsconfig.json +7 -0
@@ -0,0 +1,61 @@
1
+ name: Plan Release
2
+ on:
3
+ workflow_dispatch:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ pull_request_target: # This workflow has permissions on the repo, do NOT run code from PRs in this workflow. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
9
+ types:
10
+ - labeled
11
+ - unlabeled
12
+
13
+ concurrency:
14
+ group: plan-release # only the latest one of these should ever be running
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ should-run-release-plan-prepare:
19
+ name: Should we run release-plan prepare?
20
+ runs-on: ubuntu-latest
21
+ outputs:
22
+ should-prepare: ${{ steps.should-prepare.outputs.should-prepare }}
23
+ steps:
24
+ - uses: release-plan/actions/should-prepare-release@v1
25
+ with:
26
+ ref: 'main'
27
+ id: should-prepare
28
+
29
+ create-prepare-release-pr:
30
+ name: Create Prepare Release PR
31
+ runs-on: ubuntu-latest
32
+ timeout-minutes: 5
33
+ needs: should-run-release-plan-prepare
34
+ permissions:
35
+ contents: write
36
+ issues: read
37
+ pull-requests: write
38
+ if: needs.should-run-release-plan-prepare.outputs.should-prepare == 'true'
39
+ steps:
40
+ - uses: release-plan/actions/prepare@v1
41
+ name: Run release-plan prepare
42
+ with:
43
+ ref: 'main'
44
+ env:
45
+ GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }}
46
+ id: explanation
47
+
48
+ - uses: peter-evans/create-pull-request@v8
49
+ name: Create Prepare Release PR
50
+ with:
51
+ commit-message: "Prepare Release ${{ steps.explanation.outputs.new-version}} using 'release-plan'"
52
+ labels: "internal"
53
+ sign-commits: true
54
+ branch: release-preview
55
+ title: Prepare Release ${{ steps.explanation.outputs.new-version }}
56
+ body: |
57
+ This PR is a preview of the release that [release-plan](https://github.com/embroider-build/release-plan) has prepared. To release you should just merge this PR 👍
58
+
59
+ -----------------------------------------
60
+
61
+ ${{ steps.explanation.outputs.text }}
@@ -0,0 +1,41 @@
1
+ # For every push to the primary branch with .release-plan.json modified,
2
+ # runs release-plan.
3
+
4
+ name: Publish Stable
5
+
6
+ on:
7
+ workflow_dispatch:
8
+ push:
9
+ branches:
10
+ - main
11
+ - master
12
+ paths:
13
+ - '.release-plan.json'
14
+
15
+ concurrency:
16
+ group: publish-${{ github.head_ref || github.ref }}
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ publish:
21
+ name: "NPM Publish"
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: write
25
+ id-token: write
26
+ attestations: write
27
+
28
+ steps:
29
+ - uses: actions/checkout@v6
30
+ - uses: pnpm/action-setup@v4
31
+ - uses: actions/setup-node@v6
32
+ with:
33
+ node-version: 22
34
+ registry-url: 'https://registry.npmjs.org'
35
+ cache: pnpm
36
+ - run: npm install -g npm@latest # ensure that the globally installed npm is new enough to support OIDC
37
+ - run: pnpm install --frozen-lockfile
38
+ - name: Publish to NPM
39
+ run: NPM_CONFIG_PROVENANCE=true pnpm release-plan publish
40
+ env:
41
+ GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }}
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 24
@@ -0,0 +1,2 @@
1
+ pnpm-lock.yaml
2
+
@@ -0,0 +1,22 @@
1
+ {
2
+ "solution": {
3
+ "ember.nvp": {
4
+ "impact": "minor",
5
+ "oldVersion": "0.0.0",
6
+ "newVersion": "0.1.0",
7
+ "tagName": "latest",
8
+ "constraints": [
9
+ {
10
+ "impact": "minor",
11
+ "reason": "Appears in changelog section :rocket: Enhancement"
12
+ },
13
+ {
14
+ "impact": "patch",
15
+ "reason": "Appears in changelog section :house: Internal"
16
+ }
17
+ ],
18
+ "pkgJSONPath": "./package.json"
19
+ }
20
+ },
21
+ "description": "## Release (2026-01-21)\n\n* ember.nvp 0.1.0 (minor)\n\n#### :rocket: Enhancement\n* `ember.nvp`\n * [#1](https://github.com/NullVoxPopuli/ember.nvp/pull/1) Implementation planning ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :house: Internal\n* `ember.nvp`\n * [#2](https://github.com/NullVoxPopuli/ember.nvp/pull/2) pnpm dlx create-release-plan-setup@latest --update ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#1](https://github.com/NullVoxPopuli/ember.nvp/pull/1) Implementation planning ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
22
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## Release (2026-01-21)
4
+
5
+ * ember.nvp 0.1.0 (minor)
6
+
7
+ #### :rocket: Enhancement
8
+ * `ember.nvp`
9
+ * [#1](https://github.com/NullVoxPopuli/ember.nvp/pull/1) Implementation planning ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
10
+
11
+ #### :house: Internal
12
+ * `ember.nvp`
13
+ * [#2](https://github.com/NullVoxPopuli/ember.nvp/pull/2) pnpm dlx create-release-plan-setup@latest --update ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
14
+ * [#1](https://github.com/NullVoxPopuli/ember.nvp/pull/1) Implementation planning ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
15
+
16
+ #### Committers: 1
17
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
package/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  _ember project generator: alternative to the official blueprints, but more modern -- hopefully one day to upstream back in to ember-cli / official blueprints_
4
4
 
5
-
6
5
  ## Reqs
7
6
 
8
7
  - node 24+
@@ -14,7 +13,229 @@ _ember project generator: alternative to the official blueprints, but more moder
14
13
  - choose your features
15
14
 
16
15
  Good for:
16
+
17
17
  - demos
18
18
  - reproductions
19
19
  - existing monorepos
20
20
  - example integrations with other tools
21
+
22
+ ## Usage
23
+
24
+ Install dependencies first:
25
+
26
+ ```bash
27
+ pnpm install
28
+ ```
29
+
30
+ Then run the interactive CLI:
31
+
32
+ ```bash
33
+ node src/cli/index.js
34
+ ```
35
+
36
+ Or link it locally for easier testing:
37
+
38
+ ```bash
39
+ pnpm link --global
40
+ ember.nvp
41
+ ```
42
+
43
+ ### Example Session
44
+
45
+ ```
46
+ ┌ ember.nvp
47
+ │
48
+ ◆ What is your project name?
49
+ │ my-awesome-app
50
+ │
51
+ ◇ Base Layer
52
+ │ minimal layer is always included.
53
+ │ It provides: type: "module", no compat, no testing, no linting.
54
+ │ Perfect for demos and reproductions.
55
+ │
56
+ ◆ Select additional features: (press space to select)
57
+ │ ◻ ESLint - Code linting with ESLint
58
+ │ ◻ Prettier - Code formatting with Prettier
59
+ │ ◻ QUnit - Testing with QUnit
60
+ │ ◻ Vitest - Testing with Vitest (modern & fast)
61
+ │
62
+ ◆ Which package manager?
63
+ │ ● pnpm - Fast, disk space efficient
64
+ │ npm - Node default
65
+ │ yarn - Classic alternative
66
+ │
67
+ ◇ Project created!
68
+ │
69
+ ◇ Next steps
70
+ │ cd my-awesome-app
71
+ │ pnpm install
72
+ │ pnpm start
73
+ │
74
+ └ ✓ Project ready! Happy coding!
75
+ ```
76
+
77
+ The CLI will guide you through:
78
+
79
+ 1. **Project name** - Choose your project name
80
+ 2. **Feature selection** - Select which layers you want (linting, testing, formatting)
81
+ 3. **Package manager** - Choose pnpm, npm, or yarn
82
+
83
+ ## Layers
84
+
85
+ Each layer is a standalone module that can add features to your ember project:
86
+
87
+ ### 🎯 Minimal (always included)
88
+
89
+ The base layer, matching the `--minimal` flag from [ember-cli/ember-app-blueprint#49](https://github.com/ember-cli/ember-app-blueprint/pull/49):
90
+
91
+ - ✅ `"type": "module"` in package.json
92
+ - ✅ No @embroider/compat (faster builds)
93
+ - ✅ No testing framework (minimal setup)
94
+ - ✅ No linting or formatting
95
+ - ✅ No ember-welcome-page
96
+ - ✅ Vite-based with modern Ember
97
+
98
+ Perfect for demos, reproductions, and learning!
99
+
100
+ ### 📝 ESLint (optional)
101
+
102
+ Adds modern ESLint configuration with:
103
+
104
+ - TypeScript support
105
+ - Ember plugin
106
+ - Flat config (ESLint 9+)
107
+ - Prettier compatibility
108
+
109
+ ### 🎨 Prettier (optional)
110
+
111
+ Code formatting with:
112
+
113
+ - GTS/GJS template support
114
+ - Sensible defaults
115
+ - Format scripts
116
+
117
+ ### 🧪 QUnit (optional)
118
+
119
+ Traditional Ember testing with:
120
+
121
+ - QUnit test framework
122
+ - @ember/test-helpers
123
+ - Test HTML setup
124
+ - Playwright for CI
125
+
126
+ ### ⚡ Vitest (optional)
127
+
128
+ Modern testing alternative:
129
+
130
+ - Vitest (faster than QUnit)
131
+ - @ember/test-helpers
132
+ - Happy DOM
133
+ - Watch mode & UI
134
+
135
+ ## Architecture
136
+
137
+ ```
138
+ src/
139
+ ├── cli/ # Interactive CLI entry point
140
+ │ └── index.js # Main CLI with prompts
141
+ ├── layers/ # Feature layers (auto-discovered)
142
+ │ ├── minimal/ # Base layer (always included)
143
+ │ │ ├── index.js # Layer definition with run function
144
+ │ │ └── files/ # Template files to copy
145
+ │ ├── eslint/
146
+ │ │ ├── index.js
147
+ │ │ └── files/
148
+ │ ├── prettier/
149
+ │ │ ├── index.js
150
+ │ │ └── files/
151
+ │ ├── qunit/
152
+ │ │ ├── index.js
153
+ │ │ └── files/
154
+ │ └── vitest/
155
+ │ ├── index.js
156
+ │ └── files/
157
+ └── lib/ # Shared utilities
158
+ └── generator.js # Project generation orchestration
159
+ ```
160
+
161
+ ### Layer Architecture
162
+
163
+ Each layer now uses **ember-apply** utilities for robust file and package.json manipulation:
164
+
165
+ - **`index.js`** - Exports an object with:
166
+ - `label` - Display name
167
+ - `description` - Short description
168
+ - `run({ targetDir, projectName })` - Async function that applies the layer
169
+
170
+ - **`files/`** - Directory containing template files to copy to the target project
171
+ - Use `__PROJECT_NAME__` placeholder for project name substitution
172
+ - Files are copied using `ember-apply`'s `files.applyFolder()`
173
+
174
+ ### How Layers Work
175
+
176
+ 1. **Discovery**: CLI scans `src/layers/` and imports each `index.js`
177
+ 2. **Selection**: User selects which optional layers to include
178
+ 3. **Execution**: Each layer's `run()` function is called in sequence:
179
+ ```js
180
+ await layer.run({ targetDir: "/path/to/project", projectName: "my-app" });
181
+ ```
182
+ 4. **Layer Functions**: Inside `run()`, layers use `ember-apply` to:
183
+ - Copy files from `files/` directory
184
+ - Add dependencies/devDependencies to package.json
185
+ - Add npm scripts
186
+ - Modify package.json metadata
187
+ - Transform existing files (e.g., replace placeholders)
188
+
189
+ ### Adding New Layers
190
+
191
+ To add a new layer, create a new directory in `src/layers/` with:
192
+
193
+ 1. **`index.js`** - Layer definition:
194
+
195
+ ```js
196
+ import { packageJson, files } from "ember-apply";
197
+ import { dirname, join } from "node:path";
198
+ import { fileURLToPath } from "node:url";
199
+
200
+ const __dirname = dirname(fileURLToPath(import.meta.url));
201
+
202
+ export default {
203
+ label: "My Feature",
204
+ description: "What this feature does",
205
+
206
+ async run({ targetDir, projectName }) {
207
+ // Copy files from files/ directory
208
+ await files.applyFolder(join(__dirname, "files"), targetDir);
209
+
210
+ // Add dependencies
211
+ await packageJson.addDependencies(
212
+ {
213
+ "some-package": "^1.0.0",
214
+ },
215
+ targetDir,
216
+ );
217
+
218
+ // Add devDependencies
219
+ await packageJson.addDevDependencies(
220
+ {
221
+ "dev-package": "^2.0.0",
222
+ },
223
+ targetDir,
224
+ );
225
+
226
+ // Add scripts
227
+ await packageJson.addScripts(
228
+ {
229
+ "my-script": 'echo "Hello"',
230
+ },
231
+ targetDir,
232
+ );
233
+ },
234
+ };
235
+ ```
236
+
237
+ 2. **`files/`** directory - Template files to copy:
238
+ - Use `__PROJECT_NAME__` as a placeholder for the project name
239
+ - Files will be copied to the target directory maintaining structure
240
+
241
+ The CLI will automatically discover and offer it as an option!
package/RELEASE.md ADDED
@@ -0,0 +1,27 @@
1
+ # Release Process
2
+
3
+ Releases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used to prepare the release once the PR is merged.
4
+
5
+ ## Preparation
6
+
7
+ Since the majority of the actual release process is automated, the remaining tasks before releasing are:
8
+
9
+ - correctly labeling **all** pull requests that have been merged since the last release
10
+ - updating pull request titles so they make sense to our users
11
+
12
+ Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall
13
+ guiding principle here is that changelogs are for humans, not machines.
14
+
15
+ When reviewing merged PR's the labels to be used are:
16
+
17
+ - breaking - Used when the PR is considered a breaking change.
18
+ - enhancement - Used when the PR adds a new feature or enhancement.
19
+ - bug - Used when the PR fixes a bug included in a previous release.
20
+ - documentation - Used when the PR adds or updates documentation.
21
+ - internal - Internal changes or things that don't fit in any other category.
22
+
23
+ **Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal`
24
+
25
+ ## Release
26
+
27
+ Once the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/NullVoxPopuli/ember.nvp/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR
package/package.json CHANGED
@@ -1,10 +1,35 @@
1
1
  {
2
2
  "name": "ember.nvp",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "ember project generator, alternative to the official blueprints, but more modern -- hopefully one day to upstream back in to ember-cli / official blueprints",
5
- "dependencies": {},
6
- "devDependencies": {},
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git@github.com:NullVoxPopuli/ember.nvp.git"
8
+ },
9
+ "type": "module",
10
+ "bin": "./src/cli/index.js",
11
+ "dependencies": {
12
+ "@clack/prompts": "^0.11.0",
13
+ "ember-apply": "^2.16.0",
14
+ "picocolors": "^1.1.1"
15
+ },
16
+ "devDependencies": {
17
+ "@ember/app-tsconfig": "^2.0.0",
18
+ "@ember/library-tsconfig": "^2.0.0",
19
+ "@glint/ember-tsc": "^1.0.9",
20
+ "@glint/template": "^1.7.3",
21
+ "@glint/tsserver-plugin": "^2.0.9",
22
+ "ember-source": "^6.10.0",
23
+ "prettier": "^3.7.4",
24
+ "prettier-plugin-ember-template-tag": "^2.1.2",
25
+ "release-plan": "^0.17.4"
26
+ },
7
27
  "engines": {
8
28
  "node": ">= 24"
29
+ },
30
+ "scripts": {
31
+ "format": "prettier . --write",
32
+ "lint:format": "prettier . --check",
33
+ "new": "node ./src/cli/index.js"
9
34
  }
10
35
  }
@@ -0,0 +1,19 @@
1
+ import { mkdir } from "node:fs/promises";
2
+
3
+ /**
4
+ * Generate project files by running layer functions
5
+ */
6
+ export async function generateProject(projectPath, projectName, layers) {
7
+ // Create project directory
8
+ await mkdir(projectPath, { recursive: true });
9
+
10
+ // Run each layer's run function in sequence
11
+ for (const layer of layers) {
12
+ if (typeof layer.run === "function") {
13
+ await layer.run({
14
+ targetDir: projectPath,
15
+ projectName,
16
+ });
17
+ }
18
+ }
19
+ }
package/src/cli/index.js CHANGED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as p from "@clack/prompts";
4
+ import { join } from "node:path";
5
+ import pc from "picocolors";
6
+ import { generateProject } from "./generator.js";
7
+ import { askName } from './questions/name.js';
8
+ import { askLayers } from './questions/layers.js';
9
+ import { askPackageManager } from './questions/package-manager.js';
10
+
11
+ async function main() {
12
+ console.clear();
13
+
14
+ p.intro(pc.bgCyan(pc.black(" ember.nvp ")));
15
+
16
+ const projectName = await askName();
17
+ const selectedLayers = await askLayers();
18
+ const packageManager = await askPackageManager();
19
+
20
+ const s = p.spinner();
21
+ s.start("Creating your Ember app...");
22
+
23
+ try {
24
+ // Generate the project
25
+ const projectPath = join(process.cwd(), projectName);
26
+ await generateProject(projectPath, projectName, selectedLayers);
27
+
28
+ s.stop("Project created!");
29
+
30
+ p.note(
31
+ `cd ${projectName}\n` +
32
+ `${packageManager} install\n` +
33
+ `${packageManager} ${packageManager === "npm" ? "run " : ""}start`,
34
+ "Next steps",
35
+ );
36
+
37
+ p.outro(pc.green("✓") + " Project ready! " + pc.dim("Happy coding!"));
38
+ } catch (err) {
39
+ s.stop("Failed to create project");
40
+ p.cancel(`Error: ${err.message}`);
41
+ throw err;
42
+ }
43
+ }
44
+
45
+ main().catch((err) => {
46
+ console.error(err);
47
+ process.exit(1);
48
+ });
@@ -0,0 +1,75 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+
4
+ import * as p from "@clack/prompts";
5
+
6
+ export async function askLayers() {
7
+
8
+ // Discover available layers
9
+ const layers = await discoverLayers();
10
+
11
+ // Separate minimal from other layers
12
+ const minimalLayer = layers.find((l) => l.name === "minimal");
13
+ const optionalLayers = layers.filter((l) => l.name !== "minimal");
14
+
15
+ p.note(
16
+ `${pc.cyan("minimal")} layer is always included.\n` +
17
+ 'It provides: type: "module", no compat, no testing, no linting.\n' +
18
+ "Perfect for demos and reproductions.",
19
+ "Base Layer",
20
+ );
21
+
22
+ // Let user select additional features
23
+ const selectedFeatures = await p.multiselect({
24
+ message: "Select additional features:",
25
+ options: optionalLayers.map((layer) => ({
26
+ value: layer.name,
27
+ label: layer.label || layer.name,
28
+ hint: layer.description,
29
+ })),
30
+ required: false,
31
+ });
32
+
33
+ if (p.isCancel(selectedFeatures)) {
34
+ p.cancel("Operation cancelled");
35
+ return process.exit(0);
36
+ }
37
+
38
+ // Build the final configuration
39
+ const selectedLayers = [
40
+ minimalLayer,
41
+ ...optionalLayers.filter((l) => selectedFeatures.includes(l.name)),
42
+ ].filter(Boolean);
43
+
44
+ return selectedLayers;
45
+
46
+ }
47
+
48
+ async function discoverLayers() {
49
+ const layersDir = join(import.meta.dirname, '..', "..", "layers");
50
+ const entries = await readdir(layersDir, { withFileTypes: true });
51
+
52
+ const layers = [];
53
+
54
+ for (const entry of entries) {
55
+ if (entry.isDirectory()) {
56
+ const layerPath = join(layersDir, entry.name, "index.js");
57
+ try {
58
+ const layer = await import(layerPath);
59
+ layers.push({
60
+ name: entry.name,
61
+ ...layer.default,
62
+ });
63
+ } catch (err) {
64
+ // Skip layers that don't have proper exports
65
+ console.warn(
66
+ `Warning: Could not load layer ${entry.name}:`,
67
+ err.message,
68
+ );
69
+ }
70
+ }
71
+ }
72
+
73
+ return layers;
74
+ }
75
+
@@ -0,0 +1,22 @@
1
+ import * as p from "@clack/prompts";
2
+
3
+ export async function askName() {
4
+ const projectName = await p.text({
5
+ message: "What is your project name?",
6
+ placeholder: "my-app",
7
+ defaultValue: 'my-app',
8
+ validate(value) {
9
+ if (value.length === 0) return "Project name is required";
10
+ if (!/^[a-z0-9-]+$/.test(value)) {
11
+ return "Project name must be lowercase and can only contain letters, numbers, and hyphens";
12
+ }
13
+ },
14
+ });
15
+
16
+ if (p.isCancel(projectName)) {
17
+ p.cancel("Operation cancelled");
18
+ return process.exit(0);
19
+ }
20
+
21
+ return projectName;
22
+ }
@@ -0,0 +1,21 @@
1
+ import * as p from "@clack/prompts";
2
+
3
+ export async function askPackageManager() {
4
+
5
+ const packageManager = await p.select({
6
+ message: "Which package manager?",
7
+ options: [
8
+ { value: "pnpm", label: "pnpm", hint: "Fast, disk space efficient" },
9
+ { value: "npm", label: "npm", hint: "Node default" },
10
+ { value: "yarn", label: "yarn", hint: "Classic alternative" },
11
+ ],
12
+ });
13
+
14
+ if (p.isCancel(packageManager)) {
15
+ p.cancel("Operation cancelled");
16
+ return process.exit(0);
17
+ }
18
+
19
+ return packageManager;
20
+ }
21
+
@@ -0,0 +1,37 @@
1
+ import globals from "globals";
2
+ import js from "@eslint/js";
3
+ import ts from "typescript-eslint";
4
+ import ember from "eslint-plugin-ember";
5
+ import prettier from "eslint-config-prettier";
6
+
7
+ export default [
8
+ js.configs.recommended,
9
+ ...ts.configs.recommended,
10
+ ...ember.configs["flat/recommended"],
11
+ prettier,
12
+ {
13
+ languageOptions: {
14
+ globals: {
15
+ ...globals.browser,
16
+ },
17
+ parserOptions: {
18
+ ecmaVersion: "latest",
19
+ sourceType: "module",
20
+ },
21
+ },
22
+ },
23
+ {
24
+ files: ["**/*.gts", "**/*.gjs"],
25
+ languageOptions: {
26
+ parser: ts.parser,
27
+ parserOptions: {
28
+ ecmaVersion: "latest",
29
+ sourceType: "module",
30
+ extraFileExtensions: [".gts", ".gjs"],
31
+ },
32
+ },
33
+ },
34
+ {
35
+ ignores: ["dist/", "node_modules/", ".vite/"],
36
+ },
37
+ ];