@buildinternet/uploads 0.49.0 → 0.50.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
|
@@ -441,6 +441,8 @@ export interface UsageResult {
|
|
|
441
441
|
uploadsRemaining?: number;
|
|
442
442
|
/** Bytes still on hosted storage (shared-lane residue). */
|
|
443
443
|
sharedBytes?: number;
|
|
444
|
+
/** Objects still on hosted storage (shared-lane residue). */
|
|
445
|
+
sharedObjects?: number;
|
|
444
446
|
/** "shared" = BYO bucket active: the storage cap meters only hosted
|
|
445
447
|
* residue; the customer's own bucket is unmetered. */
|
|
446
448
|
storageBudgetBasis?: "total" | "shared";
|
package/dist/commands.js
CHANGED
|
@@ -649,6 +649,13 @@ export async function syncAttachmentsComment(client, target, run, workspace, opt
|
|
|
649
649
|
// byte-identical.
|
|
650
650
|
const path = metadata?.path;
|
|
651
651
|
const state = metadata?.state;
|
|
652
|
+
// Server-derived image dimensions (parity with the bot path's
|
|
653
|
+
// COMMENT_META_KEYS hydration): finite positive numbers only, field
|
|
654
|
+
// omitted entirely when neither parses.
|
|
655
|
+
const imgWidth = Number(metadata?.["image.width"]);
|
|
656
|
+
const imgHeight = Number(metadata?.["image.height"]);
|
|
657
|
+
const hasImgWidth = Number.isFinite(imgWidth) && imgWidth > 0;
|
|
658
|
+
const hasImgHeight = Number.isFinite(imgHeight) && imgHeight > 0;
|
|
652
659
|
return {
|
|
653
660
|
key,
|
|
654
661
|
url,
|
|
@@ -657,6 +664,14 @@ export async function syncAttachmentsComment(client, target, run, workspace, opt
|
|
|
657
664
|
...(path || state
|
|
658
665
|
? { meta: { ...(path ? { path } : {}), ...(state ? { state } : {}) } }
|
|
659
666
|
: {}),
|
|
667
|
+
...(hasImgWidth || hasImgHeight
|
|
668
|
+
? {
|
|
669
|
+
imageMeta: {
|
|
670
|
+
...(hasImgWidth ? { width: imgWidth } : {}),
|
|
671
|
+
...(hasImgHeight ? { height: imgHeight } : {}),
|
|
672
|
+
},
|
|
673
|
+
}
|
|
674
|
+
: {}),
|
|
660
675
|
};
|
|
661
676
|
}));
|
|
662
677
|
const galleries = [];
|
|
@@ -107,6 +107,11 @@ export interface AttachmentItem {
|
|
|
107
107
|
width?: number;
|
|
108
108
|
height?: number;
|
|
109
109
|
};
|
|
110
|
+
/** Server-derived pixel dimensions for an image (never client-settable). */
|
|
111
|
+
imageMeta?: {
|
|
112
|
+
width?: number;
|
|
113
|
+
height?: number;
|
|
114
|
+
};
|
|
110
115
|
}
|
|
111
116
|
/** A public gallery linked to the PR or issue whose managed comment is syncing. */
|
|
112
117
|
export interface GalleryCommentItem {
|
|
@@ -144,6 +149,11 @@ export declare function attachmentPairWidth(density?: AttachmentDensity): number
|
|
|
144
149
|
* only affects managed-comment auto layout; other callers leave it `"dense"`.
|
|
145
150
|
*/
|
|
146
151
|
export declare function attachmentImageWidth(filename: string, density?: AttachmentDensity): number;
|
|
152
|
+
/**
|
|
153
|
+
* Icons and other small assets: both dimensions known and no edge above this
|
|
154
|
+
* flow inline at natural size rather than each becoming a full row.
|
|
155
|
+
*/
|
|
156
|
+
export declare const SMALL_ASSET_MAX_EDGE = 200;
|
|
147
157
|
/**
|
|
148
158
|
* Render the one marker-owned GitHub comment. When there are no galleries this
|
|
149
159
|
* intentionally preserves the legacy attachment-only body byte-for-byte.
|
|
@@ -183,19 +183,32 @@ function formatDuration(seconds) {
|
|
|
183
183
|
return `${h}:${String(m).padStart(2, "0")}:${ss}`;
|
|
184
184
|
}
|
|
185
185
|
/**
|
|
186
|
-
* Display width for
|
|
187
|
-
* density table's tiers — a raw 1920 would blow out the
|
|
188
|
-
* the result is capped at the real width so a small
|
|
186
|
+
* Display width for an image or video-poster embed. Real dimensions only
|
|
187
|
+
* *select* among the density table's tiers — a raw 1920 would blow out the
|
|
188
|
+
* comment column — and the result is capped at the real width so a small
|
|
189
|
+
* asset is never upscaled into a blurry tile. Without dims (either missing),
|
|
190
|
+
* falls back to the filename heuristic exactly, keeping dimension-less
|
|
191
|
+
* renders byte-identical to before dims existed.
|
|
189
192
|
*/
|
|
190
|
-
function
|
|
191
|
-
const w =
|
|
192
|
-
const h =
|
|
193
|
+
function naturalMediaWidth(dims, filename, density = "dense") {
|
|
194
|
+
const w = dims?.width ?? 0;
|
|
195
|
+
const h = dims?.height ?? 0;
|
|
193
196
|
if (w <= 0 || h <= 0)
|
|
194
197
|
return attachmentImageWidth(filename, density);
|
|
195
198
|
const table = WIDTH_BY_DENSITY[density];
|
|
196
199
|
const chosen = h > w ? table.portrait : w / h >= 16 / 9 ? table.wide : table.default;
|
|
197
200
|
return Math.min(chosen, w);
|
|
198
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Icons and other small assets: both dimensions known and no edge above this
|
|
204
|
+
* flow inline at natural size rather than each becoming a full row.
|
|
205
|
+
*/
|
|
206
|
+
export const SMALL_ASSET_MAX_EDGE = 200;
|
|
207
|
+
function isSmallAsset(dims) {
|
|
208
|
+
const w = dims?.width ?? 0;
|
|
209
|
+
const h = dims?.height ?? 0;
|
|
210
|
+
return w > 0 && h > 0 && Math.max(w, h) <= SMALL_ASSET_MAX_EDGE;
|
|
211
|
+
}
|
|
199
212
|
function escapeHtmlAttr(s) {
|
|
200
213
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
201
214
|
}
|
|
@@ -360,7 +373,7 @@ function renderPairCell(item, label, options, density) {
|
|
|
360
373
|
const name = item.key.slice(item.key.lastIndexOf("/") + 1);
|
|
361
374
|
const src = item.embedUrl ?? item.url;
|
|
362
375
|
const link = item.pageUrl ?? item.url;
|
|
363
|
-
const autoPx = Math.min(
|
|
376
|
+
const autoPx = Math.min(naturalMediaWidth(item.imageMeta, name, density), attachmentPairWidth(density));
|
|
364
377
|
const w = resolvedWidth(autoPx, options);
|
|
365
378
|
const alt = escapeHtmlAttr(name);
|
|
366
379
|
const href = escapeHtmlAttr((link ?? src));
|
|
@@ -480,7 +493,7 @@ export function attachmentsCommentBody(items, galleries = [], marker = ATTACHMEN
|
|
|
480
493
|
}
|
|
481
494
|
if (isPosterVideo) {
|
|
482
495
|
inlinedImages++;
|
|
483
|
-
const autoPx =
|
|
496
|
+
const autoPx = naturalMediaWidth(item.videoMeta, name, density);
|
|
484
497
|
const w = resolvedWidth(autoPx, options);
|
|
485
498
|
const href = escapeHtmlAttr(link ?? item.posterUrl);
|
|
486
499
|
lines.push(`<a href="${href}">${imgTag(w, escapeHtmlAttr(name), escapeHtmlAttr(item.posterUrl))}</a>`);
|
|
@@ -496,10 +509,53 @@ export function attachmentsCommentBody(items, galleries = [], marker = ATTACHMEN
|
|
|
496
509
|
lines.push(parts.join(" · "), "");
|
|
497
510
|
}
|
|
498
511
|
else if (isImage) {
|
|
512
|
+
// Small-asset flow layout: consecutive captionless small images (icons)
|
|
513
|
+
// join onto one line so GitHub flows them horizontally at natural size
|
|
514
|
+
// instead of stacking a giant column per icon. Auto mode only — a
|
|
515
|
+
// numeric imageWidth override or "full" keeps today's one-per-row form.
|
|
516
|
+
const smallGroupable = (candidate, i) => {
|
|
517
|
+
if (options.imageWidth !== "auto")
|
|
518
|
+
return false;
|
|
519
|
+
if (consumedByPair.has(i) || partnerOf.has(i))
|
|
520
|
+
return false;
|
|
521
|
+
if (!isSmallAsset(candidate.imageMeta))
|
|
522
|
+
return false;
|
|
523
|
+
if (metaCaptionValues(candidate.meta, options).length > 0)
|
|
524
|
+
return false;
|
|
525
|
+
const n = candidate.key.slice(candidate.key.lastIndexOf("/") + 1);
|
|
526
|
+
const s = candidate.embedUrl ?? candidate.url;
|
|
527
|
+
return Boolean(s) && inferContentType(n).startsWith("image/");
|
|
528
|
+
};
|
|
529
|
+
if (smallGroupable(item, idx)) {
|
|
530
|
+
const group = [item];
|
|
531
|
+
let j = idx + 1;
|
|
532
|
+
// Each grouped image still counts toward maxInlineImages.
|
|
533
|
+
while (j < sorted.length &&
|
|
534
|
+
inlinedImages + group.length < options.maxInlineImages &&
|
|
535
|
+
smallGroupable(sorted[j], j)) {
|
|
536
|
+
group.push(sorted[j]);
|
|
537
|
+
j++;
|
|
538
|
+
}
|
|
539
|
+
if (group.length > 1) {
|
|
540
|
+
inlinedImages += group.length;
|
|
541
|
+
lines.push(group
|
|
542
|
+
.map((g) => {
|
|
543
|
+
const gName = g.key.slice(g.key.lastIndexOf("/") + 1);
|
|
544
|
+
const gSrc = (g.embedUrl ?? g.url);
|
|
545
|
+
const gHref = escapeHtmlAttr((g.pageUrl ?? g.url ?? gSrc));
|
|
546
|
+
// Natural width — a small asset is never upscaled.
|
|
547
|
+
const gW = g.imageMeta?.width;
|
|
548
|
+
return `<a href="${gHref}">${imgTag(gW, escapeHtmlAttr(gName), escapeHtmlAttr(gSrc))}</a>`;
|
|
549
|
+
})
|
|
550
|
+
.join(" "), "");
|
|
551
|
+
idx = j - 1;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
499
555
|
inlinedImages++;
|
|
500
556
|
// Markdown ![]() has no width control — phone frames become full-column giants.
|
|
501
557
|
// img src uses embed host when available (Camo revalidates); click-through prefers the file page.
|
|
502
|
-
const autoPx =
|
|
558
|
+
const autoPx = naturalMediaWidth(item.imageMeta, name, density);
|
|
503
559
|
const w = resolvedWidth(autoPx, options);
|
|
504
560
|
const alt = escapeHtmlAttr(name);
|
|
505
561
|
const href = escapeHtmlAttr(link ?? src);
|
|
@@ -191,13 +191,30 @@ export const usageResultSchema = objectSchema({
|
|
|
191
191
|
workspace: { type: "string" },
|
|
192
192
|
bytes: { type: "number" },
|
|
193
193
|
objects: { type: "number" },
|
|
194
|
+
sharedBytes: { type: "number" },
|
|
195
|
+
sharedObjects: { type: "number" },
|
|
194
196
|
uploadsInPeriod: { type: "number" },
|
|
195
197
|
periodStart: { type: "string" },
|
|
196
198
|
updatedAt: { type: "string" },
|
|
199
|
+
storageBudgetBasis: { type: "string", enum: ["total", "shared"] },
|
|
197
200
|
maxStorageBytes: { type: "number" },
|
|
198
201
|
storageRemainingBytes: { type: "number" },
|
|
199
202
|
maxUploadsPerPeriod: { type: "number" },
|
|
200
203
|
uploadsRemaining: { type: "number" },
|
|
204
|
+
// GET /:workspace/usage (routes/workspace-usage.ts) also stamps these on
|
|
205
|
+
// every response — not part of `usageWithLimits`'s return, so easy to miss.
|
|
206
|
+
scopes: { type: "array", items: { type: "string" } },
|
|
207
|
+
plan: { type: "string" },
|
|
208
|
+
storage: objectSchema({
|
|
209
|
+
mode: { type: "string", enum: ["shared", "byo"] },
|
|
210
|
+
fallbackLanes: { type: "number" },
|
|
211
|
+
health: objectSchema({
|
|
212
|
+
ok: { type: "boolean" },
|
|
213
|
+
code: { type: "string" },
|
|
214
|
+
message: { type: "string" },
|
|
215
|
+
since: { type: "string" },
|
|
216
|
+
}, ["ok"]),
|
|
217
|
+
}, ["mode", "fallbackLanes", "health"]),
|
|
201
218
|
});
|
|
202
219
|
export const reconcileResultSchema = objectSchema({
|
|
203
220
|
workspace: { type: "string" },
|