ember.nvp 0.2.0 → 0.2.2

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.
@@ -27,13 +27,12 @@ jobs:
27
27
 
28
28
  steps:
29
29
  - uses: actions/checkout@v6
30
- - uses: pnpm/action-setup@v4
30
+ - uses: pnpm/action-setup@v5
31
31
  - uses: actions/setup-node@v6
32
32
  with:
33
- node-version: 22
33
+ node-version: 24
34
34
  registry-url: "https://registry.npmjs.org"
35
35
  cache: pnpm
36
- - run: npm install -g npm@latest # ensure that the globally installed npm is new enough to support OIDC
37
36
  - run: pnpm install --frozen-lockfile
38
37
  - name: Publish to NPM
39
38
  run: NPM_CONFIG_PROVENANCE=true pnpm release-plan publish
@@ -1,30 +1,31 @@
1
1
  {
2
2
  "solution": {
3
3
  "ember.nvp": {
4
- "impact": "minor",
5
- "oldVersion": "0.1.0",
6
- "newVersion": "0.2.0",
4
+ "impact": "patch",
5
+ "oldVersion": "0.2.1",
6
+ "newVersion": "0.2.2",
7
7
  "tagName": "latest",
8
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 :bug: Bug Fix"
16
- },
17
9
  {
18
10
  "impact": "patch",
19
- "reason": "Appears in changelog section :memo: Documentation"
20
- },
11
+ "reason": "Appears in changelog section :house: Internal"
12
+ }
13
+ ],
14
+ "pkgJSONPath": "./package.json"
15
+ },
16
+ "@nullvoxpopuli/ember-vite": {
17
+ "impact": "patch",
18
+ "oldVersion": "1.0.0",
19
+ "newVersion": "1.0.1",
20
+ "tagName": "latest",
21
+ "constraints": [
21
22
  {
22
23
  "impact": "patch",
23
24
  "reason": "Appears in changelog section :house: Internal"
24
25
  }
25
26
  ],
26
- "pkgJSONPath": "./package.json"
27
+ "pkgJSONPath": "./packages/vite/package.json"
27
28
  }
28
29
  },
29
- "description": "## Release (2026-01-28)\n\n* ember.nvp 0.2.0 (minor)\n\n#### :rocket: Enhancement\n* `ember.nvp`\n * [#20](https://github.com/NullVoxPopuli/ember.nvp/pull/20) Implement idemponent and modern qunit layer ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#18](https://github.com/NullVoxPopuli/ember.nvp/pull/18) Ensure the TS utilities handle the babel plugin ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#17](https://github.com/NullVoxPopuli/ember.nvp/pull/17) Support emitting JavaScript projects ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#16](https://github.com/NullVoxPopuli/ember.nvp/pull/16) Commit changes automatically if git is in use ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#14](https://github.com/NullVoxPopuli/ember.nvp/pull/14) Add idempotent GitHub Actions ci.yml ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#11](https://github.com/NullVoxPopuli/ember.nvp/pull/11) Add idempotent renovate config generation ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#9](https://github.com/NullVoxPopuli/ember.nvp/pull/9) Add eslint codemod/applyable, enable type checking internally on the JS ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#8](https://github.com/NullVoxPopuli/ember.nvp/pull/8) Add idempotent prettier codemod/applyable ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#6](https://github.com/NullVoxPopuli/ember.nvp/pull/6) Add git support with proper defaults (ie automatic --skip-git if you are already in a git repo) ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#4](https://github.com/NullVoxPopuli/ember.nvp/pull/4) Implementation: minimal-app ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :bug: Bug Fix\n* `ember.nvp`\n * [#22](https://github.com/NullVoxPopuli/ember.nvp/pull/22) Fix accidental stdoutput during git interactions ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#10](https://github.com/NullVoxPopuli/ember.nvp/pull/10) Various CLI fixes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :memo: Documentation\n* `ember.nvp`\n * [#12](https://github.com/NullVoxPopuli/ember.nvp/pull/12) Clarify rationale and usage caveats in README ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :house: Internal\n* `ember.nvp`\n * [#19](https://github.com/NullVoxPopuli/ember.nvp/pull/19) Forgot to enable tests for GH Actions ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#21](https://github.com/NullVoxPopuli/ember.nvp/pull/21) Refactor hases and wantses ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#15](https://github.com/NullVoxPopuli/ember.nvp/pull/15) Add reapply test utility for more easily re-applying layers in the manual testing ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#13](https://github.com/NullVoxPopuli/ember.nvp/pull/13) Speed up permutations test ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n * [#7](https://github.com/NullVoxPopuli/ember.nvp/pull/7) Implement idempotent testing for layers that may (or may not) have been applied during the initial generation of a project ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
30
+ "description": "## Release (2026-06-01)\n\n* ember.nvp 0.2.2 (patch)\n* @nullvoxpopuli/ember-vite 1.0.1 (patch)\n\n#### :house: Internal\n* `@nullvoxpopuli/ember-vite`\n * [#36](https://github.com/NullVoxPopuli/ember.nvp/pull/36) Fix npm provenance failure for @nullvoxpopuli/ember-vite publish ([@Copilot](https://github.com/apps/copilot-swe-agent))\n* `ember.nvp`, `@nullvoxpopuli/ember-vite`\n * [#37](https://github.com/NullVoxPopuli/ember.nvp/pull/37) Revert \"Prepare Release\" ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 2\n- Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
30
31
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## Release (2026-06-01)
4
+
5
+ * ember.nvp 0.2.2 (patch)
6
+ * @nullvoxpopuli/ember-vite 1.0.1 (patch)
7
+
8
+ #### :house: Internal
9
+ * `@nullvoxpopuli/ember-vite`
10
+ * [#36](https://github.com/NullVoxPopuli/ember.nvp/pull/36) Fix npm provenance failure for @nullvoxpopuli/ember-vite publish ([@Copilot](https://github.com/apps/copilot-swe-agent))
11
+ * `ember.nvp`, `@nullvoxpopuli/ember-vite`
12
+ * [#37](https://github.com/NullVoxPopuli/ember.nvp/pull/37) Revert "Prepare Release" ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
13
+
14
+ #### Committers: 2
15
+ - Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))
16
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
17
+
18
+ ## Release (2026-05-15)
19
+
20
+ * ember.nvp 0.2.1 (patch)
21
+
22
+ #### :bug: Bug Fix
23
+ * `ember.nvp`
24
+ * [#29](https://github.com/NullVoxPopuli/ember.nvp/pull/29) Fix production vite build broken by setTesting in app/config.ts ([@Copilot](https://github.com/apps/copilot-swe-agent))
25
+
26
+ #### :house: Internal
27
+ * `ember.nvp`
28
+ * [#31](https://github.com/NullVoxPopuli/ember.nvp/pull/31) Update release-plan ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
29
+
30
+ #### Committers: 2
31
+ - Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))
32
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
33
+
3
34
  ## Release (2026-01-28)
