ember.nvp 1.13.0 → 1.15.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 +3 -3
- package/CHANGELOG.md +22 -0
- package/README.md +28 -0
- package/package.json +2 -1
- package/src/cli/generator.js +10 -0
- package/src/cli/index.js +19 -2
- package/src/cli/questions/layers.js +3 -2
- package/src/cli/questions/migration.js +69 -0
- package/src/index.js +1 -0
- package/src/layers/qunit/index.js +55 -1
- package/src/layers/qunit/library-ts-files/config/tsconfig.publish.json +8 -0
- package/src/migration-from/addons.js +154 -0
- package/src/migration-from/app.js +1117 -0
- package/src/migration-from/at-ember/addon-blueprint/index.js +515 -0
- package/src/migration-from/at-ember/addon-blueprint/rollup.js +188 -0
- package/src/migration-from/at-ember/app-blueprint/index.js +60 -0
- package/src/migration-from/at-ember-tooling/classic-build-addon-blueprint/index.js +601 -0
- package/src/migration-from/at-ember-tooling/classic-build-app-blueprint/index.js +118 -0
- package/src/migration-from/babel.js +207 -0
- package/src/migration-from/ember-cli-build.js +238 -0
- package/src/migration-from/files.js +120 -0
- package/src/migration-from/findings.js +69 -0
- package/src/migration-from/index.js +81 -0
- package/src/migration-from/javascript.js +217 -0
- package/src/migration-from/layers.js +170 -0
- package/src/migration-from/library.js +311 -0
- package/src/utils/types.ts +69 -0
- package/test/ember.nvp/layer-qunit.test.ts +53 -6
- package/test/ember.nvp/migration-from-fixtures.ts +506 -0
- package/test/ember.nvp/migration-from.test.ts +475 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { packageJson } from "ember-apply";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import semver from "semver";
|
|
6
|
+
import { getLatest } from "#utils/npm.js";
|
|
7
|
+
import { readJSON } from "#utils/fs.js";
|
|
8
|
+
import { listFiles, move } from "./files.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What the ember.nvp library base builds with.
|
|
12
|
+
* Read from the base, so that a migrated library matches a generated one.
|
|
13
|
+
*/
|
|
14
|
+
const BASE_MANIFEST = readJSON(
|
|
15
|
+
join(import.meta.dirname, "../bases/minimal-library/files/package.json"),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} root
|
|
20
|
+
* @param {string} srcDir
|
|
21
|
+
* @returns {boolean} whether the source has or imports CSS
|
|
22
|
+
*/
|
|
23
|
+
export function usesCss(root, srcDir) {
|
|
24
|
+
let files = listFiles(root, srcDir);
|
|
25
|
+
|
|
26
|
+
if (files.some((file) => file.endsWith(".css"))) return true;
|
|
27
|
+
|
|
28
|
+
return files.some(
|
|
29
|
+
(file) =>
|
|
30
|
+
/\.(js|ts|gjs|gts)$/.test(file) &&
|
|
31
|
+
/import\s+["'][^"']+\.css["']/.test(readFileSync(join(root, file), "utf-8")),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Source extensions that build to `.js`
|
|
37
|
+
*/
|
|
38
|
+
const SOURCE_EXTENSIONS = "{js,ts,gjs,gts}";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Entries in tsdown's glob syntax.
|
|
42
|
+
*
|
|
43
|
+
* `@embroider/addon-dev` matches `publicEntrypoints` against the built `.js` files.
|
|
44
|
+
* tsdown matches `entry` against the source files, which have any source extension.
|
|
45
|
+
*
|
|
46
|
+
* **\/*.js → ./src/**\/*.{js,ts,gjs,gts}
|
|
47
|
+
* index.js → ./src/index.{js,ts,gjs,gts}
|
|
48
|
+
*
|
|
49
|
+
* Declarations and co-located tests are never entries.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} srcDir
|
|
52
|
+
* @param {string[]} publicEntrypoints globs relative to `srcDir`, for built files
|
|
53
|
+
* @returns {string[]}
|
|
54
|
+
*/
|
|
55
|
+
export function entriesFor(srcDir, publicEntrypoints) {
|
|
56
|
+
/** @type {string[]} */
|
|
57
|
+
let entries = [];
|
|
58
|
+
|
|
59
|
+
let everything = publicEntrypoints.some((glob) => glob === "**/*.js" || glob === "**/*");
|
|
60
|
+
let globs = everything ? ["**/*.js"] : publicEntrypoints;
|
|
61
|
+
|
|
62
|
+
for (let glob of globs) {
|
|
63
|
+
if (!glob.endsWith(".js") && !glob.endsWith("*")) continue;
|
|
64
|
+
|
|
65
|
+
let source = glob.endsWith(".js")
|
|
66
|
+
? glob.slice(0, -".js".length) + `.${SOURCE_EXTENSIONS}`
|
|
67
|
+
: glob;
|
|
68
|
+
|
|
69
|
+
entries.push(`./${srcDir}/${source}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
entries.push(`!./${srcDir}/**/*.d.ts`, `!./${srcDir}/**/*-test.${SOURCE_EXTENSIONS}`);
|
|
73
|
+
|
|
74
|
+
return entries;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @typedef {object} TsdownSetup
|
|
79
|
+
* @property {string[]} entry
|
|
80
|
+
* @property {string[] | undefined} appReexports globs for built files, as `@embroider/addon-dev` takes them
|
|
81
|
+
* @property {boolean} css whether the source imports CSS
|
|
82
|
+
* @property {boolean} typescript
|
|
83
|
+
* @property {string | undefined} outDir when it is not `dist`
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The same shape as the library base's tsdown.config.js,
|
|
88
|
+
* so that layers can add properties to it.
|
|
89
|
+
*
|
|
90
|
+
* @param {TsdownSetup} setup
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
export function tsdownConfig({ entry, appReexports, css, typescript, outDir }) {
|
|
94
|
+
let lines = [
|
|
95
|
+
`import { defineConfig } from "tsdown";`,
|
|
96
|
+
`import { ember } from "@nullvoxpopuli/ember-rolldown";`,
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
if (appReexports) {
|
|
100
|
+
lines.push(`import { appReexports } from "@nullvoxpopuli/ember-rolldown/app-reexports";`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
lines.push("", "export default defineConfig({", ` entry: ${JSON.stringify(entry)},`);
|
|
104
|
+
|
|
105
|
+
if (outDir) {
|
|
106
|
+
lines.push(` outDir: ${JSON.stringify(outDir)},`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!typescript) {
|
|
110
|
+
lines.push(" dts: false,");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (css) {
|
|
114
|
+
// keeps `import "./x.css"` in the output, so that apps load the styles
|
|
115
|
+
lines.push(" css: { inject: true },");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let plugins = appReexports ? `ember(), appReexports(${JSON.stringify(appReexports)})` : "ember()";
|
|
119
|
+
|
|
120
|
+
lines.push(` plugins: [${plugins}],`, "});", "");
|
|
121
|
+
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Builds with tsdown instead of the old blueprint's build:
|
|
127
|
+
* - writes tsdown.config.js
|
|
128
|
+
* - points the build scripts at tsdown
|
|
129
|
+
* - adds the base's build dependencies
|
|
130
|
+
* - publishes declarations from `dist`, where tsdown writes them
|
|
131
|
+
*
|
|
132
|
+
* @param {import('#utils/project.js').Project} project
|
|
133
|
+
* @param {TsdownSetup} setup
|
|
134
|
+
*/
|
|
135
|
+
export async function buildWithTsdown(project, setup) {
|
|
136
|
+
await writeFile(project.path("tsdown.config.js"), tsdownConfig(setup));
|
|
137
|
+
|
|
138
|
+
await packageJson.modify((json) => {
|
|
139
|
+
json.type = "module";
|
|
140
|
+
json.scripts ||= {};
|
|
141
|
+
json.scripts.build = BASE_MANIFEST.scripts.build;
|
|
142
|
+
json.scripts.prepack = BASE_MANIFEST.scripts.prepack;
|
|
143
|
+
json.scripts.start = BASE_MANIFEST.scripts.start;
|
|
144
|
+
|
|
145
|
+
if (json.exports) json.exports = fromDeclarationsToDist(json.exports);
|
|
146
|
+
if (json.typesVersions) json.typesVersions = fromDeclarationsToDist(json.typesVersions);
|
|
147
|
+
if (typeof json.types === "string") json.types = fromDeclarationsToDist(json.types);
|
|
148
|
+
|
|
149
|
+
if (Array.isArray(json.files)) {
|
|
150
|
+
json.files = json.files.filter(
|
|
151
|
+
/** @param {string} file */ (file) => file !== "declarations" && file !== "declarations/",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}, project.directory);
|
|
155
|
+
|
|
156
|
+
let base = BASE_MANIFEST.devDependencies;
|
|
157
|
+
/** @type {Record<string, string>} */
|
|
158
|
+
let wanted = {
|
|
159
|
+
tsdown: base.tsdown,
|
|
160
|
+
"@nullvoxpopuli/ember-rolldown": base["@nullvoxpopuli/ember-rolldown"],
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if (setup.typescript) {
|
|
164
|
+
wanted["@ember/library-tsconfig"] = base["@ember/library-tsconfig"];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (setup.css) {
|
|
168
|
+
// released in lockstep with tsdown
|
|
169
|
+
wanted["@tsdown/css"] = base.tsdown;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await packageJson.addDevDependencies(await getLatest(wanted), project.directory);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The qunit layer's test app boots with the strict resolver of ember-source 7.2.
|
|
177
|
+
*
|
|
178
|
+
* A library's own ember-source is a devDependency, for its tests.
|
|
179
|
+
* Moving it forward changes nothing for the apps that use the library.
|
|
180
|
+
*
|
|
181
|
+
* @param {import('#utils/project.js').Project} project
|
|
182
|
+
*/
|
|
183
|
+
export async function useEmberSourceForTests(project) {
|
|
184
|
+
if (!project.wantsLayer("qunit")) return;
|
|
185
|
+
|
|
186
|
+
let manifest = await packageJson.read(project.directory);
|
|
187
|
+
let range = manifest.devDependencies?.["ember-source"];
|
|
188
|
+
let wanted = BASE_MANIFEST.devDependencies["ember-source"];
|
|
189
|
+
|
|
190
|
+
if (range && !semver.validRange(range)) return;
|
|
191
|
+
|
|
192
|
+
let current = range ? semver.minVersion(range) : undefined;
|
|
193
|
+
let minimum = /** @type {semver.SemVer} */ (semver.minVersion(wanted));
|
|
194
|
+
|
|
195
|
+
if (current && semver.gte(current, minimum)) return;
|
|
196
|
+
|
|
197
|
+
await packageJson.addDevDependencies(
|
|
198
|
+
await getLatest({ "ember-source": wanted }),
|
|
199
|
+
project.directory,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @param {unknown} value a package.json field
|
|
205
|
+
* @returns {any} the same field, with `declarations/` paths moved to `dist/`
|
|
206
|
+
*/
|
|
207
|
+
function fromDeclarationsToDist(value) {
|
|
208
|
+
if (typeof value === "string") {
|
|
209
|
+
return value.replace(/^(\.\/)?declarations\//, "$1dist/");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (Array.isArray(value)) {
|
|
213
|
+
return value.map(fromDeclarationsToDist);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (value && typeof value === "object") {
|
|
217
|
+
/** @type {Record<string, unknown>} */
|
|
218
|
+
let result = {};
|
|
219
|
+
|
|
220
|
+
for (let [key, inner] of Object.entries(value)) {
|
|
221
|
+
result[key] = fromDeclarationsToDist(inner);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Config files in the project root and in `config/` that Node must load as CommonJS.
|
|
232
|
+
* Only files that ember.nvp does not remove are listed.
|
|
233
|
+
*/
|
|
234
|
+
const COMMONJS_CANDIDATES = [
|
|
235
|
+
".eslintrc.js",
|
|
236
|
+
".prettierrc.js",
|
|
237
|
+
".stylelintrc.js",
|
|
238
|
+
".template-lintrc.js",
|
|
239
|
+
"addon-main.js",
|
|
240
|
+
"babel.config.js",
|
|
241
|
+
"babel.publish.config.js",
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* ember.nvp projects are `"type": "module"`,
|
|
246
|
+
* so a `.js` file that uses `module.exports` must become `.cjs`.
|
|
247
|
+
*
|
|
248
|
+
* References in package.json follow the rename.
|
|
249
|
+
*
|
|
250
|
+
* @param {import('#utils/project.js').Project} project
|
|
251
|
+
* @returns {Promise<string[]>} the renamed files
|
|
252
|
+
*/
|
|
253
|
+
export async function renameCommonJSFiles(project) {
|
|
254
|
+
/** @type {string[]} */
|
|
255
|
+
let renamed = [];
|
|
256
|
+
|
|
257
|
+
for (let file of COMMONJS_CANDIDATES) {
|
|
258
|
+
let path = project.path(file);
|
|
259
|
+
|
|
260
|
+
if (!existsSync(path)) continue;
|
|
261
|
+
|
|
262
|
+
let source = readFileSync(path, "utf-8");
|
|
263
|
+
|
|
264
|
+
if (!/\bmodule\.exports\b|\brequire\(/.test(source)) continue;
|
|
265
|
+
|
|
266
|
+
let to = file.replace(/\.js$/, ".cjs");
|
|
267
|
+
|
|
268
|
+
await move(project, file, to);
|
|
269
|
+
renamed.push(file);
|
|
270
|
+
|
|
271
|
+
await packageJson.modify((json) => {
|
|
272
|
+
json.exports &&= renameIn(json.exports, file, to);
|
|
273
|
+
json.files &&= renameIn(json.files, file, to);
|
|
274
|
+
|
|
275
|
+
if (json["ember-addon"]?.main) {
|
|
276
|
+
json["ember-addon"].main = renameIn(json["ember-addon"].main, file, to);
|
|
277
|
+
}
|
|
278
|
+
}, project.directory);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return renamed;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @param {unknown} value
|
|
286
|
+
* @param {string} from
|
|
287
|
+
* @param {string} to
|
|
288
|
+
* @returns {any}
|
|
289
|
+
*/
|
|
290
|
+
function renameIn(value, from, to) {
|
|
291
|
+
if (typeof value === "string") {
|
|
292
|
+
return value === from || value === `./${from}` ? value.replace(from, to) : value;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (Array.isArray(value)) {
|
|
296
|
+
return value.map((inner) => renameIn(inner, from, to));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (value && typeof value === "object") {
|
|
300
|
+
/** @type {Record<string, unknown>} */
|
|
301
|
+
let result = {};
|
|
302
|
+
|
|
303
|
+
for (let [key, inner] of Object.entries(value)) {
|
|
304
|
+
result[key] = renameIn(inner, from, to);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return value;
|
|
311
|
+
}
|
package/src/utils/types.ts
CHANGED
|
@@ -101,6 +101,75 @@ export interface Base {
|
|
|
101
101
|
run: (project: Project) => Promise<void>;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Something a migration found, for the user to act on.
|
|
106
|
+
*/
|
|
107
|
+
export interface Finding {
|
|
108
|
+
/**
|
|
109
|
+
* What was found, as a short noun phrase
|
|
110
|
+
*/
|
|
111
|
+
title: string;
|
|
112
|
+
/**
|
|
113
|
+
* The files, packages, or options where it was found
|
|
114
|
+
*/
|
|
115
|
+
where?: string[];
|
|
116
|
+
/**
|
|
117
|
+
* What to do about it
|
|
118
|
+
*/
|
|
119
|
+
action: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface MigrationReport {
|
|
123
|
+
/**
|
|
124
|
+
* Features that ember.nvp does not support.
|
|
125
|
+
* Any of these stops the migration, before anything is written.
|
|
126
|
+
*/
|
|
127
|
+
unsupported: Finding[];
|
|
128
|
+
/**
|
|
129
|
+
* Work for the user after the migration
|
|
130
|
+
*/
|
|
131
|
+
todo: Finding[];
|
|
132
|
+
/**
|
|
133
|
+
* Layers that replace tooling from the old blueprint.
|
|
134
|
+
* The CLI selects them by default.
|
|
135
|
+
*/
|
|
136
|
+
layers: string[];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Moves a project from an older blueprint to an ember.nvp base.
|
|
141
|
+
*/
|
|
142
|
+
export interface Migration {
|
|
143
|
+
/**
|
|
144
|
+
* The blueprint's package name
|
|
145
|
+
*/
|
|
146
|
+
label: string;
|
|
147
|
+
/**
|
|
148
|
+
* The project type after the migration
|
|
149
|
+
*/
|
|
150
|
+
type: ProjectType;
|
|
151
|
+
/**
|
|
152
|
+
* Whether the directory holds a project from this blueprint
|
|
153
|
+
*/
|
|
154
|
+
detect: (directory: string) => boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Reads the project and changes nothing.
|
|
157
|
+
*
|
|
158
|
+
* Files are read from `project.directory`.
|
|
159
|
+
* Installed dependencies are read from `project.desires.path`,
|
|
160
|
+
* because a stage has no node_modules.
|
|
161
|
+
*/
|
|
162
|
+
check: (project: Project) => Promise<MigrationReport>;
|
|
163
|
+
/**
|
|
164
|
+
* Removes or rewrites the old blueprint's files,
|
|
165
|
+
* so that the base and the layers can apply their own.
|
|
166
|
+
*
|
|
167
|
+
* Runs before the base, and only when `check` found nothing unsupported.
|
|
168
|
+
* Adds to `report.todo` what depends on the selected layers.
|
|
169
|
+
*/
|
|
170
|
+
run: (project: Project, report: MigrationReport) => Promise<void>;
|
|
171
|
+
}
|
|
172
|
+
|
|
104
173
|
export interface DiscoveredLayer extends Layer {
|
|
105
174
|
/**
|
|
106
175
|
* The unique name of the layer.
|
|
@@ -161,6 +161,7 @@ describe("layer: qunit", () => {
|
|
|
161
161
|
"README.md",
|
|
162
162
|
"config/test/babel.config.js",
|
|
163
163
|
"config/test/testem.cjs",
|
|
164
|
+
"config/tsconfig.publish.json",
|
|
164
165
|
"package.json",
|
|
165
166
|
"src/index.ts",
|
|
166
167
|
"tests/rendering/.gitkeep",
|
|
@@ -258,15 +259,38 @@ describe("layer: qunit", () => {
|
|
|
258
259
|
`);
|
|
259
260
|
});
|
|
260
261
|
|
|
261
|
-
it("
|
|
262
|
+
it("builds with a publish tsconfig, so tsconfig.json can cover the tests", async () => {
|
|
262
263
|
expect(await project.read("tsdown.config.js")).toMatchInlineSnapshot(`
|
|
263
264
|
"import { defineConfig } from "tsdown";
|
|
264
265
|
import { ember } from "@nullvoxpopuli/ember-rolldown";
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
export default defineConfig({ entry: ["./src/index.ts"], plugins: [ember()], tsconfig: "./config/tsconfig.publish.json" })"
|
|
267
|
+
`);
|
|
268
|
+
expect(await project.read("config/tsconfig.publish.json")).toMatchInlineSnapshot(`
|
|
269
|
+
"{
|
|
270
|
+
"extends": "../tsconfig.json",
|
|
271
|
+
"include": ["../src"],
|
|
272
|
+
"compilerOptions": {
|
|
273
|
+
"rootDir": "../src",
|
|
274
|
+
"isolatedDeclarations": true
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
"
|
|
278
|
+
`);
|
|
279
|
+
expect(await project.read("tsconfig.json")).toMatchInlineSnapshot(`
|
|
280
|
+
"{
|
|
281
|
+
"extends": "@ember/library-tsconfig",
|
|
282
|
+
"include": ["src", "tests"],
|
|
283
|
+
"compilerOptions": {
|
|
284
|
+
"lib": ["esnext", "dom", "dom.iterable"],
|
|
285
|
+
"types": ["ember-source/types", "@glint/ember-tsc/types"],
|
|
286
|
+
},
|
|
287
|
+
"contentMappers": [
|
|
288
|
+
{
|
|
289
|
+
"package": "ember-content-mapper",
|
|
290
|
+
"extensions": [".gts", ".gjs"],
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
}
|
|
270
294
|
"
|
|
271
295
|
`);
|
|
272
296
|
});
|
|
@@ -292,6 +316,29 @@ describe("layer: qunit", () => {
|
|
|
292
316
|
});
|
|
293
317
|
});
|
|
294
318
|
|
|
319
|
+
describe("library (TypeScript) with eslint", () => {
|
|
320
|
+
it("lints the tests", { timeout: 300_000 }, async () => {
|
|
321
|
+
const project = await generate({
|
|
322
|
+
type: "library",
|
|
323
|
+
name: "my-lib",
|
|
324
|
+
layers: ["eslint-bundled", "qunit", "typescript"],
|
|
325
|
+
});
|
|
326
|
+
dirs.push(project.directory);
|
|
327
|
+
|
|
328
|
+
let install = await execa("pnpm install", { cwd: project.directory, shell: true });
|
|
329
|
+
expect(install.exitCode).toBe(0);
|
|
330
|
+
|
|
331
|
+
// eslint's project service cannot parse a file that no tsconfig includes
|
|
332
|
+
let lint = await execa("pnpm lint:eslint", {
|
|
333
|
+
cwd: project.directory,
|
|
334
|
+
shell: true,
|
|
335
|
+
reject: false,
|
|
336
|
+
all: true,
|
|
337
|
+
});
|
|
338
|
+
expect(lint.exitCode, lint.all).toBe(0);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
295
342
|
describe("library (JavaScript)", () => {
|
|
296
343
|
let project: Project;
|
|
297
344
|
|