@koda-sl/baker-cli 0.324.0 → 0.325.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/README.md CHANGED
@@ -1429,6 +1429,8 @@ baker experiment fold # make the page
1429
1429
 
1430
1430
  **Finishing with the variant takes two beats, and `fold` is the second.** `finish --keep variant` records which version is live and the edge serves it to everyone at once. `baker experiment fold` moves that version into the page's own folder, archives the one that lost as a self-contained snapshot under `src/_variants/<page id>/<n>/`, and deletes the variant folder. Until it runs, the page is served by a rewrite and cannot be tested again. Run `fold` with no arguments after any test ends, then publish; Baker drops the rewrite by itself once the new pages are live.
1431
1431
 
1432
+ **The archive is a record, not code that ships.** It is never built, never served, nothing in the workspace may edit it, and no quality check can fail on it or rewrite it — those go together: a record you are forbidden to change must not be something a check can turn red, or the only way past the check is to destroy the record. Its references to shared parts of the site are re-aimed as it is written, so a snapshot opened a year later still names the layout and sections that rendered it; links in the markup are left exactly as the page wrote them.
1433
+
1432
1434
  **`plan` before building anything.** At a 3% conversion rate a +20% lift needs roughly 14,000 visitors *per variant*; `plan` says how long that is at this page's traffic, lists what this page's visitors actually went on to do, and what a 14, 28, 56 or 90-day window could settle. The duration is a guide and never a finish line — the test ends when the numbers are clear, and a page with no traffic yet still runs, sized on a stand-in that `warning` names (`--baseline 0.05` states the rate to expect). What `plan` refuses is what cannot be measured: no conversion defined, or a goal the page has demonstrably never produced. `--lift` sets the smallest lift worth detecting (default `0.2`) — halving it roughly quadruples the traffic needed.
1433
1435
 
1434
1436
  ### Google Analytics 4 (`baker ga4`)
@@ -205,4 +205,4 @@ export {
205
205
  apiGet,
206
206
  apiPost
207
207
  };
