@buildinternet/uploads 0.16.0 → 0.16.1
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/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/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: {
|