tosijs-ui 1.6.21 → 1.6.22
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 +36 -0
- package/dist/code-editor.d.ts +9 -35
- package/dist/code-editor.js +62 -90
- package/dist/doc-system/site/dev-server.js +56 -4
- package/dist/doc-system/site/epub-cli.d.ts +1 -0
- package/dist/doc-system/site/epub-cli.js +22 -0
- package/dist/doc-system/site/epub.js +6 -0
- package/dist/doc-system/site/make-llms-txt.js +0 -16
- package/dist/doc-system/site/orchestrator.js +102 -47
- package/dist/doc-system/site/site-config.d.ts +10 -0
- package/dist/iife.js +109 -153
- package/dist/iife.js.map +6 -34
- package/dist/live-example/component.js +14 -192
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +2 -9
- package/package.json +1 -16
- package/tls/create-dev-certs.sh +9 -25
- package/dist/code-editor-cm.d.ts +0 -48
- package/dist/code-editor-cm.js +0 -219
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.22
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The dev server no longer leaks memory on every rebuild — update as a priority if
|
|
8
|
+
you use `tosijs-ui/site`.** `buildSite()` called `Bun.build()` in-process, and Bun's
|
|
9
|
+
bundler never returns its native arena: RSS grows monotonically per call with no
|
|
10
|
+
plateau (40 sequential builds of one entry = **+367MB**, still climbing ~5MB/build at
|
|
11
|
+
the end), while the JS heap stays flat — so it is invisible to `Bun.gc()` and to any
|
|
12
|
+
heap profiler. `devServer()` calls it once per rebuild in a process that runs for
|
|
13
|
+
days, so it compounds: a ~2-day watch session reached **136GB RSS** and took the
|
|
14
|
+
machine down with it. Filed upstream as
|
|
15
|
+
[oven-sh/bun#34053](https://github.com/oven-sh/bun/issues/34053).
|
|
16
|
+
|
|
17
|
+
The bundle now builds in a **child process** (`bun build`), whose memory the OS
|
|
18
|
+
reclaims on exit — the same 15 bundles leave the parent **+0.5MB** instead of +192MB.
|
|
19
|
+
`buildEpub()` moved to a child too (happy-dom + `@resvg/resvg-js` are both native and
|
|
20
|
+
retaining, and it runs on every rebuild), with a 120s timeout kill so a hung or failed
|
|
21
|
+
ePub warns instead of wedging the rebuild. Measured on this repo's own dev server:
|
|
22
|
+
**baseline RSS 503MB → 150MB, and per-rebuild growth 26–59MB → ~2.7MB.**
|
|
23
|
+
|
|
24
|
+
Bundle output is byte-for-byte identical; this is purely where the work runs.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Dev-server memory watchdog.** `devServer()` now samples RSS after each rebuild and,
|
|
29
|
+
past a ceiling (`memoryLimitMb` in the site config, or `DEV_MEMORY_LIMIT_MB`; default
|
|
30
|
+
4096), prints the growth-per-rebuild and exits rather than let a leak thrash the
|
|
31
|
+
machine. It distinguishes real growth (a leak — report it) from a build whose baseline
|
|
32
|
+
simply exceeds the ceiling (raise the ceiling).
|
|
33
|
+
|
|
34
|
+
## 1.6.21
|
|
35
|
+
|
|
36
|
+
- Memory-routed nav-toggle fix; `buildSite` `.tjs` hooks (`libraryBuild`,
|
|
37
|
+
`generateCssPreload` — see BUILD-TJS-HOOK.md); tjs-lang 0.9.0.
|
|
38
|
+
|
|
3
39
|
## 1.6.20
|
|
4
40
|
|
|
5
41
|
### Fixed
|
package/dist/code-editor.d.ts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import { Component as WebComponent, ElementCreator
|
|
2
|
-
|
|
3
|
-
interface CodeEditorParts extends PartsMap {
|
|
4
|
-
host: HTMLDivElement;
|
|
5
|
-
}
|
|
6
|
-
export declare class CodeEditor extends WebComponent<CodeEditorParts> {
|
|
1
|
+
import { Component as WebComponent, ElementCreator } from 'tosijs';
|
|
2
|
+
export declare class CodeEditor extends WebComponent {
|
|
7
3
|
static preferredTagName: string;
|
|
8
4
|
private source;
|
|
9
|
-
private _handle;
|
|
10
|
-
private _loadPromise;
|
|
11
|
-
private _appliedMode;
|
|
12
|
-
private _appliedDisabled;
|
|
13
5
|
get value(): string;
|
|
14
6
|
set value(text: string);
|
|
15
7
|
private _original;
|
|
@@ -20,16 +12,16 @@ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
|
|
|
20
12
|
showDiff(on: boolean): void;
|
|
21
13
|
static initAttributes: {
|
|
22
14
|
mode: string;
|
|
15
|
+
theme: string;
|
|
23
16
|
disabled: boolean;
|
|
24
17
|
};
|
|
25
18
|
role: string;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
content: () => HTMLDivElement[];
|
|
19
|
+
private _ace;
|
|
20
|
+
private _editor;
|
|
21
|
+
private _editorPromise;
|
|
22
|
+
options: any;
|
|
23
|
+
get ace(): any;
|
|
24
|
+
get editor(): any;
|
|
33
25
|
static shadowStyleSpec: {
|
|
34
26
|
':host': {
|
|
35
27
|
display: string;
|
|
@@ -37,27 +29,9 @@ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
|
|
|
37
29
|
width: string;
|
|
38
30
|
height: string;
|
|
39
31
|
};
|
|
40
|
-
'[part="host"]': {
|
|
41
|
-
height: string;
|
|
42
|
-
};
|
|
43
|
-
'.cm-editor': {
|
|
44
|
-
height: string;
|
|
45
|
-
};
|
|
46
|
-
'.cm-scroller': {
|
|
47
|
-
outline: string;
|
|
48
|
-
fontFamily: string;
|
|
49
|
-
};
|
|
50
32
|
};
|
|
51
33
|
onResize(): void;
|
|
52
34
|
connectedCallback(): void;
|
|
53
|
-
private isTjsMode;
|
|
54
|
-
/**
|
|
55
|
-
* When in tjs mode, lazily upgrade the editor to tjs-lang's CodeMirror language +
|
|
56
|
-
* autocomplete. No-op (keeps TS highlighting) if not tjs, if tjs-lang isn't
|
|
57
|
-
* installed, or if the mode/handle changed before the async load resolved.
|
|
58
|
-
*/
|
|
59
|
-
private applyTjsExtension;
|
|
60
35
|
render(): void;
|
|
61
36
|
}
|
|
62
37
|
export declare const codeEditor: ElementCreator<CodeEditor>;
|
|
63
|
-
export {};
|
package/dist/code-editor.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/*#
|
|
2
2
|
# code
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
An [ACE Editor](https://ace.c9.io/) wrapper.
|
|
5
5
|
|
|
6
6
|
Sometimes, it's nice to be able to just toss a code-editor in a web-page.
|
|
7
7
|
|
|
8
|
-
`<tosi-code>`'s `value` is the code it contains. Its `mode` attribute sets the
|
|
9
|
-
|
|
8
|
+
`<tosi-code>`'s `value` is the code it contains. Its `mode` attribute sets the language, and you can further configure
|
|
9
|
+
the ACE editor instance via its `options` property.
|
|
10
10
|
|
|
11
11
|
```html
|
|
12
12
|
<tosi-code style="width: 100%; height: 100%" mode="css">
|
|
@@ -16,33 +16,51 @@ body {
|
|
|
16
16
|
</tosi-code>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The `<tosi-code>` element has an `editor` property that gives you its
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
CodeMirror is loaded lazily on first use — a page with no `<tosi-code>` bundles
|
|
24
|
-
none of it.
|
|
19
|
+
The `<tosi-code>` element has an `editor` property that gives you its ACE editor instance,
|
|
20
|
+
and an `ace` property that returns the `ace` module, giving you complete access to the
|
|
21
|
+
[Ace API](https://ace.c9.io/api/index.html).
|
|
25
22
|
*/
|
|
26
23
|
/*{ "parent": "Components" }*/
|
|
27
|
-
import { Component as WebComponent
|
|
24
|
+
import { Component as WebComponent } from 'tosijs';
|
|
25
|
+
import { scriptTag } from './via-tag';
|
|
28
26
|
import { tosiDiff } from './diff';
|
|
29
|
-
const
|
|
27
|
+
const ACE_BASE_URL = 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.2/';
|
|
28
|
+
const DEFAULT_THEME = 'ace/theme/tomorrow';
|
|
29
|
+
const getAce = async () => {
|
|
30
|
+
const { ace } = await scriptTag(`${ACE_BASE_URL}ace.min.js`);
|
|
31
|
+
return ace;
|
|
32
|
+
};
|
|
33
|
+
const makeCodeEditor = async (codeElement, mode = 'html', options = {}, theme = DEFAULT_THEME) => {
|
|
34
|
+
const ace = await getAce();
|
|
35
|
+
ace.config.set('basePath', ACE_BASE_URL);
|
|
36
|
+
const editor = ace.edit(codeElement, {
|
|
37
|
+
mode: `ace/mode/${mode}`,
|
|
38
|
+
tabSize: 2,
|
|
39
|
+
useSoftTabs: true,
|
|
40
|
+
useWorker: false,
|
|
41
|
+
// Wrap wide lines instead of scrolling horizontally; indentedSoftWrap keeps
|
|
42
|
+
// wrapped continuations aligned under the line's indent.
|
|
43
|
+
wrap: true,
|
|
44
|
+
indentedSoftWrap: true,
|
|
45
|
+
...options,
|
|
46
|
+
});
|
|
47
|
+
editor.setTheme(theme);
|
|
48
|
+
return { ace, editor };
|
|
49
|
+
};
|
|
30
50
|
export class CodeEditor extends WebComponent {
|
|
31
51
|
static preferredTagName = 'tosi-code';
|
|
32
52
|
source = '';
|
|
33
|
-
_handle;
|
|
34
|
-
_loadPromise;
|
|
35
|
-
_appliedMode = '';
|
|
36
|
-
_appliedDisabled;
|
|
37
53
|
get value() {
|
|
38
|
-
return this.
|
|
54
|
+
return this.editor === undefined ? this.source : this.editor.getValue();
|
|
39
55
|
}
|
|
40
56
|
set value(text) {
|
|
41
|
-
if (this.
|
|
42
|
-
this.
|
|
57
|
+
if (this.editor === undefined) {
|
|
58
|
+
this.source = text;
|
|
43
59
|
}
|
|
44
60
|
else {
|
|
45
|
-
this.
|
|
61
|
+
this.editor.setValue(text);
|
|
62
|
+
this.editor.clearSelection();
|
|
63
|
+
this.editor.session.getUndoManager().reset();
|
|
46
64
|
}
|
|
47
65
|
}
|
|
48
66
|
// Baseline for `showDiff()` — the version to diff the current `value` against.
|
|
@@ -54,9 +72,9 @@ export class CodeEditor extends WebComponent {
|
|
|
54
72
|
set original(text) {
|
|
55
73
|
this._original = text;
|
|
56
74
|
}
|
|
57
|
-
// Diff overlay — built only on the editor's public surface
|
|
58
|
-
// and the tosi-diff component, never the underlying
|
|
59
|
-
// editor
|
|
75
|
+
// Diff overlay — deliberately built only on the editor's public surface
|
|
76
|
+
// (`value` + `original`) and the tosi-diff component, never the underlying
|
|
77
|
+
// editor's API, so it survives a future Ace → CodeMirror swap untouched.
|
|
60
78
|
diffOverlay;
|
|
61
79
|
get showingDiff() {
|
|
62
80
|
return this.diffOverlay !== undefined && !this.diffOverlay.hidden;
|
|
@@ -85,27 +103,20 @@ export class CodeEditor extends WebComponent {
|
|
|
85
103
|
}
|
|
86
104
|
static initAttributes = {
|
|
87
105
|
mode: 'javascript',
|
|
106
|
+
theme: DEFAULT_THEME,
|
|
88
107
|
disabled: false,
|
|
89
108
|
};
|
|
90
109
|
role = 'code editor';
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
110
|
+
_ace;
|
|
111
|
+
_editor;
|
|
112
|
+
_editorPromise;
|
|
113
|
+
options = {};
|
|
114
|
+
get ace() {
|
|
115
|
+
return this._ace;
|
|
94
116
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this._handle?.undo();
|
|
98
|
-
}
|
|
99
|
-
redo() {
|
|
100
|
-
this._handle?.redo();
|
|
101
|
-
}
|
|
102
|
-
canUndo() {
|
|
103
|
-
return this._handle?.canUndo() ?? false;
|
|
104
|
-
}
|
|
105
|
-
canRedo() {
|
|
106
|
-
return this._handle?.canRedo() ?? false;
|
|
117
|
+
get editor() {
|
|
118
|
+
return this._editor;
|
|
107
119
|
}
|
|
108
|
-
content = () => [div({ part: 'host' })];
|
|
109
120
|
static shadowStyleSpec = {
|
|
110
121
|
':host': {
|
|
111
122
|
display: 'block',
|
|
@@ -113,71 +124,32 @@ export class CodeEditor extends WebComponent {
|
|
|
113
124
|
width: '100%',
|
|
114
125
|
height: '100%',
|
|
115
126
|
},
|
|
116
|
-
'[part="host"]': { height: '100%' },
|
|
117
|
-
'.cm-editor': { height: '100%' },
|
|
118
|
-
'.cm-scroller': {
|
|
119
|
-
outline: 'none',
|
|
120
|
-
fontFamily: "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
|
121
|
-
},
|
|
122
127
|
};
|
|
123
128
|
onResize() {
|
|
124
|
-
this.
|
|
129
|
+
if (this.editor !== undefined) {
|
|
130
|
+
this.editor.resize(true);
|
|
131
|
+
}
|
|
125
132
|
}
|
|
126
133
|
connectedCallback() {
|
|
127
134
|
super.connectedCallback();
|
|
128
135
|
if (this.source === '') {
|
|
129
136
|
this.value = this.textContent !== null ? this.textContent.trim() : '';
|
|
130
137
|
}
|
|
131
|
-
if (this.
|
|
132
|
-
|
|
133
|
-
this.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
onChange: (value) => this.dispatchEvent(new CustomEvent('change', { detail: { value } })),
|
|
140
|
-
});
|
|
141
|
-
this._handle = handle;
|
|
142
|
-
this._appliedMode = this.mode;
|
|
143
|
-
this._appliedDisabled = this.disabled;
|
|
144
|
-
this.applyTjsExtension();
|
|
145
|
-
return handle;
|
|
138
|
+
if (this._editorPromise === undefined) {
|
|
139
|
+
this._editorPromise = makeCodeEditor(this, this.mode, this.options, this.theme);
|
|
140
|
+
this._editorPromise.then(({ ace, editor }) => {
|
|
141
|
+
this._ace = ace;
|
|
142
|
+
this._editor = editor;
|
|
143
|
+
editor.setValue(this.source, 1);
|
|
144
|
+
editor.clearSelection();
|
|
145
|
+
editor.session.getUndoManager().reset();
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
-
isTjsMode() {
|
|
150
|
-
return this.mode === 'tjs' || this.mode === 'ajs';
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* When in tjs mode, lazily upgrade the editor to tjs-lang's CodeMirror language +
|
|
154
|
-
* autocomplete. No-op (keeps TS highlighting) if not tjs, if tjs-lang isn't
|
|
155
|
-
* installed, or if the mode/handle changed before the async load resolved.
|
|
156
|
-
*/
|
|
157
|
-
applyTjsExtension() {
|
|
158
|
-
const handle = this._handle;
|
|
159
|
-
if (!handle || !this.isTjsMode())
|
|
160
|
-
return;
|
|
161
|
-
import('./code-editor-cm')
|
|
162
|
-
.then(({ loadTjsExtension }) => loadTjsExtension())
|
|
163
|
-
.then((ext) => {
|
|
164
|
-
if (ext && this._handle === handle && this.isTjsMode()) {
|
|
165
|
-
handle.setLanguageExtension(ext);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
149
|
render() {
|
|
170
150
|
super.render();
|
|
171
|
-
if (this.
|
|
172
|
-
|
|
173
|
-
this._handle.setReadOnly(this.disabled);
|
|
174
|
-
this._appliedDisabled = this.disabled;
|
|
175
|
-
}
|
|
176
|
-
if (this.mode !== this._appliedMode) {
|
|
177
|
-
this._handle.setMode(this.mode);
|
|
178
|
-
this._appliedMode = this.mode;
|
|
179
|
-
this.applyTjsExtension();
|
|
180
|
-
}
|
|
151
|
+
if (this._editorPromise !== undefined) {
|
|
152
|
+
this._editorPromise.then(({ editor }) => editor.setReadOnly(this.disabled));
|
|
181
153
|
}
|
|
182
154
|
}
|
|
183
155
|
}
|
|
@@ -48,10 +48,10 @@ async function ensureHaltijaChannel(port) {
|
|
|
48
48
|
// HTTPS dev page has no mixed-content), while the `hj` CLI drives over its
|
|
49
49
|
// default HTTP 8700 — one server, both transports, shared state. HTTPS cert
|
|
50
50
|
// is mkcert-trusted. Output quiet so it doesn't drown the dev log.
|
|
51
|
-
// `@
|
|
52
|
-
// Electron app CI-only
|
|
53
|
-
//
|
|
54
|
-
spawn(['bunx', 'haltija@
|
|
51
|
+
// Track the `@beta` tag: in-browser (widget-mode) WebRTC screen capture —
|
|
52
|
+
// which makes the Electron app CI-only — landed in the beta channel, ahead
|
|
53
|
+
// of the `latest` dist-tag.
|
|
54
|
+
spawn(['bunx', 'haltija@beta', '--server', '--both'], {
|
|
55
55
|
stdout: 'ignore',
|
|
56
56
|
stderr: 'ignore',
|
|
57
57
|
});
|
|
@@ -223,6 +223,57 @@ export async function devServer(config, opts = {}) {
|
|
|
223
223
|
// never regenerated — leaving the page's /iife.js to 404 into the SPA
|
|
224
224
|
// fallback (it "loads as html"). Serialize builds and coalesce bursts.
|
|
225
225
|
const runBuild = opts.build ?? (() => buildSite(config));
|
|
226
|
+
// Memory watchdog.
|
|
227
|
+
//
|
|
228
|
+
// The build hands work to native code (the bundler, the HTML parser, the SVG
|
|
229
|
+
// rasterizer) which can strand memory the JS heap never sees — so nothing
|
|
230
|
+
// here can GC it away, and `heapUsed` stays flat while RSS climbs. A watch
|
|
231
|
+
// process lives for DAYS across thousands of rebuilds, so a per-rebuild leak
|
|
232
|
+
// compounds until the machine swaps itself to death. That is not
|
|
233
|
+
// hypothetical: a 2-day session reached 136GB RSS and took the machine down
|
|
234
|
+
// (Bun.build's native arena — see orchestrator.ts and oven-sh/bun#34053).
|
|
235
|
+
// Dying loudly beats being the reason a laptop overheats: the dev server is
|
|
236
|
+
// one keystroke to restart, the machine is not.
|
|
237
|
+
const rssMb = () => Math.round(process.memoryUsage().rss / 1e6);
|
|
238
|
+
const limitMb = Number(process.env.DEV_MEMORY_LIMIT_MB ?? config.memoryLimitMb ?? 4096);
|
|
239
|
+
let baselineMb = 0;
|
|
240
|
+
let rebuilds = 0;
|
|
241
|
+
let warned = false;
|
|
242
|
+
const checkMemory = () => {
|
|
243
|
+
const mb = rssMb();
|
|
244
|
+
rebuilds += 1;
|
|
245
|
+
if (!baselineMb)
|
|
246
|
+
baselineMb = mb;
|
|
247
|
+
const growth = mb - baselineMb;
|
|
248
|
+
const each = rebuilds > 1 ? growth / (rebuilds - 1) : 0;
|
|
249
|
+
if (mb >= limitMb) {
|
|
250
|
+
// Distinguish the two ways to get here, because the advice is opposite:
|
|
251
|
+
// memory that GREW across rebuilds is a leak (report it); a build that was
|
|
252
|
+
// simply born bigger than the ceiling just needs a bigger ceiling.
|
|
253
|
+
const leaking = rebuilds > 1 && each >= 1;
|
|
254
|
+
const diagnosis = leaking
|
|
255
|
+
? ` ${rebuilds} rebuilds, +${growth}MB since the first ` +
|
|
256
|
+
`(~${each.toFixed(1)}MB per rebuild).\n\n` +
|
|
257
|
+
` Growth per rebuild should be ~0, so this is a leak, not your project\n` +
|
|
258
|
+
` getting bigger. Restarting reclaims it — please report the numbers\n` +
|
|
259
|
+
` above. If this build genuinely needs the headroom, raise the ceiling\n` +
|
|
260
|
+
` with DEV_MEMORY_LIMIT_MB=<mb> or memoryLimitMb in your site config.\n`
|
|
261
|
+
: ` ${rebuilds} rebuild${rebuilds === 1 ? '' : 's'}, ` +
|
|
262
|
+
`+${growth}MB since the first — i.e. it is not growing.\n\n` +
|
|
263
|
+
` This build's baseline footprint is simply above the ceiling, which is\n` +
|
|
264
|
+
` not a leak. Raise it with DEV_MEMORY_LIMIT_MB=<mb> or memoryLimitMb in\n` +
|
|
265
|
+
` your site config.\n`;
|
|
266
|
+
console.error(`\n🛑 dev server stopping: ${mb}MB RSS, over the ${limitMb}MB limit.\n\n` +
|
|
267
|
+
diagnosis);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
if (!warned && mb > limitMb * 0.6) {
|
|
271
|
+
warned = true;
|
|
272
|
+
console.warn(`⚠️ dev server at ${mb}MB RSS after ${rebuilds} rebuilds (+${growth}MB, ` +
|
|
273
|
+
`~${each.toFixed(1)}MB each; limit ${limitMb}MB). Growth per rebuild ` +
|
|
274
|
+
`should be ~0 — if this keeps climbing, restart and report it.`);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
226
277
|
let building = false;
|
|
227
278
|
let pending = false;
|
|
228
279
|
const rebuild = async () => {
|
|
@@ -239,6 +290,7 @@ export async function devServer(config, opts = {}) {
|
|
|
239
290
|
}
|
|
240
291
|
finally {
|
|
241
292
|
building = false;
|
|
293
|
+
checkMemory();
|
|
242
294
|
if (pending) {
|
|
243
295
|
pending = false;
|
|
244
296
|
void rebuild();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Standalone ePub build — the subprocess entry point for buildEpub().
|
|
3
|
+
|
|
4
|
+
buildEpub() drives happy-dom (HTML→XHTML for 50+ chapters) and @resvg/resvg-js
|
|
5
|
+
(cover rasterization). Both hold native/retained memory that the JS heap can't
|
|
6
|
+
see, and the ePub is rebuilt on EVERY dev-server rebuild — so in-process it
|
|
7
|
+
strands memory in a watch process that lives for days. Running it in a child
|
|
8
|
+
hands all of it back to the OS on exit. Same reasoning as the bundle step; see
|
|
9
|
+
the Bun.build note in orchestrator.ts.
|
|
10
|
+
|
|
11
|
+
bun epub-cli.ts <payload.json> # payload = { config, opts }
|
|
12
|
+
|
|
13
|
+
Build-time only. Never import this from browser code.
|
|
14
|
+
*/
|
|
15
|
+
import { buildEpub } from './epub';
|
|
16
|
+
const payloadPath = process.argv[2];
|
|
17
|
+
if (!payloadPath) {
|
|
18
|
+
console.error('epub-cli: expected a payload file path');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const { config, opts } = JSON.parse(await Bun.file(payloadPath).text());
|
|
22
|
+
await buildEpub(config, opts ?? {});
|
|
@@ -535,6 +535,12 @@ export async function buildEpub(config, opts = {}) {
|
|
|
535
535
|
const output = path.resolve(opts.output ?? path.join(outDir, `${slugify(meta.title)}.epub`));
|
|
536
536
|
await zipEpub(buildDir, output);
|
|
537
537
|
fs.rmSync(buildDir, { recursive: true, force: true });
|
|
538
|
+
// Release the parser window: an unclosed happy-dom Window holds its whole
|
|
539
|
+
// document (every chapter parsed) plus its timers. Hygiene, not a proven win —
|
|
540
|
+
// buildEpub still grows ~1.5MB per call with this in place, so something here
|
|
541
|
+
// is still retained. buildEpub now runs in a child process (see orchestrator),
|
|
542
|
+
// which is what actually bounds it.
|
|
543
|
+
win?.close?.();
|
|
538
544
|
console.log(`epub: ${output} (${chapters.length} chapters)`);
|
|
539
545
|
return output;
|
|
540
546
|
}
|
|
@@ -102,21 +102,6 @@ export function generateLlmsTxt(outputPath, meta = {}, corpus) {
|
|
|
102
102
|
console.warn('llms.txt: no readable package.json — using config values only');
|
|
103
103
|
}
|
|
104
104
|
const entries = corpus ? entriesFromCorpus(corpus, meta) : entriesFromSrcScan();
|
|
105
|
-
// Only orient agents about the live-example execution model when the corpus
|
|
106
|
-
// actually has live examples (a book / pure-docs site has none).
|
|
107
|
-
const exampleFence = /```(js|tjs|ts|html|css)\b/;
|
|
108
|
-
const hasLiveExamples = !!corpus && corpus.some((d) => exampleFence.test(d.text ?? ''));
|
|
109
|
-
const liveExampleNote = hasLiveExamples
|
|
110
|
-
? [
|
|
111
|
-
'Live examples run inline on the shared page by default (not sandboxed):',
|
|
112
|
-
'each has its own `preview` element, but they share one tosijs module, so',
|
|
113
|
-
'`tosi()` state singletons are shared across every example on a page. This is',
|
|
114
|
-
'deliberate (it shows tosi isolation and lets you drive demos via the global',
|
|
115
|
-
'singleton). Namespace example state with unique top-level keys so examples',
|
|
116
|
-
"don't clobber each other; the `iframe` attribute isolates DOM/CSS, not state.",
|
|
117
|
-
'',
|
|
118
|
-
]
|
|
119
|
-
: [];
|
|
120
105
|
const name = meta.name ?? pkg.name ?? '';
|
|
121
106
|
const description = meta.description ?? pkg.description ?? '';
|
|
122
107
|
const links = [];
|
|
@@ -140,7 +125,6 @@ export function generateLlmsTxt(outputPath, meta = {}, corpus) {
|
|
|
140
125
|
'',
|
|
141
126
|
'Full documentation, with live code examples, is at the links below.',
|
|
142
127
|
'',
|
|
143
|
-
...liveExampleNote,
|
|
144
128
|
'## Pages',
|
|
145
129
|
'',
|
|
146
130
|
];
|