@buildinternet/uploads 0.9.0 → 0.10.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/README.md +23 -7
- package/dist/cli-args.d.ts +2 -0
- package/dist/cli-args.js +5 -0
- package/dist/cli-brand.d.ts +89 -0
- package/dist/cli-brand.js +166 -0
- package/dist/cli-catalog.d.ts +32 -0
- package/dist/cli-catalog.js +187 -0
- package/dist/cli-help.d.ts +26 -0
- package/dist/cli-help.js +176 -0
- package/dist/cli-style.d.ts +42 -0
- package/dist/cli-style.js +108 -0
- package/dist/cli.js +67 -73
- package/dist/client.d.ts +19 -0
- package/dist/client.js +31 -0
- package/dist/commands/admin-enrollment.js +2 -1
- package/dist/commands/completion.d.ts +3 -0
- package/dist/commands/completion.js +284 -0
- package/dist/commands/config.js +9 -7
- package/dist/commands/install.js +2 -1
- package/dist/commands/invite.js +15 -2
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.js +55 -10
- package/dist/commands/mcp.js +2 -1
- package/dist/commands/session.d.ts +31 -0
- package/dist/commands/session.js +140 -0
- package/dist/commands/setup.js +2 -1
- package/dist/commands.js +41 -20
- package/dist/config-file.d.ts +9 -0
- package/dist/config-file.js +39 -9
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/format-bytes.d.ts +5 -0
- package/dist/format-bytes.js +11 -0
- package/dist/update-check.d.ts +10 -0
- package/dist/update-check.js +28 -12
- package/package.json +1 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session commands: whoami / status (show identity) and logout (clear token).
|
|
3
|
+
*/
|
|
4
|
+
import { describeConfigSources, loadConfigFile, redactToken, removeConfigKeys, resolveConfig, resolveConfigPath, workspaceFromToken, } from "../config.js";
|
|
5
|
+
import { flagBool, flagString, parseCommandArgs } from "../cli-args.js";
|
|
6
|
+
import { writeCommandHelp } from "../cli-style.js";
|
|
7
|
+
import { writeJson } from "../io.js";
|
|
8
|
+
const WHOAMI_HELP = `uploads whoami
|
|
9
|
+
|
|
10
|
+
Show the active CLI identity: workspace, token (redacted), API URL, and where
|
|
11
|
+
each value came from. Alias: uploads status.
|
|
12
|
+
|
|
13
|
+
Does not call the API (offline). Use uploads doctor to verify the token works.
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--path <file> Config file to inspect (default: shared buildinternet config)
|
|
17
|
+
--json JSON on stdout
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
uploads whoami
|
|
21
|
+
uploads status --json
|
|
22
|
+
uploads whoami --path ./my.env
|
|
23
|
+
`;
|
|
24
|
+
const LOGOUT_HELP = `uploads logout
|
|
25
|
+
|
|
26
|
+
Remove the saved UPLOADS_TOKEN from the shared config file so this machine is
|
|
27
|
+
no longer signed in for the CLI. Does not revoke the token on the server.
|
|
28
|
+
|
|
29
|
+
Environment variables (UPLOADS_TOKEN) are not unset — export them yourself if set.
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--path <file> Config file to edit (default: shared buildinternet config)
|
|
33
|
+
--json JSON on stdout
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
uploads logout
|
|
37
|
+
uploads logout --path ./my.env
|
|
38
|
+
`;
|
|
39
|
+
export function buildWhoamiReport(opts) {
|
|
40
|
+
const flags = {
|
|
41
|
+
envFile: opts.envFile,
|
|
42
|
+
token: opts.token,
|
|
43
|
+
workspace: opts.workspace,
|
|
44
|
+
apiUrl: opts.apiUrl,
|
|
45
|
+
};
|
|
46
|
+
const config = resolveConfig({ ...flags, requireToken: false });
|
|
47
|
+
const sources = describeConfigSources(flags);
|
|
48
|
+
const tokenInConfig = Boolean(loadConfigFile(config.configPath).UPLOADS_TOKEN);
|
|
49
|
+
return {
|
|
50
|
+
signedIn: Boolean(config.token),
|
|
51
|
+
workspace: config.workspace,
|
|
52
|
+
workspaceSource: sources.workspace,
|
|
53
|
+
workspaceFromToken: config.token ? workspaceFromToken(config.token) : undefined,
|
|
54
|
+
token: redactToken(config.token || undefined),
|
|
55
|
+
tokenSource: sources.token,
|
|
56
|
+
tokenInConfig,
|
|
57
|
+
apiUrl: config.apiUrl,
|
|
58
|
+
apiUrlSource: sources.apiUrl,
|
|
59
|
+
configPath: config.configPath,
|
|
60
|
+
configExists: config.configExists,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function formatWhoami(report) {
|
|
64
|
+
const cfg = `${report.configPath}${report.configExists ? "" : " (missing)"}`;
|
|
65
|
+
const lines = [
|
|
66
|
+
`signed in: ${report.signedIn ? "yes" : "no"}`,
|
|
67
|
+
`workspace: ${report.workspace} (${report.workspaceSource})`,
|
|
68
|
+
];
|
|
69
|
+
if (report.workspaceFromToken && report.workspaceFromToken !== report.workspace) {
|
|
70
|
+
lines.push(`token ws: ${report.workspaceFromToken} (encoded in token)`);
|
|
71
|
+
}
|
|
72
|
+
if (report.signedIn) {
|
|
73
|
+
lines.push(`token: ${report.token} (${report.tokenSource})`);
|
|
74
|
+
}
|
|
75
|
+
lines.push(`api: ${report.apiUrl} (${report.apiUrlSource})`);
|
|
76
|
+
lines.push(`config: ${cfg}`);
|
|
77
|
+
if (!report.signedIn) {
|
|
78
|
+
lines.push("", "hint: run uploads login to sign in");
|
|
79
|
+
}
|
|
80
|
+
else if (report.tokenSource === "env" && !report.tokenInConfig) {
|
|
81
|
+
lines.push("", "note: token is from the environment, not the config file", " uploads logout only clears the config file");
|
|
82
|
+
}
|
|
83
|
+
return lines.join("\n") + "\n";
|
|
84
|
+
}
|
|
85
|
+
function wantsJson(opts, parsed) {
|
|
86
|
+
return Boolean(opts.json || flagBool(parsed.flags, "--json"));
|
|
87
|
+
}
|
|
88
|
+
export async function runWhoami(args, opts, help = false) {
|
|
89
|
+
const parsed = parseCommandArgs(args);
|
|
90
|
+
if (help || parsed.help) {
|
|
91
|
+
writeCommandHelp(WHOAMI_HELP);
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
const report = buildWhoamiReport({
|
|
95
|
+
envFile: flagString(parsed.flags, "--path") ?? opts.envFile,
|
|
96
|
+
token: opts.token,
|
|
97
|
+
workspace: opts.workspace,
|
|
98
|
+
apiUrl: opts.apiUrl,
|
|
99
|
+
});
|
|
100
|
+
if (wantsJson(opts, parsed))
|
|
101
|
+
await writeJson(report);
|
|
102
|
+
else
|
|
103
|
+
process.stdout.write(formatWhoami(report));
|
|
104
|
+
return report.signedIn ? 0 : 1;
|
|
105
|
+
}
|
|
106
|
+
export async function runLogout(args, opts, help = false) {
|
|
107
|
+
const parsed = parseCommandArgs(args);
|
|
108
|
+
if (help || parsed.help) {
|
|
109
|
+
writeCommandHelp(LOGOUT_HELP);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
const path = flagString(parsed.flags, "--path") ?? resolveConfigPath({ envFile: opts.envFile });
|
|
113
|
+
const hadFileToken = Boolean(loadConfigFile(path).UPLOADS_TOKEN);
|
|
114
|
+
const envTokenStillSet = Boolean(process.env.UPLOADS_TOKEN);
|
|
115
|
+
const result = removeConfigKeys(path, ["UPLOADS_TOKEN"]);
|
|
116
|
+
const payload = {
|
|
117
|
+
path: result.path,
|
|
118
|
+
removed: result.removed,
|
|
119
|
+
configExisted: result.existed,
|
|
120
|
+
hadTokenInConfig: hadFileToken,
|
|
121
|
+
envTokenStillSet,
|
|
122
|
+
};
|
|
123
|
+
if (wantsJson(opts, parsed)) {
|
|
124
|
+
await writeJson(payload);
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
if (result.removed.includes("UPLOADS_TOKEN")) {
|
|
128
|
+
process.stdout.write(`signed out — removed UPLOADS_TOKEN from ${result.path}\n`);
|
|
129
|
+
}
|
|
130
|
+
else if (!result.existed) {
|
|
131
|
+
process.stdout.write(`no config file at ${result.path} — already signed out\n`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
process.stdout.write(`no UPLOADS_TOKEN in ${result.path} — already signed out\n`);
|
|
135
|
+
}
|
|
136
|
+
if (envTokenStillSet) {
|
|
137
|
+
process.stderr.write("note: UPLOADS_TOKEN is still set in the environment; unset it to fully sign out\n");
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createUploadsClient } from "../client.js";
|
|
|
2
2
|
import { DEFAULT_API_URL, DEFAULT_WORKSPACE, describeConfigSources, putDefaultsToConfigValues, redactToken, resolveApiUrl, resolveConfig, resolveConfigPath, resolvePutDefaults, writeConfigKeys, workspaceFromToken, } from "../config.js";
|
|
3
3
|
import { flagBool, flagInt, flagString, parseCommandArgs, UsageError } from "../cli-args.js";
|
|
4
4
|
import { UploadsError } from "../errors.js";
|
|
5
|
+
import { writeCommandHelp } from "../cli-style.js";
|
|
5
6
|
const SETUP_HELP = `uploads setup — guided CLI configuration
|
|
6
7
|
|
|
7
8
|
Writes UPLOADS_* keys to the shared buildinternet config file and prints
|
|
@@ -99,7 +100,7 @@ function formatWizard(status) {
|
|
|
99
100
|
export async function runSetup(args, opts, help = false) {
|
|
100
101
|
const parsed = parseCommandArgs(args);
|
|
101
102
|
if (help || parsed.help) {
|
|
102
|
-
|
|
103
|
+
writeCommandHelp(SETUP_HELP);
|
|
103
104
|
return 0;
|
|
104
105
|
}
|
|
105
106
|
const apiUrl = flagString(parsed.flags, "--api-url");
|
package/dist/commands.js
CHANGED
|
@@ -14,7 +14,9 @@ import { resolvePutPrefix } from "./destinations.js";
|
|
|
14
14
|
import { optimizeImageForUpload, rewriteKeyExtension, } from "./optimize.js";
|
|
15
15
|
import { applyFrame, resolveFrameId } from "./frame.js";
|
|
16
16
|
import { buildCliProvenance } from "./provenance.js";
|
|
17
|
+
import { formatByteSize } from "./format-bytes.js";
|
|
17
18
|
import { packageVersion } from "./package-version.js";
|
|
19
|
+
import { writeCommandHelp } from "./cli-style.js";
|
|
18
20
|
/** Read a local file (or `-` for stdin). Missing path → FILE_NOT_FOUND (exit 2). */
|
|
19
21
|
export function readFileArg(fileArg) {
|
|
20
22
|
try {
|
|
@@ -44,6 +46,11 @@ Uploads are public. --pr/--issue keys include the repo, number, and filename and
|
|
|
44
46
|
remain public even for private/internal GitHub repositories. Upload only media
|
|
45
47
|
that is safe at a predictable public URL.
|
|
46
48
|
|
|
49
|
+
Re-uploading the same key overwrites in place (no prompt) so embeds hot-swap;
|
|
50
|
+
human mode prints ">> replaced existing object (same URL)" after a real put,
|
|
51
|
+
or ">> would replace existing object (same URL)" on --dry-run when the key
|
|
52
|
+
already exists.
|
|
53
|
+
|
|
47
54
|
Human/json output includes durable url and (when dual-host applies) embedUrl.
|
|
48
55
|
MARKDOWN prefers embedUrl for GitHub. Override: UPLOADS_EMBED_PUBLIC_BASE_URL.
|
|
49
56
|
|
|
@@ -77,7 +84,7 @@ Options:
|
|
|
77
84
|
Re-uploading to an existing key WITH --meta replaces that file's
|
|
78
85
|
entire metadata set; without --meta the existing metadata is
|
|
79
86
|
preserved. Use "uploads meta set" to edit individual keys.
|
|
80
|
-
--dry-run Print key + public URL without uploading. Not with --comment/--gallery
|
|
87
|
+
--dry-run Print key + public URL without uploading; reports if the key would replace an existing object. Not with --comment/--gallery
|
|
81
88
|
|
|
82
89
|
Exit codes: 0 ok · 2 usage/token/file · 3 auth/policy · 4 network · 1 other.
|
|
83
90
|
Scripted formats (json|url|markdown) also print failures on stdout.
|
|
@@ -148,13 +155,20 @@ export function optimizeOptionsFromFlags(flags, defaults) {
|
|
|
148
155
|
}
|
|
149
156
|
function formatOptimizeNote(opt) {
|
|
150
157
|
if (opt.optimized) {
|
|
151
|
-
return `optimized ${opt.originalBytes} → ${opt.outputBytes}
|
|
158
|
+
return `optimized ${formatByteSize(opt.originalBytes)} → ${formatByteSize(opt.outputBytes)} (${opt.filename})`;
|
|
152
159
|
}
|
|
153
160
|
if (opt.skippedReason && opt.skippedReason !== "disabled") {
|
|
154
161
|
return `optimize skipped (${opt.skippedReason})`;
|
|
155
162
|
}
|
|
156
163
|
return undefined;
|
|
157
164
|
}
|
|
165
|
+
function writeReplacedNote(replaced, quiet, dryRun = false) {
|
|
166
|
+
if (!quiet && replaced) {
|
|
167
|
+
process.stderr.write(dryRun
|
|
168
|
+
? `>> would replace existing object (same URL)\n`
|
|
169
|
+
: `>> replaced existing object (same URL)\n`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
158
172
|
/** Frame (optional) then optimize — shared by put/attach/MCP. */
|
|
159
173
|
export async function prepareImageForUpload(bytes, filename, opts) {
|
|
160
174
|
let currentBytes = bytes;
|
|
@@ -259,6 +273,10 @@ Attachments are public and their repo/number/filename keys are predictable.
|
|
|
259
273
|
Private/internal GitHub repository visibility does not restrict access; upload
|
|
260
274
|
only media that is safe at a public URL.
|
|
261
275
|
|
|
276
|
+
Same filename under the same PR/issue overwrites in place (no prompt) so the
|
|
277
|
+
URL and every embed hot-swap. Human mode prints ">> replaced existing object
|
|
278
|
+
(same URL)" when that happens.
|
|
279
|
+
|
|
262
280
|
Still images are optimized to WebP by default (same as put). Use --no-optimize
|
|
263
281
|
to upload originals. Optional --frame wraps images in device/browser chrome.
|
|
264
282
|
|
|
@@ -293,11 +311,11 @@ Examples:
|
|
|
293
311
|
export async function runAttach(ctx, args, help = false, run = execRunner) {
|
|
294
312
|
const parsed = parseCommandArgs(args);
|
|
295
313
|
if (help || parsed.help) {
|
|
296
|
-
|
|
314
|
+
writeCommandHelp(ATTACH_HELP);
|
|
297
315
|
return 0;
|
|
298
316
|
}
|
|
299
317
|
if (parsed.positionals.length === 0) {
|
|
300
|
-
|
|
318
|
+
writeCommandHelp(ATTACH_HELP);
|
|
301
319
|
return 2;
|
|
302
320
|
}
|
|
303
321
|
if (parsed.flags.has("--no-comment") && typeof parsed.flags.get("--no-comment") === "string") {
|
|
@@ -348,6 +366,7 @@ export async function runAttach(ctx, args, help = false, run = execRunner) {
|
|
|
348
366
|
}),
|
|
349
367
|
metadata,
|
|
350
368
|
});
|
|
369
|
+
writeReplacedNote(result.replaced, ctx.quiet || ctx.json);
|
|
351
370
|
const embedSrc = urlForGithubEmbed(result.url, result.embedUrl);
|
|
352
371
|
results.push({
|
|
353
372
|
...result,
|
|
@@ -393,17 +412,17 @@ export async function runAttach(ctx, args, help = false, run = execRunner) {
|
|
|
393
412
|
}
|
|
394
413
|
export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
395
414
|
if (help) {
|
|
396
|
-
|
|
415
|
+
writeCommandHelp(PUT_HELP);
|
|
397
416
|
return 0;
|
|
398
417
|
}
|
|
399
418
|
const parsed = parseCommandArgs(args);
|
|
400
419
|
if (parsed.help) {
|
|
401
|
-
|
|
420
|
+
writeCommandHelp(PUT_HELP);
|
|
402
421
|
return 0;
|
|
403
422
|
}
|
|
404
423
|
const fileArg = parsed.positionals[0];
|
|
405
424
|
if (!fileArg) {
|
|
406
|
-
|
|
425
|
+
writeCommandHelp(PUT_HELP);
|
|
407
426
|
return 2;
|
|
408
427
|
}
|
|
409
428
|
const keyHint = flagString(parsed.flags, "--key");
|
|
@@ -562,6 +581,8 @@ export async function runPut(ctx, args, help = false, run = execRunner) {
|
|
|
562
581
|
}),
|
|
563
582
|
metadata,
|
|
564
583
|
});
|
|
584
|
+
if (format === "human")
|
|
585
|
+
writeReplacedNote(result.replaced, ctx.quiet, dryRun);
|
|
565
586
|
const embedSrc = urlForGithubEmbed(result.url, result.embedUrl);
|
|
566
587
|
const markdown = buildMarkdown(embedSrc, { alt, width });
|
|
567
588
|
let gallery;
|
|
@@ -673,7 +694,7 @@ export async function runGallery(ctx, args, help = false) {
|
|
|
673
694
|
const parsed = parseCommandArgs(args);
|
|
674
695
|
const action = parsed.positionals[0];
|
|
675
696
|
if (help || parsed.help || !action) {
|
|
676
|
-
|
|
697
|
+
writeCommandHelp(GALLERY_HELP);
|
|
677
698
|
return help || parsed.help ? 0 : 2;
|
|
678
699
|
}
|
|
679
700
|
switch (action) {
|
|
@@ -887,7 +908,7 @@ async function runFindFiles(ctx, filters, flags) {
|
|
|
887
908
|
export async function runList(ctx, args, help = false, run = execRunner) {
|
|
888
909
|
const parsed = parseCommandArgs(args);
|
|
889
910
|
if (help || parsed.help) {
|
|
890
|
-
|
|
911
|
+
writeCommandHelp(LIST_HELP);
|
|
891
912
|
return 0;
|
|
892
913
|
}
|
|
893
914
|
const metaPairs = flagValues(parsed.flags, "--meta");
|
|
@@ -945,11 +966,11 @@ Examples:
|
|
|
945
966
|
export async function runFind(ctx, args, help = false) {
|
|
946
967
|
const parsed = parseCommandArgs(args);
|
|
947
968
|
if (help || parsed.help) {
|
|
948
|
-
|
|
969
|
+
writeCommandHelp(FIND_HELP);
|
|
949
970
|
return 0;
|
|
950
971
|
}
|
|
951
972
|
if (parsed.positionals.length === 0) {
|
|
952
|
-
|
|
973
|
+
writeCommandHelp(FIND_HELP);
|
|
953
974
|
return 2;
|
|
954
975
|
}
|
|
955
976
|
const filters = parseMetaFlags(parsed.positionals);
|
|
@@ -974,7 +995,7 @@ export async function runMeta(ctx, args, help = false) {
|
|
|
974
995
|
const parsed = parseCommandArgs(args);
|
|
975
996
|
const action = parsed.positionals[0];
|
|
976
997
|
if (help || parsed.help || !action) {
|
|
977
|
-
|
|
998
|
+
writeCommandHelp(META_HELP);
|
|
978
999
|
return help || parsed.help ? 0 : 2;
|
|
979
1000
|
}
|
|
980
1001
|
switch (action) {
|
|
@@ -1034,12 +1055,12 @@ Examples:
|
|
|
1034
1055
|
export async function runDelete(ctx, args, help = false) {
|
|
1035
1056
|
const parsed = parseCommandArgs(args);
|
|
1036
1057
|
if (help || parsed.help) {
|
|
1037
|
-
|
|
1058
|
+
writeCommandHelp(DELETE_HELP);
|
|
1038
1059
|
return 0;
|
|
1039
1060
|
}
|
|
1040
1061
|
const key = parsed.positionals[0];
|
|
1041
1062
|
if (!key) {
|
|
1042
|
-
|
|
1063
|
+
writeCommandHelp(DELETE_HELP);
|
|
1043
1064
|
return 2;
|
|
1044
1065
|
}
|
|
1045
1066
|
if (flagBool(parsed.flags, "--dry-run")) {
|
|
@@ -1071,7 +1092,7 @@ Examples:
|
|
|
1071
1092
|
export async function runComment(ctx, args, help = false, run = execRunner) {
|
|
1072
1093
|
const parsed = parseCommandArgs(args);
|
|
1073
1094
|
if (help || parsed.help) {
|
|
1074
|
-
|
|
1095
|
+
writeCommandHelp(COMMENT_HELP);
|
|
1075
1096
|
return 0;
|
|
1076
1097
|
}
|
|
1077
1098
|
const target = ghTargetFromFlags(parsed.flags, run);
|
|
@@ -1099,7 +1120,7 @@ Examples:
|
|
|
1099
1120
|
`;
|
|
1100
1121
|
export async function runUsage(ctx, args, help = false) {
|
|
1101
1122
|
if (help || parseCommandArgs(args).help) {
|
|
1102
|
-
|
|
1123
|
+
writeCommandHelp(USAGE_HELP);
|
|
1103
1124
|
return 0;
|
|
1104
1125
|
}
|
|
1105
1126
|
const result = await ctx.client.usage();
|
|
@@ -1127,7 +1148,7 @@ Examples:
|
|
|
1127
1148
|
`;
|
|
1128
1149
|
export async function runReconcile(ctx, args, help = false) {
|
|
1129
1150
|
if (help || parseCommandArgs(args).help) {
|
|
1130
|
-
|
|
1151
|
+
writeCommandHelp(RECONCILE_HELP);
|
|
1131
1152
|
return 0;
|
|
1132
1153
|
}
|
|
1133
1154
|
const result = await ctx.client.reconcile();
|
|
@@ -1150,7 +1171,7 @@ Examples:
|
|
|
1150
1171
|
`;
|
|
1151
1172
|
export async function runPurgeExpired(ctx, args, help = false) {
|
|
1152
1173
|
if (help || parseCommandArgs(args).help) {
|
|
1153
|
-
|
|
1174
|
+
writeCommandHelp(PURGE_HELP);
|
|
1154
1175
|
return 0;
|
|
1155
1176
|
}
|
|
1156
1177
|
const result = await ctx.client.purgeExpired();
|
|
@@ -1176,7 +1197,7 @@ Examples:
|
|
|
1176
1197
|
`;
|
|
1177
1198
|
export async function runHealth(ctx, args, help = false) {
|
|
1178
1199
|
if (help || parseCommandArgs(args).help) {
|
|
1179
|
-
|
|
1200
|
+
writeCommandHelp(HEALTH_HELP);
|
|
1180
1201
|
return 0;
|
|
1181
1202
|
}
|
|
1182
1203
|
const result = await createUploadsClient({
|
|
@@ -1260,7 +1281,7 @@ export async function buildDoctorReport(config, client) {
|
|
|
1260
1281
|
}
|
|
1261
1282
|
export async function runDoctor(ctx, args, help = false) {
|
|
1262
1283
|
if (help || parseCommandArgs(args).help) {
|
|
1263
|
-
|
|
1284
|
+
writeCommandHelp(DOCTOR_HELP);
|
|
1264
1285
|
return 0;
|
|
1265
1286
|
}
|
|
1266
1287
|
const report = await buildDoctorReport(ctx.config, ctx.client);
|
package/dist/config-file.d.ts
CHANGED
|
@@ -40,4 +40,13 @@ export declare function writeConfigKeys(path: string, keys: UploadsConfigValues,
|
|
|
40
40
|
updated: string[];
|
|
41
41
|
};
|
|
42
42
|
export declare function configValuesFromClient(config: Partial<UploadsClientConfig>, defaults?: PutDefaults): UploadsConfigValues;
|
|
43
|
+
/**
|
|
44
|
+
* Remove keys from the shared config file (e.g. logout clears UPLOADS_TOKEN).
|
|
45
|
+
* No-op for missing file/keys. Preserves other lines and comments.
|
|
46
|
+
*/
|
|
47
|
+
export declare function removeConfigKeys(path: string, keys: readonly string[]): {
|
|
48
|
+
path: string;
|
|
49
|
+
removed: string[];
|
|
50
|
+
existed: boolean;
|
|
51
|
+
};
|
|
43
52
|
export { PUT_DEFAULT_KEY_MAP };
|
package/dist/config-file.js
CHANGED
|
@@ -226,15 +226,7 @@ export function writeConfigKeys(path, keys, opts) {
|
|
|
226
226
|
updated.push(key);
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
|
-
|
|
230
|
-
writeFileSync(tmp, lines.join("\n").replace(/\n*$/, "\n"), { encoding: "utf8", mode: 0o600 });
|
|
231
|
-
renameSync(tmp, path);
|
|
232
|
-
try {
|
|
233
|
-
chmodSync(path, 0o600);
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
/* Windows/filesystems may not support modes. */
|
|
237
|
-
}
|
|
229
|
+
writeConfigFileAtomic(path, lines.join("\n"));
|
|
238
230
|
return { path, created: !existed, updated };
|
|
239
231
|
}
|
|
240
232
|
export function configValuesFromClient(config, defaults) {
|
|
@@ -248,4 +240,42 @@ export function configValuesFromClient(config, defaults) {
|
|
|
248
240
|
Object.assign(out, putDefaultsToConfigValues(defaults ?? {}));
|
|
249
241
|
return out;
|
|
250
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Remove keys from the shared config file (e.g. logout clears UPLOADS_TOKEN).
|
|
245
|
+
* No-op for missing file/keys. Preserves other lines and comments.
|
|
246
|
+
*/
|
|
247
|
+
export function removeConfigKeys(path, keys) {
|
|
248
|
+
if (!existsSync(path))
|
|
249
|
+
return { path, removed: [], existed: false };
|
|
250
|
+
const keySet = new Set(keys);
|
|
251
|
+
const removed = [];
|
|
252
|
+
const kept = [];
|
|
253
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
254
|
+
const parsed = parseEnvLine(line);
|
|
255
|
+
if (parsed && keySet.has(parsed.key)) {
|
|
256
|
+
if (!removed.includes(parsed.key))
|
|
257
|
+
removed.push(parsed.key);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
kept.push(line);
|
|
261
|
+
}
|
|
262
|
+
if (removed.length === 0)
|
|
263
|
+
return { path, removed: [], existed: true };
|
|
264
|
+
// Drop consecutive blank lines left by removals
|
|
265
|
+
const collapsed = kept.filter((line, i) => !(line === "" && i > 0 && kept[i - 1] === ""));
|
|
266
|
+
writeConfigFileAtomic(path, collapsed.join("\n"));
|
|
267
|
+
return { path, removed, existed: true };
|
|
268
|
+
}
|
|
269
|
+
/** Atomic write + mode 0o600 (best-effort on platforms without chmod). */
|
|
270
|
+
function writeConfigFileAtomic(path, body) {
|
|
271
|
+
const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
272
|
+
writeFileSync(tmp, body.replace(/\n*$/, "\n"), { encoding: "utf8", mode: 0o600 });
|
|
273
|
+
renameSync(tmp, path);
|
|
274
|
+
try {
|
|
275
|
+
chmodSync(path, 0o600);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
/* Windows/filesystems may not support modes. */
|
|
279
|
+
}
|
|
280
|
+
}
|
|
251
281
|
export { PUT_DEFAULT_KEY_MAP };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { defaultConfigPath, resolveConfigPath, loadConfigFile, redactToken, writeConfigKeys, configValuesFromClient, putDefaultsToConfigValues, resolvePutDefaults, mergePutDefaults, UPLOADS_CONFIG_KEYS, type UploadsConfigKey, type UploadsConfigValues, type PutDefaults, } from "./config-file.js";
|
|
1
|
+
export { defaultConfigPath, resolveConfigPath, loadConfigFile, redactToken, writeConfigKeys, removeConfigKeys, configValuesFromClient, putDefaultsToConfigValues, resolvePutDefaults, mergePutDefaults, UPLOADS_CONFIG_KEYS, type UploadsConfigKey, type UploadsConfigValues, type PutDefaults, } from "./config-file.js";
|
|
2
2
|
export interface UploadsClientConfig {
|
|
3
3
|
apiUrl: string;
|
|
4
4
|
workspace: string;
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { loadConfigFile, resolveConfigPath } from "./config-file.js";
|
|
3
3
|
import { UploadsError } from "./errors.js";
|
|
4
|
-
export { defaultConfigPath, resolveConfigPath, loadConfigFile, redactToken, writeConfigKeys, configValuesFromClient, putDefaultsToConfigValues, resolvePutDefaults, mergePutDefaults, UPLOADS_CONFIG_KEYS, } from "./config-file.js";
|
|
4
|
+
export { defaultConfigPath, resolveConfigPath, loadConfigFile, redactToken, writeConfigKeys, removeConfigKeys, configValuesFromClient, putDefaultsToConfigValues, resolvePutDefaults, mergePutDefaults, UPLOADS_CONFIG_KEYS, } from "./config-file.js";
|
|
5
5
|
export const DEFAULT_API_URL = "https://api.uploads.sh";
|
|
6
6
|
export const DEFAULT_WORKSPACE = "default";
|
|
7
7
|
const TOKEN_WORKSPACE_RE = /^up_([a-z0-9][a-z0-9-]{1,62})_/;
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type UploadsErrorCode = "MISSING_TOKEN" | "NO_PUBLIC_URL" | "FILE_NOT_FOUND" | "NOT_FOUND" | "UNAUTHORIZED" | "INVALID_KEY" | "KEY_POLICY" | "STORAGE_QUOTA" | "UPLOAD_BUDGET" | "API_ERROR" | "NETWORK" | "USAGE";
|
|
1
|
+
export type UploadsErrorCode = "MISSING_TOKEN" | "NO_PUBLIC_URL" | "FILE_NOT_FOUND" | "NOT_FOUND" | "UNAUTHORIZED" | "INVALID_KEY" | "KEY_POLICY" | "STORAGE_QUOTA" | "UPLOAD_BUDGET" | "GITHUB_REQUIRED" | "API_ERROR" | "NETWORK" | "USAGE";
|
|
2
2
|
export declare class UploadsError extends Error {
|
|
3
3
|
readonly code: UploadsErrorCode;
|
|
4
4
|
readonly status?: number;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable size for CLI notes (1024-based).
|
|
3
|
+
* files-sdk has no size formatter — only raw `size` on head/upload results.
|
|
4
|
+
*/
|
|
5
|
+
export function formatByteSize(bytes) {
|
|
6
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
7
|
+
return "0 B";
|
|
8
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
9
|
+
const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
|
10
|
+
return `${(bytes / 1024 ** exponent).toFixed(exponent === 0 ? 0 : 1)} ${units[exponent]}`;
|
|
11
|
+
}
|
package/dist/update-check.d.ts
CHANGED
|
@@ -23,6 +23,16 @@ export declare function parseSemver(version: string): [number, number, number] |
|
|
|
23
23
|
export declare function isNewerVersion(latest: string, current: string): boolean;
|
|
24
24
|
export declare function readUpdateCache(path: string): UpdateCache | undefined;
|
|
25
25
|
export declare function writeUpdateCache(path: string, cache: UpdateCache): void;
|
|
26
|
+
export interface UpdateStatus {
|
|
27
|
+
current: string;
|
|
28
|
+
latest?: string;
|
|
29
|
+
updateAvailable: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve current vs latest published version (cache + optional network).
|
|
33
|
+
* Always resolves; never throws. Does not print.
|
|
34
|
+
*/
|
|
35
|
+
export declare function checkForUpdate(opts?: UpdateCheckOptions): Promise<UpdateStatus>;
|
|
26
36
|
/**
|
|
27
37
|
* If a newer published version is known (or can be fetched within the timeout),
|
|
28
38
|
* write a one-line stderr hint. Always resolves; never throws.
|
package/dist/update-check.js
CHANGED
|
@@ -69,20 +69,21 @@ export function writeUpdateCache(path, cache) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
72
|
+
* Resolve current vs latest published version (cache + optional network).
|
|
73
|
+
* Always resolves; never throws. Does not print.
|
|
74
74
|
*/
|
|
75
|
-
export async function
|
|
75
|
+
export async function checkForUpdate(opts = {}) {
|
|
76
|
+
const current = opts.currentVersion ?? packageVersion();
|
|
76
77
|
try {
|
|
77
|
-
if (opts.quiet || opts.command === "mcp")
|
|
78
|
-
return;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
if (opts.quiet || opts.command === "mcp") {
|
|
79
|
+
return { current, updateAvailable: false };
|
|
80
|
+
}
|
|
81
|
+
if (truthyEnv("UPLOADS_NO_UPDATE") || truthyEnv("NO_UPDATE_NOTIFIER")) {
|
|
82
|
+
return { current, updateAvailable: false };
|
|
83
|
+
}
|
|
82
84
|
const cachePath = opts.cachePath ?? defaultCachePath();
|
|
83
85
|
const now = opts.now ?? Date.now();
|
|
84
86
|
const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
85
|
-
const write = opts.write ?? ((text) => process.stderr.write(text));
|
|
86
87
|
const cached = readUpdateCache(cachePath);
|
|
87
88
|
let latest;
|
|
88
89
|
if (cached && now - cached.checkedAt < ttlMs && cached.current === current) {
|
|
@@ -98,9 +99,24 @@ export async function maybeHintUpdate(opts = {}) {
|
|
|
98
99
|
latest = cached.latest; // stale cache if network failed
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
const updateAvailable = Boolean(latest && isNewerVersion(latest, current));
|
|
103
|
+
return { current, latest, updateAvailable };
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return { current, updateAvailable: false };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* If a newer published version is known (or can be fetched within the timeout),
|
|
111
|
+
* write a one-line stderr hint. Always resolves; never throws.
|
|
112
|
+
*/
|
|
113
|
+
export async function maybeHintUpdate(opts = {}) {
|
|
114
|
+
try {
|
|
115
|
+
const status = await checkForUpdate(opts);
|
|
116
|
+
if (!status.updateAvailable || !status.latest)
|
|
117
|
+
return;
|
|
118
|
+
const write = opts.write ?? ((text) => process.stderr.write(text));
|
|
119
|
+
write(`hint: ${PACKAGE_NAME}@${status.latest} is available (you have ${status.current}). Update: npm i -g ${PACKAGE_NAME}\n`);
|
|
104
120
|
}
|
|
105
121
|
catch {
|
|
106
122
|
// Never surface update-check failures.
|