@isentinel/eslint-config 6.0.0-beta.2 → 6.0.0-beta.21

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/dist/cli.mjs CHANGED
@@ -1 +1,720 @@
1
+ import { cancel, confirm, group, intro, log, multiselect, note, outro } from "@clack/prompts";
2
+ import ansis from "ansis";
3
+ import process from "node:process";
4
+ import yargs from "yargs";
5
+ import { hideBin } from "yargs/helpers";
6
+ import fs from "node:fs";
7
+ import fsp from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { existsSync, readFileSync } from "fs";
10
+ import { execSync } from "node:child_process";
11
+ //#region package.json
12
+ var version = "6.0.0-beta.21";
13
+ var package_default = {
14
+ name: "@isentinel/eslint-config",
15
+ version,
16
+ description: "iSentinel's ESLint config",
17
+ keywords: [
18
+ "eslint-config",
19
+ "roblox",
20
+ "roblox-ts",
21
+ "rbxts"
22
+ ],
23
+ homepage: "https://github.com/christopher-buss/eslint-config",
24
+ bugs: { "url": "https://github.com/christopher-buss/eslint-config/issues" },
25
+ repository: {
26
+ "type": "git",
27
+ "url": "git+https://github.com/christopher-buss/eslint-config.git"
28
+ },
29
+ license: "MIT",
30
+ author: "Christopher Buss <christopher.buss@pm.me> (https://github.com/christopher-buss)",
31
+ contributors: [{
32
+ "name": "Anthony Fu",
33
+ "email": "anthonyfu117@hotmail.com",
34
+ "url": "https://github.com/antfu"
35
+ }],
36
+ sideEffects: false,
37
+ type: "module",
38
+ exports: {
39
+ ".": "./dist/index.mjs",
40
+ "./cli": "./dist/cli.mjs",
41
+ "./eslint": {
42
+ "types": "./dist/index.d.mts",
43
+ "default": "./dist/index.mjs"
44
+ },
45
+ "./formatter-agents": "./dist/formatter-agents.mjs",
46
+ "./oxlint": {
47
+ "types": "./dist/oxlint.d.mts",
48
+ "default": "./dist/oxlint.mjs"
49
+ },
50
+ "./package.json": "./package.json"
51
+ },
52
+ main: "./dist/index.mjs",
53
+ module: "./dist/index.mjs",
54
+ types: "./dist/index.d.mts",
55
+ bin: {
56
+ "eslint-config": "./bin/index.js",
57
+ "isentinel-lint": "./bin/lint.js"
58
+ },
59
+ files: ["bin", "dist"],
60
+ scripts: {
61
+ "build": "nr gen && tsdown && node scripts/check-dist.ts",
62
+ "build:inspector": "pnpm build && pnpm dlx @eslint/config-inspector build --config eslint-inspector.config.ts --out-dir ./.eslint-config-inspector",
63
+ "dev": "npx @eslint/config-inspector --config eslint-inspector.config.ts",
64
+ "gen": "node scripts/typegen.ts && node scripts/typegen-oxlint.ts && node scripts/typegen-defaults.ts && node scripts/typegen-defaults-oxlint.ts && node scripts/versiongen.ts",
65
+ "postgen": "echo 'Generation complete!'",
66
+ "lint": "concurrently 'oxlint' 'eslint --cache'",
67
+ "lint:ci": "concurrently 'oxlint' 'eslint --cache --cache-strategy content'",
68
+ "oxlint": "oxlint",
69
+ "prepack": "nr build",
70
+ "release": "bumpp",
71
+ "stub": "tsdown",
72
+ "test": "nr gen && vitest run",
73
+ "test:watch": "vitest",
74
+ "typecheck": "tsc --noEmit",
75
+ "validate:oxlint": "node scripts/validate-oxlint.ts",
76
+ "watch": "tsdown --watch --dts"
77
+ },
78
+ dependencies: {
79
+ "@antfu/install-pkg": "catalog:prod",
80
+ "@clack/prompts": "catalog:prod",
81
+ "@cspell/eslint-plugin": "catalog:prod",
82
+ "@e18e/eslint-plugin": "catalog:prod",
83
+ "@eslint-community/eslint-plugin-eslint-comments": "catalog:prod",
84
+ "@eslint/markdown": "catalog:prod",
85
+ "@isentinel/dict-rbxts": "catalog:prod",
86
+ "@isentinel/dict-roblox": "catalog:prod",
87
+ "@oxlint/plugins": "catalog:prod",
88
+ "@pobammer-ts/small-rules": "catalog:prod",
89
+ "@stylistic/eslint-plugin": "catalog:prod",
90
+ "@typescript-eslint/eslint-plugin": "catalog:prod",
91
+ "@typescript-eslint/parser": "catalog:prod",
92
+ "ansis": "catalog:prod",
93
+ "concurrently": "catalog:prod",
94
+ "eslint-config-flat-gitignore": "catalog:prod",
95
+ "eslint-config-prettier": "catalog:prod",
96
+ "eslint-flat-config-utils": "catalog:prod",
97
+ "eslint-merge-processors": "catalog:prod",
98
+ "eslint-plugin-antfu": "catalog:prod",
99
+ "eslint-plugin-better-max-params": "catalog:prod",
100
+ "eslint-plugin-comment-length": "catalog:prod",
101
+ "eslint-plugin-de-morgan": "catalog:prod",
102
+ "eslint-plugin-flawless": "catalog:prod",
103
+ "eslint-plugin-format-lua": "catalog:prod",
104
+ "eslint-plugin-import-lite": "catalog:prod",
105
+ "eslint-plugin-jsdoc": "catalog:prod",
106
+ "eslint-plugin-jsonc": "catalog:prod",
107
+ "eslint-plugin-oxfmt": "catalog:prod",
108
+ "eslint-plugin-package-json": "catalog:prod",
109
+ "eslint-plugin-perfectionist": "catalog:prod",
110
+ "eslint-plugin-pnpm": "catalog:prod",
111
+ "eslint-plugin-promise": "catalog:prod",
112
+ "eslint-plugin-roblox-ts": "catalog:prod",
113
+ "eslint-plugin-sentinel": "catalog:prod",
114
+ "eslint-plugin-sonarjs": "catalog:prod",
115
+ "eslint-plugin-toml": "catalog:prod",
116
+ "eslint-plugin-unicorn": "catalog:prod",
117
+ "eslint-plugin-unused-imports": "catalog:prod",
118
+ "eslint-plugin-yml": "catalog:prod",
119
+ "file-entry-cache": "catalog:prod",
120
+ "find-up-simple": "catalog:prod",
121
+ "globals": "catalog:prod",
122
+ "jsonc-eslint-parser": "catalog:prod",
123
+ "local-pkg": "catalog:prod",
124
+ "oxfmt": "catalog:prod",
125
+ "oxlint-plugin-eslint": "catalog:prod",
126
+ "oxlint-plugin-oxlint-comments": "catalog:prod",
127
+ "prettier": "catalog:prod",
128
+ "semver": "catalog:prod",
129
+ "toml-eslint-parser": "catalog:prod",
130
+ "yaml-eslint-parser": "catalog:prod",
131
+ "yargs": "catalog:prod"
132
+ },
133
+ devDependencies: {
134
+ "@antfu/ni": "catalog:dev",
135
+ "@eslint-react/shared": "catalog:dev",
136
+ "@eslint/config-inspector": "catalog:dev",
137
+ "@isentinel/eslint-config": "workspace:*",
138
+ "@isentinel/tsconfig": "catalog:dev",
139
+ "@stylistic/eslint-plugin-migrate": "catalog:dev",
140
+ "@total-typescript/shoehorn": "catalog:dev",
141
+ "@types/node": "catalog:dev",
142
+ "@types/picomatch": "catalog:dev",
143
+ "@types/semver": "catalog:dev",
144
+ "@types/yargs": "catalog:dev",
145
+ "@vitest/eslint-plugin": "catalog:peer",
146
+ "bumpp": "catalog:dev",
147
+ "eslint": "catalog:peer",
148
+ "eslint-plugin-erasable-syntax-only": "catalog:peer",
149
+ "eslint-plugin-eslint-plugin": "catalog:peer",
150
+ "eslint-plugin-jest": "catalog:peer",
151
+ "eslint-plugin-jest-extended": "catalog:peer",
152
+ "eslint-plugin-n": "catalog:peer",
153
+ "eslint-plugin-react-jsx": "catalog:peer",
154
+ "eslint-plugin-react-naming-convention": "catalog:peer",
155
+ "eslint-plugin-react-x": "catalog:peer",
156
+ "eslint-typegen": "catalog:dev",
157
+ "jiti": "catalog:dev",
158
+ "oxlint": "catalog:peer",
159
+ "oxlint-tsgolint": "catalog:peer",
160
+ "parse-gitignore-ts": "catalog:dev",
161
+ "picomatch": "catalog:dev",
162
+ "pnpm-workspace-yaml": "catalog:dev",
163
+ "publint": "catalog:dev",
164
+ "tsdown": "catalog:dev",
165
+ "type-fest": "catalog:dev",
166
+ "typescript": "catalog:dev",
167
+ "unplugin-unused": "catalog:dev",
168
+ "vitest": "catalog:dev"
169
+ },
170
+ peerDependencies: {
171
+ "@vitest/eslint-plugin": "^1.6.4",
172
+ "eslint": "^10.4.0",
173
+ "eslint-plugin-eslint-plugin": "^7.5.0",
174
+ "eslint-plugin-jest": "^29.15.0",
175
+ "eslint-plugin-jest-extended": "^3.0.0",
176
+ "eslint-plugin-n": "^17.0.0 || ^18.0.0",
177
+ "eslint-plugin-react-jsx": "^5.10.0",
178
+ "eslint-plugin-react-naming-convention": "^5.10.0",
179
+ "eslint-plugin-react-x": "^5.10.0",
180
+ "oxlint": ">=1.73.0",
181
+ "oxlint-tsgolint": ">=0.24.0"
182
+ },
183
+ peerDependenciesMeta: {
184
+ "@vitest/eslint-plugin": { "optional": true },
185
+ "eslint-plugin-eslint-plugin": { "optional": true },
186
+ "eslint-plugin-jest": { "optional": true },
187
+ "eslint-plugin-jest-extended": { "optional": true },
188
+ "eslint-plugin-n": { "optional": true },
189
+ "eslint-plugin-react-jsx": { "optional": true },
190
+ "eslint-plugin-react-naming-convention": { "optional": true },
191
+ "eslint-plugin-react-x": { "optional": true },
192
+ "oxlint": { "optional": true },
193
+ "oxlint-tsgolint": { "optional": true }
194
+ },
195
+ packageManager: "pnpm@11.15.1+sha512.81350b07e53c9538a02f1f2303b4290fa2d7be04e56e2a970c4cc4b417dc761de196edabd49d55c7dc9580db81007c44143e4e3d7e462b3000d23c255122d065",
196
+ engines: { "node": ">=24.12.0" },
197
+ publishConfig: { "access": "public" }
198
+ };
199
+ //#endregion
200
+ //#region src/cli/constants.ts
201
+ const vscodeSettingsString = `
202
+ // Disable the default formatter, use eslint instead
203
+ "prettier.enable": false,
204
+ "editor.formatOnSave": false,
205
+
206
+ // Auto fix
207
+ "editor.codeActionsOnSave": {
208
+ "source.fixAll.eslint": "explicit",
209
+ "source.organizeImports": "never"
210
+ },
211
+
212
+ // Silent the stylistic rules in you IDE, but still auto fix them
213
+ "eslint.rules.customizations": [
214
+ { "rule": "style/*", "severity": "off", "fixable": true },
215
+ { "rule": "format/*", "severity": "off", "fixable": true },
216
+ { "rule": "*fmt/*", "severity": "off", "fixable": true },
217
+ { "rule": "*-indent", "severity": "off", "fixable": true },
218
+ { "rule": "*-spacing", "severity": "off", "fixable": true },
219
+ { "rule": "*-spaces", "severity": "off", "fixable": true },
220
+ { "rule": "*-order", "severity": "off", "fixable": true },
221
+ { "rule": "*-dangle", "severity": "off", "fixable": true },
222
+ { "rule": "*-newline", "severity": "off", "fixable": true },
223
+ { "rule": "*quotes", "severity": "off", "fixable": true },
224
+ { "rule": "*semi", "severity": "off", "fixable": true }
225
+ ],
226
+
227
+ // Enable eslint for all supported languages
228
+ "eslint.validate": [
229
+ "typescript",
230
+ "typescriptreact",
231
+ "markdown",
232
+ "json",
233
+ "jsonc",
234
+ "yaml",
235
+ "toml",
236
+ "luau"
237
+ ]
238
+ `;
239
+ const frameworkOptions = [{
240
+ label: ansis.red("Test"),
241
+ value: "test"
242
+ }, {
243
+ label: ansis.cyan("React"),
244
+ value: "react"
245
+ }];
246
+ const frameworks = frameworkOptions.map(({ value }) => value);
247
+ const dependenciesMap = {
248
+ react: [
249
+ "eslint-plugin-react-x",
250
+ "eslint-plugin-react-jsx",
251
+ "eslint-plugin-react-naming-convention"
252
+ ],
253
+ test: ["eslint-plugin-jest"]
254
+ };
255
+ //#endregion
256
+ //#region src/guards.ts
257
+ /**
258
+ * Whether a value is an array whose every element is a string.
259
+ *
260
+ * @param value - The value to test.
261
+ * @returns Whether the value is a `string` array.
262
+ */
263
+ function isStringArray(value) {
264
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
265
+ }
266
+ //#endregion
267
+ //#region node_modules/.pnpm/parse-gitignore-ts@1.0.0/node_modules/parse-gitignore-ts/dist/index.mjs
268
+ var isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
269
+ var INVALID_PATH_CHARS_REGEX = /[<>:"|?*\n\r\t\f\x00-\x1F]/;
270
+ var GLOBSTAR_REGEX = /(?:^|\/)[*]{2}($|\/)/;
271
+ var MAX_PATH_LENGTH = 248;
272
+ var isValidPath = (input) => {
273
+ if (typeof input === "string") return input.length <= MAX_PATH_LENGTH && !INVALID_PATH_CHARS_REGEX.test(input);
274
+ return false;
275
+ };
276
+ var split = (str) => String(str).split(/\r\n?|\n/);
277
+ var isComment = (str) => str.startsWith("#");
278
+ var isParsed = (input) => isObject(input) && "patterns" in input && "sections" in input;
279
+ var patterns = (input) => {
280
+ return split(input).map((l) => l.trim()).filter((line) => line !== "" && !isComment(line));
281
+ };
282
+ var parse = (input, options = {}) => {
283
+ let filepath = options.path;
284
+ if (isParsed(input)) return input;
285
+ if (isValidPath(input) && existsSync(input)) {
286
+ filepath = input;
287
+ input = readFileSync(input);
288
+ }
289
+ const lines = split(input);
290
+ const names = /* @__PURE__ */ new Map();
291
+ const parsed = {
292
+ sections: [],
293
+ patterns: []
294
+ };
295
+ let section = {
296
+ name: "default",
297
+ patterns: []
298
+ };
299
+ let prev = null;
300
+ for (const line of lines) {
301
+ const value = line.trim();
302
+ if (value.startsWith("#")) {
303
+ const match = /^#+\s*(.*)\s*$/.exec(value);
304
+ const name = match ? match[1] : "";
305
+ if (prev) {
306
+ names.delete(prev.name);
307
+ prev.comment = prev.comment ? `${prev.comment}
308
+ ${value}` : value;
309
+ prev.name = name ? `${prev.name.trim()}
310
+ ${name.trim()}` : prev.name.trim();
311
+ names.set(prev.name.toLowerCase().trim(), prev);
312
+ continue;
313
+ }
314
+ section = {
315
+ name: name.trim(),
316
+ comment: value,
317
+ patterns: []
318
+ };
319
+ names.set(section.name.toLowerCase(), section);
320
+ parsed.sections.push(section);
321
+ prev = section;
322
+ continue;
323
+ }
324
+ if (value !== "") {
325
+ section.patterns.push(value);
326
+ parsed.patterns.push(value);
327
+ }
328
+ prev = null;
329
+ }
330
+ if (options.dedupe === true || options.unique === true) return dedupe(parsed, {
331
+ ...options,
332
+ format: false
333
+ });
334
+ parsed.path = filepath;
335
+ parsed.input = Buffer.from(input.toString());
336
+ const result = parsed;
337
+ result.format = (opts = {}) => format(result, {
338
+ ...options,
339
+ ...opts
340
+ });
341
+ result.dedupe = (opts = {}) => dedupe(result, {
342
+ ...options,
343
+ ...opts
344
+ });
345
+ result.globs = (opts = {}) => globs(result, {
346
+ path: filepath,
347
+ ...options,
348
+ ...opts
349
+ });
350
+ return result;
351
+ };
352
+ var parseFile = (filepath, options = {}) => {
353
+ return parse(readFileSync(filepath, "utf8"), options);
354
+ };
355
+ var dedupe = (input, options = {}) => {
356
+ const parsed = parse(input, {
357
+ ...options,
358
+ dedupe: false
359
+ });
360
+ const names = /* @__PURE__ */ new Map();
361
+ const res = {
362
+ sections: [],
363
+ patterns: []
364
+ };
365
+ const patternsSet = /* @__PURE__ */ new Set();
366
+ let current;
367
+ for (const section of parsed.sections) {
368
+ const { name = "", comment, patterns: patterns2 } = section;
369
+ const key = name.trim().toLowerCase();
370
+ for (const pattern of patterns2) patternsSet.add(pattern);
371
+ if (name && names.has(key)) {
372
+ current = names.get(key);
373
+ current.patterns = [...current.patterns, ...patterns2];
374
+ } else {
375
+ current = {
376
+ name,
377
+ comment,
378
+ patterns: patterns2
379
+ };
380
+ res.sections.push(current);
381
+ names.set(key, current);
382
+ }
383
+ }
384
+ for (const section of res.sections) section.patterns = [...new Set(section.patterns)];
385
+ res.patterns = [...patternsSet];
386
+ const result = res;
387
+ result.format = (opts = {}) => format(result, {
388
+ ...options,
389
+ ...opts
390
+ });
391
+ result.dedupe = (opts = {}) => dedupe(result, {
392
+ ...options,
393
+ ...opts
394
+ });
395
+ result.globs = (opts = {}) => globs(result, {
396
+ path: parsed.path,
397
+ ...options,
398
+ ...opts
399
+ });
400
+ return result;
401
+ };
402
+ var glob = (pattern, options) => {
403
+ if (GLOBSTAR_REGEX.test(pattern)) return pattern;
404
+ let relative = false;
405
+ if (pattern.startsWith("/")) {
406
+ pattern = pattern.slice(1);
407
+ relative = true;
408
+ } else if (pattern.slice(1, pattern.length - 1).includes("/")) relative = true;
409
+ pattern += pattern.endsWith("/") ? "**/" : "/**";
410
+ return relative ? pattern : `**/${pattern}`;
411
+ };
412
+ var globs = (input, options = {}) => {
413
+ const parsed = parse(input, options);
414
+ const result = [];
415
+ let index = 0;
416
+ const inputPatterns = parsed.patterns.concat(options.ignore || []).concat((options.unignore || []).map((p) => !p.startsWith("!") ? "!" + p : p));
417
+ const push = (prefix, pattern) => {
418
+ const prev = result[result.length - 1];
419
+ const type = prefix ? "unignore" : "ignore";
420
+ if (prev && prev.type === type) {
421
+ if (!prev.patterns.includes(pattern)) prev.patterns.push(pattern);
422
+ } else {
423
+ result.push({
424
+ type,
425
+ path: options.path || null,
426
+ patterns: [pattern],
427
+ index
428
+ });
429
+ index++;
430
+ }
431
+ };
432
+ for (let pattern of inputPatterns) {
433
+ let prefix = "";
434
+ if (pattern.startsWith("!")) {
435
+ pattern = pattern.slice(1);
436
+ prefix = "!";
437
+ }
438
+ push(prefix, pattern.startsWith("/") ? pattern.slice(1) : pattern);
439
+ push(prefix, glob(pattern));
440
+ }
441
+ return result;
442
+ };
443
+ var formatSection = (section = {
444
+ name: "",
445
+ patterns: []
446
+ }) => {
447
+ const output = [section.comment || ""];
448
+ if (section.patterns?.length) {
449
+ output.push(section.patterns.join("\n"));
450
+ output.push("");
451
+ }
452
+ return output.join("\n");
453
+ };
454
+ var format = (input, options = {}) => {
455
+ const parsed = parse(input, options);
456
+ const fn = options.formatSection || formatSection;
457
+ const sections = parsed.sections || parsed;
458
+ const output = [];
459
+ for (const section of [].concat(sections)) output.push(fn(section));
460
+ return output.join("\n");
461
+ };
462
+ var parseGitignore = parse;
463
+ parseGitignore.file = parseFile;
464
+ parseGitignore.parse = parse;
465
+ parseGitignore.dedupe = dedupe;
466
+ parseGitignore.format = format;
467
+ parseGitignore.globs = globs;
468
+ parseGitignore.formatSection = formatSection;
469
+ parseGitignore.patterns = patterns;
470
+ var index_default = parseGitignore;
471
+ /*!
472
+ * parse-gitignore-ts
473
+ * TypeScript version of parse-gitignore <https://github.com/jonschlinkert/parse-gitignore>
474
+ * Original Copyright (c) 2015-present, Jon Schlinkert.
475
+ * Released under the MIT License.
476
+ */
477
+ //#endregion
478
+ //#region src/cli/utils.ts
479
+ function getEslintConfigContent(mainConfig, additionalConfigs) {
480
+ return `
481
+ import isentinel from '@isentinel/eslint-config'
482
+
483
+ export default isentinel({
484
+ ${mainConfig}
485
+ }${additionalConfigs?.map((config) => `,{\n${config}\n}`).join(",") ?? ""})
486
+ `.trimStart();
487
+ }
488
+ function isGitClean() {
489
+ try {
490
+ execSync("git diff-index --quiet HEAD --");
491
+ return true;
492
+ } catch {
493
+ return false;
494
+ }
495
+ }
496
+ //#endregion
497
+ //#region src/cli/stages/update-eslint-files.ts
498
+ const LEGACY_CONFIG_REGEX = /eslint|prettier/;
499
+ async function updateEslintFiles(result) {
500
+ const cwd = process.cwd();
501
+ const pathESLintIgnore = path.join(cwd, ".eslintignore");
502
+ const pathPackageJSON = path.join(cwd, "package.json");
503
+ const packageContent = await fsp.readFile(pathPackageJSON, "utf-8");
504
+ const configFileName = JSON.parse(packageContent)["type"] === "module" ? "eslint.config.js" : "eslint.config.mjs";
505
+ const pathFlatConfig = path.join(cwd, configFileName);
506
+ const eslintIgnores = [];
507
+ if (fs.existsSync(pathESLintIgnore)) {
508
+ log.step(ansis.cyan("Migrating existing .eslintignore"));
509
+ const globs = index_default(await fsp.readFile(pathESLintIgnore, "utf-8")).globs();
510
+ for (const glob of globs) if (glob.type === "ignore") eslintIgnores.push(...glob.patterns);
511
+ else eslintIgnores.push(...glob.patterns.map((pattern) => `!${pattern}`));
512
+ }
513
+ const configLines = [];
514
+ if (eslintIgnores.length) configLines.push(`ignores: ${JSON.stringify(eslintIgnores)},`);
515
+ for (const framework of result.frameworks) configLines.push(`${framework}: true,`);
516
+ const eslintConfigContent = getEslintConfigContent(configLines.map((index) => ` ${index}`).join("\n"), []);
517
+ await fsp.writeFile(pathFlatConfig, eslintConfigContent);
518
+ log.success(ansis.green(`Created ${configFileName}`));
519
+ const files = fs.readdirSync(cwd);
520
+ const legacyConfig = [];
521
+ for (const file of files) if (LEGACY_CONFIG_REGEX.test(file) && !file.includes("eslint.config.")) legacyConfig.push(file);
522
+ if (legacyConfig.length) note(ansis.dim(legacyConfig.join(", ")), "You can now remove those files manually");
523
+ }
524
+ //#endregion
525
+ //#region src/cli/constants-generated.ts
526
+ const versionsMap = {
527
+ "eslint": "10.7.0",
528
+ "eslint-plugin-jest": "29.15.4",
529
+ "eslint-plugin-react-jsx": "5.17.2",
530
+ "eslint-plugin-react-naming-convention": "5.17.2",
531
+ "eslint-plugin-react-x": "5.17.2"
532
+ };
533
+ //#endregion
534
+ //#region src/cli/stages/update-package-json.ts
535
+ /** Scripts the wizard wires up so `pnpm lint` drives the hybrid runner. */
536
+ const LINT_SCRIPTS = {
537
+ "lint": "isentinel-lint",
538
+ "lint:fix": "isentinel-lint --fix"
539
+ };
540
+ /**
541
+ * Merge the lint scripts into a `scripts` block in place. Missing scripts are
542
+ * always added. An existing script with identical content is left untouched. An
543
+ * existing script with different content is preserved in skip-prompt mode, and
544
+ * otherwise only overwritten when {@link ConfirmOverwrite} resolves true.
545
+ *
546
+ * @param scripts - The consumer's `scripts` block, mutated in place.
547
+ * @param options - Skip-prompt flag and the overwrite confirmation callback.
548
+ * @returns Which scripts were added and which were overwritten.
549
+ */
550
+ async function mergeLintScripts(scripts, options) {
551
+ const added = [];
552
+ const overwritten = [];
553
+ for (const [name, desired] of Object.entries(LINT_SCRIPTS)) {
554
+ const existing = scripts[name];
555
+ if (existing === void 0) {
556
+ scripts[name] = desired;
557
+ added.push(name);
558
+ continue;
559
+ }
560
+ if (existing === desired || options.skipPrompt) continue;
561
+ if (await options.confirmOverwrite?.(name, existing, desired) === true) {
562
+ scripts[name] = desired;
563
+ overwritten.push(name);
564
+ }
565
+ }
566
+ return {
567
+ added,
568
+ overwritten
569
+ };
570
+ }
571
+ async function updatePackageJson(result, skipPrompt = false) {
572
+ const cwd = process.cwd();
573
+ const pathPackageJSON = path.join(cwd, "package.json");
574
+ log.step(ansis.cyan(`Bumping @isentinel/eslint-config to v${version}`));
575
+ const packageContent = await fsp.readFile(pathPackageJSON, "utf-8");
576
+ const parsedPackage = JSON.parse(packageContent);
577
+ parsedPackage.devDependencies ??= {};
578
+ parsedPackage.devDependencies["@isentinel/eslint-config"] = `^${version}`;
579
+ parsedPackage.devDependencies["eslint"] ??= versionsMap.eslint;
580
+ const addedPackages = [];
581
+ for (const framework of result.frameworks) {
582
+ if (!(framework in dependenciesMap)) continue;
583
+ const dependencies = dependenciesMap[framework];
584
+ for (const dependency of dependencies) {
585
+ parsedPackage.devDependencies[dependency] = versionsMap[dependency];
586
+ addedPackages.push(dependency);
587
+ }
588
+ }
589
+ if (addedPackages.length) note(ansis.dim(addedPackages.join(", ")), "Added packages");
590
+ parsedPackage.scripts ??= {};
591
+ const { scripts = {} } = parsedPackage;
592
+ const { added, overwritten } = await mergeLintScripts(scripts, {
593
+ confirmOverwrite: async (name, existing, desired) => {
594
+ return await confirm({
595
+ initialValue: false,
596
+ message: `A "${name}" script already exists ("${existing}"). Overwrite it with "${desired}"?`
597
+ }) === true;
598
+ },
599
+ skipPrompt
600
+ });
601
+ const touched = [...added, ...overwritten];
602
+ if (touched.length) note(ansis.dim(touched.map((name) => `${name}: ${scripts[name]}`).join("\n")), "Lint scripts");
603
+ await fsp.writeFile(pathPackageJSON, JSON.stringify(parsedPackage, null, 2));
604
+ log.success(ansis.green("Changes wrote to package.json"));
605
+ }
606
+ //#endregion
607
+ //#region src/cli/stages/update-vscode-settings.ts
608
+ async function updateVscodeSettings(result) {
609
+ if (!result.updateVscodeSettings) return;
610
+ const cwd = process.cwd();
611
+ const dotVscodePath = path.join(cwd, ".vscode");
612
+ const settingsPath = path.join(dotVscodePath, "settings.json");
613
+ if (!fs.existsSync(dotVscodePath)) await fsp.mkdir(dotVscodePath, { recursive: true });
614
+ if (!fs.existsSync(settingsPath)) {
615
+ await fsp.writeFile(settingsPath, `{${vscodeSettingsString}}\n`, "utf-8");
616
+ log.success(ansis.green("Created .vscode/settings.json"));
617
+ return;
618
+ }
619
+ let settingsContent = await fsp.readFile(settingsPath, "utf8");
620
+ settingsContent = settingsContent.trim();
621
+ if (settingsContent.endsWith("}")) settingsContent = settingsContent.slice(0, -1).trimEnd();
622
+ settingsContent += settingsContent.endsWith(",") || settingsContent.endsWith("{") ? "" : ",";
623
+ settingsContent += `${vscodeSettingsString}}\n`;
624
+ await fsp.writeFile(settingsPath, settingsContent, "utf-8");
625
+ log.success(ansis.green("Updated .vscode/settings.json"));
626
+ }
627
+ //#endregion
628
+ //#region src/cli/run.ts
629
+ async function run(options = {}) {
630
+ const argumentSkipPrompt = !!(process.env["SKIP_PROMPT"] ?? "") || options.yes;
631
+ const argumentTemplate = options.frameworks?.map((framework) => framework.trim()).filter((framework) => {
632
+ return frameworks.some((valid) => valid === framework);
633
+ });
634
+ const eslintConfigFiles = fs.readdirSync(process.cwd()).filter((file) => file.startsWith("eslint.config."));
635
+ if (eslintConfigFiles.length > 0) {
636
+ log.warn(ansis.yellow(`${eslintConfigFiles[0]} already exists, migration wizard exited.`));
637
+ return process.exit(1);
638
+ }
639
+ let result = {
640
+ frameworks: argumentTemplate ?? [],
641
+ uncommittedConfirmed: false,
642
+ updateVscodeSettings: true
643
+ };
644
+ if (argumentSkipPrompt !== true) {
645
+ const { frameworks: groupedFrameworks, uncommittedConfirmed, updateVscodeSettings: shouldUpdateVscode } = await group({
646
+ uncommittedConfirmed: async () => {
647
+ if (isGitClean()) return true;
648
+ return confirm({
649
+ initialValue: false,
650
+ message: "There are uncommitted changes in the current repository, are you sure to continue?"
651
+ });
652
+ },
653
+ frameworks: async ({ results }) => {
654
+ if ((argumentTemplate && argumentTemplate.length > 0 && argumentTemplate.every((template) => frameworks.includes(template))) === true || results.uncommittedConfirmed !== true) return;
655
+ const message = argumentTemplate && argumentTemplate.length > 0 ? `"${argumentTemplate.join(", ")}" isn't a valid template. Please choose from below: ` : "Select a framework:";
656
+ return multiselect({
657
+ message: ansis.reset(message),
658
+ options: frameworkOptions,
659
+ required: false
660
+ });
661
+ },
662
+ updateVscodeSettings: async ({ results }) => {
663
+ if (results.uncommittedConfirmed !== true) return;
664
+ return confirm({
665
+ initialValue: true,
666
+ message: "Update .vscode/settings.json for better VS Code experience?"
667
+ });
668
+ }
669
+ }, { onCancel: () => {
670
+ cancel("Operation cancelled.");
671
+ process.exit(0);
672
+ } });
673
+ result = {
674
+ frameworks: isStringArray(groupedFrameworks) ? groupedFrameworks.filter((value) => {
675
+ return frameworks.some((option) => option === value);
676
+ }) : [],
677
+ uncommittedConfirmed: uncommittedConfirmed === true,
678
+ updateVscodeSettings: shouldUpdateVscode !== false
679
+ };
680
+ if (!result.uncommittedConfirmed) return process.exit(1);
681
+ }
682
+ await updatePackageJson(result, argumentSkipPrompt === true);
683
+ await updateEslintFiles(result);
684
+ await updateVscodeSettings(result);
685
+ log.success(ansis.green("Setup completed"));
686
+ outro(`Now you can update the dependencies by running ${ansis.blue("pnpm install")} and lint by running ${ansis.blue("pnpm lint")} (or ${ansis.blue("pnpm lint:fix")})\n`);
687
+ }
688
+ //#endregion
689
+ //#region src/cli/index.ts
690
+ function header() {
691
+ console.log("\n");
692
+ const introText = ansis.green("@isentinel/eslint-config");
693
+ const versionText = `v${package_default.version}`;
694
+ intro(introText + ansis.dim(versionText));
695
+ }
696
+ yargs(hideBin(process.argv)).scriptName("@isentinel/eslint-config").usage("").command("*", "Run the initialization or migration", (args) => {
697
+ return args.option("yes", {
698
+ alias: "y",
699
+ description: "Skip prompts and use default values",
700
+ type: "boolean"
701
+ }).option("template", {
702
+ alias: "t",
703
+ description: "Use the framework template for optimal customization: react, jest",
704
+ type: "string"
705
+ }).help();
706
+ }, async (args) => {
707
+ header();
708
+ try {
709
+ await run({
710
+ ...args,
711
+ frameworks: args.template !== void 0 ? [args.template] : void 0
712
+ });
713
+ } catch (err) {
714
+ log.error(ansis.inverse(ansis.red(" Failed to migrate ")));
715
+ log.error(ansis.red(`✘ ${String(err)}`));
716
+ process.exit(1);
717
+ }
718
+ }).showHelpOnFail(false).alias("h", "help").version("version", package_default.version).alias("v", "version").help().argv;
719
+ //#endregion
1
720
  export {};