hatchkit 0.1.13 → 0.1.15

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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Multi-select prompt with intuitive keybindings:
3
+ * ↑↓ navigate
4
+ * Enter toggle the highlighted option
5
+ * Tab submit (advance to the next prompt)
6
+ * Space also toggles (kept as an alias for inquirer-muscle-memory)
7
+ *
8
+ * Built on @inquirer/core so it composes with the rest of the
9
+ * @inquirer/prompts the CLI already uses (input/select/confirm). The
10
+ * vanilla `checkbox` from @inquirer/prompts uses Space=toggle +
11
+ * Enter=submit, which conflicts with the rest of the flow where Enter
12
+ * is the universal "go" key — this swap makes Enter behave consistently
13
+ * (it always advances state on the current prompt) and Tab moves to the
14
+ * next prompt, which is the same convention as web forms.
15
+ */
16
+ import { Separator } from "@inquirer/core";
17
+ export interface MultiselectChoice<Value> {
18
+ value: Value;
19
+ name?: string;
20
+ description?: string;
21
+ disabled?: boolean | string;
22
+ checked?: boolean;
23
+ }
24
+ export interface MultiselectConfig<Value> {
25
+ message: string;
26
+ choices: ReadonlyArray<Separator | MultiselectChoice<Value>>;
27
+ pageSize?: number;
28
+ loop?: boolean;
29
+ required?: boolean;
30
+ }
31
+ /** Typed wrapper that gives back `Value[]` for callers. */
32
+ export declare function multiselect<Value>(config: MultiselectConfig<Value>): Promise<Value[]>;
33
+ export { Separator };
34
+ //# sourceMappingURL=multiselect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multiselect.d.ts","sourceRoot":"","sources":["../../src/utils/multiselect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,SAAS,EAeV,MAAM,gBAAgB,CAAC;AAIxB,MAAM,WAAW,iBAAiB,CAAC,KAAK;IACtC,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB,CAAC,KAAK;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,CAAC,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAkKD,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,iBAAiB,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAErF;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Multi-select prompt with intuitive keybindings:
3
+ * ↑↓ navigate
4
+ * Enter toggle the highlighted option
5
+ * Tab submit (advance to the next prompt)
6
+ * Space also toggles (kept as an alias for inquirer-muscle-memory)
7
+ *
8
+ * Built on @inquirer/core so it composes with the rest of the
9
+ * @inquirer/prompts the CLI already uses (input/select/confirm). The
10
+ * vanilla `checkbox` from @inquirer/prompts uses Space=toggle +
11
+ * Enter=submit, which conflicts with the rest of the flow where Enter
12
+ * is the universal "go" key — this swap makes Enter behave consistently
13
+ * (it always advances state on the current prompt) and Tab moves to the
14
+ * next prompt, which is the same convention as web forms.
15
+ */
16
+ import { Separator, ValidationError, createPrompt, isDownKey, isEnterKey, isNumberKey, isSpaceKey, isTabKey, isUpKey, makeTheme, useKeypress, useMemo, usePagination, usePrefix, useState, } from "@inquirer/core";
17
+ import figures from "@inquirer/figures";
18
+ import chalk from "chalk";
19
+ function isSelectable(item) {
20
+ return !Separator.isSeparator(item) && !item.disabled;
21
+ }
22
+ function normalize(choices) {
23
+ return choices.map((c) => {
24
+ if (Separator.isSeparator(c))
25
+ return c;
26
+ const name = c.name ?? String(c.value);
27
+ return {
28
+ value: c.value,
29
+ name,
30
+ description: c.description,
31
+ disabled: c.disabled ?? false,
32
+ checked: c.checked ?? false,
33
+ };
34
+ });
35
+ }
36
+ const corePrompt = createPrompt((config, done) => {
37
+ const { pageSize = 10, loop = true, required = false } = config;
38
+ const theme = makeTheme();
39
+ const [status, setStatus] = useState("idle");
40
+ const prefix = usePrefix({ status, theme });
41
+ const [items, setItems] = useState(normalize(config.choices));
42
+ const bounds = useMemo(() => {
43
+ const first = items.findIndex(isSelectable);
44
+ let last = -1;
45
+ for (let i = items.length - 1; i >= 0; i--) {
46
+ if (isSelectable(items[i])) {
47
+ last = i;
48
+ break;
49
+ }
50
+ }
51
+ if (first === -1) {
52
+ throw new ValidationError("[multiselect] No selectable choices.");
53
+ }
54
+ return { first, last };
55
+ }, [items]);
56
+ const [active, setActive] = useState(bounds.first);
57
+ const [errorMsg, setError] = useState();
58
+ const toggleAt = (index) => {
59
+ setError(undefined);
60
+ setItems(items.map((c, i) => {
61
+ if (i !== index)
62
+ return c;
63
+ if (!isSelectable(c))
64
+ return c;
65
+ return { ...c, checked: !c.checked };
66
+ }));
67
+ };
68
+ useKeypress((key) => {
69
+ if (isTabKey(key)) {
70
+ const selection = items.filter((c) => isSelectable(c) && c.checked);
71
+ if (required && selection.length === 0) {
72
+ setError("Pick at least one (use Enter to toggle).");
73
+ return;
74
+ }
75
+ setStatus("done");
76
+ done(selection.map((c) => c.value));
77
+ return;
78
+ }
79
+ if (isEnterKey(key) || isSpaceKey(key)) {
80
+ toggleAt(active);
81
+ return;
82
+ }
83
+ if (isUpKey(key) || isDownKey(key)) {
84
+ if (loop ||
85
+ (isUpKey(key) && active !== bounds.first) ||
86
+ (isDownKey(key) && active !== bounds.last)) {
87
+ const offset = isUpKey(key) ? -1 : 1;
88
+ let next = active;
89
+ do {
90
+ next = (next + offset + items.length) % items.length;
91
+ } while (!isSelectable(items[next]));
92
+ setActive(next);
93
+ }
94
+ return;
95
+ }
96
+ if (isNumberKey(key)) {
97
+ const idx = Number(key.name) - 1;
98
+ let seen = -1;
99
+ const pos = items.findIndex((it) => {
100
+ if (Separator.isSeparator(it))
101
+ return false;
102
+ seen++;
103
+ return seen === idx;
104
+ });
105
+ if (pos !== -1 && isSelectable(items[pos])) {
106
+ setActive(pos);
107
+ toggleAt(pos);
108
+ }
109
+ }
110
+ });
111
+ const message = theme.style.message(config.message, status);
112
+ if (status === "done") {
113
+ const selected = items.filter((c) => isSelectable(c) && c.checked);
114
+ const tail = selected.length > 0 ? selected.map((c) => c.name).join(", ") : chalk.dim("none");
115
+ return `${prefix} ${message} ${theme.style.answer(tail)}`;
116
+ }
117
+ let description;
118
+ const page = usePagination({
119
+ items,
120
+ active,
121
+ renderItem({ item, isActive }) {
122
+ if (Separator.isSeparator(item))
123
+ return ` ${item.separator}`;
124
+ if (item.disabled) {
125
+ const label = typeof item.disabled === "string" ? item.disabled : "(disabled)";
126
+ return chalk.dim(`- ${item.name} ${label}`);
127
+ }
128
+ if (isActive)
129
+ description = item.description;
130
+ const box = item.checked ? chalk.green(figures.circleFilled) : figures.circle;
131
+ const cursor = isActive ? figures.pointer : " ";
132
+ const line = `${cursor}${box} ${item.name}`;
133
+ return isActive ? theme.style.highlight(line) : line;
134
+ },
135
+ pageSize,
136
+ loop,
137
+ });
138
+ const help = chalk.dim(`${chalk.bold("↑↓")} navigate · ${chalk.bold("enter")} toggle · ${chalk.bold("tab")} submit`);
139
+ return [
140
+ `${prefix} ${message}`,
141
+ page,
142
+ " ",
143
+ description ? chalk.cyan(description) : "",
144
+ errorMsg ? theme.style.error(errorMsg) : "",
145
+ help,
146
+ ]
147
+ .filter(Boolean)
148
+ .join("\n")
149
+ .trimEnd();
150
+ });
151
+ /** Typed wrapper that gives back `Value[]` for callers. */
152
+ export function multiselect(config) {
153
+ return corePrompt(config);
154
+ }
155
+ export { Separator };
156
+ //# sourceMappingURL=multiselect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multiselect.js","sourceRoot":"","sources":["../../src/utils/multiselect.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hatchkit",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "packageManager": "pnpm@10.33.2",
5
5
  "description": "Interactive CLI for scaffolding full-stack projects and provisioning observability/email clients",
