ember.nvp 1.9.0 → 1.10.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 (43) hide show
  1. package/.release-plan.json +22 -4
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +65 -4
  4. package/package.json +2 -1
  5. package/packages/rolldown/package.json +6 -2
  6. package/packages/rolldown/src/dts.test.ts +105 -1
  7. package/packages/rolldown/src/isolated-declarations.ts +67 -61
  8. package/packages/rolldown/src/transform.test.ts +212 -15
  9. package/packages/rolldown/src/transform.ts +106 -26
  10. package/src/cli/args.js +103 -14
  11. package/src/cli/generator.js +2 -1
  12. package/src/cli/help.js +66 -16
  13. package/src/cli/index.js +15 -0
  14. package/src/cli/questions/layer-options.js +130 -0
  15. package/src/cli/questions/validate-option.js +106 -0
  16. package/src/layers/eslint-bundled/app.test.ts +182 -0
  17. package/src/layers/eslint-bundled/index.js +172 -0
  18. package/src/layers/expect-type/index.js +11 -1
  19. package/src/layers/github-actions/app.test.ts +1 -1
  20. package/src/layers/typescript/app.test.ts +6 -0
  21. package/src/layers/typescript/index.js +48 -148
  22. package/src/layers/typescript/typescript-6.js +102 -0
  23. package/src/layers/typescript/typescript-7.js +177 -0
  24. package/src/utils/manifest.js +13 -0
  25. package/src/utils/npm.js +27 -0
  26. package/src/utils/project.js +23 -0
  27. package/src/utils/types.ts +52 -1
  28. package/src/utils/typescript.js +147 -0
  29. package/test/ember.nvp/base-library.test.ts +11 -0
  30. package/test/ember.nvp/cli.test.ts +86 -1
  31. package/test/ember.nvp/layer-options.test.ts +248 -0
  32. package/test/ember.nvp/layer-readme.test.ts +4 -2
  33. package/test/ember.nvp/layer-typescript.test.ts +302 -0
  34. package/test/ember.nvp/layers.test.ts +1 -2
  35. package/test/ember.nvp/permutations/shared.ts +3 -4
  36. package/test/ember.nvp/validate-option.test.ts +152 -0
  37. package/test/test-helpers/permutate.test.ts +2 -7
  38. package/test/test-helpers.ts +8 -1
  39. package/src/layers/eslint-bundled-ember/index.js +0 -91
  40. package/src/layers/eslint-bundled-nvp/app.test.ts +0 -91
  41. package/src/layers/eslint-bundled-nvp/index.js +0 -91
  42. /package/src/layers/{eslint-bundled-ember/files → eslint-bundled/files/ember}/eslint.config.js +0 -0
  43. /package/src/layers/{eslint-bundled-nvp/files → eslint-bundled/files/nvp}/eslint.config.js +0 -0
