@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/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,122 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.2 (2026-11-08)
|
|
4
|
+
|
|
5
|
+
- **Plugin console output keeps its order** (johnhenry/sitefile#164): a
|
|
6
|
+
plugin's console lines are written before its result, in both the
|
|
7
|
+
TypeScript and Rust runners, so they always become build warnings (a race
|
|
8
|
+
dropped some). Fixed in 5689229 (#164).
|
|
9
|
+
- **Docs: the tool is "sitefile".** The product name is lowercase where it
|
|
10
|
+
stands alone; "Sitefile" stays for the file (`Sitefile`, `Sitefile.lock`, "a
|
|
11
|
+
Sitefile") and the language. The desktop app is Sitefile Desktop.
|
|
12
|
+
|
|
13
|
+
## 0.0.1 (2026-11-08)
|
|
14
|
+
|
|
15
|
+
- **`serve` serves archive files** (johnhenry/sitefile#155): `sitefile serve
|
|
16
|
+
site.zip` (and `serve -d`) serves a zip, tar, tar.gz/tgz, Web Bundle or OCI
|
|
17
|
+
image archive (`build -o x.oci.tar`, `save`) the way it serves a directory.
|
|
18
|
+
The format comes from the bytes; a path-like argument that is a file is an
|
|
19
|
+
archive, and a bare name is an archive file when no image has that name.
|
|
20
|
+
Zip, tar and wbn are extracted with the default archive limits and served
|
|
21
|
+
without hidden files (as a directory is); an OCI archive serves its first
|
|
22
|
+
image as the store would, with every blob checked against its digest. The
|
|
23
|
+
file is read again on every start and `server restart`. Server records gain
|
|
24
|
+
`{"kind": "archive", "path": …}` (older records load unchanged); Rust adds
|
|
25
|
+
`ServerSource::Archive`, `servers::load_archive_tree` and
|
|
26
|
+
`load_archive_tree_with`, with every existing signature unchanged. A file
|
|
27
|
+
that is not an archive is `E_TYPE` naming it. docs/CLI.md, "Site servers".
|
|
28
|
+
- **Explicit network settings: proxy, host rules, connect/read timeouts**
|
|
29
|
+
(johnhenry/sitefile#156), for networks that need a proxy and for sitefile
|
|
30
|
+
Desktop's Settings screen. `--proxy` / `SITEFILE_PROXY` (an `http://` proxy:
|
|
31
|
+
a CONNECT tunnel for https, absolute form for http; `HTTP(S)_PROXY` is still
|
|
32
|
+
never read), `--no-proxy` / `SITEFILE_NO_PROXY` (`NO_PROXY` syntax),
|
|
33
|
+
`--allow-hosts` / `SITEFILE_ALLOW_HOSTS` and `--deny-hosts` /
|
|
34
|
+
`SITEFILE_DENY_HOSTS` (checked on every hop, registries included; deny beats
|
|
35
|
+
allow; the new code `E_HOST_BLOCKED` names the rule), and `--connect-timeout`
|
|
36
|
+
/ `--read-timeout` / `SITEFILE_FETCH_TIMEOUT`, on `build`, `validate`,
|
|
37
|
+
`inspect`, `graph`, `why`, `pull`, `push`, `vendor` and `outdated`. A proxy
|
|
38
|
+
never widens the fetch policy: the target is resolved and checked against
|
|
39
|
+
the private-address rule before the proxy is asked. Embedders pass the same
|
|
40
|
+
settings as `FetchPolicy.network` / `RegistryOptions.network`
|
|
41
|
+
(`NetworkSettings`, additive in both implementations). docs/CLI.md,
|
|
42
|
+
"Network"; `test/network.test.ts`, `rust/src/resources/network.rs`, and the
|
|
43
|
+
`net-proxy*`, `net-no-proxy`, `net-allow-hosts` and `net-deny-hosts`
|
|
44
|
+
conformance cases. Added in c131eb3 (#162).
|
|
45
|
+
|
|
46
|
+
- **Library API for embedders** (johnhenry/sitefile#153), found by embedding
|
|
47
|
+
the crate in Sitefile Desktop. Rust: `servers::serve_detached`,
|
|
48
|
+
`restart_detached`, `start_detached`, `stop_server`, `resolve_source`,
|
|
49
|
+
`parse_publish` and `DetachedServe` (the CLI's `serve -d`, `server restart`
|
|
50
|
+
and `server stop` now call them); `executor::store_build_as_image` with
|
|
51
|
+
`ImageInfo` and `StoredImage` (what `build -t` calls);
|
|
52
|
+
`BuildOptions::store_home`, so `FROM <local image>`, `BASE` and the wasm
|
|
53
|
+
module cache use an embedder's store instead of `$SITEFILE_HOME`;
|
|
54
|
+
`BuildOptions::cancel` with `CancelToken`, checked between imports, stages,
|
|
55
|
+
operations and plugin or tool calls, failing with the new code
|
|
56
|
+
`E_CANCELLED`; detached workers are reaped on a background thread, so a
|
|
57
|
+
long-lived host can stop them without waiting out the grace period.
|
|
58
|
+
Both implementations: TRANSFORM cache entries record their PLUGIN or TOOL
|
|
59
|
+
(`"plugin"`, same bytes in both; older entries are still read, keys are
|
|
60
|
+
unchanged), with `usage_by_plugin`/`usageByPlugin`,
|
|
61
|
+
`prune_plugin`/`prunePlugin` and `sitefile cache prune --plugin <name>`; the
|
|
62
|
+
`E_UNSUPPORTED` error of a PLUGIN or TOOL that is not allowed carries
|
|
63
|
+
`needs` (kind, name, flag), with the message unchanged. CLI output is
|
|
64
|
+
otherwise unchanged. docs/RUST.md, "Embedding the crate";
|
|
65
|
+
`rust/tests/embed.rs`. Added in e0c555d (#154).
|
|
66
|
+
|
|
67
|
+
- **`EXPORT … AS wbn COMPRESSION gzip`** (johnhenry/sitefile#143): a Web Bundle
|
|
68
|
+
export can now be gzip(`application/webbundle`), the `.wbn` archive format of
|
|
69
|
+
`@johnhenry/packfile` (`toArchive`/`fromArchive`) and `@johnhenry/fileable`
|
|
70
|
+
(`encode="wbn"`). `COMPRESSION` used to be `E_VALIDATION` on anything but
|
|
71
|
+
`oci`; it now applies to `oci` and `wbn`, and on `wbn` defaults to `none`, so
|
|
72
|
+
existing exports are byte-for-byte unchanged. The gzip layer only wraps the
|
|
73
|
+
finished bundle and uses the shared deterministic encoder (`src/deflate.ts`,
|
|
74
|
+
`rust/src/deflate.rs`), so both CLIs write identical bytes. On the CLI,
|
|
75
|
+
`-o site.wbn.gz` selects a gzipped wbn (also for `sitefile save`), the
|
|
76
|
+
default destination of such a Sitefile is `dist.wbn.gz`, and the new
|
|
77
|
+
`--compression gzip|none` overrides `EXPORT … COMPRESSION` for the export's
|
|
78
|
+
format (oci or wbn; any other format is a usage error); `--oci-compression`
|
|
79
|
+
stays as the oci-only spelling. A Sitefile's `COMPRESSION` belongs to its own
|
|
80
|
+
`AS` format: `--format wbn` on an `AS oci COMPRESSION …` Sitefile does not
|
|
81
|
+
inherit it. `test/packfile-interop.test.ts` now reads a gzip export built with
|
|
82
|
+
`BASE https://packfile.invalid/` through plain `fromArchive(buffer)`; new
|
|
83
|
+
conformance cases compare the bytes of both implementations. Added in
|
|
84
|
+
3c537de (#149).
|
|
85
|
+
|
|
86
|
+
- **Example: a fileable plugin for TRANSFORM** (`examples/fileable/`,
|
|
87
|
+
johnhenry/sitefile#145). A JavaScript plugin renders a `@johnhenry/fileable`
|
|
88
|
+
tree into the build with fileable's in-memory `toFiles()` and returns
|
|
89
|
+
`{ files }`, so a JSX file tree needs no directory or zip on disk in
|
|
90
|
+
between. fileable stays out of sitefile's dependencies: it is a
|
|
91
|
+
devDependency, and `examples/fileable/vendor.mjs` copies the files its
|
|
92
|
+
browser entry point loads into the plugin folder (gitignored; nothing
|
|
93
|
+
generated is committed), the only folder a plugin can read. Its `cmd`
|
|
94
|
+
attribute and other features that need files, processes or the environment
|
|
95
|
+
fail with a clear `E_PLUGIN`. Checked by `npm run examples` and by
|
|
96
|
+
conformance (both CLIs). docs/PLUGINS.md, "Example: rendering a fileable
|
|
97
|
+
tree". Added in 0b5c4b3 (#147).
|
|
98
|
+
|
|
99
|
+
- **`EXPORT … AS wbn` bakes HEADER and exact REDIRECT rules into the bundle's
|
|
100
|
+
responses** (johnhenry/sitefile#144, #150). Each response gets the headers of the
|
|
101
|
+
`/_headers` blocks matching its URL, with `sitefile serve`'s matching
|
|
102
|
+
(`serve-rules.ts`; a directory URL like `<base>docs/` matches with its
|
|
103
|
+
slash and gets its own response when its headers differ). Exact-path
|
|
104
|
+
`/_redirects` rules with a 3xx status become responses with that status, a
|
|
105
|
+
`location` and an empty body, in `serve`'s order (a forced `301!` replaces
|
|
106
|
+
the file; other rules apply only where `serve` would find nothing). Splats,
|
|
107
|
+
`200` rewrites and `404` rules stay in `/_redirects`, which, like
|
|
108
|
+
`/_headers`, is still in the bundle. Bundles of trees without those files
|
|
109
|
+
are byte-identical to before. packfile's `createWebBundleRouter()` now
|
|
110
|
+
answers with the HEADER values and redirects (`test/packfile-interop.test.ts`);
|
|
111
|
+
conformance cases `ok-wbn-rules` and `ok-wbn-rules-files` compare both
|
|
112
|
+
implementations' bytes. The TypeScript `headersFor` no longer drops a
|
|
113
|
+
header named `__proto__` (the Rust port never did); this also
|
|
114
|
+
changes the TypeScript `sitefile serve`, which now sends such a header as
|
|
115
|
+
the Rust binary does. Added in 429ab24 (#150).
|
|
116
|
+
|
|
3
117
|
## 0.0.0 — first npm release (2026-11-06)
|
|
4
118
|
|
|
5
|
-
`@johnhenry/sitefile` is a new package:
|
|
119
|
+
`@johnhenry/sitefile` is a new package: sitefile was never published to npm
|
|
6
120
|
under any name, and `0.0.0` is its first version. It joins the @johnhenry
|
|
7
121
|
family as its static build orchestrator, next to `@johnhenry/fileable` (file
|
|
8
122
|
trees in JSX) and `@johnhenry/packfile` (the Web Bundle reader and server).
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# sitefile
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@johnhenry/sitefile)
|
|
4
4
|
[](https://github.com/johnhenry/sitefile/actions/workflows/ci.yml)
|
|
@@ -84,10 +84,10 @@ no runtime dependencies.
|
|
|
84
84
|
| `TOOL name` + `TRANSFORM … WITH name TO <dest> [OPTIONS …] [FOR …] ARGS …` | yes; run an external program (e.g. `ffmpeg`) you allow with `--allow-exec name`: argv only, no shell, temp files, scrubbed environment (see [docs/PLUGINS.md](docs/PLUGINS.md)) |
|
|
85
85
|
| `SITEMAP BASE <url> [TO <dest>]` | yes; a sitemaps.org `/sitemap.xml` of the `.html` pages so far |
|
|
86
86
|
| `FEED <import> BASE <url> LINK <path> [TO <dest>] [TITLE <text>]` | yes; an Atom `/feed.xml` from Markdown posts (`title` and `date` required) |
|
|
87
|
-
| `REDIRECT <from> <to> [STATUS 301\|302\|303\|307\|308]`, `HEADER <pattern> <Name>: <value>` | yes; `/_redirects` and `/_headers` for Netlify / Cloudflare Pages (and `sitefile serve`) |
|
|
87
|
+
| `REDIRECT <from> <to> [STATUS 301\|302\|303\|307\|308]`, `HEADER <pattern> <Name>: <value>` | yes; `/_redirects` and `/_headers` for Netlify / Cloudflare Pages (and `sitefile serve`); a `wbn` export also bakes HEADER values into each response and exact-path REDIRECTs into 3xx responses (splats stay in `/_redirects`) |
|
|
88
88
|
| `CHECKLINKS [--warn] [--external]` | yes; fails (or warns) on links to files that do not exist, with file and line; `--external` requests http(s) links under the network policy |
|
|
89
89
|
| `FINGERPRINT [--overwrite] <pattern>… [MANIFEST <dest>]` | yes; renames assets to `name.<hash>.ext` and rewrites the HTML and CSS that reference them, plus a JSON manifest |
|
|
90
|
-
| `EXPORT <path> [AS directory\|zip\|tar\|tgz\|wbn\|oci] [BASE <https-url>] [AT <root>]` | yes; archives are deterministic, `wbn` is an unsigned Web Bundle (needs `BASE` or `--base-url`), `oci` is a single-layer OCI image of the files (under `AT`, default `/srv/www`) |
|
|
90
|
+
| `EXPORT <path> [AS directory\|zip\|tar\|tgz\|wbn\|oci] [BASE <https-url>] [AT <root>] [COMPRESSION gzip\|none]` | yes; archives are deterministic, `wbn` is an unsigned Web Bundle (needs `BASE` or `--base-url`; `COMPRESSION gzip` gzips it, default `none`), `oci` is a single-layer OCI image of the files (under `AT`, default `/srv/www`; `COMPRESSION` sets its layer, default `gzip`) |
|
|
91
91
|
|
|
92
92
|
Templates can also live in the Sitefile itself (see `examples/inline/`):
|
|
93
93
|
|
|
@@ -163,7 +163,7 @@ build used, `--cache-from <ref>` reuses them (see [docs/CLI.md](docs/CLI.md#regi
|
|
|
163
163
|
## CLI
|
|
164
164
|
|
|
165
165
|
```
|
|
166
|
-
sitefile build [context] [-o dist] [--format directory|zip|tar|tgz|wbn] [--base-url URL]
|
|
166
|
+
sitefile build [context] [-o dist] [--format directory|zip|tar|tgz|wbn] [--base-url URL] [--compression gzip|none]
|
|
167
167
|
[--build-arg K=V | --build-arg K] [--build-arg-file vars.json|.yaml|.env] [--force]
|
|
168
168
|
[--frozen-lockfile | --update-lock] [--strict-pins] [--allow-http] [--allow-private-network]
|
|
169
169
|
[--allow-hardlinks] [--allow-exec name[=/abs/path]]... [--allow-plugin name]... [--allow-plugins]
|
|
@@ -181,7 +181,7 @@ sitefile build [context] -t name[:tag] # also store the build as an image
|
|
|
181
181
|
sitefile images | image inspect|diff|prune | tag | rmi | save
|
|
182
182
|
sitefile cache info | cache prune [--older-than days] # TRANSFORM result cache
|
|
183
183
|
sitefile cache du # cache sizes; SITEFILE_CACHE_MAX bounds them (default 1G)
|
|
184
|
-
sitefile serve [-d] [--name n] [-p [host:]port] [--spa] <image|./dir>
|
|
184
|
+
sitefile serve [-d] [--name n] [-p [host:]port] [--spa] <image|./dir|site.zip>
|
|
185
185
|
sitefile serve --watch [-p [host:]port] [--spa] [context] # build in memory, serve, live reload
|
|
186
186
|
sitefile servers [-a] | server stop|rm [-f]|restart|logs [-f] <name>
|
|
187
187
|
```
|
|
@@ -273,6 +273,7 @@ CycloneDX file list to the image as OCI 1.1 referrers (deterministic, pushed wit
|
|
|
273
273
|
sitefile build examples/hello --format zip # examples/hello/dist.zip
|
|
274
274
|
sitefile build examples/hello -o hello.tar.gz # format from the extension
|
|
275
275
|
sitefile build examples/hello --format wbn --base-url https://example.com/
|
|
276
|
+
sitefile build examples/hello -o hello.wbn.gz --base-url https://example.com/ # gzip(Web Bundle)
|
|
276
277
|
```
|
|
277
278
|
|
|
278
279
|
Archives are reproducible: sorted entries, fixed timestamps (or `SOURCE_DATE_EPOCH`),
|
|
@@ -324,6 +325,7 @@ stopgap in VS Code.
|
|
|
324
325
|
- [docs/RUST.md](docs/RUST.md) — the Rust port: layout, parity checks, known differences
|
|
325
326
|
- [docs/LSP.md](docs/LSP.md) — the language server for editors (`sitefile lsp`, Rust binary)
|
|
326
327
|
- [docs/PLUGINS.md](docs/PLUGINS.md) — writing renderer plugins, and the sandbox
|
|
328
|
+
- [docs/DOCKER.md](docs/DOCKER.md) — sitefile next to Docker: site images Docker runs, Dockerfile stages, shared registries
|
|
327
329
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — invariants and module layout
|
|
328
330
|
- [docs/MILESTONES.md](docs/MILESTONES.md), [docs/TASKS.md](docs/TASKS.md)
|
|
329
331
|
- [AGENTS.md](AGENTS.md) — the verification loop and rules for anyone (human or agent) changing this repo
|
|
@@ -378,7 +380,7 @@ stage), so building one is treated as running untrusted input. Templates,
|
|
|
378
380
|
imports and WebAssembly plugins are data; JavaScript plugins and external
|
|
379
381
|
tools are code, and run only when you name them on the command line.
|
|
380
382
|
|
|
381
|
-
**What
|
|
383
|
+
**What sitefile guarantees:**
|
|
382
384
|
|
|
383
385
|
- **Local reads stay inside the build context.** No `..`, absolute paths or
|
|
384
386
|
symlinks out; hard-linked files are refused (`E_HARDLINK`) unless
|
|
@@ -388,7 +390,11 @@ tools are code, and run only when you name them on the command line.
|
|
|
388
390
|
- **Remote imports are `https:` only, to public addresses only** — checked at
|
|
389
391
|
connect time and on every redirect (SSRF), size- and time-limited, never
|
|
390
392
|
decompressed or executed. `--allow-http` and `--allow-private-network` are the
|
|
391
|
-
only ways to widen this.
|
|
393
|
+
only ways to widen this. An explicit proxy (`--proxy` / `$SITEFILE_PROXY`;
|
|
394
|
+
`HTTP(S)_PROXY` is never read) does not widen it either: sitefile resolves and
|
|
395
|
+
checks the target itself before handing it to the proxy. `--allow-hosts` and
|
|
396
|
+
`--deny-hosts` narrow it further, on every hop
|
|
397
|
+
([docs/CLI.md](docs/CLI.md#network-proxy-host-rules-and-timeouts)).
|
|
392
398
|
- **Pins are enforced.** Unpinned URLs are recorded in `Sitefile.lock` and must
|
|
393
399
|
match on later builds; `--strict-pins` refuses any remote import or BASE image
|
|
394
400
|
that is not pinned (`INTEGRITY`, `@sha256:` or a lockfile entry).
|
|
@@ -441,35 +447,67 @@ tools are code, and run only when you name them on the command line.
|
|
|
441
447
|
`INTEGRITY`, `@sha256:` and `--strict-pins` for builds that must be
|
|
442
448
|
reproducible.
|
|
443
449
|
- **Web Bundles are unsigned.** `EXPORT … AS wbn` writes an unsigned b2 bundle;
|
|
444
|
-
signing (for example for an Isolated Web App) happens outside
|
|
450
|
+
signing (for example for an Isolated Web App) happens outside sitefile.
|
|
445
451
|
- **Widening flags are yours to justify.** `--allow-http`,
|
|
446
452
|
`--allow-private-network`, `--allow-hardlinks`, `--force` and `serve -p
|
|
447
453
|
0.0.0.0:…` each turn off one of the guarantees above for that build.
|
|
448
454
|
|
|
449
455
|
## Family
|
|
450
456
|
|
|
451
|
-
|
|
457
|
+
sitefile is the build orchestrator of the @johnhenry family's static tools. It
|
|
452
458
|
runs the pipeline (pinned imports, stages, templates, sandboxed plugins, checks,
|
|
453
459
|
exports); its siblings write file trees in code and serve the result. It has no
|
|
454
|
-
runtime dependency on either: they meet at file formats, a directory
|
|
455
|
-
Web Bundle.
|
|
460
|
+
runtime dependency on either: they meet at file formats, a directory, a zip or
|
|
461
|
+
a `.wbn` Web Bundle.
|
|
456
462
|
|
|
457
463
|
- **[`@johnhenry/fileable`](https://github.com/johnhenry/fileable)** -- writes
|
|
458
464
|
file trees in JSX (`Dir`, `File`, `Rm`), for scaffolding and codegen. A
|
|
459
|
-
fileable build's output
|
|
460
|
-
|
|
461
|
-
./site.zip AS tree` and `COPY gen:/ /` (both give the same output). Its
|
|
462
|
-
`encode="wbn"` archive is gzip around a Web Bundle, which `AS tree` does not
|
|
463
|
-
read yet (johnhenry/sitefile#141). Neither package depends on the other.
|
|
465
|
+
fileable build's output is an ordinary sitefile input. Neither package
|
|
466
|
+
depends on the other.
|
|
464
467
|
- **[`@johnhenry/packfile`](https://github.com/johnhenry/packfile)** -- the
|
|
465
|
-
family's Web Bundle reader and in-memory server. `EXPORT
|
|
466
|
-
writes
|
|
467
|
-
|
|
468
|
-
`
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
468
|
+
family's Web Bundle reader and in-memory server. It reads what `EXPORT … AS
|
|
469
|
+
wbn` writes and serves it as a `(Request) => Response` handler.
|
|
470
|
+
`@johnhenry/packfile` is a devDependency here for
|
|
471
|
+
`test/packfile-interop.test.ts` only.
|
|
472
|
+
|
|
473
|
+
### Working together: what works today
|
|
474
|
+
|
|
475
|
+
Checked on 2026-11-07 against the npm releases (`@johnhenry/sitefile` 0.0.0,
|
|
476
|
+
`@johnhenry/fileable` 0.1.1, `@johnhenry/packfile` 0.2.0) and each
|
|
477
|
+
repository's `main`. "Fixed on main, in the next release" means merged but
|
|
478
|
+
not published: the npm versions still behave as the rest of that row says.
|
|
479
|
+
|
|
480
|
+
| From | To | How | Status |
|
|
481
|
+
| --- | --- | --- | --- |
|
|
482
|
+
| fileable, loose output | sitefile | `COPY ./generated/ /` | Works |
|
|
483
|
+
| fileable, `encode="zip"` | sitefile | `IMPORT gen FROM ./site.zip AS tree`, then `COPY gen:/ /` | Works (same output as loose) |
|
|
484
|
+
| fileable, `encode="wbn"` | sitefile | `IMPORT … AS tree` | **Not yet:** it's gzip around a Web Bundle, and `AS tree` reads only plain bundles ([#141](https://github.com/johnhenry/sitefile/issues/141)) |
|
|
485
|
+
| fileable tree, rendered during a build | Sitefile | a JavaScript `TRANSFORM` plugin calling fileable's in-memory `toFiles()` | An example on main, not part of the npm package: [`examples/fileable/`](examples/fileable/) ([#145](https://github.com/johnhenry/sitefile/issues/145)). fileable is vendored into the plugin folder; `cmd`, local `src=` and `env://` fail with `E_PLUGIN` |
|
|
486
|
+
| sitefile, `EXPORT / AS wbn BASE <url>` | packfile | `fromWebBundle(buffer, { baseURL })` from `@johnhenry/packfile/web-bundle`, then `createRouter(files)` | Works; tested here (`test/packfile-interop.test.ts`), and packfile's own tests read a bundle written by sitefile 0.0.0 |
|
|
487
|
+
| sitefile `.wbn` | packfile `fromArchive()` | with 0.0.0 / 0.2.0: gzip it, and export with `BASE https://packfile.invalid/` | Works with that workaround. **Fixed on main, in the next release:** `EXPORT … AS wbn COMPRESSION gzip` (or `-o site.wbn.gz`) writes the gzip ([#143](https://github.com/johnhenry/sitefile/issues/143)), and `fromArchive()` takes a `baseURL` or the origin of the bundle's primary URL, so any `BASE` reads back ([packfile#18](https://github.com/johnhenry/packfile/issues/18)) |
|
|
488
|
+
| sitefile `HEADER` / `REDIRECT` | packfile `createWebBundleRouter()` | -- | **Not applied in 0.0.0:** only the `/_headers` and `/_redirects` files are in the bundle. **Fixed on main, in the next release:** each response carries its HEADER values, and exact-path 301/302/303/307/308 REDIRECTs are 3xx responses with `location` ([#144](https://github.com/johnhenry/sitefile/issues/144)). Splats, `200` rewrites and `404` rules stay in `/_redirects` only |
|
|
489
|
+
| packfile `toArchive()` | sitefile | `IMPORT … AS tree` | **Not yet** (same as fileable's `.wbn`, [#141](https://github.com/johnhenry/sitefile/issues/141)) |
|
|
490
|
+
|
|
491
|
+
Gotchas when serving a Sitefile bundle with packfile:
|
|
492
|
+
|
|
493
|
+
- **Pass `createRouter()` a path string for non-ASCII paths.** A `Request`
|
|
494
|
+
for `/docs/ünï/page.html` returns 404 because the pathname isn't
|
|
495
|
+
percent-decoded; the string `"/docs/ünï/page.html"` works
|
|
496
|
+
([packfile#20](https://github.com/johnhenry/packfile/issues/20)).
|
|
497
|
+
- **`fromWebBundle` skips the bundle's entry at the bare base URL.** sitefile
|
|
498
|
+
stores the index page there as well as at `/index.html`; you get it from
|
|
499
|
+
`index.html`.
|
|
500
|
+
- **The `baseURL` you pass to `fromWebBundle` must be the `BASE` you exported
|
|
501
|
+
with.** Entries under any other origin are skipped, so the wrong base reads
|
|
502
|
+
back as an empty map, without an error. packfile's `fromArchive()` on its
|
|
503
|
+
main branch (next release) defaults to the origin of the primary URL, which
|
|
504
|
+
sitefile writes only when the site has `/index.html`; with a `BASE` that has
|
|
505
|
+
a path (`https://site.example/blog/`), the keys start with `blog/` unless you
|
|
506
|
+
pass that `baseURL`.
|
|
507
|
+
- **HEADER and REDIRECT need `createWebBundleRouter()`** (on main, next
|
|
508
|
+
release): `createWebBundleRouter(new wbn.Bundle(bytes), { baseURL })` serves
|
|
509
|
+
each exchange's own status and headers. `createRouter()` serves a files map,
|
|
510
|
+
so it answers with the files only.
|
|
473
511
|
|
|
474
512
|
## License
|
|
475
513
|
|
package/lib/src/checklinks.js
CHANGED
|
@@ -44,6 +44,8 @@ function externalReason(e, url, policy) {
|
|
|
44
44
|
return /^http:/i.test(url) && !policy.allowHttp ? 'plain http is checked only with --allow-http' : 'not allowed by the network policy';
|
|
45
45
|
case 'E_SSRF_BLOCKED':
|
|
46
46
|
return 'a non-public address (checked only with --allow-private-network)';
|
|
47
|
+
case 'E_HOST_BLOCKED':
|
|
48
|
+
return 'refused by the allow-hosts/deny-hosts rules';
|
|
47
49
|
case 'E_REDIRECT':
|
|
48
50
|
return 'too many or invalid redirects';
|
|
49
51
|
default:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sourceDateEpoch } from '../archive-writers.js';
|
|
2
2
|
import { SitefileError } from '../errors.js';
|
|
3
|
-
import { imageAttestations } from '../executor.js';
|
|
3
|
+
import { imageAttestations, sitefileCompression } from '../executor.js';
|
|
4
4
|
import { imageMeta } from '../oci.js';
|
|
5
5
|
import { parseLocalRef } from '../ref.js';
|
|
6
6
|
import { shortId, Store } from '../store.js';
|
|
@@ -20,7 +20,7 @@ export async function storeBuildAsImage(result, tags, info) {
|
|
|
20
20
|
const store = await Store.open();
|
|
21
21
|
const { id } = await store.addImage(result.output, {
|
|
22
22
|
root: info.ociRoot ?? result.plan.exportRoot,
|
|
23
|
-
compression: info.ociCompression ?? result.plan
|
|
23
|
+
compression: info.ociCompression ?? sitefileCompression(result.plan, 'oci'),
|
|
24
24
|
platform: info.platform,
|
|
25
25
|
epoch: sourceDateEpoch(),
|
|
26
26
|
base: result.base,
|
package/lib/src/cli/cache.js
CHANGED
|
@@ -24,17 +24,24 @@ const info = {
|
|
|
24
24
|
const prune = {
|
|
25
25
|
name: 'cache prune',
|
|
26
26
|
summary: 'delete cached TRANSFORM results',
|
|
27
|
-
usage: 'cache prune [--older-than <days> | --max-size <size>
|
|
28
|
-
description: 'Delete every cached TRANSFORM result, or only entries not used for the given number of days, and the blobs no\nentry still uses. With --max-size, instead apply the automatic size limit now with that size
|
|
29
|
-
flags: [
|
|
30
|
-
|
|
27
|
+
usage: 'cache prune [--older-than <days>] [--plugin <name>] | --max-size <size>',
|
|
28
|
+
description: 'Delete every cached TRANSFORM result, or only entries not used for the given number of days, and the blobs no\nentry still uses. With --plugin, only the results of that PLUGIN or TOOL (entries written before results\nrecorded their plugin are kept). With --max-size, instead apply the automatic size limit now with that size:\nleast recently used evictable data (TRANSFORM results, compiled wasm modules, untagged images and unused\nblobs) is deleted until the rest fits. Tagged images are never touched.',
|
|
29
|
+
flags: [
|
|
30
|
+
' --older-than <days> keep entries used within this many days',
|
|
31
|
+
' --plugin <name> only the results of this PLUGIN or TOOL',
|
|
32
|
+
' --max-size <size> shrink evictable data to this size (e.g. 500M, 2G)',
|
|
33
|
+
],
|
|
34
|
+
options: { 'older-than': { type: 'string' }, plugin: { type: 'string' }, 'max-size': { type: 'string' } },
|
|
31
35
|
async run(v, pos) {
|
|
32
36
|
requireArgs(pos, 0, 0, 'no arguments');
|
|
33
37
|
const raw = v['older-than'];
|
|
34
38
|
const max = str(v['max-size']);
|
|
39
|
+
const plugin = str(v['plugin']);
|
|
35
40
|
if (max !== undefined) {
|
|
36
41
|
if (raw !== undefined)
|
|
37
42
|
usage('--older-than and --max-size conflict');
|
|
43
|
+
if (plugin !== undefined)
|
|
44
|
+
usage('--plugin and --max-size conflict');
|
|
38
45
|
const n = parseSize(max);
|
|
39
46
|
if (n === undefined || n === 0)
|
|
40
47
|
usage('--max-size expects a size greater than 0, such as 500M or 2G');
|
|
@@ -49,7 +56,10 @@ const prune = {
|
|
|
49
56
|
usage('--older-than expects a positive number of days');
|
|
50
57
|
ms = days * 86_400_000;
|
|
51
58
|
}
|
|
52
|
-
|
|
59
|
+
if (plugin !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(plugin))
|
|
60
|
+
usage(`--plugin expects a PLUGIN or TOOL name (an identifier), got '${plugin}'`);
|
|
61
|
+
const cache = new TransformCache(transformCacheDir());
|
|
62
|
+
const r = plugin === undefined ? await cache.prune(ms) : await cache.prunePlugin(plugin, ms);
|
|
53
63
|
process.stdout.write(`Deleted ${r.entries} entr${r.entries === 1 ? 'y' : 'ies'} and ${r.blobs} blob${r.blobs === 1 ? '' : 's'}\nTotal reclaimed space: ${humanSize(r.bytes)}\n`);
|
|
54
64
|
return 0;
|
|
55
65
|
},
|
package/lib/src/cli/images.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { sourceDateEpoch, writeTar } from '../archive-writers.js';
|
|
5
5
|
import { SitefileError } from '../errors.js';
|
|
6
6
|
import { exportDirectory, exportFile } from '../exporter.js';
|
|
7
|
-
import { encodeTree, formatFromExtension, isArchiveFormat } from '../export-formats.js';
|
|
7
|
+
import { compressionFromExtension, encodeTree, formatFromExtension, isArchiveFormat } from '../export-formats.js';
|
|
8
8
|
import { EXPORT_FORMATS, parseExportFormat } from '../language/ast.js';
|
|
9
9
|
import { MEDIA, siteLayerIndex } from '../oci.js';
|
|
10
10
|
import { looksLikeImageId, parseLocalRef, refKey } from '../ref.js';
|
|
@@ -175,7 +175,7 @@ const save = {
|
|
|
175
175
|
name: 'save',
|
|
176
176
|
summary: 'export an image to a directory or archive',
|
|
177
177
|
usage: 'save <ref|id> -o <out> [--format fmt] [--base-url url] [--force]',
|
|
178
|
-
description: 'Write an image\'s files to a directory, or to a zip, tar, tar.gz (tgz) or wbn archive, or the image itself as\nan OCI archive (-o x.oci.tar) or OCI layout directory (--format oci). The format is chosen like "build -o":\nfrom --format, else the extension (.zip .tar .tar.gz .tgz .wbn .oci.tar), else a directory.\nArchives are deterministic (set SOURCE_DATE_EPOCH to stamp them). A non-empty destination needs --force.',
|
|
178
|
+
description: 'Write an image\'s files to a directory, or to a zip, tar, tar.gz (tgz) or wbn archive, or the image itself as\nan OCI archive (-o x.oci.tar) or OCI layout directory (--format oci). The format is chosen like "build -o":\nfrom --format, else the extension (.zip .tar .tar.gz .tgz .wbn .wbn.gz .oci.tar), else a directory; a wbn\nwritten to *.wbn.gz is gzipped.\nArchives are deterministic (set SOURCE_DATE_EPOCH to stamp them). A non-empty destination needs --force.',
|
|
179
179
|
flags: [
|
|
180
180
|
'-o, --out <path> destination (required)',
|
|
181
181
|
` --format <fmt> ${EXPORT_FORMATS.join(' | ')} (alias tar.gz)`,
|
|
@@ -223,7 +223,7 @@ const save = {
|
|
|
223
223
|
process.stdout.write(`Saved ${shortId(id)} (${tree.size} files) to ${shown(w.outDir)}\n`);
|
|
224
224
|
}
|
|
225
225
|
else {
|
|
226
|
-
const data = encodeTree(tree, format, { epoch: sourceDateEpoch(), baseUrl: str(v['base-url']) });
|
|
226
|
+
const data = encodeTree(tree, format, { epoch: sourceDateEpoch(), baseUrl: str(v['base-url']), compression: compressionFromExtension(out, format) });
|
|
227
227
|
const w = await exportFile(data, out, opts);
|
|
228
228
|
process.stdout.write(`Saved ${shortId(id)} (${tree.size} files) to ${shown(w.outFile)} (${format === 'tgz' ? 'tar.gz' : format}, ${w.bytes} bytes)\n`);
|
|
229
229
|
}
|
package/lib/src/cli/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ export declare const EXTRA_COMMANDS: readonly string[];
|
|
|
5
5
|
* an editor tool. Here `sitefile lsp` is a usage error that points there.
|
|
6
6
|
*/
|
|
7
7
|
export declare 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)";
|
|
8
|
-
export declare const EXTRA_HELP = "\nImages (local store: $SITEFILE_HOME or ~/.sitefile):\n sitefile build [context] -t name[:tag] also store the result as an image (repeatable -t)\n sitefile images [--json] [--digests] [-q] list images (alias: image ls)\n sitefile image inspect <ref> [--json] show an image manifest\n sitefile image prune delete unreferenced manifests and blobs\n sitefile image diff <a> <b> added/removed/changed paths\n sitefile tag <src> <dst> add a name:tag\n sitefile rmi [-f] <ref|id>... remove tags / images\n sitefile save <ref> -o out[.zip|.tar|.tar.gz|.wbn] export an image\n sitefile push <registry/name[:tag]> push a (tagged) image to an OCI registry\n sitefile pull <[registry/]name[:tag][@digest]> pull an image (a bare name means Docker Hub)\n\nVariants:\n sitefile bake [targets...] [-f file] [--print] build several targets (matrix, groups) from\n sitefile.bake.yaml; --print shows the plan\n\nOffline builds:\n sitefile vendor [context] [--vendor-dir dir] download remote imports and the BASE image into\n vendor/sitefile; builds then read them from there\n\nPins:\n sitefile outdated [context] [--json] compare remote imports and the BASE image with\n their pins (never changes anything)\n\nBuild cache (TRANSFORM results: $SITEFILE_HOME/cache/transform):\n sitefile cache info [--json] location, entries, size\n sitefile cache prune [--older-than <days>] delete cached results\n sitefile cache prune --max-size <size> shrink evictable data (LRU) to <size>\n sitefile cache du [--json] size of every cache and the store (alias: cache ls)\n Evictable data over $SITEFILE_CACHE_MAX (default 1GiB, 0 = no limit) is deleted after each build.\n\nServing sites (detached servers are tracked by name):\n sitefile serve [--name n] [-p [host:]port] [-d] [--spa] <ref|./dir>\n sitefile serve --watch [-p [host:]port] [ctx] build in memory, serve, rebuild + live reload\n sitefile servers [-a] [--json] list servers (alias: server ls)\n sitefile server stop|rm [-f]|restart|logs [-f] <name|id>\n\nEditor support:\n sitefile lsp [--stdio] language server over stdio (Rust build only; docs/LSP.md)\n\nRun 'sitefile <command> --help' for the options of a command.\n";
|
|
8
|
+
export declare const EXTRA_HELP = "\nImages (local store: $SITEFILE_HOME or ~/.sitefile):\n sitefile build [context] -t name[:tag] also store the result as an image (repeatable -t)\n sitefile images [--json] [--digests] [-q] list images (alias: image ls)\n sitefile image inspect <ref> [--json] show an image manifest\n sitefile image prune delete unreferenced manifests and blobs\n sitefile image diff <a> <b> added/removed/changed paths\n sitefile tag <src> <dst> add a name:tag\n sitefile rmi [-f] <ref|id>... remove tags / images\n sitefile save <ref> -o out[.zip|.tar|.tar.gz|.wbn] export an image\n sitefile push <registry/name[:tag]> push a (tagged) image to an OCI registry\n sitefile pull <[registry/]name[:tag][@digest]> pull an image (a bare name means Docker Hub)\n\nVariants:\n sitefile bake [targets...] [-f file] [--print] build several targets (matrix, groups) from\n sitefile.bake.yaml; --print shows the plan\n\nOffline builds:\n sitefile vendor [context] [--vendor-dir dir] download remote imports and the BASE image into\n vendor/sitefile; builds then read them from there\n\nPins:\n sitefile outdated [context] [--json] compare remote imports and the BASE image with\n their pins (never changes anything)\n\nBuild cache (TRANSFORM results: $SITEFILE_HOME/cache/transform):\n sitefile cache info [--json] location, entries, size\n sitefile cache prune [--older-than <days>] delete cached results\n sitefile cache prune --max-size <size> shrink evictable data (LRU) to <size>\n sitefile cache du [--json] size of every cache and the store (alias: cache ls)\n Evictable data over $SITEFILE_CACHE_MAX (default 1GiB, 0 = no limit) is deleted after each build.\n\nServing sites (detached servers are tracked by name):\n sitefile serve [--name n] [-p [host:]port] [-d] [--spa] <ref|./dir|file>\n sitefile serve --watch [-p [host:]port] [ctx] build in memory, serve, rebuild + live reload\n sitefile servers [-a] [--json] list servers (alias: server ls)\n sitefile server stop|rm [-f]|restart|logs [-f] <name|id>\n\nEditor support:\n sitefile lsp [--stdio] language server over stdio (Rust build only; docs/LSP.md)\n\nRun 'sitefile <command> --help' for the options of a command.\n";
|
|
9
9
|
export declare function isExtraCommand(name: string | undefined): boolean;
|
|
10
10
|
export declare function runExtraCommand(argv: readonly string[]): Promise<number>;
|
package/lib/src/cli/index.js
CHANGED
|
@@ -51,7 +51,7 @@ Build cache (TRANSFORM results: $SITEFILE_HOME/cache/transform):
|
|
|
51
51
|
Evictable data over $SITEFILE_CACHE_MAX (default 1GiB, 0 = no limit) is deleted after each build.
|
|
52
52
|
|
|
53
53
|
Serving sites (detached servers are tracked by name):
|
|
54
|
-
sitefile serve [--name n] [-p [host:]port] [-d] [--spa] <ref|./dir>
|
|
54
|
+
sitefile serve [--name n] [-p [host:]port] [-d] [--spa] <ref|./dir|file>
|
|
55
55
|
sitefile serve --watch [-p [host:]port] [ctx] build in memory, serve, rebuild + live reload
|
|
56
56
|
sitefile servers [-a] [--json] list servers (alias: server ls)
|
|
57
57
|
sitefile server stop|rm [-f]|restart|logs [-f] <name|id>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type NetworkSettings } from '../resources/network.js';
|
|
2
|
+
/** parseArgs options for the network flags (build, validate, inspect, graph, why, pull, push, vendor, outdated). */
|
|
3
|
+
export declare const NETWORK_OPTIONS: {
|
|
4
|
+
readonly proxy: {
|
|
5
|
+
readonly type: 'string';
|
|
6
|
+
};
|
|
7
|
+
readonly 'no-proxy': {
|
|
8
|
+
readonly type: 'string';
|
|
9
|
+
readonly multiple: true;
|
|
10
|
+
};
|
|
11
|
+
readonly 'allow-hosts': {
|
|
12
|
+
readonly type: 'string';
|
|
13
|
+
readonly multiple: true;
|
|
14
|
+
};
|
|
15
|
+
readonly 'deny-hosts': {
|
|
16
|
+
readonly type: 'string';
|
|
17
|
+
readonly multiple: true;
|
|
18
|
+
};
|
|
19
|
+
readonly 'connect-timeout': {
|
|
20
|
+
readonly type: 'string';
|
|
21
|
+
};
|
|
22
|
+
readonly 'read-timeout': {
|
|
23
|
+
readonly type: 'string';
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/** Help lines for {@link NETWORK_OPTIONS}, at the given flag-column width. */
|
|
27
|
+
export declare function networkHelp(width: number): string[];
|
|
28
|
+
type Values = Record<string, string | boolean | Array<string | boolean> | undefined>;
|
|
29
|
+
/**
|
|
30
|
+
* The network settings for a command: the SITEFILE_* environment, then the flags on top (flags replace
|
|
31
|
+
* the proxy and timeouts and add to the host lists). Throws E_USAGE for a bad value.
|
|
32
|
+
*/
|
|
33
|
+
export declare function networkOption(v: Values, env?: NodeJS.ProcessEnv): NetworkSettings | undefined;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SitefileError } from '../errors.js';
|
|
2
|
+
import { mergeNetwork, networkFromEnv, networkProblem, splitList } from '../resources/network.js';
|
|
3
|
+
/** parseArgs options for the network flags (build, validate, inspect, graph, why, pull, push, vendor, outdated). */
|
|
4
|
+
export const NETWORK_OPTIONS = {
|
|
5
|
+
proxy: { type: 'string' },
|
|
6
|
+
'no-proxy': { type: 'string', multiple: true },
|
|
7
|
+
'allow-hosts': { type: 'string', multiple: true },
|
|
8
|
+
'deny-hosts': { type: 'string', multiple: true },
|
|
9
|
+
'connect-timeout': { type: 'string' },
|
|
10
|
+
'read-timeout': { type: 'string' },
|
|
11
|
+
};
|
|
12
|
+
/** Help lines for {@link NETWORK_OPTIONS}, at the given flag-column width. */
|
|
13
|
+
export function networkHelp(width) {
|
|
14
|
+
const rows = [
|
|
15
|
+
['--proxy <url>', ['send requests through this HTTP proxy: http://[user:password@]host[:port]', '(also $SITEFILE_PROXY; HTTP(S)_PROXY are never read)']],
|
|
16
|
+
['--no-proxy <hosts>', ['hosts reached directly, not through the proxy (NO_PROXY syntax; repeatable,', 'comma-separated; also $SITEFILE_NO_PROXY)']],
|
|
17
|
+
['--allow-hosts <hosts>', ['contact only hosts matching these patterns (repeatable, comma-separated;', 'also $SITEFILE_ALLOW_HOSTS)']],
|
|
18
|
+
['--deny-hosts <hosts>', ['never contact hosts matching these patterns; beats --allow-hosts', '(repeatable, comma-separated; also $SITEFILE_DENY_HOSTS)']],
|
|
19
|
+
['--connect-timeout <ms>', ['time limit for opening each connection (also $SITEFILE_FETCH_TIMEOUT)']],
|
|
20
|
+
['--read-timeout <ms>', ['longest wait for data on a connection (also $SITEFILE_FETCH_TIMEOUT)']],
|
|
21
|
+
];
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const [flag, lines] of rows) {
|
|
24
|
+
lines.forEach((l, i) => out.push(`${(i === 0 ? flag : '').padEnd(width)}${l}`));
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function ms(raw, flag) {
|
|
29
|
+
if (raw === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const n = Number(raw);
|
|
32
|
+
if (!/^\d+$/.test(raw) || !Number.isSafeInteger(n) || n <= 0)
|
|
33
|
+
throw new SitefileError('E_USAGE', `${flag} expects a positive integer`);
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
36
|
+
const strings = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string') : []);
|
|
37
|
+
/**
|
|
38
|
+
* The network settings for a command: the SITEFILE_* environment, then the flags on top (flags replace
|
|
39
|
+
* the proxy and timeouts and add to the host lists). Throws E_USAGE for a bad value.
|
|
40
|
+
*/
|
|
41
|
+
export function networkOption(v, env = process.env) {
|
|
42
|
+
const flags = {
|
|
43
|
+
...(typeof v.proxy === 'string' ? { proxy: v.proxy } : {}),
|
|
44
|
+
noProxy: strings(v['no-proxy']).flatMap(splitList),
|
|
45
|
+
allowHosts: strings(v['allow-hosts']).flatMap(splitList),
|
|
46
|
+
denyHosts: strings(v['deny-hosts']).flatMap(splitList),
|
|
47
|
+
connectTimeoutMs: ms(typeof v['connect-timeout'] === 'string' ? v['connect-timeout'] : undefined, '--connect-timeout'),
|
|
48
|
+
readTimeoutMs: ms(typeof v['read-timeout'] === 'string' ? v['read-timeout'] : undefined, '--read-timeout'),
|
|
49
|
+
};
|
|
50
|
+
const merged = mergeNetwork(networkFromEnv(env), flags);
|
|
51
|
+
const problem = networkProblem(merged);
|
|
52
|
+
if (problem)
|
|
53
|
+
throw new SitefileError('E_USAGE', problem);
|
|
54
|
+
return Object.keys(merged).length ? merged : undefined;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=network.js.map
|
package/lib/src/cli/outdated.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NETWORK_OPTIONS, networkHelp, networkOption } from './network.js';
|
|
1
2
|
import { insecureRegistriesFromEnv } from '../base-image.js';
|
|
2
3
|
import { collectBuildArgs } from '../build-args.js';
|
|
3
4
|
import { outdated } from '../outdated.js';
|
|
@@ -47,10 +48,12 @@ const outdatedCommand = {
|
|
|
47
48
|
'--allow-private-network permit loopback/private network addresses',
|
|
48
49
|
`--max-download-bytes <n> per-resource size limit (default ${DEFAULT_FETCH_POLICY.maxBytes})`,
|
|
49
50
|
`--timeout <ms> per-resource fetch timeout (default ${DEFAULT_FETCH_POLICY.timeoutMs})`,
|
|
51
|
+
...networkHelp(30),
|
|
50
52
|
'--base <image> check this BASE image (overrides EXPORT ... AS oci BASE)',
|
|
51
53
|
'--insecure-registry <h> allow plain http to registry host[:port] (repeatable)',
|
|
52
54
|
],
|
|
53
55
|
options: {
|
|
56
|
+
...NETWORK_OPTIONS,
|
|
54
57
|
file: { type: 'string', short: 'f' },
|
|
55
58
|
json: { type: 'boolean' },
|
|
56
59
|
'build-arg': { type: 'string', multiple: true },
|
|
@@ -68,6 +71,7 @@ const outdatedCommand = {
|
|
|
68
71
|
requireArgs(pos, 0, 1, 'a context directory');
|
|
69
72
|
const maxBytes = positive(str(v['max-download-bytes']), '--max-download-bytes');
|
|
70
73
|
const timeoutMs = positive(str(v.timeout), '--timeout');
|
|
74
|
+
const network = networkOption(v);
|
|
71
75
|
const file = str(v.file);
|
|
72
76
|
const base = str(v.base);
|
|
73
77
|
const r = await outdated({
|
|
@@ -80,6 +84,7 @@ const outdatedCommand = {
|
|
|
80
84
|
allowPrivateNetwork: !!v['allow-private-network'],
|
|
81
85
|
...(maxBytes ? { maxBytes } : {}),
|
|
82
86
|
...(timeoutMs ? { timeoutMs } : {}),
|
|
87
|
+
...(network ? { network } : {}),
|
|
83
88
|
},
|
|
84
89
|
...(v['no-lockfile'] ? { lockfile: false } : {}),
|
|
85
90
|
...(base !== undefined ? { baseImage: base } : {}),
|
package/lib/src/cli/registry.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NETWORK_OPTIONS, networkHelp, networkOption } from './network.js';
|
|
1
2
|
import { insecureRegistriesFromEnv, makeClient } from '../base-image.js';
|
|
2
3
|
import { SitefileError } from '../errors.js';
|
|
3
4
|
import { parseRef, refKey, withDefaultRegistry, canonicalRef } from '../ref.js';
|
|
@@ -30,9 +31,11 @@ const COMMON_FLAGS = [
|
|
|
30
31
|
' $SITEFILE_REGISTRY_AUTH or ~/.docker/config.json (auths, credHelpers, credsStore)',
|
|
31
32
|
'--password-stdin read the password from stdin',
|
|
32
33
|
'--allow-private-network let redirects/token servers on other hosts resolve to private addresses',
|
|
33
|
-
'--timeout <ms> per-request idle timeout (default 30000)',
|
|
34
|
+
'--timeout <ms> per-request idle timeout (default 30000; --read-timeout wins)',
|
|
35
|
+
...networkHelp(36),
|
|
34
36
|
];
|
|
35
37
|
const COMMON_OPTIONS = {
|
|
38
|
+
...NETWORK_OPTIONS,
|
|
36
39
|
'insecure-registry': { type: 'string', multiple: true },
|
|
37
40
|
username: { type: 'string' },
|
|
38
41
|
'password-stdin': { type: 'boolean' },
|
|
@@ -52,12 +55,14 @@ async function clientOptions(v) {
|
|
|
52
55
|
}
|
|
53
56
|
const timeoutMs = positive(str(v.timeout), '--timeout');
|
|
54
57
|
const maxBlob = positive(str(v['max-download-bytes']), '--max-download-bytes');
|
|
58
|
+
const network = networkOption(v);
|
|
55
59
|
return {
|
|
56
60
|
explicit,
|
|
57
61
|
options: {
|
|
58
62
|
insecureRegistries: insecureRegistriesFromEnv(strs(v['insecure-registry'])),
|
|
59
63
|
allowPrivateNetwork: !!v['allow-private-network'],
|
|
60
64
|
limits: { ...(timeoutMs ? { timeoutMs } : {}), ...(maxBlob ? { maxBlobBytes: maxBlob } : {}) },
|
|
65
|
+
...(network ? { network } : {}),
|
|
61
66
|
},
|
|
62
67
|
};
|
|
63
68
|
}
|