frontpl 0.5.0 → 0.6.1

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).
@@ -131,6 +177,10 @@ What it does:
131
177
  - `format:check`: `oxfmt --check`
132
178
  - Ensures `devDependencies.oxfmt` exists (defaults to `latest` when missing)
133
179
  - Creates or updates `.oxfmtrc.json`
180
+ - Rebuild mode writes baseline formatter options:
181
+ - `$schema: "./node_modules/oxfmt/configuration_schema.json"`
182
+ - `useTabs: false`, `indentWidth: 2`, `lineWidth: 100`
183
+ - `trailingComma: "all"`, `semi: true`, `singleQuote: false`, `arrowParens: "always"`
134
184
  - Optionally removes `prettier` / `prettier-plugin-*` / `@prettier/plugin-*` dependencies, `package.json#prettier`, and Prettier config files (`.prettierrc*`, `prettier.config.*`)
135
185
  - Optionally installs dependencies with detected package manager
136
186
 
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-BtUDK9bn.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-BtUDK9bn.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
@@ -136,7 +136,16 @@ function oxlintConfigTemplate({ useVitest }) {
136
136
  ].join("\n");
137
137
  }
138
138
  function oxfmtConfigTemplate() {
139
- return JSON.stringify({ $schema: "https://json.schemastore.org/oxfmtrc.json" }, null, 2) + "\n";
139
+ return JSON.stringify({
140
+ $schema: "./node_modules/oxfmt/configuration_schema.json",
141
+ useTabs: false,
142
+ indentWidth: 2,
143
+ lineWidth: 100,
144
+ trailingComma: "all",
145
+ semi: true,
146
+ singleQuote: false,
147
+ arrowParens: "always"
148
+ }, null, 2) + "\n";
140
149
  }
