@johnhenry/sitefile 0.0.0 → 0.0.2
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 +115 -1
- package/README.md +62 -24
- package/lib/src/checklinks.js +2 -0
- package/lib/src/cli/build-image.js +2 -2
- package/lib/src/cli/cache.js +15 -5
- package/lib/src/cli/images.js +3 -3
- package/lib/src/cli/index.d.ts +1 -1
- package/lib/src/cli/index.js +1 -1
- package/lib/src/cli/network.d.ts +34 -0
- package/lib/src/cli/network.js +56 -0
- package/lib/src/cli/outdated.js +5 -0
- package/lib/src/cli/registry.js +6 -1
- package/lib/src/cli/servers.js +23 -7
- package/lib/src/cli/vendor.js +5 -0
- package/lib/src/cli.js +44 -8
- package/lib/src/errors.d.ts +25 -3
- package/lib/src/errors.js +9 -0
- package/lib/src/executor.d.ts +7 -0
- package/lib/src/executor.js +18 -10
- package/lib/src/export-formats.d.ts +10 -3
- package/lib/src/export-formats.js +17 -6
- package/lib/src/index.d.ts +2 -1
- package/lib/src/index.js +2 -1
- package/lib/src/language/ast.d.ts +9 -2
- package/lib/src/outdated.js +1 -1
- package/lib/src/planner.d.ts +1 -1
- package/lib/src/planner.js +2 -2
- package/lib/src/plugins/cache.d.ts +29 -3
- package/lib/src/plugins/cache.js +104 -5
- package/lib/src/plugins/host.js +3 -2
- package/lib/src/registry.d.ts +6 -0
- package/lib/src/registry.js +38 -19
- package/lib/src/resources/https.d.ts +35 -1
- package/lib/src/resources/https.js +175 -18
- package/lib/src/resources/network.d.ts +70 -0
- package/lib/src/resources/network.js +304 -0
- package/lib/src/resources/types.d.ts +6 -0
- package/lib/src/secrets.js +1 -1
- package/lib/src/serve-rules.js +2 -1
- package/lib/src/servers.d.ts +14 -0
- package/lib/src/servers.js +89 -3
- package/lib/src/vendor.js +1 -1
- package/lib/src/wbn.d.ts +21 -0
- package/lib/src/wbn.js +111 -17
- package/package.json +2 -1
package/lib/src/cli/servers.js
CHANGED
|
@@ -33,12 +33,26 @@ export function parsePublish(spec) {
|
|
|
33
33
|
return { host, port };
|
|
34
34
|
}
|
|
35
35
|
const isWildcard = (h) => h === '0.0.0.0' || h === '::';
|
|
36
|
+
async function isFile(p) {
|
|
37
|
+
return fs.stat(p).then((s) => s.isFile(), () => false);
|
|
38
|
+
}
|
|
39
|
+
/** An archive file source: labelled by its absolute path, stored by its real path. */
|
|
40
|
+
async function archiveSource(arg) {
|
|
41
|
+
const file = path.resolve(arg);
|
|
42
|
+
return { label: file, source: { kind: 'archive', path: await fs.realpath(file).catch(() => file) } };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A directory or archive file when `arg` looks like a path, else an image reference or ID, else an
|
|
46
|
+
* archive file by that name (`site.zip`). Whether a file is an archive is checked when it is loaded.
|
|
47
|
+
*/
|
|
36
48
|
async function resolveSource(store, arg) {
|
|
37
49
|
if (/^(\.\.?|~.*|\.\.?\/.*|\/.*|.*\/)$/.test(arg)) {
|
|
38
50
|
const dir = path.resolve(arg);
|
|
51
|
+
if (await isFile(dir))
|
|
52
|
+
return archiveSource(arg);
|
|
39
53
|
const st = await fs.stat(dir).catch(() => undefined);
|
|
40
54
|
if (!st?.isDirectory())
|
|
41
|
-
throw new SitefileError('E_NOT_FOUND', `'${arg}' is not a directory`);
|
|
55
|
+
throw new SitefileError('E_NOT_FOUND', `'${arg}' is not a directory or a file`);
|
|
42
56
|
return { label: dir, source: { kind: 'dir', path: await fs.realpath(dir) } };
|
|
43
57
|
}
|
|
44
58
|
try {
|
|
@@ -48,6 +62,8 @@ async function resolveSource(store, arg) {
|
|
|
48
62
|
if (e instanceof SitefileError && e.code === 'E_NOT_FOUND' && (await fs.stat(arg).then((s) => s.isDirectory(), () => false))) {
|
|
49
63
|
throw new SitefileError('E_NOT_FOUND', `${e.message} (to serve the directory, write ./${arg})`);
|
|
50
64
|
}
|
|
65
|
+
if (await isFile(arg))
|
|
66
|
+
return archiveSource(arg);
|
|
51
67
|
throw e;
|
|
52
68
|
}
|
|
53
69
|
}
|
|
@@ -88,9 +104,9 @@ async function startDetached(reg, state) {
|
|
|
88
104
|
}
|
|
89
105
|
const run = {
|
|
90
106
|
name: 'serve',
|
|
91
|
-
summary: 'serve an image or
|
|
92
|
-
usage: 'serve [--name n] [-p [host:]port] [-d] [--spa] <ref|id|./dir> | serve --watch [options] [context]',
|
|
93
|
-
description: 'Serve an image from the local store,
|
|
107
|
+
summary: 'serve an image, directory or archive file over HTTP',
|
|
108
|
+
usage: 'serve [--name n] [-p [host:]port] [-d] [--spa] <ref|id|./dir|file> | serve --watch [options] [context]',
|
|
109
|
+
description: 'Serve an image from the local store, a directory (paths starting with ./, ../ or /), or an archive file over HTTP:\nzip, tar, tar.gz, wbn, or an OCI archive as build -o x.oci.tar and save write it (the format is read from the\nbytes). A directory or archive is a snapshot read at start. GET and HEAD only; no directory listings.\nBinds 127.0.0.1 on a free port unless -p is given;\n0.0.0.0 is bound only when you pass it explicitly. Foreground by default (Ctrl-C stops it); -d detaches and\nrecords the server for servers, server stop, rm, logs and restart.\n\nWith --watch the argument is a build context (default .): its Sitefile is built in memory and served, rebuilt\nwhen the context, the Sitefile or a --build-arg-file changes, and open pages reload after each good rebuild\n(HTML responses get a small script listening on /__sitefile/reload). A failed rebuild keeps serving the last\ngood build. Nothing is written to the context except Sitefile.lock.',
|
|
94
110
|
flags: [
|
|
95
111
|
'--name <n> name for the server (-d only; default site-<6 hex>)',
|
|
96
112
|
'-p, --publish <[host:]port> address to bind (default 127.0.0.1, a free port)',
|
|
@@ -123,7 +139,7 @@ const run = {
|
|
|
123
139
|
for (const f of WATCH_ONLY)
|
|
124
140
|
if (v[f] !== undefined)
|
|
125
141
|
usage(`--${f} needs --watch`);
|
|
126
|
-
requireArgs(pos, 1, 1, 'an image reference or
|
|
142
|
+
requireArgs(pos, 1, 1, 'an image reference, ./directory or archive file');
|
|
127
143
|
const bind = parsePublish(str(v.publish));
|
|
128
144
|
const name = str(v.name);
|
|
129
145
|
if (name !== undefined && !v.detach)
|
|
@@ -141,7 +157,7 @@ const run = {
|
|
|
141
157
|
await closeServer(server);
|
|
142
158
|
return 0;
|
|
143
159
|
}
|
|
144
|
-
await loadServedTree(store, source); // fail fast on a bad image or
|
|
160
|
+
await loadServedTree(store, source); // fail fast on a bad image, directory or archive
|
|
145
161
|
const reg = await ServerRegistry.open(store);
|
|
146
162
|
const { id, name: finalName } = await reg.allocate(name);
|
|
147
163
|
const state = {
|
|
@@ -415,7 +431,7 @@ const restart = {
|
|
|
415
431
|
name: 'restart',
|
|
416
432
|
summary: 'restart a site server',
|
|
417
433
|
usage: 'server restart [--time secs] <name|id>',
|
|
418
|
-
description: 'Stop the server if it is running, then start it again with the same name, image and bind address.\nImages are served by the same image ID; a directory is re-read.',
|
|
434
|
+
description: 'Stop the server if it is running, then start it again with the same name, image and bind address.\nImages are served by the same image ID; a directory or archive file is re-read.',
|
|
419
435
|
flags: ['--time <secs> grace period before SIGKILL (default 5)'],
|
|
420
436
|
options: { time: { type: 'string' } },
|
|
421
437
|
async run(v, pos) {
|
package/lib/src/cli/vendor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NETWORK_OPTIONS, networkHelp, networkOption } from './network.js';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { insecureRegistriesFromEnv } from '../base-image.js';
|
|
3
4
|
import { collectBuildArgs } from '../build-args.js';
|
|
@@ -31,11 +32,13 @@ const vendorCommand = {
|
|
|
31
32
|
'--allow-private-network permit loopback/private network addresses',
|
|
32
33
|
`--max-download-bytes <n> per-resource size limit (default ${DEFAULT_FETCH_POLICY.maxBytes})`,
|
|
33
34
|
`--timeout <ms> per-resource fetch timeout (default ${DEFAULT_FETCH_POLICY.timeoutMs})`,
|
|
35
|
+
...networkHelp(30),
|
|
34
36
|
'--base <image> vendor this BASE image (overrides EXPORT ... AS oci BASE)',
|
|
35
37
|
'--platform <os/arch> BASE platform to vendor (default linux/amd64)',
|
|
36
38
|
'--insecure-registry <h> allow plain http to registry host[:port] (repeatable)',
|
|
37
39
|
],
|
|
38
40
|
options: {
|
|
41
|
+
...NETWORK_OPTIONS,
|
|
39
42
|
file: { type: 'string', short: 'f' },
|
|
40
43
|
'vendor-dir': { type: 'string' },
|
|
41
44
|
'build-arg': { type: 'string', multiple: true },
|
|
@@ -58,6 +61,7 @@ const vendorCommand = {
|
|
|
58
61
|
const context = pos[0] ?? '.';
|
|
59
62
|
const maxBytes = positive(str(v['max-download-bytes']), '--max-download-bytes');
|
|
60
63
|
const timeoutMs = positive(str(v.timeout), '--timeout');
|
|
64
|
+
const network = networkOption(v);
|
|
61
65
|
const file = str(v.file);
|
|
62
66
|
const vendorDir = str(v['vendor-dir']);
|
|
63
67
|
const base = str(v.base);
|
|
@@ -72,6 +76,7 @@ const vendorCommand = {
|
|
|
72
76
|
allowPrivateNetwork: !!v['allow-private-network'],
|
|
73
77
|
...(maxBytes ? { maxBytes } : {}),
|
|
74
78
|
...(timeoutMs ? { timeoutMs } : {}),
|
|
79
|
+
...(network ? { network } : {}),
|
|
75
80
|
},
|
|
76
81
|
...(vendorDir !== undefined ? { vendorDir } : {}),
|
|
77
82
|
...(base !== undefined ? { baseImage: base } : {}),
|
package/lib/src/cli.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { SitefileError, isSitefileError } from './errors.js';
|
|
5
|
-
import { build, buildToDirectory, checkPlanPins, lintBuild, planBuild } from './executor.js';
|
|
5
|
+
import { build, buildToDirectory, checkPlanPins, lintBuild, planBuild, sitefileCompression } from './executor.js';
|
|
6
6
|
import { LINT_RULES } from './lint.js';
|
|
7
|
-
import { defaultOutName, formatFromExtension } from './export-formats.js';
|
|
7
|
+
import { compressionFromExtension, defaultOutName, formatFromExtension } from './export-formats.js';
|
|
8
8
|
import { collectBuildArgs } from './build-args.js';
|
|
9
9
|
import { insecureRegistriesFromEnv, makeClient } from './base-image.js';
|
|
10
10
|
import { cacheLimitFromEnv } from './cache-limit.js';
|
|
@@ -24,6 +24,7 @@ import { promises as fs } from 'node:fs';
|
|
|
24
24
|
import { graphDot, graphText, planGraph, why, whyText } from './explain.js';
|
|
25
25
|
import { isGitContext, materializeGitContext } from './cli/git-context.js';
|
|
26
26
|
import { loadSecrets } from './secrets.js';
|
|
27
|
+
import { NETWORK_OPTIONS, networkHelp, networkOption } from './cli/network.js';
|
|
27
28
|
const VERSION = SITEFILE_VERSION;
|
|
28
29
|
const HELP = `sitefile ${VERSION} — Docker-inspired static artifact builder
|
|
29
30
|
|
|
@@ -43,16 +44,19 @@ Options:
|
|
|
43
44
|
--target <stage> stop after the named stage and export its whole tree instead of
|
|
44
45
|
the EXPORT path (EXPORT's format still applies; no _redirects/_headers)
|
|
45
46
|
-o, --out <path> export destination (build only): a directory for 'directory',
|
|
46
|
-
a file for zip, tar, tgz, wbn
|
|
47
|
+
a file for zip, tar, tgz, wbn (*.wbn.gz: a gzipped bundle)
|
|
47
48
|
-t, --tag <name[:tag]> also store the build as a local image (build only, repeatable);
|
|
48
49
|
with -t and no -o nothing is written to the context
|
|
49
50
|
--format <fmt> directory | zip | tar | tgz (alias tar.gz) | wbn | oci (build only).
|
|
50
51
|
oci writes an OCI archive for -o *.tar (e.g. site.oci.tar), else an
|
|
51
52
|
OCI image layout directory.
|
|
52
|
-
Default: the --out extension (.zip .tar .tar.gz .tgz .wbn .oci.tar),
|
|
53
|
+
Default: the --out extension (.zip .tar .tar.gz .tgz .wbn .wbn.gz .oci.tar),
|
|
53
54
|
else the Sitefile's EXPORT ... AS <fmt>
|
|
54
55
|
--oci-root <path> directory inside an oci image layer (default /srv/www; overrides EXPORT ... AT)
|
|
55
|
-
--
|
|
56
|
+
--compression <c> gzip or none (build only; overrides EXPORT ... COMPRESSION): the oci site layer
|
|
57
|
+
(default gzip; none: plain tar), or the whole wbn (default none; gzip:
|
|
58
|
+
gzip(application/webbundle), also chosen by -o *.wbn.gz)
|
|
59
|
+
--oci-compression <c> the same as --compression, for oci only
|
|
56
60
|
--label <key=value> add an OCI image config label (repeatable)
|
|
57
61
|
--git-labels add org.opencontainers.image.source/revision from the git checkout
|
|
58
62
|
(off by default: they change the image ID on every commit)
|
|
@@ -98,6 +102,7 @@ Options:
|
|
|
98
102
|
--allow-hardlinks permit hard-linked files in the context (you assert they are safe)
|
|
99
103
|
--max-download-bytes <n> per-resource size limit (default ${DEFAULT_FETCH_POLICY.maxBytes})
|
|
100
104
|
--timeout <ms> per-resource fetch timeout (default ${DEFAULT_FETCH_POLICY.timeoutMs})
|
|
105
|
+
${networkHelp(25).map((l) => ` ${l}`).join('\n')}
|
|
101
106
|
--json machine-readable output (validate, inspect, graph, why)
|
|
102
107
|
--dot Graphviz DOT output (graph only)
|
|
103
108
|
--watch build, then rebuild when the context, the Sitefile or a
|
|
@@ -121,6 +126,7 @@ export async function main(argv, run = {}) {
|
|
|
121
126
|
args: [...argv],
|
|
122
127
|
allowPositionals: true,
|
|
123
128
|
options: {
|
|
129
|
+
...NETWORK_OPTIONS,
|
|
124
130
|
file: { type: 'string', short: 'f' },
|
|
125
131
|
target: { type: 'string' },
|
|
126
132
|
out: { type: 'string', short: 'o' },
|
|
@@ -129,6 +135,7 @@ export async function main(argv, run = {}) {
|
|
|
129
135
|
'base-url': { type: 'string' },
|
|
130
136
|
'oci-root': { type: 'string' },
|
|
131
137
|
'oci-compression': { type: 'string' },
|
|
138
|
+
compression: { type: 'string' },
|
|
132
139
|
label: { type: 'string', multiple: true },
|
|
133
140
|
'git-labels': { type: 'boolean' },
|
|
134
141
|
provenance: { type: 'boolean' },
|
|
@@ -255,11 +262,13 @@ export async function main(argv, run = {}) {
|
|
|
255
262
|
let failed = false;
|
|
256
263
|
let fetched;
|
|
257
264
|
try {
|
|
265
|
+
const network = networkOption(o);
|
|
258
266
|
const fetchPolicy = {
|
|
259
267
|
allowHttp: !!o['allow-http'],
|
|
260
268
|
allowPrivateNetwork: !!o['allow-private-network'],
|
|
261
269
|
...(o['max-download-bytes'] ? { maxBytes: positiveInt(o['max-download-bytes'], '--max-download-bytes') } : {}),
|
|
262
270
|
...(o.timeout ? { timeoutMs: positiveInt(o.timeout, '--timeout') } : {}),
|
|
271
|
+
...(network ? { network } : {}),
|
|
263
272
|
};
|
|
264
273
|
if (gitContext) {
|
|
265
274
|
// A Git URL context: fetched into a temporary directory, built from there, then removed.
|
|
@@ -272,7 +281,7 @@ export async function main(argv, run = {}) {
|
|
|
272
281
|
? new RegistryCache({
|
|
273
282
|
from: cacheFrom.map((r) => parseCacheRef('--cache-from', r)),
|
|
274
283
|
to: cacheTo[0] !== undefined ? parseCacheRef('--cache-to', cacheTo[0]) : undefined,
|
|
275
|
-
client: (registry) => makeClient(registry, { insecureRegistries: insecureRegistriesFromEnv(o['insecure-registry'] ?? []), allowPrivateNetwork: !!o['allow-private-network'] }),
|
|
284
|
+
client: (registry) => makeClient(registry, { insecureRegistries: insecureRegistriesFromEnv(o['insecure-registry'] ?? []), allowPrivateNetwork: !!o['allow-private-network'], network }),
|
|
276
285
|
limit: cacheLimitFromEnv(),
|
|
277
286
|
warn: (m) => process.stderr.write(`warning: ${m}\n`),
|
|
278
287
|
notice: (m) => process.stderr.write(`${m}\n`),
|
|
@@ -293,14 +302,26 @@ export async function main(argv, run = {}) {
|
|
|
293
302
|
if (!ociCompression)
|
|
294
303
|
return usageError(`--oci-compression expects gzip or none, got '${o['oci-compression']}'`);
|
|
295
304
|
}
|
|
305
|
+
let compression;
|
|
306
|
+
if (o.compression !== undefined) {
|
|
307
|
+
compression = parseOciCompression(o.compression);
|
|
308
|
+
if (!compression)
|
|
309
|
+
return usageError(`--compression expects gzip or none, got '${o.compression}'`);
|
|
310
|
+
if (ociCompression !== undefined)
|
|
311
|
+
return usageError('--compression and --oci-compression set the same thing; give one');
|
|
312
|
+
}
|
|
296
313
|
// No --format: an archive extension on --out picks the format, so `-o site.zip`
|
|
297
314
|
// writes a zip instead of a directory named site.zip.
|
|
298
315
|
if (!formatOverride && o.out)
|
|
299
316
|
formatOverride = formatFromExtension(o.out);
|
|
300
317
|
// The default destination depends on the format, which the Sitefile may set.
|
|
301
|
-
|
|
318
|
+
// (A wbn's default name also depends on its COMPRESSION: dist.wbn or dist.wbn.gz.)
|
|
319
|
+
const sitefilePlan = command === 'build' && !o.out && (!formatOverride || formatOverride === 'wbn') ? await planBuild({ contextDir: context, ...(file ? { sitefile: file } : {}), buildArgs }) : undefined;
|
|
320
|
+
const sitefileFormat = formatOverride ? undefined : sitefilePlan?.exportFormat;
|
|
321
|
+
const defaultFormat = formatOverride ?? sitefileFormat ?? 'directory';
|
|
322
|
+
const defaultCompression = defaultFormat === 'wbn' ? (compression ?? (sitefilePlan ? sitefileCompression(sitefilePlan, 'wbn') : undefined)) : undefined;
|
|
302
323
|
// A Git context's default destination is in the current directory, not the temporary checkout.
|
|
303
|
-
const out = o.out ?? path.join(fetched ? '.' : context, defaultOutName(
|
|
324
|
+
const out = o.out ?? path.join(fetched ? '.' : context, defaultOutName(defaultFormat, defaultCompression));
|
|
304
325
|
run.wrote?.push(out);
|
|
305
326
|
const options = {
|
|
306
327
|
contextDir: context,
|
|
@@ -335,6 +356,20 @@ export async function main(argv, run = {}) {
|
|
|
335
356
|
...(profiler ? { profiler, alsoWrites: [o.profile] } : {}),
|
|
336
357
|
...(run.memo ? { memo: run.memo } : {}),
|
|
337
358
|
};
|
|
359
|
+
// --compression belongs to the export's own format, oci or wbn (-t without -o stores an oci image);
|
|
360
|
+
// a *.wbn.gz destination means a gzipped bundle.
|
|
361
|
+
let wbnCompression;
|
|
362
|
+
if (command === 'build' && compression !== undefined) {
|
|
363
|
+
const fmt = (o.tag ?? []).length && !o.out ? 'oci' : (formatOverride ?? sitefileFormat ?? (await planBuild(options)).exportFormat);
|
|
364
|
+
if (fmt === 'oci')
|
|
365
|
+
ociCompression = compression;
|
|
366
|
+
else if (fmt === 'wbn')
|
|
367
|
+
wbnCompression = compression;
|
|
368
|
+
else
|
|
369
|
+
return usageError(`--compression applies only to oci and wbn exports, not ${fmt}`);
|
|
370
|
+
}
|
|
371
|
+
if (formatOverride === 'wbn' && o.out)
|
|
372
|
+
wbnCompression ??= compressionFromExtension(o.out, 'wbn');
|
|
338
373
|
switch (command) {
|
|
339
374
|
case 'build': {
|
|
340
375
|
const tags = o.tag ?? [];
|
|
@@ -369,6 +404,7 @@ export async function main(argv, run = {}) {
|
|
|
369
404
|
...(o['base-url'] !== undefined ? { baseUrl: o['base-url'] } : {}),
|
|
370
405
|
...(o['oci-root'] !== undefined ? { ociRoot: o['oci-root'] } : {}),
|
|
371
406
|
...(ociCompression !== undefined ? { ociCompression } : {}),
|
|
407
|
+
...(wbnCompression !== undefined ? { wbnCompression } : {}),
|
|
372
408
|
...(o.platform !== undefined ? { platform: o.platform } : {}),
|
|
373
409
|
imageLabels: labels,
|
|
374
410
|
gitLabels: !!o['git-labels'],
|
package/lib/src/errors.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface SourceSpan {
|
|
|
10
10
|
*/
|
|
11
11
|
export type ErrorCode = 'E_SYNTAX' | 'E_UNKNOWN_INSTRUCTION' | 'E_UNSUPPORTED' | 'E_VALIDATION' | 'E_UNDEFINED' | 'E_DUPLICATE' | 'E_MISSING_ARG' | 'E_TYPE' | 'E_PATH' | 'E_PATH_ESCAPE' | 'E_NOT_FOUND' | 'E_UNSUPPORTED_FILE' | 'E_HARDLINK'
|
|
12
12
|
/** Two paths equal under Unicode simple case folding, or two context names equal after NFC. */
|
|
13
|
-
| 'E_PATH_COLLISION' | 'E_CONFLICT' | 'E_PROTOCOL' | 'E_SSRF_BLOCKED' | 'E_FETCH' | 'E_FETCH_STATUS' | 'E_FETCH_TIMEOUT' | 'E_TOO_LARGE' | 'E_REDIRECT' | 'E_INTEGRITY' | 'E_LOCK_MISMATCH' | 'E_LOCK_MISSING'
|
|
13
|
+
| 'E_PATH_COLLISION' | 'E_CONFLICT' | 'E_PROTOCOL' | 'E_SSRF_BLOCKED' | 'E_HOST_BLOCKED' | 'E_FETCH' | 'E_FETCH_STATUS' | 'E_FETCH_TIMEOUT' | 'E_TOO_LARGE' | 'E_REDIRECT' | 'E_INTEGRITY' | 'E_LOCK_MISMATCH' | 'E_LOCK_MISSING'
|
|
14
14
|
/** A remote input (IMPORT URL or BASE image) has no pin, under --strict-pins. */
|
|
15
15
|
| 'E_UNPINNED'
|
|
16
16
|
/** A malformed or unsupported Git smart HTTP response or packfile (git+https IMPORT, Git URL contexts). */
|
|
@@ -22,13 +22,35 @@ export type ErrorCode = 'E_SYNTAX' | 'E_UNKNOWN_INSTRUCTION' | 'E_UNSUPPORTED' |
|
|
|
22
22
|
/** CHECKLINKS found references to files that do not exist (or, with --external, URLs that do not answer). */
|
|
23
23
|
| 'E_BROKEN_LINK' | 'E_EXPORT' | 'E_EXPORT_UNSAFE' | 'E_STORE' | 'E_SERVER' | 'E_REGISTRY' | 'E_AUTH'
|
|
24
24
|
/** One or more `sitefile bake` targets failed. */
|
|
25
|
-
| 'E_BAKE' | 'E_USAGE'
|
|
25
|
+
| 'E_BAKE' | 'E_USAGE'
|
|
26
|
+
/** The build was stopped by its embedder (the Rust library's `CancelToken`; see docs/RUST.md). */
|
|
27
|
+
| 'E_CANCELLED';
|
|
28
|
+
/**
|
|
29
|
+
* The permission a build needs: carried by the `E_UNSUPPORTED` error of a JavaScript PLUGIN
|
|
30
|
+
* (`--allow-plugin <name>`) or a TOOL (`--allow-exec <name>`) that was not allowed, so callers
|
|
31
|
+
* need not parse the message.
|
|
32
|
+
*/
|
|
33
|
+
export interface Needs {
|
|
34
|
+
readonly kind: 'plugin' | 'tool';
|
|
35
|
+
/** The declared PLUGIN or TOOL name. */
|
|
36
|
+
readonly name: string;
|
|
37
|
+
/** The flag that grants it. */
|
|
38
|
+
readonly flag: '--allow-plugin' | '--allow-exec';
|
|
39
|
+
}
|
|
40
|
+
export declare function needsPlugin(name: string): Needs;
|
|
41
|
+
export declare function needsTool(name: string): Needs;
|
|
42
|
+
export interface SitefileErrorOptions extends ErrorOptions {
|
|
43
|
+
/** What must be allowed (permission errors only). */
|
|
44
|
+
readonly needs?: Needs;
|
|
45
|
+
}
|
|
26
46
|
export declare class SitefileError extends Error {
|
|
27
47
|
readonly code: ErrorCode;
|
|
28
48
|
readonly span: SourceSpan | undefined;
|
|
29
49
|
/** The message without the location prefix. */
|
|
30
50
|
readonly detail: string;
|
|
31
|
-
|
|
51
|
+
/** For a permission error (`E_UNSUPPORTED` at a PLUGIN or TOOL line): what must be allowed. Not part of the message. */
|
|
52
|
+
readonly needs: Needs | undefined;
|
|
53
|
+
constructor(code: ErrorCode, detail: string, span?: SourceSpan, options?: SitefileErrorOptions);
|
|
32
54
|
}
|
|
33
55
|
export declare function formatSpan(span: SourceSpan): string;
|
|
34
56
|
export declare function isSitefileError(e: unknown, code?: ErrorCode): e is SitefileError;
|
package/lib/src/errors.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
|
+
export function needsPlugin(name) {
|
|
2
|
+
return { kind: 'plugin', name, flag: '--allow-plugin' };
|
|
3
|
+
}
|
|
4
|
+
export function needsTool(name) {
|
|
5
|
+
return { kind: 'tool', name, flag: '--allow-exec' };
|
|
6
|
+
}
|
|
1
7
|
export class SitefileError extends Error {
|
|
2
8
|
code;
|
|
3
9
|
span;
|
|
4
10
|
/** The message without the location prefix. */
|
|
5
11
|
detail;
|
|
12
|
+
/** For a permission error (`E_UNSUPPORTED` at a PLUGIN or TOOL line): what must be allowed. Not part of the message. */
|
|
13
|
+
needs;
|
|
6
14
|
constructor(code, detail, span, options) {
|
|
7
15
|
super(span ? `${formatSpan(span)}: ${detail}` : detail, options);
|
|
8
16
|
this.name = 'SitefileError';
|
|
9
17
|
this.code = code;
|
|
10
18
|
this.span = span;
|
|
11
19
|
this.detail = detail;
|
|
20
|
+
this.needs = options?.needs;
|
|
12
21
|
}
|
|
13
22
|
}
|
|
14
23
|
export function formatSpan(span) {
|
package/lib/src/executor.d.ts
CHANGED
|
@@ -198,6 +198,8 @@ export interface ExportTarget {
|
|
|
198
198
|
readonly ociRoot?: string;
|
|
199
199
|
/** oci: site layer encoding (overrides EXPORT ... COMPRESSION; default gzip). */
|
|
200
200
|
readonly ociCompression?: OciCompression;
|
|
201
|
+
/** wbn: `gzip` wraps the bundle in gzip (overrides EXPORT ... COMPRESSION; default none). */
|
|
202
|
+
readonly wbnCompression?: OciCompression;
|
|
201
203
|
/** Extra OCI config labels. */
|
|
202
204
|
readonly imageLabels?: Readonly<Record<string, string>>;
|
|
203
205
|
/** Add org.opencontainers.image.source/revision from the git checkout (opt-in). */
|
|
@@ -231,6 +233,11 @@ export declare function buildToDirectory(options: BuildOptions & ExportTarget &
|
|
|
231
233
|
readonly outDir: string;
|
|
232
234
|
readonly force?: boolean;
|
|
233
235
|
}): Promise<ExportedBuild>;
|
|
236
|
+
/**
|
|
237
|
+
* The Sitefile's `EXPORT ... COMPRESSION` for an export in `format`: it belongs to the EXPORT's own format,
|
|
238
|
+
* so a `--format` override to another format does not inherit it (undefined: that format's default).
|
|
239
|
+
*/
|
|
240
|
+
export declare function sitefileCompression(plan: Pick<Plan, 'exportFormat' | 'exportCompression'>, format: ExportFormat): OciCompression | undefined;
|
|
234
241
|
/**
|
|
235
242
|
* The attestations (`--provenance`, `--sbom`) of an image built from `result`: in-toto statements
|
|
236
243
|
* whose subject is the image manifest. They never change the image itself.
|
package/lib/src/executor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { chmod, mkdir, mkdtemp, readdir, readFile, realpath, rm, writeFile } fro
|
|
|
2
2
|
import { statSync } from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { SitefileError } from './errors.js';
|
|
5
|
+
import { needsPlugin, needsTool, SitefileError } from './errors.js';
|
|
6
6
|
import { sha256 } from './digest.js';
|
|
7
7
|
import { FEED_ITEM } from './language/ast.js';
|
|
8
8
|
import { fragmentText, parseProgram } from './language/validate.js';
|
|
@@ -237,7 +237,7 @@ async function buildWith(options, tempDirs) {
|
|
|
237
237
|
vendor,
|
|
238
238
|
registry: {
|
|
239
239
|
...options.registry,
|
|
240
|
-
allowPrivateNetwork: options.registry?.allowPrivateNetwork ?? policy.allowPrivateNetwork,
|
|
240
|
+
allowPrivateNetwork: options.registry?.allowPrivateNetwork ?? policy.allowPrivateNetwork, network: options.registry?.network ?? policy.network,
|
|
241
241
|
},
|
|
242
242
|
warn,
|
|
243
243
|
span: b.span,
|
|
@@ -388,7 +388,7 @@ async function buildWith(options, tempDirs) {
|
|
|
388
388
|
vendor,
|
|
389
389
|
registry: {
|
|
390
390
|
...options.registry,
|
|
391
|
-
allowPrivateNetwork: options.registry?.allowPrivateNetwork ?? policy.allowPrivateNetwork,
|
|
391
|
+
allowPrivateNetwork: options.registry?.allowPrivateNetwork ?? policy.allowPrivateNetwork, network: options.registry?.network ?? policy.network,
|
|
392
392
|
},
|
|
393
393
|
warn,
|
|
394
394
|
})
|
|
@@ -492,7 +492,7 @@ async function exportBuild(result, format, options) {
|
|
|
492
492
|
if (format === 'oci') {
|
|
493
493
|
const img = buildOciImage(result.output, {
|
|
494
494
|
root: options.ociRoot ?? result.plan.exportRoot,
|
|
495
|
-
compression: options.ociCompression ?? result.plan
|
|
495
|
+
compression: options.ociCompression ?? sitefileCompression(result.plan, 'oci'),
|
|
496
496
|
platform: options.platform,
|
|
497
497
|
epoch: sourceDateEpoch(),
|
|
498
498
|
base: result.base,
|
|
@@ -517,10 +517,18 @@ async function exportBuild(result, format, options) {
|
|
|
517
517
|
const data = encodeTree(result.output, format, {
|
|
518
518
|
epoch: sourceDateEpoch(),
|
|
519
519
|
baseUrl: options.baseUrl ?? result.plan.exportBase,
|
|
520
|
+
...(format === 'wbn' ? { compression: options.wbnCompression ?? sitefileCompression(result.plan, 'wbn') } : {}),
|
|
520
521
|
});
|
|
521
522
|
const written = await exportFile(data, options.outDir, exportOptions);
|
|
522
523
|
return { ...result, format, outDir: written.outFile, bytes: written.bytes, fileDigest: sha256(data) };
|
|
523
524
|
}
|
|
525
|
+
/**
|
|
526
|
+
* The Sitefile's `EXPORT ... COMPRESSION` for an export in `format`: it belongs to the EXPORT's own format,
|
|
527
|
+
* so a `--format` override to another format does not inherit it (undefined: that format's default).
|
|
528
|
+
*/
|
|
529
|
+
export function sitefileCompression(plan, format) {
|
|
530
|
+
return plan.exportFormat === format ? plan.exportCompression : undefined;
|
|
531
|
+
}
|
|
524
532
|
/**
|
|
525
533
|
* The attestations (`--provenance`, `--sbom`) of an image built from `result`: in-toto statements
|
|
526
534
|
* whose subject is the image manifest. They never change the image itself.
|
|
@@ -1377,7 +1385,7 @@ function checkPlugins(p, options, warn) {
|
|
|
1377
1385
|
const allowed = (spec) => {
|
|
1378
1386
|
if (all || names.has(spec.name))
|
|
1379
1387
|
return;
|
|
1380
|
-
throw new SitefileError('E_UNSUPPORTED', `PLUGIN ${spec.name} runs JavaScript, which must be allowed explicitly: add --allow-plugin ${spec.name} (or --allow-plugins to allow every PLUGIN); WebAssembly plugins need no flag`, spec.span);
|
|
1388
|
+
throw new SitefileError('E_UNSUPPORTED', `PLUGIN ${spec.name} runs JavaScript, which must be allowed explicitly: add --allow-plugin ${spec.name} (or --allow-plugins to allow every PLUGIN); WebAssembly plugins need no flag`, spec.span, { needs: needsPlugin(spec.name) });
|
|
1381
1389
|
};
|
|
1382
1390
|
for (const spec of p.plugins)
|
|
1383
1391
|
if (!spec.path.endsWith('.wasm'))
|
|
@@ -1401,7 +1409,7 @@ async function resolveTools(p, options, warn) {
|
|
|
1401
1409
|
const out = [];
|
|
1402
1410
|
for (const t of p.tools) {
|
|
1403
1411
|
if (!allowed.has(t.name)) {
|
|
1404
|
-
throw new SitefileError('E_UNSUPPORTED', `TOOL ${t.name} runs an external program, which must be allowed explicitly: add --allow-exec ${t.name} (or --allow-exec ${t.name}=/absolute/path)`, t.span);
|
|
1412
|
+
throw new SitefileError('E_UNSUPPORTED', `TOOL ${t.name} runs an external program, which must be allowed explicitly: add --allow-exec ${t.name} (or --allow-exec ${t.name}=/absolute/path)`, t.span, { needs: needsTool(t.name) });
|
|
1405
1413
|
}
|
|
1406
1414
|
const tool = await resolveTool(t.name, allowed.get(t.name), process.env['PATH'], t.span);
|
|
1407
1415
|
options.onNotice?.(`tool ${t.name} -> ${tool.path}`);
|
|
@@ -1547,7 +1555,7 @@ async function produceTransform(op, env, opId) {
|
|
|
1547
1555
|
})
|
|
1548
1556
|
: undefined;
|
|
1549
1557
|
const call = begin(env.profiler, `TRANSFORM ${op.plugin} ${dest.path}`, 'transform', { plugin: op.plugin, tool: tool !== undefined, dest: dest.path });
|
|
1550
|
-
let result = cached ? await env.cache.get(cached) : undefined;
|
|
1558
|
+
let result = cached ? await env.cache.get(cached, op.plugin) : undefined;
|
|
1551
1559
|
const cacheState = !cached ? 'off' : result ? 'hit' : 'miss';
|
|
1552
1560
|
if (!result) {
|
|
1553
1561
|
if (tool) {
|
|
@@ -1560,7 +1568,7 @@ async function produceTransform(op, env, opId) {
|
|
|
1560
1568
|
result = await env.plugins.call(op.plugin, request, op.span);
|
|
1561
1569
|
}
|
|
1562
1570
|
if (cached && (tool || env.plugins.cacheable(op.plugin)))
|
|
1563
|
-
await env.cache.put(cached, result);
|
|
1571
|
+
await env.cache.put(cached, result, op.plugin);
|
|
1564
1572
|
}
|
|
1565
1573
|
call.end({ cache: cacheState });
|
|
1566
1574
|
if (result.kind === 'file') {
|
|
@@ -1722,7 +1730,7 @@ async function transformFiles(op, env, dir, ctx) {
|
|
|
1722
1730
|
cache: cached !== undefined ? 'miss' : 'off',
|
|
1723
1731
|
};
|
|
1724
1732
|
if (cached !== undefined) {
|
|
1725
|
-
const hit = await env.cache.get(cached);
|
|
1733
|
+
const hit = await env.cache.get(cached, op.plugin);
|
|
1726
1734
|
if (hit) {
|
|
1727
1735
|
span.start();
|
|
1728
1736
|
span.end({ cache: 'hit' });
|
|
@@ -1766,7 +1774,7 @@ async function transformFiles(op, env, dir, ctx) {
|
|
|
1766
1774
|
throw namingFile(job.error.error, job.label);
|
|
1767
1775
|
const result = job.result;
|
|
1768
1776
|
if (job.cache === 'miss' && store())
|
|
1769
|
-
await env.cache.put(job.cached, result);
|
|
1777
|
+
await env.cache.put(job.cached, result, op.plugin);
|
|
1770
1778
|
const files = [];
|
|
1771
1779
|
if (result.kind === 'file') {
|
|
1772
1780
|
if (job.dest.trailingSlash) {
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { type WriterOptions } from './archive-writers.js';
|
|
2
|
-
import type { ExportFormat } from './language/ast.js';
|
|
2
|
+
import type { ExportFormat, OciCompression } from './language/ast.js';
|
|
3
3
|
import type { Tree } from './tree.js';
|
|
4
4
|
export type ArchiveFormat = Exclude<ExportFormat, 'directory' | 'oci'>;
|
|
5
5
|
/** Single-file formats encoded by {@link encodeTree}. `oci` is handled separately (layout directory or OCI archive). */
|
|
6
6
|
export declare function isArchiveFormat(f: ExportFormat): f is ArchiveFormat;
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Default output path relative to the context: `dist`, `dist.zip`, `dist.tar`, `dist.tar.gz`, `dist.wbn`
|
|
9
|
+
* (`dist.wbn.gz` for a gzipped bundle), `dist.oci.tar`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function defaultOutName(format: ExportFormat, compression?: OciCompression): string;
|
|
9
12
|
export interface EncodeOptions extends WriterOptions {
|
|
10
13
|
/** Required for `wbn`: absolute https URL ending in `/`. */
|
|
11
14
|
readonly baseUrl?: string | undefined;
|
|
15
|
+
/** `wbn`: `gzip` wraps the finished bundle in gzip (the shared deterministic encoder); default none. */
|
|
16
|
+
readonly compression?: OciCompression | undefined;
|
|
12
17
|
}
|
|
13
18
|
/** Serialize a tree in an archive format. Deterministic: see archive-writers.ts and wbn.ts. */
|
|
14
19
|
export declare function encodeTree(tree: Tree, format: ArchiveFormat, options?: EncodeOptions): Uint8Array;
|
|
15
20
|
/** The export format implied by an output file name, if it has an archive extension. */
|
|
16
21
|
export declare function formatFromExtension(out: string): ExportFormat | undefined;
|
|
22
|
+
/** The compression implied by an output file name for `format`: `gzip` for a wbn written to `*.wbn.gz`. */
|
|
23
|
+
export declare function compressionFromExtension(out: string, format: ExportFormat): OciCompression | undefined;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { writeTar, writeTgz, writeZip } from './archive-writers.js';
|
|
2
|
+
import { gzip } from './deflate.js';
|
|
2
3
|
import { writeWebBundle } from './wbn.js';
|
|
3
4
|
/** Single-file formats encoded by {@link encodeTree}. `oci` is handled separately (layout directory or OCI archive). */
|
|
4
5
|
export function isArchiveFormat(f) {
|
|
5
6
|
return f !== 'directory' && f !== 'oci';
|
|
6
7
|
}
|
|
7
|
-
/**
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Default output path relative to the context: `dist`, `dist.zip`, `dist.tar`, `dist.tar.gz`, `dist.wbn`
|
|
10
|
+
* (`dist.wbn.gz` for a gzipped bundle), `dist.oci.tar`.
|
|
11
|
+
*/
|
|
12
|
+
export function defaultOutName(format, compression) {
|
|
9
13
|
switch (format) {
|
|
10
14
|
case 'directory':
|
|
11
15
|
return 'dist';
|
|
@@ -16,7 +20,7 @@ export function defaultOutName(format) {
|
|
|
16
20
|
case 'tgz':
|
|
17
21
|
return 'dist.tar.gz';
|
|
18
22
|
case 'wbn':
|
|
19
|
-
return 'dist.wbn';
|
|
23
|
+
return compression === 'gzip' ? 'dist.wbn.gz' : 'dist.wbn';
|
|
20
24
|
case 'oci':
|
|
21
25
|
return 'dist.oci.tar';
|
|
22
26
|
}
|
|
@@ -30,8 +34,11 @@ export function encodeTree(tree, format, options = {}) {
|
|
|
30
34
|
return writeTar(tree, options);
|
|
31
35
|
case 'tgz':
|
|
32
36
|
return writeTgz(tree, options);
|
|
33
|
-
case 'wbn':
|
|
34
|
-
|
|
37
|
+
case 'wbn': {
|
|
38
|
+
// The bundle is written first, unchanged; gzip only wraps its bytes (header mtime 0, see deflate.ts).
|
|
39
|
+
const bundle = writeWebBundle(tree, options.baseUrl);
|
|
40
|
+
return options.compression === 'gzip' ? gzip(bundle) : bundle;
|
|
41
|
+
}
|
|
35
42
|
}
|
|
36
43
|
}
|
|
37
44
|
/** The export format implied by an output file name, if it has an archive extension. */
|
|
@@ -45,8 +52,12 @@ export function formatFromExtension(out) {
|
|
|
45
52
|
return 'tar';
|
|
46
53
|
if (name.endsWith('.zip'))
|
|
47
54
|
return 'zip';
|
|
48
|
-
if (name.endsWith('.wbn'))
|
|
55
|
+
if (name.endsWith('.wbn') || name.endsWith('.wbn.gz'))
|
|
49
56
|
return 'wbn';
|
|
50
57
|
return undefined;
|
|
51
58
|
}
|
|
59
|
+
/** The compression implied by an output file name for `format`: `gzip` for a wbn written to `*.wbn.gz`. */
|
|
60
|
+
export function compressionFromExtension(out, format) {
|
|
61
|
+
return format === 'wbn' && out.toLowerCase().endsWith('.wbn.gz') ? 'gzip' : undefined;
|
|
62
|
+
}
|
|
52
63
|
//# sourceMappingURL=export-formats.js.map
|
package/lib/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { SitefileError, isSitefileError, formatSpan, type ErrorCode, type SourceSpan } from './errors.js';
|
|
1
|
+
export { SitefileError, isSitefileError, formatSpan, needsPlugin, needsTool, type ErrorCode, type Needs, type SourceSpan } from './errors.js';
|
|
2
2
|
export type * from './language/ast.js';
|
|
3
3
|
export { parseSitefile } from './language/parser.js';
|
|
4
4
|
export { validate, parseProgram } from './language/validate.js';
|
|
@@ -9,4 +9,5 @@ export { BuildMemo, type MemoStats } from './memo.js';
|
|
|
9
9
|
export { Profiler } from './profile.js';
|
|
10
10
|
export { Tree, makeFile, type FileNode, type ConflictPolicy } from './tree.js';
|
|
11
11
|
export { DEFAULT_ARCHIVE_LIMITS, DEFAULT_FETCH_POLICY, type ArchiveLimits, type FetchPolicy, type Resource } from './resources/types.js';
|
|
12
|
+
export { networkFromEnv, mergeNetwork, networkProblem, hostMatches, type NetworkSettings } from './resources/network.js';
|
|
12
13
|
export { extractArchive } from './resources/archive.js';
|
package/lib/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { SitefileError, isSitefileError, formatSpan } from './errors.js';
|
|
1
|
+
export { SitefileError, isSitefileError, formatSpan, needsPlugin, needsTool } from './errors.js';
|
|
2
2
|
export { parseSitefile } from './language/parser.js';
|
|
3
3
|
export { validate, parseProgram } from './language/validate.js';
|
|
4
4
|
export { plan } from './planner.js';
|
|
@@ -8,5 +8,6 @@ export { BuildMemo } from './memo.js';
|
|
|
8
8
|
export { Profiler } from './profile.js';
|
|
9
9
|
export { Tree, makeFile } from './tree.js';
|
|
10
10
|
export { DEFAULT_ARCHIVE_LIMITS, DEFAULT_FETCH_POLICY } from './resources/types.js';
|
|
11
|
+
export { networkFromEnv, mergeNetwork, networkProblem, hostMatches } from './resources/network.js';
|
|
11
12
|
export { extractArchive } from './resources/archive.js';
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
|
@@ -201,13 +201,20 @@ export interface ExportInstruction {
|
|
|
201
201
|
readonly base?: Operand;
|
|
202
202
|
/** `AT </srv/www>`: where the files live inside the layer (`oci` only). */
|
|
203
203
|
readonly at?: Operand;
|
|
204
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* `COMPRESSION gzip|none` (`oci` and `wbn` only): how the oci site layer is stored (default gzip),
|
|
206
|
+
* or whether the whole Web Bundle is gzipped (default none).
|
|
207
|
+
*/
|
|
205
208
|
readonly compression?: {
|
|
206
209
|
readonly value: OciCompression;
|
|
207
210
|
readonly span: SourceSpan;
|
|
208
211
|
};
|
|
209
212
|
}
|
|
210
|
-
/**
|
|
213
|
+
/**
|
|
214
|
+
* `EXPORT ... COMPRESSION`: for `oci`, the site layer encoding, `gzip` (tar+gzip, the default) or `none`
|
|
215
|
+
* (plain tar); for `wbn`, `gzip` wraps the bundle in gzip (the family's `.wbn.gz`) and `none` (the default)
|
|
216
|
+
* writes the plain bundle.
|
|
217
|
+
*/
|
|
211
218
|
export type OciCompression = 'gzip' | 'none';
|
|
212
219
|
export declare const OCI_COMPRESSIONS: readonly OciCompression[];
|
|
213
220
|
/** Parse a compression name (case-insensitive); undefined if unknown. */
|
package/lib/src/outdated.js
CHANGED
|
@@ -160,7 +160,7 @@ export async function outdated(options) {
|
|
|
160
160
|
return entry({ kind, name, source, pin, pinnedBy, latest: null, status: 'digest-only', newerTag: null, error: null });
|
|
161
161
|
}
|
|
162
162
|
const tag = ref.tag ?? 'latest';
|
|
163
|
-
const client = makeClient(registry, { ...options.registry, allowPrivateNetwork: options.registry?.allowPrivateNetwork ?? policy.allowPrivateNetwork });
|
|
163
|
+
const client = makeClient(registry, { ...options.registry, allowPrivateNetwork: options.registry?.allowPrivateNetwork ?? policy.allowPrivateNetwork, network: options.registry?.network ?? policy.network });
|
|
164
164
|
const latest = (await client.getManifest(ref.name, tag)).digest;
|
|
165
165
|
let newerTag = null;
|
|
166
166
|
if (versionTag(tag)) {
|
package/lib/src/planner.d.ts
CHANGED
|
@@ -259,7 +259,7 @@ export interface Plan {
|
|
|
259
259
|
readonly exportBase: string | undefined;
|
|
260
260
|
/** Evaluated `AT` clause of EXPORT ... AS oci: the root directory inside the image layer. */
|
|
261
261
|
readonly exportRoot: string | undefined;
|
|
262
|
-
/** `COMPRESSION` clause of EXPORT ... AS oci (undefined: gzip). */
|
|
262
|
+
/** `COMPRESSION` clause of EXPORT ... AS oci or wbn (undefined: gzip for oci, none for wbn). */
|
|
263
263
|
readonly exportCompression: OciCompression | undefined;
|
|
264
264
|
/** REDIRECT lines in source order (any stage); EXPORT writes them to `/_redirects`. */
|
|
265
265
|
readonly redirects: readonly RedirectSpec[];
|
package/lib/src/planner.js
CHANGED
|
@@ -240,8 +240,8 @@ export function plan(program, buildArgs = {}, argSource = (n) => `--build-arg ${
|
|
|
240
240
|
if (ins.at && ins.format !== 'oci')
|
|
241
241
|
throw new SitefileError('E_VALIDATION', 'EXPORT ... AT applies only to AS oci', ins.at.span);
|
|
242
242
|
exportRoot = ins.at ? normalizeVirtual(evaluateOperand(ins.at, vars), ins.at.span, 'EXPORT AT path').path : undefined;
|
|
243
|
-
if (ins.compression && ins.format !== 'oci')
|
|
244
|
-
throw new SitefileError('E_VALIDATION', 'EXPORT ... COMPRESSION applies only to AS oci', ins.compression.span);
|
|
243
|
+
if (ins.compression && ins.format !== 'oci' && ins.format !== 'wbn')
|
|
244
|
+
throw new SitefileError('E_VALIDATION', 'EXPORT ... COMPRESSION applies only to AS oci and AS wbn', ins.compression.span);
|
|
245
245
|
exportCompression = ins.compression?.value;
|
|
246
246
|
break;
|
|
247
247
|
}
|