@buildinternet/uploads 0.1.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 +64 -0
- package/bin/uploads.js +9 -0
- package/dist/agent.d.ts +8 -0
- package/dist/agent.js +24 -0
- package/dist/cli-args.d.ts +39 -0
- package/dist/cli-args.js +129 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +158 -0
- package/dist/client.d.ts +55 -0
- package/dist/client.js +97 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +202 -0
- package/dist/commands/setup.d.ts +4 -0
- package/dist/commands/setup.js +223 -0
- package/dist/commands.d.ts +19 -0
- package/dist/commands.js +451 -0
- package/dist/config-file.d.ts +37 -0
- package/dist/config-file.js +204 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +157 -0
- package/dist/embed.d.ts +6 -0
- package/dist/embed.js +30 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +10 -0
- package/dist/github-gh.d.ts +19 -0
- package/dist/github-gh.js +93 -0
- package/dist/github.d.ts +24 -0
- package/dist/github.js +44 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/keys.d.ts +11 -0
- package/dist/keys.js +34 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @buildinternet/uploads
|
|
2
|
+
|
|
3
|
+
CLI and client for **uploads.sh** — upload files, get public URLs, and produce GitHub-ready markdown. Successor to the R2 scripts in `buildinternet-skills/github-screenshots`.
|
|
4
|
+
|
|
5
|
+
## CLI
|
|
6
|
+
|
|
7
|
+
Binary: `uploads` (also `pnpm uploads` from repo root after `pnpm install`).
|
|
8
|
+
|
|
9
|
+
Install globally or run a pinned version without installing:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --global @buildinternet/uploads
|
|
13
|
+
npx @buildinternet/uploads@0.1.0 --help
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm uploads setup --env-file .env
|
|
18
|
+
pnpm uploads attach ./before.png ./after.png --env-file .env
|
|
19
|
+
pnpm uploads put ./shot.png --env-file .env
|
|
20
|
+
pnpm uploads put ./after.png --pr 123 --comment --env-file .env
|
|
21
|
+
pnpm uploads doctor --env-file .env
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Commands: `attach`, `put`, `comment`, `list`, `delete`, `setup`, `config`, `doctor`, `health`.
|
|
25
|
+
|
|
26
|
+
`attach` is the agent-friendly default for GitHub media. It accepts one or more files,
|
|
27
|
+
infers the pull request for the current branch via `gh`, uploads stable URLs, and creates
|
|
28
|
+
or updates one managed attachments comment. Use `--pr`, `--issue`, and `--repo` to select
|
|
29
|
+
the target explicitly, or `--no-comment` to upload without changing GitHub comments.
|
|
30
|
+
|
|
31
|
+
Config layers (first match wins): CLI flags → env vars → `--env-file` → `~/.config/buildinternet/config`. See `config.example` for keys.
|
|
32
|
+
|
|
33
|
+
## Programmatic use
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { createUploadsClient } from "@buildinternet/uploads";
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Agent/MCP helpers: `@buildinternet/uploads/agent` (`createUploadsWorkerFileTools` for Workers).
|
|
40
|
+
|
|
41
|
+
## Layout
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
src/
|
|
45
|
+
cli.ts Entry + help
|
|
46
|
+
commands.ts put, list, delete, comment, …
|
|
47
|
+
client.ts HTTP client for the API
|
|
48
|
+
github.ts PR/issue key paths + attachment comments
|
|
49
|
+
embed.ts Markdown image output
|
|
50
|
+
bin/uploads.js Bin shim
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pnpm build # tsc → dist/
|
|
57
|
+
pnpm typecheck
|
|
58
|
+
pnpm test
|
|
59
|
+
pnpm pack:check # verify the npm tarball contents
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Maintainer release instructions: [`docs/releasing.md`](../../docs/releasing.md).
|
|
63
|
+
|
|
64
|
+
Agent-oriented usage: [`skills/uploads-cli/SKILL.md`](../../skills/uploads-cli/SKILL.md). REST details: [`docs/api.md`](../../docs/api.md).
|
package/bin/uploads.js
ADDED
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Files } from "files-sdk";
|
|
2
|
+
import { type FileToolsOptions } from "files-sdk/ai-sdk";
|
|
3
|
+
export type { FileReadToolName, FileToolName, FileWriteToolName } from "files-sdk/ai-sdk";
|
|
4
|
+
/**
|
|
5
|
+
* Worker-side agent tools (Mode A). Pass `Files` from `createStorage()` —
|
|
6
|
+
* reuses files-sdk tool schemas; do not duplicate MCP tool definitions here.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createUploadsWorkerFileTools(files: Files, opts?: Omit<FileToolsOptions, "files">): import("files-sdk/ai-sdk").FileTools | import("files-sdk/ai-sdk").ReadOnlyFileTools;
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createFileTools } from "files-sdk/ai-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Worker-side agent tools (Mode A). Pass `Files` from `createStorage()` —
|
|
4
|
+
* reuses files-sdk tool schemas; do not duplicate MCP tool definitions here.
|
|
5
|
+
*/
|
|
6
|
+
export function createUploadsWorkerFileTools(files, opts = {}) {
|
|
7
|
+
const { overrides, requireApproval, ...rest } = opts;
|
|
8
|
+
return createFileTools({
|
|
9
|
+
files,
|
|
10
|
+
requireApproval: requireApproval ?? {
|
|
11
|
+
deleteFile: true,
|
|
12
|
+
uploadFile: false,
|
|
13
|
+
copyFile: true,
|
|
14
|
+
signUploadUrl: true,
|
|
15
|
+
},
|
|
16
|
+
overrides: {
|
|
17
|
+
uploadFile: {
|
|
18
|
+
description: "Upload a file for public hosting (e.g. GitHub embeds). Prefer keys under screenshots/.",
|
|
19
|
+
},
|
|
20
|
+
...overrides,
|
|
21
|
+
},
|
|
22
|
+
...rest,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface GlobalFlags {
|
|
2
|
+
apiUrl?: string;
|
|
3
|
+
workspace?: string;
|
|
4
|
+
token?: string;
|
|
5
|
+
envFile?: string;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
quiet?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ParsedArgv {
|
|
10
|
+
globals: GlobalFlags;
|
|
11
|
+
help: boolean;
|
|
12
|
+
command?: string;
|
|
13
|
+
/** Args starting at the command name (includes command-specific flags). */
|
|
14
|
+
rest: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function isHelpFlag(arg: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Parse global flags that appear before the subcommand. Stops at the first
|
|
19
|
+
* positional token (the command name) or an unrecognized flag.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseArgv(argv: string[]): ParsedArgv;
|
|
22
|
+
export declare class UsageError extends Error {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
export interface CommandFlags {
|
|
26
|
+
positionals: string[];
|
|
27
|
+
flags: Map<string, string | boolean>;
|
|
28
|
+
help: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse command-specific args. Supports `--flag value`, `--flag=value`, and
|
|
32
|
+
* boolean `--flag` flags.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseCommandArgs(args: string[]): CommandFlags;
|
|
35
|
+
export declare function flagString(flags: CommandFlags["flags"], name: string): string | undefined;
|
|
36
|
+
export declare function flagBool(flags: CommandFlags["flags"], name: string): boolean;
|
|
37
|
+
/** Command-level workspace override (`--workspace` / `-w`). */
|
|
38
|
+
export declare function commandWorkspace(flags: CommandFlags["flags"]): string | undefined;
|
|
39
|
+
export declare function flagInt(flags: CommandFlags["flags"], name: string, label: string): number | undefined;
|
package/dist/cli-args.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const VALUE_GLOBALS = new Set(["--api-url", "--workspace", "-w", "--token", "--env-file"]);
|
|
2
|
+
export function isHelpFlag(arg) {
|
|
3
|
+
return arg === "-h" || arg === "--help";
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Parse global flags that appear before the subcommand. Stops at the first
|
|
7
|
+
* positional token (the command name) or an unrecognized flag.
|
|
8
|
+
*/
|
|
9
|
+
export function parseArgv(argv) {
|
|
10
|
+
const args = argv.slice(2);
|
|
11
|
+
const globals = {};
|
|
12
|
+
let help = false;
|
|
13
|
+
let i = 0;
|
|
14
|
+
while (i < args.length) {
|
|
15
|
+
const arg = args[i];
|
|
16
|
+
if (isHelpFlag(arg)) {
|
|
17
|
+
help = true;
|
|
18
|
+
i++;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg === "--json") {
|
|
22
|
+
globals.json = true;
|
|
23
|
+
i++;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (arg === "--quiet") {
|
|
27
|
+
globals.quiet = true;
|
|
28
|
+
i++;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (VALUE_GLOBALS.has(arg)) {
|
|
32
|
+
const value = args[i + 1];
|
|
33
|
+
if (!value || value.startsWith("-")) {
|
|
34
|
+
throw new UsageError(`missing value for ${arg}`);
|
|
35
|
+
}
|
|
36
|
+
switch (arg) {
|
|
37
|
+
case "--api-url":
|
|
38
|
+
globals.apiUrl = value;
|
|
39
|
+
break;
|
|
40
|
+
case "--workspace":
|
|
41
|
+
case "-w":
|
|
42
|
+
globals.workspace = value;
|
|
43
|
+
break;
|
|
44
|
+
case "--token":
|
|
45
|
+
globals.token = value;
|
|
46
|
+
break;
|
|
47
|
+
case "--env-file":
|
|
48
|
+
globals.envFile = value;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
i += 2;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (arg.startsWith("-"))
|
|
55
|
+
break;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
const rest = args.slice(i);
|
|
59
|
+
return { globals, help, command: rest[0], rest };
|
|
60
|
+
}
|
|
61
|
+
export class UsageError extends Error {
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "UsageError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse command-specific args. Supports `--flag value`, `--flag=value`, and
|
|
69
|
+
* boolean `--flag` flags.
|
|
70
|
+
*/
|
|
71
|
+
export function parseCommandArgs(args) {
|
|
72
|
+
const positionals = [];
|
|
73
|
+
const flags = new Map();
|
|
74
|
+
let help = false;
|
|
75
|
+
let i = 0;
|
|
76
|
+
while (i < args.length) {
|
|
77
|
+
const arg = args[i];
|
|
78
|
+
if (isHelpFlag(arg)) {
|
|
79
|
+
help = true;
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (arg.startsWith("--")) {
|
|
84
|
+
const eq = arg.indexOf("=");
|
|
85
|
+
if (eq !== -1) {
|
|
86
|
+
flags.set(arg.slice(0, eq), arg.slice(eq + 1));
|
|
87
|
+
i++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const name = arg;
|
|
91
|
+
const next = args[i + 1];
|
|
92
|
+
if (next && !next.startsWith("-")) {
|
|
93
|
+
flags.set(name, next);
|
|
94
|
+
i += 2;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
flags.set(name, true);
|
|
98
|
+
i++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
positionals.push(arg);
|
|
102
|
+
i++;
|
|
103
|
+
}
|
|
104
|
+
return { positionals, flags, help };
|
|
105
|
+
}
|
|
106
|
+
export function flagString(flags, name) {
|
|
107
|
+
const value = flags.get(name);
|
|
108
|
+
return typeof value === "string" ? value : undefined;
|
|
109
|
+
}
|
|
110
|
+
export function flagBool(flags, name) {
|
|
111
|
+
return flags.get(name) === true;
|
|
112
|
+
}
|
|
113
|
+
/** Command-level workspace override (`--workspace` / `-w`). */
|
|
114
|
+
export function commandWorkspace(flags) {
|
|
115
|
+
return flagString(flags, "--workspace") ?? flagString(flags, "-w");
|
|
116
|
+
}
|
|
117
|
+
export function flagInt(flags, name, label) {
|
|
118
|
+
const raw = flagString(flags, name);
|
|
119
|
+
if (raw === undefined)
|
|
120
|
+
return undefined;
|
|
121
|
+
if (!/^\d+$/.test(raw)) {
|
|
122
|
+
throw new UsageError(`invalid ${label}: must be a positive integer (got ${raw})`);
|
|
123
|
+
}
|
|
124
|
+
const n = Number.parseInt(raw, 10);
|
|
125
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
126
|
+
throw new UsageError(`invalid ${label}: must be a positive integer (got ${raw})`);
|
|
127
|
+
}
|
|
128
|
+
return n;
|
|
129
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runCli(argv: string[]): Promise<number>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createUploadsClient } from "./client.js";
|
|
2
|
+
import { resolveApiUrl, resolveConfig } from "./config.js";
|
|
3
|
+
import { UploadsError } from "./errors.js";
|
|
4
|
+
import { commandWorkspace, isHelpFlag, parseArgv, parseCommandArgs, UsageError, } from "./cli-args.js";
|
|
5
|
+
import { runPut, runAttach, runList, runDelete, runHealth, runDoctor, runComment, } from "./commands.js";
|
|
6
|
+
import { runConfig } from "./commands/config.js";
|
|
7
|
+
import { runSetup } from "./commands/setup.js";
|
|
8
|
+
const ROOT_HELP = `uploads — CLI for uploads.sh (GitHub image embeds)
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
uploads [globals] <command> [args]
|
|
12
|
+
|
|
13
|
+
Config (first match wins, per key):
|
|
14
|
+
CLI flags --api-url, --token, --workspace
|
|
15
|
+
environment UPLOADS_API_URL, UPLOADS_TOKEN, UPLOADS_WORKSPACE
|
|
16
|
+
--env-file <path>
|
|
17
|
+
$BUILDINTERNET_CONFIG
|
|
18
|
+
~/.config/buildinternet/config (shared with github-screenshots)
|
|
19
|
+
|
|
20
|
+
Workspace (within config layers):
|
|
21
|
+
--workspace, -w override — global (before command) or per-command (after)
|
|
22
|
+
UPLOADS_WORKSPACE env / config file
|
|
23
|
+
(else inferred from token up_<name>_…, else "default")
|
|
24
|
+
|
|
25
|
+
Other globals (before command):
|
|
26
|
+
--api-url <url> default: https://api.uploads.sh
|
|
27
|
+
--token <token> or UPLOADS_TOKEN
|
|
28
|
+
--env-file <path>
|
|
29
|
+
--json JSON on stdout
|
|
30
|
+
--quiet
|
|
31
|
+
|
|
32
|
+
Commands:
|
|
33
|
+
attach <file...> Attach media to the current PR (stable URLs + managed comment)
|
|
34
|
+
put <file> Upload (+ URL + markdown for GitHub)
|
|
35
|
+
comment Create/update a PR/issue attachments comment (via gh)
|
|
36
|
+
list List objects
|
|
37
|
+
delete <key> Delete object
|
|
38
|
+
setup Guided config + token minting steps
|
|
39
|
+
config Show path, init, or set shared config
|
|
40
|
+
doctor Health + auth + workspace checks
|
|
41
|
+
health API liveness (no auth)
|
|
42
|
+
|
|
43
|
+
Put/list defaults (config file or env):
|
|
44
|
+
UPLOADS_DEFAULT_PREFIX, UPLOADS_DEFAULT_REPO, UPLOADS_DEFAULT_REF
|
|
45
|
+
UPLOADS_DEFAULT_WIDTH, UPLOADS_NO_GIT
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
uploads setup
|
|
49
|
+
uploads setup --token up_default_… --repo myorg/myapp
|
|
50
|
+
uploads attach ./before.png ./after.png
|
|
51
|
+
uploads put ./shot.png --ref 42
|
|
52
|
+
uploads doctor
|
|
53
|
+
|
|
54
|
+
Agent/MCP: use createUploadsWorkerFileTools() from @buildinternet/uploads/agent on the Worker.
|
|
55
|
+
`;
|
|
56
|
+
function createContext(globals, requireToken, commandArgs) {
|
|
57
|
+
const cmdWorkspace = commandWorkspace(parseCommandArgs(commandArgs).flags);
|
|
58
|
+
const config = resolveConfig({
|
|
59
|
+
apiUrl: globals.apiUrl,
|
|
60
|
+
workspace: cmdWorkspace ?? globals.workspace,
|
|
61
|
+
token: globals.token,
|
|
62
|
+
envFile: globals.envFile,
|
|
63
|
+
requireToken,
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
config,
|
|
67
|
+
client: createUploadsClient(config),
|
|
68
|
+
json: globals.json ?? false,
|
|
69
|
+
quiet: globals.quiet ?? false,
|
|
70
|
+
envFile: globals.envFile,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function exitCode(err) {
|
|
74
|
+
if (err instanceof UsageError)
|
|
75
|
+
return 2;
|
|
76
|
+
if (err instanceof UploadsError) {
|
|
77
|
+
switch (err.code) {
|
|
78
|
+
case "MISSING_TOKEN":
|
|
79
|
+
case "USAGE":
|
|
80
|
+
return 2;
|
|
81
|
+
case "UNAUTHORIZED":
|
|
82
|
+
case "NOT_FOUND":
|
|
83
|
+
return 3;
|
|
84
|
+
case "NETWORK":
|
|
85
|
+
return 4;
|
|
86
|
+
default:
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
function errorOut(err, json) {
|
|
93
|
+
const payload = err instanceof UploadsError
|
|
94
|
+
? { error: err.message, code: err.code, status: err.status }
|
|
95
|
+
: err instanceof UsageError
|
|
96
|
+
? { error: err.message, code: "USAGE" }
|
|
97
|
+
: { error: err instanceof Error ? err.message : String(err) };
|
|
98
|
+
if (json)
|
|
99
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
100
|
+
else {
|
|
101
|
+
const msg = payload.error;
|
|
102
|
+
if (msg.includes("\n"))
|
|
103
|
+
process.stderr.write(`${msg}\n`);
|
|
104
|
+
else
|
|
105
|
+
process.stderr.write(`error: ${msg}\n`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export async function runCli(argv) {
|
|
109
|
+
try {
|
|
110
|
+
const parsed = parseArgv(argv);
|
|
111
|
+
const json = parsed.globals.json ?? false;
|
|
112
|
+
if (!parsed.command) {
|
|
113
|
+
process.stderr.write(ROOT_HELP);
|
|
114
|
+
return parsed.help ? 0 : 2;
|
|
115
|
+
}
|
|
116
|
+
const cmdArgs = parsed.rest.slice(1);
|
|
117
|
+
const showHelp = parsed.help || cmdArgs.some(isHelpFlag);
|
|
118
|
+
switch (parsed.command) {
|
|
119
|
+
case "health":
|
|
120
|
+
return runHealth({ apiUrl: resolveApiUrl(parsed.globals), json }, cmdArgs, showHelp);
|
|
121
|
+
case "config":
|
|
122
|
+
return runConfig(cmdArgs, { json, envFile: parsed.globals.envFile }, showHelp);
|
|
123
|
+
case "setup":
|
|
124
|
+
return runSetup(cmdArgs, { json, envFile: parsed.globals.envFile }, showHelp);
|
|
125
|
+
case "attach":
|
|
126
|
+
case "put":
|
|
127
|
+
case "list":
|
|
128
|
+
case "delete":
|
|
129
|
+
case "doctor":
|
|
130
|
+
case "comment": {
|
|
131
|
+
const ctx = createContext(parsed.globals, !showHelp, cmdArgs);
|
|
132
|
+
switch (parsed.command) {
|
|
133
|
+
case "attach":
|
|
134
|
+
return runAttach(ctx, cmdArgs, showHelp);
|
|
135
|
+
case "put":
|
|
136
|
+
return runPut(ctx, cmdArgs, showHelp);
|
|
137
|
+
case "comment":
|
|
138
|
+
return runComment(ctx, cmdArgs, showHelp);
|
|
139
|
+
case "list":
|
|
140
|
+
return runList(ctx, cmdArgs, showHelp);
|
|
141
|
+
case "delete":
|
|
142
|
+
return runDelete(ctx, cmdArgs, showHelp);
|
|
143
|
+
case "doctor":
|
|
144
|
+
return runDoctor(ctx, cmdArgs, showHelp);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
default:
|
|
148
|
+
process.stderr.write(`unknown command: ${parsed.command}\n\n${ROOT_HELP}`);
|
|
149
|
+
return 2;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
errorOut(err, argv.includes("--json"));
|
|
154
|
+
if (err instanceof UsageError && !argv.includes("--json"))
|
|
155
|
+
process.stderr.write(`\n${ROOT_HELP}`);
|
|
156
|
+
return exitCode(err);
|
|
157
|
+
}
|
|
158
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { UploadsClientConfig } from "./config.js";
|
|
2
|
+
export interface PutOptions {
|
|
3
|
+
key?: string;
|
|
4
|
+
contentType?: string;
|
|
5
|
+
prefix?: string;
|
|
6
|
+
repo?: string;
|
|
7
|
+
ref?: string;
|
|
8
|
+
deriveRepoFromGit?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ListOptions {
|
|
11
|
+
prefix?: string;
|
|
12
|
+
limit?: number;
|
|
13
|
+
cursor?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface PutResult {
|
|
16
|
+
workspace: string;
|
|
17
|
+
key: string;
|
|
18
|
+
url: string;
|
|
19
|
+
size: number;
|
|
20
|
+
contentType: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ListItem {
|
|
23
|
+
key: string;
|
|
24
|
+
url: string | null;
|
|
25
|
+
size?: number;
|
|
26
|
+
uploaded?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ListResult {
|
|
29
|
+
items: ListItem[];
|
|
30
|
+
cursor: string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface HeadResult {
|
|
33
|
+
key: string;
|
|
34
|
+
url: string | null;
|
|
35
|
+
size: number;
|
|
36
|
+
contentType: string;
|
|
37
|
+
uploaded?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface DeleteResult {
|
|
40
|
+
key: string;
|
|
41
|
+
deleted: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface HealthResult {
|
|
44
|
+
ok: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare function createUploadsClient(config: UploadsClientConfig): {
|
|
47
|
+
put(body: Uint8Array, opts: PutOptions & {
|
|
48
|
+
filename: string;
|
|
49
|
+
}): Promise<PutResult>;
|
|
50
|
+
list(opts?: ListOptions): Promise<ListResult>;
|
|
51
|
+
delete(key: string): Promise<DeleteResult>;
|
|
52
|
+
head(key: string): Promise<HeadResult>;
|
|
53
|
+
health(): Promise<HealthResult>;
|
|
54
|
+
};
|
|
55
|
+
export type UploadsClient = ReturnType<typeof createUploadsClient>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { inferContentType } from "./embed.js";
|
|
2
|
+
import { UploadsError } from "./errors.js";
|
|
3
|
+
import { buildScreenshotKey } from "./keys.js";
|
|
4
|
+
function encodeKeyPath(key) {
|
|
5
|
+
return key.split("/").map(encodeURIComponent).join("/");
|
|
6
|
+
}
|
|
7
|
+
function filesBase(config) {
|
|
8
|
+
return `${config.apiUrl}/v1/${encodeURIComponent(config.workspace)}/files`;
|
|
9
|
+
}
|
|
10
|
+
function mapApiError(status, error) {
|
|
11
|
+
const normalized = error.toLowerCase();
|
|
12
|
+
if (status === 401 || normalized === "unauthorized") {
|
|
13
|
+
return new UploadsError(error, "UNAUTHORIZED", status);
|
|
14
|
+
}
|
|
15
|
+
if (status === 404 || normalized === "not found") {
|
|
16
|
+
return new UploadsError(error, "NOT_FOUND", status);
|
|
17
|
+
}
|
|
18
|
+
if (status === 400 && normalized === "invalid key") {
|
|
19
|
+
return new UploadsError(error, "INVALID_KEY", status);
|
|
20
|
+
}
|
|
21
|
+
return new UploadsError(error, "API_ERROR", status);
|
|
22
|
+
}
|
|
23
|
+
async function parseErrorResponse(res) {
|
|
24
|
+
const body = await res.json().catch(() => ({}));
|
|
25
|
+
const message = typeof body === "object" && body && "error" in body && typeof body.error === "string"
|
|
26
|
+
? body.error
|
|
27
|
+
: res.statusText || "request failed";
|
|
28
|
+
return mapApiError(res.status, message);
|
|
29
|
+
}
|
|
30
|
+
export function createUploadsClient(config) {
|
|
31
|
+
async function request(method, path, opts) {
|
|
32
|
+
const headers = { ...opts?.headers };
|
|
33
|
+
if (opts?.auth !== false) {
|
|
34
|
+
headers.Authorization = `Bearer ${config.token}`;
|
|
35
|
+
}
|
|
36
|
+
let res;
|
|
37
|
+
try {
|
|
38
|
+
res = await fetch(path, {
|
|
39
|
+
method,
|
|
40
|
+
headers,
|
|
41
|
+
body: opts?.body,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const message = err instanceof Error ? err.message : "network request failed";
|
|
46
|
+
throw new UploadsError(message, "NETWORK");
|
|
47
|
+
}
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
throw await parseErrorResponse(res);
|
|
50
|
+
}
|
|
51
|
+
if (res.status === 204)
|
|
52
|
+
return undefined;
|
|
53
|
+
return (await res.json());
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
async put(body, opts) {
|
|
57
|
+
const key = opts.key ??
|
|
58
|
+
(await buildScreenshotKey({
|
|
59
|
+
filename: opts.filename,
|
|
60
|
+
fileBytes: body,
|
|
61
|
+
prefix: opts.prefix,
|
|
62
|
+
repo: opts.repo,
|
|
63
|
+
ref: opts.ref,
|
|
64
|
+
deriveRepoFromGit: opts.deriveRepoFromGit,
|
|
65
|
+
}));
|
|
66
|
+
const contentType = opts.contentType ?? inferContentType(opts.filename);
|
|
67
|
+
const result = await request("PUT", `${filesBase(config)}/${encodeKeyPath(key)}`, {
|
|
68
|
+
body,
|
|
69
|
+
headers: { "Content-Type": contentType },
|
|
70
|
+
});
|
|
71
|
+
if (result.url == null) {
|
|
72
|
+
throw new UploadsError("upload succeeded but workspace has no publicBaseUrl", "NO_PUBLIC_URL", 201);
|
|
73
|
+
}
|
|
74
|
+
return { ...result, url: result.url };
|
|
75
|
+
},
|
|
76
|
+
async list(opts = {}) {
|
|
77
|
+
const params = new URLSearchParams();
|
|
78
|
+
if (opts.prefix)
|
|
79
|
+
params.set("prefix", opts.prefix);
|
|
80
|
+
if (opts.limit != null)
|
|
81
|
+
params.set("limit", String(opts.limit));
|
|
82
|
+
if (opts.cursor)
|
|
83
|
+
params.set("cursor", opts.cursor);
|
|
84
|
+
const qs = params.toString();
|
|
85
|
+
return request("GET", `${filesBase(config)}${qs ? `?${qs}` : ""}`);
|
|
86
|
+
},
|
|
87
|
+
async delete(key) {
|
|
88
|
+
return request("DELETE", `${filesBase(config)}/${encodeKeyPath(key)}`);
|
|
89
|
+
},
|
|
90
|
+
async head(key) {
|
|
91
|
+
return request("GET", `${filesBase(config)}/${encodeKeyPath(key)}`);
|
|
92
|
+
},
|
|
93
|
+
async health() {
|
|
94
|
+
return request("GET", `${config.apiUrl}/health`, { auth: false });
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|