@useinsider/eslint-config 3.0.2 → 4.0.0-beta.1

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.
Files changed (57) hide show
  1. package/README.md +285 -64
  2. package/dist/builders/configFiles.d.ts +13 -0
  3. package/dist/builders/oxlint.d.ts +51 -0
  4. package/dist/builders/oxlintOwnership.d.ts +43 -0
  5. package/dist/cli.cjs +584 -27
  6. package/dist/cli.cjs.map +1 -1
  7. package/dist/configs/dependencies.d.ts +3 -0
  8. package/dist/{javascript-VBXqjVi8.cjs → javascript-DH8-N38w.cjs} +5 -5
  9. package/dist/{javascript-VBXqjVi8.cjs.map → javascript-DH8-N38w.cjs.map} +1 -1
  10. package/dist/{javascript-dom-5jRL_yKP.cjs → javascript-dom-CvtQ6_9W.cjs} +4 -4
  11. package/dist/{javascript-dom-5jRL_yKP.cjs.map → javascript-dom-CvtQ6_9W.cjs.map} +1 -1
  12. package/dist/{javascript-node-C1AU_4X2.cjs → javascript-node-BNsy3loY.cjs} +4 -4
  13. package/dist/{javascript-node-C1AU_4X2.cjs.map → javascript-node-BNsy3loY.cjs.map} +1 -1
  14. package/dist/{jest-CHzjzBtf.cjs → jest-4Es9Ggaa.cjs} +3 -3
  15. package/dist/{jest-CHzjzBtf.cjs.map → jest-4Es9Ggaa.cjs.map} +1 -1
  16. package/dist/main.cjs +452 -73
  17. package/dist/main.cjs.map +1 -1
  18. package/dist/main.d.ts +9 -2
  19. package/dist/main.js +435 -56
  20. package/dist/main.js.map +1 -1
  21. package/dist/oxlintOwnership-7hpdB3d1.cjs +199 -0
  22. package/dist/oxlintOwnership-7hpdB3d1.cjs.map +1 -0
  23. package/dist/oxlintOwnership-Cx0hgA_J.js +127 -0
  24. package/dist/oxlintOwnership-Cx0hgA_J.js.map +1 -0
  25. package/dist/removePluginsFromConfigs-D0oSUj2C.cjs.map +1 -1
  26. package/dist/removePluginsFromConfigs-DzqX2Ju2.js.map +1 -1
  27. package/dist/{stylistic-Dio0AM2E.cjs → stylistic-CoP3MT1Y.cjs} +4 -4
  28. package/dist/{stylistic-Dio0AM2E.cjs.map → stylistic-CoP3MT1Y.cjs.map} +1 -1
  29. package/dist/{typescript-C1icmOxY.cjs → typescript-D3VeLnpn.cjs} +7 -7
  30. package/dist/{typescript-C1icmOxY.cjs.map → typescript-D3VeLnpn.cjs.map} +1 -1
  31. package/dist/{typescript-dom-RUJn2WCZ.cjs → typescript-dom-iB_xBTAD.cjs} +4 -4
  32. package/dist/{typescript-dom-RUJn2WCZ.cjs.map → typescript-dom-iB_xBTAD.cjs.map} +1 -1
  33. package/dist/{typescript-node-CBEzcc56.cjs → typescript-node-Cq6qhj53.cjs} +4 -4
  34. package/dist/{typescript-node-CBEzcc56.cjs.map → typescript-node-Cq6qhj53.cjs.map} +1 -1
  35. package/dist/utils/ensureConfigStructure.d.ts +9 -4
  36. package/dist/utils/ensureOxlintConfig.d.ts +9 -0
  37. package/dist/utils/silenceRules.d.ts +9 -0
  38. package/dist/{vitest-BomI8gtu.cjs → vitest-BlGQswK2.cjs} +3 -3
  39. package/dist/{vitest-BomI8gtu.cjs.map → vitest-BlGQswK2.cjs.map} +1 -1
  40. package/dist/{vue2-azKwRgaJ.cjs → vue2-Da1i_oVn.cjs} +8 -8
  41. package/dist/{vue2-azKwRgaJ.cjs.map → vue2-Da1i_oVn.cjs.map} +1 -1
  42. package/dist/{vue2-options-api-ma3J-bvM.cjs → vue2-options-api-BSdrUm8H.cjs} +8 -8
  43. package/dist/{vue2-options-api-ma3J-bvM.cjs.map → vue2-options-api-BSdrUm8H.cjs.map} +1 -1
  44. package/dist/{vue2-typescript-CZSHzp7I.cjs → vue2-typescript-dXLRSGNP.cjs} +10 -10
  45. package/dist/{vue2-typescript-CZSHzp7I.cjs.map → vue2-typescript-dXLRSGNP.cjs.map} +1 -1
  46. package/dist/{vue2-typescript-options-api-B8OzZlje.cjs → vue2-typescript-options-api-LM7U5dWq.cjs} +10 -10
  47. package/dist/{vue2-typescript-options-api-B8OzZlje.cjs.map → vue2-typescript-options-api-LM7U5dWq.cjs.map} +1 -1
  48. package/dist/{vue3-Df5UQ1Ys.cjs → vue3-621EM2Ii.cjs} +8 -8
  49. package/dist/{vue3-Df5UQ1Ys.cjs.map → vue3-621EM2Ii.cjs.map} +1 -1
  50. package/dist/{vue3-options-api-Bx7yYHza.cjs → vue3-options-api-CC9_fAiq.cjs} +8 -8
  51. package/dist/{vue3-options-api-Bx7yYHza.cjs.map → vue3-options-api-CC9_fAiq.cjs.map} +1 -1
  52. package/dist/{vue3-typescript-C0O1JZlk.cjs → vue3-typescript-DONe-IKz.cjs} +10 -10
  53. package/dist/{vue3-typescript-C0O1JZlk.cjs.map → vue3-typescript-DONe-IKz.cjs.map} +1 -1
  54. package/dist/{vue3-typescript-options-api-cPal4O-K.cjs → vue3-typescript-options-api-D4pqpDFs.cjs} +10 -10
  55. package/dist/{vue3-typescript-options-api-cPal4O-K.cjs.map → vue3-typescript-options-api-D4pqpDFs.cjs.map} +1 -1
  56. package/package.json +8 -5
  57. 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 require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
