frontpl 0.5.0 → 0.6.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 CHANGED
@@ -80,6 +80,52 @@ What it does:
80
80
 
81
81
  Use `--yes` (or `-y`) to skip confirmations and use defaults inferred from root scripts.
82
82
 
83
+ ### `frontpl pkg`
84
+
85
+ Normalize current directory `package.json` for npm publishing (requires `github.com` git remote).
86
+
87
+ What it does:
88
+
89
+ - Requires current directory to contain `package.json`
90
+ - Requires git repository with `remote.origin.url` on `github.com`
91
+ - Updates publish metadata:
92
+ - `homepage`
93
+ - `bugs.url`
94
+ - `repository` (`type`, `url`, and `directory` when run in monorepo subfolder)
95
+ - Applies publish defaults when missing:
96
+ - `private: false`, `version: "0.0.0"`
97
+ - `license`:
98
+ - interactive select on each run (`MIT` / `Apache-2.0`)
99
+ - with `--yes`, keeps existing license; defaults to `MIT` only when missing
100
+ - option keys aligned with GitHub Licenses API: `mit` / `apache-2.0`
101
+ - `type: "module"`, `files: ["dist"]`
102
+ - `main`, `types`, and `exports` pointing to `dist/index`
103
+ - `publishConfig.access: "public"`
104
+ - `engines.node: ">=22.0.0"`
105
+ - `scripts.prepublishOnly` from existing `scripts.build`
106
+
107
+ Use `--yes` (or `-y`) to skip confirmation.
108
+
109
+ ### `frontpl bump [target]`
110
+
111
+ Update current `package.json#version` without opening an editor.
112
+
113
+ Interactive mode (`frontpl bump`):
114
+
115
+ - Shows concrete candidate versions for `patch` / `minor` / `major`
116
+ - e.g. current `1.2.3` -> `1.2.4` / `1.3.0` / `2.0.0`
117
+ - `custom`: set explicit version text
118
+
119
+ Direct mode (`frontpl bump <target>`):
120
+
121
+ - `frontpl bump patch`
122
+ - `frontpl bump minor`
123
+ - `frontpl bump major`
124
+ - `frontpl bump 1.8.0`
125
+
126
+ When using `minor`/`major`, trailing segments are reset to `0`.
127
+ Use `--dry-run` to preview from/to version without writing `package.json`.
128
+
83
129
  ### `frontpl ci`
84
130
 
85
131
  Add or update CI/Release workflows for an existing project (run it in your repo root).
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as runInit, i as runAdd, n as runOxlint, r as runCi, t as runOxfmt } from "./oxfmt-DQBJ0JMs.mjs";
2
+ import { a as runBump, i as runCi, n as runOxfmt, o as runAdd, r as runOxlint, s as runInit, t as runPackage } from "./package-D5wcPeH8.mjs";
3
3
  import bin from "tiny-bin";
4
4
 
5
5
  //#region src/cli.ts
@@ -19,6 +19,13 @@ async function main() {
19
19
  await runOxlint({ yes: options.yes === true });
20
20
  }).command("oxfmt", "Add/migrate formatter to oxfmt in current project").option("--yes, -y", "Skip confirmations and use defaults").action(async (options) => {
21
21
  await runOxfmt({ yes: options.yes === true });
22
+ }).command("bump", "Bump package.json version").argument("[target]", "patch | minor | major | <version>").option("--dry-run", "Show the next version without writing package.json").action(async (options, args) => {
23
+ await runBump({
24
+ targetArg: args[0],
25
+ dryRun: options.dryRun === true
26
+ });
27
+ }).command("pkg", "Normalize package.json for npm publishing using GitHub remote").option("--yes, -y", "Skip confirmations and use defaults").action(async (options) => {
28
+ await runPackage({ yes: options.yes === true });
22
29
  }).run();
23
30
  }
24
31
  main();
package/dist/index.d.mts CHANGED
@@ -1,12 +1,22 @@
1
1
  //#region src/commands/add.d.ts
