ember.nvp 1.9.0 → 1.11.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/.release-plan.json +4 -4
- package/CHANGELOG.md +31 -0
- package/README.md +65 -4
- package/package.json +2 -1
- package/packages/rolldown/package.json +6 -2
- package/packages/rolldown/src/dts.test.ts +105 -1
- package/packages/rolldown/src/isolated-declarations.ts +67 -61
- package/packages/rolldown/src/transform.test.ts +212 -15
- package/packages/rolldown/src/transform.ts +106 -26
- package/src/cli/args.js +105 -15
- package/src/cli/generator.js +2 -1
- package/src/cli/help.js +76 -16
- package/src/cli/index.js +15 -0
- package/src/cli/questions/layer-options.js +130 -0
- package/src/cli/questions/validate-option.js +106 -0
- package/src/layers/eslint-bundled/app.test.ts +182 -0
- package/src/layers/eslint-bundled/index.js +172 -0
- package/src/layers/expect-type/index.js +11 -1
- package/src/layers/github-actions/app.test.ts +1 -1
- package/src/layers/typescript/app.test.ts +6 -0
- package/src/layers/typescript/index.js +48 -148
- package/src/layers/typescript/typescript-6.js +102 -0
- package/src/layers/typescript/typescript-7.js +177 -0
- package/src/utils/manifest.js +13 -0
- package/src/utils/npm.js +27 -0
- package/src/utils/project.js +23 -0
- package/src/utils/types.ts +52 -1
- package/src/utils/typescript.js +147 -0
- package/test/ember.nvp/base-library.test.ts +11 -0
- package/test/ember.nvp/cli.test.ts +90 -3
- package/test/ember.nvp/layer-options.test.ts +249 -0
- package/test/ember.nvp/layer-readme.test.ts +4 -2
- package/test/ember.nvp/layer-typescript.test.ts +302 -0
- package/test/ember.nvp/layers.test.ts +1 -2
- package/test/ember.nvp/permutations/shared.ts +3 -4
- package/test/ember.nvp/validate-option.test.ts +152 -0
- package/test/test-helpers/permutate.test.ts +2 -7
- package/test/test-helpers.ts +8 -1
- package/src/layers/eslint-bundled-ember/index.js +0 -91
- package/src/layers/eslint-bundled-nvp/app.test.ts +0 -91
- package/src/layers/eslint-bundled-nvp/index.js +0 -91
- /package/src/layers/{eslint-bundled-ember/files → eslint-bundled/files/ember}/eslint.config.js +0 -0
- /package/src/layers/{eslint-bundled-nvp/files → eslint-bundled/files/nvp}/eslint.config.js +0 -0
package/src/cli/help.js
CHANGED
|
@@ -1,32 +1,92 @@
|
|
|
1
1
|
import { styleText } from "node:util";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* CLI flags only carry strings and booleans
|
|
5
|
+
*
|
|
6
|
+
* @type {Record<import('#types').LayerOptionType, string>}
|
|
7
|
+
*/
|
|
8
|
+
const FLAG_TYPES = {
|
|
9
|
+
text: "string",
|
|
10
|
+
number: "number",
|
|
11
|
+
confirm: "boolean",
|
|
12
|
+
select: "string",
|
|
13
|
+
multiselect: "string",
|
|
14
|
+
};
|
|
15
|
+
|
|
3
16
|
/**
|
|
4
17
|
* Print help text to stdout.
|
|
5
18
|
*
|
|
6
19
|
* @param {Record<string, any>} coreOptions
|
|
20
|
+
* @param {import('#types').DiscoveredLayer[]} [layers]
|
|
7
21
|
*/
|
|
8
|
-
export function printHelp(coreOptions) {
|
|
22
|
+
export function printHelp(coreOptions, layers = []) {
|
|
9
23
|
const title = styleText(["bgCyan", "black"], " ember.nvp ");
|
|
10
24
|
console.log(`${title}\n`);
|
|
11
25
|
console.log(`${styleText("bold", "Usage:")} npx ember.nvp [options]\n`);
|
|
12
26
|
|
|
13
27
|
console.log(styleText("bold", "Core Options:"));
|
|
14
28
|
for (const [name, config] of Object.entries(coreOptions)) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
printOption({
|
|
30
|
+
flag: `--${name}`,
|
|
31
|
+
alias: config.short ? `-${config.short}, ` : " ",
|
|
32
|
+
type: config.type,
|
|
33
|
+
multiple: config.multiple,
|
|
34
|
+
description: config.description,
|
|
35
|
+
choices: config.choices,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const withOptions = layers.filter((layer) => layer.options);
|
|
40
|
+
|
|
41
|
+
if (withOptions.length === 0) return;
|
|
42
|
+
|
|
43
|
+
console.log(styleText("bold", "Layer Options:"));
|
|
44
|
+
for (const layer of withOptions) {
|
|
45
|
+
console.log(` Layer: ${styleText(["cyan", "bold"], layer.name)}`);
|
|
46
|
+
for (const [key, schema] of Object.entries(layer.options ?? {})) {
|
|
47
|
+
const name = `${layer.name}.${key}`;
|
|
48
|
+
|
|
49
|
+
printOption({
|
|
50
|
+
flag: schema.type === "confirm" ? `--[no-]${name}` : `--${name}`,
|
|
51
|
+
alias: " ",
|
|
52
|
+
type: FLAG_TYPES[schema.type],
|
|
53
|
+
multiple: schema.type === "multiselect",
|
|
54
|
+
description: schema.prompt,
|
|
55
|
+
choices: schema.options?.map((choice) => choice.value),
|
|
56
|
+
fallback: schema.default,
|
|
57
|
+
});
|
|
29
58
|
}
|
|
30
|
-
console.log("");
|
|
31
59
|
}
|
|
32
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {{
|
|
64
|
+
* flag: string,
|
|
65
|
+
* alias: string,
|
|
66
|
+
* type?: string,
|
|
67
|
+
* multiple?: boolean,
|
|
68
|
+
* description?: string,
|
|
69
|
+
* choices?: string[],
|
|
70
|
+
* fallback?: unknown,
|
|
71
|
+
* }} option
|
|
72
|
+
*/
|
|
73
|
+
function printOption({ flag, alias, type, multiple, description = "", choices, fallback }) {
|
|
74
|
+
const typeStr = type ? styleText("dim", `<${type}>`) : "";
|
|
75
|
+
const repeatStr = multiple
|
|
76
|
+
? type
|
|
77
|
+
? ` ${styleText("dim", "[")}${styleText("cyan", flag)} ${typeStr}${styleText("dim", " ...]")}`
|
|
78
|
+
: ` ${styleText("dim", "[")}${styleText("cyan", flag)}${styleText("dim", " ...]")}`
|
|
79
|
+
: "";
|
|
80
|
+
const flagAndType = [styleText("cyan", flag), typeStr].filter(Boolean).join(" ");
|
|
81
|
+
const choicesStr = choices
|
|
82
|
+
? styleText("yellow", ` [choices: ${choices.map((c) => `"${c}"`).join(", ")}]`)
|
|
83
|
+
: "";
|
|
84
|
+
const fallbackStr =
|
|
85
|
+
fallback === undefined ? "" : styleText("dim", ` [default: ${JSON.stringify(fallback)}]`);
|
|
86
|
+
|
|
87
|
+
console.log(` ${styleText("cyan", alias)}${flagAndType}${repeatStr}`);
|
|
88
|
+
if (description || choicesStr || fallbackStr) {
|
|
89
|
+
console.log(` ${description}${choicesStr}${fallbackStr}`);
|
|
90
|
+
}
|
|
91
|
+
console.log("");
|
|
92
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as p from "@clack/prompts";
|
|
|
4
4
|
import { generateProject } from "./generator.js";
|
|
5
5
|
import { askName } from "./questions/name.js";
|
|
6
6
|
import { askLayers } from "./questions/layers.js";
|
|
7
|
+
import { askLayerOptions } from "./questions/layer-options.js";
|
|
7
8
|
import { askProjectType } from "./questions/project-type.js";
|
|
8
9
|
import { askPackageManager } from "./questions/package-manager.js";
|
|
9
10
|
import { askPath } from "./questions/path.js";
|
|
@@ -35,6 +36,19 @@ async function main() {
|
|
|
35
36
|
const selectedLayers = await askLayers(projectType);
|
|
36
37
|
const packageManager = await askPackageManager();
|
|
37
38
|
|
|
39
|
+
// Layer options start from what the project already uses
|
|
40
|
+
const existing =
|
|
41
|
+
replaceOrUpdate === "update"
|
|
42
|
+
? new Project(projectPath, {
|
|
43
|
+
name: projectName,
|
|
44
|
+
type: projectType,
|
|
45
|
+
path: projectPath,
|
|
46
|
+
layers: selectedLayers,
|
|
47
|
+
packageManager,
|
|
48
|
+
})
|
|
49
|
+
: undefined;
|
|
50
|
+
const layerOptions = await askLayerOptions(selectedLayers, existing);
|
|
51
|
+
|
|
38
52
|
await askIfOK();
|
|
39
53
|
|
|
40
54
|
/**
|
|
@@ -60,6 +74,7 @@ async function main() {
|
|
|
60
74
|
path: projectPath,
|
|
61
75
|
layers: selectedLayers,
|
|
62
76
|
packageManager,
|
|
77
|
+
options: layerOptions,
|
|
63
78
|
});
|
|
64
79
|
|
|
65
80
|
const s = p.spinner();
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { styleText } from "node:util";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { printArgInUse, parseLayerOptionsFromParsedArgs } from "#args";
|
|
4
|
+
import { validateOption } from "./validate-option.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Prompt the user for options defined on the selected layers.
|
|
8
|
+
* Skips options that were explicitly provided via CLI flags.
|
|
9
|
+
*
|
|
10
|
+
* @param {import('#types').DiscoveredLayer[]} selectedLayers
|
|
11
|
+
* @param {import('#utils/project.js').Project} [existing] the project being updated, for `detect`
|
|
12
|
+
* @returns {Promise<Record<string, Record<string, any>>>}
|
|
13
|
+
*/
|
|
14
|
+
export async function askLayerOptions(selectedLayers, existing) {
|
|
15
|
+
const cliOptions = parseLayerOptionsFromParsedArgs(selectedLayers);
|
|
16
|
+
/** @type {Record<string, Record<string, any>>} */
|
|
17
|
+
const result = {};
|
|
18
|
+
|
|
19
|
+
for (const layer of selectedLayers) {
|
|
20
|
+
if (!layer.options) continue;
|
|
21
|
+
|
|
22
|
+
const layerResult = (result[layer.name] ??= {});
|
|
23
|
+
|
|
24
|
+
for (const [key, schema] of Object.entries(layer.options)) {
|
|
25
|
+
const fromCli = cliOptions[layer.name]?.[key];
|
|
26
|
+
|
|
27
|
+
if (fromCli !== undefined) {
|
|
28
|
+
layerResult[key] = fromCli;
|
|
29
|
+
printArgInUse(`${layer.name}.${key}`, String(fromCli));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const message = `${styleText("magentaBright", layer.name)}: ${schema.prompt}`;
|
|
34
|
+
const detected = existing && schema.detect ? await schema.detect(existing) : undefined;
|
|
35
|
+
|
|
36
|
+
layerResult[key] = await ask(message, schema, detected ?? schema.default);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} message
|
|
45
|
+
* @param {import('#types').LayerOptionSchema} schema
|
|
46
|
+
* @param {any} initial
|
|
47
|
+
* @returns {Promise<unknown>}
|
|
48
|
+
*/
|
|
49
|
+
async function ask(message, schema, initial) {
|
|
50
|
+
switch (schema.type) {
|
|
51
|
+
case "text":
|
|
52
|
+
case "number": {
|
|
53
|
+
const fallback = initial === undefined ? undefined : String(initial);
|
|
54
|
+
|
|
55
|
+
const answer = await p.text({
|
|
56
|
+
message,
|
|
57
|
+
placeholder: fallback,
|
|
58
|
+
defaultValue: fallback,
|
|
59
|
+
/**
|
|
60
|
+
* clack swaps in the defaultValue after validation,
|
|
61
|
+
* so an empty answer is checked as the default.
|
|
62
|
+
*/
|
|
63
|
+
validate: (input) => {
|
|
64
|
+
const result = validateOption(schema, input || fallback || "");
|
|
65
|
+
|
|
66
|
+
return result.ok ? undefined : result.error;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// converts "number" answers, which clack returns as strings
|
|
71
|
+
const result = validateOption(schema, exitIfCancelled(answer));
|
|
72
|
+
|
|
73
|
+
if (!result.ok) throw new Error(result.error);
|
|
74
|
+
|
|
75
|
+
return result.value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case "confirm":
|
|
79
|
+
return exitIfCancelled(
|
|
80
|
+
await p.confirm({
|
|
81
|
+
message,
|
|
82
|
+
initialValue: initial ?? true,
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
case "select":
|
|
87
|
+
return exitIfCancelled(
|
|
88
|
+
await p.select({
|
|
89
|
+
message,
|
|
90
|
+
options: schema.options ?? [],
|
|
91
|
+
initialValue: initial,
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
case "multiselect": {
|
|
96
|
+
// clack's multiselect has no validate, so ask again until the selection passes
|
|
97
|
+
while (true) {
|
|
98
|
+
const answer = await p.multiselect({
|
|
99
|
+
message,
|
|
100
|
+
options: schema.options ?? [],
|
|
101
|
+
initialValues: initial ?? [],
|
|
102
|
+
required: false,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const result = validateOption(schema, exitIfCancelled(answer));
|
|
106
|
+
|
|
107
|
+
if (result.ok) return result.value;
|
|
108
|
+
|
|
109
|
+
p.log.error(result.error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
default:
|
|
114
|
+
throw new Error(`Unknown option type '${String(schema.type)}'`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @template T
|
|
120
|
+
* @param {T | symbol} answer
|
|
121
|
+
* @returns {T}
|
|
122
|
+
*/
|
|
123
|
+
function exitIfCancelled(answer) {
|
|
124
|
+
if (p.isCancel(answer)) {
|
|
125
|
+
p.cancel("Operation cancelled");
|
|
126
|
+
return process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return answer;
|
|
130
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { runValidation } from "@clack/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('#types').LayerOptionSchema} LayerOptionSchema
|
|
5
|
+
*
|
|
6
|
+
* @typedef {{ ok: true, value: any }} ValidationSuccess
|
|
7
|
+
* @typedef {{ ok: false, error: string }} ValidationFailure
|
|
8
|
+
* @typedef {ValidationSuccess | ValidationFailure} ValidationResult
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert a value from a CLI flag or a prompt to the option's type,
|
|
13
|
+
* then run the option's own `validate`.
|
|
14
|
+
*
|
|
15
|
+
* Defaults are not applied here.
|
|
16
|
+
* See `Project#getLayerOptions`.
|
|
17
|
+
*
|
|
18
|
+
* @param {LayerOptionSchema} schema
|
|
19
|
+
* @param {unknown} rawValue
|
|
20
|
+
* @returns {ValidationResult}
|
|
21
|
+
*/
|
|
22
|
+
export function validateOption(schema, rawValue) {
|
|
23
|
+
const converted = convert(schema, rawValue);
|
|
24
|
+
|
|
25
|
+
if (!converted.ok || !schema.validate) return converted;
|
|
26
|
+
|
|
27
|
+
const error = runValidation(schema.validate, converted.value);
|
|
28
|
+
|
|
29
|
+
if (!error) return converted;
|
|
30
|
+
|
|
31
|
+
return { ok: false, error: error instanceof Error ? error.message : error };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {LayerOptionSchema} schema
|
|
36
|
+
* @param {unknown} rawValue
|
|
37
|
+
* @returns {ValidationResult}
|
|
38
|
+
*/
|
|
39
|
+
function convert(schema, rawValue) {
|
|
40
|
+
switch (schema.type) {
|
|
41
|
+
case "text":
|
|
42
|
+
return { ok: true, value: String(rawValue) };
|
|
43
|
+
|
|
44
|
+
case "number": {
|
|
45
|
+
const text = String(rawValue).trim();
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Number() also accepts "", "0x10", and "1e3",
|
|
49
|
+
* which nobody means when they type a count.
|
|
50
|
+
*/
|
|
51
|
+
if (!/^-?\d+(\.\d+)?$/.test(text)) {
|
|
52
|
+
return { ok: false, error: `'${text}' is not a number` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { ok: true, value: Number(text) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case "confirm":
|
|
59
|
+
return typeof rawValue === "boolean"
|
|
60
|
+
? { ok: true, value: rawValue }
|
|
61
|
+
: { ok: false, error: `'${String(rawValue)}' is not true or false` };
|
|
62
|
+
|
|
63
|
+
case "select": {
|
|
64
|
+
const allowed = choicesOf(schema);
|
|
65
|
+
|
|
66
|
+
return typeof rawValue === "string" && allowed.includes(rawValue)
|
|
67
|
+
? { ok: true, value: rawValue }
|
|
68
|
+
: { ok: false, error: notAChoice(rawValue, allowed) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case "multiselect": {
|
|
72
|
+
const allowed = choicesOf(schema);
|
|
73
|
+
|
|
74
|
+
if (!Array.isArray(rawValue)) {
|
|
75
|
+
return { ok: false, error: `'${String(rawValue)}' is not a list` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const item of rawValue) {
|
|
79
|
+
if (!allowed.includes(item)) {
|
|
80
|
+
return { ok: false, error: notAChoice(item, allowed) };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { ok: true, value: rawValue };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
default:
|
|
88
|
+
return { ok: false, error: `Unknown option type '${String(schema.type)}'` };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {LayerOptionSchema} schema
|
|
94
|
+
* @returns {string[]}
|
|
95
|
+
*/
|
|
96
|
+
function choicesOf(schema) {
|
|
97
|
+
return (schema.options ?? []).map((choice) => choice.value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {unknown} value
|
|
102
|
+
* @param {string[]} allowed
|
|
103
|
+
*/
|
|
104
|
+
function notAChoice(value, allowed) {
|
|
105
|
+
return `Invalid option '${String(value)}'. Must be one of: ${allowed.join(", ")}`;
|
|
106
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { beforeAll, describe, it, expect as hardExpect, afterAll } from "vitest";
|
|
2
|
+
import { expectIsSetup, generate, layers, reapply } from "#test-helpers";
|
|
3
|
+
import { packageJson } from "ember-apply";
|
|
4
|
+
|
|
5
|
+
import type { Project } from "ember.nvp";
|
|
6
|
+
import { rm } from "node:fs/promises";
|
|
7
|
+
|
|
8
|
+
const expect = hardExpect.soft;
|
|
9
|
+
|
|
10
|
+
let layer = layers.find((layer) => layer.name === "eslint-bundled")!;
|
|
11
|
+
|
|
12
|
+
const CONFIG_PACKAGES = {
|
|
13
|
+
ember: "ember-eslint",
|
|
14
|
+
nvp: "@nullvoxpopuli/eslint-configs",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe.each(Object.entries(CONFIG_PACKAGES))("preset: %s", (preset, configPackage) => {
|
|
18
|
+
const options = { "eslint-bundled": { preset } };
|
|
19
|
+
|
|
20
|
+
describe("starting without eslint-bundled", () => {
|
|
21
|
+
let project: Project;
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
project = await generate({
|
|
25
|
+
type: "app",
|
|
26
|
+
packageManager: "pnpm",
|
|
27
|
+
layers: ["git"],
|
|
28
|
+
options,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterAll(async () => {
|
|
33
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("applying eslint-bundled", async () => {
|
|
37
|
+
await reapply(project, ["eslint-bundled"]);
|
|
38
|
+
|
|
39
|
+
await expectIsSetup(project, layer);
|
|
40
|
+
expect(await project.gitHasDiff()).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("starting with eslint-bundled", () => {
|
|
45
|
+
let project: Project;
|
|
46
|
+
|
|
47
|
+
beforeAll(async () => {
|
|
48
|
+
project = await generate({
|
|
49
|
+
type: "app",
|
|
50
|
+
packageManager: "pnpm",
|
|
51
|
+
layers: ["eslint-bundled", "git"],
|
|
52
|
+
options,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterAll(async () => {
|
|
57
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("installs only this preset's config package", async () => {
|
|
61
|
+
let manifest = await packageJson.read(project.directory);
|
|
62
|
+
let installed = Object.values(CONFIG_PACKAGES).filter(
|
|
63
|
+
(name) => manifest.devDependencies?.[name],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(installed).toEqual([configPackage]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("detects the preset", async () => {
|
|
70
|
+
expect(await layer.options?.preset?.detect?.(project)).toBe(preset);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("reapplying eslint-bundled causes no changes", async () => {
|
|
74
|
+
await reapply(project, ["eslint-bundled"]);
|
|
75
|
+
|
|
76
|
+
await expectIsSetup(project, layer);
|
|
77
|
+
expect(await project.gitHasDiff()).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("package.json scripts", () => {
|
|
82
|
+
let project: Project;
|
|
83
|
+
|
|
84
|
+
beforeAll(async () => {
|
|
85
|
+
project = await generate({
|
|
86
|
+
type: "app",
|
|
87
|
+
packageManager: "pnpm",
|
|
88
|
+
layers: ["eslint-bundled", "git"],
|
|
89
|
+
options,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
let { exitCode } = await project.run("pnpm install");
|
|
93
|
+
|
|
94
|
+
hardExpect(exitCode).toBe(0);
|
|
95
|
+
|
|
96
|
+
await expectIsSetup(project, layer);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
afterAll(async () => {
|
|
100
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("linting and fixing works", async () => {
|
|
104
|
+
{
|
|
105
|
+
let { exitCode } = await project.run("pnpm lint:eslint --fix");
|
|
106
|
+
|
|
107
|
+
expect(exitCode).toBe(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
let { exitCode } = await project.run("pnpm lint:eslint");
|
|
112
|
+
|
|
113
|
+
expect(exitCode).toBe(0);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe.each([
|
|
120
|
+
["ember", "nvp"],
|
|
121
|
+
["nvp", "ember"],
|
|
122
|
+
] as const)("switching presets: %s to %s", (from, to) => {
|
|
123
|
+
let project: Project;
|
|
124
|
+
|
|
125
|
+
beforeAll(async () => {
|
|
126
|
+
let before = await generate({
|
|
127
|
+
type: "app",
|
|
128
|
+
packageManager: "pnpm",
|
|
129
|
+
layers: ["eslint-bundled", "git"],
|
|
130
|
+
options: { "eslint-bundled": { preset: from } },
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
project = await generate({
|
|
134
|
+
directory: before.directory,
|
|
135
|
+
type: "app",
|
|
136
|
+
packageManager: "pnpm",
|
|
137
|
+
layers: ["eslint-bundled", "git"],
|
|
138
|
+
options: { "eslint-bundled": { preset: to } },
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
afterAll(async () => {
|
|
143
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("replaces the config package and the config", async () => {
|
|
147
|
+
let manifest = await packageJson.read(project.directory);
|
|
148
|
+
|
|
149
|
+
expect(manifest.devDependencies).toHaveProperty(CONFIG_PACKAGES[to]);
|
|
150
|
+
expect(manifest.devDependencies).not.toHaveProperty(CONFIG_PACKAGES[from]);
|
|
151
|
+
expect(await project.read("eslint.config.js")).toContain(CONFIG_PACKAGES[to]);
|
|
152
|
+
expect(await project.read("eslint.config.js")).not.toContain(CONFIG_PACKAGES[from]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("is setup, and detects the new preset", async () => {
|
|
156
|
+
await expectIsSetup(project, layer);
|
|
157
|
+
expect(await layer.options?.preset?.detect?.(project)).toBe(to);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("reapplying causes no changes", async () => {
|
|
161
|
+
await reapply(project, ["eslint-bundled", "git"]);
|
|
162
|
+
|
|
163
|
+
expect(await project.gitHasDiff()).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("linting and fixing works", async () => {
|
|
167
|
+
let install = await project.run("pnpm install");
|
|
168
|
+
hardExpect(install.exitCode).toBe(0);
|
|
169
|
+
|
|
170
|
+
{
|
|
171
|
+
let { exitCode } = await project.run("pnpm lint:eslint --fix");
|
|
172
|
+
|
|
173
|
+
expect(exitCode).toBe(0);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
{
|
|
177
|
+
let { exitCode } = await project.run("pnpm lint:eslint");
|
|
178
|
+
|
|
179
|
+
expect(exitCode).toBe(0);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
});
|