3
+ const require_oxlintOwnership = require("./oxlintOwnership-7hpdB3d1.cjs");
4
4
  let globals = require("globals");
5
- globals = require_rolldown_runtime.__toESM(globals, 1);
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 = require_rolldown_runtime.__toESM(typescript, 1);
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",
@@ -329,12 +331,13 @@ function parseScaffoldArgs(argv) {
329
331
  function printUsage(stderr, unknown) {
330
332
  stderr(`[useinsider/eslint-config] Unknown subcommand: ${unknown}`);
331
333
  stderr("Usage:");
332
- stderr(" npx @useinsider/eslint-config [--yes|-y] Scaffold a new eslint.config.mjs");
334
+ stderr(" npx @useinsider/eslint-config [--yes|-y]");
335
+ stderr(" Scaffold insider.lint.*, eslint.config.* and oxlint.config.*");
333
336
  stderr(" npx @useinsider/eslint-config migrate [path] [--yes|-y] [--no-install]");
334
- stderr(" Migrate an existing v1 useInsider() config to v2");
337
+ stderr(" Split an existing useInsider() or .eslintrc* config into the three-file layout");
335
338
  }
336
339
  function isScaffoldArgv(argv) {
337
- return argv.length === 0 || argv.every((arg) => isYesFlag(arg));
340
+ return argv.every((arg) => isYesFlag(arg));
338
341
  }
339
342
  async function dispatch(argv, cwd, deps) {
340
343
  if (isScaffoldArgv(argv)) {
@@ -358,6 +361,9 @@ async function dispatch(argv, cwd, deps) {
358
361
  return 1;
359
362
  }
360
363
  //#endregion
364
+ //#region src/configs/dependencies.ts
365
+ var dependencies$14 = { oxlint: true };
366
+ //#endregion
361
367
  //#region src/configs/jest/dependencies.ts
362
368
  var dependencies$13 = { "eslint-plugin-jest": true };
363
369
  //#endregion
@@ -471,6 +477,7 @@ function buildDependencyList(answers, rawManifest = readOwnEslintConfigManifest$
471
477
  deps.add(`@useinsider/eslint-config@${manifest.version}`);
472
478
  deps.add(pinned(manifest, "eslint"));
473
479
  deps.add(pinned(manifest, "globals"));
480
+ applyRecord(deps, manifest, dependencies$14);
474
481
  const wantsTypescript = answers.languages.includes("typescript");
475
482
  if (answers.framework === "vue") applyRecord(deps, manifest, vueDepsFor(answers.vueMajor ?? 3, wantsTypescript));
476
483
  else if (wantsTypescript) applyRecord(deps, manifest, dependencies$12);
@@ -702,6 +709,132 @@ async function installWithRetry(opts) {
702
709
  }
703
710
  }
704
711
  //#endregion
712
+ //#region src/cli/lintLayout.ts
713
+ var OXLINT_LINT_SCRIPT = "oxlint --disable-nested-config && eslint .";
714
+ /**
715
+ * Extension for the lint module and the oxlint config. oxlint only discovers `oxlint.config.ts` and
716
+ * `oxlint.config.mts`, and Node warns on a bare `.ts` file inside a package that declares no
717
+ * `"type"`, so such a consumer gets the unambiguous `.mts` form.
718
+ * @example
719
+ * resolveLayoutExtension('/repo'); // 'ts' when /repo/package.json declares "type": "module"
720
+ */
721
+ function resolveLayoutExtension(cwd) {
722
+ try {
723
+ const raw = (0, node_fs.readFileSync)((0, node_path.join)(cwd, "package.json"), "utf8");
724
+ return JSON.parse(raw).type === "module" ? "ts" : "mts";
725
+ } catch {
726
+ return "mts";
727
+ }
728
+ }
729
+ function findExistingEslintConfig(cwd, fallback) {
730
+ return [
731
+ "ts",
732
+ "mts",
733
+ "cts",
734
+ "js",
735
+ "mjs",
736
+ "cjs"
737
+ ].map((ext) => `eslint.config.${ext}`).find((filename) => (0, node_fs.existsSync)((0, node_path.join)(cwd, filename))) ?? fallback;
738
+ }
739
+ /**
740
+ * The three filenames `useInsider()` requires, resolved against a consumer directory. An existing
741
+ * `eslint.config.*` keeps its name so a migration does not leave two flat configs behind.
742
+ * @example
743
+ * resolveLintLayout('/repo'); // { lintModule: 'insider.lint.mts', eslintConfig: 'eslint.config.mjs', ... }
744
+ */
745
+ function resolveLintLayout(cwd, eslintConfigFallback = "eslint.config.mjs") {
746
+ const extension = resolveLayoutExtension(cwd);
747
+ return {
748
+ extension,
749
+ lintModule: `insider.lint.${extension}`,
750
+ oxlintConfig: `oxlint.config.${extension}`,
751
+ eslintConfig: findExistingEslintConfig(cwd, eslintConfigFallback)
752
+ };
753
+ }
754
+ /**
755
+ * Body of the generated `eslint.config.*`. The import names the lint module's real extension
756
+ * because neither linter's loader applies TypeScript's `.js` → `.ts` specifier rewriting.
757
+ * @example
758
+ * renderEslintConfigFile('insider.lint.ts'); // "import { useInsiderESLint } from './insider.lint.ts';\n…"
759
+ */
760
+ function renderEslintConfigFile(lintModule) {
761
+ return [
762
+ `import { useInsiderESLint } from './${lintModule}';`,
763
+ "",
764
+ "export default useInsiderESLint();",
765
+ ""
766
+ ].join("\n");
767
+ }
768
+ /**
769
+ * Body of the generated `oxlint.config.*`.
770
+ * @example
771
+ * renderOxlintConfigFile('insider.lint.mts'); // "…export default await useInsiderOXC();\n"
772
+ */
773
+ function renderOxlintConfigFile(lintModule) {
774
+ return [
775
+ `import { useInsiderOXC } from './${lintModule}';`,
776
+ "",
777
+ "export default await useInsiderOXC();",
778
+ ""
779
+ ].join("\n");
780
+ }
781
+ /**
782
+ * The two thin files that sit beside a lint module, ready to write.
783
+ * @example
784
+ * renderLintLayoutFiles(resolveLintLayout('/repo')).map(file => file.filename);
785
+ * // ['eslint.config.mjs', 'oxlint.config.mts']
786
+ */
787
+ function renderLintLayoutFiles(layout) {
788
+ return [{
789
+ filename: layout.eslintConfig,
790
+ source: renderEslintConfigFile(layout.lintModule)
791
+ }, {
792
+ filename: layout.oxlintConfig,
793
+ source: renderOxlintConfigFile(layout.lintModule)
794
+ }];
795
+ }
796
+ var DEFAULT_INDENT = 2;
797
+ function detectIndent$1(raw) {
798
+ const firstBreak = raw.indexOf("\n");
799
+ if (firstBreak === -1) return DEFAULT_INDENT;
800
+ const rest = raw.slice(firstBreak + 1);
801
+ const width = rest.length - rest.trimStart().length;
802
+ return width > 0 ? width : DEFAULT_INDENT;
803
+ }
804
+ /**
805
+ * Rewrites the consumer's `lint` script so both linters run. Returns the manifest untouched when it
806
+ * already runs oxlint, so re-running the CLI is idempotent.
807
+ * @example
808
+ * setLintScript('{"scripts":{"lint":"eslint ."}}').manifest;
809
+ * // '…"lint": "oxlint --disable-nested-config && eslint ."…'
810
+ */
811
+ function setLintScript(rawManifest) {
812
+ let parsed;
813
+ try {
814
+ parsed = JSON.parse(rawManifest);
815
+ } catch {
816
+ return {
817
+ manifest: rawManifest,
818
+ changed: false
819
+ };
820
+ }
821
+ const previous = parsed.scripts?.lint;
822
+ if (previous?.includes("oxlint")) return {
823
+ manifest: rawManifest,
824
+ changed: false,
825
+ previous
826
+ };
827
+ parsed.scripts = {
828
+ ...parsed.scripts,
829
+ lint: OXLINT_LINT_SCRIPT
830
+ };
831
+ return {
832
+ manifest: `${JSON.stringify(parsed, null, detectIndent$1(rawManifest))}\n`,
833
+ changed: true,
834
+ previous
835
+ };
836
+ }
837
+ //#endregion
705
838
  //#region src/cli/quote.ts
706
839
  function quote(value) {
707
840
  return `'${JSON.stringify(value).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
@@ -995,11 +1128,17 @@ function formatScope$1(scope, entries, baseIndent) {
995
1128
  function renderUseInsiderBody(merged) {
996
1129
  return [...merged.entries()].map(([scope, entries]) => formatScope$1(scope, entries, indent$1)).join("\n");
997
1130
  }
998
- function renderConfigFile(answers) {
1131
+ /**
1132
+ * Body of the generated `insider.lint.*` module — the one file that calls `useInsider()` and the
1133
+ * only one both linter configs import.
1134
+ * @example
1135
+ * renderLintModuleFile(answers); // "…export const { useInsiderESLint, useInsiderOXC } = useInsider({…"
1136
+ */
1137
+ function renderLintModuleFile(answers) {
999
1138
  return [
1000
1139
  "import { useInsider } from '@useinsider/eslint-config';",
1001
1140
  "",
1002
- "export default useInsider({",
1141
+ "export const { useInsiderESLint, useInsiderOXC } = useInsider({",
1003
1142
  renderUseInsiderBody(mergeByScope(buildScopedConfigs(answers))),
1004
1143
  "});",
1005
1144
  ""
@@ -1440,7 +1579,11 @@ var SCOPE_TO_DEPS = {
1440
1579
  vitest: dependencies$11,
1441
1580
  jsdocBasedTs: dependencies$3
1442
1581
  };
1443
- var ALL_PRESET_DEPS = [dependencies$2, ...Object.values(SCOPE_TO_DEPS)];
1582
+ var ALL_PRESET_DEPS = [
1583
+ dependencies$14,
1584
+ dependencies$2,
1585
+ ...Object.values(SCOPE_TO_DEPS)
1586
+ ];
1444
1587
  var PRESET_DEPENDENCY_KEYS = new Set(ALL_PRESET_DEPS.flatMap((record) => Object.keys(record)));
1445
1588
  function isEslintRelatedDependency(name) {
1446
1589
  if (SAFELIST.has(name)) return false;
@@ -1538,6 +1681,9 @@ function buildInstallList(scopes, manifest) {
1538
1681
  const fallbacks = [];
1539
1682
  appendSpec(specs, "@useinsider/eslint-config@latest");
1540
1683
  appendSpec(specs, resolveSpec("eslint", manifest, fallbacks));
1684
+ Object.entries(dependencies$14).forEach(([packageName, shouldInstall]) => {
1685
+ if (shouldInstall) appendSpec(specs, resolveSpec(packageName, manifest, fallbacks));
1686
+ });
1541
1687
  [...scopes].forEach((scope) => {
1542
1688
  const record = SCOPE_TO_DEPS[scope];
1543
1689
  if (!record) return;
@@ -1597,6 +1743,340 @@ function pruneConsumerPackageJson(cwd) {
1597
1743
  };
1598
1744
  }
1599
1745
  //#endregion
1746
+ //#region src/cli/tsconfigFlag.ts
1747
+ var TS_EXTENSIONS_FLAG = "allowImportingTsExtensions";
1748
+ var TS_EXTENSIONS_SNIPPET = [
1749
+ "{",
1750
+ " \"compilerOptions\": {",
1751
+ ` "${TS_EXTENSIONS_FLAG}": true`,
1752
+ " }",
1753
+ "}"
1754
+ ].join("\n");
1755
+ var DEFAULT_INDENT_UNIT = " ";
1756
+ function readEffectiveFlag(path, raw) {
1757
+ const { config } = typescript.parseConfigFileTextToJson(path, raw);
1758
+ if (!config) return false;
1759
+ return typescript.parseJsonConfigFileContent(config, typescript.sys, (0, node_path.dirname)(path), void 0, path).options.allowImportingTsExtensions === true;
1760
+ }
1761
+ /**
1762
+ * The consumer's root `tsconfig.json` plus whether `allowImportingTsExtensions` already resolves to
1763
+ * `true` — including when it arrives through `extends`, which is followed the same way `tsc` does.
1764
+ * @example
1765
+ * readTsconfigState('/repo')?.enabled; // true when /repo/tsconfig.json extends a base that sets it
1766
+ */
1767
+ function readTsconfigState(cwd) {
1768
+ const path = (0, node_path.join)(cwd, "tsconfig.json");
1769
+ if (!(0, node_fs.existsSync)(path)) return null;
1770
+ try {
1771
+ const raw = (0, node_fs.readFileSync)(path, "utf8");
1772
+ return {
1773
+ path,
1774
+ raw,
1775
+ enabled: readEffectiveFlag(path, raw)
1776
+ };
1777
+ } catch {
1778
+ return null;
1779
+ }
1780
+ }
1781
+ function rootObject(source) {
1782
+ const [statement] = source.statements;
1783
+ const expression = statement?.expression;
1784
+ return expression && typescript.isObjectLiteralExpression(expression) ? expression : null;
1785
+ }
1786
+ function findProperty$2(object, name) {
1787
+ return object.properties.find((property) => typescript.isPropertyAssignment(property) && typescript.isStringLiteral(property.name) && property.name.text === name);
1788
+ }
1789
+ function indentAt(raw, position) {
1790
+ const lineStart = raw.lastIndexOf("\n", position - 1) + 1;
1791
+ const prefix = raw.slice(lineStart, position);
1792
+ return prefix.trim().length === 0 ? prefix : "";
1793
+ }
1794
+ function detectIndentUnit(raw) {
1795
+ const match = /\n([\t ]+)\S/u.exec(raw);
1796
+ return match ? match[1] : DEFAULT_INDENT_UNIT;
1797
+ }
1798
+ function splice(raw, start, end, text) {
1799
+ return raw.slice(0, start) + text + raw.slice(end);
1800
+ }
1801
+ function setExistingFlag(raw, source, property) {
1802
+ return splice(raw, property.initializer.getStart(source), property.initializer.getEnd(), "true");
1803
+ }
1804
+ function insertIntoCompilerOptions(raw, source, compilerOptions, object) {
1805
+ const openBrace = object.getStart(source) + 1;
1806
+ const [first] = object.properties;
1807
+ if (first) return splice(raw, openBrace, openBrace, `\n${indentAt(raw, first.getStart(source)) || detectIndentUnit(raw).repeat(2)}"${TS_EXTENSIONS_FLAG}": true,`);
1808
+ const outer = indentAt(raw, compilerOptions.getStart(source));
1809
+ const unit = detectIndentUnit(raw);
1810
+ return splice(raw, openBrace, object.getEnd() - 1, `\n${outer}${unit}"${TS_EXTENSIONS_FLAG}": true\n${outer}`);
1811
+ }
1812
+ function insertCompilerOptions(raw, source, root) {
1813
+ const openBrace = root.getStart(source) + 1;
1814
+ const unit = detectIndentUnit(raw);
1815
+ const [first] = root.properties;
1816
+ const indent = first ? indentAt(raw, first.getStart(source)) || unit : unit;
1817
+ const block = `\n${indent}"compilerOptions": {\n${indent}${unit}"${TS_EXTENSIONS_FLAG}": true\n${indent}}`;
1818
+ if (first) return splice(raw, openBrace, openBrace, `${block},`);
1819
+ return splice(raw, openBrace, root.getEnd() - 1, `${block}\n`);
1820
+ }
1821
+ /**
1822
+ * Turns `allowImportingTsExtensions` on in a `tsconfig.json` source without reformatting it. The
1823
+ * file is edited through its JSON syntax tree rather than re-serialized, so comments, key order and
1824
+ * indentation survive.
1825
+ * @example
1826
+ * enableTsExtensionsFlag('{\n "compilerOptions": {\n "strict": true\n }\n}').changed; // true
1827
+ */
1828
+ function enableTsExtensionsFlag(raw) {
1829
+ const source = typescript.parseJsonText("tsconfig.json", raw);
1830
+ const root = rootObject(source);
1831
+ if (!root) return {
1832
+ source: raw,
1833
+ changed: false
1834
+ };
1835
+ const compilerOptions = findProperty$2(root, "compilerOptions");
1836
+ if (!compilerOptions) return {
1837
+ source: insertCompilerOptions(raw, source, root),
1838
+ changed: true
1839
+ };
1840
+ if (!typescript.isObjectLiteralExpression(compilerOptions.initializer)) return {
1841
+ source: raw,
1842
+ changed: false
1843
+ };
1844
+ const existing = findProperty$2(compilerOptions.initializer, TS_EXTENSIONS_FLAG);
1845
+ if (existing) {
1846
+ if (existing.initializer.kind === typescript.SyntaxKind.TrueKeyword) return {
1847
+ source: raw,
1848
+ changed: false
1849
+ };
1850
+ return {
1851
+ source: setExistingFlag(raw, source, existing),
1852
+ changed: true
1853
+ };
1854
+ }
1855
+ return {
1856
+ source: insertIntoCompilerOptions(raw, source, compilerOptions, compilerOptions.initializer),
1857
+ changed: true
1858
+ };
1859
+ }
1860
+ function noteManualStep(clack, lintModule) {
1861
+ if (!clack.note) return;
1862
+ 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");
1863
+ }
1864
+ async function confirmFlag(clack, path) {
1865
+ const result = await clack.confirm({
1866
+ message: `Set \`${TS_EXTENSIONS_FLAG}: true\` in ${path}? Without it \`tsc\` fails with TS5097 on the generated config files.`,
1867
+ initialValue: true
1868
+ });
1869
+ return !clack.isCancel(result) && result === true;
1870
+ }
1871
+ /**
1872
+ * Offers to set `allowImportingTsExtensions` on the consumer's `tsconfig.json`, and prints the exact
1873
+ * snippet when the offer is declined. A no-op when there is no `tsconfig.json` or the flag already
1874
+ * resolves to `true` through the `extends` chain.
1875
+ * @example
1876
+ * await applyTsExtensionsFlag(clack, { cwd: '/repo', lintModule: 'insider.lint.ts' });
1877
+ */
1878
+ async function applyTsExtensionsFlag(clack, input) {
1879
+ const state = readTsconfigState(input.cwd);
1880
+ if (!state || state.enabled) return;
1881
+ if (!(input.autoConfirm || await confirmFlag(clack, state.path))) {
1882
+ noteManualStep(clack, input.lintModule);
1883
+ return;
1884
+ }
1885
+ const result = enableTsExtensionsFlag(state.raw);
1886
+ if (!result.changed) {
1887
+ noteManualStep(clack, input.lintModule);
1888
+ return;
1889
+ }
1890
+ (0, node_fs.writeFileSync)(state.path, result.source, "utf8");
1891
+ if (clack.note) clack.note(`Set \`${TS_EXTENSIONS_FLAG}: true\` in ${state.path}.`, "Updated tsconfig.json");
1892
+ }
1893
+ //#endregion
1894
+ //#region src/cli/migrate/disableDirectives.ts
1895
+ var DIRECTIVE_HEAD = /^\s*eslint-disable(-next-line|-line)?(?![\w-])/u;
1896
+ var SCRIPT_BLOCK = /<script\b[^>]*>([\s\S]*?)<\/script>/giu;
1897
+ var SFC_EXTENSIONS = [
1898
+ ".astro",
1899
+ ".svelte",
1900
+ ".vue"
1901
+ ];
1902
+ var JSX_EXTENSIONS = [
1903
+ ".cjs",
1904
+ ".js",
1905
+ ".jsx",
1906
+ ".mjs",
1907
+ ".tsx"
1908
+ ];
1909
+ var LINTED_EXTENSIONS = [
1910
+ ".astro",
1911
+ ".cjs",
1912
+ ".cts",
1913
+ ".js",
1914
+ ".jsx",
1915
+ ".mjs",
1916
+ ".mts",
1917
+ ".svelte",
1918
+ ".ts",
1919
+ ".tsx",
1920
+ ".vue"
1921
+ ];
1922
+ var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
1923
+ ".git",
1924
+ "coverage",
1925
+ "dist",
1926
+ "build",
1927
+ "node_modules",
1928
+ "out",
1929
+ "vendor"
1930
+ ]);
1931
+ function parseDirectiveBody(body) {
1932
+ const [ruleList, ...reasonParts] = body.split("--");
1933
+ return {
1934
+ rules: ruleList.split(",").map((rule) => rule.trim()).filter((rule) => rule.length > 0),
1935
+ reason: reasonParts.join("--").trim()
1936
+ };
1937
+ }
1938
+ function formatDirective(owner, block, kind, rules, reason) {
1939
+ const text = `${owner}-disable${kind} ${reason ? `${rules.join(", ")} -- ${reason}` : rules.join(", ")}`;
1940
+ return block ? `/* ${text} */` : `// ${text}`;
1941
+ }
1942
+ /**
1943
+ * Whether the split hands this rule to oxlint for the given file, so an `eslint-disable` comment
1944
+ * naming it would read as an unused directive to ESLint.
1945
+ * @example
1946
+ * movesToOxlint('no-console', '.ts'); // true
1947
+ * movesToOxlint('@typescript-eslint/no-unused-vars', '.vue'); // false — ESLint still runs it in SFCs
1948
+ */
1949
+ function movesToOxlint(rule, fileExtension) {
1950
+ if (!require_oxlintOwnership.isOxlintOwned(rule)) return false;
1951
+ return !(SFC_EXTENSIONS.includes(fileExtension) && require_oxlintOwnership.isVueUnsafeRule(rule));
1952
+ }
1953
+ function toSpans(comments, offset) {
1954
+ return comments.map((comment) => ({
1955
+ start: comment.range[0] + offset,
1956
+ end: comment.range[1] + offset,
1957
+ block: comment.type === _typescript_eslint_types.AST_TOKEN_TYPES.Block,
1958
+ value: comment.value
1959
+ }));
1960
+ }
1961
+ function parseComments(source, jsx) {
1962
+ try {
1963
+ return (0, _typescript_eslint_parser.parse)(source, {
1964
+ comment: true,
1965
+ jsx,
1966
+ range: true
1967
+ }).comments ?? [];
1968
+ } catch {
1969
+ return null;
1970
+ }
1971
+ }
1972
+ function collectFromScript(source, jsx, offset) {
1973
+ const comments = parseComments(source, jsx) ?? parseComments(source, !jsx);
1974
+ return comments ? toSpans(comments, offset) : [];
1975
+ }
1976
+ var CLOSING_SCRIPT_TAG_LENGTH = 9;
1977
+ function collectSfcComments(source, jsx) {
1978
+ return [...source.matchAll(SCRIPT_BLOCK)].flatMap((match) => {
1979
+ const [whole, body] = match;
1980
+ return collectFromScript(body, jsx, (match.index ?? 0) + whole.length - body.length - CLOSING_SCRIPT_TAG_LENGTH);
1981
+ });
1982
+ }
1983
+ /**
1984
+ * Every real comment node in the file, with ranges relative to the whole source. Only comments are
1985
+ * returned, so a string or template literal that happens to spell out a directive is invisible here.
1986
+ * @example
1987
+ * collectComments("const label = '// eslint-disable-next-line';\n", '.ts'); // []
1988
+ */
1989
+ function collectComments(source, fileExtension) {
1990
+ const jsx = JSX_EXTENSIONS.includes(fileExtension);
1991
+ if (SFC_EXTENSIONS.includes(fileExtension)) return collectSfcComments(source, jsx);
1992
+ return collectFromScript(source, jsx, 0);
1993
+ }
1994
+ function startsItsOwnLine(source, start) {
1995
+ const lineStart = source.lastIndexOf("\n", start - 1) + 1;
1996
+ return source.slice(lineStart, start).trim().length === 0;
1997
+ }
1998
+ function splitLayout(source, span, kind) {
1999
+ if (kind !== "" || !startsItsOwnLine(source, span.start)) return {
2000
+ block: true,
2001
+ separator: " "
2002
+ };
2003
+ const indent = " ".repeat(span.start - (source.lastIndexOf("\n", span.start - 1) + 1));
2004
+ return {
2005
+ block: span.block,
2006
+ separator: `\n${indent}`
2007
+ };
2008
+ }
2009
+ function replacementFor(source, span, fileExtension) {
2010
+ const head = DIRECTIVE_HEAD.exec(span.value);
2011
+ if (!head) return null;
2012
+ const kind = head[1] ?? "";
2013
+ const { rules, reason } = parseDirectiveBody(span.value.slice(head[0].length));
2014
+ if (rules.length === 0) return null;
2015
+ const moved = rules.filter((rule) => movesToOxlint(rule, fileExtension));
2016
+ if (moved.length === 0) return null;
2017
+ const kept = rules.filter((rule) => !moved.includes(rule));
2018
+ if (kept.length === 0) return {
2019
+ start: span.start,
2020
+ end: span.end,
2021
+ text: formatDirective("oxlint", span.block, kind, moved, reason)
2022
+ };
2023
+ const { block, separator } = splitLayout(source, span, kind);
2024
+ const text = formatDirective("oxlint", block, kind, moved, reason) + separator + formatDirective("eslint", block, kind, kept, reason);
2025
+ return {
2026
+ start: span.start,
2027
+ end: span.end,
2028
+ text
2029
+ };
2030
+ }
2031
+ /**
2032
+ * Re-points the rules oxlint now owns onto an `oxlint-disable` comment, leaving the ESLint-owned
2033
+ * ones behind. Only real comment nodes are rewritten, so a string or template literal spelling out
2034
+ * a directive is left alone.
2035
+ * @example
2036
+ * rewriteDirectives('// <eslint>-disable-next-line no-console\nlog();', '.ts').source;
2037
+ * // '// oxlint-disable-next-line no-console\nlog();'
2038
+ */
2039
+ function rewriteDirectives(source, fileExtension) {
2040
+ const replacements = collectComments(source, fileExtension).map((span) => replacementFor(source, span, fileExtension)).filter((replacement) => replacement !== null).sort((left, right) => right.start - left.start);
2041
+ if (replacements.length === 0) return {
2042
+ source,
2043
+ changed: false
2044
+ };
2045
+ return {
2046
+ source: replacements.reduce((accumulator, replacement) => accumulator.slice(0, replacement.start) + replacement.text + accumulator.slice(replacement.end), source),
2047
+ changed: true
2048
+ };
2049
+ }
2050
+ function isLintedFile(filePath) {
2051
+ return LINTED_EXTENSIONS.includes((0, node_path.extname)(filePath));
2052
+ }
2053
+ function collectFiles(directory, accumulator) {
2054
+ (0, node_fs.readdirSync)(directory).forEach((entry) => {
2055
+ if (SKIPPED_DIRECTORIES.has(entry)) return;
2056
+ const full = (0, node_path.join)(directory, entry);
2057
+ if ((0, node_fs.statSync)(full).isDirectory()) {
2058
+ collectFiles(full, accumulator);
2059
+ return;
2060
+ }
2061
+ if (isLintedFile(full)) accumulator.push(full);
2062
+ });
2063
+ }
2064
+ /**
2065
+ * Applies {@link rewriteDirectives} across a consumer tree, skipping build output and dependencies.
2066
+ * @example
2067
+ * rewriteDisableDirectives('/repo').rewritten; // ['/repo/src/logger.ts']
2068
+ */
2069
+ function rewriteDisableDirectives(cwd) {
2070
+ const files = [];
2071
+ collectFiles(cwd, files);
2072
+ return { rewritten: files.filter((filePath) => {
2073
+ const result = rewriteDirectives((0, node_fs.readFileSync)(filePath, "utf8"), (0, node_path.extname)(filePath));
2074
+ if (!result.changed) return false;
2075
+ (0, node_fs.writeFileSync)(filePath, result.source, "utf8");
2076
+ return true;
2077
+ }) };
2078
+ }
2079
+ //#endregion
1600
2080
  //#region src/cli/migrate/stringEntry.ts
