@buildinternet/uploads 0.43.0 → 0.45.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 +35 -0
- package/dist/client.js +14 -0
- package/dist/commands/hook.d.ts +6 -2
- package/dist/commands/hook.js +31 -9
- package/dist/commands/screenshot.js +90 -20
- package/dist/commands.d.ts +76 -0
- package/dist/commands.js +378 -79
- package/dist/comment-config.generated.d.ts +3 -0
- package/dist/comment-config.generated.js +13 -0
- package/dist/comment-render.generated.d.ts +4 -1
- package/dist/comment-render.generated.js +15 -2
- package/dist/config-file.d.ts +4 -1
- package/dist/config-file.js +10 -0
- package/dist/github-gh.d.ts +27 -0
- package/dist/github-gh.js +66 -0
- package/dist/mcp/output-schemas.js +24 -1
- package/dist/mcp/tools.js +132 -27
- package/package.json +1 -1
package/dist/commands.js
CHANGED
|
@@ -14,7 +14,7 @@ import { parseMetaFlags, validateMetaMap } from "./metadata.js";
|
|
|
14
14
|
import { mergeDerivedMeta, nearMissMetaWarnings, validateStateValue } from "./metadata-vocab.js";
|
|
15
15
|
import { mergeSidecarMeta } from "./sidecar.js";
|
|
16
16
|
import { ghAttachmentKeyForMode, ghBranchAttachmentKeyForMode, ghBranchKeyPrefix, ghKeyPrefix, ghPrivateKeyPrefix, ghPrivateBranchKeyPrefix, ghMetadataFromTarget, parseGhKey, parseGhPrivateKey, ghMetadataForBranch, attachmentsCommentBody, attachmentsMarker, AUTO_RENDER_OPTIONS, GH_FALLBACK_AUTHOR_NOTE, normalizeGithubCoordinate, } from "./github.js";
|
|
17
|
-
import { resolveRepo, resolveCurrentPullRequest, resolveCurrentBranch, resolveDefaultBranch, classifyGhNumber, execRunner, timedExecRunner, ghMetadataFromTargetWithTitle, upsertAttachmentsComment, } from "./github-gh.js";
|
|
17
|
+
import { resolveRepo, resolveCurrentPullRequest, resolveCurrentBranch, resolveDefaultBranch, classifyGhNumber, execRunner, timedExecRunner, ghMetadataFromTargetWithTitle, upsertAttachmentsComment, hasLinkCandidate, extractCandidateUrls, fetchAdoptionCandidateText, } from "./github-gh.js";
|
|
18
18
|
import { deriveRepoFromGit, deriveRepoSlugFromGit } from "./keys.js";
|
|
19
19
|
import { noProjectContextNudge } from "./project-context-nudge.js";
|
|
20
20
|
import { resolvePutPrefix } from "./destinations.js";
|
|
@@ -152,6 +152,7 @@ Options:
|
|
|
152
152
|
--no-git Don't derive --repo from git (or UPLOADS_NO_GIT=1)
|
|
153
153
|
--auto Resolve current PR/issue and stamp gh.* metadata (default on)
|
|
154
154
|
--no-auto Skip gh.* auto-resolution (also skipped by --no-git or UPLOADS_NO_AUTO_META=1)
|
|
155
|
+
--no-pr Skip auto-PR context (or UPLOADS_NO_AUTO_PR=1) — see below
|
|
155
156
|
--workspace, -w <name> Override workspace (wins over UPLOADS_WORKSPACE and token inference)
|
|
156
157
|
--format human|url|markdown|json
|
|
157
158
|
--pr <num> Attach to a pull request: key gh/<owner>/<repo>/pull/<num>/<name> (stable URL, no hash)
|
|
@@ -175,9 +176,17 @@ Options:
|
|
|
175
176
|
--dry-run Print key + public URL without uploading; reports if the key would replace
|
|
176
177
|
(or, on a strict key, be refused). Not with --gallery
|
|
177
178
|
|
|
178
|
-
A bare put (no --pr/--issue/--key) on a
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
A bare put (no --pr/--issue/--key/--ref/--prefix/--destination) on a git branch
|
|
180
|
+
that maps to exactly one open PR now behaves as if --pr <n> had been passed
|
|
181
|
+
(issue #700): stable gh/ key, managed comment sync — instead of the #403
|
|
182
|
+
branch-staging default. A one-line note announces this (stderr in human mode,
|
|
183
|
+
the "hint" field in --format json). Opt out with --no-pr, UPLOADS_NO_AUTO_PR=1,
|
|
184
|
+
or config UPLOADS_NO_AUTO_PR=1; never fires outside a git repo, on the default
|
|
185
|
+
branch, with --no-git, or when no single open PR can be resolved (falls back to
|
|
186
|
+
branch staging, then the plain dated layout). When it doesn't fire and the
|
|
187
|
+
upload lands on the dated layout with a detectable PR, a similar one-line nudge
|
|
188
|
+
names the PR and a ready-made follow-up (uploads attach --pr <n> <key>...).
|
|
189
|
+
Suppress either note with --quiet, UPLOADS_NO_NUDGE=1, or config UPLOADS_NO_NUDGE=1.
|
|
181
190
|
|
|
182
191
|
Exit codes: 0 ok · 2 usage/token/file · 3 auth/policy · 4 network · 1 other (incl. partial multi-file failure).
|
|
183
192
|
Scripted formats (json|url|markdown) also print failures on stdout.
|
|
@@ -555,6 +564,59 @@ export async function syncAttachmentsComment(client, target, run, workspace, opt
|
|
|
555
564
|
`Posting via local gh in the meantime.\n`);
|
|
556
565
|
}
|
|
557
566
|
}
|
|
567
|
+
// Link adoption (issue #708): local-gh fallback parity with the bot's own
|
|
568
|
+
// adoption (issue #701, apps/api/src/github-link-adopt.ts). When the bot
|
|
569
|
+
// already handled this target the server already adopted for us, so this
|
|
570
|
+
// only runs once we've fallen through to gh. Scans the PR/issue body and
|
|
571
|
+
// every comment for pasted uploads.sh URLs and adopts each one that
|
|
572
|
+
// resolves (server-side, inside `POST .../github/attach`) to a file in
|
|
573
|
+
// THIS workspace's own bound-repo attachment prefix — copy, never move,
|
|
574
|
+
// same as the bot path. Best-effort end to end: any failure (not a git
|
|
575
|
+
// repo, `gh` unavailable/unauthenticated, config unreadable) degrades to
|
|
576
|
+
// "adopt nothing" rather than blocking the comment sync it rides along
|
|
577
|
+
// with.
|
|
578
|
+
let adoptedCount = 0;
|
|
579
|
+
let preAdoptionAttachmentCount;
|
|
580
|
+
try {
|
|
581
|
+
const root = run("git", ["rev-parse", "--show-toplevel"]).trim();
|
|
582
|
+
const { config: adoptConfig } = readLocalRepoCommentConfig(root);
|
|
583
|
+
const { options: adoptOptions } = resolveCommentOptions(adoptConfig, null);
|
|
584
|
+
if (adoptOptions.adoptLinkedFiles) {
|
|
585
|
+
const text = fetchAdoptionCandidateText(target, run);
|
|
586
|
+
if (hasLinkCandidate(text)) {
|
|
587
|
+
const urls = extractCandidateUrls(text);
|
|
588
|
+
if (urls.length > 0) {
|
|
589
|
+
// Baseline BEFORE this pass's adoptions land (mirrors the bot
|
|
590
|
+
// path's `gatherCommentBody` call before its own copies) — feeds
|
|
591
|
+
// the noise guard below without a lone adoption inflating its own
|
|
592
|
+
// count. Plain prefix only (not the private-prefix listing done
|
|
593
|
+
// for the final render below) — good enough for a guard decision.
|
|
594
|
+
preAdoptionAttachmentCount = (await client.listAll({ prefix: ghKeyPrefix(target) }))
|
|
595
|
+
.length;
|
|
596
|
+
for (const url of urls) {
|
|
597
|
+
try {
|
|
598
|
+
await client.attachExisting({
|
|
599
|
+
source: url,
|
|
600
|
+
repo: target.repo,
|
|
601
|
+
...(target.kind === "pull" ? { pr: target.num } : { issue: target.num }),
|
|
602
|
+
});
|
|
603
|
+
adoptedCount++;
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
// Not a resolvable uploads.sh URL, belongs to a different
|
|
607
|
+
// workspace, or the source was deleted — silently dropped,
|
|
608
|
+
// matching the bot path's contract (a throw from
|
|
609
|
+
// `resolveAttachSourceKey` is caught per-URL there too).
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch {
|
|
617
|
+
// Not a git repo, `.uploads.yml` unreadable, or `gh` unavailable for the
|
|
618
|
+
// PR/comments fetch — degrade to no adoption this pass.
|
|
619
|
+
}
|
|
558
620
|
// gh fallback: gather from this workspace's own data and post via local `gh`.
|
|
559
621
|
// Note (issues #304, #365): this CLI process has no server-side
|
|
560
622
|
// WorkspaceRecord in scope, so it cannot honor a workspace's
|
|
@@ -647,13 +709,23 @@ export async function syncAttachmentsComment(client, target, run, workspace, opt
|
|
|
647
709
|
}
|
|
648
710
|
// Append only on the local-gh path: bot posts already carry the uploads-sh
|
|
649
711
|
// bot identity, so this note would be wrong there.
|
|
650
|
-
const body = `${attachmentsCommentBody(items, previewGalleries, marker, renderOptions)}\n${GH_FALLBACK_AUTHOR_NOTE}`;
|
|
712
|
+
const body = `${attachmentsCommentBody(items, previewGalleries, marker, renderOptions, target)}\n${GH_FALLBACK_AUTHOR_NOTE}`;
|
|
651
713
|
const count = items.length + previewGalleries.length;
|
|
714
|
+
// Noise guard (issue #708, mirrors the bot's `shouldSyncAfterAdopt`): a
|
|
715
|
+
// lone adopted link with nothing else already attached is already fully
|
|
716
|
+
// visible inline in the PR/comment — don't create a brand-new comment just
|
|
717
|
+
// to repeat it. `upsertAttachmentsComment` still PATCHes an existing
|
|
718
|
+
// managed comment unconditionally (its own `if (existing)` branch runs
|
|
719
|
+
// regardless of `createIfMissing`), so "a managed comment already exists"
|
|
720
|
+
// and "other attachments are already present" both heal/sync for free
|
|
721
|
+
// here without any extra condition — this only ever suppresses a fresh
|
|
722
|
+
// create.
|
|
723
|
+
const skipLoneAdoptionCreate = adoptedCount === 1 && (preAdoptionAttachmentCount ?? 0) === 0;
|
|
652
724
|
// Empty (count 0) renders the neutral empty-state body but must not create a
|
|
653
725
|
// comment — it only rewrites one that already exists (`action: "skipped"`
|
|
654
726
|
// when none does).
|
|
655
727
|
const { action } = upsertAttachmentsComment(target, body, run, marker, {
|
|
656
|
-
createIfMissing: count > 0,
|
|
728
|
+
createIfMissing: count > 0 && !skipLoneAdoptionCreate,
|
|
657
729
|
});
|
|
658
730
|
return { action, count, via: "gh" };
|
|
659
731
|
}
|
|
@@ -683,6 +755,15 @@ derived metadata (path/url/env/viewport/state) is merged in automatically —
|
|
|
683
755
|
explicit --meta/--state always win. A regenerated or edited file loses its
|
|
684
756
|
sidecar silently (hash no longer matches).
|
|
685
757
|
|
|
758
|
+
An argument that doesn't exist on disk but resolves as an already-uploaded
|
|
759
|
+
object — a bare key (e.g. "f/AbC123/shot.webp") or an uploads.sh URL (storage
|
|
760
|
+
host, embed host, or /f/ page) — attaches via a server-side copy instead of a
|
|
761
|
+
re-upload: the source's own derived metadata (path/url/viewport/state/…)
|
|
762
|
+
rides along, and gh.repo/gh.kind/gh.number/gh.ref are stamped fresh. Copy by
|
|
763
|
+
default; --move deletes the source after a successful copy. A path that
|
|
764
|
+
exists on disk always wins as a local file, even if it also happens to look
|
|
765
|
+
like a key.
|
|
766
|
+
|
|
686
767
|
Branch staging (pre-PR): --branch [name] stages files against a git branch
|
|
687
768
|
before a pull request exists, e.g. for a coding agent working a branch that
|
|
688
769
|
hasn't opened a PR yet. Key: gh/<owner>/<repo>/branch/<branch>/<filename>
|
|
@@ -714,6 +795,8 @@ Options:
|
|
|
714
795
|
resolved PR and refresh the comment; not with
|
|
715
796
|
--branch/--issue/--no-promote
|
|
716
797
|
--no-promote Skip auto-promoting branch-staged attachments (default path only)
|
|
798
|
+
--move With an already-uploaded key/URL argument: delete the source
|
|
799
|
+
object after a successful server-side copy (default: copy)
|
|
717
800
|
--repo <owner/repo> Repository (default: gh/git inference)
|
|
718
801
|
--no-comment Upload only; don't create/update the managed comment
|
|
719
802
|
--content-type <mime> Override Content-Type (applied to every file; ignored when optimize rewrites)
|
|
@@ -768,6 +851,57 @@ sentMetadata) {
|
|
|
768
851
|
const missingPath = uploads.some((u, i) => u.contentType.startsWith("image/") && !sentMetadata[i]?.path);
|
|
769
852
|
return missingPath ? "tip: add --meta path=/route so this shot is findable by page" : undefined;
|
|
770
853
|
}
|
|
854
|
+
/**
|
|
855
|
+
* Bounded fan-out for `uploads attach`'s already-uploaded-object args (issue
|
|
856
|
+
* #702) — attach args are independent server calls (no shared batch state
|
|
857
|
+
* like `uploadAttachments`'s optimize/frame prep), so this stays a thin
|
|
858
|
+
* wrapper rather than a variant of that function.
|
|
859
|
+
*/
|
|
860
|
+
const ATTACH_EXISTING_CONCURRENCY = 4;
|
|
861
|
+
async function attachExistingBatch(client, target, sources, move) {
|
|
862
|
+
const outcomes = await mapBounded(sources, ATTACH_EXISTING_CONCURRENCY, async (source) => {
|
|
863
|
+
try {
|
|
864
|
+
const result = await client.attachExisting({
|
|
865
|
+
source,
|
|
866
|
+
repo: target.repo,
|
|
867
|
+
pr: target.kind === "pull" ? target.num : undefined,
|
|
868
|
+
issue: target.kind === "issues" ? target.num : undefined,
|
|
869
|
+
move,
|
|
870
|
+
});
|
|
871
|
+
return { ok: true, source, result };
|
|
872
|
+
}
|
|
873
|
+
catch (err) {
|
|
874
|
+
const message = err instanceof UploadsError && err.code === "NOT_FOUND"
|
|
875
|
+
? `not a local file, and no such object in this workspace: ${source}`
|
|
876
|
+
: err instanceof Error
|
|
877
|
+
? err.message
|
|
878
|
+
: String(err);
|
|
879
|
+
return {
|
|
880
|
+
ok: false,
|
|
881
|
+
source,
|
|
882
|
+
cause: err,
|
|
883
|
+
error: {
|
|
884
|
+
message,
|
|
885
|
+
code: err instanceof UploadsError ? err.code : undefined,
|
|
886
|
+
status: err instanceof UploadsError ? err.status : undefined,
|
|
887
|
+
},
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
const results = [];
|
|
892
|
+
const failures = [];
|
|
893
|
+
let firstError;
|
|
894
|
+
for (const outcome of outcomes) {
|
|
895
|
+
if (outcome.ok) {
|
|
896
|
+
results.push(outcome.result);
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
failures.push({ file: outcome.source, error: outcome.error });
|
|
900
|
+
firstError ??= outcome.cause;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return { results, failures, firstError };
|
|
904
|
+
}
|
|
771
905
|
/**
|
|
772
906
|
* Shared prepare + put loop for both PR/issue attach (`uploadAttachments`)
|
|
773
907
|
* and branch-staged attach (`uploadBranchAttachments`) — bounded concurrency,
|
|
@@ -1021,6 +1155,9 @@ export async function runAttach(ctx, args, help = false, run = execRunner) {
|
|
|
1021
1155
|
if (parsed.flags.has("--no-promote") && typeof parsed.flags.get("--no-promote") === "string") {
|
|
1022
1156
|
throw new UsageError("--no-promote takes no value — place it after the file arguments");
|
|
1023
1157
|
}
|
|
1158
|
+
if (parsed.flags.has("--move") && typeof parsed.flags.get("--move") === "string") {
|
|
1159
|
+
throw new UsageError("--move takes no value — place it after the file arguments");
|
|
1160
|
+
}
|
|
1024
1161
|
if (parsed.flags.has("--promote")) {
|
|
1025
1162
|
if (parsed.positionals.length > 0) {
|
|
1026
1163
|
throw new UsageError("--promote takes no file arguments — attaching a file to a PR already auto-promotes " +
|
|
@@ -1073,23 +1210,61 @@ export async function runAttach(ctx, args, help = false, run = execRunner) {
|
|
|
1073
1210
|
};
|
|
1074
1211
|
if (Object.keys(metadata).length > 0)
|
|
1075
1212
|
validateMetaMap(metadata);
|
|
1213
|
+
// Args that exist on disk are always local files, even if they'd also
|
|
1214
|
+
// parse as a key/URL. Everything else is a candidate for the server-side
|
|
1215
|
+
// attach-existing path (issue #702) — resolved/validated server-side, so a
|
|
1216
|
+
// typo'd path and a genuinely-missing object key report the same way.
|
|
1217
|
+
const localFiles = parsed.positionals.filter((p) => existsSync(p));
|
|
1218
|
+
const remoteArgs = parsed.positionals.filter((p) => !existsSync(p));
|
|
1219
|
+
const moveExisting = parsed.flags.has("--move");
|
|
1220
|
+
if (moveExisting && remoteArgs.length === 0) {
|
|
1221
|
+
throw new UsageError("--move only applies to already-uploaded key/URL arguments");
|
|
1222
|
+
}
|
|
1076
1223
|
const logHuman = !ctx.quiet && !ctx.json;
|
|
1077
|
-
if (logHuman) {
|
|
1078
|
-
const n =
|
|
1224
|
+
if (logHuman && localFiles.length > 0) {
|
|
1225
|
+
const n = localFiles.length;
|
|
1079
1226
|
process.stderr.write(`>> uploading ${n} file${n === 1 ? "" : "s"}\n`);
|
|
1080
1227
|
}
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1228
|
+
const uploadResult = localFiles.length > 0
|
|
1229
|
+
? await uploadAttachments({
|
|
1230
|
+
client: ctx.client,
|
|
1231
|
+
target,
|
|
1232
|
+
files: localFiles,
|
|
1233
|
+
contentType: contentTypeOverride,
|
|
1234
|
+
optimize: optimizeOpts,
|
|
1235
|
+
frame: frameOpts,
|
|
1236
|
+
metadata,
|
|
1237
|
+
deriveImageFacts: derivedMetaEnabled(parsed.flags, defaults),
|
|
1238
|
+
})
|
|
1239
|
+
: { uploads: [], failures: [], firstError: undefined, sentMetadata: [] };
|
|
1240
|
+
const { uploads, sentMetadata } = uploadResult;
|
|
1241
|
+
const localFailures = uploadResult.failures;
|
|
1242
|
+
if (logHuman && remoteArgs.length > 0) {
|
|
1243
|
+
const n = remoteArgs.length;
|
|
1244
|
+
process.stderr.write(`>> attaching ${n} existing object${n === 1 ? "" : "s"}\n`);
|
|
1245
|
+
}
|
|
1246
|
+
const remoteResult = remoteArgs.length > 0
|
|
1247
|
+
? await attachExistingBatch(ctx.client, target, remoteArgs, moveExisting)
|
|
1248
|
+
: {
|
|
1249
|
+
results: [],
|
|
1250
|
+
failures: [],
|
|
1251
|
+
firstError: undefined,
|
|
1252
|
+
};
|
|
1253
|
+
const { results: attachedExisting, failures: remoteFailures } = remoteResult;
|
|
1254
|
+
const failures = [...localFailures, ...remoteFailures];
|
|
1255
|
+
const firstError = localFailures.length > 0 ? uploadResult.firstError : remoteResult.firstError;
|
|
1256
|
+
// Single-arg total failure: rethrow so CLI exit codes stay auth/network-aware.
|
|
1257
|
+
// The remote-attach path's friendlier not-found message (attachExistingBatch)
|
|
1258
|
+
// wins over the raw client error text, but the original error's class/code
|
|
1259
|
+
// (UploadsError) is preserved so exit-code mapping stays unaffected.
|
|
1260
|
+
if (uploads.length === 0 &&
|
|
1261
|
+
attachedExisting.length === 0 &&
|
|
1262
|
+
failures.length === 1 &&
|
|
1263
|
+
parsed.positionals.length === 1) {
|
|
1264
|
+
const only = failures[0];
|
|
1265
|
+
if (firstError instanceof UploadsError && firstError.message !== only.error.message) {
|
|
1266
|
+
throw new UploadsError(only.error.message, firstError.code, firstError.status);
|
|
1267
|
+
}
|
|
1093
1268
|
throw firstError instanceof Error ? firstError : new Error(String(firstError));
|
|
1094
1269
|
}
|
|
1095
1270
|
// Auto-promote: before the comment sync, best-effort promote this
|
|
@@ -1131,6 +1306,7 @@ export async function runAttach(ctx, args, help = false, run = execRunner) {
|
|
|
1131
1306
|
await writeJson({
|
|
1132
1307
|
target,
|
|
1133
1308
|
uploads,
|
|
1309
|
+
attachedExisting,
|
|
1134
1310
|
failures,
|
|
1135
1311
|
comment,
|
|
1136
1312
|
commentError,
|
|
@@ -1152,6 +1328,13 @@ export async function runAttach(ctx, args, help = false, run = execRunner) {
|
|
|
1152
1328
|
const embedLine = result.embedUrl ? `EMBED: ${result.embedUrl}\n` : "";
|
|
1153
1329
|
await writeStdout(`URL: ${result.url}\n${embedLine}MARKDOWN: ${result.markdown}\n`);
|
|
1154
1330
|
}
|
|
1331
|
+
for (const attached of attachedExisting) {
|
|
1332
|
+
if (logHuman) {
|
|
1333
|
+
process.stderr.write(`>> ${attached.source.key}: attached${attached.moved ? " (moved)" : ""} as ${attached.key}\n`);
|
|
1334
|
+
}
|
|
1335
|
+
const embedLine = attached.embedUrl ? `EMBED: ${attached.embedUrl}\n` : "";
|
|
1336
|
+
await writeStdout(`URL: ${attached.url}\n${embedLine}`);
|
|
1337
|
+
}
|
|
1155
1338
|
for (const failure of failures) {
|
|
1156
1339
|
process.stderr.write(`warning: could not upload ${failure.file}: ${failure.error.message}\n`);
|
|
1157
1340
|
}
|
|
@@ -1347,36 +1530,54 @@ async function runAttachPromoteOnly(ctx, parsed, run) {
|
|
|
1347
1530
|
* enough to never be felt as a hang. */
|
|
1348
1531
|
const PUT_NUDGE_GH_TIMEOUT_MS = 3000;
|
|
1349
1532
|
/**
|
|
1350
|
-
* The bare-put nudge's wording (issue #393
|
|
1351
|
-
* as an upgrade from a targetless `put`.
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1533
|
+
* The bare-put nudge's wording (issue #393, made concrete by issue #700):
|
|
1534
|
+
* teaches `--pr`/`attach --branch` as an upgrade from a targetless `put`.
|
|
1535
|
+
* `pr` present → names the PR and, once upload `keys` are known, appends a
|
|
1536
|
+
* ready-made follow-up command naming them verbatim (e.g. `uploads attach
|
|
1537
|
+
* --pr 1250 f/abc123.webp`); otherwise a generic variant that still points
|
|
1538
|
+
* at `--pr <num>`. Used verbatim for both the human-mode stderr line and the
|
|
1539
|
+
* JSON `hint` field.
|
|
1354
1540
|
*/
|
|
1355
|
-
function putNudgeText(branch, pr) {
|
|
1541
|
+
export function putNudgeText(branch, pr, keys = []) {
|
|
1356
1542
|
const prClause = pr !== undefined ? ` (PR #${pr} open) — rerun with --pr ${pr}` : ` — rerun with --pr <num>`;
|
|
1357
|
-
|
|
1358
|
-
`that collects this PR's media, or stage pre-PR files with: uploads attach <file> --branch
|
|
1543
|
+
const base = `note: on branch ${branch}${prClause} for a stable key plus a managed comment ` +
|
|
1544
|
+
`that collects this PR's media, or stage pre-PR files with: uploads attach <file> --branch`;
|
|
1545
|
+
if (pr === undefined || keys.length === 0)
|
|
1546
|
+
return base;
|
|
1547
|
+
return `${base}. Already uploaded? uploads attach --pr ${pr} ${keys.join(" ")}`;
|
|
1359
1548
|
}
|
|
1360
1549
|
/**
|
|
1361
|
-
*
|
|
1362
|
-
*
|
|
1550
|
+
* Auto-PR note (issue #700): announces at the moment it fires that a bare
|
|
1551
|
+
* put/screenshot on this branch was auto-attached to `pr` — the default
|
|
1552
|
+
* behavior change this issue introduces — and how to opt out.
|
|
1553
|
+
*/
|
|
1554
|
+
export function autoPrNoteText(pr) {
|
|
1555
|
+
return (`note: branch maps to open PR #${pr} — auto-attached (stable key + managed comment sync). ` +
|
|
1556
|
+
`Opt out with --no-pr or UPLOADS_NO_AUTO_PR=1.`);
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Best-effort bare-put/screenshot nudge context (issue #393): resolves the
|
|
1560
|
+
* branch and, when detectable, the open PR for it — fires only when there is
|
|
1561
|
+
* no targeting flag at all (`--pr`/`--issue`/`--key`; `--branch` too, though
|
|
1363
1562
|
* `put` doesn't currently accept it — defensive parity with `attach`), is
|
|
1364
1563
|
* inside a git repo (reusing `deriveRepoFromGit`, the same detection the
|
|
1365
1564
|
* default screenshot key's repo segment uses), and the current branch isn't
|
|
1366
1565
|
* the default one. Never throws — any failure (not a repo, detached HEAD,
|
|
1367
1566
|
* `gh` missing/unauthenticated/timed out) degrades to "no nudge" or, once a
|
|
1368
|
-
* branch is already known, to
|
|
1369
|
-
* put's exit code, stdout, or upload
|
|
1567
|
+
* branch is already known, to a context with `pr: undefined` (the generic
|
|
1568
|
+
* no-PR wording). Must never affect put's exit code, stdout, or upload
|
|
1569
|
+
* behavior. Callers turn the result into text via `putNudgeText`, once any
|
|
1570
|
+
* upload keys are known.
|
|
1370
1571
|
*/
|
|
1371
|
-
function
|
|
1372
|
-
const {
|
|
1373
|
-
if (
|
|
1572
|
+
export function resolvePutNudgeContext(opts) {
|
|
1573
|
+
const { quiet, noNudge, ghTarget, keyHint, hasBranchFlag, noGit, repoArg, run } = opts;
|
|
1574
|
+
if (quiet)
|
|
1374
1575
|
return undefined;
|
|
1375
|
-
if (
|
|
1576
|
+
if (noNudge)
|
|
1376
1577
|
return undefined;
|
|
1377
1578
|
if (ghTarget || keyHint || noGit)
|
|
1378
1579
|
return undefined;
|
|
1379
|
-
if (
|
|
1580
|
+
if (hasBranchFlag)
|
|
1380
1581
|
return undefined; // not a real put flag today; defensive only
|
|
1381
1582
|
try {
|
|
1382
1583
|
if (deriveRepoFromGit(run) === undefined)
|
|
@@ -1401,19 +1602,69 @@ function resolvePutNudge(opts) {
|
|
|
1401
1602
|
// fast/fake, and execFileSync's `timeout` option is meaningless
|
|
1402
1603
|
// against anything that isn't actually shelling out.
|
|
1403
1604
|
const timed = run === execRunner ? timedExecRunner(PUT_NUDGE_GH_TIMEOUT_MS) : run;
|
|
1404
|
-
const repoArg = flagString(flags, "--repo") ?? defaults.repo;
|
|
1405
1605
|
const repo = resolveRepo(repoArg, timed);
|
|
1406
1606
|
pr = resolveCurrentPullRequest(repo, timed).num;
|
|
1407
1607
|
}
|
|
1408
1608
|
catch {
|
|
1409
1609
|
pr = undefined; // gh missing/unauthenticated/timed out/no open PR — generic wording
|
|
1410
1610
|
}
|
|
1411
|
-
return
|
|
1611
|
+
return { branch, pr };
|
|
1412
1612
|
}
|
|
1413
1613
|
catch {
|
|
1414
1614
|
return undefined;
|
|
1415
1615
|
}
|
|
1416
1616
|
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Auto-PR context (issue #700): when a bare put/screenshot has no explicit
|
|
1619
|
+
* destination flag at all (`--pr`/`--issue`/`--key`/`--ref`/`--prefix`/
|
|
1620
|
+
* `--destination`, and for `screenshot` no explicit `--branch`) and runs on a
|
|
1621
|
+
* branch that maps to exactly one open PR, this resolves that PR so the
|
|
1622
|
+
* caller can behave as if `--pr <n>` had been passed — stable key + managed
|
|
1623
|
+
* comment sync — instead of the #403/#469 staging default or the plain dated
|
|
1624
|
+
* layout. `resolveCurrentPullRequest`'s `gh pr view <branch>` lookup is
|
|
1625
|
+
* already the unambiguous case: it names the single open PR whose head is
|
|
1626
|
+
* that branch, or fails (no open PR, or `gh` unavailable/unauthenticated) —
|
|
1627
|
+
* there is no "ambiguous, more than one" state to further disambiguate.
|
|
1628
|
+
* Opt-out: `noAutoPr` (the caller folds in `--no-pr` and
|
|
1629
|
+
* `UPLOADS_NO_AUTO_PR=1`/config). Never fires outside a git checkout, on the
|
|
1630
|
+
* default branch, or with `--no-git`; any failure (not a repo, detached
|
|
1631
|
+
* HEAD, gh missing/unauthenticated/timed out, no open PR) degrades to
|
|
1632
|
+
* undefined so the caller falls back to its normal staging/dated behavior.
|
|
1633
|
+
*/
|
|
1634
|
+
export function resolveAutoPrTarget(opts) {
|
|
1635
|
+
const { ghTarget, keyHint, refArg, prefixArg, destinationArg, branchArg, noGit, noAutoPr, repoArg, run, } = opts;
|
|
1636
|
+
if (noAutoPr)
|
|
1637
|
+
return undefined;
|
|
1638
|
+
if (ghTarget || keyHint || noGit)
|
|
1639
|
+
return undefined;
|
|
1640
|
+
if (refArg || prefixArg || destinationArg || branchArg !== undefined)
|
|
1641
|
+
return undefined;
|
|
1642
|
+
try {
|
|
1643
|
+
if (deriveRepoFromGit(run) === undefined)
|
|
1644
|
+
return undefined; // not a (usable) git repo
|
|
1645
|
+
let branch;
|
|
1646
|
+
try {
|
|
1647
|
+
branch = resolveCurrentBranch(run);
|
|
1648
|
+
}
|
|
1649
|
+
catch {
|
|
1650
|
+
return undefined; // detached HEAD, or git unavailable
|
|
1651
|
+
}
|
|
1652
|
+
const defaultBranch = resolveDefaultBranch(run);
|
|
1653
|
+
const onDefaultBranch = defaultBranch
|
|
1654
|
+
? branch === defaultBranch
|
|
1655
|
+
: branch === "main" || branch === "master"; // undetermined: err toward the old default
|
|
1656
|
+
if (onDefaultBranch)
|
|
1657
|
+
return undefined;
|
|
1658
|
+
// Same bounded-timeout treatment as the #393 nudge's `gh pr view` call —
|
|
1659
|
+
// this must never be felt as a hang.
|
|
1660
|
+
const timed = run === execRunner ? timedExecRunner(PUT_NUDGE_GH_TIMEOUT_MS) : run;
|
|
1661
|
+
const repo = resolveRepo(repoArg, timed);
|
|
1662
|
+
return resolveCurrentPullRequest(repo, timed);
|
|
1663
|
+
}
|
|
1664
|
+
catch {
|
|
1665
|
+
return undefined; // gh/git unavailable, no open PR, or repo unresolvable
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1417
1668
|
/**
|
|
1418
1669
|
* Bare-put branch-staging trigger (issue #403): put on a non-default git
|
|
1419
1670
|
* branch stages to the branch prefix by default — the branch becomes the
|
|
@@ -1638,10 +1889,39 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1638
1889
|
});
|
|
1639
1890
|
}
|
|
1640
1891
|
const multi = files.length > 1;
|
|
1892
|
+
// Resolved early (issue #700): both the auto-PR opt-out default and the
|
|
1893
|
+
// `--no-git`-gated staging/auto-PR detection below need it before the rest
|
|
1894
|
+
// of put's flag parsing.
|
|
1895
|
+
const defaults = resolvePutDefaults({ envFile: ctx.envFile });
|
|
1896
|
+
const noGit = flagBool(parsed.flags, "--no-git") || defaults.noGit === true;
|
|
1641
1897
|
const keyHint = flagString(parsed.flags, "--key");
|
|
1642
1898
|
const destFlag = flagString(parsed.flags, "--destination");
|
|
1643
1899
|
const prefixFlag = flagString(parsed.flags, "--prefix");
|
|
1644
1900
|
const ghTarget = ghTargetFromFlags(parsed.flags, run);
|
|
1901
|
+
if (parsed.flags.has("--no-pr") && typeof parsed.flags.get("--no-pr") === "string") {
|
|
1902
|
+
throw new UsageError("--no-pr takes no value");
|
|
1903
|
+
}
|
|
1904
|
+
const noAutoPr = flagBool(parsed.flags, "--no-pr") || defaults.noAutoPr === true;
|
|
1905
|
+
// Auto-PR context (issue #700): a bare put (no --pr/--issue/--key/--ref/
|
|
1906
|
+
// --prefix/--destination, not --no-git/--no-pr) on a branch that maps to
|
|
1907
|
+
// exactly one open PR behaves as if `--pr <n>` had been passed — see
|
|
1908
|
+
// resolveAutoPrTarget. Supersedes both the #403 staging default and the
|
|
1909
|
+
// #393 nudge for this case; computed before the gh.* metadata resolution
|
|
1910
|
+
// below since it takes over that resolution entirely.
|
|
1911
|
+
const autoPrTarget = ghTarget
|
|
1912
|
+
? undefined
|
|
1913
|
+
: resolveAutoPrTarget({
|
|
1914
|
+
ghTarget,
|
|
1915
|
+
keyHint,
|
|
1916
|
+
refArg: flagString(parsed.flags, "--ref"),
|
|
1917
|
+
prefixArg: prefixFlag,
|
|
1918
|
+
destinationArg: destFlag,
|
|
1919
|
+
noGit,
|
|
1920
|
+
noAutoPr,
|
|
1921
|
+
repoArg: flagString(parsed.flags, "--repo") ?? defaults.repo,
|
|
1922
|
+
run,
|
|
1923
|
+
});
|
|
1924
|
+
const effectiveGhTarget = ghTarget ?? autoPrTarget;
|
|
1645
1925
|
// Comment sync runs by default with --pr/--issue (matches `attach`); opt
|
|
1646
1926
|
// out with --no-comment. --comment is accepted as a redundant no-op for
|
|
1647
1927
|
// back-compat with scripts written before this default flipped (#537).
|
|
@@ -1676,7 +1956,7 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1676
1956
|
if (parsed.flags.has("--no-auto") && typeof parsed.flags.get("--no-auto") === "string") {
|
|
1677
1957
|
throw new UsageError("--no-auto takes no value");
|
|
1678
1958
|
}
|
|
1679
|
-
if (parsed.flags.has("--no-comment") && !
|
|
1959
|
+
if (parsed.flags.has("--no-comment") && !effectiveGhTarget) {
|
|
1680
1960
|
throw new UsageError("--no-comment requires --pr or --issue");
|
|
1681
1961
|
}
|
|
1682
1962
|
if (multi) {
|
|
@@ -1713,7 +1993,7 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1713
1993
|
destination: destFlag,
|
|
1714
1994
|
prefix: prefixFlag,
|
|
1715
1995
|
key: keyHint,
|
|
1716
|
-
ghAttachment: Boolean(
|
|
1996
|
+
ghAttachment: Boolean(effectiveGhTarget),
|
|
1717
1997
|
});
|
|
1718
1998
|
}
|
|
1719
1999
|
catch (err) {
|
|
@@ -1729,7 +2009,6 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1729
2009
|
return raw;
|
|
1730
2010
|
throw new UsageError(`invalid --format: ${raw}`);
|
|
1731
2011
|
})();
|
|
1732
|
-
const defaults = resolvePutDefaults({ envFile: ctx.envFile });
|
|
1733
2012
|
const optimizeOpts = optimizeOptionsFromFlags(parsed.flags, defaults);
|
|
1734
2013
|
const frameOpts = frameOptionsFromFlags(parsed.flags);
|
|
1735
2014
|
const contentTypeOverride = flagString(parsed.flags, "--content-type");
|
|
@@ -1742,16 +2021,16 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1742
2021
|
throw new UsageError(`invalid --width: ${widthRaw}`);
|
|
1743
2022
|
})()
|
|
1744
2023
|
: defaults.width;
|
|
1745
|
-
const noGit = flagBool(parsed.flags, "--no-git") || defaults.noGit === true;
|
|
1746
2024
|
// Bare-put branch staging (issue #403): a bare put (no --pr/--issue/--key/
|
|
1747
2025
|
// --ref/--prefix/--destination, not --no-git) on a non-default git branch
|
|
1748
2026
|
// stages to the branch prefix — identical key/metadata to `attach
|
|
1749
2027
|
// --branch` — instead of the dated layout. Computed before gh.* metadata
|
|
1750
2028
|
// resolution below since it takes over that resolution entirely (branch
|
|
1751
2029
|
// metadata, not PR/issue metadata) and supersedes the #393 nudge for this
|
|
1752
|
-
// case.
|
|
2030
|
+
// case. `effectiveGhTarget` (explicit --pr/--issue OR the #700 auto-PR
|
|
2031
|
+
// match) wins over staging, same as it wins over the dated layout.
|
|
1753
2032
|
const stagingTarget = resolvePutStagingTarget({
|
|
1754
|
-
ghTarget,
|
|
2033
|
+
ghTarget: effectiveGhTarget,
|
|
1755
2034
|
keyHint,
|
|
1756
2035
|
refArg: flagString(parsed.flags, "--ref"),
|
|
1757
2036
|
prefixArg: prefixFlag,
|
|
@@ -1760,15 +2039,16 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1760
2039
|
repoArg: flagString(parsed.flags, "--repo") ?? defaults.repo,
|
|
1761
2040
|
run,
|
|
1762
2041
|
});
|
|
1763
|
-
// gh.* metadata: explicit --pr/--issue target
|
|
1764
|
-
// wins over --meta the same way (matches attach
|
|
1765
|
-
// best-effort auto resolution (on by default) where
|
|
1766
|
-
// --no-auto, or UPLOADS_NO_AUTO_META disable auto;
|
|
1767
|
-
// config default but never past --no-git (no repo
|
|
2042
|
+
// gh.* metadata: explicit --pr/--issue target (or the #700 auto-PR match)
|
|
2043
|
+
// wins over --meta; staging wins over --meta the same way (matches attach
|
|
2044
|
+
// --branch); otherwise best-effort auto resolution (on by default) where
|
|
2045
|
+
// --meta wins. --no-git, --no-auto, or UPLOADS_NO_AUTO_META disable auto;
|
|
2046
|
+
// --auto forces past the config default but never past --no-git (no repo
|
|
2047
|
+
// to resolve).
|
|
1768
2048
|
let metadata = userMeta;
|
|
1769
2049
|
let attachedRef;
|
|
1770
|
-
if (
|
|
1771
|
-
const merged = { ...userMeta, ...ghMetadataFromTargetWithTitle(
|
|
2050
|
+
if (effectiveGhTarget) {
|
|
2051
|
+
const merged = { ...userMeta, ...ghMetadataFromTargetWithTitle(effectiveGhTarget, run) };
|
|
1772
2052
|
validateMetaMap(merged); // enforce 24-key/8KB caps on the merged map (matches attach)
|
|
1773
2053
|
metadata = merged;
|
|
1774
2054
|
attachedRef = merged["gh.ref"];
|
|
@@ -1818,23 +2098,26 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1818
2098
|
// buckets — one advisory line at the moment the context went missing.
|
|
1819
2099
|
// --no-git is an explicit choice, so it suppresses the nudge too.
|
|
1820
2100
|
const contextNudge = !ctx.quiet && !defaults.noNudge && !noGit ? noProjectContextNudge(metadata) : undefined;
|
|
1821
|
-
// Bare-put nudge (issue #393): only relevant when
|
|
1822
|
-
// — once `stagingTarget` resolves,
|
|
1823
|
-
// point at, so this is skipped
|
|
1824
|
-
// Still fires as before for a
|
|
1825
|
-
// a detectable PR (e.g. an
|
|
1826
|
-
//
|
|
1827
|
-
//
|
|
1828
|
-
//
|
|
1829
|
-
|
|
2101
|
+
// Bare-put nudge (issue #393): only relevant when neither auto-PR nor
|
|
2102
|
+
// staging took over — once `effectiveGhTarget`/`stagingTarget` resolves,
|
|
2103
|
+
// that IS the upgrade the nudge used to point at, so this is skipped
|
|
2104
|
+
// entirely rather than firing redundantly. Still fires as before for a
|
|
2105
|
+
// bare put that lands on the dated layout with a detectable PR (e.g. an
|
|
2106
|
+
// explicit --ref/--prefix opts out of staging AND auto-PR, or --no-pr/
|
|
2107
|
+
// UPLOADS_NO_AUTO_PR opts out of auto-PR specifically). The concrete
|
|
2108
|
+
// key-naming text (issue #700) is finished below, once upload keys exist.
|
|
2109
|
+
// Best-effort — see resolvePutNudgeContext; never affects exit code,
|
|
2110
|
+
// stdout, or the upload.
|
|
2111
|
+
const nudgeContext = effectiveGhTarget || stagingTarget
|
|
1830
2112
|
? undefined
|
|
1831
|
-
:
|
|
1832
|
-
ctx,
|
|
1833
|
-
|
|
2113
|
+
: resolvePutNudgeContext({
|
|
2114
|
+
quiet: ctx.quiet,
|
|
2115
|
+
noNudge: defaults.noNudge === true,
|
|
1834
2116
|
ghTarget,
|
|
1835
2117
|
keyHint,
|
|
2118
|
+
hasBranchFlag: parsed.flags.has("--branch"),
|
|
1836
2119
|
noGit,
|
|
1837
|
-
defaults,
|
|
2120
|
+
repoArg: flagString(parsed.flags, "--repo") ?? defaults.repo,
|
|
1838
2121
|
run,
|
|
1839
2122
|
});
|
|
1840
2123
|
// Staging note (issue #403): same suppression as the #393 nudge
|
|
@@ -1843,6 +2126,12 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1843
2126
|
const stagingNote = stagingTarget && !ctx.quiet && !defaults.noNudge
|
|
1844
2127
|
? putStagingNoteText(stagingTarget.branch)
|
|
1845
2128
|
: undefined;
|
|
2129
|
+
// Auto-PR note (issue #700): announces the default-behavior change at the
|
|
2130
|
+
// moment it fires, so a bare put that silently became a --pr attach isn't
|
|
2131
|
+
// a surprise — names the PR and how to opt out. Same suppression as the
|
|
2132
|
+
// other advisories (--quiet, UPLOADS_NO_NUDGE=1); NOT gated by --no-pr/
|
|
2133
|
+
// UPLOADS_NO_AUTO_PR since those are what prevent it from firing at all.
|
|
2134
|
+
const autoPrNote = autoPrTarget && !ctx.quiet && !defaults.noNudge ? autoPrNoteText(autoPrTarget.num) : undefined;
|
|
1846
2135
|
const logHuman = !ctx.quiet && format === "human";
|
|
1847
2136
|
if (logHuman) {
|
|
1848
2137
|
if (multi) {
|
|
@@ -1860,7 +2149,7 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1860
2149
|
files,
|
|
1861
2150
|
nameOverride: nameFlag,
|
|
1862
2151
|
explicitKey: keyHint,
|
|
1863
|
-
ghTarget,
|
|
2152
|
+
ghTarget: effectiveGhTarget,
|
|
1864
2153
|
ghBranchTarget: stagingTarget,
|
|
1865
2154
|
prefix: resolvedPrefix ?? defaults.prefix,
|
|
1866
2155
|
repo: flagString(parsed.flags, "--repo") ?? defaults.repo,
|
|
@@ -1880,6 +2169,13 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1880
2169
|
if (uploads.length === 0 && failures.length > 0 && !multi) {
|
|
1881
2170
|
throw firstError instanceof Error ? firstError : new Error(String(firstError));
|
|
1882
2171
|
}
|
|
2172
|
+
// Concrete bare-put nudge text (issue #700): built once upload keys exist,
|
|
2173
|
+
// so the ready-made follow-up names them, e.g.
|
|
2174
|
+
// "uploads attach --pr 1250 f/abc123.webp". Falls back to the plain
|
|
2175
|
+
// #393 wording when there are no successful uploads to name.
|
|
2176
|
+
const nudge = nudgeContext && uploads.length > 0
|
|
2177
|
+
? putNudgeText(nudgeContext.branch, nudgeContext.pr, uploads.map((u) => u.key))
|
|
2178
|
+
: undefined;
|
|
1883
2179
|
// Stage-time binding warning (issue #398/#400): same check `attach
|
|
1884
2180
|
// --branch` runs, now also on the bare-put staging path. Best-effort — see
|
|
1885
2181
|
// resolveStageBindingWarning; never affects exit code or the upload.
|
|
@@ -1887,20 +2183,19 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1887
2183
|
? await resolveStageBindingWarning({ ctx, defaults, repo: stagingTarget.repo })
|
|
1888
2184
|
: undefined;
|
|
1889
2185
|
// Lever 3 (issue #469): tip when a --pr/--issue put lands an image with no
|
|
1890
|
-
// `path` meta. Only relevant on the
|
|
1891
|
-
//
|
|
1892
|
-
// nothing to look up from a page later.
|
|
1893
|
-
const pathHint =
|
|
2186
|
+
// `path` meta. Only relevant on the (explicit or auto) gh target path — the
|
|
2187
|
+
// bare-put staging/dated paths aren't attached to a PR/issue yet, so
|
|
2188
|
+
// there's nothing to look up from a page later.
|
|
2189
|
+
const pathHint = effectiveGhTarget && uploads.length > 0 && !ctx.quiet
|
|
1894
2190
|
? pathMetaHintFor(uploads, sentMetadata)
|
|
1895
2191
|
: undefined;
|
|
1896
|
-
// One JSON `hint` slot, shared
|
|
1897
|
-
//
|
|
1898
|
-
//
|
|
1899
|
-
//
|
|
1900
|
-
//
|
|
1901
|
-
//
|
|
1902
|
-
|
|
1903
|
-
const jsonHint = nudge ?? bindingWarning ?? stagingNote ?? pathHint ?? contextNudge;
|
|
2192
|
+
// One JSON `hint` slot, shared across every advisory this command can
|
|
2193
|
+
// surface. `autoPrNote` and `nudge` are mutually exclusive with each other
|
|
2194
|
+
// and with `stagingNote` (each corresponds to a different destination the
|
|
2195
|
+
// upload landed on); pathHint only ever fires on the gh-target path, so it
|
|
2196
|
+
// never competes with the other three. Same precedence stderr prints,
|
|
2197
|
+
// below.
|
|
2198
|
+
const jsonHint = autoPrNote ?? nudge ?? bindingWarning ?? stagingNote ?? pathHint ?? contextNudge;
|
|
1904
2199
|
const galleriesByKey = new Map();
|
|
1905
2200
|
let galleryHadError = false;
|
|
1906
2201
|
if (galleryId && uploads.length > 0) {
|
|
@@ -1923,9 +2218,9 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1923
2218
|
}
|
|
1924
2219
|
let comment;
|
|
1925
2220
|
let commentError;
|
|
1926
|
-
if (wantComment &&
|
|
2221
|
+
if (wantComment && effectiveGhTarget && !dryRun && uploads.length > 0) {
|
|
1927
2222
|
try {
|
|
1928
|
-
comment = await syncAttachmentsComment(ctx.client,
|
|
2223
|
+
comment = await syncAttachmentsComment(ctx.client, effectiveGhTarget, run, ctx.config.workspace);
|
|
1929
2224
|
if (logHuman)
|
|
1930
2225
|
process.stderr.write(`>> attachments comment ${comment.action}${commentViaSuffix(comment.via)}\n`);
|
|
1931
2226
|
}
|
|
@@ -1977,6 +2272,8 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
1977
2272
|
for (const failure of failures) {
|
|
1978
2273
|
process.stderr.write(`warning: could not upload ${failure.file}: ${failure.error.message}\n`);
|
|
1979
2274
|
}
|
|
2275
|
+
if (autoPrNote)
|
|
2276
|
+
process.stderr.write(`${autoPrNote}\n`);
|
|
1980
2277
|
if (nudge)
|
|
1981
2278
|
process.stderr.write(`${nudge}\n`);
|
|
1982
2279
|
if (stagingNote)
|
|
@@ -2040,6 +2337,8 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
2040
2337
|
if (gallery?.error) {
|
|
2041
2338
|
process.stderr.write(`warning: upload succeeded but adding it to gallery ${gallery.id} failed: ${gallery.error.message}\n`);
|
|
2042
2339
|
}
|
|
2340
|
+
if (autoPrNote && format !== "json")
|
|
2341
|
+
process.stderr.write(`${autoPrNote}\n`);
|
|
2043
2342
|
if (nudge && format !== "json")
|
|
2044
2343
|
process.stderr.write(`${nudge}\n`);
|
|
2045
2344
|
if (stagingNote && format !== "json")
|