@daugasauron/dolly-build 0.1.5-rc.0 → 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.
- package/README.md +259 -6
- package/host/requirements.mjs +1 -1
- package/host/runtime/abi.mjs +41 -0
- package/package.json +10 -4
- package/packages.json +101 -0
- package/{src → page}/image-entry.mjs +10 -6
- package/{src → page}/image-inputs.mjs +11 -0
- package/{src → page}/image-names.mjs +2 -2
- package/page/sha256.mjs +65 -0
- package/{src → page}/snapshot-records.mjs +63 -44
- package/{src → page}/static-asset.mjs +36 -20
- package/{src → page}/version.mjs +1 -1
- package/scripts/dolly-build-init/Dollyfile +3 -7
- package/scripts/dolly-build-init/public/icon.svg +1 -0
- package/scripts/dolly-build-init/public/index.html +6 -58
- package/scripts/dolly-build-init/public/main.mjs +52 -0
- package/scripts/dolly-build-init/public/page.css +3 -0
- package/scripts/dolly-build-init/public/skills/changelog/SKILL.md +5 -0
- package/scripts/dolly-build-init/workflow.yml +42 -0
- package/scripts/dolly-build.mjs +194 -62
- package/scripts/dollyfile-graph.mjs +3 -3
- package/scripts/image-directory.mjs +6 -7
- package/scripts/page-image-build.mjs +29 -10
- package/scripts/recipe-files.mjs +3 -3
- package/scripts/release-layout.mjs +1 -1
- package/scripts/serve.mjs +40 -10
- package/scripts/sha256.mjs +4 -0
- package/scripts/share-pages-snapshots.mjs +10 -30
- package/scripts/snapshot-identity.mjs +1 -2
- package/scripts/system-snapshot-format.mjs +11 -9
- package/scripts/wasm-interface.mjs +2 -2
- package/{src → site}/dollyfile-graph.mjs +1 -1
- package/{src → site}/dollyfile-view.mjs +1 -1
- /package/{src → page}/process-abi.mjs +0 -0
- /package/{src → page}/wasm-interface.mjs +0 -0
- /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,
|
|
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 =
|
|
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 =
|
|
28
|
+
part = new Map(); size = DOLLY_SNAPSHOT_HEADER_BYTES;
|
|
27
29
|
};
|
|
28
30
|
for (const { path, record, encodedPath } of entries) {
|
|
29
|
-
const length =
|
|
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
|
|
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
|
|
158
|
-
|
|
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 "../
|
|
3
|
-
import { validateProcessInterface } from "../
|
|
4
|
-
import { decodeImageEntry } from "../
|
|
5
|
-
import { decodeSnapshotRecords } from "../
|
|
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 ===
|
|
16
|
-
if (kind ===
|
|
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 ===
|
|
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 !==
|
|
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 !==
|
|
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 "../
|
|
3
|
-
export * from "../
|
|
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 "
|
|
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 });
|
|
File without changes
|
|
File without changes
|
|
File without changes
|