frontpl 0.4.0 → 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 +28 -2
- package/dist/cli.mjs +6 -1
- package/dist/index.d.mts +24 -1
- package/dist/index.mjs +2 -2
- package/dist/{oxfmt-Bgtl3zwv.mjs → oxfmt-DQBJ0JMs.mjs} +551 -413
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ frontpl init my-frontend
|
|
|
27
27
|
Follow the prompts to choose:
|
|
28
28
|
|
|
29
29
|
- Package manager (`npm`/`pnpm`/`yarn`/`bun`/`deno`)
|
|
30
|
+
- Optional `pnpm workspace` mode (monorepo skeleton)
|
|
30
31
|
- Optional tooling: `oxlint`, `oxfmt`, `vitest`, `tsdown`
|
|
31
32
|
|
|
32
33
|
When `oxlint` is enabled, generated projects use `@kingsword/lint-config` via `oxlint.config.ts`.
|
|
@@ -53,6 +54,32 @@ Generated output includes (based on options):
|
|
|
53
54
|
- Optional configs: `oxlint.config.ts`, `.oxfmtrc.json`, `tsdown.config.ts`
|
|
54
55
|
- Optional GitHub Actions workflows in `.github/workflows/`
|
|
55
56
|
|
|
57
|
+
When `pnpm workspace mode` is enabled:
|
|
58
|
+
|
|
59
|
+
- Root contains `pnpm-workspace.yaml` and the workspace `package.json`
|
|
60
|
+
- `oxlint`/`oxfmt` scripts, dependencies, and config files are generated at the workspace root
|
|
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.
|
|
82
|
+
|
|
56
83
|
### `frontpl ci`
|
|
57
84
|
|
|
58
85
|
Add or update CI/Release workflows for an existing project (run it in your repo root).
|
|
@@ -102,8 +129,6 @@ What it does:
|
|
|
102
129
|
- Ensures `package.json` scripts use:
|
|
103
130
|
- `format`: `oxfmt`
|
|
104
131
|
- `format:check`: `oxfmt --check`
|
|
105
|
-
- `fmt`: `oxfmt`
|
|
106
|
-
- `fmt:check`: `oxfmt --check`
|
|
107
132
|
- Ensures `devDependencies.oxfmt` exists (defaults to `latest` when missing)
|
|
108
133
|
- Creates or updates `.oxfmtrc.json`
|
|
109
134
|
- Optionally removes `prettier` / `prettier-plugin-*` / `@prettier/plugin-*` dependencies, `package.json#prettier`, and Prettier config files (`.prettierrc*`, `prettier.config.*`)
|
|
@@ -138,6 +163,7 @@ When CI workflows are enabled, frontpl can also generate `.github/dependabot.yml
|
|
|
138
163
|
- Keeps `github-actions` updates enabled
|
|
139
164
|
- Adds grouped dependencies updates (`groups.dependencies`)
|
|
140
165
|
- Uses the selected `workingDirectory` (`.` -> `/`, monorepo package -> `/packages/<name>`)
|
|
166
|
+
- In `frontpl init` + `pnpm workspace mode`, default `workingDirectory` is workspace root (`/`)
|
|
141
167
|
- Maps JavaScript package managers (`npm`/`pnpm`/`yarn`/`bun`) to Dependabot `package-ecosystem: "npm"`
|
|
142
168
|
|
|
143
169
|
## Development
|
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;
|
|
@@ -46,6 +57,18 @@ declare function packageJsonTemplate(opts: {
|
|
|
46
57
|
useTsdown: boolean;
|
|
47
58
|
tsdownVersion?: string;
|
|
48
59
|
}): string;
|
|
60
|
+
declare function workspaceRootPackageJsonTemplate(opts: {
|
|
61
|
+
name: string;
|
|
62
|
+
packageManager: string;
|
|
63
|
+
useOxlint: boolean;
|
|
64
|
+
oxlintVersion?: string;
|
|
65
|
+
oxlintTsgolintVersion?: string;
|
|
66
|
+
kingswordLintConfigVersion?: string;
|
|
67
|
+
useOxfmt: boolean;
|
|
68
|
+
oxfmtVersion?: string;
|
|
69
|
+
useVitest: boolean;
|
|
70
|
+
useTsdown: boolean;
|
|
71
|
+
}): string;
|
|
49
72
|
declare function githubCliCiWorkflowTemplate(opts: {
|
|
50
73
|
packageManager: "npm" | "pnpm" | "yarn" | "bun" | "deno";
|
|
51
74
|
nodeVersion: number;
|
|
@@ -65,4 +88,4 @@ declare function githubDependabotTemplate(opts: {
|
|
|
65
88
|
workingDirectory: string;
|
|
66
89
|
}): string;
|
|
67
90
|
//#endregion
|
|
68
|
-
export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runCi, runInit, runOxfmt, runOxlint, validateProjectName };
|
|
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 };
|
|
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
|
|
@@ -152,28 +149,33 @@ function tsdownConfigTemplate() {
|
|
|
152
149
|
""
|
|
153
150
|
].join("\n");
|
|
154
151
|
}
|
|
155
|
-
function
|
|
156
|
-
const scripts = {};
|
|
152
|
+
function applyLintAndFormatScripts(scripts, opts) {
|
|
157
153
|
if (opts.useOxlint) {
|
|
158
154
|
const oxlintCmd = "oxlint --type-aware --type-check";
|
|
159
155
|
scripts.lint = oxlintCmd;
|
|
160
156
|
scripts["lint:fix"] = `${oxlintCmd} --fix`;
|
|
161
|
-
}
|
|
157
|
+
}
|
|
162
158
|
if (opts.useOxfmt) {
|
|
163
159
|
scripts.format = "oxfmt";
|
|
164
160
|
scripts["format:check"] = "oxfmt --check";
|
|
165
|
-
scripts.fmt = "oxfmt";
|
|
166
|
-
scripts["fmt:check"] = "oxfmt --check";
|
|
167
161
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const devDependencies = { typescript: opts.typescriptVersion };
|
|
162
|
+
}
|
|
163
|
+
function applyLintAndFormatDependencies(devDependencies, opts) {
|
|
171
164
|
if (opts.useOxlint) {
|
|
172
165
|
if (opts.oxlintVersion) devDependencies.oxlint = opts.oxlintVersion;
|
|
173
166
|
if (opts.oxlintTsgolintVersion) devDependencies["oxlint-tsgolint"] = opts.oxlintTsgolintVersion;
|
|
174
167
|
if (opts.kingswordLintConfigVersion) devDependencies["@kingsword/lint-config"] = opts.kingswordLintConfigVersion;
|
|
175
168
|
}
|
|
176
169
|
if (opts.useOxfmt && opts.oxfmtVersion) devDependencies.oxfmt = opts.oxfmtVersion;
|
|
170
|
+
}
|
|
171
|
+
function packageJsonTemplate(opts) {
|
|
172
|
+
const scripts = {};
|
|
173
|
+
if (!opts.useOxlint && opts.includeTypecheckWithoutOxlint !== false) scripts.typecheck = "tsc --noEmit";
|
|
174
|
+
applyLintAndFormatScripts(scripts, opts);
|
|
175
|
+
if (opts.useVitest) scripts.test = "vitest";
|
|
176
|
+
if (opts.useTsdown) scripts.build = "tsdown";
|
|
177
|
+
const devDependencies = { typescript: opts.typescriptVersion };
|
|
178
|
+
applyLintAndFormatDependencies(devDependencies, opts);
|
|
177
179
|
if (opts.useVitest && opts.vitestVersion) devDependencies.vitest = opts.vitestVersion;
|
|
178
180
|
if (opts.useTsdown && opts.tsdownVersion) devDependencies.tsdown = opts.tsdownVersion;
|
|
179
181
|
return JSON.stringify({
|
|
@@ -186,6 +188,22 @@ function packageJsonTemplate(opts) {
|
|
|
186
188
|
packageManager: opts.packageManager
|
|
187
189
|
}, null, 2) + "\n";
|
|
188
190
|
}
|
|
191
|
+
function workspaceRootPackageJsonTemplate(opts) {
|
|
192
|
+
const scripts = {};
|
|
193
|
+
applyLintAndFormatScripts(scripts, opts);
|
|
194
|
+
if (opts.useVitest) scripts.test = "pnpm -r --if-present run test";
|
|
195
|
+
if (opts.useTsdown) scripts.build = "pnpm -r --if-present run build";
|
|
196
|
+
const devDependencies = {};
|
|
197
|
+
applyLintAndFormatDependencies(devDependencies, opts);
|
|
198
|
+
const manifest = {
|
|
199
|
+
name: opts.name,
|
|
200
|
+
private: true,
|
|
201
|
+
packageManager: opts.packageManager
|
|
202
|
+
};
|
|
203
|
+
if (Object.keys(scripts).length > 0) manifest.scripts = scripts;
|
|
204
|
+
if (Object.keys(devDependencies).length > 0) manifest.devDependencies = devDependencies;
|
|
205
|
+
return JSON.stringify(manifest, null, 2) + "\n";
|
|
206
|
+
}
|
|
189
207
|
const DEFAULT_WORKFLOWS_REF = "7320d30bcd47cee17cc2d8d28250ba1ab1f742b8";
|
|
190
208
|
const DEFAULT_WORKFLOWS_VERSION = "v1.0.3";
|
|
191
209
|
function resolveWorkflowsPin(opts) {
|
|
@@ -376,267 +394,58 @@ function yamlString(value) {
|
|
|
376
394
|
}
|
|
377
395
|
|
|
378
396
|
//#endregion
|
|
379
|
-
//#region src/
|
|
380
|
-
async function
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const rootDir = process.cwd();
|
|
384
|
-
const detectedPackageManager = await detectPackageManager(rootDir);
|
|
385
|
-
const packageManager = await select({
|
|
386
|
-
message: detectedPackageManager ? `Package manager (detected: ${detectedPackageManager})` : "Package manager",
|
|
387
|
-
initialValue: detectedPackageManager ?? "pnpm",
|
|
388
|
-
options: [
|
|
389
|
-
{
|
|
390
|
-
value: "npm",
|
|
391
|
-
label: "npm"
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
value: "yarn",
|
|
395
|
-
label: "yarn"
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
value: "pnpm",
|
|
399
|
-
label: "pnpm"
|
|
400
|
-
},
|
|
401
|
-
{
|
|
402
|
-
value: "bun",
|
|
403
|
-
label: "bun"
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
value: "deno",
|
|
407
|
-
label: "deno"
|
|
408
|
-
}
|
|
409
|
-
]
|
|
410
|
-
});
|
|
411
|
-
if (isCancel(packageManager)) return abort$2();
|
|
412
|
-
const candidates = await listPackageCandidates(rootDir, packageManager);
|
|
413
|
-
if (candidates.length === 0) {
|
|
414
|
-
cancel("No package found. Run this command in a project root (with package.json or deno.json).");
|
|
415
|
-
process.exitCode = 1;
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
const initialWorkingDirectory = await detectWorkingDirectory(rootDir, candidates);
|
|
419
|
-
const workingDirectory = candidates.length === 1 ? candidates[0] : await select({
|
|
420
|
-
message: "Working directory (package folder)",
|
|
421
|
-
initialValue: initialWorkingDirectory,
|
|
422
|
-
options: candidates.map((c) => ({
|
|
423
|
-
value: c,
|
|
424
|
-
label: c
|
|
425
|
-
}))
|
|
426
|
-
});
|
|
427
|
-
if (isCancel(workingDirectory)) return abort$2();
|
|
428
|
-
const nodeVersionDefault = await detectNodeMajorVersion(rootDir) ?? 22;
|
|
429
|
-
const nodeVersionText = await text({
|
|
430
|
-
message: "Node.js major version (for GitHub Actions)",
|
|
431
|
-
initialValue: String(nodeVersionDefault),
|
|
432
|
-
validate: (value = "") => {
|
|
433
|
-
const major = Number.parseInt(value.trim(), 10);
|
|
434
|
-
if (!Number.isFinite(major) || major <= 0) return "Enter a valid major version (e.g. 22)";
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
if (isCancel(nodeVersionText)) return abort$2();
|
|
438
|
-
const nodeVersion = Number.parseInt(String(nodeVersionText).trim(), 10);
|
|
439
|
-
const { runLint, runFormatCheck, runTests, lintCommand, formatCheckCommand, testCommand } = await resolveCiCommands(rootDir, workingDirectory, packageManager);
|
|
440
|
-
const addRelease = await confirm({
|
|
441
|
-
message: "Add release workflow too?",
|
|
442
|
-
initialValue: true
|
|
443
|
-
});
|
|
444
|
-
if (isCancel(addRelease)) return abort$2();
|
|
445
|
-
const releaseMode = addRelease ? await select({
|
|
446
|
-
message: "Release workflows",
|
|
447
|
-
initialValue: "tag",
|
|
448
|
-
options: [
|
|
449
|
-
{
|
|
450
|
-
value: "tag",
|
|
451
|
-
label: "Tag push (vX.Y.Z) — recommended"
|
|
452
|
-
},
|
|
453
|
-
{
|
|
454
|
-
value: "commit",
|
|
455
|
-
label: "Release commit (chore(release): vX.Y.Z) — legacy"
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
value: "both",
|
|
459
|
-
label: "Both (tag + commit)"
|
|
460
|
-
}
|
|
461
|
-
]
|
|
462
|
-
}) : void 0;
|
|
463
|
-
if (isCancel(releaseMode)) return abort$2();
|
|
464
|
-
const trustedPublishing = addRelease && packageManager !== "deno" ? await confirm({
|
|
465
|
-
message: "Release: npm trusted publishing (OIDC)?",
|
|
466
|
-
initialValue: true
|
|
467
|
-
}) : void 0;
|
|
468
|
-
if (isCancel(trustedPublishing)) return abort$2();
|
|
469
|
-
const addDependabot = await pathExists(path.join(rootDir, ".git")) ? await confirm({
|
|
470
|
-
message: "Add/update Dependabot config (.github/dependabot.yml)?",
|
|
471
|
-
initialValue: true
|
|
472
|
-
}) : false;
|
|
473
|
-
if (isCancel(addDependabot)) return abort$2();
|
|
474
|
-
const ciWorkflowPath = path.join(rootDir, ".github/workflows/ci.yml");
|
|
475
|
-
const releaseWorkflowPath = path.join(rootDir, ".github/workflows/release.yml");
|
|
476
|
-
const dependabotPath = path.join(rootDir, ".github/dependabot.yml");
|
|
477
|
-
if (!await confirmOverwriteIfExists(ciWorkflowPath, ".github/workflows/ci.yml")) {
|
|
478
|
-
cancel("Skipped CI workflow");
|
|
479
|
-
process.exitCode = 0;
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
await writeText(ciWorkflowPath, githubCliCiWorkflowTemplate({
|
|
483
|
-
packageManager,
|
|
484
|
-
nodeVersion,
|
|
485
|
-
workingDirectory,
|
|
486
|
-
runLint,
|
|
487
|
-
runFormatCheck,
|
|
488
|
-
runTests,
|
|
489
|
-
lintCommand,
|
|
490
|
-
formatCheckCommand,
|
|
491
|
-
testCommand
|
|
492
|
-
}));
|
|
493
|
-
if (addRelease) {
|
|
494
|
-
if (await confirmOverwriteIfExists(releaseWorkflowPath, ".github/workflows/release.yml")) await writeText(releaseWorkflowPath, (releaseMode === "both" ? githubCliReleaseBothWorkflowTemplate : releaseMode === "commit" ? githubCliReleaseWorkflowTemplate : githubCliReleaseTagWorkflowTemplate)({
|
|
495
|
-
packageManager,
|
|
496
|
-
nodeVersion,
|
|
497
|
-
workingDirectory,
|
|
498
|
-
trustedPublishing
|
|
499
|
-
}));
|
|
500
|
-
}
|
|
501
|
-
if (addDependabot) {
|
|
502
|
-
if (await confirmOverwriteIfExists(dependabotPath, ".github/dependabot.yml")) await writeText(dependabotPath, githubDependabotTemplate({
|
|
503
|
-
packageManager,
|
|
504
|
-
workingDirectory
|
|
505
|
-
}));
|
|
506
|
-
}
|
|
507
|
-
outro(addRelease ? "Done. Generated CI + release workflows (and optional Dependabot)." : "Done. Generated CI workflow (and optional Dependabot).");
|
|
508
|
-
} catch (err) {
|
|
509
|
-
if (err instanceof CancelledError$2) return;
|
|
510
|
-
throw err;
|
|
511
|
-
}
|
|
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");
|
|
512
401
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
402
|
+
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/lib/utils.ts
|
|
405
|
+
async function pathExists(pathname) {
|
|
406
|
+
try {
|
|
407
|
+
await access(pathname);
|
|
408
|
+
return true;
|
|
409
|
+
} catch {
|
|
410
|
+
return false;
|
|
516
411
|
}
|
|
517
|
-
};
|
|
518
|
-
function abort$2(opts = {}) {
|
|
519
|
-
cancel(opts.message ?? "Cancelled");
|
|
520
|
-
process.exitCode = opts.exitCode ?? 0;
|
|
521
|
-
throw new CancelledError$2();
|
|
522
412
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
async function listPackageCandidates(rootDir, packageManager) {
|
|
533
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
534
|
-
if (await pathExists(path.join(rootDir, "package.json"))) candidates.add(".");
|
|
535
|
-
if (packageManager === "deno" && (await pathExists(path.join(rootDir, "deno.json")) || await pathExists(path.join(rootDir, "deno.jsonc")))) candidates.add(".");
|
|
536
|
-
for (const base of ["packages", "apps"]) {
|
|
537
|
-
const baseDir = path.join(rootDir, base);
|
|
538
|
-
if (!await pathExists(baseDir)) continue;
|
|
539
|
-
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
540
|
-
for (const entry of entries) {
|
|
541
|
-
if (!entry.isDirectory()) continue;
|
|
542
|
-
if (await pathExists(path.join(baseDir, entry.name, "package.json"))) candidates.add(path.posix.join(base, entry.name));
|
|
543
|
-
}
|
|
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;
|
|
544
421
|
}
|
|
545
|
-
return [...candidates];
|
|
546
422
|
}
|
|
547
|
-
async function
|
|
548
|
-
|
|
549
|
-
const rootScripts = (await readPackageJson(path.join(rootDir, "package.json")))?.scripts ?? {};
|
|
550
|
-
const rootHasScripts = Object.keys(rootScripts).length > 0;
|
|
551
|
-
const nonRoot = candidates.filter((c) => c !== ".");
|
|
552
|
-
if (!rootHasScripts && nonRoot.length === 1) return nonRoot[0];
|
|
553
|
-
return ".";
|
|
423
|
+
async function writePackageJson(filePath, value) {
|
|
424
|
+
await writeText(filePath, JSON.stringify(value, null, 2) + "\n");
|
|
554
425
|
}
|
|
555
|
-
async function
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
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;
|
|
561
431
|
}
|
|
562
|
-
const
|
|
563
|
-
if (
|
|
564
|
-
|
|
565
|
-
if (
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const major = Number.parseInt(trimmed.split(".")[0] ?? "", 10);
|
|
571
|
-
if (!Number.isFinite(major) || major <= 0) return;
|
|
572
|
-
return major;
|
|
573
|
-
}
|
|
574
|
-
async function resolveCiCommands(rootDir, workingDirectory, packageManager) {
|
|
575
|
-
if (packageManager === "deno") return {
|
|
576
|
-
runLint: true,
|
|
577
|
-
runFormatCheck: true,
|
|
578
|
-
runTests: true
|
|
579
|
-
};
|
|
580
|
-
const pkg = await readPackageJson(path.join(rootDir, workingDirectory, "package.json"));
|
|
581
|
-
if (!pkg) return abort$2({
|
|
582
|
-
message: `Missing package.json in ${workingDirectory}`,
|
|
583
|
-
exitCode: 1
|
|
584
|
-
});
|
|
585
|
-
const scripts = pkg.scripts ?? {};
|
|
586
|
-
const hasLint = typeof scripts.lint === "string";
|
|
587
|
-
const hasTest = typeof scripts.test === "string";
|
|
588
|
-
const hasFormatCheck = typeof scripts["format:check"] === "string";
|
|
589
|
-
const hasFmtCheck = typeof scripts["fmt:check"] === "string";
|
|
590
|
-
const runLintDefault = hasLint;
|
|
591
|
-
const runFormatCheckDefault = hasFormatCheck || hasFmtCheck;
|
|
592
|
-
const runTestsDefault = hasTest;
|
|
593
|
-
const runLint = await confirm({
|
|
594
|
-
message: `CI: run lint${hasLint ? "" : " (no lint script detected)"}`,
|
|
595
|
-
initialValue: runLintDefault
|
|
596
|
-
});
|
|
597
|
-
if (isCancel(runLint)) return abort$2();
|
|
598
|
-
const runFormatCheck = await confirm({
|
|
599
|
-
message: `CI: run format check${runFormatCheckDefault ? "" : " (no format check script detected)"}`,
|
|
600
|
-
initialValue: runFormatCheckDefault
|
|
601
|
-
});
|
|
602
|
-
if (isCancel(runFormatCheck)) return abort$2();
|
|
603
|
-
const runTests = await confirm({
|
|
604
|
-
message: `CI: run tests${hasTest ? "" : " (no test script detected)"}`,
|
|
605
|
-
initialValue: runTestsDefault
|
|
606
|
-
});
|
|
607
|
-
if (isCancel(runTests)) return abort$2();
|
|
608
|
-
return {
|
|
609
|
-
runLint,
|
|
610
|
-
runFormatCheck,
|
|
611
|
-
runTests,
|
|
612
|
-
lintCommand: runLint && hasLint ? pmRun$1(packageManager, "lint") : runLint ? await promptCommand("Lint command", pmRun$1(packageManager, "lint")) : void 0,
|
|
613
|
-
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,
|
|
614
|
-
testCommand: runTests && hasTest ? pmRun$1(packageManager, "test") : runTests ? await promptCommand("Test command", pmRun$1(packageManager, "test")) : void 0
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
async function promptCommand(message, initialValue) {
|
|
618
|
-
const value = await text({
|
|
619
|
-
message,
|
|
620
|
-
initialValue,
|
|
621
|
-
validate: (v = "") => !v.trim() ? "Command is required" : void 0
|
|
622
|
-
});
|
|
623
|
-
if (isCancel(value)) return abort$2();
|
|
624
|
-
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;
|
|
625
440
|
}
|
|
626
|
-
function
|
|
627
|
-
|
|
628
|
-
case "npm": return `npm run ${script}`;
|
|
629
|
-
case "pnpm": return `pnpm run ${script}`;
|
|
630
|
-
case "yarn": return `yarn ${script}`;
|
|
631
|
-
case "bun": return `bun run ${script}`;
|
|
632
|
-
case "deno": return script;
|
|
633
|
-
}
|
|
441
|
+
function isPackageManager(value) {
|
|
442
|
+
return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun" || value === "deno";
|
|
634
443
|
}
|
|
635
444
|
|
|
636
445
|
//#endregion
|
|
637
446
|
//#region src/lib/exec.ts
|
|
638
447
|
async function exec(command, args, opts = {}) {
|
|
639
|
-
const resolved = resolveCommand
|
|
448
|
+
const resolved = resolveCommand(command);
|
|
640
449
|
return new Promise((resolve) => {
|
|
641
450
|
const child = spawn(resolved, args, {
|
|
642
451
|
cwd: opts.cwd,
|
|
@@ -648,52 +457,6 @@ async function exec(command, args, opts = {}) {
|
|
|
648
457
|
child.on("error", () => resolve({ ok: false }));
|
|
649
458
|
});
|
|
650
459
|
}
|
|
651
|
-
function resolveCommand$1(command) {
|
|
652
|
-
if (process.platform !== "win32") return command;
|
|
653
|
-
if (command === "npm") return "npm.cmd";
|
|
654
|
-
if (command === "pnpm") return "pnpm.cmd";
|
|
655
|
-
if (command === "yarn") return "yarn.cmd";
|
|
656
|
-
return command;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
//#endregion
|
|
660
|
-
//#region src/lib/versions.ts
|
|
661
|
-
async function detectPackageManagerVersion(pm) {
|
|
662
|
-
switch (pm) {
|
|
663
|
-
case "npm": return (await execCapture("npm", ["--version"])).stdout.trim() || void 0;
|
|
664
|
-
case "pnpm": return (await execCapture("pnpm", ["--version"])).stdout.trim() || void 0;
|
|
665
|
-
case "yarn": return (await execCapture("yarn", ["--version"])).stdout.trim() || void 0;
|
|
666
|
-
case "bun": return (await execCapture("bun", ["--version"])).stdout.trim() || void 0;
|
|
667
|
-
case "deno": return ((await execCapture("deno", ["--version"])).stdout.trim().split("\n")[0] ?? "").match(/deno\\s+([0-9]+\\.[0-9]+\\.[0-9]+)/)?.[1];
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
async function execCapture(command, args) {
|
|
671
|
-
const resolved = resolveCommand(command);
|
|
672
|
-
return new Promise((resolve) => {
|
|
673
|
-
const child = spawn(resolved, args, {
|
|
674
|
-
cwd: os.tmpdir(),
|
|
675
|
-
stdio: [
|
|
676
|
-
"ignore",
|
|
677
|
-
"pipe",
|
|
678
|
-
"ignore"
|
|
679
|
-
],
|
|
680
|
-
shell: false,
|
|
681
|
-
env: process.env
|
|
682
|
-
});
|
|
683
|
-
const chunks = [];
|
|
684
|
-
child.stdout.on("data", (d) => chunks.push(Buffer.from(d)));
|
|
685
|
-
child.on("close", (code) => {
|
|
686
|
-
resolve({
|
|
687
|
-
ok: code === 0,
|
|
688
|
-
stdout: Buffer.concat(chunks).toString("utf8")
|
|
689
|
-
});
|
|
690
|
-
});
|
|
691
|
-
child.on("error", () => resolve({
|
|
692
|
-
ok: false,
|
|
693
|
-
stdout: ""
|
|
694
|
-
}));
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
460
|
function resolveCommand(command) {
|
|
698
461
|
if (process.platform !== "win32") return command;
|
|
699
462
|
if (command === "npm") return "npm.cmd";
|
|
@@ -704,7 +467,7 @@ function resolveCommand(command) {
|
|
|
704
467
|
|
|
705
468
|
//#endregion
|
|
706
469
|
//#region src/commands/init.ts
|
|
707
|
-
function pmRun(pm, script) {
|
|
470
|
+
function pmRun$1(pm, script) {
|
|
708
471
|
switch (pm) {
|
|
709
472
|
case "npm": return `npm run ${script}`;
|
|
710
473
|
case "pnpm": return `pnpm run ${script}`;
|
|
@@ -720,7 +483,7 @@ async function runInit({ nameArg }) {
|
|
|
720
483
|
initialValue: nameArg ?? "my-frontend",
|
|
721
484
|
validate: validateProjectName
|
|
722
485
|
});
|
|
723
|
-
if (isCancel(projectName)) return onCancel();
|
|
486
|
+
if (isCancel(projectName)) return onCancel$1();
|
|
724
487
|
const packageManager = await select({
|
|
725
488
|
message: "Package manager",
|
|
726
489
|
initialValue: "pnpm",
|
|
@@ -747,37 +510,37 @@ async function runInit({ nameArg }) {
|
|
|
747
510
|
}
|
|
748
511
|
]
|
|
749
512
|
});
|
|
750
|
-
if (isCancel(packageManager)) return onCancel();
|
|
513
|
+
if (isCancel(packageManager)) return onCancel$1();
|
|
751
514
|
const pnpmWorkspace = packageManager === "pnpm" ? await confirm({
|
|
752
515
|
message: "pnpm workspace mode (monorepo skeleton)?",
|
|
753
516
|
initialValue: false
|
|
754
517
|
}) : false;
|
|
755
|
-
if (isCancel(pnpmWorkspace)) return onCancel();
|
|
518
|
+
if (isCancel(pnpmWorkspace)) return onCancel$1();
|
|
756
519
|
const useOxlint = await confirm({
|
|
757
520
|
message: "Enable oxlint (@kingsword/lint-config preset)?",
|
|
758
521
|
initialValue: true
|
|
759
522
|
});
|
|
760
|
-
if (isCancel(useOxlint)) return onCancel();
|
|
523
|
+
if (isCancel(useOxlint)) return onCancel$1();
|
|
761
524
|
const useOxfmt = await confirm({
|
|
762
525
|
message: "Enable oxfmt (code formatting)?",
|
|
763
526
|
initialValue: true
|
|
764
527
|
});
|
|
765
|
-
if (isCancel(useOxfmt)) return onCancel();
|
|
528
|
+
if (isCancel(useOxfmt)) return onCancel$1();
|
|
766
529
|
const useVitest = await confirm({
|
|
767
530
|
message: "Add Vitest?",
|
|
768
531
|
initialValue: false
|
|
769
532
|
});
|
|
770
|
-
if (isCancel(useVitest)) return onCancel();
|
|
533
|
+
if (isCancel(useVitest)) return onCancel$1();
|
|
771
534
|
const useTsdown = await confirm({
|
|
772
535
|
message: "Add tsdown build?",
|
|
773
536
|
initialValue: true
|
|
774
537
|
});
|
|
775
|
-
if (isCancel(useTsdown)) return onCancel();
|
|
538
|
+
if (isCancel(useTsdown)) return onCancel$1();
|
|
776
539
|
const initGit = await confirm({
|
|
777
540
|
message: "Initialize a git repository?",
|
|
778
541
|
initialValue: true
|
|
779
542
|
});
|
|
780
|
-
if (isCancel(initGit)) return onCancel();
|
|
543
|
+
if (isCancel(initGit)) return onCancel$1();
|
|
781
544
|
const githubActions = await select({
|
|
782
545
|
message: "GitHub Actions workflows",
|
|
783
546
|
initialValue: "ci",
|
|
@@ -796,7 +559,7 @@ async function runInit({ nameArg }) {
|
|
|
796
559
|
}
|
|
797
560
|
]
|
|
798
561
|
});
|
|
799
|
-
if (isCancel(githubActions)) return onCancel();
|
|
562
|
+
if (isCancel(githubActions)) return onCancel$1();
|
|
800
563
|
const releaseMode = githubActions === "ci+release" ? await select({
|
|
801
564
|
message: "Release workflows",
|
|
802
565
|
initialValue: "tag",
|
|
@@ -815,17 +578,17 @@ async function runInit({ nameArg }) {
|
|
|
815
578
|
}
|
|
816
579
|
]
|
|
817
580
|
}) : void 0;
|
|
818
|
-
if (isCancel(releaseMode)) return onCancel();
|
|
581
|
+
if (isCancel(releaseMode)) return onCancel$1();
|
|
819
582
|
const addDependabot = initGit && githubActions !== "none" ? await confirm({
|
|
820
583
|
message: "Add Dependabot config (.github/dependabot.yml)?",
|
|
821
584
|
initialValue: true
|
|
822
585
|
}) : false;
|
|
823
|
-
if (isCancel(addDependabot)) return onCancel();
|
|
586
|
+
if (isCancel(addDependabot)) return onCancel$1();
|
|
824
587
|
const trustedPublishing = githubActions === "ci+release" && packageManager !== "deno" ? await confirm({
|
|
825
588
|
message: "Release: npm trusted publishing (OIDC)?",
|
|
826
589
|
initialValue: true
|
|
827
590
|
}) : void 0;
|
|
828
|
-
if (isCancel(trustedPublishing)) return onCancel();
|
|
591
|
+
if (isCancel(trustedPublishing)) return onCancel$1();
|
|
829
592
|
const rootDir = path.resolve(process.cwd(), projectName);
|
|
830
593
|
if (await pathExists(rootDir)) {
|
|
831
594
|
cancel(`Directory already exists: ${rootDir}`);
|
|
@@ -833,6 +596,10 @@ async function runInit({ nameArg }) {
|
|
|
833
596
|
return;
|
|
834
597
|
}
|
|
835
598
|
const pkgDir = pnpmWorkspace ? path.join(rootDir, "packages", projectName) : rootDir;
|
|
599
|
+
const toolingDir = pnpmWorkspace ? rootDir : pkgDir;
|
|
600
|
+
const packageUseOxlint = pnpmWorkspace ? false : useOxlint;
|
|
601
|
+
const packageUseOxfmt = pnpmWorkspace ? false : useOxfmt;
|
|
602
|
+
const packageIncludeTypecheckWithoutOxlint = !(pnpmWorkspace && useOxlint);
|
|
836
603
|
const pmVersion = await detectPackageManagerVersion(packageManager);
|
|
837
604
|
const packageManagerField = pmVersion ? `${packageManager}@${pmVersion}` : `${packageManager}@latest`;
|
|
838
605
|
await mkdir(path.join(pkgDir, "src"), { recursive: true });
|
|
@@ -847,11 +614,18 @@ async function runInit({ nameArg }) {
|
|
|
847
614
|
" - \"packages/*\"",
|
|
848
615
|
""
|
|
849
616
|
].join("\n"));
|
|
850
|
-
await writeText(path.join(rootDir, "package.json"),
|
|
617
|
+
await writeText(path.join(rootDir, "package.json"), workspaceRootPackageJsonTemplate({
|
|
851
618
|
name: projectName,
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
619
|
+
packageManager: packageManagerField,
|
|
620
|
+
useOxlint,
|
|
621
|
+
oxlintVersion: "latest",
|
|
622
|
+
oxlintTsgolintVersion: "latest",
|
|
623
|
+
kingswordLintConfigVersion: "latest",
|
|
624
|
+
useOxfmt,
|
|
625
|
+
oxfmtVersion: "latest",
|
|
626
|
+
useVitest,
|
|
627
|
+
useTsdown
|
|
628
|
+
}));
|
|
855
629
|
}
|
|
856
630
|
await Promise.all([
|
|
857
631
|
writeText(path.join(pkgDir, "README.md"), readmeTemplate(projectName)),
|
|
@@ -861,11 +635,12 @@ async function runInit({ nameArg }) {
|
|
|
861
635
|
name: projectName,
|
|
862
636
|
packageManager: packageManagerField,
|
|
863
637
|
typescriptVersion: "latest",
|
|
864
|
-
useOxlint,
|
|
638
|
+
useOxlint: packageUseOxlint,
|
|
639
|
+
includeTypecheckWithoutOxlint: packageIncludeTypecheckWithoutOxlint,
|
|
865
640
|
oxlintVersion: "latest",
|
|
866
641
|
oxlintTsgolintVersion: "latest",
|
|
867
642
|
kingswordLintConfigVersion: "latest",
|
|
868
|
-
useOxfmt,
|
|
643
|
+
useOxfmt: packageUseOxfmt,
|
|
869
644
|
oxfmtVersion: "latest",
|
|
870
645
|
useVitest,
|
|
871
646
|
vitestVersion: "latest",
|
|
@@ -873,71 +648,423 @@ async function runInit({ nameArg }) {
|
|
|
873
648
|
tsdownVersion: "latest"
|
|
874
649
|
}))
|
|
875
650
|
]);
|
|
876
|
-
if (useOxlint) await writeText(path.join(
|
|
877
|
-
if (useOxfmt) await writeText(path.join(
|
|
651
|
+
if (useOxlint) await writeText(path.join(toolingDir, "oxlint.config.ts"), oxlintConfigTemplate({ useVitest }));
|
|
652
|
+
if (useOxfmt) await writeText(path.join(toolingDir, ".oxfmtrc.json"), oxfmtConfigTemplate());
|
|
878
653
|
if (useVitest) await writeText(path.join(pkgDir, "src/index.test.ts"), srcVitestTemplate());
|
|
879
654
|
if (useTsdown) await writeText(path.join(pkgDir, "tsdown.config.ts"), tsdownConfigTemplate());
|
|
880
655
|
if (packageManager === "deno") await writeText(path.join(rootDir, "deno.json"), JSON.stringify({ nodeModulesDir: "auto" }, null, 2) + "\n");
|
|
881
656
|
if (githubActions !== "none") {
|
|
882
|
-
const workingDirectory =
|
|
883
|
-
const lintCommand = useOxlint && packageManager !== "deno" ? pmRun(packageManager, "lint") : void 0;
|
|
884
|
-
const formatCheckCommand = useOxfmt && packageManager !== "deno" ? pmRun(packageManager, "format:check") : void 0;
|
|
885
|
-
const testCommand = useVitest && packageManager !== "deno" ? pmRun(packageManager, "test") : void 0;
|
|
657
|
+
const workingDirectory = ".";
|
|
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;
|
|
886
661
|
await writeText(path.join(rootDir, ".github/workflows/ci.yml"), githubCliCiWorkflowTemplate({
|
|
887
662
|
packageManager,
|
|
888
|
-
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,
|
|
889
921
|
workingDirectory,
|
|
890
|
-
runLint
|
|
891
|
-
runFormatCheck
|
|
892
|
-
runTests
|
|
922
|
+
runLint,
|
|
923
|
+
runFormatCheck,
|
|
924
|
+
runTests,
|
|
893
925
|
lintCommand,
|
|
894
926
|
formatCheckCommand,
|
|
895
927
|
testCommand
|
|
896
928
|
}));
|
|
897
|
-
if (
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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;
|
|
901
947
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
nodeVersion: 22,
|
|
907
|
-
workingDirectory,
|
|
908
|
-
trustedPublishing
|
|
909
|
-
}));
|
|
948
|
+
}
|
|
949
|
+
var CancelledError$2 = class extends Error {
|
|
950
|
+
constructor() {
|
|
951
|
+
super("Cancelled");
|
|
910
952
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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
|
+
}
|
|
918
980
|
}
|
|
919
|
-
|
|
920
|
-
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];
|
|
921
982
|
}
|
|
922
|
-
function
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
if (
|
|
928
|
-
|
|
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 ".";
|
|
929
990
|
}
|
|
930
|
-
function
|
|
931
|
-
|
|
932
|
-
|
|
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);
|
|
933
1003
|
}
|
|
934
|
-
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) {
|
|
935
1062
|
switch (pm) {
|
|
936
|
-
case "npm": return
|
|
937
|
-
case "pnpm": return
|
|
938
|
-
case "yarn": return
|
|
939
|
-
case "bun": return
|
|
940
|
-
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;
|
|
941
1068
|
}
|
|
942
1069
|
}
|
|
943
1070
|
|
|
@@ -1190,7 +1317,9 @@ function abort$1(opts = {}) {
|
|
|
1190
1317
|
//#region src/commands/oxfmt.ts
|
|
1191
1318
|
const OXFMT_SCRIPTS = {
|
|
1192
1319
|
format: "oxfmt",
|
|
1193
|
-
"format:check": "oxfmt --check"
|
|
1320
|
+
"format:check": "oxfmt --check"
|
|
1321
|
+
};
|
|
1322
|
+
const OXFMT_LEGACY_SCRIPTS = {
|
|
1194
1323
|
fmt: "oxfmt",
|
|
1195
1324
|
"fmt:check": "oxfmt --check"
|
|
1196
1325
|
};
|
|
@@ -1245,6 +1374,7 @@ async function runOxfmt({ yes = false } = {}) {
|
|
|
1245
1374
|
rootDir
|
|
1246
1375
|
});
|
|
1247
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";
|
|
1248
1378
|
const depSummary = stats.addedOxfmtDependency ? "added devDependency: oxfmt" : "devDependency oxfmt already present";
|
|
1249
1379
|
const removedDepsSummary = stats.removedDependencies.length > 0 ? `removed prettier deps: ${stats.removedDependencies.join(", ")}` : "no prettier deps removed";
|
|
1250
1380
|
const removedPackageJsonPrettierSummary = stats.removedPackageJsonPrettierConfig ? "removed package.json#prettier" : "no package.json#prettier removed";
|
|
@@ -1254,6 +1384,7 @@ async function runOxfmt({ yes = false } = {}) {
|
|
|
1254
1384
|
outro([
|
|
1255
1385
|
"Done. Applied oxfmt migration.",
|
|
1256
1386
|
`- ${scriptSummary}`,
|
|
1387
|
+
`- ${legacyScriptSummary}`,
|
|
1257
1388
|
`- ${depSummary}`,
|
|
1258
1389
|
`- ${removedDepsSummary}`,
|
|
1259
1390
|
`- ${removedPackageJsonPrettierSummary}`,
|
|
@@ -1286,6 +1417,12 @@ async function migrateToOxfmt(opts) {
|
|
|
1286
1417
|
scripts[name] = command;
|
|
1287
1418
|
scriptsUpdated.push(name);
|
|
1288
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
|
+
}
|
|
1289
1426
|
pkg.scripts = scripts;
|
|
1290
1427
|
const devDependencies = { ...pkg.devDependencies };
|
|
1291
1428
|
let addedOxfmtDependency = false;
|
|
@@ -1323,6 +1460,7 @@ async function migrateToOxfmt(opts) {
|
|
|
1323
1460
|
return {
|
|
1324
1461
|
scriptsUpdated,
|
|
1325
1462
|
scriptsKept,
|
|
1463
|
+
removedLegacyScripts,
|
|
1326
1464
|
addedOxfmtDependency,
|
|
1327
1465
|
removedPackageJsonPrettierConfig,
|
|
1328
1466
|
removedDependencies,
|
|
@@ -1454,4 +1592,4 @@ function abort(opts = {}) {
|
|
|
1454
1592
|
}
|
|
1455
1593
|
|
|
1456
1594
|
//#endregion
|
|
1457
|
-
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": {
|