@koderlabs/tasks-cli 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/LICENSE +179 -0
- package/README.md +9 -0
- package/dist/index.cjs +1045 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1011 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// src/index.ts
|
|
33
|
+
var index_exports = {};
|
|
34
|
+
__export(index_exports, {
|
|
35
|
+
buildProgram: () => buildProgram,
|
|
36
|
+
main: () => main
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
var import_commander = require("commander");
|
|
40
|
+
var import_picocolors8 = __toESM(require("picocolors"), 1);
|
|
41
|
+
|
|
42
|
+
// src/api.ts
|
|
43
|
+
var ApiError = class extends Error {
|
|
44
|
+
static {
|
|
45
|
+
__name(this, "ApiError");
|
|
46
|
+
}
|
|
47
|
+
status;
|
|
48
|
+
body;
|
|
49
|
+
constructor(status, message, body) {
|
|
50
|
+
super(message), this.status = status, this.body = body;
|
|
51
|
+
this.name = "ApiError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var ApiClient = class {
|
|
55
|
+
static {
|
|
56
|
+
__name(this, "ApiClient");
|
|
57
|
+
}
|
|
58
|
+
apiUrl;
|
|
59
|
+
key;
|
|
60
|
+
fetchImpl;
|
|
61
|
+
maxRetries;
|
|
62
|
+
retryBaseMs;
|
|
63
|
+
constructor(opts) {
|
|
64
|
+
this.apiUrl = opts.apiUrl.replace(/\/+$/, "");
|
|
65
|
+
this.key = opts.key;
|
|
66
|
+
this.fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
67
|
+
this.maxRetries = opts.maxRetries ?? 3;
|
|
68
|
+
this.retryBaseMs = opts.retryBaseMs ?? 500;
|
|
69
|
+
if (!this.fetchImpl) {
|
|
70
|
+
throw new Error("No fetch implementation available. Use Node >= 18.");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Upload one or more source-map artifacts to a release. */
|
|
74
|
+
async uploadSourceMaps(version, environment, files) {
|
|
75
|
+
const url = `${this.apiUrl}/sdk/source-maps?version=${encodeURIComponent(version)}&environment=${encodeURIComponent(environment)}`;
|
|
76
|
+
const form = new FormData();
|
|
77
|
+
for (const f of files) {
|
|
78
|
+
const bytes = toUint8(f.content);
|
|
79
|
+
const blob = new Blob([
|
|
80
|
+
bytes
|
|
81
|
+
], {
|
|
82
|
+
type: "application/json"
|
|
83
|
+
});
|
|
84
|
+
form.append("files", blob, f.filename);
|
|
85
|
+
}
|
|
86
|
+
return this.request("POST", url, form);
|
|
87
|
+
}
|
|
88
|
+
/** Create (or upsert) a release without uploading any artifacts. */
|
|
89
|
+
async createRelease(version, environment) {
|
|
90
|
+
return this.uploadSourceMaps(version, environment, []);
|
|
91
|
+
}
|
|
92
|
+
async request(method, url, body) {
|
|
93
|
+
let attempt = 0;
|
|
94
|
+
let lastErr;
|
|
95
|
+
while (attempt <= this.maxRetries) {
|
|
96
|
+
try {
|
|
97
|
+
const res = await this.fetchImpl(url, {
|
|
98
|
+
method,
|
|
99
|
+
headers: {
|
|
100
|
+
authorization: `Bearer ${this.key}`
|
|
101
|
+
},
|
|
102
|
+
body
|
|
103
|
+
});
|
|
104
|
+
if (res.status === 401 || res.status === 403) {
|
|
105
|
+
const txt = await safeText(res);
|
|
106
|
+
throw new ApiError(res.status, `Authentication failed (HTTP ${res.status}). Check your management key. ${txt}`.trim(), txt);
|
|
107
|
+
}
|
|
108
|
+
if (res.status === 429 || res.status >= 500) {
|
|
109
|
+
const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
|
|
110
|
+
if (attempt < this.maxRetries) {
|
|
111
|
+
const wait2 = retryAfter ?? this.retryBaseMs * Math.pow(2, attempt);
|
|
112
|
+
await sleep(wait2);
|
|
113
|
+
attempt++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const txt = await safeText(res);
|
|
117
|
+
throw new ApiError(res.status, `HTTP ${res.status} after ${attempt + 1} attempts. ${txt}`.trim(), txt);
|
|
118
|
+
}
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
const txt = await safeText(res);
|
|
121
|
+
throw new ApiError(res.status, `HTTP ${res.status}: ${txt}`.trim(), txt);
|
|
122
|
+
}
|
|
123
|
+
const json = await res.json();
|
|
124
|
+
return json && typeof json === "object" && "data" in json ? json.data : json;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (err instanceof ApiError && err.status !== 429 && err.status < 500) throw err;
|
|
127
|
+
lastErr = err;
|
|
128
|
+
if (attempt >= this.maxRetries) throw err;
|
|
129
|
+
await sleep(this.retryBaseMs * Math.pow(2, attempt));
|
|
130
|
+
attempt++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
throw lastErr ?? new Error("Unreachable");
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
function toUint8(content) {
|
|
137
|
+
if (content instanceof Uint8Array) return content;
|
|
138
|
+
return new Uint8Array(content);
|
|
139
|
+
}
|
|
140
|
+
__name(toUint8, "toUint8");
|
|
141
|
+
async function safeText(res) {
|
|
142
|
+
try {
|
|
143
|
+
return await res.text();
|
|
144
|
+
} catch {
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
__name(safeText, "safeText");
|
|
149
|
+
function parseRetryAfter(header) {
|
|
150
|
+
if (!header) return null;
|
|
151
|
+
const n = Number(header);
|
|
152
|
+
if (Number.isFinite(n) && n >= 0) return n * 1e3;
|
|
153
|
+
const date = Date.parse(header);
|
|
154
|
+
if (Number.isFinite(date)) return Math.max(0, date - Date.now());
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
__name(parseRetryAfter, "parseRetryAfter");
|
|
158
|
+
function sleep(ms) {
|
|
159
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
160
|
+
}
|
|
161
|
+
__name(sleep, "sleep");
|
|
162
|
+
|
|
163
|
+
// src/config.ts
|
|
164
|
+
var import_node_fs = require("fs");
|
|
165
|
+
var path = __toESM(require("path"), 1);
|
|
166
|
+
var DEFAULT_API_URL = "http://localhost:11002/api/v1";
|
|
167
|
+
var RC_FILENAME = ".tasksrc.json";
|
|
168
|
+
function findRcFile(start) {
|
|
169
|
+
let dir = path.resolve(start);
|
|
170
|
+
while (true) {
|
|
171
|
+
const candidate = path.join(dir, RC_FILENAME);
|
|
172
|
+
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
173
|
+
const parent = path.dirname(dir);
|
|
174
|
+
if (parent === dir) return void 0;
|
|
175
|
+
dir = parent;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
__name(findRcFile, "findRcFile");
|
|
179
|
+
function loadRc(start) {
|
|
180
|
+
const file = findRcFile(start);
|
|
181
|
+
if (!file) return {
|
|
182
|
+
data: {}
|
|
183
|
+
};
|
|
184
|
+
try {
|
|
185
|
+
const data = JSON.parse((0, import_node_fs.readFileSync)(file, "utf-8"));
|
|
186
|
+
return {
|
|
187
|
+
file,
|
|
188
|
+
data
|
|
189
|
+
};
|
|
190
|
+
} catch {
|
|
191
|
+
return {
|
|
192
|
+
file,
|
|
193
|
+
data: {}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
__name(loadRc, "loadRc");
|
|
198
|
+
function resolveConfig(input = {}, env = process.env) {
|
|
199
|
+
const { file: rcFile, data: rc } = loadRc(input.cwd ?? process.cwd());
|
|
200
|
+
const apiUrl = (input.apiUrl ?? env.INSTANTTASKS_ENDPOINT ?? env.INSTANTTASKS_API_URL ?? rc.endpoint ?? rc.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
201
|
+
const key = input.key ?? env.INSTANTTASKS_INGEST_KEY ?? env.INSTANTTASKS_MANAGEMENT_KEY ?? rc.ingestKey ?? rc.key ?? "";
|
|
202
|
+
const projectId = input.projectId ?? env.INSTANTTASKS_PROJECT_ID ?? rc.projectId;
|
|
203
|
+
if (!key && !input.optionalKey) {
|
|
204
|
+
throw new Error("Missing key. Pass --key sk_live_... / pk_live_..., set INSTANTTASKS_INGEST_KEY, or add it to .tasksrc.json.");
|
|
205
|
+
}
|
|
206
|
+
if (key && !/^(sk|pk)_/.test(key)) {
|
|
207
|
+
throw new Error(`Invalid key format: expected sk_live_... / pk_live_..., got "${key.slice(0, 6)}\u2026".`);
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
apiUrl,
|
|
211
|
+
key,
|
|
212
|
+
projectId,
|
|
213
|
+
rcFile
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
__name(resolveConfig, "resolveConfig");
|
|
217
|
+
|
|
218
|
+
// src/commands/upload-sourcemaps.ts
|
|
219
|
+
var import_node_fs2 = require("fs");
|
|
220
|
+
var path2 = __toESM(require("path"), 1);
|
|
221
|
+
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
222
|
+
async function uploadSourcemaps(opts) {
|
|
223
|
+
const log = opts.log ?? ((s) => console.log(s));
|
|
224
|
+
const env = opts.environment ?? "production";
|
|
225
|
+
const root = path2.resolve(opts.dir);
|
|
226
|
+
const maps = await walkForMaps(root);
|
|
227
|
+
if (maps.length === 0) {
|
|
228
|
+
log(import_picocolors.default.yellow(`No .js.map files found under ${root}`));
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
log(import_picocolors.default.cyan(`Uploading ${maps.length} source map(s) to release ${opts.release} (${env})\u2026`));
|
|
232
|
+
const files = [];
|
|
233
|
+
for (const absPath of maps) {
|
|
234
|
+
const rel = path2.relative(root, absPath).split(path2.sep).join("/");
|
|
235
|
+
const url = opts.urlPrefix ? joinUrl(opts.urlPrefix, rel.replace(/\.map$/, "")) : void 0;
|
|
236
|
+
const content = await import_node_fs2.promises.readFile(absPath);
|
|
237
|
+
files.push({
|
|
238
|
+
filename: path2.basename(absPath),
|
|
239
|
+
url,
|
|
240
|
+
content
|
|
241
|
+
});
|
|
242
|
+
log(` ${import_picocolors.default.dim("\u2192")} ${rel}${url ? import_picocolors.default.dim(` (${url})`) : ""}`);
|
|
243
|
+
}
|
|
244
|
+
const result = await opts.apiClient.uploadSourceMaps(opts.release, env, files);
|
|
245
|
+
log(import_picocolors.default.green(`Done. Release ${result.release.version} now has ${result.uploaded.length} artifact(s).`));
|
|
246
|
+
return result.uploaded.length;
|
|
247
|
+
}
|
|
248
|
+
__name(uploadSourcemaps, "uploadSourcemaps");
|
|
249
|
+
async function walkForMaps(dir) {
|
|
250
|
+
const out = [];
|
|
251
|
+
let entries;
|
|
252
|
+
try {
|
|
253
|
+
entries = await import_node_fs2.promises.readdir(dir, {
|
|
254
|
+
withFileTypes: true
|
|
255
|
+
});
|
|
256
|
+
} catch (err) {
|
|
257
|
+
throw new Error(`Cannot read directory ${dir}: ${err.message}`);
|
|
258
|
+
}
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
const full = path2.join(dir, entry.name);
|
|
261
|
+
if (entry.isDirectory()) {
|
|
262
|
+
const nested = await walkForMaps(full);
|
|
263
|
+
out.push(...nested);
|
|
264
|
+
} else if (entry.isFile() && entry.name.endsWith(".js.map")) {
|
|
265
|
+
out.push(full);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return out.sort();
|
|
269
|
+
}
|
|
270
|
+
__name(walkForMaps, "walkForMaps");
|
|
271
|
+
function joinUrl(prefix, rel) {
|
|
272
|
+
const left = prefix.replace(/\/+$/, "");
|
|
273
|
+
const right = rel.replace(/^\/+/, "");
|
|
274
|
+
return `${left}/${right}`;
|
|
275
|
+
}
|
|
276
|
+
__name(joinUrl, "joinUrl");
|
|
277
|
+
|
|
278
|
+
// src/commands/releases.ts
|
|
279
|
+
var import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
280
|
+
async function createRelease(opts) {
|
|
281
|
+
const log = opts.log ?? ((s) => console.log(s));
|
|
282
|
+
const env = opts.environment ?? "production";
|
|
283
|
+
log(import_picocolors2.default.cyan(`Creating release ${opts.name} (${env})\u2026`));
|
|
284
|
+
const result = await opts.apiClient.createRelease(opts.name, env);
|
|
285
|
+
log(import_picocolors2.default.green(`Release ${result.release.version} ready (id=${result.release.id}).`));
|
|
286
|
+
return result.release.id;
|
|
287
|
+
}
|
|
288
|
+
__name(createRelease, "createRelease");
|
|
289
|
+
|
|
290
|
+
// src/commands/doctor.ts
|
|
291
|
+
var import_picocolors3 = __toESM(require("picocolors"), 1);
|
|
292
|
+
async function doctor(opts = {}) {
|
|
293
|
+
const log = opts.log ?? ((s) => console.log(s));
|
|
294
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
295
|
+
const checks = [];
|
|
296
|
+
let cfg;
|
|
297
|
+
try {
|
|
298
|
+
cfg = resolveConfig({
|
|
299
|
+
apiUrl: opts.apiUrl,
|
|
300
|
+
key: opts.key,
|
|
301
|
+
projectId: opts.projectId
|
|
302
|
+
});
|
|
303
|
+
checks.push({
|
|
304
|
+
name: "config",
|
|
305
|
+
ok: true,
|
|
306
|
+
detail: `endpoint=${cfg.apiUrl}${cfg.rcFile ? ` (rc=${cfg.rcFile})` : ""}`
|
|
307
|
+
});
|
|
308
|
+
} catch (err) {
|
|
309
|
+
checks.push({
|
|
310
|
+
name: "config",
|
|
311
|
+
ok: false,
|
|
312
|
+
detail: err.message
|
|
313
|
+
});
|
|
314
|
+
return finish(checks, log);
|
|
315
|
+
}
|
|
316
|
+
if (!cfg.projectId) {
|
|
317
|
+
checks.push({
|
|
318
|
+
name: "projectId",
|
|
319
|
+
ok: false,
|
|
320
|
+
detail: "INSTANTTASKS_PROJECT_ID not set (some commands require it)."
|
|
321
|
+
});
|
|
322
|
+
} else {
|
|
323
|
+
checks.push({
|
|
324
|
+
name: "projectId",
|
|
325
|
+
ok: true,
|
|
326
|
+
detail: cfg.projectId
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
const configUrl = `${cfg.apiUrl}/sdk/v1/config`;
|
|
330
|
+
try {
|
|
331
|
+
const res = await fetchImpl(configUrl, {
|
|
332
|
+
method: "GET",
|
|
333
|
+
headers: {
|
|
334
|
+
authorization: `Bearer ${cfg.key}`
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
if (res.ok) {
|
|
338
|
+
checks.push({
|
|
339
|
+
name: "endpoint",
|
|
340
|
+
ok: true,
|
|
341
|
+
detail: `${configUrl} \u2192 ${res.status}`
|
|
342
|
+
});
|
|
343
|
+
checks.push({
|
|
344
|
+
name: "ingestKey",
|
|
345
|
+
ok: true,
|
|
346
|
+
detail: "authenticated"
|
|
347
|
+
});
|
|
348
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
349
|
+
checks.push({
|
|
350
|
+
name: "endpoint",
|
|
351
|
+
ok: true,
|
|
352
|
+
detail: `${configUrl} reachable (${res.status})`
|
|
353
|
+
});
|
|
354
|
+
checks.push({
|
|
355
|
+
name: "ingestKey",
|
|
356
|
+
ok: false,
|
|
357
|
+
detail: `key rejected (HTTP ${res.status}) \u2014 check INSTANTTASKS_INGEST_KEY`
|
|
358
|
+
});
|
|
359
|
+
} else {
|
|
360
|
+
checks.push({
|
|
361
|
+
name: "endpoint",
|
|
362
|
+
ok: false,
|
|
363
|
+
detail: `${configUrl} \u2192 HTTP ${res.status}`
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
} catch (err) {
|
|
367
|
+
checks.push({
|
|
368
|
+
name: "endpoint",
|
|
369
|
+
ok: false,
|
|
370
|
+
detail: `cannot reach ${configUrl}: ${err.message}`
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
if (cfg.key.startsWith("pk_")) {
|
|
374
|
+
checks.push({
|
|
375
|
+
name: "sourcemapPermission",
|
|
376
|
+
ok: false,
|
|
377
|
+
detail: "pk_ keys cannot upload source maps \u2014 use sk_live_... for CI uploads"
|
|
378
|
+
});
|
|
379
|
+
} else if (cfg.key.startsWith("sk_")) {
|
|
380
|
+
checks.push({
|
|
381
|
+
name: "sourcemapPermission",
|
|
382
|
+
ok: true,
|
|
383
|
+
detail: "sk_ key has upload role"
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return finish(checks, log);
|
|
387
|
+
}
|
|
388
|
+
__name(doctor, "doctor");
|
|
389
|
+
function finish(checks, log) {
|
|
390
|
+
for (const c of checks) {
|
|
391
|
+
const mark = c.ok ? import_picocolors3.default.green("\u2713") : import_picocolors3.default.red("\u2717");
|
|
392
|
+
log(` ${mark} ${import_picocolors3.default.bold(c.name.padEnd(20))} ${c.ok ? import_picocolors3.default.dim(c.detail) : import_picocolors3.default.yellow(c.detail)}`);
|
|
393
|
+
}
|
|
394
|
+
const ok = checks.every((c) => c.ok);
|
|
395
|
+
log("");
|
|
396
|
+
log(ok ? import_picocolors3.default.green("All checks passed.") : import_picocolors3.default.red("One or more checks failed."));
|
|
397
|
+
return {
|
|
398
|
+
ok,
|
|
399
|
+
checks
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
__name(finish, "finish");
|
|
403
|
+
|
|
404
|
+
// src/commands/sourcemap-upload.ts
|
|
405
|
+
var import_node_fs3 = require("fs");
|
|
406
|
+
var path3 = __toESM(require("path"), 1);
|
|
407
|
+
var import_picocolors4 = __toESM(require("picocolors"), 1);
|
|
408
|
+
var DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
|
|
409
|
+
var MAX_RETRIES = 3;
|
|
410
|
+
var RETRY_BASE_MS = 500;
|
|
411
|
+
async function sourcemapUpload(opts) {
|
|
412
|
+
const log = opts.log ?? ((s) => console.log(s));
|
|
413
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
414
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
415
|
+
const strict = opts.strict !== false;
|
|
416
|
+
const root = path3.resolve(opts.dist);
|
|
417
|
+
const maps = await walk(root);
|
|
418
|
+
if (maps.length === 0) {
|
|
419
|
+
log(import_picocolors4.default.yellow(`No .map files found under ${root}`));
|
|
420
|
+
return {
|
|
421
|
+
uploaded: 0,
|
|
422
|
+
failed: 0,
|
|
423
|
+
skipped: 0
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
log(import_picocolors4.default.cyan(`Uploading ${maps.length} source map(s)${opts.release ? ` for release ${opts.release}` : ""}\u2026`));
|
|
427
|
+
let uploaded = 0;
|
|
428
|
+
let failed = 0;
|
|
429
|
+
let skipped = 0;
|
|
430
|
+
for (const absPath of maps) {
|
|
431
|
+
const safe = safeRelative(root, absPath);
|
|
432
|
+
if (!safe) {
|
|
433
|
+
log(import_picocolors4.default.red(` \u2717 ${absPath} \u2014 path escapes dist root`));
|
|
434
|
+
failed++;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const stat = await import_node_fs3.promises.stat(absPath);
|
|
438
|
+
if (stat.size > maxBytes) {
|
|
439
|
+
log(import_picocolors4.default.yellow(` \u26A0 ${safe} \u2014 ${stat.size} bytes exceeds cap ${maxBytes}, skipping`));
|
|
440
|
+
skipped++;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
const content = await import_node_fs3.promises.readFile(absPath, "utf-8");
|
|
444
|
+
const ok = await postWithRetry(fetchImpl, opts.apiUrl, opts.key, {
|
|
445
|
+
path: safe,
|
|
446
|
+
content,
|
|
447
|
+
release: opts.release,
|
|
448
|
+
environment: opts.environment
|
|
449
|
+
});
|
|
450
|
+
if (ok.ok) {
|
|
451
|
+
log(` ${import_picocolors4.default.green("\u2713")} ${safe}`);
|
|
452
|
+
uploaded++;
|
|
453
|
+
} else {
|
|
454
|
+
log(import_picocolors4.default.red(` \u2717 ${safe} \u2014 ${ok.detail}`));
|
|
455
|
+
failed++;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
log("");
|
|
459
|
+
log(`${import_picocolors4.default.bold("Summary:")} ${import_picocolors4.default.green(`${uploaded} uploaded`)}, ${failed ? import_picocolors4.default.red(`${failed} failed`) : `${failed} failed`}, ${skipped} skipped.`);
|
|
460
|
+
if (failed > 0 && strict) {
|
|
461
|
+
throw new Error(`${failed} source-map upload(s) failed (use --no-strict to ignore).`);
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
uploaded,
|
|
465
|
+
failed,
|
|
466
|
+
skipped
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
__name(sourcemapUpload, "sourcemapUpload");
|
|
470
|
+
function safeRelative(root, abs) {
|
|
471
|
+
const resolvedRoot = path3.resolve(root);
|
|
472
|
+
const resolved = path3.resolve(abs);
|
|
473
|
+
if (!resolved.startsWith(resolvedRoot + path3.sep) && resolved !== resolvedRoot) return null;
|
|
474
|
+
const rel = path3.relative(resolvedRoot, resolved);
|
|
475
|
+
if (!rel || rel.startsWith("..") || path3.isAbsolute(rel)) return null;
|
|
476
|
+
return rel.split(path3.sep).join("/");
|
|
477
|
+
}
|
|
478
|
+
__name(safeRelative, "safeRelative");
|
|
479
|
+
async function walk(dir) {
|
|
480
|
+
const out = [];
|
|
481
|
+
let entries;
|
|
482
|
+
try {
|
|
483
|
+
entries = await import_node_fs3.promises.readdir(dir, {
|
|
484
|
+
withFileTypes: true
|
|
485
|
+
});
|
|
486
|
+
} catch {
|
|
487
|
+
return out;
|
|
488
|
+
}
|
|
489
|
+
for (const entry of entries) {
|
|
490
|
+
if (entry.isSymbolicLink()) continue;
|
|
491
|
+
const full = path3.join(dir, entry.name);
|
|
492
|
+
if (entry.isDirectory()) {
|
|
493
|
+
out.push(...await walk(full));
|
|
494
|
+
} else if (entry.isFile() && entry.name.endsWith(".map")) {
|
|
495
|
+
out.push(full);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return out.sort();
|
|
499
|
+
}
|
|
500
|
+
__name(walk, "walk");
|
|
501
|
+
async function postWithRetry(fetchImpl, apiUrl, key, body) {
|
|
502
|
+
const url = `${apiUrl.replace(/\/+$/, "")}/sdk/source-maps`;
|
|
503
|
+
let lastDetail = "unknown";
|
|
504
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
505
|
+
try {
|
|
506
|
+
const res = await fetchImpl(url, {
|
|
507
|
+
method: "POST",
|
|
508
|
+
headers: {
|
|
509
|
+
"content-type": "application/json",
|
|
510
|
+
authorization: `Bearer ${key}`
|
|
511
|
+
},
|
|
512
|
+
body: JSON.stringify(body)
|
|
513
|
+
});
|
|
514
|
+
if (res.ok) return {
|
|
515
|
+
ok: true,
|
|
516
|
+
detail: `HTTP ${res.status}`
|
|
517
|
+
};
|
|
518
|
+
lastDetail = `HTTP ${res.status}`;
|
|
519
|
+
if (res.status !== 429 && (res.status < 500 || res.status >= 600)) {
|
|
520
|
+
return {
|
|
521
|
+
ok: false,
|
|
522
|
+
detail: lastDetail
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
} catch (err) {
|
|
526
|
+
lastDetail = err.message;
|
|
527
|
+
}
|
|
528
|
+
if (attempt < MAX_RETRIES) {
|
|
529
|
+
await new Promise((r) => setTimeout(r, RETRY_BASE_MS * Math.pow(2, attempt)));
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
ok: false,
|
|
534
|
+
detail: lastDetail
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
__name(postWithRetry, "postWithRetry");
|
|
538
|
+
|
|
539
|
+
// src/commands/symbols-upload.ts
|
|
540
|
+
var import_node_fs4 = require("fs");
|
|
541
|
+
var path4 = __toESM(require("path"), 1);
|
|
542
|
+
var import_picocolors5 = __toESM(require("picocolors"), 1);
|
|
543
|
+
var NATIVE_SYMBOLS_PATH = "/sdk/native-symbols";
|
|
544
|
+
async function uploadIos(dsymPath, opts) {
|
|
545
|
+
const log = opts.log ?? ((s) => console.log(s));
|
|
546
|
+
const root = path4.resolve(dsymPath);
|
|
547
|
+
if (!root.endsWith(".dSYM")) {
|
|
548
|
+
log(import_picocolors5.default.yellow(`Warning: ${root} does not end in .dSYM \u2014 proceeding anyway.`));
|
|
549
|
+
}
|
|
550
|
+
const dwarfRoot = path4.join(root, "Contents", "Resources", "DWARF");
|
|
551
|
+
const files = await listFiles(dwarfRoot);
|
|
552
|
+
if (files.length === 0) {
|
|
553
|
+
log(import_picocolors5.default.yellow(`No DWARF files found under ${dwarfRoot}`));
|
|
554
|
+
return {
|
|
555
|
+
uploaded: 0,
|
|
556
|
+
failed: 0,
|
|
557
|
+
endpointMissing: false
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
log(import_picocolors5.default.cyan(`Uploading ${files.length} iOS symbol file(s)\u2026`));
|
|
561
|
+
return uploadAll(files, "ios", opts, log);
|
|
562
|
+
}
|
|
563
|
+
__name(uploadIos, "uploadIos");
|
|
564
|
+
async function uploadAndroid(mappingPath, opts) {
|
|
565
|
+
const log = opts.log ?? ((s) => console.log(s));
|
|
566
|
+
const resolved = path4.resolve(mappingPath);
|
|
567
|
+
const stat = await import_node_fs4.promises.stat(resolved).catch(() => null);
|
|
568
|
+
if (!stat?.isFile()) {
|
|
569
|
+
throw new Error(`mapping.txt not found at ${resolved}`);
|
|
570
|
+
}
|
|
571
|
+
log(import_picocolors5.default.cyan(`Uploading Android mapping ${resolved}\u2026`));
|
|
572
|
+
return uploadAll([
|
|
573
|
+
resolved
|
|
574
|
+
], "android", opts, log);
|
|
575
|
+
}
|
|
576
|
+
__name(uploadAndroid, "uploadAndroid");
|
|
577
|
+
async function uploadAll(files, platform, opts, log) {
|
|
578
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
579
|
+
const url = `${opts.apiUrl.replace(/\/+$/, "")}${NATIVE_SYMBOLS_PATH}`;
|
|
580
|
+
let uploaded = 0;
|
|
581
|
+
let failed = 0;
|
|
582
|
+
let endpointMissing = false;
|
|
583
|
+
for (const file of files) {
|
|
584
|
+
const content = await import_node_fs4.promises.readFile(file);
|
|
585
|
+
const body = {
|
|
586
|
+
platform,
|
|
587
|
+
filename: path4.basename(file),
|
|
588
|
+
release: opts.release,
|
|
589
|
+
// Base64-encoded so binary DWARFs survive JSON transport.
|
|
590
|
+
contentBase64: content.toString("base64")
|
|
591
|
+
};
|
|
592
|
+
try {
|
|
593
|
+
const res = await fetchImpl(url, {
|
|
594
|
+
method: "POST",
|
|
595
|
+
headers: {
|
|
596
|
+
"content-type": "application/json",
|
|
597
|
+
authorization: `Bearer ${opts.key}`
|
|
598
|
+
},
|
|
599
|
+
body: JSON.stringify(body)
|
|
600
|
+
});
|
|
601
|
+
if (res.status === 404) {
|
|
602
|
+
endpointMissing = true;
|
|
603
|
+
log(import_picocolors5.default.yellow(` \u26A0 ${path4.basename(file)} \u2014 endpoint pending (${NATIVE_SYMBOLS_PATH} returned 404)`));
|
|
604
|
+
} else if (res.ok) {
|
|
605
|
+
log(` ${import_picocolors5.default.green("\u2713")} ${path4.basename(file)}`);
|
|
606
|
+
uploaded++;
|
|
607
|
+
} else {
|
|
608
|
+
log(import_picocolors5.default.red(` \u2717 ${path4.basename(file)} \u2014 HTTP ${res.status}`));
|
|
609
|
+
failed++;
|
|
610
|
+
}
|
|
611
|
+
} catch (err) {
|
|
612
|
+
log(import_picocolors5.default.red(` \u2717 ${path4.basename(file)} \u2014 ${err.message}`));
|
|
613
|
+
failed++;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (endpointMissing) {
|
|
617
|
+
log("");
|
|
618
|
+
log(import_picocolors5.default.yellow(`Backend resolver pending: ${NATIVE_SYMBOLS_PATH} is not yet implemented. Symbol files were prepared but not stored server-side.`));
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
uploaded,
|
|
622
|
+
failed,
|
|
623
|
+
endpointMissing
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
__name(uploadAll, "uploadAll");
|
|
627
|
+
async function listFiles(dir) {
|
|
628
|
+
const out = [];
|
|
629
|
+
let entries;
|
|
630
|
+
try {
|
|
631
|
+
entries = await import_node_fs4.promises.readdir(dir, {
|
|
632
|
+
withFileTypes: true
|
|
633
|
+
});
|
|
634
|
+
} catch {
|
|
635
|
+
return out;
|
|
636
|
+
}
|
|
637
|
+
for (const entry of entries) {
|
|
638
|
+
if (entry.isSymbolicLink()) continue;
|
|
639
|
+
const full = path4.join(dir, entry.name);
|
|
640
|
+
if (entry.isDirectory()) {
|
|
641
|
+
out.push(...await listFiles(full));
|
|
642
|
+
} else if (entry.isFile()) {
|
|
643
|
+
out.push(full);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return out;
|
|
647
|
+
}
|
|
648
|
+
__name(listFiles, "listFiles");
|
|
649
|
+
|
|
650
|
+
// src/commands/init.ts
|
|
651
|
+
var import_node_fs5 = require("fs");
|
|
652
|
+
var path5 = __toESM(require("path"), 1);
|
|
653
|
+
var import_picocolors6 = __toESM(require("picocolors"), 1);
|
|
654
|
+
var SUPPORTED_PLATFORMS = [
|
|
655
|
+
"web",
|
|
656
|
+
"react",
|
|
657
|
+
"nextjs",
|
|
658
|
+
"vue",
|
|
659
|
+
"rn",
|
|
660
|
+
"nestjs"
|
|
661
|
+
];
|
|
662
|
+
var RC_TEMPLATE = `{
|
|
663
|
+
"endpoint": "https://tasks.koderlabs.net/api/v1",
|
|
664
|
+
"ingestKey": "pk_live_REPLACE_ME",
|
|
665
|
+
"projectId": "REPLACE_WITH_PROJECT_UUID"
|
|
666
|
+
}
|
|
667
|
+
`;
|
|
668
|
+
var README_SNIPPET = `
|
|
669
|
+
## InstantTasks SDK
|
|
670
|
+
|
|
671
|
+
Run \`npx @koderlabs/tasks-cli doctor\` to verify your setup.
|
|
672
|
+
|
|
673
|
+
Upload source maps in CI:
|
|
674
|
+
|
|
675
|
+
\`\`\`sh
|
|
676
|
+
npx @koderlabs/tasks-cli sourcemap upload ./dist --release "$GIT_SHA"
|
|
677
|
+
\`\`\`
|
|
678
|
+
`;
|
|
679
|
+
function planInit(platform) {
|
|
680
|
+
const files = [
|
|
681
|
+
{
|
|
682
|
+
filepath: ".tasksrc.json",
|
|
683
|
+
contents: RC_TEMPLATE
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
filepath: "INSTANTTASKS.md",
|
|
687
|
+
contents: README_SNIPPET.trim() + "\n"
|
|
688
|
+
}
|
|
689
|
+
];
|
|
690
|
+
files.push({
|
|
691
|
+
filepath: snippetPath(platform),
|
|
692
|
+
contents: snippetFor(platform)
|
|
693
|
+
});
|
|
694
|
+
return files;
|
|
695
|
+
}
|
|
696
|
+
__name(planInit, "planInit");
|
|
697
|
+
function snippetPath(platform) {
|
|
698
|
+
switch (platform) {
|
|
699
|
+
case "web":
|
|
700
|
+
return "src/tasks-sdk.ts";
|
|
701
|
+
case "react":
|
|
702
|
+
return "src/tasks-sdk.ts";
|
|
703
|
+
case "nextjs":
|
|
704
|
+
return "instrumentation.ts";
|
|
705
|
+
case "vue":
|
|
706
|
+
return "src/tasks-sdk.ts";
|
|
707
|
+
case "rn":
|
|
708
|
+
return "src/tasks-sdk.ts";
|
|
709
|
+
case "nestjs":
|
|
710
|
+
return "src/tasks-sdk.ts";
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
__name(snippetPath, "snippetPath");
|
|
714
|
+
function snippetFor(platform) {
|
|
715
|
+
switch (platform) {
|
|
716
|
+
case "web":
|
|
717
|
+
case "react":
|
|
718
|
+
case "vue":
|
|
719
|
+
return `import { init } from '@koderlabs/tasks-sdk-browser';
|
|
720
|
+
|
|
721
|
+
init({
|
|
722
|
+
endpoint: import.meta.env.VITE_TASKS_ENDPOINT,
|
|
723
|
+
ingestKey: import.meta.env.VITE_TASKS_INGEST_KEY,
|
|
724
|
+
release: import.meta.env.VITE_RELEASE,
|
|
725
|
+
});
|
|
726
|
+
`;
|
|
727
|
+
case "nextjs":
|
|
728
|
+
return `// Next.js 13+ instrumentation hook \u2014 auto-loaded once per process.
|
|
729
|
+
export async function register() {
|
|
730
|
+
const { init } = await import('@koderlabs/tasks-sdk-nextjs');
|
|
731
|
+
init({
|
|
732
|
+
endpoint: process.env.NEXT_PUBLIC_TASKS_ENDPOINT!,
|
|
733
|
+
ingestKey: process.env.NEXT_PUBLIC_TASKS_INGEST_KEY!,
|
|
734
|
+
release: process.env.NEXT_PUBLIC_RELEASE,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
`;
|
|
738
|
+
case "rn":
|
|
739
|
+
return `import { init } from '@koderlabs/tasks-sdk-rn';
|
|
740
|
+
|
|
741
|
+
init({
|
|
742
|
+
endpoint: process.env.EXPO_PUBLIC_TASKS_ENDPOINT!,
|
|
743
|
+
ingestKey: process.env.EXPO_PUBLIC_TASKS_INGEST_KEY!,
|
|
744
|
+
release: process.env.EXPO_PUBLIC_RELEASE,
|
|
745
|
+
});
|
|
746
|
+
`;
|
|
747
|
+
case "nestjs":
|
|
748
|
+
return `import { init } from '@koderlabs/tasks-sdk-node';
|
|
749
|
+
|
|
750
|
+
init({
|
|
751
|
+
endpoint: process.env.INSTANTTASKS_ENDPOINT!,
|
|
752
|
+
ingestKey: process.env.INSTANTTASKS_INGEST_KEY!,
|
|
753
|
+
release: process.env.RELEASE,
|
|
754
|
+
});
|
|
755
|
+
`;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
__name(snippetFor, "snippetFor");
|
|
759
|
+
async function initScaffold(opts) {
|
|
760
|
+
const log = opts.log ?? ((s) => console.log(s));
|
|
761
|
+
const cwd = path5.resolve(opts.cwd ?? process.cwd());
|
|
762
|
+
const files = planInit(opts.platform);
|
|
763
|
+
const written = [];
|
|
764
|
+
const skipped = [];
|
|
765
|
+
log(import_picocolors6.default.cyan(`Scaffolding InstantTasks for ${import_picocolors6.default.bold(opts.platform)}\u2026`));
|
|
766
|
+
for (const f of files) {
|
|
767
|
+
const abs = path5.join(cwd, f.filepath);
|
|
768
|
+
const exists = await import_node_fs5.promises.stat(abs).then(() => true).catch(() => false);
|
|
769
|
+
if (opts.dryRun) {
|
|
770
|
+
log(import_picocolors6.default.dim(` [dry-run] would write ${f.filepath}${exists ? " (overwrite)" : ""}`));
|
|
771
|
+
log(import_picocolors6.default.dim(" \u250C\u2500"));
|
|
772
|
+
for (const line of f.contents.split("\n")) log(import_picocolors6.default.dim(` \u2502 ${line}`));
|
|
773
|
+
log(import_picocolors6.default.dim(" \u2514\u2500"));
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (exists && !opts.force) {
|
|
777
|
+
log(import_picocolors6.default.yellow(` \u26A0 ${f.filepath} exists \u2014 skipping (pass --force to overwrite)`));
|
|
778
|
+
skipped.push(f.filepath);
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
await import_node_fs5.promises.mkdir(path5.dirname(abs), {
|
|
782
|
+
recursive: true
|
|
783
|
+
});
|
|
784
|
+
await import_node_fs5.promises.writeFile(abs, f.contents, "utf-8");
|
|
785
|
+
log(` ${import_picocolors6.default.green("\u2713")} ${f.filepath}`);
|
|
786
|
+
written.push(f.filepath);
|
|
787
|
+
}
|
|
788
|
+
if (!opts.dryRun) {
|
|
789
|
+
log("");
|
|
790
|
+
log(import_picocolors6.default.green("Done. Edit .tasksrc.json with your project credentials, then run:"));
|
|
791
|
+
log(` ${import_picocolors6.default.bold("npx @koderlabs/tasks-cli doctor")}`);
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
files,
|
|
795
|
+
written,
|
|
796
|
+
skipped
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
__name(initScaffold, "initScaffold");
|
|
800
|
+
|
|
801
|
+
// src/commands/events-tail.ts
|
|
802
|
+
var import_picocolors7 = __toESM(require("picocolors"), 1);
|
|
803
|
+
async function eventsTail(opts) {
|
|
804
|
+
const log = opts.log ?? ((s) => console.log(s));
|
|
805
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
806
|
+
const interval = opts.intervalMs ?? 5e3;
|
|
807
|
+
let since = opts.since ?? Date.now();
|
|
808
|
+
log(import_picocolors7.default.cyan(`Tailing events (poll every ${interval}ms). Ctrl-C to exit.`));
|
|
809
|
+
while (!opts.signal?.aborted) {
|
|
810
|
+
const params = new URLSearchParams({
|
|
811
|
+
since: String(since)
|
|
812
|
+
});
|
|
813
|
+
if (opts.projectId) params.set("projectId", opts.projectId);
|
|
814
|
+
for (const [k, v] of Object.entries(opts.filter ?? {})) params.set(k, v);
|
|
815
|
+
const url = `${opts.apiUrl.replace(/\/+$/, "")}/sdk/v1/events?${params.toString()}`;
|
|
816
|
+
try {
|
|
817
|
+
const res = await fetchImpl(url, {
|
|
818
|
+
method: "GET",
|
|
819
|
+
headers: {
|
|
820
|
+
authorization: `Bearer ${opts.key}`
|
|
821
|
+
},
|
|
822
|
+
signal: opts.signal
|
|
823
|
+
});
|
|
824
|
+
if (res.status === 404) {
|
|
825
|
+
log(import_picocolors7.default.yellow(`events endpoint not available yet (404). Backend needs to ship /sdk/v1/events \u2014 exiting tail loop.`));
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (res.ok) {
|
|
829
|
+
const body = await res.json();
|
|
830
|
+
const rows = Array.isArray(body) ? body : Array.isArray(body?.data) ? body.data : [];
|
|
831
|
+
for (const row of rows) {
|
|
832
|
+
const ts = typeof row.ts === "number" ? new Date(row.ts).toISOString() : String(row.ts);
|
|
833
|
+
const kind = row.kind ?? "event";
|
|
834
|
+
const message = row.message ?? "";
|
|
835
|
+
const url2 = row.url ? import_picocolors7.default.dim(` ${row.url}`) : "";
|
|
836
|
+
log(`${import_picocolors7.default.dim(ts)} | ${import_picocolors7.default.bold(kind.padEnd(12))} | ${message}${url2}`);
|
|
837
|
+
if (typeof row.ts === "number") since = Math.max(since, row.ts + 1);
|
|
838
|
+
}
|
|
839
|
+
if (rows.length === 0) {
|
|
840
|
+
since = Date.now();
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
log(import_picocolors7.default.yellow(`poll failed: HTTP ${res.status}`));
|
|
844
|
+
}
|
|
845
|
+
} catch (err) {
|
|
846
|
+
if (err.name === "AbortError") return;
|
|
847
|
+
log(import_picocolors7.default.yellow(`poll error: ${err.message}`));
|
|
848
|
+
}
|
|
849
|
+
await wait(interval, opts.signal);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
__name(eventsTail, "eventsTail");
|
|
853
|
+
function wait(ms, signal) {
|
|
854
|
+
return new Promise((resolve6) => {
|
|
855
|
+
const t = setTimeout(resolve6, ms);
|
|
856
|
+
signal?.addEventListener("abort", () => {
|
|
857
|
+
clearTimeout(t);
|
|
858
|
+
resolve6();
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
__name(wait, "wait");
|
|
863
|
+
function parseSince(input, now = Date.now()) {
|
|
864
|
+
if (!input) return now;
|
|
865
|
+
const match = input.match(/^(\d+)([smhd])$/);
|
|
866
|
+
if (!match) {
|
|
867
|
+
const n = Number(input);
|
|
868
|
+
return Number.isFinite(n) ? n : now;
|
|
869
|
+
}
|
|
870
|
+
const value = parseInt(match[1], 10);
|
|
871
|
+
const unit = match[2];
|
|
872
|
+
const mult = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
873
|
+
return now - value * mult;
|
|
874
|
+
}
|
|
875
|
+
__name(parseSince, "parseSince");
|
|
876
|
+
|
|
877
|
+
// src/index.ts
|
|
878
|
+
var VERSION = "0.0.0";
|
|
879
|
+
function buildProgram() {
|
|
880
|
+
const program = new import_commander.Command();
|
|
881
|
+
program.name("tasks").description("InstantTasks CLI \u2014 doctor, source maps, symbols, init, events tail").version(VERSION);
|
|
882
|
+
program.command("doctor").description("Validate config + connectivity (CI-friendly; non-zero on failure)").option("--api-url <url>", "API base URL (defaults to env / .tasksrc.json)").option("--key <key>", "Ingest or management key").option("--project <id>", "Project ID").action(async (opts) => {
|
|
883
|
+
const result = await doctor({
|
|
884
|
+
apiUrl: opts.apiUrl,
|
|
885
|
+
key: opts.key,
|
|
886
|
+
projectId: opts.project
|
|
887
|
+
});
|
|
888
|
+
if (!result.ok) process.exit(1);
|
|
889
|
+
});
|
|
890
|
+
const sourcemap = program.command("sourcemap").description("Source-map utilities");
|
|
891
|
+
sourcemap.command("upload <dist>").description("Recursively upload .map files from a dist directory").option("--release <sha>", "Release name / version (associates maps with a release)").option("--environment <env>", "Release environment", "production").option("--max-size <bytes>", "Per-file size cap in bytes", String(50 * 1024 * 1024)).option("--no-strict", "Do not exit non-zero on individual file failures").option("--api-url <url>", "API base URL").option("--key <key>", "Management key (sk_live_...)").action(async (dist, opts) => {
|
|
892
|
+
const cfg = resolveConfig({
|
|
893
|
+
apiUrl: opts.apiUrl,
|
|
894
|
+
key: opts.key
|
|
895
|
+
});
|
|
896
|
+
await sourcemapUpload({
|
|
897
|
+
dist,
|
|
898
|
+
apiUrl: cfg.apiUrl,
|
|
899
|
+
key: cfg.key,
|
|
900
|
+
release: opts.release,
|
|
901
|
+
environment: opts.environment,
|
|
902
|
+
maxBytes: Number(opts.maxSize),
|
|
903
|
+
strict: opts.strict !== false
|
|
904
|
+
});
|
|
905
|
+
});
|
|
906
|
+
const symbols = program.command("symbols").description("Native symbol uploads (iOS dSYM, Android ProGuard)");
|
|
907
|
+
symbols.command("upload-ios <dsym>").description("Upload DWARF files from a .dSYM bundle").option("--release <sha>", "Release name / version").option("--api-url <url>").option("--key <key>").action(async (dsym, opts) => {
|
|
908
|
+
const cfg = resolveConfig({
|
|
909
|
+
apiUrl: opts.apiUrl,
|
|
910
|
+
key: opts.key
|
|
911
|
+
});
|
|
912
|
+
await uploadIos(dsym, {
|
|
913
|
+
apiUrl: cfg.apiUrl,
|
|
914
|
+
key: cfg.key,
|
|
915
|
+
release: opts.release
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
symbols.command("upload-android <mapping>").description("Upload an Android ProGuard mapping.txt").option("--release <sha>", "Release name / version").option("--api-url <url>").option("--key <key>").action(async (mapping, opts) => {
|
|
919
|
+
const cfg = resolveConfig({
|
|
920
|
+
apiUrl: opts.apiUrl,
|
|
921
|
+
key: opts.key
|
|
922
|
+
});
|
|
923
|
+
await uploadAndroid(mapping, {
|
|
924
|
+
apiUrl: cfg.apiUrl,
|
|
925
|
+
key: cfg.key,
|
|
926
|
+
release: opts.release
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
program.command("init <platform>").description(`Scaffold .tasksrc.json + init snippet (platforms: ${SUPPORTED_PLATFORMS.join(", ")})`).option("--dry-run", "Print files that would be written but do not write them").option("--force", "Overwrite existing files").action(async (platform, opts) => {
|
|
930
|
+
if (!SUPPORTED_PLATFORMS.includes(platform)) {
|
|
931
|
+
throw new Error(`Unsupported platform "${platform}". Pick one of: ${SUPPORTED_PLATFORMS.join(", ")}`);
|
|
932
|
+
}
|
|
933
|
+
await initScaffold({
|
|
934
|
+
platform,
|
|
935
|
+
dryRun: opts.dryRun,
|
|
936
|
+
force: opts.force
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
const events = program.command("events").description("Event utilities");
|
|
940
|
+
events.command("tail").description("Stream recent SDK events (long-poll fallback until WebSocket lands)").option("--filter <kv...>", "Filter, e.g. project=<id>").option("--since <duration>", "Lookback window (e.g. 10m, 1h)").option("--interval <ms>", "Poll interval in ms", "5000").option("--api-url <url>").option("--key <key>").option("--project <id>").action(async (opts) => {
|
|
941
|
+
const cfg = resolveConfig({
|
|
942
|
+
apiUrl: opts.apiUrl,
|
|
943
|
+
key: opts.key,
|
|
944
|
+
projectId: opts.project
|
|
945
|
+
});
|
|
946
|
+
const filter = {};
|
|
947
|
+
const rawFilters = Array.isArray(opts.filter) ? opts.filter : opts.filter ? [
|
|
948
|
+
opts.filter
|
|
949
|
+
] : [];
|
|
950
|
+
for (const kv of rawFilters) {
|
|
951
|
+
const [k, v] = String(kv).split("=");
|
|
952
|
+
if (k && v) filter[k] = v;
|
|
953
|
+
}
|
|
954
|
+
const controller = new AbortController();
|
|
955
|
+
const onSig = /* @__PURE__ */ __name(() => {
|
|
956
|
+
console.log(import_picocolors8.default.dim("\n (exiting)"));
|
|
957
|
+
controller.abort();
|
|
958
|
+
}, "onSig");
|
|
959
|
+
process.once("SIGINT", onSig);
|
|
960
|
+
process.once("SIGTERM", onSig);
|
|
961
|
+
await eventsTail({
|
|
962
|
+
apiUrl: cfg.apiUrl,
|
|
963
|
+
key: cfg.key,
|
|
964
|
+
projectId: cfg.projectId,
|
|
965
|
+
since: parseSince(opts.since),
|
|
966
|
+
intervalMs: Number(opts.interval) || 5e3,
|
|
967
|
+
filter,
|
|
968
|
+
signal: controller.signal
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
program.command("upload-sourcemaps").description("[legacy] Upload .js.map files in a directory to a release").requiredOption("--release <name>", "Release name / version").requiredOption("--dir <path>", "Directory to walk for *.js.map files").option("--environment <env>", "Release environment", "production").option("--url-prefix <prefix>", "Public URL prefix for source files").option("--project <projectId>").option("--api-url <url>").option("--key <key>").action(async (opts) => {
|
|
972
|
+
const cfg = resolveConfig({
|
|
973
|
+
apiUrl: opts.apiUrl,
|
|
974
|
+
key: opts.key,
|
|
975
|
+
projectId: opts.project
|
|
976
|
+
});
|
|
977
|
+
const client = new ApiClient({
|
|
978
|
+
apiUrl: cfg.apiUrl,
|
|
979
|
+
key: cfg.key
|
|
980
|
+
});
|
|
981
|
+
await uploadSourcemaps({
|
|
982
|
+
release: opts.release,
|
|
983
|
+
dir: opts.dir,
|
|
984
|
+
environment: opts.environment,
|
|
985
|
+
urlPrefix: opts.urlPrefix,
|
|
986
|
+
apiClient: client
|
|
987
|
+
});
|
|
988
|
+
});
|
|
989
|
+
program.command("create-release").description("Create (or upsert) a release on the server").requiredOption("--name <name>", "Release name / version").option("--environment <env>", "Release environment", "production").option("--project <projectId>").option("--api-url <url>").option("--key <key>").action(async (opts) => {
|
|
990
|
+
const cfg = resolveConfig({
|
|
991
|
+
apiUrl: opts.apiUrl,
|
|
992
|
+
key: opts.key,
|
|
993
|
+
projectId: opts.project
|
|
994
|
+
});
|
|
995
|
+
const client = new ApiClient({
|
|
996
|
+
apiUrl: cfg.apiUrl,
|
|
997
|
+
key: cfg.key
|
|
998
|
+
});
|
|
999
|
+
await createRelease({
|
|
1000
|
+
name: opts.name,
|
|
1001
|
+
environment: opts.environment,
|
|
1002
|
+
apiClient: client
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
return program;
|
|
1006
|
+
}
|
|
1007
|
+
__name(buildProgram, "buildProgram");
|
|
1008
|
+
async function main(argv = process.argv) {
|
|
1009
|
+
const program = buildProgram();
|
|
1010
|
+
program.exitOverride();
|
|
1011
|
+
try {
|
|
1012
|
+
await program.parseAsync(argv);
|
|
1013
|
+
return 0;
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
if (err instanceof ApiError) {
|
|
1016
|
+
console.error(import_picocolors8.default.red(`API error (${err.status}): ${err.message}`));
|
|
1017
|
+
return 1;
|
|
1018
|
+
}
|
|
1019
|
+
const anyErr = err;
|
|
1020
|
+
if (anyErr && typeof anyErr === "object" && "code" in anyErr) {
|
|
1021
|
+
if (anyErr.code === "commander.helpDisplayed" || anyErr.code === "commander.version") return 0;
|
|
1022
|
+
if (anyErr.code === "commander.help") return 0;
|
|
1023
|
+
}
|
|
1024
|
+
console.error(import_picocolors8.default.red(err.message || String(err)));
|
|
1025
|
+
return 1;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
__name(main, "main");
|
|
1029
|
+
var isEntry = (() => {
|
|
1030
|
+
try {
|
|
1031
|
+
const argv1 = process.argv[1] ?? "";
|
|
1032
|
+
return /(?:^|[\\/])(tasks|instanttasks-cli|index\.[mc]?js)$/.test(argv1);
|
|
1033
|
+
} catch {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
})();
|
|
1037
|
+
if (isEntry) {
|
|
1038
|
+
main().then((code) => process.exit(code));
|
|
1039
|
+
}
|
|
1040
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1041
|
+
0 && (module.exports = {
|
|
1042
|
+
buildProgram,
|
|
1043
|
+
main
|
|
1044
|
+
});
|
|
1045
|
+
//# sourceMappingURL=index.cjs.map
|