1601
2081
  function toEntries(values) {
1602
2082
  return values.map((value) => ({ value }));
@@ -2214,7 +2694,7 @@ function appendScope$1(config, scope, entry) {
2214
2694
  }
2215
2695
  function mergeGlobals(base, extra) {
2216
2696
  if (!base && !extra) return;
2217
- const merged = { ...base ?? {} };
2697
+ const merged = { ...base };
2218
2698
  if (extra) Object.entries(extra).forEach(([key, value]) => {
2219
2699
  if (!(key in merged)) merged[key] = value;
2220
2700
  });
@@ -3539,11 +4019,11 @@ function renderV2(plan) {
3539
4019
  parts.push("");
3540
4020
  if (plan.useInsiderLeadingComment !== void 0) parts.push(plan.useInsiderLeadingComment);
3541
4021
  if (!hasAnyContent(plan)) {
3542
- parts.push("export default useInsider({});");
4022
+ parts.push("export const { useInsiderESLint, useInsiderOXC } = useInsider({});");
3543
4023
  parts.push("");
3544
4024
  return pruneUnusedImports(parts.join("\n"));
3545
4025
  }
3546
- parts.push("export default useInsider({");
4026
+ parts.push("export const { useInsiderESLint, useInsiderOXC } = useInsider({");
3547
4027
  parts.push(renderConfigBody(plan));
3548
4028
  parts.push("});");
3549
4029
  parts.push("");
@@ -3903,14 +4383,52 @@ function formatWarnings(warnings) {
3903
4383
  }
3904
4384
  //#endregion
3905
4385
  //#region src/cli/migrate/writer.ts
4386
+ function writeLayout(eslintConfigPath, lintModuleSource) {
4387
+ const directory = (0, node_path.dirname)(eslintConfigPath);
4388
+ const layout = resolveLintLayout(directory, (0, node_path.basename)(eslintConfigPath));
4389
+ const lintModulePath = (0, node_path.join)(directory, layout.lintModule);
4390
+ (0, node_fs.writeFileSync)(lintModulePath, lintModuleSource, "utf8");
4391
+ const writtenPaths = [lintModulePath];
4392
+ renderLintLayoutFiles({
4393
+ ...layout,
4394
+ eslintConfig: (0, node_path.basename)(eslintConfigPath)
4395
+ }).forEach((file) => {
4396
+ const path = (0, node_path.join)(directory, file.filename);
4397
+ (0, node_fs.writeFileSync)(path, file.source, "utf8");
4398
+ writtenPaths.push(path);
4399
+ });
4400
+ return {
4401
+ lintModulePath,
4402
+ writtenPaths
4403
+ };
4404
+ }
4405
+ /**
4406
+ * Writes the three-file layout beside an existing flat config: the rendered `useInsider()` call
4407
+ * becomes `insider.lint.*`, and `eslint.config.*` / `oxlint.config.*` become thin re-exports.
4408
+ * @example
4409
+ * writeMigration('/repo/eslint.config.mjs', source).writtenPaths.length; // 3
4410
+ */
3906
4411
  function writeMigration(targetPath, newSource) {
3907
- (0, node_fs.writeFileSync)(targetPath, newSource, "utf8");
3908
- return { targetPath };
4412
+ const { lintModulePath, writtenPaths } = writeLayout(targetPath, newSource);
4413
+ return {
4414
+ targetPath,
4415
+ lintModulePath,
4416
+ writtenPaths
4417
+ };
3909
4418
  }
4419
+ /**
4420
+ * Same as {@link writeMigration}, but for a legacy `.eslintrc*` source that has to be replaced by a
4421
+ * differently-named flat config. Nominates the old file for deletion when the name changed.
4422
+ * @example
4423
+ * writeMigrationLegacy('/repo/.eslintrc.cjs', '/repo/eslint.config.mjs', source).deletedSourceCandidate;
4424
+ * // '/repo/.eslintrc.cjs'
4425
+ */
3910
4426
  function writeMigrationLegacy(sourcePath, writePath, newSource) {
3911
- (0, node_fs.writeFileSync)(writePath, newSource, "utf8");
4427
+ const { lintModulePath, writtenPaths } = writeLayout(writePath, newSource);
3912
4428
  return {
3913
4429
  targetPath: writePath,
4430
+ lintModulePath,
4431
+ writtenPaths,
3914
4432
  deletedSourceCandidate: (0, node_path.basename)(sourcePath) === (0, node_path.basename)(writePath) ? void 0 : sourcePath
3915
4433
  };
3916
4434
  }
@@ -4080,14 +4598,9 @@ function emitSummary(clack, plan, format) {
4080
4598
  if (plan.warnings.length > 0 && clack.note) clack.note(formatWarnings(plan.warnings), "Warnings");
4081
4599
  }
4082
4600
  function writeAndAnnounce(clack, targetPath, writePath, rendered, format) {
4083
- if (format === "eslintrc") {
4084
- const result = writeMigrationLegacy(targetPath, writePath, rendered);
4085
- clack.outro(`Wrote ${writePath}`);
4086
- return { deletedSourceCandidate: result.deletedSourceCandidate };
4087
- }
4088
- writeMigration(targetPath, rendered);
4089
- clack.outro(`Wrote ${targetPath}`);
4090
- return {};
4601
+ const result = format === "eslintrc" ? writeMigrationLegacy(targetPath, writePath, rendered) : writeMigration(targetPath, rendered);
4602
+ clack.outro(`Wrote ${result.writtenPaths.join(", ")}`);
4603
+ return { deletedSourceCandidate: result.deletedSourceCandidate };
4091
4604
  }
4092
4605
  async function confirmOverwriteExistingOutput(clack, writePath, sourcePath, initial = false) {
4093
4606
  const message = `An \`${(0, node_path.basename)(writePath)}\` already exists. Overwrite + delete \`${(0, node_path.basename)(sourcePath)}\`?`;
@@ -4232,6 +4745,22 @@ function performWriteAndCleanup(clack, resolved, prepared) {
4232
4745
  if (prepared.eslintIgnore) safeUnlink(prepared.eslintIgnore.path, clack, ".eslintignore");
4233
4746
  if (outcome.deletedSourceCandidate) safeUnlink(outcome.deletedSourceCandidate, clack, "source config");
4234
4747
  }
4748
+ function applyLintScript$1(clack, directory) {
4749
+ const manifestPath = (0, node_path.join)(directory, "package.json");
4750
+ if (!(0, node_fs.existsSync)(manifestPath)) return;
4751
+ const result = setLintScript((0, node_fs.readFileSync)(manifestPath, "utf8"));
4752
+ if (!result.changed) return;
4753
+ (0, node_fs.writeFileSync)(manifestPath, result.manifest, "utf8");
4754
+ if (clack.note) {
4755
+ const previous = result.previous ?? "(none)";
4756
+ clack.note(`lint: ${previous} -> oxlint --disable-nested-config && eslint .`, "Updated package.json script");
4757
+ }
4758
+ }
4759
+ function rewriteDirectivesWithNote(clack, directory) {
4760
+ const { rewritten } = rewriteDisableDirectives(directory);
4761
+ if (rewritten.length === 0 || !clack.note) return;
4762
+ clack.note(`Re-pointed eslint-disable comments onto oxlint-disable in ${rewritten.length} file(s).`, "Disable directives");
4763
+ }
4235
4764
  function loadPreviousMigrate(clack, cwd) {
4236
4765
  const restored = loadSession({ cwd });
4237
4766
  const previousMigrate = restored?.command === "migrate" ? restored.migrate : void 0;
@@ -4271,6 +4800,14 @@ async function runMigrate(input, clackOverride) {
4271
4800
  if (!await runConfirmations(clack, input, resolved.targetPath, prepared.writePath, previousMigrate)) return 0;
4272
4801
  persistMigrateSession(input, prepared, resolved);
4273
4802
  performWriteAndCleanup(clack, resolved, prepared);
4803
+ applyLintScript$1(clack, (0, node_path.dirname)(prepared.writePath));
4804
+ rewriteDirectivesWithNote(clack, (0, node_path.dirname)(prepared.writePath));
4805
+ const consumerDir = (0, node_path.dirname)(prepared.writePath);
4806
+ await applyTsExtensionsFlag(clack, {
4807
+ cwd: consumerDir,
4808
+ lintModule: resolveLintLayout(consumerDir).lintModule,
4809
+ autoConfirm: input.yes
4810
+ });
4274
4811
  if (depSummary) await applyDepChanges(clack, depSummary, input);
4275
4812
  clearSession({ cwd: input.cwd });
4276
4813
  return 0;
@@ -4438,6 +4975,22 @@ async function resolveHybridAnswers(clack, input, answers) {
4438
4975
  };
4439
4976
  return answers;
4440
4977
  }
4978
+ function applyLintScript(cwd) {
4979
+ const manifestPath = (0, node_path.join)(cwd, "package.json");
4980
+ if (!(0, node_fs.existsSync)(manifestPath)) return;
4981
+ const result = setLintScript((0, node_fs.readFileSync)(manifestPath, "utf8"));
4982
+ if (result.changed) (0, node_fs.writeFileSync)(manifestPath, result.manifest, "utf8");
4983
+ }
4984
+ function writeLintLayout(cwd, lintModuleSource) {
4985
+ const layout = resolveLintLayout(cwd);
4986
+ const lintModulePath = (0, node_path.join)(cwd, layout.lintModule);
4987
+ (0, node_fs.writeFileSync)(lintModulePath, lintModuleSource, "utf8");
4988
+ renderLintLayoutFiles(layout).forEach((file) => {
4989
+ (0, node_fs.writeFileSync)((0, node_path.join)(cwd, file.filename), file.source, "utf8");
4990
+ });
4991
+ applyLintScript(cwd);
4992
+ return lintModulePath;
4993
+ }
4441
4994
  async function installAndWrite(clack, input, answers, options) {
4442
4995
  if (options.pruneBeforeInstall) noteOverridePrune(clack, pruneConsumerPackageJson(input.cwd).removed);
4443
4996
  const deps = buildDependencyList(answers);
@@ -4450,9 +5003,7 @@ async function installAndWrite(clack, input, answers, options) {
4450
5003
  installer: input.overrides?.installer ?? installDependencies,
4451
5004
  fsOps: input.overrides?.wipeFs
4452
5005
  });
4453
- const targetPath = (0, node_path.join)(input.cwd, "eslint.config.mjs");
4454
- (0, node_fs.writeFileSync)(targetPath, renderConfigFile(answers), "utf8");
4455
- return targetPath;
5006
+ return writeLintLayout(input.cwd, renderLintModuleFile(answers));
4456
5007
  }
4457
5008
  async function runScaffold(input, clackOverride) {
4458
5009
  const clack = clackOverride ?? await loadClack();
@@ -4474,8 +5025,13 @@ async function runScaffold(input, clackOverride) {
4474
5025
  }
4475
5026
  }, { cwd: input.cwd });
4476
5027
  const targetPath = await installAndWrite(clack, input, resolvedAnswers, { pruneBeforeInstall: viaOverride });
5028
+ await applyTsExtensionsFlag(clack, {
5029
+ cwd: input.cwd,
5030
+ lintModule: resolveLintLayout(input.cwd).lintModule,
5031
+ autoConfirm: input.yes
5032
+ });
4477
5033
  clearSession({ cwd: input.cwd });
4478
- clack.outro(`Wrote ${targetPath}`);
5034
+ clack.outro(`Wrote ${targetPath} and its sibling linter configs`);
4479
5035
  return 0;
4480
5036
  }
4481
5037
  async function runScaffoldSubcommand(scaffoldInput) {
@@ -4514,5 +5070,6 @@ if (!isUnderTest()) main().then(exitCleanly).catch((error) => {
4514
5070
  });
4515
5071
  //#endregion
4516
5072
  exports.runScaffold = runScaffold;
5073
+ exports.writeLintLayout = writeLintLayout;
4517
5074
 
4518
5075
  //# sourceMappingURL=cli.cjs.map