@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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type SourceSpan } from './errors.js';
|
|
2
|
+
import { Tree } from './tree.js';
|
|
3
|
+
/** Hex digits of the SHA-256 of the content in a fingerprinted name. */
|
|
4
|
+
export declare const HASH_LENGTH = 10;
|
|
5
|
+
/**
|
|
6
|
+
* Check a FINGERPRINT pattern: an absolute path (NFC) with no control characters. A trailing `/`
|
|
7
|
+
* means everything below that directory (`/assets/` is `/assets/**`).
|
|
8
|
+
*/
|
|
9
|
+
export declare function checkPattern(raw: string, span: SourceSpan): string;
|
|
10
|
+
/**
|
|
11
|
+
* Whether a tree path matches a pattern. Segments are compared one by one; a `**` segment matches any
|
|
12
|
+
* number of segments (also none). Matching is by code point and case-sensitive.
|
|
13
|
+
*/
|
|
14
|
+
export declare function globMatches(pattern: string, path: string): boolean;
|
|
15
|
+
/** `name.<hash>.ext`: the hash goes before the last extension (`app.min.<hash>.js`), or at the end (`LICENSE.<hash>`, `.env.<hash>`). */
|
|
16
|
+
export declare function hashedName(path: string, hash: string): string;
|
|
17
|
+
export interface FingerprintOp {
|
|
18
|
+
readonly patterns: readonly string[];
|
|
19
|
+
readonly manifest?: string;
|
|
20
|
+
/** The directory the site is served from (the EXPORT path): absolute references resolve below it. */
|
|
21
|
+
readonly root: string;
|
|
22
|
+
readonly overwrite: boolean;
|
|
23
|
+
readonly span: SourceSpan;
|
|
24
|
+
}
|
|
25
|
+
export interface Fingerprinted {
|
|
26
|
+
readonly tree: Tree;
|
|
27
|
+
/** Old path -> new path, sorted by old path. */
|
|
28
|
+
readonly renames: ReadonlyArray<readonly [string, string]>;
|
|
29
|
+
}
|
|
30
|
+
/** The manifest: a JSON object of old path -> new path, sorted, two-space indented, with a final newline. */
|
|
31
|
+
export declare function manifestJson(renames: ReadonlyArray<readonly [string, string]>): string;
|
|
32
|
+
/**
|
|
33
|
+
* Fingerprint the files of `tree` that match the patterns. Each is renamed to `name.<hash>.ext`, where the
|
|
34
|
+
* hash is of its final content: a CSS file's references to other fingerprinted files are rewritten first, so
|
|
35
|
+
* its hash covers them (a cycle of such references is an error). Then every HTML and CSS file in the tree has
|
|
36
|
+
* its references to renamed files rewritten. The renamed and rewritten files get the origin `origin`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function fingerprint(tree: Tree, op: FingerprintOp, origin: string, warn: (m: string) => void): Fingerprinted;
|
|
39
|
+
/** Compose rename maps: `earlier` then `later` (a renamed file renamed again maps to its last name). */
|
|
40
|
+
export declare function composeRenames(earlier: ReadonlyMap<string, string>, later: ReadonlyArray<readonly [string, string]>): Map<string, string>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// FINGERPRINT (docs/LANGUAGE.md, "Fingerprinting"): rename assets to content-hashed names and rewrite
|
|
2
|
+
// the HTML and CSS that reference them. Ported as rust/src/fingerprint.rs: both must produce the same
|
|
3
|
+
// tree, manifest and messages.
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { SitefileError } from './errors.js';
|
|
6
|
+
import { referenceKind, resolveReference, rewriteReferences, scanFile } from './links.js';
|
|
7
|
+
import { nfc } from './paths.js';
|
|
8
|
+
import { makeFile, Tree } from './tree.js';
|
|
9
|
+
/** Hex digits of the SHA-256 of the content in a fingerprinted name. */
|
|
10
|
+
export const HASH_LENGTH = 10;
|
|
11
|
+
/** Names FINGERPRINT never renames (besides HTML pages): files hosts look up by name. */
|
|
12
|
+
const FIXED_NAMES = new Set(['_redirects', '_headers']);
|
|
13
|
+
// ---------------------------------------------------------------- patterns
|
|
14
|
+
/**
|
|
15
|
+
* Check a FINGERPRINT pattern: an absolute path (NFC) with no control characters. A trailing `/`
|
|
16
|
+
* means everything below that directory (`/assets/` is `/assets/**`).
|
|
17
|
+
*/
|
|
18
|
+
export function checkPattern(raw, span) {
|
|
19
|
+
if (/[\u0000-\u001f\u007f]/.test(raw))
|
|
20
|
+
throw new SitefileError('E_VALIDATION', 'FINGERPRINT pattern must not contain control characters', span);
|
|
21
|
+
if (!raw.startsWith('/'))
|
|
22
|
+
throw new SitefileError('E_VALIDATION', `FINGERPRINT pattern '${raw}' must be an absolute path starting with '/' (e.g. /assets/ or /**/*.css)`, span);
|
|
23
|
+
const p = nfc(raw);
|
|
24
|
+
return p.endsWith('/') ? p + '**' : p;
|
|
25
|
+
}
|
|
26
|
+
/** One path segment against one pattern segment: `*` is any run of characters, `?` any one. */
|
|
27
|
+
function segmentMatches(pat, s, pi = 0, si = 0) {
|
|
28
|
+
while (pi < pat.length) {
|
|
29
|
+
const c = pat[pi];
|
|
30
|
+
if (c === '*') {
|
|
31
|
+
for (let k = si; k <= s.length; k++)
|
|
32
|
+
if (segmentMatches(pat, s, pi + 1, k))
|
|
33
|
+
return true;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (si >= s.length || (c !== '?' && c !== s[si]))
|
|
37
|
+
return false;
|
|
38
|
+
pi++;
|
|
39
|
+
si++;
|
|
40
|
+
}
|
|
41
|
+
return si === s.length;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether a tree path matches a pattern. Segments are compared one by one; a `**` segment matches any
|
|
45
|
+
* number of segments (also none). Matching is by code point and case-sensitive.
|
|
46
|
+
*/
|
|
47
|
+
export function globMatches(pattern, path) {
|
|
48
|
+
const pat = pattern.split('/').slice(1).map((s) => [...s]);
|
|
49
|
+
const segs = path.split('/').slice(1).map((s) => [...s]);
|
|
50
|
+
const go = (pi, si) => {
|
|
51
|
+
if (pi === pat.length)
|
|
52
|
+
return si === segs.length;
|
|
53
|
+
const p = pat[pi];
|
|
54
|
+
if (p.length === 2 && p[0] === '*' && p[1] === '*') {
|
|
55
|
+
for (let k = si; k <= segs.length; k++)
|
|
56
|
+
if (go(pi + 1, k))
|
|
57
|
+
return true;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return si < segs.length && segmentMatches(p, segs[si]) && go(pi + 1, si + 1);
|
|
61
|
+
};
|
|
62
|
+
return go(0, 0);
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------- names
|
|
65
|
+
/** `name.<hash>.ext`: the hash goes before the last extension (`app.min.<hash>.js`), or at the end (`LICENSE.<hash>`, `.env.<hash>`). */
|
|
66
|
+
export function hashedName(path, hash) {
|
|
67
|
+
const slash = path.lastIndexOf('/');
|
|
68
|
+
const base = path.slice(slash + 1);
|
|
69
|
+
const dot = base.lastIndexOf('.');
|
|
70
|
+
const named = dot > 0 ? `${base.slice(0, dot)}.${hash}${base.slice(dot)}` : `${base}.${hash}`;
|
|
71
|
+
return path.slice(0, slash + 1) + named;
|
|
72
|
+
}
|
|
73
|
+
function contentHash(bytes) {
|
|
74
|
+
return createHash('sha256').update(bytes).digest('hex').slice(0, HASH_LENGTH);
|
|
75
|
+
}
|
|
76
|
+
/** Whether FINGERPRINT may rename this path: not an HTML page and not a host file such as `_redirects`. */
|
|
77
|
+
function renamable(path) {
|
|
78
|
+
return referenceKind(path) !== 'html' && !FIXED_NAMES.has(path.slice(path.lastIndexOf('/') + 1));
|
|
79
|
+
}
|
|
80
|
+
function utf8Compare(a, b) {
|
|
81
|
+
return Buffer.compare(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));
|
|
82
|
+
}
|
|
83
|
+
/** The manifest: a JSON object of old path -> new path, sorted, two-space indented, with a final newline. */
|
|
84
|
+
export function manifestJson(renames) {
|
|
85
|
+
if (renames.length === 0)
|
|
86
|
+
return '{}\n';
|
|
87
|
+
return '{\n' + renames.map(([a, b]) => ` ${JSON.stringify(a)}: ${JSON.stringify(b)}`).join(',\n') + '\n}\n';
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Fingerprint the files of `tree` that match the patterns. Each is renamed to `name.<hash>.ext`, where the
|
|
91
|
+
* hash is of its final content: a CSS file's references to other fingerprinted files are rewritten first, so
|
|
92
|
+
* its hash covers them (a cycle of such references is an error). Then every HTML and CSS file in the tree has
|
|
93
|
+
* its references to renamed files rewritten. The renamed and rewritten files get the origin `origin`.
|
|
94
|
+
*/
|
|
95
|
+
export function fingerprint(tree, op, origin, warn) {
|
|
96
|
+
const policy = op.overwrite ? 'overwrite' : 'error';
|
|
97
|
+
const entries = tree.entries().sort(([a], [b]) => utf8Compare(a, b));
|
|
98
|
+
const selected = entries.filter(([p]) => renamable(p) && p !== op.manifest && op.patterns.some((pat) => globMatches(pat, p)));
|
|
99
|
+
if (selected.length === 0)
|
|
100
|
+
warn(`${origin}: no files match ${op.patterns.join(' ')}`);
|
|
101
|
+
const isSelected = new Set(selected.map(([p]) => p));
|
|
102
|
+
const scans = new Map();
|
|
103
|
+
const scan = (p, node) => {
|
|
104
|
+
if (!scans.has(p))
|
|
105
|
+
scans.set(p, scanFile(p, node.bytes));
|
|
106
|
+
return scans.get(p);
|
|
107
|
+
};
|
|
108
|
+
// Hash in dependency order: a CSS file after the fingerprinted files it references.
|
|
109
|
+
const renamed = new Map();
|
|
110
|
+
const content = new Map();
|
|
111
|
+
const state = new Map();
|
|
112
|
+
const byPath = new Map(entries);
|
|
113
|
+
const visit = (p, stack) => {
|
|
114
|
+
const st = state.get(p);
|
|
115
|
+
if (st === 'done')
|
|
116
|
+
return;
|
|
117
|
+
if (st === 'visiting') {
|
|
118
|
+
const cycle = [...stack.slice(stack.indexOf(p)), p].join(' -> ');
|
|
119
|
+
throw new SitefileError('E_VALIDATION', `FINGERPRINT: ${cycle} is a cycle of references, so no file's name can include the hash of the files it references; leave one of them out of FINGERPRINT`, op.span);
|
|
120
|
+
}
|
|
121
|
+
state.set(p, 'visiting');
|
|
122
|
+
const node = byPath.get(p);
|
|
123
|
+
const s = referenceKind(p) === 'css' ? scan(p, node) : undefined;
|
|
124
|
+
let bytes = node.bytes;
|
|
125
|
+
if (s) {
|
|
126
|
+
for (const r of s.refs) {
|
|
127
|
+
const t = resolveReference(r.url, p, op.root);
|
|
128
|
+
if (t.kind === 'internal' && !t.dir && isSelected.has(t.path))
|
|
129
|
+
visit(t.path, [...stack, p]);
|
|
130
|
+
}
|
|
131
|
+
const text = rewriteReferences(s.text, s.refs, p, op.root, (q) => renamed.get(q));
|
|
132
|
+
if (text !== undefined)
|
|
133
|
+
bytes = new TextEncoder().encode(text);
|
|
134
|
+
}
|
|
135
|
+
renamed.set(p, hashedName(p, contentHash(bytes)));
|
|
136
|
+
content.set(p, bytes);
|
|
137
|
+
state.set(p, 'done');
|
|
138
|
+
};
|
|
139
|
+
for (const [p] of selected)
|
|
140
|
+
visit(p, []);
|
|
141
|
+
const files = [];
|
|
142
|
+
for (const [p, node] of entries) {
|
|
143
|
+
if (isSelected.has(p))
|
|
144
|
+
continue;
|
|
145
|
+
const s = referenceKind(p) !== undefined ? scan(p, node) : undefined;
|
|
146
|
+
const text = s ? rewriteReferences(s.text, s.refs, p, op.root, (q) => renamed.get(q)) : undefined;
|
|
147
|
+
files.push([p, text === undefined ? node : makeFile(new TextEncoder().encode(text), origin, node.mode)]);
|
|
148
|
+
}
|
|
149
|
+
const renames = selected.map(([p]) => [p, renamed.get(p)]);
|
|
150
|
+
let out = Tree.fromFiles(files);
|
|
151
|
+
out = out.withFiles(selected.map(([p, node]) => [renamed.get(p), makeFile(content.get(p), origin, node.mode)]), policy, op.span);
|
|
152
|
+
if (op.manifest !== undefined)
|
|
153
|
+
out = out.withFiles([[op.manifest, makeFile(new TextEncoder().encode(manifestJson(renames)), origin)]], policy, op.span);
|
|
154
|
+
return { tree: out, renames };
|
|
155
|
+
}
|
|
156
|
+
/** Compose rename maps: `earlier` then `later` (a renamed file renamed again maps to its last name). */
|
|
157
|
+
export function composeRenames(earlier, later) {
|
|
158
|
+
const next = new Map(later);
|
|
159
|
+
const intermediate = new Set(earlier.values());
|
|
160
|
+
const out = new Map();
|
|
161
|
+
for (const [a, b] of earlier)
|
|
162
|
+
out.set(a, next.get(b) ?? b);
|
|
163
|
+
for (const [a, b] of later)
|
|
164
|
+
if (!intermediate.has(a))
|
|
165
|
+
out.set(a, b);
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontmatter parser. The block between the `---` lines is parsed with the
|
|
3
|
+
* YAML subset documented in `yaml.ts` (nested maps and lists, flow collections,
|
|
4
|
+
* block scalars, quoted strings, ...), but it must be a mapping at the top level.
|
|
5
|
+
*
|
|
6
|
+
* Frontmatter exists only when the text starts (after an optional BOM) with a
|
|
7
|
+
* `---` line. It ends at the next line that is exactly `---` or `...`.
|
|
8
|
+
*
|
|
9
|
+
* Every error is reported as E_FRONTMATTER with the Sitefile-file line and column.
|
|
10
|
+
*/
|
|
11
|
+
export type FrontmatterValue = string | number | boolean | null | FrontmatterValue[] | {
|
|
12
|
+
[key: string]: FrontmatterValue;
|
|
13
|
+
};
|
|
14
|
+
export interface ParsedFrontmatter {
|
|
15
|
+
data: {
|
|
16
|
+
[key: string]: FrontmatterValue;
|
|
17
|
+
};
|
|
18
|
+
body: string;
|
|
19
|
+
/** 1-based line of the file on which `body` starts. */
|
|
20
|
+
bodyLine: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function parseFrontmatter(text: string, file: string): ParsedFrontmatter;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { SitefileError } from './errors.js';
|
|
2
|
+
import { parseYamlDocument } from './yaml.js';
|
|
3
|
+
export function parseFrontmatter(text, file) {
|
|
4
|
+
const src = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
5
|
+
// Split into lines, remembering where each starts so the body can be sliced.
|
|
6
|
+
const lines = [];
|
|
7
|
+
const starts = [];
|
|
8
|
+
for (let pos = 0; pos <= src.length;) {
|
|
9
|
+
let nl = src.indexOf('\n', pos);
|
|
10
|
+
if (nl === -1)
|
|
11
|
+
nl = src.length;
|
|
12
|
+
starts.push(pos);
|
|
13
|
+
lines.push(src.slice(pos, nl).replace(/\r$/, ''));
|
|
14
|
+
pos = nl + 1;
|
|
15
|
+
}
|
|
16
|
+
if (lines[0] !== '---')
|
|
17
|
+
return { data: {}, body: text, bodyLine: 1 };
|
|
18
|
+
let close = -1;
|
|
19
|
+
for (let i = 1; i < lines.length; i++) {
|
|
20
|
+
if (lines[i] === '---' || lines[i] === '...') {
|
|
21
|
+
close = i;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (close === -1) {
|
|
26
|
+
throw new SitefileError('E_FRONTMATTER', "unterminated frontmatter: missing closing '---'", { file, line: 1, column: 1 });
|
|
27
|
+
}
|
|
28
|
+
const data = parseBlock(lines.slice(1, close).join('\n'), file);
|
|
29
|
+
const bodyStart = close + 1 < starts.length ? (starts[close + 1] ?? src.length) : src.length;
|
|
30
|
+
return { data, body: src.slice(bodyStart), bodyLine: close + 2 };
|
|
31
|
+
}
|
|
32
|
+
function parseBlock(block, file) {
|
|
33
|
+
try {
|
|
34
|
+
// The block starts on file line 2 (line 1 is the opening '---').
|
|
35
|
+
const value = parseYamlDocument(block, file, 2, true);
|
|
36
|
+
return (value ?? {});
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
if (e instanceof SitefileError && e.code === 'E_DATA') {
|
|
40
|
+
throw new SitefileError('E_FRONTMATTER', e.detail, e.span, { cause: e });
|
|
41
|
+
}
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=frontmatter.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { SitefileError, isSitefileError, formatSpan, needsPlugin, needsTool, type ErrorCode, type Needs, type SourceSpan } from './errors.js';
|
|
2
|
+
export type * from './language/ast.js';
|
|
3
|
+
export { parseSitefile } from './language/parser.js';
|
|
4
|
+
export { validate, parseProgram } from './language/validate.js';
|
|
5
|
+
export { plan, type Plan, type Operation, type ImportSpec, type SourceRef } from './planner.js';
|
|
6
|
+
export { build, buildToDirectory, planBuild, loadProgram, type BuildOptions, type BuildResult } from './executor.js';
|
|
7
|
+
export { exportDirectory } from './exporter.js';
|
|
8
|
+
export { BuildMemo, type MemoStats } from './memo.js';
|
|
9
|
+
export { Profiler } from './profile.js';
|
|
10
|
+
export { Tree, makeFile, type FileNode, type ConflictPolicy } from './tree.js';
|
|
11
|
+
export { DEFAULT_ARCHIVE_LIMITS, DEFAULT_FETCH_POLICY, type ArchiveLimits, type FetchPolicy, type Resource } from './resources/types.js';
|
|
12
|
+
export { networkFromEnv, mergeNetwork, networkProblem, hostMatches, type NetworkSettings } from './resources/network.js';
|
|
13
|
+
export { extractArchive } from './resources/archive.js';
|
package/lib/src/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { SitefileError, isSitefileError, formatSpan, needsPlugin, needsTool } from './errors.js';
|
|
2
|
+
export { parseSitefile } from './language/parser.js';
|
|
3
|
+
export { validate, parseProgram } from './language/validate.js';
|
|
4
|
+
export { plan } from './planner.js';
|
|
5
|
+
export { build, buildToDirectory, planBuild, loadProgram } from './executor.js';
|
|
6
|
+
export { exportDirectory } from './exporter.js';
|
|
7
|
+
export { BuildMemo } from './memo.js';
|
|
8
|
+
export { Profiler } from './profile.js';
|
|
9
|
+
export { Tree, makeFile } from './tree.js';
|
|
10
|
+
export { DEFAULT_ARCHIVE_LIMITS, DEFAULT_FETCH_POLICY } from './resources/types.js';
|
|
11
|
+
export { networkFromEnv, mergeNetwork, networkProblem, hostMatches } from './resources/network.js';
|
|
12
|
+
export { extractArchive } from './resources/archive.js';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import type { SourceSpan } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* A string with `${...}` interpolations, split into parts.
|
|
4
|
+
* In Sitefile operands `path` is always empty (only ARG/LET names are allowed);
|
|
5
|
+
* in RENDER templates `path` holds dotted JSON access (`${data.site.title}`).
|
|
6
|
+
*/
|
|
7
|
+
export type TemplatePart = {
|
|
8
|
+
readonly kind: 'text';
|
|
9
|
+
readonly value: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: 'var';
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly path: readonly string[];
|
|
14
|
+
readonly span: SourceSpan;
|
|
15
|
+
};
|
|
16
|
+
/** One instruction argument: a quoted or bare string, possibly interpolated. */
|
|
17
|
+
export interface Operand {
|
|
18
|
+
readonly parts: readonly TemplatePart[];
|
|
19
|
+
readonly span: SourceSpan;
|
|
20
|
+
}
|
|
21
|
+
export type ValueType = 'string' | 'number' | 'boolean';
|
|
22
|
+
export type ImportKind = 'file' | 'text' | 'json' | 'tree' | 'markdown' | 'yaml' | 'csv' | 'wbn' | 'html' | 'sitefile';
|
|
23
|
+
export type EscapeMode = 'none' | 'html';
|
|
24
|
+
export interface FromInstruction {
|
|
25
|
+
readonly kind: 'FROM';
|
|
26
|
+
readonly span: SourceSpan;
|
|
27
|
+
/**
|
|
28
|
+
* `scratch` keyword, an operand (an earlier stage name or a context-relative
|
|
29
|
+
* directory; the planner decides, see docs/LANGUAGE.md), or `image <ref> [AT <dir>]`.
|
|
30
|
+
*/
|
|
31
|
+
readonly base: FromBase;
|
|
32
|
+
readonly alias?: string;
|
|
33
|
+
/** Set when written as `STAGE <name> [FROM <base>]`; otherwise the instruction is `FROM … [AS name]`. Only used in error messages. */
|
|
34
|
+
readonly keyword?: 'STAGE';
|
|
35
|
+
}
|
|
36
|
+
/** `FROM image <ref> [AT <dir>]`: start from the files of an OCI site image (a registry or the local store). */
|
|
37
|
+
export interface ImageBase {
|
|
38
|
+
readonly kind: 'image';
|
|
39
|
+
readonly ref: Operand;
|
|
40
|
+
/** `AT <dir>`: the directory inside the image to start from (default: the image's `dev.sitefile.root` label). */
|
|
41
|
+
readonly at?: Operand;
|
|
42
|
+
}
|
|
43
|
+
export type FromBase = 'scratch' | Operand | ImageBase;
|
|
44
|
+
/** Whether a FROM base is `image <ref>`. */
|
|
45
|
+
export declare function isImageBase(b: FromBase): b is ImageBase;
|
|
46
|
+
export interface ArgInstruction {
|
|
47
|
+
readonly kind: 'ARG';
|
|
48
|
+
readonly span: SourceSpan;
|
|
49
|
+
readonly name: string;
|
|
50
|
+
/** Defaults to 'string'. */
|
|
51
|
+
readonly type: ValueType;
|
|
52
|
+
readonly defaultValue?: Operand;
|
|
53
|
+
}
|
|
54
|
+
export interface LetInstruction {
|
|
55
|
+
readonly kind: 'LET';
|
|
56
|
+
readonly span: SourceSpan;
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly type: ValueType;
|
|
59
|
+
readonly value: Operand;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `LET name: html = <markup>` or `LET name: html <<HTML … HTML`: an HTML fragment, a template that
|
|
63
|
+
* is not evaluated where it is declared but where a RENDER template uses it (`${name}`), against
|
|
64
|
+
* that RENDER's tree, loop variables and escape mode. Its markup is inserted as written.
|
|
65
|
+
*/
|
|
66
|
+
export interface FragmentInstruction {
|
|
67
|
+
readonly kind: 'LET';
|
|
68
|
+
readonly span: SourceSpan;
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly type: 'html';
|
|
71
|
+
readonly body: FragmentBody;
|
|
72
|
+
}
|
|
73
|
+
/** The template of an html LET: the rest of its line (`span` is its first character), or a heredoc. */
|
|
74
|
+
export type FragmentBody = {
|
|
75
|
+
readonly kind: 'line';
|
|
76
|
+
readonly text: string;
|
|
77
|
+
readonly span: SourceSpan;
|
|
78
|
+
} | InlineBody;
|
|
79
|
+
/** Whether a LET is an html fragment. */
|
|
80
|
+
export declare function isFragment(ins: Instruction): ins is FragmentInstruction;
|
|
81
|
+
export interface ImportInstruction {
|
|
82
|
+
readonly kind: 'IMPORT';
|
|
83
|
+
readonly span: SourceSpan;
|
|
84
|
+
readonly name: string;
|
|
85
|
+
readonly uri: Operand;
|
|
86
|
+
/** Omitted: inferred (local directory -> tree, everything else -> file). */
|
|
87
|
+
readonly as?: ImportKind;
|
|
88
|
+
/** `sha256:<64 lowercase hex>` */
|
|
89
|
+
readonly integrity?: string;
|
|
90
|
+
/** `SORT BY key [ASC|DESC]`, for `AS markdown` collections. */
|
|
91
|
+
readonly sort?: {
|
|
92
|
+
readonly key: string;
|
|
93
|
+
readonly descending: boolean;
|
|
94
|
+
readonly span: SourceSpan;
|
|
95
|
+
};
|
|
96
|
+
/** `SCHEMA <path>`: a local YAML/JSON schema every document (or list element) must match. */
|
|
97
|
+
readonly schema?: {
|
|
98
|
+
readonly path: Operand;
|
|
99
|
+
readonly span: SourceSpan;
|
|
100
|
+
};
|
|
101
|
+
/** `DRAFTS <true|false>`: whether elements with `draft: true` are kept (evaluated by the planner). */
|
|
102
|
+
readonly drafts?: {
|
|
103
|
+
readonly value: Operand;
|
|
104
|
+
readonly span: SourceSpan;
|
|
105
|
+
};
|
|
106
|
+
/** `SECRET <id> AS HEADER <Name>` / `SECRET <id> AS BEARER` (build secrets, sent with remote requests). */
|
|
107
|
+
readonly secrets?: readonly ImportSecret[];
|
|
108
|
+
/** `ARG NAME=value` (repeatable): build args passed to a Sitefile imported `AS sitefile`. */
|
|
109
|
+
readonly args?: readonly ImportArg[];
|
|
110
|
+
}
|
|
111
|
+
/** `ARG NAME=value` on an `IMPORT … AS sitefile`: sets an ARG of the imported Sitefile. */
|
|
112
|
+
export interface ImportArg {
|
|
113
|
+
readonly name: string;
|
|
114
|
+
readonly value: Operand;
|
|
115
|
+
readonly span: SourceSpan;
|
|
116
|
+
}
|
|
117
|
+
/** A build secret sent as an HTTP request header of a remote IMPORT. */
|
|
118
|
+
export interface ImportSecret {
|
|
119
|
+
/** The `--secret id=…` it names. */
|
|
120
|
+
readonly id: string;
|
|
121
|
+
/** Header name as written (`Authorization` for BEARER). */
|
|
122
|
+
readonly header: string;
|
|
123
|
+
/** `AS BEARER`: the header value is `Bearer <secret>`. */
|
|
124
|
+
readonly bearer: boolean;
|
|
125
|
+
readonly span: SourceSpan;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Content written inline in the Sitefile with a heredoc (`COPY <<CSS /a.css`,
|
|
129
|
+
* `RENDER <<HTML TO /index.html`): the lines after the instruction, up to a line
|
|
130
|
+
* that is exactly the delimiter.
|
|
131
|
+
*/
|
|
132
|
+
export interface InlineBody {
|
|
133
|
+
readonly kind: 'inline';
|
|
134
|
+
/** The delimiter word, e.g. `HTML`. */
|
|
135
|
+
readonly delimiter: string;
|
|
136
|
+
/** The body, each line ending in `\n` (empty string for an empty body). */
|
|
137
|
+
readonly text: string;
|
|
138
|
+
/** Span of the `<<` marker. */
|
|
139
|
+
readonly span: SourceSpan;
|
|
140
|
+
/** 1-based Sitefile line of the body's first line. */
|
|
141
|
+
readonly bodyLine: number;
|
|
142
|
+
}
|
|
143
|
+
export declare function isInline(x: Operand | InlineBody): x is InlineBody;
|
|
144
|
+
/** `--from=<stage>`: read the source from an earlier stage's final tree. */
|
|
145
|
+
export interface StageRef {
|
|
146
|
+
readonly name: string;
|
|
147
|
+
readonly span: SourceSpan;
|
|
148
|
+
}
|
|
149
|
+
export interface CopyInstruction {
|
|
150
|
+
readonly kind: 'COPY';
|
|
151
|
+
readonly span: SourceSpan;
|
|
152
|
+
readonly source: Operand | InlineBody;
|
|
153
|
+
readonly dest: Operand;
|
|
154
|
+
readonly from?: StageRef;
|
|
155
|
+
/** `--overwrite`: replace files that differ instead of failing. */
|
|
156
|
+
readonly overwrite: boolean;
|
|
157
|
+
/** `--exclude=<pattern>` (repeatable): `.sitefileignore` patterns, relative to the source directory. */
|
|
158
|
+
readonly exclude?: readonly string[];
|
|
159
|
+
/** `--chmod=644|755`: the mode of every copied file (0o644 or 0o755). */
|
|
160
|
+
readonly chmod?: number;
|
|
161
|
+
/** `--parents`: keep the source's path under the destination. */
|
|
162
|
+
readonly parents?: true;
|
|
163
|
+
}
|
|
164
|
+
export interface RenderInstruction {
|
|
165
|
+
readonly kind: 'RENDER';
|
|
166
|
+
readonly span: SourceSpan;
|
|
167
|
+
readonly template: Operand | InlineBody;
|
|
168
|
+
readonly dest: Operand;
|
|
169
|
+
readonly from?: StageRef;
|
|
170
|
+
/** `--escape=html|none`, default none. */
|
|
171
|
+
readonly escape: EscapeMode;
|
|
172
|
+
/** `FOR item IN list [BY key] [PAGE n [FIRST <dest>]]`: render once per element, group or page of an imported list. */
|
|
173
|
+
readonly each?: RenderEach;
|
|
174
|
+
readonly overwrite: boolean;
|
|
175
|
+
/** `--inline-svg`: the template's `${file … | svg}` may paste SVG markup into the page. */
|
|
176
|
+
readonly inlineSvg?: true;
|
|
177
|
+
}
|
|
178
|
+
/** `FOR item IN list [BY key] [PAGE n [FIRST <dest>]]` of a RENDER. */
|
|
179
|
+
export interface RenderEach {
|
|
180
|
+
readonly as: string;
|
|
181
|
+
readonly list: string;
|
|
182
|
+
readonly span: SourceSpan;
|
|
183
|
+
/** `BY key`: one element per distinct value of the field (tag pages). */
|
|
184
|
+
readonly by?: {
|
|
185
|
+
readonly key: string;
|
|
186
|
+
readonly span: SourceSpan;
|
|
187
|
+
};
|
|
188
|
+
/** `PAGE n [FIRST <dest>]`: pages of n items; FIRST is the first page's destination. */
|
|
189
|
+
readonly page?: {
|
|
190
|
+
readonly size: Operand;
|
|
191
|
+
readonly first?: Operand;
|
|
192
|
+
readonly span: SourceSpan;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export interface ExportInstruction {
|
|
196
|
+
readonly kind: 'EXPORT';
|
|
197
|
+
readonly span: SourceSpan;
|
|
198
|
+
readonly path: Operand;
|
|
199
|
+
readonly format: ExportFormat;
|
|
200
|
+
/** `BASE <https-url>`: the URL the exported tree is mounted at (required for `wbn`). */
|
|
201
|
+
readonly base?: Operand;
|
|
202
|
+
/** `AT </srv/www>`: where the files live inside the layer (`oci` only). */
|
|
203
|
+
readonly at?: Operand;
|
|
204
|
+
/**
|
|
205
|
+
* `COMPRESSION gzip|none` (`oci` and `wbn` only): how the oci site layer is stored (default gzip),
|
|
206
|
+
* or whether the whole Web Bundle is gzipped (default none).
|
|
207
|
+
*/
|
|
208
|
+
readonly compression?: {
|
|
209
|
+
readonly value: OciCompression;
|
|
210
|
+
readonly span: SourceSpan;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* `EXPORT ... COMPRESSION`: for `oci`, the site layer encoding, `gzip` (tar+gzip, the default) or `none`
|
|
215
|
+
* (plain tar); for `wbn`, `gzip` wraps the bundle in gzip (the family's `.wbn.gz`) and `none` (the default)
|
|
216
|
+
* writes the plain bundle.
|
|
217
|
+
*/
|
|
218
|
+
export type OciCompression = 'gzip' | 'none';
|
|
219
|
+
export declare const OCI_COMPRESSIONS: readonly OciCompression[];
|
|
220
|
+
/** Parse a compression name (case-insensitive); undefined if unknown. */
|
|
221
|
+
export declare function parseOciCompression(name: string): OciCompression | undefined;
|
|
222
|
+
export type ExportFormat = 'directory' | 'zip' | 'tar' | 'tgz' | 'wbn' | 'oci';
|
|
223
|
+
export declare const EXPORT_FORMATS: readonly ExportFormat[];
|
|
224
|
+
/** Parse a format name (`tar.gz` is an alias for `tgz`); undefined if unknown. */
|
|
225
|
+
export declare function parseExportFormat(name: string): ExportFormat | undefined;
|
|
226
|
+
/** `PLUGIN name FROM ./plugins/x.mjs [INTEGRITY sha256:…]`: declare a renderer. */
|
|
227
|
+
export interface PluginInstruction {
|
|
228
|
+
readonly kind: 'PLUGIN';
|
|
229
|
+
readonly span: SourceSpan;
|
|
230
|
+
readonly name: string;
|
|
231
|
+
readonly uri: Operand;
|
|
232
|
+
readonly integrity?: string;
|
|
233
|
+
}
|
|
234
|
+
/** `TOOL name`: declare an external program (run only when the CLI allows it with `--allow-exec name`). */
|
|
235
|
+
export interface ToolInstruction {
|
|
236
|
+
readonly kind: 'TOOL';
|
|
237
|
+
readonly span: SourceSpan;
|
|
238
|
+
readonly name: string;
|
|
239
|
+
}
|
|
240
|
+
/** One `key=value` of a TRANSFORM's `OPTIONS` clause. */
|
|
241
|
+
export interface TransformOption {
|
|
242
|
+
readonly key: string;
|
|
243
|
+
readonly value: Operand;
|
|
244
|
+
readonly span: SourceSpan;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* `TRANSFORM [--overwrite] [--from=stage] [--include=<pattern>…] [--exclude=<pattern>…] [<source>]
|
|
248
|
+
* WITH <plugin> TO <dest> [OPTIONS key=value …] [FOR item IN list] [ARGS <argument> …]`
|
|
249
|
+
* ARGS (last on the line) is the argv template when `<plugin>` is a TOOL. A directory source runs
|
|
250
|
+
* the plugin once per file (`--include`/`--exclude` pick the files).
|
|
251
|
+
*/
|
|
252
|
+
export interface TransformInstruction {
|
|
253
|
+
readonly kind: 'TRANSFORM';
|
|
254
|
+
readonly span: SourceSpan;
|
|
255
|
+
/** Omitted only with FOR: the plugin then gets just the element. */
|
|
256
|
+
readonly source?: Operand | InlineBody;
|
|
257
|
+
readonly from?: StageRef;
|
|
258
|
+
readonly plugin: string;
|
|
259
|
+
readonly pluginSpan: SourceSpan;
|
|
260
|
+
readonly dest: Operand;
|
|
261
|
+
readonly options: readonly TransformOption[];
|
|
262
|
+
readonly each?: {
|
|
263
|
+
readonly as: string;
|
|
264
|
+
readonly list: string;
|
|
265
|
+
readonly span: SourceSpan;
|
|
266
|
+
};
|
|
267
|
+
/** argv template of a TOOL: `{input}`, `{output}` and `{option.key}` are replaced by the host. */
|
|
268
|
+
readonly args?: readonly Operand[];
|
|
269
|
+
/** `--include` patterns (`.sitefileignore` syntax): a directory source's files to keep. */
|
|
270
|
+
readonly include?: readonly string[];
|
|
271
|
+
/** `--exclude` patterns (`.sitefileignore` syntax): a directory source's files to leave out. */
|
|
272
|
+
readonly exclude?: readonly string[];
|
|
273
|
+
readonly overwrite: boolean;
|
|
274
|
+
}
|
|
275
|
+
/** `SITEMAP [--overwrite] BASE <url> [TO <dest>]`: write a sitemaps.org file listing the stage's `.html` files. */
|
|
276
|
+
export interface SitemapInstruction {
|
|
277
|
+
readonly kind: 'SITEMAP';
|
|
278
|
+
readonly span: SourceSpan;
|
|
279
|
+
readonly base: Operand;
|
|
280
|
+
/** Default `/sitemap.xml`. */
|
|
281
|
+
readonly dest?: Operand;
|
|
282
|
+
readonly overwrite: boolean;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* `FEED [--overwrite] <import> BASE <url> LINK <path> [TO <dest>] [TITLE <text>]`: an Atom feed
|
|
286
|
+
* from a list import. LINK is evaluated per element, with the element as `${item…}`.
|
|
287
|
+
*/
|
|
288
|
+
export interface FeedInstruction {
|
|
289
|
+
readonly kind: 'FEED';
|
|
290
|
+
readonly span: SourceSpan;
|
|
291
|
+
readonly list: string;
|
|
292
|
+
readonly listSpan: SourceSpan;
|
|
293
|
+
readonly base: Operand;
|
|
294
|
+
readonly link: Operand;
|
|
295
|
+
/** Default `/feed.xml`. */
|
|
296
|
+
readonly dest?: Operand;
|
|
297
|
+
readonly title?: Operand;
|
|
298
|
+
readonly overwrite: boolean;
|
|
299
|
+
}
|
|
300
|
+
/** The loop variable LINK of a FEED uses for the current element. */
|
|
301
|
+
export declare const FEED_ITEM = "item";
|
|
302
|
+
/** `REDIRECT [--overwrite] <from> <to> [STATUS 301|302|303|307|308]`: a line of `/_redirects`. */
|
|
303
|
+
export interface RedirectInstruction {
|
|
304
|
+
readonly kind: 'REDIRECT';
|
|
305
|
+
readonly span: SourceSpan;
|
|
306
|
+
readonly from: Operand;
|
|
307
|
+
readonly to: Operand;
|
|
308
|
+
readonly status: RedirectStatus;
|
|
309
|
+
readonly overwrite: boolean;
|
|
310
|
+
}
|
|
311
|
+
export type RedirectStatus = 301 | 302 | 303 | 307 | 308;
|
|
312
|
+
export declare const REDIRECT_STATUSES: readonly RedirectStatus[];
|
|
313
|
+
/** `HEADER [--overwrite] <path-pattern> <Name>: <value …>`: a header in `/_headers`. */
|
|
314
|
+
export interface HeaderInstruction {
|
|
315
|
+
readonly kind: 'HEADER';
|
|
316
|
+
readonly span: SourceSpan;
|
|
317
|
+
readonly pattern: Operand;
|
|
318
|
+
/** The header name without its trailing `:`. */
|
|
319
|
+
readonly name: Operand;
|
|
320
|
+
/** The rest of the line; the words are joined with one space. */
|
|
321
|
+
readonly value: readonly Operand[];
|
|
322
|
+
readonly overwrite: boolean;
|
|
323
|
+
}
|
|
324
|
+
/** `FINGERPRINT [--overwrite] <pattern>… [MANIFEST <dest>]`: rename matching files to `name.<hash>.ext` and rewrite the HTML and CSS that reference them. */
|
|
325
|
+
export interface FingerprintInstruction {
|
|
326
|
+
readonly kind: 'FINGERPRINT';
|
|
327
|
+
readonly span: SourceSpan;
|
|
328
|
+
/** Absolute path patterns (`*`, `?`, `**`; a trailing `/` means everything below). */
|
|
329
|
+
readonly patterns: readonly Operand[];
|
|
330
|
+
/** `MANIFEST <dest>`: where to write the JSON map of old to new paths. */
|
|
331
|
+
readonly manifest?: Operand;
|
|
332
|
+
readonly overwrite: boolean;
|
|
333
|
+
}
|
|
334
|
+
/** `CHECKLINKS [--warn] [--external]`: report references in the stage's HTML and CSS that point at nothing. */
|
|
335
|
+
export interface CheckLinksInstruction {
|
|
336
|
+
readonly kind: 'CHECKLINKS';
|
|
337
|
+
readonly span: SourceSpan;
|
|
338
|
+
/** `--warn`: print each broken link as a warning instead of failing. */
|
|
339
|
+
readonly warn: boolean;
|
|
340
|
+
/** `--external`: also request http(s) links (under the network policy). */
|
|
341
|
+
readonly external: boolean;
|
|
342
|
+
}
|
|
343
|
+
export type Instruction = FromInstruction | ArgInstruction | LetInstruction | FragmentInstruction | ImportInstruction | CopyInstruction | RenderInstruction | PluginInstruction | ToolInstruction | TransformInstruction | SitemapInstruction | FeedInstruction | RedirectInstruction | HeaderInstruction | FingerprintInstruction | CheckLinksInstruction | ExportInstruction;
|
|
344
|
+
/** A parsed and semantically validated Sitefile. */
|
|
345
|
+
export interface Program {
|
|
346
|
+
readonly file: string;
|
|
347
|
+
readonly instructions: readonly Instruction[];
|
|
348
|
+
}
|
|
349
|
+
export declare function literalText(op: Operand | InlineBody): string | undefined;
|
|
350
|
+
/** Names of ARG, LET, IMPORT and stages (`FROM … AS name`, `--from=name`). */
|
|
351
|
+
export declare const IDENT_RE: RegExp;
|
|
352
|
+
/** A stage of an imported Sitefile: `<import>+<stage>` (`IMPORT theme … AS sitefile`, then `FROM theme+assets`). */
|
|
353
|
+
export declare const IMPORTED_STAGE_RE: RegExp;
|
|
354
|
+
/** `theme+assets` -> `{ import: 'theme', stage: 'assets' }`; undefined for anything else. */
|
|
355
|
+
export declare function importedStage(name: string): {
|
|
356
|
+
readonly import: string;
|
|
357
|
+
readonly stage: string;
|
|
358
|
+
} | undefined;
|
|
359
|
+
/** Every stage alias declared anywhere in the file. */
|
|
360
|
+
export declare function stageNames(instructions: readonly Instruction[]): Set<string>;
|