141
150
  function tsdownConfigTemplate() {
142
151
  return [
@@ -427,7 +436,7 @@ async function detectPackageManager(rootDir) {
427
436
  const pmField = (await readPackageJson(path.join(rootDir, "package.json")))?.packageManager;
428
437
  if (pmField) {
429
438
  const pm = pmField.split("@")[0] ?? "";
430
- if (isPackageManager(pm)) return pm;
439
+ if (isPackageManager$1(pm)) return pm;
431
440
  }
432
441
  const candidates = [];
433
442
  if (await pathExists(path.join(rootDir, "pnpm-lock.yaml"))) candidates.push("pnpm");
@@ -438,7 +447,7 @@ async function detectPackageManager(rootDir) {
438
447
  if (await pathExists(path.join(rootDir, "deno.json")) || await pathExists(path.join(rootDir, "deno.jsonc"))) candidates.push("deno");
439
448
  return candidates.length === 1 ? candidates[0] : void 0;
440
449
  }
441
- function isPackageManager(value) {
450
+ function isPackageManager$1(value) {
442
451
  return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun" || value === "deno";
443
452
  }
444
453
 
@@ -467,7 +476,7 @@ function resolveCommand(command) {
467
476
 
468
477
  //#endregion
469
478
  //#region src/commands/init.ts
470
- function pmRun$1(pm, script) {
479
+ function pmRun$2(pm, script) {
471
480
  switch (pm) {
472
481
  case "npm": return `npm run ${script}`;
473
482
  case "pnpm": return `pnpm run ${script}`;
@@ -483,7 +492,7 @@ async function runInit({ nameArg }) {
483
492
  initialValue: nameArg ?? "my-frontend",
484
493
  validate: validateProjectName
485
494
  });
486
- if (isCancel(projectName)) return onCancel$1();
495
+ if (isCancel(projectName)) return onCancel$3();
487
496
  const packageManager = await select({
488
497
  message: "Package manager",
489
498
  initialValue: "pnpm",
@@ -510,37 +519,37 @@ async function runInit({ nameArg }) {
510
519
  }
511
520
  ]
512
521
  });
513
- if (isCancel(packageManager)) return onCancel$1();
522
+ if (isCancel(packageManager)) return onCancel$3();
514
523
  const pnpmWorkspace = packageManager === "pnpm" ? await confirm({
515
524
  message: "pnpm workspace mode (monorepo skeleton)?",
516
525
  initialValue: false
517
526
  }) : false;
518
- if (isCancel(pnpmWorkspace)) return onCancel$1();
527
+ if (isCancel(pnpmWorkspace)) return onCancel$3();
519
528
  const useOxlint = await confirm({
520
529
  message: "Enable oxlint (@kingsword/lint-config preset)?",
521
530
  initialValue: true
522
531
  });
523
- if (isCancel(useOxlint)) return onCancel$1();
532
+ if (isCancel(useOxlint)) return onCancel$3();
524
533
  const useOxfmt = await confirm({
525
534
  message: "Enable oxfmt (code formatting)?",
526
535
  initialValue: true
527
536
  });
528
- if (isCancel(useOxfmt)) return onCancel$1();
537
+ if (isCancel(useOxfmt)) return onCancel$3();
529
538
  const useVitest = await confirm({
530
539
  message: "Add Vitest?",
531
540
  initialValue: false
532
541
  });
533
- if (isCancel(useVitest)) return onCancel$1();
542
+ if (isCancel(useVitest)) return onCancel$3();
534
543
  const useTsdown = await confirm({
535
544
  message: "Add tsdown build?",
536
545
  initialValue: true
537
546
  });
538
- if (isCancel(useTsdown)) return onCancel$1();
547
+ if (isCancel(useTsdown)) return onCancel$3();
539
548
  const initGit = await confirm({
540
549
  message: "Initialize a git repository?",
541
550
  initialValue: true
542
551
  });
543
- if (isCancel(initGit)) return onCancel$1();
552
+ if (isCancel(initGit)) return onCancel$3();
544
553
  const githubActions = await select({
545
554
  message: "GitHub Actions workflows",
546
555
  initialValue: "ci",
@@ -559,7 +568,7 @@ async function runInit({ nameArg }) {
559
568
  }
560
569
  ]
561
570
  });
562
- if (isCancel(githubActions)) return onCancel$1();
571
+ if (isCancel(githubActions)) return onCancel$3();
563
572
  const releaseMode = githubActions === "ci+release" ? await select({
564
573
  message: "Release workflows",
565
574
  initialValue: "tag",
@@ -578,17 +587,17 @@ async function runInit({ nameArg }) {
578
587
  }
579
588
  ]
580
589
  }) : void 0;
581
- if (isCancel(releaseMode)) return onCancel$1();
590
+ if (isCancel(releaseMode)) return onCancel$3();
582
591
  const addDependabot = initGit && githubActions !== "none" ? await confirm({
583
592
  message: "Add Dependabot config (.github/dependabot.yml)?",
584
593
  initialValue: true
585
594
  }) : false;
586
- if (isCancel(addDependabot)) return onCancel$1();
595
+ if (isCancel(addDependabot)) return onCancel$3();
587
596
  const trustedPublishing = githubActions === "ci+release" && packageManager !== "deno" ? await confirm({
588
597
  message: "Release: npm trusted publishing (OIDC)?",
589
598
  initialValue: true
590
599
  }) : void 0;
591
- if (isCancel(trustedPublishing)) return onCancel$1();
600
+ if (isCancel(trustedPublishing)) return onCancel$3();
592
601
  const rootDir = path.resolve(process.cwd(), projectName);
593
602
  if (await pathExists(rootDir)) {
594
603
  cancel(`Directory already exists: ${rootDir}`);
@@ -655,9 +664,9 @@ async function runInit({ nameArg }) {
655
664
  if (packageManager === "deno") await writeText(path.join(rootDir, "deno.json"), JSON.stringify({ nodeModulesDir: "auto" }, null, 2) + "\n");
656
665
  if (githubActions !== "none") {
657
666
  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;
667
+ const lintCommand = useOxlint && packageManager !== "deno" ? pmRun$2(packageManager, "lint") : void 0;
668
+ const formatCheckCommand = useOxfmt && packageManager !== "deno" ? pmRun$2(packageManager, "format:check") : void 0;
669
+ const testCommand = useVitest && packageManager !== "deno" ? pmRun$2(packageManager, "test") : void 0;
661
670
  await writeText(path.join(rootDir, ".github/workflows/ci.yml"), githubCliCiWorkflowTemplate({
662
671
  packageManager,
663
672
  nodeVersion: 22,
@@ -702,7 +711,7 @@ function validateProjectName(value) {
702
711
  if (name.startsWith("_")) return "Project name cannot start with '_'";
703
712
  if (!/^[A-Za-z0-9._-]+$/.test(name)) return "Use letters, numbers, '.', '_' or '-'";
704
713
  }
705
- function onCancel$1() {
714
+ function onCancel$3() {
706
715
  cancel("Cancelled");
707
716
  process.exitCode = 0;
708
717
  }
@@ -740,7 +749,7 @@ async function runAdd({ nameArg, yes = false } = {}) {
740
749
  validate: validateProjectName
741
750
  });
742
751
  if (!packageName) return;
743
- if (isCancel(packageName)) return onCancel();
752
+ if (isCancel(packageName)) return onCancel$2();
744
753
  const packageDir = path.join(rootDir, "packages", packageName);
745
754
  if (await pathExists(packageDir)) {
746
755
  cancel(`Package already exists: ${packageDir}`);
@@ -753,12 +762,12 @@ async function runAdd({ nameArg, yes = false } = {}) {
753
762
  message: "Add Vitest?",
754
763
  initialValue: useVitestDefault
755
764
  });
756
- if (isCancel(useVitest)) return onCancel();
765
+ if (isCancel(useVitest)) return onCancel$2();
757
766
  const useTsdown = yes ? useTsdownDefault : await confirm({
758
767
  message: "Add tsdown build?",
759
768
  initialValue: useTsdownDefault
760
769
  });
761
- if (isCancel(useTsdown)) return onCancel();
770
+ if (isCancel(useTsdown)) return onCancel$2();
762
771
  const resolvedPackageManagerField = await resolvePnpmPackageManagerField(packageManagerField);
763
772
  const rootHasOxlint = Boolean(rootPkg?.devDependencies?.oxlint) || Boolean(rootPkg?.devDependencies?.["oxlint-tsgolint"]) || await pathExists(path.join(rootDir, "oxlint.config.ts"));
764
773
  const typescriptVersion = rootPkg?.devDependencies?.typescript ?? "latest";
@@ -806,7 +815,157 @@ function resolvePackageNameFromArg(nameArg) {
806
815
  }
807
816
  return value;
808
817
  }
809
- function onCancel() {
818
+ function onCancel$2() {
819
+ cancel("Cancelled");
820
+ process.exitCode = 0;
821
+ }
822
+
823
+ //#endregion
824
+ //#region src/commands/bump.ts
825
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
826
+ async function runBump({ targetArg, dryRun = false } = {}) {
827
+ intro("frontpl (bump)");
828
+ const cwd = process.cwd();
829
+ const packageJsonPath = path.join(cwd, "package.json");
830
+ const pkg = await readPackageJson(packageJsonPath);
831
+ if (!pkg) {
832
+ cancel("Missing package.json. Run this command in a Node package directory.");
833
+ process.exitCode = 1;
834
+ return;
835
+ }
836
+ const currentVersion = pkg.version?.trim();
837
+ const resolved = targetArg ? resolveVersionFromTargetArg({
838
+ currentVersion,
839
+ targetArg
840
+ }) : await resolveVersionInteractively({ currentVersion });
841
+ if (isCancel(resolved)) return onCancel$1();
842
+ if (!resolved.ok) {
843
+ cancel(resolved.reason);
844
+ process.exitCode = 1;
845
+ return;
846
+ }
847
+ if (!dryRun) {
848
+ pkg.version = resolved.nextVersion;
849
+ await writePackageJson(packageJsonPath, pkg);
850
+ }
851
+ outro([
852
+ dryRun ? "Dry run. Calculated package.json version update." : "Done. Updated package.json version.",
853
+ `- from: ${currentVersion ?? "(missing)"}`,
854
+ `- to: ${resolved.nextVersion}`,
855
+ `- mode: ${resolved.mode}`
856
+ ].join("\n"));
857
+ }
858
+ function resolveVersionFromTargetArg(opts) {
859
+ const target = opts.targetArg.trim();
860
+ if (!target) return {
861
+ ok: false,
862
+ reason: "Target is required (patch | minor | major | <version>)"
863
+ };
864
+ if (target === "patch" || target === "minor" || target === "major") {
865
+ const nextVersion = bumpSemver(opts.currentVersion, target);
866
+ if (!nextVersion) return {
867
+ ok: false,
868
+ reason: "Current version is missing or invalid for bump mode. Set an explicit version (e.g. `frontpl bump 1.2.3`)."
869
+ };
870
+ return {
871
+ ok: true,
872
+ mode: target,
873
+ nextVersion
874
+ };
875
+ }
876
+ if (!SEMVER_PATTERN.test(target)) return {
877
+ ok: false,
878
+ reason: "Invalid version. Use semver like 1.2.3 (or prerelease/build variants)."
879
+ };
880
+ return {
881
+ ok: true,
882
+ mode: "set",
883
+ nextVersion: target
884
+ };
885
+ }
886
+ async function resolveVersionInteractively(opts) {
887
+ const currentVersion = opts.currentVersion;
888
+ const patchVersion = bumpSemver(currentVersion, "patch");
889
+ const minorVersion = bumpSemver(currentVersion, "minor");
890
+ const majorVersion = bumpSemver(currentVersion, "major");
891
+ if (!patchVersion || !minorVersion || !majorVersion) {
892
+ const customVersion = await text({
893
+ message: "Current version is missing/invalid. Enter target version",
894
+ initialValue: "0.1.0",
895
+ validate: (value = "") => SEMVER_PATTERN.test(value.trim()) ? void 0 : "Use semver like 1.2.3 (or prerelease/build variants)"
896
+ });
897
+ if (isCancel(customVersion)) return customVersion;
898
+ return {
899
+ ok: true,
900
+ mode: "set",
901
+ nextVersion: String(customVersion).trim()
902
+ };
903
+ }
904
+ const choice = await select({
905
+ message: `Select next version (current: ${currentVersion})`,
906
+ initialValue: "patch",
907
+ options: [
908
+ {
909
+ value: "patch",
910
+ label: patchVersion,
911
+ hint: "patch"
912
+ },
913
+ {
914
+ value: "minor",
915
+ label: minorVersion,
916
+ hint: "minor"
917
+ },
918
+ {
919
+ value: "major",
920
+ label: majorVersion,
921
+ hint: "major"
922
+ },
923
+ {
924
+ value: "custom",
925
+ label: "custom",
926
+ hint: "set explicit version"
927
+ }
928
+ ]
929
+ });
930
+ if (isCancel(choice)) return choice;
931
+ if (choice === "custom") {
932
+ const customVersion = await text({
933
+ message: "Target version",
934
+ initialValue: currentVersion,
935
+ validate: (value = "") => SEMVER_PATTERN.test(value.trim()) ? void 0 : "Use semver like 1.2.3 (or prerelease/build variants)"
936
+ });
937
+ if (isCancel(customVersion)) return customVersion;
938
+ return {
939
+ ok: true,
940
+ mode: "set",
941
+ nextVersion: String(customVersion).trim()
942
+ };
943
+ }
944
+ return {
945
+ ok: true,
946
+ mode: choice,
947
+ nextVersion: choice === "patch" ? patchVersion : choice === "minor" ? minorVersion : majorVersion
948
+ };
949
+ }
950
+ function bumpSemver(version, mode) {
951
+ const parsed = parseSemverCore(version);
952
+ if (!parsed) return;
953
+ const { major, minor, patch } = parsed;
954
+ if (mode === "patch") return `${major}.${minor}.${patch + 1}`;
955
+ if (mode === "minor") return `${major}.${minor + 1}.0`;
956
+ return `${major + 1}.0.0`;
957
+ }
958
+ function parseSemverCore(version) {
959
+ if (!version) return;
960
+ const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
961
+ if (!match) return;
962
+ return {
963
+ major: Number.parseInt(match[1], 10),
964
+ minor: Number.parseInt(match[2], 10),
965
+ patch: Number.parseInt(match[3], 10)
966
+ };
967
+ }
968
+ function onCancel$1() {
810
969
  cancel("Cancelled");
811
970
  process.exitCode = 0;
812
971
  }
@@ -1044,9 +1203,9 @@ async function resolveCiCommands(rootDir, workingDirectory, packageManager) {
1044
1203
  runLint,
1045
1204
  runFormatCheck,
1046
1205
  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
1206
+ lintCommand: runLint && hasLint ? pmRun$1(packageManager, "lint") : runLint ? await promptCommand("Lint command", pmRun$1(packageManager, "lint")) : void 0,
1207
+ formatCheckCommand: runFormatCheck && hasFormatCheck ? pmRun$1(packageManager, "format:check") : runFormatCheck ? await promptCommand("Format check command", pmRun$1(packageManager, "format:check")) : void 0,
1208
+ testCommand: runTests && hasTest ? pmRun$1(packageManager, "test") : runTests ? await promptCommand("Test command", pmRun$1(packageManager, "test")) : void 0
1050
1209
  };
1051
1210
  }
1052
1211
  async function promptCommand(message, initialValue) {
@@ -1058,7 +1217,7 @@ async function promptCommand(message, initialValue) {
1058
1217
  if (isCancel(value)) return abort$2();
1059
1218
  return String(value).trim();
1060
1219
  }
1061
- function pmRun(pm, script) {
1220
+ function pmRun$1(pm, script) {
1062
1221
  switch (pm) {
1063
1222
  case "npm": return `npm run ${script}`;
1064
1223
  case "pnpm": return `pnpm run ${script}`;
@@ -1592,4 +1751,193 @@ function abort(opts = {}) {
1592
1751
  }
1593
1752
 
1594
1753
  //#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 };
1754
+ //#region src/commands/package.ts
1755
+ async function runPackage({ yes = false } = {}) {
1756
+ intro("frontpl (pkg)");
1757
+ const cwd = process.cwd();
1758
+ const packageJsonPath = path.join(cwd, "package.json");
1759
+ const pkg = await readPackageJson(packageJsonPath);
1760
+ if (!pkg) {
1761
+ cancel("Missing package.json. Run this command in a Node package directory.");
1762
+ process.exitCode = 1;
1763
+ return;
1764
+ }
1765
+ const gitRoot = runGitCapture(cwd, ["rev-parse", "--show-toplevel"]);
1766
+ if (!gitRoot) {
1767
+ cancel("Current directory is not inside a git repository.");
1768
+ process.exitCode = 1;
1769
+ return;
1770
+ }
1771
+ const originUrl = runGitCapture(cwd, [
1772
+ "config",
1773
+ "--get",
1774
+ "remote.origin.url"
1775
+ ]);
1776
+ if (!originUrl) {
1777
+ cancel("Missing git remote `origin`. Set a GitHub remote before normalizing package.json.");
1778
+ process.exitCode = 1;
1779
+ return;
1780
+ }
1781
+ const repository = parseGithubRepository(originUrl);
1782
+ if (!repository) {
1783
+ cancel("Only github.com remotes are supported by `frontpl pkg`.");
1784
+ process.exitCode = 1;
1785
+ return;
1786
+ }
1787
+ const shouldApply = yes || await confirm({
1788
+ message: "Normalize package.json for npm publish defaults?",
1789
+ initialValue: true
1790
+ });
1791
+ if (isCancel(shouldApply)) return onCancel();
1792
+ if (!shouldApply) {
1793
+ cancel("Skipped package.json normalization");
1794
+ process.exitCode = 0;
1795
+ return;
1796
+ }
1797
+ const packageManager = await resolvePackageManager(pkg.packageManager, gitRoot, cwd);
1798
+ const selectedLicense = await resolveLicenseSelection(pkg, yes);
1799
+ if (isCancel(selectedLicense)) return onCancel();
1800
+ const repositoryDirectory = resolveRepositoryDirectory(gitRoot, cwd);
1801
+ normalizePackageJson(pkg, {
1802
+ repository,
1803
+ repositoryDirectory,
1804
+ packageManager,
1805
+ selectedLicense: selectedLicense ?? void 0
1806
+ });
1807
+ await writePackageJson(packageJsonPath, pkg);
1808
+ outro([
1809
+ "Done. Normalized package.json for publishing.",
1810
+ `- repository: ${repository.owner}/${repository.repo}`,
1811
+ `- packageManager: ${packageManager}`,
1812
+ `- repositoryDirectory: ${repositoryDirectory ?? "(root)"}`
1813
+ ].join("\n"));
1814
+ }
1815
+ function normalizePackageJson(pkg, opts) {
1816
+ const { repository, repositoryDirectory, packageManager, selectedLicense } = opts;
1817
+ pkg.private = false;
1818
+ if (!pkg.version) pkg.version = "0.0.0";
1819
+ pkg.license = selectedLicense ?? pkg.license ?? "MIT";
1820
+ if (!pkg.type) pkg.type = "module";
1821
+ if (!pkg.files || pkg.files.length === 0) pkg.files = ["dist"];
1822
+ if (!pkg.main) pkg.main = "./dist/index.mjs";
1823
+ if (!pkg.types) pkg.types = "./dist/index.d.mts";
1824
+ if (!pkg.exports) pkg.exports = { ".": {
1825
+ types: pkg.types,
1826
+ import: pkg.main,
1827
+ require: pkg.main
1828
+ } };
1829
+ const repoUrl = `git+https://github.com/${repository.owner}/${repository.repo}.git`;
1830
+ pkg.homepage = `https://github.com/${repository.owner}/${repository.repo}#readme`;
1831
+ pkg.bugs = { url: `https://github.com/${repository.owner}/${repository.repo}/issues` };
1832
+ pkg.repository = {
1833
+ type: "git",
1834
+ url: repoUrl,
1835
+ ...repositoryDirectory ? { directory: repositoryDirectory } : {}
1836
+ };
1837
+ const publishConfig = pkg.publishConfig && typeof pkg.publishConfig === "object" ? { ...pkg.publishConfig } : {};
1838
+ if (!publishConfig.access) publishConfig.access = "public";
1839
+ pkg.publishConfig = publishConfig;
1840
+ const engines = pkg.engines ? { ...pkg.engines } : {};
1841
+ if (!engines.node) engines.node = ">=22.0.0";
1842
+ pkg.engines = engines;
1843
+ if (pkg.scripts && typeof pkg.scripts.build === "string" && !pkg.scripts.prepublishOnly) pkg.scripts = {
1844
+ ...pkg.scripts,
1845
+ prepublishOnly: pmRun(packageManager, "build")
1846
+ };
1847
+ }
1848
+ async function resolveLicenseSelection(pkg, yes) {
1849
+ if (yes) return pkg.license ? void 0 : "MIT";
1850
+ const currentLicense = pkg.license?.trim();
1851
+ const selectedLicense = await select({
1852
+ message: currentLicense ? `Select license for publish metadata (current: ${currentLicense})` : "package.json missing `license`. Select one",
1853
+ initialValue: currentLicense === "Apache-2.0" ? "Apache-2.0" : "MIT",
1854
+ options: [{
1855
+ value: "MIT",
1856
+ label: "MIT",
1857
+ hint: "GitHub API key: mit"
1858
+ }, {
1859
+ value: "Apache-2.0",
1860
+ label: "Apache-2.0",
1861
+ hint: "GitHub API key: apache-2.0"
1862
+ }]
1863
+ });
1864
+ if (isCancel(selectedLicense)) return selectedLicense;
1865
+ return selectedLicense;
1866
+ }
1867
+ async function resolvePackageManager(packageManagerField, gitRoot, cwd) {
1868
+ const fromField = packageManagerField?.split("@")[0];
1869
+ if (isPackageManager(fromField)) return fromField;
1870
+ const fromGitRoot = await detectPackageManager(gitRoot);
1871
+ if (fromGitRoot) return fromGitRoot;
1872
+ const fromCwd = await detectPackageManager(cwd);
1873
+ if (fromCwd) return fromCwd;
1874
+ return "pnpm";
1875
+ }
1876
+ function isPackageManager(value) {
1877
+ return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun" || value === "deno";
1878
+ }
1879
+ function resolveRepositoryDirectory(gitRoot, cwd) {
1880
+ const relativePath = path.relative(gitRoot, cwd);
1881
+ if (!relativePath || relativePath === ".") return;
1882
+ if (relativePath.startsWith("..")) return;
1883
+ return relativePath.split(path.sep).join("/");
1884
+ }
1885
+ function parseGithubRepository(remoteUrl) {
1886
+ const trimmed = remoteUrl.trim();
1887
+ const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
1888
+ if (sshMatch) return {
1889
+ owner: sshMatch[1],
1890
+ repo: sshMatch[2]
1891
+ };
1892
+ const sshProtocolMatch = trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
1893
+ if (sshProtocolMatch) return {
1894
+ owner: sshProtocolMatch[1],
1895
+ repo: sshProtocolMatch[2]
1896
+ };
1897
+ const gitProtocolMatch = trimmed.match(/^git:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
1898
+ if (gitProtocolMatch) return {
1899
+ owner: gitProtocolMatch[1],
1900
+ repo: gitProtocolMatch[2]
1901
+ };
1902
+ try {
1903
+ const url = new URL(trimmed);
1904
+ if (url.hostname !== "github.com") return;
1905
+ const [owner, repo] = url.pathname.replace(/^\/|\/$/g, "").split("/");
1906
+ if (!owner || !repo) return;
1907
+ return {
1908
+ owner,
1909
+ repo: repo.replace(/\.git$/, "")
1910
+ };
1911
+ } catch {
1912
+ return;
1913
+ }
1914
+ }
1915
+ function runGitCapture(cwd, args) {
1916
+ const result = spawnSync("git", args, {
1917
+ cwd,
1918
+ encoding: "utf8",
1919
+ stdio: [
1920
+ "ignore",
1921
+ "pipe",
1922
+ "ignore"
1923
+ ]
1924
+ });
1925
+ if (result.status !== 0) return;
1926
+ return result.stdout.trim() || void 0;
1927
+ }
1928
+ function pmRun(pm, script) {
1929
+ switch (pm) {
1930
+ case "npm": return `npm run ${script}`;
1931
+ case "pnpm": return `pnpm run ${script}`;
1932
+ case "yarn": return `yarn ${script}`;
1933
+ case "bun": return `bun run ${script}`;
1934
+ case "deno": return script;
1935
+ }
1936
+ }
1937
+ function onCancel() {
1938
+ cancel("Cancelled");
1939
+ process.exitCode = 0;
1940
+ }
1941
+
1942
+ //#endregion
1943
+ 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.1",
4
4
  "description": "Interactive CLI to scaffold standardized frontend project templates.",
5
5
  "keywords": [
6
6
  "cli",