208
- //# sourceMappingURL=chunk-VEF7YWWM.js.map
208
+ //# sourceMappingURL=chunk-KAE5M5R7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { debugLogHttp, readBodyForLog } from \"./debugLog.ts\";\nimport { getEnv } from \"./env.ts\";\n\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst MAX_TOTAL_WAIT_MS = 2 * 60 * 1000;\n\nasync function fetchWithRateLimitRetry(url: string, init: RequestInit): Promise<Response> {\n let totalWaited = 0;\n\n for (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {\n const response = await fetch(url, init);\n\n if (response.status !== 429 || attempt >= MAX_RATE_LIMIT_RETRIES) {\n return response;\n }\n\n const retryAfterHeader = response.headers.get(\"Retry-After\");\n const waitMs = retryAfterHeader ? Number(retryAfterHeader) * 1000 : 2000 * 2 ** attempt;\n\n if (totalWaited + waitMs > MAX_TOTAL_WAIT_MS) {\n return response;\n }\n\n totalWaited += waitMs;\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n\n return fetch(url, init);\n}\n\nexport type ErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"VALIDATION_ERROR\"\n | \"RATE_LIMITED\"\n | \"INTERNAL_ERROR\"\n | \"NETWORK_ERROR\"\n | \"TIMEOUT\"\n | \"IMAGE_PROCESSING_ERROR\";\n\nexport class ApiError extends Error {\n code: ErrorCode;\n\n constructor(code: ErrorCode, message: string) {\n super(message);\n this.name = \"ApiError\";\n this.code = code;\n }\n}\n\nconst CONVEX_ID_RE = /^[a-zA-Z0-9_]+$/;\n\nfunction hasControlCharacters(value: string): boolean {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n // Allow tab (9), newline (10), carriage return (13)\n if (code < 32 && code !== 9 && code !== 10 && code !== 13) {\n return true;\n }\n }\n return false;\n}\n\nfunction validateStringValue(value: string): void {\n if (hasControlCharacters(value)) {\n throw new ApiError(\"VALIDATION_ERROR\", \"String value contains invalid control characters\");\n }\n}\n\nexport function validateConvexId(id: string): void {\n if (!CONVEX_ID_RE.test(id)) {\n throw new ApiError(\"VALIDATION_ERROR\", `Invalid ID format: \"${id}\". Expected alphanumeric string.`);\n }\n}\n\nfunction sanitizeParams(params: Record<string, string>): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(params)) {\n validateStringValue(value);\n sanitized[key] = value;\n }\n return sanitized;\n}\n\n/**\n * Sent on every call so the backend can attribute immediately-applied writes\n * (library images and videos, followed advertisers) to the Session they were\n * made from. Absent outside a chat-attached environment.\n */\nfunction chatHeader(): Record<string, string> {\n const chatId = getEnv().BAKER_CHAT_ID;\n return chatId ? { \"x-baker-chat-id\": chatId } : {};\n}\n\nfunction mapHttpError(status: number): ErrorCode {\n if (status === 401) {\n return \"UNAUTHORIZED\";\n }\n // 403 is never \"not connected\" — every backend `FORBIDDEN` means connected but\n // not allowed (an asset outside the granted scope, a capability the account\n // lacks). Folding it into UNAUTHORIZED sent agents down the reconnect path,\n // which cannot fix a permission gap and wastes the user's time.\n if (status === 403) {\n return \"FORBIDDEN\";\n }\n if (status === 404) {\n return \"NOT_FOUND\";\n }\n if (status === 422 || status === 400) {\n return \"VALIDATION_ERROR\";\n }\n // 409 is a state the caller has to change, never a fault to sit out. It is\n // what the backend returns for \"this workspace has not switched analytics on\n // yet\" and its siblings — folded into INTERNAL_ERROR, that reaches an agent\n // as a transient failure worth retrying, which it never is.\n if (status === 409) {\n return \"CONFLICT\";\n }\n if (status === 429) {\n return \"RATE_LIMITED\";\n }\n return \"INTERNAL_ERROR\";\n}\n\nasync function handleResponse<T>(response: Response): Promise<T> {\n const body = await response.text();\n\n if (!response.ok) {\n let message = `HTTP ${response.status}: ${response.statusText}`;\n try {\n const parsed = JSON.parse(body) as { error?: string | { message?: string }; message?: string };\n if (typeof parsed.error === \"string\") {\n message = parsed.error;\n } else if (parsed.error?.message) {\n message = parsed.error.message;\n } else if (parsed.message) {\n message = parsed.message;\n }\n } catch {\n // Use default message\n }\n throw new ApiError(mapHttpError(response.status), message);\n }\n\n try {\n return JSON.parse(body) as T;\n } catch {\n throw new ApiError(\"INTERNAL_ERROR\", \"Failed to parse API response as JSON\");\n }\n}\n\n/**\n * A GET that also hands back the response headers.\n *\n * Some endpoints have to say something *about* the answer that the answer has\n * no room for — `GET /api/testimonials` returns a bare array and sets\n * `X-Baker-Next-Cursor` when rows past this page remain unexamined, so the rows\n * alone cannot tell one page of a library from the whole of it.\n */\nexport async function apiGetWithHeaders<T>(\n path: string,\n params?: Record<string, string>,\n): Promise<{ data: T; headers: Headers }> {\n const env = getEnv();\n const url = new URL(path, env.BAKER_API_URL);\n if (params) {\n const clean = sanitizeParams(params);\n for (const [key, value] of Object.entries(clean)) {\n url.searchParams.set(key, value);\n }\n }\n\n const urlStr = url.toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n Accept: \"application/json\",\n ...chatHeader(),\n },\n signal: AbortSignal.timeout(60_000),\n });\n } catch (err) {\n debugLogHttp({ source: \"cli\", method: \"GET\", url: urlStr, durationMs: Date.now() - startedAt, error: err });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", \"Request timed out after 60 seconds\");\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"GET\",\n url: urlStr,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return { data: await handleResponse<T>(response), headers: response.headers };\n}\n\nexport async function apiGet<T>(path: string, params?: Record<string, string>): Promise<T> {\n return (await apiGetWithHeaders<T>(path, params)).data;\n}\n\nexport async function apiPost<T>(path: string, body: unknown, opts?: { timeoutMs?: number }): Promise<T> {\n const env = getEnv();\n const timeoutMs = opts?.timeoutMs ?? 60_000;\n const urlStr = new URL(path, env.BAKER_API_URL).toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...chatHeader(),\n },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (err) {\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n durationMs: Date.now() - startedAt,\n error: err,\n });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", `Request timed out after ${Math.round(timeoutMs / 1000)} seconds`);\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return handleResponse<T>(response);\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ApiError,
3
3
  apiGet
