frontpl 0.4.1 → 0.5.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 +20 -2
- package/dist/cli.mjs +6 -1
- package/dist/index.d.mts +12 -1
- package/dist/index.mjs +2 -2
- package/dist/{oxfmt-DlwbhnpJ.mjs → oxfmt-DQBJ0JMs.mjs} +504 -399
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -59,6 +59,26 @@ When `pnpm workspace mode` is enabled:
|
|
|
59
59
|
- Root contains `pnpm-workspace.yaml` and the workspace `package.json`
|
|
60
60
|
- `oxlint`/`oxfmt` scripts, dependencies, and config files are generated at the workspace root
|
|
61
61
|
- App/library package is scaffolded under `packages/<name>/` with its own `package.json`, `src`, and `tsconfig.json`
|
|
62
|
+
- If root `oxlint` is enabled, package `package.json` does not add redundant `typecheck: tsc --noEmit`
|
|
63
|
+
|
|
64
|
+
### `frontpl add [name]`
|
|
65
|
+
|
|
66
|
+
Add a new package under `packages/<name>/` in an existing `pnpm workspace`.
|
|
67
|
+
|
|
68
|
+
What it does:
|
|
69
|
+
|
|
70
|
+
- Requires workspace root (`pnpm-workspace.yaml`) and `pnpm` package manager
|
|
71
|
+
- Generates package baseline files:
|
|
72
|
+
- `packages/<name>/package.json`
|
|
73
|
+
- `packages/<name>/README.md`
|
|
74
|
+
- `packages/<name>/src/index.ts`
|
|
75
|
+
- `packages/<name>/tsconfig.json`
|
|
76
|
+
- Optionally adds `vitest` (`src/index.test.ts`) and `tsdown` (`tsdown.config.ts`)
|
|
77
|
+
- Reuses root toolchain strategy:
|
|
78
|
+
- package does not scaffold `oxlint`/`oxfmt` scripts
|
|
79
|
+
- if root `oxlint` exists, package does not scaffold `typecheck: tsc --noEmit`
|
|
80
|
+
|
|
81
|
+
Use `--yes` (or `-y`) to skip confirmations and use defaults inferred from root scripts.
|
|
62
82
|
|
|
63
83
|
### `frontpl ci`
|
|
64
84
|
|
|
@@ -109,8 +129,6 @@ What it does:
|
|
|
109
129
|
- Ensures `package.json` scripts use:
|
|
110
130
|
- `format`: `oxfmt`
|
|
111
131
|
- `format:check`: `oxfmt --check`
|
|
112
|
-
- `fmt`: `oxfmt`
|
|
113
|
-
- `fmt:check`: `oxfmt --check`
|
|
114
132
|
- Ensures `devDependencies.oxfmt` exists (defaults to `latest` when missing)
|
|
115
133
|
- Creates or updates `.oxfmtrc.json`
|
|
116
134
|
- Optionally removes `prettier` / `prettier-plugin-*` / `@prettier/plugin-*` dependencies, `package.json#prettier`, and Prettier config files (`.prettierrc*`, `prettier.config.*`)
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as runInit, i as runAdd, n as runOxlint, r as runCi, t as runOxfmt } from "./oxfmt-DQBJ0JMs.mjs";
|
|
3
3
|
import bin from "tiny-bin";
|
|
4
4
|
|
|
5
5
|
//#region src/cli.ts
|
|
@@ -10,6 +10,11 @@ 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("add", "Add a new package to an existing pnpm workspace").argument("[name]", "Package name (directory name under packages/)").option("--yes, -y", "Skip confirmations and use defaults").action(async (options, args) => {
|
|
14
|
+
await runAdd({
|
|
15
|
+
nameArg: args[0],
|
|
16
|
+
yes: options.yes === true
|
|
17
|
+
});
|
|
13
18
|
}).command("oxlint", "Add/migrate linter to oxlint in current project").option("--yes, -y", "Skip confirmations and use defaults").action(async (options) => {
|
|
14
19
|
await runOxlint({ yes: options.yes === true });
|
|
15
20
|
}).command("oxfmt", "Add/migrate formatter to oxfmt in current project").option("--yes, -y", "Skip confirmations and use defaults").action(async (options) => {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
//#region src/commands/add.d.ts
|
|
2
|
+
type CommandOptions$2 = {
|
|
3
|
+
nameArg?: string;
|
|
4
|
+
yes?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare function runAdd({
|
|
7
|
+
nameArg,
|
|
8
|
+
yes
|
|
9
|
+
}?: CommandOptions$2): Promise<void>;
|
|
10
|
+
//#endregion
|
|
1
11
|
//#region src/commands/ci.d.ts
|
|
2
12
|
declare function runCi(): Promise<undefined>;
|
|
3
13
|
//#endregion
|
|
@@ -36,6 +46,7 @@ declare function packageJsonTemplate(opts: {
|
|
|
36
46
|
packageManager: string;
|
|
37
47
|
typescriptVersion: string;
|
|
38
48
|
useOxlint: boolean;
|
|
49
|
+
includeTypecheckWithoutOxlint?: boolean;
|
|
39
50
|
oxlintVersion?: string;
|
|
40
51
|
oxlintTsgolintVersion?: string;
|
|
41
52
|
kingswordLintConfigVersion?: string;
|
|
@@ -77,4 +88,4 @@ declare function githubDependabotTemplate(opts: {
|
|
|
77
88
|
workingDirectory: string;
|
|
78
89
|
}): string;
|
|
79
90
|
//#endregion
|
|
80
|
-
export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runCi, runInit, runOxfmt, runOxlint, validateProjectName, workspaceRootPackageJsonTemplate };
|
|
91
|
+
export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runAdd, runCi, runInit, runOxfmt, runOxlint, validateProjectName, workspaceRootPackageJsonTemplate };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as runInit, c as githubDependabotTemplate, d as workspaceRootPackageJsonTemplate, i as runAdd, l as oxlintConfigTemplate, n as runOxlint, o as validateProjectName, r as runCi, s as githubCliCiWorkflowTemplate, t as runOxfmt, u as packageJsonTemplate } from "./oxfmt-DQBJ0JMs.mjs";
|
|
2
2
|
|
|
3
|
-
export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runCi, runInit, runOxfmt, runOxlint, validateProjectName, workspaceRootPackageJsonTemplate };
|
|
3
|
+
export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runAdd, runCi, runInit, runOxfmt, runOxlint, validateProjectName, workspaceRootPackageJsonTemplate };
|
|
@@ -5,52 +5,49 @@ import process from "node:process";
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
|
|
8
|
-
//#region src/lib/
|
|
9
|
-
async function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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;
|
|
8
|
+
//#region src/lib/versions.ts
|
|
9
|
+
async function detectPackageManagerVersion(pm) {
|
|
10
|
+
switch (pm) {
|
|
11
|
+
case "npm": return (await execCapture("npm", ["--version"])).stdout.trim() || void 0;
|
|
12
|
+
case "pnpm": return (await execCapture("pnpm", ["--version"])).stdout.trim() || void 0;
|
|
13
|
+
case "yarn": return (await execCapture("yarn", ["--version"])).stdout.trim() || void 0;
|
|
14
|
+
case "bun": return (await execCapture("bun", ["--version"])).stdout.trim() || void 0;
|
|
15
|
+
case "deno": return ((await execCapture("deno", ["--version"])).stdout.trim().split("\n")[0] ?? "").match(/deno\\s+([0-9]+\\.[0-9]+\\.[0-9]+)/)?.[1];
|
|
32
16
|
}
|
|
33
17
|
}
|
|
34
|
-
async function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
18
|
+
async function execCapture(command, args) {
|
|
19
|
+
const resolved = resolveCommand$1(command);
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const child = spawn(resolved, args, {
|
|
22
|
+
cwd: os.tmpdir(),
|
|
23
|
+
stdio: [
|
|
24
|
+
"ignore",
|
|
25
|
+
"pipe",
|
|
26
|
+
"ignore"
|
|
27
|
+
],
|
|
28
|
+
shell: false,
|
|
29
|
+
env: process.env
|
|
30
|
+
});
|
|
31
|
+
const chunks = [];
|
|
32
|
+
child.stdout.on("data", (d) => chunks.push(Buffer.from(d)));
|
|
33
|
+
child.on("close", (code) => {
|
|
34
|
+
resolve({
|
|
35
|
+
ok: code === 0,
|
|
36
|
+
stdout: Buffer.concat(chunks).toString("utf8")
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
child.on("error", () => resolve({
|
|
40
|
+
ok: false,
|
|
41
|
+
stdout: ""
|
|
42
|
+
}));
|
|
43
|
+
});
|
|
51
44
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
45
|
+
function resolveCommand$1(command) {
|
|
46
|
+
if (process.platform !== "win32") return command;
|
|
47
|
+
if (command === "npm") return "npm.cmd";
|
|
48
|
+
if (command === "pnpm") return "pnpm.cmd";
|
|
49
|
+
if (command === "yarn") return "yarn.cmd";
|
|
50
|
+
return command;
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
//#endregion
|
|
@@ -161,8 +158,6 @@ function applyLintAndFormatScripts(scripts, opts) {
|
|
|
161
158
|
if (opts.useOxfmt) {
|
|
162
159
|
scripts.format = "oxfmt";
|
|
163
160
|
scripts["format:check"] = "oxfmt --check";
|
|
164
|
-
scripts.fmt = "oxfmt";
|
|
165
|
-
scripts["fmt:check"] = "oxfmt --check";
|
|
166
161
|
}
|
|
167
162
|
}
|
|
168
163
|
function applyLintAndFormatDependencies(devDependencies, opts) {
|
|
@@ -175,7 +170,7 @@ function applyLintAndFormatDependencies(devDependencies, opts) {
|
|
|
175
170
|
}
|
|
176
171
|
function packageJsonTemplate(opts) {
|
|
177
172
|
const scripts = {};
|
|
178
|
-
if (!opts.useOxlint) scripts.typecheck = "tsc --noEmit";
|
|
173
|
+
if (!opts.useOxlint && opts.includeTypecheckWithoutOxlint !== false) scripts.typecheck = "tsc --noEmit";
|
|
179
174
|
applyLintAndFormatScripts(scripts, opts);
|
|
180
175
|
if (opts.useVitest) scripts.test = "vitest";
|
|
181
176
|
if (opts.useTsdown) scripts.build = "tsdown";
|
|
@@ -399,267 +394,58 @@ function yamlString(value) {
|
|
|
399
394
|
}
|
|
400
395
|
|
|
401
396
|
//#endregion
|
|
402
|
-
//#region src/
|
|
403
|
-
async function
|
|
397
|
+
//#region src/lib/fs.ts
|
|
398
|
+
async function writeText(filePath, contents) {
|
|
399
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
400
|
+
await writeFile(filePath, contents, "utf8");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/lib/utils.ts
|
|
405
|
+
async function pathExists(pathname) {
|
|
404
406
|
try {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
message: detectedPackageManager ? `Package manager (detected: ${detectedPackageManager})` : "Package manager",
|
|
410
|
-
initialValue: detectedPackageManager ?? "pnpm",
|
|
411
|
-
options: [
|
|
412
|
-
{
|
|
413
|
-
value: "npm",
|
|
414
|
-
label: "npm"
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
value: "yarn",
|
|
418
|
-
label: "yarn"
|
|
419
|
-
},
|
|
420
|
-
{
|
|
421
|
-
value: "pnpm",
|
|
422
|
-
label: "pnpm"
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
value: "bun",
|
|
426
|
-
label: "bun"
|
|
427
|
-
},
|
|
428
|
-
{
|
|
429
|
-
value: "deno",
|
|
430
|
-
label: "deno"
|
|
431
|
-
}
|
|
432
|
-
]
|
|
433
|
-
});
|
|
434
|
-
if (isCancel(packageManager)) return abort$2();
|
|
435
|
-
const candidates = await listPackageCandidates(rootDir, packageManager);
|
|
436
|
-
if (candidates.length === 0) {
|
|
437
|
-
cancel("No package found. Run this command in a project root (with package.json or deno.json).");
|
|
438
|
-
process.exitCode = 1;
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
const initialWorkingDirectory = await detectWorkingDirectory(rootDir, candidates);
|
|
442
|
-
const workingDirectory = candidates.length === 1 ? candidates[0] : await select({
|
|
443
|
-
message: "Working directory (package folder)",
|
|
444
|
-
initialValue: initialWorkingDirectory,
|
|
445
|
-
options: candidates.map((c) => ({
|
|
446
|
-
value: c,
|
|
447
|
-
label: c
|
|
448
|
-
}))
|
|
449
|
-
});
|
|
450
|
-
if (isCancel(workingDirectory)) return abort$2();
|
|
451
|
-
const nodeVersionDefault = await detectNodeMajorVersion(rootDir) ?? 22;
|
|
452
|
-
const nodeVersionText = await text({
|
|
453
|
-
message: "Node.js major version (for GitHub Actions)",
|
|
454
|
-
initialValue: String(nodeVersionDefault),
|
|
455
|
-
validate: (value = "") => {
|
|
456
|
-
const major = Number.parseInt(value.trim(), 10);
|
|
457
|
-
if (!Number.isFinite(major) || major <= 0) return "Enter a valid major version (e.g. 22)";
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
if (isCancel(nodeVersionText)) return abort$2();
|
|
461
|
-
const nodeVersion = Number.parseInt(String(nodeVersionText).trim(), 10);
|
|
462
|
-
const { runLint, runFormatCheck, runTests, lintCommand, formatCheckCommand, testCommand } = await resolveCiCommands(rootDir, workingDirectory, packageManager);
|
|
463
|
-
const addRelease = await confirm({
|
|
464
|
-
message: "Add release workflow too?",
|
|
465
|
-
initialValue: true
|
|
466
|
-
});
|
|
467
|
-
if (isCancel(addRelease)) return abort$2();
|
|
468
|
-
const releaseMode = addRelease ? await select({
|
|
469
|
-
message: "Release workflows",
|
|
470
|
-
initialValue: "tag",
|
|
471
|
-
options: [
|
|
472
|
-
{
|
|
473
|
-
value: "tag",
|
|
474
|
-
label: "Tag push (vX.Y.Z) — recommended"
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
value: "commit",
|
|
478
|
-
label: "Release commit (chore(release): vX.Y.Z) — legacy"
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
value: "both",
|
|
482
|
-
label: "Both (tag + commit)"
|
|
483
|
-
}
|
|
484
|
-
]
|
|
485
|
-
}) : void 0;
|
|
486
|
-
if (isCancel(releaseMode)) return abort$2();
|
|
487
|
-
const trustedPublishing = addRelease && packageManager !== "deno" ? await confirm({
|
|
488
|
-
message: "Release: npm trusted publishing (OIDC)?",
|
|
489
|
-
initialValue: true
|
|
490
|
-
}) : void 0;
|
|
491
|
-
if (isCancel(trustedPublishing)) return abort$2();
|
|
492
|
-
const addDependabot = await pathExists(path.join(rootDir, ".git")) ? await confirm({
|
|
493
|
-
message: "Add/update Dependabot config (.github/dependabot.yml)?",
|
|
494
|
-
initialValue: true
|
|
495
|
-
}) : false;
|
|
496
|
-
if (isCancel(addDependabot)) return abort$2();
|
|
497
|
-
const ciWorkflowPath = path.join(rootDir, ".github/workflows/ci.yml");
|
|
498
|
-
const releaseWorkflowPath = path.join(rootDir, ".github/workflows/release.yml");
|
|
499
|
-
const dependabotPath = path.join(rootDir, ".github/dependabot.yml");
|
|
500
|
-
if (!await confirmOverwriteIfExists(ciWorkflowPath, ".github/workflows/ci.yml")) {
|
|
501
|
-
cancel("Skipped CI workflow");
|
|
502
|
-
process.exitCode = 0;
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
await writeText(ciWorkflowPath, githubCliCiWorkflowTemplate({
|
|
506
|
-
packageManager,
|
|
507
|
-
nodeVersion,
|
|
508
|
-
workingDirectory,
|
|
509
|
-
runLint,
|
|
510
|
-
runFormatCheck,
|
|
511
|
-
runTests,
|
|
512
|
-
lintCommand,
|
|
513
|
-
formatCheckCommand,
|
|
514
|
-
testCommand
|
|
515
|
-
}));
|
|
516
|
-
if (addRelease) {
|
|
517
|
-
if (await confirmOverwriteIfExists(releaseWorkflowPath, ".github/workflows/release.yml")) await writeText(releaseWorkflowPath, (releaseMode === "both" ? githubCliReleaseBothWorkflowTemplate : releaseMode === "commit" ? githubCliReleaseWorkflowTemplate : githubCliReleaseTagWorkflowTemplate)({
|
|
518
|
-
packageManager,
|
|
519
|
-
nodeVersion,
|
|
520
|
-
workingDirectory,
|
|
521
|
-
trustedPublishing
|
|
522
|
-
}));
|
|
523
|
-
}
|
|
524
|
-
if (addDependabot) {
|
|
525
|
-
if (await confirmOverwriteIfExists(dependabotPath, ".github/dependabot.yml")) await writeText(dependabotPath, githubDependabotTemplate({
|
|
526
|
-
packageManager,
|
|
527
|
-
workingDirectory
|
|
528
|
-
}));
|
|
529
|
-
}
|
|
530
|
-
outro(addRelease ? "Done. Generated CI + release workflows (and optional Dependabot)." : "Done. Generated CI workflow (and optional Dependabot).");
|
|
531
|
-
} catch (err) {
|
|
532
|
-
if (err instanceof CancelledError$2) return;
|
|
533
|
-
throw err;
|
|
407
|
+
await access(pathname);
|
|
408
|
+
return true;
|
|
409
|
+
} catch {
|
|
410
|
+
return false;
|
|
534
411
|
}
|
|
535
412
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
413
|
+
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/lib/project.ts
|
|
416
|
+
async function readPackageJson(filePath) {
|
|
417
|
+
try {
|
|
418
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
419
|
+
} catch {
|
|
420
|
+
return;
|
|
539
421
|
}
|
|
540
|
-
};
|
|
541
|
-
function abort$2(opts = {}) {
|
|
542
|
-
cancel(opts.message ?? "Cancelled");
|
|
543
|
-
process.exitCode = opts.exitCode ?? 0;
|
|
544
|
-
throw new CancelledError$2();
|
|
545
422
|
}
|
|
546
|
-
async function
|
|
547
|
-
|
|
548
|
-
const overwrite = await confirm({
|
|
549
|
-
message: `Overwrite existing ${label}?`,
|
|
550
|
-
initialValue: true
|
|
551
|
-
});
|
|
552
|
-
if (isCancel(overwrite)) return abort$2();
|
|
553
|
-
return overwrite;
|
|
423
|
+
async function writePackageJson(filePath, value) {
|
|
424
|
+
await writeText(filePath, JSON.stringify(value, null, 2) + "\n");
|
|
554
425
|
}
|
|
555
|
-
async function
|
|
556
|
-
const
|
|
557
|
-
if (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const baseDir = path.join(rootDir, base);
|
|
561
|
-
if (!await pathExists(baseDir)) continue;
|
|
562
|
-
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
563
|
-
for (const entry of entries) {
|
|
564
|
-
if (!entry.isDirectory()) continue;
|
|
565
|
-
if (await pathExists(path.join(baseDir, entry.name, "package.json"))) candidates.add(path.posix.join(base, entry.name));
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return [...candidates];
|
|
569
|
-
}
|
|
570
|
-
async function detectWorkingDirectory(rootDir, candidates) {
|
|
571
|
-
if (candidates.length === 1) return candidates[0];
|
|
572
|
-
const rootScripts = (await readPackageJson(path.join(rootDir, "package.json")))?.scripts ?? {};
|
|
573
|
-
const rootHasScripts = Object.keys(rootScripts).length > 0;
|
|
574
|
-
const nonRoot = candidates.filter((c) => c !== ".");
|
|
575
|
-
if (!rootHasScripts && nonRoot.length === 1) return nonRoot[0];
|
|
576
|
-
return ".";
|
|
577
|
-
}
|
|
578
|
-
async function detectNodeMajorVersion(rootDir) {
|
|
579
|
-
for (const file of [".nvmrc", ".node-version"]) {
|
|
580
|
-
const filePath = path.join(rootDir, file);
|
|
581
|
-
if (!await pathExists(filePath)) continue;
|
|
582
|
-
const major = parseMajorVersion((await readFile(filePath, "utf8")).split("\n")[0]?.trim() ?? "");
|
|
583
|
-
if (major) return major;
|
|
426
|
+
async function detectPackageManager(rootDir) {
|
|
427
|
+
const pmField = (await readPackageJson(path.join(rootDir, "package.json")))?.packageManager;
|
|
428
|
+
if (pmField) {
|
|
429
|
+
const pm = pmField.split("@")[0] ?? "";
|
|
430
|
+
if (isPackageManager(pm)) return pm;
|
|
584
431
|
}
|
|
585
|
-
const
|
|
586
|
-
if (
|
|
587
|
-
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const major = Number.parseInt(trimmed.split(".")[0] ?? "", 10);
|
|
594
|
-
if (!Number.isFinite(major) || major <= 0) return;
|
|
595
|
-
return major;
|
|
596
|
-
}
|
|
597
|
-
async function resolveCiCommands(rootDir, workingDirectory, packageManager) {
|
|
598
|
-
if (packageManager === "deno") return {
|
|
599
|
-
runLint: true,
|
|
600
|
-
runFormatCheck: true,
|
|
601
|
-
runTests: true
|
|
602
|
-
};
|
|
603
|
-
const pkg = await readPackageJson(path.join(rootDir, workingDirectory, "package.json"));
|
|
604
|
-
if (!pkg) return abort$2({
|
|
605
|
-
message: `Missing package.json in ${workingDirectory}`,
|
|
606
|
-
exitCode: 1
|
|
607
|
-
});
|
|
608
|
-
const scripts = pkg.scripts ?? {};
|
|
609
|
-
const hasLint = typeof scripts.lint === "string";
|
|
610
|
-
const hasTest = typeof scripts.test === "string";
|
|
611
|
-
const hasFormatCheck = typeof scripts["format:check"] === "string";
|
|
612
|
-
const hasFmtCheck = typeof scripts["fmt:check"] === "string";
|
|
613
|
-
const runLintDefault = hasLint;
|
|
614
|
-
const runFormatCheckDefault = hasFormatCheck || hasFmtCheck;
|
|
615
|
-
const runTestsDefault = hasTest;
|
|
616
|
-
const runLint = await confirm({
|
|
617
|
-
message: `CI: run lint${hasLint ? "" : " (no lint script detected)"}`,
|
|
618
|
-
initialValue: runLintDefault
|
|
619
|
-
});
|
|
620
|
-
if (isCancel(runLint)) return abort$2();
|
|
621
|
-
const runFormatCheck = await confirm({
|
|
622
|
-
message: `CI: run format check${runFormatCheckDefault ? "" : " (no format check script detected)"}`,
|
|
623
|
-
initialValue: runFormatCheckDefault
|
|
624
|
-
});
|
|
625
|
-
if (isCancel(runFormatCheck)) return abort$2();
|
|
626
|
-
const runTests = await confirm({
|
|
627
|
-
message: `CI: run tests${hasTest ? "" : " (no test script detected)"}`,
|
|
628
|
-
initialValue: runTestsDefault
|
|
629
|
-
});
|
|
630
|
-
if (isCancel(runTests)) return abort$2();
|
|
631
|
-
return {
|
|
632
|
-
runLint,
|
|
633
|
-
runFormatCheck,
|
|
634
|
-
runTests,
|
|
635
|
-
lintCommand: runLint && hasLint ? pmRun$1(packageManager, "lint") : runLint ? await promptCommand("Lint command", pmRun$1(packageManager, "lint")) : void 0,
|
|
636
|
-
formatCheckCommand: runFormatCheck && hasFormatCheck ? pmRun$1(packageManager, "format:check") : runFormatCheck && hasFmtCheck ? pmRun$1(packageManager, "fmt:check") : runFormatCheck ? await promptCommand("Format check command", pmRun$1(packageManager, "format:check")) : void 0,
|
|
637
|
-
testCommand: runTests && hasTest ? pmRun$1(packageManager, "test") : runTests ? await promptCommand("Test command", pmRun$1(packageManager, "test")) : void 0
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
async function promptCommand(message, initialValue) {
|
|
641
|
-
const value = await text({
|
|
642
|
-
message,
|
|
643
|
-
initialValue,
|
|
644
|
-
validate: (v = "") => !v.trim() ? "Command is required" : void 0
|
|
645
|
-
});
|
|
646
|
-
if (isCancel(value)) return abort$2();
|
|
647
|
-
return String(value).trim();
|
|
432
|
+
const candidates = [];
|
|
433
|
+
if (await pathExists(path.join(rootDir, "pnpm-lock.yaml"))) candidates.push("pnpm");
|
|
434
|
+
if (await pathExists(path.join(rootDir, "yarn.lock"))) candidates.push("yarn");
|
|
435
|
+
if (await pathExists(path.join(rootDir, "package-lock.json"))) candidates.push("npm");
|
|
436
|
+
if (await pathExists(path.join(rootDir, "bun.lockb"))) candidates.push("bun");
|
|
437
|
+
if (await pathExists(path.join(rootDir, "bun.lock"))) candidates.push("bun");
|
|
438
|
+
if (await pathExists(path.join(rootDir, "deno.json")) || await pathExists(path.join(rootDir, "deno.jsonc"))) candidates.push("deno");
|
|
439
|
+
return candidates.length === 1 ? candidates[0] : void 0;
|
|
648
440
|
}
|
|
649
|
-
function
|
|
650
|
-
|
|
651
|
-
case "npm": return `npm run ${script}`;
|
|
652
|
-
case "pnpm": return `pnpm run ${script}`;
|
|
653
|
-
case "yarn": return `yarn ${script}`;
|
|
654
|
-
case "bun": return `bun run ${script}`;
|
|
655
|
-
case "deno": return script;
|
|
656
|
-
}
|
|
441
|
+
function isPackageManager(value) {
|
|
442
|
+
return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun" || value === "deno";
|
|
657
443
|
}
|
|
658
444
|
|
|
659
445
|
//#endregion
|
|
660
446
|
//#region src/lib/exec.ts
|
|
661
447
|
async function exec(command, args, opts = {}) {
|
|
662
|
-
const resolved = resolveCommand
|
|
448
|
+
const resolved = resolveCommand(command);
|
|
663
449
|
return new Promise((resolve) => {
|
|
664
450
|
const child = spawn(resolved, args, {
|
|
665
451
|
cwd: opts.cwd,
|
|
@@ -671,52 +457,6 @@ async function exec(command, args, opts = {}) {
|
|
|
671
457
|
child.on("error", () => resolve({ ok: false }));
|
|
672
458
|
});
|
|
673
459
|
}
|
|
674
|
-
function resolveCommand$1(command) {
|
|
675
|
-
if (process.platform !== "win32") return command;
|
|
676
|
-
if (command === "npm") return "npm.cmd";
|
|
677
|
-
if (command === "pnpm") return "pnpm.cmd";
|
|
678
|
-
if (command === "yarn") return "yarn.cmd";
|
|
679
|
-
return command;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
//#endregion
|
|
683
|
-
//#region src/lib/versions.ts
|
|
684
|
-
async function detectPackageManagerVersion(pm) {
|
|
685
|
-
switch (pm) {
|
|
686
|
-
case "npm": return (await execCapture("npm", ["--version"])).stdout.trim() || void 0;
|
|
687
|
-
case "pnpm": return (await execCapture("pnpm", ["--version"])).stdout.trim() || void 0;
|
|
688
|
-
case "yarn": return (await execCapture("yarn", ["--version"])).stdout.trim() || void 0;
|
|
689
|
-
case "bun": return (await execCapture("bun", ["--version"])).stdout.trim() || void 0;
|
|
690
|
-
case "deno": return ((await execCapture("deno", ["--version"])).stdout.trim().split("\n")[0] ?? "").match(/deno\\s+([0-9]+\\.[0-9]+\\.[0-9]+)/)?.[1];
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
async function execCapture(command, args) {
|
|
694
|
-
const resolved = resolveCommand(command);
|
|
695
|
-
return new Promise((resolve) => {
|
|
696
|
-
const child = spawn(resolved, args, {
|
|
697
|
-
cwd: os.tmpdir(),
|
|
698
|
-
stdio: [
|
|
699
|
-
"ignore",
|
|
700
|
-
"pipe",
|
|
701
|
-
"ignore"
|
|
702
|
-
],
|
|
703
|
-
shell: false,
|
|
704
|
-
env: process.env
|
|
705
|
-
});
|
|
706
|
-
const chunks = [];
|
|
707
|
-
child.stdout.on("data", (d) => chunks.push(Buffer.from(d)));
|
|
708
|
-
child.on("close", (code) => {
|
|
709
|
-
resolve({
|
|
710
|
-
ok: code === 0,
|
|
711
|
-
stdout: Buffer.concat(chunks).toString("utf8")
|
|
712
|
-
});
|
|
713
|
-
});
|
|
714
|
-
child.on("error", () => resolve({
|
|
715
|
-
ok: false,
|
|
716
|
-
stdout: ""
|
|
717
|
-
}));
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
460
|
function resolveCommand(command) {
|
|
721
461
|
if (process.platform !== "win32") return command;
|
|
722
462
|
if (command === "npm") return "npm.cmd";
|
|
@@ -727,7 +467,7 @@ function resolveCommand(command) {
|
|
|
727
467
|
|
|
728
468
|
//#endregion
|
|
729
469
|
//#region src/commands/init.ts
|
|
730
|
-
function pmRun(pm, script) {
|
|
470
|
+
function pmRun$1(pm, script) {
|
|
731
471
|
switch (pm) {
|
|
732
472
|
case "npm": return `npm run ${script}`;
|
|
733
473
|
case "pnpm": return `pnpm run ${script}`;
|
|
@@ -743,7 +483,7 @@ async function runInit({ nameArg }) {
|
|
|
743
483
|
initialValue: nameArg ?? "my-frontend",
|
|
744
484
|
validate: validateProjectName
|
|
745
485
|
});
|
|
746
|
-
if (isCancel(projectName)) return onCancel();
|
|
486
|
+
if (isCancel(projectName)) return onCancel$1();
|
|
747
487
|
const packageManager = await select({
|
|
748
488
|
message: "Package manager",
|
|
749
489
|
initialValue: "pnpm",
|
|
@@ -770,37 +510,37 @@ async function runInit({ nameArg }) {
|
|
|
770
510
|
}
|
|
771
511
|
]
|
|
772
512
|
});
|
|
773
|
-
if (isCancel(packageManager)) return onCancel();
|
|
513
|
+
if (isCancel(packageManager)) return onCancel$1();
|
|
774
514
|
const pnpmWorkspace = packageManager === "pnpm" ? await confirm({
|
|
775
515
|
message: "pnpm workspace mode (monorepo skeleton)?",
|
|
776
516
|
initialValue: false
|
|
777
517
|
}) : false;
|
|
778
|
-
if (isCancel(pnpmWorkspace)) return onCancel();
|
|
518
|
+
if (isCancel(pnpmWorkspace)) return onCancel$1();
|
|
779
519
|
const useOxlint = await confirm({
|
|
780
520
|
message: "Enable oxlint (@kingsword/lint-config preset)?",
|
|
781
521
|
initialValue: true
|
|
782
522
|
});
|
|
783
|
-
if (isCancel(useOxlint)) return onCancel();
|
|
523
|
+
if (isCancel(useOxlint)) return onCancel$1();
|
|
784
524
|
const useOxfmt = await confirm({
|
|
785
525
|
message: "Enable oxfmt (code formatting)?",
|
|
786
526
|
initialValue: true
|
|
787
527
|
});
|
|
788
|
-
if (isCancel(useOxfmt)) return onCancel();
|
|
528
|
+
if (isCancel(useOxfmt)) return onCancel$1();
|
|
789
529
|
const useVitest = await confirm({
|
|
790
530
|
message: "Add Vitest?",
|
|
791
531
|
initialValue: false
|
|
792
532
|
});
|
|
793
|
-
if (isCancel(useVitest)) return onCancel();
|
|
533
|
+
if (isCancel(useVitest)) return onCancel$1();
|
|
794
534
|
const useTsdown = await confirm({
|
|
795
535
|
message: "Add tsdown build?",
|
|
796
536
|
initialValue: true
|
|
797
537
|
});
|
|
798
|
-
if (isCancel(useTsdown)) return onCancel();
|
|
538
|
+
if (isCancel(useTsdown)) return onCancel$1();
|
|
799
539
|
const initGit = await confirm({
|
|
800
540
|
message: "Initialize a git repository?",
|
|
801
541
|
initialValue: true
|
|
802
542
|
});
|
|
803
|
-
if (isCancel(initGit)) return onCancel();
|
|
543
|
+
if (isCancel(initGit)) return onCancel$1();
|
|
804
544
|
const githubActions = await select({
|
|
805
545
|
message: "GitHub Actions workflows",
|
|
806
546
|
initialValue: "ci",
|
|
@@ -819,7 +559,7 @@ async function runInit({ nameArg }) {
|
|
|
819
559
|
}
|
|
820
560
|
]
|
|
821
561
|
});
|
|
822
|
-
if (isCancel(githubActions)) return onCancel();
|
|
562
|
+
if (isCancel(githubActions)) return onCancel$1();
|
|
823
563
|
const releaseMode = githubActions === "ci+release" ? await select({
|
|
824
564
|
message: "Release workflows",
|
|
825
565
|
initialValue: "tag",
|
|
@@ -838,17 +578,17 @@ async function runInit({ nameArg }) {
|
|
|
838
578
|
}
|
|
839
579
|
]
|
|
840
580
|
}) : void 0;
|
|
841
|
-
if (isCancel(releaseMode)) return onCancel();
|
|
581
|
+
if (isCancel(releaseMode)) return onCancel$1();
|
|
842
582
|
const addDependabot = initGit && githubActions !== "none" ? await confirm({
|
|
843
583
|
message: "Add Dependabot config (.github/dependabot.yml)?",
|
|
844
584
|
initialValue: true
|
|
845
585
|
}) : false;
|
|
846
|
-
if (isCancel(addDependabot)) return onCancel();
|
|
586
|
+
if (isCancel(addDependabot)) return onCancel$1();
|
|
847
587
|
const trustedPublishing = githubActions === "ci+release" && packageManager !== "deno" ? await confirm({
|
|
848
588
|
message: "Release: npm trusted publishing (OIDC)?",
|
|
849
589
|
initialValue: true
|
|
850
590
|
}) : void 0;
|
|
851
|
-
if (isCancel(trustedPublishing)) return onCancel();
|
|
591
|
+
if (isCancel(trustedPublishing)) return onCancel$1();
|
|
852
592
|
const rootDir = path.resolve(process.cwd(), projectName);
|
|
853
593
|
if (await pathExists(rootDir)) {
|
|
854
594
|
cancel(`Directory already exists: ${rootDir}`);
|
|
@@ -859,6 +599,7 @@ async function runInit({ nameArg }) {
|
|
|
859
599
|
const toolingDir = pnpmWorkspace ? rootDir : pkgDir;
|
|
860
600
|
const packageUseOxlint = pnpmWorkspace ? false : useOxlint;
|
|
861
601
|
const packageUseOxfmt = pnpmWorkspace ? false : useOxfmt;
|
|
602
|
+
const packageIncludeTypecheckWithoutOxlint = !(pnpmWorkspace && useOxlint);
|
|
862
603
|
const pmVersion = await detectPackageManagerVersion(packageManager);
|
|
863
604
|
const packageManagerField = pmVersion ? `${packageManager}@${pmVersion}` : `${packageManager}@latest`;
|
|
864
605
|
await mkdir(path.join(pkgDir, "src"), { recursive: true });
|
|
@@ -895,6 +636,7 @@ async function runInit({ nameArg }) {
|
|
|
895
636
|
packageManager: packageManagerField,
|
|
896
637
|
typescriptVersion: "latest",
|
|
897
638
|
useOxlint: packageUseOxlint,
|
|
639
|
+
includeTypecheckWithoutOxlint: packageIncludeTypecheckWithoutOxlint,
|
|
898
640
|
oxlintVersion: "latest",
|
|
899
641
|
oxlintTsgolintVersion: "latest",
|
|
900
642
|
kingswordLintConfigVersion: "latest",
|
|
@@ -913,64 +655,416 @@ async function runInit({ nameArg }) {
|
|
|
913
655
|
if (packageManager === "deno") await writeText(path.join(rootDir, "deno.json"), JSON.stringify({ nodeModulesDir: "auto" }, null, 2) + "\n");
|
|
914
656
|
if (githubActions !== "none") {
|
|
915
657
|
const workingDirectory = ".";
|
|
916
|
-
const lintCommand = useOxlint && packageManager !== "deno" ? pmRun(packageManager, "lint") : void 0;
|
|
917
|
-
const formatCheckCommand = useOxfmt && packageManager !== "deno" ? pmRun(packageManager, "format:check") : void 0;
|
|
918
|
-
const testCommand = useVitest && packageManager !== "deno" ? pmRun(packageManager, "test") : void 0;
|
|
658
|
+
const lintCommand = useOxlint && packageManager !== "deno" ? pmRun$1(packageManager, "lint") : void 0;
|
|
659
|
+
const formatCheckCommand = useOxfmt && packageManager !== "deno" ? pmRun$1(packageManager, "format:check") : void 0;
|
|
660
|
+
const testCommand = useVitest && packageManager !== "deno" ? pmRun$1(packageManager, "test") : void 0;
|
|
919
661
|
await writeText(path.join(rootDir, ".github/workflows/ci.yml"), githubCliCiWorkflowTemplate({
|
|
920
662
|
packageManager,
|
|
921
|
-
nodeVersion: 22,
|
|
663
|
+
nodeVersion: 22,
|
|
664
|
+
workingDirectory,
|
|
665
|
+
runLint: useOxlint,
|
|
666
|
+
runFormatCheck: useOxfmt,
|
|
667
|
+
runTests: useVitest,
|
|
668
|
+
lintCommand,
|
|
669
|
+
formatCheckCommand,
|
|
670
|
+
testCommand
|
|
671
|
+
}));
|
|
672
|
+
if (addDependabot) await writeText(path.join(rootDir, ".github/dependabot.yml"), githubDependabotTemplate({
|
|
673
|
+
packageManager,
|
|
674
|
+
workingDirectory
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
if (githubActions === "ci+release") {
|
|
678
|
+
const workingDirectory = pnpmWorkspace ? path.posix.join("packages", projectName) : ".";
|
|
679
|
+
await writeText(path.join(rootDir, ".github/workflows/release.yml"), (releaseMode === "both" ? githubCliReleaseBothWorkflowTemplate : releaseMode === "commit" ? githubCliReleaseWorkflowTemplate : githubCliReleaseTagWorkflowTemplate)({
|
|
680
|
+
packageManager,
|
|
681
|
+
nodeVersion: 22,
|
|
682
|
+
workingDirectory,
|
|
683
|
+
trustedPublishing
|
|
684
|
+
}));
|
|
685
|
+
}
|
|
686
|
+
const canInstall = Boolean(pmVersion);
|
|
687
|
+
let installOk = false;
|
|
688
|
+
if (canInstall) {
|
|
689
|
+
const installSpinner = spinner();
|
|
690
|
+
installSpinner.start(`Installing dependencies with ${packageManager}`);
|
|
691
|
+
installOk = (await exec(packageManager, ["install"], { cwd: rootDir })).ok;
|
|
692
|
+
installSpinner.stop(installOk ? "Dependencies installed" : "Install failed (skipped)");
|
|
693
|
+
}
|
|
694
|
+
if (initGit) await exec("git", ["init"], { cwd: rootDir });
|
|
695
|
+
outro(`Done. Next:\n cd ${projectName}${!canInstall ? `\n (${packageManager} not found, run install manually)` : !installOk ? `\n (${packageManager} install failed, run install manually)` : ""}\n ${nextStepHint(packageManager)}`);
|
|
696
|
+
}
|
|
697
|
+
function validateProjectName(value) {
|
|
698
|
+
const name = (value ?? "").trim();
|
|
699
|
+
if (!name) return "Project name is required";
|
|
700
|
+
if (name.length > 214) return "Project name is too long";
|
|
701
|
+
if (name.startsWith(".")) return "Project name cannot start with '.'";
|
|
702
|
+
if (name.startsWith("_")) return "Project name cannot start with '_'";
|
|
703
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name)) return "Use letters, numbers, '.', '_' or '-'";
|
|
704
|
+
}
|
|
705
|
+
function onCancel$1() {
|
|
706
|
+
cancel("Cancelled");
|
|
707
|
+
process.exitCode = 0;
|
|
708
|
+
}
|
|
709
|
+
function nextStepHint(pm) {
|
|
710
|
+
switch (pm) {
|
|
711
|
+
case "npm": return "npm run lint";
|
|
712
|
+
case "pnpm": return "pnpm run lint";
|
|
713
|
+
case "yarn": return "yarn lint";
|
|
714
|
+
case "bun": return "bun run lint";
|
|
715
|
+
case "deno": return "deno task lint # (or run the package.json scripts with your preferred runner)";
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
//#endregion
|
|
720
|
+
//#region src/commands/add.ts
|
|
721
|
+
async function runAdd({ nameArg, yes = false } = {}) {
|
|
722
|
+
intro("frontpl (add)");
|
|
723
|
+
const rootDir = process.cwd();
|
|
724
|
+
if (!await pathExists(path.join(rootDir, "pnpm-workspace.yaml"))) {
|
|
725
|
+
cancel("Missing pnpm-workspace.yaml. This command only supports pnpm workspace roots.");
|
|
726
|
+
process.exitCode = 1;
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const rootPkg = await readPackageJson(path.join(rootDir, "package.json"));
|
|
730
|
+
const detectedPm = await detectPackageManager(rootDir);
|
|
731
|
+
const packageManagerField = rootPkg?.packageManager?.trim();
|
|
732
|
+
if (!(packageManagerField?.startsWith("pnpm@") || detectedPm === "pnpm")) {
|
|
733
|
+
cancel("Only pnpm workspace projects are supported for `frontpl add`.");
|
|
734
|
+
process.exitCode = 1;
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const packageName = typeof nameArg === "string" && nameArg.trim().length > 0 ? resolvePackageNameFromArg(nameArg) : await text({
|
|
738
|
+
message: "Package name",
|
|
739
|
+
initialValue: "my-package",
|
|
740
|
+
validate: validateProjectName
|
|
741
|
+
});
|
|
742
|
+
if (!packageName) return;
|
|
743
|
+
if (isCancel(packageName)) return onCancel();
|
|
744
|
+
const packageDir = path.join(rootDir, "packages", packageName);
|
|
745
|
+
if (await pathExists(packageDir)) {
|
|
746
|
+
cancel(`Package already exists: ${packageDir}`);
|
|
747
|
+
process.exitCode = 1;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const useVitestDefault = typeof rootPkg?.scripts?.test === "string";
|
|
751
|
+
const useTsdownDefault = typeof rootPkg?.scripts?.build === "string";
|
|
752
|
+
const useVitest = yes ? useVitestDefault : await confirm({
|
|
753
|
+
message: "Add Vitest?",
|
|
754
|
+
initialValue: useVitestDefault
|
|
755
|
+
});
|
|
756
|
+
if (isCancel(useVitest)) return onCancel();
|
|
757
|
+
const useTsdown = yes ? useTsdownDefault : await confirm({
|
|
758
|
+
message: "Add tsdown build?",
|
|
759
|
+
initialValue: useTsdownDefault
|
|
760
|
+
});
|
|
761
|
+
if (isCancel(useTsdown)) return onCancel();
|
|
762
|
+
const resolvedPackageManagerField = await resolvePnpmPackageManagerField(packageManagerField);
|
|
763
|
+
const rootHasOxlint = Boolean(rootPkg?.devDependencies?.oxlint) || Boolean(rootPkg?.devDependencies?.["oxlint-tsgolint"]) || await pathExists(path.join(rootDir, "oxlint.config.ts"));
|
|
764
|
+
const typescriptVersion = rootPkg?.devDependencies?.typescript ?? "latest";
|
|
765
|
+
const vitestVersion = rootPkg?.devDependencies?.vitest ?? "latest";
|
|
766
|
+
const tsdownVersion = rootPkg?.devDependencies?.tsdown ?? "latest";
|
|
767
|
+
await mkdir(path.join(packageDir, "src"), { recursive: true });
|
|
768
|
+
await Promise.all([
|
|
769
|
+
writeText(path.join(packageDir, "README.md"), readmeTemplate(packageName)),
|
|
770
|
+
writeText(path.join(packageDir, "src/index.ts"), srcIndexTemplate()),
|
|
771
|
+
writeText(path.join(packageDir, "tsconfig.json"), tsconfigTemplate()),
|
|
772
|
+
writeText(path.join(packageDir, "package.json"), packageJsonTemplate({
|
|
773
|
+
name: packageName,
|
|
774
|
+
packageManager: resolvedPackageManagerField,
|
|
775
|
+
typescriptVersion,
|
|
776
|
+
useOxlint: false,
|
|
777
|
+
includeTypecheckWithoutOxlint: !rootHasOxlint,
|
|
778
|
+
useOxfmt: false,
|
|
779
|
+
useVitest,
|
|
780
|
+
vitestVersion,
|
|
781
|
+
useTsdown,
|
|
782
|
+
tsdownVersion
|
|
783
|
+
}))
|
|
784
|
+
]);
|
|
785
|
+
if (useVitest) await writeText(path.join(packageDir, "src/index.test.ts"), srcVitestTemplate());
|
|
786
|
+
if (useTsdown) await writeText(path.join(packageDir, "tsdown.config.ts"), tsdownConfigTemplate());
|
|
787
|
+
outro([
|
|
788
|
+
"Done. Added workspace package.",
|
|
789
|
+
`- path: packages/${packageName}`,
|
|
790
|
+
`- vitest: ${useVitest ? "enabled" : "disabled"}`,
|
|
791
|
+
`- tsdown: ${useTsdown ? "enabled" : "disabled"}`
|
|
792
|
+
].join("\n"));
|
|
793
|
+
}
|
|
794
|
+
async function resolvePnpmPackageManagerField(existing) {
|
|
795
|
+
if (existing?.startsWith("pnpm@")) return existing;
|
|
796
|
+
const pnpmVersion = await detectPackageManagerVersion("pnpm");
|
|
797
|
+
return pnpmVersion ? `pnpm@${pnpmVersion}` : "pnpm@latest";
|
|
798
|
+
}
|
|
799
|
+
function resolvePackageNameFromArg(nameArg) {
|
|
800
|
+
const value = nameArg.trim();
|
|
801
|
+
const invalidReason = validateProjectName(value);
|
|
802
|
+
if (invalidReason) {
|
|
803
|
+
cancel(invalidReason);
|
|
804
|
+
process.exitCode = 1;
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
return value;
|
|
808
|
+
}
|
|
809
|
+
function onCancel() {
|
|
810
|
+
cancel("Cancelled");
|
|
811
|
+
process.exitCode = 0;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
//#endregion
|
|
815
|
+
//#region src/commands/ci.ts
|
|
816
|
+
async function runCi() {
|
|
817
|
+
try {
|
|
818
|
+
intro("frontpl (ci)");
|
|
819
|
+
const rootDir = process.cwd();
|
|
820
|
+
const detectedPackageManager = await detectPackageManager(rootDir);
|
|
821
|
+
const packageManager = await select({
|
|
822
|
+
message: detectedPackageManager ? `Package manager (detected: ${detectedPackageManager})` : "Package manager",
|
|
823
|
+
initialValue: detectedPackageManager ?? "pnpm",
|
|
824
|
+
options: [
|
|
825
|
+
{
|
|
826
|
+
value: "npm",
|
|
827
|
+
label: "npm"
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
value: "yarn",
|
|
831
|
+
label: "yarn"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
value: "pnpm",
|
|
835
|
+
label: "pnpm"
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
value: "bun",
|
|
839
|
+
label: "bun"
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
value: "deno",
|
|
843
|
+
label: "deno"
|
|
844
|
+
}
|
|
845
|
+
]
|
|
846
|
+
});
|
|
847
|
+
if (isCancel(packageManager)) return abort$2();
|
|
848
|
+
const candidates = await listPackageCandidates(rootDir, packageManager);
|
|
849
|
+
if (candidates.length === 0) {
|
|
850
|
+
cancel("No package found. Run this command in a project root (with package.json or deno.json).");
|
|
851
|
+
process.exitCode = 1;
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const initialWorkingDirectory = await detectWorkingDirectory(rootDir, candidates);
|
|
855
|
+
const workingDirectory = candidates.length === 1 ? candidates[0] : await select({
|
|
856
|
+
message: "Working directory (package folder)",
|
|
857
|
+
initialValue: initialWorkingDirectory,
|
|
858
|
+
options: candidates.map((c) => ({
|
|
859
|
+
value: c,
|
|
860
|
+
label: c
|
|
861
|
+
}))
|
|
862
|
+
});
|
|
863
|
+
if (isCancel(workingDirectory)) return abort$2();
|
|
864
|
+
const nodeVersionDefault = await detectNodeMajorVersion(rootDir) ?? 22;
|
|
865
|
+
const nodeVersionText = await text({
|
|
866
|
+
message: "Node.js major version (for GitHub Actions)",
|
|
867
|
+
initialValue: String(nodeVersionDefault),
|
|
868
|
+
validate: (value = "") => {
|
|
869
|
+
const major = Number.parseInt(value.trim(), 10);
|
|
870
|
+
if (!Number.isFinite(major) || major <= 0) return "Enter a valid major version (e.g. 22)";
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
if (isCancel(nodeVersionText)) return abort$2();
|
|
874
|
+
const nodeVersion = Number.parseInt(String(nodeVersionText).trim(), 10);
|
|
875
|
+
const { runLint, runFormatCheck, runTests, lintCommand, formatCheckCommand, testCommand } = await resolveCiCommands(rootDir, workingDirectory, packageManager);
|
|
876
|
+
const addRelease = await confirm({
|
|
877
|
+
message: "Add release workflow too?",
|
|
878
|
+
initialValue: true
|
|
879
|
+
});
|
|
880
|
+
if (isCancel(addRelease)) return abort$2();
|
|
881
|
+
const releaseMode = addRelease ? await select({
|
|
882
|
+
message: "Release workflows",
|
|
883
|
+
initialValue: "tag",
|
|
884
|
+
options: [
|
|
885
|
+
{
|
|
886
|
+
value: "tag",
|
|
887
|
+
label: "Tag push (vX.Y.Z) — recommended"
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
value: "commit",
|
|
891
|
+
label: "Release commit (chore(release): vX.Y.Z) — legacy"
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
value: "both",
|
|
895
|
+
label: "Both (tag + commit)"
|
|
896
|
+
}
|
|
897
|
+
]
|
|
898
|
+
}) : void 0;
|
|
899
|
+
if (isCancel(releaseMode)) return abort$2();
|
|
900
|
+
const trustedPublishing = addRelease && packageManager !== "deno" ? await confirm({
|
|
901
|
+
message: "Release: npm trusted publishing (OIDC)?",
|
|
902
|
+
initialValue: true
|
|
903
|
+
}) : void 0;
|
|
904
|
+
if (isCancel(trustedPublishing)) return abort$2();
|
|
905
|
+
const addDependabot = await pathExists(path.join(rootDir, ".git")) ? await confirm({
|
|
906
|
+
message: "Add/update Dependabot config (.github/dependabot.yml)?",
|
|
907
|
+
initialValue: true
|
|
908
|
+
}) : false;
|
|
909
|
+
if (isCancel(addDependabot)) return abort$2();
|
|
910
|
+
const ciWorkflowPath = path.join(rootDir, ".github/workflows/ci.yml");
|
|
911
|
+
const releaseWorkflowPath = path.join(rootDir, ".github/workflows/release.yml");
|
|
912
|
+
const dependabotPath = path.join(rootDir, ".github/dependabot.yml");
|
|
913
|
+
if (!await confirmOverwriteIfExists(ciWorkflowPath, ".github/workflows/ci.yml")) {
|
|
914
|
+
cancel("Skipped CI workflow");
|
|
915
|
+
process.exitCode = 0;
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
await writeText(ciWorkflowPath, githubCliCiWorkflowTemplate({
|
|
919
|
+
packageManager,
|
|
920
|
+
nodeVersion,
|
|
922
921
|
workingDirectory,
|
|
923
|
-
runLint
|
|
924
|
-
runFormatCheck
|
|
925
|
-
runTests
|
|
922
|
+
runLint,
|
|
923
|
+
runFormatCheck,
|
|
924
|
+
runTests,
|
|
926
925
|
lintCommand,
|
|
927
926
|
formatCheckCommand,
|
|
928
927
|
testCommand
|
|
929
928
|
}));
|
|
930
|
-
if (
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
929
|
+
if (addRelease) {
|
|
930
|
+
if (await confirmOverwriteIfExists(releaseWorkflowPath, ".github/workflows/release.yml")) await writeText(releaseWorkflowPath, (releaseMode === "both" ? githubCliReleaseBothWorkflowTemplate : releaseMode === "commit" ? githubCliReleaseWorkflowTemplate : githubCliReleaseTagWorkflowTemplate)({
|
|
931
|
+
packageManager,
|
|
932
|
+
nodeVersion,
|
|
933
|
+
workingDirectory,
|
|
934
|
+
trustedPublishing
|
|
935
|
+
}));
|
|
936
|
+
}
|
|
937
|
+
if (addDependabot) {
|
|
938
|
+
if (await confirmOverwriteIfExists(dependabotPath, ".github/dependabot.yml")) await writeText(dependabotPath, githubDependabotTemplate({
|
|
939
|
+
packageManager,
|
|
940
|
+
workingDirectory
|
|
941
|
+
}));
|
|
942
|
+
}
|
|
943
|
+
outro(addRelease ? "Done. Generated CI + release workflows (and optional Dependabot)." : "Done. Generated CI workflow (and optional Dependabot).");
|
|
944
|
+
} catch (err) {
|
|
945
|
+
if (err instanceof CancelledError$2) return;
|
|
946
|
+
throw err;
|
|
934
947
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
nodeVersion: 22,
|
|
940
|
-
workingDirectory,
|
|
941
|
-
trustedPublishing
|
|
942
|
-
}));
|
|
948
|
+
}
|
|
949
|
+
var CancelledError$2 = class extends Error {
|
|
950
|
+
constructor() {
|
|
951
|
+
super("Cancelled");
|
|
943
952
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
953
|
+
};
|
|
954
|
+
function abort$2(opts = {}) {
|
|
955
|
+
cancel(opts.message ?? "Cancelled");
|
|
956
|
+
process.exitCode = opts.exitCode ?? 0;
|
|
957
|
+
throw new CancelledError$2();
|
|
958
|
+
}
|
|
959
|
+
async function confirmOverwriteIfExists(absPath, label) {
|
|
960
|
+
if (!await pathExists(absPath)) return true;
|
|
961
|
+
const overwrite = await confirm({
|
|
962
|
+
message: `Overwrite existing ${label}?`,
|
|
963
|
+
initialValue: true
|
|
964
|
+
});
|
|
965
|
+
if (isCancel(overwrite)) return abort$2();
|
|
966
|
+
return overwrite;
|
|
967
|
+
}
|
|
968
|
+
async function listPackageCandidates(rootDir, packageManager) {
|
|
969
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
970
|
+
if (await pathExists(path.join(rootDir, "package.json"))) candidates.add(".");
|
|
971
|
+
if (packageManager === "deno" && (await pathExists(path.join(rootDir, "deno.json")) || await pathExists(path.join(rootDir, "deno.jsonc")))) candidates.add(".");
|
|
972
|
+
for (const base of ["packages", "apps"]) {
|
|
973
|
+
const baseDir = path.join(rootDir, base);
|
|
974
|
+
if (!await pathExists(baseDir)) continue;
|
|
975
|
+
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
976
|
+
for (const entry of entries) {
|
|
977
|
+
if (!entry.isDirectory()) continue;
|
|
978
|
+
if (await pathExists(path.join(baseDir, entry.name, "package.json"))) candidates.add(path.posix.join(base, entry.name));
|
|
979
|
+
}
|
|
951
980
|
}
|
|
952
|
-
|
|
953
|
-
outro(`Done. Next:\n cd ${projectName}${!canInstall ? `\n (${packageManager} not found, run install manually)` : !installOk ? `\n (${packageManager} install failed, run install manually)` : ""}\n ${nextStepHint(packageManager)}`);
|
|
981
|
+
return [...candidates];
|
|
954
982
|
}
|
|
955
|
-
function
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
if (
|
|
961
|
-
|
|
983
|
+
async function detectWorkingDirectory(rootDir, candidates) {
|
|
984
|
+
if (candidates.length === 1) return candidates[0];
|
|
985
|
+
const rootScripts = (await readPackageJson(path.join(rootDir, "package.json")))?.scripts ?? {};
|
|
986
|
+
const rootHasScripts = Object.keys(rootScripts).length > 0;
|
|
987
|
+
const nonRoot = candidates.filter((c) => c !== ".");
|
|
988
|
+
if (!rootHasScripts && nonRoot.length === 1) return nonRoot[0];
|
|
989
|
+
return ".";
|
|
962
990
|
}
|
|
963
|
-
function
|
|
964
|
-
|
|
965
|
-
|
|
991
|
+
async function detectNodeMajorVersion(rootDir) {
|
|
992
|
+
for (const file of [".nvmrc", ".node-version"]) {
|
|
993
|
+
const filePath = path.join(rootDir, file);
|
|
994
|
+
if (!await pathExists(filePath)) continue;
|
|
995
|
+
const major = parseMajorVersion((await readFile(filePath, "utf8")).split("\n")[0]?.trim() ?? "");
|
|
996
|
+
if (major) return major;
|
|
997
|
+
}
|
|
998
|
+
const engine = (await readPackageJson(path.join(rootDir, "package.json")))?.engines?.node;
|
|
999
|
+
if (!engine) return;
|
|
1000
|
+
const match = engine.match(/([0-9]{2,})/);
|
|
1001
|
+
if (!match) return;
|
|
1002
|
+
return Number.parseInt(match[1], 10);
|
|
966
1003
|
}
|
|
967
|
-
function
|
|
1004
|
+
function parseMajorVersion(input) {
|
|
1005
|
+
const trimmed = input.trim().replace(/^v/, "");
|
|
1006
|
+
const major = Number.parseInt(trimmed.split(".")[0] ?? "", 10);
|
|
1007
|
+
if (!Number.isFinite(major) || major <= 0) return;
|
|
1008
|
+
return major;
|
|
1009
|
+
}
|
|
1010
|
+
async function resolveCiCommands(rootDir, workingDirectory, packageManager) {
|
|
1011
|
+
if (packageManager === "deno") return {
|
|
1012
|
+
runLint: true,
|
|
1013
|
+
runFormatCheck: true,
|
|
1014
|
+
runTests: true
|
|
1015
|
+
};
|
|
1016
|
+
const pkg = await readPackageJson(path.join(rootDir, workingDirectory, "package.json"));
|
|
1017
|
+
if (!pkg) return abort$2({
|
|
1018
|
+
message: `Missing package.json in ${workingDirectory}`,
|
|
1019
|
+
exitCode: 1
|
|
1020
|
+
});
|
|
1021
|
+
const scripts = pkg.scripts ?? {};
|
|
1022
|
+
const hasLint = typeof scripts.lint === "string";
|
|
1023
|
+
const hasTest = typeof scripts.test === "string";
|
|
1024
|
+
const hasFormatCheck = typeof scripts["format:check"] === "string";
|
|
1025
|
+
const runLintDefault = hasLint;
|
|
1026
|
+
const runFormatCheckDefault = hasFormatCheck;
|
|
1027
|
+
const runTestsDefault = hasTest;
|
|
1028
|
+
const runLint = await confirm({
|
|
1029
|
+
message: `CI: run lint${hasLint ? "" : " (no lint script detected)"}`,
|
|
1030
|
+
initialValue: runLintDefault
|
|
1031
|
+
});
|
|
1032
|
+
if (isCancel(runLint)) return abort$2();
|
|
1033
|
+
const runFormatCheck = await confirm({
|
|
1034
|
+
message: `CI: run format check${runFormatCheckDefault ? "" : " (no format check script detected)"}`,
|
|
1035
|
+
initialValue: runFormatCheckDefault
|
|
1036
|
+
});
|
|
1037
|
+
if (isCancel(runFormatCheck)) return abort$2();
|
|
1038
|
+
const runTests = await confirm({
|
|
1039
|
+
message: `CI: run tests${hasTest ? "" : " (no test script detected)"}`,
|
|
1040
|
+
initialValue: runTestsDefault
|
|
1041
|
+
});
|
|
1042
|
+
if (isCancel(runTests)) return abort$2();
|
|
1043
|
+
return {
|
|
1044
|
+
runLint,
|
|
1045
|
+
runFormatCheck,
|
|
1046
|
+
runTests,
|
|
1047
|
+
lintCommand: runLint && hasLint ? pmRun(packageManager, "lint") : runLint ? await promptCommand("Lint command", pmRun(packageManager, "lint")) : void 0,
|
|
1048
|
+
formatCheckCommand: runFormatCheck && hasFormatCheck ? pmRun(packageManager, "format:check") : runFormatCheck ? await promptCommand("Format check command", pmRun(packageManager, "format:check")) : void 0,
|
|
1049
|
+
testCommand: runTests && hasTest ? pmRun(packageManager, "test") : runTests ? await promptCommand("Test command", pmRun(packageManager, "test")) : void 0
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
async function promptCommand(message, initialValue) {
|
|
1053
|
+
const value = await text({
|
|
1054
|
+
message,
|
|
1055
|
+
initialValue,
|
|
1056
|
+
validate: (v = "") => !v.trim() ? "Command is required" : void 0
|
|
1057
|
+
});
|
|
1058
|
+
if (isCancel(value)) return abort$2();
|
|
1059
|
+
return String(value).trim();
|
|
1060
|
+
}
|
|
1061
|
+
function pmRun(pm, script) {
|
|
968
1062
|
switch (pm) {
|
|
969
|
-
case "npm": return
|
|
970
|
-
case "pnpm": return
|
|
971
|
-
case "yarn": return
|
|
972
|
-
case "bun": return
|
|
973
|
-
case "deno": return
|
|
1063
|
+
case "npm": return `npm run ${script}`;
|
|
1064
|
+
case "pnpm": return `pnpm run ${script}`;
|
|
1065
|
+
case "yarn": return `yarn ${script}`;
|
|
1066
|
+
case "bun": return `bun run ${script}`;
|
|
1067
|
+
case "deno": return script;
|
|
974
1068
|
}
|
|
975
1069
|
}
|
|
976
1070
|
|
|
@@ -1223,7 +1317,9 @@ function abort$1(opts = {}) {
|
|
|
1223
1317
|
//#region src/commands/oxfmt.ts
|
|
1224
1318
|
const OXFMT_SCRIPTS = {
|
|
1225
1319
|
format: "oxfmt",
|
|
1226
|
-
"format:check": "oxfmt --check"
|
|
1320
|
+
"format:check": "oxfmt --check"
|
|
1321
|
+
};
|
|
1322
|
+
const OXFMT_LEGACY_SCRIPTS = {
|
|
1227
1323
|
fmt: "oxfmt",
|
|
1228
1324
|
"fmt:check": "oxfmt --check"
|
|
1229
1325
|
};
|
|
@@ -1278,6 +1374,7 @@ async function runOxfmt({ yes = false } = {}) {
|
|
|
1278
1374
|
rootDir
|
|
1279
1375
|
});
|
|
1280
1376
|
const scriptSummary = stats.scriptsUpdated.length > 0 ? `updated scripts: ${stats.scriptsUpdated.join(", ")}` : stats.scriptsKept.length > 0 ? `kept existing scripts: ${stats.scriptsKept.join(", ")}` : "scripts already aligned";
|
|
1377
|
+
const legacyScriptSummary = stats.removedLegacyScripts.length > 0 ? `removed legacy scripts: ${stats.removedLegacyScripts.join(", ")}` : "no legacy scripts removed";
|
|
1281
1378
|
const depSummary = stats.addedOxfmtDependency ? "added devDependency: oxfmt" : "devDependency oxfmt already present";
|
|
1282
1379
|
const removedDepsSummary = stats.removedDependencies.length > 0 ? `removed prettier deps: ${stats.removedDependencies.join(", ")}` : "no prettier deps removed";
|
|
1283
1380
|
const removedPackageJsonPrettierSummary = stats.removedPackageJsonPrettierConfig ? "removed package.json#prettier" : "no package.json#prettier removed";
|
|
@@ -1287,6 +1384,7 @@ async function runOxfmt({ yes = false } = {}) {
|
|
|
1287
1384
|
outro([
|
|
1288
1385
|
"Done. Applied oxfmt migration.",
|
|
1289
1386
|
`- ${scriptSummary}`,
|
|
1387
|
+
`- ${legacyScriptSummary}`,
|
|
1290
1388
|
`- ${depSummary}`,
|
|
1291
1389
|
`- ${removedDepsSummary}`,
|
|
1292
1390
|
`- ${removedPackageJsonPrettierSummary}`,
|
|
@@ -1319,6 +1417,12 @@ async function migrateToOxfmt(opts) {
|
|
|
1319
1417
|
scripts[name] = command;
|
|
1320
1418
|
scriptsUpdated.push(name);
|
|
1321
1419
|
}
|
|
1420
|
+
const removedLegacyScripts = [];
|
|
1421
|
+
for (const [name, command] of Object.entries(OXFMT_LEGACY_SCRIPTS)) {
|
|
1422
|
+
if (scripts[name] !== command) continue;
|
|
1423
|
+
delete scripts[name];
|
|
1424
|
+
removedLegacyScripts.push(name);
|
|
1425
|
+
}
|
|
1322
1426
|
pkg.scripts = scripts;
|
|
1323
1427
|
const devDependencies = { ...pkg.devDependencies };
|
|
1324
1428
|
let addedOxfmtDependency = false;
|
|
@@ -1356,6 +1460,7 @@ async function migrateToOxfmt(opts) {
|
|
|
1356
1460
|
return {
|
|
1357
1461
|
scriptsUpdated,
|
|
1358
1462
|
scriptsKept,
|
|
1463
|
+
removedLegacyScripts,
|
|
1359
1464
|
addedOxfmtDependency,
|
|
1360
1465
|
removedPackageJsonPrettierConfig,
|
|
1361
1466
|
removedDependencies,
|
|
@@ -1487,4 +1592,4 @@ function abort(opts = {}) {
|
|
|
1487
1592
|
}
|
|
1488
1593
|
|
|
1489
1594
|
//#endregion
|
|
1490
|
-
export {
|
|
1595
|
+
export { runInit as a, githubDependabotTemplate as c, workspaceRootPackageJsonTemplate as d, runAdd as i, oxlintConfigTemplate as l, runOxlint as n, validateProjectName as o, runCi as r, githubCliCiWorkflowTemplate as s, runOxfmt as t, packageJsonTemplate as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frontpl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Interactive CLI to scaffold standardized frontend project templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -44,8 +44,6 @@
|
|
|
44
44
|
"lint:fix": "oxlint --type-aware --type-check --fix",
|
|
45
45
|
"format": "oxfmt",
|
|
46
46
|
"format:check": "oxfmt --check",
|
|
47
|
-
"fmt": "pnpm run format",
|
|
48
|
-
"fmt:check": "pnpm run format:check",
|
|
49
47
|
"prepublishOnly": "pnpm run build"
|
|
50
48
|
},
|
|
51
49
|
"dependencies": {
|