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.
Files changed (2) hide show
  1. package/dist/tracksiy.mjs +68 -25
  2. 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.0" : "0.0.0-dev";
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: ids.map(Number), dryRun: false });
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(Number),
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 res2 = await api2.send("POST", `organizations/${seg(org)}/chunks/missing`, { checksums: all.slice(i, i + 1e3) }, { retry: true });
1596
- for (const id of res2.missing ?? []) missing.add(id);
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
- let delay = 1e3;
1633
- while (res?.state === "pending" && res.id && Date.now() < deadline) {
1634
- await sleep3(delay);
1635
- delay = Math.min(delay * 2, 5e3);
1636
- res = await api2.get(`${path}/assemblies/${seg(res.id)}`);
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: res?.state ?? "unknown",
1682
+ id: done[0]?.res?.id,
1683
+ state,
1642
1684
  release: opts.release ?? null,
1643
- files: res?.files ?? manifest.map((m) => ({ name: m.name, kind: m.kind })),
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracksiy",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Agent-first CLI and MCP server for Tracksiy: errors, uptime, alerts, releases and source maps, from one token.",
5
5
  "type": "module",
6
6
  "bin": {