@daugasauron/dolly-build 0.1.5 → 0.1.6

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 (36) hide show
  1. package/README.md +259 -6
  2. package/host/requirements.mjs +1 -1
  3. package/host/runtime/abi.mjs +41 -0
  4. package/package.json +10 -4
  5. package/packages.json +101 -0
  6. package/{src → page}/image-entry.mjs +10 -6
  7. package/{src → page}/image-inputs.mjs +11 -0
  8. package/{src → page}/image-names.mjs +2 -2
  9. package/page/sha256.mjs +65 -0
  10. package/{src → page}/snapshot-records.mjs +63 -44
  11. package/{src → page}/static-asset.mjs +36 -20
  12. package/{src → page}/version.mjs +1 -1
  13. package/scripts/dolly-build-init/Dollyfile +3 -7
  14. package/scripts/dolly-build-init/public/icon.svg +1 -0
  15. package/scripts/dolly-build-init/public/index.html +6 -58
  16. package/scripts/dolly-build-init/public/main.mjs +52 -0
  17. package/scripts/dolly-build-init/public/page.css +3 -0
  18. package/scripts/dolly-build-init/public/skills/changelog/SKILL.md +5 -0
  19. package/scripts/dolly-build-init/workflow.yml +42 -0
  20. package/scripts/dolly-build.mjs +194 -62
  21. package/scripts/dollyfile-graph.mjs +3 -3
  22. package/scripts/image-directory.mjs +6 -7
  23. package/scripts/page-image-build.mjs +29 -10
  24. package/scripts/recipe-files.mjs +3 -3
  25. package/scripts/release-layout.mjs +1 -1
  26. package/scripts/serve.mjs +40 -10
  27. package/scripts/sha256.mjs +4 -0
  28. package/scripts/share-pages-snapshots.mjs +10 -30
  29. package/scripts/snapshot-identity.mjs +1 -2
  30. package/scripts/system-snapshot-format.mjs +11 -9
  31. package/scripts/wasm-interface.mjs +2 -2
  32. package/{src → site}/dollyfile-graph.mjs +1 -1
  33. package/{src → site}/dollyfile-view.mjs +1 -1
  34. /package/{src → page}/process-abi.mjs +0 -0
  35. /package/{src → page}/wasm-interface.mjs +0 -0
  36. /package/{src → site}/alphabetical.mjs +0 -0
@@ -1,18 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import { createHash, randomUUID } from "node:crypto";
3
+ import { sha256 as digest } from "./sha256.mjs";
3
4
  import { createWriteStream } from "node:fs";
