frontpl 0.3.2 → 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/README.md +50 -0
- package/dist/cli.mjs +5 -1
- package/dist/index.d.mts +18 -1
- package/dist/index.mjs +2 -2
- package/dist/{init-oJwslpqP.mjs → oxfmt-Bgtl3zwv.mjs} +575 -57
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -42,12 +42,14 @@ Generated lint-related dependencies (`oxlint`, `oxlint-tsgolint`, `oxfmt`, `@kin
|
|
|
42
42
|
### `frontpl [name]` / `frontpl init [name]`
|
|
43
43
|
|
|
44
44
|
Scaffold a new project into `./<name>` (or prompt for a name when omitted).
|
|
45
|
+
Project names support letters (including uppercase/camel case), numbers, `.`, `_`, `-` (cannot start with `.` or `_`).
|
|
45
46
|
|
|
46
47
|
Generated output includes (based on options):
|
|
47
48
|
|
|
48
49
|
- `.editorconfig`, `.gitignore`, `.gitattributes`
|
|
49
50
|
- `package.json` (+ scripts like optional `lint`, `format:check`, `test`, `build`)
|
|
50
51
|
- `tsconfig.json`, `src/index.ts`
|
|
52
|
+
- Relative TypeScript imports use explicit `.ts` extensions (e.g. generated `src/index.test.ts`)
|
|
51
53
|
- Optional configs: `oxlint.config.ts`, `.oxfmtrc.json`, `tsdown.config.ts`
|
|
52
54
|
- Optional GitHub Actions workflows in `.github/workflows/`
|
|
53
55
|
|
|
@@ -64,6 +66,52 @@ What it does:
|
|
|
64
66
|
- Optionally generates `.github/workflows/release.yml` (tag/commit/both)
|
|
65
67
|
- Optionally generates `.github/dependabot.yml` with grouped updates (`dependencies`, `github-actions`)
|
|
66
68
|
|
|
69
|
+
### `frontpl oxlint`
|
|
70
|
+
|
|
71
|
+
Add/migrate linting in the current project to `oxlint`.
|
|
72
|
+
|
|
73
|
+
What it does:
|
|
74
|
+
|
|
75
|
+
- Asks strategy interactively:
|
|
76
|
+
- Migrate gradually (keep existing ESLint assets)
|
|
77
|
+
- Replace ESLint directly (current mode)
|
|
78
|
+
- Ensures `package.json` scripts use:
|
|
79
|
+
- `lint`: `oxlint --type-aware --type-check`
|
|
80
|
+
- `lint:fix`: `oxlint --type-aware --type-check --fix`
|
|
81
|
+
- Removes `typecheck: tsc --noEmit` when confirmed (or by default with `--yes`)
|
|
82
|
+
- Ensures devDependencies exist:
|
|
83
|
+
- `oxlint`
|
|
84
|
+
- `oxlint-tsgolint`
|
|
85
|
+
- `@kingsword/lint-config`
|
|
86
|
+
- Creates or updates `oxlint.config.ts` using `@kingsword/lint-config`
|
|
87
|
+
- In replace mode, removes ESLint deps/configs (`eslint*`, `@eslint/*`, `@typescript-eslint/*`, `eslintConfig`, `.eslintrc*`, `eslint.config.*`)
|
|
88
|
+
- Optionally installs dependencies with detected package manager
|
|
89
|
+
|
|
90
|
+
Use `--yes` (or `-y`) to skip confirmations and apply default choices.
|
|
91
|
+
With `--yes`, strategy defaults to `replace`.
|
|
92
|
+
|
|
93
|
+
### `frontpl oxfmt`
|
|
94
|
+
|
|
95
|
+
Add/migrate formatting in the current project to `oxfmt`.
|
|
96
|
+
|
|
97
|
+
What it does:
|
|
98
|
+
|
|
99
|
+
- Asks config strategy interactively:
|
|
100
|
+
- Migrate from Prettier (`oxfmt --migrate=prettier`)
|
|
101
|
+
- Rebuild `.oxfmtrc.json` (current mode)
|
|
102
|
+
- Ensures `package.json` scripts use:
|
|
103
|
+
- `format`: `oxfmt`
|
|
104
|
+
- `format:check`: `oxfmt --check`
|
|
105
|
+
- `fmt`: `oxfmt`
|
|
106
|
+
- `fmt:check`: `oxfmt --check`
|
|
107
|
+
- Ensures `devDependencies.oxfmt` exists (defaults to `latest` when missing)
|
|
108
|
+
- Creates or updates `.oxfmtrc.json`
|
|
109
|
+
- Optionally removes `prettier` / `prettier-plugin-*` / `@prettier/plugin-*` dependencies, `package.json#prettier`, and Prettier config files (`.prettierrc*`, `prettier.config.*`)
|
|
110
|
+
- Optionally installs dependencies with detected package manager
|
|
111
|
+
|
|
112
|
+
Use `--yes` (or `-y`) to skip confirmations and apply default choices.
|
|
113
|
+
With `--yes`, config strategy defaults to rebuild `.oxfmtrc.json`.
|
|
114
|
+
|
|
67
115
|
## GitHub Actions (CI + Release)
|
|
68
116
|
|
|
69
117
|
frontpl generates workflows that call reusable workflows from `kingsword09/workflows` (pinned to commit SHA + `# vX.Y.Z` comment by default):
|
|
@@ -100,6 +148,8 @@ pnpm run lint
|
|
|
100
148
|
pnpm run build
|
|
101
149
|
node dist/cli.mjs --help
|
|
102
150
|
node dist/cli.mjs ci
|
|
151
|
+
node dist/cli.mjs oxlint --help
|
|
152
|
+
node dist/cli.mjs oxfmt --help
|
|
103
153
|
```
|
|
104
154
|
|
|
105
155
|
## Lint preset
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { a as runCi, n as runOxlint, r as runInit, t as runOxfmt } from "./oxfmt-Bgtl3zwv.mjs";
|
|
3
3
|
import bin from "tiny-bin";
|
|
4
4
|
|
|
5
5
|
//#region src/cli.ts
|
|
@@ -10,6 +10,10 @@ async function main() {
|
|
|
10
10
|
await runInit({ nameArg: args[0] });
|
|
11
11
|
}).command("ci", "Add CI/release workflows to an existing project").action(async () => {
|
|
12
12
|
await runCi();
|
|
13
|
+
}).command("oxlint", "Add/migrate linter to oxlint in current project").option("--yes, -y", "Skip confirmations and use defaults").action(async (options) => {
|
|
14
|
+
await runOxlint({ yes: options.yes === true });
|
|
15
|
+
}).command("oxfmt", "Add/migrate formatter to oxfmt in current project").option("--yes, -y", "Skip confirmations and use defaults").action(async (options) => {
|
|
16
|
+
await runOxfmt({ yes: options.yes === true });
|
|
13
17
|
}).run();
|
|
14
18
|
}
|
|
15
19
|
main();
|
package/dist/index.d.mts
CHANGED
|
@@ -7,6 +7,23 @@ declare function runInit({
|
|
|
7
7
|
}: {
|
|
8
8
|
nameArg?: string;
|
|
9
9
|
}): Promise<void>;
|
|
10
|
+
declare function validateProjectName(value: string | undefined): "Project name is required" | "Project name is too long" | "Project name cannot start with '.'" | "Project name cannot start with '_'" | "Use letters, numbers, '.', '_' or '-'" | undefined;
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/commands/oxlint.d.ts
|
|
13
|
+
type CommandOptions$1 = {
|
|
14
|
+
yes?: boolean;
|
|
15
|
+
};
|
|
16
|
+
declare function runOxlint({
|
|
17
|
+
yes
|
|
18
|
+
}?: CommandOptions$1): Promise<void>;
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/commands/oxfmt.d.ts
|
|
21
|
+
type CommandOptions = {
|
|
22
|
+
yes?: boolean;
|
|
23
|
+
};
|
|
24
|
+
declare function runOxfmt({
|
|
25
|
+
yes
|
|
26
|
+
}?: CommandOptions): Promise<void>;
|
|
10
27
|
//#endregion
|
|
11
28
|
//#region src/lib/templates.d.ts
|
|
12
29
|
declare function oxlintConfigTemplate({
|
|
@@ -48,4 +65,4 @@ declare function githubDependabotTemplate(opts: {
|
|
|
48
65
|
workingDirectory: string;
|
|
49
66
|
}): string;
|
|
50
67
|
//#endregion
|
|
51
|
-
export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runCi, runInit };
|
|
68
|
+
export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runCi, runInit, runOxfmt, runOxlint, validateProjectName };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as oxlintConfigTemplate, i as
|
|
1
|
+
import { a as runCi, c as oxlintConfigTemplate, i as validateProjectName, l as packageJsonTemplate, n as runOxlint, o as githubCliCiWorkflowTemplate, r as runInit, s as githubDependabotTemplate, t as runOxfmt } from "./oxfmt-Bgtl3zwv.mjs";
|
|
2
2
|
|
|
3
|
-
export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runCi, runInit };
|
|
3
|
+
export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runCi, runInit, runOxfmt, runOxlint, validateProjectName };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
2
|
-
import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
@@ -11,6 +11,48 @@ async function writeText(filePath, contents) {
|
|
|
11
11
|
await writeFile(filePath, contents, "utf8");
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/lib/utils.ts
|
|
16
|
+
async function pathExists(pathname) {
|
|
17
|
+
try {
|
|
18
|
+
await access(pathname);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/lib/project.ts
|
|
27
|
+
async function readPackageJson(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
30
|
+
} catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function writePackageJson(filePath, value) {
|
|
35
|
+
await writeText(filePath, JSON.stringify(value, null, 2) + "\n");
|
|
36
|
+
}
|
|
37
|
+
async function detectPackageManager(rootDir) {
|
|
38
|
+
const pmField = (await readPackageJson(path.join(rootDir, "package.json")))?.packageManager;
|
|
39
|
+
if (pmField) {
|
|
40
|
+
const pm = pmField.split("@")[0] ?? "";
|
|
41
|
+
if (isPackageManager(pm)) return pm;
|
|
42
|
+
}
|
|
43
|
+
const candidates = [];
|
|
44
|
+
if (await pathExists(path.join(rootDir, "pnpm-lock.yaml"))) candidates.push("pnpm");
|
|
45
|
+
if (await pathExists(path.join(rootDir, "yarn.lock"))) candidates.push("yarn");
|
|
46
|
+
if (await pathExists(path.join(rootDir, "package-lock.json"))) candidates.push("npm");
|
|
47
|
+
if (await pathExists(path.join(rootDir, "bun.lockb"))) candidates.push("bun");
|
|
48
|
+
if (await pathExists(path.join(rootDir, "bun.lock"))) candidates.push("bun");
|
|
49
|
+
if (await pathExists(path.join(rootDir, "deno.json")) || await pathExists(path.join(rootDir, "deno.jsonc"))) candidates.push("deno");
|
|
50
|
+
return candidates.length === 1 ? candidates[0] : void 0;
|
|
51
|
+
}
|
|
52
|
+
function isPackageManager(value) {
|
|
53
|
+
return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun" || value === "deno";
|
|
54
|
+
}
|
|
55
|
+
|
|
14
56
|
//#endregion
|
|
15
57
|
//#region src/lib/templates.ts
|
|
16
58
|
function editorconfigTemplate() {
|
|
@@ -69,7 +111,7 @@ function srcIndexTemplate() {
|
|
|
69
111
|
function srcVitestTemplate() {
|
|
70
112
|
return [
|
|
71
113
|
"import { describe, expect, it } from \"vitest\";",
|
|
72
|
-
"import { hello } from \"./index.
|
|
114
|
+
"import { hello } from \"./index.ts\";",
|
|
73
115
|
"",
|
|
74
116
|
"describe(\"hello\", () => {",
|
|
75
117
|
" it(\"greets\", () => {",
|
|
@@ -333,17 +375,6 @@ function yamlString(value) {
|
|
|
333
375
|
return JSON.stringify(value);
|
|
334
376
|
}
|
|
335
377
|
|
|
336
|
-
//#endregion
|
|
337
|
-
//#region src/lib/utils.ts
|
|
338
|
-
async function pathExists(pathname) {
|
|
339
|
-
try {
|
|
340
|
-
await access(pathname);
|
|
341
|
-
return true;
|
|
342
|
-
} catch {
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
378
|
//#endregion
|
|
348
379
|
//#region src/commands/ci.ts
|
|
349
380
|
async function runCi() {
|
|
@@ -377,7 +408,7 @@ async function runCi() {
|
|
|
377
408
|
}
|
|
378
409
|
]
|
|
379
410
|
});
|
|
380
|
-
if (isCancel(packageManager)) return abort();
|
|
411
|
+
if (isCancel(packageManager)) return abort$2();
|
|
381
412
|
const candidates = await listPackageCandidates(rootDir, packageManager);
|
|
382
413
|
if (candidates.length === 0) {
|
|
383
414
|
cancel("No package found. Run this command in a project root (with package.json or deno.json).");
|
|
@@ -393,7 +424,7 @@ async function runCi() {
|
|
|
393
424
|
label: c
|
|
394
425
|
}))
|
|
395
426
|
});
|
|
396
|
-
if (isCancel(workingDirectory)) return abort();
|
|
427
|
+
if (isCancel(workingDirectory)) return abort$2();
|
|
397
428
|
const nodeVersionDefault = await detectNodeMajorVersion(rootDir) ?? 22;
|
|
398
429
|
const nodeVersionText = await text({
|
|
399
430
|
message: "Node.js major version (for GitHub Actions)",
|
|
@@ -403,14 +434,14 @@ async function runCi() {
|
|
|
403
434
|
if (!Number.isFinite(major) || major <= 0) return "Enter a valid major version (e.g. 22)";
|
|
404
435
|
}
|
|
405
436
|
});
|
|
406
|
-
if (isCancel(nodeVersionText)) return abort();
|
|
437
|
+
if (isCancel(nodeVersionText)) return abort$2();
|
|
407
438
|
const nodeVersion = Number.parseInt(String(nodeVersionText).trim(), 10);
|
|
408
439
|
const { runLint, runFormatCheck, runTests, lintCommand, formatCheckCommand, testCommand } = await resolveCiCommands(rootDir, workingDirectory, packageManager);
|
|
409
440
|
const addRelease = await confirm({
|
|
410
441
|
message: "Add release workflow too?",
|
|
411
442
|
initialValue: true
|
|
412
443
|
});
|
|
413
|
-
if (isCancel(addRelease)) return abort();
|
|
444
|
+
if (isCancel(addRelease)) return abort$2();
|
|
414
445
|
const releaseMode = addRelease ? await select({
|
|
415
446
|
message: "Release workflows",
|
|
416
447
|
initialValue: "tag",
|
|
@@ -429,17 +460,17 @@ async function runCi() {
|
|
|
429
460
|
}
|
|
430
461
|
]
|
|
431
462
|
}) : void 0;
|
|
432
|
-
if (isCancel(releaseMode)) return abort();
|
|
463
|
+
if (isCancel(releaseMode)) return abort$2();
|
|
433
464
|
const trustedPublishing = addRelease && packageManager !== "deno" ? await confirm({
|
|
434
465
|
message: "Release: npm trusted publishing (OIDC)?",
|
|
435
466
|
initialValue: true
|
|
436
467
|
}) : void 0;
|
|
437
|
-
if (isCancel(trustedPublishing)) return abort();
|
|
468
|
+
if (isCancel(trustedPublishing)) return abort$2();
|
|
438
469
|
const addDependabot = await pathExists(path.join(rootDir, ".git")) ? await confirm({
|
|
439
470
|
message: "Add/update Dependabot config (.github/dependabot.yml)?",
|
|
440
471
|
initialValue: true
|
|
441
472
|
}) : false;
|
|
442
|
-
if (isCancel(addDependabot)) return abort();
|
|
473
|
+
if (isCancel(addDependabot)) return abort$2();
|
|
443
474
|
const ciWorkflowPath = path.join(rootDir, ".github/workflows/ci.yml");
|
|
444
475
|
const releaseWorkflowPath = path.join(rootDir, ".github/workflows/release.yml");
|
|
445
476
|
const dependabotPath = path.join(rootDir, ".github/dependabot.yml");
|
|
@@ -475,19 +506,19 @@ async function runCi() {
|
|
|
475
506
|
}
|
|
476
507
|
outro(addRelease ? "Done. Generated CI + release workflows (and optional Dependabot)." : "Done. Generated CI workflow (and optional Dependabot).");
|
|
477
508
|
} catch (err) {
|
|
478
|
-
if (err instanceof CancelledError) return;
|
|
509
|
+
if (err instanceof CancelledError$2) return;
|
|
479
510
|
throw err;
|
|
480
511
|
}
|
|
481
512
|
}
|
|
482
|
-
var CancelledError = class extends Error {
|
|
513
|
+
var CancelledError$2 = class extends Error {
|
|
483
514
|
constructor() {
|
|
484
515
|
super("Cancelled");
|
|
485
516
|
}
|
|
486
517
|
};
|
|
487
|
-
function abort(opts = {}) {
|
|
518
|
+
function abort$2(opts = {}) {
|
|
488
519
|
cancel(opts.message ?? "Cancelled");
|
|
489
520
|
process.exitCode = opts.exitCode ?? 0;
|
|
490
|
-
throw new CancelledError();
|
|
521
|
+
throw new CancelledError$2();
|
|
491
522
|
}
|
|
492
523
|
async function confirmOverwriteIfExists(absPath, label) {
|
|
493
524
|
if (!await pathExists(absPath)) return true;
|
|
@@ -495,26 +526,9 @@ async function confirmOverwriteIfExists(absPath, label) {
|
|
|
495
526
|
message: `Overwrite existing ${label}?`,
|
|
496
527
|
initialValue: true
|
|
497
528
|
});
|
|
498
|
-
if (isCancel(overwrite)) return abort();
|
|
529
|
+
if (isCancel(overwrite)) return abort$2();
|
|
499
530
|
return overwrite;
|
|
500
531
|
}
|
|
501
|
-
function isPackageManager(value) {
|
|
502
|
-
return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun" || value === "deno";
|
|
503
|
-
}
|
|
504
|
-
async function detectPackageManager(rootDir) {
|
|
505
|
-
const pmField = (await readPackageJson(path.join(rootDir, "package.json")))?.packageManager;
|
|
506
|
-
if (pmField) {
|
|
507
|
-
const pm = pmField.split("@")[0] ?? "";
|
|
508
|
-
if (isPackageManager(pm)) return pm;
|
|
509
|
-
}
|
|
510
|
-
const candidates = [];
|
|
511
|
-
if (await pathExists(path.join(rootDir, "pnpm-lock.yaml"))) candidates.push("pnpm");
|
|
512
|
-
if (await pathExists(path.join(rootDir, "yarn.lock"))) candidates.push("yarn");
|
|
513
|
-
if (await pathExists(path.join(rootDir, "package-lock.json"))) candidates.push("npm");
|
|
514
|
-
if (await pathExists(path.join(rootDir, "bun.lockb"))) candidates.push("bun");
|
|
515
|
-
if (await pathExists(path.join(rootDir, "deno.json")) || await pathExists(path.join(rootDir, "deno.jsonc"))) candidates.push("deno");
|
|
516
|
-
return candidates.length === 1 ? candidates[0] : void 0;
|
|
517
|
-
}
|
|
518
532
|
async function listPackageCandidates(rootDir, packageManager) {
|
|
519
533
|
const candidates = /* @__PURE__ */ new Set();
|
|
520
534
|
if (await pathExists(path.join(rootDir, "package.json"))) candidates.add(".");
|
|
@@ -564,7 +578,7 @@ async function resolveCiCommands(rootDir, workingDirectory, packageManager) {
|
|
|
564
578
|
runTests: true
|
|
565
579
|
};
|
|
566
580
|
const pkg = await readPackageJson(path.join(rootDir, workingDirectory, "package.json"));
|
|
567
|
-
if (!pkg) return abort({
|
|
581
|
+
if (!pkg) return abort$2({
|
|
568
582
|
message: `Missing package.json in ${workingDirectory}`,
|
|
569
583
|
exitCode: 1
|
|
570
584
|
});
|
|
@@ -580,17 +594,17 @@ async function resolveCiCommands(rootDir, workingDirectory, packageManager) {
|
|
|
580
594
|
message: `CI: run lint${hasLint ? "" : " (no lint script detected)"}`,
|
|
581
595
|
initialValue: runLintDefault
|
|
582
596
|
});
|
|
583
|
-
if (isCancel(runLint)) return abort();
|
|
597
|
+
if (isCancel(runLint)) return abort$2();
|
|
584
598
|
const runFormatCheck = await confirm({
|
|
585
599
|
message: `CI: run format check${runFormatCheckDefault ? "" : " (no format check script detected)"}`,
|
|
586
600
|
initialValue: runFormatCheckDefault
|
|
587
601
|
});
|
|
588
|
-
if (isCancel(runFormatCheck)) return abort();
|
|
602
|
+
if (isCancel(runFormatCheck)) return abort$2();
|
|
589
603
|
const runTests = await confirm({
|
|
590
604
|
message: `CI: run tests${hasTest ? "" : " (no test script detected)"}`,
|
|
591
605
|
initialValue: runTestsDefault
|
|
592
606
|
});
|
|
593
|
-
if (isCancel(runTests)) return abort();
|
|
607
|
+
if (isCancel(runTests)) return abort$2();
|
|
594
608
|
return {
|
|
595
609
|
runLint,
|
|
596
610
|
runFormatCheck,
|
|
@@ -606,7 +620,7 @@ async function promptCommand(message, initialValue) {
|
|
|
606
620
|
initialValue,
|
|
607
621
|
validate: (v = "") => !v.trim() ? "Command is required" : void 0
|
|
608
622
|
});
|
|
609
|
-
if (isCancel(value)) return abort();
|
|
623
|
+
if (isCancel(value)) return abort$2();
|
|
610
624
|
return String(value).trim();
|
|
611
625
|
}
|
|
612
626
|
function pmRun$1(pm, script) {
|
|
@@ -618,13 +632,6 @@ function pmRun$1(pm, script) {
|
|
|
618
632
|
case "deno": return script;
|
|
619
633
|
}
|
|
620
634
|
}
|
|
621
|
-
async function readPackageJson(filePath) {
|
|
622
|
-
try {
|
|
623
|
-
return JSON.parse(await readFile(filePath, "utf8"));
|
|
624
|
-
} catch {
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
635
|
|
|
629
636
|
//#endregion
|
|
630
637
|
//#region src/lib/exec.ts
|
|
@@ -918,8 +925,7 @@ function validateProjectName(value) {
|
|
|
918
925
|
if (name.length > 214) return "Project name is too long";
|
|
919
926
|
if (name.startsWith(".")) return "Project name cannot start with '.'";
|
|
920
927
|
if (name.startsWith("_")) return "Project name cannot start with '_'";
|
|
921
|
-
if (
|
|
922
|
-
if (!/^[a-z0-9._-]+$/.test(name)) return "Use letters, numbers, '.', '_' or '-'";
|
|
928
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name)) return "Use letters, numbers, '.', '_' or '-'";
|
|
923
929
|
}
|
|
924
930
|
function onCancel() {
|
|
925
931
|
cancel("Cancelled");
|
|
@@ -936,4 +942,516 @@ function nextStepHint(pm) {
|
|
|
936
942
|
}
|
|
937
943
|
|
|
938
944
|
//#endregion
|
|
939
|
-
|
|
945
|
+
//#region src/commands/oxlint.ts
|
|
946
|
+
const OXLINT_COMMAND = "oxlint --type-aware --type-check";
|
|
947
|
+
const OXLINT_FIX_COMMAND = `${OXLINT_COMMAND} --fix`;
|
|
948
|
+
const OXLINT_SCRIPTS = {
|
|
949
|
+
lint: OXLINT_COMMAND,
|
|
950
|
+
"lint:fix": OXLINT_FIX_COMMAND
|
|
951
|
+
};
|
|
952
|
+
const ESLINT_CONFIG_FILES = [
|
|
953
|
+
".eslintrc",
|
|
954
|
+
".eslintrc.js",
|
|
955
|
+
".eslintrc.cjs",
|
|
956
|
+
".eslintrc.mjs",
|
|
957
|
+
".eslintrc.json",
|
|
958
|
+
".eslintrc.yaml",
|
|
959
|
+
".eslintrc.yml",
|
|
960
|
+
".eslintrc.ts",
|
|
961
|
+
".eslintrc.cts",
|
|
962
|
+
".eslintrc.mts",
|
|
963
|
+
"eslint.config.js",
|
|
964
|
+
"eslint.config.cjs",
|
|
965
|
+
"eslint.config.mjs",
|
|
966
|
+
"eslint.config.ts",
|
|
967
|
+
"eslint.config.cts",
|
|
968
|
+
"eslint.config.mts"
|
|
969
|
+
];
|
|
970
|
+
const OXLINT_DEPENDENCIES = [
|
|
971
|
+
"oxlint",
|
|
972
|
+
"oxlint-tsgolint",
|
|
973
|
+
"@kingsword/lint-config"
|
|
974
|
+
];
|
|
975
|
+
async function runOxlint({ yes = false } = {}) {
|
|
976
|
+
try {
|
|
977
|
+
intro("frontpl (oxlint)");
|
|
978
|
+
const rootDir = process.cwd();
|
|
979
|
+
const packageJsonPath = path.join(rootDir, "package.json");
|
|
980
|
+
const oxlintConfigPath = path.join(rootDir, "oxlint.config.ts");
|
|
981
|
+
const pkg = await readPackageJson(packageJsonPath);
|
|
982
|
+
if (!pkg) {
|
|
983
|
+
cancel("Missing package.json. Run this command in a Node project root.");
|
|
984
|
+
process.exitCode = 1;
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const packageManager = await detectPackageManager(rootDir) ?? "pnpm";
|
|
988
|
+
const stats = await migrateToOxlint({
|
|
989
|
+
pkg,
|
|
990
|
+
rootDir,
|
|
991
|
+
oxlintConfigPath,
|
|
992
|
+
strategy: yes ? "replace" : await askMigrationStrategy({
|
|
993
|
+
rootDir,
|
|
994
|
+
pkg
|
|
995
|
+
}),
|
|
996
|
+
yes
|
|
997
|
+
});
|
|
998
|
+
await writePackageJson(packageJsonPath, pkg);
|
|
999
|
+
const installOk = await maybeInstallDependencies$1({
|
|
1000
|
+
yes,
|
|
1001
|
+
packageManager,
|
|
1002
|
+
rootDir
|
|
1003
|
+
});
|
|
1004
|
+
const scriptSummary = stats.scriptsUpdated.length > 0 ? `updated scripts: ${stats.scriptsUpdated.join(", ")}` : stats.scriptsKept.length > 0 ? `kept existing scripts: ${stats.scriptsKept.join(", ")}` : "scripts already aligned";
|
|
1005
|
+
const dependencySummary = stats.addedDevDependencies.length > 0 ? `added devDependencies: ${stats.addedDevDependencies.join(", ")}` : "required oxlint devDependencies already present";
|
|
1006
|
+
const typecheckSummary = stats.removedTypecheckScript ? "removed redundant typecheck script (tsc --noEmit)" : "kept typecheck script";
|
|
1007
|
+
const eslintDependencySummary = stats.removedDependencies.length > 0 ? `removed eslint deps: ${stats.removedDependencies.join(", ")}` : "no eslint deps removed";
|
|
1008
|
+
const eslintConfigSummary = stats.removedPackageJsonEslintConfig ? "removed package.json#eslintConfig" : "no package.json#eslintConfig removed";
|
|
1009
|
+
const eslintFileSummary = stats.removedConfigFiles.length > 0 ? `removed eslint config files: ${stats.removedConfigFiles.join(", ")}` : "no eslint config files removed";
|
|
1010
|
+
const oxlintConfigSummary = stats.oxlintConfigAction === "written" ? "wrote oxlint.config.ts" : "kept existing oxlint.config.ts";
|
|
1011
|
+
const installSummary = packageManager === "deno" ? "skipped dependency install (deno project)" : installOk === true ? `installed dependencies with ${packageManager}` : installOk === false ? `dependency install failed with ${packageManager}` : "skipped dependency install";
|
|
1012
|
+
outro([
|
|
1013
|
+
"Done. Applied oxlint migration.",
|
|
1014
|
+
`- strategy: ${stats.strategy === "migrate" ? "migrate (keep ESLint assets)" : "replace ESLint assets"}`,
|
|
1015
|
+
`- ${scriptSummary}`,
|
|
1016
|
+
`- ${typecheckSummary}`,
|
|
1017
|
+
`- ${dependencySummary}`,
|
|
1018
|
+
`- ${eslintDependencySummary}`,
|
|
1019
|
+
`- ${eslintConfigSummary}`,
|
|
1020
|
+
`- ${eslintFileSummary}`,
|
|
1021
|
+
`- ${oxlintConfigSummary}`,
|
|
1022
|
+
`- ${installSummary}`
|
|
1023
|
+
].join("\n"));
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
if (error instanceof CancelledError$1) return;
|
|
1026
|
+
throw error;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
async function migrateToOxlint(opts) {
|
|
1030
|
+
const { pkg, rootDir, oxlintConfigPath, strategy, yes } = opts;
|
|
1031
|
+
const scripts = { ...pkg.scripts };
|
|
1032
|
+
const conflictingScripts = Object.entries(OXLINT_SCRIPTS).filter(([name, command]) => typeof scripts[name] === "string" && scripts[name] !== command).map(([name]) => name);
|
|
1033
|
+
const shouldOverwriteConflicts = conflictingScripts.length === 0 ? true : yes ? true : await askConfirm$1({
|
|
1034
|
+
message: `Overwrite conflicting scripts (${conflictingScripts.join(", ")}) with oxlint?`,
|
|
1035
|
+
initialValue: true
|
|
1036
|
+
});
|
|
1037
|
+
const scriptsUpdated = [];
|
|
1038
|
+
const scriptsKept = [];
|
|
1039
|
+
for (const [name, command] of Object.entries(OXLINT_SCRIPTS)) {
|
|
1040
|
+
const current = scripts[name];
|
|
1041
|
+
if (current === command) continue;
|
|
1042
|
+
if (current && !shouldOverwriteConflicts) {
|
|
1043
|
+
scriptsKept.push(name);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
scripts[name] = command;
|
|
1047
|
+
scriptsUpdated.push(name);
|
|
1048
|
+
}
|
|
1049
|
+
let removedTypecheckScript = false;
|
|
1050
|
+
if (scripts.typecheck === "tsc --noEmit") {
|
|
1051
|
+
if (yes || await askConfirm$1({
|
|
1052
|
+
message: "Remove redundant typecheck script (tsc --noEmit)?",
|
|
1053
|
+
initialValue: true
|
|
1054
|
+
})) {
|
|
1055
|
+
delete scripts.typecheck;
|
|
1056
|
+
removedTypecheckScript = true;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
pkg.scripts = scripts;
|
|
1060
|
+
const devDependencies = { ...pkg.devDependencies };
|
|
1061
|
+
const addedDevDependencies = [];
|
|
1062
|
+
for (const dependency of OXLINT_DEPENDENCIES) {
|
|
1063
|
+
if (devDependencies[dependency]) continue;
|
|
1064
|
+
devDependencies[dependency] = "latest";
|
|
1065
|
+
addedDevDependencies.push(dependency);
|
|
1066
|
+
}
|
|
1067
|
+
pkg.devDependencies = devDependencies;
|
|
1068
|
+
const oxlintConfigAction = await applyOxlintConfig({
|
|
1069
|
+
pkg,
|
|
1070
|
+
oxlintConfigPath,
|
|
1071
|
+
yes
|
|
1072
|
+
});
|
|
1073
|
+
let removedDependencies = [];
|
|
1074
|
+
let removedPackageJsonEslintConfig = false;
|
|
1075
|
+
const removedConfigFiles = [];
|
|
1076
|
+
if (strategy === "replace") {
|
|
1077
|
+
removedDependencies = [...removeEslintDependencies(pkg, "dependencies"), ...removeEslintDependencies(pkg, "devDependencies")];
|
|
1078
|
+
removedPackageJsonEslintConfig = removeEslintConfigFromPackageJson(pkg);
|
|
1079
|
+
cleanupEmptyDependencyBuckets$1(pkg);
|
|
1080
|
+
for (const file of ESLINT_CONFIG_FILES) {
|
|
1081
|
+
const filePath = path.join(rootDir, file);
|
|
1082
|
+
if (!await pathExists(filePath)) continue;
|
|
1083
|
+
await unlink(filePath);
|
|
1084
|
+
removedConfigFiles.push(file);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
strategy,
|
|
1089
|
+
scriptsUpdated,
|
|
1090
|
+
scriptsKept,
|
|
1091
|
+
removedTypecheckScript,
|
|
1092
|
+
addedDevDependencies,
|
|
1093
|
+
removedDependencies,
|
|
1094
|
+
removedPackageJsonEslintConfig,
|
|
1095
|
+
removedConfigFiles,
|
|
1096
|
+
oxlintConfigAction
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
async function maybeInstallDependencies$1(opts) {
|
|
1100
|
+
const { yes, packageManager, rootDir } = opts;
|
|
1101
|
+
if (packageManager === "deno") return void 0;
|
|
1102
|
+
if (!(yes || await askConfirm$1({
|
|
1103
|
+
message: `Install dependencies now with ${packageManager}?`,
|
|
1104
|
+
initialValue: true
|
|
1105
|
+
}))) return void 0;
|
|
1106
|
+
const installSpinner = spinner();
|
|
1107
|
+
installSpinner.start(`Installing dependencies with ${packageManager}`);
|
|
1108
|
+
const result = await exec(packageManager, ["install"], { cwd: rootDir });
|
|
1109
|
+
installSpinner.stop(result.ok ? "Dependencies installed" : "Dependency install failed");
|
|
1110
|
+
return result.ok;
|
|
1111
|
+
}
|
|
1112
|
+
async function askConfirm$1(opts) {
|
|
1113
|
+
const answer = await confirm({
|
|
1114
|
+
message: opts.message,
|
|
1115
|
+
initialValue: opts.initialValue
|
|
1116
|
+
});
|
|
1117
|
+
if (isCancel(answer)) return abort$1();
|
|
1118
|
+
return answer;
|
|
1119
|
+
}
|
|
1120
|
+
async function askMigrationStrategy(opts) {
|
|
1121
|
+
const strategy = await select({
|
|
1122
|
+
message: "ESLint strategy",
|
|
1123
|
+
initialValue: await detectEslintAssets(opts.rootDir, opts.pkg) ? "migrate" : "replace",
|
|
1124
|
+
options: [{
|
|
1125
|
+
value: "migrate",
|
|
1126
|
+
label: "Migrate gradually (keep ESLint assets)"
|
|
1127
|
+
}, {
|
|
1128
|
+
value: "replace",
|
|
1129
|
+
label: "Replace ESLint directly (current mode)"
|
|
1130
|
+
}]
|
|
1131
|
+
});
|
|
1132
|
+
if (isCancel(strategy)) return abort$1();
|
|
1133
|
+
return strategy;
|
|
1134
|
+
}
|
|
1135
|
+
async function detectEslintAssets(rootDir, pkg) {
|
|
1136
|
+
if (Object.prototype.hasOwnProperty.call(pkg, "eslintConfig")) return true;
|
|
1137
|
+
const dependencies = pkg.dependencies ?? {};
|
|
1138
|
+
const devDependencies = pkg.devDependencies ?? {};
|
|
1139
|
+
if (Object.keys(dependencies).some(isEslintDependency) || Object.keys(devDependencies).some(isEslintDependency)) return true;
|
|
1140
|
+
for (const file of ESLINT_CONFIG_FILES) if (await pathExists(path.join(rootDir, file))) return true;
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
async function applyOxlintConfig(opts) {
|
|
1144
|
+
const { pkg, oxlintConfigPath, yes } = opts;
|
|
1145
|
+
if (!(!await pathExists(oxlintConfigPath) || yes || await askConfirm$1({
|
|
1146
|
+
message: "Overwrite existing oxlint.config.ts?",
|
|
1147
|
+
initialValue: true
|
|
1148
|
+
}))) return "kept-existing";
|
|
1149
|
+
await writeText(oxlintConfigPath, oxlintConfigTemplate({ useVitest: detectUseVitest(pkg.scripts) }));
|
|
1150
|
+
return "written";
|
|
1151
|
+
}
|
|
1152
|
+
function detectUseVitest(scripts) {
|
|
1153
|
+
return typeof scripts?.test === "string" && scripts.test.includes("vitest");
|
|
1154
|
+
}
|
|
1155
|
+
function removeEslintDependencies(pkg, key) {
|
|
1156
|
+
const bucket = pkg[key];
|
|
1157
|
+
if (!bucket) return [];
|
|
1158
|
+
const removed = [];
|
|
1159
|
+
for (const name of Object.keys(bucket)) {
|
|
1160
|
+
if (!isEslintDependency(name)) continue;
|
|
1161
|
+
delete bucket[name];
|
|
1162
|
+
removed.push(name);
|
|
1163
|
+
}
|
|
1164
|
+
return removed;
|
|
1165
|
+
}
|
|
1166
|
+
function isEslintDependency(name) {
|
|
1167
|
+
return name === "eslint" || name === "typescript-eslint" || name.startsWith("@eslint/") || name.startsWith("@typescript-eslint/") || name.startsWith("eslint-") || /(^|\/)eslint-(plugin|config|import-resolver)-/.test(name);
|
|
1168
|
+
}
|
|
1169
|
+
function removeEslintConfigFromPackageJson(pkg) {
|
|
1170
|
+
if (!Object.prototype.hasOwnProperty.call(pkg, "eslintConfig")) return false;
|
|
1171
|
+
delete pkg.eslintConfig;
|
|
1172
|
+
return true;
|
|
1173
|
+
}
|
|
1174
|
+
function cleanupEmptyDependencyBuckets$1(pkg) {
|
|
1175
|
+
if (pkg.dependencies && Object.keys(pkg.dependencies).length === 0) delete pkg.dependencies;
|
|
1176
|
+
if (pkg.devDependencies && Object.keys(pkg.devDependencies).length === 0) delete pkg.devDependencies;
|
|
1177
|
+
}
|
|
1178
|
+
var CancelledError$1 = class extends Error {
|
|
1179
|
+
constructor() {
|
|
1180
|
+
super("Cancelled");
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
function abort$1(opts = {}) {
|
|
1184
|
+
cancel(opts.message ?? "Cancelled");
|
|
1185
|
+
process.exitCode = opts.exitCode ?? 0;
|
|
1186
|
+
throw new CancelledError$1();
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
//#endregion
|
|
1190
|
+
//#region src/commands/oxfmt.ts
|
|
1191
|
+
const OXFMT_SCRIPTS = {
|
|
1192
|
+
format: "oxfmt",
|
|
1193
|
+
"format:check": "oxfmt --check",
|
|
1194
|
+
fmt: "oxfmt",
|
|
1195
|
+
"fmt:check": "oxfmt --check"
|
|
1196
|
+
};
|
|
1197
|
+
const PRETTIER_CONFIG_FILES = [
|
|
1198
|
+
".prettierrc",
|
|
1199
|
+
".prettierrc.json",
|
|
1200
|
+
".prettierrc.json5",
|
|
1201
|
+
".prettierrc.yaml",
|
|
1202
|
+
".prettierrc.yml",
|
|
1203
|
+
".prettierrc.toml",
|
|
1204
|
+
".prettierrc.js",
|
|
1205
|
+
".prettierrc.cjs",
|
|
1206
|
+
".prettierrc.mjs",
|
|
1207
|
+
".prettierrc.ts",
|
|
1208
|
+
".prettierrc.cts",
|
|
1209
|
+
".prettierrc.mts",
|
|
1210
|
+
"prettier.config.js",
|
|
1211
|
+
"prettier.config.cjs",
|
|
1212
|
+
"prettier.config.mjs",
|
|
1213
|
+
"prettier.config.ts",
|
|
1214
|
+
"prettier.config.cts",
|
|
1215
|
+
"prettier.config.mts"
|
|
1216
|
+
];
|
|
1217
|
+
async function runOxfmt({ yes = false } = {}) {
|
|
1218
|
+
try {
|
|
1219
|
+
intro("frontpl (oxfmt)");
|
|
1220
|
+
const rootDir = process.cwd();
|
|
1221
|
+
const packageJsonPath = path.join(rootDir, "package.json");
|
|
1222
|
+
const oxfmtConfigPath = path.join(rootDir, ".oxfmtrc.json");
|
|
1223
|
+
const pkg = await readPackageJson(packageJsonPath);
|
|
1224
|
+
if (!pkg) {
|
|
1225
|
+
cancel("Missing package.json. Run this command in a Node project root.");
|
|
1226
|
+
process.exitCode = 1;
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const packageManager = await detectPackageManager(rootDir) ?? "pnpm";
|
|
1230
|
+
const stats = await migrateToOxfmt({
|
|
1231
|
+
pkg,
|
|
1232
|
+
rootDir,
|
|
1233
|
+
oxfmtConfigPath,
|
|
1234
|
+
packageManager,
|
|
1235
|
+
configMode: yes ? "rebuild" : await askConfigMode({
|
|
1236
|
+
rootDir,
|
|
1237
|
+
pkg
|
|
1238
|
+
}),
|
|
1239
|
+
yes
|
|
1240
|
+
});
|
|
1241
|
+
await writePackageJson(packageJsonPath, pkg);
|
|
1242
|
+
const installOk = await maybeInstallDependencies({
|
|
1243
|
+
yes,
|
|
1244
|
+
packageManager,
|
|
1245
|
+
rootDir
|
|
1246
|
+
});
|
|
1247
|
+
const scriptSummary = stats.scriptsUpdated.length > 0 ? `updated scripts: ${stats.scriptsUpdated.join(", ")}` : stats.scriptsKept.length > 0 ? `kept existing scripts: ${stats.scriptsKept.join(", ")}` : "scripts already aligned";
|
|
1248
|
+
const depSummary = stats.addedOxfmtDependency ? "added devDependency: oxfmt" : "devDependency oxfmt already present";
|
|
1249
|
+
const removedDepsSummary = stats.removedDependencies.length > 0 ? `removed prettier deps: ${stats.removedDependencies.join(", ")}` : "no prettier deps removed";
|
|
1250
|
+
const removedPackageJsonPrettierSummary = stats.removedPackageJsonPrettierConfig ? "removed package.json#prettier" : "no package.json#prettier removed";
|
|
1251
|
+
const removedFilesSummary = stats.removedConfigFiles.length > 0 ? `removed prettier config files: ${stats.removedConfigFiles.join(", ")}` : "no prettier config files removed";
|
|
1252
|
+
const configSummary = stats.oxfmtConfigAction === "migrated" ? "migrated .oxfmtrc.json from prettier" : stats.oxfmtConfigAction === "rebuilt" ? "rebuilt .oxfmtrc.json" : "kept existing .oxfmtrc.json";
|
|
1253
|
+
const installSummary = packageManager === "deno" ? "skipped dependency install (deno project)" : installOk === true ? `installed dependencies with ${packageManager}` : installOk === false ? `dependency install failed with ${packageManager}` : "skipped dependency install";
|
|
1254
|
+
outro([
|
|
1255
|
+
"Done. Applied oxfmt migration.",
|
|
1256
|
+
`- ${scriptSummary}`,
|
|
1257
|
+
`- ${depSummary}`,
|
|
1258
|
+
`- ${removedDepsSummary}`,
|
|
1259
|
+
`- ${removedPackageJsonPrettierSummary}`,
|
|
1260
|
+
`- ${removedFilesSummary}`,
|
|
1261
|
+
`- ${configSummary}`,
|
|
1262
|
+
`- ${installSummary}`
|
|
1263
|
+
].join("\n"));
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
if (error instanceof CancelledError) return;
|
|
1266
|
+
throw error;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async function migrateToOxfmt(opts) {
|
|
1270
|
+
const { pkg, rootDir, oxfmtConfigPath, packageManager, configMode, yes } = opts;
|
|
1271
|
+
const scripts = { ...pkg.scripts };
|
|
1272
|
+
const conflictingScripts = Object.entries(OXFMT_SCRIPTS).filter(([name, command]) => typeof scripts[name] === "string" && scripts[name] !== command).map(([name]) => name);
|
|
1273
|
+
const shouldOverwriteConflicts = conflictingScripts.length === 0 ? true : yes ? true : await askConfirm({
|
|
1274
|
+
message: `Overwrite conflicting scripts (${conflictingScripts.join(", ")}) with oxfmt?`,
|
|
1275
|
+
initialValue: true
|
|
1276
|
+
});
|
|
1277
|
+
const scriptsUpdated = [];
|
|
1278
|
+
const scriptsKept = [];
|
|
1279
|
+
for (const [name, command] of Object.entries(OXFMT_SCRIPTS)) {
|
|
1280
|
+
const current = scripts[name];
|
|
1281
|
+
if (current === command) continue;
|
|
1282
|
+
if (current && !shouldOverwriteConflicts) {
|
|
1283
|
+
scriptsKept.push(name);
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
scripts[name] = command;
|
|
1287
|
+
scriptsUpdated.push(name);
|
|
1288
|
+
}
|
|
1289
|
+
pkg.scripts = scripts;
|
|
1290
|
+
const devDependencies = { ...pkg.devDependencies };
|
|
1291
|
+
let addedOxfmtDependency = false;
|
|
1292
|
+
if (!devDependencies.oxfmt) {
|
|
1293
|
+
devDependencies.oxfmt = "latest";
|
|
1294
|
+
addedOxfmtDependency = true;
|
|
1295
|
+
}
|
|
1296
|
+
pkg.devDependencies = devDependencies;
|
|
1297
|
+
const removePrettier = yes ? true : await askConfirm({
|
|
1298
|
+
message: "Remove prettier dependencies and config files?",
|
|
1299
|
+
initialValue: true
|
|
1300
|
+
});
|
|
1301
|
+
const removedDependencies = [];
|
|
1302
|
+
let removedPackageJsonPrettierConfig = false;
|
|
1303
|
+
if (removePrettier) {
|
|
1304
|
+
removedDependencies.push(...removePrettierDependencies(pkg, "dependencies"));
|
|
1305
|
+
removedDependencies.push(...removePrettierDependencies(pkg, "devDependencies"));
|
|
1306
|
+
removedPackageJsonPrettierConfig = removePrettierConfigFromPackageJson(pkg);
|
|
1307
|
+
cleanupEmptyDependencyBuckets(pkg);
|
|
1308
|
+
}
|
|
1309
|
+
const oxfmtConfigAction = await applyOxfmtConfig({
|
|
1310
|
+
rootDir,
|
|
1311
|
+
oxfmtConfigPath,
|
|
1312
|
+
packageManager,
|
|
1313
|
+
configMode,
|
|
1314
|
+
yes
|
|
1315
|
+
});
|
|
1316
|
+
const removedConfigFiles = [];
|
|
1317
|
+
if (removePrettier) for (const file of PRETTIER_CONFIG_FILES) {
|
|
1318
|
+
const filePath = path.join(rootDir, file);
|
|
1319
|
+
if (!await pathExists(filePath)) continue;
|
|
1320
|
+
await unlink(filePath);
|
|
1321
|
+
removedConfigFiles.push(file);
|
|
1322
|
+
}
|
|
1323
|
+
return {
|
|
1324
|
+
scriptsUpdated,
|
|
1325
|
+
scriptsKept,
|
|
1326
|
+
addedOxfmtDependency,
|
|
1327
|
+
removedPackageJsonPrettierConfig,
|
|
1328
|
+
removedDependencies,
|
|
1329
|
+
removedConfigFiles,
|
|
1330
|
+
oxfmtConfigAction
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
async function maybeInstallDependencies(opts) {
|
|
1334
|
+
const { yes, packageManager, rootDir } = opts;
|
|
1335
|
+
if (packageManager === "deno") return void 0;
|
|
1336
|
+
if (!(yes || await askConfirm({
|
|
1337
|
+
message: `Install dependencies now with ${packageManager}?`,
|
|
1338
|
+
initialValue: true
|
|
1339
|
+
}))) return void 0;
|
|
1340
|
+
const installSpinner = spinner();
|
|
1341
|
+
installSpinner.start(`Installing dependencies with ${packageManager}`);
|
|
1342
|
+
const result = await exec(packageManager, ["install"], { cwd: rootDir });
|
|
1343
|
+
installSpinner.stop(result.ok ? "Dependencies installed" : "Dependency install failed");
|
|
1344
|
+
return result.ok;
|
|
1345
|
+
}
|
|
1346
|
+
async function askConfirm(opts) {
|
|
1347
|
+
const answer = await confirm({
|
|
1348
|
+
message: opts.message,
|
|
1349
|
+
initialValue: opts.initialValue
|
|
1350
|
+
});
|
|
1351
|
+
if (isCancel(answer)) return abort();
|
|
1352
|
+
return answer;
|
|
1353
|
+
}
|
|
1354
|
+
async function askConfigMode(opts) {
|
|
1355
|
+
const mode = await select({
|
|
1356
|
+
message: "Prettier config strategy",
|
|
1357
|
+
initialValue: await detectPrettierConfig(opts.rootDir, opts.pkg) ? "migrate" : "rebuild",
|
|
1358
|
+
options: [{
|
|
1359
|
+
value: "migrate",
|
|
1360
|
+
label: "Migrate from Prettier (oxfmt --migrate=prettier)"
|
|
1361
|
+
}, {
|
|
1362
|
+
value: "rebuild",
|
|
1363
|
+
label: "Rebuild .oxfmtrc.json (current mode)"
|
|
1364
|
+
}]
|
|
1365
|
+
});
|
|
1366
|
+
if (isCancel(mode)) return abort();
|
|
1367
|
+
return mode;
|
|
1368
|
+
}
|
|
1369
|
+
async function detectPrettierConfig(rootDir, pkg) {
|
|
1370
|
+
if (Object.prototype.hasOwnProperty.call(pkg, "prettier")) return true;
|
|
1371
|
+
for (const file of PRETTIER_CONFIG_FILES) if (await pathExists(path.join(rootDir, file))) return true;
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
async function applyOxfmtConfig(opts) {
|
|
1375
|
+
const { rootDir, oxfmtConfigPath, packageManager, configMode, yes } = opts;
|
|
1376
|
+
if (!(!await pathExists(oxfmtConfigPath) || yes || await askConfirm({
|
|
1377
|
+
message: configMode === "migrate" ? "Overwrite existing .oxfmtrc.json via prettier migration?" : "Overwrite existing .oxfmtrc.json?",
|
|
1378
|
+
initialValue: true
|
|
1379
|
+
}))) return "kept-existing";
|
|
1380
|
+
if (configMode === "migrate") {
|
|
1381
|
+
if (await runOxfmtPrettierMigration({
|
|
1382
|
+
rootDir,
|
|
1383
|
+
packageManager
|
|
1384
|
+
})) return "migrated";
|
|
1385
|
+
if (!(yes || await askConfirm({
|
|
1386
|
+
message: "Migration failed. Rebuild .oxfmtrc.json with defaults instead?",
|
|
1387
|
+
initialValue: true
|
|
1388
|
+
}))) return "kept-existing";
|
|
1389
|
+
}
|
|
1390
|
+
await writeText(oxfmtConfigPath, oxfmtConfigTemplate());
|
|
1391
|
+
return "rebuilt";
|
|
1392
|
+
}
|
|
1393
|
+
async function runOxfmtPrettierMigration(opts) {
|
|
1394
|
+
const { rootDir, packageManager } = opts;
|
|
1395
|
+
const migrateSpinner = spinner();
|
|
1396
|
+
migrateSpinner.start("Migrating prettier config to .oxfmtrc.json");
|
|
1397
|
+
if ((await exec("oxfmt", ["--migrate=prettier"], { cwd: rootDir })).ok) {
|
|
1398
|
+
migrateSpinner.stop("Migrated config with oxfmt");
|
|
1399
|
+
return true;
|
|
1400
|
+
}
|
|
1401
|
+
const fallbackRun = packageManager === "pnpm" ? await exec("pnpm", [
|
|
1402
|
+
"exec",
|
|
1403
|
+
"oxfmt",
|
|
1404
|
+
"--migrate=prettier"
|
|
1405
|
+
], { cwd: rootDir }) : packageManager === "npm" ? await exec("npm", [
|
|
1406
|
+
"exec",
|
|
1407
|
+
"oxfmt",
|
|
1408
|
+
"--",
|
|
1409
|
+
"--migrate=prettier"
|
|
1410
|
+
], { cwd: rootDir }) : packageManager === "yarn" ? await exec("yarn", [
|
|
1411
|
+
"dlx",
|
|
1412
|
+
"oxfmt",
|
|
1413
|
+
"--migrate=prettier"
|
|
1414
|
+
], { cwd: rootDir }) : packageManager === "bun" ? await exec("bun", [
|
|
1415
|
+
"x",
|
|
1416
|
+
"oxfmt",
|
|
1417
|
+
"--migrate=prettier"
|
|
1418
|
+
], { cwd: rootDir }) : { ok: false };
|
|
1419
|
+
migrateSpinner.stop(fallbackRun.ok ? "Migrated config with oxfmt" : "Prettier migration failed");
|
|
1420
|
+
return fallbackRun.ok;
|
|
1421
|
+
}
|
|
1422
|
+
function removePrettierDependencies(pkg, key) {
|
|
1423
|
+
const bucket = pkg[key];
|
|
1424
|
+
if (!bucket) return [];
|
|
1425
|
+
const removed = [];
|
|
1426
|
+
for (const name of Object.keys(bucket)) {
|
|
1427
|
+
if (!isPrettierDependency(name)) continue;
|
|
1428
|
+
delete bucket[name];
|
|
1429
|
+
removed.push(name);
|
|
1430
|
+
}
|
|
1431
|
+
return removed;
|
|
1432
|
+
}
|
|
1433
|
+
function cleanupEmptyDependencyBuckets(pkg) {
|
|
1434
|
+
if (pkg.dependencies && Object.keys(pkg.dependencies).length === 0) delete pkg.dependencies;
|
|
1435
|
+
if (pkg.devDependencies && Object.keys(pkg.devDependencies).length === 0) delete pkg.devDependencies;
|
|
1436
|
+
}
|
|
1437
|
+
function removePrettierConfigFromPackageJson(pkg) {
|
|
1438
|
+
if (!Object.prototype.hasOwnProperty.call(pkg, "prettier")) return false;
|
|
1439
|
+
delete pkg.prettier;
|
|
1440
|
+
return true;
|
|
1441
|
+
}
|
|
1442
|
+
function isPrettierDependency(name) {
|
|
1443
|
+
return name === "prettier" || /(^|\/)prettier-plugin-/.test(name) || name.startsWith("@prettier/plugin-");
|
|
1444
|
+
}
|
|
1445
|
+
var CancelledError = class extends Error {
|
|
1446
|
+
constructor() {
|
|
1447
|
+
super("Cancelled");
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
function abort(opts = {}) {
|
|
1451
|
+
cancel(opts.message ?? "Cancelled");
|
|
1452
|
+
process.exitCode = opts.exitCode ?? 0;
|
|
1453
|
+
throw new CancelledError();
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
//#endregion
|
|
1457
|
+
export { runCi as a, oxlintConfigTemplate as c, validateProjectName as i, packageJsonTemplate as l, runOxlint as n, githubCliCiWorkflowTemplate as o, runInit as r, githubDependabotTemplate as s, runOxfmt as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frontpl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Interactive CLI to scaffold standardized frontend project templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -53,11 +53,11 @@
|
|
|
53
53
|
"tiny-bin": "^2.0.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@kingsword/lint-config": "^0.
|
|
56
|
+
"@kingsword/lint-config": "^0.2.1",
|
|
57
57
|
"@types/node": "^25.0.10",
|
|
58
|
-
"oxfmt": "^0.
|
|
58
|
+
"oxfmt": "^0.32.0",
|
|
59
59
|
"oxlint": "^1.46.0",
|
|
60
|
-
"oxlint-tsgolint": "^0.
|
|
60
|
+
"oxlint-tsgolint": "^0.13.0",
|
|
61
61
|
"tsdown": "^0.20.1",
|
|
62
62
|
"typescript": "^5.9.3"
|
|
63
63
|
},
|