mintree 0.4.2 → 0.4.3

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,10 @@
1
+ import { z } from "zod";
2
+ export declare const description = "Update mintree to the latest version (npm i -g mintree)";
3
+ export declare const options: z.ZodObject<{
4
+ force: z.ZodDefault<z.ZodBoolean>;
5
+ }, z.core.$strip>;
6
+ type Props = {
7
+ options: z.infer<typeof options>;
8
+ };
9
+ export default function Update({ options: opts }: Props): import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Box, Text } from "ink";
4
+ import Spinner from "ink-spinner";
5
+ import { option } from "pastel";
6
+ import { z } from "zod";
7
+ import { createRequire } from "module";
8
+ import { getLatestVersion, isNewerVersion } from "../lib/version.js";
9
+ import { installLatest, PACKAGE_NAME } from "../lib/update.js";
10
+ const require = createRequire(import.meta.url);
11
+ const { version: currentVersion } = require("../../package.json");
12
+ export const description = "Update mintree to the latest version (npm i -g mintree)";
13
+ export const options = z.object({
14
+ force: z
15
+ .boolean()
16
+ .default(false)
17
+ .describe(option({
18
+ description: "Reinstall even when you're already on the latest version",
19
+ alias: "f",
20
+ })),
21
+ });
22
+ export default function Update({ options: opts }) {
23
+ const [phase, setPhase] = useState({ kind: "checking" });
24
+ useEffect(() => {
25
+ let cancelled = false;
26
+ (async () => {
27
+ const latest = await getLatestVersion(PACKAGE_NAME);
28
+ if (cancelled)
29
+ return;
30
+ // Skip the reinstall only when we're provably current and the user
31
+ // didn't force it. A null probe (offline/private registry) falls
32
+ // through to the install so `mt update` still does something useful.
33
+ if (!opts.force && latest && !isNewerVersion(currentVersion, latest)) {
34
+ setPhase({ kind: "uptodate", latest });
35
+ return;
36
+ }
37
+ setPhase({ kind: "installing", latest });
38
+ const result = await installLatest();
39
+ if (cancelled)
40
+ return;
41
+ setPhase({ kind: "done", result, latest });
42
+ })();
43
+ return () => {
44
+ cancelled = true;
45
+ };
46
+ }, [opts.force]);
47
+ if (phase.kind === "checking") {
48
+ return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" Checking for updates... (current v", currentVersion, ")"] })] }));
49
+ }
50
+ if (phase.kind === "uptodate") {
51
+ return (_jsxs(Box, { flexDirection: "column", paddingY: 0, children: [_jsxs(Text, { color: "green", children: ["\u2713 mintree is already up to date (v", phase.latest, ")."] }), _jsx(Text, { dimColor: true, children: "Run with --force to reinstall anyway." })] }));
52
+ }
53
+ if (phase.kind === "installing") {
54
+ const target = phase.latest ? `v${phase.latest}` : "latest";
55
+ return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", "Updating mintree from v", currentVersion, " to ", target, "..."] })] }));
56
+ }
57
+ // done
58
+ const { result, latest } = phase;
59
+ if (result.ok) {
60
+ const target = latest ? `v${latest}` : "the latest version";
61
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", children: ["\u2713 mintree updated to ", target, "."] }), _jsx(Text, { dimColor: true, children: "Open a new shell (or re-run your command) to use it." })] }));
62
+ }
63
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "\u2717 Update failed." }), _jsx(Text, { children: result.message }), result.hint ? _jsx(Text, { dimColor: true, children: result.hint }) : null] }));
64
+ }
@@ -0,0 +1,16 @@
1
+ export declare const PACKAGE_NAME = "mintree";
2
+ export type UpdateResult = {
3
+ ok: true;
4
+ output: string;
5
+ } | {
6
+ ok: false;
7
+ message: string;
8
+ hint?: string;
9
+ };
10
+ /**
11
+ * Reinstalls `mintree@latest` globally via npm. Returns a discriminated result
12
+ * so the command can render a precise message instead of dumping a raw stack.
13
+ * The common failure — EACCES on a root-owned global prefix — gets a targeted
14
+ * hint pointing at the usual fixes.
15
+ */
16
+ export declare function installLatest(): Promise<UpdateResult>;
@@ -0,0 +1,43 @@
1
+ // Self-update: reinstall the globally-installed mintree from npm. The CLI is
2
+ // distributed via `npm i -g mintree`, so updating is just re-running that
3
+ // install for the `@latest` tag. We shell out to `npm` rather than reuse the
4
+ // registry probe in version.ts because npm owns the global prefix, perms, and
5
+ // bin-linking we can't replicate reliably here.
6
+ import { exec } from "child_process";
7
+ import { promisify } from "util";
8
+ const execAsync = promisify(exec);
9
+ // npm global installs can be slow on a cold cache; give them room before we
10
+ // give up. 2 minutes mirrors what a fresh `npm i -g` typically needs.
11
+ const INSTALL_TIMEOUT_MS = 120_000;
12
+ export const PACKAGE_NAME = "mintree";
13
+ /**
14
+ * Reinstalls `mintree@latest` globally via npm. Returns a discriminated result
15
+ * so the command can render a precise message instead of dumping a raw stack.
16
+ * The common failure — EACCES on a root-owned global prefix — gets a targeted
17
+ * hint pointing at the usual fixes.
18
+ */
19
+ export async function installLatest() {
20
+ try {
21
+ const { stdout, stderr } = await execAsync(`npm install -g ${PACKAGE_NAME}@latest`, {
22
+ timeout: INSTALL_TIMEOUT_MS,
23
+ });
24
+ return { ok: true, output: (stdout || stderr || "").trim() };
25
+ }
26
+ catch (err) {
27
+ const message = err instanceof Error ? err.message : String(err);
28
+ return { ok: false, message, hint: hintForError(message) };
29
+ }
30
+ }
31
+ function hintForError(message) {
32
+ const m = message.toLowerCase();
33
+ if (m.includes("eacces") || m.includes("permission denied")) {
34
+ return "npm couldn't write to its global prefix. Either fix the prefix ownership (npm docs: 'resolving EACCES permissions errors') or re-run with sudo.";
35
+ }
36
+ if (m.includes("command not found") || m.includes("not recognized")) {
37
+ return "npm wasn't found on your PATH. Install Node.js (which bundles npm) and try again.";
38
+ }
39
+ if (m.includes("etimedout") || m.includes("network") || m.includes("enotfound")) {
40
+ return "Looks like a network problem reaching the npm registry. Check your connection and retry.";
41
+ }
42
+ return undefined;
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mintree",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Issue-driven git worktrees + Claude Code sessions for repos with an opinionated SDD+TDD flow.",
5
5
  "license": "MIT",
6
6
  "author": "Martin Mineo <mmineo@canarytechnologies.com>",