4
5
  import { mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
5
6
  import { resolve } from "node:path";
6
7
  import { pipeline } from "node:stream/promises";
7
8
  import { createGzip } from "node:zlib";
8
- import { decodeSnapshotRecords, validateSnapshotPacks, MAX_SNAPSHOT_BYTES } from "../src/snapshot-records.mjs";
9
+ import { decodeSnapshotRecords, decodeSnapshotHeader, decodeRecordHeader, snapshotHeader, recordHeader,
10
+ validateSnapshotPacks } from "../page/snapshot-records.mjs";
11
+ import { DOLLY_SNAPSHOT_HEADER_BYTES, DOLLY_SNAPSHOT_RECORD_HEADER_BYTES } from "../host/runtime/abi.mjs";
9
12
  import { metadataJson } from "./image-directory.mjs";
10
13
 
11
14
  // A record is either decoded (`data`) or indexed in a snapshot file on disk
12
15
  // (`byteLength`, `source`, `offset`); the catalog is never held in memory.
13
16
  const CHUNK_BYTES = 4 * 1024 * 1024;
14
17
  const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
15
- const digest = bytes => createHash("sha256").update(bytes).digest("hex");
16
18
  const recordLength = record => record.byteLength ?? record.data.length;
17
19
  const recordKey = (kind, path) => createHash("sha256").update(`${kind}\0${path}\0`);
18
20
 
@@ -20,13 +22,13 @@ export function splitSnapshotRecords(records) {
20
22
  const entries = [...records].map(([path, record]) => ({ path, record, encodedPath: Buffer.from(path) }))
21
23
  .sort((left, right) => Buffer.compare(left.encodedPath, right.encodedPath));
22
24
  const parts = [];
23
- let part = new Map(), size = 16;
25
+ let part = new Map(), size = DOLLY_SNAPSHOT_HEADER_BYTES;
24
26
  const finish = () => {
25
27
  if (part.size) parts.push(part);
26
- part = new Map(); size = 16;
28
+ part = new Map(); size = DOLLY_SNAPSHOT_HEADER_BYTES;
27
29
  };
28
30
  for (const { path, record, encodedPath } of entries) {
29
- const length = 16 + encodedPath.length + recordLength(record);
31
+ const length = DOLLY_SNAPSHOT_RECORD_HEADER_BYTES + encodedPath.length + recordLength(record);
30
32
  if (size + length > 4 * 1024 * 1024) finish();
31
33
  part.set(path, record);
32
34
  size += length;
@@ -39,22 +41,6 @@ export function splitSnapshotRecords(records) {
39
41
  return parts;
40
42
  }
41
43
 
42
- function snapshotHeader(count) {
43
- const header = Buffer.alloc(16, "DOLLYSNP", "latin1");
44
- header.writeUInt32LE(2, 8);
45
- header.writeUInt32LE(count, 12);
46
- return header;
47
- }
48
-
49
- function recordHeader(kind, encodedPath, byteLength) {
50
- const header = Buffer.alloc(16 + encodedPath.length);
51
- header.writeUInt32LE(kind, 0);
52
- header.writeUInt32LE(encodedPath.length, 4);
53
- header.writeBigUInt64LE(BigInt(byteLength), 8);
54
- header.set(encodedPath, 16);
55
- return header;
56
- }
57
-
58
44
  async function readExact(handle, buffer, position, length) {
59
45
  let done = 0;
60
46
  while (done < length) {
@@ -148,16 +134,10 @@ async function indexSnapshot(source) {
148
134
  position += length;
149
135
  return bytes;
150
136
  };
151
- const header = await read(16);
152
- const count = header.readUInt32LE(12);
153
- if (header.toString("latin1", 0, 8) !== "DOLLYSNP" || header.readUInt32LE(8) !== 2 || count === 0 || count > 100_000) {
154
- throw new Error(`${source.name}: invalid Dolly snapshot header`);
155
- }
137
+ const count = decodeSnapshotHeader(await read(DOLLY_SNAPSHOT_HEADER_BYTES));
156
138
  for (let index = 0; index < count; index += 1) {
157
- const head = await read(16);
158
- const kind = head.readUInt32LE(0), pathLength = head.readUInt32LE(4), byteLength = Number(head.readBigUInt64LE(8));
159
- if (kind < 1 || kind > 3 || pathLength < 2 || pathLength >= 4096 || byteLength > MAX_SNAPSHOT_BYTES ||
160
- position > metadata.byteLength - pathLength - byteLength) throw new Error(`${source.name}: invalid snapshot record size`);
139
+ const { kind, pathLength, size: byteLength } = decodeRecordHeader(await read(DOLLY_SNAPSHOT_RECORD_HEADER_BYTES));
140
+ if (position > metadata.byteLength - pathLength - byteLength) throw new Error(`${source.name}: invalid snapshot record size`);
161
141
  const path = decoder.decode(await read(pathLength)), hash = recordKey(kind, path), offset = position;
162
142
  for (let done = 0; done < byteLength; done += Math.min(CHUNK_BYTES, byteLength - done)) await read(Math.min(CHUNK_BYTES, byteLength - done), hash);
163
143
  records.push({ key: hash.digest("hex"), path, kind, byteLength, source, offset });
@@ -1,9 +1,8 @@
1
- import { createHash } from "node:crypto";
2
1
  import { recipeRecords } from "./dollyfile-graph.mjs";
3
2
  import { decodeSnapshotEnvironment, validateSnapshotEntry } from "./system-snapshot-format.mjs";
3
+ import { sha256 } from "./sha256.mjs";
4
4
 
5
5
  const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
6
- export const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
7
6
 
8
7
  export function verifySnapshotIdentity(definition, graph, parsed, processContract, processAbiDigest) {
9
8
  const image = definition.image;
@@ -1,8 +1,10 @@
1
1
  import { Buffer } from "node:buffer";
2
- import { parseWasmInterface } from "../src/wasm-interface.mjs";
3
- import { validateProcessInterface } from "../src/process-abi.mjs";
4
- import { decodeImageEntry } from "../src/image-entry.mjs";
5
- import { decodeSnapshotRecords } from "../src/snapshot-records.mjs";
2
+ import { parseWasmInterface } from "../page/wasm-interface.mjs";
3
+ import { validateProcessInterface } from "../page/process-abi.mjs";
4
+ import { decodeImageEntry } from "../page/image-entry.mjs";
5
+ import { decodeSnapshotRecords } from "../page/snapshot-records.mjs";
6
+ import { DOLLY_SNAPSHOT_RECORD_DIRECTORY as DIRECTORY, DOLLY_SNAPSHOT_RECORD_FILE as FILE,
7
+ DOLLY_SNAPSHOT_RECORD_SYMLINK as SYMLINK } from "../host/runtime/abi.mjs";
6
8
 
7
9
  const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
8
10
 
@@ -12,8 +14,8 @@ export function decodeSystemSnapshot(input) {
12
14
  const files = new Map();
13
15
  const directories = new Set(["/"]);
14
16
  for (const [path, { kind, data }] of entries) {
15
- if (kind === 2) files.set(path, data);
16
- if (kind === 1) directories.add(path);
17
+ if (kind === FILE) files.set(path, data);
18
+ if (kind === DIRECTORY) directories.add(path);
17
19
  for (let slash = path.indexOf("/", 1); slash !== -1; slash = path.indexOf("/", slash + 1)) {
18
20
  directories.add(path.slice(0, slash));
19
21
  }
@@ -33,7 +35,7 @@ export function resolveSnapshotFile(snapshot, path) {
33
35
  if (part === "..") { resolved.pop(); continue; }
34
36
  const current = "/" + [...resolved, part].join("/");
35
37
  const record = snapshot.entries.get(current);
36
- if (record?.kind === 3) {
38
+ if (record?.kind === SYMLINK) {
37
39
  if (++links > 40) throw new Error(`snapshot symlink loop: ${path}`);
38
40
  const target = decoder.decode(record.data);
39
41
  if (target.startsWith("/")) resolved.length = 0;
@@ -43,13 +45,13 @@ export function resolveSnapshotFile(snapshot, path) {
43
45
  if (pending.length && !snapshot.directories.has(current)) {
44
46
  throw new Error(`snapshot parent is missing or not a directory: ${current}`);
45
47
  }
46
- if (!pending.length && record?.kind !== 2) {
48
+ if (!pending.length && record?.kind !== FILE) {
47
49
  throw new Error(`snapshot file is not retained: ${current}`);
48
50
  }
49
51
  resolved.push(part);
50
52
  }
51
53
  const record = snapshot.entries.get("/" + resolved.join("/"));
52
- if (record?.kind !== 2) throw new Error(`snapshot file is not retained: ${path}`);
54
+ if (record?.kind !== FILE) throw new Error(`snapshot file is not retained: ${path}`);
53
55
  return record.data;
54
56
  }
55
57
 
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { parseWasmInterface } from "../src/wasm-interface.mjs";
3
- export * from "../src/wasm-interface.mjs";
2
+ import { parseWasmInterface } from "../page/wasm-interface.mjs";
3
+ export * from "../page/wasm-interface.mjs";
4
4
 
5
5
  export async function readWasmInterface(path) {
6
6
  return parseWasmInterface(await readFile(path), path);
@@ -1,5 +1,5 @@
1
1
  import { inspectDollyfile, recipeFileName, imageFileName } from "./dollyfile-view.mjs";
2
- import { sha256 } from "./static-asset.mjs";
2
+ import { sha256 } from "../page/static-asset.mjs";
3
3
 
4
4
  const key = object => `${object.type}:${object.name}`;
5
5
  const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
@@ -1,5 +1,5 @@
1
1
  import { hostRequirement, hostRequirements } from "../host/requirements.mjs";
2
- import { unretainedPath, validName } from "./image-names.mjs";
2
+ import { unretainedPath, validName } from "../page/image-names.mjs";
3
3
 
4
4
  export const MAX_DOLLYFILE_BYTES = 128 * 1024;
5
5
 
File without changes
File without changes
File without changes