@johnhenry/sitefile 0.0.0-stage → 0.0.1
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 +152 -0
- package/LICENSE +21 -0
- package/README.md +513 -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 +120 -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 +119 -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/network.d.ts +34 -0
- package/lib/src/cli/network.js +56 -0
- package/lib/src/cli/outdated.d.ts +5 -0
- package/lib/src/cli/outdated.js +99 -0
- package/lib/src/cli/registry.d.ts +2 -0
- package/lib/src/cli/registry.js +127 -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 +467 -0
- package/lib/src/cli/vendor.d.ts +2 -0
- package/lib/src/cli/vendor.js +97 -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 +522 -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 +56 -0
- package/lib/src/errors.js +29 -0
- package/lib/src/executor.d.ts +393 -0
- package/lib/src/executor.js +2201 -0
- package/lib/src/explain.d.ts +64 -0
- package/lib/src/explain.js +445 -0
- package/lib/src/export-formats.d.ts +23 -0
- package/lib/src/export-formats.js +63 -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 +13 -0
- package/lib/src/index.js +13 -0
- package/lib/src/language/ast.d.ts +360 -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 +96 -0
- package/lib/src/plugins/cache.js +325 -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 +115 -0
- package/lib/src/registry.js +524 -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 +74 -0
- package/lib/src/resources/https.js +413 -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/network.d.ts +70 -0
- package/lib/src/resources/network.js +304 -0
- package/lib/src/resources/types.d.ts +69 -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 +123 -0
- package/lib/src/server.d.ts +45 -0
- package/lib/src/server.js +246 -0
- package/lib/src/servers.d.ts +85 -0
- package/lib/src/servers.js +225 -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 +26 -0
- package/lib/src/wbn.js +241 -0
- package/lib/src/yaml.d.ts +34 -0
- package/lib/src/yaml.js +562 -0
- package/package.json +59 -4
package/README.md
CHANGED
|
@@ -1,3 +1,514 @@
|
|
|
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`); a `wbn` export also bakes HEADER values into each response and exact-path REDIRECTs into 3xx responses (splats stay in `/_redirects`) |
|
|
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>] [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
|
+
|
|
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] [--compression gzip|none]
|
|
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|site.zip>
|
|
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
|
+
sitefile build examples/hello -o hello.wbn.gz --base-url https://example.com/ # gzip(Web Bundle)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Archives are reproducible: sorted entries, fixed timestamps (or `SOURCE_DATE_EPOCH`),
|
|
280
|
+
uid/gid 0, modes 0644/0755. See [docs/LANGUAGE.md](docs/LANGUAGE.md#export-formats).
|
|
281
|
+
|
|
282
|
+
## Rust implementation
|
|
283
|
+
|
|
284
|
+
`rust/` is a full port, maintained alongside the TypeScript version and checked
|
|
285
|
+
against it by `node conformance/run.mjs` (identical output on every example and
|
|
286
|
+
case). The Rust binary is the shipped product. TypeScript is the reference
|
|
287
|
+
specification and the library API, and both get every feature (see
|
|
288
|
+
[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
|
|
289
|
+
sites, with a fraction of the memory; see [docs/RUST.md](docs/RUST.md) and
|
|
290
|
+
[bench/README.md](bench/README.md).
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
(cd rust && cargo build --release) && rust/target/release/sitefile build examples/blog
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Editor support
|
|
297
|
+
|
|
298
|
+
Highlighting works on its own; the language features (diagnostics,
|
|
299
|
+
completion, hover, go to definition, references, rename, outline) come from
|
|
300
|
+
the Rust binary's language server, `sitefile lsp`
|
|
301
|
+
([docs/LSP.md](docs/LSP.md)), so put it on `PATH` with
|
|
302
|
+
`cargo install --path rust` or point the editor at it.
|
|
303
|
+
|
|
304
|
+
- **VS Code**: [editors/vscode/](editors/vscode/), a TextMate grammar plus a
|
|
305
|
+
language client that starts `sitefile lsp` (`sitefile.server.path`). Not
|
|
306
|
+
published; `(cd editors/vscode && npm install && npm run package)`, then
|
|
307
|
+
`code --install-extension editors/vscode/sitefile-0.2.0.vsix`.
|
|
308
|
+
- **Zed**: [editors/zed/](editors/zed/), tree-sitter highlighting and the
|
|
309
|
+
`sitefile-lsp` language server; **zed: install dev extension** on
|
|
310
|
+
`editors/zed`.
|
|
311
|
+
- **Neovim**: [editors/neovim/](editors/neovim/), tree-sitter queries and a
|
|
312
|
+
`vim.lsp.config` snippet for `sitefile lsp`.
|
|
313
|
+
- **Helix**: [editors/helix/](editors/helix/), queries and a `languages.toml`
|
|
314
|
+
with the grammar and the `sitefile-lsp` server.
|
|
315
|
+
|
|
316
|
+
The grammar behind Zed, Neovim and Helix is
|
|
317
|
+
[editors/tree-sitter-sitefile/](editors/tree-sitter-sitefile/). Without an
|
|
318
|
+
extension, `"files.associations": { "Sitefile": "dockerfile" }` is a usable
|
|
319
|
+
stopgap in VS Code.
|
|
320
|
+
|
|
321
|
+
## Docs
|
|
322
|
+
|
|
323
|
+
- [docs/CLI.md](docs/CLI.md) — every command, the image store layout, reference grammar
|
|
324
|
+
- [docs/LANGUAGE.md](docs/LANGUAGE.md) — syntax and the exact semantics chosen for v0.1
|
|
325
|
+
- [docs/RUST.md](docs/RUST.md) — the Rust port: layout, parity checks, known differences
|
|
326
|
+
- [docs/LSP.md](docs/LSP.md) — the language server for editors (`sitefile lsp`, Rust binary)
|
|
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
|
|
329
|
+
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — invariants and module layout
|
|
330
|
+
- [docs/MILESTONES.md](docs/MILESTONES.md), [docs/TASKS.md](docs/TASKS.md)
|
|
331
|
+
- [AGENTS.md](AGENTS.md) — the verification loop and rules for anyone (human or agent) changing this repo
|
|
332
|
+
|
|
333
|
+
## Development
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
npm ci
|
|
337
|
+
npm test # compile with tsc, then run node:test suites
|
|
338
|
+
npm run typecheck
|
|
339
|
+
(cd rust && cargo build --release && cargo test --release)
|
|
340
|
+
npm run conformance # both implementations on every example and case
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
From a clone, `node lib/src/cli.js` runs the TypeScript CLI after `npm run build`.
|
|
344
|
+
See [AGENTS.md](AGENTS.md) for the full verification loop CI runs.
|
|
345
|
+
|
|
346
|
+
## Adding a new plugin
|
|
347
|
+
|
|
348
|
+
A plugin is a custom renderer that `TRANSFORM` calls. Write one ES module in its
|
|
349
|
+
own folder that exports `transform(request)` and returns the bytes of the output
|
|
350
|
+
file (or `{ files }` for several), declare it with `PLUGIN`, and allow it when you
|
|
351
|
+
build:
|
|
352
|
+
|
|
353
|
+
```js
|
|
354
|
+
// plugins/upper/upper.mjs
|
|
355
|
+
export const apiVersion = 1;
|
|
356
|
+
export async function transform(request) {
|
|
357
|
+
return { bytes: (request.input?.text ?? '').toUpperCase() };
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
PLUGIN upper FROM ./plugins/upper/upper.mjs
|
|
363
|
+
TRANSFORM ./title.txt WITH upper TO /title.txt
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
sitefile build --allow-plugin upper
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
A WebAssembly plugin that imports nothing needs no flag. The request shape, the
|
|
371
|
+
directory form (one call per file), the ABI for `.wasm` plugins, caching and the
|
|
372
|
+
first-party image plugin are in [docs/PLUGINS.md](docs/PLUGINS.md); a complete
|
|
373
|
+
example is `examples/motion/`. Run an external program instead with `TOOL` and
|
|
374
|
+
`--allow-exec` (same document).
|
|
375
|
+
|
|
376
|
+
## Security model
|
|
377
|
+
|
|
378
|
+
A Sitefile can come from someone else (a Git URL, a theme image, a shared
|
|
379
|
+
stage), so building one is treated as running untrusted input. Templates,
|
|
380
|
+
imports and WebAssembly plugins are data; JavaScript plugins and external
|
|
381
|
+
tools are code, and run only when you name them on the command line.
|
|
382
|
+
|
|
383
|
+
**What Sitefile guarantees:**
|
|
384
|
+
|
|
385
|
+
- **Local reads stay inside the build context.** No `..`, absolute paths or
|
|
386
|
+
symlinks out; hard-linked files are refused (`E_HARDLINK`) unless
|
|
387
|
+
`--allow-hardlinks`, since a hard link can alias a file outside the context.
|
|
388
|
+
A `.sitefileignore` (gitignore-style subset) at the context root excludes files
|
|
389
|
+
from directory copies; explicitly named files are never ignored.
|
|
390
|
+
- **Remote imports are `https:` only, to public addresses only** — checked at
|
|
391
|
+
connect time and on every redirect (SSRF), size- and time-limited, never
|
|
392
|
+
decompressed or executed. `--allow-http` and `--allow-private-network` are the
|
|
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)).
|
|
398
|
+
- **Pins are enforced.** Unpinned URLs are recorded in `Sitefile.lock` and must
|
|
399
|
+
match on later builds; `--strict-pins` refuses any remote import or BASE image
|
|
400
|
+
that is not pinned (`INTEGRITY`, `@sha256:` or a lockfile entry).
|
|
401
|
+
- **Git imports run no `git`.** `IMPORT … FROM git+https://host/repo.git#ref:path`
|
|
402
|
+
speaks protocol v2 over the same HTTPS client and policy, checks every object's
|
|
403
|
+
SHA-1, records the commit in `Sitefile.lock`, keeps symlinks inside the tree and
|
|
404
|
+
refuses submodules ([docs/LANGUAGE.md](docs/LANGUAGE.md#git-sources-githttps)).
|
|
405
|
+
- **Secrets go only where they are sent.** `--secret id=token,env=API_TOKEN` with
|
|
406
|
+
`IMPORT … SECRET token AS BEARER` (or `AS HEADER X-Api-Key`) sends the secret
|
|
407
|
+
only to that import's own origin, and never into the output, caches,
|
|
408
|
+
`Sitefile.lock`, vendor files, provenance, traces or messages.
|
|
409
|
+
- **Nothing executes unless you allow it by name.** A JavaScript `PLUGIN` runs
|
|
410
|
+
only with `--allow-plugin <name>`, in its own Node process under Node's
|
|
411
|
+
permission model (read access to its own folder only, no writes, network,
|
|
412
|
+
processes or environment); a `TOOL` runs only with `--allow-exec <name>`, argv
|
|
413
|
+
only, no shell, scrubbed environment. The Sitefile cannot allow itself. A
|
|
414
|
+
WebAssembly plugin must import nothing (checked before the build starts) and
|
|
415
|
+
runs with a time and memory limit, so it needs no flag
|
|
416
|
+
([docs/PLUGINS.md](docs/PLUGINS.md#trust-model---allow-plugin)).
|
|
417
|
+
- **No silent overwrites.** Writing different content to an existing path fails
|
|
418
|
+
unless `--overwrite` is given. Two output paths that differ only in case
|
|
419
|
+
(`/A.html`, `/a.html`) always fail (`E_PATH_COLLISION`), and every path is
|
|
420
|
+
Unicode NFC, so a build gives the same files and image IDs on macOS, Windows
|
|
421
|
+
and Linux.
|
|
422
|
+
- **Exports are atomic.** A directory export is staged in a temporary sibling and
|
|
423
|
+
swapped in; a non-empty destination outside the context is only replaced with
|
|
424
|
+
`--force`. Archive exports (`zip`, `tar`, `tgz`, `wbn`) write one file via a
|
|
425
|
+
temporary sibling and rename; `--out` may not be the context, its parent, root
|
|
426
|
+
or home, nor a directory or symlink, and an existing file outside the context
|
|
427
|
+
needs `--force`.
|
|
428
|
+
- **Offline builds are verified.** `sitefile vendor` stores every remote import
|
|
429
|
+
(and the BASE image's blobs) under `vendor/sitefile/` by content digest; builds
|
|
430
|
+
then read them from there, verified, with no network access
|
|
431
|
+
([docs/LANGUAGE.md](docs/LANGUAGE.md#vendoring-sitefile-vendor)).
|
|
432
|
+
- **`sitefile serve` listens on `127.0.0.1`**, GET/HEAD only, no directory
|
|
433
|
+
listings, unless `-p` names another address.
|
|
434
|
+
|
|
435
|
+
**What is still yours:**
|
|
436
|
+
|
|
437
|
+
- **What you allow is trusted code.** Node calls its permission model a "seat
|
|
438
|
+
belt" for trusted code, not a defence against deliberately malicious code.
|
|
439
|
+
`--allow-plugin`/`--allow-plugins` on a Sitefile you did not write runs its
|
|
440
|
+
author's JavaScript on your machine; `--allow-exec` runs a program on your
|
|
441
|
+
`PATH` with the arguments the Sitefile chose. Read them first.
|
|
442
|
+
- **The content of what you build.** `RENDER --escape=html` escapes template
|
|
443
|
+
values; `${x|raw}` and templates without it do not. Markdown and data you
|
|
444
|
+
import from elsewhere end up in your pages as written.
|
|
445
|
+
- **Unpinned remote content.** The lockfile catches a change; it does not tell
|
|
446
|
+
you whether the first version you locked was the one you meant. Use
|
|
447
|
+
`INTEGRITY`, `@sha256:` and `--strict-pins` for builds that must be
|
|
448
|
+
reproducible.
|
|
449
|
+
- **Web Bundles are unsigned.** `EXPORT … AS wbn` writes an unsigned b2 bundle;
|
|
450
|
+
signing (for example for an Isolated Web App) happens outside Sitefile.
|
|
451
|
+
- **Widening flags are yours to justify.** `--allow-http`,
|
|
452
|
+
`--allow-private-network`, `--allow-hardlinks`, `--force` and `serve -p
|
|
453
|
+
0.0.0.0:…` each turn off one of the guarantees above for that build.
|
|
454
|
+
|
|
455
|
+
## Family
|
|
456
|
+
|
|
457
|
+
Sitefile is the build orchestrator of the @johnhenry family's static tools. It
|
|
458
|
+
runs the pipeline (pinned imports, stages, templates, sandboxed plugins, checks,
|
|
459
|
+
exports); its siblings write file trees in code and serve the result. It has no
|
|
460
|
+
runtime dependency on either: they meet at file formats, a directory, a zip or
|
|
461
|
+
a `.wbn` Web Bundle.
|
|
462
|
+
|
|
463
|
+
- **[`@johnhenry/fileable`](https://github.com/johnhenry/fileable)** -- writes
|
|
464
|
+
file trees in JSX (`Dir`, `File`, `Rm`), for scaffolding and codegen. A
|
|
465
|
+
fileable build's output is an ordinary Sitefile input. Neither package
|
|
466
|
+
depends on the other.
|
|
467
|
+
- **[`@johnhenry/packfile`](https://github.com/johnhenry/packfile)** -- the
|
|
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.
|
|
511
|
+
|
|
512
|
+
## License
|
|
513
|
+
|
|
514
|
+
[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;
|