tracksiy 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tracksiy.mjs +68 -25
- package/package.json +1 -1
package/dist/tracksiy.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
|
-
var VERSION = true ? "0.1.
|
|
7
|
+
var VERSION = true ? "0.1.1" : "0.0.0-dev";
|
|
8
8
|
var USER_AGENT = `tracksiy-cli/${VERSION} (node ${process.versions.node}; ${process.platform})`;
|
|
9
9
|
|
|
10
10
|
// src/core/errors.ts
|
|
@@ -1011,7 +1011,7 @@ function statusCommand(name, status2, summary) {
|
|
|
1011
1011
|
const ids = ctx.input.ids;
|
|
1012
1012
|
for (const id of ids) if (!/^\d+$/.test(id)) throw usageError("bad_value", `Issue ids are numeric; got ${JSON.stringify(id)}`);
|
|
1013
1013
|
if (ids.length === 1) return ctx.api.send("POST", await projectPath(ctx, `issues/${ids[0]}/status`), { status: status2 });
|
|
1014
|
-
return ctx.api.send("POST", await projectPath(ctx, "issues/status"), { status: status2, ids
|
|
1014
|
+
return ctx.api.send("POST", await projectPath(ctx, "issues/status"), { status: status2, ids, dryRun: false });
|
|
1015
1015
|
}
|
|
1016
1016
|
};
|
|
1017
1017
|
}
|
|
@@ -1042,7 +1042,7 @@ var bulk = {
|
|
|
1042
1042
|
const hasFilter = Object.keys(filter).length > 0;
|
|
1043
1043
|
const body = compact({
|
|
1044
1044
|
status: ctx.input.to,
|
|
1045
|
-
ids: ctx.input.ids?.map(
|
|
1045
|
+
ids: ctx.input.ids?.map(String),
|
|
1046
1046
|
filter: hasFilter ? filter : void 0,
|
|
1047
1047
|
all: ctx.input.all || void 0,
|
|
1048
1048
|
dryRun: !ctx.input.apply
|
|
@@ -1245,9 +1245,14 @@ var test = {
|
|
|
1245
1245
|
context: "project",
|
|
1246
1246
|
effect: "write",
|
|
1247
1247
|
endpoint: { method: "POST", path: "/api/agent/v1/projects/{org}/{project}/alerts/test" },
|
|
1248
|
+
description: "By default only the recipients of the agent-managed (`agent:`) rules are tested; the note says how many rules made in the dashboard were left out. --all-rules tests every rule's recipients. An email recipient with no address goes to the project's team members, and is a failure when nobody is eligible.",
|
|
1249
|
+
flags: {
|
|
1250
|
+
"all-rules": { type: "boolean", description: "Also test the recipients of rules made outside the agent API (e.g. in the dashboard)." }
|
|
1251
|
+
},
|
|
1252
|
+
examples: [{ command: "tracksiy alerts test --all-rules", description: "Prove every rule on the project can reach someone." }],
|
|
1248
1253
|
output: "Per-recipient delivery results with ok/status/error.",
|
|
1249
1254
|
async run(ctx) {
|
|
1250
|
-
const data = await ctx.api.send("POST", await projectPath(ctx, "alerts/test"));
|
|
1255
|
+
const data = await ctx.api.send("POST", await projectPath(ctx, "alerts/test"), ctx.input.allRules ? { allRules: true } : void 0);
|
|
1251
1256
|
const results = data?.results ?? [];
|
|
1252
1257
|
if (!results.length) {
|
|
1253
1258
|
throw new CheckFailed("This project has no recipients, so nobody would be told.", data, "Declare one with `tracksiy alerts set --recipient <type>:<target>`.");
|
|
@@ -1580,6 +1585,30 @@ function manifestFor(files, chunkSize) {
|
|
|
1580
1585
|
return { manifest, chunks };
|
|
1581
1586
|
}
|
|
1582
1587
|
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1588
|
+
function packManifest(manifest, maxFiles, maxBytes) {
|
|
1589
|
+
const units = [];
|
|
1590
|
+
for (const entry of manifest) {
|
|
1591
|
+
const last = units[units.length - 1];
|
|
1592
|
+
if (entry.kind === "sourcemap" && last && last.length === 1 && last[0].sourcemap === entry.name) last.push(entry);
|
|
1593
|
+
else units.push([entry]);
|
|
1594
|
+
}
|
|
1595
|
+
const parts = [];
|
|
1596
|
+
let part = [];
|
|
1597
|
+
let bytes = 0;
|
|
1598
|
+
for (const unit of units) {
|
|
1599
|
+
const size = unit.reduce((n, f) => n + f.size, 0);
|
|
1600
|
+
if (part.length && (part.length + unit.length > maxFiles || bytes + size > maxBytes)) {
|
|
1601
|
+
parts.push(part);
|
|
1602
|
+
part = [];
|
|
1603
|
+
bytes = 0;
|
|
1604
|
+
}
|
|
1605
|
+
part.push(...unit);
|
|
1606
|
+
bytes += size;
|
|
1607
|
+
}
|
|
1608
|
+
if (part.length) parts.push(part);
|
|
1609
|
+
return parts;
|
|
1610
|
+
}
|
|
1611
|
+
var STATE_RANK = { error: 4, missing_chunks: 3, pending: 2 };
|
|
1583
1612
|
var UPLOAD_TIMEOUT_MS = 12e4;
|
|
1584
1613
|
async function uploadArtifacts(ctx, files, opts) {
|
|
1585
1614
|
const { org, project: project2 } = await ctx.project();
|
|
@@ -1588,12 +1617,14 @@ async function uploadArtifacts(ctx, files, opts) {
|
|
|
1588
1617
|
const chunkSize = options.chunkSize ?? 8 * 1024 * 1024;
|
|
1589
1618
|
const perRequest = options.maxChunksPerRequest ?? 1;
|
|
1590
1619
|
const maxRequest = options.maxRequestSize ?? chunkSize;
|
|
1620
|
+
const maxFiles = options.maxFilesPerRequest ?? 1e3;
|
|
1621
|
+
const maxAssemble = options.maxAssembleSize ?? Number.MAX_SAFE_INTEGER;
|
|
1591
1622
|
const { manifest, chunks } = manifestFor(files, chunkSize);
|
|
1592
1623
|
const all = [...chunks.keys()];
|
|
1593
1624
|
const missing = /* @__PURE__ */ new Set();
|
|
1594
1625
|
for (let i = 0; i < all.length; i += 1e3) {
|
|
1595
|
-
const
|
|
1596
|
-
for (const id of
|
|
1626
|
+
const res = await api2.send("POST", `organizations/${seg(org)}/chunks/missing`, { checksums: all.slice(i, i + 1e3) }, { retry: true });
|
|
1627
|
+
for (const id of res.missing ?? []) missing.add(id);
|
|
1597
1628
|
}
|
|
1598
1629
|
let uploadedBytes = 0;
|
|
1599
1630
|
const send = async (ids) => {
|
|
@@ -1619,31 +1650,43 @@ async function uploadArtifacts(ctx, files, opts) {
|
|
|
1619
1650
|
};
|
|
1620
1651
|
await send([...missing]);
|
|
1621
1652
|
const path = `projects/${seg(org)}/${seg(project2)}/artifacts`;
|
|
1622
|
-
const body = { release: opts.release ?? null, dist: opts.dist ?? null, files: manifest };
|
|
1623
1653
|
const slow = { retry: true, timeoutMs: Math.max(UPLOAD_TIMEOUT_MS, ctx.config.timeoutMs) };
|
|
1624
|
-
let res = await api2.send("POST", path, body, slow);
|
|
1625
|
-
if (res?.state === "missing_chunks") {
|
|
1626
|
-
const again = (res.missingChunks ?? []).filter((id) => chunks.has(id));
|
|
1627
|
-
for (const id of again) missing.add(id);
|
|
1628
|
-
await send(again);
|
|
1629
|
-
res = await api2.send("POST", path, body, slow);
|
|
1630
|
-
}
|
|
1631
1654
|
const deadline = Date.now() + opts.waitSeconds * 1e3;
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
await
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1655
|
+
const assemble = async (part) => {
|
|
1656
|
+
const body = { release: opts.release ?? null, dist: opts.dist ?? null, files: part };
|
|
1657
|
+
let res = await api2.send("POST", path, body, slow);
|
|
1658
|
+
if (res?.state === "missing_chunks") {
|
|
1659
|
+
const again = (res.missingChunks ?? []).filter((id) => chunks.has(id));
|
|
1660
|
+
for (const id of again) missing.add(id);
|
|
1661
|
+
await send(again);
|
|
1662
|
+
res = await api2.send("POST", path, body, slow);
|
|
1663
|
+
}
|
|
1664
|
+
let delay = 1e3;
|
|
1665
|
+
while (res?.state === "pending" && res.id && Date.now() < deadline) {
|
|
1666
|
+
await sleep3(delay);
|
|
1667
|
+
delay = Math.min(delay * 2, 5e3);
|
|
1668
|
+
res = await api2.get(`${path}/assemblies/${seg(res.id)}`);
|
|
1669
|
+
}
|
|
1670
|
+
return { res, part };
|
|
1671
|
+
};
|
|
1672
|
+
const parts = packManifest(manifest, maxFiles, maxAssemble);
|
|
1673
|
+
const done = [];
|
|
1674
|
+
for (const [n, part] of parts.entries()) {
|
|
1675
|
+
if (parts.length > 1) ctx.log(`assembling part ${n + 1}/${parts.length}: ${part.length} file(s)`);
|
|
1676
|
+
done.push(await assemble(part));
|
|
1677
|
+
}
|
|
1678
|
+
const states = done.map(({ res }) => res?.state ?? "unknown");
|
|
1679
|
+
const state = states.reduce((worst, s) => (STATE_RANK[s] ?? 0) > (STATE_RANK[worst] ?? 0) ? s : worst, states[0]);
|
|
1638
1680
|
const total = [...chunks.values()].reduce((n, b) => n + b.length, 0);
|
|
1639
1681
|
const result = {
|
|
1640
|
-
id: res?.id,
|
|
1641
|
-
state
|
|
1682
|
+
id: done[0]?.res?.id,
|
|
1683
|
+
state,
|
|
1642
1684
|
release: opts.release ?? null,
|
|
1643
|
-
files: res?.files ??
|
|
1685
|
+
files: done.flatMap(({ res, part }) => res?.files ?? part.map((m) => ({ name: m.name, kind: m.kind }))),
|
|
1644
1686
|
chunks: { total: all.length, uploaded: missing.size, alreadyOnServer: all.length - missing.size },
|
|
1645
1687
|
bytes: { total, uploaded: uploadedBytes }
|
|
1646
1688
|
};
|
|
1689
|
+
if (parts.length > 1) result.parts = done.map(({ res, part }) => ({ id: res?.id, state: res?.state ?? "unknown", files: part.length }));
|
|
1647
1690
|
if (result.state === "error" || result.state === "missing_chunks") {
|
|
1648
1691
|
const failed = result.files.filter((f) => f?.state === "error");
|
|
1649
1692
|
const what = failed.length ? `${failed.length} of ${result.files.length} file(s) were refused: ${failed.slice(0, 5).map((f) => `${f.name} (${f.detail ?? "error"})`).join("; ")}${failed.length > 5 ? "; ..." : ""}` : `Assembly ended in state ${result.state}.`;
|
|
@@ -1736,7 +1779,7 @@ ${d.injected} injected, ${d.alreadyInjected} already, ${d.withoutMap} without a
|
|
|
1736
1779
|
var sourcemapsUpload = {
|
|
1737
1780
|
path: ["sourcemaps", "upload"],
|
|
1738
1781
|
summary: "Upload built JavaScript and source maps so stack traces show original files and lines. Only missing bytes are sent.",
|
|
1739
|
-
description: "Files are matched to events by debug ID (run `sourcemaps inject` first, or pass --inject) or, without debug IDs, by --release plus the file's URL. --url-prefix must match how the browser loads the files (~/ matches any host).",
|
|
1782
|
+
description: "Files are matched to events by debug ID (run `sourcemaps inject` first, or pass --inject) or, without debug IDs, by --release plus the file's URL. --url-prefix must match how the browser loads the files (~/ matches any host). A file's name is its path relative to the directory given, so pass the build directory: a file given on its own is named by its basename alone. Any number of files: past the server's per-assembly limit (maxFilesPerRequest, 1000) the upload is assembled in parts, each source file with its map.",
|
|
1740
1783
|
context: "project",
|
|
1741
1784
|
effect: "write",
|
|
1742
1785
|
idempotent: true,
|
|
@@ -1755,7 +1798,7 @@ var sourcemapsUpload = {
|
|
|
1755
1798
|
{ command: "tracksiy sourcemaps upload dist --inject", description: "Inject and upload a Vite build." },
|
|
1756
1799
|
{ command: "tracksiy sourcemaps upload public/build --release shop@$(git rev-parse --short HEAD) --url-prefix ~/build/", description: "Release-based, for apps that set a release in the SDK." }
|
|
1757
1800
|
],
|
|
1758
|
-
output: "{id, state, release, files[{name, kind, state, detail}], chunks{total, uploaded, alreadyOnServer}, bytes{total, uploaded}, skipped[] (minified files without a map)}",
|
|
1801
|
+
output: "{id, state, release, files[{name, kind, state, detail}], chunks{total, uploaded, alreadyOnServer}, bytes{total, uploaded}, parts[{id, state, files}] (only when assembled in parts), skipped[] (minified files without a map)}",
|
|
1759
1802
|
text: (d) => d.dryRun ? table(d.files, ["kind", ["debug id", (r) => r.debugId ?? "-"], "name", "size"]) : `${d.state}: ${d.files.length} file(s), ${d.chunks.uploaded}/${d.chunks.total} chunks sent (${d.chunks.alreadyOnServer} already on the server)
|
|
1760
1803
|
${table(d.files, ["kind", "state", "name", "detail"])}${d.skipped?.length ? `
|
|
1761
1804
|
|