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
@@ -0,0 +1,81 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+
4
+ import type { Project } from "ember.nvp";
5
+
6
+ /**
7
+ * The library base generates an empty `src/`; tests that build or test a
8
+ * library write this example source (a class component, a template-only
9
+ * component, and a plain module) into the generated project first.
10
+ */
11
+ export const librarySource = {
12
+ typescript: {
13
+ "src/index.ts": `export { Badge, type BadgeSignature } from "./components/badge.gts";
14
+ export { default as Greeting, type GreetingSignature } from "./components/greeting.gts";
15
+ export { add } from "./utils/math.ts";
16
+ `,
17
+ "src/components/greeting.gts": `import Component from "@glimmer/component";
18
+
19
+ export interface GreetingSignature {
20
+ Element: HTMLParagraphElement;
21
+ Args: {
22
+ name: string;
23
+ };
24
+ }
25
+
26
+ export default class Greeting extends Component<GreetingSignature> {
27
+ <template>
28
+ <p ...attributes>Hello, {{@name}}!</p>
29
+ </template>
30
+ }
31
+ `,
32
+ "src/components/badge.gts": `import type { TOC } from "@ember/component/template-only";
33
+
34
+ export interface BadgeSignature {
35
+ Element: HTMLSpanElement;
36
+ Blocks: {
37
+ default: [];
38
+ };
39
+ }
40
+
41
+ export const Badge: TOC<BadgeSignature> = <template>
42
+ <span class="badge" ...attributes>{{yield}}</span>
43
+ </template>;
44
+ `,
45
+ "src/utils/math.ts": `export function add(a: number, b: number): number {
46
+ return a + b;
47
+ }
48
+ `,
49
+ },
50
+ javascript: {
51
+ "src/index.js": `export { Badge } from "./components/badge.gjs";
52
+ export { default as Greeting } from "./components/greeting.gjs";
53
+ export { add } from "./utils/math.js";
54
+ `,
55
+ "src/components/greeting.gjs": `import Component from "@glimmer/component";
56
+
57
+ export default class Greeting extends Component {
58
+ <template>
59
+ <p ...attributes>Hello, {{@name}}!</p>
60
+ </template>
61
+ }
62
+ `,
63
+ "src/components/badge.gjs": `export const Badge = <template>
64
+ <span class="badge" ...attributes>{{yield}}</span>
65
+ </template>;
66
+ `,
67
+ "src/utils/math.js": `export function add(a, b) {
68
+ return a + b;
69
+ }
70
+ `,
71
+ },
72
+ };
73
+
74
+ export async function writeLibrarySource(project: Project, flavor: keyof typeof librarySource) {
75
+ for (let [filePath, contents] of Object.entries(librarySource[flavor])) {
76
+ let target = join(project.directory, filePath);
77
+
78
+ await mkdir(dirname(target), { recursive: true });
79
+ await writeFile(target, contents);
80
+ }
81
+ }
@@ -0,0 +1,3 @@
1
+ import { testPermutations } from "./shared.ts";
2
+
3
+ testPermutations("minimal-app", { index: 0, total: 2 });
@@ -0,0 +1,3 @@
1
+ import { testPermutations } from "./shared.ts";
2
+
3
+ testPermutations("minimal-app", { index: 1, total: 2 });
@@ -0,0 +1,3 @@
1
+ import { testPermutations } from "./shared.ts";
2
+
3
+ testPermutations("minimal-library", { index: 0, total: 2 });
@@ -0,0 +1,3 @@
1
+ import { testPermutations } from "./shared.ts";
2
+
3
+ testPermutations("minimal-library", { index: 1, total: 2 });
@@ -0,0 +1,239 @@
1
+ import { beforeAll, describe, it, expect, afterAll } from "vitest";
2
+ import { generate, permutate, bases, layers, reapply } from "#test-helpers";
3
+ import { TODO } from "#layers";
4
+
5
+ import { rm } from "node:fs/promises";
6
+
7
+ import type { Project } from "ember.nvp";
8
+ import type { ProjectType } from "#types";
9
+
10
+ const baseline = "<baseline>";
11
+
12
+ const RE_APPLY_ONLY = new Set<string>([
13
+ // "typescript"
14
+ // "renovate",
15
+ // "github-actions",
16
+ ]);
17
+ const INITIAL_ONLY = new Set<string>([
18
+ // baseline,
19
+ // "eslint-bundled-nvp",
20
+ // "typescript"
21
+ ]);
22
+
23
+ /**
24
+ * Layers that don't yet generate a working setup for a project type.
25
+ * Every layer is *supposed* to support every base eventually -- shrink
26
+ * these as layers learn.
27
+ */
28
+ const NOT_YET_SUPPORTED: Record<ProjectType, Set<string>> = {
29
+ // publish checks: apps aren't published
30
+ app: new Set(["publint", "are-the-types-wrong"]),
31
+ // libraries have no Application to wire inspector support into
32
+ library: new Set(["inspector-support"]),
33
+ };
34
+
35
+ const eachBase = bases.map((base) => {
36
+ const type: ProjectType = base === "minimal-app" ? "app" : "library";
37
+
38
+ const applicableLayers = layers.filter((layer) => !NOT_YET_SUPPORTED[type].has(layer.name));
39
+
40
+ const permutations = permutate(applicableLayers.map((layer) => layer.name));
41
+ permutations.push([baseline]);
42
+
43
+ const eachPermutation = permutations
44
+ .filter((permutation) => {
45
+ if (INITIAL_ONLY.size > 0 && !permutation.some((x) => INITIAL_ONLY.has(x))) {
46
+ return;
47
+ }
48
+
49
+ // Not implemented yet
50
+ if (permutation.some((x) => TODO.has(x))) {
51
+ return;
52
+ }
53
+
54
+ return true;
55
+ })
56
+ .map((permutation) => ({ permutation, name: permutation.join(", ") }));
57
+
58
+ return { name: base, type, applicableLayers, eachPermutation };
59
+ });
60
+
61
+ /**
62
+ * The permutation matrix grows exponentially with the layer count, so
63
+ * each base runs from its own test file(s) -- CI runs them as separate,
64
+ * parallel jobs. A base whose matrix outgrows one job's budget runs from
65
+ * several files, each taking a deterministic 1-of-N slice.
66
+ *
67
+ * @param base which base's permutations this file runs
68
+ * @param slice which 1-of-N slice of the permutations this file runs
69
+ */
70
+ export function testPermutations(
71
+ base: (typeof bases)[number],
72
+ slice: { index: number; total: number } = { index: 0, total: 1 },
73
+ ) {
74
+ const {
75
+ type,
76
+ applicableLayers,
77
+ eachPermutation: allPermutations,
78
+ } = eachBase.find((b) => b.name === base)!;
79
+
80
+ const eachPermutation = allPermutations.filter((_, i) => i % slice.total === slice.index);
81
+
82
+ describe(base, () => {
83
+ describe.each(eachPermutation)("layers: $name", ({ permutation }) => {
84
+ let project: Project;
85
+ let layerNames = permutation.filter((x) => x !== baseline);
86
+ let startingLayers = layers.filter((layer) => layerNames.includes(layer.name));
87
+
88
+ beforeAll(async () => {
89
+ project = await generate({
90
+ type,
91
+ layers: layerNames,
92
+ });
93
+ });
94
+
95
+ afterAll(async () => {
96
+ if (process.env.CI) {
97
+ return;
98
+ }
99
+
100
+ if (project?.directory) {
101
+ await rm(project.directory, {
102
+ recursive: true,
103
+ force: true,
104
+ maxRetries: 3,
105
+ retryDelay: 100,
106
+ });
107
+ }
108
+ });
109
+
110
+ it("successfully setup the layers", async () => {
111
+ if (startingLayers.length === 0) {
112
+ // TODO: test baseline stuff more?
113
+ expect(true, "No layers to verify").toBe(true);
114
+ }
115
+
116
+ for (let layer of startingLayers) {
117
+ expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
118
+
119
+ let result = await layer.isSetup(project);
120
+
121
+ expect(result, `${layer.name} is setup`).toBe(true);
122
+ }
123
+ });
124
+
125
+ /**
126
+ * Are these valuable? no one would invoke the layers like this
127
+ */
128
+ // describe.skip("(re)applying", () => {
129
+ // for (let layer of layers) {
130
+ // if (RE_APPLY_ONLY.size > 0 && !RE_APPLY_ONLY.has(layer.name)) {
131
+ // continue;
132
+ // }
133
+
134
+ // if (TODO.has(layer.name)) {
135
+ // continue;
136
+ // }
137
+
138
+ // describe(layer.name, () => {
139
+ // let expected = startingLayers.find((l) => l.name === layer.name);
140
+
141
+ // beforeAll(async () => {
142
+ // await layer.run(project);
143
+ // });
144
+
145
+ // if (expected) {
146
+ // it("is applied", async () => {
147
+ // expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
148
+
149
+ // let result = await layer.isSetup(project, true);
150
+
151
+ // if (typeof result === "object") {
152
+ // expect(result.reasons, `${layer.name} is setup`).deep.equal([]);
153
+ // expect(result.isSetup, `${layer.name} is setup`).toBe(true);
154
+ // return;
155
+ // }
156
+
157
+ // expect(result, `${layer.name} is setup`).toBe(true);
158
+
159
+ // return;
160
+ // });
161
+ // } else {
162
+ // it("is not applied", async () => {
163
+ // expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
164
+
165
+ // let result = await layer.isSetup(project, true);
166
+
167
+ // if (typeof result === "object") {
168
+ // expect(result.isSetup, `${layer.name} is not setup`).toBe(false);
169
+ // hardExpect(result.reasons.length, `${layer.name} is not setup`).toBeGreaterThan(0);
170
+ // return;
171
+ // }
172
+
173
+ // expect(result, `${layer.name} is not setup`).toBe(false);
174
+ // });
175
+ // }
176
+ // });
177
+ // }
178
+ // });
179
+
180
+ /**
181
+ * Simulates running the CLI again with different options selected
182
+ * on the same project
183
+ */
184
+ describe("apply anew", () => {
185
+ let newLayers = applicableLayers
186
+ .filter((layer) => {
187
+ if (RE_APPLY_ONLY.size > 0 && !RE_APPLY_ONLY.has(layer.name)) {
188
+ return false;
189
+ }
190
+
191
+ if (TODO.has(layer.name)) {
192
+ return false;
193
+ }
194
+
195
+ return true;
196
+ })
197
+ .map((layer) => ({ name: layer.name, layer }));
198
+
199
+ describe.each(newLayers)("$name", ({ layer }) => {
200
+ beforeAll(async () => {
201
+ // Simulate running the CLI again, adding one more option while
202
+ // keeping the previously-selected layers.
203
+ await reapply(project, [...new Set([...layerNames, layer.name])]);
204
+ });
205
+
206
+ it.for(startingLayers)("starting layers still present: $name", async (layer) => {
207
+ expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
208
+
209
+ let result = await layer.isSetup(project, true);
210
+
211
+ if (typeof result === "object") {
212
+ expect(result.reasons, `${layer.name} is setup`).deep.equal([]);
213
+ expect(result.isSetup, `${layer.name} is setup`).toBe(true);
214
+ return;
215
+ }
216
+
217
+ expect(result, `${layer.name} is setup`).toBe(true);
218
+ });
219
+
220
+ it("is applied", async () => {
221
+ expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
222
+
223
+ let result = await layer.isSetup(project, true);
224
+
225
+ if (typeof result === "object") {
226
+ expect(result.reasons, `${layer.name} is setup`).deep.equal([]);
227
+ expect(result.isSetup, `${layer.name} is setup`).toBe(true);
228
+ return;
229
+ }
230
+
231
+ expect(result, `${layer.name} is setup`).toBe(true);
232
+
233
+ return;
234
+ });
235
+ });
236
+ });
237
+ });
238
+ });
239
+ }
@@ -0,0 +1,235 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+
6
+ import { rewriteImportsToMatchFiles } from "#utils/rewrite-imports.js";
7
+
8
+ /**
9
+ * Generation renames files (e.g. type removal turns .ts into .js), so
10
+ * import specifiers are checked against the emitted project tree:
11
+ * whatever file actually exists on disk wins.
12
+ */
13
+ describe("rewriteImportsToMatchFiles", () => {
14
+ const dirs: string[] = [];
15
+
16
+ afterEach(async () => {
17
+ for (const dir of dirs.splice(0)) {
18
+ await rm(dir, { recursive: true, force: true });
19
+ }
20
+ });
21
+
22
+ /**
23
+ * Lays out an emitted-project fixture on disk (the way generation
24
+ * stages real files) and returns its root.
25
+ */
26
+ async function makeProject(files: Record<string, string>) {
27
+ const dir = await mkdtemp(join(tmpdir(), "rewrite-imports-test-"));
28
+ dirs.push(dir);
29
+
30
+ for (const [path, contents] of Object.entries(files)) {
31
+ await mkdir(join(dir, dirname(path)), { recursive: true });
32
+ await writeFile(join(dir, path), contents);
33
+ }
34
+
35
+ return dir;
36
+ }
37
+
38
+ const manifest = JSON.stringify({
39
+ name: "fixture",
40
+ imports: {
41
+ "#app/*": "./app/*",
42
+ "#config": "./app/config.js",
43
+ },
44
+ });
45
+
46
+ it("rewrites a specifier whose file was renamed", async () => {
47
+ const project = await makeProject({
48
+ "package.json": manifest,
49
+ "app/app.js": "",
50
+ "app/config.js": "",
51
+ });
52
+
53
+ const result = rewriteImportsToMatchFiles(
54
+ [`import Application from "#app/app.ts";`, `import config from "#config";`].join("\n"),
55
+ join(project, "tests/test-helper.js"),
56
+ );
57
+
58
+ expect(result).toMatchInlineSnapshot(`
59
+ "import Application from "#app/app.js";
60
+ import config from "#config";"
61
+ `);
62
+ });
63
+
64
+ it("leaves a specifier alone when its file exists as written", async () => {
65
+ const project = await makeProject({
66
+ "package.json": manifest,
67
+ "app/helpers/x.ts": "",
68
+ });
69
+
70
+ const result = rewriteImportsToMatchFiles(
71
+ `import x from "./helpers/x.ts";`,
72
+ join(project, "app/consumer.ts"),
73
+ );
74
+
75
+ expect(result).toMatchInlineSnapshot(`"import x from "./helpers/x.ts";"`);
76
+ });
77
+
78
+ it("adds the on-disk extension to extensionless local imports", async () => {
79
+ const project = await makeProject({
80
+ "package.json": manifest,
81
+ "app/helpers/x.js": "",
82
+ "app/app.ts": "",
83
+ });
84
+
85
+ const result = rewriteImportsToMatchFiles(
86
+ [`import x from "./helpers/x";`, `import Application from "#app/app";`].join("\n"),
87
+ join(project, "app/consumer.js"),
88
+ );
89
+
90
+ expect(result).toMatchInlineSnapshot(`
91
+ "import x from "./helpers/x.js";
92
+ import Application from "#app/app.ts";"
93
+ `);
94
+ });
95
+
96
+ it("keeps an extensionless specifier whose fully-specified form would not map", async () => {
97
+ const project = await makeProject({
98
+ "package.json": manifest,
99
+ "app/config.js": "",
100
+ });
101
+
102
+ // `#config` maps to "./app/config.js" -- the extension lives in the
103
+ // `imports` target, so "#config.js" would not resolve
104
+ const result = rewriteImportsToMatchFiles(
105
+ `import config from "#config";`,
106
+ join(project, "app/consumer.js"),
107
+ );
108
+
109
+ expect(result).toMatchInlineSnapshot(`"import config from "#config";"`);
110
+ });
111
+
112
+ it("rewrites relative, re-export, and dynamic import() specifiers", async () => {
113
+ const project = await makeProject({
114
+ "package.json": manifest,
115
+ "app/thing.js": "",
116
+ "shared/other.js": "",
117
+ "app/lazy.js": "",
118
+ });
119
+
120
+ const result = rewriteImportsToMatchFiles(
121
+ [
122
+ `export { thing } from "./thing.ts";`,
123
+ `export * from "../shared/other.ts";`,
124
+ `export async function load() {`,
125
+ ` return await import("./lazy.ts");`,
126
+ `}`,
127
+ ].join("\n"),
128
+ join(project, "app/consumer.js"),
129
+ );
130
+
131
+ expect(result).toMatchInlineSnapshot(`
132
+ "export { thing } from "./thing.js";
133
+ export * from "../shared/other.js";
134
+ export async function load() {
135
+ return await import("./lazy.js");
136
+ }"
137
+ `);
138
+ });
139
+
140
+ it("leaves a specifier alone when no file matches at all", async () => {
141
+ const project = await makeProject({
142
+ "package.json": manifest,
143
+ });
144
+
145
+ const result = rewriteImportsToMatchFiles(
146
+ `import missing from "./does-not-exist.ts";`,
147
+ join(project, "app/consumer.js"),
148
+ );
149
+
150
+ expect(result).toMatchInlineSnapshot(`"import missing from "./does-not-exist.ts";"`);
151
+ });
152
+
153
+ it("leaves package specifiers and non-import strings alone", async () => {
154
+ const project = await makeProject({
155
+ "package.json": manifest,
156
+ "app/looks-like-a-file.js": "",
157
+ });
158
+
159
+ const result = rewriteImportsToMatchFiles(
160
+ [`import lib from "some-lib/file.ts";`, `const notAnImport = "./looks-like-a-file.ts";`].join(
161
+ "\n",
162
+ ),
163
+ join(project, "app/consumer.js"),
164
+ );
165
+
166
+ expect(result).toMatchInlineSnapshot(`
167
+ "import lib from "some-lib/file.ts";
168
+ const notAnImport = "./looks-like-a-file.ts";"
169
+ `);
170
+ });
171
+
172
+ it("rewrites imports in gjs files while preserving the template", async () => {
173
+ const project = await makeProject({
174
+ "package.json": manifest,
175
+ "app/components/button.gjs": "",
176
+ });
177
+
178
+ const result = rewriteImportsToMatchFiles(
179
+ [
180
+ `import Component from "@glimmer/component";`,
181
+ `import Button from "./button.gts";`,
182
+ ``,
183
+ `export default class Foo extends Component {`,
184
+ ` <template>`,
185
+ ` <Button />`,
186
+ ` </template>`,
187
+ `}`,
188
+ ].join("\n"),
189
+ join(project, "app/components/foo.gjs"),
190
+ );
191
+
192
+ expect(result).toMatchInlineSnapshot(`
193
+ "import Component from "@glimmer/component";
194
+ import Button from "./button.gjs";
195
+ export default class Foo extends Component {
196
+ <template><Button /></template>
197
+ }"
198
+ `);
199
+ });
200
+
201
+ it("keeps comments in files whose imports are rewritten", async () => {
202
+ const project = await makeProject({
203
+ "package.json": manifest,
204
+ "app/app.js": "",
205
+ });
206
+
207
+ const result = rewriteImportsToMatchFiles(
208
+ [
209
+ `// top comment`,
210
+ `import Application from "#app/app.ts";`,
211
+ ``,
212
+ `/**`,
213
+ ` * doc comment for start`,
214
+ ` */`,
215
+ `export function start() {`,
216
+ ` // inner comment`,
217
+ ` Application.create();`,
218
+ `}`,
219
+ ].join("\n"),
220
+ join(project, "tests/test-helper.js"),
221
+ );
222
+
223
+ expect(result).toMatchInlineSnapshot(`
224
+ "// top comment
225
+ import Application from "#app/app.js";
226
+ /**
227
+ * doc comment for start
228
+ */
229
+ export function start() {
230
+ // inner comment
231
+ Application.create();
232
+ }"
233
+ `);
234
+ });
235
+ });