6
6
  "type": "module",
@@ -24,13 +24,15 @@
24
24
  "check": "pnpm run typecheck && pnpm run lint && pnpm run test",
25
25
  "install-local": "pnpm run build && npm install -g .",
26
26
  "release": "pnpm run release:patch",
27
- "release:patch": "node scripts/release-prep.mjs && npm version patch -m \"chore: release v%s\" && pnpm run _release:finish",
28
- "release:minor": "node scripts/release-prep.mjs && npm version minor -m \"chore: release v%s\" && pnpm run _release:finish",
29
- "release:major": "node scripts/release-prep.mjs && npm version major -m \"chore: release v%s\" && pnpm run _release:finish",
27
+ "release:patch": "node scripts/release-prep.mjs && node scripts/release-bump.mjs patch && pnpm run _release:finish",
28
+ "release:minor": "node scripts/release-prep.mjs && node scripts/release-bump.mjs minor && pnpm run _release:finish",
29
+ "release:major": "node scripts/release-prep.mjs && node scripts/release-bump.mjs major && pnpm run _release:finish",
30
30
  "_release:finish": "pnpm run build && pnpm run typecheck && npm publish --access public && git push --follow-tags && npm install -g .",
31
31
  "prepublishOnly": "pnpm run build"
32
32
  },
