tosijs-ui 1.16.14 → 1.16.16
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 +79 -0
- package/dist/code-editor-cm.js +38 -21
- package/dist/doc-system/site/asset-stamp.d.ts +5 -0
- package/dist/doc-system/site/asset-stamp.js +11 -0
- package/dist/doc-system/site/check-examples.js +4 -2
- package/dist/doc-system/site/codemirror-copies.d.ts +13 -0
- package/dist/doc-system/site/codemirror-copies.js +115 -0
- package/dist/doc-system/site/index.d.ts +2 -0
- package/dist/doc-system/site/index.js +3 -0
- package/dist/doc-system/site/make-llms-txt.d.ts +10 -8
- package/dist/doc-system/site/make-llms-txt.js +24 -20
- package/dist/doc-system/site/orchestrator.d.ts +3 -0
- package/dist/doc-system/site/orchestrator.js +42 -29
- package/dist/doc-system/site/restamp.d.ts +29 -0
- package/dist/doc-system/site/restamp.js +166 -0
- package/dist/iife.js +90 -86
- package/dist/iife.js.map +8 -8
- package/dist/live-example/code-transform.d.ts +35 -1
- package/dist/live-example/code-transform.js +65 -10
- package/dist/live-example/component.d.ts +27 -1
- package/dist/live-example/component.js +121 -38
- package/dist/live-example/dialects.d.ts +10 -6
- package/dist/live-example/styles.d.ts +25 -0
- package/dist/live-example/styles.js +26 -0
- package/dist/live-example/types.d.ts +9 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +3 -10
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.16.16
|
|
4
|
+
|
|
5
|
+
For tjs-lang's book (GitHub #216 item 3 and #210 item 5), plus one change to `llms.txt`.
|
|
6
|
+
Both tjs-lang items are feature-detected, so nothing changes until your tjs-lang has them.
|
|
7
|
+
|
|
8
|
+
- **A console line shows its own value, and the JavaScript it became.** A dialect's
|
|
9
|
+
transform may now return `prelude` and `body` beside `code`: its runtime setup, and what
|
|
10
|
+
the source itself became. When it does (tjs-lang 0.14.0-rc.5 and later), a line typed at
|
|
11
|
+
an example's console is evaluated for its own value, so a declaration shows `undefined`
|
|
12
|
+
where it used to show the setup's last assignment, and a line that needs both the setup
|
|
13
|
+
and `await` shows what it resolved to. The input also gains a small **JS** button that
|
|
14
|
+
shows the JavaScript for that line. A transform that returns only `code` behaves as
|
|
15
|
+
before, with no button.
|
|
16
|
+
- **`tjs` and `ts` examples may be written as modules.** `export` is removed from `tjs` and
|
|
17
|
+
`ts` examples before they run, by tjs-lang's own parser (`stripExports`, from
|
|
18
|
+
0.14.0-rc.4), and the build's example check does the same. An example runs as a function
|
|
19
|
+
body, where `export` is a syntax error, so with an older tjs-lang it fails as it always
|
|
20
|
+
has. `js` examples are unchanged.
|
|
21
|
+
- **The tjs-lang peer range admits the 0.14 release candidates** (`^0.13.1 || ^0.14.0-rc.5`).
|
|
22
|
+
It read `^0.14.0`, which excludes every prerelease, so `npm install` refused the one
|
|
23
|
+
tjs-lang that has these features. Run here with 0.14.0-rc.5 installed: the browser lane
|
|
24
|
+
on Chromium, and the new tests of both features on all three browsers.
|
|
25
|
+
- **`registerDialect` documents what a transform should return.** `testRunner` has been
|
|
26
|
+
honoured since 1.16.8 but was not in the type or the docs: a dialect that returns only
|
|
27
|
+
`code` has its inline tests run from their raw text, with JavaScript's semantics. The
|
|
28
|
+
`transform` type now names `testRunner`, `prelude` and `body`.
|
|
29
|
+
- **An example re-run while it was still loading could leave its page never finishing its
|
|
30
|
+
tests.** A run replaced by a newer one (tests turned on, or an edit, while the first run
|
|
31
|
+
was still awaiting a fetch) went on to run its test block against a preview no longer in
|
|
32
|
+
the page. A test that waits for something to render then waited for ever and the example
|
|
33
|
+
stayed marked as running, so the page was reported as not finished after 45 seconds. A
|
|
34
|
+
replaced run now stops before its tests, and one replaced while its tests are running
|
|
35
|
+
no longer writes its results over the newer run's. Seen on the data-table page off
|
|
36
|
+
localhost.
|
|
37
|
+
- **`llms.txt` no longer carries a paragraph about the dev server.** A site with
|
|
38
|
+
`haltijaDev` set used to tell readers of its `llms.txt` to run `bun start` and drive the
|
|
39
|
+
page with the `hj` CLI. That file is for whoever consumes the library, and those readers
|
|
40
|
+
have no repo to run. If you relied on it to brief an agent working in your project, put
|
|
41
|
+
the same note in your `CLAUDE.md` or `AGENTS.md`.
|
|
42
|
+
|
|
43
|
+
The tjs-lang this package pins for sites that load it from a CDN is still 0.13.13, which has
|
|
44
|
+
neither feature. A site that serves its own tjs-lang (the installed peer, copied
|
|
45
|
+
same-origin by the build) gets both as soon as that is 0.14.0-rc.5.
|
|
46
|
+
|
|
47
|
+
`LlmsTxtMeta.haltijaDev` is still accepted and now ignored (deprecated). `dist/iife.js`
|
|
48
|
+
grows by about 0.4 kB gzip.
|
|
49
|
+
|
|
50
|
+
## 1.16.15
|
|
51
|
+
|
|
52
|
+
For projects that build their own docs bundle, from tosijs's upgrade to 1.16.13 (board
|
|
53
|
+
#3248). Two failures that left a green build over a broken site now say so.
|
|
54
|
+
|
|
55
|
+
- **`restampSite(config)`, from `tosijs-ui/site`.** If your site has no `bundleEntry` and you
|
|
56
|
+
overwrite `/iife.js` after `buildSite`, call it afterwards. It checks that the file you
|
|
57
|
+
wrote registers `<tosi-doc-system>`, and `<tosi-example>` when the corpus has live
|
|
58
|
+
examples, and then re-stamps the script and stylesheet `?v=` in every page from the files
|
|
59
|
+
actually on disk (the stamp `buildSite` wrote described the bundle it replaced, so a
|
|
60
|
+
returning visitor could run a cached library against new pages). A bundle whose entry
|
|
61
|
+
imports only the root `tosijs-ui` registers neither element since 1.14.0, and until now
|
|
62
|
+
nothing noticed: pages served, no header, no nav, no examples, no doc tests. A warning by
|
|
63
|
+
default. Under `strict: true` it throws before writing anything. It also throws when
|
|
64
|
+
there is nothing to re-stamp, on a `bundleEntry` site, and when it cannot read the corpus.
|
|
65
|
+
- **The build reports two copies of CodeMirror.** After an upgrade a lockfile can keep an
|
|
66
|
+
older hoisted `@codemirror/state` while the newer one nests under
|
|
67
|
+
`node_modules/tosijs-ui/`; every `<tosi-code>` is then blank and nothing fails.
|
|
68
|
+
`buildSite` now asks the module resolver whether the packages the editor is built from
|
|
69
|
+
agree, and names both copies and the command that fixes it
|
|
70
|
+
(`bun update @codemirror/state @codemirror/view`). A warning by default. **Under `strict: true` it fails
|
|
71
|
+
`buildSite` on a site with a `bundleEntry`, and fails `restampSite`**, which is new in a
|
|
72
|
+
patch: those are the two places a bundle is built from the install, and such a build was
|
|
73
|
+
already producing blank editors. Otherwise `buildSite` says it once and carries on. At runtime `<tosi-code>` logs the same explanation beside CodeMirror's own
|
|
74
|
+
"Unrecognized extension value" error. Cost: `iife.js` +176 bytes gzip.
|
|
75
|
+
- **Fixed: `llms.txt` told agents to try `/carousel/index.md` on every site.** That is a
|
|
76
|
+
tosijs-ui page. The example is now a page of the site being built.
|
|
77
|
+
- **The "relative imports without a file extension" warning says what it read.** It is
|
|
78
|
+
about the `.js` files tsc emitted, at the moment it emitted them. A project that replaces
|
|
79
|
+
those with bundles afterwards could not find the files it named; the warning now says so
|
|
80
|
+
and points at `"emitDeclarationOnly": true`.
|
|
81
|
+
|
|
3
82
|
## 1.16.14
|
|
4
83
|
|
|
5
84
|
More reach for curated schema forms (#221): into arrays and unions, grouped into sections,
|
package/dist/code-editor-cm.js
CHANGED
|
@@ -197,27 +197,44 @@ export function createCmEditor(parent, opts = {}) {
|
|
|
197
197
|
const readonly = new Compartment();
|
|
198
198
|
const highlight = new Compartment();
|
|
199
199
|
const langExt = opts.languageExtension ?? standardLanguageBundle(opts.mode ?? 'javascript');
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
200
|
+
let view;
|
|
201
|
+
try {
|
|
202
|
+
view = new EditorView({
|
|
203
|
+
parent,
|
|
204
|
+
root: opts.root,
|
|
205
|
+
state: EditorState.create({
|
|
206
|
+
doc: opts.value ?? '',
|
|
207
|
+
extensions: [
|
|
208
|
+
baseExtensions(),
|
|
209
|
+
highlight.of(highlightFor(isDarkMode())),
|
|
210
|
+
language.of(langExt),
|
|
211
|
+
readonly.of(EditorState.readOnly.of(!!opts.readOnly)),
|
|
212
|
+
EditorView.updateListener.of((u) => {
|
|
213
|
+
if (!u.docChanged || !opts.onChange)
|
|
214
|
+
return;
|
|
215
|
+
// A programmatic `value` set is not a change the USER made. See `programmatic`.
|
|
216
|
+
if (u.transactions.some((t) => t.annotation(programmatic)))
|
|
217
|
+
return;
|
|
218
|
+
opts.onChange(u.state.doc.toString());
|
|
219
|
+
}),
|
|
220
|
+
],
|
|
221
|
+
}),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
/*
|
|
226
|
+
CodeMirror recognises extensions by identity, so a second copy of `@codemirror/state`
|
|
227
|
+
in the install makes it reject its own language packages. The editor is then blank and
|
|
228
|
+
the message names an internal; say what happened and what fixes it (board #3248).
|
|
229
|
+
*/
|
|
230
|
+
if (/Unrecognized extension value/.test(String(error?.message))) {
|
|
231
|
+
console.error('<tosi-code>: CodeMirror rejected an extension, which means two copies of ' +
|
|
232
|
+
'@codemirror/state (or @codemirror/view) are loaded. Every CodeMirror package ' +
|
|
233
|
+
'must share one. Run `bun update @codemirror/state @codemirror/view` (or ' +
|
|
234
|
+
'`npm dedupe`), then rebuild.');
|
|
235
|
+
}
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
221
238
|
// Re-apply the right highlight palette when the doc-system toggles dark mode
|
|
222
239
|
// (it flips the `darkmode` class on <body>).
|
|
223
240
|
let dark = isDarkMode();
|
|
@@ -22,6 +22,11 @@ export interface AssetStampResult {
|
|
|
22
22
|
* keeps executing the bundle it cached hours ago — through edits, through reloads.
|
|
23
23
|
*/
|
|
24
24
|
export declare function computeAssetStamp(paths: string[], fallback: string): Promise<AssetStampResult>;
|
|
25
|
+
/**
|
|
26
|
+
* The assets the stamp covers, in hashing order. ONE list, for `buildSite` and for
|
|
27
|
+
* `restampSite`: two would let a re-stamp hash a different set and never match a build.
|
|
28
|
+
*/
|
|
29
|
+
export declare function assetStampInputs(publicDir: string, scriptName: string, hydrateName?: string): string[];
|
|
25
30
|
/**
|
|
26
31
|
* The message for a named-but-missing input. Separate so the test asserts the real string
|
|
27
32
|
* rather than a paraphrase of it.
|
|
@@ -53,6 +53,17 @@ export async function computeAssetStamp(paths, fallback) {
|
|
|
53
53
|
usedFallback: !sawAny,
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The assets the stamp covers, in hashing order. ONE list, for `buildSite` and for
|
|
58
|
+
* `restampSite`: two would let a re-stamp hash a different set and never match a build.
|
|
59
|
+
*/
|
|
60
|
+
export function assetStampInputs(publicDir, scriptName, hydrateName) {
|
|
61
|
+
return [
|
|
62
|
+
hydrateName && `${publicDir}/${hydrateName}`,
|
|
63
|
+
`${publicDir}/${scriptName}`,
|
|
64
|
+
`${publicDir}/doc-system.css`,
|
|
65
|
+
].filter(Boolean);
|
|
66
|
+
}
|
|
56
67
|
/**
|
|
57
68
|
* The message for a named-but-missing input. Separate so the test asserts the real string
|
|
58
69
|
* rather than a paraphrase of it.
|
|
@@ -22,7 +22,7 @@ executed, so they're skipped.
|
|
|
22
22
|
Build-time only (bun). Never import from browser code.
|
|
23
23
|
*/
|
|
24
24
|
import { collectCodeTokens } from '../code-fences.js';
|
|
25
|
-
import { rewriteImports, AsyncFunction, loadTransform, transformAvailable, UnsupportedImportError, } from '../../live-example/code-transform.js';
|
|
25
|
+
import { rewriteImports, AsyncFunction, loadTransform, transformAvailable, UnsupportedImportError, withoutExports, } from '../../live-example/code-transform.js';
|
|
26
26
|
import { isLiveFence } from '../example-policy.js';
|
|
27
27
|
// The default live-example context (matches the IIFE globals the pages provide).
|
|
28
28
|
// A project that sets a custom `context` on its <tosi-doc-system> can pass its
|
|
@@ -130,7 +130,9 @@ export async function checkExamples(docs, opts = {}) {
|
|
|
130
130
|
// We only need to validate that the source builds, not reproduce tjs
|
|
131
131
|
// lowering exactly.
|
|
132
132
|
tsTranspiler ??= new Bun.Transpiler({ loader: 'ts' });
|
|
133
|
-
|
|
133
|
+
// The page removes `export` from a `ts` example when its tjs-lang can; so does
|
|
134
|
+
// this, or a module-style example the page runs would fail the build.
|
|
135
|
+
js = await withoutExports(tsTranspiler.transformSync(rewritten));
|
|
134
136
|
}
|
|
135
137
|
else if (dialect === 'tjs') {
|
|
136
138
|
const transform = await loadTransform('tjs');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
var Bun: any;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* For each package that must be shared, the distinct copies reachable from tosijs-ui, from
|
|
6
|
+
* every `@codemirror/*` package it depends on, and from tjs-lang's editor extension. More than one path for a name is
|
|
7
|
+
* the defect. `uiDir` is where tosijs-ui itself is installed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function codemirrorCopies(root: string, uiDir: string): Record<string, string[]>;
|
|
10
|
+
/** What to say about `codemirrorCopies`' answer, or null when each package has one copy. */
|
|
11
|
+
export declare function duplicateCodemirrorWarning(copies: Record<string, string[]>): string | null;
|
|
12
|
+
/** tosijs-ui's own package directory, found from this module's location. */
|
|
13
|
+
export declare function tosijsUiDir(moduleDir: string): string;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Two copies of CodeMirror in one install (board #3248, item 2; the same failure as #131).
|
|
3
|
+
|
|
4
|
+
Every CodeMirror package must share ONE `@codemirror/state` (and one `@codemirror/view`):
|
|
5
|
+
extensions are recognised by identity, so a language package holding a different copy hands
|
|
6
|
+
the editor values it rejects ("Unrecognized extension value in extension set"), and every
|
|
7
|
+
`<tosi-code>` renders blank while the build and the tests stay green.
|
|
8
|
+
|
|
9
|
+
It arrives through an ordinary upgrade. A lockfile that pinned an older hoisted
|
|
10
|
+
`@codemirror/state` keeps it, the package manager nests the newer one tosijs-ui now needs
|
|
11
|
+
under `node_modules/tosijs-ui/`, and the `lang-*` packages go on resolving the hoisted copy.
|
|
12
|
+
|
|
13
|
+
Build-time only: it asks the module resolver, which is the thing that decides what loads.
|
|
14
|
+
*/
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { existsSync, readFileSync, realpathSync } from 'fs';
|
|
17
|
+
const SHARED = ['@codemirror/state', '@codemirror/view'];
|
|
18
|
+
/** The package directory a resolved file belongs to (the one holding its package.json). */
|
|
19
|
+
function packageDirOf(file) {
|
|
20
|
+
let dir = path.dirname(file);
|
|
21
|
+
while (dir !== path.dirname(dir)) {
|
|
22
|
+
if (existsSync(path.join(dir, 'package.json')))
|
|
23
|
+
return dir;
|
|
24
|
+
dir = path.dirname(dir);
|
|
25
|
+
}
|
|
26
|
+
return path.dirname(file);
|
|
27
|
+
}
|
|
28
|
+
function resolveFrom(specifier, from) {
|
|
29
|
+
try {
|
|
30
|
+
return realpathSync(Bun.resolveSync(specifier, from));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* For each package that must be shared, the distinct copies reachable from tosijs-ui, from
|
|
38
|
+
* every `@codemirror/*` package it depends on, and from tjs-lang's editor extension. More than one path for a name is
|
|
39
|
+
* the defect. `uiDir` is where tosijs-ui itself is installed.
|
|
40
|
+
*/
|
|
41
|
+
export function codemirrorCopies(root, uiDir) {
|
|
42
|
+
let deps = [];
|
|
43
|
+
try {
|
|
44
|
+
const pkg = JSON.parse(readFileSync(path.join(uiDir, 'package.json'), 'utf8'));
|
|
45
|
+
deps = Object.keys(pkg.dependencies ?? {}).filter((name) => name.startsWith('@codemirror/'));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// no package.json to read: only the two direct resolutions are compared
|
|
49
|
+
}
|
|
50
|
+
// Where each CodeMirror package tosijs-ui uses actually lives: each resolves its OWN
|
|
51
|
+
// dependencies from there, which is where a second copy shows up.
|
|
52
|
+
/*
|
|
53
|
+
NOT the project root. A project may hold its own, unrelated `@codemirror/state` for an
|
|
54
|
+
editor of its own, beside a consistent set under tosijs-ui: that is two copies in the
|
|
55
|
+
install and no defect. The defect is the packages the editor is BUILT FROM disagreeing,
|
|
56
|
+
and the upgrade case shows up there, since the hoisted `lang-*` resolve the hoisted copy.
|
|
57
|
+
*/
|
|
58
|
+
const origins = new Set([uiDir]);
|
|
59
|
+
for (const dep of deps) {
|
|
60
|
+
const resolved = resolveFrom(dep, uiDir);
|
|
61
|
+
if (resolved)
|
|
62
|
+
origins.add(packageDirOf(resolved));
|
|
63
|
+
}
|
|
64
|
+
// tjs-lang's CodeMirror extension is bundled into the same editor (see bundle-guard.ts).
|
|
65
|
+
const tjs = resolveFrom('tjs-lang/editors/codemirror', root);
|
|
66
|
+
if (tjs)
|
|
67
|
+
origins.add(packageDirOf(tjs));
|
|
68
|
+
const copies = {};
|
|
69
|
+
for (const name of SHARED) {
|
|
70
|
+
const found = new Set();
|
|
71
|
+
for (const origin of origins) {
|
|
72
|
+
const resolved = resolveFrom(name, origin);
|
|
73
|
+
if (resolved)
|
|
74
|
+
found.add(packageDirOf(resolved));
|
|
75
|
+
}
|
|
76
|
+
copies[name] = [...found].sort();
|
|
77
|
+
}
|
|
78
|
+
return copies;
|
|
79
|
+
}
|
|
80
|
+
/** What to say about `codemirrorCopies`' answer, or null when each package has one copy. */
|
|
81
|
+
export function duplicateCodemirrorWarning(copies) {
|
|
82
|
+
const doubled = Object.entries(copies).filter(([, dirs]) => dirs.length > 1);
|
|
83
|
+
if (doubled.length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
return (`\n⚠️ This install has more than one copy of a CodeMirror package that must be shared:\n` +
|
|
86
|
+
doubled
|
|
87
|
+
.map(([name, dirs]) => ` ${name}\n` + dirs.map((dir) => ` ${dir}\n`).join(''))
|
|
88
|
+
.join('') +
|
|
89
|
+
` CodeMirror recognises extensions by identity, so in a bundle built from this install\n` +
|
|
90
|
+
` every <tosi-code> renders blank ("Unrecognized extension value in extension set")\n` +
|
|
91
|
+
` and nothing fails. It usually follows an upgrade: the lockfile kept an older hoisted\n` +
|
|
92
|
+
` copy. Bring them back to one:\n` +
|
|
93
|
+
` bun update ${doubled
|
|
94
|
+
.map(([name]) => name)
|
|
95
|
+
.join(' ')} (npm: npm dedupe)\n`);
|
|
96
|
+
}
|
|
97
|
+
/** tosijs-ui's own package directory, found from this module's location. */
|
|
98
|
+
export function tosijsUiDir(moduleDir) {
|
|
99
|
+
let dir = moduleDir;
|
|
100
|
+
while (dir !== path.dirname(dir)) {
|
|
101
|
+
const file = path.join(dir, 'package.json');
|
|
102
|
+
if (existsSync(file)) {
|
|
103
|
+
try {
|
|
104
|
+
if (JSON.parse(readFileSync(file, 'utf8')).name === 'tosijs-ui') {
|
|
105
|
+
return dir;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// unreadable: keep walking up
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
dir = path.dirname(dir);
|
|
113
|
+
}
|
|
114
|
+
return moduleDir;
|
|
115
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { defineSiteConfig } from './site-config.js';
|
|
2
2
|
export type { SiteConfig, SiteHost, LibraryBuildContext, } from './site-config.js';
|
|
3
3
|
export { buildSite } from './orchestrator.js';
|
|
4
|
+
export { restampSite } from './restamp.js';
|
|
5
|
+
export type { RestampResult } from './restamp.js';
|
|
4
6
|
export { SiteMisconfiguredError } from './build-warnings.js';
|
|
5
7
|
export { testReportVerdict } from './test-report.js';
|
|
6
8
|
export type { TestReportLike, TestReportVerdict } from './test-report.js';
|
|
@@ -5,6 +5,9 @@ browser code. See ./README is at ../doc-site-system.md and the repo docs.
|
|
|
5
5
|
*/
|
|
6
6
|
export { defineSiteConfig } from './site-config.js';
|
|
7
7
|
export { buildSite } from './orchestrator.js';
|
|
8
|
+
// For a project that replaces the hydration bundle after `buildSite`: re-stamp the pages
|
|
9
|
+
// from what is served, and check it registers the doc system (board #3248).
|
|
10
|
+
export { restampSite } from './restamp.js';
|
|
8
11
|
// Thrown by `buildSite` under `strict: true` — exported so a caller can tell a refused
|
|
9
12
|
// misconfiguration from a crash (`catch (e) { if (e instanceof SiteMisconfiguredError) … }`).
|
|
10
13
|
export { SiteMisconfiguredError } from './build-warnings.js';
|
|
@@ -30,14 +30,9 @@ export interface LlmsTxtMeta {
|
|
|
30
30
|
/** optional framing line(s) under the description */
|
|
31
31
|
tagline?: string;
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* only by reading the doc-site-system page in full.
|
|
37
|
-
*
|
|
38
|
-
* `'tunnel'` also carries that meaning — it is `true` plus a remote path — so the note is
|
|
39
|
-
* written on truthiness, not on `=== true`. Getting that wrong would silently drop the note
|
|
40
|
-
* for exactly the projects that enabled the MORE capable mode.
|
|
33
|
+
* @deprecated Ignored since 1.16.16. It used to add a paragraph about driving the dev
|
|
34
|
+
* server with the `hj` CLI; `llms.txt` is for whoever consumes the library, so that note
|
|
35
|
+
* belongs in the project's own agent instructions. Kept so existing callers still compile.
|
|
41
36
|
*/
|
|
42
37
|
haltijaDev?: boolean | 'tunnel';
|
|
43
38
|
/**
|
|
@@ -56,6 +51,13 @@ export interface LlmsTxtMeta {
|
|
|
56
51
|
* docs and needs no `dist/` library output.
|
|
57
52
|
*/
|
|
58
53
|
export declare function entriesFromCorpus(corpus: CorpusDoc[], meta: LlmsTxtMeta): LlmsEntry[];
|
|
54
|
+
/**
|
|
55
|
+
* A real page of THIS site to show the `index.md` rule with. It was hard-coded to
|
|
56
|
+
* `/carousel/index.md`, a tosijs-ui page, so every other project's llms.txt pointed agents
|
|
57
|
+
* at a URL it does not have (board #3248). The first page that has a markdown copy and is
|
|
58
|
+
* not the site root.
|
|
59
|
+
*/
|
|
60
|
+
export declare function markdownExample(corpus: CorpusDoc[], meta: LlmsTxtMeta): string | undefined;
|
|
59
61
|
/**
|
|
60
62
|
* Write an `llms.txt` index. Pass the extracted `corpus` (the build does) to
|
|
61
63
|
* index every doc by its rendered URL; omit it to fall back to the legacy
|
|
@@ -93,6 +93,25 @@ function entriesFromSrcScan() {
|
|
|
93
93
|
}
|
|
94
94
|
return entries.sort((a, b) => a.title.localeCompare(b.title));
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* A real page of THIS site to show the `index.md` rule with. It was hard-coded to
|
|
98
|
+
* `/carousel/index.md`, a tosijs-ui page, so every other project's llms.txt pointed agents
|
|
99
|
+
* at a URL it does not have (board #3248). The first page that has a markdown copy and is
|
|
100
|
+
* not the site root.
|
|
101
|
+
*/
|
|
102
|
+
export function markdownExample(corpus, meta) {
|
|
103
|
+
const slugMap = buildSlugMap(corpus);
|
|
104
|
+
for (const doc of corpus) {
|
|
105
|
+
if (!doc.title || doc.hidden || doc.noindex) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const pagePath = pathForSlug(slugMap[doc.filename]);
|
|
109
|
+
if (pagePath === '/')
|
|
110
|
+
continue;
|
|
111
|
+
return `${withBase(meta.basePath, pagePath)}index.md`;
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
96
115
|
/**
|
|
97
116
|
* Write an `llms.txt` index. Pass the extracted `corpus` (the build does) to
|
|
98
117
|
* index every doc by its rendered URL; omit it to fall back to the legacy
|
|
@@ -128,28 +147,14 @@ export function generateLlmsTxt(outputPath, meta = {}, corpus) {
|
|
|
128
147
|
'',
|
|
129
148
|
]
|
|
130
149
|
: [];
|
|
131
|
-
/*
|
|
132
|
-
Only advertise browser control when the project has actually opted in. Telling an agent to
|
|
133
|
-
drive a page that has no dev channel sends it after a capability that will not answer, and
|
|
134
|
-
a wrong affordance costs more than a missing one.
|
|
135
|
-
*/
|
|
136
|
-
const haltijaNote = meta.haltijaDev
|
|
137
|
-
? [
|
|
138
|
-
'Running this project locally (`bun start`), the dev server injects a',
|
|
139
|
-
'localhost-gated dev channel, so an agent can DRIVE the live page with the `hj`',
|
|
140
|
-
'CLI — `hj navigate`, `hj eval`, `hj tree` — instead of inferring behaviour from',
|
|
141
|
-
'source. Injected at serve time only, never in the built output. Note that no',
|
|
142
|
-
'requestAnimationFrame runs under `hj eval`, and tosijs renders are rAF-driven, so',
|
|
143
|
-
'use it to inspect STATE; a painted-output check needs a real test lane.',
|
|
144
|
-
'',
|
|
145
|
-
]
|
|
146
|
-
: [];
|
|
147
150
|
// Only for a corpus-driven index: the legacy scan links to dist/ files, not pages.
|
|
151
|
+
const example = corpus ? markdownExample(corpus, meta) : undefined;
|
|
148
152
|
const markdownNote = corpus && meta.markdownPages !== false
|
|
149
153
|
? [
|
|
150
|
-
'Pages below are also available as plain markdown: append `index.md` to the URL'
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
'Pages below are also available as plain markdown: append `index.md` to the URL' +
|
|
155
|
+
(example ? '' : '.'),
|
|
156
|
+
...(example ? [`(for example \`${example}\`).`] : []),
|
|
157
|
+
'Prefer that if you only need the text. A page marked noindex has no markdown copy.',
|
|
153
158
|
'',
|
|
154
159
|
]
|
|
155
160
|
: [];
|
|
@@ -178,7 +183,6 @@ export function generateLlmsTxt(outputPath, meta = {}, corpus) {
|
|
|
178
183
|
'',
|
|
179
184
|
...markdownNote,
|
|
180
185
|
...liveExampleNote,
|
|
181
|
-
...haltijaNote,
|
|
182
186
|
'## Pages',
|
|
183
187
|
'',
|
|
184
188
|
];
|
|
@@ -21,7 +21,8 @@ import * as path from 'path';
|
|
|
21
21
|
* that asks "is this fence live?" calls it first rather than depending on build order: the
|
|
22
22
|
* misconfiguration checks run long before the page generator, which used to be the only caller.
|
|
23
23
|
*/
|
|
24
|
-
|
|
24
|
+
let codemirrorWarned = false;
|
|
25
|
+
export function registerSiteDialects(config) {
|
|
25
26
|
for (const name of normalizeDialectNames(config.dialects)) {
|
|
26
27
|
if (!BUILT_IN_DIALECTS.has(name))
|
|
27
28
|
registerLiveLanguage(name);
|
|
@@ -30,7 +31,7 @@ function registerSiteDialects(config) {
|
|
|
30
31
|
import { namedBooks, partitionByBook, DEFAULT_BOOK } from '../book-target.js';
|
|
31
32
|
import { attachCovers, listEpubVolumes, printCoverManifestUrl, VOLUME_MANIFEST, renderEpubDownloads, } from './epub-volumes.js';
|
|
32
33
|
import { buildSlugMap } from '../routing.js';
|
|
33
|
-
import { computeAssetStamp, missingStampInputWarning } from './asset-stamp.js';
|
|
34
|
+
import { assetStampInputs, computeAssetStamp, missingStampInputWarning, } from './asset-stamp.js';
|
|
34
35
|
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, } from 'fs';
|
|
35
36
|
import { tmpdir } from 'os';
|
|
36
37
|
import { $, spawn } from 'bun';
|
|
@@ -43,10 +44,12 @@ import { generateLlmsTxt } from './make-llms-txt.js';
|
|
|
43
44
|
import { DEFAULT_DOC_IGNORES } from './docs.js';
|
|
44
45
|
import { writesOutsideOutputDir } from './host-preset.js';
|
|
45
46
|
import { generateSite } from './generate-site.js';
|
|
47
|
+
import { servedBundleProblem } from './restamp.js';
|
|
48
|
+
import { codemirrorCopies, duplicateCodemirrorWarning, tosijsUiDir, } from './codemirror-copies.js';
|
|
46
49
|
import { findOutputDirOverlap, resolveBundleDir } from './output-guard.js';
|
|
47
50
|
import { acquireBuildLock, describeHolder } from './build-lock.js';
|
|
48
51
|
import { sourcemapWarning } from './sourcemap-check.js';
|
|
49
|
-
import { basePathDoubling,
|
|
52
|
+
import { basePathDoubling, firebasePublicMismatch } from './host-preset.js';
|
|
50
53
|
import { preflight } from './preflight.js';
|
|
51
54
|
import { auditDependencies, reportAudit } from './audit-guard.js';
|
|
52
55
|
import { gatherBuildStamp, hashOutput, stampToWrite, } from './build-stamp.js';
|
|
@@ -452,6 +455,28 @@ export async function buildSite(config, opts = {}) {
|
|
|
452
455
|
const PUBLIC = path.resolve(PROJECT_ROOT, config.outputDir ?? 'docs');
|
|
453
456
|
const DIST = path.resolve(PROJECT_ROOT, 'dist');
|
|
454
457
|
/*
|
|
458
|
+
Two copies of CodeMirror blank every <tosi-code>, and nothing else says so (#3248). Asked
|
|
459
|
+
first, of the module resolver: it costs a couple of dozen resolutions, and a site built
|
|
460
|
+
on that install is broken however the rest of the build goes.
|
|
461
|
+
*/
|
|
462
|
+
{
|
|
463
|
+
const doubled = duplicateCodemirrorWarning(codemirrorCopies(process.cwd(), tosijsUiDir(import.meta.dir)));
|
|
464
|
+
/*
|
|
465
|
+
Fatal under `strict` only where buildSite itself bundles the editor (`bundleEntry`).
|
|
466
|
+
Without one the site ships tosijs-ui's prebuilt bundle, which the install cannot
|
|
467
|
+
break, so the copies matter only to a bundle the project builds for itself, and
|
|
468
|
+
`restampSite` is where that project gets a failing build. Otherwise said once per
|
|
469
|
+
process: a watch rebuild has nothing new to add.
|
|
470
|
+
*/
|
|
471
|
+
if (doubled) {
|
|
472
|
+
if (config.bundleEntry && config.strict)
|
|
473
|
+
misconfigured(true, doubled);
|
|
474
|
+
else if (!codemirrorWarned)
|
|
475
|
+
console.warn(doubled);
|
|
476
|
+
codemirrorWarned = true;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/*
|
|
455
480
|
Where the hydration bundle is BUILT. Defaults to the site output, not to `dist` (#69).
|
|
456
481
|
|
|
457
482
|
It used to be built into `dist` unconditionally — the same directory `emitLibrary` /
|
|
@@ -848,9 +873,15 @@ export async function buildSite(config, opts = {}) {
|
|
|
848
873
|
existsSync(DIST)) {
|
|
849
874
|
const bad = extensionlessImports(DIST);
|
|
850
875
|
if (bad.length) {
|
|
851
|
-
console.warn(`⚠️ dist/
|
|
852
|
-
`(ERR_MODULE_NOT_FOUND), even though Bun and bundlers
|
|
853
|
-
`instead of ${config.libraryTsconfig ? 'libraryTsconfig' : 'emitLibrary'} (tosijs-ui#169). First: ${bad[0]}`
|
|
876
|
+
console.warn(`⚠️ The .js files tsc just emitted into dist/ have ${bad.length} relative import(s) without a ` +
|
|
877
|
+
`file extension — Node rejects them (ERR_MODULE_NOT_FOUND), even though Bun and bundlers ` +
|
|
878
|
+
`resolve them. Use \`libraryBundle\` instead of ${config.libraryTsconfig ? 'libraryTsconfig' : 'emitLibrary'} (tosijs-ui#169). First: ${bad[0]}\n` +
|
|
879
|
+
// A project that publishes bundles it builds afterwards sees this about files
|
|
880
|
+
// it then replaces, and went looking for them in its final dist/ (board #3248).
|
|
881
|
+
` If a later step replaces these files with bundles, they are not what you publish: ` +
|
|
882
|
+
(config.libraryTsconfig
|
|
883
|
+
? `set "emitDeclarationOnly": true in ${config.libraryTsconfig} so tsc emits only the .d.ts files.`
|
|
884
|
+
: `use \`libraryTsconfig\` with "emitDeclarationOnly": true so tsc emits only the .d.ts files.`));
|
|
854
885
|
}
|
|
855
886
|
}
|
|
856
887
|
// The hydration bundle. If bundleEntry is set we build it (IIFE), else pages
|
|
@@ -1145,23 +1176,10 @@ export async function buildSite(config, opts = {}) {
|
|
|
1145
1176
|
for (const f of await Array.fromAsync(new Bun.Glob('**/*.js').scan({ cwd: PUBLIC, absolute: true }))) {
|
|
1146
1177
|
bundled += await Bun.file(f).text();
|
|
1147
1178
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
` no menu, no live examples, and no error in the console.\n\n` +
|
|
1153
|
-
` bundleEntry REPLACES tosijs-ui's bundle; it does not extend it. Add:\n` +
|
|
1154
|
-
` import 'tosijs-ui/doc-browser'\n` +
|
|
1155
|
-
(docsHaveExamples && !reg.liveExample
|
|
1156
|
-
? ` import 'tosijs-ui/live-example' // this corpus has live examples\n`
|
|
1157
|
-
: '') +
|
|
1158
|
-
` to ${config.bundleEntry}.\n`);
|
|
1159
|
-
}
|
|
1160
|
-
else if (docsHaveExamples && !reg.liveExample) {
|
|
1161
|
-
misconfigured(config.strict, `\n⚠️ This corpus has executable fences but ${config.bundleEntry} does not\n` +
|
|
1162
|
-
` register <tosi-example>. Add \`import 'tosijs-ui/live-example'\` — the\n` +
|
|
1163
|
-
` code blocks will render as plain text otherwise.\n`);
|
|
1164
|
-
}
|
|
1179
|
+
// ONE wording and one rule, shared with restampSite (restamp.ts).
|
|
1180
|
+
const problem = servedBundleProblem(bundled, String(config.bundleEntry), docsHaveExamples, String(config.bundleEntry));
|
|
1181
|
+
if (problem)
|
|
1182
|
+
misconfigured(config.strict, problem);
|
|
1165
1183
|
}
|
|
1166
1184
|
// Report the always-loaded weight (entry, not the lazy editor chunks) so a
|
|
1167
1185
|
// regression that pulls CodeMirror back into the entry is visible.
|
|
@@ -1206,7 +1224,6 @@ export async function buildSite(config, opts = {}) {
|
|
|
1206
1224
|
baseUrl: config.baseUrl,
|
|
1207
1225
|
basePath: config.basePath,
|
|
1208
1226
|
projectLinks: config.projectLinks,
|
|
1209
|
-
haltijaDev: config.haltijaDev,
|
|
1210
1227
|
markdownPages: config.markdownPages,
|
|
1211
1228
|
liveExamples: config.liveExamples,
|
|
1212
1229
|
}, corpus);
|
|
@@ -1425,11 +1442,7 @@ export async function buildSite(config, opts = {}) {
|
|
|
1425
1442
|
written, so hashing the directory would hash the previous build. The bundle and the
|
|
1426
1443
|
stylesheet exist by now, and they are exactly what a stale cache serves.
|
|
1427
1444
|
*/
|
|
1428
|
-
const stampInputs =
|
|
1429
|
-
hydrateName && `${PUBLIC}/${hydrateName}`,
|
|
1430
|
-
`${PUBLIC}/${scriptName}`,
|
|
1431
|
-
`${PUBLIC}/doc-system.css`,
|
|
1432
|
-
].filter(Boolean);
|
|
1445
|
+
const stampInputs = assetStampInputs(PUBLIC, scriptName, hydrateName);
|
|
1433
1446
|
/*
|
|
1434
1447
|
Always a CONTENT HASH — the `opts.lock ? version : hash` branch is gone (major M3).
|
|
1435
1448
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SiteConfig } from './site-config.js';
|
|
2
|
+
export interface RestampResult {
|
|
3
|
+
/** the stamp now in every page */
|
|
4
|
+
stamp: string;
|
|
5
|
+
/** the stamp `buildSite` had written */
|
|
6
|
+
previous: string;
|
|
7
|
+
/** pages that carried a stamp */
|
|
8
|
+
pages: number;
|
|
9
|
+
/** pages whose stamp changed (0 when the bundle was not actually replaced) */
|
|
10
|
+
changed: number;
|
|
11
|
+
}
|
|
12
|
+
/** The hydration entry `buildSite` emitted for a `bundleEntry`, if there is one. */
|
|
13
|
+
export declare function hydrateEntryIn(publicDir: string): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* What is wrong with the scripts the pages hydrate from, or null. ONE wording and one rule,
|
|
16
|
+
* for `buildSite` (asked of a `bundleEntry`'s output) and `restampSite` (asked of a file the
|
|
17
|
+
* project wrote itself). `subject` names what was read; `fix` says where the import goes.
|
|
18
|
+
*/
|
|
19
|
+
export declare function servedBundleProblem(source: string, subject: string, docsHaveExamples: boolean, fix?: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Re-stamp a built site from the assets actually on disk, and check the hydration bundle.
|
|
22
|
+
* Call it after replacing `/iife.js` (or whatever `scriptUrl` names) in `outputDir`.
|
|
23
|
+
*
|
|
24
|
+
* Throws when no page carries a stamp (the build did not run, or `outputDir` is wrong): a
|
|
25
|
+
* restamp that silently did nothing is the failure it exists to prevent. A bundle that does
|
|
26
|
+
* not register the doc system is a warning, and fails under `strict: true`, as in
|
|
27
|
+
* `buildSite`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function restampSite(config: SiteConfig): Promise<RestampResult>;
|