@bensandee/tooling 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -48,18 +48,6 @@ const RenovateSchema = z.object({
48
48
  $schema: z.string().optional(),
49
49
  extends: z.array(z.string()).optional()
50
50
  }).loose();
51
- const KnipWorkspaceConfig = z.object({
52
- entry: z.array(z.string()).optional(),
53
- project: z.array(z.string()).optional(),
54
- ignore: z.array(z.string()).optional()
55
- });
56
- const KnipSchema = z.object({
57
- $schema: z.string().optional(),
58
- entry: z.array(z.string()).optional(),
59
- project: z.array(z.string()).optional(),
60
- ignore: z.array(z.string()).optional(),
61
- workspaces: z.record(z.string(), KnipWorkspaceConfig).optional()
62
- }).loose();
63
51
  /** Parse a JSONC string as a tsconfig.json. Returns a typed object with `{}` fallback on failure. */
64
52
  function parseTsconfig(raw) {
65
53
  const result = TsconfigSchema.safeParse(parse(raw));
@@ -70,11 +58,6 @@ function parseRenovateJson(raw) {
70
58
  const result = RenovateSchema.safeParse(parse(raw));
71
59
  return result.success ? result.data : {};
72
60
  }
73
- /** Parse a JSONC string as a knip.json. Returns a typed object with `{}` fallback on failure. */
74
- function parseKnipJson(raw) {
75
- const result = KnipSchema.safeParse(parse(raw));
76
- return result.success ? result.data : {};
77
- }
78
61
  /** Parse a JSON string as a package.json. Returns `undefined` on failure. */
79
62
  function parsePackageJson(raw) {
80
63
  try {
@@ -208,11 +191,11 @@ async function runInitPrompts(targetDir) {
208
191
  p.cancel("Cancelled.");
209
192
  process.exit(0);
210
193
  }
211
- const useLintRules = await p.confirm({
212
- message: "Include @bensandee/lint-rules?",
194
+ const useEslintPlugin = await p.confirm({
195
+ message: "Include @bensandee/eslint-plugin?",
213
196
  initialValue: true
214
197
  });
215
- if (isCancelled(useLintRules)) {
198
+ if (isCancelled(useEslintPlugin)) {
216
199
  p.cancel("Cancelled.");
217
200
  process.exit(0);
218
201
  }
@@ -361,7 +344,7 @@ async function runInitPrompts(targetDir) {
361
344
  name,
362
345
  isNew: !isExisting,
363
346
  structure,
364
- useLintRules,
347
+ useEslintPlugin,
365
348
  formatter,
366
349
  setupVitest,
367
350
  ci,
@@ -380,7 +363,7 @@ function buildDefaultConfig(targetDir, flags) {
380
363
  name: existingPkg?.name ?? path.basename(targetDir),
381
364
  isNew: !detected.hasPackageJson,
382
365
  structure: detected.hasPnpmWorkspace ? "monorepo" : "single",
383
- useLintRules: flags.lintRules ?? true,
366
+ useEslintPlugin: flags.eslintPlugin ?? true,
384
367
  formatter: detected.legacyConfigs.some((l) => l.tool === "prettier") ? "prettier" : "oxfmt",
385
368
  setupVitest: !detected.hasVitestConfig,
386
369
  ci: flags.noCi ? "none" : DEFAULT_CI,
@@ -526,7 +509,7 @@ async function generatePackageJson(ctx) {
526
509
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
527
510
  devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "latest";
528
511
  devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "latest";
529
- if (ctx.config.useLintRules) devDeps["@bensandee/lint-rules"] = isWorkspacePackage(ctx, "@bensandee/lint-rules") ? "workspace:*" : "latest";
512
+ if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "latest";
530
513
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = "0.35.0";
531
514
  if (ctx.config.formatter === "prettier") devDeps["prettier"] = "3.8.1";
532
515
  addReleaseDeps(devDeps, ctx.config);
@@ -709,7 +692,7 @@ function generateMigratePrompt(results, config, detected) {
709
692
  sections.push("");
710
693
  sections.push("- **Lint errors**: fix the code rather than adding disable comments or rule exceptions");
711
694
  sections.push("- **Test failures**: update the test or fix the underlying bug rather than skipping or deleting the test");
712
- sections.push("- **Knip findings**: remove genuinely unused code/exports/dependencies rather than adding ignores to `knip.json`");
695
+ sections.push("- **Knip findings**: remove genuinely unused code/exports/dependencies rather than adding ignores to `knip.config.ts`");
713
696
  sections.push("- **Type errors**: add proper types rather than using `any` or `@ts-expect-error`");
714
697
  sections.push("");
715
698
  sections.push("Only suppress an issue if there is a clear, documented reason why the fix is not feasible (e.g. a third-party type mismatch). Leave a comment explaining why.");
@@ -941,7 +924,7 @@ export default defineConfig({
941
924
  `;
942
925
  async function generateOxlint(ctx) {
943
926
  const filePath = "oxlint.config.ts";
944
- const content = ctx.config.useLintRules ? CONFIG_WITH_LINT_RULES : CONFIG_PRESET_ONLY;
927
+ const content = ctx.config.useEslintPlugin ? CONFIG_WITH_LINT_RULES : CONFIG_PRESET_ONLY;
945
928
  const existing = ctx.read(filePath);
946
929
  if (existing) {
947
930
  if (existing === content) return {
@@ -1051,12 +1034,19 @@ const STANDARD_ENTRIES = [
1051
1034
  "!.env.example",
1052
1035
  ".tooling-migrate.md"
1053
1036
  ];
1037
+ /** Normalize a gitignore entry for comparison: strip leading `/` and trailing `/`. */
1038
+ function normalizeEntry(entry) {
1039
+ let s = entry.trim();
1040
+ if (s.startsWith("/")) s = s.slice(1);
1041
+ if (s.endsWith("/")) s = s.slice(0, -1);
1042
+ return s;
1043
+ }
1054
1044
  async function generateGitignore(ctx) {
1055
1045
  const filePath = ".gitignore";
1056
1046
  const existing = ctx.read(filePath);
1057
1047
  if (existing) {
1058
- const existingLines = new Set(existing.split("\n").map((line) => line.trim()).filter((line) => line.length > 0));
1059
- const missing = STANDARD_ENTRIES.filter((entry) => !existingLines.has(entry));
1048
+ const existingNormalized = new Set(existing.split("\n").map(normalizeEntry).filter((line) => line.length > 0));
1049
+ const missing = STANDARD_ENTRIES.filter((entry) => !existingNormalized.has(normalizeEntry(entry)));
1060
1050
  if (missing.length === 0) return {
1061
1051
  filePath,
1062
1052
  action: "skipped",
@@ -1138,71 +1128,71 @@ async function generateCi(ctx) {
1138
1128
 
1139
1129
  //#endregion
1140
1130
  //#region src/generators/knip.ts
1141
- const KNIP_CONFIG_SINGLE = {
1142
- $schema: "https://unpkg.com/knip@latest/schema.json",
1143
- entry: ["src/index.ts"],
1144
- project: ["src/**/*.ts"],
1145
- ignore: ["dist/**"]
1146
- };
1147
- const KNIP_CONFIG_MONOREPO = {
1148
- $schema: "https://unpkg.com/knip@latest/schema.json",
1149
- workspaces: {
1150
- ".": {
1151
- entry: [],
1152
- project: []
1153
- },
1154
- "packages/*": {
1155
- entry: ["src/index.ts", "src/bin.ts"],
1156
- project: ["src/**/*.ts"],
1157
- ignore: ["dist/**"]
1158
- }
1159
- }
1160
- };
1131
+ const KNIP_CONFIG_SINGLE = `import type { KnipConfig } from "knip";
1132
+
1133
+ export default {
1134
+ entry: ["src/index.ts"],
1135
+ project: ["src/**/*.ts"],
1136
+ ignore: ["dist/**"],
1137
+ } satisfies KnipConfig;
1138
+ `;
1139
+ const KNIP_CONFIG_MONOREPO = `import type { KnipConfig } from "knip";
1140
+
1141
+ export default {
1142
+ workspaces: {
1143
+ ".": {
1144
+ entry: [],
1145
+ project: [],
1146
+ },
1147
+ "packages/*": {
1148
+ entry: ["src/index.ts", "src/bin.ts"],
1149
+ project: ["src/**/*.ts"],
1150
+ ignore: ["dist/**"],
1151
+ },
1152
+ },
1153
+ } satisfies KnipConfig;
1154
+ `;
1155
+ /** All known knip config file locations, in priority order. */
1156
+ const KNIP_CONFIG_PATHS = [
1157
+ "knip.config.ts",
1158
+ "knip.config.mts",
1159
+ "knip.json",
1160
+ "knip.jsonc",
1161
+ "knip.ts",
1162
+ "knip.mts"
1163
+ ];
1161
1164
  async function generateKnip(ctx) {
1162
- const filePath = "knip.json";
1163
- const existing = ctx.read(filePath);
1165
+ const filePath = "knip.config.ts";
1164
1166
  const isMonorepo = ctx.config.structure === "monorepo";
1165
- if (existing) {
1166
- const parsed = parseKnipJson(existing);
1167
- const changes = [];
1168
- if (isMonorepo && !parsed.workspaces) {
1169
- parsed.workspaces = KNIP_CONFIG_MONOREPO.workspaces;
1170
- changes.push("added monorepo workspaces config");
1171
- }
1172
- if (!isMonorepo) {
1173
- if (!parsed.entry) {
1174
- parsed.entry = KNIP_CONFIG_SINGLE.entry;
1175
- changes.push("added entry patterns");
1176
- }
1177
- if (!parsed.project) {
1178
- parsed.project = KNIP_CONFIG_SINGLE.project;
1179
- changes.push("added project patterns");
1180
- }
1181
- }
1182
- if (changes.length === 0) return {
1183
- filePath,
1184
- action: "skipped",
1185
- description: "Already configured"
1186
- };
1187
- ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
1188
- return {
1189
- filePath,
1190
- action: "updated",
1191
- description: changes.join(", ")
1192
- };
1193
- }
1167
+ const existingPath = KNIP_CONFIG_PATHS.find((p) => ctx.exists(p));
1168
+ if (existingPath) return {
1169
+ filePath: existingPath,
1170
+ action: "skipped",
1171
+ description: existingPath === filePath ? "Already configured" : `Existing config found at ${existingPath}`
1172
+ };
1194
1173
  const config = isMonorepo ? KNIP_CONFIG_MONOREPO : KNIP_CONFIG_SINGLE;
1195
- ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
1174
+ ctx.write(filePath, config);
1196
1175
  return {
1197
1176
  filePath,
1198
1177
  action: "created",
1199
- description: "Generated knip.json for dead code analysis"
1178
+ description: "Generated knip.config.ts for dead code analysis"
1200
1179
  };
1201
1180
  }
1202
1181
 
1203
1182
  //#endregion
1204
1183
  //#region src/generators/renovate.ts
1205
- const SHARED_PRESET = "@bensandee/config";
1184
+ const SHARED_PRESET = "local>bensandee/tooling";
1185
+ /** Deprecated npm-based preset to migrate away from. */
1186
+ const LEGACY_PRESET = "@bensandee/config";
1187
+ /** All known renovate config file locations, in priority order. */
1188
+ const RENOVATE_CONFIG_PATHS = [
1189
+ "renovate.json",
1190
+ "renovate.json5",
1191
+ ".renovaterc",
1192
+ ".renovaterc.json",
1193
+ ".github/renovate.json",
1194
+ ".github/renovate.json5"
1195
+ ];
1206
1196
  async function generateRenovate(ctx) {
1207
1197
  const filePath = "renovate.json";
1208
1198
  if (!ctx.config.setupRenovate) return {
@@ -1210,26 +1200,45 @@ async function generateRenovate(ctx) {
1210
1200
  action: "skipped",
1211
1201
  description: "Renovate not requested"
1212
1202
  };
1213
- const existing = ctx.read(filePath);
1214
- if (existing) {
1215
- const parsed = parseRenovateJson(existing);
1216
- const existingExtends = parsed.extends ?? [];
1217
- if (!existingExtends.includes(SHARED_PRESET)) {
1218
- existingExtends.unshift(SHARED_PRESET);
1219
- parsed.extends = existingExtends;
1220
- ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
1203
+ const existingPath = RENOVATE_CONFIG_PATHS.find((p) => ctx.exists(p));
1204
+ if (existingPath === filePath) {
1205
+ const existing = ctx.read(filePath);
1206
+ if (existing) {
1207
+ const parsed = parseRenovateJson(existing);
1208
+ const existingExtends = parsed.extends ?? [];
1209
+ const legacyIndex = existingExtends.indexOf(LEGACY_PRESET);
1210
+ if (legacyIndex !== -1) {
1211
+ existingExtends[legacyIndex] = SHARED_PRESET;
1212
+ parsed.extends = existingExtends;
1213
+ ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
1214
+ return {
1215
+ filePath,
1216
+ action: "updated",
1217
+ description: `Migrated extends: ${LEGACY_PRESET} → ${SHARED_PRESET}`
1218
+ };
1219
+ }
1220
+ if (!existingExtends.includes(SHARED_PRESET)) {
1221
+ existingExtends.unshift(SHARED_PRESET);
1222
+ parsed.extends = existingExtends;
1223
+ ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
1224
+ return {
1225
+ filePath,
1226
+ action: "updated",
1227
+ description: `Added extends: ${SHARED_PRESET}`
1228
+ };
1229
+ }
1221
1230
  return {
1222
1231
  filePath,
1223
- action: "updated",
1224
- description: `Added extends: ${SHARED_PRESET}`
1232
+ action: "skipped",
1233
+ description: "Already extends shared config"
1225
1234
  };
1226
1235
  }
1227
- return {
1228
- filePath,
1229
- action: "skipped",
1230
- description: "Already extends shared config"
1231
- };
1232
1236
  }
1237
+ if (existingPath) return {
1238
+ filePath: existingPath,
1239
+ action: "skipped",
1240
+ description: `Existing config found at ${existingPath}`
1241
+ };
1233
1242
  const config = {
1234
1243
  $schema: "https://docs.renovatebot.com/renovate-schema.json",
1235
1244
  extends: [SHARED_PRESET]
@@ -1670,31 +1679,50 @@ async function generateReleaseCi(ctx) {
1670
1679
  //#endregion
1671
1680
  //#region src/generators/lint-staged.ts
1672
1681
  function buildConfig(formatter) {
1673
- return `export default {\n "*": "${formatter === "prettier" ? "prettier --write" : "oxfmt"}",\n};\n`;
1682
+ return `export default {\n "*": "${formatter === "prettier" ? "prettier --write" : "oxfmt --no-error-on-unmatched-pattern"}",\n};\n`;
1674
1683
  }
1675
1684
  const HUSKY_PRE_COMMIT = "pnpm exec lint-staged\n";
1685
+ /** All known lint-staged config file locations, in priority order. */
1686
+ const LINT_STAGED_CONFIG_PATHS = [
1687
+ "lint-staged.config.mjs",
1688
+ "lint-staged.config.js",
1689
+ "lint-staged.config.cjs",
1690
+ ".lintstagedrc",
1691
+ ".lintstagedrc.json",
1692
+ ".lintstagedrc.yaml",
1693
+ ".lintstagedrc.yml",
1694
+ ".lintstagedrc.mjs",
1695
+ ".lintstagedrc.cjs"
1696
+ ];
1676
1697
  async function generateLintStaged(ctx) {
1677
1698
  const filePath = "lint-staged.config.mjs";
1678
1699
  const huskyPath = ".husky/pre-commit";
1679
1700
  const content = buildConfig(ctx.config.formatter);
1680
- const existing = ctx.read(filePath);
1681
1701
  if (ctx.read(huskyPath) !== HUSKY_PRE_COMMIT) ctx.write(huskyPath, HUSKY_PRE_COMMIT);
1682
- if (existing) {
1683
- if (existing === content) return {
1684
- filePath,
1685
- action: "skipped",
1686
- description: "Already configured"
1687
- };
1688
- if (await ctx.confirmOverwrite(filePath) === "skip") return {
1689
- filePath,
1690
- action: "skipped",
1691
- description: "Existing lint-staged config preserved"
1692
- };
1693
- }
1702
+ const existingPath = LINT_STAGED_CONFIG_PATHS.find((p) => ctx.exists(p));
1703
+ if (existingPath === filePath) {
1704
+ const existing = ctx.read(filePath);
1705
+ if (existing) {
1706
+ if (existing === content) return {
1707
+ filePath,
1708
+ action: "skipped",
1709
+ description: "Already configured"
1710
+ };
1711
+ if (await ctx.confirmOverwrite(filePath) === "skip") return {
1712
+ filePath,
1713
+ action: "skipped",
1714
+ description: "Existing lint-staged config preserved"
1715
+ };
1716
+ }
1717
+ } else if (existingPath) return {
1718
+ filePath: existingPath,
1719
+ action: "skipped",
1720
+ description: `Existing config found at ${existingPath}`
1721
+ };
1694
1722
  ctx.write(filePath, content);
1695
1723
  return {
1696
1724
  filePath,
1697
- action: existing ? "updated" : "created",
1725
+ action: existingPath === filePath ? "updated" : "created",
1698
1726
  description: "Generated lint-staged config and husky pre-commit hook"
1699
1727
  };
1700
1728
  }
@@ -1717,9 +1745,9 @@ const initCommand = defineCommand({
1717
1745
  alias: "y",
1718
1746
  description: "Accept all defaults (non-interactive)"
1719
1747
  },
1720
- "lint-rules": {
1748
+ "eslint-plugin": {
1721
1749
  type: "boolean",
1722
- description: "Include @bensandee/lint-rules (default: true)"
1750
+ description: "Include @bensandee/eslint-plugin (default: true)"
1723
1751
  },
1724
1752
  "no-ci": {
1725
1753
  type: "boolean",
@@ -1733,7 +1761,7 @@ const initCommand = defineCommand({
1733
1761
  async run({ args }) {
1734
1762
  const targetDir = path.resolve(args.dir ?? ".");
1735
1763
  await runInit(args.yes ? buildDefaultConfig(targetDir, {
1736
- lintRules: args["lint-rules"] === true ? true : void 0,
1764
+ eslintPlugin: args["eslint-plugin"] === true ? true : void 0,
1737
1765
  noCi: args["no-ci"] === true ? true : void 0
1738
1766
  }) : await runInitPrompts(targetDir), args["no-prompt"] === true ? { noPrompt: true } : {});
1739
1767
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "bin": {
6
6
  "tooling": "./dist/bin.mjs"
@@ -9,17 +9,13 @@
9
9
  "dist"
10
10
  ],
11
11
  "type": "module",
12
- "main": "./dist/index.mjs",
13
- "module": "./dist/index.mjs",
14
- "types": "./dist/index.d.mts",
15
12
  "imports": {
16
13
  "#src/*": "./src/*.ts"
17
14
  },
18
15
  "exports": {
19
- ".": {
20
- "types": "./dist/index.d.mts",
21
- "import": "./dist/index.mjs"
22
- }
16
+ ".": "./dist/index.mjs",
17
+ "./bin": "./dist/bin.mjs",
18
+ "./package.json": "./package.json"
23
19
  },
24
20
  "publishConfig": {
25
21
  "access": "public"
@@ -35,7 +31,7 @@
35
31
  "tsdown": "0.20.3",
36
32
  "typescript": "5.9.3",
37
33
  "vitest": "4.0.18",
38
- "@bensandee/config": "0.2.0"
34
+ "@bensandee/config": "0.4.0"
39
35
  },
40
36
  "scripts": {
41
37
  "build": "tsdown",
package/dist/bin.d.mts DELETED
@@ -1 +0,0 @@
1
- export { };
package/dist/index.d.mts DELETED
@@ -1,102 +0,0 @@
1
- import { z } from "zod";
2
-
3
- //#region src/utils/json.d.ts
4
- declare const PackageJsonSchema: z.ZodObject<{
5
- name: z.ZodOptional<z.ZodString>;
6
- version: z.ZodOptional<z.ZodString>;
7
- private: z.ZodOptional<z.ZodBoolean>;
8
- type: z.ZodOptional<z.ZodString>;
9
- scripts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
10
- dependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
11
- devDependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
12
- bin: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
13
- exports: z.ZodOptional<z.ZodUnknown>;
14
- main: z.ZodOptional<z.ZodString>;
15
- types: z.ZodOptional<z.ZodString>;
16
- typings: z.ZodOptional<z.ZodString>;
17
- engines: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
18
- }, z.core.$loose>;
19
- type PackageJson = z.infer<typeof PackageJsonSchema>;
20
- //#endregion
21
- //#region src/types.d.ts
22
- type CiPlatform = "github" | "forgejo" | "none";
23
- type ReleaseStrategy = "release-it" | "commit-and-tag-version" | "changesets" | "none";
24
- /** User's answers from the interactive prompt or CLI flags. */
25
- interface ProjectConfig {
26
- /** Project name (from package.json name or user input) */
27
- name: string;
28
- /** Whether this is a new project or existing */
29
- isNew: boolean;
30
- /** Project structure */
31
- structure: "single" | "monorepo";
32
- /** Include @bensandee/lint-rules oxlint plugin */
33
- useLintRules: boolean;
34
- /** Formatter choice */
35
- formatter: "oxfmt" | "prettier";
36
- /** Set up vitest with a starter test */
37
- setupVitest: boolean;
38
- /** CI platform choice */
39
- ci: CiPlatform;
40
- /** Set up Renovate for automated dependency updates */
41
- setupRenovate: boolean;
42
- /** Release management strategy */
43
- releaseStrategy: ReleaseStrategy;
44
- /** Project type determines tsconfig base configuration */
45
- projectType: "default" | "node" | "react" | "library";
46
- /** Auto-detect and configure tsconfig bases for monorepo packages */
47
- detectPackageTypes: boolean;
48
- /** Target directory (default: cwd) */
49
- targetDir: string;
50
- }
51
- /** Result from a single generator: what file was written and how. */
52
- interface GeneratorResult {
53
- filePath: string;
54
- action: "created" | "updated" | "skipped";
55
- /** Human-readable description of what changed */
56
- description: string;
57
- }
58
- /** Context passed to each generator function. */
59
- interface GeneratorContext {
60
- config: ProjectConfig;
61
- /** Absolute path to target directory */
62
- targetDir: string;
63
- /** Pre-parsed package.json from the target directory, or undefined if missing/invalid */
64
- packageJson: PackageJson | undefined;
65
- /** Check whether a file exists in the target directory */
66
- exists: (relativePath: string) => boolean;
67
- /** Read an existing file from the target directory, returns undefined if not found */
68
- read: (relativePath: string) => string | undefined;
69
- /** Write a file to the target directory (creating directories as needed) */
70
- write: (relativePath: string, content: string) => void;
71
- /** Prompt user for conflict resolution on non-mergeable files */
72
- confirmOverwrite: (relativePath: string) => Promise<"overwrite" | "skip">;
73
- }
74
- /** Generator function signature. */
75
- type Generator = (ctx: GeneratorContext) => Promise<GeneratorResult>;
76
- /** State detected from an existing project directory. */
77
- interface DetectedProjectState {
78
- hasPackageJson: boolean;
79
- hasTsconfig: boolean;
80
- hasOxlintConfig: boolean;
81
- /** Legacy .oxlintrc.json found (should be migrated to oxlint.config.ts) */
82
- hasLegacyOxlintJson: boolean;
83
- hasGitignore: boolean;
84
- hasVitestConfig: boolean;
85
- hasTsdownConfig: boolean;
86
- hasPnpmWorkspace: boolean;
87
- hasKnipConfig: boolean;
88
- hasRenovateConfig: boolean;
89
- hasReleaseItConfig: boolean;
90
- hasCommitAndTagVersionConfig: boolean;
91
- hasChangesetsConfig: boolean;
92
- /** Legacy tooling configs found */
93
- legacyConfigs: LegacyConfig[];
94
- }
95
- declare const LEGACY_TOOLS: readonly ["eslint", "prettier", "jest", "webpack", "rollup"];
96
- type LegacyTool = (typeof LEGACY_TOOLS)[number];
97
- interface LegacyConfig {
98
- tool: LegacyTool;
99
- files: string[];
100
- }
101
- //#endregion
102
- export { type DetectedProjectState, type Generator, type GeneratorContext, type GeneratorResult, type LegacyConfig, type ProjectConfig };