@buildinternet/uploads 0.26.1 → 0.28.0
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 +5 -0
- package/dist/cli-brand.js +4 -1
- package/dist/cli-catalog.js +6 -0
- package/dist/cli-help.js +1 -0
- package/dist/cli.js +4 -0
- package/dist/client.d.ts +8 -0
- package/dist/client.js +7 -0
- package/dist/commands/install.d.ts +8 -0
- package/dist/commands/install.js +1 -1
- package/dist/commands/screenshot.js +85 -12
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +125 -0
- package/dist/commands.d.ts +33 -1
- package/dist/commands.js +121 -15
- package/dist/github-gh.d.ts +23 -1
- package/dist/github-gh.js +59 -8
- package/dist/github.d.ts +6 -0
- package/dist/github.js +12 -0
- package/dist/install-source.d.ts +14 -0
- package/dist/install-source.js +42 -0
- package/dist/mcp/tools.js +42 -23
- package/dist/sidecar.d.ts +31 -0
- package/dist/sidecar.js +111 -0
- package/dist/update-check.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** The sidecar path for a given local file — `<file>.uploads.json`. */
|
|
2
|
+
export declare function sidecarPath(filePath: string): string;
|
|
3
|
+
/** Hex-encoded SHA-256 of `bytes`. */
|
|
4
|
+
export declare function sha256Hex(bytes: Uint8Array): string;
|
|
5
|
+
/** Keep only entries whose key is in the closed canonical metadata vocabulary. */
|
|
6
|
+
export declare function restrictToCanonicalMeta(meta: Record<string, string>): Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Write a sidecar manifest next to `filePath`, recording `meta` (restricted
|
|
9
|
+
* to the canonical vocabulary) and the SHA-256 of `bytes` (the exact bytes
|
|
10
|
+
* being written to `filePath`). No-ops when `meta` is empty — an image with
|
|
11
|
+
* no derived metadata gets no sidecar. Best-effort: a write failure (e.g. a
|
|
12
|
+
* read-only directory) is swallowed, matching the rest of the derived-
|
|
13
|
+
* metadata pipeline's "never fail the primary operation" contract.
|
|
14
|
+
*/
|
|
15
|
+
export declare function writeSidecarMeta(filePath: string, bytes: Uint8Array, meta: Record<string, string>): void;
|
|
16
|
+
/**
|
|
17
|
+
* Read back a sidecar manifest for `filePath`, only when it is present,
|
|
18
|
+
* well-formed, and its recorded hash matches `bytes` (the file's current
|
|
19
|
+
* content, as read for the upload in progress). Returns `undefined` on any
|
|
20
|
+
* absence, parse failure, malformed shape, or hash mismatch — a sidecar is a
|
|
21
|
+
* best-effort convenience and must never fail or noise an upload. Returned
|
|
22
|
+
* keys are always a subset of `CANONICAL_META_KEYS`, so a hand-edited
|
|
23
|
+
* manifest can never inject arbitrary metadata.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readSidecarMeta(filePath: string, bytes: Uint8Array): Record<string, string> | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Merge `filePath`'s sidecar metadata (if any, per {@link readSidecarMeta})
|
|
28
|
+
* under `baseMeta` — explicit metadata always wins. Shared by the `put` and
|
|
29
|
+
* `attach` upload loops (issue #469 lever 2).
|
|
30
|
+
*/
|
|
31
|
+
export declare function mergeSidecarMeta(filePath: string, bytes: Uint8Array, baseMeta: Record<string, string> | undefined): Record<string, string> | undefined;
|
package/dist/sidecar.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidecar manifest for `uploads screenshot --out`: derived metadata written
|
|
3
|
+
* next to a local screenshot file so a later `put`/`attach` of that exact
|
|
4
|
+
* file can recover the metadata the hosted copy would have gotten at capture
|
|
5
|
+
* time. See issue #469 lever 2 (the "sidecar manifest" variant — the
|
|
6
|
+
* alternative, content-hash-keyed server-side inheritance, is out of scope
|
|
7
|
+
* here).
|
|
8
|
+
*
|
|
9
|
+
* File: `<file>.uploads.json` next to `<file>`, e.g. `shot.png.uploads.json`.
|
|
10
|
+
* Shape: `{ version, sha256, meta }`. `sha256` is the SHA-256 of the exact
|
|
11
|
+
* bytes written to `<file>` at capture time — read-back compares it against
|
|
12
|
+
* the file's *current* bytes, so a file that was regenerated or hand-edited
|
|
13
|
+
* since capture silently loses its sidecar instead of attaching stale
|
|
14
|
+
* metadata to a different image.
|
|
15
|
+
*
|
|
16
|
+
* `meta` is filtered to the closed `CANONICAL_META_KEYS` vocabulary
|
|
17
|
+
* (metadata-vocab.ts) on both write and read: the sidecar is a plain JSON
|
|
18
|
+
* file sitting next to the image, so it must never be a channel for
|
|
19
|
+
* arbitrary metadata even if hand-edited.
|
|
20
|
+
*/
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { CANONICAL_META_KEYS, mergeDerivedMeta } from "./metadata-vocab.js";
|
|
24
|
+
const SIDECAR_VERSION = 1;
|
|
25
|
+
/** The sidecar path for a given local file — `<file>.uploads.json`. */
|
|
26
|
+
export function sidecarPath(filePath) {
|
|
27
|
+
return `${filePath}.uploads.json`;
|
|
28
|
+
}
|
|
29
|
+
/** Hex-encoded SHA-256 of `bytes`. */
|
|
30
|
+
export function sha256Hex(bytes) {
|
|
31
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
32
|
+
}
|
|
33
|
+
/** Keep only entries whose key is in the closed canonical metadata vocabulary. */
|
|
34
|
+
export function restrictToCanonicalMeta(meta) {
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const key of CANONICAL_META_KEYS) {
|
|
37
|
+
if (Object.prototype.hasOwnProperty.call(meta, key))
|
|
38
|
+
out[key] = meta[key];
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function isPlainStringRecord(value) {
|
|
43
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
44
|
+
return false;
|
|
45
|
+
return Object.values(value).every((v) => typeof v === "string");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write a sidecar manifest next to `filePath`, recording `meta` (restricted
|
|
49
|
+
* to the canonical vocabulary) and the SHA-256 of `bytes` (the exact bytes
|
|
50
|
+
* being written to `filePath`). No-ops when `meta` is empty — an image with
|
|
51
|
+
* no derived metadata gets no sidecar. Best-effort: a write failure (e.g. a
|
|
52
|
+
* read-only directory) is swallowed, matching the rest of the derived-
|
|
53
|
+
* metadata pipeline's "never fail the primary operation" contract.
|
|
54
|
+
*/
|
|
55
|
+
export function writeSidecarMeta(filePath, bytes, meta) {
|
|
56
|
+
const restricted = restrictToCanonicalMeta(meta);
|
|
57
|
+
if (Object.keys(restricted).length === 0)
|
|
58
|
+
return;
|
|
59
|
+
try {
|
|
60
|
+
const manifest = {
|
|
61
|
+
version: SIDECAR_VERSION,
|
|
62
|
+
sha256: sha256Hex(bytes),
|
|
63
|
+
meta: restricted,
|
|
64
|
+
};
|
|
65
|
+
writeFileSync(sidecarPath(filePath), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// best-effort — never fail the screenshot over a sidecar write
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read back a sidecar manifest for `filePath`, only when it is present,
|
|
73
|
+
* well-formed, and its recorded hash matches `bytes` (the file's current
|
|
74
|
+
* content, as read for the upload in progress). Returns `undefined` on any
|
|
75
|
+
* absence, parse failure, malformed shape, or hash mismatch — a sidecar is a
|
|
76
|
+
* best-effort convenience and must never fail or noise an upload. Returned
|
|
77
|
+
* keys are always a subset of `CANONICAL_META_KEYS`, so a hand-edited
|
|
78
|
+
* manifest can never inject arbitrary metadata.
|
|
79
|
+
*/
|
|
80
|
+
export function readSidecarMeta(filePath, bytes) {
|
|
81
|
+
const path = sidecarPath(filePath);
|
|
82
|
+
try {
|
|
83
|
+
if (!existsSync(path))
|
|
84
|
+
return undefined;
|
|
85
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
86
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
87
|
+
return undefined;
|
|
88
|
+
const candidate = parsed;
|
|
89
|
+
if (candidate.version !== SIDECAR_VERSION ||
|
|
90
|
+
typeof candidate.sha256 !== "string" ||
|
|
91
|
+
!isPlainStringRecord(candidate.meta)) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
if (candidate.sha256 !== sha256Hex(bytes))
|
|
95
|
+
return undefined; // stale/regenerated file
|
|
96
|
+
const restricted = restrictToCanonicalMeta(candidate.meta);
|
|
97
|
+
return Object.keys(restricted).length > 0 ? restricted : undefined;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Merge `filePath`'s sidecar metadata (if any, per {@link readSidecarMeta})
|
|
105
|
+
* under `baseMeta` — explicit metadata always wins. Shared by the `put` and
|
|
106
|
+
* `attach` upload loops (issue #469 lever 2).
|
|
107
|
+
*/
|
|
108
|
+
export function mergeSidecarMeta(filePath, bytes, baseMeta) {
|
|
109
|
+
const sidecarMeta = readSidecarMeta(filePath, bytes);
|
|
110
|
+
return sidecarMeta ? mergeDerivedMeta(baseMeta ?? {}, sidecarMeta) : baseMeta;
|
|
111
|
+
}
|
package/dist/update-check.js
CHANGED
|
@@ -116,7 +116,7 @@ export async function maybeHintUpdate(opts = {}) {
|
|
|
116
116
|
if (!status.updateAvailable || !status.latest)
|
|
117
117
|
return;
|
|
118
118
|
const write = opts.write ?? ((text) => process.stderr.write(text));
|
|
119
|
-
write(`hint: ${PACKAGE_NAME}@${status.latest} is available (you have ${status.current}). Update:
|
|
119
|
+
write(`hint: ${PACKAGE_NAME}@${status.latest} is available (you have ${status.current}). Update: uploads update\n`);
|
|
120
120
|
}
|
|
121
121
|
catch {
|
|
122
122
|
// Never surface update-check failures.
|