tosijs-ui 1.7.0-beta.5 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +10 -0
- package/dist/code-editor.d.ts +4 -3
- package/dist/code-editor.js +7 -7
- package/dist/doc-browser.js +15 -4
- package/dist/doc-system/css-var-editor.d.ts +18 -0
- package/dist/doc-system/css-var-editor.js +85 -0
- package/dist/doc-system/doc-system.d.ts +1 -0
- package/dist/doc-system/doc-system.js +20 -0
- package/dist/doc-system/render.js +19 -11
- package/dist/doc-system/site/check-examples-cli.js +7 -1
- package/dist/doc-system/site/check-examples.d.ts +1 -0
- package/dist/doc-system/site/check-examples.js +1 -1
- package/dist/doc-system/site/dev-server.js +5 -1
- package/dist/doc-system/site/orchestrator.js +111 -38
- package/dist/doc-system/site/site-config.d.ts +16 -0
- package/dist/hydrate/hydrate.js +80 -80
- package/dist/hydrate/hydrate.js.map +14 -13
- package/dist/hydrate/tjs-import-resolver-akt3k27z.js +4 -0
- package/dist/hydrate/tjs-import-resolver-akt3k27z.js.map +10 -0
- package/dist/iife.js +131 -131
- package/dist/iife.js.map +15 -13
- package/dist/live-example/code-transform.d.ts +1 -11
- package/dist/live-example/code-transform.js +63 -5
- package/dist/live-example/component.d.ts +3 -0
- package/dist/live-example/component.js +39 -10
- package/dist/live-example/insert-examples.js +14 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.7.0
|
|
4
|
+
|
|
5
|
+
> ### ⚠️ BREAKING CHANGE — `<tosi-code>` moved from ACE to CodeMirror 6
|
|
6
|
+
>
|
|
7
|
+
> This is a **breaking change shipping under a minor version**, deliberately. `<tosi-code>`
|
|
8
|
+
> (and the `<tosi-code>`-backed code panels in live examples / the doc browser) is now a
|
|
9
|
+
> CodeMirror 6 wrapper instead of ACE. The public contract that **survives** is unchanged:
|
|
10
|
+
> `value`, `mode`, the `change` event, `disabled`, and `undo`/`redo`. What is **removed**:
|
|
11
|
+
> the ACE-era **`theme`** and **`options`** props — there is no compatibility shim for them
|
|
12
|
+
> (CodeMirror's theming model is different in kind, so a shim would silently no-op). If you set
|
|
13
|
+
> either, migrate: dark mode is now automatic (driven by `body.darkmode`), and editor styling
|
|
14
|
+
> comes from the `--code-bg` / `--text-color` CSS variables. The `editor` property now exposes
|
|
15
|
+
> a CodeMirror `EditorView` rather than an ACE editor.
|
|
16
|
+
>
|
|
17
|
+
> **Why a minor, not 2.0.0:** the `2.0` name is reserved for the tjs-native tosijs port that
|
|
18
|
+
> follows tosijs 2.0; spending it on an editor swap now would missignal that larger change.
|
|
19
|
+
> The deviation from strict semver is intentional and called out here + in the README. **To
|
|
20
|
+
> defer the change, pin `tosijs-ui@1.6`.** Everything else in 1.7.0 is additive.
|
|
21
|
+
|
|
22
|
+
The headline: the editor became first-class for **tjs** and **WebAssembly**, and doc pages got
|
|
23
|
+
dramatically lighter — a reader loads a page with **neither the tjs transpiler nor CodeMirror on
|
|
24
|
+
first paint**, both arriving only when a code panel is opened to edit.
|
|
25
|
+
|
|
26
|
+
### CodeMirror 6 editor (replaces ACE)
|
|
27
|
+
|
|
28
|
+
`<tosi-code>` is a CodeMirror 6 wrapper. The heavy CM code (`code-editor-cm.ts`) is a **lazy
|
|
29
|
+
chunk** loaded on first edit — for ESM/bundler consumers a page with no editor bundles none of
|
|
30
|
+
it. (The IIFE build cannot code-split, so the CDN `<script>` path still inlines it.) Modes:
|
|
31
|
+
`javascript`, `typescript`, `tjs`, `ajs`, `css`, `html`, `markdown`. New: `undo()`/`redo()`/
|
|
32
|
+
`canUndo()`/`canRedo()`, `showDiff(on)` (diffs against a captured baseline via `tosi-diff`), and
|
|
33
|
+
automatic dark mode via a `highlight` compartment + a `body.darkmode` observer.
|
|
34
|
+
|
|
35
|
+
### First-class tjs + inline WebAssembly in live examples
|
|
36
|
+
|
|
37
|
+
`tjs`/`ajs` example blocks get tjs-lang's CodeMirror language + **runtime-value autocomplete**
|
|
38
|
+
(completion resolves the live values of an example's locals). Inline `wasm{}` examples run — the
|
|
39
|
+
WASM kernel actually compiles, guarded in CI so a silent fall-back to JS is caught. tjs-lang
|
|
40
|
+
bumped **0.9.0 → 0.12.0** across the release (memory-storm fix in 0.10.1; import-resolver in
|
|
41
|
+
0.11.0; VM security-review fixes in 0.12.0).
|
|
42
|
+
|
|
43
|
+
### Self-contained, transpiler-free example pages
|
|
44
|
+
|
|
45
|
+
Each `tjs` example is transpiled **at build time** and its JS baked into the page as a hidden,
|
|
46
|
+
non-executing `<script type="application/tosi-transpiled">`; the example runs from that bake, so
|
|
47
|
+
no transpiler loads for a reader. The bakes ship per-doc in `docs.json` too, so client-side
|
|
48
|
+
navigation gets them at zero extra first-paint cost. Editing an example drops the bake and loads
|
|
49
|
+
the real transpiler + editor on demand; saving keeps the transpiled JS.
|
|
50
|
+
|
|
51
|
+
### Live examples can import real npm packages (experimental)
|
|
52
|
+
|
|
53
|
+
With `importResolver` enabled, a live example can `import` any npm package — resolved through a
|
|
54
|
+
same-origin service worker (tjs-lang's import-resolver). Three execution modes are flaggable on
|
|
55
|
+
the fence: `inline` (default), `iframe` (DOM/CSS isolation), `ide` (recognized; iframe path for
|
|
56
|
+
now). Fence grammar is order-free: `` ```ts:ide#demo `` and `` ```ts#demo:ide `` both parse.
|
|
57
|
+
|
|
58
|
+
### Doc-system & build
|
|
59
|
+
|
|
60
|
+
Pre-rendered, hydrating doc pages (the chrome renders server-side and hydrates in place — no
|
|
61
|
+
opacity gate on first paint). The hydration bundle ESM-splits CodeMirror off the critical path.
|
|
62
|
+
Machine-safety guards for the long-lived dev server: an RSS ceiling, an 8-hour idle exit, and a
|
|
63
|
+
preflight that refuses to build on an already-dying machine (Bun's `Bun.build()` native leak,
|
|
64
|
+
oven-sh/bun#34053, is worked around by shelling out to the `bun build` CLI). A live
|
|
65
|
+
`<tosi-css-var-editor>` on component pages. Nested `<tosi-doc-system>` instances no longer share
|
|
66
|
+
state (each gets its own observable registry key).
|
|
67
|
+
|
|
68
|
+
### Peers
|
|
69
|
+
|
|
70
|
+
`tosijs` peer floor `^1.7.0` — the 1.7 line is a co-released, lockstep stack, and this is the
|
|
71
|
+
version tosijs-ui is built, tested, and (in the iife) bundled against; we don't verify against
|
|
72
|
+
older tosijs, so the declared floor matches what's actually tested. `tjs-lang` `^0.12.0` — a lazy, optional
|
|
73
|
+
peer (a plain component consumer never pulls it in). `@codemirror/*` are the only hard runtime
|
|
74
|
+
dependencies (a deliberate divergence from the zero-runtime-dep rule; they must share one
|
|
75
|
+
`@codemirror/state` instance).
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
_The beta changelogs below are retained for detail; 1.7.0 consolidates betas 1–5 + rc.1. Built on tosijs 1.7.0._
|
|
80
|
+
|
|
3
81
|
## 1.7.0-beta.3
|
|
4
82
|
|
|
5
83
|
**Self-contained examples, and CodeMirror + the tjs transpiler are now edit-time only.** A
|
package/README.md
CHANGED
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
|
|
17
17
|
Copyright ©2023-2025 Tonio Loewald
|
|
18
18
|
|
|
19
|
+
## ⚠️ Breaking change in 1.7.0 — `<tosi-code>` (ACE → CodeMirror 6)
|
|
20
|
+
|
|
21
|
+
**1.7.0 is a breaking release shipping under a minor version, deliberately.** `<tosi-code>`
|
|
22
|
+
moved from ACE to CodeMirror 6. `value`, `mode`, the `change` event, `disabled`, and
|
|
23
|
+
`undo`/`redo` are unchanged; the ACE-era **`theme`** and **`options`** props are **removed** with
|
|
24
|
+
no shim. Dark mode is now automatic (`body.darkmode`) and styling comes from `--code-bg` /
|
|
25
|
+
`--text-color`. The `2.0` name is reserved for the tjs-native tosijs port, so this ships as
|
|
26
|
+
1.7.0 — **pin `tosijs-ui@1.6` to defer.** Full detail and rationale in
|
|
27
|
+
[CHANGELOG.md](./CHANGELOG.md).
|
|
28
|
+
|
|
19
29
|
## the tosijs-ui library
|
|
20
30
|
|
|
21
31
|
A set of [web-components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)
|
package/dist/code-editor.d.ts
CHANGED
|
@@ -48,9 +48,10 @@ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
|
|
|
48
48
|
* JS and CDN consumers — the audience this component's own docs court — would get the
|
|
49
49
|
* bare TypeError with no explanation at all.
|
|
50
50
|
*
|
|
51
|
-
* So:
|
|
52
|
-
*
|
|
53
|
-
*
|
|
51
|
+
* So: one neutral note on first access. `editor` is NOT deprecated — it is the
|
|
52
|
+
* supported CM6 accessor — so this is `console.info` (not `warn`) and leads with that,
|
|
53
|
+
* to help a 1.6→1.7 migrator without scolding correct CM6 use. Use
|
|
54
|
+
* `undo()`/`redo()`/`canUndo()`/`canRedo()` for history — they survived the migration.
|
|
54
55
|
*/
|
|
55
56
|
get editor(): CmHandle['view'] | undefined;
|
|
56
57
|
/** @deprecated Removed in 1.7 — CodeMirror themes via `--code-bg`/`--text-color`. */
|
package/dist/code-editor.js
CHANGED
|
@@ -187,17 +187,17 @@ export class CodeEditor extends WebComponent {
|
|
|
187
187
|
* JS and CDN consumers — the audience this component's own docs court — would get the
|
|
188
188
|
* bare TypeError with no explanation at all.
|
|
189
189
|
*
|
|
190
|
-
* So:
|
|
191
|
-
*
|
|
192
|
-
*
|
|
190
|
+
* So: one neutral note on first access. `editor` is NOT deprecated — it is the
|
|
191
|
+
* supported CM6 accessor — so this is `console.info` (not `warn`) and leads with that,
|
|
192
|
+
* to help a 1.6→1.7 migrator without scolding correct CM6 use. Use
|
|
193
|
+
* `undo()`/`redo()`/`canUndo()`/`canRedo()` for history — they survived the migration.
|
|
193
194
|
*/
|
|
194
195
|
get editor() {
|
|
195
196
|
if (!warnedEditor) {
|
|
196
197
|
warnedEditor = true;
|
|
197
|
-
console.
|
|
198
|
-
'in 1.6)
|
|
199
|
-
'`
|
|
200
|
-
'canUndo()/canRedo() for history; see the CodeMirror 6 docs for the rest.');
|
|
198
|
+
console.info('<tosi-code>.editor is the CodeMirror 6 EditorView (the supported accessor; it ' +
|
|
199
|
+
'was an ACE editor in 1.6). ACE-era `editor.session` / `getSession()` / ' +
|
|
200
|
+
'`setOption()` are gone — use undo()/redo()/canUndo()/canRedo() for history.');
|
|
201
201
|
}
|
|
202
202
|
return this._handle?.view;
|
|
203
203
|
}
|
package/dist/doc-browser.js
CHANGED
|
@@ -146,7 +146,7 @@ interface ProjectLinks {
|
|
|
146
146
|
|
|
147
147
|
The `tosijs-ui` demo is a complete working example. See:
|
|
148
148
|
|
|
149
|
-
- `/
|
|
149
|
+
- `/tosijs-site.config.ts` - How this repo configures its doc site (`defineSiteConfig` / `tosijs-ui/site`)
|
|
150
150
|
- `/src/doc-system/site/docs.ts` - The extraction tool (`extractDocs`, exported from `tosijs-ui/site`)
|
|
151
151
|
- `/src/doc-browser.ts` - The createDocBrowser implementation
|
|
152
152
|
*/
|
|
@@ -252,6 +252,9 @@ const testIndicatorStyleSpec = {
|
|
|
252
252
|
borderRadius: vars.roundedRadius,
|
|
253
253
|
},
|
|
254
254
|
};
|
|
255
|
+
// Monotonic per-page counter so each createDocBrowser() call gets a distinct
|
|
256
|
+
// tosi() registry key (see stateKey below) and two browsers can't share state.
|
|
257
|
+
let docBrowserSeq = 0;
|
|
255
258
|
export function createDocBrowser(options) {
|
|
256
259
|
const { docs, context = {}, projectName = '', projectLinks = {}, navSize = 200, minSize = 600, routing = 'query', initialRoute, onRouteChange, navbarLinks, contentElement, } = options;
|
|
257
260
|
// Memory routing is fully self-contained: it never reads or writes
|
|
@@ -320,13 +323,21 @@ export function createDocBrowser(options) {
|
|
|
320
323
|
};
|
|
321
324
|
const docName = filenameFromLocation() || docs[0]?.filename || 'README.md';
|
|
322
325
|
const currentDoc = docs.find((doc) => doc.filename === docName) || docs[0];
|
|
323
|
-
|
|
324
|
-
|
|
326
|
+
// tosi() registers its top-level keys in a GLOBAL observable registry, so two
|
|
327
|
+
// doc browsers on one page (e.g. a page whose live example embeds a nested
|
|
328
|
+
// <tosi-doc-system>) would BOTH register `app` and share it — the second wins,
|
|
329
|
+
// so the outer browser's nav / currentDoc / edit-source silently read the
|
|
330
|
+
// nested one's doc. Give each instance its own key. (This is the same collision
|
|
331
|
+
// the live-example docs warn about: never register a generic `app`.)
|
|
332
|
+
const stateKey = `docBrowser${docBrowserSeq++}`;
|
|
333
|
+
const state = tosi({
|
|
334
|
+
[stateKey]: {
|
|
325
335
|
docs,
|
|
326
336
|
currentDoc,
|
|
327
337
|
compact: false,
|
|
328
338
|
},
|
|
329
339
|
});
|
|
340
|
+
const app = state[stateKey];
|
|
330
341
|
// Assigned by the hierarchical nav builder (path routing); re-applies current
|
|
331
342
|
// highlight, test status, search visibility, and auto-open imperatively.
|
|
332
343
|
// resetOpen=true collapses every section except the current doc's.
|
|
@@ -1044,7 +1055,7 @@ export function createDocBrowser(options) {
|
|
|
1044
1055
|
},
|
|
1045
1056
|
}, template(a({
|
|
1046
1057
|
class: 'doc-link',
|
|
1047
|
-
bindCurrent:
|
|
1058
|
+
bindCurrent: `${stateKey}.currentDoc.filename`,
|
|
1048
1059
|
bindDocLink: '^.filename',
|
|
1049
1060
|
bindTestStatus: '^.testStatus',
|
|
1050
1061
|
onClick(event) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Component as WebComponent, ElementCreator } from 'tosijs';
|
|
2
|
+
import { TosiForm } from '../form';
|
|
3
|
+
declare class TosiCssVarEditor extends WebComponent {
|
|
4
|
+
static preferredTagName: string;
|
|
5
|
+
static initAttributes: {
|
|
6
|
+
elementSelector: string;
|
|
7
|
+
targetSelector: string;
|
|
8
|
+
};
|
|
9
|
+
content: () => (HTMLHeadingElement | TosiForm)[];
|
|
10
|
+
private retryTimer?;
|
|
11
|
+
private retries;
|
|
12
|
+
loadVars: () => void;
|
|
13
|
+
update: () => void;
|
|
14
|
+
connectedCallback(): void;
|
|
15
|
+
disconnectedCallback(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare const tosiCssVarEditor: ElementCreator<TosiCssVarEditor>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
`<tosi-css-var-editor>` — a doc-system affordance (NOT a general library component).
|
|
3
|
+
Reads the CSS custom properties a component defines in its shadow `<style>` and builds
|
|
4
|
+
a live form to tweak them, applying changes to the matching element(s) on the page. It
|
|
5
|
+
is registered as a side effect of loading the doc-system (see doc-system.ts) so doc pages
|
|
6
|
+
can drop `<tosi-css-var-editor element-selector="tosi-widget">` under an example; it is
|
|
7
|
+
deliberately not exported from `tosijs-ui`.
|
|
8
|
+
*/
|
|
9
|
+
import { Component as WebComponent, elements, Color, } from 'tosijs';
|
|
10
|
+
import { tosiForm, tosiField } from '../form';
|
|
11
|
+
const { h2, code } = elements;
|
|
12
|
+
// A value is a color if it's a hex literal or an rgb()/hsl() (with or without alpha)
|
|
13
|
+
// functional notation — the forms getComputedStyle / getPropertyValue return. The old
|
|
14
|
+
// regex (`[\d()a-fA-F]+`) rejected the commas and spaces in `rgb(255, 0, 0)`, so every
|
|
15
|
+
// functional color fell through to a plain text field.
|
|
16
|
+
const COLOR_RE = /^(#[0-9a-f]{3,8}|(?:rgb|hsl)a?\([^)]*\))$/i;
|
|
17
|
+
class TosiCssVarEditor extends WebComponent {
|
|
18
|
+
static preferredTagName = 'tosi-css-var-editor';
|
|
19
|
+
static initAttributes = {
|
|
20
|
+
elementSelector: '',
|
|
21
|
+
targetSelector: '',
|
|
22
|
+
};
|
|
23
|
+
content = () => [
|
|
24
|
+
h2({ part: 'title' }, 'CSS variables'),
|
|
25
|
+
tosiForm({ part: 'variables', changeCallback: this.update }),
|
|
26
|
+
];
|
|
27
|
+
// The example the editor targets can mount a frame or two after this element does;
|
|
28
|
+
// retry a bounded number of times (≈10s) rather than polling forever, and cancel on
|
|
29
|
+
// disconnect so a removed editor can't keep firing timers against a dead page.
|
|
30
|
+
retryTimer;
|
|
31
|
+
retries = 0;
|
|
32
|
+
loadVars = () => {
|
|
33
|
+
const { title, variables } = this.parts;
|
|
34
|
+
variables.textContent = '';
|
|
35
|
+
if (!this.elementSelector)
|
|
36
|
+
return;
|
|
37
|
+
title.textContent = `CSS variables for ${this.elementSelector}`;
|
|
38
|
+
const element = document.querySelector(this.elementSelector);
|
|
39
|
+
if (!element) {
|
|
40
|
+
if (this.retries++ < 40)
|
|
41
|
+
this.retryTimer = setTimeout(this.loadVars, 250);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const styleNode = element.shadowRoot
|
|
45
|
+
? element.shadowRoot.querySelector('style')
|
|
46
|
+
: document.querySelector(`style#${this.elementSelector}-component`);
|
|
47
|
+
if (!styleNode || !styleNode.textContent)
|
|
48
|
+
return;
|
|
49
|
+
const computedStyle = getComputedStyle(element);
|
|
50
|
+
const cssVars = [
|
|
51
|
+
...new Set(styleNode.textContent.match(/--[\w-]+/g) ?? []),
|
|
52
|
+
];
|
|
53
|
+
for (const cssVar of cssVars) {
|
|
54
|
+
let value = computedStyle.getPropertyValue(cssVar).trim();
|
|
55
|
+
const type = COLOR_RE.test(value) ? 'color' : 'string';
|
|
56
|
+
if (type === 'color')
|
|
57
|
+
value = Color.fromCss(value).html;
|
|
58
|
+
variables.append(tosiField(code(cssVar), { key: cssVar, value, type }));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
update = () => {
|
|
62
|
+
const selector = this.targetSelector || this.elementSelector;
|
|
63
|
+
if (!selector)
|
|
64
|
+
return;
|
|
65
|
+
const targets = [...document.querySelectorAll(selector)];
|
|
66
|
+
const { value } = this.parts.variables;
|
|
67
|
+
for (const target of targets) {
|
|
68
|
+
for (const key of Object.keys(value)) {
|
|
69
|
+
target.style.setProperty(key, value[key]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
connectedCallback() {
|
|
74
|
+
super.connectedCallback();
|
|
75
|
+
this.retries = 0;
|
|
76
|
+
this.loadVars();
|
|
77
|
+
this.parts.variables.addEventListener('change', this.update);
|
|
78
|
+
}
|
|
79
|
+
disconnectedCallback() {
|
|
80
|
+
super.disconnectedCallback();
|
|
81
|
+
if (this.retryTimer)
|
|
82
|
+
clearTimeout(this.retryTimer);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export const tosiCssVarEditor = TosiCssVarEditor.elementCreator();
|
|
@@ -64,6 +64,9 @@ import { docSystemStyleSpec } from './doc-system-styles';
|
|
|
64
64
|
import { icons } from '../icons';
|
|
65
65
|
import { popMenu } from '../menu';
|
|
66
66
|
import { i18n, setLocale, initLocalization } from '../localize';
|
|
67
|
+
// Side-effect: register <tosi-css-var-editor> so doc pages can drop the live CSS-var
|
|
68
|
+
// tweaker under an example. Part of the doc-system, NOT re-exported from tosijs-ui.
|
|
69
|
+
import './css-var-editor';
|
|
67
70
|
const { button, div } = elements;
|
|
68
71
|
const PREFS_KEY = 'tosi-doc-system-prefs';
|
|
69
72
|
export class TosiDocSystem extends Component {
|
|
@@ -425,3 +428,20 @@ export class TosiDocSystem extends Component {
|
|
|
425
428
|
}
|
|
426
429
|
}
|
|
427
430
|
export const tosiDocSystem = TosiDocSystem.elementCreator();
|
|
431
|
+
// Register the import-resolver service worker when the build opted in — the config
|
|
432
|
+
// global is injected into the page head by orchestrator's importResolverHead (gated on
|
|
433
|
+
// SiteConfig.importResolver). Dynamic import keeps tjs-lang/import-resolver out of the
|
|
434
|
+
// bundle unless a site actually enables it, and out of a plain component consumer's
|
|
435
|
+
// graph entirely. Experimental; 1.7.0 ships with the flag off. See import-resolver-plan.md.
|
|
436
|
+
function initImportResolver() {
|
|
437
|
+
if (typeof document === 'undefined')
|
|
438
|
+
return;
|
|
439
|
+
const cfg = globalThis
|
|
440
|
+
.__TOSI_IMPORT_RESOLVER;
|
|
441
|
+
if (!cfg)
|
|
442
|
+
return;
|
|
443
|
+
void import('tjs-lang/import-resolver')
|
|
444
|
+
.then(({ registerImportResolver }) => registerImportResolver(cfg))
|
|
445
|
+
.catch((e) => console.warn('import-resolver: registration failed', e));
|
|
446
|
+
}
|
|
447
|
+
initImportResolver();
|
|
@@ -15,20 +15,28 @@ const docMarked = new Marked();
|
|
|
15
15
|
docMarked.use({
|
|
16
16
|
renderer: {
|
|
17
17
|
code(token) {
|
|
18
|
+
// Fence info grammar: `<lang>` optionally with a `:<mode>` and/or `#<id>` in
|
|
19
|
+
// EITHER order — `js`, `css#anchor`, `js:iframe`, `ts:ide#demo`, `ts#demo:ide`.
|
|
20
|
+
// `:mode` (inline | iframe | ide) sets the live example's execution mode; `#id`
|
|
21
|
+
// gives it a stable anchor. `#id` is `[A-Za-z0-9_-]+` and `:mode` is `[a-z]+`, so
|
|
22
|
+
// the two can't overlap — parse each independently, order-free. Both are stripped
|
|
23
|
+
// so the language stays clean (`language-js`) for grouping/highlighting.
|
|
18
24
|
const info = String(token.lang || '');
|
|
19
|
-
const
|
|
20
|
-
const id =
|
|
21
|
-
|
|
22
|
-
: (info.slice(hash + 1).match(/^[A-Za-z0-9_-]+/)?.[0] ?? '');
|
|
25
|
+
const lang = info.match(/^[a-z]+/)?.[0] ?? '';
|
|
26
|
+
const id = info.match(/#([A-Za-z0-9_-]+)/)?.[1] ?? '';
|
|
27
|
+
const mode = info.match(/:([a-z]+)/)?.[1] ?? '';
|
|
23
28
|
const bake = currentBakes?.get(token.text);
|
|
24
|
-
if (!id && !bake)
|
|
29
|
+
if (!id && !mode && !bake)
|
|
25
30
|
return false; // default rendering — byte-identical
|
|
26
|
-
let html = baseRenderer.code({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
let html = baseRenderer.code({ ...token, lang });
|
|
32
|
+
const attrs = [
|
|
33
|
+
id && `data-example-id="${id}"`,
|
|
34
|
+
mode && `data-example-mode="${mode}"`,
|
|
35
|
+
]
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.join(' ');
|
|
38
|
+
if (attrs)
|
|
39
|
+
html = html.replace(/^<pre>/, `<pre ${attrs}>`);
|
|
32
40
|
if (bake) {
|
|
33
41
|
// `<` → < prevents a `</script>` inside the JS from breaking the tag;
|
|
34
42
|
// JSON.parse decodes it unchanged at hydration.
|
|
@@ -32,8 +32,14 @@ if (!docsJson) {
|
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
const contextKeys = process.argv.slice(3);
|
|
35
|
+
// When the site enables the import-resolver, the parent passes its prefix so
|
|
36
|
+
// non-context imports validate as dynamic `<prefix><spec>` imports rather than failing.
|
|
37
|
+
const importPrefix = process.env.TOSI_IMPORT_PREFIX || undefined;
|
|
35
38
|
const corpus = JSON.parse(await Bun.file(docsJson).text());
|
|
36
|
-
const { problems, bakes } = await checkExamples(corpus,
|
|
39
|
+
const { problems, bakes } = await checkExamples(corpus, {
|
|
40
|
+
...(contextKeys.length ? { contextKeys } : {}),
|
|
41
|
+
...(importPrefix ? { importPrefix } : {}),
|
|
42
|
+
});
|
|
37
43
|
// stdout is the channel — the parent parses this. Anything else this process prints
|
|
38
44
|
// (warnings from the transform, say) goes to stderr so it can't corrupt the payload.
|
|
39
45
|
// Maps can't JSON-roundtrip, so bakes go over as [filename, [source, {dialect,js}][]][].
|
|
@@ -25,6 +25,7 @@ export interface ExampleCheck {
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function checkExamples(docs: Doc[], opts?: {
|
|
27
27
|
contextKeys?: string[];
|
|
28
|
+
importPrefix?: string;
|
|
28
29
|
}): Promise<ExampleCheck>;
|
|
29
30
|
/** Format problems for a build log. */
|
|
30
31
|
export declare function formatExampleProblems(problems: ExampleProblem[]): string;
|
|
@@ -66,7 +66,7 @@ export async function checkExamples(docs, opts = {}) {
|
|
|
66
66
|
// `test` blocks are conventional JS/TS, transpiled as plain js.
|
|
67
67
|
const dialect = block.lang === 'test' ? 'js' : block.lang;
|
|
68
68
|
try {
|
|
69
|
-
const rewritten = rewriteImports(block.text, contextKeys);
|
|
69
|
+
const rewritten = rewriteImports(block.text, contextKeys, opts.importPrefix);
|
|
70
70
|
let js;
|
|
71
71
|
if (dialect === 'ts') {
|
|
72
72
|
// Use bun's own transpiler — network-free (the runtime `ts` path
|
|
@@ -40,7 +40,7 @@ const DEFAULT_IDLE_HOURS = 8;
|
|
|
40
40
|
* - `hj` action commands (`navigate`, `click`, …) exit NON-ZERO on failure, so the
|
|
41
41
|
* test lane below can trust an exit code instead of racing to a timeout.
|
|
42
42
|
*/
|
|
43
|
-
const HALTIJA_PKG = process.env.HALTIJA_VERSION ?? 'haltija@^1.
|
|
43
|
+
const HALTIJA_PKG = process.env.HALTIJA_VERSION ?? 'haltija@^1.5.0';
|
|
44
44
|
/**
|
|
45
45
|
* Resolve the idle-exit timeout to milliseconds (0 = disabled).
|
|
46
46
|
*
|
|
@@ -130,6 +130,9 @@ async function killStrayServer(port) {
|
|
|
130
130
|
catch {
|
|
131
131
|
continue; // already gone
|
|
132
132
|
}
|
|
133
|
+
// Leave a receipt: reclaiming a port is destructive, so if it ever hits a server
|
|
134
|
+
// the developer actually wanted, the log says exactly which pid/comm on which port.
|
|
135
|
+
console.warn(`↻ reclaimed port ${port}: SIGTERM → pid ${pid} (${comm || 'unknown'})`);
|
|
133
136
|
// Give it a moment to close the listener, then insist.
|
|
134
137
|
for (let i = 0; i < 20; i++) {
|
|
135
138
|
await Bun.sleep(50);
|
|
@@ -142,6 +145,7 @@ async function killStrayServer(port) {
|
|
|
142
145
|
if (i === 19) {
|
|
143
146
|
try {
|
|
144
147
|
process.kill(pid, 'SIGKILL');
|
|
148
|
+
console.warn(` pid ${pid} ignored SIGTERM — sent SIGKILL`);
|
|
145
149
|
}
|
|
146
150
|
catch {
|
|
147
151
|
// gone between the check and the signal — fine
|