@buildinternet/uploads 0.29.0 → 0.30.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/dist/client.d.ts CHANGED
@@ -82,6 +82,24 @@ export interface PutResult {
82
82
  * non-`gh/` key, existing object, no `replace`) instead of overwriting.
83
83
  */
84
84
  wouldRefuse?: boolean;
85
+ /**
86
+ * The object's R2 provenance bag (`client`, `source-name`,
87
+ * `content-sha256`) — what the upload was made *by*, not the tags it was
88
+ * tagged *with*. Absent on a dry run, and on API deployments older than the
89
+ * split that gave this bag its own name.
90
+ */
91
+ provenance?: Record<string, string>;
92
+ /**
93
+ * The queryable metadata (D1) this put stored, including server-derived
94
+ * pairs the client never sent (`gh.uploader`). Absent when the put carried
95
+ * no metadata — that case leaves any existing tags untouched, so the server
96
+ * reports nothing rather than implying an empty set. Means the same thing
97
+ * on `getMetadata`, `patchMetadata`, and `list({ metadata: true })`.
98
+ *
99
+ * An API older than the split returns the provenance bag here instead, so a
100
+ * client that must work against both reads `path`-style tags defensively —
101
+ * see `pathMetaHintFor` in commands.ts, which prefers what it sent.
102
+ */
85
103
  metadata?: Record<string, string>;
86
104
  }
87
105
  export interface ListItem {
@@ -106,7 +124,12 @@ export interface HeadResult {
106
124
  size: number;
107
125
  contentType: string;
108
126
  uploaded?: string;
109
- metadata?: Record<string, string>;
127
+ /**
128
+ * The object's R2 provenance bag — see `PutResult.provenance`. A plain head
129
+ * returns no queryable metadata at all: that tier lives in a separate store
130
+ * and takes a separate read, so call `getMetadata(key)` for it.
131
+ */
132
+ provenance?: Record<string, string>;
110
133
  }
111
134
  export interface DeleteResult {
112
135
  key: string;
@@ -123,6 +123,13 @@ export interface UploadPreparedImageResult {
123
123
  result: PutResult;
124
124
  prepared: PreparedUpload;
125
125
  markdown: string;
126
+ /**
127
+ * The queryable metadata this upload actually sent — `opts.metadata` after
128
+ * any derived image facts were merged in. Callers that need to reason about
129
+ * what was stored (see `pathMetaHintFor`) must read this, not
130
+ * `result.metadata`, which is the API's R2 provenance echo.
131
+ */
132
+ sentMetadata?: Record<string, string>;
126
133
  }
127
134
  /**
128
135
  * Shared bytes-oriented upload tail: frame + optimize the bytes, resolve the
@@ -182,15 +189,22 @@ export declare function syncAttachmentsComment(client: UploadsClient, target: Gh
182
189
  * (same tier as `state=`), and unlike `uploads screenshot` (which derives it
183
190
  * from the captured URL), a plain `attach`/`put --pr`/`put --issue` of an
184
191
  * already-existing image has nothing to derive it from, so it's easy to
185
- * forget. Fires once per batch (not per file) checks the metadata the
186
- * server actually stored (`PutResult.metadata`), not what was requested, so
187
- * a merge/validation drop still surfaces the gap. Non-image uploads (zips,
192
+ * forget. Fires once per batch (not per file). Non-image uploads (zips,
188
193
  * PDFs, etc.) are exempt — "findable by page" doesn't apply to them.
194
+ *
195
+ * Checks the *resolved* metadata each upload actually sent (`--meta` pairs +
196
+ * sidecar manifest + derived image facts, index-aligned with `uploads`) —
197
+ * NOT `PutResult.metadata`. That field is the API's echo of the object's R2
198
+ * provenance bag (`client`, `source-name`, `content-sha256`, `uploaded-at`),
199
+ * never the queryable D1 tags, so it can't answer this question: reading it
200
+ * made the tip fire on every image, including ones uploaded with an explicit
201
+ * `--meta path=` (PR #509).
189
202
  */
190
- export declare function pathMetaHintFor(uploads: {
203
+ export declare function pathMetaHintFor(uploads: readonly {
191
204
  contentType: string;
192
- metadata?: Record<string, string>;
193
- }[]): string | undefined;
205
+ }[],
206
+ /** Index-aligned with `uploads` — see `uploadPuts`/`uploadAttachments`. */
207
+ sentMetadata: readonly (Record<string, string> | undefined)[]): string | undefined;
194
208
  export type AttachUploadItem = PutResult & {
195
209
  file: string;
196
210
  markdown: string;
@@ -211,6 +225,20 @@ export type AttachFailure = {
211
225
  status?: number;
212
226
  };
213
227
  };
228
+ /** Shared shape of every prepare + put batch (`uploadPuts`/`uploadAttachments`). */
229
+ export interface UploadBatchResult<T> {
230
+ uploads: T[];
231
+ failures: AttachFailure[];
232
+ /** The original cause of the first failure — for rethrowing single-file CLI paths. */
233
+ firstError?: unknown;
234
+ /**
235
+ * Index-aligned with `uploads`: the queryable metadata each upload actually
236
+ * sent (flags + sidecar + derived image facts). Kept beside the items rather
237
+ * than on them so it stays out of the `--format json` upload objects, which
238
+ * spread the item wholesale. See `pathMetaHintFor`.
239
+ */
240
+ sentMetadata: (Record<string, string> | undefined)[];
241
+ }
214
242
  /**
215
243
  * Prepare + put each path as a PR/issue attachment with bounded concurrency.
216
244
  * Per-file errors collect in `failures` (does not throw). `firstError` is the
@@ -233,11 +261,7 @@ export declare function uploadAttachments(opts: {
233
261
  /** Provenance `client` field (default uploads-cli). */
234
262
  provenanceClient?: string;
235
263
  concurrency?: number;
236
- }): Promise<{
237
- uploads: AttachUploadItem[];
238
- failures: AttachFailure[];
239
- firstError?: unknown;
240
- }>;
264
+ }): Promise<UploadBatchResult<AttachUploadItem>>;
241
265
  /** A branch to stage attachments against pre-PR (`uploads attach --branch`). */
