@bongo/bongo 0.0.0-stage → 0.1.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/bin.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ const { parseArgs } = require('node:util');
3
+
4
+ let args = process.argv.slice(2);
5
+ // A bare `--host` exposes the server on every interface, as sirv-cli's does.
6
+ args = args.map((arg, i) => (arg === '--host' || arg === '-H') && !/^[^-]/.test(args[i + 1] || '') ? '--host=0.0.0.0' : arg);
7
+
8
+ try {
9
+ var { values, positionals } = parseArgs({
10
+ args,
11
+ allowPositionals: true,
12
+ allowNegative: true,
13
+ options: {
14
+ cwd: { type: 'string', short: 'C', default: '.' },
15
+ host: { type: 'string', short: 'H' },
16
+ port: { type: 'string', short: 'p' },
17
+ minify: { type: 'boolean', short: 'm', default: false },
18
+ debug: { type: 'boolean', short: 'd', default: false },
19
+ write: { type: 'boolean', short: 'w', default: false },
20
+ fresh: { type: 'boolean', short: 'f', default: false },
21
+ drafts: { type: 'boolean' },
22
+ version: { type: 'boolean', short: 'v' },
23
+ help: { type: 'boolean', short: 'h' },
24
+ },
25
+ });
26
+ var port = values.port === undefined ? undefined : Number(values.port);
27
+ if (port !== undefined && !(Number.isInteger(port) && port > 0 && port < 65536)) {
28
+ throw new Error(`Invalid --port "${values.port}"; use a port number`);
29
+ }
30
+ } catch (err) {
31
+ console.error('ERROR', err.message);
32
+ process.exit(1);
33
+ }
34
+
35
+ if (values.version) {
36
+ const pkg = require('./package.json');
37
+ console.log(`${pkg.name}, v${pkg.version}`);
38
+ process.exit(0);
39
+ }
40
+
41
+ if (values.help) {
42
+ let output = '';
43
+ output += '\n Usage';
44
+ output += '\n bongo <command> [options]';
45
+ output += '\n';
46
+ output += '\n Available Commands';
47
+ output += '\n build Build the source directory';
48
+ output += '\n dev Develop with a live-reload server';
49
+ output += '\n';
50
+ output += '\n Options';
51
+ output += '\n -C, --cwd Directory to resolve from (default .)';
52
+ output += '\n -H, --host Host to bind; bare flag exposes network (default localhost)';
53
+ output += '\n -p, --port Port to bind (default 3000)';
54
+ output += '\n -m, --minify Compress built assets (default false)';
55
+ output += '\n -d, --debug Log each build stage and its time (default false)';
56
+ output += '\n -w, --write Dev only: also write the output to public/ (default false)';
57
+ output += '\n -f, --fresh Render every page, not only what changed (default false)';
58
+ output += '\n --drafts Build drafts; --no-drafts leaves them out (default false; dev true)';
59
+ output += '\n -v, --version Displays current version';
60
+ output += '\n -h, --help Displays this message';
61
+ output += '\n';
62
+
63
+ process.stdout.write(output + '\n');
64
+ process.exit(0);
65
+ }
66
+
67
+ (async function () {
68
+ try {
69
+ let name = (positionals[0] || '').trim().toLowerCase();
70
+ if (!name) throw new Error('Missing <command> argument');
71
+
72
+ let command = require('.')[name];
73
+ if (!command) throw new Error(`Unknown "${name}" command`);
74
+
75
+ let { cwd, host, minify, debug, write, fresh, drafts } = values;
76
+ await command({ cwd, host, port, minify, debug, write, fresh, drafts });
77
+ } catch (err) {
78
+ console.error('ERROR', err.stack);
79
+ process.exit(1);
80
+ }
81
+ })();
package/client.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type { ClientOptions, BongoEvent, BongoEventDetails } from './src/client/index';
2
+ export { client, navigate, on, off } from './src/client/index';
package/client.js ADDED
@@ -0,0 +1 @@
1
+ const e={fade:[`@keyframes bongo-fade-in{from{opacity:0}to{opacity:1}}`,`@keyframes bongo-fade-out{from{opacity:1}to{opacity:0}}`,`::view-transition-old(root){animation-name:bongo-fade-out;animation-duration:.18s}`,`::view-transition-new(root){animation-name:bongo-fade-in;animation-duration:.18s}`].join(``),slide:[`@keyframes bongo-slide-in{from{transform:translateX(16px);opacity:0}to{transform:none;opacity:1}}`,`@keyframes bongo-slide-out{from{transform:none;opacity:1}to{transform:translateX(-16px);opacity:0}}`,`::view-transition-old(root){animation-name:bongo-slide-out;animation-duration:.22s}`,`::view-transition-new(root){animation-name:bongo-slide-in;animation-duration:.22s}`].join(``)};function t(e){if(e===!0)return`fade`;if(e)return e}function n(t){let n=e[t];if(!n||document.querySelector(`style[data-bongo-preset="${t}"]`))return;let r=document.createElement(`style`);r.setAttribute(`data-bongo-preset`,t),r.textContent=n,document.head.appendChild(r)}const r=/* @__PURE__ */ new WeakMap;function i(e){return[...e.head.querySelectorAll(`link[rel~=stylesheet], style`)].filter(e=>!e.closest(`noscript`))}function a(e){let t=`href`in e?e.href:e.textContent;return JSON.stringify([e.localName,t,e.media,e.hasAttribute(`disabled`)])}function o(e,t){return e.hasAttribute(`disabled`)||`href`in e&&!e.getAttribute(`href`)?!1:!e.media||!t||t.matchMedia(e.media).matches}function s(e){for(let t of i(e))r.set(t,a(t))}function c(e,t,n){let s=e.defaultView,c=i(e).filter(e=>r.has(e)),l=/* @__PURE__ */ new Set,u=[],d=[],f=[],p=-1,m=0,h,g=()=>{for(let{el:e}of u){let t=e.sheet;t&&!t.disabled&&(t.disabled=!0,d.push(t))}},_=()=>{g(),m=s?.requestAnimationFrame(_)??0};for(let n of i(t)){let t=a(n),i=c.findIndex((e,n)=>n>p&&r.get(e)===t);if(i!==-1){l.add(h=c[i]),p=i;continue}let d=e.importNode(n,!0);u.push({el:d,id:t}),o(n,s)&&f.push(new Promise(e=>{d.addEventListener(`load`,()=>e(),{once:!0}),d.addEventListener(`error`,()=>e(),{once:!0})}).then(g)),h?h.after(d):c.length?c[0].before(d):e.head.append(d),h=d}return u.length&&_(),{ready:new Promise(e=>{if(!f.length||n.aborted)return e();let t=setTimeout(r,3e3);n.addEventListener(`abort`,r),Promise.all(f).then(r);function r(){clearTimeout(t),n.removeEventListener(`abort`,r),e()}}),commit(){s?.cancelAnimationFrame(m);for(let e of d)e.disabled=!1;for(let{el:e,id:t}of u)r.set(e,t);for(let e of c)l.has(e)||(r.delete(e),e.remove());u=[]},drop(){s?.cancelAnimationFrame(m);for(let{el:e}of u)e.remove();u=[]}}}function l(e,t,n){let r=new URL(n).origin,a=new Set(i(e).map(e=>`href`in e?e.href:``)),s=/* @__PURE__ */ new Set;for(let l of i(t))if(`href`in l&&o(l,e.defaultView)){try{var c=new URL(l.getAttribute(`href`)||``,n)}catch{continue}c.origin===r&&!a.has(c.href)&&s.add(c.href)}return[...s]}const u=/* @__PURE__ */ new Map;function d(e){if(e===!0)return`hover`;if(e)return e}function f(e){let t=u.get(e);if(t){if(Date.now()>t.expires)u.delete(e);else return t.html}}function p(e){if(!e||!e.href||e.hasAttribute(`prefetch:disable`)||e.hasAttribute(`transition:disable`))return!1;let t;try{t=new URL(e.href)}catch{return!1}return!(t.origin!==location.origin||t.href===location.href||f(t.href))}function m(e){if(f(e))return;let t=fetch(e,{headers:{Accept:`text/html`}}).then(e=>{if(!e.ok)throw Error(`prefetch: ${e.status}`);return e.text()}).catch(t=>{throw u.delete(e),t});u.set(e,{html:t,expires:Date.now()+3e4}),t.then(t=>{let n=new DOMParser().parseFromString(t,`text/html`);for(let t of l(document,n,e))fetch(t).then(e=>e.blob()).catch(()=>{})},()=>{})}function h(e){if(!e)return;if(document.addEventListener(`touchstart`,e=>{let t=e.target?.closest?.(`a`);p(t)&&m(t.href)},{capture:!0,passive:!0}),e===`tap`){document.addEventListener(`mousedown`,e=>{let t=e.target?.closest?.(`a`);p(t)&&m(t.href)},!0);return}let t=null;document.addEventListener(`mouseover`,e=>{let n=e.target?.closest?.(`a`);if(t&&t.anchor!==n&&(clearTimeout(t.timer),t=null),!p(n)||t?.anchor===n)return;let r=n;t={anchor:r,timer:setTimeout(()=>{m(r.href),t=null},80)}},!0),document.addEventListener(`mouseout`,e=>{if(!t)return;let n=e.relatedTarget?.closest?.(`a`);t.anchor!==n&&(clearTimeout(t.timer),t=null)},!0)}const g=/* @__PURE__ */ new Map,_=`bongo:live`;let v=!1;const y=/* @__PURE__ */ new Map;function b(e,t){let n=g.get(e);if(n)for(let r of n)try{r(t)}catch(t){console.error(`[bongo] handler for ${e} threw:`,t)}}function x(e,t){let n=g.get(e);return n||g.set(e,n=/* @__PURE__ */ new Set),n.add(t),()=>n.delete(t)}function S(e,t){g.get(e)?.delete(t)}function C(e){return v?v=!1:!(!e.canIntercept||e.hashChange||e.navigationType===`reload`||e.downloadRequest||new URL(e.destination.url).origin!==location.origin||e.sourceElement?.hasAttribute(`transition:disable`))}async function w(e,t){let n=f(e.href);if(n)try{return await n}catch{}let r=await fetch(e.href,{headers:{Accept:`text/html`},signal:t});if(!r.ok)throw Error(`Navigation fetch failed: ${r.status} ${r.statusText}`);return r.text()}function T(e){let t=/* @__PURE__ */ new Map;for(let n of e.querySelectorAll(`[transition\\:persist]`)){let e=n.getAttribute(`transition:persist`)||n.id;e&&t.set(e,n)}return t}function E(e,t){e.title=t.title;let n=T(e),r=e.activeElement,i=[];for(let[e,r]of T(t)){let t=n.get(e);t&&(i.push({el:t,top:t.scrollTop,left:t.scrollLeft}),r.replaceWith(t))}let a=e.getElementById(_);t.getElementById(_)?.remove(),e.body.replaceWith(t.body),a&&e.body.prepend(a);for(let{el:e,top:t,left:n}of i)e.scrollTop=t,e.scrollLeft=n;let o=e.defaultView;o&&r instanceof o.HTMLElement&&r!==e.activeElement&&i.some(({el:e})=>e.contains(r))&&r.focus({preventScroll:!0})}async function D(e,t){let n=e&&document.startViewTransition;if(!n)return t();let r=n.call(document,t);r.ready.catch(()=>{}),r.finished.catch(()=>{}),await r.updateCallbackDone,await r.finished}function O(e){y.set(e.href,{x:window.scrollX,y:window.scrollY})}function k(e,t){if(e.navigationType===`traverse`){let e=y.get(t.href);if(e){window.scrollTo(e.x,e.y);return}window.scrollTo(0,0)}else{if(t.hash){let e=decodeURIComponent(t.hash.slice(1)),n=e&&document.getElementById(e);if(n){n.scrollIntoView();return}}window.scrollTo(0,0)}}function A(){let e=document.getElementById(_);return e||!document.body?e??void 0:(e=document.createElement(`div`),e.id=_,e.setAttribute(`role`,`status`),e.setAttribute(`aria-live`,`polite`),e.style.cssText=`position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0`,document.body.prepend(e),e)}function j(e){v=e}function M(e={}){let r=globalThis.navigation;if(b(`bongo:ready`,{url:new URL(location.href)}),h(d(e.prefetch)),!r)return;s(document),`scrollRestoration`in history&&(history.scrollRestoration=`manual`);let i=t(e.transitions);i&&n(i),A(),document.addEventListener(`click`,e=>{let t=e.target?.closest?.(`a[transition\\:disable]`);j(!!t)},!0),r.addEventListener(`navigate`,e=>{if(!C(e))return;let t=new URL(location.href),n=new URL(e.destination.url);O(t),e.intercept({async handler(){let{signal:r}=e;try{b(`bongo:navigate`,{to:n,from:t});let a=await w(n,r);if(r.aborted)return;let o=new DOMParser().parseFromString(a,`text/html`);b(`bongo:replace`,{to:n,from:t,doc:o});let s=c(document,o,r);try{if(await s.ready,r.aborted)return;await D(!!i,()=>{r.aborted||(s.commit(),E(document,o),k(e,n),b(`bongo:replaced`,{to:n,from:t}))})}finally{s.drop()}if(r.aborted)return;let l=A();l&&(l.textContent=document.title),b(`bongo:navigated`,{to:n,from:t})}catch(e){if(r.aborted)return;throw b(`bongo:error`,{to:n,error:e instanceof Error?e:Error(String(e))}),e}}})})}async function N(e,t={}){let n=new URL(e,location.href),r=globalThis.navigation;if(r){let{committed:e,finished:i}=r.navigate(n.href,{history:t.replace?`replace`:`push`});e?.catch(()=>{});try{await i}catch(e){if(e instanceof DOMException&&e.name===`AbortError`)return;throw e}}else location[t.replace?`replace`:`assign`](n.href)}export{M as client,N as navigate,S as off,x as on};
package/index.d.ts ADDED
@@ -0,0 +1,305 @@
1
+ import type { Args, Blocks } from 'tempura';
2
+
3
+ type Promisable<T> = Promise<T> | T;
4
+
5
+ export interface Argv {
6
+ cwd: string;
7
+ dev?: boolean;
8
+ minify?: boolean;
9
+ /** `bongo dev` only: also write the output to `public/`. @default false */
10
+ write?: boolean;
11
+ /** Render every page, and ignore what the last build kept. @default false */
12
+ fresh?: boolean;
13
+ /** Build each draft: a page with `draft: true`, or a `date` after the build starts, unless it has `draft: false`. @default false, and true for `bongo dev` */
14
+ drafts?: boolean;
15
+ // added via normalize
16
+ src?: string;
17
+ dest?: string;
18
+ assets?: string;
19
+ content?: string;
20
+ data?: string;
21
+ partials?: string;
22
+ layouts?: string;
23
+ debug?: boolean;
24
+ port?: number;
25
+ host?: string;
26
+ }
27
+
28
+ export type Loader = (
29
+ file: string,
30
+ content: Uint8Array | string,
31
+ utils: Loader.Utilities,
32
+ ) => Promisable<Loader.Result>;
33
+
34
+ export namespace Loader {
35
+ type Metadata = Record<string, unknown>;
36
+
37
+ // Raw source map (v3). Loaders may also return the map as a JSON string —
38
+ // consumers (rolldown, lightningcss) accept either.
39
+ interface SourceMapObject {
40
+ version: number;
41
+ sources: (string | null)[];
42
+ mappings: string;
43
+ names?: string[];
44
+ file?: string;
45
+ sourceRoot?: string;
46
+ sourcesContent?: (string | null)[];
47
+ }
48
+
49
+ type SourceMap = string | SourceMapObject;
50
+
51
+ /** A heading of the HTML that a loader writes. Without `html`, bongo uses the escaped `text`. */
52
+ type Heading = Omit<Template.Heading, 'html'> & { html?: string };
53
+
54
+ interface Result {
55
+ filename: string;
56
+ content: Uint8Array | string;
57
+ metadata?: Loader.Metadata;
58
+ sourcemap?: Loader.SourceMap | null;
59
+ /** The headings of `content`, in document order. They become `$page.headings`. */
60
+ headings?: Loader.Heading[];
61
+ }
62
+
63
+ interface Utilities {
64
+ decode(input: Uint8Array): string;
65
+ render(partial: string, ...args: any[]): Promisable<string>;
66
+ frontmatter(content: string): {
67
+ offset: number;
68
+ value: Metadata;
69
+ };
70
+ /**
71
+ * Parse element attributes as data; values are never evaluated.
72
+ * @example utils.attrs('name="Luke" count=2 tags=[a, b] featured') //=> { name: 'Luke', count: 2, tags: ['a', 'b'], featured: true }
73
+ */
74
+ attrs(input: string): Record<string, unknown>;
75
+ }
76
+ }
77
+
78
+ export type { Args, Blocks };
79
+
80
+ export namespace Template {
81
+ // TODO: import from tempura
82
+ type Compiler<T extends Args = Args> = (data: T, blocks?: Blocks) => Promise<string> | string;
83
+ type Partials = Blocks;
84
+
85
+ interface Menu {
86
+ name: string;
87
+ href: string;
88
+ }
89
+
90
+ interface Site {
91
+ url: string;
92
+ lang: string;
93
+ title: string;
94
+ description?: string;
95
+ menu?: {
96
+ [name: string]: Menu;
97
+ };
98
+ }
99
+
100
+ /**
101
+ * A heading of a page, as its loader reports it. `.html` and `.hbs` content has none.
102
+ * @example { level: 2, id: 'install', text: 'Install bongo', html: 'Install <code>bongo</code>' }
103
+ */
104
+ type Heading = {
105
+ level: number;
106
+ /** The anchor that the heading has in the page HTML. */
107
+ id: string;
108
+ /** Plain text, entities resolved. Escape it for HTML. */
109
+ text: string;
110
+ /** The inner HTML, such as inline code. */
111
+ html: string;
112
+ };
113
+
114
+ // TODO: lastmodified value
115
+ // TODO: file.abs path
116
+ // TODO: file.rel path
117
+ interface Page {
118
+ url: string;
119
+ path: string;
120
+ title: string;
121
+ summary: string;
122
+ section: string;
123
+ tags: string[];
124
+ headings: Heading[];
125
+ params: Params;
126
+ /** Whether the page is a draft: it has `draft: true`, or a `date` after the build started, unless it has `draft: false`. Only a build with drafts has one. */
127
+ draft: boolean;
128
+ content: string;
129
+ scripts: string;
130
+ styles: string;
131
+ }
132
+
133
+ type PageInfo = {
134
+ path: string; // URL path (e.g. '/blog/post-1/')
135
+ url: string; // absolute URL (e.g. 'https://example.com/blog/post-1/')
136
+ title: string;
137
+ summary: string;
138
+ section: string; // directory prefix (e.g. 'blog', '' for root)
139
+ tags: string[]; // parsed from frontmatter
140
+ headings: Heading[]; // from the page's loader; empty for .html and .hbs content
141
+ params: Params; // full frontmatter
142
+ /** Whether the page is a draft: it has `draft: true`, or a `date` after the build started, unless it has `draft: false`. Only a build with drafts has one. */
143
+ draft: boolean;
144
+ /** The `<bongo:alternate>` elements of the page, as `{ type: href }`, such as `{ 'text/markdown': '/config.md' }`. Only output files and data modules see it, since a page's elements are found after its layouts render. */
145
+ alternates?: Record<string, string>;
146
+ };
147
+
148
+ type TagEntry = {
149
+ tag: string;
150
+ pages: PageInfo[];
151
+ };
152
+
153
+ /** `$assets` in layouts, partials, and `.hbs` content. */
154
+ type Assets = {
155
+ /**
156
+ * Read a file of `assets/`; with an encoding, as text.
157
+ * @example {{{ await $assets.load('img/logo.svg', 'utf8') }}}
158
+ */
159
+ load(filename: string, encoding?: BufferEncoding): Promise<string | Buffer>;
160
+ /**
161
+ * Draw a partial as an image, with the values of the current page, and return the URL path of the image.
162
+ * The extension of `output` picks the format: `.svg` and `.png` work everywhere, `.jpg` and `.webp` need Bun.
163
+ * Without `output`, the image is a PNG named from its partial and content. Needs `satori`, and `@resvg/resvg-js` for all but SVG.
164
+ * @example {{#var card = await $assets.render('og/card', 'og/' + $utils.slug($page.title) + '.png')}}
165
+ */
166
+ render(template: string, output?: string, options?: Render): Promise<string>;
167
+ };
168
+
169
+ type Render = {
170
+ /** @default 1200 */
171
+ width?: number;
172
+ /** @default 630 */
173
+ height?: number;
174
+ /** Values for the partial, as the attributes of `{{#partial}}` are. */
175
+ args?: Record<string, unknown>;
176
+ };
177
+
178
+ interface Params {
179
+ [name: string]: unknown;
180
+ }
181
+ }
182
+
183
+ interface Helpers {
184
+ [name: string]: Function | Promise<any> | Helpers;
185
+ }
186
+
187
+ export namespace Build {
188
+ /** A content file that a build writes: a page, or an output file such as `sitemap.xml.hbs`. */
189
+ type File = {
190
+ /** The URL path, like `/blog/post/` for a page or `/blog/index.xml` for an output file. */
191
+ path: string;
192
+ /** The frontmatter. */
193
+ params: Template.Params;
194
+ };
195
+
196
+ type Context = File & {
197
+ kind: 'page' | 'output';
198
+ /** Every output file of the build, sorted by path. */
199
+ outputs: File[];
200
+ /** The config, with its plugins merged. */
201
+ config: Required<Config>;
202
+ };
203
+
204
+ /**
205
+ * Change a rendered page or output file before it is written. Pages arrive with their final asset URLs.
206
+ * @example const year: Build.Transform = (text, ctx) => ctx.kind === 'page' ? text.replace('{year}', '2026') : text;
207
+ */
208
+ type Transform = (text: string, ctx: Context) => Promisable<string>;
209
+
210
+ type Hooks = {
211
+ /** Plugins run first, in order, then the config's own; each gets the text the last one returned. */
212
+ transform?: Transform;
213
+ };
214
+
215
+ /**
216
+ * What the default export of a data module, such as `content/search.json.ts`, receives when it is a function.
217
+ * @example export default ({ $pages }: Build.Data) => $pages.map(page => ({ title: page.title, path: page.path }));
218
+ */
219
+ type Data = {
220
+ $site: Template.Site;
221
+ $pages: Template.PageInfo[];
222
+ /** The pages in the module's own folder. */
223
+ $section: Template.PageInfo[];
224
+ $tags: Template.TagEntry[];
225
+ $data: Record<string, unknown>;
226
+ $utils: NonNullable<Config['$utils']>;
227
+ };
228
+ }
229
+
230
+ export namespace Images {
231
+ type Format = 'avif' | 'webp' | 'jpeg' | 'png';
232
+
233
+ type Options = {
234
+ /** Output widths in pixels. A source is never enlarged: wider ones become its own width. @default [640, 960, 1280, 1920, 2880] */
235
+ widths?: number[];
236
+ /** Output formats, best first; `original` is the format of each source. Content images use only the first; the last is the `<img>` fallback of `<bongo:image>`. `avif` needs an Apple Silicon M3+ Mac or Windows. @default ['webp', 'original'] */
237
+ formats?: (Format | 'original')[];
238
+ /** Encoder quality from 1 to 100, for all lossy formats or per format. PNG is always lossless. @default { avif: 50, webp: 80, jpeg: 80 } */
239
+ quality?: number | Partial<Record<Format, number>>;
240
+ /** The `sizes` of images that do not set one. A leading `auto` applies only to lazy images. @default 'auto, 100vw' */
241
+ sizes?: string;
242
+ /** Process `<img>` elements in content that have a relative `src`. @default true */
243
+ content?: boolean;
244
+ /** Delete cached images and renders that no build has used for this many days, checked at most once a day; `false` keeps them all. @default 30 */
245
+ prune?: number | false;
246
+ };
247
+ }
248
+
249
+ export interface Config {
250
+ $site: Template.Site;
251
+ // bongo provides these built-ins; a config may override or extend them
252
+ $utils?: Helpers & {
253
+ truncate?(text: string, length: number): string;
254
+ countwords?(text: string): number;
255
+ slug?(text: string): string;
256
+ excerpt?(html: string, length: number): string;
257
+ minutes?(text: string, wpm?: number): number;
258
+ json?(value: unknown): string;
259
+ url?: Helpers & {
260
+ abs?(to: string, fro?: string): string;
261
+ path?(to: string, fro?: string): string;
262
+ /**
263
+ * The path of a page plus an extension, as `<bongo:alternate>` names its file.
264
+ * @example $utils.url.ext('/config/', 'md') //=> '/config.md'
265
+ */
266
+ ext?(path: string, ext: string): string;
267
+ };
268
+ date?: Helpers & {
269
+ format?(value: string|number|Date, options?: Intl.DateTimeFormatOptions): string;
270
+ };
271
+ };
272
+ $loaders?: {
273
+ [extension: string]: Loader;
274
+ };
275
+ $partials?: Template.Partials;
276
+ $build?: {
277
+ /**
278
+ * The browsers to build for, as a browserslist query or a list of queries. It sets the CSS prefixes and the syntax that CSS and JavaScript are lowered to.
279
+ * @default 'defaults'
280
+ * @example $build: { target: ['chrome >= 120', 'safari >= 17', 'firefox >= 121'] }
281
+ */
282
+ target?: string | string[];
283
+ };
284
+ $images?: Images.Options;
285
+ $hooks?: Build.Hooks;
286
+ plugins?: Partial<Config>[];
287
+ }
288
+
289
+ /**
290
+ * Run one build, as `bongo build` does. Resolves when the site is written.
291
+ * @example await build({ cwd: 'site', minify: true });
292
+ */
293
+ export function build(argv: Argv): Promise<void>;
294
+
295
+ /**
296
+ * Start the dev server, as `bongo dev` does. Resolves after the first build; the server keeps running.
297
+ * @example await dev({ cwd: 'site', port: 3000 });
298
+ */
299
+ export function dev(argv: Argv): Promise<void>;
300
+
301
+ /**
302
+ * Return the config as it is, typed as `Config`, for a JavaScript config file.
303
+ * @example export default define({ $site: { title: 'My Site', url: 'https://example.com/', lang: 'en' } });
304
+ */
305
+ export function define(config: Config): Config;