4
- } from "./chunk-VEF7YWWM.js";
4
+ } from "./chunk-KAE5M5R7.js";
5
5
  import {
6
6
  getEnv
7
7
  } from "./chunk-GJVXXYCX.js";
@@ -294,4 +294,4 @@ export {
294
294
  writeAdsOutput,
295
295
  resolveCustomerId
296
296
  };
297
- //# sourceMappingURL=chunk-BIQH2QBE.js.map
297
+ //# sourceMappingURL=chunk-MSZSDYOJ.js.map
@@ -2,10 +2,10 @@ import {
2
2
  handleConnectionError,
3
3
  needsConnectionFix,
4
4
  writeAdsJson
5
- } from "./chunk-BIQH2QBE.js";
5
+ } from "./chunk-MSZSDYOJ.js";
6
6
  import {
7
7
  ApiError
8
- } from "./chunk-VEF7YWWM.js";
8
+ } from "./chunk-KAE5M5R7.js";
9
9
  import {
10
10
  getEnv
11
11
  } from "./chunk-GJVXXYCX.js";
@@ -108,4 +108,4 @@ export {
108
108
  csvOrJson,
109
109
  resolveEffectiveStatus
110
110
  };
111
- //# sourceMappingURL=chunk-OY3N2KHF.js.map
111
+ //# sourceMappingURL=chunk-UWFF6WEV.js.map
package/dist/cli.js CHANGED
@@ -76,7 +76,7 @@ import {
76
76
  resolveAccountIdArg,
77
77
  resolveEffectiveStatus,
78
78
  todayIso
79
- } from "./chunk-OY3N2KHF.js";
79
+ } from "./chunk-UWFF6WEV.js";
80
80
  import {
81
81
  buildQueryCacheKey,
82
82
  cacheGet,
@@ -92,14 +92,14 @@ import {
92
92
  writeAdsJson,
93
93
  writeAdsOutput,
94
94
  writeJsonEnvelope
95
- } from "./chunk-BIQH2QBE.js";
95
+ } from "./chunk-MSZSDYOJ.js";
96
96
  import {
97
97
  ApiError,
98
98
  apiGet,
99
99
  apiGetWithHeaders,
100
100
  apiPost,
101
101
  validateConvexId
102
- } from "./chunk-VEF7YWWM.js";
102
+ } from "./chunk-KAE5M5R7.js";
103
103
  import {
104
104
  installStreamTaps,
105
105
  logInvocation
@@ -13233,6 +13233,13 @@ var CAPTURED_WITHOUT_ROLE = [
13233
13233
  "targetid"
13234
13234
  ].sort();
13235
13235
 
13236
+ // ../api/src/analytics/busy.ts
13237
+ var ANALYTICS_BUSY_MESSAGE = "Analytics is busy right now. The numbers will be there in a moment \u2014 try again shortly.";
13238
+ function isAnalyticsBusy(message) {
13239
+ return typeof message === "string" && message.includes(ANALYTICS_BUSY_MESSAGE);
13240
+ }
13241
+ var ANALYTICS_BUSY_FIX = "Nothing went wrong and nothing was lost \u2014 the analytics store was briefly at capacity. Wait a few seconds and ask for the same report again. If you were in the middle of a larger job, carry on with the rest of it and come back to this read; do not abandon the job.";
13242
+
13236
13243
  // ../api/src/analytics/conversionNames.ts
13237
13244
  function normalizeConversionName(name) {
13238
13245
  return name.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter((word) => word !== "").map((word) => word.length > 3 && word.endsWith("s") ? word.slice(0, -1) : word).join(" ");
@@ -24816,10 +24823,10 @@ Example: baker ads meta ads retag 120252158038400247 --url-tags "utm_source={{si
24816
24823
  }
24817
24824
  },
