@johnhenry/sitefile 0.0.0-stage → 0.0.0
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/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/README.md +475 -2
- package/lib/src/archive-writers.d.ts +31 -0
- package/lib/src/archive-writers.js +197 -0
- package/lib/src/attest.d.ts +126 -0
- package/lib/src/attest.js +220 -0
- package/lib/src/bake.d.ts +74 -0
- package/lib/src/bake.js +372 -0
- package/lib/src/base-image.d.ts +76 -0
- package/lib/src/base-image.js +138 -0
- package/lib/src/build-args.d.ts +26 -0
- package/lib/src/build-args.js +164 -0
- package/lib/src/cache-limit.d.ts +54 -0
- package/lib/src/cache-limit.js +239 -0
- package/lib/src/casefold-data.d.ts +2 -0
- package/lib/src/casefold-data.js +217 -0
- package/lib/src/cbor.d.ts +39 -0
- package/lib/src/cbor.js +174 -0
- package/lib/src/checklinks.d.ts +42 -0
- package/lib/src/checklinks.js +118 -0
- package/lib/src/cli/bake.d.ts +2 -0
- package/lib/src/cli/bake.js +179 -0
- package/lib/src/cli/build-image.d.ts +21 -0
- package/lib/src/cli/build-image.js +42 -0
- package/lib/src/cli/cache.d.ts +8 -0
- package/lib/src/cli/cache.js +109 -0
- package/lib/src/cli/common.d.ts +29 -0
- package/lib/src/cli/common.js +100 -0
- package/lib/src/cli/git-context.d.ts +18 -0
- package/lib/src/cli/git-context.js +48 -0
- package/lib/src/cli/images.d.ts +6 -0
- package/lib/src/cli/images.js +285 -0
- package/lib/src/cli/index.d.ts +10 -0
- package/lib/src/cli/index.js +95 -0
- package/lib/src/cli/outdated.d.ts +5 -0
- package/lib/src/cli/outdated.js +94 -0
- package/lib/src/cli/registry.d.ts +2 -0
- package/lib/src/cli/registry.js +122 -0
- package/lib/src/cli/serve-worker.d.ts +2 -0
- package/lib/src/cli/serve-worker.js +42 -0
- package/lib/src/cli/servers.d.ts +15 -0
- package/lib/src/cli/servers.js +451 -0
- package/lib/src/cli/vendor.d.ts +2 -0
- package/lib/src/cli/vendor.js +92 -0
- package/lib/src/cli/watch.d.ts +26 -0
- package/lib/src/cli/watch.js +81 -0
- package/lib/src/cli.d.ts +12 -0
- package/lib/src/cli.js +486 -0
- package/lib/src/csv.d.ts +27 -0
- package/lib/src/csv.js +117 -0
- package/lib/src/deflate.d.ts +76 -0
- package/lib/src/deflate.js +604 -0
- package/lib/src/digest.d.ts +3 -0
- package/lib/src/digest.js +7 -0
- package/lib/src/errors.d.ts +34 -0
- package/lib/src/errors.js +20 -0
- package/lib/src/executor.d.ts +386 -0
- package/lib/src/executor.js +2193 -0
- package/lib/src/explain.d.ts +64 -0
- package/lib/src/explain.js +445 -0
- package/lib/src/export-formats.d.ts +16 -0
- package/lib/src/export-formats.js +52 -0
- package/lib/src/exporter.d.ts +103 -0
- package/lib/src/exporter.js +676 -0
- package/lib/src/fingerprint.d.ts +40 -0
- package/lib/src/fingerprint.js +168 -0
- package/lib/src/frontmatter.d.ts +22 -0
- package/lib/src/frontmatter.js +45 -0
- package/lib/src/index.d.ts +12 -0
- package/lib/src/index.js +12 -0
- package/lib/src/language/ast.d.ts +353 -0
- package/lib/src/language/ast.js +57 -0
- package/lib/src/language/parser.d.ts +3 -0
- package/lib/src/language/parser.js +612 -0
- package/lib/src/language/scanner.d.ts +110 -0
- package/lib/src/language/scanner.js +449 -0
- package/lib/src/language/validate.d.ts +13 -0
- package/lib/src/language/validate.js +367 -0
- package/lib/src/links.d.ts +79 -0
- package/lib/src/links.js +583 -0
- package/lib/src/lint.d.ts +10 -0
- package/lib/src/lint.js +223 -0
- package/lib/src/markdown.d.ts +29 -0
- package/lib/src/markdown.js +612 -0
- package/lib/src/media.d.ts +82 -0
- package/lib/src/media.js +650 -0
- package/lib/src/memo.d.ts +57 -0
- package/lib/src/memo.js +93 -0
- package/lib/src/oci.d.ts +178 -0
- package/lib/src/oci.js +380 -0
- package/lib/src/outdated.d.ts +46 -0
- package/lib/src/outdated.js +195 -0
- package/lib/src/pages.d.ts +10 -0
- package/lib/src/pages.js +54 -0
- package/lib/src/paths.d.ts +39 -0
- package/lib/src/paths.js +101 -0
- package/lib/src/planner.d.ts +297 -0
- package/lib/src/planner.js +410 -0
- package/lib/src/plugins/cache.d.ts +70 -0
- package/lib/src/plugins/cache.js +226 -0
- package/lib/src/plugins/exec.d.ts +51 -0
- package/lib/src/plugins/exec.js +144 -0
- package/lib/src/plugins/host.d.ts +75 -0
- package/lib/src/plugins/host.js +277 -0
- package/lib/src/plugins/wasm.d.ts +23 -0
- package/lib/src/plugins/wasm.js +249 -0
- package/lib/src/profile.d.ts +42 -0
- package/lib/src/profile.js +68 -0
- package/lib/src/ref.d.ts +40 -0
- package/lib/src/ref.js +79 -0
- package/lib/src/registry-auth.d.ts +32 -0
- package/lib/src/registry-auth.js +151 -0
- package/lib/src/registry-cache.d.ts +71 -0
- package/lib/src/registry-cache.js +333 -0
- package/lib/src/registry-ops.d.ts +51 -0
- package/lib/src/registry-ops.js +248 -0
- package/lib/src/registry.d.ts +109 -0
- package/lib/src/registry.js +505 -0
- package/lib/src/render.d.ts +134 -0
- package/lib/src/render.js +576 -0
- package/lib/src/resources/archive.d.ts +11 -0
- package/lib/src/resources/archive.js +306 -0
- package/lib/src/resources/bundle-data.d.ts +16 -0
- package/lib/src/resources/bundle-data.js +50 -0
- package/lib/src/resources/convert.d.ts +12 -0
- package/lib/src/resources/convert.js +63 -0
- package/lib/src/resources/documents.d.ts +35 -0
- package/lib/src/resources/documents.js +122 -0
- package/lib/src/resources/git.d.ts +103 -0
- package/lib/src/resources/git.js +720 -0
- package/lib/src/resources/https.d.ts +40 -0
- package/lib/src/resources/https.js +256 -0
- package/lib/src/resources/ignore.d.ts +33 -0
- package/lib/src/resources/ignore.js +119 -0
- package/lib/src/resources/local.d.ts +64 -0
- package/lib/src/resources/local.js +248 -0
- package/lib/src/resources/lockfile.d.ts +52 -0
- package/lib/src/resources/lockfile.js +172 -0
- package/lib/src/resources/netpolicy.d.ts +6 -0
- package/lib/src/resources/netpolicy.js +82 -0
- package/lib/src/resources/types.d.ts +63 -0
- package/lib/src/resources/types.js +13 -0
- package/lib/src/resources/vendor.d.ts +94 -0
- package/lib/src/resources/vendor.js +296 -0
- package/lib/src/resources/wbn-reader.d.ts +44 -0
- package/lib/src/resources/wbn-reader.js +302 -0
- package/lib/src/schema.d.ts +25 -0
- package/lib/src/schema.js +169 -0
- package/lib/src/secrets.d.ts +21 -0
- package/lib/src/secrets.js +103 -0
- package/lib/src/serve-rules.d.ts +29 -0
- package/lib/src/serve-rules.js +122 -0
- package/lib/src/server.d.ts +45 -0
- package/lib/src/server.js +246 -0
- package/lib/src/servers.d.ts +71 -0
- package/lib/src/servers.js +139 -0
- package/lib/src/site.d.ts +60 -0
- package/lib/src/site.js +169 -0
- package/lib/src/store.d.ts +151 -0
- package/lib/src/store.js +596 -0
- package/lib/src/tree.d.ts +50 -0
- package/lib/src/tree.js +134 -0
- package/lib/src/values.d.ts +7 -0
- package/lib/src/values.js +37 -0
- package/lib/src/vendor.d.ts +19 -0
- package/lib/src/vendor.js +145 -0
- package/lib/src/watch.d.ts +44 -0
- package/lib/src/watch.js +137 -0
- package/lib/src/wbn.d.ts +5 -0
- package/lib/src/wbn.js +147 -0
- package/lib/src/yaml.d.ts +34 -0
- package/lib/src/yaml.js +562 -0
- package/package.json +58 -4
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare function cborHead(major: number, n: number | bigint): Uint8Array;
|
|
2
|
+
export declare const cborUint: (n: number | bigint) => Uint8Array;
|
|
3
|
+
export declare const cborBytes: (b: Uint8Array) => Uint8Array;
|
|
4
|
+
export declare const cborText: (s: string) => Uint8Array;
|
|
5
|
+
/** Array of already-encoded items. */
|
|
6
|
+
export declare const cborArray: (items: readonly Uint8Array[]) => Uint8Array;
|
|
7
|
+
/** Map of already-encoded [key, value] pairs, in deterministic key order. */
|
|
8
|
+
export declare function cborMap(entries: ReadonlyArray<readonly [Uint8Array, Uint8Array]>): Uint8Array;
|
|
9
|
+
/**
|
|
10
|
+
* Decoded CBOR. Unsigned integers above 2^53-1 and negative integers below
|
|
11
|
+
* -(2^53-1) come back as bigint. Byte strings are views into the input (not
|
|
12
|
+
* copies). Maps are `Map`s (never plain objects, so keys like `__proto__` are
|
|
13
|
+
* inert); a key that appears twice is an error.
|
|
14
|
+
*/
|
|
15
|
+
export type CborValue = number | bigint | string | boolean | null | Uint8Array | CborValue[] | Map<CborValue, CborValue>;
|
|
16
|
+
export interface CborLimits {
|
|
17
|
+
/** Maximum nesting of arrays/maps (default 32). */
|
|
18
|
+
maxDepth?: number;
|
|
19
|
+
/** Maximum number of data items decoded in total (default 1 000 000). */
|
|
20
|
+
maxItems?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare class CborError extends Error {
|
|
23
|
+
readonly offset: number;
|
|
24
|
+
constructor(message: string, offset: number);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read one item head (major type + argument) at `pos`. Definite lengths only:
|
|
28
|
+
* additional info 28-31 (reserved and indefinite) is an error.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readCborHead(bytes: Uint8Array, pos: number): {
|
|
31
|
+
major: number;
|
|
32
|
+
arg: number | bigint;
|
|
33
|
+
next: number;
|
|
34
|
+
};
|
|
35
|
+
/** Decode the single CBOR item at the start of `bytes`; `end` is the offset just past it. */
|
|
36
|
+
export declare function decodeCbor(bytes: Uint8Array, limits?: CborLimits): {
|
|
37
|
+
value: CborValue;
|
|
38
|
+
end: number;
|
|
39
|
+
};
|
package/lib/src/cbor.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal deterministic CBOR (RFC 8949) encoder: just unsigned integers,
|
|
3
|
+
* byte strings, text strings, arrays and maps. Every value is encoded to bytes
|
|
4
|
+
* eagerly; maps are emitted in the RFC 8949 section 4.2.1 core deterministic
|
|
5
|
+
* order (keys sorted bytewise by their encoded form, which puts shorter first).
|
|
6
|
+
*/
|
|
7
|
+
const enc = new TextEncoder();
|
|
8
|
+
export function cborHead(major, n) {
|
|
9
|
+
const m = major << 5;
|
|
10
|
+
if (n < 24)
|
|
11
|
+
return Uint8Array.of(m | Number(n));
|
|
12
|
+
if (n < 0x100)
|
|
13
|
+
return Uint8Array.of(m | 24, Number(n));
|
|
14
|
+
if (n < 0x10000)
|
|
15
|
+
return Uint8Array.of(m | 25, Number(n) >> 8, Number(n) & 0xff);
|
|
16
|
+
if (n < 0x100000000) {
|
|
17
|
+
const b = Buffer.alloc(5);
|
|
18
|
+
b[0] = m | 26;
|
|
19
|
+
b.writeUInt32BE(Number(n), 1);
|
|
20
|
+
return b;
|
|
21
|
+
}
|
|
22
|
+
const b = Buffer.alloc(9);
|
|
23
|
+
b[0] = m | 27;
|
|
24
|
+
b.writeBigUInt64BE(BigInt(n), 1);
|
|
25
|
+
return b;
|
|
26
|
+
}
|
|
27
|
+
const concat = (parts) => Buffer.concat(parts);
|
|
28
|
+
export const cborUint = (n) => cborHead(0, n);
|
|
29
|
+
export const cborBytes = (b) => concat([cborHead(2, b.length), b]);
|
|
30
|
+
export const cborText = (s) => {
|
|
31
|
+
const b = enc.encode(s);
|
|
32
|
+
return concat([cborHead(3, b.length), b]);
|
|
33
|
+
};
|
|
34
|
+
/** Array of already-encoded items. */
|
|
35
|
+
export const cborArray = (items) => concat([cborHead(4, items.length), ...items]);
|
|
36
|
+
/** Map of already-encoded [key, value] pairs, in deterministic key order. */
|
|
37
|
+
export function cborMap(entries) {
|
|
38
|
+
const sorted = [...entries].sort(([a], [b]) => Buffer.compare(a, b));
|
|
39
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
40
|
+
if (Buffer.compare(sorted[i - 1][0], sorted[i][0]) === 0)
|
|
41
|
+
throw new Error('duplicate CBOR map key');
|
|
42
|
+
}
|
|
43
|
+
return concat([cborHead(5, sorted.length), ...sorted.flatMap(([k, v]) => [k, v])]);
|
|
44
|
+
}
|
|
45
|
+
export class CborError extends Error {
|
|
46
|
+
offset;
|
|
47
|
+
constructor(message, offset) {
|
|
48
|
+
super(`${message} (at byte ${offset})`);
|
|
49
|
+
this.offset = offset;
|
|
50
|
+
this.name = 'CborError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Read one item head (major type + argument) at `pos`. Definite lengths only:
|
|
55
|
+
* additional info 28-31 (reserved and indefinite) is an error.
|
|
56
|
+
*/
|
|
57
|
+
export function readCborHead(bytes, pos) {
|
|
58
|
+
if (pos >= bytes.length)
|
|
59
|
+
throw new CborError('unexpected end of input', pos);
|
|
60
|
+
const ib = bytes[pos];
|
|
61
|
+
const major = ib >> 5;
|
|
62
|
+
const ai = ib & 31;
|
|
63
|
+
const need = (n) => {
|
|
64
|
+
if (pos + 1 + n > bytes.length)
|
|
65
|
+
throw new CborError('unexpected end of input', pos);
|
|
66
|
+
};
|
|
67
|
+
if (ai < 24)
|
|
68
|
+
return { major, arg: ai, next: pos + 1 };
|
|
69
|
+
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
70
|
+
switch (ai) {
|
|
71
|
+
case 24:
|
|
72
|
+
need(1);
|
|
73
|
+
return { major, arg: bytes[pos + 1], next: pos + 2 };
|
|
74
|
+
case 25:
|
|
75
|
+
need(2);
|
|
76
|
+
return { major, arg: v.getUint16(pos + 1), next: pos + 3 };
|
|
77
|
+
case 26:
|
|
78
|
+
need(4);
|
|
79
|
+
return { major, arg: v.getUint32(pos + 1), next: pos + 5 };
|
|
80
|
+
case 27: {
|
|
81
|
+
need(8);
|
|
82
|
+
const n = v.getBigUint64(pos + 1);
|
|
83
|
+
return { major, arg: n <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(n) : n, next: pos + 9 };
|
|
84
|
+
}
|
|
85
|
+
default:
|
|
86
|
+
throw new CborError(ai === 31 ? 'indefinite-length items are not supported' : 'reserved additional information', pos);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
90
|
+
/** Decode the single CBOR item at the start of `bytes`; `end` is the offset just past it. */
|
|
91
|
+
export function decodeCbor(bytes, limits = {}) {
|
|
92
|
+
const maxDepth = limits.maxDepth ?? 32;
|
|
93
|
+
const maxItems = limits.maxItems ?? 1_000_000;
|
|
94
|
+
let items = 0;
|
|
95
|
+
const item = (pos, depth) => {
|
|
96
|
+
if (++items > maxItems)
|
|
97
|
+
throw new CborError(`more than ${maxItems} items`, pos);
|
|
98
|
+
const { major, arg, next } = readCborHead(bytes, pos);
|
|
99
|
+
const remaining = bytes.length - next;
|
|
100
|
+
// A declared length can never exceed what is left of the input (every
|
|
101
|
+
// element or byte takes at least one byte), so nothing is sized from it.
|
|
102
|
+
const length = (min) => {
|
|
103
|
+
if (typeof arg === 'bigint' || arg * min > remaining)
|
|
104
|
+
throw new CborError('declared length exceeds remaining input', pos);
|
|
105
|
+
return arg;
|
|
106
|
+
};
|
|
107
|
+
switch (major) {
|
|
108
|
+
case 0:
|
|
109
|
+
return { value: arg, end: next };
|
|
110
|
+
case 1:
|
|
111
|
+
return { value: typeof arg === 'bigint' ? -1n - arg : -1 - arg, end: next };
|
|
112
|
+
case 2: {
|
|
113
|
+
const n = length(1);
|
|
114
|
+
return { value: bytes.subarray(next, next + n), end: next + n };
|
|
115
|
+
}
|
|
116
|
+
case 3: {
|
|
117
|
+
const n = length(1);
|
|
118
|
+
try {
|
|
119
|
+
return { value: utf8.decode(bytes.subarray(next, next + n)), end: next + n };
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
throw new CborError('invalid UTF-8 in text string', next);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
case 4: {
|
|
126
|
+
const n = length(1);
|
|
127
|
+
if (depth >= maxDepth)
|
|
128
|
+
throw new CborError(`nesting deeper than ${maxDepth}`, pos);
|
|
129
|
+
const out = [];
|
|
130
|
+
let p = next;
|
|
131
|
+
for (let i = 0; i < n; i++) {
|
|
132
|
+
const r = item(p, depth + 1);
|
|
133
|
+
out.push(r.value);
|
|
134
|
+
p = r.end;
|
|
135
|
+
}
|
|
136
|
+
return { value: out, end: p };
|
|
137
|
+
}
|
|
138
|
+
case 5: {
|
|
139
|
+
const n = length(2);
|
|
140
|
+
if (depth >= maxDepth)
|
|
141
|
+
throw new CborError(`nesting deeper than ${maxDepth}`, pos);
|
|
142
|
+
const out = new Map();
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
let p = next;
|
|
145
|
+
for (let i = 0; i < n; i++) {
|
|
146
|
+
const k = item(p, depth + 1);
|
|
147
|
+
const id = Buffer.from(bytes.subarray(p, k.end)).toString('latin1');
|
|
148
|
+
if (seen.has(id))
|
|
149
|
+
throw new CborError('duplicate map key', p);
|
|
150
|
+
seen.add(id);
|
|
151
|
+
const v = item(k.end, depth + 1);
|
|
152
|
+
out.set(k.value, v.value);
|
|
153
|
+
p = v.end;
|
|
154
|
+
}
|
|
155
|
+
return { value: out, end: p };
|
|
156
|
+
}
|
|
157
|
+
case 6:
|
|
158
|
+
throw new CborError('tags are not supported', pos);
|
|
159
|
+
default: {
|
|
160
|
+
// major 7: only the simple values false, true and null.
|
|
161
|
+
const ib = bytes[pos];
|
|
162
|
+
if (ib === 0xf4)
|
|
163
|
+
return { value: false, end: next };
|
|
164
|
+
if (ib === 0xf5)
|
|
165
|
+
return { value: true, end: next };
|
|
166
|
+
if (ib === 0xf6)
|
|
167
|
+
return { value: null, end: next };
|
|
168
|
+
throw new CborError('unsupported simple value or float', pos);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
return item(0, 0);
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=cbor.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type SourceSpan } from './errors.js';
|
|
2
|
+
import type { FetchPolicy } from './resources/types.js';
|
|
3
|
+
import type { Tree } from './tree.js';
|
|
4
|
+
export interface CheckLinksOp {
|
|
5
|
+
/** Report broken links as warnings instead of failing. */
|
|
6
|
+
readonly warn: boolean;
|
|
7
|
+
/** Also request http(s) URLs (under the network policy). */
|
|
8
|
+
readonly external: boolean;
|
|
9
|
+
/** The EXPORT path: absolute references resolve below it. */
|
|
10
|
+
readonly root: string;
|
|
11
|
+
/** REDIRECT sources (`/old`, `/blog/*`): a link to one of them is not broken. */
|
|
12
|
+
readonly redirects: readonly string[];
|
|
13
|
+
readonly span: SourceSpan;
|
|
14
|
+
}
|
|
15
|
+
export interface BrokenLink {
|
|
16
|
+
/** The referring file (a tree path) and the line of the reference. */
|
|
17
|
+
readonly file: string;
|
|
18
|
+
readonly line: number;
|
|
19
|
+
/** The URL as the file has it (character references and escapes decoded). */
|
|
20
|
+
readonly url: string;
|
|
21
|
+
readonly reason: string;
|
|
22
|
+
}
|
|
23
|
+
/** The most broken links an error lists (CHECKLINKS --warn prints every one). */
|
|
24
|
+
export declare const MAX_LISTED = 20;
|
|
25
|
+
/**
|
|
26
|
+
* Whether a tree path is served the way `sitefile serve` and static hosts serve it: the file itself, `<path>.html`
|
|
27
|
+
* (clean URLs) or `<path>/index.html`; for a directory URL (`…/`), its `index.html`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isServed(tree: Tree, path: string, dir: boolean): boolean;
|
|
30
|
+
/** Whether a REDIRECT source covers a URL path: equal, or a `/*` prefix of it. */
|
|
31
|
+
export declare function redirected(sources: readonly string[], url: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Every broken link in the HTML and CSS files of `tree`, by file (in path order) and then in the order of the
|
|
34
|
+
* file. An internal link is broken when its target is not served (see {@link isServed}) and no REDIRECT covers
|
|
35
|
+
* it; with `external`, an http(s) link is broken when requesting it fails or answers 400 or more (each URL is
|
|
36
|
+
* requested once, in order of first use).
|
|
37
|
+
*/
|
|
38
|
+
export declare function brokenLinks(tree: Tree, op: CheckLinksOp, policy: FetchPolicy): Promise<BrokenLink[]>;
|
|
39
|
+
/** One broken link as messages show it: `/index.html:3: css/a.css (not found: /css/a.css)`. */
|
|
40
|
+
export declare function brokenLinkText(b: BrokenLink): string;
|
|
41
|
+
/** Report the broken links: a warning each with `--warn`, otherwise one E_BROKEN_LINK error listing them. */
|
|
42
|
+
export declare function reportBrokenLinks(broken: readonly BrokenLink[], op: CheckLinksOp, origin: string, warn: (m: string) => void): void;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// CHECKLINKS (docs/LANGUAGE.md, "Link checking"): find the references in a stage's HTML and CSS files that
|
|
2
|
+
// point at nothing. Ported as rust/src/checklinks.rs: both report the same links, in the same order, with the
|
|
3
|
+
// same messages.
|
|
4
|
+
import { isSitefileError, SitefileError } from './errors.js';
|
|
5
|
+
import { resolveReference, scanFile } from './links.js';
|
|
6
|
+
import { fetchRemote } from './resources/https.js';
|
|
7
|
+
/** The most broken links an error lists (CHECKLINKS --warn prints every one). */
|
|
8
|
+
export const MAX_LISTED = 20;
|
|
9
|
+
/**
|
|
10
|
+
* Whether a tree path is served the way `sitefile serve` and static hosts serve it: the file itself, `<path>.html`
|
|
11
|
+
* (clean URLs) or `<path>/index.html`; for a directory URL (`…/`), its `index.html`.
|
|
12
|
+
*/
|
|
13
|
+
export function isServed(tree, path, dir) {
|
|
14
|
+
const index = path === '/' ? '/index.html' : `${path}/index.html`;
|
|
15
|
+
if (dir)
|
|
16
|
+
return tree.isFile(index);
|
|
17
|
+
return tree.isFile(path) || (path !== '/' && tree.isFile(`${path}.html`)) || (tree.isDirectory(path) && tree.isFile(index));
|
|
18
|
+
}
|
|
19
|
+
/** The URL path of a tree path below the root, or undefined outside it. */
|
|
20
|
+
function urlPath(root, path) {
|
|
21
|
+
if (root === '/')
|
|
22
|
+
return path;
|
|
23
|
+
if (path === root)
|
|
24
|
+
return '/';
|
|
25
|
+
return path.startsWith(root + '/') ? path.slice(root.length) : undefined;
|
|
26
|
+
}
|
|
27
|
+
/** Whether a REDIRECT source covers a URL path: equal, or a `/*` prefix of it. */
|
|
28
|
+
export function redirected(sources, url) {
|
|
29
|
+
return sources.some((from) => from === url || (from.endsWith('/*') && url.startsWith(from.slice(0, -1))));
|
|
30
|
+
}
|
|
31
|
+
/** Why an external URL is broken (undefined when it answered), from the error fetching it. */
|
|
32
|
+
function externalReason(e, url, policy) {
|
|
33
|
+
switch (e.code) {
|
|
34
|
+
case 'E_TOO_LARGE':
|
|
35
|
+
// The limit is 0 bytes: it answered 200 with a body.
|
|
36
|
+
return undefined;
|
|
37
|
+
case 'E_FETCH_STATUS': {
|
|
38
|
+
const status = Number(/HTTP (\d+)$/.exec(e.detail)?.[1] ?? '0');
|
|
39
|
+
return status >= 400 || status === 0 ? `HTTP ${status}` : undefined;
|
|
40
|
+
}
|
|
41
|
+
case 'E_FETCH_TIMEOUT':
|
|
42
|
+
return `no answer within ${policy.timeoutMs} ms`;
|
|
43
|
+
case 'E_PROTOCOL':
|
|
44
|
+
return /^http:/i.test(url) && !policy.allowHttp ? 'plain http is checked only with --allow-http' : 'not allowed by the network policy';
|
|
45
|
+
case 'E_SSRF_BLOCKED':
|
|
46
|
+
return 'a non-public address (checked only with --allow-private-network)';
|
|
47
|
+
case 'E_REDIRECT':
|
|
48
|
+
return 'too many or invalid redirects';
|
|
49
|
+
default:
|
|
50
|
+
return 'could not connect';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Every broken link in the HTML and CSS files of `tree`, by file (in path order) and then in the order of the
|
|
55
|
+
* file. An internal link is broken when its target is not served (see {@link isServed}) and no REDIRECT covers
|
|
56
|
+
* it; with `external`, an http(s) link is broken when requesting it fails or answers 400 or more (each URL is
|
|
57
|
+
* requested once, in order of first use).
|
|
58
|
+
*/
|
|
59
|
+
export async function brokenLinks(tree, op, policy) {
|
|
60
|
+
const out = [];
|
|
61
|
+
const external = new Map();
|
|
62
|
+
const entries = tree.entries().sort(([a], [b]) => Buffer.compare(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8')));
|
|
63
|
+
for (const [file, node] of entries) {
|
|
64
|
+
const scan = scanFile(file, node.bytes);
|
|
65
|
+
if (!scan)
|
|
66
|
+
continue;
|
|
67
|
+
for (const r of scan.refs) {
|
|
68
|
+
const t = resolveReference(r.url, file, op.root);
|
|
69
|
+
if (t.kind === 'none')
|
|
70
|
+
continue;
|
|
71
|
+
let reason;
|
|
72
|
+
if (t.kind === 'external') {
|
|
73
|
+
if (!op.external)
|
|
74
|
+
continue;
|
|
75
|
+
const url = t.url.startsWith('//') ? 'https:' + t.url : t.url;
|
|
76
|
+
if (!external.has(url)) {
|
|
77
|
+
try {
|
|
78
|
+
await fetchRemote(url, { ...policy, maxBytes: 0 }, op.span);
|
|
79
|
+
external.set(url, undefined);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
if (!isSitefileError(e))
|
|
83
|
+
throw e;
|
|
84
|
+
external.set(url, externalReason(e, url, policy));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
reason = external.get(url);
|
|
88
|
+
}
|
|
89
|
+
else if (!isServed(tree, t.path, t.dir)) {
|
|
90
|
+
const url = urlPath(op.root, t.path);
|
|
91
|
+
const display = t.dir && t.path !== '/' ? t.path + '/' : t.path;
|
|
92
|
+
if (url === undefined || !redirected(op.redirects, t.dir && url !== '/' ? url + '/' : url))
|
|
93
|
+
reason = `not found: ${display}`;
|
|
94
|
+
}
|
|
95
|
+
if (reason !== undefined)
|
|
96
|
+
out.push({ file, line: r.line, url: r.url, reason });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/** One broken link as messages show it: `/index.html:3: css/a.css (not found: /css/a.css)`. */
|
|
102
|
+
export function brokenLinkText(b) {
|
|
103
|
+
return `${b.file}:${b.line}: ${b.url} (${b.reason})`;
|
|
104
|
+
}
|
|
105
|
+
/** Report the broken links: a warning each with `--warn`, otherwise one E_BROKEN_LINK error listing them. */
|
|
106
|
+
export function reportBrokenLinks(broken, op, origin, warn) {
|
|
107
|
+
if (broken.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
if (op.warn) {
|
|
110
|
+
for (const b of broken)
|
|
111
|
+
warn(`${origin}: broken link ${brokenLinkText(b)}`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const listed = broken.slice(0, MAX_LISTED).map((b) => `\n ${brokenLinkText(b)}`);
|
|
115
|
+
const more = broken.length > MAX_LISTED ? `\n … and ${broken.length - MAX_LISTED} more (CHECKLINKS --warn lists them all)` : '';
|
|
116
|
+
throw new SitefileError('E_BROKEN_LINK', `CHECKLINKS: ${broken.length} broken link${broken.length === 1 ? '' : 's'}:${listed.join('')}${more}`, op.span);
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=checklinks.js.map
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { bakePlan, bakePlanJson, bakePlanText, buildArgv, findBakeFile } from '../bake.js';
|
|
6
|
+
import { CACHE_MAX_ENV } from '../cache-limit.js';
|
|
7
|
+
import { evictAfterBuild } from './cache.js';
|
|
8
|
+
import { str, strs, usage } from './common.js';
|
|
9
|
+
/** The CLI entry the targets run (lib/src/cli.js). */
|
|
10
|
+
const CLI = fileURLToPath(new URL('../cli.js', import.meta.url));
|
|
11
|
+
const DEFAULT_JOBS = 4;
|
|
12
|
+
/** Flags passed unchanged to every target's build. */
|
|
13
|
+
const PASS_BOOLEANS = ['allow-plugins', 'no-cache', 'strict-pins', 'frozen-lockfile', 'update-lock', 'no-lockfile', 'allow-http', 'allow-private-network', 'allow-hardlinks', 'force'];
|
|
14
|
+
const PASS_STRINGS = ['vendor-dir', 'max-download-bytes', 'timeout', 'plugin-timeout'];
|
|
15
|
+
const PASS_LISTS = ['allow-exec', 'allow-plugin', 'cache-from', 'insecure-registry', 'build-arg-file', 'build-arg'];
|
|
16
|
+
function positive(raw, flag) {
|
|
17
|
+
if (raw === undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
const n = Number(raw);
|
|
20
|
+
if (!Number.isSafeInteger(n) || n <= 0)
|
|
21
|
+
usage(`${flag} expects a positive integer`);
|
|
22
|
+
return n;
|
|
23
|
+
}
|
|
24
|
+
/** The flags every target's build gets, in a fixed order. */
|
|
25
|
+
function passThrough(v) {
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const f of PASS_BOOLEANS)
|
|
28
|
+
if (v[f])
|
|
29
|
+
out.push(`--${f}`);
|
|
30
|
+
for (const f of PASS_STRINGS) {
|
|
31
|
+
const s = str(v[f]);
|
|
32
|
+
if (s !== undefined)
|
|
33
|
+
out.push(`--${f}`, s);
|
|
34
|
+
}
|
|
35
|
+
for (const f of PASS_LISTS)
|
|
36
|
+
for (const s of strs(v[f]))
|
|
37
|
+
out.push(`--${f}`, s);
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function runBuild(argv) {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
// Each target evicts nothing; bake applies the cache size limit once, at the end.
|
|
43
|
+
const child = spawn(process.execPath, [CLI, ...argv], { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, [CACHE_MAX_ENV]: '0' } });
|
|
44
|
+
const out = [];
|
|
45
|
+
const err = [];
|
|
46
|
+
child.stdout.on('data', (b) => out.push(b));
|
|
47
|
+
child.stderr.on('data', (b) => err.push(b));
|
|
48
|
+
child.on('error', (e) => resolve({ code: 1, stdout: '', stderr: `error: cannot run the build: ${e.message}\n` }));
|
|
49
|
+
child.on('close', (code) => resolve({ code: code ?? 1, stdout: Buffer.concat(out).toString('utf8'), stderr: Buffer.concat(err).toString('utf8') }));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Each line of `text` with `[name] ` in front. */
|
|
53
|
+
function prefixed(name, text) {
|
|
54
|
+
if (text === '')
|
|
55
|
+
return '';
|
|
56
|
+
const lines = text.split('\n');
|
|
57
|
+
if (lines[lines.length - 1] === '')
|
|
58
|
+
lines.pop();
|
|
59
|
+
return lines.map((l) => `[${name}] ${l}\n`).join('');
|
|
60
|
+
}
|
|
61
|
+
/** The Sitefile.lock a target's build may write (targets that share one never run at the same time). */
|
|
62
|
+
function lockKey(t, v) {
|
|
63
|
+
if (v['no-lockfile'] || v['frozen-lockfile'])
|
|
64
|
+
return undefined;
|
|
65
|
+
return path.resolve(path.dirname(t.file), 'Sitefile.lock');
|
|
66
|
+
}
|
|
67
|
+
const bakeCommand = {
|
|
68
|
+
name: 'bake',
|
|
69
|
+
summary: 'build several variants (targets, matrix, groups) from a bake file',
|
|
70
|
+
usage: 'bake [targets...] [-f sitefile.bake.yaml] [options]',
|
|
71
|
+
description: "Build the targets of a bake file (default: sitefile.bake.yaml, .yml or .json in the current directory), like\n'docker buildx bake'. Each target is a 'sitefile build' with its own context, Sitefile, build args, --target\nstage, output and tags; a matrix expands one target into one per combination of values. Name targets or\ngroups to build only those (default: the 'default' group, else every target). Paths in the bake file are\nrelative to it. Up to --jobs builds run at once; each target's output is printed, prefixed with its\nname, in plan order. Every target runs even when one fails; then the exit code is 1.",
|
|
72
|
+
flags: [
|
|
73
|
+
'-f, --file <path> bake file (default: sitefile.bake.yaml, .yml or .json)',
|
|
74
|
+
'--print print the resolved targets instead of building (text, or --json)',
|
|
75
|
+
'--json with --print: machine-readable plan',
|
|
76
|
+
`-j, --jobs <n> builds to run at once (default ${DEFAULT_JOBS})`,
|
|
77
|
+
'--profile <dir> write each target\'s timing trace to <dir>/<target>.json',
|
|
78
|
+
'--build-arg <NAME=value> set an ARG in every target (wins over the bake file; repeatable)',
|
|
79
|
+
'--build-arg-file <path> read ARGs for every target from a file (after the targets\' own; repeatable)',
|
|
80
|
+
'Passed to every build: --allow-exec, --allow-plugin, --allow-plugins, --no-cache, --cache-from, --strict-pins,',
|
|
81
|
+
' --frozen-lockfile, --update-lock, --no-lockfile, --vendor-dir, --allow-http, --allow-private-network,',
|
|
82
|
+
' --allow-hardlinks, --max-download-bytes, --timeout, --plugin-timeout, --insecure-registry, --force',
|
|
83
|
+
],
|
|
84
|
+
options: {
|
|
85
|
+
file: { type: 'string', short: 'f' },
|
|
86
|
+
print: { type: 'boolean' },
|
|
87
|
+
json: { type: 'boolean' },
|
|
88
|
+
jobs: { type: 'string', short: 'j' },
|
|
89
|
+
profile: { type: 'string' },
|
|
90
|
+
'cache-to': { type: 'string', multiple: true },
|
|
91
|
+
...Object.fromEntries(PASS_BOOLEANS.map((f) => [f, { type: 'boolean' }])),
|
|
92
|
+
...Object.fromEntries(PASS_STRINGS.map((f) => [f, { type: 'string' }])),
|
|
93
|
+
...Object.fromEntries(PASS_LISTS.map((f) => [f, { type: 'string', multiple: true }])),
|
|
94
|
+
},
|
|
95
|
+
async run(v, pos) {
|
|
96
|
+
if (v.json && !v.print)
|
|
97
|
+
usage('bake: --json is only for --print');
|
|
98
|
+
if (strs(v['cache-to']).length)
|
|
99
|
+
usage("bake: --cache-to is not supported (each target would replace the others' results); push from one 'sitefile build --cache-to'");
|
|
100
|
+
if (v['frozen-lockfile'] && v['update-lock'])
|
|
101
|
+
usage('--frozen-lockfile and --update-lock conflict');
|
|
102
|
+
if (v['strict-pins'] && v['update-lock'])
|
|
103
|
+
usage('--strict-pins and --update-lock conflict');
|
|
104
|
+
if (v['no-cache'] && strs(v['cache-from']).length)
|
|
105
|
+
usage('--no-cache cannot be combined with --cache-from or --cache-to');
|
|
106
|
+
const jobs = positive(str(v.jobs), '--jobs') ?? DEFAULT_JOBS;
|
|
107
|
+
const profile = str(v.profile);
|
|
108
|
+
if (profile === '')
|
|
109
|
+
usage('--profile expects a directory path');
|
|
110
|
+
const plan = await bakePlan(await findBakeFile(str(v.file)), pos);
|
|
111
|
+
if (v.print) {
|
|
112
|
+
process.stdout.write(v.json ? JSON.stringify(bakePlanJson(plan), null, 2) + '\n' : bakePlanText(plan));
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
if (profile !== undefined)
|
|
116
|
+
await mkdir(profile, { recursive: true });
|
|
117
|
+
const extra = passThrough(v);
|
|
118
|
+
const targets = plan.targets;
|
|
119
|
+
const results = targets.map(() => undefined);
|
|
120
|
+
const busy = new Set();
|
|
121
|
+
const pending = targets.map((_, i) => i);
|
|
122
|
+
let printed = 0;
|
|
123
|
+
const flush = () => {
|
|
124
|
+
while (printed < targets.length && results[printed] !== undefined) {
|
|
125
|
+
const r = results[printed];
|
|
126
|
+
const name = targets[printed].name;
|
|
127
|
+
process.stderr.write(prefixed(name, r.stderr));
|
|
128
|
+
process.stdout.write(prefixed(name, r.stdout));
|
|
129
|
+
printed++;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
await new Promise((done) => {
|
|
133
|
+
if (targets.length === 0)
|
|
134
|
+
return done();
|
|
135
|
+
let running = 0;
|
|
136
|
+
const pump = () => {
|
|
137
|
+
while (running < jobs) {
|
|
138
|
+
// The first pending target whose Sitefile.lock no running build may write.
|
|
139
|
+
const k = pending.findIndex((i) => {
|
|
140
|
+
const key = lockKey(targets[i], v);
|
|
141
|
+
return key === undefined || !busy.has(key);
|
|
142
|
+
});
|
|
143
|
+
if (k === -1)
|
|
144
|
+
break;
|
|
145
|
+
const i = pending.splice(k, 1)[0];
|
|
146
|
+
const t = targets[i];
|
|
147
|
+
const key = lockKey(t, v);
|
|
148
|
+
if (key !== undefined)
|
|
149
|
+
busy.add(key);
|
|
150
|
+
running++;
|
|
151
|
+
const argv = [...buildArgv(t), ...extra, ...(profile !== undefined ? ['--profile', path.join(profile, `${t.name}.json`)] : [])];
|
|
152
|
+
void runBuild(argv).then((r) => {
|
|
153
|
+
results[i] = r;
|
|
154
|
+
if (key !== undefined)
|
|
155
|
+
busy.delete(key);
|
|
156
|
+
running--;
|
|
157
|
+
flush();
|
|
158
|
+
if (running === 0 && pending.length === 0)
|
|
159
|
+
done();
|
|
160
|
+
else
|
|
161
|
+
pump();
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
pump();
|
|
166
|
+
});
|
|
167
|
+
await evictAfterBuild();
|
|
168
|
+
const failed = targets.filter((_, i) => results[i].code !== 0).map((t) => t.name);
|
|
169
|
+
const n = `${targets.length} target${targets.length === 1 ? '' : 's'}`;
|
|
170
|
+
if (failed.length) {
|
|
171
|
+
process.stderr.write(`error[E_BAKE]: ${failed.length} of ${n} failed: ${failed.join(', ')}\n`);
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
process.stdout.write(`Baked ${n}\n`);
|
|
175
|
+
return 0;
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
export const bakeCommands = new Map([['bake', bakeCommand]]);
|
|
179
|
+
//# sourceMappingURL=bake.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AttestRequest } from '../attest.js';
|
|
2
|
+
import { type BuildOptions, type BuildResult } from '../executor.js';
|
|
3
|
+
import type { OciCompression } from '../language/ast.js';
|
|
4
|
+
/** Validate `-t` values before building: local name[:tag] only. Throws E_USAGE / E_UNSUPPORTED. */
|
|
5
|
+
export declare function validateImageTags(tags: readonly string[]): void;
|
|
6
|
+
/**
|
|
7
|
+
* Store a build's exported tree as an OCI image in the local store and point each tag at it.
|
|
8
|
+
* Build-arg values are never recorded (nothing but the files and OCI labels reach the image).
|
|
9
|
+
*/
|
|
10
|
+
export declare function storeBuildAsImage(result: BuildResult, tags: readonly string[], info: {
|
|
11
|
+
readonly contextDir: string;
|
|
12
|
+
readonly ociRoot?: string | undefined;
|
|
13
|
+
readonly ociCompression?: OciCompression | undefined;
|
|
14
|
+
readonly platform?: string | undefined;
|
|
15
|
+
readonly labels?: Readonly<Record<string, string>> | undefined;
|
|
16
|
+
readonly gitLabels?: boolean | undefined;
|
|
17
|
+
/** `--provenance` / `--sbom`: attach these attestations to the stored image (they are pushed with it). */
|
|
18
|
+
readonly attest?: AttestRequest | undefined;
|
|
19
|
+
/** What the attestations record (the Sitefile, build args). */
|
|
20
|
+
readonly build?: Pick<BuildOptions, 'contextDir' | 'sitefile' | 'buildArgs'> | undefined;
|
|
21
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { sourceDateEpoch } from '../archive-writers.js';
|
|
2
|
+
import { SitefileError } from '../errors.js';
|
|
3
|
+
import { imageAttestations } from '../executor.js';
|
|
4
|
+
import { imageMeta } from '../oci.js';
|
|
5
|
+
import { parseLocalRef } from '../ref.js';
|
|
6
|
+
import { shortId, Store } from '../store.js';
|
|
7
|
+
/** Validate `-t` values before building: local name[:tag] only. Throws E_USAGE / E_UNSUPPORTED. */
|
|
8
|
+
export function validateImageTags(tags) {
|
|
9
|
+
for (const t of tags) {
|
|
10
|
+
const ref = parseLocalRef(t);
|
|
11
|
+
if (ref.digest !== undefined)
|
|
12
|
+
throw new SitefileError('E_USAGE', `-t '${t}': a tag cannot include a digest`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Store a build's exported tree as an OCI image in the local store and point each tag at it.
|
|
17
|
+
* Build-arg values are never recorded (nothing but the files and OCI labels reach the image).
|
|
18
|
+
*/
|
|
19
|
+
export async function storeBuildAsImage(result, tags, info) {
|
|
20
|
+
const store = await Store.open();
|
|
21
|
+
const { id } = await store.addImage(result.output, {
|
|
22
|
+
root: info.ociRoot ?? result.plan.exportRoot,
|
|
23
|
+
compression: info.ociCompression ?? result.plan.exportCompression,
|
|
24
|
+
platform: info.platform,
|
|
25
|
+
epoch: sourceDateEpoch(),
|
|
26
|
+
base: result.base,
|
|
27
|
+
...(await imageMeta(info.contextDir, { git: !!info.gitLabels, ...(info.labels ? { labels: info.labels } : {}) })),
|
|
28
|
+
});
|
|
29
|
+
if (info.attest && (info.attest.provenance || info.attest.sbom)) {
|
|
30
|
+
const manifestBytes = await store.readBlob(id);
|
|
31
|
+
const d = await store.details(id);
|
|
32
|
+
const img = { manifestDigest: id, manifestBytes, configBytes: await store.readBlob(d.manifest.config.digest), root: d.root };
|
|
33
|
+
const opts = { contextDir: info.contextDir, ...info.build, ...(info.platform !== undefined ? { platform: info.platform } : {}) };
|
|
34
|
+
await store.putReferrers(id, await imageAttestations(result, img, opts, info.attest));
|
|
35
|
+
}
|
|
36
|
+
for (const t of tags) {
|
|
37
|
+
const key = await store.tag(id, parseLocalRef(t));
|
|
38
|
+
process.stdout.write(`Tagged ${key} ${shortId(id)} (${result.output.size} file${result.output.size === 1 ? '' : 's'})\n`);
|
|
39
|
+
}
|
|
40
|
+
return id;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=build-image.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Command } from './common.js';
|
|
2
|
+
/** Where TRANSFORM results are cached: beside the image store, in `$SITEFILE_HOME/cache/transform`. */
|
|
3
|
+
export declare function transformCacheDir(env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
/**
|
|
5
|
+
* End of `build`/`inspect`: apply $SITEFILE_CACHE_MAX. Never fails: problems are warnings.
|
|
6
|
+
*/
|
|
7
|
+
export declare function evictAfterBuild(env?: NodeJS.ProcessEnv): Promise<void>;
|
|
8
|
+
export declare const cacheSubcommands: ReadonlyMap<string, Command>;
|