@buildinternet/uploads 0.1.1 → 0.2.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/LICENSE +21 -0
- package/README.md +17 -2
- package/dist/cli.js +27 -2
- package/dist/client.d.ts +48 -1
- package/dist/client.js +49 -12
- package/dist/commands/install.d.ts +8 -0
- package/dist/commands/install.js +133 -0
- package/dist/commands/mcp.d.ts +4 -0
- package/dist/commands/mcp.js +39 -0
- package/dist/commands.d.ts +47 -0
- package/dist/commands.js +150 -57
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/io.d.ts +3 -0
- package/dist/io.js +9 -0
- package/dist/mcp/args.d.ts +4 -0
- package/dist/mcp/args.js +26 -0
- package/dist/mcp/server.d.ts +19 -0
- package/dist/mcp/server.js +109 -0
- package/dist/mcp/stdio.d.ts +3 -0
- package/dist/mcp/stdio.js +14 -0
- package/dist/mcp/tools.d.ts +10 -0
- package/dist/mcp/tools.js +386 -0
- package/package.json +12 -9
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool set mirroring the CLI commands (put, attach, list, delete,
|
|
3
|
+
* usage, reconcile, purge_expired, comment, health, doctor). Config is
|
|
4
|
+
* resolved fresh per tool call so a
|
|
5
|
+
* per-call `workspace` argument behaves like the CLI's --workspace flag, and
|
|
6
|
+
* a missing token surfaces as a tool error rather than a startup failure.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { basename } from "node:path";
|
|
10
|
+
import { createUploadsClient } from "../client.js";
|
|
11
|
+
import { buildDoctorReport, makeGhTarget, syncAttachmentsComment } from "../commands.js";
|
|
12
|
+
import { resolveConfig, resolvePutDefaults, } from "../config.js";
|
|
13
|
+
import { buildMarkdown } from "../embed.js";
|
|
14
|
+
import { ghAttachmentKey, ghKeyPrefix } from "../github.js";
|
|
15
|
+
import { execRunner, resolveCurrentPullRequest, resolveRepo, } from "../github-gh.js";
|
|
16
|
+
import { optPosInt, optString, usage } from "./args.js";
|
|
17
|
+
function optBool(args, name) {
|
|
18
|
+
const v = args[name];
|
|
19
|
+
if (v === undefined || v === null)
|
|
20
|
+
return false;
|
|
21
|
+
if (typeof v !== "boolean")
|
|
22
|
+
usage(`${name} must be a boolean`);
|
|
23
|
+
return v;
|
|
24
|
+
}
|
|
25
|
+
function optStringArray(args, name) {
|
|
26
|
+
const v = args[name];
|
|
27
|
+
if (v === undefined || v === null)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (!Array.isArray(v) || v.some((item) => typeof item !== "string")) {
|
|
30
|
+
usage(`${name} must be an array of strings`);
|
|
31
|
+
}
|
|
32
|
+
return v;
|
|
33
|
+
}
|
|
34
|
+
/** Reads pr/issue (+ repo) into a GhTarget; undefined when neither is present. */
|
|
35
|
+
function ghTargetFromArgs(args, run) {
|
|
36
|
+
return makeGhTarget(optPosInt(args, "pr"), optPosInt(args, "issue"), optString(args, "repo"), run);
|
|
37
|
+
}
|
|
38
|
+
const workspaceProp = {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Override the workspace for this call (like the CLI's --workspace flag).",
|
|
41
|
+
};
|
|
42
|
+
/** pr/issue/repo schema properties shared by the tools that resolve a GhTarget. */
|
|
43
|
+
function ghTargetProps(action) {
|
|
44
|
+
return {
|
|
45
|
+
pr: {
|
|
46
|
+
type: "number",
|
|
47
|
+
description: `${action} this pull request. Mutually exclusive with issue.`,
|
|
48
|
+
},
|
|
49
|
+
issue: {
|
|
50
|
+
type: "number",
|
|
51
|
+
description: `${action} this issue. Mutually exclusive with pr.`,
|
|
52
|
+
},
|
|
53
|
+
repo: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "owner/name repository (default: gh/git inference).",
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function createUploadsMcpTools(opts) {
|
|
60
|
+
const { globals } = opts;
|
|
61
|
+
const run = opts.runner ?? execRunner;
|
|
62
|
+
const clientFactory = opts.clientFactory ?? createUploadsClient;
|
|
63
|
+
function clientFor(args, requireToken = true) {
|
|
64
|
+
const config = resolveConfig({
|
|
65
|
+
apiUrl: globals.apiUrl,
|
|
66
|
+
token: globals.token,
|
|
67
|
+
envFile: globals.envFile,
|
|
68
|
+
workspace: optString(args, "workspace") ?? globals.workspace,
|
|
69
|
+
requireToken,
|
|
70
|
+
});
|
|
71
|
+
return { config, client: clientFactory(config) };
|
|
72
|
+
}
|
|
73
|
+
const syncComment = async (client, target) => {
|
|
74
|
+
let comment;
|
|
75
|
+
let commentError;
|
|
76
|
+
try {
|
|
77
|
+
comment = await syncAttachmentsComment(client, target, run);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
// Uploads already succeeded; the comment is best-effort by design.
|
|
81
|
+
commentError = err instanceof Error ? err.message : String(err);
|
|
82
|
+
}
|
|
83
|
+
return { comment, commentError };
|
|
84
|
+
};
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
name: "put",
|
|
88
|
+
description: "Upload a file to uploads.sh and get a public URL plus GitHub-ready embed markdown (the returned `markdown` is ready to paste into a PR or issue). Pass `file` (a local path) or `contentBase64` + `filename` for in-memory content; with `pr`/`issue` the key is stable (same filename → same URL) and `comment` syncs the managed attachments comment.",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
file: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Path of the file to upload. Exactly one of file or contentBase64 is required.",
|
|
95
|
+
},
|
|
96
|
+
contentBase64: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Base64-encoded file content for in-memory uploads; requires filename.",
|
|
99
|
+
},
|
|
100
|
+
filename: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "Filename for contentBase64 content (drives the key and content type).",
|
|
103
|
+
},
|
|
104
|
+
key: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Explicit object key (default: <prefix>/<repo>/<ref>/<name>-<hash>.<ext>). Cannot be combined with pr/issue.",
|
|
107
|
+
},
|
|
108
|
+
prefix: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "Key prefix (default: screenshots, or UPLOADS_DEFAULT_PREFIX). Cannot be combined with pr/issue.",
|
|
111
|
+
},
|
|
112
|
+
...ghTargetProps("Attach to"),
|
|
113
|
+
// put's repo doubles as the default key layout's repo segment.
|
|
114
|
+
repo: {
|
|
115
|
+
type: "string",
|
|
116
|
+
description: "owner/name repo segment (default: git remote, or UPLOADS_DEFAULT_REPO).",
|
|
117
|
+
},
|
|
118
|
+
ref: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "PR/issue/branch key segment (default: today, or UPLOADS_DEFAULT_REF). Cannot be combined with pr/issue.",
|
|
121
|
+
},
|
|
122
|
+
alt: { type: "string", description: "Alt text for the markdown (default: filename)." },
|
|
123
|
+
width: {
|
|
124
|
+
type: "number",
|
|
125
|
+
description: "Emit <img width=…> markdown instead of a plain image embed.",
|
|
126
|
+
},
|
|
127
|
+
contentType: { type: "string", description: "Override the Content-Type." },
|
|
128
|
+
noGit: { type: "boolean", description: "Don't derive the repo segment from git." },
|
|
129
|
+
comment: {
|
|
130
|
+
type: "boolean",
|
|
131
|
+
description: "With pr/issue: create or update the managed attachments comment via local gh auth (best-effort).",
|
|
132
|
+
},
|
|
133
|
+
workspace: workspaceProp,
|
|
134
|
+
},
|
|
135
|
+
additionalProperties: false,
|
|
136
|
+
},
|
|
137
|
+
async handler(args) {
|
|
138
|
+
const file = optString(args, "file");
|
|
139
|
+
const contentBase64 = optString(args, "contentBase64");
|
|
140
|
+
if ((file === undefined) === (contentBase64 === undefined)) {
|
|
141
|
+
usage("exactly one of file or contentBase64 is required");
|
|
142
|
+
}
|
|
143
|
+
const filenameArg = optString(args, "filename");
|
|
144
|
+
if (contentBase64 !== undefined && !filenameArg) {
|
|
145
|
+
usage("filename is required with contentBase64");
|
|
146
|
+
}
|
|
147
|
+
const target = ghTargetFromArgs(args, run);
|
|
148
|
+
const wantComment = optBool(args, "comment");
|
|
149
|
+
const key = optString(args, "key");
|
|
150
|
+
const prefixArg = optString(args, "prefix");
|
|
151
|
+
const refArg = optString(args, "ref");
|
|
152
|
+
if (wantComment && !target)
|
|
153
|
+
usage("comment requires pr or issue");
|
|
154
|
+
if (target) {
|
|
155
|
+
if (key)
|
|
156
|
+
usage("key cannot be combined with pr/issue");
|
|
157
|
+
if (refArg)
|
|
158
|
+
usage("ref cannot be combined with pr/issue");
|
|
159
|
+
if (prefixArg)
|
|
160
|
+
usage("prefix cannot be combined with pr/issue");
|
|
161
|
+
}
|
|
162
|
+
const { client } = clientFor(args);
|
|
163
|
+
const bytes = file !== undefined
|
|
164
|
+
? new Uint8Array(readFileSync(file))
|
|
165
|
+
: new Uint8Array(Buffer.from(contentBase64, "base64"));
|
|
166
|
+
const filename = file !== undefined ? (filenameArg ?? basename(file)) : filenameArg;
|
|
167
|
+
const defaults = resolvePutDefaults({ envFile: globals.envFile });
|
|
168
|
+
const noGit = optBool(args, "noGit") || defaults.noGit === true;
|
|
169
|
+
const result = await client.put(bytes, {
|
|
170
|
+
filename,
|
|
171
|
+
key: target ? ghAttachmentKey(target, filename) : key,
|
|
172
|
+
prefix: prefixArg ?? defaults.prefix,
|
|
173
|
+
repo: optString(args, "repo") ?? defaults.repo,
|
|
174
|
+
ref: refArg ?? defaults.ref,
|
|
175
|
+
contentType: optString(args, "contentType"),
|
|
176
|
+
deriveRepoFromGit: !noGit,
|
|
177
|
+
});
|
|
178
|
+
const markdown = buildMarkdown(result.url, {
|
|
179
|
+
alt: optString(args, "alt") ?? filename,
|
|
180
|
+
width: optPosInt(args, "width") ?? defaults.width,
|
|
181
|
+
});
|
|
182
|
+
if (wantComment && target) {
|
|
183
|
+
const { comment, commentError } = await syncComment(client, target);
|
|
184
|
+
return { ...result, markdown, comment, commentError };
|
|
185
|
+
}
|
|
186
|
+
return { ...result, markdown };
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "attach",
|
|
191
|
+
description: "Upload one or more files as stable PR/issue attachments and maintain a single managed GitHub comment listing them (each upload's `markdown` is ready to paste into GitHub). With no pr/issue, targets the pull request for the current branch.",
|
|
192
|
+
inputSchema: {
|
|
193
|
+
type: "object",
|
|
194
|
+
properties: {
|
|
195
|
+
files: {
|
|
196
|
+
type: "array",
|
|
197
|
+
items: { type: "string" },
|
|
198
|
+
description: "Paths of the files to upload (at least one).",
|
|
199
|
+
},
|
|
200
|
+
...ghTargetProps("Attach to"),
|
|
201
|
+
noComment: {
|
|
202
|
+
type: "boolean",
|
|
203
|
+
description: "Upload only; don't create/update the managed comment.",
|
|
204
|
+
},
|
|
205
|
+
contentType: {
|
|
206
|
+
type: "string",
|
|
207
|
+
description: "Override the Content-Type (applied to every file).",
|
|
208
|
+
},
|
|
209
|
+
workspace: workspaceProp,
|
|
210
|
+
},
|
|
211
|
+
required: ["files"],
|
|
212
|
+
additionalProperties: false,
|
|
213
|
+
},
|
|
214
|
+
async handler(args) {
|
|
215
|
+
const files = optStringArray(args, "files");
|
|
216
|
+
if (!files || files.length === 0)
|
|
217
|
+
usage("files must be a non-empty array of paths");
|
|
218
|
+
const explicitTarget = ghTargetFromArgs(args, run);
|
|
219
|
+
const target = explicitTarget ??
|
|
220
|
+
resolveCurrentPullRequest(resolveRepo(optString(args, "repo"), run), run);
|
|
221
|
+
const { client } = clientFor(args);
|
|
222
|
+
const contentType = optString(args, "contentType");
|
|
223
|
+
const uploads = [];
|
|
224
|
+
for (const file of files) {
|
|
225
|
+
const filename = basename(file);
|
|
226
|
+
const result = await client.put(new Uint8Array(readFileSync(file)), {
|
|
227
|
+
filename,
|
|
228
|
+
key: ghAttachmentKey(target, filename),
|
|
229
|
+
contentType,
|
|
230
|
+
});
|
|
231
|
+
uploads.push({ ...result, markdown: buildMarkdown(result.url, { alt: filename }) });
|
|
232
|
+
}
|
|
233
|
+
if (optBool(args, "noComment"))
|
|
234
|
+
return { target, uploads };
|
|
235
|
+
const { comment, commentError } = await syncComment(client, target);
|
|
236
|
+
return { target, uploads, comment, commentError };
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "list",
|
|
241
|
+
description: "List uploaded objects in the workspace, filtered by key prefix or by a PR/issue's attachments. Paginate with cursor, or set all to fetch every page.",
|
|
242
|
+
inputSchema: {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: {
|
|
245
|
+
prefix: {
|
|
246
|
+
type: "string",
|
|
247
|
+
description: "Key prefix filter (default: UPLOADS_DEFAULT_PREFIX + '/'). Cannot be combined with pr/issue.",
|
|
248
|
+
},
|
|
249
|
+
...ghTargetProps("List attachments for"),
|
|
250
|
+
limit: { type: "number", description: "Page size." },
|
|
251
|
+
cursor: { type: "string", description: "Pagination cursor from a previous call." },
|
|
252
|
+
all: { type: "boolean", description: "Follow cursors and return every page." },
|
|
253
|
+
workspace: workspaceProp,
|
|
254
|
+
},
|
|
255
|
+
additionalProperties: false,
|
|
256
|
+
},
|
|
257
|
+
async handler(args) {
|
|
258
|
+
const defaults = resolvePutDefaults({ envFile: globals.envFile });
|
|
259
|
+
const prefixArg = optString(args, "prefix");
|
|
260
|
+
let prefix = prefixArg ?? (defaults.prefix ? `${defaults.prefix}/` : undefined);
|
|
261
|
+
const target = ghTargetFromArgs(args, run);
|
|
262
|
+
if (target) {
|
|
263
|
+
if (prefixArg)
|
|
264
|
+
usage("prefix cannot be combined with pr/issue");
|
|
265
|
+
prefix = ghKeyPrefix(target);
|
|
266
|
+
}
|
|
267
|
+
const limit = optPosInt(args, "limit");
|
|
268
|
+
const cursor = optString(args, "cursor");
|
|
269
|
+
const { client } = clientFor(args);
|
|
270
|
+
if (optBool(args, "all")) {
|
|
271
|
+
const items = await client.listAll({ prefix, limit, cursor });
|
|
272
|
+
return { items, cursor: null };
|
|
273
|
+
}
|
|
274
|
+
return client.list({ prefix, limit, cursor });
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "delete",
|
|
279
|
+
description: "Delete an uploaded object by key. Set dryRun to preview without deleting.",
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: "object",
|
|
282
|
+
properties: {
|
|
283
|
+
key: { type: "string", description: "Object key to delete." },
|
|
284
|
+
dryRun: {
|
|
285
|
+
type: "boolean",
|
|
286
|
+
description: "Report what would be deleted without deleting.",
|
|
287
|
+
},
|
|
288
|
+
workspace: workspaceProp,
|
|
289
|
+
},
|
|
290
|
+
required: ["key"],
|
|
291
|
+
additionalProperties: false,
|
|
292
|
+
},
|
|
293
|
+
async handler(args) {
|
|
294
|
+
const key = optString(args, "key");
|
|
295
|
+
if (!key)
|
|
296
|
+
usage("key is required");
|
|
297
|
+
if (optBool(args, "dryRun"))
|
|
298
|
+
return { key, deleted: false, dryRun: true };
|
|
299
|
+
const { client } = clientFor(args);
|
|
300
|
+
return client.delete(key);
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "usage",
|
|
305
|
+
description: "Workspace storage and monthly upload counters (and remaining headroom when budgets are configured). Same as `uploads usage`.",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: { workspace: workspaceProp },
|
|
309
|
+
additionalProperties: false,
|
|
310
|
+
},
|
|
311
|
+
async handler(args) {
|
|
312
|
+
const { client } = clientFor(args);
|
|
313
|
+
return client.usage();
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "reconcile",
|
|
318
|
+
description: "Rebuild usage ledger bytes/objects from storage (source of truth). Preserves the monthly upload counter. Requires files:write. Same as `uploads reconcile`.",
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: { workspace: workspaceProp },
|
|
322
|
+
additionalProperties: false,
|
|
323
|
+
},
|
|
324
|
+
async handler(args) {
|
|
325
|
+
const { client } = clientFor(args);
|
|
326
|
+
return client.reconcile();
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "purge_expired",
|
|
331
|
+
description: "Delete objects older than the workspace retentionDays setting, then reconcile. Skips if retention is unset. Requires files:delete. Same as `uploads purge-expired`.",
|
|
332
|
+
inputSchema: {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: { workspace: workspaceProp },
|
|
335
|
+
additionalProperties: false,
|
|
336
|
+
},
|
|
337
|
+
async handler(args) {
|
|
338
|
+
const { client } = clientFor(args);
|
|
339
|
+
return client.purgeExpired();
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "comment",
|
|
344
|
+
description: "Create or update the managed attachments comment on a GitHub PR or issue, listing everything uploaded for it. Uses local gh auth; edits its own prior comment in place and never touches other comments.",
|
|
345
|
+
inputSchema: {
|
|
346
|
+
type: "object",
|
|
347
|
+
properties: {
|
|
348
|
+
...ghTargetProps("Comment on"),
|
|
349
|
+
workspace: workspaceProp,
|
|
350
|
+
},
|
|
351
|
+
additionalProperties: false,
|
|
352
|
+
},
|
|
353
|
+
async handler(args) {
|
|
354
|
+
const target = ghTargetFromArgs(args, run);
|
|
355
|
+
if (!target)
|
|
356
|
+
usage("comment requires pr or issue");
|
|
357
|
+
const { client } = clientFor(args);
|
|
358
|
+
const result = await syncAttachmentsComment(client, target, run);
|
|
359
|
+
return { ...target, ...result };
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: "health",
|
|
364
|
+
description: "Check uploads.sh API liveness. No auth or arguments required.",
|
|
365
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
366
|
+
async handler(args) {
|
|
367
|
+
const { config, client } = clientFor(args, false);
|
|
368
|
+
const result = await client.health();
|
|
369
|
+
return { ...result, apiUrl: config.apiUrl };
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "doctor",
|
|
374
|
+
description: "Diagnose the configuration: API health, token auth, and workspace/token alignment. Returns the same report as `uploads doctor --json`, including hints.",
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: "object",
|
|
377
|
+
properties: { workspace: workspaceProp },
|
|
378
|
+
additionalProperties: false,
|
|
379
|
+
},
|
|
380
|
+
async handler(args) {
|
|
381
|
+
const { config, client } = clientFor(args);
|
|
382
|
+
return buildDoctorReport(config, client);
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
];
|
|
386
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buildinternet/uploads",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI and client for uploads.sh — workspace-scoped image hosting for GitHub embeds",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"./agent": {
|
|
19
19
|
"types": "./dist/agent.d.ts",
|
|
20
20
|
"import": "./dist/agent.js"
|
|
21
|
+
},
|
|
22
|
+
"./mcp": {
|
|
23
|
+
"types": "./dist/mcp/server.d.ts",
|
|
24
|
+
"import": "./dist/mcp/server.js"
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"bin": {
|
|
@@ -28,13 +32,6 @@
|
|
|
28
32
|
"dist",
|
|
29
33
|
"README.md"
|
|
30
34
|
],
|
|
31
|
-
"scripts": {
|
|
32
|
-
"test": "vitest run",
|
|
33
|
-
"typecheck": "tsc --noEmit",
|
|
34
|
-
"build": "tsc",
|
|
35
|
-
"pack:check": "node ./scripts/check-pack.mjs",
|
|
36
|
-
"prepublishOnly": "npm run build"
|
|
37
|
-
},
|
|
38
35
|
"engines": {
|
|
39
36
|
"node": ">=22"
|
|
40
37
|
},
|
|
@@ -56,5 +53,11 @@
|
|
|
56
53
|
"publishConfig": {
|
|
57
54
|
"access": "public",
|
|
58
55
|
"provenance": true
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p test",
|
|
60
|
+
"build": "tsc",
|
|
61
|
+
"pack:check": "node ./scripts/check-pack.mjs"
|
|
59
62
|
}
|
|
60
|
-
}
|
|
63
|
+
}
|