24818
24825
  run: async ({ args: args4 }) => {
24819
- const { apiPost: apiPost2 } = await import("./client-DGQVUFLK.js");
24826
+ const { apiPost: apiPost2 } = await import("./client-XUSSAB6O.js");
24820
24827
  const { requireChatId: requireChatId2 } = await import("./env-VIA5U56G.js");
24821
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-6ER7S4BM.js");
24822
- const { handleMetaError: handleMetaError2 } = await import("./shared-6Z32L7SG.js");
24828
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-ZPACGSH2.js");
24829
+ const { handleMetaError: handleMetaError2 } = await import("./shared-OHOCOE2L.js");
24823
24830
  try {
24824
24831
  const accountId = bareAccountId2(args4);
24825
24832
  const urlTags = typeof args4["url-tags"] === "string" ? args4["url-tags"] : void 0;
@@ -24964,10 +24971,10 @@ function duplicateCommand2(entity, label) {
24964
24971
  replace: { type: "boolean", description: "Pause the original once the copy publishes" }
24965
24972
  },
24966
24973
  run: async ({ args: args4 }) => {
24967
- const { apiPost: apiPost2 } = await import("./client-DGQVUFLK.js");
24974
+ const { apiPost: apiPost2 } = await import("./client-XUSSAB6O.js");
24968
24975
  const { requireChatId: requireChatId2 } = await import("./env-VIA5U56G.js");
24969
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-6ER7S4BM.js");
24970
- const { handleMetaError: handleMetaError2 } = await import("./shared-6Z32L7SG.js");
24976
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-ZPACGSH2.js");
24977
+ const { handleMetaError: handleMetaError2 } = await import("./shared-OHOCOE2L.js");
24971
24978
  try {
24972
24979
  const accountId = bareAccountId2(args4);
24973
24980
  const chatId = requireChatId2();
@@ -30381,6 +30388,14 @@ function mergeStoredWithCounts(stored, reported) {
30381
30388
  }).sort((a, b) => a.name.localeCompare(b.name));
30382
30389
  }
30383
30390
 
30391
+ // src/commands/analytics/errorEnvelope.ts
30392
+ function analyticsErrorEnvelope(err) {
30393
+ if (err instanceof ApiError) {
30394
+ return isAnalyticsBusy(err.message) ? { code: err.code, message: err.message, fix: ANALYTICS_BUSY_FIX } : { code: err.code, message: err.message };
30395
+ }
30396
+ return { code: "NETWORK_ERROR", message: err instanceof Error ? err.message : "Unexpected error" };
30397
+ }
30398
+
30384
30399
  // src/commands/analytics/feedRows.ts
30385
30400
  function isStreamRow(value) {
30386
30401
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -32069,14 +32084,7 @@ var SHARED_ARGS = {
32069
32084
  }
32070
32085
  };
32071
32086
  function handleError3(err) {
32072
- if (err instanceof ApiError) {
32073
- writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });
32074
- process.exit(1);
32075
- }
32076
- writeJsonEnvelope({
32077
- ok: false,
32078
- error: { code: "NETWORK_ERROR", message: err instanceof Error ? err.message : "Unexpected error" }
32079
- });
32087
+ writeJsonEnvelope({ ok: false, error: analyticsErrorEnvelope(err) });
32080
32088
  process.exit(1);
32081
32089
  }
