ember.nvp 0.5.0 → 0.7.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 (91) hide show
  1. package/.github/workflows/ci.yml +17 -2
  2. package/.release-plan.json +84 -4
  3. package/CHANGELOG.md +68 -0
  4. package/README.md +29 -0
  5. package/package.json +4 -2
  6. package/packages/build-tooling-utils/README.md +37 -0
  7. package/packages/build-tooling-utils/index.ts +2 -0
  8. package/packages/build-tooling-utils/package.json +42 -0
  9. package/packages/build-tooling-utils/rolldown.config.js +18 -0
  10. package/packages/build-tooling-utils/src/extensions.ts +18 -0
  11. package/packages/build-tooling-utils/src/maybe-babel.ts +146 -0
  12. package/packages/rolldown/README.md +115 -0
  13. package/packages/rolldown/index.ts +56 -0
  14. package/packages/rolldown/package.json +59 -0
  15. package/packages/rolldown/rolldown.config.js +21 -0
  16. package/packages/rolldown/src/babel.ts +136 -0
  17. package/packages/rolldown/src/define-config.ts +32 -0
  18. package/packages/rolldown/src/externals.ts +83 -0
  19. package/packages/rolldown/src/isolated-declarations.ts +65 -0
  20. package/packages/rolldown/src/transform.ts +162 -0
  21. package/packages/vite/README.md +7 -21
  22. package/packages/vite/index.ts +1 -1
  23. package/packages/vite/package.json +4 -6
  24. package/packages/vite/src/ember.ts +129 -15
  25. package/packages/vite/src/test-html.ts +86 -0
  26. package/src/bases/minimal-app/files/app/router.ts +1 -0
  27. package/src/bases/minimal-app/files/tsconfig.json +1 -0
  28. package/src/bases/minimal-app/index.js +2 -2
  29. package/src/bases/minimal-library/files/README.md +41 -0
  30. package/src/bases/minimal-library/files/package.json +49 -0
  31. package/src/bases/minimal-library/files/tsconfig.json +10 -0
  32. package/src/bases/minimal-library/files/tsdown.config.js +6 -0
  33. package/src/bases/minimal-library/index.js +158 -2
  34. package/src/cli/args.js +7 -1
  35. package/src/cli/index.js +90 -16
  36. package/src/cli/questions/layers.js +5 -2
  37. package/src/cli/questions/project-type.js +0 -5
  38. package/src/cli/questions/write.js +169 -0
  39. package/src/index.js +1 -0
  40. package/src/layers/are-the-types-wrong/index.js +74 -0
  41. package/src/layers/git/files/gitignore +14 -1
  42. package/src/layers/git/index.js +6 -5
  43. package/src/layers/github-actions/index.js +12 -8
  44. package/src/layers/index.js +1 -1
  45. package/src/layers/inspector-support/index.js +101 -0
  46. package/src/layers/prettier/files/.prettierignore +4 -0
  47. package/src/layers/publint/index.js +68 -0
  48. package/src/layers/qunit/files/tests/unit/macros-test.ts +2 -2
  49. package/src/layers/qunit/index.js +107 -7
  50. package/src/layers/qunit/library-files/config/test/babel.config.js +41 -0
  51. package/src/layers/qunit/library-files/config/test/testem.cjs +26 -0
  52. package/src/layers/qunit/library-files/tests/test-helper.ts +36 -0
  53. package/src/layers/qunit/library-files/tests/unit/.gitkeep +0 -0
  54. package/src/layers/qunit/library-files/vite.config.mjs +6 -0
  55. package/src/layers/typescript/index.js +43 -7
  56. package/src/layers/vitest/files/vitest.config.mjs +11 -11
  57. package/src/layers/vitest/index.js +70 -32
  58. package/src/utils/babel.js +9 -6
  59. package/src/utils/fs.js +44 -3
  60. package/src/utils/inspector-support.js +196 -0
  61. package/src/utils/npm.js +21 -7
  62. package/src/utils/remove-types.js +5 -0
  63. package/src/utils/rewrite-imports.js +157 -0
  64. package/src/utils/stage.js +317 -0
  65. package/src/utils/tsdown-config.js +85 -0
  66. package/src/utils/types.ts +5 -1
  67. package/test/ember.nvp/base-library.test.ts +232 -0
  68. package/test/ember.nvp/generator-replace-or-update.test.ts +2 -3
  69. package/test/ember.nvp/inspector-support.test.ts +179 -0
  70. package/test/ember.nvp/layer-inspector-support.test.ts +69 -0
  71. package/test/ember.nvp/layer-publish-checks.test.ts +103 -0
  72. package/test/ember.nvp/layer-qunit.test.ts +376 -15
  73. package/test/ember.nvp/layer-vitest.test.ts +250 -0
  74. package/test/ember.nvp/layers.test.ts +16 -0
  75. package/test/ember.nvp/library-src-fixtures.ts +81 -0
  76. package/test/ember.nvp/permutations/app-1.test.ts +3 -0
  77. package/test/ember.nvp/permutations/app-2.test.ts +3 -0
  78. package/test/ember.nvp/permutations/library-1.test.ts +3 -0
  79. package/test/ember.nvp/permutations/library-2.test.ts +3 -0
  80. package/test/ember.nvp/permutations/shared.ts +239 -0
  81. package/test/ember.nvp/rewrite-imports.test.ts +235 -0
  82. package/test/ember.nvp/stage.test.ts +188 -0
  83. package/test/test-helpers/permutate.test.ts +17 -0
  84. package/test/test-helpers.ts +20 -7
  85. package/tsconfig.json +1 -0
  86. package/packages/vite/src/maybe-babel.ts +0 -219
  87. package/src/layers/qunit/files/run-tests.mjs +0 -30
  88. package/src/layers/vitest/files/tests/test-helper.ts +0 -5
  89. package/test/ember.nvp/permutations.test.ts +0 -200
  90. /package/src/{layers/vitest/files/tests/integration/.gitkeep → bases/minimal-library/files/src/index.ts} +0 -0
  91. /package/src/layers/{vitest/files/tests/unit → qunit/library-files/tests/rendering}/.gitkeep +0 -0
