@buildinternet/uploads 0.16.0 → 0.17.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/bin/dist-staleness.d.mts +8 -0
- package/bin/dist-staleness.mjs +94 -0
- package/bin/uploads.js +8 -1
- package/dist/client.d.ts +7 -0
- package/dist/commands.js +14 -2
- package/dist/github.js +1 -0
- package/dist/mcp/tools.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Dev-only staleness guard for the linked `uploads` CLI (issue #295).
|
|
2
|
+
//
|
|
3
|
+
// A globally-linked `uploads` binary resolves to this monorepo package and
|
|
4
|
+
// imports the compiled `dist/cli.js`. `dist/` is only rebuilt by an explicit
|
|
5
|
+
// `pnpm --filter @buildinternet/uploads build`, so after pulling new source
|
|
6
|
+
// the linked CLI can silently keep running old compiled code while
|
|
7
|
+
// `--version` still reports the current package version.
|
|
8
|
+
//
|
|
9
|
+
// This module is intentionally plain, dependency-free JS (not compiled)
|
|
10
|
+
// so it can run before `dist/` is imported, and so it has no cost at all
|
|
11
|
+
// for a published npm install: `files` in package.json ships only
|
|
12
|
+
// `bin/`, `dist/`, and `README.md` — never `src/` — so `sourceDir` below
|
|
13
|
+
// simply won't exist there, and `checkDistStaleness` returns early after
|
|
14
|
+
// one `existsSync` call.
|
|
15
|
+
|
|
16
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
|
|
19
|
+
/** Recursively find the newest mtime (ms) among files under `dir`. */
|
|
20
|
+
function newestMtimeMs(dir) {
|
|
21
|
+
let newest = -Infinity;
|
|
22
|
+
const stack = [dir];
|
|
23
|
+
while (stack.length > 0) {
|
|
24
|
+
const current = stack.pop();
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
28
|
+
} catch {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
33
|
+
const full = join(current, entry.name);
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
stack.push(full);
|
|
36
|
+
} else if (entry.isFile()) {
|
|
37
|
+
try {
|
|
38
|
+
const mtimeMs = statSync(full).mtimeMs;
|
|
39
|
+
if (mtimeMs > newest) newest = mtimeMs;
|
|
40
|
+
} catch {
|
|
41
|
+
// ignore races (file removed between readdir and stat)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return newest;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compare source mtime against dist mtime.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} packageRoot absolute path to packages/uploads
|
|
53
|
+
* @returns {{ stale: boolean, checked: boolean, reason?: string }}
|
|
54
|
+
* `checked` is false when there's no source tree to compare against
|
|
55
|
+
* (i.e. a published install) — the check is a no-op in that case.
|
|
56
|
+
*/
|
|
57
|
+
export function checkDistStaleness(packageRoot) {
|
|
58
|
+
const sourceDir = join(packageRoot, "src");
|
|
59
|
+
const distDir = join(packageRoot, "dist");
|
|
60
|
+
|
|
61
|
+
// No src/ tree ships in the published npm package (see package.json
|
|
62
|
+
// "files"), so this is the cheapest reliable signal that we're running
|
|
63
|
+
// inside the monorepo (dev/linked context) rather than a published install.
|
|
64
|
+
if (!existsSync(sourceDir)) {
|
|
65
|
+
return { stale: false, checked: false };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!existsSync(distDir)) {
|
|
69
|
+
return { stale: true, checked: true, reason: "dist/ is missing" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const sourceMtime = newestMtimeMs(sourceDir);
|
|
73
|
+
const distMtime = newestMtimeMs(distDir);
|
|
74
|
+
|
|
75
|
+
if (sourceMtime > distMtime) {
|
|
76
|
+
return { stale: true, checked: true, reason: "dist/ predates src/" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { stale: false, checked: true };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Print a one-line stderr warning if the dev build looks stale. Never throws. */
|
|
83
|
+
export function warnIfDistStale(packageRoot) {
|
|
84
|
+
try {
|
|
85
|
+
const result = checkDistStaleness(packageRoot);
|
|
86
|
+
if (result.checked && result.stale) {
|
|
87
|
+
process.stderr.write(
|
|
88
|
+
`warning: uploads dev build is stale (${result.reason}) — run \`pnpm --filter @buildinternet/uploads build\`\n`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
// Never let the staleness check itself break the CLI.
|
|
93
|
+
}
|
|
94
|
+
}
|
package/bin/uploads.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { warnIfDistStale } from "./dist-staleness.mjs";
|
|
5
|
+
|
|
6
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
warnIfDistStale(packageRoot);
|
|
8
|
+
|
|
9
|
+
const { runCli } = await import("../dist/cli.js");
|
|
3
10
|
|
|
4
11
|
runCli(process.argv)
|
|
5
12
|
.then((code) => process.exit(code ?? 0))
|
package/dist/client.d.ts
CHANGED
|
@@ -236,6 +236,13 @@ export interface GithubHealthResult {
|
|
|
236
236
|
events: string[] | null;
|
|
237
237
|
missingEvents: string[];
|
|
238
238
|
requiredEvents: string[];
|
|
239
|
+
/**
|
|
240
|
+
* Recommended-but-non-gating events, e.g. `issue_comment` (issue #333).
|
|
241
|
+
* Optional: older servers' health payload predates this field — treat a
|
|
242
|
+
* missing field as "no recommendations", not an error.
|
|
243
|
+
*/
|
|
244
|
+
recommendedEvents?: string[];
|
|
245
|
+
missingRecommendedEvents?: string[];
|
|
239
246
|
hint?: string;
|
|
240
247
|
}
|
|
241
248
|
export interface UsageResult {
|
package/dist/commands.js
CHANGED
|
@@ -1755,6 +1755,16 @@ Examples:
|
|
|
1755
1755
|
uploads github unlink --repo buildinternet/uploads
|
|
1756
1756
|
uploads github doctor
|
|
1757
1757
|
`;
|
|
1758
|
+
/** Older servers' health payload predates recommendedEvents/missingRecommendedEvents — treat as no recommendations rather than crashing. */
|
|
1759
|
+
function missingRecommendedEventsOf(result) {
|
|
1760
|
+
return Array.isArray(result.missingRecommendedEvents) ? result.missingRecommendedEvents : [];
|
|
1761
|
+
}
|
|
1762
|
+
function recommendedNoteLine(result) {
|
|
1763
|
+
const missing = missingRecommendedEventsOf(result);
|
|
1764
|
+
if (missing.length === 0)
|
|
1765
|
+
return "";
|
|
1766
|
+
return `note: not subscribed to ${missing.join(", ")} (recommended) — enables bot-comment self-healing; subscribe under the App's Permissions & events\n`;
|
|
1767
|
+
}
|
|
1758
1768
|
function formatGithubDoctor(result) {
|
|
1759
1769
|
if (!result.configured) {
|
|
1760
1770
|
return `github app: not configured on this server${result.hint ? ` — ${result.hint}` : ""}\n`;
|
|
@@ -1763,10 +1773,12 @@ function formatGithubDoctor(result) {
|
|
|
1763
1773
|
return `github app: configured, but health check failed${result.hint ? ` — ${result.hint}` : ""}\n`;
|
|
1764
1774
|
}
|
|
1765
1775
|
if (result.ok) {
|
|
1766
|
-
return `github app: ok — subscribed to ${result.requiredEvents.join(", ")}\n
|
|
1776
|
+
return (`github app: ok — subscribed to ${result.requiredEvents.join(", ")}\n` +
|
|
1777
|
+
recommendedNoteLine(result));
|
|
1767
1778
|
}
|
|
1768
1779
|
return (`github app: missing webhook event subscription(s): ${result.missingEvents.join(", ")}\n` +
|
|
1769
|
-
(result.hint ? ` ${result.hint}\n` : "")
|
|
1780
|
+
(result.hint ? ` ${result.hint}\n` : "") +
|
|
1781
|
+
recommendedNoteLine(result));
|
|
1770
1782
|
}
|
|
1771
1783
|
function formatGithubLink(repo, result) {
|
|
1772
1784
|
return result.workspace
|
package/dist/github.js
CHANGED
|
@@ -239,5 +239,6 @@ export function attachmentsCommentBody(items, galleries = [], marker = ATTACHMEN
|
|
|
239
239
|
lines.push("", "</details>", "");
|
|
240
240
|
}
|
|
241
241
|
lines.push('<sub>Maintained by <a href="https://uploads.sh">uploads.sh</a> — re-uploading a file with the same name updates it everywhere it is embedded.</sub>');
|
|
242
|
+
lines.push('<sub>Add media: <code>uploads put <file> --pr <N> --comment</code> (or <code>--issue <N></code>) · <a href="https://uploads.sh/docs/github-app">docs</a></sub>');
|
|
242
243
|
return lines.join("\n");
|
|
243
244
|
}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -334,7 +334,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
334
334
|
noGit: { type: "boolean", description: "Don't derive the repo segment from git." },
|
|
335
335
|
comment: {
|
|
336
336
|
type: "boolean",
|
|
337
|
-
description: "With pr/issue: create or update the managed attachments comment via local gh auth (best-effort).",
|
|
337
|
+
description: "With pr/issue: create or update the managed attachments comment. Posts as uploads-sh[bot] when the GitHub App is installed on the repo; otherwise via local gh auth (best-effort).",
|
|
338
338
|
},
|
|
339
339
|
dryRun: {
|
|
340
340
|
type: "boolean",
|
|
@@ -1049,7 +1049,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1049
1049
|
},
|
|
1050
1050
|
{
|
|
1051
1051
|
name: "comment",
|
|
1052
|
-
description: "Create or update the managed attachments comment on a GitHub PR or issue, listing everything uploaded for it.
|
|
1052
|
+
description: "Create or update the managed attachments comment on a GitHub PR or issue, listing everything uploaded for it. Posts as uploads-sh[bot] when the GitHub App is installed on the repo; otherwise via local gh auth. Edits its own prior comment in place and never touches other comments.",
|
|
1053
1053
|
inputSchema: {
|
|
1054
1054
|
type: "object",
|
|
1055
1055
|
properties: {
|