2
- type CommandOptions$2 = {
2
+ type CommandOptions$4 = {
3
3
  nameArg?: string;
4
4
  yes?: boolean;
5
5
  };
6
6
  declare function runAdd({
7
7
  nameArg,
8
8
  yes
9
- }?: CommandOptions$2): Promise<void>;
9
+ }?: CommandOptions$4): Promise<void>;
10
+ //#endregion
11
+ //#region src/commands/bump.d.ts
12
+ type CommandOptions$3 = {
13
+ targetArg?: string;
14
+ dryRun?: boolean;
15
+ };
16
+ declare function runBump({
17
+ targetArg,
18
+ dryRun
19
+ }?: CommandOptions$3): Promise<void>;
10
20
  //#endregion
11
21
  //#region src/commands/ci.d.ts
12
22
  declare function runCi(): Promise<undefined>;
@@ -20,19 +30,27 @@ declare function runInit({
20
30
  declare function validateProjectName(value: string | undefined): "Project name is required" | "Project name is too long" | "Project name cannot start with '.'" | "Project name cannot start with '_'" | "Use letters, numbers, '.', '_' or '-'" | undefined;
21
31
  //#endregion
22
32
  //#region src/commands/oxlint.d.ts
23
- type CommandOptions$1 = {
33
+ type CommandOptions$2 = {
24
34
  yes?: boolean;
25
35
  };
26
36
  declare function runOxlint({
27
37
  yes
28
- }?: CommandOptions$1): Promise<void>;
38
+ }?: CommandOptions$2): Promise<void>;
29
39
  //#endregion
30
40
  //#region src/commands/oxfmt.d.ts
31
- type CommandOptions = {
41
+ type CommandOptions$1 = {
32
42
  yes?: boolean;
33
43
  };
34
44
  declare function runOxfmt({
35
45
  yes
46
+ }?: CommandOptions$1): Promise<void>;
47
+ //#endregion
48
+ //#region src/commands/package.d.ts
49
+ type CommandOptions = {
50
+ yes?: boolean;
51
+ };
52
+ declare function runPackage({
53
+ yes
36
54
  }?: CommandOptions): Promise<void>;
37
55
  //#endregion
38
56
  //#region src/lib/templates.d.ts
@@ -88,4 +106,4 @@ declare function githubDependabotTemplate(opts: {
88
106
  workingDirectory: string;
89
107
  }): string;
90
108
  //#endregion
91
- export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runAdd, runCi, runInit, runOxfmt, runOxlint, validateProjectName, workspaceRootPackageJsonTemplate };
109
+ export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runAdd, runBump, runCi, runInit, runOxfmt, runOxlint, runPackage, validateProjectName, workspaceRootPackageJsonTemplate };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
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";
1
+ import { a as runBump, c as validateProjectName, d as oxlintConfigTemplate, f as packageJsonTemplate, i as runCi, l as githubCliCiWorkflowTemplate, n as runOxfmt, o as runAdd, p as workspaceRootPackageJsonTemplate, r as runOxlint, s as runInit, t as runPackage, u as githubDependabotTemplate } from "./package-D5wcPeH8.mjs";
2
2
 
3
- export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runAdd, runCi, runInit, runOxfmt, runOxlint, validateProjectName, workspaceRootPackageJsonTemplate };
3
+ export { githubCliCiWorkflowTemplate, githubDependabotTemplate, oxlintConfigTemplate, packageJsonTemplate, runAdd, runBump, runCi, runInit, runOxfmt, runOxlint, runPackage, validateProjectName, workspaceRootPackageJsonTemplate };
@@ -2,7 +2,7 @@ import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "
2
2
  import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
- import { spawn } from "node:child_process";
5
+ import { spawn, spawnSync } from "node:child_process";
6
6
  import os from "node:os";
7
7
 
8
8
  //#region src/lib/versions.ts
