ember.nvp 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +6 -0
- package/.release-plan.json +23 -15
- package/CHANGELOG.md +30 -0
- package/package.json +1 -1
- package/packages/rolldown/README.md +62 -0
- package/packages/rolldown/package.json +13 -4
- package/packages/rolldown/rolldown.config.js +5 -3
- package/packages/rolldown/src/app-reexports.test.ts +220 -0
- package/packages/rolldown/src/app-reexports.ts +152 -0
- package/packages/rolldown/src/config.ts +1 -1
- package/packages/rolldown/src/dts.test.ts +166 -0
- package/packages/rolldown/src/transform.test.ts +173 -0
- package/src/bases/minimal-extension/extension.test.ts +251 -0
- package/src/bases/minimal-extension/files/.env.development +8 -0
- package/src/bases/minimal-extension/files/README.md +36 -0
- package/src/bases/minimal-extension/files/app/app.ts +21 -0
- package/src/bases/minimal-extension/files/app/boot.ts +8 -0
- package/src/bases/minimal-extension/files/app/config.ts +22 -0
- package/src/bases/minimal-extension/files/app/router.ts +10 -0
- package/src/bases/minimal-extension/files/app/templates/application.gts +5 -0
- package/src/bases/minimal-extension/files/babel.config.js +49 -0
- package/src/bases/minimal-extension/files/index.html +17 -0
- package/src/bases/minimal-extension/files/package.json +58 -0
- package/src/bases/minimal-extension/files/public/manifest.json +9 -0
- package/src/bases/minimal-extension/files/tsconfig.json +13 -0
- package/src/bases/minimal-extension/files/vite.config.mjs +6 -0
- package/src/bases/minimal-extension/index.js +142 -0
- package/src/cli/generator.js +4 -0
- package/src/cli/questions/project-type.js +9 -2
- package/src/layers/renovate/index.js +4 -3
- package/src/layers/typescript/index.js +6 -0
- package/src/utils/types.ts +1 -1
- package/test/ember.nvp/base-library.test.ts +39 -32
- package/test/ember.nvp/layer-publish-checks.test.ts +4 -13
- package/test/ember.nvp/layer-qunit.test.ts +13 -30
- package/test/ember.nvp/layer-vitest.test.ts +4 -18
- package/test/ember.nvp/permutations/app-checks.test.ts +3 -0
- package/test/ember.nvp/permutations/extension-1.test.ts +3 -0
- package/test/ember.nvp/permutations/extension-2.test.ts +3 -0
- package/test/ember.nvp/permutations/extension-checks.test.ts +3 -0
- package/test/ember.nvp/permutations/library-checks.test.ts +3 -0
- package/test/ember.nvp/permutations/shared.ts +68 -19
- package/test/test-helpers.ts +25 -30
- package/vitest.config.js +1 -1
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
import { describe, it, beforeAll, afterAll, expect } from "vitest";
|
|
2
|
-
import { generate,
|
|
2
|
+
import { generate, listFiles } from "#test-helpers";
|
|
3
3
|
import { writeLibrarySource } from "./library-src-fixtures.ts";
|
|
4
4
|
import { execa } from "execa";
|
|
5
|
-
import { mkdir,
|
|
6
|
-
import { dirname, join
|
|
5
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
7
|
|
|
8
8
|
import type { Project } from "ember.nvp";
|
|
9
9
|
|
|
10
|
-
async function listFiles(directory: string): Promise<string[]> {
|
|
11
|
-
const entries = await readdir(directory, { withFileTypes: true, recursive: true });
|
|
12
|
-
|
|
13
|
-
return entries
|
|
14
|
-
.filter((entry) => entry.isFile())
|
|
15
|
-
.map((entry) => relative(directory, join(entry.parentPath, entry.name)))
|
|
16
|
-
.filter((path) => !path.split(sep).includes("node_modules"))
|
|
17
|
-
.filter((path) => !path.startsWith(`dist${sep}`))
|
|
18
|
-
.sort();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function read(project: Project, filePath: string): Promise<string> {
|
|
22
|
-
return readFile(join(project.directory, filePath), "utf-8");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
10
|
async function installAndTest(project: Project) {
|
|
26
|
-
await pinYukuParser(project);
|
|
27
|
-
|
|
28
11
|
let install = await execa("pnpm install", { cwd: project.directory, shell: true });
|
|
29
12
|
expect(install.exitCode).toBe(0);
|
|
30
13
|
|
|
@@ -169,7 +152,7 @@ describe("layer: qunit", () => {
|
|
|
169
152
|
});
|
|
170
153
|
|
|
171
154
|
it("generates the expected files, without any index.html", async () => {
|
|
172
|
-
let files = await listFiles(project.directory);
|
|
155
|
+
let files = await listFiles(project.directory, { includeDist: false });
|
|
173
156
|
|
|
174
157
|
expect(files.some((file) => file.endsWith("index.html"))).toBe(false);
|
|
175
158
|
expect(files).toMatchInlineSnapshot(`
|
|
@@ -191,7 +174,7 @@ describe("layer: qunit", () => {
|
|
|
191
174
|
});
|
|
192
175
|
|
|
193
176
|
it("generates the test build config", async () => {
|
|
194
|
-
expect(await read(
|
|
177
|
+
expect(await project.read("vite.config.mjs")).toMatchInlineSnapshot(`
|
|
195
178
|
"import { defineConfig } from "vite";
|
|
196
179
|
import { ember } from "@nullvoxpopuli/ember-vite";
|
|
197
180
|
|
|
@@ -200,7 +183,7 @@ describe("layer: qunit", () => {
|
|
|
200
183
|
});
|
|
201
184
|
"
|
|
202
185
|
`);
|
|
203
|
-
expect(await read(
|
|
186
|
+
expect(await project.read("config/test/babel.config.js")).toMatchInlineSnapshot(`
|
|
204
187
|
"import { buildMacros } from "@embroider/macros/babel";
|
|
205
188
|
|
|
206
189
|
const macros = buildMacros({
|
|
@@ -244,7 +227,7 @@ describe("layer: qunit", () => {
|
|
|
244
227
|
};
|
|
245
228
|
"
|
|
246
229
|
`);
|
|
247
|
-
expect(await read(
|
|
230
|
+
expect(await project.read("config/test/testem.cjs")).toMatchInlineSnapshot(`
|
|
248
231
|
""use strict";
|
|
249
232
|
|
|
250
233
|
if (typeof module !== "undefined") {
|
|
@@ -276,7 +259,7 @@ describe("layer: qunit", () => {
|
|
|
276
259
|
});
|
|
277
260
|
|
|
278
261
|
it("leaves the publish build config alone", async () => {
|
|
279
|
-
expect(await read(
|
|
262
|
+
expect(await project.read("tsdown.config.js")).toMatchInlineSnapshot(`
|
|
280
263
|
"import { defineConfig } from "tsdown";
|
|
281
264
|
import { ember } from "@nullvoxpopuli/ember-rolldown";
|
|
282
265
|
|
|
@@ -298,7 +281,7 @@ describe("layer: qunit", () => {
|
|
|
298
281
|
let build = await execa("pnpm build", { cwd: project.directory, shell: true });
|
|
299
282
|
expect(build.exitCode).toBe(0);
|
|
300
283
|
|
|
301
|
-
let output = await read(
|
|
284
|
+
let output = await project.read("dist/index.js");
|
|
302
285
|
|
|
303
286
|
expect(output).toContain("precompileTemplate");
|
|
304
287
|
expect(output).not.toContain("createTemplateFactory");
|
|
@@ -314,7 +297,7 @@ describe("layer: qunit", () => {
|
|
|
314
297
|
});
|
|
315
298
|
|
|
316
299
|
it("generates the expected files, without any index.html", async () => {
|
|
317
|
-
let files = await listFiles(project.directory);
|
|
300
|
+
let files = await listFiles(project.directory, { includeDist: false });
|
|
318
301
|
|
|
319
302
|
expect(files.some((file) => file.endsWith("index.html"))).toBe(false);
|
|
320
303
|
expect(files).toMatchInlineSnapshot(`
|
|
@@ -335,7 +318,7 @@ describe("layer: qunit", () => {
|
|
|
335
318
|
});
|
|
336
319
|
|
|
337
320
|
it("has no TypeScript leftovers in the test build config", async () => {
|
|
338
|
-
expect(await read(
|
|
321
|
+
expect(await project.read("config/test/babel.config.js")).toMatchInlineSnapshot(`
|
|
339
322
|
"import { buildMacros } from "@embroider/macros/babel";
|
|
340
323
|
|
|
341
324
|
const macros = buildMacros({
|
|
@@ -367,7 +350,7 @@ describe("layer: qunit", () => {
|
|
|
367
350
|
};
|
|
368
351
|
"
|
|
369
352
|
`);
|
|
370
|
-
expect(await read(
|
|
353
|
+
expect(await project.read("tsdown.config.js")).toMatchInlineSnapshot(`
|
|
371
354
|
"import { defineConfig } from "tsdown";
|
|
372
355
|
import { ember } from "@nullvoxpopuli/ember-rolldown";
|
|
373
356
|
|
|
@@ -390,7 +373,7 @@ describe("layer: qunit", () => {
|
|
|
390
373
|
let build = await execa("pnpm build", { cwd: project.directory, shell: true });
|
|
391
374
|
expect(build.exitCode).toBe(0);
|
|
392
375
|
|
|
393
|
-
let output = await read(
|
|
376
|
+
let output = await project.read("dist/index.js");
|
|
394
377
|
|
|
395
378
|
expect(output).toContain("precompileTemplate");
|
|
396
379
|
expect(output).not.toContain("createTemplateFactory");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, it, beforeAll, afterAll, expect } from "vitest";
|
|
2
|
-
import { generate } from "#test-helpers";
|
|
2
|
+
import { generate, listFiles } from "#test-helpers";
|
|
3
3
|
import { writeLibrarySource } from "./library-src-fixtures.ts";
|
|
4
4
|
import { execa } from "execa";
|
|
5
|
-
import { mkdir,
|
|
6
|
-
import { dirname, join
|
|
5
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
7
|
|
|
8
8
|
import type { Project } from "ember.nvp";
|
|
9
9
|
|
|
@@ -14,20 +14,6 @@ import type { Project } from "ember.nvp";
|
|
|
14
14
|
* headless-browser mode), asserting the specs genuinely run and pass.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
async function listFiles(directory: string): Promise<string[]> {
|
|
18
|
-
const entries = await readdir(directory, { withFileTypes: true, recursive: true });
|
|
19
|
-
|
|
20
|
-
return entries
|
|
21
|
-
.filter((entry) => entry.isFile())
|
|
22
|
-
.map((entry) => relative(directory, join(entry.parentPath, entry.name)))
|
|
23
|
-
.filter((path) => !path.split(sep).includes("node_modules"))
|
|
24
|
-
.sort();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function read(project: Project, filePath: string): Promise<string> {
|
|
28
|
-
return readFile(join(project.directory, filePath), "utf-8");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
17
|
async function emit(project: Project, files: Record<string, string>) {
|
|
32
18
|
for (let [path, contents] of Object.entries(files)) {
|
|
33
19
|
let filePath = join(project.directory, path);
|
|
@@ -200,7 +186,7 @@ describe("layer: vitest", () => {
|
|
|
200
186
|
});
|
|
201
187
|
|
|
202
188
|
it("generates the vitest config", async () => {
|
|
203
|
-
expect(await read(
|
|
189
|
+
expect(await project.read("vitest.config.mjs")).toMatchInlineSnapshot(`
|
|
204
190
|
"import { ember } from "@nullvoxpopuli/ember-vite";
|
|
205
191
|
import { webdriverio } from "@vitest/browser-webdriverio";
|
|
206
192
|
import { defineConfig } from "vitest/config";
|
|
@@ -30,30 +30,73 @@ const NOT_YET_SUPPORTED: Record<ProjectType, Set<string>> = {
|
|
|
30
30
|
app: new Set(["publint", "are-the-types-wrong"]),
|
|
31
31
|
// libraries have no Application to wire inspector support into
|
|
32
32
|
library: new Set(["inspector-support"]),
|
|
33
|
+
// publish checks: extensions aren't published (to npm)
|
|
34
|
+
extension: new Set(["publint", "are-the-types-wrong"]),
|
|
33
35
|
};
|
|
34
36
|
|
|
37
|
+
const TYPE_FOR_BASE: Record<(typeof bases)[number], ProjectType> = {
|
|
38
|
+
"minimal-app": "app",
|
|
39
|
+
"minimal-library": "library",
|
|
40
|
+
"minimal-extension": "extension",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Layers that only add lint / format / publish checks. They don't affect
|
|
45
|
+
* the runtime shape of a project, so they permutate among themselves in a
|
|
46
|
+
* small "checks" matrix instead of multiplying the main one. ("apply anew"
|
|
47
|
+
* still applies each of them on top of every main permutation, so linear
|
|
48
|
+
* cross-group coverage remains.)
|
|
49
|
+
*/
|
|
50
|
+
const CHECK_LAYERS = new Set<string>([
|
|
51
|
+
"eslint-bundled-ember",
|
|
52
|
+
"eslint-bundled-nvp",
|
|
53
|
+
"eslint-ejected",
|
|
54
|
+
"prettier",
|
|
55
|
+
"publint",
|
|
56
|
+
"are-the-types-wrong",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
type PermutationGroup = "main" | "checks";
|
|
60
|
+
|
|
35
61
|
const eachBase = bases.map((base) => {
|
|
36
|
-
const type
|
|
62
|
+
const type = TYPE_FOR_BASE[base]!;
|
|
37
63
|
|
|
38
64
|
const applicableLayers = layers.filter((layer) => !NOT_YET_SUPPORTED[type].has(layer.name));
|
|
39
65
|
|
|
40
|
-
const
|
|
41
|
-
|
|
66
|
+
const toPermutations = (layerNames: string[], withBaseline: boolean) => {
|
|
67
|
+
const permutations = permutate(layerNames);
|
|
68
|
+
|
|
69
|
+
if (withBaseline) {
|
|
70
|
+
permutations.push([baseline]);
|
|
71
|
+
}
|
|
42
72
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
return permutations
|
|
74
|
+
.filter((permutation) => {
|
|
75
|
+
if (INITIAL_ONLY.size > 0 && !permutation.some((x) => INITIAL_ONLY.has(x))) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
48
78
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
// Not implemented yet
|
|
80
|
+
if (permutation.some((x) => TODO.has(x))) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
53
83
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
return true;
|
|
85
|
+
})
|
|
86
|
+
.map((permutation) => ({ permutation, name: permutation.join(", ") }));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const applicableNames = applicableLayers.map((layer) => layer.name);
|
|
90
|
+
const eachPermutation: Record<PermutationGroup, ReturnType<typeof toPermutations>> = {
|
|
91
|
+
main: toPermutations(
|
|
92
|
+
applicableNames.filter((name) => !CHECK_LAYERS.has(name)),
|
|
93
|
+
true,
|
|
94
|
+
),
|
|
95
|
+
checks: toPermutations(
|
|
96
|
+
applicableNames.filter((name) => CHECK_LAYERS.has(name)),
|
|
97
|
+
false,
|
|
98
|
+
),
|
|
99
|
+
};
|
|
57
100
|
|
|
58
101
|
return { name: base, type, applicableLayers, eachPermutation };
|
|
59
102
|
});
|
|
@@ -62,14 +105,20 @@ const eachBase = bases.map((base) => {
|
|
|
62
105
|
* The permutation matrix grows exponentially with the layer count, so
|
|
63
106
|
* each base runs from its own test file(s) -- CI runs them as separate,
|
|
64
107
|
* parallel jobs. A base whose matrix outgrows one job's budget runs from
|
|
65
|
-
* several files, each taking a deterministic 1-of-N slice.
|
|
108
|
+
* several files, each taking a deterministic 1-of-N slice. The "checks"
|
|
109
|
+
* group (CHECK_LAYERS) runs from its own file(s) the same way.
|
|
66
110
|
*
|
|
67
111
|
* @param base which base's permutations this file runs
|
|
68
|
-
* @param
|
|
112
|
+
* @param options.index / options.total which 1-of-N slice of the permutations this file runs
|
|
113
|
+
* @param options.group which permutation group this file runs
|
|
69
114
|
*/
|
|
70
115
|
export function testPermutations(
|
|
71
116
|
base: (typeof bases)[number],
|
|
72
|
-
|
|
117
|
+
{
|
|
118
|
+
index = 0,
|
|
119
|
+
total = 1,
|
|
120
|
+
group = "main",
|
|
121
|
+
}: { index?: number; total?: number; group?: PermutationGroup } = {},
|
|
73
122
|
) {
|
|
74
123
|
const {
|
|
75
124
|
type,
|
|
@@ -77,7 +126,7 @@ export function testPermutations(
|
|
|
77
126
|
eachPermutation: allPermutations,
|
|
78
127
|
} = eachBase.find((b) => b.name === base)!;
|
|
79
128
|
|
|
80
|
-
const eachPermutation = allPermutations.filter((_, i) => i %
|
|
129
|
+
const eachPermutation = allPermutations[group].filter((_, i) => i % total === index);
|
|
81
130
|
|
|
82
131
|
describe(base, () => {
|
|
83
132
|
describe.each(eachPermutation)("layers: $name", ({ permutation }) => {
|
package/test/test-helpers.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
3
|
import { styleText } from "node:util";
|
|
4
4
|
import { Readable, Writable } from "node:stream";
|
|
5
|
-
import { mkdtemp,
|
|
5
|
+
import { mkdtemp, readdir } from "node:fs/promises";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { generateProject, Project } from "ember.nvp";
|
|
8
8
|
import { discoverLayers } from "#layers";
|
|
9
|
-
import type { DiscoveredLayer } from "#types";
|
|
9
|
+
import type { DiscoveredLayer, ProjectType } from "#types";
|
|
10
10
|
import { expect } from "vitest";
|
|
11
11
|
|
|
12
12
|
const minimalApp = "minimal-app";
|
|
13
13
|
const minimalAddon = "minimal-library";
|
|
14
|
+
const minimalExtension = "minimal-extension";
|
|
14
15
|
|
|
15
|
-
export const bases = [minimalApp, minimalAddon];
|
|
16
|
+
export const bases = [minimalApp, minimalAddon, minimalExtension];
|
|
16
17
|
|
|
17
18
|
export const layers = await discoverLayers();
|
|
18
19
|
|
|
@@ -67,31 +68,6 @@ export async function mktemp(name = "ember.nvp_test-") {
|
|
|
67
68
|
return await mkdtemp(join(tmpdir(), `${name}-`));
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
-
* Temporary: `rolldown-plugin-dts` (used by tsdown for `.d.ts` generation)
|
|
72
|
-
* imports `walk` from `yuku-parser`, but yuku-parser >= 0.6.6 moved `walk` to
|
|
73
|
-
* `yuku-ast`. Its dependency range still floats to the broken versions, so a
|
|
74
|
-
* fresh install of a generated library crashes when tsdown builds.
|
|
75
|
-
*
|
|
76
|
-
* The yuku toolchain publishes in lockstep on a shared `@yuku-toolchain/types`,
|
|
77
|
-
* so `yuku-parser` and `yuku-codegen` must be pinned together (pinning only one
|
|
78
|
-
* splits the toolchain and the dts/publint pipeline silently bails). Pin the
|
|
79
|
-
* last working set before install -- here in the test harness only, so the
|
|
80
|
-
* generated blueprint stays override-free. Remove once the plugin is fixed:
|
|
81
|
-
* https://github.com/sxzz/rolldown-plugin-dts/issues/277
|
|
82
|
-
*/
|
|
83
|
-
export async function pinYukuParser(project: Project): Promise<void> {
|
|
84
|
-
const manifestPath = join(project.directory, "package.json");
|
|
85
|
-
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
86
|
-
|
|
87
|
-
manifest.pnpm ??= {};
|
|
88
|
-
manifest.pnpm.overrides ??= {};
|
|
89
|
-
manifest.pnpm.overrides["yuku-parser"] = "0.6.5";
|
|
90
|
-
manifest.pnpm.overrides["yuku-codegen"] = "0.6.5";
|
|
91
|
-
|
|
92
|
-
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
71
|
export async function reapply(project: Project, layers: string[]) {
|
|
96
72
|
return await generate({
|
|
97
73
|
directory: project.directory,
|
|
@@ -112,7 +88,7 @@ export async function generate({
|
|
|
112
88
|
directory?: string;
|
|
113
89
|
layers?: string[];
|
|
114
90
|
name?: string;
|
|
115
|
-
type?:
|
|
91
|
+
type?: ProjectType;
|
|
116
92
|
packageManager?: "pnpm" | "npm";
|
|
117
93
|
}): Promise<Project> {
|
|
118
94
|
const tempDir = directory ?? (await mktemp(name));
|
|
@@ -211,3 +187,22 @@ export async function expectIsSetup(project: Project, layer: DiscoveredLayer) {
|
|
|
211
187
|
|
|
212
188
|
expect(result, `${layer.name} is setup`).toBe(true);
|
|
213
189
|
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* List every file under `directory` (recursively) as sorted paths relative
|
|
193
|
+
* to `directory`. `node_modules` is always excluded; pass
|
|
194
|
+
* `{ includeDist: false }` to also exclude the `dist/` build output.
|
|
195
|
+
*/
|
|
196
|
+
export async function listFiles(
|
|
197
|
+
directory: string,
|
|
198
|
+
{ includeDist = true }: { includeDist?: boolean } = {},
|
|
199
|
+
): Promise<string[]> {
|
|
200
|
+
const entries = await readdir(directory, { withFileTypes: true, recursive: true });
|
|
201
|
+
|
|
202
|
+
return entries
|
|
203
|
+
.filter((entry) => entry.isFile())
|
|
204
|
+
.map((entry) => relative(directory, join(entry.parentPath, entry.name)))
|
|
205
|
+
.filter((path) => !path.split(sep).includes("node_modules"))
|
|
206
|
+
.filter((path) => includeDist || !path.startsWith(`dist${sep}`))
|
|
207
|
+
.sort();
|
|
208
|
+
}
|