33
33
  "dependencies": {
34
+ "@inquirer/core": "^10.3.0",
35
+ "@inquirer/figures": "^1.0.0",
34
36
  "@inquirer/prompts": "^7.0.0",
35
37
  "chalk": "^5.3.0",
36
38
  "conf": "^13.0.0",
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * release-bump — bump cli/package.json + commit + tag, atomically.
4
+ *
5
+ * Replaces `npm version <bump> -m "..."` because npm's built-in
6
+ * version command silently skips the commit/tag step in this monorepo
7
+ * layout (cli/ is a workspace member, not the repo root, and the lock
8
+ * file npm tries to stage doesn't exist anymore — pnpm-lock.yaml at
9
+ * the workspace root is the source of truth). The result: every
10
+ * release left cli/package.json modified-but-uncommitted, the publish
11
+ * succeeded against an untracked version, and the release machinery
12
+ * drifted from git history.
13
+ *
14
+ * Doing the three steps ourselves is unambiguous: bump, stage, commit,
15
+ * tag. release-prep.mjs has already verified the working tree is
16
+ * clean before this runs, so the commit only contains the version bump.
17
+ *
18
+ * Usage: node scripts/release-bump.mjs <patch|minor|major>
19
+ */
20
+ import { execSync } from "node:child_process";
21
+ import { readFileSync, writeFileSync } from "node:fs";
22
+ import { dirname, join } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+
25
+ const here = dirname(fileURLToPath(import.meta.url));
26
+ const cliDir = join(here, "..");
27
+ const pkgPath = join(cliDir, "package.json");
28
+
29
+ const bumpKind = process.argv[2];
30
+ if (!["patch", "minor", "major"].includes(bumpKind)) {
31
+ console.error(`release-bump: expected patch|minor|major, got ${bumpKind ?? "(nothing)"}`);
32
+ process.exit(1);
33
+ }
34
+
35
+ function sh(cmd, opts = {}) {
36
+ return execSync(cmd, { encoding: "utf-8", ...opts }).trim();
37
+ }
38
+
39
+ const repoRoot = sh("git rev-parse --show-toplevel");
40
+
41
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
42
+ const current = pkg.version;
43
+ const [maj, min, pat] = current.split(".").map((n) => Number(n) || 0);
44
+ const next =
45
+ bumpKind === "major"
46
+ ? `${maj + 1}.0.0`
47
+ : bumpKind === "minor"
48
+ ? `${maj}.${min + 1}.0`
49
+ : `${maj}.${min}.${pat + 1}`;
50
+
51
+ pkg.version = next;
52
+ // npm's `npm version` keeps a trailing newline; preserve that so diffs
53
+ // stay 1 line and don't churn whitespace.
54
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf-8");
55
+ console.log(` release-bump: ${current} → ${next}`);
56
+
57
+ // Stage + commit + tag from the repo root so the path in the commit
58
+ // is `cli/package.json` regardless of where the script was invoked.
59
+ sh(`git add ${JSON.stringify(join("cli", "package.json"))}`, { cwd: repoRoot });
60
+ sh(`git commit -m ${JSON.stringify(`chore: release v${next}`)}`, { cwd: repoRoot });
61
+ sh(`git tag ${JSON.stringify(`v${next}`)}`, { cwd: repoRoot });
62
+ console.log(` release-bump: committed + tagged v${next}`);
@@ -8,11 +8,10 @@
8
8
  * same baseline.
9
9
  *
10
10
  * ALSO refuses when the local cli/package.json version is at-or-
11
- * behind the version published to npm. `npm version patch` increments
12
- * from the LOCAL version, not the registry's, so drift between the
13
- * two leads to the script generating a number that's already taken
14
- * and the publish step crashing after build/typecheck. Catch it up
15
- * front instead.
11
+ * behind the version published to npm. release-bump.mjs increments
12
+ * from the LOCAL version, so drift between local and registry leads
13
+ * to the bump generating a number that's already taken and the
14
+ * publish step crashing after build/typecheck. Catch it up front.
16
15
  *
17
16
  * Skip with RELEASE_SKIP_PREP=1 (e.g. a CI-driven release that's
18
17
  * already vouched for cleanliness). RELEASE_SKIP_NPM_CHECK=1 only
@@ -118,13 +117,13 @@ function checkNpmDrift(repoRoot) {
118
117
  return [
119
118
  `local ${name}@${local} is behind the registry's ${registry}.`,
120
119
  "",
121
- " `npm version patch` increments from the LOCAL version, so a release now",
120
+ " release-bump.mjs increments from the LOCAL version, so a release now",
122
121
  " would try to publish a version that's already taken. Sync first:",
123
122
  "",
124
123
  ` cd ${join(repoRoot, "cli")}`,
125
124
  ` npm version ${registry} --no-git-tag-version # match the registry`,
126
125
  ` cd ${repoRoot}`,
127
- ` git add cli/package.json cli/package-lock.json`,
126
+ ` git add cli/package.json`,
128
127
  ` git commit -m "chore: release v${registry}"`,
129
128
  ` git tag v${registry}`,
130
129
  "",