@@ -427,7 +427,7 @@ async function detectPackageManager(rootDir) {
427
427
  const pmField = (await readPackageJson(path.join(rootDir, "package.json")))?.packageManager;
428
428
  if (pmField) {
429
429
  const pm = pmField.split("@")[0] ?? "";
430
- if (isPackageManager(pm)) return pm;
430
+ if (isPackageManager$1(pm)) return pm;
431
431
  }
432
432
  const candidates = [];
433
433
  if (await pathExists(path.join(rootDir, "pnpm-lock.yaml"))) candidates.push("pnpm");
@@ -438,7 +438,7 @@ async function detectPackageManager(rootDir) {
438
438
  if (await pathExists(path.join(rootDir, "deno.json")) || await pathExists(path.join(rootDir, "deno.jsonc"))) candidates.push("deno");
439
439
  return candidates.length === 1 ? candidates[0] : void 0;
440
440
  }
441
- function isPackageManager(value) {
441
+ function isPackageManager$1(value) {
442
442
  return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun" || value === "deno";
443
443
  }
444
444
 
@@ -467,7 +467,7 @@ function resolveCommand(command) {
467
467
 
468
468
  //#endregion
469
469
  //#region src/commands/init.ts
470
- function pmRun$1(pm, script) {
470
+ function pmRun$2(pm, script) {
471
471
  switch (pm) {
472
472
  case "npm": return `npm run ${script}`;
473
473
  case "pnpm": return `pnpm run ${script}`;
@@ -483,7 +483,7 @@ async function runInit({ nameArg }) {
483
483
  initialValue: nameArg ?? "my-frontend",
484
484
  validate: validateProjectName
485
485
  });
486
- if (isCancel(projectName)) return onCancel$1();
486
+ if (isCancel(projectName)) return onCancel$3();
487
487
  const packageManager = await select({
488
488
  message: "Package manager",
489
489
  initialValue: "pnpm",
@@ -510,37 +510,37 @@ async function runInit({ nameArg }) {
510
510
  }
511
511
  ]
512
512
  });
513
- if (isCancel(packageManager)) return onCancel$1();
513
+ if (isCancel(packageManager)) return onCancel$3();
514
514
  const pnpmWorkspace = packageManager === "pnpm" ? await confirm({
515
515
  message: "pnpm workspace mode (monorepo skeleton)?",
516
516
  initialValue: false
517
517
  }) : false;
518
- if (isCancel(pnpmWorkspace)) return onCancel$1();
518
+ if (isCancel(pnpmWorkspace)) return onCancel$3();
519
519
  const useOxlint = await confirm({
520
520
  message: "Enable oxlint (@kingsword/lint-config preset)?",
521
521
  initialValue: true
522
522
  });
523
- if (isCancel(useOxlint)) return onCancel$1();
523
+ if (isCancel(useOxlint)) return onCancel$3();
524
524
  const useOxfmt = await confirm({
525
525
  message: "Enable oxfmt (code formatting)?",
526
526
  initialValue: true
527
527
  });
528
- if (isCancel(useOxfmt)) return onCancel$1();
528
+ if (isCancel(useOxfmt)) return onCancel$3();
529
529
  const useVitest = await confirm({
530
530
  message: "Add Vitest?",
531
531
  initialValue: false
532
532
  });
533
- if (isCancel(useVitest)) return onCancel$1();
533
+ if (isCancel(useVitest)) return onCancel$3();
534
534
  const useTsdown = await confirm({
535
535
  message: "Add tsdown build?",
536
536
  initialValue: true
537
537
  });
538
- if (isCancel(useTsdown)) return onCancel$1();
538
+ if (isCancel(useTsdown)) return onCancel$3();
539
539
  const initGit = await confirm({
540
540
  message: "Initialize a git repository?",
541
541
  initialValue: true
542
542
  });
543
- if (isCancel(initGit)) return onCancel$1();
543
+ if (isCancel(initGit)) return onCancel$3();
544
544
  const githubActions = await select({
545
545
  message: "GitHub Actions workflows",
546
546
  initialValue: "ci",
@@ -559,7 +559,7 @@ async function runInit({ nameArg }) {
559
559
  }
560
560
  ]
561
561
  });
562
- if (isCancel(githubActions)) return onCancel$1();
562
+ if (isCancel(githubActions)) return onCancel$3();
563
563
  const releaseMode = githubActions === "ci+release" ? await select({
564
564
  message: "Release workflows",
565
565
  initialValue: "tag",
@@ -578,17 +578,17 @@ async function runInit({ nameArg }) {
578
578
  }
579
579
  ]
580
580
  }) : void 0;
581
- if (isCancel(releaseMode)) return onCancel$1();
581
+ if (isCancel(releaseMode)) return onCancel$3();
582
582
  const addDependabot = initGit && githubActions !== "none" ? await confirm({
583
583
  message: "Add Dependabot config (.github/dependabot.yml)?",
584
584
  initialValue: true
585
585
  }) : false;
586
- if (isCancel(addDependabot)) return onCancel$1();
586
+ if (isCancel(addDependabot)) return onCancel$3();
587
587
  const trustedPublishing = githubActions === "ci+release" && packageManager !== "deno" ? await confirm({
588
588
  message: "Release: npm trusted publishing (OIDC)?",
589
589
  initialValue: true
590
590
  }) : void 0;
591
- if (isCancel(trustedPublishing)) return onCancel$1();
591
+ if (isCancel(trustedPublishing)) return onCancel$3();
592
592
  const rootDir = path.resolve(process.cwd(), projectName);
593
593
  if (await pathExists(rootDir)) {
594
594
  cancel(`Directory already exists: ${rootDir}`);
@@ -655,9 +655,9 @@ async function runInit({ nameArg }) {
655
655
  if (packageManager === "deno") await writeText(path.join(rootDir, "deno.json"), JSON.stringify({ nodeModulesDir: "auto" }, null, 2) + "\n");
656
656
  if (githubActions !== "none") {
657
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;
658
+ const lintCommand = useOxlint && packageManager !== "deno" ? pmRun$2(packageManager, "lint") : void 0;
659
+ const formatCheckCommand = useOxfmt && packageManager !== "deno" ? pmRun$2(packageManager, "format:check") : void 0;
660
+ const testCommand = useVitest && packageManager !== "deno" ? pmRun$2(packageManager, "test") : void 0;
661
661
  await writeText(path.join(rootDir, ".github/workflows/ci.yml"), githubCliCiWorkflowTemplate({
662
662
  packageManager,
663
663
  nodeVersion: 22,
@@ -702,7 +702,7 @@ function validateProjectName(value) {
702
702
  if (name.startsWith("_")) return "Project name cannot start with '_'";
703
703
  if (!/^[A-Za-z0-9._-]+$/.test(name)) return "Use letters, numbers, '.', '_' or '-'";
704
704
  }
705
- function onCancel$1() {
705
+ function onCancel$3() {
706
706
  cancel("Cancelled");
707
707
  process.exitCode = 0;
708
708
  }
@@ -740,7 +740,7 @@ async function runAdd({ nameArg, yes = false } = {}) {
740
740
  validate: validateProjectName
741
741
  });
742
742
  if (!packageName) return;
743
- if (isCancel(packageName)) return onCancel();
743
+ if (isCancel(packageName)) return onCancel$2();
744
744
  const packageDir = path.join(rootDir, "packages", packageName);
745
745
  if (await pathExists(packageDir)) {
746
746
  cancel(`Package already exists: ${packageDir}`);
@@ -753,12 +753,12 @@ async function runAdd({ nameArg, yes = false } = {}) {
753
753
  message: "Add Vitest?",
754
754
  initialValue: useVitestDefault
755
755
  });
756
- if (isCancel(useVitest)) return onCancel();
756
+ if (isCancel(useVitest)) return onCancel$2();
757
757
  const useTsdown = yes ? useTsdownDefault : await confirm({
758
758
  message: "Add tsdown build?",
759
759
  initialValue: useTsdownDefault
760
760
  });
761
- if (isCancel(useTsdown)) return onCancel();
761
+ if (isCancel(useTsdown)) return onCancel$2();
762
762
  const resolvedPackageManagerField = await resolvePnpmPackageManagerField(packageManagerField);
763
763
  const rootHasOxlint = Boolean(rootPkg?.devDependencies?.oxlint) || Boolean(rootPkg?.devDependencies?.["oxlint-tsgolint"]) || await pathExists(path.join(rootDir, "oxlint.config.ts"));
764
764
  const typescriptVersion = rootPkg?.devDependencies?.typescript ?? "latest";
@@ -806,7 +806,157 @@ function resolvePackageNameFromArg(nameArg) {
806
806
  }
807
807
  return value;
808
808
  }
809
- function onCancel() {
809
+ function onCancel$2() {
810
+ cancel("Cancelled");
811
+ process.exitCode = 0;
812
+ }
813
+
814
+ //#endregion
815
+ //#region src/commands/bump.ts
816
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
817
+ async function runBump({ targetArg, dryRun = false } = {}) {
818
+ intro("frontpl (bump)");
819
+ const cwd = process.cwd();
820
+ const packageJsonPath = path.join(cwd, "package.json");
821
+ const pkg = await readPackageJson(packageJsonPath);
822
+ if (!pkg) {
823
+ cancel("Missing package.json. Run this command in a Node package directory.");
824
+ process.exitCode = 1;
825
+ return;
826
+ }
827
+ const currentVersion = pkg.version?.trim();
828
+ const resolved = targetArg ? resolveVersionFromTargetArg({
829
+ currentVersion,
830
+ targetArg
831
+ }) : await resolveVersionInteractively({ currentVersion });
832
+ if (isCancel(resolved)) return onCancel$1();
833
+ if (!resolved.ok) {
834
+ cancel(resolved.reason);
835
+ process.exitCode = 1;
836
+ return;
837
+ }
838
+ if (!dryRun) {
839
+ pkg.version = resolved.nextVersion;
840
+ await writePackageJson(packageJsonPath, pkg);
841
+ }
842
+ outro([
843
+ dryRun ? "Dry run. Calculated package.json version update." : "Done. Updated package.json version.",
844
+ `- from: ${currentVersion ?? "(missing)"}`,
845
+ `- to: ${resolved.nextVersion}`,
846
+ `- mode: ${resolved.mode}`
847
+ ].join("\n"));
848
+ }
849
+ function resolveVersionFromTargetArg(opts) {
850
+ const target = opts.targetArg.trim();
851
+ if (!target) return {
852
+ ok: false,
853
+ reason: "Target is required (patch | minor | major | <version>)"
854
+ };
855
+ if (target === "patch" || target === "minor" || target === "major") {
856
+ const nextVersion = bumpSemver(opts.currentVersion, target);
857
+ if (!nextVersion) return {
858
+ ok: false,
859
+ reason: "Current version is missing or invalid for bump mode. Set an explicit version (e.g. `frontpl bump 1.2.3`)."
860
+ };
861
+ return {
862
+ ok: true,
863
+ mode: target,
864
+ nextVersion
865
+ };
866
+ }
867
+ if (!SEMVER_PATTERN.test(target)) return {
868
+ ok: false,
869
+ reason: "Invalid version. Use semver like 1.2.3 (or prerelease/build variants)."
870
+ };
871
+ return {
872
+ ok: true,
873
+ mode: "set",
874
+ nextVersion: target
875
+ };
876
+ }
877
+ async function resolveVersionInteractively(opts) {
878
+ const currentVersion = opts.currentVersion;
879
+ const patchVersion = bumpSemver(currentVersion, "patch");
880
+ const minorVersion = bumpSemver(currentVersion, "minor");
881
+ const majorVersion = bumpSemver(currentVersion, "major");
882
+ if (!patchVersion || !minorVersion || !majorVersion) {
883
+ const customVersion = await text({
884
+ message: "Current version is missing/invalid. Enter target version",
885
+ initialValue: "0.1.0",
886
+ validate: (value = "") => SEMVER_PATTERN.test(value.trim()) ? void 0 : "Use semver like 1.2.3 (or prerelease/build variants)"
887
+ });
888
+ if (isCancel(customVersion)) return customVersion;
889
+ return {
890
+ ok: true,
891
+ mode: "set",
892
+ nextVersion: String(customVersion).trim()
893
+ };
894
+ }
895
+ const choice = await select({
896
+ message: `Select next version (current: ${currentVersion})`,
897
+ initialValue: "patch",
898
+ options: [
899
+ {
900
+ value: "patch",
901
+ label: patchVersion,
902
+ hint: "patch"
903
+ },
904
+ {
905
+ value: "minor",
906
+ label: minorVersion,
907
+ hint: "minor"
908
+ },
909
+ {
910
+ value: "major",
911
+ label: majorVersion,
912
+ hint: "major"
913
+ },
914
+ {
915
+ value: "custom",
916
+ label: "custom",
917
+ hint: "set explicit version"
918
+ }
919
+ ]
920
+ });
921
+ if (isCancel(choice)) return choice;
922
+ if (choice === "custom") {
923
+ const customVersion = await text({
924
+ message: "Target version",
925
+ initialValue: currentVersion,
926
+ validate: (value = "") => SEMVER_PATTERN.test(value.trim()) ? void 0 : "Use semver like 1.2.3 (or prerelease/build variants)"
927
+ });
928
+ if (isCancel(customVersion)) return customVersion;
929
+ return {
930
+ ok: true,
931
+ mode: "set",
932
+ nextVersion: String(customVersion).trim()
933
+ };
934
+ }
935
+ return {
936
+ ok: true,
937
+ mode: choice,
938
+ nextVersion: choice === "patch" ? patchVersion : choice === "minor" ? minorVersion : majorVersion
939
+ };
940
+ }
941
+ function bumpSemver(version, mode) {
942
+ const parsed = parseSemverCore(version);
943
+ if (!parsed) return;
944
+ const { major, minor, patch } = parsed;
945
+ if (mode === "patch") return `${major}.${minor}.${patch + 1}`;
946
+ if (mode === "minor") return `${major}.${minor + 1}.0`;
947
+ return `${major + 1}.0.0`;
948
+ }
949
+ function parseSemverCore(version) {
950
+ if (!version) return;
951
+ const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
952
+ if (!match) return;
953
+ return {
954
+ major: Number.parseInt(match[1], 10),
955
+ minor: Number.parseInt(match[2], 10),
956
+ patch: Number.parseInt(match[3], 10)
957
+ };
958
+ }
959
+ function onCancel$1() {
810
960
  cancel("Cancelled");
811
961
  process.exitCode = 0;
812
962
  }
@@ -1044,9 +1194,9 @@ async function resolveCiCommands(rootDir, workingDirectory, packageManager) {
1044
1194
  runLint,
1045
1195
  runFormatCheck,
1046
1196
  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
1197
+ lintCommand: runLint && hasLint ? pmRun$1(packageManager, "lint") : runLint ? await promptCommand("Lint command", pmRun$1(packageManager, "lint")) : void 0,
1198
+ formatCheckCommand: runFormatCheck && hasFormatCheck ? pmRun$1(packageManager, "format:check") : runFormatCheck ? await promptCommand("Format check command", pmRun$1(packageManager, "format:check")) : void 0,
1199
+ testCommand: runTests && hasTest ? pmRun$1(packageManager, "test") : runTests ? await promptCommand("Test command", pmRun$1(packageManager, "test")) : void 0
1050
1200
  };
1051
1201
  }
1052
1202
  async function promptCommand(message, initialValue) {
@@ -1058,7 +1208,7 @@ async function promptCommand(message, initialValue) {
1058
1208
  if (isCancel(value)) return abort$2();
1059
1209
  return String(value).trim();
1060
1210
  }
1061
- function pmRun(pm, script) {
1211
+ function pmRun$1(pm, script) {
1062
1212
  switch (pm) {
1063
1213
  case "npm": return `npm run ${script}`;
1064
1214
  case "pnpm": return `pnpm run ${script}`;
@@ -1592,4 +1742,193 @@ function abort(opts = {}) {
1592
1742
  }
1593
1743
 
1594
1744
  //#endregion
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 };
1745
+ //#region src/commands/package.ts
1746
+ async function runPackage({ yes = false } = {}) {
1747
+ intro("frontpl (pkg)");
1748
+ const cwd = process.cwd();
1749
+ const packageJsonPath = path.join(cwd, "package.json");
1750
+ const pkg = await readPackageJson(packageJsonPath);
1751
+ if (!pkg) {
1752
+ cancel("Missing package.json. Run this command in a Node package directory.");
1753
+ process.exitCode = 1;
1754
+ return;
1755
+ }
1756
+ const gitRoot = runGitCapture(cwd, ["rev-parse", "--show-toplevel"]);
1757
+ if (!gitRoot) {
1758
+ cancel("Current directory is not inside a git repository.");
1759
+ process.exitCode = 1;
1760
+ return;
1761
+ }
1762
+ const originUrl = runGitCapture(cwd, [
1763
+ "config",
1764
+ "--get",
1765
+ "remote.origin.url"
1766
+ ]);
1767
+ if (!originUrl) {
1768
+ cancel("Missing git remote `origin`. Set a GitHub remote before normalizing package.json.");
1769
+ process.exitCode = 1;
1770
+ return;
1771
+ }
1772
+ const repository = parseGithubRepository(originUrl);
1773
+ if (!repository) {
1774
+ cancel("Only github.com remotes are supported by `frontpl pkg`.");
1775
+ process.exitCode = 1;
1776
+ return;
1777
+ }
1778
+ const shouldApply = yes || await confirm({
1779
+ message: "Normalize package.json for npm publish defaults?",
1780
+ initialValue: true
1781
+ });
1782
+ if (isCancel(shouldApply)) return onCancel();
1783
+ if (!shouldApply) {
1784
+ cancel("Skipped package.json normalization");
1785
+ process.exitCode = 0;
1786
+ return;
1787
+ }
1788
+ const packageManager = await resolvePackageManager(pkg.packageManager, gitRoot, cwd);
1789
+ const selectedLicense = await resolveLicenseSelection(pkg, yes);
1790
+ if (isCancel(selectedLicense)) return onCancel();
1791
+ const repositoryDirectory = resolveRepositoryDirectory(gitRoot, cwd);
1792
+ normalizePackageJson(pkg, {
1793
+ repository,
1794
+ repositoryDirectory,
1795
+ packageManager,
1796
+ selectedLicense: selectedLicense ?? void 0
1797
+ });
1798
+ await writePackageJson(packageJsonPath, pkg);
1799
+ outro([
1800
+ "Done. Normalized package.json for publishing.",
1801
+ `- repository: ${repository.owner}/${repository.repo}`,
1802
+ `- packageManager: ${packageManager}`,
1803
+ `- repositoryDirectory: ${repositoryDirectory ?? "(root)"}`
1804
+ ].join("\n"));
1805
+ }
1806
+ function normalizePackageJson(pkg, opts) {
1807
+ const { repository, repositoryDirectory, packageManager, selectedLicense } = opts;
1808
+ pkg.private = false;
1809
+ if (!pkg.version) pkg.version = "0.0.0";
1810
+ pkg.license = selectedLicense ?? pkg.license ?? "MIT";
1811
+ if (!pkg.type) pkg.type = "module";
1812
+ if (!pkg.files || pkg.files.length === 0) pkg.files = ["dist"];
1813
+ if (!pkg.main) pkg.main = "./dist/index.mjs";
1814
+ if (!pkg.types) pkg.types = "./dist/index.d.mts";
1815
+ if (!pkg.exports) pkg.exports = { ".": {
1816
+ types: pkg.types,
1817
+ import: pkg.main,
1818
+ require: pkg.main
1819
+ } };
1820
+ const repoUrl = `git+https://github.com/${repository.owner}/${repository.repo}.git`;
1821
+ pkg.homepage = `https://github.com/${repository.owner}/${repository.repo}#readme`;
1822
+ pkg.bugs = { url: `https://github.com/${repository.owner}/${repository.repo}/issues` };
1823
+ pkg.repository = {
1824
+ type: "git",
1825
+ url: repoUrl,
1826
+ ...repositoryDirectory ? { directory: repositoryDirectory } : {}
1827
+ };
1828
+ const publishConfig = pkg.publishConfig && typeof pkg.publishConfig === "object" ? { ...pkg.publishConfig } : {};
1829
+ if (!publishConfig.access) publishConfig.access = "public";
1830
+ pkg.publishConfig = publishConfig;
1831
+ const engines = pkg.engines ? { ...pkg.engines } : {};
1832
+ if (!engines.node) engines.node = ">=22.0.0";
1833
+ pkg.engines = engines;
1834
+ if (pkg.scripts && typeof pkg.scripts.build === "string" && !pkg.scripts.prepublishOnly) pkg.scripts = {
1835
+ ...pkg.scripts,
1836
+ prepublishOnly: pmRun(packageManager, "build")
1837
+ };
1838
+ }
1839
+ async function resolveLicenseSelection(pkg, yes) {
1840
+ if (yes) return pkg.license ? void 0 : "MIT";
1841
+ const currentLicense = pkg.license?.trim();
1842
+ const selectedLicense = await select({
1843
+ message: currentLicense ? `Select license for publish metadata (current: ${currentLicense})` : "package.json missing `license`. Select one",
1844
+ initialValue: currentLicense === "Apache-2.0" ? "Apache-2.0" : "MIT",
1845
+ options: [{
1846
+ value: "MIT",
1847
+ label: "MIT",
1848
+ hint: "GitHub API key: mit"
1849
+ }, {
1850
+ value: "Apache-2.0",
1851
+ label: "Apache-2.0",
1852
+ hint: "GitHub API key: apache-2.0"
1853
+ }]
1854
+ });
1855
+ if (isCancel(selectedLicense)) return selectedLicense;
1856
+ return selectedLicense;
1857
+ }
1858
+ async function resolvePackageManager(packageManagerField, gitRoot, cwd) {
1859
+ const fromField = packageManagerField?.split("@")[0];
1860
+ if (isPackageManager(fromField)) return fromField;
1861
+ const fromGitRoot = await detectPackageManager(gitRoot);
1862
+ if (fromGitRoot) return fromGitRoot;
1863
+ const fromCwd = await detectPackageManager(cwd);
1864
+ if (fromCwd) return fromCwd;
1865
+ return "pnpm";
1866
+ }
1867
+ function isPackageManager(value) {
1868
+ return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun" || value === "deno";
1869
+ }
1870
+ function resolveRepositoryDirectory(gitRoot, cwd) {
1871
+ const relativePath = path.relative(gitRoot, cwd);
1872
+ if (!relativePath || relativePath === ".") return;
1873
+ if (relativePath.startsWith("..")) return;
1874
+ return relativePath.split(path.sep).join("/");
1875
+ }
1876
+ function parseGithubRepository(remoteUrl) {
1877
+ const trimmed = remoteUrl.trim();
1878
+ const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
1879
+ if (sshMatch) return {
1880
+ owner: sshMatch[1],
1881
+ repo: sshMatch[2]
1882
+ };
1883
+ const sshProtocolMatch = trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
1884
+ if (sshProtocolMatch) return {
1885
+ owner: sshProtocolMatch[1],
1886
+ repo: sshProtocolMatch[2]
1887
+ };
1888
+ const gitProtocolMatch = trimmed.match(/^git:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
1889
+ if (gitProtocolMatch) return {
1890
+ owner: gitProtocolMatch[1],
1891
+ repo: gitProtocolMatch[2]
1892
+ };
1893
+ try {
1894
+ const url = new URL(trimmed);
1895
+ if (url.hostname !== "github.com") return;
1896
+ const [owner, repo] = url.pathname.replace(/^\/|\/$/g, "").split("/");
1897
+ if (!owner || !repo) return;
1898
+ return {
1899
+ owner,
1900
+ repo: repo.replace(/\.git$/, "")
1901
+ };
1902
+ } catch {
1903
+ return;
1904
+ }
1905
+ }
1906
+ function runGitCapture(cwd, args) {
1907
+ const result = spawnSync("git", args, {
1908
+ cwd,
1909
+ encoding: "utf8",
1910
+ stdio: [
1911
+ "ignore",
1912
+ "pipe",
1913
+ "ignore"
1914
+ ]
1915
+ });
1916
+ if (result.status !== 0) return;
1917
+ return result.stdout.trim() || void 0;
1918
+ }
1919
+ function pmRun(pm, script) {
1920
+ switch (pm) {
1921
+ case "npm": return `npm run ${script}`;
1922
+ case "pnpm": return `pnpm run ${script}`;
1923
+ case "yarn": return `yarn ${script}`;
1924
+ case "bun": return `bun run ${script}`;
1925
+ case "deno": return script;
1926
+ }
1927
+ }
1928
+ function onCancel() {
1929
+ cancel("Cancelled");
1930
+ process.exitCode = 0;
1931
+ }
1932
+
1933
+ //#endregion
1934
+ export { runBump as a, validateProjectName as c, oxlintConfigTemplate as d, packageJsonTemplate as f, runCi as i, githubCliCiWorkflowTemplate as l, runOxfmt as n, runAdd as o, workspaceRootPackageJsonTemplate as p, runOxlint as r, runInit as s, runPackage as t, githubDependabotTemplate as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontpl",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Interactive CLI to scaffold standardized frontend project templates.",
5
5
  "keywords": [
6
6
  "cli",