@useinsider/eslint-config 3.0.2 → 4.0.0-beta.2
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/README.md +376 -64
- package/dist/builders/configFiles.d.ts +13 -0
- package/dist/builders/oxlint.d.ts +51 -0
- package/dist/builders/oxlintOwnership.d.ts +43 -0
- package/dist/builders/scopeBlocks.d.ts +6 -0
- package/dist/cli.cjs +1028 -86
- package/dist/cli.cjs.map +1 -1
- package/dist/configs/dependencies.d.ts +3 -0
- package/dist/{javascript-VBXqjVi8.cjs → javascript-DH8-N38w.cjs} +5 -5
- package/dist/{javascript-VBXqjVi8.cjs.map → javascript-DH8-N38w.cjs.map} +1 -1
- package/dist/{javascript-dom-5jRL_yKP.cjs → javascript-dom-CvtQ6_9W.cjs} +4 -4
- package/dist/{javascript-dom-5jRL_yKP.cjs.map → javascript-dom-CvtQ6_9W.cjs.map} +1 -1
- package/dist/{javascript-node-C1AU_4X2.cjs → javascript-node-BNsy3loY.cjs} +4 -4
- package/dist/{javascript-node-C1AU_4X2.cjs.map → javascript-node-BNsy3loY.cjs.map} +1 -1
- package/dist/{jest-CHzjzBtf.cjs → jest-4Es9Ggaa.cjs} +3 -3
- package/dist/{jest-CHzjzBtf.cjs.map → jest-4Es9Ggaa.cjs.map} +1 -1
- package/dist/main.cjs +763 -78
- package/dist/main.cjs.map +1 -1
- package/dist/main.d.ts +9 -2
- package/dist/main.js +746 -61
- package/dist/main.js.map +1 -1
- package/dist/oxlintOwnership-7hpdB3d1.cjs +199 -0
- package/dist/oxlintOwnership-7hpdB3d1.cjs.map +1 -0
- package/dist/oxlintOwnership-Cx0hgA_J.js +127 -0
- package/dist/oxlintOwnership-Cx0hgA_J.js.map +1 -0
- package/dist/removePluginsFromConfigs-D0oSUj2C.cjs.map +1 -1
- package/dist/removePluginsFromConfigs-DzqX2Ju2.js.map +1 -1
- package/dist/{stylistic-Dio0AM2E.cjs → stylistic-CoP3MT1Y.cjs} +4 -4
- package/dist/{stylistic-Dio0AM2E.cjs.map → stylistic-CoP3MT1Y.cjs.map} +1 -1
- package/dist/{typescript-C1icmOxY.cjs → typescript-D3VeLnpn.cjs} +7 -7
- package/dist/{typescript-C1icmOxY.cjs.map → typescript-D3VeLnpn.cjs.map} +1 -1
- package/dist/{typescript-dom-RUJn2WCZ.cjs → typescript-dom-iB_xBTAD.cjs} +4 -4
- package/dist/{typescript-dom-RUJn2WCZ.cjs.map → typescript-dom-iB_xBTAD.cjs.map} +1 -1
- package/dist/{typescript-node-CBEzcc56.cjs → typescript-node-Cq6qhj53.cjs} +4 -4
- package/dist/{typescript-node-CBEzcc56.cjs.map → typescript-node-Cq6qhj53.cjs.map} +1 -1
- package/dist/utils/ensureConfigStructure.d.ts +15 -6
- package/dist/utils/ensureOxlintConfig.d.ts +10 -0
- package/dist/utils/layoutDiagnosis.d.ts +63 -0
- package/dist/utils/offerMigration.d.ts +42 -0
- package/dist/utils/silenceRules.d.ts +9 -0
- package/dist/{vitest-BomI8gtu.cjs → vitest-BlGQswK2.cjs} +3 -3
- package/dist/{vitest-BomI8gtu.cjs.map → vitest-BlGQswK2.cjs.map} +1 -1
- package/dist/{vue2-azKwRgaJ.cjs → vue2-Da1i_oVn.cjs} +8 -8
- package/dist/{vue2-azKwRgaJ.cjs.map → vue2-Da1i_oVn.cjs.map} +1 -1
- package/dist/{vue2-options-api-ma3J-bvM.cjs → vue2-options-api-BSdrUm8H.cjs} +8 -8
- package/dist/{vue2-options-api-ma3J-bvM.cjs.map → vue2-options-api-BSdrUm8H.cjs.map} +1 -1
- package/dist/{vue2-typescript-CZSHzp7I.cjs → vue2-typescript-dXLRSGNP.cjs} +10 -10
- package/dist/{vue2-typescript-CZSHzp7I.cjs.map → vue2-typescript-dXLRSGNP.cjs.map} +1 -1
- package/dist/{vue2-typescript-options-api-B8OzZlje.cjs → vue2-typescript-options-api-LM7U5dWq.cjs} +10 -10
- package/dist/{vue2-typescript-options-api-B8OzZlje.cjs.map → vue2-typescript-options-api-LM7U5dWq.cjs.map} +1 -1
- package/dist/{vue3-Df5UQ1Ys.cjs → vue3-621EM2Ii.cjs} +8 -8
- package/dist/{vue3-Df5UQ1Ys.cjs.map → vue3-621EM2Ii.cjs.map} +1 -1
- package/dist/{vue3-options-api-Bx7yYHza.cjs → vue3-options-api-CC9_fAiq.cjs} +8 -8
- package/dist/{vue3-options-api-Bx7yYHza.cjs.map → vue3-options-api-CC9_fAiq.cjs.map} +1 -1
- package/dist/{vue3-typescript-C0O1JZlk.cjs → vue3-typescript-DONe-IKz.cjs} +10 -10
- package/dist/{vue3-typescript-C0O1JZlk.cjs.map → vue3-typescript-DONe-IKz.cjs.map} +1 -1
- package/dist/{vue3-typescript-options-api-cPal4O-K.cjs → vue3-typescript-options-api-D4pqpDFs.cjs} +10 -10
- package/dist/{vue3-typescript-options-api-cPal4O-K.cjs.map → vue3-typescript-options-api-D4pqpDFs.cjs.map} +1 -1
- package/package.json +8 -5
- package/dist/rolldown-runtime-VH7oDXx4.cjs +0 -28
package/dist/cli.cjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const
|
|
3
|
+
const require_oxlintOwnership = require("./oxlintOwnership-7hpdB3d1.cjs");
|
|
4
4
|
let globals = require("globals");
|
|
5
|
-
globals =
|
|
5
|
+
globals = require_oxlintOwnership.__toESM(globals, 1);
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
let node_url = require("node:url");
|
|
9
|
+
let _typescript_eslint_types = require("@typescript-eslint/types");
|
|
9
10
|
let node_crypto = require("node:crypto");
|
|
10
11
|
let node_os = require("node:os");
|
|
11
12
|
let typescript = require("typescript");
|
|
12
|
-
typescript =
|
|
13
|
+
typescript = require_oxlintOwnership.__toESM(typescript, 1);
|
|
14
|
+
let _typescript_eslint_parser = require("@typescript-eslint/parser");
|
|
13
15
|
//#region src/cli/detect.ts
|
|
14
16
|
var FLAT_CONFIG_FILENAMES = [
|
|
15
17
|
"eslint.config.mjs",
|
|
@@ -307,20 +309,26 @@ function detectVueApiPattern(input) {
|
|
|
307
309
|
//#region src/cli/dispatch.ts
|
|
308
310
|
var yesFlags = /* @__PURE__ */ new Set(["--yes", "-y"]);
|
|
309
311
|
var noInstallFlags = /* @__PURE__ */ new Set(["--no-install"]);
|
|
312
|
+
var fromLintFlags = /* @__PURE__ */ new Set(["--from-lint"]);
|
|
310
313
|
function isYesFlag(arg) {
|
|
311
314
|
return yesFlags.has(arg);
|
|
312
315
|
}
|
|
313
316
|
function isNoInstallFlag(arg) {
|
|
314
317
|
return noInstallFlags.has(arg);
|
|
315
318
|
}
|
|
319
|
+
function isFromLintFlag(arg) {
|
|
320
|
+
return fromLintFlags.has(arg);
|
|
321
|
+
}
|
|
316
322
|
function parseMigrateArgs(rest) {
|
|
317
323
|
const yes = rest.some(isYesFlag);
|
|
318
324
|
const noInstall = rest.some(isNoInstallFlag);
|
|
319
|
-
const
|
|
325
|
+
const fromLint = rest.some(isFromLintFlag);
|
|
326
|
+
const positional = rest.filter((arg) => !isYesFlag(arg) && !isNoInstallFlag(arg) && !isFromLintFlag(arg));
|
|
320
327
|
return {
|
|
321
328
|
target: positional.length > 0 ? positional[positional.length - 1] : void 0,
|
|
322
329
|
yes,
|
|
323
|
-
noInstall
|
|
330
|
+
noInstall,
|
|
331
|
+
fromLint
|
|
324
332
|
};
|
|
325
333
|
}
|
|
326
334
|
function parseScaffoldArgs(argv) {
|
|
@@ -329,12 +337,14 @@ function parseScaffoldArgs(argv) {
|
|
|
329
337
|
function printUsage(stderr, unknown) {
|
|
330
338
|
stderr(`[useinsider/eslint-config] Unknown subcommand: ${unknown}`);
|
|
331
339
|
stderr("Usage:");
|
|
332
|
-
stderr(" npx @useinsider/eslint-config [--yes|-y]
|
|
333
|
-
stderr("
|
|
334
|
-
stderr("
|
|
340
|
+
stderr(" npx @useinsider/eslint-config [--yes|-y]");
|
|
341
|
+
stderr(" Scaffold insider.lint.*, eslint.config.* and oxlint.config.*");
|
|
342
|
+
stderr(" npx @useinsider/eslint-config migrate [path] [--yes|-y] [--no-install] [--from-lint]");
|
|
343
|
+
stderr(" Split an existing useInsider() or .eslintrc* config into the three-file layout");
|
|
344
|
+
stderr(" --from-lint: started by a failing lint run — confirm first, exit non-zero if declined");
|
|
335
345
|
}
|
|
336
346
|
function isScaffoldArgv(argv) {
|
|
337
|
-
return argv.
|
|
347
|
+
return argv.every((arg) => isYesFlag(arg));
|
|
338
348
|
}
|
|
339
349
|
async function dispatch(argv, cwd, deps) {
|
|
340
350
|
if (isScaffoldArgv(argv)) {
|
|
@@ -346,18 +356,22 @@ async function dispatch(argv, cwd, deps) {
|
|
|
346
356
|
}
|
|
347
357
|
const [subcommand, ...rest] = argv;
|
|
348
358
|
if (subcommand === "migrate") {
|
|
349
|
-
const { target, yes, noInstall } = parseMigrateArgs(rest);
|
|
359
|
+
const { target, yes, noInstall, fromLint } = parseMigrateArgs(rest);
|
|
350
360
|
return deps.migrate({
|
|
351
361
|
cwd,
|
|
352
362
|
target,
|
|
353
363
|
yes,
|
|
354
|
-
noInstall
|
|
364
|
+
noInstall,
|
|
365
|
+
fromLint
|
|
355
366
|
});
|
|
356
367
|
}
|
|
357
368
|
printUsage(deps.stderr, subcommand);
|
|
358
369
|
return 1;
|
|
359
370
|
}
|
|
360
371
|
//#endregion
|
|
372
|
+
//#region src/configs/dependencies.ts
|
|
373
|
+
var dependencies$14 = { oxlint: true };
|
|
374
|
+
//#endregion
|
|
361
375
|
//#region src/configs/jest/dependencies.ts
|
|
362
376
|
var dependencies$13 = { "eslint-plugin-jest": true };
|
|
363
377
|
//#endregion
|
|
@@ -471,6 +485,7 @@ function buildDependencyList(answers, rawManifest = readOwnEslintConfigManifest$
|
|
|
471
485
|
deps.add(`@useinsider/eslint-config@${manifest.version}`);
|
|
472
486
|
deps.add(pinned(manifest, "eslint"));
|
|
473
487
|
deps.add(pinned(manifest, "globals"));
|
|
488
|
+
applyRecord(deps, manifest, dependencies$14);
|
|
474
489
|
const wantsTypescript = answers.languages.includes("typescript");
|
|
475
490
|
if (answers.framework === "vue") applyRecord(deps, manifest, vueDepsFor(answers.vueMajor ?? 3, wantsTypescript));
|
|
476
491
|
else if (wantsTypescript) applyRecord(deps, manifest, dependencies$12);
|
|
@@ -654,39 +669,66 @@ function noteGenericError(clack, summary, context, specs) {
|
|
|
654
669
|
async function tryRetryInstall(clack, installer, deps, context) {
|
|
655
670
|
try {
|
|
656
671
|
await installer(deps);
|
|
672
|
+
return { installed: true };
|
|
657
673
|
} catch (retryError) {
|
|
658
|
-
|
|
674
|
+
const retryReason = retryError instanceof Error ? retryError.message : String(retryError);
|
|
675
|
+
noteError(clack, "Retry install failed after wipe: ", retryReason, context, deps);
|
|
676
|
+
return {
|
|
677
|
+
installed: false,
|
|
678
|
+
reason: retryReason
|
|
679
|
+
};
|
|
659
680
|
}
|
|
660
681
|
}
|
|
661
682
|
async function tryGenericRetryInstall(clack, installer, deps, context) {
|
|
662
683
|
try {
|
|
663
684
|
await installer(deps);
|
|
685
|
+
return { installed: true };
|
|
664
686
|
} catch (retryError) {
|
|
665
|
-
|
|
687
|
+
const summary = buildGenericSummary(`Clean reinstall failed: ${retryError instanceof Error ? retryError.message : String(retryError)}`);
|
|
688
|
+
noteGenericError(clack, summary, context, deps);
|
|
689
|
+
return {
|
|
690
|
+
installed: false,
|
|
691
|
+
reason: summary
|
|
692
|
+
};
|
|
666
693
|
}
|
|
667
694
|
}
|
|
668
695
|
async function handlePeerDepFailure(clack, deps, autoConfirm, installer, reason, context) {
|
|
669
696
|
if (!await confirmPeerDepWipe(clack, autoConfirm)) {
|
|
670
697
|
noteError(clack, "Install failed: ", reason, context, deps);
|
|
671
|
-
return
|
|
698
|
+
return {
|
|
699
|
+
installed: false,
|
|
700
|
+
reason
|
|
701
|
+
};
|
|
672
702
|
}
|
|
673
703
|
performFullWipe(clack, context);
|
|
674
|
-
|
|
704
|
+
return tryRetryInstall(clack, installer, deps, context);
|
|
675
705
|
}
|
|
676
706
|
async function handleGenericFailure(clack, deps, autoConfirm, installer, reason, context) {
|
|
677
707
|
const summary = buildGenericSummary(reason);
|
|
678
708
|
if (!await confirmCleanReinstall(clack, autoConfirm, summary)) {
|
|
679
709
|
if (clack.note) clack.note(`${summary}\nSkipping clean reinstall. Please resolve the issue manually and re-run.`, "Error");
|
|
680
|
-
return
|
|
710
|
+
return {
|
|
711
|
+
installed: false,
|
|
712
|
+
reason: summary
|
|
713
|
+
};
|
|
681
714
|
}
|
|
682
715
|
performFullWipe(clack, context);
|
|
683
|
-
|
|
684
|
-
}
|
|
716
|
+
return tryGenericRetryInstall(clack, installer, deps, context);
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Installs the dependencies, offering one wipe-and-retry when the first attempt fails, and reports
|
|
720
|
+
* whether they ended up installed. Every failure path is already explained to the consumer through
|
|
721
|
+
* `clack`, so the outcome exists for the caller's exit code rather than for a second message.
|
|
722
|
+
* @example
|
|
723
|
+
* const outcome = await installWithRetry(options);
|
|
724
|
+
* if (!outcome.installed) { return 1; }
|
|
725
|
+
*/
|
|
685
726
|
async function installWithRetry(opts) {
|
|
686
727
|
const installer = opts.installer ?? defaultInstaller;
|
|
687
728
|
const fs = opts.fsOps ?? defaultWipeFs();
|
|
688
729
|
try {
|
|
689
730
|
await installer(opts.deps);
|
|
731
|
+
return { installed: true };
|
|
690
732
|
} catch (error) {
|
|
691
733
|
const reason = error instanceof Error ? error.message : String(error);
|
|
692
734
|
const context = {
|
|
@@ -694,12 +736,303 @@ async function installWithRetry(opts) {
|
|
|
694
736
|
fs,
|
|
695
737
|
detected: detectLockfile(opts.cwd, fs.exists)
|
|
696
738
|
};
|
|
697
|
-
if (isPeerDepFailure(reason))
|
|
698
|
-
|
|
739
|
+
if (isPeerDepFailure(reason)) return handlePeerDepFailure(opts.clack, opts.deps, opts.autoConfirm, installer, reason, context);
|
|
740
|
+
return handleGenericFailure(opts.clack, opts.deps, opts.autoConfirm, installer, reason, context);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/cli/lintLayout.ts
|
|
745
|
+
var OXLINT_COMMAND = "oxlint --disable-nested-config";
|
|
746
|
+
var OXLINT_LINT_SCRIPT = `${OXLINT_COMMAND} && eslint .`;
|
|
747
|
+
/**
|
|
748
|
+
* Extension for the lint module and the oxlint config. oxlint only discovers `oxlint.config.ts` and
|
|
749
|
+
* `oxlint.config.mts`, and Node warns on a bare `.ts` file inside a package that declares no
|
|
750
|
+
* `"type"`, so such a consumer gets the unambiguous `.mts` form.
|
|
751
|
+
* @example
|
|
752
|
+
* resolveLayoutExtension('/repo'); // 'ts' when /repo/package.json declares "type": "module"
|
|
753
|
+
*/
|
|
754
|
+
function resolveLayoutExtension(cwd) {
|
|
755
|
+
try {
|
|
756
|
+
const raw = (0, node_fs.readFileSync)((0, node_path.join)(cwd, "package.json"), "utf8");
|
|
757
|
+
return JSON.parse(raw).type === "module" ? "ts" : "mts";
|
|
758
|
+
} catch {
|
|
759
|
+
return "mts";
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
var ESLINT_CONFIG_EXTENSIONS = [
|
|
763
|
+
"ts",
|
|
764
|
+
"mts",
|
|
765
|
+
"cts",
|
|
766
|
+
"js",
|
|
767
|
+
"mjs",
|
|
768
|
+
"cjs"
|
|
769
|
+
];
|
|
770
|
+
var TYPESCRIPT_CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
771
|
+
"cts",
|
|
772
|
+
"mts",
|
|
773
|
+
"ts"
|
|
774
|
+
]);
|
|
775
|
+
function extensionOf(filename) {
|
|
776
|
+
return filename.slice(filename.lastIndexOf(".") + 1);
|
|
777
|
+
}
|
|
778
|
+
function existingEslintConfigs(cwd) {
|
|
779
|
+
return ESLINT_CONFIG_EXTENSIONS.map((extension) => `eslint.config.${extension}`).filter((filename) => (0, node_fs.existsSync)((0, node_path.join)(cwd, filename)));
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* The three filenames `useInsider()` requires, resolved against a consumer directory. A JavaScript
|
|
783
|
+
* flat config is renamed to the TypeScript extension — it cannot import the lint module on every
|
|
784
|
+
* supported Node version — and every other `eslint.config.*` in the directory is reported through
|
|
785
|
+
* `replacedEslintConfigs` so the caller deletes it. ESLint resolves `.js`/`.mjs`/`.cjs` before the
|
|
786
|
+
* TypeScript extensions, so a leftover JavaScript config keeps winning over the one just written.
|
|
787
|
+
* @example
|
|
788
|
+
* resolveLintLayout('/repo');
|
|
789
|
+
* // { eslintConfig: 'eslint.config.ts', replacedEslintConfigs: ['eslint.config.js'], ... }
|
|
790
|
+
*/
|
|
791
|
+
function resolveLintLayout(cwd) {
|
|
792
|
+
const extension = resolveLayoutExtension(cwd);
|
|
793
|
+
const existing = existingEslintConfigs(cwd);
|
|
794
|
+
const eslintConfig = existing.find((filename) => TYPESCRIPT_CONFIG_EXTENSIONS.has(extensionOf(filename))) ?? `eslint.config.${extension}`;
|
|
795
|
+
return {
|
|
796
|
+
extension,
|
|
797
|
+
lintModule: `insider.lint.${extension}`,
|
|
798
|
+
oxlintConfig: `oxlint.config.${extension}`,
|
|
799
|
+
eslintConfig,
|
|
800
|
+
replacedEslintConfigs: existing.filter((filename) => filename !== eslintConfig)
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Body of the generated `eslint.config.*`. The import names the lint module's real extension
|
|
805
|
+
* because neither linter's loader applies TypeScript's `.js` → `.ts` specifier rewriting.
|
|
806
|
+
* @example
|
|
807
|
+
* renderEslintConfigFile('insider.lint.ts'); // "import { useInsiderESLint } from './insider.lint.ts';\n…"
|
|
808
|
+
*/
|
|
809
|
+
function renderEslintConfigFile(lintModule) {
|
|
810
|
+
return [
|
|
811
|
+
`import { useInsiderESLint } from './${lintModule}';`,
|
|
812
|
+
"",
|
|
813
|
+
"export default useInsiderESLint();",
|
|
814
|
+
""
|
|
815
|
+
].join("\n");
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Body of the generated `oxlint.config.*`.
|
|
819
|
+
* @example
|
|
820
|
+
* renderOxlintConfigFile('insider.lint.mts'); // "…export default await useInsiderOXC();\n"
|
|
821
|
+
*/
|
|
822
|
+
function renderOxlintConfigFile(lintModule) {
|
|
823
|
+
return [
|
|
824
|
+
`import { useInsiderOXC } from './${lintModule}';`,
|
|
825
|
+
"",
|
|
826
|
+
"export default await useInsiderOXC();",
|
|
827
|
+
""
|
|
828
|
+
].join("\n");
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* The two thin files that sit beside a lint module, ready to write.
|
|
832
|
+
* @example
|
|
833
|
+
* renderLintLayoutFiles(resolveLintLayout('/repo')).map(file => file.filename);
|
|
834
|
+
* // ['eslint.config.mts', 'oxlint.config.mts']
|
|
835
|
+
*/
|
|
836
|
+
function renderLintLayoutFiles(layout) {
|
|
837
|
+
return [{
|
|
838
|
+
filename: layout.eslintConfig,
|
|
839
|
+
source: renderEslintConfigFile(layout.lintModule)
|
|
840
|
+
}, {
|
|
841
|
+
filename: layout.oxlintConfig,
|
|
842
|
+
source: renderOxlintConfigFile(layout.lintModule)
|
|
843
|
+
}];
|
|
844
|
+
}
|
|
845
|
+
var DEFAULT_INDENT = 2;
|
|
846
|
+
function detectIndent$1(raw) {
|
|
847
|
+
const firstBreak = raw.indexOf("\n");
|
|
848
|
+
if (firstBreak === -1) return DEFAULT_INDENT;
|
|
849
|
+
const rest = raw.slice(firstBreak + 1);
|
|
850
|
+
const width = rest.length - rest.trimStart().length;
|
|
851
|
+
return width > 0 ? width : DEFAULT_INDENT;
|
|
852
|
+
}
|
|
853
|
+
var SEGMENT_SEPARATOR = /&&|\|\||[;|]/u;
|
|
854
|
+
var ASSIGNMENT = /^[A-Za-z_]\w*=/u;
|
|
855
|
+
var WRAPPER_COMMANDS = /* @__PURE__ */ new Set([
|
|
856
|
+
"bunx",
|
|
857
|
+
"cross-env",
|
|
858
|
+
"dotenv",
|
|
859
|
+
"exec",
|
|
860
|
+
"npx",
|
|
861
|
+
"pnpm",
|
|
862
|
+
"run",
|
|
863
|
+
"yarn"
|
|
864
|
+
]);
|
|
865
|
+
function baseName(token) {
|
|
866
|
+
return token.slice(token.lastIndexOf("/") + 1).replace(/\.[mc]?js$/u, "");
|
|
867
|
+
}
|
|
868
|
+
function segmentsOf(command) {
|
|
869
|
+
return command.split(SEGMENT_SEPARATOR).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
870
|
+
}
|
|
871
|
+
function tokensOf(segment) {
|
|
872
|
+
return segment.split(/\s+/u).filter((token) => token.length > 0 && !ASSIGNMENT.test(token));
|
|
873
|
+
}
|
|
874
|
+
function commandWordOf(segment) {
|
|
875
|
+
return tokensOf(segment).map(baseName).find((word) => !word.startsWith("-") && !WRAPPER_COMMANDS.has(word)) ?? null;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Whether a package script really invokes the `eslint` binary, as opposed to delegating to another
|
|
879
|
+
* script that does. `npm run lint:es` is not an ESLint invocation; `NODE_OPTIONS=… eslint .` is.
|
|
880
|
+
* @example
|
|
881
|
+
* invokesBinary('npm run lint:es-ci -- --cache', 'eslint'); // false
|
|
882
|
+
*/
|
|
883
|
+
function invokesBinary(command, binary) {
|
|
884
|
+
return segmentsOf(command).some((segment) => commandWordOf(segment) === binary);
|
|
885
|
+
}
|
|
886
|
+
function oxlintPrefixFor(command) {
|
|
887
|
+
return tokensOf(command).includes("--fix") ? `${OXLINT_COMMAND} --fix` : OXLINT_COMMAND;
|
|
888
|
+
}
|
|
889
|
+
var CANONICAL_LINT_SCRIPT = "lint";
|
|
890
|
+
function referencedScripts(command, names) {
|
|
891
|
+
return tokensOf(command).map((token) => token.replace(/^["']|["']$/gu, "").replace(/^npm:/u, "")).filter((token) => names.has(token));
|
|
892
|
+
}
|
|
893
|
+
function collectReachable(scripts, names, script, seen) {
|
|
894
|
+
if (seen.has(script)) return;
|
|
895
|
+
seen.add(script);
|
|
896
|
+
referencedScripts(scripts[script] ?? "", names).forEach((next) => {
|
|
897
|
+
collectReachable(scripts, names, next, seen);
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
function reachableFromLint(scripts) {
|
|
901
|
+
const names = new Set(Object.keys(scripts));
|
|
902
|
+
const seen = /* @__PURE__ */ new Set();
|
|
903
|
+
if (names.has(CANONICAL_LINT_SCRIPT)) collectReachable(scripts, names, CANONICAL_LINT_SCRIPT, seen);
|
|
904
|
+
return seen;
|
|
905
|
+
}
|
|
906
|
+
function fixingRank(candidate) {
|
|
907
|
+
return tokensOf(candidate.previous).includes("--fix") ? 1 : 0;
|
|
908
|
+
}
|
|
909
|
+
function compareCandidates(left, right) {
|
|
910
|
+
return fixingRank(left) - fixingRank(right) || left.script.length - right.script.length || left.script.localeCompare(right.script);
|
|
911
|
+
}
|
|
912
|
+
function selectTarget(scripts, candidates) {
|
|
913
|
+
if (candidates.length === 0) return null;
|
|
914
|
+
const reachable = reachableFromLint(scripts);
|
|
915
|
+
const preferred = candidates.filter((candidate) => reachable.has(candidate.script));
|
|
916
|
+
return [...preferred.length > 0 ? preferred : candidates].sort(compareCandidates)[0];
|
|
917
|
+
}
|
|
918
|
+
function planChanges(scripts) {
|
|
919
|
+
const alreadyRunsOxlint = [];
|
|
920
|
+
const candidates = [];
|
|
921
|
+
Object.entries(scripts).forEach(([script, previous]) => {
|
|
922
|
+
if (!invokesBinary(previous, "eslint")) return;
|
|
923
|
+
if (invokesBinary(previous, "oxlint")) {
|
|
924
|
+
alreadyRunsOxlint.push(script);
|
|
699
925
|
return;
|
|
700
926
|
}
|
|
701
|
-
|
|
927
|
+
candidates.push({
|
|
928
|
+
script,
|
|
929
|
+
previous
|
|
930
|
+
});
|
|
931
|
+
});
|
|
932
|
+
const target = selectTarget(scripts, candidates);
|
|
933
|
+
if (!target) return {
|
|
934
|
+
changes: [],
|
|
935
|
+
alreadyRunsOxlint,
|
|
936
|
+
skipped: []
|
|
937
|
+
};
|
|
938
|
+
return {
|
|
939
|
+
changes: [{
|
|
940
|
+
script: target.script,
|
|
941
|
+
previous: target.previous,
|
|
942
|
+
next: `${oxlintPrefixFor(target.previous)} && ${target.previous}`
|
|
943
|
+
}],
|
|
944
|
+
alreadyRunsOxlint,
|
|
945
|
+
skipped: candidates.filter((candidate) => candidate.script !== target.script)
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Adds oxlint beside the one script that actually runs ESLint, by prepending to it rather than
|
|
950
|
+
* replacing it — so a composed `lint` such as `run-p lint:type lint:es lint:style` keeps its
|
|
951
|
+
* type-check and stylelint passes and only its `lint:es-ci` leaf is rewritten. Sibling entry points
|
|
952
|
+
* that also run ESLint (`lint:fix`, `lint:ci`) are reported through `skipped` rather than rewritten,
|
|
953
|
+
* so oxlint runs once per lint pass. Never rewrites a script it did not author: when nothing invokes
|
|
954
|
+
* `eslint` and a `lint` script already exists, the manifest is left alone and the conflict reported.
|
|
955
|
+
* @example
|
|
956
|
+
* setLintScript('{"scripts":{"lint":"eslint ."}}').changes;
|
|
957
|
+
* // [{ script: 'lint', previous: 'eslint .', next: 'oxlint --disable-nested-config && eslint .' }]
|
|
958
|
+
*/
|
|
959
|
+
function setLintScript(rawManifest) {
|
|
960
|
+
let parsed;
|
|
961
|
+
try {
|
|
962
|
+
parsed = JSON.parse(rawManifest);
|
|
963
|
+
} catch {
|
|
964
|
+
return {
|
|
965
|
+
manifest: rawManifest,
|
|
966
|
+
changed: false,
|
|
967
|
+
changes: [],
|
|
968
|
+
alreadyRunsOxlint: [],
|
|
969
|
+
skipped: []
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
const scripts = parsed.scripts ?? {};
|
|
973
|
+
const { changes, alreadyRunsOxlint, skipped } = planChanges(scripts);
|
|
974
|
+
const existingLint = scripts.lint;
|
|
975
|
+
if (changes.length === 0 && alreadyRunsOxlint.length === 0) {
|
|
976
|
+
if (existingLint !== void 0) return {
|
|
977
|
+
manifest: rawManifest,
|
|
978
|
+
changed: false,
|
|
979
|
+
changes: [],
|
|
980
|
+
alreadyRunsOxlint: [],
|
|
981
|
+
skipped: [],
|
|
982
|
+
unresolved: {
|
|
983
|
+
reason: "lint-script-runs-neither",
|
|
984
|
+
script: "lint",
|
|
985
|
+
previous: existingLint
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
changes.push({
|
|
989
|
+
script: "lint",
|
|
990
|
+
previous: "",
|
|
991
|
+
next: OXLINT_LINT_SCRIPT
|
|
992
|
+
});
|
|
702
993
|
}
|
|
994
|
+
if (changes.length === 0) return {
|
|
995
|
+
manifest: rawManifest,
|
|
996
|
+
changed: false,
|
|
997
|
+
changes: [],
|
|
998
|
+
alreadyRunsOxlint,
|
|
999
|
+
skipped
|
|
1000
|
+
};
|
|
1001
|
+
parsed.scripts = { ...scripts };
|
|
1002
|
+
changes.forEach((change) => {
|
|
1003
|
+
parsed.scripts[change.script] = change.next;
|
|
1004
|
+
});
|
|
1005
|
+
return {
|
|
1006
|
+
manifest: `${JSON.stringify(parsed, null, detectIndent$1(rawManifest))}\n`,
|
|
1007
|
+
changed: true,
|
|
1008
|
+
changes,
|
|
1009
|
+
alreadyRunsOxlint,
|
|
1010
|
+
skipped
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* The scripts that still run ESLint alone, for the CLI to print. Each line carries the oxlint prefix
|
|
1015
|
+
* that script would need, so a consumer who wants oxlint on a second entry point can paste it.
|
|
1016
|
+
* @example
|
|
1017
|
+
* formatSkippedLintScripts([{ script: 'lint:fix', previous: 'eslint . --fix' }]);
|
|
1018
|
+
* // 'lint:fix\n prefix with: oxlint --disable-nested-config --fix && '
|
|
1019
|
+
*/
|
|
1020
|
+
function formatSkippedLintScripts(skipped) {
|
|
1021
|
+
return skipped.map((candidate) => `${candidate.script}\n prefix with: ${oxlintPrefixFor(candidate.previous)} && `).join("\n\n");
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* One line per rewritten script, for the CLI to print. The previous command survives verbatim inside
|
|
1025
|
+
* the new one, so the report doubles as the recovery instructions.
|
|
1026
|
+
* @example
|
|
1027
|
+
* formatLintScriptChanges([{ script: 'lint:es-ci', previous: 'eslint .', next: 'oxlint && eslint .' }]);
|
|
1028
|
+
* // 'lint:es-ci\n before: eslint .\n after: oxlint && eslint .'
|
|
1029
|
+
*/
|
|
1030
|
+
function formatLintScriptChanges(changes) {
|
|
1031
|
+
return changes.map((change) => [
|
|
1032
|
+
change.script,
|
|
1033
|
+
` before: ${change.previous === "" ? "(no such script)" : change.previous}`,
|
|
1034
|
+
` after: ${change.next}`
|
|
1035
|
+
].join("\n")).join("\n\n");
|
|
703
1036
|
}
|
|
704
1037
|
//#endregion
|
|
705
1038
|
//#region src/cli/quote.ts
|
|
@@ -995,11 +1328,17 @@ function formatScope$1(scope, entries, baseIndent) {
|
|
|
995
1328
|
function renderUseInsiderBody(merged) {
|
|
996
1329
|
return [...merged.entries()].map(([scope, entries]) => formatScope$1(scope, entries, indent$1)).join("\n");
|
|
997
1330
|
}
|
|
998
|
-
|
|
1331
|
+
/**
|
|
1332
|
+
* Body of the generated `insider.lint.*` module — the one file that calls `useInsider()` and the
|
|
1333
|
+
* only one both linter configs import.
|
|
1334
|
+
* @example
|
|
1335
|
+
* renderLintModuleFile(answers); // "…export const { useInsiderESLint, useInsiderOXC } = useInsider({…"
|
|
1336
|
+
*/
|
|
1337
|
+
function renderLintModuleFile(answers) {
|
|
999
1338
|
return [
|
|
1000
1339
|
"import { useInsider } from '@useinsider/eslint-config';",
|
|
1001
1340
|
"",
|
|
1002
|
-
"export
|
|
1341
|
+
"export const { useInsiderESLint, useInsiderOXC } = useInsider({",
|
|
1003
1342
|
renderUseInsiderBody(mergeByScope(buildScopedConfigs(answers))),
|
|
1004
1343
|
"});",
|
|
1005
1344
|
""
|
|
@@ -1440,7 +1779,11 @@ var SCOPE_TO_DEPS = {
|
|
|
1440
1779
|
vitest: dependencies$11,
|
|
1441
1780
|
jsdocBasedTs: dependencies$3
|
|
1442
1781
|
};
|
|
1443
|
-
var ALL_PRESET_DEPS = [
|
|
1782
|
+
var ALL_PRESET_DEPS = [
|
|
1783
|
+
dependencies$14,
|
|
1784
|
+
dependencies$2,
|
|
1785
|
+
...Object.values(SCOPE_TO_DEPS)
|
|
1786
|
+
];
|
|
1444
1787
|
var PRESET_DEPENDENCY_KEYS = new Set(ALL_PRESET_DEPS.flatMap((record) => Object.keys(record)));
|
|
1445
1788
|
function isEslintRelatedDependency(name) {
|
|
1446
1789
|
if (SAFELIST.has(name)) return false;
|
|
@@ -1538,6 +1881,9 @@ function buildInstallList(scopes, manifest) {
|
|
|
1538
1881
|
const fallbacks = [];
|
|
1539
1882
|
appendSpec(specs, "@useinsider/eslint-config@latest");
|
|
1540
1883
|
appendSpec(specs, resolveSpec("eslint", manifest, fallbacks));
|
|
1884
|
+
Object.entries(dependencies$14).forEach(([packageName, shouldInstall]) => {
|
|
1885
|
+
if (shouldInstall) appendSpec(specs, resolveSpec(packageName, manifest, fallbacks));
|
|
1886
|
+
});
|
|
1541
1887
|
[...scopes].forEach((scope) => {
|
|
1542
1888
|
const record = SCOPE_TO_DEPS[scope];
|
|
1543
1889
|
if (!record) return;
|
|
@@ -1597,6 +1943,448 @@ function pruneConsumerPackageJson(cwd) {
|
|
|
1597
1943
|
};
|
|
1598
1944
|
}
|
|
1599
1945
|
//#endregion
|
|
1946
|
+
//#region src/cli/tsconfigFlag.ts
|
|
1947
|
+
var TS_EXTENSIONS_FLAG = "allowImportingTsExtensions";
|
|
1948
|
+
var TS_EXTENSIONS_SNIPPET = [
|
|
1949
|
+
"{",
|
|
1950
|
+
" \"compilerOptions\": {",
|
|
1951
|
+
` "${TS_EXTENSIONS_FLAG}": true`,
|
|
1952
|
+
" }",
|
|
1953
|
+
"}"
|
|
1954
|
+
].join("\n");
|
|
1955
|
+
var DEFAULT_INDENT_UNIT = " ";
|
|
1956
|
+
function acceptsTsExtensionsFlag(options) {
|
|
1957
|
+
return options.noEmit === true || options.emitDeclarationOnly === true || options.rewriteRelativeImportExtensions === true;
|
|
1958
|
+
}
|
|
1959
|
+
function parseTsconfig(path, raw) {
|
|
1960
|
+
const { config } = typescript.parseConfigFileTextToJson(path, raw);
|
|
1961
|
+
if (!config) return null;
|
|
1962
|
+
const parsed = typescript.parseJsonConfigFileContent(config, typescript.sys, (0, node_path.dirname)(path), void 0, path);
|
|
1963
|
+
return {
|
|
1964
|
+
enabled: parsed.options.allowImportingTsExtensions === true,
|
|
1965
|
+
acceptsFlag: acceptsTsExtensionsFlag(parsed.options),
|
|
1966
|
+
fileNames: parsed.fileNames,
|
|
1967
|
+
references: (parsed.projectReferences ?? []).map((reference) => reference.path)
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
function readTsconfigAt(path, governedFiles) {
|
|
1971
|
+
if (!(0, node_fs.existsSync)(path)) return null;
|
|
1972
|
+
try {
|
|
1973
|
+
const raw = (0, node_fs.readFileSync)(path, "utf8");
|
|
1974
|
+
const parsed = parseTsconfig(path, raw);
|
|
1975
|
+
if (!parsed) return {
|
|
1976
|
+
state: {
|
|
1977
|
+
path,
|
|
1978
|
+
raw,
|
|
1979
|
+
enabled: false,
|
|
1980
|
+
governs: false,
|
|
1981
|
+
acceptsFlag: false
|
|
1982
|
+
},
|
|
1983
|
+
references: []
|
|
1984
|
+
};
|
|
1985
|
+
const owned = new Set(parsed.fileNames.map((name) => typescript.sys.resolvePath(name)));
|
|
1986
|
+
const governs = governedFiles.some((file) => owned.has(typescript.sys.resolvePath(file)));
|
|
1987
|
+
return {
|
|
1988
|
+
state: {
|
|
1989
|
+
path,
|
|
1990
|
+
raw,
|
|
1991
|
+
enabled: parsed.enabled,
|
|
1992
|
+
governs,
|
|
1993
|
+
acceptsFlag: parsed.acceptsFlag
|
|
1994
|
+
},
|
|
1995
|
+
references: parsed.references
|
|
1996
|
+
};
|
|
1997
|
+
} catch {
|
|
1998
|
+
return null;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
function resolveReferencePath(reference) {
|
|
2002
|
+
if (!(0, node_fs.existsSync)(reference)) return reference;
|
|
2003
|
+
return typescript.sys.directoryExists(reference) ? (0, node_path.join)(reference, "tsconfig.json") : reference;
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Every tsconfig in the consumer's solution — the root plus each project reference — annotated with
|
|
2007
|
+
* whether it actually type-checks the generated lint files. A solution-style root has an empty file
|
|
2008
|
+
* set, so the project that governs `insider.lint.ts` is usually a referenced `tsconfig.node.json`,
|
|
2009
|
+
* and that is the one `tsc` reports TS5097 from.
|
|
2010
|
+
* @example
|
|
2011
|
+
* collectTsconfigStates('/repo', ['/repo/insider.lint.ts']).filter(state => state.governs);
|
|
2012
|
+
* // [{ path: '/repo/tsconfig.node.json', enabled: true, governs: true, ... }]
|
|
2013
|
+
*/
|
|
2014
|
+
function collectTsconfigStates(cwd, governedFiles = []) {
|
|
2015
|
+
const root = readTsconfigAt((0, node_path.join)(cwd, "tsconfig.json"), governedFiles);
|
|
2016
|
+
if (!root) return [];
|
|
2017
|
+
const seen = /* @__PURE__ */ new Set([root.state.path]);
|
|
2018
|
+
const states = [root.state];
|
|
2019
|
+
root.references.forEach((reference) => {
|
|
2020
|
+
const path = resolveReferencePath(reference);
|
|
2021
|
+
if (seen.has(path)) return;
|
|
2022
|
+
seen.add(path);
|
|
2023
|
+
const referenced = readTsconfigAt(path, governedFiles);
|
|
2024
|
+
if (referenced) states.push(referenced.state);
|
|
2025
|
+
});
|
|
2026
|
+
return states;
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* The tsconfigs that must gain `allowImportingTsExtensions`: the ones that type-check the generated
|
|
2030
|
+
* files and do not already resolve the flag to `true`. Empty when an applicable config already sets
|
|
2031
|
+
* it, and empty when no config type-checks the generated files at all — a tsconfig that never sees
|
|
2032
|
+
* them cannot raise TS5097, so editing it would only add a flag it has no use for.
|
|
2033
|
+
* @example
|
|
2034
|
+
* tsconfigsNeedingFlag('/repo', ['/repo/insider.lint.ts']).map(state => state.path); // []
|
|
2035
|
+
*/
|
|
2036
|
+
function tsconfigsNeedingFlag(cwd, governedFiles) {
|
|
2037
|
+
return collectTsconfigStates(cwd, governedFiles).filter((state) => state.governs && !state.enabled);
|
|
2038
|
+
}
|
|
2039
|
+
function rootObject(source) {
|
|
2040
|
+
const [statement] = source.statements;
|
|
2041
|
+
const expression = statement?.expression;
|
|
2042
|
+
return expression && typescript.isObjectLiteralExpression(expression) ? expression : null;
|
|
2043
|
+
}
|
|
2044
|
+
function findProperty$2(object, name) {
|
|
2045
|
+
return object.properties.find((property) => typescript.isPropertyAssignment(property) && typescript.isStringLiteral(property.name) && property.name.text === name);
|
|
2046
|
+
}
|
|
2047
|
+
function indentAt(raw, position) {
|
|
2048
|
+
const lineStart = raw.lastIndexOf("\n", position - 1) + 1;
|
|
2049
|
+
const prefix = raw.slice(lineStart, position);
|
|
2050
|
+
return prefix.trim().length === 0 ? prefix : "";
|
|
2051
|
+
}
|
|
2052
|
+
function detectIndentUnit(raw) {
|
|
2053
|
+
const match = /\n([\t ]+)\S/u.exec(raw);
|
|
2054
|
+
return match ? match[1] : DEFAULT_INDENT_UNIT;
|
|
2055
|
+
}
|
|
2056
|
+
function splice(raw, start, end, text) {
|
|
2057
|
+
return raw.slice(0, start) + text + raw.slice(end);
|
|
2058
|
+
}
|
|
2059
|
+
function setExistingFlag(raw, source, property) {
|
|
2060
|
+
return splice(raw, property.initializer.getStart(source), property.initializer.getEnd(), "true");
|
|
2061
|
+
}
|
|
2062
|
+
function insertIntoCompilerOptions(raw, source, compilerOptions, object) {
|
|
2063
|
+
const openBrace = object.getStart(source) + 1;
|
|
2064
|
+
const [first] = object.properties;
|
|
2065
|
+
if (first) return splice(raw, openBrace, openBrace, `\n${indentAt(raw, first.getStart(source)) || detectIndentUnit(raw).repeat(2)}"${TS_EXTENSIONS_FLAG}": true,`);
|
|
2066
|
+
const outer = indentAt(raw, compilerOptions.getStart(source));
|
|
2067
|
+
const unit = detectIndentUnit(raw);
|
|
2068
|
+
return splice(raw, openBrace, object.getEnd() - 1, `\n${outer}${unit}"${TS_EXTENSIONS_FLAG}": true\n${outer}`);
|
|
2069
|
+
}
|
|
2070
|
+
function insertCompilerOptions(raw, source, root) {
|
|
2071
|
+
const openBrace = root.getStart(source) + 1;
|
|
2072
|
+
const unit = detectIndentUnit(raw);
|
|
2073
|
+
const [first] = root.properties;
|
|
2074
|
+
const indent = first ? indentAt(raw, first.getStart(source)) || unit : unit;
|
|
2075
|
+
const block = `\n${indent}"compilerOptions": {\n${indent}${unit}"${TS_EXTENSIONS_FLAG}": true\n${indent}}`;
|
|
2076
|
+
if (first) return splice(raw, openBrace, openBrace, `${block},`);
|
|
2077
|
+
return splice(raw, openBrace, root.getEnd() - 1, `${block}\n`);
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Turns `allowImportingTsExtensions` on in a `tsconfig.json` source without reformatting it. The
|
|
2081
|
+
* file is edited through its JSON syntax tree rather than re-serialized, so comments, key order and
|
|
2082
|
+
* indentation survive.
|
|
2083
|
+
* @example
|
|
2084
|
+
* enableTsExtensionsFlag('{\n "compilerOptions": {\n "strict": true\n }\n}').changed; // true
|
|
2085
|
+
*/
|
|
2086
|
+
function enableTsExtensionsFlag(raw) {
|
|
2087
|
+
const source = typescript.parseJsonText("tsconfig.json", raw);
|
|
2088
|
+
const root = rootObject(source);
|
|
2089
|
+
if (!root) return {
|
|
2090
|
+
source: raw,
|
|
2091
|
+
changed: false
|
|
2092
|
+
};
|
|
2093
|
+
const compilerOptions = findProperty$2(root, "compilerOptions");
|
|
2094
|
+
if (!compilerOptions) return {
|
|
2095
|
+
source: insertCompilerOptions(raw, source, root),
|
|
2096
|
+
changed: true
|
|
2097
|
+
};
|
|
2098
|
+
if (!typescript.isObjectLiteralExpression(compilerOptions.initializer)) return {
|
|
2099
|
+
source: raw,
|
|
2100
|
+
changed: false
|
|
2101
|
+
};
|
|
2102
|
+
const existing = findProperty$2(compilerOptions.initializer, TS_EXTENSIONS_FLAG);
|
|
2103
|
+
if (existing) {
|
|
2104
|
+
if (existing.initializer.kind === typescript.SyntaxKind.TrueKeyword) return {
|
|
2105
|
+
source: raw,
|
|
2106
|
+
changed: false
|
|
2107
|
+
};
|
|
2108
|
+
return {
|
|
2109
|
+
source: setExistingFlag(raw, source, existing),
|
|
2110
|
+
changed: true
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
return {
|
|
2114
|
+
source: insertIntoCompilerOptions(raw, source, compilerOptions, compilerOptions.initializer),
|
|
2115
|
+
changed: true
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
function noteManualStep(clack, lintModule) {
|
|
2119
|
+
if (!clack.note) return;
|
|
2120
|
+
clack.note(`\`eslint.config.*\` imports \`./${lintModule}\` by its real extension, which \`tsc\` rejects with TS5097 unless \`${TS_EXTENSIONS_FLAG}\` is on. Add this to your tsconfig.json:\n\n${TS_EXTENSIONS_SNIPPET}`, "tsconfig.json not updated");
|
|
2121
|
+
}
|
|
2122
|
+
function noteEmitConflict(clack, path, lintModule) {
|
|
2123
|
+
if (!clack.note) return;
|
|
2124
|
+
clack.note(`${path} type-checks \`./${lintModule}\`, so \`tsc\` needs \`${TS_EXTENSIONS_FLAG}\` there — but that project emits, and TypeScript rejects the pair with TS5096. Set \`noEmit\`, \`emitDeclarationOnly\` or \`rewriteRelativeImportExtensions\` on it first, then add:\n\n${TS_EXTENSIONS_SNIPPET}`, "tsconfig.json not updated");
|
|
2125
|
+
}
|
|
2126
|
+
async function confirmFlag(clack, path) {
|
|
2127
|
+
const result = await clack.confirm({
|
|
2128
|
+
message: `Set \`${TS_EXTENSIONS_FLAG}: true\` in ${path}? Without it \`tsc\` fails with TS5097 on the generated config files.`,
|
|
2129
|
+
initialValue: true
|
|
2130
|
+
});
|
|
2131
|
+
return !clack.isCancel(result) && result === true;
|
|
2132
|
+
}
|
|
2133
|
+
function governedFilesOf(input) {
|
|
2134
|
+
return [input.lintModule, input.eslintConfig].filter((name) => Boolean(name)).map((name) => (0, node_path.join)(input.cwd, name));
|
|
2135
|
+
}
|
|
2136
|
+
async function applyToOne(clack, state, input) {
|
|
2137
|
+
if (!state.acceptsFlag) {
|
|
2138
|
+
noteEmitConflict(clack, state.path, input.lintModule);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
if (!(input.autoConfirm || await confirmFlag(clack, state.path))) {
|
|
2142
|
+
noteManualStep(clack, input.lintModule);
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
const result = enableTsExtensionsFlag(state.raw);
|
|
2146
|
+
if (!result.changed) {
|
|
2147
|
+
noteManualStep(clack, input.lintModule);
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
(0, node_fs.writeFileSync)(state.path, result.source, "utf8");
|
|
2151
|
+
if (clack.note) clack.note(`Set \`${TS_EXTENSIONS_FLAG}: true\` in ${state.path}.`, "Updated tsconfig.json");
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Offers to set `allowImportingTsExtensions` on every tsconfig that actually type-checks the
|
|
2155
|
+
* generated lint files, and prints the exact snippet when an offer is declined or when the project
|
|
2156
|
+
* emits — in which case writing the flag would trade TS5097 for TS5096. A no-op when there is no
|
|
2157
|
+
* `tsconfig.json`, when no config governs the generated files, or when an applicable config already
|
|
2158
|
+
* resolves the flag to `true` — through its own `compilerOptions`, through `extends`, or by being a
|
|
2159
|
+
* project reference that already sets it.
|
|
2160
|
+
* @example
|
|
2161
|
+
* await applyTsExtensionsFlag(clack, { cwd: '/repo', lintModule: 'insider.lint.ts' });
|
|
2162
|
+
*/
|
|
2163
|
+
async function applyTsExtensionsFlag(clack, input) {
|
|
2164
|
+
await tsconfigsNeedingFlag(input.cwd, governedFilesOf(input)).reduce(async (previous, state) => {
|
|
2165
|
+
await previous;
|
|
2166
|
+
return applyToOne(clack, state, input);
|
|
2167
|
+
}, Promise.resolve());
|
|
2168
|
+
}
|
|
2169
|
+
//#endregion
|
|
2170
|
+
//#region src/cli/migrate/disableDirectives.ts
|
|
2171
|
+
var DIRECTIVE_HEAD = /^\s*eslint-disable(-next-line|-line)?(?![\w-])/u;
|
|
2172
|
+
var SCRIPT_BLOCK = /<script\b[^>]*>([\s\S]*?)<\/script>/giu;
|
|
2173
|
+
var SFC_EXTENSIONS = [
|
|
2174
|
+
".astro",
|
|
2175
|
+
".svelte",
|
|
2176
|
+
".vue"
|
|
2177
|
+
];
|
|
2178
|
+
var JSX_EXTENSIONS = [
|
|
2179
|
+
".cjs",
|
|
2180
|
+
".js",
|
|
2181
|
+
".jsx",
|
|
2182
|
+
".mjs",
|
|
2183
|
+
".tsx"
|
|
2184
|
+
];
|
|
2185
|
+
var LINTED_EXTENSIONS = [
|
|
2186
|
+
".astro",
|
|
2187
|
+
".cjs",
|
|
2188
|
+
".cts",
|
|
2189
|
+
".js",
|
|
2190
|
+
".jsx",
|
|
2191
|
+
".mjs",
|
|
2192
|
+
".mts",
|
|
2193
|
+
".svelte",
|
|
2194
|
+
".ts",
|
|
2195
|
+
".tsx",
|
|
2196
|
+
".vue"
|
|
2197
|
+
];
|
|
2198
|
+
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
2199
|
+
".git",
|
|
2200
|
+
"coverage",
|
|
2201
|
+
"dist",
|
|
2202
|
+
"build",
|
|
2203
|
+
"node_modules",
|
|
2204
|
+
"out",
|
|
2205
|
+
"vendor"
|
|
2206
|
+
]);
|
|
2207
|
+
function parseDirectiveBody(body) {
|
|
2208
|
+
const [ruleList, ...reasonParts] = body.split("--");
|
|
2209
|
+
return {
|
|
2210
|
+
rules: ruleList.split(",").map((rule) => rule.trim()).filter((rule) => rule.length > 0),
|
|
2211
|
+
reason: reasonParts.join("--").trim()
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
function formatDirective(owner, block, kind, rules, reason) {
|
|
2215
|
+
const text = `${owner}-disable${kind} ${reason ? `${rules.join(", ")} -- ${reason}` : rules.join(", ")}`;
|
|
2216
|
+
return block ? `/* ${text} */` : `// ${text}`;
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Whether the split hands this rule to oxlint for the given file, so an `eslint-disable` comment
|
|
2220
|
+
* naming it would read as an unused directive to ESLint.
|
|
2221
|
+
* @example
|
|
2222
|
+
* movesToOxlint('no-console', '.ts'); // true
|
|
2223
|
+
* movesToOxlint('@typescript-eslint/no-unused-vars', '.vue'); // false — ESLint still runs it in SFCs
|
|
2224
|
+
*/
|
|
2225
|
+
function movesToOxlint(rule, fileExtension) {
|
|
2226
|
+
if (!require_oxlintOwnership.isOxlintOwned(rule)) return false;
|
|
2227
|
+
return !(SFC_EXTENSIONS.includes(fileExtension) && require_oxlintOwnership.isVueUnsafeRule(rule));
|
|
2228
|
+
}
|
|
2229
|
+
function toSpans(comments, offset) {
|
|
2230
|
+
return comments.map((comment) => ({
|
|
2231
|
+
start: comment.range[0] + offset,
|
|
2232
|
+
end: comment.range[1] + offset,
|
|
2233
|
+
block: comment.type === _typescript_eslint_types.AST_TOKEN_TYPES.Block,
|
|
2234
|
+
value: comment.value
|
|
2235
|
+
}));
|
|
2236
|
+
}
|
|
2237
|
+
function parseComments(source, jsx) {
|
|
2238
|
+
try {
|
|
2239
|
+
return (0, _typescript_eslint_parser.parse)(source, {
|
|
2240
|
+
comment: true,
|
|
2241
|
+
jsx,
|
|
2242
|
+
range: true
|
|
2243
|
+
}).comments ?? [];
|
|
2244
|
+
} catch {
|
|
2245
|
+
return null;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
function collectFromScript(source, jsx, offset) {
|
|
2249
|
+
const comments = parseComments(source, jsx) ?? parseComments(source, !jsx);
|
|
2250
|
+
return comments ? toSpans(comments, offset) : [];
|
|
2251
|
+
}
|
|
2252
|
+
var CLOSING_SCRIPT_TAG_LENGTH = 9;
|
|
2253
|
+
function collectSfcComments(source, jsx) {
|
|
2254
|
+
return [...source.matchAll(SCRIPT_BLOCK)].flatMap((match) => {
|
|
2255
|
+
const [whole, body] = match;
|
|
2256
|
+
return collectFromScript(body, jsx, (match.index ?? 0) + whole.length - body.length - CLOSING_SCRIPT_TAG_LENGTH);
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Every real comment node in the file, with ranges relative to the whole source. Only comments are
|
|
2261
|
+
* returned, so a string or template literal that happens to spell out a directive is invisible here.
|
|
2262
|
+
* @example
|
|
2263
|
+
* collectComments("const label = '// eslint-disable-next-line';\n", '.ts'); // []
|
|
2264
|
+
*/
|
|
2265
|
+
function collectComments(source, fileExtension) {
|
|
2266
|
+
const jsx = JSX_EXTENSIONS.includes(fileExtension);
|
|
2267
|
+
if (SFC_EXTENSIONS.includes(fileExtension)) return collectSfcComments(source, jsx);
|
|
2268
|
+
return collectFromScript(source, jsx, 0);
|
|
2269
|
+
}
|
|
2270
|
+
function startsItsOwnLine(source, start) {
|
|
2271
|
+
const lineStart = source.lastIndexOf("\n", start - 1) + 1;
|
|
2272
|
+
return source.slice(lineStart, start).trim().length === 0;
|
|
2273
|
+
}
|
|
2274
|
+
function splitLayout(source, span, kind) {
|
|
2275
|
+
if (kind !== "" || !startsItsOwnLine(source, span.start)) return {
|
|
2276
|
+
block: true,
|
|
2277
|
+
separator: " "
|
|
2278
|
+
};
|
|
2279
|
+
const indent = " ".repeat(span.start - (source.lastIndexOf("\n", span.start - 1) + 1));
|
|
2280
|
+
return {
|
|
2281
|
+
block: span.block,
|
|
2282
|
+
separator: `\n${indent}`
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
function replacementFor(source, span, fileExtension) {
|
|
2286
|
+
const head = DIRECTIVE_HEAD.exec(span.value);
|
|
2287
|
+
if (!head) return null;
|
|
2288
|
+
const kind = head[1] ?? "";
|
|
2289
|
+
const { rules, reason } = parseDirectiveBody(span.value.slice(head[0].length));
|
|
2290
|
+
if (rules.length === 0) return null;
|
|
2291
|
+
const moved = rules.filter((rule) => movesToOxlint(rule, fileExtension));
|
|
2292
|
+
if (moved.length === 0) return null;
|
|
2293
|
+
const kept = rules.filter((rule) => !moved.includes(rule));
|
|
2294
|
+
if (kept.length === 0) return {
|
|
2295
|
+
start: span.start,
|
|
2296
|
+
end: span.end,
|
|
2297
|
+
text: formatDirective("oxlint", span.block, kind, moved, reason)
|
|
2298
|
+
};
|
|
2299
|
+
const { block, separator } = splitLayout(source, span, kind);
|
|
2300
|
+
const text = formatDirective("oxlint", block, kind, moved, reason) + separator + formatDirective("eslint", block, kind, kept, reason);
|
|
2301
|
+
return {
|
|
2302
|
+
start: span.start,
|
|
2303
|
+
end: span.end,
|
|
2304
|
+
text
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Re-points the rules oxlint now owns onto an `oxlint-disable` comment, leaving the ESLint-owned
|
|
2309
|
+
* ones behind. Only real comment nodes are rewritten, so a string or template literal spelling out
|
|
2310
|
+
* a directive is left alone.
|
|
2311
|
+
* @example
|
|
2312
|
+
* rewriteDirectives('// <eslint>-disable-next-line no-console\nlog();', '.ts').source;
|
|
2313
|
+
* // '// oxlint-disable-next-line no-console\nlog();'
|
|
2314
|
+
*/
|
|
2315
|
+
function rewriteDirectives(source, fileExtension) {
|
|
2316
|
+
const replacements = collectComments(source, fileExtension).map((span) => replacementFor(source, span, fileExtension)).filter((replacement) => replacement !== null).sort((left, right) => right.start - left.start);
|
|
2317
|
+
if (replacements.length === 0) return {
|
|
2318
|
+
source,
|
|
2319
|
+
changed: false
|
|
2320
|
+
};
|
|
2321
|
+
return {
|
|
2322
|
+
source: replacements.reduce((accumulator, replacement) => accumulator.slice(0, replacement.start) + replacement.text + accumulator.slice(replacement.end), source),
|
|
2323
|
+
changed: true
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
function isLintedFile(filePath) {
|
|
2327
|
+
return LINTED_EXTENSIONS.includes((0, node_path.extname)(filePath));
|
|
2328
|
+
}
|
|
2329
|
+
function readEntries(directory) {
|
|
2330
|
+
try {
|
|
2331
|
+
return (0, node_fs.readdirSync)(directory, { withFileTypes: true });
|
|
2332
|
+
} catch {
|
|
2333
|
+
return [];
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
function realPathOf(path) {
|
|
2337
|
+
try {
|
|
2338
|
+
return (0, node_fs.realpathSync)(path);
|
|
2339
|
+
} catch {
|
|
2340
|
+
return null;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function collectFiles(directory, accumulator, visited) {
|
|
2344
|
+
const real = realPathOf(directory);
|
|
2345
|
+
if (real === null || visited.has(real)) return;
|
|
2346
|
+
visited.add(real);
|
|
2347
|
+
readEntries(directory).forEach((entry) => {
|
|
2348
|
+
if (SKIPPED_DIRECTORIES.has(entry.name)) return;
|
|
2349
|
+
const full = (0, node_path.join)(directory, entry.name);
|
|
2350
|
+
if (entry.isDirectory()) {
|
|
2351
|
+
collectFiles(full, accumulator, visited);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
if (entry.isFile() && isLintedFile(full)) accumulator.push(full);
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Every file whose `eslint-disable` comments would be re-pointed, and the rewritten source, without
|
|
2359
|
+
* touching disk. The directive rewrite is the largest diff a migration produces, so the count is
|
|
2360
|
+
* reported — and confirmed — before anything is written.
|
|
2361
|
+
* @example
|
|
2362
|
+
* previewDisableDirectives('/repo').length; // 131
|
|
2363
|
+
*/
|
|
2364
|
+
function previewDisableDirectives(cwd) {
|
|
2365
|
+
const files = [];
|
|
2366
|
+
collectFiles(cwd, files, /* @__PURE__ */ new Set());
|
|
2367
|
+
return files.reduce((candidates, filePath) => {
|
|
2368
|
+
const result = rewriteDirectives((0, node_fs.readFileSync)(filePath, "utf8"), (0, node_path.extname)(filePath));
|
|
2369
|
+
if (!result.changed) return candidates;
|
|
2370
|
+
return [...candidates, {
|
|
2371
|
+
path: filePath,
|
|
2372
|
+
source: result.source
|
|
2373
|
+
}];
|
|
2374
|
+
}, []);
|
|
2375
|
+
}
|
|
2376
|
+
/**
|
|
2377
|
+
* Applies {@link rewriteDirectives} across a consumer tree, skipping build output and dependencies.
|
|
2378
|
+
* @example
|
|
2379
|
+
* rewriteDisableDirectives('/repo').rewritten; // ['/repo/src/logger.ts']
|
|
2380
|
+
*/
|
|
2381
|
+
function rewriteDisableDirectives(cwd, candidates = previewDisableDirectives(cwd)) {
|
|
2382
|
+
candidates.forEach((candidate) => {
|
|
2383
|
+
(0, node_fs.writeFileSync)(candidate.path, candidate.source, "utf8");
|
|
2384
|
+
});
|
|
2385
|
+
return { rewritten: candidates.map((candidate) => candidate.path) };
|
|
2386
|
+
}
|
|
2387
|
+
//#endregion
|
|
1600
2388
|
//#region src/cli/migrate/stringEntry.ts
|
|
1601
2389
|
function toEntries(values) {
|
|
1602
2390
|
return values.map((value) => ({ value }));
|
|
@@ -2214,7 +3002,7 @@ function appendScope$1(config, scope, entry) {
|
|
|
2214
3002
|
}
|
|
2215
3003
|
function mergeGlobals(base, extra) {
|
|
2216
3004
|
if (!base && !extra) return;
|
|
2217
|
-
const merged = { ...base
|
|
3005
|
+
const merged = { ...base };
|
|
2218
3006
|
if (extra) Object.entries(extra).forEach(([key, value]) => {
|
|
2219
3007
|
if (!(key in merged)) merged[key] = value;
|
|
2220
3008
|
});
|
|
@@ -3539,11 +4327,11 @@ function renderV2(plan) {
|
|
|
3539
4327
|
parts.push("");
|
|
3540
4328
|
if (plan.useInsiderLeadingComment !== void 0) parts.push(plan.useInsiderLeadingComment);
|
|
3541
4329
|
if (!hasAnyContent(plan)) {
|
|
3542
|
-
parts.push("export
|
|
4330
|
+
parts.push("export const { useInsiderESLint, useInsiderOXC } = useInsider({});");
|
|
3543
4331
|
parts.push("");
|
|
3544
4332
|
return pruneUnusedImports(parts.join("\n"));
|
|
3545
4333
|
}
|
|
3546
|
-
parts.push("export
|
|
4334
|
+
parts.push("export const { useInsiderESLint, useInsiderOXC } = useInsider({");
|
|
3547
4335
|
parts.push(renderConfigBody(plan));
|
|
3548
4336
|
parts.push("});");
|
|
3549
4337
|
parts.push("");
|
|
@@ -3903,15 +4691,56 @@ function formatWarnings(warnings) {
|
|
|
3903
4691
|
}
|
|
3904
4692
|
//#endregion
|
|
3905
4693
|
//#region src/cli/migrate/writer.ts
|
|
4694
|
+
function writeLayout(directory, lintModuleSource) {
|
|
4695
|
+
const layout = resolveLintLayout(directory);
|
|
4696
|
+
const lintModulePath = (0, node_path.join)(directory, layout.lintModule);
|
|
4697
|
+
(0, node_fs.writeFileSync)(lintModulePath, lintModuleSource, "utf8");
|
|
4698
|
+
const writtenPaths = [lintModulePath];
|
|
4699
|
+
renderLintLayoutFiles(layout).forEach((file) => {
|
|
4700
|
+
const path = (0, node_path.join)(directory, file.filename);
|
|
4701
|
+
(0, node_fs.writeFileSync)(path, file.source, "utf8");
|
|
4702
|
+
writtenPaths.push(path);
|
|
4703
|
+
});
|
|
4704
|
+
return {
|
|
4705
|
+
eslintConfigPath: (0, node_path.join)(directory, layout.eslintConfig),
|
|
4706
|
+
lintModulePath,
|
|
4707
|
+
writtenPaths,
|
|
4708
|
+
replacedEslintConfigPaths: layout.replacedEslintConfigs.map((name) => (0, node_path.join)(directory, name))
|
|
4709
|
+
};
|
|
4710
|
+
}
|
|
4711
|
+
function withoutDuplicates(paths, keep) {
|
|
4712
|
+
return [...new Set(paths.filter((path) => path !== keep))];
|
|
4713
|
+
}
|
|
4714
|
+
/**
|
|
4715
|
+
* Writes the three-file layout beside an existing flat config: the rendered `useInsider()` call
|
|
4716
|
+
* becomes `insider.lint.*`, and `eslint.config.*` / `oxlint.config.*` become thin re-exports. A
|
|
4717
|
+
* JavaScript-extension source config is superseded by the TypeScript one and nominated for deletion.
|
|
4718
|
+
* @example
|
|
4719
|
+
* writeMigration('/repo/eslint.config.js', source).deletedSourceCandidates; // ['/repo/eslint.config.js']
|
|
4720
|
+
*/
|
|
3906
4721
|
function writeMigration(targetPath, newSource) {
|
|
3907
|
-
(0,
|
|
3908
|
-
return {
|
|
4722
|
+
const result = writeLayout((0, node_path.dirname)(targetPath), newSource);
|
|
4723
|
+
return {
|
|
4724
|
+
targetPath: result.eslintConfigPath,
|
|
4725
|
+
lintModulePath: result.lintModulePath,
|
|
4726
|
+
writtenPaths: result.writtenPaths,
|
|
4727
|
+
deletedSourceCandidates: withoutDuplicates([...result.replacedEslintConfigPaths, targetPath], result.eslintConfigPath)
|
|
4728
|
+
};
|
|
3909
4729
|
}
|
|
4730
|
+
/**
|
|
4731
|
+
* Same as {@link writeMigration}, but for a legacy `.eslintrc*` source that has to be replaced by a
|
|
4732
|
+
* differently-named flat config.
|
|
4733
|
+
* @example
|
|
4734
|
+
* writeMigrationLegacy('/repo/.eslintrc.cjs', '/repo/eslint.config.mts', src).deletedSourceCandidates;
|
|
4735
|
+
* // ['/repo/.eslintrc.cjs']
|
|
4736
|
+
*/
|
|
3910
4737
|
function writeMigrationLegacy(sourcePath, writePath, newSource) {
|
|
3911
|
-
(0,
|
|
4738
|
+
const result = writeLayout((0, node_path.dirname)(writePath), newSource);
|
|
3912
4739
|
return {
|
|
3913
|
-
targetPath:
|
|
3914
|
-
|
|
4740
|
+
targetPath: result.eslintConfigPath,
|
|
4741
|
+
lintModulePath: result.lintModulePath,
|
|
4742
|
+
writtenPaths: result.writtenPaths,
|
|
4743
|
+
deletedSourceCandidates: withoutDuplicates([...result.replacedEslintConfigPaths, sourcePath], result.eslintConfigPath)
|
|
3915
4744
|
};
|
|
3916
4745
|
}
|
|
3917
4746
|
//#endregion
|
|
@@ -3986,26 +4815,14 @@ function summary(plan, format) {
|
|
|
3986
4815
|
function planFromSource(source, filePath, format) {
|
|
3987
4816
|
if (format === "flat") {
|
|
3988
4817
|
const parseResult = parseV1Config(source, filePath);
|
|
3989
|
-
|
|
3990
|
-
const plan = transformToV2(parseResult);
|
|
3991
|
-
return {
|
|
3992
|
-
plan,
|
|
3993
|
-
rendered: renderV2(plan)
|
|
3994
|
-
};
|
|
4818
|
+
return isV1Shape(parseResult) ? transformToV2(parseResult) : null;
|
|
3995
4819
|
}
|
|
3996
4820
|
const parseResult = parseEslintrc(source, filePath);
|
|
3997
|
-
|
|
3998
|
-
const plan = transformEslintrcToV2(parseResult);
|
|
3999
|
-
return {
|
|
4000
|
-
plan,
|
|
4001
|
-
rendered: renderV2(plan)
|
|
4002
|
-
};
|
|
4821
|
+
return parseResult.found ? transformEslintrcToV2(parseResult) : null;
|
|
4003
4822
|
}
|
|
4004
|
-
function targetWritePath(cwd, sourcePath
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
const dir = sourcePath.slice(0, sourcePath.length - base.length);
|
|
4008
|
-
return (0, node_path.join)(dir.length > 0 ? dir : cwd, "eslint.config.mjs");
|
|
4823
|
+
function targetWritePath(cwd, sourcePath) {
|
|
4824
|
+
const directory = (0, node_path.dirname)(sourcePath) || cwd;
|
|
4825
|
+
return (0, node_path.join)(directory, resolveLintLayout(directory).eslintConfig);
|
|
4009
4826
|
}
|
|
4010
4827
|
function parseEslintIgnoreContents(raw) {
|
|
4011
4828
|
return raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
@@ -4039,6 +4856,17 @@ function describeDepChanges(depChanges) {
|
|
|
4039
4856
|
lines.push(`- Install (${depChanges.install.specs.length}): ${depChanges.install.specs.join(", ")}`);
|
|
4040
4857
|
return lines.join("\n");
|
|
4041
4858
|
}
|
|
4859
|
+
async function confirmGuidedStart(clack, sourcePath) {
|
|
4860
|
+
const result = await clack.confirm({
|
|
4861
|
+
message: `${(0, node_path.basename)(sourcePath)} still uses the v3 single-file layout. Migrate it now?`,
|
|
4862
|
+
initialValue: true
|
|
4863
|
+
});
|
|
4864
|
+
return !clack.isCancel(result) && result === true;
|
|
4865
|
+
}
|
|
4866
|
+
function noteConfigRename(clack, sourcePath, writePath) {
|
|
4867
|
+
if (!clack.note || (0, node_path.basename)(sourcePath) === (0, node_path.basename)(writePath)) return;
|
|
4868
|
+
clack.note(`${(0, node_path.basename)(sourcePath)} -> ${(0, node_path.basename)(writePath)}\n\nESLint loads a JavaScript config through Node, so importing the TypeScript lint module from one needs unflagged type stripping (Node >= 22.18) and fails on Node 20. A TypeScript config goes through jiti and loads on every supported version.`, "Renaming the flat config");
|
|
4869
|
+
}
|
|
4042
4870
|
async function confirmWrite(clack, writePath, initial = true) {
|
|
4043
4871
|
const message = `Write migrated config to ${writePath}?`;
|
|
4044
4872
|
const result = await clack.confirm({
|
|
@@ -4080,17 +4908,19 @@ function emitSummary(clack, plan, format) {
|
|
|
4080
4908
|
if (plan.warnings.length > 0 && clack.note) clack.note(formatWarnings(plan.warnings), "Warnings");
|
|
4081
4909
|
}
|
|
4082
4910
|
function writeAndAnnounce(clack, targetPath, writePath, rendered, format) {
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4911
|
+
const result = format === "eslintrc" ? writeMigrationLegacy(targetPath, writePath, rendered) : writeMigration(targetPath, rendered);
|
|
4912
|
+
clack.outro(`Wrote ${result.writtenPaths.join(", ")}`);
|
|
4913
|
+
return { deletedSourceCandidates: result.deletedSourceCandidates };
|
|
4914
|
+
}
|
|
4915
|
+
function supersededFlatConfigs(sourcePath, writePath) {
|
|
4916
|
+
const directory = (0, node_path.dirname)(writePath);
|
|
4917
|
+
const { replacedEslintConfigs } = resolveLintLayout(directory);
|
|
4918
|
+
const candidates = [...replacedEslintConfigs.map((filename) => (0, node_path.join)(directory, filename)), writePath];
|
|
4919
|
+
return [...new Set(candidates.filter((path) => path !== sourcePath && (0, node_fs.existsSync)(path)))];
|
|
4920
|
+
}
|
|
4921
|
+
async function confirmOverwriteExistingOutput(clack, superseded, sourcePath, initial = false) {
|
|
4922
|
+
const names = superseded.map((path) => (0, node_path.basename)(path));
|
|
4923
|
+
const message = `${names.length === 1 ? `An \`${names[0]}\` already exists` : `\`${names.join("`, `")}\` already exist`}. Overwrite + delete \`${(0, node_path.basename)(sourcePath)}\`?`;
|
|
4094
4924
|
const result = await clack.confirm({
|
|
4095
4925
|
message,
|
|
4096
4926
|
initialValue: initial
|
|
@@ -4126,10 +4956,9 @@ function resolveSource(input, clack) {
|
|
|
4126
4956
|
};
|
|
4127
4957
|
}
|
|
4128
4958
|
function prepareMigration(resolved, input) {
|
|
4129
|
-
const
|
|
4130
|
-
if (!
|
|
4131
|
-
const
|
|
4132
|
-
const writePath = targetWritePath(input.cwd, resolved.targetPath, resolved.format);
|
|
4959
|
+
const plan = planFromSource(resolved.source, resolved.targetPath, resolved.format);
|
|
4960
|
+
if (!plan) return null;
|
|
4961
|
+
const writePath = targetWritePath(input.cwd, resolved.targetPath);
|
|
4133
4962
|
const eslintIgnore = findEslintIgnore(resolved.targetPath);
|
|
4134
4963
|
if (eslintIgnore && eslintIgnore.patterns.length > 0) mergeIgnorePatterns(plan, eslintIgnore.patterns);
|
|
4135
4964
|
return {
|
|
@@ -4140,8 +4969,9 @@ function prepareMigration(resolved, input) {
|
|
|
4140
4969
|
};
|
|
4141
4970
|
}
|
|
4142
4971
|
async function runConfirmations(clack, input, targetPath, writePath, previous) {
|
|
4143
|
-
|
|
4144
|
-
|
|
4972
|
+
const superseded = supersededFlatConfigs(targetPath, writePath);
|
|
4973
|
+
if (superseded.length > 0) {
|
|
4974
|
+
if (!await confirmOverwriteExistingOutput(clack, superseded, targetPath, previous?.overwriteExisting ?? false)) {
|
|
4145
4975
|
clack.outro("Aborted: existing output kept.");
|
|
4146
4976
|
return false;
|
|
4147
4977
|
}
|
|
@@ -4199,7 +5029,7 @@ function resolveInstallCwd(depSummary, input) {
|
|
|
4199
5029
|
return input.cwd;
|
|
4200
5030
|
}
|
|
4201
5031
|
async function runInstaller(clack, depSummary, input) {
|
|
4202
|
-
|
|
5032
|
+
return installWithRetry({
|
|
4203
5033
|
deps: depSummary.install.specs,
|
|
4204
5034
|
cwd: resolveInstallCwd(depSummary, input),
|
|
4205
5035
|
autoConfirm: Boolean(input.yes),
|
|
@@ -4211,26 +5041,73 @@ async function runInstaller(clack, depSummary, input) {
|
|
|
4211
5041
|
async function applyDepChanges(clack, depSummary, input) {
|
|
4212
5042
|
if (!depSummary.packageJsonPath) {
|
|
4213
5043
|
if (clack.note) clack.note("No package.json found alongside the migrated config — skipping dependency updates.", "Notice");
|
|
4214
|
-
return;
|
|
5044
|
+
return true;
|
|
4215
5045
|
}
|
|
4216
5046
|
persistPrunedManifest(depSummary);
|
|
4217
5047
|
if (input.noInstall) {
|
|
4218
5048
|
emitInstallNextSteps(clack, depSummary, input.cwd);
|
|
4219
|
-
return;
|
|
5049
|
+
return true;
|
|
4220
5050
|
}
|
|
4221
5051
|
if (!input.yes) {
|
|
4222
5052
|
if (!await confirmInstall(clack, depSummary)) {
|
|
4223
5053
|
emitInstallNextSteps(clack, depSummary, input.cwd);
|
|
4224
|
-
return;
|
|
5054
|
+
return true;
|
|
4225
5055
|
}
|
|
4226
5056
|
}
|
|
4227
5057
|
noteFallbacks(clack, depSummary);
|
|
4228
|
-
await runInstaller(clack, depSummary, input)
|
|
5058
|
+
if (!(await runInstaller(clack, depSummary, input)).installed) {
|
|
5059
|
+
emitInstallNextSteps(clack, depSummary, input.cwd);
|
|
5060
|
+
return false;
|
|
5061
|
+
}
|
|
5062
|
+
return true;
|
|
4229
5063
|
}
|
|
4230
5064
|
function performWriteAndCleanup(clack, resolved, prepared) {
|
|
4231
5065
|
const outcome = writeAndAnnounce(clack, resolved.targetPath, prepared.writePath, prepared.rendered, resolved.format);
|
|
4232
5066
|
if (prepared.eslintIgnore) safeUnlink(prepared.eslintIgnore.path, clack, ".eslintignore");
|
|
4233
|
-
|
|
5067
|
+
outcome.deletedSourceCandidates.forEach((path) => {
|
|
5068
|
+
safeUnlink(path, clack, "superseded config");
|
|
5069
|
+
});
|
|
5070
|
+
}
|
|
5071
|
+
function noteLintScriptOutcome$1(clack, result) {
|
|
5072
|
+
if (!clack.note) return;
|
|
5073
|
+
if (result.unresolved) {
|
|
5074
|
+
clack.note(`Left \`${result.unresolved.script}\` untouched — it runs neither eslint nor oxlint:\n\n ${result.unresolved.previous}\n\nAdd \`${OXLINT_LINT_SCRIPT}\` yourself, next to whatever invokes ESLint.`, "package.json script not changed");
|
|
5075
|
+
return;
|
|
5076
|
+
}
|
|
5077
|
+
if (result.alreadyRunsOxlint.length > 0 && result.changes.length === 0) {
|
|
5078
|
+
clack.note(`\`${result.alreadyRunsOxlint.join("`, `")}\` already run oxlint — left alone.`, "package.json scripts unchanged");
|
|
5079
|
+
return;
|
|
5080
|
+
}
|
|
5081
|
+
if (result.skipped.length > 0) clack.note(`${formatSkippedLintScripts(result.skipped)}\n\noxlint was added to one entry point only, so it does not run twice over the same tree.`, "package.json scripts left alone");
|
|
5082
|
+
if (result.changes.length === 0) return;
|
|
5083
|
+
clack.note(`${formatLintScriptChanges(result.changes)}\n\noxlint was prepended, never substituted, so every command that was there still runs.`, "Updated package.json scripts");
|
|
5084
|
+
}
|
|
5085
|
+
function applyLintScript$1(clack, directory) {
|
|
5086
|
+
const manifestPath = (0, node_path.join)(directory, "package.json");
|
|
5087
|
+
if (!(0, node_fs.existsSync)(manifestPath)) return;
|
|
5088
|
+
const result = setLintScript((0, node_fs.readFileSync)(manifestPath, "utf8"));
|
|
5089
|
+
if (result.changed) (0, node_fs.writeFileSync)(manifestPath, result.manifest, "utf8");
|
|
5090
|
+
noteLintScriptOutcome$1(clack, result);
|
|
5091
|
+
}
|
|
5092
|
+
async function rewriteDirectivesWithNote(clack, directory, autoConfirm) {
|
|
5093
|
+
const candidates = previewDisableDirectives(directory);
|
|
5094
|
+
if (candidates.length === 0) return;
|
|
5095
|
+
const count = candidates.length;
|
|
5096
|
+
if (clack.note) clack.note(`${count} file(s) carry an \`eslint-disable\` naming a rule oxlint now owns. Left as they are, ESLint reports each one as an unused directive.
|
|
5097
|
+
|
|
5098
|
+
This is a separate, source-wide diff — review it on its own commit.`, "Disable directives");
|
|
5099
|
+
if (!autoConfirm) {
|
|
5100
|
+
const proceed = await clack.confirm({
|
|
5101
|
+
message: `Re-point \`eslint-disable\` onto \`oxlint-disable\` in ${count} file(s) now?`,
|
|
5102
|
+
initialValue: true
|
|
5103
|
+
});
|
|
5104
|
+
if (clack.isCancel(proceed) || proceed !== true) {
|
|
5105
|
+
if (clack.note) clack.note("Skipped. Re-run `npx @useinsider/eslint-config migrate` to apply it later.", "Disable directives not rewritten");
|
|
5106
|
+
return;
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
const { rewritten } = rewriteDisableDirectives(directory, candidates);
|
|
5110
|
+
if (clack.note) clack.note(`Re-pointed directives in ${rewritten.length} file(s).`, "Disable directives");
|
|
4234
5111
|
}
|
|
4235
5112
|
function loadPreviousMigrate(clack, cwd) {
|
|
4236
5113
|
const restored = loadSession({ cwd });
|
|
@@ -4242,7 +5119,7 @@ function emitNothingToMigrate(clack, format) {
|
|
|
4242
5119
|
clack.outro(format === "eslintrc" ? "No legacy `.eslintrc` shape found — nothing to migrate." : "No v1 useInsider patterns found — nothing to migrate.");
|
|
4243
5120
|
}
|
|
4244
5121
|
function persistMigrateSession(input, prepared, resolved) {
|
|
4245
|
-
const overwriteExisting =
|
|
5122
|
+
const overwriteExisting = supersededFlatConfigs(resolved.targetPath, prepared.writePath).length > 0;
|
|
4246
5123
|
saveSession({
|
|
4247
5124
|
command: "migrate",
|
|
4248
5125
|
migrate: {
|
|
@@ -4254,26 +5131,48 @@ function persistMigrateSession(input, prepared, resolved) {
|
|
|
4254
5131
|
}
|
|
4255
5132
|
}, { cwd: input.cwd });
|
|
4256
5133
|
}
|
|
5134
|
+
async function finishMigrate(clack, depSummary, input) {
|
|
5135
|
+
if (depSummary && !await applyDepChanges(clack, depSummary, input)) {
|
|
5136
|
+
clack.cancel("Config migrated, but the dependencies did not install. Install them and re-run lint.");
|
|
5137
|
+
return 1;
|
|
5138
|
+
}
|
|
5139
|
+
clearSession({ cwd: input.cwd });
|
|
5140
|
+
return 0;
|
|
5141
|
+
}
|
|
4257
5142
|
async function runMigrate(input, clackOverride) {
|
|
4258
5143
|
const clack = clackOverride ?? await loadClack();
|
|
4259
5144
|
const sourceResult = resolveSource(input, clack);
|
|
4260
5145
|
if (!sourceResult.ok) return sourceResult.exitCode;
|
|
4261
5146
|
const { resolved } = sourceResult;
|
|
4262
5147
|
clack.intro(`Migrating ${resolved.targetPath}`);
|
|
5148
|
+
const declinedExitCode = input.fromLint ? 1 : 0;
|
|
5149
|
+
if (input.fromLint && !input.yes && !await confirmGuidedStart(clack, resolved.targetPath)) {
|
|
5150
|
+
clack.outro("Left the config as it is — nothing was written.");
|
|
5151
|
+
return declinedExitCode;
|
|
5152
|
+
}
|
|
4263
5153
|
const previousMigrate = loadPreviousMigrate(clack, input.cwd);
|
|
4264
5154
|
const prepared = prepareMigration(resolved, input);
|
|
4265
5155
|
if (!prepared) {
|
|
4266
5156
|
emitNothingToMigrate(clack, resolved.format);
|
|
4267
|
-
return
|
|
5157
|
+
return declinedExitCode;
|
|
4268
5158
|
}
|
|
4269
5159
|
emitSummary(clack, prepared.plan, resolved.format);
|
|
5160
|
+
noteConfigRename(clack, resolved.targetPath, prepared.writePath);
|
|
4270
5161
|
const depSummary = computeDepSummary(prepared.plan, prepared.writePath, input.overrides);
|
|
4271
|
-
if (!await runConfirmations(clack, input, resolved.targetPath, prepared.writePath, previousMigrate)) return
|
|
5162
|
+
if (!await runConfirmations(clack, input, resolved.targetPath, prepared.writePath, previousMigrate)) return declinedExitCode;
|
|
4272
5163
|
persistMigrateSession(input, prepared, resolved);
|
|
4273
5164
|
performWriteAndCleanup(clack, resolved, prepared);
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
5165
|
+
applyLintScript$1(clack, (0, node_path.dirname)(prepared.writePath));
|
|
5166
|
+
await rewriteDirectivesWithNote(clack, (0, node_path.dirname)(prepared.writePath), Boolean(input.yes));
|
|
5167
|
+
const consumerDir = (0, node_path.dirname)(prepared.writePath);
|
|
5168
|
+
const consumerLayout = resolveLintLayout(consumerDir);
|
|
5169
|
+
await applyTsExtensionsFlag(clack, {
|
|
5170
|
+
cwd: consumerDir,
|
|
5171
|
+
lintModule: consumerLayout.lintModule,
|
|
5172
|
+
eslintConfig: consumerLayout.eslintConfig,
|
|
5173
|
+
autoConfirm: input.yes
|
|
5174
|
+
});
|
|
5175
|
+
return finishMigrate(clack, depSummary, input);
|
|
4277
5176
|
}
|
|
4278
5177
|
//#endregion
|
|
4279
5178
|
//#region src/cli/index.ts
|
|
@@ -4438,21 +5337,50 @@ async function resolveHybridAnswers(clack, input, answers) {
|
|
|
4438
5337
|
};
|
|
4439
5338
|
return answers;
|
|
4440
5339
|
}
|
|
5340
|
+
function applyLintScript(cwd) {
|
|
5341
|
+
const manifestPath = (0, node_path.join)(cwd, "package.json");
|
|
5342
|
+
if (!(0, node_fs.existsSync)(manifestPath)) return null;
|
|
5343
|
+
const result = setLintScript((0, node_fs.readFileSync)(manifestPath, "utf8"));
|
|
5344
|
+
if (result.changed) (0, node_fs.writeFileSync)(manifestPath, result.manifest, "utf8");
|
|
5345
|
+
return result;
|
|
5346
|
+
}
|
|
5347
|
+
function noteLintScriptOutcome(clack, result) {
|
|
5348
|
+
if (!clack.note || !result) return;
|
|
5349
|
+
if (result.unresolved) {
|
|
5350
|
+
clack.note(`Left \`${result.unresolved.script}\` untouched — it runs neither eslint nor oxlint:\n\n ${result.unresolved.previous}\n\nAdd \`${OXLINT_LINT_SCRIPT}\` yourself, next to whatever invokes ESLint.`, "package.json script not changed");
|
|
5351
|
+
return;
|
|
5352
|
+
}
|
|
5353
|
+
if (result.skipped.length > 0) clack.note(`${formatSkippedLintScripts(result.skipped)}\n\noxlint was added to one entry point only, so it does not run twice over the same tree.`, "package.json scripts left alone");
|
|
5354
|
+
if (result.changes.length > 0) clack.note(formatLintScriptChanges(result.changes), "Updated package.json scripts");
|
|
5355
|
+
}
|
|
5356
|
+
function writeLintLayout(cwd, lintModuleSource) {
|
|
5357
|
+
const layout = resolveLintLayout(cwd);
|
|
5358
|
+
const lintModulePath = (0, node_path.join)(cwd, layout.lintModule);
|
|
5359
|
+
(0, node_fs.writeFileSync)(lintModulePath, lintModuleSource, "utf8");
|
|
5360
|
+
renderLintLayoutFiles(layout).forEach((file) => {
|
|
5361
|
+
(0, node_fs.writeFileSync)((0, node_path.join)(cwd, file.filename), file.source, "utf8");
|
|
5362
|
+
});
|
|
5363
|
+
layout.replacedEslintConfigs.forEach((filename) => {
|
|
5364
|
+
(0, node_fs.rmSync)((0, node_path.join)(cwd, filename), { force: true });
|
|
5365
|
+
});
|
|
5366
|
+
return {
|
|
5367
|
+
lintModulePath,
|
|
5368
|
+
lintScript: applyLintScript(cwd)
|
|
5369
|
+
};
|
|
5370
|
+
}
|
|
4441
5371
|
async function installAndWrite(clack, input, answers, options) {
|
|
4442
5372
|
if (options.pruneBeforeInstall) noteOverridePrune(clack, pruneConsumerPackageJson(input.cwd).removed);
|
|
4443
5373
|
const deps = buildDependencyList(answers);
|
|
4444
5374
|
if (clack.note) clack.note(deps.map((dep) => ` - ${dep}`).join("\n"), "Installing dev dependencies");
|
|
4445
|
-
await installWithRetry({
|
|
5375
|
+
if (!(await installWithRetry({
|
|
4446
5376
|
deps,
|
|
4447
5377
|
cwd: input.cwd,
|
|
4448
5378
|
autoConfirm: Boolean(input.yes),
|
|
4449
5379
|
clack,
|
|
4450
5380
|
installer: input.overrides?.installer ?? installDependencies,
|
|
4451
5381
|
fsOps: input.overrides?.wipeFs
|
|
4452
|
-
});
|
|
4453
|
-
|
|
4454
|
-
(0, node_fs.writeFileSync)(targetPath, renderConfigFile(answers), "utf8");
|
|
4455
|
-
return targetPath;
|
|
5382
|
+
})).installed) return null;
|
|
5383
|
+
return writeLintLayout(input.cwd, renderLintModuleFile(answers));
|
|
4456
5384
|
}
|
|
4457
5385
|
async function runScaffold(input, clackOverride) {
|
|
4458
5386
|
const clack = clackOverride ?? await loadClack();
|
|
@@ -4473,9 +5401,21 @@ async function runScaffold(input, clackOverride) {
|
|
|
4473
5401
|
existingConfigChoice: "override"
|
|
4474
5402
|
}
|
|
4475
5403
|
}, { cwd: input.cwd });
|
|
4476
|
-
const
|
|
5404
|
+
const written = await installAndWrite(clack, input, resolvedAnswers, { pruneBeforeInstall: viaOverride });
|
|
5405
|
+
if (!written) {
|
|
5406
|
+
clack.cancel("Dependencies were not installed — nothing was written. Fix the install and re-run.");
|
|
5407
|
+
return 1;
|
|
5408
|
+
}
|
|
5409
|
+
noteLintScriptOutcome(clack, written.lintScript);
|
|
5410
|
+
const layout = resolveLintLayout(input.cwd);
|
|
5411
|
+
await applyTsExtensionsFlag(clack, {
|
|
5412
|
+
cwd: input.cwd,
|
|
5413
|
+
lintModule: layout.lintModule,
|
|
5414
|
+
eslintConfig: layout.eslintConfig,
|
|
5415
|
+
autoConfirm: input.yes
|
|
5416
|
+
});
|
|
4477
5417
|
clearSession({ cwd: input.cwd });
|
|
4478
|
-
clack.outro(`Wrote ${
|
|
5418
|
+
clack.outro(`Wrote ${written.lintModulePath} and its sibling linter configs`);
|
|
4479
5419
|
return 0;
|
|
4480
5420
|
}
|
|
4481
5421
|
async function runScaffoldSubcommand(scaffoldInput) {
|
|
@@ -4486,7 +5426,8 @@ async function runMigrateSubcommand(input) {
|
|
|
4486
5426
|
cwd: input.cwd,
|
|
4487
5427
|
target: input.target,
|
|
4488
5428
|
yes: input.yes,
|
|
4489
|
-
noInstall: input.noInstall
|
|
5429
|
+
noInstall: input.noInstall,
|
|
5430
|
+
fromLint: input.fromLint
|
|
4490
5431
|
});
|
|
4491
5432
|
}
|
|
4492
5433
|
function writeStderr(line) {
|
|
@@ -4514,5 +5455,6 @@ if (!isUnderTest()) main().then(exitCleanly).catch((error) => {
|
|
|
4514
5455
|
});
|
|
4515
5456
|
//#endregion
|
|
4516
5457
|
exports.runScaffold = runScaffold;
|
|
5458
|
+
exports.writeLintLayout = writeLintLayout;
|
|
4517
5459
|
|
|
4518
5460
|
//# sourceMappingURL=cli.cjs.map
|