@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,95 @@
|
|
|
1
|
+
import { runCommand, usageError } from './common.js';
|
|
2
|
+
import { imageCommands, imageSubcommands } from './images.js';
|
|
3
|
+
import { registryCommands } from './registry.js';
|
|
4
|
+
import { serverCommands, serverSubcommands } from './servers.js';
|
|
5
|
+
import { cacheSubcommands } from './cache.js';
|
|
6
|
+
import { vendorCommands } from './vendor.js';
|
|
7
|
+
import { outdatedCommands } from './outdated.js';
|
|
8
|
+
import { bakeCommands } from './bake.js';
|
|
9
|
+
/**
|
|
10
|
+
* Image-store and site-server commands. They live outside cli.ts' build/validate/inspect
|
|
11
|
+
* parser: each has its own options and `--help`.
|
|
12
|
+
*/
|
|
13
|
+
const TOP = new Map([...imageCommands, ...serverCommands, ...registryCommands, ...vendorCommands, ...outdatedCommands, ...bakeCommands].filter(([k]) => !k.includes(' ')));
|
|
14
|
+
export const EXTRA_COMMANDS = [...TOP.keys(), 'image', 'server', 'cache', 'lsp'];
|
|
15
|
+
/**
|
|
16
|
+
* The language server exists only in the Rust implementation (a tooling
|
|
17
|
+
* exception, docs/RUST.md): the language is the same in both, the server is
|
|
18
|
+
* an editor tool. Here `sitefile lsp` is a usage error that points there.
|
|
19
|
+
*/
|
|
20
|
+
export const LSP_UNAVAILABLE = "'sitefile lsp' (the language server) is provided by the Rust implementation: build it with 'cd rust && cargo build --release' and run 'rust/target/release/sitefile lsp' (see docs/LSP.md)";
|
|
21
|
+
export const EXTRA_HELP = `
|
|
22
|
+
Images (local store: $SITEFILE_HOME or ~/.sitefile):
|
|
23
|
+
sitefile build [context] -t name[:tag] also store the result as an image (repeatable -t)
|
|
24
|
+
sitefile images [--json] [--digests] [-q] list images (alias: image ls)
|
|
25
|
+
sitefile image inspect <ref> [--json] show an image manifest
|
|
26
|
+
sitefile image prune delete unreferenced manifests and blobs
|
|
27
|
+
sitefile image diff <a> <b> added/removed/changed paths
|
|
28
|
+
sitefile tag <src> <dst> add a name:tag
|
|
29
|
+
sitefile rmi [-f] <ref|id>... remove tags / images
|
|
30
|
+
sitefile save <ref> -o out[.zip|.tar|.tar.gz|.wbn] export an image
|
|
31
|
+
sitefile push <registry/name[:tag]> push a (tagged) image to an OCI registry
|
|
32
|
+
sitefile pull <[registry/]name[:tag][@digest]> pull an image (a bare name means Docker Hub)
|
|
33
|
+
|
|
34
|
+
Variants:
|
|
35
|
+
sitefile bake [targets...] [-f file] [--print] build several targets (matrix, groups) from
|
|
36
|
+
sitefile.bake.yaml; --print shows the plan
|
|
37
|
+
|
|
38
|
+
Offline builds:
|
|
39
|
+
sitefile vendor [context] [--vendor-dir dir] download remote imports and the BASE image into
|
|
40
|
+
vendor/sitefile; builds then read them from there
|
|
41
|
+
|
|
42
|
+
Pins:
|
|
43
|
+
sitefile outdated [context] [--json] compare remote imports and the BASE image with
|
|
44
|
+
their pins (never changes anything)
|
|
45
|
+
|
|
46
|
+
Build cache (TRANSFORM results: $SITEFILE_HOME/cache/transform):
|
|
47
|
+
sitefile cache info [--json] location, entries, size
|
|
48
|
+
sitefile cache prune [--older-than <days>] delete cached results
|
|
49
|
+
sitefile cache prune --max-size <size> shrink evictable data (LRU) to <size>
|
|
50
|
+
sitefile cache du [--json] size of every cache and the store (alias: cache ls)
|
|
51
|
+
Evictable data over $SITEFILE_CACHE_MAX (default 1GiB, 0 = no limit) is deleted after each build.
|
|
52
|
+
|
|
53
|
+
Serving sites (detached servers are tracked by name):
|
|
54
|
+
sitefile serve [--name n] [-p [host:]port] [-d] [--spa] <ref|./dir>
|
|
55
|
+
sitefile serve --watch [-p [host:]port] [ctx] build in memory, serve, rebuild + live reload
|
|
56
|
+
sitefile servers [-a] [--json] list servers (alias: server ls)
|
|
57
|
+
sitefile server stop|rm [-f]|restart|logs [-f] <name|id>
|
|
58
|
+
|
|
59
|
+
Editor support:
|
|
60
|
+
sitefile lsp [--stdio] language server over stdio (Rust build only; docs/LSP.md)
|
|
61
|
+
|
|
62
|
+
Run 'sitefile <command> --help' for the options of a command.
|
|
63
|
+
`;
|
|
64
|
+
export function isExtraCommand(name) {
|
|
65
|
+
return name !== undefined && EXTRA_COMMANDS.includes(name);
|
|
66
|
+
}
|
|
67
|
+
const GROUPS = new Map([
|
|
68
|
+
['image', { subs: imageSubcommands, usage: 'image <ls|inspect|tag|rm|save|prune|diff>' }],
|
|
69
|
+
['server', { subs: serverSubcommands, usage: 'server <ls|stop|rm|logs|restart>' }],
|
|
70
|
+
['cache', { subs: cacheSubcommands, usage: 'cache <info|prune|du>' }],
|
|
71
|
+
]);
|
|
72
|
+
export async function runExtraCommand(argv) {
|
|
73
|
+
const [name, ...rest] = argv;
|
|
74
|
+
if (name === 'lsp')
|
|
75
|
+
return usageError(LSP_UNAVAILABLE);
|
|
76
|
+
const group = name === undefined ? undefined : GROUPS.get(name);
|
|
77
|
+
if (group) {
|
|
78
|
+
const [sub, ...args] = rest;
|
|
79
|
+
const cmd = sub === undefined ? undefined : group.subs.get(sub);
|
|
80
|
+
if (!cmd) {
|
|
81
|
+
if (sub === undefined || sub === '--help' || sub === '-h') {
|
|
82
|
+
const lines = [...new Set(group.subs.values())].map((c) => ` ${c.usage.padEnd(44)} ${c.summary}`);
|
|
83
|
+
process.stdout.write(`Usage: sitefile ${group.usage} ...\n\n${lines.join('\n')}\n`);
|
|
84
|
+
return sub === undefined ? 2 : 0;
|
|
85
|
+
}
|
|
86
|
+
return usageError(`unknown ${name} command '${sub}'`);
|
|
87
|
+
}
|
|
88
|
+
return runCommand(cmd, args);
|
|
89
|
+
}
|
|
90
|
+
const cmd = name === undefined ? undefined : TOP.get(name);
|
|
91
|
+
if (!cmd)
|
|
92
|
+
return usageError(`unknown command '${name ?? ''}'`);
|
|
93
|
+
return runCommand(cmd, rest);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type OutdatedReport } from '../outdated.js';
|
|
2
|
+
import { type Command } from './common.js';
|
|
3
|
+
/** The text report: a table, one line per error, and a summary line. */
|
|
4
|
+
export declare function formatOutdated(r: OutdatedReport): string;
|
|
5
|
+
export declare const outdatedCommands: ReadonlyMap<string, Command>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { insecureRegistriesFromEnv } from '../base-image.js';
|
|
2
|
+
import { collectBuildArgs } from '../build-args.js';
|
|
3
|
+
import { outdated } from '../outdated.js';
|
|
4
|
+
import { loadSecrets } from '../secrets.js';
|
|
5
|
+
import { DEFAULT_FETCH_POLICY } from '../resources/types.js';
|
|
6
|
+
import { shortId } from '../store.js';
|
|
7
|
+
import { requireArgs, str, strs, table, usage } from './common.js';
|
|
8
|
+
function positive(raw, flag) {
|
|
9
|
+
if (raw === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
const n = Number(raw);
|
|
12
|
+
if (!Number.isSafeInteger(n) || n <= 0)
|
|
13
|
+
usage(`${flag} expects a positive integer`);
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
/** The order statuses are counted in the summary line. */
|
|
17
|
+
const ORDER = ['changed', 'unpinned', 'error', 'digest-only', 'current'];
|
|
18
|
+
/** The text report: a table, one line per error, and a summary line. */
|
|
19
|
+
export function formatOutdated(r) {
|
|
20
|
+
if (r.inputs.length === 0)
|
|
21
|
+
return 'No remote inputs: the Sitefile has no remote IMPORT, no registry FROM image and no BASE image\n';
|
|
22
|
+
const short = (d) => (d === null ? '-' : shortId(d));
|
|
23
|
+
const rows = [['KIND', 'NAME', 'SOURCE', 'PINNED', 'BY', 'LATEST', 'STATUS', 'NEWER TAG']];
|
|
24
|
+
for (const i of r.inputs)
|
|
25
|
+
rows.push([i.kind, i.name, i.source, short(i.pin), i.pinnedBy ?? '-', short(i.latest), i.status, i.newerTag ?? '-']);
|
|
26
|
+
let out = table(rows);
|
|
27
|
+
for (const i of r.inputs)
|
|
28
|
+
if (i.error)
|
|
29
|
+
out += `${i.name}: error[${i.error.code}]: ${i.error.message}\n`;
|
|
30
|
+
const n = r.inputs.length;
|
|
31
|
+
const counts = ORDER.filter((s) => r.summary[s] > 0).map((s) => `${r.summary[s]} ${s === 'error' && r.summary[s] !== 1 ? 'errors' : s}`);
|
|
32
|
+
return `${out}${n} remote input${n === 1 ? '' : 's'}: ${counts.join(', ')}\n`;
|
|
33
|
+
}
|
|
34
|
+
const outdatedCommand = {
|
|
35
|
+
name: 'outdated',
|
|
36
|
+
summary: 'check whether pinned remote imports and BASE images have changed upstream',
|
|
37
|
+
usage: 'outdated [context] [options]',
|
|
38
|
+
description: "Report, for every remote IMPORT and the BASE image, what it is pinned to (INTEGRITY, @sha256: or\nSitefile.lock) and what it resolves to now. Never changes anything: use 'sitefile build --update-lock'\nto accept new content. An https URL is fetched again and its sha256 compared (there is no notion of a\nnewer version of a URL); a BASE tag is resolved again (changed = the tag moved), and for a version-like\ntag (1.2, v1.2.3-alpine) the registry's tag list is searched for a greater tag of the same shape.",
|
|
39
|
+
flags: [
|
|
40
|
+
'-f, --file <path> Sitefile path (default: <context>/Sitefile)',
|
|
41
|
+
'--json machine-readable output',
|
|
42
|
+
'--build-arg <NAME=value> set an ARG (repeatable); --build-arg NAME reads $NAME',
|
|
43
|
+
'--build-arg-file <path> read ARGs from a .json, .yaml/.yml or .env file (repeatable)',
|
|
44
|
+
'--secret id=<id>,env=<VAR> a build secret for IMPORT ... SECRET (or id=<id>,src=<file>; repeatable)',
|
|
45
|
+
'--no-lockfile ignore Sitefile.lock (only INTEGRITY and @sha256: count as pins)',
|
|
46
|
+
'--allow-http permit plain http:// imports',
|
|
47
|
+
'--allow-private-network permit loopback/private network addresses',
|
|
48
|
+
`--max-download-bytes <n> per-resource size limit (default ${DEFAULT_FETCH_POLICY.maxBytes})`,
|
|
49
|
+
`--timeout <ms> per-resource fetch timeout (default ${DEFAULT_FETCH_POLICY.timeoutMs})`,
|
|
50
|
+
'--base <image> check this BASE image (overrides EXPORT ... AS oci BASE)',
|
|
51
|
+
'--insecure-registry <h> allow plain http to registry host[:port] (repeatable)',
|
|
52
|
+
],
|
|
53
|
+
options: {
|
|
54
|
+
file: { type: 'string', short: 'f' },
|
|
55
|
+
json: { type: 'boolean' },
|
|
56
|
+
'build-arg': { type: 'string', multiple: true },
|
|
57
|
+
'build-arg-file': { type: 'string', multiple: true },
|
|
58
|
+
secret: { type: 'string', multiple: true },
|
|
59
|
+
'no-lockfile': { type: 'boolean' },
|
|
60
|
+
'allow-http': { type: 'boolean' },
|
|
61
|
+
'allow-private-network': { type: 'boolean' },
|
|
62
|
+
'max-download-bytes': { type: 'string' },
|
|
63
|
+
timeout: { type: 'string' },
|
|
64
|
+
base: { type: 'string' },
|
|
65
|
+
'insecure-registry': { type: 'string', multiple: true },
|
|
66
|
+
},
|
|
67
|
+
async run(v, pos) {
|
|
68
|
+
requireArgs(pos, 0, 1, 'a context directory');
|
|
69
|
+
const maxBytes = positive(str(v['max-download-bytes']), '--max-download-bytes');
|
|
70
|
+
const timeoutMs = positive(str(v.timeout), '--timeout');
|
|
71
|
+
const file = str(v.file);
|
|
72
|
+
const base = str(v.base);
|
|
73
|
+
const r = await outdated({
|
|
74
|
+
contextDir: pos[0] ?? '.',
|
|
75
|
+
...(file ? { sitefile: file } : {}),
|
|
76
|
+
buildArgs: await collectBuildArgs(strs(v['build-arg-file']), strs(v['build-arg'])),
|
|
77
|
+
secrets: await loadSecrets(strs(v.secret)),
|
|
78
|
+
fetchPolicy: {
|
|
79
|
+
allowHttp: !!v['allow-http'],
|
|
80
|
+
allowPrivateNetwork: !!v['allow-private-network'],
|
|
81
|
+
...(maxBytes ? { maxBytes } : {}),
|
|
82
|
+
...(timeoutMs ? { timeoutMs } : {}),
|
|
83
|
+
},
|
|
84
|
+
...(v['no-lockfile'] ? { lockfile: false } : {}),
|
|
85
|
+
...(base !== undefined ? { baseImage: base } : {}),
|
|
86
|
+
registry: { insecureRegistries: insecureRegistriesFromEnv(strs(v['insecure-registry'])) },
|
|
87
|
+
onWarning: (m) => process.stderr.write(`warning: ${m}\n`),
|
|
88
|
+
});
|
|
89
|
+
process.stdout.write(v.json ? JSON.stringify(r, null, 2) + '\n' : formatOutdated(r));
|
|
90
|
+
return 0;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
export const outdatedCommands = new Map([['outdated', outdatedCommand]]);
|
|
94
|
+
//# sourceMappingURL=outdated.js.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { insecureRegistriesFromEnv, makeClient } from '../base-image.js';
|
|
2
|
+
import { SitefileError } from '../errors.js';
|
|
3
|
+
import { parseRef, refKey, withDefaultRegistry, canonicalRef } from '../ref.js';
|
|
4
|
+
import { pullImage, pushImage } from '../registry-ops.js';
|
|
5
|
+
import { shortId, Store } from '../store.js';
|
|
6
|
+
import { requireArgs, str, strs, usage } from './common.js';
|
|
7
|
+
async function readStdin() {
|
|
8
|
+
const chunks = [];
|
|
9
|
+
let n = 0;
|
|
10
|
+
for await (const c of process.stdin) {
|
|
11
|
+
n += c.length;
|
|
12
|
+
if (n > 64 * 1024)
|
|
13
|
+
usage('--password-stdin: input is too long');
|
|
14
|
+
chunks.push(c);
|
|
15
|
+
}
|
|
16
|
+
return Buffer.concat(chunks).toString('utf8').replace(/\r?\n$/, '');
|
|
17
|
+
}
|
|
18
|
+
function positive(raw, flag) {
|
|
19
|
+
if (raw === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
const n = Number(raw);
|
|
22
|
+
if (!Number.isSafeInteger(n) || n <= 0)
|
|
23
|
+
usage(`${flag} expects a positive integer`);
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
26
|
+
const COMMON_FLAGS = [
|
|
27
|
+
'--insecure-registry <host[:port]> allow plain http to this registry (repeatable; localhost always may;',
|
|
28
|
+
' also $SITEFILE_INSECURE_REGISTRIES, comma-separated)',
|
|
29
|
+
'--username <name> registry user (with --password-stdin); otherwise credentials come from',
|
|
30
|
+
' $SITEFILE_REGISTRY_AUTH or ~/.docker/config.json (auths, credHelpers, credsStore)',
|
|
31
|
+
'--password-stdin read the password from stdin',
|
|
32
|
+
'--allow-private-network let redirects/token servers on other hosts resolve to private addresses',
|
|
33
|
+
'--timeout <ms> per-request idle timeout (default 30000)',
|
|
34
|
+
];
|
|
35
|
+
const COMMON_OPTIONS = {
|
|
36
|
+
'insecure-registry': { type: 'string', multiple: true },
|
|
37
|
+
username: { type: 'string' },
|
|
38
|
+
'password-stdin': { type: 'boolean' },
|
|
39
|
+
'allow-private-network': { type: 'boolean' },
|
|
40
|
+
timeout: { type: 'string' },
|
|
41
|
+
};
|
|
42
|
+
async function clientOptions(v) {
|
|
43
|
+
let explicit;
|
|
44
|
+
const username = str(v.username);
|
|
45
|
+
if (v['password-stdin']) {
|
|
46
|
+
if (!username)
|
|
47
|
+
usage('--password-stdin needs --username');
|
|
48
|
+
explicit = { username, password: await readStdin() };
|
|
49
|
+
}
|
|
50
|
+
else if (username) {
|
|
51
|
+
usage('--username needs --password-stdin (passwords are not accepted on the command line)');
|
|
52
|
+
}
|
|
53
|
+
const timeoutMs = positive(str(v.timeout), '--timeout');
|
|
54
|
+
const maxBlob = positive(str(v['max-download-bytes']), '--max-download-bytes');
|
|
55
|
+
return {
|
|
56
|
+
explicit,
|
|
57
|
+
options: {
|
|
58
|
+
insecureRegistries: insecureRegistriesFromEnv(strs(v['insecure-registry'])),
|
|
59
|
+
allowPrivateNetwork: !!v['allow-private-network'],
|
|
60
|
+
limits: { ...(timeoutMs ? { timeoutMs } : {}), ...(maxBlob ? { maxBlobBytes: maxBlob } : {}) },
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const push = {
|
|
65
|
+
name: 'push',
|
|
66
|
+
summary: 'push an image to a registry',
|
|
67
|
+
usage: 'push <registry/name[:tag]> [options]',
|
|
68
|
+
description: 'Push the local image of that name to an OCI registry (distribution spec v1.1): blobs the registry lacks\nare uploaded (layers of a BASE image are mounted from its repository when it is on the same registry),\nthen the manifest. Name the image first: sitefile tag blog:1 localhost:5000/blog:1.\nThe registry host is always explicit (there is no Docker Hub default for push: use docker.io/user/name).\nPlain http is allowed for localhost and --insecure-registry hosts only.',
|
|
69
|
+
flags: COMMON_FLAGS,
|
|
70
|
+
options: COMMON_OPTIONS,
|
|
71
|
+
async run(v, pos) {
|
|
72
|
+
requireArgs(pos, 1, 1, 'an image reference');
|
|
73
|
+
const input = pos[0];
|
|
74
|
+
const ref0 = parseRef(input);
|
|
75
|
+
if (ref0.registry === undefined) {
|
|
76
|
+
usage(`'${input}' has no registry host; tag the image first, e.g. sitefile tag ${input} localhost:5000/${input}`);
|
|
77
|
+
}
|
|
78
|
+
const ref = canonicalRef(ref0);
|
|
79
|
+
const store = await Store.open();
|
|
80
|
+
const refs = await store.refs();
|
|
81
|
+
const id = ref.digest !== undefined && ref.tag === undefined ? ref.digest : refs[refKey({ ...ref, digest: undefined })];
|
|
82
|
+
if (id === undefined || !(await store.hasImage(id))) {
|
|
83
|
+
throw new SitefileError('E_NOT_FOUND', `no local image ${refKey({ ...ref, digest: undefined })}; tag one first: sitefile tag <image> ${refKey({ ...ref, digest: undefined })}`);
|
|
84
|
+
}
|
|
85
|
+
const { options, explicit } = await clientOptions(v);
|
|
86
|
+
const client = makeClient(ref.registry, { ...options, explicitCredentials: explicit });
|
|
87
|
+
process.stdout.write(`The push refers to repository [${ref.registry}/${ref.name}]\n`);
|
|
88
|
+
const r = await pushImage(store, client, id, ref, { onEvent: (m) => process.stdout.write(`${m}\n`) });
|
|
89
|
+
process.stdout.write(`${ref.tag ?? 'digest'}: digest: ${r.digest} size: ${r.size}\n`);
|
|
90
|
+
return 0;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const pull = {
|
|
94
|
+
name: 'pull',
|
|
95
|
+
summary: 'pull an image from a registry',
|
|
96
|
+
usage: 'pull <[registry/]name[:tag][@sha256:...]> [options]',
|
|
97
|
+
description: 'Pull an image into the local store and tag it name:tag as written. A name without a registry host\nmeans Docker Hub (nginx -> docker.io/library/nginx). For a multi-platform image the --platform variant is\nchosen (default linux/amd64). Every blob is sha256-verified while it streams in, and a manifest fetched by\ndigest must hash to it. The result is stored as-is (Docker or OCI manifests); only images built by\nsitefile can be served or saved. Pin with @sha256:... for reproducibility.',
|
|
98
|
+
flags: ['--platform <os/arch> variant to choose from an image index (default linux/amd64)', '--max-download-bytes <n> per-blob size limit (default 2 GiB)', ...COMMON_FLAGS],
|
|
99
|
+
options: { ...COMMON_OPTIONS, platform: { type: 'string' }, 'max-download-bytes': { type: 'string' } },
|
|
100
|
+
async run(v, pos) {
|
|
101
|
+
requireArgs(pos, 1, 1, 'an image reference');
|
|
102
|
+
const ref = withDefaultRegistry(parseRef(pos[0]));
|
|
103
|
+
const store = await Store.open();
|
|
104
|
+
const { options, explicit } = await clientOptions(v);
|
|
105
|
+
const client = makeClient(ref.registry, { ...options, explicitCredentials: explicit });
|
|
106
|
+
const platform = str(v.platform);
|
|
107
|
+
const r = await pullImage(store, client, ref, {
|
|
108
|
+
platform,
|
|
109
|
+
enforcePlatform: platform !== undefined,
|
|
110
|
+
tag: ref.digest === undefined,
|
|
111
|
+
onEvent: (m) => process.stdout.write(`${m}\n`),
|
|
112
|
+
});
|
|
113
|
+
process.stdout.write(`Digest: ${r.rootDigest}\n`);
|
|
114
|
+
process.stdout.write(`Pulled ${ref.digest === undefined ? refKey(ref) : `${ref.registry}/${ref.name}@${ref.digest}`} (image ${shortId(r.id)})\n`);
|
|
115
|
+
return 0;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
export const registryCommands = new Map([
|
|
119
|
+
['push', push],
|
|
120
|
+
['pull', pull],
|
|
121
|
+
]);
|
|
122
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Detached server process spawned by `sitefile serve -d` / `restart`.
|
|
3
|
+
// Usage: node serve-worker.js <server id>. State and logs live in $SITEFILE_HOME/servers.
|
|
4
|
+
import { SitefileError } from '../errors.js';
|
|
5
|
+
import { closeServer, createSiteServer, formatUrl, listen } from '../server.js';
|
|
6
|
+
import { loadServedTree, ServerRegistry } from '../servers.js';
|
|
7
|
+
const stamp = (m) => void process.stdout.write(`${new Date().toISOString()} ${m}\n`);
|
|
8
|
+
async function main() {
|
|
9
|
+
const id = process.argv[2] ?? '';
|
|
10
|
+
const reg = await ServerRegistry.open();
|
|
11
|
+
const state = await reg.read(id);
|
|
12
|
+
if (!state)
|
|
13
|
+
throw new SitefileError('E_SERVER', `no state for server '${id}'`);
|
|
14
|
+
try {
|
|
15
|
+
const tree = await loadServedTree(reg.store, state.source);
|
|
16
|
+
const server = createSiteServer(tree, { spa: state.spa, serverId: id, log: stamp });
|
|
17
|
+
const addr = await listen(server, state.bind.host, state.bind.port);
|
|
18
|
+
await reg.update(id, { pid: process.pid, host: addr.host, port: addr.port, ready: true, error: undefined });
|
|
19
|
+
stamp(`serving ${state.image} (${tree.size} files) at ${formatUrl(addr.host, addr.port)}`);
|
|
20
|
+
const stop = (signal) => () => {
|
|
21
|
+
stamp(`received ${signal}, shutting down`);
|
|
22
|
+
void closeServer(server).then(async () => {
|
|
23
|
+
await reg.update(id, { ready: false }).catch(() => undefined);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
});
|
|
26
|
+
setTimeout(() => process.exit(0), 3000).unref();
|
|
27
|
+
};
|
|
28
|
+
process.on('SIGTERM', stop('SIGTERM'));
|
|
29
|
+
process.on('SIGINT', stop('SIGINT'));
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
const message = e instanceof SitefileError ? `error[${e.code}]: ${e.message}` : `error: ${e.message}`;
|
|
33
|
+
process.stderr.write(`${new Date().toISOString()} ${message}\n`);
|
|
34
|
+
await reg.update(id, { pid: 0, ready: false, error: message }).catch(() => undefined);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
main().catch((e) => {
|
|
39
|
+
process.stderr.write(`${e?.stack ?? String(e)}\n`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
42
|
+
//# sourceMappingURL=serve-worker.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ServerRegistry, type ServerState } from '../servers.js';
|
|
2
|
+
import { type Command } from './common.js';
|
|
3
|
+
/** `-p` value: `port`, `host:port` or `[v6]:port`. Default bind is 127.0.0.1 with a free port. */
|
|
4
|
+
export declare function parsePublish(spec: string | undefined): {
|
|
5
|
+
host: string;
|
|
6
|
+
port: number;
|
|
7
|
+
};
|
|
8
|
+
/** SIGTERM, wait, then SIGKILL. Returns false if the server was not running. */
|
|
9
|
+
export declare function stopServer(reg: ServerRegistry, s: ServerState, graceMs: number): Promise<boolean>;
|
|
10
|
+
/** The last n lines of text, each newline-terminated; a trailing newline is not an extra empty line. */
|
|
11
|
+
export declare function tailLines(text: string, n: number): string;
|
|
12
|
+
/** Top-level: `sitefile serve`, `sitefile servers`. */
|
|
13
|
+
export declare const serverCommands: ReadonlyMap<string, Command>;
|
|
14
|
+
/** `sitefile server <ls|stop|rm|logs|restart>`. */
|
|
15
|
+
export declare const serverSubcommands: ReadonlyMap<string, Command>;
|