@@ -0,0 +1,302 @@
1
+ import { describe, it, beforeAll, afterAll, expect } from "vitest";
2
+ import { generate, expectIsSetup, layers, reapply } from "#test-helpers";
3
+ import { writeLibrarySource } from "./library-src-fixtures.ts";
4
+ import { execa } from "execa";
5
+ import { rm, writeFile } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import { packageJson } from "ember-apply";
8
+
9
+ import type { Project } from "ember.nvp";
10
+
11
+ const typescript = layers.find((layer) => layer.name === "typescript")!;
12
+ const eslint = layers.find((layer) => layer.name === "eslint-bundled")!;
13
+
14
+ const typescript6 = { typescript: { version: "6" }, "eslint-bundled": { preset: "nvp" } };
15
+ const typescript7 = { typescript: { version: "7" }, "eslint-bundled": { preset: "nvp" } };
16
+
17
+ function run(project: Project, command: string) {
18
+ return execa(command, { cwd: project.directory, shell: true, all: true, reject: false });
19
+ }
20
+
21
+ async function devDependencies(project: Project): Promise<Record<string, string>> {
22
+ let manifest = await packageJson.read(project.directory);
23
+
24
+ return manifest.devDependencies ?? {};
25
+ }
26
+
27
+ describe("layer: typescript, version 7", () => {
28
+ const dirs: string[] = [];
29
+
30
+ afterAll(async () => {
31
+ if (process.env.CI) return;
32
+
33
+ for (const dir of dirs) {
34
+ await rm(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
35
+ }
36
+ });
37
+
38
+ describe("app with eslint", () => {
39
+ let project: Project;
40
+
41
+ beforeAll(async () => {
42
+ project = await generate({
43
+ type: "app",
44
+ layers: ["typescript", "eslint-bundled", "git"],
45
+ options: typescript7,
46
+ });
47
+ dirs.push(project.directory);
48
+ });
49
+
50
+ it("is setup", async () => {
51
+ await expectIsSetup(project, typescript);
52
+ await expectIsSetup(project, eslint);
53
+ });
54
+
55
+ it("installs TypeScript 7 as @typescript/native, and TypeScript 6 for typescript-eslint", async () => {
56
+ let deps = await devDependencies(project);
57
+
58
+ expect(deps["@typescript/native"]).toMatch(/^npm:typescript@7\./);
59
+ expect(deps["typescript"]).toMatch(/^npm:@typescript\/typescript6@6\./);
60
+ expect(deps).toHaveProperty("ember-content-mapper");
61
+ expect(deps).not.toHaveProperty("@glint/tsserver-plugin");
62
+ });
63
+
64
+ it("reapplying causes no changes", async () => {
65
+ await reapply(project, ["typescript", "eslint-bundled", "git"]);
66
+
67
+ expect(await project.gitHasDiff()).toBe(false);
68
+ });
69
+
70
+ describe("after install", () => {
71
+ beforeAll(async () => {
72
+ let install = await run(project, "pnpm install");
73
+ expect(install.exitCode, install.all).toBe(0);
74
+ });
75
+
76
+ it("type checking works", async () => {
77
+ let result = await run(project, "pnpm lint:types");
78
+
79
+ expect(result.exitCode, result.all).toBe(0);
80
+ });
81
+
82
+ it("reports template errors in .gts files", async () => {
83
+ let file = "app/templates/application.gts";
84
+ let original = await project.read(file);
85
+
86
+ await writeFile(
87
+ join(project.directory, file),
88
+ `const count: number = 1;\n\n<template>{{count.nope}}</template>\n`,
89
+ );
90
+
91
+ let result = await run(project, "pnpm lint:types");
92
+
93
+ await writeFile(join(project.directory, file), original!);
94
+
95
+ expect(result.exitCode).not.toBe(0);
96
+ expect(result.all).toContain("application.gts");
97
+ expect(result.all).toContain("'nope'");
98
+ });
99
+
100
+ it("linting works", async () => {
101
+ await run(project, "pnpm lint:eslint --fix");
102
+
103
+ let result = await run(project, "pnpm lint:eslint");
104
+
105
+ expect(result.exitCode, result.all).toBe(0);
106
+ });
107
+
108
+ it("build works", async () => {
109
+ let result = await run(project, "pnpm build");
110
+
111
+ expect(result.exitCode, result.all).toBe(0);
112
+ });
113
+ });
114
+ });
115
+
116
+ describe("app without eslint", () => {
117
+ let project: Project;
118
+
119
+ beforeAll(async () => {
120
+ project = await generate({ type: "app", layers: ["typescript"], options: typescript7 });
121
+ dirs.push(project.directory);
122
+ });
123
+
124
+ it("is setup", async () => {
125
+ await expectIsSetup(project, typescript);
126
+ });
127
+
128
+ it("has no typescript, because nothing needs TypeScript 6", async () => {
129
+ expect(await devDependencies(project)).not.toHaveProperty("typescript");
130
+ });
131
+
132
+ it("type checking works", async () => {
133
+ let install = await run(project, "pnpm install");
134
+ expect(install.exitCode, install.all).toBe(0);
135
+
136
+ let result = await run(project, "pnpm lint:types");
137
+
138
+ expect(result.exitCode, result.all).toBe(0);
139
+ });
140
+ });
141
+
142
+ describe.each(["app", "library"] as const)(
143
+ "migrating a TypeScript 6 %s without eslint",
144
+ (type) => {
145
+ let project: Project;
146
+
147
+ beforeAll(async () => {
148
+ let layers = ["typescript", "git"];
149
+ let before = await generate({
150
+ type,
151
+ layers,
152
+ options: { typescript: { version: "6" } },
153
+ });
154
+ dirs.push(before.directory);
155
+
156
+ expect(await devDependencies(before)).toHaveProperty("typescript");
157
+
158
+ project = await generate({
159
+ directory: before.directory,
160
+ type,
161
+ layers,
162
+ options: { typescript: { version: "7" } },
163
+ });
164
+ });
165
+
166
+ it("is setup, and detects version 7", async () => {
167
+ await expectIsSetup(project, typescript);
168
+ expect(await typescript.options?.version?.detect?.(project)).toBe("7");
169
+ });
170
+
171
+ it("removes typescript and the tsserver plugin, because nothing needs TypeScript 6", async () => {
172
+ let manifest = await packageJson.read(project.directory);
173
+
174
+ expect(manifest.scripts?.["lint:types"]).toBe("tsc --noEmit --runExternalCode");
175
+ expect(manifest.devDependencies?.["@typescript/native"]).toMatch(/^npm:typescript@7\./);
176
+ expect(manifest.devDependencies).not.toHaveProperty("typescript");
177
+ expect(manifest.devDependencies).not.toHaveProperty("@glint/tsserver-plugin");
178
+ });
179
+
180
+ it("reapplying causes no changes", async () => {
181
+ await reapply(project, ["typescript", "git"]);
182
+
183
+ expect(await project.gitHasDiff()).toBe(false);
184
+ });
185
+
186
+ it("type checking and the build work", async () => {
187
+ if (type === "library") await writeLibrarySource(project, "typescript");
188
+
189
+ let install = await run(project, "pnpm install");
190
+ expect(install.exitCode, install.all).toBe(0);
191
+
192
+ let types = await run(project, "pnpm lint:types");
193
+ expect(types.exitCode, types.all).toBe(0);
194
+
195
+ let build = await run(project, "pnpm build");
196
+ expect(build.exitCode, build.all).toBe(0);
197
+ });
198
+ },
199
+ );
200
+
201
+ describe("migrating a TypeScript 6 app with eslint", () => {
202
+ let project: Project;
203
+
204
+ beforeAll(async () => {
205
+ let layers = ["typescript", "eslint-bundled", "git"];
206
+ let before = await generate({ type: "app", layers, options: typescript6 });
207
+ dirs.push(before.directory);
208
+
209
+ project = await generate({
210
+ directory: before.directory,
211
+ type: "app",
212
+ layers,
213
+ options: typescript7,
214
+ });
215
+ });
216
+
217
+ it("is setup", async () => {
218
+ await expectIsSetup(project, typescript);
219
+ await expectIsSetup(project, eslint);
220
+ });
221
+
222
+ it("detects the version the project uses", async () => {
223
+ expect(await typescript.options?.version?.detect?.(project)).toBe("7");
224
+ });
225
+
226
+ it("switches to TypeScript 7 and keeps TypeScript 6 for typescript-eslint", async () => {
227
+ let manifest = await packageJson.read(project.directory);
228
+
229
+ expect(manifest.scripts?.["lint:types"]).toBe("tsc --noEmit --runExternalCode");
230
+ expect(manifest.devDependencies?.["typescript"]).toMatch(/^npm:@typescript\/typescript6@/);
231
+ expect(manifest.devDependencies).not.toHaveProperty("@glint/tsserver-plugin");
232
+ });
233
+
234
+ it("type checking and linting work", async () => {
235
+ let install = await run(project, "pnpm install");
236
+ expect(install.exitCode, install.all).toBe(0);
237
+
238
+ let types = await run(project, "pnpm lint:types");
239
+ expect(types.exitCode, types.all).toBe(0);
240
+
241
+ await run(project, "pnpm lint:eslint --fix");
242
+
243
+ let lint = await run(project, "pnpm lint:eslint");
244
+ expect(lint.exitCode, lint.all).toBe(0);
245
+ });
246
+ });
247
+
248
+ describe("library", () => {
249
+ let project: Project;
250
+
251
+ beforeAll(async () => {
252
+ project = await generate({
253
+ type: "library",
254
+ name: "my-lib",
255
+ layers: ["typescript", "expect-type"],
256
+ options: typescript7,
257
+ });
258
+ dirs.push(project.directory);
259
+
260
+ await writeLibrarySource(project, "typescript");
261
+
262
+ let install = await run(project, "pnpm install");
263
+ expect(install.exitCode, install.all).toBe(0);
264
+ });
265
+
266
+ it("is setup", async () => {
267
+ await expectIsSetup(project, typescript);
268
+ });
269
+
270
+ it("has no typescript, because nothing needs TypeScript 6", async () => {
271
+ expect(await devDependencies(project)).not.toHaveProperty("typescript");
272
+ });
273
+
274
+ it("type checking works", async () => {
275
+ let result = await run(project, "pnpm lint:types");
276
+
277
+ expect(result.exitCode, result.all).toBe(0);
278
+ });
279
+
280
+ it("type tests resolve .gts modules", async () => {
281
+ await writeFile(
282
+ join(project.directory, "type-tests/greeting.test.ts"),
283
+ `import { expectTypeOf } from "expect-type";
284
+ import { Greeting, type GreetingSignature } from "../src/index.ts";
285
+
286
+ expectTypeOf(Greeting).not.toBeAny();
287
+ expectTypeOf<GreetingSignature["Args"]>().toEqualTypeOf<{ name: string }>();
288
+ `,
289
+ );
290
+
291
+ let result = await run(project, "pnpm lint:type-tests");
292
+
293
+ expect(result.exitCode, result.all).toBe(0);
294
+ });
295
+
296
+ it("build works", async () => {
297
+ let result = await run(project, "pnpm build");
298
+
299
+ expect(result.exitCode, result.all).toBe(0);
300
+ });
301
+ });
302
+ });
@@ -11,8 +11,7 @@ describe("discoverLayers", () => {
11
11
  expect(layerNames).toMatchInlineSnapshot(`
12
12
  [
13
13
  "are-the-types-wrong",
14
- "eslint-bundled-ember",
15
- "eslint-bundled-nvp",
14
+ "eslint-bundled",
16
15
  "expect-type",
17
16
  "git",
18
17
  "github-actions",
@@ -16,7 +16,7 @@ const RE_APPLY_ONLY = new Set<string>([
16
16
  ]);
17
17
  const INITIAL_ONLY = new Set<string>([
18
18
  // baseline,
19
- // "eslint-bundled-nvp",
19
+ // "eslint-bundled",
20
20
  // "typescript"
21
21
  ]);
22
22
 
@@ -45,7 +45,7 @@ const TYPE_FOR_BASE: Record<(typeof bases)[number], ProjectType> = {
45
45
  };
46
46
 
47
47
  /**
48
- * Layers that only add lint / format / publish checks.
48
+ * Layers that only add lint / format / type / publish checks.
49
49
  *
50
50
  * They don't affect the runtime shape of a project,
51
51
  * so they permutate among themselves in a small "checks" matrix
@@ -55,8 +55,7 @@ const TYPE_FOR_BASE: Record<(typeof bases)[number], ProjectType> = {
55
55
  * so linear cross-group coverage remains.
56
56
  */
57
57
  const CHECK_LAYERS = new Set<string>([
58
- "eslint-bundled-ember",
59
- "eslint-bundled-nvp",
58
+ "eslint-bundled",
60
59
  "eslint-ejected",
61
60
  "prettier",
62
61
  "publint",
@@ -0,0 +1,152 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { validateOption } from "../../src/cli/questions/validate-option.js";
3
+ import type { LayerOptionSchema } from "../../src/utils/types.js";
4
+
5
+ describe("validateOption", () => {
6
+ describe("number validation", () => {
7
+ const schema: LayerOptionSchema = {
8
+ type: "number",
9
+ prompt: "Enter number",
10
+ default: 10,
11
+ validate: (val: number) => (val > 0 ? undefined : "Must be greater than 0"),
12
+ };
13
+
14
+ it("validates valid numbers", () => {
15
+ expect(validateOption(schema, 5)).toEqual({ ok: true, value: 5 });
16
+ expect(validateOption(schema, "42")).toEqual({ ok: true, value: 42 });
17
+ expect(validateOption(schema, " 2.5 ")).toEqual({ ok: true, value: 2.5 });
18
+ });
19
+
20
+ it.each(["", "abc", "0x10", "1e3"])("fails on '%s'", (input) => {
21
+ expect(validateOption(schema, input)).toEqual({
22
+ ok: false,
23
+ error: `'${input}' is not a number`,
24
+ });
25
+ });
26
+
27
+ it("fails custom validate function", () => {
28
+ expect(validateOption(schema, -5)).toEqual({ ok: false, error: "Must be greater than 0" });
29
+ });
30
+ });
31
+
32
+ describe("text validation", () => {
33
+ const schema: LayerOptionSchema = {
34
+ type: "text",
35
+ prompt: "Enter text",
36
+ default: "default-title",
37
+ validate: (val: string) => (val.length >= 3 ? undefined : "Minimum 3 chars"),
38
+ };
39
+
40
+ it("validates valid text", () => {
41
+ expect(validateOption(schema, "hello")).toEqual({ ok: true, value: "hello" });
42
+ });
43
+
44
+ it("fails custom validate function", () => {
45
+ expect(validateOption(schema, "hi")).toEqual({ ok: false, error: "Minimum 3 chars" });
46
+ });
47
+
48
+ it("fails with the message of an Error", () => {
49
+ const strict: LayerOptionSchema = { ...schema, validate: () => new Error("Nope") };
50
+
51
+ expect(validateOption(strict, "hello")).toEqual({ ok: false, error: "Nope" });
52
+ });
53
+ });
54
+
55
+ describe("confirm validation", () => {
56
+ const schema: LayerOptionSchema = {
57
+ type: "confirm",
58
+ prompt: "Confirm action",
59
+ default: true,
60
+ };
61
+
62
+ it("validates booleans", () => {
63
+ expect(validateOption(schema, true)).toEqual({ ok: true, value: true });
64
+ expect(validateOption(schema, false)).toEqual({ ok: true, value: false });
65
+ });
66
+
67
+ it("fails on strings", () => {
68
+ expect(validateOption(schema, "yes")).toEqual({
69
+ ok: false,
70
+ error: "'yes' is not true or false",
71
+ });
72
+ });
73
+ });
74
+
75
+ describe("select validation", () => {
76
+ const schema: LayerOptionSchema = {
77
+ type: "select",
78
+ prompt: "Select option",
79
+ default: "standard",
80
+ options: [
81
+ { label: "Standard", value: "standard" },
82
+ { label: "Deluxe", value: "deluxe" },
83
+ ],
84
+ };
85
+
86
+ it("validates valid option choice", () => {
87
+ expect(validateOption(schema, "deluxe")).toEqual({ ok: true, value: "deluxe" });
88
+ });
89
+
90
+ it("fails unlisted choice", () => {
91
+ expect(validateOption(schema, "ultra")).toEqual({
92
+ ok: false,
93
+ error: "Invalid option 'ultra'. Must be one of: standard, deluxe",
94
+ });
95
+ });
96
+ });
97
+
98
+ describe("multiselect validation", () => {
99
+ const schema: LayerOptionSchema = {
100
+ type: "multiselect",
101
+ prompt: "Select options",
102
+ options: [
103
+ { label: "A", value: "a" },
104
+ { label: "B", value: "b" },
105
+ ],
106
+ validate: (val: string[]) => (val.length > 0 ? undefined : "Select at least one"),
107
+ };
108
+
109
+ it("validates listed choices", () => {
110
+ expect(validateOption(schema, ["a", "b"])).toEqual({ ok: true, value: ["a", "b"] });
111
+ });
112
+
113
+ it("fails unlisted choice", () => {
114
+ expect(validateOption(schema, ["a", "c"])).toEqual({
115
+ ok: false,
116
+ error: "Invalid option 'c'. Must be one of: a, b",
117
+ });
118
+ });
119
+
120
+ it("fails custom validate function", () => {
121
+ expect(validateOption(schema, [])).toEqual({ ok: false, error: "Select at least one" });
122
+ });
123
+ });
124
+
125
+ describe("Standard Schema validation", () => {
126
+ // A hand-written schema, so the test needs no schema library
127
+ const positive = {
128
+ "~standard": {
129
+ version: 1 as const,
130
+ vendor: "test",
131
+ validate: (value: unknown) =>
132
+ typeof value === "number" && value > 0
133
+ ? { value }
134
+ : { issues: [{ message: "Must be positive" }] },
135
+ },
136
+ };
137
+
138
+ const schema: LayerOptionSchema = {
139
+ type: "number",
140
+ prompt: "Enter number",
141
+ validate: positive,
142
+ };
143
+
144
+ it("passes values the schema accepts", () => {
145
+ expect(validateOption(schema, "3")).toEqual({ ok: true, value: 3 });
146
+ });
147
+
148
+ it("fails with the schema's first issue", () => {
149
+ expect(validateOption(schema, "-3")).toEqual({ ok: false, error: "Must be positive" });
150
+ });
151
+ });
152
+ });
@@ -25,12 +25,7 @@ describe("permutate", () => {
25
25
  });
26
26
 
27
27
  it("enforces mutual exclusivity for eslint-prefixed entries", () => {
28
- const result = permutate([
29
- "eslint-bundled-ember",
30
- "eslint-bundled-nvp",
31
- "eslint-ejected",
32
- "other",
33
- ]);
28
+ const result = permutate(["eslint-bundled", "eslint-ejected", "other"]);
34
29
 
35
30
  // Check that no permutation has more than one eslint-prefixed entry
36
31
  for (const permutation of result) {
@@ -40,7 +35,7 @@ describe("permutate", () => {
40
35
  });
41
36
 
42
37
  it("allows permutations with 0 or 1 eslint entry", () => {
43
- const result = permutate(["eslint-bundled-ember", "eslint-bundled-nvp", "other"]);
38
+ const result = permutate(["eslint-bundled", "eslint-ejected", "other"]);
44
39
 
45
40
  const hasNoEslint = result.some((perm) => !perm.some((item) => item.startsWith("eslint-")));
46
41
  const hasOneEslint = result.some(
@@ -6,7 +6,7 @@ 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, ProjectType } from "#types";
9
+ import type { Answers, DiscoveredLayer, ProjectType } from "#types";
10
10
  import { expect } from "vitest";
11
11
 
12
12
  const minimalApp = "minimal-app";
@@ -78,6 +78,7 @@ export async function reapply(project: Project, layers: string[]) {
78
78
  name: project.name,
79
79
  type: project.type,
80
80
  packageManager: project.packageManager,
81
+ options: project.desires.options,
81
82
  });
82
83
  }
83
84
 
@@ -87,12 +88,17 @@ export async function generate({
87
88
  type = "app",
88
89
  packageManager = "pnpm",
89
90
  directory,
91
+ options,
90
92
  }: {
91
93
  directory?: string;
92
94
  layers?: string[];
93
95
  name?: string;
94
96
  type?: ProjectType;
95
97
  packageManager?: "pnpm" | "npm";
98
+ /**
99
+ * Layer options, keyed by layer name
100
+ */
101
+ options?: Answers["options"];
96
102
  }): Promise<Project> {
97
103
  const tempDir = directory ?? (await mktemp(name));
98
104
 
@@ -103,6 +109,7 @@ export async function generate({
103
109
  path: tempDir,
104
110
  layers: selectedLayers,
105
111
  packageManager,
112
+ options,
106
113
  });
107
114
 
108
115
  await generateProject(project);
@@ -1,91 +0,0 @@
1
- import { getLatest } from "#utils/npm.js";
2
- import { files, packageJson } from "ember-apply";
3
- import { formatLabel } from "#utils/cli.js";
4
- import { maybeLintWithConcurrently } from "#consolidators/linting.js";
5
- import { existsSync } from "node:fs";
6
- import { join } from "node:path";
7
- import { hasDevDeps } from "#utils/manifest.js";
8
-
9
- /**
10
- * @type {import('#types').Layer}
11
- */
12
- export default {
13
- label: formatLabel("ESLint", "ember's encapsulated config"),
14
- hint: `Minimal dependencies added to package.json. Not official, but follows the official configuration`,
15
-
16
- async run(project) {
17
- await files.applyFolder(join(import.meta.dirname, "files"), project.directory);
18
-
19
- await packageJson.addDevDependencies(
20
- await getLatest({
21
- "ember-eslint": "^1.0.0",
22
- eslint: "^10.9.1",
23
- }),
24
- project.directory,
25
- );
26
-
27
- await packageJson.addScripts(
28
- {
29
- "lint:eslint": "eslint . --cache",
30
- "lint:eslint:fix": "eslint . --fix",
31
- },
32
- project.directory,
33
- );
34
-
35
- await maybeLintWithConcurrently(project);
36
- },
37
-
38
- /**
39
- * @overload
40
- * @param {import('#utils/project.js').Project} project
41
- * @param {true} explain
42
- * @returns {Promise<{ isSetup: boolean; reasons: string[] }>}
43
- */
44
- /**
45
- * @overload
46
- * @param {import('#utils/project.js').Project} project
47
- * @param {boolean | undefined} [explain]
48
- * @returns {Promise<boolean>}
49
- */
50
- async isSetup(project, explain) {
51
- const reasons = [];
52
-
53
- if (!existsSync(join(project.directory, "eslint.config.js"))) {
54
- reasons.push("eslint.config.js is missing");
55
-
56
- if (!explain) return false;
57
- }
58
-
59
- let manifest = await packageJson.read(project.directory);
60
-
61
- if (!hasDevDeps(manifest, ["ember-eslint", "eslint"])) {
62
- reasons.push("missing required dependencies: ember-eslint, eslint");
63
-
64
- if (!explain) return false;
65
- }
66
-
67
- let scripts = Object.values(manifest.scripts ?? {}).filter((script) =>
68
- script.includes("eslint"),
69
- );
70
-
71
- if (!scripts.some((script) => script.includes("--cache"))) {
72
- reasons.push("missing eslint script with --cache flag");
73
-
74
- if (!explain) return false;
75
- }
76
- if (!scripts.some((script) => script.includes("--fix"))) {
77
- reasons.push("missing eslint script with --fix flag");
78
-
79
- if (!explain) return false;
80
- }
81
-
82
- if (explain) {
83
- return {
84
- isSetup: reasons.length === 0,
85
- reasons,
86
- };
87
- }
88
-
89
- return reasons.length === 0;
90
- },
91
- };