@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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.0 — first npm release (2026-11-06)
|
|
4
|
+
|
|
5
|
+
`@johnhenry/sitefile` is a new package: Sitefile was never published to npm
|
|
6
|
+
under any name, and `0.0.0` is its first version. It joins the @johnhenry
|
|
7
|
+
family as its static build orchestrator, next to `@johnhenry/fileable` (file
|
|
8
|
+
trees in JSX) and `@johnhenry/packfile` (the Web Bundle reader and server).
|
|
9
|
+
|
|
10
|
+
- **Published as `@johnhenry/sitefile`** (was `sitefile`, `private: true`,
|
|
11
|
+
`0.1.0` in `package.json` only). The tarball ships `lib/src/` (compiled by
|
|
12
|
+
`prepack`, no source maps), the README, `LICENSE` and this file; it has no
|
|
13
|
+
runtime dependencies. The Rust port stays at `0.1.0` in `rust/Cargo.toml`
|
|
14
|
+
and is not published (johnhenry/sitefile#69).
|
|
15
|
+
- **MIT `LICENSE` file added.** `package.json` and `rust/Cargo.toml` already
|
|
16
|
+
said MIT. See johnhenry/sitefile#69.
|
|
17
|
+
- **Node floor raised from 22 to 26** (`engines`, `.nvmrc`, CI), the family
|
|
18
|
+
floor. The suite passed unchanged on Node 26.
|
|
19
|
+
- **CI on every push and pull request** (johnhenry/sitefile#67): typecheck,
|
|
20
|
+
tests and the examples smoke test (`npm run examples`), the Rust
|
|
21
|
+
fmt/build/test (clippy non-blocking until triaged), conformance between the
|
|
22
|
+
two implementations with the release binary, the VS Code extension and
|
|
23
|
+
tree-sitter grammar tests, actionlint, and the family's workflow lint.
|
|
24
|
+
`publish.yml` publishes from `main` through npm trusted publishing.
|
|
25
|
+
- **Interop with `@johnhenry/packfile` is tested**
|
|
26
|
+
(`test/packfile-interop.test.ts`, packfile as a devDependency only): packfile
|
|
27
|
+
reads a Sitefile `.wbn` back into exactly the exported files and serves it,
|
|
28
|
+
and its `fromArchive()` reads the same bundle once gzipped. The other
|
|
29
|
+
direction, importing gzip-wrapped bundles from fileable or packfile with
|
|
30
|
+
`AS tree`, is johnhenry/sitefile#141.
|
|
31
|
+
- **Fixed: a replaced archive destination went unnoticed on Linux.** Before
|
|
32
|
+
renaming the new archive into place, `exportFile` (and the Rust
|
|
33
|
+
`export_file`) checked that the existing destination was still the file
|
|
34
|
+
they inspected by device and inode number only. Linux often gives a file
|
|
35
|
+
created right after an unlink the same inode number, so a destination
|
|
36
|
+
swapped mid-export passed the check and was replaced. The check now also
|
|
37
|
+
compares size and nanosecond `ctime`. Found by the new CI's first Linux
|
|
38
|
+
run; the existing test only ever passed on macOS. Fixed in 0e0a1a1 (#142).
|
|
39
|
+
- **Fixed: a case-only collision inside one directory had no source line.**
|
|
40
|
+
Two names that differ only in case in a directory read by `COPY` (possible
|
|
41
|
+
on a case-sensitive file system) failed with `E_PATH_COLLISION` but without
|
|
42
|
+
the instruction's line; both implementations now report it. Its test only
|
|
43
|
+
runs on case-sensitive file systems, so it had never run. Fixed in 0e0a1a1
|
|
44
|
+
(#142).
|
|
45
|
+
- **Docs:** README badges, `## Contents`, `## Install`, `## Adding a new
|
|
46
|
+
plugin`, `## Security model` (the former "Safety defaults", with what stays
|
|
47
|
+
the user's responsibility), `## Family`; `examples/README.md`;
|
|
48
|
+
`AGENTS.md` reshaped into the family playbook with `CLAUDE.md` as a symlink.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 John Henry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,476 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sitefile
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@johnhenry/sitefile)
|
|
4
|
+
[](https://github.com/johnhenry/sitefile/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Full documentation: [opensource.johnhenry.me/sitefile](https://opensource.johnhenry.me/sitefile/)
|
|
8
|
+
|
|
9
|
+
Docker-inspired static artifact builder. Compose immutable filesystem trees from
|
|
10
|
+
local and remote sources, render templates, and export a deployable directory —
|
|
11
|
+
no container runtime, no shell, no runtime dependencies.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
# examples/hello/Sitefile
|
|
15
|
+
ARG TITLE = "Hello"
|
|
16
|
+
LET GREETING = "Welcome to ${TITLE}."
|
|
17
|
+
COPY ./public/ /
|
|
18
|
+
RENDER --escape=html ./templates/index.html TO /index.html
|
|
19
|
+
EXPORT / AS directory
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
sitefile build examples/hello --build-arg TITLE="My site"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This writes `examples/hello/dist/index.html` (with the title substituted) and
|
|
27
|
+
`examples/hello/dist/style.css`.
|
|
28
|
+
|
|
29
|
+
## Contents
|
|
30
|
+
|
|
31
|
+
- [Install](#install)
|
|
32
|
+
- [Status](#status)
|
|
33
|
+
- [CLI](#cli)
|
|
34
|
+
- [Variants: `sitefile bake`](#variants-sitefile-bake)
|
|
35
|
+
- [Watch mode](#watch-mode)
|
|
36
|
+
- [Profiling a build](#profiling-a-build)
|
|
37
|
+
- [Images and servers](#images-and-servers)
|
|
38
|
+
- [Export formats](#export-formats)
|
|
39
|
+
- [Rust implementation](#rust-implementation)
|
|
40
|
+
- [Editor support](#editor-support)
|
|
41
|
+
- [Docs](#docs)
|
|
42
|
+
- [Development](#development)
|
|
43
|
+
- [Adding a new plugin](#adding-a-new-plugin)
|
|
44
|
+
- [Security model](#security-model)
|
|
45
|
+
- [Family](#family)
|
|
46
|
+
- [License](#license)
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install -g @johnhenry/sitefile # the `sitefile` command (TypeScript implementation)
|
|
52
|
+
npx @johnhenry/sitefile build . # or without installing
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Requires Node.js 26 or later; the npm package has no runtime dependencies.
|
|
56
|
+
The Rust binary (faster, and the one that provides `sitefile lsp` for editors)
|
|
57
|
+
builds from this repository: `cargo install --path rust`. Prebuilt binaries are
|
|
58
|
+
not published yet (johnhenry/sitefile#69).
|
|
59
|
+
|
|
60
|
+
`@johnhenry/sitefile` is a new package: it was never published to npm under any
|
|
61
|
+
name, and `0.0.0` is its first version.
|
|
62
|
+
|
|
63
|
+
The library API is the same engine the CLI uses (`import { … } from
|
|
64
|
+
"@johnhenry/sitefile"`); see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
65
|
+
|
|
66
|
+
## Status
|
|
67
|
+
|
|
68
|
+
Milestones **M0** (local vertical slice), **M1** (remote inputs) and **M2**
|
|
69
|
+
(trees and stages: multi-stage builds, `COPY --from`, archive imports,
|
|
70
|
+
`.sitefileignore`; see `examples/multistage`) are implemented and tested; see [docs/TASKS.md](docs/TASKS.md). Requires Node.js 22+,
|
|
71
|
+
no runtime dependencies.
|
|
72
|
+
|
|
73
|
+
| Instruction | Supported |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `FROM scratch\|<dir>\|<stage>\|image <ref> [AT <dir>] [AS name]` | yes; several FROMs make a multi-stage build, the last stage is exported; `image` starts from a site image's files (a theme: `examples/theme`, `examples/themed-site`), locked in `Sitefile.lock` when it comes from a registry |
|
|
76
|
+
| `IMPORT name FROM <dir>\|git+https://…#ref:dir AS sitefile [ARG NAME=value]…` | yes; the stages of another Sitefile, local or from Git, as `name+<stage>` in `FROM` and `--from` (like Earthly targets; `examples/shared-stages`), with its own ARGs and paths, pinned in `Sitefile.lock` |
|
|
77
|
+
| `ARG NAME[: type] [= value]` | yes, `string`/`number`/`boolean`, `--build-arg` |
|
|
78
|
+
| `LET NAME[: type] = value` | yes |
|
|
79
|
+
| `IMPORT name FROM <path or https URL> [AS file\|text\|json\|yaml\|csv\|markdown\|wbn\|tree] [INTEGRITY sha256:…] [SORT BY key [DESC]] [SCHEMA <file>] [DRAFTS true\|false]` | yes; `AS tree` unpacks tar, tar.gz, zip and Web Bundles; `AS markdown` reads posts with frontmatter (`SCHEMA` checks it against a YAML/JSON schema); `AS yaml`/`csv`/`wbn` give data for templates |
|
|
80
|
+
| `COPY [--overwrite] [--from=<stage>] <source> <dest>` | yes; source is a local path, `name:/path`, `<<EOF` inline content, or with `--from` an absolute path in an earlier stage |
|
|
81
|
+
| `RENDER [--escape=html] [--overwrite] [--from=<stage>] <template> TO <dest> [FOR item IN list [BY key] [PAGE n [FIRST <dest>]]]` | yes; template is a file or `<<EOF` inline body; `${NAME}`, `${json.path}`, `${#each}`, `${#if}`, `${x\|raw}`; `FOR` renders one page per element, `BY` one per tag, `PAGE` paginates |
|
|
82
|
+
| `PLUGIN name FROM ./plugins/x.mjs\|x.wasm [INTEGRITY sha256:…]` | yes; a custom renderer: a sandboxed JS module (runs only with `--allow-plugin name`) or an import-free WebAssembly module (no flag needed; see [docs/PLUGINS.md](docs/PLUGINS.md), `examples/wasm/`) |
|
|
83
|
+
| `TRANSFORM [--include/--exclude=<pattern>] [<source>] WITH name TO <dest> [OPTIONS k=v …] [FOR item IN list]` | yes; e.g. text → animated SVG (`examples/motion/`), responsive images (`examples/image/`); a directory source runs once per file (`TO /img/thumbs/*.png`) |
|
|
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
|
+
| `SITEMAP BASE <url> [TO <dest>]` | yes; a sitemaps.org `/sitemap.xml` of the `.html` pages so far |
|
|
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`) |
|
|
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
|
+
| `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`) |
|
|
91
|
+
|
|
92
|
+
Templates can also live in the Sitefile itself (see `examples/inline/`):
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
RENDER --escape=html <<HTML TO /index.html
|
|
96
|
+
<h1>${TITLE}</h1>
|
|
97
|
+
HTML
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
A blog from Markdown posts with frontmatter (see `examples/blog/`):
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
ARG DRAFTS: boolean = false # --build-arg DRAFTS=true previews drafts
|
|
104
|
+
IMPORT posts FROM ./posts AS markdown SORT BY date DESC SCHEMA ./post.schema.yaml DRAFTS ${DRAFTS}
|
|
105
|
+
RENDER --escape=html ./templates/index.html TO /page/${page.number}/index.html FOR page IN posts PAGE 10 FIRST /index.html
|
|
106
|
+
RENDER --escape=html ./templates/post.html TO /posts/${post.slug}/index.html FOR post IN posts
|
|
107
|
+
RENDER --escape=html ./templates/tag.html TO /tags/${tag.key}/index.html FOR tag IN posts BY tags
|
|
108
|
+
FEED posts BASE ${SITE_URL} LINK /posts/${item.slug}/index.html TITLE ${SITE_TITLE}
|
|
109
|
+
SITEMAP BASE ${SITE_URL}
|
|
110
|
+
FINGERPRINT /style.css MANIFEST /asset-manifest.json
|
|
111
|
+
CHECKLINKS
|
|
112
|
+
REDIRECT /feed /feed.xml
|
|
113
|
+
HEADER /*.css Cache-Control: public, max-age=3600
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Data files are readable too (see `examples/data/`):
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
IMPORT site FROM ./site.yaml AS yaml # ${site.nav.0.title}
|
|
120
|
+
IMPORT team FROM ./team.csv AS csv SORT BY joined # FOR person IN team
|
|
121
|
+
IMPORT bundle FROM ./vendor/hello.wbn AS wbn # ${#each bundle.entries as e}
|
|
122
|
+
IMPORT hello FROM ./vendor/hello.wbn AS tree # COPY hello:/ /hello/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Custom renderers are plugins that run in a sandboxed Node process (see `examples/motion/`):
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
PLUGIN motion FROM ./plugins/motion.mjs
|
|
129
|
+
TRANSFORM ./title.txt WITH motion TO /title.svg OPTIONS style=typewriter
|
|
130
|
+
TRANSFORM WITH motion TO /cards/${post.slug}.svg OPTIONS text=${post.title} FOR post IN posts
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A JavaScript plugin is code from the repository you are building, so it runs only when you allow it:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
sitefile build --allow-plugin motion # or --allow-plugins for every PLUGIN
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
WebAssembly plugins (`PLUGIN … FROM ./x.wasm`) import nothing, can only compute, and need no flag (see `examples/wasm/`).
|
|
140
|
+
|
|
141
|
+
Images: the first-party image plugin (`plugins/image/image.wasm`, WebAssembly) decodes PNG, JPEG, GIF and WebP, resizes with a fixed Lanczos-3 filter and writes PNG, JPEG or lossless WebP, with a metadata file listing each variant's path, width, height and a ready `srcset` (see [docs/PLUGINS.md](docs/PLUGINS.md#the-image-plugin), `examples/image/`):
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
PLUGIN image FROM ./plugins/image.wasm INTEGRITY sha256:…
|
|
145
|
+
TRANSFORM ./photos/sky.jpg WITH image TO /img/ OPTIONS widths=400,800,1200 quality=80
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
External programs are explicit opt-ins: declare a `TOOL`, and allow it on the command line.
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
TOOL ffmpeg
|
|
152
|
+
TRANSFORM WITH ffmpeg TO /intro.mp4 ARGS -f lavfi -i "color=c=black:s=1280x720:d=3" -fflags +bitexact -map_metadata -1 {output}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
sitefile build --allow-exec ffmpeg
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
TRANSFORM results are cached in `$SITEFILE_HOME/cache/transform` (`--no-cache` to skip; `sitefile cache info|prune`).
|
|
160
|
+
CI runs and teammates can share them through an OCI registry: `--cache-to <registry/name:tag>` pushes the results a
|
|
161
|
+
build used, `--cache-from <ref>` reuses them (see [docs/CLI.md](docs/CLI.md#registry-cache)).
|
|
162
|
+
|
|
163
|
+
## CLI
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
sitefile build [context] [-o dist] [--format directory|zip|tar|tgz|wbn] [--base-url URL]
|
|
167
|
+
[--build-arg K=V | --build-arg K] [--build-arg-file vars.json|.yaml|.env] [--force]
|
|
168
|
+
[--frozen-lockfile | --update-lock] [--strict-pins] [--allow-http] [--allow-private-network]
|
|
169
|
+
[--allow-hardlinks] [--allow-exec name[=/abs/path]]... [--allow-plugin name]... [--allow-plugins]
|
|
170
|
+
[--no-cache] [--cache-from registry/name:tag]... [--cache-to registry/name:tag] [--watch] [--target stage] [--profile trace.json]
|
|
171
|
+
sitefile validate [context] [--json] [--target stage]
|
|
172
|
+
sitefile inspect [context] [--json] [--target stage]
|
|
173
|
+
sitefile graph [context] [--json | --dot] # dependency graph of imports, stages and operations
|
|
174
|
+
sitefile why <path> [context] [--json] # which instructions wrote an exported file, and what overwrote it
|
|
175
|
+
sitefile vendor [context] [--vendor-dir vendor/sitefile] # download remote inputs for offline builds
|
|
176
|
+
sitefile outdated [context] [--json] # compare remote imports and BASE images with their pins (read-only)
|
|
177
|
+
sitefile bake [targets...] [-f sitefile.bake.yaml] [--print [--json]] [-j n] # build several variants
|
|
178
|
+
|
|
179
|
+
# images and servers (see below and docs/CLI.md)
|
|
180
|
+
sitefile build [context] -t name[:tag] # also store the build as an image
|
|
181
|
+
sitefile images | image inspect|diff|prune | tag | rmi | save
|
|
182
|
+
sitefile cache info | cache prune [--older-than days] # TRANSFORM result cache
|
|
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>
|
|
185
|
+
sitefile serve --watch [-p [host:]port] [--spa] [context] # build in memory, serve, live reload
|
|
186
|
+
sitefile servers [-a] | server stop|rm [-f]|restart|logs [-f] <name>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`--target <stage>` (like `docker build --target`) stops after a named stage and exports that stage's whole
|
|
190
|
+
tree, in the EXPORT format but without the EXPORT path or `_redirects`/`_headers`; an unknown name is a usage
|
|
191
|
+
error listing the stages. See [docs/CLI.md](docs/CLI.md#build-target---target-stage).
|
|
192
|
+
`sitefile graph` and `sitefile why` are described in [docs/CLI.md](docs/CLI.md#explaining-a-build-graph-and-why).
|
|
193
|
+
|
|
194
|
+
## Variants: `sitefile bake`
|
|
195
|
+
|
|
196
|
+
Like `docker buildx bake`, `sitefile bake` builds several variants of a site (locales, staging and production)
|
|
197
|
+
from one bake file, `sitefile.bake.yaml` (or `.yml`, `.json`), with the outputs side by side:
|
|
198
|
+
|
|
199
|
+
```yaml
|
|
200
|
+
groups:
|
|
201
|
+
default: [site]
|
|
202
|
+
targets:
|
|
203
|
+
site:
|
|
204
|
+
matrix: # one target per combination: site-en-staging, site-en-production, ...
|
|
205
|
+
locale: [en, fr]
|
|
206
|
+
env: [staging, production]
|
|
207
|
+
arg-files: ["env/${env}.env"] # ${name} is a matrix value
|
|
208
|
+
args: {LOCALE: "${locale}"}
|
|
209
|
+
output: dist/${env}/${locale}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
cd examples/bake && sitefile bake # four builds, up to 4 at a time (-j), sharing the TRANSFORM cache
|
|
214
|
+
sitefile bake --print # the resolved targets (--json for tools), without building
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Targets also take `context`, `file`, `target` (a stage), `format`, `tags` and `inherits`. Every target runs even
|
|
218
|
+
when one fails (then the exit code is 1); outputs that overlap or a tag used twice are refused before anything is
|
|
219
|
+
built. See [docs/CLI.md](docs/CLI.md#variants-sitefile-bake).
|
|
220
|
+
|
|
221
|
+
## Watch mode
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
sitefile build --watch examples/hello # rebuild dist/ whenever the context, Sitefile or a --build-arg-file changes
|
|
225
|
+
sitefile serve --watch examples/hello # build in memory, serve on 127.0.0.1, reload open pages after each rebuild
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Rebuilds are debounced (100 ms) and changes made during a build become one follow-up build. The output
|
|
229
|
+
directory, `.git`, `Sitefile.lock` and the store are not watched. A failed rebuild prints the error and keeps the
|
|
230
|
+
last good output (exports are swapped in atomically; `serve --watch` keeps serving the last good build). Live
|
|
231
|
+
reload is a small script added to HTML responses only under `serve --watch`, listening on the reserved
|
|
232
|
+
`/__sitefile/reload` event stream; it is never written to exports. Rebuilds are incremental: steps whose inputs
|
|
233
|
+
are unchanged (by content digest) are reused from the previous build, in memory, and the result is identical to a
|
|
234
|
+
clean build (`SITEFILE_NO_INCREMENTAL=1` turns it off). See [docs/CLI.md](docs/CLI.md#watch-mode).
|
|
235
|
+
|
|
236
|
+
## Profiling a build
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
sitefile build examples/blog --profile trace.json # open in ui.perfetto.dev or chrome://tracing
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`--profile` writes a Chrome trace of the build: parsing, each import, plugin start, stage, operation, TRANSFORM call
|
|
243
|
+
(with its cache hit or miss), the BASE pull and the export, so slow steps stand out. See
|
|
244
|
+
[docs/CLI.md](docs/CLI.md#profiling---profile).
|
|
245
|
+
|
|
246
|
+
## Images and servers
|
|
247
|
+
|
|
248
|
+
Builds can be kept in a local, Docker-like **image store**, which is a standard
|
|
249
|
+
[OCI image layout](https://github.com/opencontainers/image-spec/blob/main/image-layout.md)
|
|
250
|
+
in `$SITEFILE_HOME` (default `~/.sitefile`). An image is the site's files as one
|
|
251
|
+
layer; its ID is the manifest digest, and building identical output gives the same ID.
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
sitefile build examples/hello -t hello:1 # no -o: stored only, nothing written to the context
|
|
255
|
+
sitefile images # REPOSITORY TAG IMAGE ID CREATED FILES SIZE
|
|
256
|
+
sitefile save hello:1 -o hello.oci.tar # or .zip / .tar.gz / .wbn / a directory
|
|
257
|
+
sitefile serve -d --name site -p 8080 hello:1 # serve it, in the background
|
|
258
|
+
sitefile servers; sitefile server logs site; sitefile server stop site; sitefile server rm site
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`serve` serves GET/HEAD only on `127.0.0.1` (never `0.0.0.0` unless you ask for it in `-p`), with ETags,
|
|
262
|
+
correct MIME types and no directory listings; it serves `/404.html` for missing paths, `/about` from
|
|
263
|
+
`/about.html`, and honours the site's `_redirects` and `_headers` (exact paths and `/*` splats). `EXPORT / AS oci` (or `build -o site.oci.tar`) writes the
|
|
264
|
+
same OCI image without the store; on its own it contains files only, but `EXPORT / AS oci BASE nginx:alpine
|
|
265
|
+
AT /usr/share/nginx/html` (or `--base`) builds a runnable image on top of a pulled base, and `sitefile push` / `pull`
|
|
266
|
+
move images to and from any OCI registry. `--provenance` and `--sbom` attach a SLSA provenance statement and a
|
|
267
|
+
CycloneDX file list to the image as OCI 1.1 referrers (deterministic, pushed with it; the image ID does not change). See
|
|
268
|
+
[docs/CLI.md](docs/CLI.md).
|
|
269
|
+
|
|
270
|
+
## Export formats
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
sitefile build examples/hello --format zip # examples/hello/dist.zip
|
|
274
|
+
sitefile build examples/hello -o hello.tar.gz # format from the extension
|
|
275
|
+
sitefile build examples/hello --format wbn --base-url https://example.com/
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Archives are reproducible: sorted entries, fixed timestamps (or `SOURCE_DATE_EPOCH`),
|
|
279
|
+
uid/gid 0, modes 0644/0755. See [docs/LANGUAGE.md](docs/LANGUAGE.md#export-formats).
|
|
280
|
+
|
|
281
|
+
## Rust implementation
|
|
282
|
+
|
|
283
|
+
`rust/` is a full port, maintained alongside the TypeScript version and checked
|
|
284
|
+
against it by `node conformance/run.mjs` (identical output on every example and
|
|
285
|
+
case). The Rust binary is the shipped product. TypeScript is the reference
|
|
286
|
+
specification and the library API, and both get every feature (see
|
|
287
|
+
[docs/RUST.md](docs/RUST.md#roles-decided-in-82)). It starts in ~4 ms instead of ~70 ms and is 1.3–2× faster on large
|
|
288
|
+
sites, with a fraction of the memory; see [docs/RUST.md](docs/RUST.md) and
|
|
289
|
+
[bench/README.md](bench/README.md).
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
(cd rust && cargo build --release) && rust/target/release/sitefile build examples/blog
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Editor support
|
|
296
|
+
|
|
297
|
+
Highlighting works on its own; the language features (diagnostics,
|
|
298
|
+
completion, hover, go to definition, references, rename, outline) come from
|
|
299
|
+
the Rust binary's language server, `sitefile lsp`
|
|
300
|
+
([docs/LSP.md](docs/LSP.md)), so put it on `PATH` with
|
|
301
|
+
`cargo install --path rust` or point the editor at it.
|
|
302
|
+
|
|
303
|
+
- **VS Code**: [editors/vscode/](editors/vscode/), a TextMate grammar plus a
|
|
304
|
+
language client that starts `sitefile lsp` (`sitefile.server.path`). Not
|
|
305
|
+
published; `(cd editors/vscode && npm install && npm run package)`, then
|
|
306
|
+
`code --install-extension editors/vscode/sitefile-0.2.0.vsix`.
|
|
307
|
+
- **Zed**: [editors/zed/](editors/zed/), tree-sitter highlighting and the
|
|
308
|
+
`sitefile-lsp` language server; **zed: install dev extension** on
|
|
309
|
+
`editors/zed`.
|
|
310
|
+
- **Neovim**: [editors/neovim/](editors/neovim/), tree-sitter queries and a
|
|
311
|
+
`vim.lsp.config` snippet for `sitefile lsp`.
|
|
312
|
+
- **Helix**: [editors/helix/](editors/helix/), queries and a `languages.toml`
|
|
313
|
+
with the grammar and the `sitefile-lsp` server.
|
|
314
|
+
|
|
315
|
+
The grammar behind Zed, Neovim and Helix is
|
|
316
|
+
[editors/tree-sitter-sitefile/](editors/tree-sitter-sitefile/). Without an
|
|
317
|
+
extension, `"files.associations": { "Sitefile": "dockerfile" }` is a usable
|
|
318
|
+
stopgap in VS Code.
|
|
319
|
+
|
|
320
|
+
## Docs
|
|
321
|
+
|
|
322
|
+
- [docs/CLI.md](docs/CLI.md) — every command, the image store layout, reference grammar
|
|
323
|
+
- [docs/LANGUAGE.md](docs/LANGUAGE.md) — syntax and the exact semantics chosen for v0.1
|
|
324
|
+
- [docs/RUST.md](docs/RUST.md) — the Rust port: layout, parity checks, known differences
|
|
325
|
+
- [docs/LSP.md](docs/LSP.md) — the language server for editors (`sitefile lsp`, Rust binary)
|
|
326
|
+
- [docs/PLUGINS.md](docs/PLUGINS.md) — writing renderer plugins, and the sandbox
|
|
327
|
+
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — invariants and module layout
|
|
328
|
+
- [docs/MILESTONES.md](docs/MILESTONES.md), [docs/TASKS.md](docs/TASKS.md)
|
|
329
|
+
- [AGENTS.md](AGENTS.md) — the verification loop and rules for anyone (human or agent) changing this repo
|
|
330
|
+
|
|
331
|
+
## Development
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
npm ci
|
|
335
|
+
npm test # compile with tsc, then run node:test suites
|
|
336
|
+
npm run typecheck
|
|
337
|
+
(cd rust && cargo build --release && cargo test --release)
|
|
338
|
+
npm run conformance # both implementations on every example and case
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
From a clone, `node lib/src/cli.js` runs the TypeScript CLI after `npm run build`.
|
|
342
|
+
See [AGENTS.md](AGENTS.md) for the full verification loop CI runs.
|
|
343
|
+
|
|
344
|
+
## Adding a new plugin
|
|
345
|
+
|
|
346
|
+
A plugin is a custom renderer that `TRANSFORM` calls. Write one ES module in its
|
|
347
|
+
own folder that exports `transform(request)` and returns the bytes of the output
|
|
348
|
+
file (or `{ files }` for several), declare it with `PLUGIN`, and allow it when you
|
|
349
|
+
build:
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
// plugins/upper/upper.mjs
|
|
353
|
+
export const apiVersion = 1;
|
|
354
|
+
export async function transform(request) {
|
|
355
|
+
return { bytes: (request.input?.text ?? '').toUpperCase() };
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
PLUGIN upper FROM ./plugins/upper/upper.mjs
|
|
361
|
+
TRANSFORM ./title.txt WITH upper TO /title.txt
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
sitefile build --allow-plugin upper
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
A WebAssembly plugin that imports nothing needs no flag. The request shape, the
|
|
369
|
+
directory form (one call per file), the ABI for `.wasm` plugins, caching and the
|
|
370
|
+
first-party image plugin are in [docs/PLUGINS.md](docs/PLUGINS.md); a complete
|
|
371
|
+
example is `examples/motion/`. Run an external program instead with `TOOL` and
|
|
372
|
+
`--allow-exec` (same document).
|
|
373
|
+
|
|
374
|
+
## Security model
|
|
375
|
+
|
|
376
|
+
A Sitefile can come from someone else (a Git URL, a theme image, a shared
|
|
377
|
+
stage), so building one is treated as running untrusted input. Templates,
|
|
378
|
+
imports and WebAssembly plugins are data; JavaScript plugins and external
|
|
379
|
+
tools are code, and run only when you name them on the command line.
|
|
380
|
+
|
|
381
|
+
**What Sitefile guarantees:**
|
|
382
|
+
|
|
383
|
+
- **Local reads stay inside the build context.** No `..`, absolute paths or
|
|
384
|
+
symlinks out; hard-linked files are refused (`E_HARDLINK`) unless
|
|
385
|
+
`--allow-hardlinks`, since a hard link can alias a file outside the context.
|
|
386
|
+
A `.sitefileignore` (gitignore-style subset) at the context root excludes files
|
|
387
|
+
from directory copies; explicitly named files are never ignored.
|
|
388
|
+
- **Remote imports are `https:` only, to public addresses only** — checked at
|
|
389
|
+
connect time and on every redirect (SSRF), size- and time-limited, never
|
|
390
|
+
decompressed or executed. `--allow-http` and `--allow-private-network` are the
|
|
391
|
+
only ways to widen this.
|
|
392
|
+
- **Pins are enforced.** Unpinned URLs are recorded in `Sitefile.lock` and must
|
|
393
|
+
match on later builds; `--strict-pins` refuses any remote import or BASE image
|
|
394
|
+
that is not pinned (`INTEGRITY`, `@sha256:` or a lockfile entry).
|
|
395
|
+
- **Git imports run no `git`.** `IMPORT … FROM git+https://host/repo.git#ref:path`
|
|
396
|
+
speaks protocol v2 over the same HTTPS client and policy, checks every object's
|
|
397
|
+
SHA-1, records the commit in `Sitefile.lock`, keeps symlinks inside the tree and
|
|
398
|
+
refuses submodules ([docs/LANGUAGE.md](docs/LANGUAGE.md#git-sources-githttps)).
|
|
399
|
+
- **Secrets go only where they are sent.** `--secret id=token,env=API_TOKEN` with
|
|
400
|
+
`IMPORT … SECRET token AS BEARER` (or `AS HEADER X-Api-Key`) sends the secret
|
|
401
|
+
only to that import's own origin, and never into the output, caches,
|
|
402
|
+
`Sitefile.lock`, vendor files, provenance, traces or messages.
|
|
403
|
+
- **Nothing executes unless you allow it by name.** A JavaScript `PLUGIN` runs
|
|
404
|
+
only with `--allow-plugin <name>`, in its own Node process under Node's
|
|
405
|
+
permission model (read access to its own folder only, no writes, network,
|
|
406
|
+
processes or environment); a `TOOL` runs only with `--allow-exec <name>`, argv
|
|
407
|
+
only, no shell, scrubbed environment. The Sitefile cannot allow itself. A
|
|
408
|
+
WebAssembly plugin must import nothing (checked before the build starts) and
|
|
409
|
+
runs with a time and memory limit, so it needs no flag
|
|
410
|
+
([docs/PLUGINS.md](docs/PLUGINS.md#trust-model---allow-plugin)).
|
|
411
|
+
- **No silent overwrites.** Writing different content to an existing path fails
|
|
412
|
+
unless `--overwrite` is given. Two output paths that differ only in case
|
|
413
|
+
(`/A.html`, `/a.html`) always fail (`E_PATH_COLLISION`), and every path is
|
|
414
|
+
Unicode NFC, so a build gives the same files and image IDs on macOS, Windows
|
|
415
|
+
and Linux.
|
|
416
|
+
- **Exports are atomic.** A directory export is staged in a temporary sibling and
|
|
417
|
+
swapped in; a non-empty destination outside the context is only replaced with
|
|
418
|
+
`--force`. Archive exports (`zip`, `tar`, `tgz`, `wbn`) write one file via a
|
|
419
|
+
temporary sibling and rename; `--out` may not be the context, its parent, root
|
|
420
|
+
or home, nor a directory or symlink, and an existing file outside the context
|
|
421
|
+
needs `--force`.
|
|
422
|
+
- **Offline builds are verified.** `sitefile vendor` stores every remote import
|
|
423
|
+
(and the BASE image's blobs) under `vendor/sitefile/` by content digest; builds
|
|
424
|
+
then read them from there, verified, with no network access
|
|
425
|
+
([docs/LANGUAGE.md](docs/LANGUAGE.md#vendoring-sitefile-vendor)).
|
|
426
|
+
- **`sitefile serve` listens on `127.0.0.1`**, GET/HEAD only, no directory
|
|
427
|
+
listings, unless `-p` names another address.
|
|
428
|
+
|
|
429
|
+
**What is still yours:**
|
|
430
|
+
|
|
431
|
+
- **What you allow is trusted code.** Node calls its permission model a "seat
|
|
432
|
+
belt" for trusted code, not a defence against deliberately malicious code.
|
|
433
|
+
`--allow-plugin`/`--allow-plugins` on a Sitefile you did not write runs its
|
|
434
|
+
author's JavaScript on your machine; `--allow-exec` runs a program on your
|
|
435
|
+
`PATH` with the arguments the Sitefile chose. Read them first.
|
|
436
|
+
- **The content of what you build.** `RENDER --escape=html` escapes template
|
|
437
|
+
values; `${x|raw}` and templates without it do not. Markdown and data you
|
|
438
|
+
import from elsewhere end up in your pages as written.
|
|
439
|
+
- **Unpinned remote content.** The lockfile catches a change; it does not tell
|
|
440
|
+
you whether the first version you locked was the one you meant. Use
|
|
441
|
+
`INTEGRITY`, `@sha256:` and `--strict-pins` for builds that must be
|
|
442
|
+
reproducible.
|
|
443
|
+
- **Web Bundles are unsigned.** `EXPORT … AS wbn` writes an unsigned b2 bundle;
|
|
444
|
+
signing (for example for an Isolated Web App) happens outside Sitefile.
|
|
445
|
+
- **Widening flags are yours to justify.** `--allow-http`,
|
|
446
|
+
`--allow-private-network`, `--allow-hardlinks`, `--force` and `serve -p
|
|
447
|
+
0.0.0.0:…` each turn off one of the guarantees above for that build.
|
|
448
|
+
|
|
449
|
+
## Family
|
|
450
|
+
|
|
451
|
+
Sitefile is the build orchestrator of the @johnhenry family's static tools. It
|
|
452
|
+
runs the pipeline (pinned imports, stages, templates, sandboxed plugins, checks,
|
|
453
|
+
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 or a `.wbn`
|
|
455
|
+
Web Bundle.
|
|
456
|
+
|
|
457
|
+
- **[`@johnhenry/fileable`](https://github.com/johnhenry/fileable)** -- writes
|
|
458
|
+
file trees in JSX (`Dir`, `File`, `Rm`), for scaffolding and codegen. A
|
|
459
|
+
fileable build's output directory, or its `encode="zip"` archive, is an
|
|
460
|
+
ordinary Sitefile input: `COPY ./generated/ /`, or `IMPORT gen FROM
|
|
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.
|
|
464
|
+
- **[`@johnhenry/packfile`](https://github.com/johnhenry/packfile)** -- the
|
|
465
|
+
family's Web Bundle reader and in-memory server. `EXPORT / AS wbn BASE <url>`
|
|
466
|
+
writes a plain b2 Web Bundle that packfile's `fromWebBundle(buffer, { baseURL })`
|
|
467
|
+
reads back into exactly the exported files, and `createRouter()` serves as a
|
|
468
|
+
`(Request) => Response` handler. packfile's own archive format
|
|
469
|
+
(`fromArchive()`) is gzip around the same bundle under
|
|
470
|
+
`https://packfile.invalid/`: gzip a Sitefile bundle exported with that BASE and
|
|
471
|
+
`fromArchive()` reads it as is. `test/packfile-interop.test.ts` checks all
|
|
472
|
+
three; `@johnhenry/packfile` is a devDependency for that test only.
|
|
473
|
+
|
|
474
|
+
## License
|
|
475
|
+
|
|
476
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { crc32 } from './deflate.js';
|
|
2
|
+
import type { Tree } from './tree.js';
|
|
3
|
+
/**
|
|
4
|
+
* Deterministic archive writers. The same tree always yields byte-identical
|
|
5
|
+
* output: entries are sorted by path, there are
|
|
6
|
+
* no directory entries, uid/gid are 0, there are no user/group names, modes are
|
|
7
|
+
* the tree's 0644/0755, and every timestamp is fixed. Compression uses the
|
|
8
|
+
* deterministic encoder in deflate.ts (shared with the Rust port), so zip,
|
|
9
|
+
* tar.gz and OCI layer bytes are the same in both implementations.
|
|
10
|
+
*/
|
|
11
|
+
export interface WriterOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Seconds since the Unix epoch used for every entry timestamp (the
|
|
14
|
+
* `SOURCE_DATE_EPOCH` convention). Default: 1980-01-01 00:00 for zip (the DOS
|
|
15
|
+
* epoch) and 0 for tar. The gzip header mtime is always 0.
|
|
16
|
+
*/
|
|
17
|
+
readonly epoch?: number | undefined;
|
|
18
|
+
/** tar only: directory entries (relative, no slashes at the ends, e.g. `srv`, `srv/www`) written before the files. */
|
|
19
|
+
readonly dirs?: readonly string[] | undefined;
|
|
20
|
+
}
|
|
21
|
+
/** Read `SOURCE_DATE_EPOCH`; undefined if unset, E_USAGE if malformed. */
|
|
22
|
+
export declare function sourceDateEpoch(env?: NodeJS.ProcessEnv): number | undefined;
|
|
23
|
+
export { crc32 };
|
|
24
|
+
/** Zip (no zip64). Deflate when it shrinks the data, otherwise stored. */
|
|
25
|
+
export declare function writeZip(tree: Tree, options?: WriterOptions): Uint8Array;
|
|
26
|
+
/** POSIX ustar; long names use the prefix field, then a pax `path` extended header. */
|
|
27
|
+
export declare function writeTar(tree: Tree, options?: WriterOptions): Uint8Array;
|
|
28
|
+
/** gzip of the tar, header mtime 0 and no filename, so the stream is reproducible. */
|
|
29
|
+
export declare function writeTgz(tree: Tree, options?: WriterOptions): Uint8Array;
|
|
30
|
+
/** Reproducible gzip of an already-written tar (see deflate.ts for the exact format). */
|
|
31
|
+
export declare function gzipTar(tar: Uint8Array): Uint8Array;
|