4
35
 
5
36
  * ember.nvp 0.2.0 (minor)
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "ember.nvp",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git@github.com:NullVoxPopuli/ember.nvp.git"
8
8
  },
9
9
  "type": "module",
10
- "bin": "./src/cli/index.js",
11
- "exports": "./src/index.js",
12
10
  "imports": {
13
11
  "#args": "./src/cli/args.js",
14
12
  "#bases/*": "./src/bases/*/index.js",
@@ -18,10 +16,12 @@
18
16
  "#utils/*": "./src/utils/*",
19
17
  "#test-helpers": "./test/test-helpers.ts"
20
18
  },
19
+ "exports": "./src/index.js",
20
+ "bin": "./src/cli/index.js",
21
21
  "dependencies": {
22
22
  "@clack/prompts": "^0.11.0",
23
- "babel-remove-types": "^1.1.0",
24
- "content-tag": "^4.1.0",
23
+ "babel-remove-types": "^2.0.0",
24
+ "content-tag": "^4.2.0",
25
25
  "ember-apply": "^2.17.0",
26
26
  "execa": "^9.6.1",
27
27
  "latest-version": "^9.0.0",
@@ -30,27 +30,27 @@
30
30
  "devDependencies": {
31
31
  "@ember/app-tsconfig": "^2.0.0",
32
32
  "@ember/library-tsconfig": "^2.0.0",
33
- "@glint/ember-tsc": "^1.0.9",
34
- "@glint/template": "^1.7.3",
35
- "@glint/tsserver-plugin": "^2.0.9",
33
+ "@glint/ember-tsc": "^1.7.5",
34
+ "@glint/template": "^1.7.7",
35
+ "@glint/tsserver-plugin": "^2.5.5",
36
36
  "concurrently": "^9.2.1",
37
- "ember-source": "^6.10.0",
38
- "prettier": "^3.7.4",
39
- "prettier-plugin-ember-template-tag": "^2.1.2",
40
- "release-plan": "^0.17.4",
41
- "rimraf": "^6.1.2",
42
- "vite": "^7.3.1",
43
- "vitest": "^4.0.17"
37
+ "ember-source": "^7.0.0",
38
+ "prettier": "^3.8.3",
39
+ "prettier-plugin-ember-template-tag": "^2.1.6",
40
+ "release-plan": "^0.18.0",
41
+ "rimraf": "^6.1.3",
42
+ "vite": "^8.0.14",
43
+ "vitest": "^4.1.7"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">= 24"
47
47
  },
48
48
  "scripts": {
49
+ "format": "prettier . --write",
49
50
  "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto",
50
51
  "lint:fix": "pnpm format",
51
- "lint:types": "tsc --noEmit",
52
- "format": "prettier . --write",
53
52
  "lint:format": "prettier . --check",
53
+ "lint:types": "tsc --noEmit",
54
54
  "new": "./test/manual.sh",
55
55
  "test:node": "FORCE_COLOR=1 vitest run --reporter=verbose --printConsoleTrace",
56
56
  "test:vitest": "FORCE_COLOR=1 vitest"
@@ -0,0 +1,70 @@
1
+ # `@nullvoxpopuli/ember-vite`
2
+
3
+ A speed-optimized default meta-config for ember projects.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm add @nullvoxpopuli/ember-vite
9
+ ```
10
+
11
+ Requires node 24+
12
+
13
+ ## Usage
14
+
15
+ in your vite config
16
+
17
+ ```js
18
+ import { defineConfig } from "vite";
19
+ import { ember } from "@nullvoxpopuli/ember-vite";
20
+
21
+ export default defineConfig({
22
+ plugins: [
23
+ ember(),
24
+ ],
25
+ }
26
+ ```
27
+
28
+ And remove any plugins from embroider or babel.
29
+ Then delete from your package.json: `@rollup/plugin-babel`, `@embroider/core`, `@embroider/vite`.
30
+
31
+ > [!NOTE]
32
+ > babel related deps are still needed for linting, so don't delete those
33
+
34
+ ## Configuration
35
+
36
+ The `ember()` plugin takes the following options:
37
+
38
+ ```ts
39
+ ember({
40
+ // optional production configs
41
+ production: {
42
+ // optional configuration of codeSplitting.groups
43
+ // in the vite config.
44
+ //
45
+ // Docs here:
46
+ // https://rolldown.rs/reference/OutputOptions.codeSplitting#groups
47
+ codeSplittingGroups: [
48
+ /* ... */
49
+ ],
50
+ },
51
+ // optional babel config
52
+ babel: {
53
+ // defaults to true.
54
+ // when babel runs, do so in parallel across many cores.
55
+ parallel: false,
56
+ // optional way to configure when babel is activateed.
57
+ // by default, all transforming is oxc, except when babel is needed
58
+ // (for things not currently implemented in oxc)
59
+ include: {
60
+ // if a file imports from anything in this array, use babel instead of
61
+ // oxc for that file.
62
+ //
63
+ // For example: ['ember-concurrency', 'ember-intl/format-message'],
64
+ whenImporting: [
65
+ /* ... */
66
+ ],
67
+ },
68
+ },
69
+ });
70
+ ```
@@ -0,0 +1,2 @@
1
+ export { ember } from "./src/ember.ts";
2
+ export { maybeBabel } from "./src/maybe-babel.ts";
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsc" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
19
+ else
20
+ exec node "$basedir/../vite/bin/vite.js" "$@"
21
+ fi
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@nullvoxpopuli/ember-vite",
3
+ "version": "1.0.1",
4
+ "description": "Meta plugin for ember projects focusing on speed.",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "keywords": [
8
+ "vite",
9
+ "ember",
10
+ "meta plugin"
11
+ ],
12
+ "author": "NullVoxPopuli",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/NullVoxPopuli/ember.nvp",
17
+ "directory": "packages/vite"
18
+ },
19
+ "packageManager": "pnpm@10.28.0",
20
+ "dependencies": {
21
+ "@babel/core": "^7.28.6",
22
+ "@embroider/core": "^4.4.2",
23
+ "@embroider/vite": "^1.5.0",
24
+ "@rollup/plugin-babel": "^7.1.0",
25
+ "oxc-parser": "^0.132.0",
26
+ "oxc-transform": "^0.132.0",
27
+ "zimmerframe": "^1.1.4"
28
+ },
29
+ "engines": {
30
+ "node": ">= 24"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.9.3",
34
+ "vite": "^8.0.14"
35
+ }
36
+ }
@@ -0,0 +1,239 @@
1
+ import { ember as embroiderEmber, extensions, resolver, templateTag } from "@embroider/vite";
2
+ import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import type { ConfigEnv, ResolvedConfig } from "vite";
5
+
6
+ import { maybeBabel } from "./maybe-babel.ts";
7
+
8
+ const emberConfig = embroiderEmber()[3]!;
9
+
10
+ const cwd = process.cwd();
11
+ function absolutePath(relativePath: string) {
12
+ return join(cwd, relativePath);
13
+ }
14
+
15
+ interface Config {
16
+ babel?: {
17
+ /**
18
+ * defaults to true.
19
+ * when babel runs, do so in parallel across many cores.
20
+ */
21
+ parallel?: false;
22
+
23
+ /**
24
+ * optional way to configure when babel is activateed.
25
+ * by default, all transforming is oxc, except when babel is needed
26
+ * (for things not currently implemented in oxc)
27
+ */
28
+ include?: {
29
+ /**
30
+ * if a file imports from anything in this array, use babel instead of
31
+ * oxc for that file.
32
+ *
33
+ * For example: ['ember-concurrency'],
34
+ */
35
+ whenImporting?: (string | "ember-concurrency" | "ember-scoped-css")[];
36
+ };
37
+ };
38
+ /**
39
+ * optional production configs
40
+ */
41
+ production?: {
42
+ /**
43
+ * optional configuration of codeSplitting.groups
44
+ * in the vite config.
45
+ *
46
+ * Docs here:
47
+ * https://rolldown.rs/reference/OutputOptions.codeSplitting#groups
48
+ */
49
+ codeSplittingGroups: any[];
50
+ };
51
+ }
52
+
53
+ export function ember(nvpConfig: Config = {}) {
54
+ const babelConfigFile = resolve(join(process.cwd(), "./babel.config.js"));
55
+ const defaultEnv = { mode: process.env.NODE_ENV || "development" };
56
+
57
+ /*
58
+ * Plugins must be returned as top-level array entries — NOT pushed into
59
+ * config.plugins inside a config() hook. Vite 8 extracts user plugins
60
+ * *before* config hooks run, so anything added later is invisible to the
61
+ * plugin container (resolveId, load, etc. hooks never fire).
62
+ */
63
+ return [
64
+ {
65
+ // no compat
66
+ name: "ember:nullvoxpopuli",
67
+ async config(viteConfig: any, env: ConfigEnv) {
68
+ // @ts-expect-error it exists
69
+ await emberConfig.config(viteConfig, env);
70
+
71
+ viteConfig.experimental ||= {};
72
+ viteConfig.build ||= {};
73
+ viteConfig.optimizeDeps ||= {};
74
+
75
+ viteConfig.oxc = {
76
+ // babel handles legacy decorators
77
+ // decorator: {
78
+ // legacy: true,
79
+ // },
80
+ };
81
+ // config.experimental.bundledDev = true;
82
+ viteConfig.experimental.nativeMagicString = true;
83
+ delete viteConfig.esbuild;
84
+ delete viteConfig.optimizeDeps.esbuildOptions;
85
+
86
+ viteConfig.build.reportCompressedSize = false;
87
+ viteConfig.build.extensions = extensions;
88
+ viteConfig.optimizeDeps.extensions = [".gjs", ".gts"];
89
+ viteConfig.optimizeDeps.rolldownOptions ||= {};
90
+ viteConfig.optimizeDeps.rolldownOptions.tsconfig = true;
91
+
92
+ applyConfig(viteConfig, env, nvpConfig);
93
+
94
+ viteConfig.optimizeDeps.rolldownOptions.plugins = [
95
+ resolver({ rolldown: true }),
96
+ templateTag(),
97
+
98
+ // Libraries will have precompileTemplate and macros, etc,
99
+ // and we need to compile that away using this app's
100
+ // template compiler
101
+ maybeBabel({ env, parallel: nvpConfig.babel?.parallel, configFile: babelConfigFile }),
102
+ ];
103
+ },
104
+ },
105
+ resolver({ rolldown: true }),
106
+ templateTag(),
107
+ maybeBabel({
108
+ env: defaultEnv,
109
+ parallel: nvpConfig.babel?.parallel,
110
+ configFile: babelConfigFile,
111
+ }),
112
+ ];
113
+ }
114
+
115
+ const MODE_DEV = "development";
116
+
117
+ function isDev(mode: string) {
118
+ return mode === MODE_DEV;
119
+ }
120
+
121
+ function applyConfig(viteConfig: ResolvedConfig, env: ConfigEnv, nvpConfig: Config) {
122
+ if (isDev(env.mode)) {
123
+ return dev(viteConfig, nvpConfig);
124
+ }
125
+
126
+ return prod(viteConfig, nvpConfig);
127
+ }
128
+
129
+ function dev(viteConfig: ResolvedConfig, nvpConfig: Config) {
130
+ /**************************************
131
+ *
132
+ * build config for tests
133
+ *
134
+ * NOTE: we can't minify because some tests
135
+ * are checking x.constructor.name, which changes when minified
136
+ *
137
+ *************************************/
138
+ Object.assign(viteConfig.build, {
139
+ sourcemap: true,
140
+ cleanCssOptions: { sourceMap: true },
141
+ minify: false,
142
+ cssMinify: false,
143
+ });
144
+
145
+ viteConfig.build.rolldownOptions ||= {};
146
+ viteConfig.build.rolldownOptions.input ||= {};
147
+ viteConfig.build.rolldownOptions.output ||= {};
148
+
149
+ Object.assign(viteConfig.build.rolldownOptions.input, {
150
+ main: absolutePath("./index.html"),
151
+ });
152
+
153
+ if (existsSync("./tests.html")) {
154
+ Object.assign(viteConfig.build.rolldownOptions.input, {
155
+ tests: absolutePath("./tests.html"),
156
+ });
157
+ }
158
+
159
+ if (existsSync("./tests/index.html")) {
160
+ Object.assign(viteConfig.build.rolldownOptions.input, {
161
+ tests: absolutePath("./tests/index.html"),
162
+ });
163
+ }
164
+
165
+ Object.assign(viteConfig.build.rolldownOptions.output, {
166
+ cleanDir: true,
167
+ strictExecutionOrder: true,
168
+ codeSplitting: {
169
+ groups: [
170
+ // our "test" product-land code happens to not match this pattern
171
+ { name: "tests", test: /[.-]test\.g?(j|t)s/, priority: 10 },
172
+ {
173
+ name: "common",
174
+ minShareCount: 10,
175
+ minSize: 10000,
176
+ maxSize: 1 * 1024 * 1024, // 1~ish MB
177
+ priority: 9,
178
+ },
179
+ {
180
+ name: "uncommon",
181
+ minShareCount: 2,
182
+ minSize: 10000,
183
+ maxSize: 1 * 1024 * 1024, // 1~ish MB
184
+ priority: 5,
185
+ },
186
+ ],
187
+ },
188
+ });
189
+
190
+ return;
191
+ }
192
+ function prod(viteConfig: ResolvedConfig, nvpConfig: Config) {
193
+ Object.assign(viteConfig.build, {
194
+ minify: "oxc",
195
+ sourcemap: true,
196
+ reportCompressedSize: false,
197
+ modulePreload: true,
198
+ cleanCssOptions: { sourceMap: true },
199
+ });
200
+
201
+ viteConfig.build.rolldownOptions ||= {};
202
+ viteConfig.build.rolldownOptions.input ||= {};
203
+ viteConfig.build.rolldownOptions.optimization ||= {};
204
+ viteConfig.build.rolldownOptions.output ||= {};
205
+
206
+ Object.assign(viteConfig.build.rolldownOptions.input, {
207
+ main: absolutePath("./index.html"),
208
+ });
209
+
210
+ Object.assign(viteConfig.build.rolldownOptions.optimization, {
211
+ inlineConst: {
212
+ mode: "smart",
213
+ pass: 3,
214
+ },
215
+ pifeForModuleWrappers: true,
216
+ });
217
+
218
+ Object.assign(viteConfig.build.rolldownOptions.output, {
219
+ minify: true,
220
+ cleanDir: true,
221
+ /**
222
+ * This is needed due to how we manage module state.
223
+ * This can create waterfalls when too much top-level await is used.
224
+ */
225
+ strictExecutionOrder: true,
226
+ /**
227
+ * Code splitting currently makes things worse
228
+ */
229
+ codeSplitting: {
230
+ /**
231
+ * Kind of a shame this isn't automatic based on dynamic import usage
232
+ * in vite8.
233
+ * Kinda forces us to know every potential problem area of the app,
234
+ * rather than rely on automatic optimizations based on import/module usage
235
+ */
236
+ groups: [...(nvpConfig?.production?.codeSplittingGroups ?? [])],
237
+ },
238
+ });
239
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Reasons we have to have babel
3
+ * - decorators
4
+ * - ember-concurrency
5
+ * - scoped-css
6
+ * - template compilation optimization
7
+ */
8
+ import { extensions } from "@embroider/vite";
9
+ import { babel } from "@rollup/plugin-babel";
10
+ import { join, resolve } from "node:path";
11
+ import { parse as oxcParse } from "oxc-parser";
12
+ import { transform } from "oxc-transform";
13
+ import { walk } from "zimmerframe";
14
+
15
+ /**
16
+ * if a file imports any of these, it must be optimize
17
+ */
18
+ const babelTemplateImports = new Set([
19
+ // Templates
20
+ "@ember/template-compiler",
21
+ "@ember/template-compilation",
22
+ "ember-cli-htmlbars",
23
+ "ember-cli-htmlbars-inline-precompile",
24
+ "htmlbars-inline-precompile",
25
+ ]);
26
+
27
+ const babelMacroImports = new Set([
28
+ // Macros
29
+ "@embroider/macros",
30
+ "@glimmer/env",
31
+ "@ember/application/deprecations",
32
+ // Macros only needed in prod
33
+ "@ember/debug",
34
+ ]); // Babel plugins required from libraries
35
+ const babelOtherImports = new Set([
36
+ "ember-concurrency",
37
+ "ember-scoped-css",
38
+ "ember-intl/helpers/format-message",
39
+ ]);
40
+
41
+ const babelRequiredImports = [...babelTemplateImports, ...babelMacroImports, ...babelOtherImports];
42
+
43
+ const counts = {
44
+ total: 0,
45
+ babel: 0,
46
+ why: {
47
+ decorators: 0,
48
+ formatMessage: 0,
49
+ imports: {
50
+ templates: 0,
51
+ macros: 0,
52
+ other: 0,
53
+ },
54
+ initializeRuntimeMacrosConfig: 0,
55
+ },
56
+ };
57
+ export function maybeBabel(options: { parallel?: boolean; configFile: string; env: any }) {
58
+ const { env, parallel, ...restOptions } = options;
59
+ const original = (() => {
60
+ const babelPath = resolve(join(process.cwd(), "./babel.config.js"));
61
+
62
+ return babel({
63
+ babelHelpers: "runtime",
64
+ extensions,
65
+ skipPreflightCheck: true,
66
+ ...restOptions,
67
+ configFile: babelPath,
68
+ parallel: parallel ?? true,
69
+ });
70
+ })();
71
+
72
+ /**
73
+ * In @rollup/plugin-babel v7+, the `transform` hook is the object form
74
+ * `{ filter, handler }` rather than a plain function. Normalize to the
75
+ * underlying handler so we can invoke it directly.
76
+ */
77
+ const originalTransform = (
78
+ typeof original.transform === "function" ? original.transform : original.transform?.handler
79
+ )!;
80
+
81
+ const babelMacros = new Set(babelRequiredImports);
82
+ if (env.mode === "development") {
83
+ babelMacros.delete("@ember/debug");
84
+ }
85
+
86
+ async function doTransform(this: any, code: string, id: string) {
87
+ counts.total++;
88
+
89
+ const ext = id.split(".").at(-1);
90
+ const lang = (ext === "gjs" ? "js" : ext === "gts" ? "ts" : ext) as
91
+ | "js"
92
+ | "ts"
93
+ | "jsx"
94
+ | "tsx"
95
+ | "dts"
96
+ | undefined;
97
+
98
+ let needsBabel = false;
99
+
100
+ const estree = await oxcParse(id, code, { lang });
101
+
102
+ walk(
103
+ estree.program as any,
104
+ /* state */ {},
105
+ {
106
+ Decorator(_node: any, { stop }: any) {
107
+ needsBabel = true;
108
+ counts.why.decorators++;
109
+ stop();
110
+ },
111
+ MemberExpression(node: any, { stop }: any) {
112
+ if (node.property?.name === "formatMessage" && node.object?.name === "intl") {
113
+ needsBabel = true;
114
+ counts.why.formatMessage++;
115
+ stop();
116
+ }
117
+ },
118
+ ImportDeclaration(node: any, { stop }: any) {
119
+ const value = node.source.value;
120
+ if (babelMacros.has(value)) {
121
+ needsBabel = true;
122
+
123
+ if (babelTemplateImports.has(value)) {
124
+ counts.why.imports.templates++;
125
+ } else if (babelMacroImports.has(value)) {
126
+ counts.why.imports.macros++;
127
+ } else {
128
+ counts.why.imports.other++;
129
+ }
130
+
131
+ stop();
132
+ }
133
+ },
134
+ },
135
+ );
136
+
137
+ if (!needsBabel && code.includes("initializeRuntimeMacrosConfig")) {
138
+ counts.why.initializeRuntimeMacrosConfig++;
139
+ needsBabel = true;
140
+ }
141
+
142
+ if (needsBabel) {
143
+ counts.babel++;
144
+
145
+ const result = await originalTransform.call(this, code, id);
146
+
147
+ return result;
148
+ }
149
+
150
+ if (ext === "json") {
151
+ return;
152
+ }
153
+
154
+ if (ext === "js" || ext === "gjs") {
155
+ // We don't need to process JS
156
+ return null;
157
+ }
158
+
159
+ const result = await transform(id, code, {
160
+ lang,
161
+ typescript: {
162
+ onlyRemoveTypeImports: true,
163
+ /**
164
+ * We should work towards disabling this.
165
+ */
166
+ allowNamespaces: true,
167
+ removeClassFieldsWithoutInitializer: false,
168
+ rewriteImportExtensions: false,
169
+ },
170
+ });
171
+
172
+ if (result.errors?.length) {
173
+ console.error(`Errors during oxc-transform of ${id}:`);
174
+ for (const err of result.errors) {
175
+ if (err.labels) {
176
+ console.error(err.labels);
177
+ }
178
+ console.error(err.codeframe || err.message);
179
+ }
180
+ throw result.errors;
181
+ }
182
+
183
+ return result;
184
+ }
185
+
186
+ return {
187
+ enforce: "pre",
188
+ buildEnd() {
189
+ if (process.env.INSPECT) {
190
+ console.debug(`Babel usage: ${counts.babel} / ${counts.total}`, counts.why);
191
+ }
192
+ },
193
+ ...original,
194
+ name: "nullvoxpopuli:babel",
195
+ transform: {
196
+ filter: {
197
+ id: {
198
+ include: [/\.js/, /\.gjs/, /\.ts/, /\.gts/],
199
+ exclude: [/\.json/],
200
+ },
201
+ // Enabling this opts us out of template compilation
202
+ // it's an AND instead of OR
203
+ // code: {
204
+ // include: [/initializeRuntimeMacrosConfig/, /precompileTemplate/],
205
+ // },
206
+ },
207
+ handler(this: any, code: string, id: string) {
208
+ try {
209
+ return doTransform.call(this, code, id);
210
+ } catch (e) {
211
+ // Use VSCode JavaScript Debug Terminal
212
+ // eslint-disable-next-line no-debugger
213
+ debugger;
214
+ throw e;
215
+ }
216
+ },
217
+ },
218
+ };
219
+ }
@@ -1,3 +1,4 @@
1
1
  packages:
2
2
  - .
3
3
  - src/bases/*/files
4
+ - packages/*
@@ -15,16 +15,3 @@ const ENV: Config = {
15
15
  };
16
16
 
17
17
  export default ENV;
18
-
19
- // @ts-expect-error: Ignoreing private API
20
- import { getGlobalConfig } from "@embroider/macros/src/addon/runtime";
21
-
22
- export function enterTestMode() {
23
- ENV.locationType = "none";
24
- ENV.APP.rootElement = "#ember-testing";
25
- ENV.APP.autoboot = false;
26
-
27
- const theMacrosGlobal = getGlobalConfig();
28
- theMacrosGlobal["@embroider/macros"] ||= {};
29
- theMacrosGlobal["@embroider/macros"].isTesting ||= true;
30
- }
@@ -1,6 +1,12 @@
1
1
  import { buildMacros } from "@embroider/macros/babel";
2
2
 
3
- const macros = buildMacros();
3
+ const macros = buildMacros({
4
+ configure(config) {
5
+ if (process.env.EMBER_ENV === "test") {
6
+ config.enableRuntimeMode();
7
+ }
8
+ },
9
+ });
4
10
 
5
11
  export default {
6
12
  plugins: [
@@ -25,28 +25,28 @@
25
25
  "start": "vite"
26
26
  },
27
27
  "dependencies": {
28
- "@embroider/macros": "^1.19.6",
28
+ "@embroider/macros": "^1.20.3",
29
29
  "@embroider/router": "^3.0.6",
30
- "@glimmer/component": "^2.0.0",
31
- "decorator-transforms": "^2.3.1",
32
- "ember-source": "~6.10.0",
30
+ "@glimmer/component": "^2.1.1",
31
+ "decorator-transforms": "^2.3.2",
32
+ "ember-source": "~7.0.0",
33
33
  "ember-strict-application-resolver": "^0.1.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@babel/core": "^7.28.5",
37
- "@babel/plugin-transform-runtime": "^7.28.5",
38
- "@babel/plugin-transform-typescript": "^7.28.5",
39
- "@babel/runtime": "^7.28.4",
36
+ "@babel/core": "^7.29.7",
37
+ "@babel/plugin-transform-runtime": "^7.29.7",
38
+ "@babel/plugin-transform-typescript": "^7.29.7",
39
+ "@babel/runtime": "^7.29.7",
40
40
  "@ember/app-tsconfig": "^2.0.0",
41
41
  "@embroider/core": "^4.4.2",
42
- "@embroider/vite": "^1.5.0",
43
- "@glint/ember-tsc": "^1.0.8",
44
- "@glint/template": "^1.7.3",
45
- "@glint/tsserver-plugin": "^2.0.8",
46
- "@rollup/plugin-babel": "^6.1.0",
42
+ "@glint/ember-tsc": "^1.7.5",
43
+ "@glint/template": "^1.7.7",
44
+ "@glint/tsserver-plugin": "^2.5.5",
45
+ "@nullvoxpopuli/ember-vite": "workspace:*",
46
+ "@rollup/plugin-babel": "^7.1.0",
47
47
  "babel-plugin-ember-template-compilation": "^4.0.0",
48
- "typescript": "^5.9.3",
49
- "vite": "^7.3.0"
48
+ "typescript": "^6.0.3",
49
+ "vite": "^8.0.14"
50
50
  },
51
51
  "engines": {
52
52
  "node": ">= 24"
@@ -1,13 +1,6 @@
1
1
  import { defineConfig } from "vite";
2
- import { extensions, ember } from "@embroider/vite";
3
- import { babel } from "@rollup/plugin-babel";
2
+ import { ember } from "@nullvoxpopuli/ember-vite";
4
3
 
5
4
  export default defineConfig({
6
- plugins: [
7
- ember(),
8
- babel({
9
- babelHelpers: "runtime",
10
- extensions,
11
- }),
12
- ],
5
+ plugins: [ember()],
13
6
  });
@@ -1,6 +1,7 @@
1
- import * as p from "@clack/prompts";
1
+ import { files } from "ember-apply";
2
2
  import { hasGit, initGit, isInGit } from "#utils/git.js";
3
3
  import { formatLabel } from "#utils/cli.js";
4
+ import { join } from "node:path";
4
5
 
5
6
  export default {
6
7
  label: formatLabel("git init"),
@@ -21,7 +22,11 @@ export default {
21
22
  return;
22
23
  }
23
24
 
24
- return initGit(project.directory);
25
+ let initOk = initGit(project.directory);
26
+ if (initOk) {
27
+ await files.applyFolder(join(import.meta.dirname, "files"), project.directory);
28
+ }
29
+ return initOk;
25
30
  },
26
31
 
27
32
  /**
@@ -11,12 +11,13 @@ import { getPendingWaiterState } from "@ember/test-waiters";
11
11
  import * as QUnit from "qunit";
12
12
  import { setup } from "qunit-dom";
13
13
  import { setupEmberOnerrorValidation, start as qunitStart } from "ember-qunit";
14
+ import { setTesting } from "@embroider/macros";
14
15
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15
16
  // @ts-expect-error
16
17
  import { getGlobalConfig } from "@embroider/macros/src/addon/runtime";
17
18
 
18
19
  import Application from "#app/app";
19
- import config, { enterTestMode } from "#config";
20
+ import config from "#config";
20
21
 
21
22
  Object.assign(window, {
22
23
  visit,
@@ -39,7 +40,10 @@ Object.assign(window, {
39
40
  });
40
41
 
41
42
  export function start() {
42
- enterTestMode();
43
+ config.locationType = "none";
44
+ config.APP.rootElement = "#ember-testing";
45
+ config.APP.autoboot = false;
46
+ setTesting(true);
43
47
 
44
48
  const theMacrosGlobal = getGlobalConfig();
45
49
 
@@ -0,0 +1,26 @@
1
+ import { module, test } from "qunit";
2
+ import { isTesting } from "@embroider/macros";
3
+ import { assert as emberAssert } from "@ember/debug";
4
+
5
+ module("@embroider/macros | runtime mode in tests", function () {
6
+ /**
7
+ * In compile-time mode `isTesting()` is inlined as `false` at build time,
8
+ * regardless of what the runtime global config says. Only when the test
9
+ * build runs the macros plugin in runtime mode (see babel.config.js +
10
+ * EMBER_ENV=test) does `isTesting()` reflect the value set by
11
+ * `setTesting(true)` in tests/test-helper.ts.
12
+ */
13
+ test("isTesting() is true", function (assert) {
14
+ assert.strictEqual(isTesting(), true);
15
+ });
16
+
17
+ /**
18
+ * `@ember/debug`'s `assert(description, condition)` is only active when
19
+ * DEBUG is true (i.e. non-production builds). Assertions are stripped
20
+ * from production bundles, so this also doubles as a sanity check that
21
+ * the test build keeps DEBUG enabled.
22
+ */
23
+ test("@ember/debug assert throws when the condition is false", function (assert) {
24
+ assert.throws(() => emberAssert("string", false));
25
+ });
26
+ });
@@ -38,7 +38,7 @@ export default {
38
38
  // Add scripts
39
39
  await packageJson.addScripts(
40
40
  {
41
- "build:test": "vite build --mode development",
41
+ "build:test": "EMBER_ENV=test NODE_ENV=development vite build --mode development",
42
42
  "test:ci": "testem ci --port 0",
43
43
  test: "pnpm build:test && pnpm test:ci",
44
44
  },
package/src/utils/npm.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import latestVersion from "latest-version";
2
+ import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
2
4
 
3
5
  /**
4
6
  * @type {{ [name: string]: { [version: string]: string } }}
@@ -9,6 +11,8 @@ const CACHE = {};
9
11
  * @param {{ [name: string]: string }} deps map of dep name to semver range
10
12
  */
11
13
  export async function getLatest(deps) {
14
+ let needsLocalLink = await needsWorkspace();
15
+
12
16
  let results = await Promise.all(
13
17
  Object.entries(deps).map(async ([dep, range]) => {
14
18
  let existing = CACHE[dep]?.[range];
@@ -16,7 +20,25 @@ export async function getLatest(deps) {
16
20
  return [dep, existing];
17
21
  }
18
22
 
19
- let version = await latestVersion(dep, { version: range });
23
+ let version;
24
+
25
+ /**
26
+ * HACK FOR CI.
27
+ * In practice, this package will be published separately, and that version will be used.
28
+ * Only the local, not-yet-published @nullvoxpopuli/ember-vite package needs the link;
29
+ * every other dependency must still resolve its real version.
30
+ *
31
+ * We use `link:` (a symlink) rather than `file:` (a copy into the store)
32
+ * on purpose: the package ships TypeScript source, and Node 24 refuses to
33
+ * strip types for files physically located under node_modules. A symlink
34
+ * makes Node resolve the realpath to packages/vite (outside node_modules),
35
+ * so its `.ts` runs directly.
36
+ */
37
+ if (needsLocalLink && dep === "@nullvoxpopuli/ember-vite") {
38
+ version = "link:" + resolve(join(import.meta.dirname, "../../packages/vite"));
39
+ } else {
40
+ version = await latestVersion(dep, { version: range });
41
+ }
20
42
 
21
43
  CACHE[dep] ||= {};
22
44
  CACHE[dep][range] = version;
@@ -27,3 +49,15 @@ export async function getLatest(deps) {
27
49
 
28
50
  return Object.fromEntries(results);
29
51
  }
52
+
53
+ async function needsWorkspace() {
54
+ if (process.env.GITHUB_REPOSITORY === "NullVoxPopuli/ember.nvp") return true;
55
+
56
+ let root = resolve(import.meta.dirname, "../../");
57
+
58
+ if (existsSync(join(root, ".git"))) {
59
+ return true;
60
+ }
61
+
62
+ return false;
63
+ }
package/tsconfig.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "exclude": [
9
9
  "node_modules",
10
10
  "dist",
11
+ "packages",
11
12
  "src/**/layers/*/files/**",
12
13
  "src/**/bases/*/files/**",
13
14
  "my-app"
File without changes