ember.nvp 1.0.0 → 1.2.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 +11 -15
- package/CHANGELOG.md +47 -0
- package/package.json +1 -1
- package/packages/rolldown/README.md +101 -1
- 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/babel.test.ts +17 -0
- package/packages/rolldown/src/babel.ts +21 -3
- package/packages/rolldown/src/config.ts +1 -1
- package/packages/rolldown/src/dts.test.ts +366 -0
- package/packages/rolldown/src/transform.test.ts +208 -0
- package/packages/rolldown/src/transform.ts +64 -31
- 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/ember.nvp/scoped-css-library.test.ts +211 -0
- package/test/test-helpers.ts +25 -30
- package/vitest.config.js +1 -1
|
@@ -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 }) => {
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { describe, it, beforeAll, afterAll, expect } from "vitest";
|
|
2
|
+
import { generate, listFiles } from "#test-helpers";
|
|
3
|
+
import { execa } from "execa";
|
|
4
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
import type { Project } from "ember.nvp";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Integration test for building a library that uses ember-scoped-css through
|
|
11
|
+
* `@nullvoxpopuli/ember-rolldown` (tsdown):
|
|
12
|
+
*
|
|
13
|
+
* - the `scopedCSS()` unplugin (ember-scoped-css/rollup) resolves and rewrites
|
|
14
|
+
* the `?scoped=` CSS requests the template transform injects
|
|
15
|
+
* - the template transform + babel plugin ride along via `ember()`'s
|
|
16
|
+
* `babel.templateTransforms` / `babel.plugins` options
|
|
17
|
+
* - `@tsdown/css` with `css.inject` bundles the scoped CSS into
|
|
18
|
+
* `dist/style.css` and keeps the import in `dist/index.js`, so consuming
|
|
19
|
+
* apps load the styles through the module graph
|
|
20
|
+
*
|
|
21
|
+
* Covers all three authoring modes: co-located `.css`, inline
|
|
22
|
+
* `<style scoped>`, and the `scopedClass` pseudo-helper in module code.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const source = {
|
|
26
|
+
"tsdown.config.js": `import { defineConfig } from "tsdown";
|
|
27
|
+
import { ember } from "@nullvoxpopuli/ember-rolldown";
|
|
28
|
+
import { scopedCSS } from "ember-scoped-css/rollup";
|
|
29
|
+
import { scopedCSS as scopedCssBabel } from "ember-scoped-css/babel";
|
|
30
|
+
|
|
31
|
+
export default defineConfig({
|
|
32
|
+
entry: ["./src/index.ts"],
|
|
33
|
+
css: { inject: true },
|
|
34
|
+
plugins: [
|
|
35
|
+
ember({
|
|
36
|
+
babel: {
|
|
37
|
+
plugins: [scopedCssBabel()],
|
|
38
|
+
templateTransforms: [scopedCssBabel.template({})],
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
scopedCSS(),
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
`,
|
|
45
|
+
"src/index.ts": `export { Badge, type BadgeSignature } from "./components/badge.gts";
|
|
46
|
+
export { Banner, type BannerSignature } from "./components/banner.gts";
|
|
47
|
+
export { Chip, type ChipSignature, chipClass } from "./components/chip.gts";
|
|
48
|
+
`,
|
|
49
|
+
// co-located CSS
|
|
50
|
+
"src/components/badge.gts": `import type { TOC } from "@ember/component/template-only";
|
|
51
|
+
|
|
52
|
+
export interface BadgeSignature {
|
|
53
|
+
Element: HTMLSpanElement;
|
|
54
|
+
Blocks: {
|
|
55
|
+
default: [];
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const Badge: TOC<BadgeSignature> = <template>
|
|
60
|
+
<span class="badge" ...attributes>{{yield}}</span>
|
|
61
|
+
</template>;
|
|
62
|
+
`,
|
|
63
|
+
"src/components/badge.css": `.badge {
|
|
64
|
+
border-radius: 0.25rem;
|
|
65
|
+
padding: 0.25rem 0.5rem;
|
|
66
|
+
}
|
|
67
|
+
`,
|
|
68
|
+
// inline <style scoped>
|
|
69
|
+
"src/components/banner.gts": `import type { TOC } from "@ember/component/template-only";
|
|
70
|
+
|
|
71
|
+
export interface BannerSignature {
|
|
72
|
+
Element: HTMLDivElement;
|
|
73
|
+
Blocks: {
|
|
74
|
+
default: [];
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const Banner: TOC<BannerSignature> = <template>
|
|
79
|
+
<style scoped>
|
|
80
|
+
.banner {
|
|
81
|
+
border: 1px solid;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
<div class="banner" ...attributes>{{yield}}</div>
|
|
85
|
+
</template>;
|
|
86
|
+
`,
|
|
87
|
+
// scopedClass pseudo-helper in module code
|
|
88
|
+
"src/components/chip.gts": `import type { TOC } from "@ember/component/template-only";
|
|
89
|
+
import { scopedClass } from "ember-scoped-css";
|
|
90
|
+
|
|
91
|
+
export const chipClass: string = scopedClass("chip");
|
|
92
|
+
|
|
93
|
+
export interface ChipSignature {
|
|
94
|
+
Element: HTMLSpanElement;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const Chip: TOC<ChipSignature> = <template>
|
|
98
|
+
<span class="chip" ...attributes></span>
|
|
99
|
+
</template>;
|
|
100
|
+
`,
|
|
101
|
+
"src/components/chip.css": `.chip {
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
}
|
|
104
|
+
`,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
async function writeSource(project: Project) {
|
|
108
|
+
for (let [filePath, contents] of Object.entries(source)) {
|
|
109
|
+
let target = join(project.directory, filePath);
|
|
110
|
+
|
|
111
|
+
await mkdir(dirname(target), { recursive: true });
|
|
112
|
+
await writeFile(target, contents);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function addDependencies(project: Project) {
|
|
117
|
+
let manifestPath = join(project.directory, "package.json");
|
|
118
|
+
let manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
119
|
+
|
|
120
|
+
// @tsdown/css releases in lockstep with tsdown; both ranges track 0.22.x
|
|
121
|
+
manifest.devDependencies["@tsdown/css"] = "^0.22.13";
|
|
122
|
+
manifest.devDependencies["ember-scoped-css"] = "^3.1.0";
|
|
123
|
+
|
|
124
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
describe("library with ember-scoped-css", () => {
|
|
128
|
+
let project: Project;
|
|
129
|
+
|
|
130
|
+
beforeAll(async () => {
|
|
131
|
+
project = await generate({ type: "library", name: "scoped-lib", layers: ["typescript"] });
|
|
132
|
+
|
|
133
|
+
await writeSource(project);
|
|
134
|
+
await addDependencies(project);
|
|
135
|
+
|
|
136
|
+
let install = await execa("pnpm install", { cwd: project.directory, shell: true });
|
|
137
|
+
expect(install.exitCode).toBe(0);
|
|
138
|
+
|
|
139
|
+
let build = await execa("pnpm build", { cwd: project.directory, shell: true });
|
|
140
|
+
expect(build.exitCode).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
afterAll(async () => {
|
|
144
|
+
if (process.env.CI) return;
|
|
145
|
+
|
|
146
|
+
await rm(project.directory, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("emits the expected files", async () => {
|
|
150
|
+
expect(await listFiles(join(project.directory, "dist"))).toMatchInlineSnapshot(`
|
|
151
|
+
[
|
|
152
|
+
"index.d.ts",
|
|
153
|
+
"index.d.ts.map",
|
|
154
|
+
"index.js",
|
|
155
|
+
"index.js.map",
|
|
156
|
+
"style.css",
|
|
157
|
+
]
|
|
158
|
+
`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("scopes classes in the precompiled templates and keeps the CSS import", async () => {
|
|
162
|
+
let output = await project.read("dist/index.js");
|
|
163
|
+
|
|
164
|
+
expect(output).toMatchInlineSnapshot(`
|
|
165
|
+
"import './style.css';
|
|
166
|
+
import { precompileTemplate } from "@ember/template-compilation";
|
|
167
|
+
import { setComponentTemplate } from "@ember/component";
|
|
168
|
+
import templateOnly from "@ember/component/template-only";
|
|
169
|
+
//#region src/components/badge.ts
|
|
170
|
+
const Badge = setComponentTemplate(precompileTemplate("<span class=\\"badge_e1025e732\\" ...attributes>{{yield}}</span>", { strictMode: true }), templateOnly());
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/components/banner.ts
|
|
173
|
+
const Banner = setComponentTemplate(precompileTemplate("\\n<div class=\\"banner_e88952290\\" ...attributes>{{yield}}</div>", { strictMode: true }), templateOnly());
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/components/chip.ts
|
|
176
|
+
const chipClass = "chip_ec34dd962";
|
|
177
|
+
const Chip = setComponentTemplate(precompileTemplate("<span class=\\"chip_ec34dd962\\" ...attributes></span>", { strictMode: true }), templateOnly());
|
|
178
|
+
//#endregion
|
|
179
|
+
export { Badge, Banner, Chip, chipClass };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=index.js.map"
|
|
182
|
+
`);
|
|
183
|
+
|
|
184
|
+
// the scopedClass import is compiled away; nothing of ember-scoped-css
|
|
185
|
+
// remains at runtime
|
|
186
|
+
expect(output).not.toContain("ember-scoped-css");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("rewrites and bundles all three CSS sources", async () => {
|
|
190
|
+
expect(await project.read("dist/style.css")).toMatchInlineSnapshot(`
|
|
191
|
+
".badge_e1025e732 {
|
|
192
|
+
border-radius: .25rem;
|
|
193
|
+
padding: .25rem .5rem;
|
|
194
|
+
}
|
|
195
|
+
.banner_e88952290 {
|
|
196
|
+
border: 1px solid;
|
|
197
|
+
}
|
|
198
|
+
.chip_ec34dd962 {
|
|
199
|
+
display: inline-flex;
|
|
200
|
+
}
|
|
201
|
+
"
|
|
202
|
+
`);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("emits declarations untouched by scoping", async () => {
|
|
206
|
+
let declarations = await project.read("dist/index.d.ts");
|
|
207
|
+
|
|
208
|
+
expect(declarations).toContain("declare const Badge: TOC<BadgeSignature>");
|
|
209
|
+
expect(declarations).not.toContain("badge_");
|
|
210
|
+
});
|
|
211
|
+
});
|
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
|
+
}
|