ember.nvp 0.6.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 (85) hide show
  1. package/.github/workflows/ci.yml +17 -2
  2. package/.release-plan.json +88 -4
  3. package/CHANGELOG.md +57 -0
  4. package/package.json +3 -2
  5. package/packages/build-tooling-utils/README.md +37 -0
  6. package/packages/build-tooling-utils/index.ts +2 -0
  7. package/packages/build-tooling-utils/package.json +42 -0
  8. package/packages/build-tooling-utils/rolldown.config.js +18 -0
  9. package/packages/build-tooling-utils/src/extensions.ts +18 -0
  10. package/packages/build-tooling-utils/src/maybe-babel.ts +146 -0
  11. package/packages/rolldown/README.md +115 -0
  12. package/packages/rolldown/index.ts +56 -0
  13. package/packages/rolldown/package.json +59 -0
  14. package/packages/rolldown/rolldown.config.js +21 -0
  15. package/packages/rolldown/src/babel.ts +136 -0
  16. package/packages/rolldown/src/define-config.ts +32 -0
  17. package/packages/rolldown/src/externals.ts +83 -0
  18. package/packages/rolldown/src/isolated-declarations.ts +65 -0
  19. package/packages/rolldown/src/transform.ts +162 -0
  20. package/packages/vite/README.md +7 -21
  21. package/packages/vite/index.ts +1 -1
  22. package/packages/vite/package.json +4 -6
  23. package/packages/vite/src/ember.ts +129 -15
  24. package/packages/vite/src/test-html.ts +86 -0
  25. package/src/bases/minimal-app/files/app/router.ts +1 -0
  26. package/src/bases/minimal-app/files/tsconfig.json +1 -0
  27. package/src/bases/minimal-app/index.js +2 -2
  28. package/src/bases/minimal-library/files/README.md +41 -0
  29. package/src/bases/minimal-library/files/package.json +49 -0
  30. package/src/bases/minimal-library/files/tsconfig.json +10 -0
  31. package/src/bases/minimal-library/files/tsdown.config.js +6 -0
  32. package/src/bases/minimal-library/index.js +158 -2
  33. package/src/cli/index.js +21 -2
  34. package/src/cli/questions/layers.js +5 -2
  35. package/src/cli/questions/project-type.js +0 -5
  36. package/src/layers/are-the-types-wrong/index.js +74 -0
  37. package/src/layers/git/files/gitignore +14 -1
  38. package/src/layers/git/index.js +6 -5
  39. package/src/layers/github-actions/index.js +12 -8
  40. package/src/layers/index.js +1 -1
  41. package/src/layers/inspector-support/index.js +101 -0
  42. package/src/layers/prettier/files/.prettierignore +4 -0
  43. package/src/layers/publint/index.js +68 -0
  44. package/src/layers/qunit/files/tests/unit/macros-test.ts +2 -2
  45. package/src/layers/qunit/index.js +107 -7
  46. package/src/layers/qunit/library-files/config/test/babel.config.js +41 -0
  47. package/src/layers/qunit/library-files/config/test/testem.cjs +26 -0
  48. package/src/layers/qunit/library-files/tests/test-helper.ts +36 -0
  49. package/src/layers/qunit/library-files/tests/unit/.gitkeep +0 -0
  50. package/src/layers/qunit/library-files/vite.config.mjs +6 -0
  51. package/src/layers/typescript/index.js +43 -7
  52. package/src/layers/vitest/files/vitest.config.mjs +11 -11
  53. package/src/layers/vitest/index.js +70 -32
  54. package/src/utils/babel.js +9 -6
  55. package/src/utils/fs.js +44 -3
  56. package/src/utils/inspector-support.js +196 -0
  57. package/src/utils/npm.js +21 -7
  58. package/src/utils/remove-types.js +5 -0
  59. package/src/utils/rewrite-imports.js +157 -0
  60. package/src/utils/tsdown-config.js +85 -0
  61. package/src/utils/types.ts +5 -1
  62. package/test/ember.nvp/base-library.test.ts +232 -0
  63. package/test/ember.nvp/generator-replace-or-update.test.ts +2 -3
  64. package/test/ember.nvp/inspector-support.test.ts +179 -0
  65. package/test/ember.nvp/layer-inspector-support.test.ts +69 -0
  66. package/test/ember.nvp/layer-publish-checks.test.ts +103 -0
  67. package/test/ember.nvp/layer-qunit.test.ts +376 -15
  68. package/test/ember.nvp/layer-vitest.test.ts +250 -0
  69. package/test/ember.nvp/layers.test.ts +16 -0
  70. package/test/ember.nvp/library-src-fixtures.ts +81 -0
  71. package/test/ember.nvp/permutations/app-1.test.ts +3 -0
  72. package/test/ember.nvp/permutations/app-2.test.ts +3 -0
  73. package/test/ember.nvp/permutations/library-1.test.ts +3 -0
  74. package/test/ember.nvp/permutations/library-2.test.ts +3 -0
  75. package/test/ember.nvp/permutations/shared.ts +239 -0
  76. package/test/ember.nvp/rewrite-imports.test.ts +235 -0
  77. package/test/test-helpers/permutate.test.ts +17 -0
  78. package/test/test-helpers.ts +20 -7
  79. package/tsconfig.json +1 -0
  80. package/packages/vite/src/maybe-babel.ts +0 -219
  81. package/src/layers/qunit/files/run-tests.mjs +0 -30
  82. package/src/layers/vitest/files/tests/test-helper.ts +0 -5
  83. package/test/ember.nvp/permutations.test.ts +0 -200
  84. /package/src/{layers/vitest/files/tests/integration/.gitkeep → bases/minimal-library/files/src/index.ts} +0 -0
  85. /package/src/layers/{vitest/files/tests/unit → qunit/library-files/tests/rendering}/.gitkeep +0 -0