242
266
  export interface BranchTarget {
243
267
  repo: string;
@@ -266,11 +290,7 @@ export declare function uploadBranchAttachments(opts: {
266
290
  deriveImageFacts?: boolean;
267
291
  provenanceClient?: string;
268
292
  concurrency?: number;
269
- }): Promise<{
270
- uploads: AttachUploadItem[];
271
- failures: AttachFailure[];
272
- firstError?: unknown;
273
- }>;
293
+ }): Promise<UploadBatchResult<AttachUploadItem>>;
274
294
  export type PutUploadItem = PutResult & {
275
295
  file: string;
276
296
  markdown: string;
@@ -317,11 +337,7 @@ export declare function uploadPuts(opts: {
317
337
  alt?: string;
318
338
  width?: number;
319
339
  concurrency?: number;
320
- }): Promise<{
321
- uploads: PutUploadItem[];
322
- failures: AttachFailure[];
323
- firstError?: unknown;
324
- }>;
340
+ }): Promise<UploadBatchResult<PutUploadItem>>;
325
341
  export declare function runAttach(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
326
342
  /**
327
343
  * One source of truth for the "staged, but not going to auto-attach" advisory
package/dist/commands.js CHANGED
@@ -410,7 +410,7 @@ export async function uploadPreparedImage(client, bytes, sourceName, opts) {
410
410
  alt: opts.alt(prepared),
411
411
  width: opts.width,
412
412
  });
413
- return { result, prepared, markdown };
413
+ return { result, prepared, markdown, sentMetadata: metadata };
414
414
  }
415
415
  export function frameOptionsFromFlags(flags) {
416
416
  const raw = flagString(flags, "--frame");
@@ -647,13 +647,21 @@ Examples:
647
647
  * (same tier as `state=`), and unlike `uploads screenshot` (which derives it
648
648
  * from the captured URL), a plain `attach`/`put --pr`/`put --issue` of an
649
649
  * already-existing image has nothing to derive it from, so it's easy to
650
- * forget. Fires once per batch (not per file) checks the metadata the
651
- * server actually stored (`PutResult.metadata`), not what was requested, so
652
- * a merge/validation drop still surfaces the gap. Non-image uploads (zips,
650
+ * forget. Fires once per batch (not per file). Non-image uploads (zips,
653
651
  * PDFs, etc.) are exempt — "findable by page" doesn't apply to them.
652
+ *
653
+ * Checks the *resolved* metadata each upload actually sent (`--meta` pairs +
654
+ * sidecar manifest + derived image facts, index-aligned with `uploads`) —
655
+ * NOT `PutResult.metadata`. That field is the API's echo of the object's R2
656
+ * provenance bag (`client`, `source-name`, `content-sha256`, `uploaded-at`),
657
+ * never the queryable D1 tags, so it can't answer this question: reading it
658
+ * made the tip fire on every image, including ones uploaded with an explicit
659
+ * `--meta path=` (PR #509).
654
660
  */
655
- export function pathMetaHintFor(uploads) {
656
- const missingPath = uploads.some((u) => u.contentType.startsWith("image/") && !u.metadata?.path);
661
+ export function pathMetaHintFor(uploads,
662
+ /** Index-aligned with `uploads` see `uploadPuts`/`uploadAttachments`. */
663
+ sentMetadata) {
664
+ const missingPath = uploads.some((u, i) => u.contentType.startsWith("image/") && !sentMetadata[i]?.path);
657
665
  return missingPath ? "tip: add --meta path=/route so this shot is findable by page" : undefined;
658
666
  }
659
667
  /**
@@ -697,6 +705,7 @@ async function uploadAttachmentBatch(opts) {
697
705
  });
698
706
  return {
699
707
  ok: true,
708
+ sentMetadata: metadata,
700
709
  upload: {
701
710
  ...result,
702
711
  file,
@@ -719,17 +728,21 @@ async function uploadAttachmentBatch(opts) {
719
728
  }
720
729
  });
721
730
  const uploads = [];
731
+ const sentMetadata = [];
722
732
  const failures = [];
723
733
  let firstError;
724
734
  for (const slot of slots) {
725
- if (slot.ok)
735
+ // Pushed together so the two arrays stay index-aligned across failures.
736
+ if (slot.ok) {
726
737
  uploads.push(slot.upload);
738
+ sentMetadata.push(slot.sentMetadata);
739
+ }
727
740
  else {
728
741
  firstError ??= slot.err;
729
742
  failures.push({ file: slot.file, error: errorDetail(slot.err) });
730
743
  }
731
744
  }
732
- return { uploads, failures, firstError };
745
+ return { uploads, failures, firstError, sentMetadata };
733
746
  }
734
747
  /**
735
748
  * Prepare + put each path as a PR/issue attachment with bounded concurrency.
@@ -786,7 +799,7 @@ export async function uploadPuts(opts) {
786
799
  // Sidecar manifest from a prior `screenshot --out` of this exact file
787
800
  // (issue #469 lever 2) — see mergeSidecarMeta. Not applicable to stdin.
788
801
  const metadata = file !== "-" ? mergeSidecarMeta(file, bytes, opts.metadata) : opts.metadata;
789
- const { result, prepared, markdown } = await uploadPreparedImage(opts.client, bytes, sourceName, {
802
+ const { result, prepared, markdown, sentMetadata } = await uploadPreparedImage(opts.client, bytes, sourceName, {
790
803
  frame: opts.frame,
791
804
  optimize: opts.optimize,
792
805
  ghTarget: opts.ghTarget,
@@ -807,6 +820,7 @@ export async function uploadPuts(opts) {
807
820
  });
808
821
  return {
809
822
  ok: true,
823
+ sentMetadata,
810
824
  upload: {
811
825
  ...result,
812
826
  file,
@@ -827,17 +841,21 @@ export async function uploadPuts(opts) {
827
841
  }
828
842
  });
829
843
  const uploads = [];
844
+ const sentMetadata = [];
830
845
  const failures = [];
831
846
  let firstError;
832
847
  for (const slot of slots) {
833
- if (slot.ok)
848
+ // Pushed together so the two arrays stay index-aligned across failures.
849
+ if (slot.ok) {
834
850
  uploads.push(slot.upload);
851
+ sentMetadata.push(slot.sentMetadata);
852
+ }
835
853
  else {
836
854
  firstError ??= slot.err;
837
855
  failures.push({ file: slot.file, error: errorDetail(slot.err) });
838
856
  }
839
857
  }
840
- return { uploads, failures, firstError };
858
+ return { uploads, failures, firstError, sentMetadata };
841
859
  }
842
860
  /**
843
861
  * Best-effort call to `POST /v1/:workspace/github/promote` (server contract,
@@ -931,7 +949,7 @@ export async function runAttach(ctx, args, help = false, run = execRunner) {
931
949
  const n = parsed.positionals.length;
932
950
  process.stderr.write(`>> uploading ${n} file${n === 1 ? "" : "s"}\n`);
933
951
  }
934
- const { uploads, failures, firstError } = await uploadAttachments({
952
+ const { uploads, failures, firstError, sentMetadata } = await uploadAttachments({
935
953
  client: ctx.client,
936
954
  target,
937
955
  files: parsed.positionals,
@@ -979,7 +997,7 @@ export async function runAttach(ctx, args, help = false, run = execRunner) {
979
997
  }
980
998
  }
981
999
  // Lever 3 (issue #469): tip when an image lands here with no `path` meta.
982
- const pathHint = uploads.length > 0 && !ctx.quiet ? pathMetaHintFor(uploads) : undefined;
1000
+ const pathHint = uploads.length > 0 && !ctx.quiet ? pathMetaHintFor(uploads, sentMetadata) : undefined;
983
1001
  if (ctx.json) {
984
1002
  await writeJson({
985
1003
  target,
@@ -1672,7 +1690,7 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
1672
1690
  if (attachedRef)
1673
1691
  process.stderr.write(`>> attached to ${attachedRef}\n`);
1674
1692
  }
1675
- const { uploads, failures, firstError } = await uploadPuts({
1693
+ const { uploads, failures, firstError, sentMetadata } = await uploadPuts({
1676
1694
  client: ctx.client,
1677
1695
  files,
1678
1696
  nameOverride: nameFlag,
@@ -1707,7 +1725,9 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
1707
1725
  // `path` meta. Only relevant on the ghTarget path — the bare-put paths
1708
1726
  // above (staging/auto/dated) aren't attached to a PR/issue yet, so there's
1709
1727
  // nothing to look up from a page later.
1710
- const pathHint = ghTarget && uploads.length > 0 && !ctx.quiet ? pathMetaHintFor(uploads) : undefined;
1728
+ const pathHint = ghTarget && uploads.length > 0 && !ctx.quiet
1729
+ ? pathMetaHintFor(uploads, sentMetadata)
1730
+ : undefined;
1711
1731
  // One JSON `hint` slot, shared with the #393 nudge (mutually exclusive with
1712
1732
  // it — nudge is undefined whenever staging took over). When staging fires,
1713
1733
  // prefer the more actionable binding warning over the generic staging note
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildinternet/uploads",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "CLI and client for uploads.sh — workspace-scoped image hosting for GitHub embeds",
5
5
  "type": "module",
6
6
  "sideEffects": false,