32082
32090
  function pageOf(args4) {
@@ -44660,6 +44668,7 @@ async function readForms(root, slug) {
44660
44668
  }
44661
44669
 
44662
44670
  // src/commands/experiment/fold.ts
44671
+ import { statSync } from "fs";
44663
44672
  import { cp as cp3, mkdir as mkdir9, readdir as readdir12, readFile as readFile25, rm as rm8, stat as stat6, writeFile as writeFile12 } from "fs/promises";
44664
44673
  import path32 from "path";
44665
44674
 
@@ -44787,20 +44796,42 @@ async function archiveContents(opts) {
44787
44796
  return targets;
44788
44797
  }
44789
44798
  function repointIntoArchive(text2, sourceAbs, destAbs, archivedBySource) {
44790
- return text2.replace(QUOTED_RELATIVE2, (match, quote, spec) => {
44791
- const resolved = resolveWithExtensions(path32.resolve(path32.dirname(sourceAbs), spec), archivedBySource);
44799
+ const fromDir = path32.dirname(sourceAbs);
44800
+ const toDir = path32.dirname(destAbs);
44801
+ return text2.replace(QUOTED_RELATIVE2, (match, quote, spec, offset) => {
44802
+ const [specPath, suffix] = splitSuffix(spec);
44803
+ const target = path32.resolve(fromDir, specPath);
44804
+ const resolved = resolveInArchive(target, archivedBySource) ?? resolveOnDisk(target, isImportPosition(text2, offset));
44792
44805
  if (resolved === null) return match;
44793
- const rel = posix2(path32.relative(path32.dirname(destAbs), resolved));
44794
- return `${quote}${rel.startsWith(".") ? rel : `./${rel}`}${quote}`;
44806
+ const rel = posix2(path32.relative(toDir, resolved));
44807
+ return `${quote}${rel.startsWith(".") ? rel : `./${rel}`}${suffix}${quote}`;
44795
44808
  });
44796
44809
  }
44797
- function resolveWithExtensions(base, archivedBySource) {
44798
- for (const suffix of ["", ".astro", ".ts", ".tsx", ".js", ".jsx", ".mjs", "/index.astro", "/index.ts"]) {
44810
+ function splitSuffix(spec) {
44811
+ const cut = spec.search(/[?#]/);
44812
+ return cut === -1 ? [spec, ""] : [spec.slice(0, cut), spec.slice(cut)];
44813
+ }
44814
+ var EXTENSIONS = ["", ".astro", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
44815
+ function resolveInArchive(base, archivedBySource) {
44816
+ for (const suffix of [...EXTENSIONS, "/index.astro", "/index.ts"]) {
44799
44817
  const hit = archivedBySource.get(base + suffix);
44800
44818
  if (hit !== void 0) return hit;
44801
44819
  }
44802
44820
  return null;
44803
44821
  }
44822
+ function isImportPosition(text2, offset) {
44823
+ return /\b(?:from|import|require)\s*\(?\s*$/.test(text2.slice(Math.max(0, offset - 64), offset));
44824
+ }
44825
+ function resolveOnDisk(base, allowDirectoryIndex) {
44826
+ const candidates = allowDirectoryIndex ? [...EXTENSIONS, "/index.astro", "/index.ts", "/index.js"] : EXTENSIONS;
44827
+ for (const suffix of candidates) {
44828
+ try {
44829
+ if (statSync(base + suffix).isFile()) return base + suffix;
44830
+ } catch {
44831
+ }
44832
+ }
44833
+ return null;
44834
+ }
44804
44835
  async function promoteVariantFiles(opts) {
44805
44836
  const { root, pageDir, variantDir } = opts;
44806
44837
  const written = [];