@@ -69,7 +69,7 @@ concurrency:
69
69
  cancel-in-progress: true`;
70
70
 
71
71
  const lint = {
72
- npm: `lint:
72
+ npm: ` lint:
73
73
  name: "Lints"
74
74
  runs-on: ubuntu-latest
75
75
  steps:
@@ -78,18 +78,20 @@ const lint = {
78
78
  with:
79
79
  node-version: 24
80
80
  - run: npm install
81
- - run: npm run lint`,
81
+ - run: npm run lint
82
+ `,
82
83
  pnpm: ` lint:
83
84
  name: "Lints"
84
85
  runs-on: ubuntu-latest
85
86
  steps:
86
87
  - uses: actions/checkout@v6
87
88
  - uses: wyvox/action-setup-pnpm@v3
88
- - run: pnpm lint`,
89
+ - run: pnpm lint
90
+ `,
89
91
  };
90
92
 
91
93
  const test = {
92
- npm: `test:
94
+ npm: ` test:
93
95
  name: "Tests"
94
96
  runs-on: ubuntu-latest
95
97
  steps:
@@ -98,14 +100,16 @@ const test = {
98
100
  with:
99
101
  node-version: 24
100
102
  - run: npm install
101
- - run: npm test`,
103
+ - run: npm test
104
+ `,
102
105
  pnpm: ` test:
103
106
  name: "Tests"
104
107
  runs-on: ubuntu-latest
105
108
  steps:
106
109
  - uses: actions/checkout@v6
107
110
  - uses: wyvox/action-setup-pnpm@v3
108
- - run: pnpm test`,
111
+ - run: pnpm test
112
+ `,
109
113
  };
110
114
 
111
115
  /**
@@ -175,7 +179,7 @@ async function addOrUpdateLints(project, file) {
175
179
  file += "\n\njobs:\n";
176
180
  }
177
181
 
178
- return file + "\n" + lint[project.desires.packageManager];
182
+ return file + lint[project.desires.packageManager];
179
183
  }
180
184
 
181
185
  /**
@@ -196,5 +200,5 @@ async function addOrUpdateTests(project, file) {
196
200
  file += "\n\njobs:\n";
197
201
  }
198
202
 
199
- return file + "\n" + test[project.desires.packageManager];
203
+ return file + test[project.desires.packageManager];
200
204
  }
@@ -4,7 +4,7 @@ import { join } from "node:path";
4
4
  /**
5
5
  * @type {Set<string>}
6
6
  */
7
- export const TODO = new Set(["vitest", "release-plan", "eslint-ejected"]);
7
+ export const TODO = new Set(["release-plan", "eslint-ejected"]);
8
8
 
9
9
  /**
10
10
  * @returns {Promise<Array<import('#types').DiscoveredLayer>>}
@@ -0,0 +1,101 @@
1
+ import { packageJson } from "ember-apply";
2
+ import { writeFile } from "node:fs/promises";
3
+ import { getLatest } from "#utils/npm.js";
4
+ import {
5
+ hasInspectorSupport,
6
+ INSPECTOR_PACKAGE,
7
+ wireInspectorSupport,
8
+ } from "#utils/inspector-support.js";
9
+
10
+ const deps = {
11
+ [INSPECTOR_PACKAGE]: "^0.1.3",
12
+ };
13
+
14
+ const APPLICATION_FILES = ["app/app.ts", "app/app.js"];
15
+
16
+ /**
17
+ * @param {import('#utils/project.js').Project} project
18
+ * @returns {string | undefined}
19
+ */
20
+ function findApplicationFile(project) {
21
+ return APPLICATION_FILES.find((file) => project.hasFile(file));
22
+ }
23
+
24
+ /**
25
+ * @type {import('#types').Layer}
26
+ */
27
+ export default {
28
+ label: "Inspector Support",
29
+ hint: "Make the app inspectable by the Ember Inspector browser extension",
30
+
31
+ async run(project) {
32
+ let file = findApplicationFile(project);
33
+
34
+ // libraries have no Application to wire
35
+ if (!file) return;
36
+
37
+ let path = project.path(file);
38
+ let code = (await project.read(file)) ?? "";
39
+ let wired = wireInspectorSupport(code, path);
40
+
41
+ if (wired !== code) {
42
+ await writeFile(path, wired);
43
+ }
44
+
45
+ // the dependency is only useful once the Application actually got
46
+ // wired (the codemod leaves unrecognized Application shapes alone)
47
+ if (!hasInspectorSupport(wired, path)) return;
48
+
49
+ await packageJson.addDevDependencies(await getLatest(deps), project.directory);
50
+ },
51
+
52
+ /**
53
+ * @overload
54
+ * @param {import('#utils/project.js').Project} project
55
+ * @param {true} explain
56
+ * @returns {Promise<{ isSetup: boolean; reasons: string[] }>}
57
+ */
58
+ /**
59
+ * @overload
60
+ * @param {import('#utils/project.js').Project} project
61
+ * @param {boolean | undefined} [explain]
62
+ * @returns {Promise<boolean>}
63
+ */
64
+ async isSetup(project, explain) {
65
+ const reasons = [];
66
+ let file = findApplicationFile(project);
67
+
68
+ if (!file) {
69
+ if (!explain) return false;
70
+
71
+ reasons.push("there is no Application to wire (no app/app.ts or app/app.js)");
72
+ } else {
73
+ let code = (await project.read(file)) ?? "";
74
+
75
+ if (!hasInspectorSupport(code, project.path(file))) {
76
+ if (!explain) return false;
77
+
78
+ reasons.push(`${file} does not wire ${INSPECTOR_PACKAGE} into the Application`);
79
+ }
80
+ }
81
+
82
+ let manifest = await packageJson.read(project.directory);
83
+
84
+ for (let dep of Object.keys(deps)) {
85
+ if (!manifest.devDependencies?.[dep]) {
86
+ if (!explain) return false;
87
+
88
+ reasons.push(`package.json is missing ${dep} in devDependencies`);
89
+ }
90
+ }
91
+
92
+ if (explain) {
93
+ return {
94
+ isSetup: reasons.length === 0,
95
+ reasons,
96
+ };
97
+ }
98
+
99
+ return reasons.length === 0;
100
+ },
101
+ };
@@ -8,6 +8,10 @@
8
8
  # dependencies
9
9
  /node_modules/
10
10
 
11
+ # lockfiles
12
+ pnpm-lock.yaml
13
+ package-lock.json
14
+
11
15
  # misc
12
16
  /.env*
13
17
  /.vite/
@@ -0,0 +1,68 @@
1
+ import { packageJson } from "ember-apply";
2
+ import { getLatest } from "#utils/npm.js";
3
+ import { addTsdownConfigProperty, hasTsdownConfigProperty } from "#utils/tsdown-config.js";
4
+
5
+ const deps = {
6
+ publint: "^0.3.21",
7
+ };
8
+
9
+ /**
10
+ * @type {import('#types').Layer}
11
+ */
12
+ export default {
13
+ label: "publint",
14
+ hint: "validate the publishable package shape on every build",
15
+
16
+ async run(project) {
17
+ // Publish checks: apps aren't published
18
+ if (project.type !== "library") return;
19
+
20
+ await addTsdownConfigProperty(project, "publint", "publint: true");
21
+ await packageJson.addDevDependencies(await getLatest(deps), project.directory);
22
+ },
23
+
24
+ /**
25
+ * @overload
26
+ * @param {import('#utils/project.js').Project} project
27
+ * @param {true} explain
28
+ * @returns {Promise<{ isSetup: boolean; reasons: string[] }>}
29
+ */
30
+ /**
31
+ * @overload
32
+ * @param {import('#utils/project.js').Project} project
33
+ * @param {boolean | undefined} [explain]
34
+ * @returns {Promise<boolean>}
35
+ */
36
+ async isSetup(project, explain) {
37
+ /** @type {string[]} */
38
+ const reasons = [];
39
+
40
+ // Nothing to set up outside libraries, so nothing can be missing
41
+ if (project.type !== "library") {
42
+ return explain ? { isSetup: true, reasons } : true;
43
+ }
44
+
45
+ if (!(await hasTsdownConfigProperty(project, "publint"))) {
46
+ if (!explain) return false;
47
+
48
+ reasons.push("tsdown.config.js does not configure publint");
49
+ }
50
+
51
+ let manifest = await packageJson.read(project.directory);
52
+
53
+ if (!manifest.devDependencies?.["publint"]) {
54
+ if (!explain) return false;
55
+
56
+ reasons.push("package.json is missing publint in devDependencies");
57
+ }
58
+
59
+ if (explain) {
60
+ return {
61
+ isSetup: reasons.length === 0,
62
+ reasons,
63
+ };
64
+ }
65
+
66
+ return reasons.length === 0;
67
+ },
68
+ };
@@ -1,6 +1,6 @@
1
+ import { assert as emberAssert } from "@ember/debug";
1
2
  import { module, test } from "qunit";
2
3
  import { isTesting } from "@embroider/macros";
3
- import { assert as emberAssert } from "@ember/debug";
4
4
 
5
5
  module("@embroider/macros | runtime mode in tests", function () {
6
6
  /**
@@ -11,7 +11,7 @@ module("@embroider/macros | runtime mode in tests", function () {
11
11
  * `setTesting(true)` in tests/test-helper.ts.
12
12
  */
13
13
  test("isTesting() is true", function (assert) {
14
- assert.strictEqual(isTesting(), true);
14
+ assert.true(isTesting());
15
15
  });
16
16
 
17
17
  /**
@@ -1,6 +1,7 @@
1
1
  import { applyFolderTo } from "#utils/fs.js";
2
2
  import { getLatest } from "#utils/npm.js";
3
- import { packageJson, files } from "ember-apply";
3
+ import { hasConfiguredPlugin, prependPlugin, removeConfiguredPlugin } from "#utils/babel.js";
4
+ import { packageJson } from "ember-apply";
4
5
  import { join } from "node:path";
5
6
 
6
7
  const deps = {
@@ -16,6 +17,41 @@ const tsDeps = {
16
17
  "@types/qunit": "^2.19.12",
17
18
  };
18
19
 
20
+ /**
21
+ * Libraries don't have the app base's vite stack, so the test build
22
+ * brings the pieces the generated configs and test-helper import
23
+ * directly. Everything else (babel itself, the embroider machinery)
24
+ * comes with @nullvoxpopuli/ember-vite.
25
+ */
26
+ const libraryDeps = {
27
+ ...deps,
28
+ "@embroider/macros": "^1.20.3",
29
+ "@nullvoxpopuli/ember-vite": "workspace:*",
30
+ "babel-plugin-ember-template-compilation": "^4.0.0",
31
+ "ember-strict-application-resolver": "^0.1.0",
32
+ vite: "^8.0.14",
33
+ };
34
+ const libraryTsDeps = {
35
+ ...tsDeps,
36
+ "@babel/plugin-transform-typescript": "^7.28.5",
37
+ };
38
+
39
+ const TEST_BABEL_CONFIG = "config/test/babel.config.js";
40
+
41
+ /**
42
+ * @param {import('#utils/project.js').Project} project
43
+ */
44
+ function depsFor(project) {
45
+ return project.type === "library" ? libraryDeps : deps;
46
+ }
47
+
48
+ /**
49
+ * @param {import('#utils/project.js').Project} project
50
+ */
51
+ function tsDepsFor(project) {
52
+ return project.type === "library" ? libraryTsDeps : tsDeps;
53
+ }
54
+
19
55
  /**
20
56
  * @type {import('#types').Layer}
21
57
  */
@@ -23,14 +59,23 @@ export default {
23
59
  label: "QUnit",
24
60
 
25
61
  async run(project) {
26
- let filePath = join(import.meta.dirname, "files");
27
- await applyFolderTo(filePath, project);
62
+ let isLibrary = project.type === "library";
28
63
  let ts = await project.hasOrWantsLayer("typescript");
29
64
 
65
+ if (isLibrary) {
66
+ await addSourceImports(project);
67
+ }
68
+
69
+ await applyFolderTo(join(import.meta.dirname, isLibrary ? "library-files" : "files"), project);
70
+
71
+ if (isLibrary) {
72
+ await syncTestBabelConfig(project, ts);
73
+ }
74
+
30
75
  await packageJson.addDevDependencies(
31
76
  await getLatest({
32
- ...deps,
33
- ...(ts ? tsDeps : {}),
77
+ ...depsFor(project),
78
+ ...(ts ? tsDepsFor(project) : {}),
34
79
  }),
35
80
  project.directory,
36
81
  );
@@ -39,7 +84,9 @@ export default {
39
84
  await packageJson.addScripts(
40
85
  {
41
86
  "build:test": "EMBER_ENV=test NODE_ENV=development vite build --mode development",
42
- "test:ci": "testem ci --port 0",
87
+ "test:ci": isLibrary
88
+ ? "testem ci --file config/test/testem.cjs --port 0"
89
+ : "testem ci --port 0",
43
90
  test: "pnpm build:test && pnpm test:ci",
44
91
  },
45
92
  project.directory,
@@ -59,7 +106,10 @@ export default {
59
106
  */
60
107
  async isSetup(project, explain) {
61
108
  const reasons = [];
62
- let depsToCheck = [Object.keys(deps), project.wantsTypeScript ? Object.keys(tsDeps) : ""]
109
+ let depsToCheck = [
110
+ Object.keys(depsFor(project)),
111
+ project.wantsTypeScript ? Object.keys(tsDepsFor(project)) : "",
112
+ ]
63
113
  .flat()
64
114
  .filter(Boolean);
65
115
 
@@ -100,3 +150,53 @@ export default {
100
150
  return reasons.length === 0;
101
151
  },
102
152
  };
153
+
154
+ /**
155
+ * Tests import the library's source (not its published dist), so the
156
+ * `#src/*` subpath must exist before the test files are applied --
157
+ * applying rewrites their import extensions by resolving each specifier
158
+ * through this mapping.
159
+ *
160
+ * @param {import('#utils/project.js').Project} project
161
+ */
162
+ async function addSourceImports(project) {
163
+ await packageJson.modify((json) => {
164
+ json.imports ||= {};
165
+ json.imports["#src/*"] ||= "./src/*";
166
+ }, project.directory);
167
+ }
168
+
169
+ /**
170
+ * The test babel config is the qunit layer's own (a root babel config,
171
+ * when a library has one, belongs to the publish build), so this layer
172
+ * also keeps the config's TypeScript handling in step with the project:
173
+ * the shipped config carries the TS plugin, which must not survive in a
174
+ * JavaScript project, and must come back when a project adopts the
175
+ * typescript layer later.
176
+ *
177
+ * @param {import('#utils/project.js').Project} project
178
+ * @param {boolean} ts
179
+ */
180
+ async function syncTestBabelConfig(project, ts) {
181
+ if (!ts) {
182
+ await removeConfiguredPlugin(project, "@babel/plugin-transform-typescript", TEST_BABEL_CONFIG);
183
+ return;
184
+ }
185
+
186
+ if (await hasConfiguredPlugin(project, "@babel/plugin-transform-typescript", TEST_BABEL_CONFIG)) {
187
+ return;
188
+ }
189
+
190
+ await prependPlugin(
191
+ project,
192
+ `[
193
+ "@babel/plugin-transform-typescript",
194
+ {
195
+ allExtensions: true,
196
+ onlyRemoveTypeImports: true,
197
+ allowDeclareFields: true,
198
+ },
199
+ ]`,
200
+ TEST_BABEL_CONFIG,
201
+ );
202
+ }
@@ -0,0 +1,41 @@
1
+ import { buildMacros } from "@embroider/macros/babel";
2
+
3
+ const macros = buildMacros({
4
+ configure(config) {
5
+ if (process.env.EMBER_ENV === "test") {
6
+ config.enableRuntimeMode();
7
+ }
8
+ },
9
+ });
10
+
11
+ export default {
12
+ plugins: [
13
+ [
14
+ "@babel/plugin-transform-typescript",
15
+ {
16
+ allExtensions: true,
17
+ onlyRemoveTypeImports: true,
18
+ allowDeclareFields: true,
19
+ },
20
+ ],
21
+ [
22
+ "babel-plugin-ember-template-compilation",
23
+ {
24
+ transforms: [...macros.templateMacros],
25
+ },
26
+ ],
27
+ [
28
+ "module:decorator-transforms",
29
+ {
30
+ runtime: {
31
+ import: import.meta.resolve("decorator-transforms/runtime-esm"),
32
+ },
33
+ },
34
+ ],
35
+ ...macros.babelMacros,
36
+ ],
37
+
38
+ generatorOpts: {
39
+ compact: false,
40
+ },
41
+ };
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ if (typeof module !== "undefined") {
4
+ module.exports = {
5
+ test_page: "tests/index.html?hidepassed",
6
+ cwd: "dist",
7
+ disable_watching: true,
8
+ launch_in_ci: ["Chrome"],
9
+ launch_in_dev: ["Chrome"],
10
+ browser_start_timeout: 120,
11
+ browser_args: {
12
+ Chrome: {
13
+ ci: [
14
+ // --no-sandbox is needed when running Chrome inside a container
15
+ process.env.CI ? "--no-sandbox" : null,
16
+ "--headless",
17
+ "--disable-dev-shm-usage",
18
+ "--disable-software-rasterizer",
19
+ "--mute-audio",
20
+ "--remote-debugging-port=0",
21
+ "--window-size=1440,900",
22
+ ].filter(Boolean),
23
+ },
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,36 @@
1
+ import EmberRouter from "@ember/routing/router";
2
+ import { resetOnerror, setApplication } from "@ember/test-helpers";
3
+ import * as QUnit from "qunit";
4
+ import { setup } from "qunit-dom";
5
+ import { setupEmberOnerrorValidation, start as qunitStart } from "ember-qunit";
6
+ import { setTesting } from "@embroider/macros";
7
+ import EmberApp from "ember-strict-application-resolver";
8
+
9
+ class Router extends EmberRouter {
10
+ location = "none";
11
+ rootURL = "/";
12
+ }
13
+
14
+ Router.map(function () {});
15
+
16
+ class TestApp extends EmberApp {
17
+ modules = {
18
+ "./router": Router,
19
+ };
20
+ }
21
+
22
+ export function start() {
23
+ setTesting(true);
24
+ setApplication(
25
+ TestApp.create({
26
+ autoboot: false,
27
+ rootElement: "#ember-testing",
28
+ }),
29
+ );
30
+ setup(QUnit.assert);
31
+ setupEmberOnerrorValidation();
32
+
33
+ QUnit.testDone(resetOnerror);
34
+
35
+ qunitStart();
36
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "vite";
2
+ import { ember } from "@nullvoxpopuli/ember-vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [ember({ babel: { configFile: "./config/test/babel.config.js" } })],
6
+ });
@@ -9,21 +9,50 @@ const bases = join(import.meta.dirname, "../../bases");
9
9
  const appBase = join(bases, "minimal-app/files");
10
10
  const libraryBase = join(bases, "minimal-library/files");
11
11
 
12
- const deps = {
13
- "@babel/plugin-transform-typescript": "^7.28.5",
14
- "@ember/app-tsconfig": "^2.0.0",
12
+ const sharedDeps = {
15
13
  "@glint/ember-tsc": "^1.0.8",
16
14
  "@glint/template": "^1.7.3",
17
15
  "@glint/tsserver-plugin": "^2.0.8",
18
- typescript: "^5.9.3",
16
+ typescript: "^6.0.3",
17
+ };
18
+
19
+ const appDeps = {
20
+ // Apps strip types via their own babel.config.js; libraries have no babel
21
+ // config -- ember() handles type stripping.
22
+ "@babel/plugin-transform-typescript": "^7.28.5",
23
+ "@ember/app-tsconfig": "^2.0.0",
24
+ };
25
+
26
+ const libraryDeps = {
27
+ "@ember/library-tsconfig": "^2.0.0",
19
28
  };
20
29
 
30
+ /**
31
+ * @param {import('#utils/project.js').Project} project
32
+ */
33
+ function depsFor(project) {
34
+ return {
35
+ ...sharedDeps,
36
+ ...(project.type === "library" ? libraryDeps : appDeps),
37
+ };
38
+ }
39
+
21
40
  /**
22
41
  * @type {import('#types').Layer}
23
42
  */
24
43
  export default {
25
44
  label: "TypeScript",
26
45
 
46
+ /**
47
+ * Libraries publish declarations, so they are TypeScript unless the
48
+ * user opts out.
49
+ *
50
+ * @param {import('#types').ProjectType} projectType
51
+ */
52
+ defaultValue(projectType) {
53
+ return projectType === "library";
54
+ },
55
+
27
56
  async run(project) {
28
57
  /**
29
58
  * if jsconfig exists, switch to tsconfig
@@ -57,7 +86,9 @@ export default {
57
86
  reasons.push("tsconfig.json is missing");
58
87
  }
59
88
 
60
- if (!(await hasConfiguredTSBabel(project))) {
89
+ // Only projects with their own babel config need the TS plugin in it;
90
+ // without one (libraries), ember() strips types.
91
+ if (project.hasFile("babel.config.js") && !(await hasConfiguredTSBabel(project))) {
61
92
  if (!explain) return false;
62
93
 
63
94
  reasons.push(`babel.config is missing @babel/plugin-transform-typescript`);
@@ -71,7 +102,7 @@ export default {
71
102
  reasons.push(`package.json is missing the "lint:types" script`);
72
103
  }
73
104
 
74
- for (let dep of Object.keys(deps)) {
105
+ for (let dep of Object.keys(depsFor(project))) {
75
106
  if (!manifest.devDependencies?.[dep]) {
76
107
  if (!explain) return false;
77
108
 
@@ -101,7 +132,7 @@ async function updatePackageJson(project) {
101
132
  Object.assign(json.scripts, {
102
133
  "lint:types": "ember-tsc --noEmit",
103
134
  });
104
- Object.assign(json.devDependencies, await getLatest(deps));
135
+ Object.assign(json.devDependencies, await getLatest(depsFor(project)));
105
136
  }, project.directory);
106
137
  }
107
138
 
@@ -127,6 +158,11 @@ async function addTSConfig(project) {
127
158
  * @param {import('#utils/project.js').Project} project
128
159
  */
129
160
  async function updateBabelConfig(project) {
161
+ if (!project.hasFile("babel.config.js")) {
162
+ // No babel config to patch (libraries): ember() strips types.
163
+ return;
164
+ }
165
+
130
166
  if (await hasConfiguredTSBabel(project)) {
131
167
  return;
132
168
  }
@@ -1,18 +1,18 @@
1
+ import { ember } from "@nullvoxpopuli/ember-vite";
2
+ import { webdriverio } from "@vitest/browser-webdriverio";
1
3
  import { defineConfig } from "vitest/config";
2
- import { extensions, ember } from "@embroider/vite";
3
- import { babel } from "@rollup/plugin-babel";
4
4
 
5
5
  export default defineConfig({
6
- plugins: [
7
- ember(),
8
- babel({
9
- babelHelpers: "runtime",
10
- extensions: extensions.flatMap((extension) => [extension, ".gts", ".gjs"]),
11
- }),
12
- ],
6
+ plugins: [ember()],
13
7
  test: {
14
- environment: "happy-dom",
15
- setupFiles: ["./tests/test-helper.ts"],
16
8
  include: ["tests/**/*-test.{js,ts,gjs,gts}"],
9
+ maxConcurrency: 1,
10
+ passWithNoTests: true,
11
+ browser: {
12
+ provider: webdriverio(),
13
+ enabled: true,
14
+ headless: true,
15
+ instances: [{ browser: "chrome" }],
16
+ },
17
17
  },
18
18
  });