hacklab 0.7.2 → 0.7.5
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 +16 -1
- package/dist/api-client.d.ts +18 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +40 -0
- package/dist/api-client.js.map +1 -0
- package/dist/card.d.ts +66 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +151 -0
- package/dist/card.js.map +1 -0
- package/dist/commands/chat.d.ts +21 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +109 -14
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/drop.d.ts +1 -1
- package/dist/commands/drop.d.ts.map +1 -1
- package/dist/commands/drop.js +6 -1
- package/dist/commands/drop.js.map +1 -1
- package/dist/commands/essay.d.ts.map +1 -1
- package/dist/commands/essay.js +13 -0
- package/dist/commands/essay.js.map +1 -1
- package/dist/commands/hacker.d.ts +2 -0
- package/dist/commands/hacker.d.ts.map +1 -0
- package/dist/commands/hacker.js +104 -0
- package/dist/commands/hacker.js.map +1 -0
- package/dist/commands/join.d.ts.map +1 -1
- package/dist/commands/join.js +22 -0
- package/dist/commands/join.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +13 -0
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +2 -0
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/profile.d.ts +60 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +478 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/commands/project.d.ts +5 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +387 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +14 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +92 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/posthog.d.ts +9 -0
- package/dist/posthog.d.ts.map +1 -0
- package/dist/posthog.js +20 -0
- package/dist/posthog.js.map +1 -0
- package/dist/project-infer.d.ts +50 -0
- package/dist/project-infer.d.ts.map +1 -0
- package/dist/project-infer.js +211 -0
- package/dist/project-infer.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +25 -5
- package/dist/registry.js.map +1 -1
- package/dist/ui.d.ts +12 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +56 -0
- package/dist/ui.js.map +1 -1
- package/dist/utils/npmGlobal.d.ts +30 -0
- package/dist/utils/npmGlobal.d.ts.map +1 -0
- package/dist/utils/npmGlobal.js +69 -0
- package/dist/utils/npmGlobal.js.map +1 -0
- package/dist/utils/updateCheck.d.ts +6 -0
- package/dist/utils/updateCheck.d.ts.map +1 -1
- package/dist/utils/updateCheck.js +7 -2
- package/dist/utils/updateCheck.js.map +1 -1
- package/package.json +3 -1
- package/dist/commands/brag.d.ts +0 -5
- package/dist/commands/brag.d.ts.map +0 -1
- package/dist/commands/brag.js +0 -87
- package/dist/commands/brag.js.map +0 -1
- package/dist/project-brag.d.ts +0 -58
- package/dist/project-brag.d.ts.map +0 -1
- package/dist/project-brag.js +0 -270
- package/dist/project-brag.js.map +0 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PostHog } from 'posthog-node';
|
|
2
|
+
export declare const posthog: PostHog;
|
|
3
|
+
/**
|
|
4
|
+
* Capture an event and immediately flush it to PostHog. Since the CLI is a
|
|
5
|
+
* short-lived process, flushing after every capture ensures the event is sent
|
|
6
|
+
* before the process exits. No-ops silently if distinctId is absent.
|
|
7
|
+
*/
|
|
8
|
+
export declare function captureEvent(distinctId: string | undefined, event: string, properties?: Record<string, unknown>): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=posthog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posthog.d.ts","sourceRoot":"","sources":["../src/posthog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,eAAO,MAAM,OAAO,SASnB,CAAA;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,IAAI,CAAC,CAIf"}
|
package/dist/posthog.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PostHog } from 'posthog-node';
|
|
2
|
+
export const posthog = new PostHog(process.env.POSTHOG_API_KEY ??
|
|
3
|
+
'phc_CT4u3J62yYaAHUcnYKjmnpAGXTrWqv7ZbmvYQcUTEACK', {
|
|
4
|
+
host: process.env.POSTHOG_HOST ?? 'https://eu.i.posthog.com',
|
|
5
|
+
flushAt: 1,
|
|
6
|
+
flushInterval: 0,
|
|
7
|
+
enableExceptionAutocapture: true,
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Capture an event and immediately flush it to PostHog. Since the CLI is a
|
|
11
|
+
* short-lived process, flushing after every capture ensures the event is sent
|
|
12
|
+
* before the process exits. No-ops silently if distinctId is absent.
|
|
13
|
+
*/
|
|
14
|
+
export async function captureEvent(distinctId, event, properties) {
|
|
15
|
+
if (!distinctId)
|
|
16
|
+
return;
|
|
17
|
+
posthog.capture({ distinctId, event, properties });
|
|
18
|
+
await posthog.flush();
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=posthog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posthog.js","sourceRoot":"","sources":["../src/posthog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,OAAO,CAChC,OAAO,CAAC,GAAG,CAAC,eAAe;IACzB,kDAAkD,EACpD;IACE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,0BAA0B;IAC5D,OAAO,EAAE,CAAC;IACV,aAAa,EAAE,CAAC;IAChB,0BAA0B,EAAE,IAAI;CACjC,CACF,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAA8B,EAC9B,KAAa,EACb,UAAoC;IAEpC,IAAI,CAAC,UAAU;QAAE,OAAM;IACvB,OAAO,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAA;IAClD,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA;AACvB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type RepoRemote = {
|
|
2
|
+
/** Canonical https URL, no trailing `.git`. */
|
|
3
|
+
url: string;
|
|
4
|
+
/** `owner/name` host path when it parses, e.g. `acme/hacklab`. */
|
|
5
|
+
owner: string | null;
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
export type InferredProject = {
|
|
9
|
+
slug: string;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string | null;
|
|
12
|
+
tags: string[];
|
|
13
|
+
repoUrl: string | null;
|
|
14
|
+
liveUrl: string | null;
|
|
15
|
+
/** Full README markdown, the project page's long-form content. */
|
|
16
|
+
content: string | null;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Normalize any common git remote spelling to a canonical https URL.
|
|
20
|
+
* `git@github.com:acme/hacklab.git`, `ssh://git@github.com/acme/hacklab.git`,
|
|
21
|
+
* and `https://github.com/acme/hacklab.git` all become
|
|
22
|
+
* `https://github.com/acme/hacklab`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeRepoUrl(remote: string): RepoRemote | null;
|
|
25
|
+
/** Repo-name-derived slug matching the server's PROJECT_SLUG_PATTERN. */
|
|
26
|
+
export declare function slugFromName(name: string): string;
|
|
27
|
+
/** The first ATX heading's text, e.g. `# hacklab` -> `hacklab`. */
|
|
28
|
+
export declare function titleFromReadme(markdown: string): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* The first prose paragraph of a README: skips headings, badge/image-only
|
|
31
|
+
* lines, block quotes, code fences, and HTML, then joins the first run of
|
|
32
|
+
* plain text lines. This is the description fallback when package.json has
|
|
33
|
+
* none.
|
|
34
|
+
*/
|
|
35
|
+
export declare function firstParagraph(markdown: string): string | null;
|
|
36
|
+
/** package.json keywords first, then detected stack tags, deduped, capped. */
|
|
37
|
+
export declare function detectTags(pkg: {
|
|
38
|
+
keywords?: unknown;
|
|
39
|
+
dependencies?: Record<string, string>;
|
|
40
|
+
devDependencies?: Record<string, string>;
|
|
41
|
+
}): string[];
|
|
42
|
+
/** The repo root for cwd, or null when not inside a git work tree. */
|
|
43
|
+
export declare function findRepoRoot(cwd: string): Promise<string | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Build the best project we can from the repo at `root`. Precedence per
|
|
46
|
+
* field: README title > repo name; package.json description > README first
|
|
47
|
+
* paragraph; homepage is the only liveUrl source (anything else is guessing).
|
|
48
|
+
*/
|
|
49
|
+
export declare function inferProject(root: string): Promise<InferredProject>;
|
|
50
|
+
//# sourceMappingURL=project-infer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-infer.d.ts","sourceRoot":"","sources":["../src/project-infer.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,UAAU,GAAG;IACvB,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAA;IACX,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,kEAAkE;IAClE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAwBlE;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASjD;AAWD,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiC9D;AA2BD,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,GAAG,EAAE;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACzC,GAAG,MAAM,EAAE,CAqBX;AAWD,sEAAsE;AACtE,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAEtE;AAmBD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAoCzE"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
// Infer a publishable project from the repo you're standing in — the machine
|
|
6
|
+
// already knows the title, description, links, and tags, so no manifest file
|
|
7
|
+
// is ever required. Everything here is best-effort: a missing README or
|
|
8
|
+
// package.json just means fewer prefilled fields, never a failure. The only
|
|
9
|
+
// hard requirement is a git repo (identity + idempotency come from the
|
|
10
|
+
// remote), enforced by the command, not here.
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
/**
|
|
13
|
+
* Normalize any common git remote spelling to a canonical https URL.
|
|
14
|
+
* `git@github.com:acme/hacklab.git`, `ssh://git@github.com/acme/hacklab.git`,
|
|
15
|
+
* and `https://github.com/acme/hacklab.git` all become
|
|
16
|
+
* `https://github.com/acme/hacklab`.
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeRepoUrl(remote) {
|
|
19
|
+
const trimmed = remote.trim();
|
|
20
|
+
if (!trimmed)
|
|
21
|
+
return null;
|
|
22
|
+
const scp = trimmed.match(/^(?:[\w.-]+@)?([\w.-]+):(?!\/\/)(.+)$/);
|
|
23
|
+
const url = trimmed.match(/^(?:https?|ssh|git):\/\/(?:[\w.-]+@)?([\w.-]+)(?::\d+)?\/(.+)$/);
|
|
24
|
+
const host = url?.[1] ?? scp?.[1];
|
|
25
|
+
let path = url?.[2] ?? scp?.[2];
|
|
26
|
+
if (!host || !path)
|
|
27
|
+
return null;
|
|
28
|
+
path = path.replace(/\.git$/i, '').replace(/\/+$/, '');
|
|
29
|
+
if (!path)
|
|
30
|
+
return null;
|
|
31
|
+
const segments = path.split('/');
|
|
32
|
+
const name = segments.at(-1);
|
|
33
|
+
if (!name)
|
|
34
|
+
return null;
|
|
35
|
+
return {
|
|
36
|
+
url: `https://${host}/${path}`,
|
|
37
|
+
owner: segments.at(-2) ?? null,
|
|
38
|
+
name,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Repo-name-derived slug matching the server's PROJECT_SLUG_PATTERN. */
|
|
42
|
+
export function slugFromName(name) {
|
|
43
|
+
const slug = name
|
|
44
|
+
.trim()
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
47
|
+
.replace(/^-+|-+$/g, '')
|
|
48
|
+
.slice(0, 100)
|
|
49
|
+
.replace(/-+$/, '');
|
|
50
|
+
return slug || 'project';
|
|
51
|
+
}
|
|
52
|
+
/** Strip inline markdown (links, images, emphasis, code ticks) to plain text. */
|
|
53
|
+
function stripInlineMarkdown(line) {
|
|
54
|
+
return line
|
|
55
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
|
|
56
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
57
|
+
.replace(/[*_`]/g, '')
|
|
58
|
+
.trim();
|
|
59
|
+
}
|
|
60
|
+
/** The first ATX heading's text, e.g. `# hacklab` -> `hacklab`. */
|
|
61
|
+
export function titleFromReadme(markdown) {
|
|
62
|
+
for (const line of markdown.split('\n')) {
|
|
63
|
+
const match = line.match(/^#{1,2}\s+(.+?)\s*#*\s*$/);
|
|
64
|
+
if (match?.[1]) {
|
|
65
|
+
const text = stripInlineMarkdown(match[1]);
|
|
66
|
+
if (text)
|
|
67
|
+
return text;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The first prose paragraph of a README: skips headings, badge/image-only
|
|
74
|
+
* lines, block quotes, code fences, and HTML, then joins the first run of
|
|
75
|
+
* plain text lines. This is the description fallback when package.json has
|
|
76
|
+
* none.
|
|
77
|
+
*/
|
|
78
|
+
export function firstParagraph(markdown) {
|
|
79
|
+
const collected = [];
|
|
80
|
+
let inFence = false;
|
|
81
|
+
for (const raw of markdown.split('\n')) {
|
|
82
|
+
const line = raw.trim();
|
|
83
|
+
if (/^(```|~~~)/.test(line)) {
|
|
84
|
+
inFence = !inFence;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (inFence)
|
|
88
|
+
continue;
|
|
89
|
+
const isProse = line.length > 0 &&
|
|
90
|
+
!/^#/.test(line) &&
|
|
91
|
+
!/^>/.test(line) &&
|
|
92
|
+
!/^[-*+]\s/.test(line) &&
|
|
93
|
+
!/^\d+\.\s/.test(line) &&
|
|
94
|
+
!/^</.test(line) &&
|
|
95
|
+
!/^\|/.test(line) &&
|
|
96
|
+
!/^(---|===)/.test(line) &&
|
|
97
|
+
stripInlineMarkdown(line).length > 0;
|
|
98
|
+
if (isProse) {
|
|
99
|
+
collected.push(stripInlineMarkdown(line));
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (collected.length > 0)
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
if (collected.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
return collected.join(' ').replace(/\s+/g, ' ').slice(0, 2000).trim();
|
|
108
|
+
}
|
|
109
|
+
// Dependency -> tag. Deliberately coarse: tags are for browsing, not a
|
|
110
|
+
// lockfile inventory, so only broadly recognizable stack names make the cut.
|
|
111
|
+
const DEP_TAGS = [
|
|
112
|
+
[/^next$/, 'nextjs'],
|
|
113
|
+
[/^react(-dom)?$/, 'react'],
|
|
114
|
+
[/^vue$/, 'vue'],
|
|
115
|
+
[/^svelte$/, 'svelte'],
|
|
116
|
+
[/^@angular\//, 'angular'],
|
|
117
|
+
[/^solid-js$/, 'solidjs'],
|
|
118
|
+
[/^astro$/, 'astro'],
|
|
119
|
+
[/^expo$|^react-native$/, 'react-native'],
|
|
120
|
+
[/^electron$/, 'electron'],
|
|
121
|
+
[/^tailwindcss$/, 'tailwind'],
|
|
122
|
+
[/^typescript$/, 'typescript'],
|
|
123
|
+
[/^drizzle-orm$/, 'drizzle'],
|
|
124
|
+
[/^@prisma\/client$|^prisma$/, 'prisma'],
|
|
125
|
+
[/^hono$/, 'hono'],
|
|
126
|
+
[/^express$/, 'express'],
|
|
127
|
+
[/^fastify$/, 'fastify'],
|
|
128
|
+
[/^@trpc\//, 'trpc'],
|
|
129
|
+
[/^ai$|^openai$|^@anthropic-ai\//, 'ai'],
|
|
130
|
+
];
|
|
131
|
+
const MAX_TAGS = 10;
|
|
132
|
+
/** package.json keywords first, then detected stack tags, deduped, capped. */
|
|
133
|
+
export function detectTags(pkg) {
|
|
134
|
+
const tags = [];
|
|
135
|
+
const push = (tag) => {
|
|
136
|
+
const t = tag.trim().toLowerCase().slice(0, 50);
|
|
137
|
+
if (t && !tags.includes(t))
|
|
138
|
+
tags.push(t);
|
|
139
|
+
};
|
|
140
|
+
if (Array.isArray(pkg.keywords)) {
|
|
141
|
+
for (const k of pkg.keywords) {
|
|
142
|
+
if (typeof k === 'string')
|
|
143
|
+
push(k);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const deps = { ...pkg.devDependencies, ...pkg.dependencies };
|
|
147
|
+
for (const dep of Object.keys(deps)) {
|
|
148
|
+
for (const [pattern, tag] of DEP_TAGS) {
|
|
149
|
+
if (pattern.test(dep))
|
|
150
|
+
push(tag);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return tags.slice(0, MAX_TAGS);
|
|
154
|
+
}
|
|
155
|
+
async function git(cwd, ...args) {
|
|
156
|
+
try {
|
|
157
|
+
const { stdout } = await execFileAsync('git', ['-C', cwd, ...args]);
|
|
158
|
+
return stdout.trim() || null;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** The repo root for cwd, or null when not inside a git work tree. */
|
|
165
|
+
export async function findRepoRoot(cwd) {
|
|
166
|
+
return git(cwd, 'rev-parse', '--show-toplevel');
|
|
167
|
+
}
|
|
168
|
+
async function readIfExists(path) {
|
|
169
|
+
try {
|
|
170
|
+
return await readFile(path, 'utf8');
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Build the best project we can from the repo at `root`. Precedence per
|
|
178
|
+
* field: README title > repo name; package.json description > README first
|
|
179
|
+
* paragraph; homepage is the only liveUrl source (anything else is guessing).
|
|
180
|
+
*/
|
|
181
|
+
export async function inferProject(root) {
|
|
182
|
+
const remoteUrl = await git(root, 'remote', 'get-url', 'origin');
|
|
183
|
+
const remote = remoteUrl ? normalizeRepoUrl(remoteUrl) : null;
|
|
184
|
+
let pkg = {};
|
|
185
|
+
const pkgRaw = await readIfExists(join(root, 'package.json'));
|
|
186
|
+
if (pkgRaw) {
|
|
187
|
+
try {
|
|
188
|
+
pkg = JSON.parse(pkgRaw);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// A broken package.json costs its fields, nothing more.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const readme = (await readIfExists(join(root, 'README.md'))) ??
|
|
195
|
+
(await readIfExists(join(root, 'readme.md')));
|
|
196
|
+
const dirName = root.split('/').filter(Boolean).pop() ?? 'project';
|
|
197
|
+
const name = remote?.name ?? dirName;
|
|
198
|
+
const homepage = typeof pkg.homepage === 'string' && /^https?:\/\//.test(pkg.homepage)
|
|
199
|
+
? pkg.homepage
|
|
200
|
+
: null;
|
|
201
|
+
return {
|
|
202
|
+
slug: slugFromName(name),
|
|
203
|
+
title: (readme ? titleFromReadme(readme) : null) ?? name,
|
|
204
|
+
description: pkg.description?.trim() || (readme ? firstParagraph(readme) : null),
|
|
205
|
+
tags: detectTags(pkg),
|
|
206
|
+
repoUrl: remote?.url ?? null,
|
|
207
|
+
liveUrl: homepage,
|
|
208
|
+
content: readme,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=project-infer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-infer.js","sourceRoot":"","sources":["../src/project-infer.ts"],"names":[],"mappings":"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"}
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,WAAW,GAAG;IACxB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAA;IACf,0FAA0F;IAC1F,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC9C,CAAA;AAED,eAAO,MAAM,QAAQ,EAAE,WAAW,EAsFjC,CAAA;AAED,kFAAkF;AAClF,eAAO,MAAM,aAAa,UAA8B,CAAA;AAExD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEjE"}
|
package/dist/registry.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { brag, parseBragArgs } from './commands/brag.js';
|
|
2
1
|
import { chat } from './commands/chat.js';
|
|
3
2
|
import { configCommand } from './commands/config.js';
|
|
4
3
|
import { drop, parseDropArgs } from './commands/drop.js';
|
|
5
4
|
import { essay } from './commands/essay.js';
|
|
5
|
+
import { hacker } from './commands/hacker.js';
|
|
6
6
|
import { join } from './commands/join.js';
|
|
7
7
|
import { login } from './commands/login.js';
|
|
8
8
|
import { logout } from './commands/logout.js';
|
|
9
9
|
import { org } from './commands/org.js';
|
|
10
|
+
import { profile } from './commands/profile.js';
|
|
11
|
+
import { project } from './commands/project.js';
|
|
10
12
|
import { sync } from './commands/sync.js';
|
|
13
|
+
import { update } from './commands/update.js';
|
|
11
14
|
import { whoami } from './commands/whoami.js';
|
|
12
15
|
export const COMMANDS = [
|
|
13
16
|
{
|
|
@@ -54,10 +57,10 @@ export const COMMANDS = [
|
|
|
54
57
|
run: (args) => configCommand(args),
|
|
55
58
|
},
|
|
56
59
|
{
|
|
57
|
-
name: '
|
|
58
|
-
args: '[
|
|
59
|
-
summary: '
|
|
60
|
-
run: (args) =>
|
|
60
|
+
name: 'project',
|
|
61
|
+
args: '[add|list|delete]',
|
|
62
|
+
summary: 'publish this repo as a project (--json for agents)',
|
|
63
|
+
run: (args) => project(args),
|
|
61
64
|
},
|
|
62
65
|
{
|
|
63
66
|
name: 'org',
|
|
@@ -77,6 +80,23 @@ export const COMMANDS = [
|
|
|
77
80
|
summary: 'post and manage essays (post/list/view/update/delete)',
|
|
78
81
|
run: (args) => essay(args),
|
|
79
82
|
},
|
|
83
|
+
{
|
|
84
|
+
name: 'hacker',
|
|
85
|
+
args: 'view <handle>',
|
|
86
|
+
summary: "view another hacker's profile (--json for agents)",
|
|
87
|
+
run: (args) => hacker(args),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'profile',
|
|
91
|
+
args: '[view|set|edit|apply]',
|
|
92
|
+
summary: 'view and edit your own profile (--json for agents)',
|
|
93
|
+
run: (args) => profile(args),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'update',
|
|
97
|
+
summary: 'update the hacklab CLI to the latest version',
|
|
98
|
+
run: () => update(),
|
|
99
|
+
},
|
|
80
100
|
];
|
|
81
101
|
/** Every command name, in registry order — the source of truth for resolution. */
|
|
82
102
|
export const COMMAND_NAMES = COMMANDS.map((c) => c.name);
|
package/dist/registry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/ui.d.ts
CHANGED
|
@@ -5,11 +5,23 @@ export declare const red: import("chalk").ChalkInstance;
|
|
|
5
5
|
export declare const yellow: import("chalk").ChalkInstance;
|
|
6
6
|
export declare const white: import("chalk").ChalkInstance;
|
|
7
7
|
export declare const linkBlue: import("chalk").ChalkInstance;
|
|
8
|
+
export declare const mint: import("chalk").ChalkInstance;
|
|
8
9
|
/**
|
|
9
10
|
* Colour a string in the terminal colour for the rank at `level`. A null/unknown
|
|
10
11
|
* level (e.g. older messages without a sender level) falls back to plain bold.
|
|
11
12
|
*/
|
|
12
13
|
export declare function rankColor(level: number | null | undefined, text: string): string;
|
|
14
|
+
/** Display columns a plain string occupies (ANSI stripped, CJK/emoji aware). */
|
|
15
|
+
export declare function displayWidth(s: string): number;
|
|
16
|
+
/**
|
|
17
|
+
* Truncate a PLAIN string to at most `max` display columns, cutting on grapheme
|
|
18
|
+
* boundaries (never mid-emoji / mid-surrogate) and adding a 1-column ellipsis
|
|
19
|
+
* when anything was dropped. Must run before colorizing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function truncateToWidth(s: string, max: number): string;
|
|
22
|
+
/** Right-pad a PLAIN string with spaces to exactly `width` display columns. */
|
|
23
|
+
export declare function padEndTo(s: string, width: number): string;
|
|
24
|
+
export declare function stripControl(s: string): string;
|
|
13
25
|
export declare function banner(): void;
|
|
14
26
|
export declare function success(msg: string): void;
|
|
15
27
|
export declare function error(msg: string): void;
|
package/dist/ui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,GAAG,+BAAY,CAAA;AAC5B,eAAO,MAAM,IAAI,+BAAa,CAAA;AAC9B,eAAO,MAAM,KAAK,+BAAc,CAAA;AAChC,eAAO,MAAM,GAAG,+BAAY,CAAA;AAC5B,eAAO,MAAM,MAAM,+BAAe,CAAA;AAClC,eAAO,MAAM,KAAK,+BAAoB,CAAA;AAGtC,eAAO,MAAM,QAAQ,+BAAuB,CAAA;AAI5C,eAAO,MAAM,IAAI,+BAAuB,CAAA;AAwBxC;;;GAGG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,IAAI,EAAE,MAAM,GACX,MAAM,CAMR;AAcD,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAa9D;AAED,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGzD;AAOD,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG9C;AAED,wBAAgB,MAAM,SAIrB;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,QAElC;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,QAEhC;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,QAE/B"}
|
package/dist/ui.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import stringWidth from 'string-width';
|
|
2
3
|
import { getBeltColor } from './belt.js';
|
|
3
4
|
export const dim = chalk.dim;
|
|
4
5
|
export const bold = chalk.bold;
|
|
@@ -9,6 +10,10 @@ export const white = chalk.whiteBright;
|
|
|
9
10
|
// GitHub's link blue. chalk degrades the hex to the nearest ANSI colour on
|
|
10
11
|
// terminals without truecolor support.
|
|
11
12
|
export const linkBlue = chalk.hex('#79b8ff');
|
|
13
|
+
// Phosphor Mint (DESIGN.md --primary #82F5C6): the "active and alive" signal —
|
|
14
|
+
// the open-to-work status dot and lit activity cells. chalk degrades the hex on
|
|
15
|
+
// terminals without truecolor.
|
|
16
|
+
export const mint = chalk.hex('#82F5C6');
|
|
12
17
|
// Map a belt colour to the nearest standard ANSI terminal colour name. We use
|
|
13
18
|
// chalk's *named* colours (not the hex values from share-card-colors) on
|
|
14
19
|
// purpose: named colours resolve to each viewer's own terminal theme, so the
|
|
@@ -42,6 +47,57 @@ export function rankColor(level, text) {
|
|
|
42
47
|
return bold(text);
|
|
43
48
|
return BELT_ANSI[getBeltColor(level)].bold(text);
|
|
44
49
|
}
|
|
50
|
+
// --- Display-width primitives ----------------------------------------------
|
|
51
|
+
// The hacker card has a fixed column budget, and its content is user-supplied
|
|
52
|
+
// (handles, bios) that can contain CJK, emoji, and ANSI. `.length` is wrong for
|
|
53
|
+
// all three. Render order everywhere is: strip → truncate → measure → colorize
|
|
54
|
+
// LAST — colorizing before truncation would slice an ANSI escape mid-byte, and
|
|
55
|
+
// stringWidth counts ANSI as zero so a coloured string would measure short and
|
|
56
|
+
// truncate into corruption. These helpers therefore operate on PLAIN text.
|
|
57
|
+
// Ambiguous-width characters (⬤, box-drawing) count as 1 column — pinned per
|
|
58
|
+
// DESIGN.md so the card's rules and content agree with a standard terminal.
|
|
59
|
+
const WIDTH_OPTS = { ambiguousIsNarrow: true };
|
|
60
|
+
/** Display columns a plain string occupies (ANSI stripped, CJK/emoji aware). */
|
|
61
|
+
export function displayWidth(s) {
|
|
62
|
+
return stringWidth(s, WIDTH_OPTS);
|
|
63
|
+
}
|
|
64
|
+
const graphemeSegmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
|
65
|
+
/**
|
|
66
|
+
* Truncate a PLAIN string to at most `max` display columns, cutting on grapheme
|
|
67
|
+
* boundaries (never mid-emoji / mid-surrogate) and adding a 1-column ellipsis
|
|
68
|
+
* when anything was dropped. Must run before colorizing.
|
|
69
|
+
*/
|
|
70
|
+
export function truncateToWidth(s, max) {
|
|
71
|
+
if (max <= 0)
|
|
72
|
+
return '';
|
|
73
|
+
if (displayWidth(s) <= max)
|
|
74
|
+
return s;
|
|
75
|
+
const budget = max - 1; // reserve one column for the … ellipsis
|
|
76
|
+
let out = '';
|
|
77
|
+
let width = 0;
|
|
78
|
+
for (const { segment } of graphemeSegmenter.segment(s)) {
|
|
79
|
+
const w = displayWidth(segment);
|
|
80
|
+
if (width + w > budget)
|
|
81
|
+
break;
|
|
82
|
+
out += segment;
|
|
83
|
+
width += w;
|
|
84
|
+
}
|
|
85
|
+
return `${out}…`;
|
|
86
|
+
}
|
|
87
|
+
/** Right-pad a PLAIN string with spaces to exactly `width` display columns. */
|
|
88
|
+
export function padEndTo(s, width) {
|
|
89
|
+
const gap = width - displayWidth(s);
|
|
90
|
+
return gap > 0 ? s + ' '.repeat(gap) : s;
|
|
91
|
+
}
|
|
92
|
+
// Strip C0 control characters + DEL from untrusted text before printing it to
|
|
93
|
+
// the terminal. Without this, a hacker could embed ANSI escape sequences in a
|
|
94
|
+
// handle, bio, message, or any user-supplied field to clear/garble or hijack
|
|
95
|
+
// any viewer's terminal. Lives here (not in a command module) so every renderer
|
|
96
|
+
// — chat, the hacker card — can reach it without a circular import.
|
|
97
|
+
export function stripControl(s) {
|
|
98
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentionally matching control chars to remove them
|
|
99
|
+
return s.replace(/[\x00-\x1f\x7f]/g, '');
|
|
100
|
+
}
|
|
45
101
|
export function banner() {
|
|
46
102
|
console.log('');
|
|
47
103
|
console.log(bold(' ☠︎ hacklab'));
|
package/dist/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAkB,YAAY,EAAE,MAAM,WAAW,CAAA;AAExD,MAAM,CAAC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAA;AAC5B,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;AAC9B,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;AAChC,MAAM,CAAC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAA;AAC5B,MAAM,CAAC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;AAClC,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAA;AACtC,2EAA2E;AAC3E,uCAAuC;AACvC,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type SpawnSyncReturns } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* npm's executable name. On Windows npm is a `.cmd` shim, and recent Node
|
|
4
|
+
* refuses to spawn `.cmd`/`.bat` without a shell, so callers pass `shell: true`
|
|
5
|
+
* on win32 (see {@link runNpm}).
|
|
6
|
+
*/
|
|
7
|
+
export declare const NPM_BIN: string;
|
|
8
|
+
/**
|
|
9
|
+
* Run npm with fixed (never user-supplied) args. `capture` pipes stdout/stderr
|
|
10
|
+
* back as strings; otherwise the child inherits the terminal so npm's own
|
|
11
|
+
* progress shows through. `shell: true` on Windows is required to launch the
|
|
12
|
+
* `.cmd` shim; the args here are all literals, so there's nothing to escape.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runNpm(args: string[], opts?: {
|
|
15
|
+
capture?: boolean;
|
|
16
|
+
}): SpawnSyncReturns<string>;
|
|
17
|
+
/** The directory npm would drop a global package into (`npm root -g`), or ''. */
|
|
18
|
+
export declare function npmGlobalRoot(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Walk up from `p` to the nearest ancestor that exists on disk. We can't test
|
|
21
|
+
* writability of a directory npm hasn't created yet (`…/lib/node_modules`
|
|
22
|
+
* frequently doesn't exist), so we check the closest thing that does — mkdir
|
|
23
|
+
* succeeds iff that ancestor is writable.
|
|
24
|
+
*/
|
|
25
|
+
export declare function firstExistingAncestor(p: string): string;
|
|
26
|
+
/** Can the current user create/write inside `dir` (probing its nearest existing ancestor)? */
|
|
27
|
+
export declare function isWritable(dir: string): boolean;
|
|
28
|
+
/** The user-owned prefix we reconfigure npm to when the system one is read-only. */
|
|
29
|
+
export declare function userNpmPrefix(): string;
|
|
30
|
+
//# sourceMappingURL=npmGlobal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npmGlobal.d.ts","sourceRoot":"","sources":["../../src/utils/npmGlobal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAa,MAAM,oBAAoB,CAAA;AAarE;;;;GAIG;AACH,eAAO,MAAM,OAAO,QAAmD,CAAA;AAEvE;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/B,gBAAgB,CAAC,MAAM,CAAC,CAM1B;AAED,iFAAiF;AACjF,wBAAgB,aAAa,IAAI,MAAM,CAGtC;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAQvD;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAS/C;AAED,oFAAoF;AACpF,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { accessSync, constants, existsSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
// Shared plumbing for the two places that shell out to `npm i -g hacklab`: the
|
|
6
|
+
// `hacklab update` command and (conceptually) the curl|sh installer. The point
|
|
7
|
+
// of the H1 approach is to never let npm's raw EACCES stack trace reach the
|
|
8
|
+
// user: probe whether npm's global folder is writable up front, and if it isn't,
|
|
9
|
+
// reconfigure npm to a user-owned prefix (npm's own documented remedy) instead
|
|
10
|
+
// of escalating to sudo. That fix is persistent, so every future `npm i -g`
|
|
11
|
+
// works too — which is the whole reason the update nag can safely point here.
|
|
12
|
+
/**
|
|
13
|
+
* npm's executable name. On Windows npm is a `.cmd` shim, and recent Node
|
|
14
|
+
* refuses to spawn `.cmd`/`.bat` without a shell, so callers pass `shell: true`
|
|
15
|
+
* on win32 (see {@link runNpm}).
|
|
16
|
+
*/
|
|
17
|
+
export const NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
18
|
+
/**
|
|
19
|
+
* Run npm with fixed (never user-supplied) args. `capture` pipes stdout/stderr
|
|
20
|
+
* back as strings; otherwise the child inherits the terminal so npm's own
|
|
21
|
+
* progress shows through. `shell: true` on Windows is required to launch the
|
|
22
|
+
* `.cmd` shim; the args here are all literals, so there's nothing to escape.
|
|
23
|
+
*/
|
|
24
|
+
export function runNpm(args, opts = {}) {
|
|
25
|
+
return spawnSync(NPM_BIN, args, {
|
|
26
|
+
stdio: opts.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
shell: process.platform === 'win32',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** The directory npm would drop a global package into (`npm root -g`), or ''. */
|
|
32
|
+
export function npmGlobalRoot() {
|
|
33
|
+
const res = runNpm(['root', '-g'], { capture: true });
|
|
34
|
+
return res.status === 0 && res.stdout ? res.stdout.trim() : '';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Walk up from `p` to the nearest ancestor that exists on disk. We can't test
|
|
38
|
+
* writability of a directory npm hasn't created yet (`…/lib/node_modules`
|
|
39
|
+
* frequently doesn't exist), so we check the closest thing that does — mkdir
|
|
40
|
+
* succeeds iff that ancestor is writable.
|
|
41
|
+
*/
|
|
42
|
+
export function firstExistingAncestor(p) {
|
|
43
|
+
let d = p;
|
|
44
|
+
while (d && !existsSync(d)) {
|
|
45
|
+
const parent = dirname(d);
|
|
46
|
+
if (parent === d)
|
|
47
|
+
break; // hit the filesystem root
|
|
48
|
+
d = parent;
|
|
49
|
+
}
|
|
50
|
+
return d;
|
|
51
|
+
}
|
|
52
|
+
/** Can the current user create/write inside `dir` (probing its nearest existing ancestor)? */
|
|
53
|
+
export function isWritable(dir) {
|
|
54
|
+
const target = firstExistingAncestor(dir);
|
|
55
|
+
if (!target)
|
|
56
|
+
return false;
|
|
57
|
+
try {
|
|
58
|
+
accessSync(target, constants.W_OK);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** The user-owned prefix we reconfigure npm to when the system one is read-only. */
|
|
66
|
+
export function userNpmPrefix() {
|
|
67
|
+
return join(homedir(), '.npm-global');
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=npmGlobal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npmGlobal.js","sourceRoot":"","sources":["../../src/utils/npmGlobal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEzC,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,iFAAiF;AACjF,+EAA+E;AAC/E,4EAA4E;AAC5E,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAA;AAEvE;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CACpB,IAAc,EACd,OAA8B,EAAE;IAEhC,OAAO,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE;QAC9B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;QAC5D,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;KACpC,CAAC,CAAA;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACrD,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AAChE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,CAAS;IAC7C,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;QACzB,IAAI,MAAM,KAAK,CAAC;YAAE,MAAK,CAAC,0BAA0B;QAClD,CAAC,GAAG,MAAM,CAAA;IACZ,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAA;IACzC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACzB,IAAI,CAAC;QACH,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;QAClC,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAA;AACvC,CAAC"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/** Numeric-dotted compare: is published version `a` newer than running `b`? */
|
|
2
2
|
export declare function isNewerVersion(a: string, b: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Ask the npm registry for hacklab's latest published version. Timeout-guarded
|
|
5
|
+
* and never throws — a failed/slow lookup resolves to undefined so callers (the
|
|
6
|
+
* daily nag, `hacklab update`) can degrade gracefully rather than block.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fetchLatest(): Promise<string | undefined>;
|
|
3
9
|
/**
|
|
4
10
|
* Print a one-line "newer version available" nudge to stderr. No-ops (instantly,
|
|
5
11
|
* no network) when stdout isn't a TTY (piped / CI / `--json` consumers) or when
|