@isentinel/eslint-config 6.0.0-beta.12 → 6.0.0-beta.13

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