@@ -3,7 +3,10 @@ import { discoverLayers } from "#layers";
3
3
 
4
4
  import * as p from "@clack/prompts";
5
5
 
6
- export async function askLayers() {
6
+ /**
7
+ * @param {import('#types').ProjectType} projectType
8
+ */
9
+ export async function askLayers(projectType) {
7
10
  const optionalLayers = (await discoverLayers()).filter(
8
11
  (layer) => typeof layer.run === "function",
9
12
  );
@@ -34,7 +37,7 @@ export async function askLayers() {
34
37
  const defaultValues = (
35
38
  await Promise.all(
36
39
  optionalLayers.map(async (layer) => {
37
- let result = await layer.defaultValue?.();
40
+ let result = await layer.defaultValue?.(projectType);
38
41
 
39
42
  return result && layer.name;
40
43
  }),
@@ -41,11 +41,6 @@ export async function askProjectType() {
41
41
  ],
42
42
  });
43
43
 
44
- if (answer === "library") {
45
- p.cancel("Not implemented yet, apologies!");
46
- return process.exit(0);
47
- }
48
-
49
44
  if (p.isCancel(answer)) {
50
45
  p.cancel("Operation cancelled");
51
46
  return process.exit(0);
@@ -0,0 +1,74 @@
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
+ "@arethetypeswrong/core": "^0.18.5",
7
+ };
8
+
9
+ /**
10
+ * @type {import('#types').Layer}
11
+ */
12
+ export default {
13
+ label: "Are The Types Wrong?",
14
+ hint: "validate the published declarations on every build",
15
+
16
+ async run(project) {
17
+ // Publish checks: apps aren't published
18
+ if (project.type !== "library") return;
19
+
20
+ // Declarations are the thing being checked
21
+ if (!(await project.hasOrWantsLayer("typescript"))) return;
22
+
23
+ // ESM-only packages don't offer the CJS resolution modes the strict
24
+ // profile requires
25
+ await addTsdownConfigProperty(project, "attw", `attw: { profile: "esm-only" }`);
26
+ await packageJson.addDevDependencies(await getLatest(deps), project.directory);
27
+ },
28
+
29
+ /**
30
+ * @overload
31
+ * @param {import('#utils/project.js').Project} project
32
+ * @param {true} explain
33
+ * @returns {Promise<{ isSetup: boolean; reasons: string[] }>}
34
+ */
35
+ /**
36
+ * @overload
37
+ * @param {import('#utils/project.js').Project} project
38
+ * @param {boolean | undefined} [explain]
39
+ * @returns {Promise<boolean>}
40
+ */
41
+ async isSetup(project, explain) {
42
+ /** @type {string[]} */
43
+ const reasons = [];
44
+
45
+ // Nothing to set up outside TypeScript libraries, so nothing can be
46
+ // missing
47
+ if (project.type !== "library" || !(await project.hasOrWantsLayer("typescript"))) {
48
+ return explain ? { isSetup: true, reasons } : true;
49
+ }
50
+
51
+ if (!(await hasTsdownConfigProperty(project, "attw"))) {
52
+ if (!explain) return false;
53
+
54
+ reasons.push("tsdown.config.js does not configure attw");
55
+ }
56
+
57
+ let manifest = await packageJson.read(project.directory);
58
+
59
+ if (!manifest.devDependencies?.["@arethetypeswrong/core"]) {
60
+ if (!explain) return false;
61
+
62
+ reasons.push("package.json is missing @arethetypeswrong/core in devDependencies");
63
+ }
64
+
65
+ if (explain) {
66
+ return {
67
+ isSetup: reasons.length === 0,
68
+ reasons,
69
+ };
70
+ }
71
+
72
+ return reasons.length === 0;
73
+ },
74
+ };
@@ -1 +1,14 @@
1
- node_modules
1
+ # compiled output
2
+ /dist/
3
+ /dist-*
4
+
5
+ # dependencies
6
+ /node_modules/
7
+
8
+ # misc
9
+ *.local
10
+ /.pnp*
11
+ /.eslintcache
12
+ /coverage/
13
+ /npm-debug.log*
14
+ /testem.log
@@ -1,5 +1,6 @@
1
1
  import { hasGit, initGit, isInGit } from "#utils/git.js";
2
2
  import { formatLabel } from "#utils/cli.js";
3
+ import { existsSync } from "node:fs";
3
4
  import { join } from "node:path";
4
5
  import { cp } from "node:fs/promises";
5
6
 
@@ -7,10 +8,7 @@ export default {
7
8
  label: formatLabel("git init"),
8
9
  hint: "set up this project as a fresh git repository",
9
10
 
10
- /**
11
- * @param {import('#utils/project.js').Project} project
12
- */
13
- async defaultValue(project) {
11
+ async defaultValue() {
14
12
  return !isInGit();
15
13
  },
16
14
 
@@ -27,7 +25,10 @@ export default {
27
25
  let gitIgnorePath = join(import.meta.dirname, "files/gitignore");
28
26
  let targetPath = join(project.directory, ".gitignore");
29
27
 
30
- await cp(gitIgnorePath, targetPath, { force: true });
28
+ // Don't clobber a .gitignore provided by a base or by the user
29
+ if (!existsSync(targetPath)) {
30
+ await cp(gitIgnorePath, targetPath);
31
+ }
31
32
  }
32
33
 
33
34
  return initOk;
@@ -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
+ }