@tryaura/aura-testkit 0.2.1 → 0.3.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/index.js +49 -20
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1296,6 +1296,7 @@ async function readBody(response, maxBytes, binary) {
1296
1296
  status: response.status
1297
1297
  } : {
1298
1298
  body: "",
1299
+ ...responseEtag(response),
1299
1300
  kind: "response",
1300
1301
  status: response.status
1301
1302
  };
@@ -1321,10 +1322,16 @@ async function readBody(response, maxBytes, binary) {
1321
1322
  status: response.status
1322
1323
  } : {
1323
1324
  body: new TextDecoder().decode(body),
1325
+ ...responseEtag(response),
1324
1326
  kind: "response",
1325
1327
  status: response.status
1326
1328
  };
1327
1329
  }
1330
+ /** The entity tag as an optional field, so absence stays absent rather than `undefined`-valued. */
1331
+ function responseEtag(response) {
1332
+ const etag = response.headers.get("etag");
1333
+ return etag === null ? {} : { etag };
1334
+ }
1328
1335
  function concatenate(chunks, length) {
1329
1336
  const joined = new Uint8Array(length);
1330
1337
  let offset = 0;
@@ -1345,37 +1352,48 @@ function failureReason(error) {
1345
1352
  return name === "TimeoutError" || name === "AbortError" ? "timeout" : "network";
1346
1353
  }
1347
1354
  //#endregion
1348
- //#region ../core/src/managed-block/protocol.ts
1355
+ //#region ../core/src/content-hash.ts
1349
1356
  /**
1350
- * Normalizes snippet text to the bytes covered by the managed-block hash protocol.
1357
+ * Normalizes Markdown to LF and exactly one trailing newline.
1351
1358
  *
1352
- * Trailing newlines are trimmed by scanning rather than with `/\n+$/`, whose backtracking is
1353
- * quadratic when a long newline run does not reach the end of the string.
1359
+ * Line endings and trailing blank lines are what a checkout, an editor, or an append seam change
1360
+ * without changing what the text says, so a hash that counted them would report drift on every
1361
+ * machine that rewrote them — and a fragment appended in this form is the same text the hash
1362
+ * covers. Trailing newlines are trimmed by scanning rather than with `/\n+$/`, whose backtracking
1363
+ * is quadratic when a long newline run does not reach the end of the string.
1354
1364
  */
1355
- function canonicalizeManagedSnippet(content) {
1356
- const normalized = content.replace(/\r\n?/g, "\n");
1365
+ function canonicalizeContent(content) {
1366
+ const normalized = content.replace(/\r\n?/gu, "\n");
1357
1367
  let end = normalized.length;
1358
1368
  while (end > 0 && normalized[end - 1] === "\n") end -= 1;
1359
1369
  return `${normalized.slice(0, end)}\n`;
1360
1370
  }
1361
- /** Computes the protocol hash for text already in {@link canonicalizeManagedSnippet} form. */
1362
- function hashCanonicalManagedSnippet(canonical) {
1363
- return createHash("sha256").update(canonical, "utf8").digest("hex");
1364
- }
1365
- /** Computes the protocol hash for a snippet's canonical UTF-8 contents. */
1366
- function hashManagedSnippet(content) {
1367
- return hashCanonicalManagedSnippet(canonicalizeManagedSnippet(content));
1371
+ /**
1372
+ * Fingerprints Markdown Aura records but does not own: an installed snippet, a trusted preset.
1373
+ *
1374
+ * One function for both because the two records answer the same question — "is this still the text
1375
+ * the user accepted?" and two spellings of the canonical form would answer it differently on the
1376
+ * first file whose line endings a checkout rewrote.
1377
+ */
1378
+ function hashContent(content) {
1379
+ return createHash("sha256").update(canonicalizeContent(content), "utf8").digest("hex");
1368
1380
  }
1369
1381
  //#endregion
1370
- //#region ../core/src/preset/repo-trust.ts
1382
+ //#region ../core/src/preset/repo-content.ts
1371
1383
  /**
1372
- * Hashes repository preset contents for the trust record.
1384
+ * Hashes the repository content set for the trust record.
1373
1385
  *
1374
- * Uses the managed-snippet canonicalization so a checkout that rewrites line endings does not
1375
- * invalidate a trust the user already granted to the same bytes-as-authored.
1386
+ * With no snippet files the hash is exactly the preset file's own content hash, so a repository
1387
+ * that never adds `.aura/snippets` keeps every trust its users already recorded. The first
1388
+ * snippet changes the descriptor shape — and consent is re-asked, which is the point.
1376
1389
  */
1377
- function hashRepoPreset(content) {
1378
- return hashManagedSnippet(content);
1390
+ function hashRepoContentSet(presetText, snippets) {
1391
+ if (snippets.length === 0) return hashContent(presetText);
1392
+ return hashContent([
1393
+ "aura-repo-content-v1",
1394
+ `preset ${hashContent(presetText)}`,
1395
+ ...[...snippets].sort((left, right) => left.id < right.id ? -1 : 1).map((file) => `snippet ${file.id} ${hashContent(file.text)}`)
1396
+ ].join("\n"));
1379
1397
  }
1380
1398
  //#endregion
1381
1399
  //#region src/http.ts
@@ -1828,7 +1846,7 @@ async function recordTrustedPresets(homeDir, workspaceDir, presets) {
1828
1846
  throw new Error(`trustWorkspacePreset needs the seeded workspace file: ${relativePath}`);
1829
1847
  });
1830
1848
  added.push({
1831
- hash: hashRepoPreset(content),
1849
+ hash: hashRepoContentSet(content, await seededSnippets(dirname(path))),
1832
1850
  path
1833
1851
  });
1834
1852
  }
@@ -1844,6 +1862,17 @@ async function recordTrustedPresets(homeDir, workspaceDir, presets) {
1844
1862
  mode: 384
1845
1863
  });
1846
1864
  }
1865
+ /** The snippet files beside a seeded preset, in the shape the composite hash consumes. */
1866
+ async function seededSnippets(auraDir) {
1867
+ const root = join(auraDir, "snippets");
1868
+ const names = await readdir(root).catch(() => []);
1869
+ const files = [];
1870
+ for (const name of names.filter((entry) => !entry.startsWith(".") && entry.endsWith(".md"))) files.push({
1871
+ id: `repo/${name.slice(0, -3)}`,
1872
+ text: await readFile(join(root, name), "utf8")
1873
+ });
1874
+ return files;
1875
+ }
1847
1876
  function parseSeedManifest(source, path) {
1848
1877
  const value = JSON.parse(source);
1849
1878
  if (!isRecord(value)) throw new Error(`Seed manifest ${path} must contain a JSON object`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryaura/aura-testkit",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Deterministic filesystem seeds and CLI harnesses for Aura plugins and distributions.",
5
5
  "keywords": [
6
6
  "aura",
@@ -30,8 +30,8 @@
30
30
  "access": "public"
31
31
  },
32
32
  "dependencies": {
33
- "@tryaura/aura-cli": "0.2.1",
34
- "@tryaura/aura-sdk": "0.2.1"
33
+ "@tryaura/aura-cli": "0.3.1",
34
+ "@tryaura/aura-sdk": "0.3.1"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "24.13.3",