@crouton-kit/capture 1.3.4 → 1.3.6

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,87 @@
1
+ /**
2
+ * vault/paths — locate the forked vault lib source on disk + the dev-only guard.
3
+ *
4
+ * Shared foundation for the import-driven exec bundler (`bundle.ts`) and the
5
+ * on-demand doc generator (`docs.ts`). This module must NOT import `esbuild` at
6
+ * the top level — esbuild is loaded lazily (see `loadEsbuild`) so the published
7
+ * package (which ships neither `vault/` nor `esbuild`) never crashes at startup.
8
+ */
9
+
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+
13
+ /**
14
+ * The single dev-only degradation message. Thrown when `vault/libs` is missing
15
+ * (published package) or when `esbuild` cannot be loaded. (design §2.6, verbatim)
16
+ */
17
+ export const DEV_ONLY_MSG =
18
+ "This is a dev-only feature of capture — run it from a capture checkout\n" +
19
+ "(vault/ source + `pnpm install`). Not available in the published package.";
20
+
21
+ /**
22
+ * Absolute path to the forked `vault/libs` directory. Dual `__dirname`
23
+ * candidates cover the built bin (`bin/../vault/libs`) and tsx dev
24
+ * (`src/vault/../../vault/libs`) — mirrors the `__dirname`+`..` precedent in
25
+ * `capture.ts`. Throws `DEV_ONLY_MSG` if neither candidate holds the source.
26
+ */
27
+ export function vaultLibsDir(): string {
28
+ const candidates = [
29
+ path.resolve(__dirname, "..", "vault", "libs"), // built bin: bin/../vault/libs
30
+ path.resolve(__dirname, "..", "..", "vault", "libs"), // tsx dev: src/vault/../../vault/libs
31
+ ];
32
+ const hit = candidates.find((p) =>
33
+ fs.existsSync(path.join(p, "_runtime", "index.ts"))
34
+ );
35
+ if (!hit) throw new Error(DEV_ONLY_MSG);
36
+ return hit;
37
+ }
38
+
39
+ /**
40
+ * The `vault/` directory (parent of `vault/libs`). Used as esbuild's
41
+ * `resolveDir` so its native node-resolution finds `node_modules/zod` etc.
42
+ */
43
+ export function vaultRepoRoot(): string {
44
+ return path.dirname(vaultLibsDir());
45
+ }
46
+
47
+ /**
48
+ * Sorted list of available lib names — directories under `vault/libs`,
49
+ * excluding `_`-prefixed internal dirs (e.g. `_runtime`). Used by the
50
+ * resolver's "Available: …" error and by the docs/lib surfaces.
51
+ */
52
+ export function listLibs(): string[] {
53
+ return fs
54
+ .readdirSync(vaultLibsDir(), { withFileTypes: true })
55
+ .filter((e) => e.isDirectory() && !e.name.startsWith("_"))
56
+ .map((e) => e.name)
57
+ .sort();
58
+ }
59
+
60
+ /**
61
+ * The friendly "Unknown lib" message, shared by the exec resolver/preflight
62
+ * (`bundle.ts`) and the doc generator (`docs.ts`) so an unknown OR malformed
63
+ * lib name surfaces byte-identical text everywhere.
64
+ */
65
+ export function unknownLibMsg(name: string): string {
66
+ return `Unknown lib "${name}". Available: ${listLibs().join(", ")}`;
67
+ }
68
+
69
+ // A lib name is a single path segment of `[A-Za-z0-9_.-]` (covers `hunter.io`)
70
+ // with no `..`. Rejects path separators / traversal (`libs/../x`, `libs/a/b`)
71
+ // before a name reaches `path.join`/esbuild — defense-in-depth (exec already
72
+ // runs arbitrary JS, so this only buys a clearer error, not isolation).
73
+ const VALID_LIB_NAME = /^[A-Za-z0-9_.-]+$/;
74
+ export function isValidLibName(name: string): boolean {
75
+ return VALID_LIB_NAME.test(name) && !name.includes("..");
76
+ }
77
+
78
+ /**
79
+ * Lazily load `esbuild`. Kept here so both `bundle.ts` and `docs.ts` share one
80
+ * lazy loader and the top-level module never statically imports esbuild — a
81
+ * published install (no esbuild) only fails when the feature path is exercised.
82
+ */
83
+ export async function loadEsbuild(): Promise<typeof import("esbuild")> {
84
+ return await import("esbuild").catch(() => {
85
+ throw new Error(DEV_ONLY_MSG);
86
+ });
87
+ }