jsmdcui 0.11.1 → 0.12.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.
@@ -1,127 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import { evalBack } from "./README.md-rpc.js";
4
-
5
- const csl = console.log
6
- const mda = Bun.markdown.ansi
7
-
8
-
9
- import homepage from "./README.md.html"
10
- import * as backmod from "./README.md.back.js";
11
-
12
-
13
- /*
14
- // Wait until Bun fixes dynamic html import bundle
15
-
16
- process.env.PUBLIC_MDPATH ||= "README.md"
17
-
18
- const { default: homepage } =
19
- await import(
20
- "./" +
21
- process.env.PUBLIC_MDPATH +
22
- ".html"
23
- ) ;
24
-
25
- const backmod =
26
- await import(
27
- './' +
28
- process.env.PUBLIC_MDPATH +
29
- ".back.js"
30
- ) ;
31
-
32
- */
33
-
34
-
35
- function json(data, init = {}) {
36
- return Response.json(data, {
37
- headers: {
38
- "access-control-allow-origin": "*",
39
- ...init.headers,
40
- },
41
- ...init,
42
- });
43
- }
44
-
45
-
46
- export function main()
47
- {
48
-
49
-
50
-
51
- const basePath = "/" + crypto.randomUUID()
52
- const pathname = basePath + "/"
53
- const serverOptions = {
54
- port: Number(process.env.PORT || 3000),
55
- development: {
56
- hmr: true,
57
- console: true,
58
- },
59
- routes: {
60
- [basePath]: (req) => Response.redirect(new URL(pathname, req.url), 308),
61
- [pathname]: homepage,
62
-
63
- [pathname + "rpc"]: async (req) => {
64
- csl("\n"+mda("## "+req.method+" "+req.url))
65
-
66
- if (req.method === "OPTIONS") {
67
- return new Response(null, {
68
- headers: {
69
- "access-control-allow-origin": "*",
70
- "access-control-allow-methods": "POST,OPTIONS",
71
- "access-control-allow-headers": "content-type",
72
- },
73
- });
74
- }
75
-
76
- if (req.method !== "POST") {
77
- csl('illegal method')
78
- return new Response("Method Not Allowed", { status: 405 });
79
- }
80
-
81
- try {
82
- const packet = await req.json();
83
-
84
- csl('req json:',packet)
85
-
86
- const result = await evalBack(backmod, packet);
87
-
88
- return json(result ?? null);
89
- } catch (error) {
90
- return json(
91
- {
92
- ok: false,
93
- error: error?.stack || String(error),
94
- },
95
- { status: 400 },
96
- );
97
- }
98
- },
99
- },
100
-
101
- fetch() {
102
- return new Response("Not Found", { status: 404 });
103
- },
104
- };
105
-
106
- let server;
107
- try {
108
- server = Bun.serve(serverOptions);
109
- } catch (error) {
110
- const addressInUse = error?.code === "EADDRINUSE"
111
- || error?.cause?.code === "EADDRINUSE"
112
- || /EADDRINUSE|address already in use/i.test(String(error?.message || error));
113
- if (serverOptions.port !== 3000 || !addressInUse) throw error;
114
- server = Bun.serve({ ...serverOptions, port: 0 });
115
- }
116
-
117
- console.error(mda("- Bun RPC server listening on"));
118
- console.log(`http://localhost:${server.port}${pathname}`);
119
-
120
-
121
-
122
- return server
123
-
124
- } // main
125
-
126
- if(import.meta.main)
127
- main()
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env bun
2
-
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import { rpc as wuiRpcClient } from "./README.md-rpc.js";
4
- let rpc = null
5
- if(globalThis.process)
6
- {
7
- rpc = await import(
8
- "./" +
9
- (global.MDCUI_MAIN_BASE||"README.md") +
10
- ".back.js"
11
- ) ;
12
- }
13
- else
14
- rpc = wuiRpcClient ;
15
-
16
-
17
-
18
- if (typeof window !== "undefined")
19
- {
20
- // Browser
21
- setTimeout( () => {
22
- import("./README.md.front.js").then(mod=>{
23
- Object.assign(window,mod);
24
- })
25
- }, 0 ) ;
26
- }
27
-
@@ -1,325 +0,0 @@
1
- <!doctype html>
2
- <html lang="zh-TW">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>README.md</title>
7
- <style>
8
- img {
9
- max-width: 100%;
10
- height: auto;
11
- }
12
- </style>
13
- </head>
14
- <body>
15
- <section>
16
- <h1 id="bun-single-file-executable-bootstrap">Bun Single-File Executable Bootstrap</h1>
17
- <p>Build a Bun application and its runtime assets into one executable while
18
- keeping the regular main module free of Bun-specific asset imports.</p>
19
- <p>You can copy and adapt this build setup for your own project by changing its
20
- main-module path and asset list as described below.</p>
21
- <ul>
22
- <li>Bundle an asset tree alongside the application code in one executable.</li>
23
- <li>Keep the regular main module available to the Node.js ESM execution path.</li>
24
- <li>Read embedded resources first with an optional external-file fallback.</li>
25
- <li>List, extract, or bypass embedded assets with built-in CLI flags.</li>
26
- <li>Build for the current platform or pass a Bun cross-compilation target.</li>
27
- </ul>
28
- <blockquote>
29
- <p>This workflow is optional; the standard methods in the root README remain
30
- available. Bun's Android build is currently not supported.</p>
31
- </blockquote>
32
- <section>
33
- <h2 id="quick-start-for-jsmdcui">Quick start for jsmdcui</h2>
34
- <p>From the repository root, build for the current platform:</p>
35
- <pre><code class="language-shell">bun ./src/index.js --build-exe
36
- ./mdcui --version
37
- </code></pre>
38
- <p><code>--build-exe</code> runs <code>packAssets.sh</code> automatically and writes the <code>mdcui</code>
39
- executable to the current directory. To pass a Bun compilation target, use:</p>
40
- <pre><code class="language-shell">bun ./src/index.js --build-for &lt;target&gt;
41
- </code></pre>
42
- <p>Any additional arguments after <code>--build-exe</code>, or after the target passed to
43
- <code>--build-for</code>, are forwarded to the <code>bun build</code> command:</p>
44
- <pre><code class="language-shell">bun ./src/index.js --build-exe --sourcemap
45
- bun ./src/index.js --build-for &lt;target&gt; --sourcemap
46
- </code></pre>
47
- <p>Bun expects <code>--define</code> values to be JSON-style literals, so strings normally
48
- need quotes. <code>stringifyNonPrimitiveDefineValues()</code> converts a bare value with
49
- <code>JSON.stringify()</code> before it reaches Bun. jsmdcui uses it for
50
- <code>global.MDCUI_MAIN</code>.</p>
51
- <p>The switch defines below are presence-based: <code>=0</code> and <code>=false</code> still enable
52
- them. Omit a switch define to disable it; examples consistently use <code>=1</code>.</p>
53
- <ul>
54
- <li><code>MDCUI_DEFAULT_EDIT=1</code>: editable-text default.</li>
55
- <li><code>MDCUI_DEFAULT_DEMO=1</code>: no-argument <code>testapp.md</code> TUI.</li>
56
- <li><code>MDCUI_DEFAULT_DEMO_WUI=1</code>: no-argument <code>testapp.md</code> WUI.</li>
57
- <li><code>MDCUI_OVERWRITE_DEMO=1</code>: overwrite modifier; it does not select a demo.</li>
58
- <li><code>global.MDCUI_MAIN=&lt;path&gt;.md</code>: embed a custom Markdown app and its generated
59
- front, RPC, back, HTML, and server modules.</li>
60
- <li><code>global.MDCUI_MAIN_BASE</code>: generated internally from <code>MDCUI_MAIN</code>; do not pass
61
- it manually.</li>
62
- </ul>
63
- <p>Put defines after <code>--build-exe</code> (or after the <code>--build-for</code> target), and choose
64
- at most one <code>MDCUI_DEFAULT_*</code> mode.</p>
65
- <table>
66
- <thead>
67
- <tr><th>Build defines</th><th>No-argument launch</th><th>Switch UI</th></tr>
68
- </thead>
69
- <tbody>
70
- <tr><td><code>MDCUI_DEFAULT_EDIT=1</code></td><td>text editor</td><td><code>./mdcui --tui app.md</code></td></tr>
71
- <tr><td><code>MDCUI_DEFAULT_DEMO=1</code></td><td><code>testapp.md</code> TUI</td><td><code>./mdcui --wui --demo</code></td></tr>
72
- <tr><td><code>MDCUI_DEFAULT_DEMO_WUI=1</code></td><td><code>testapp.md</code> WUI</td><td><code>./mdcui --tui --demo</code></td></tr>
73
- <tr><td><code>global.MDCUI_MAIN=../中文工具.md</code></td><td>custom TUI</td><td><code>./mdcui --wui --demo-中文工具</code></td></tr>
74
- <tr><td>MAIN plus <code>MDCUI_DEFAULT_DEMO_WUI=1</code></td><td>custom WUI</td><td><code>./mdcui --tui --demo-中文工具</code></td></tr>
75
- </tbody>
76
- </table>
77
- <p>For a custom TUI-default executable:</p>
78
- <pre><code class="language-shell">bun ./src/index.js --build-exe \
79
- --define global.MDCUI_MAIN=../中文工具.md
80
- </code></pre>
81
- <p>For a custom WUI-default executable:</p>
82
- <pre><code class="language-shell">bun ./src/index.js --build-exe \
83
- --define global.MDCUI_MAIN=../中文工具.md \
84
- --define MDCUI_DEFAULT_DEMO_WUI=1
85
- </code></pre>
86
- <p>Explicit runtime arguments suppress automatic demo/WUI selection, so switching
87
- UI must repeat <code>--demo</code> or <code>--demo-中文工具</code>. The main basename must end in
88
- lowercase <code>.md</code>; its demo name allows Unicode letters/numbers (including
89
- Chinese), dots, underscores, and hyphens, but no whitespace.</p>
90
- <p>The custom demo uses embedded TUI front/RPC or the embedded WUI server when its
91
- local Markdown byte length matches the embedded asset. Missing or overwritten
92
- demos use embedded modules after the asset is written. A differing byte length
93
- warns and selects filesystem companion modules.</p>
94
- <p>To perform the same steps manually, run these commands from <code>single-exe/</code>:</p>
95
- <pre><code class="language-shell">bun ./packAssets.sh
96
- bun build --format=esm --compile --minify --bytecode ./entry.mjs --outfile=mdcui
97
- </code></pre>
98
- </section>
99
- <section>
100
- <h2 id="adapting-this-folder-to-another-project">Adapting this folder to another project</h2>
101
- <p>This folder is a reusable starting point, not a drop-in package. The current
102
- <code>entry.mjs</code> and <code>packAssets.sh</code> contain jsmdcui paths, so another project must
103
- complete the following integration steps.</p>
104
- <p>The adopting project needs Bun for packing and compiling, a <code>tar</code> command for
105
- <code>packAssets.sh</code>, and an ES-module setup. Keep <code>&quot;type&quot;: &quot;module&quot;</code> in
106
- <code>package.json</code>, or rename the copied <code>.js</code> modules to <code>.mjs</code> and update their
107
- imports. The supported uncompiled Node path requires Node.js 20.11 or newer
108
- because <code>compiled.js</code> uses <code>import.meta.dirname</code>.</p>
109
- <p><code>assetsHelper.js</code> and <code>compiled.js</code> are Node-compatible under those conditions.
110
- <code>assetsLoader.mjs</code> and <code>entry.mjs</code> are Bun-only and must not be imported by the
111
- regular Node entry path.</p>
112
- <section>
113
- <h3 id="1-copy-the-bootstrap-directory">1. Copy the bootstrap directory</h3>
114
- <p>Copy the entire <code>single-exe/</code> directory to the root of the other project and
115
- keep its name and relative position unless you also update the paths in
116
- <code>compiled.js</code> and <code>packAssets.sh</code>. A typical layout is:</p>
117
- <textarea data-mdcui-tag="text" data-mdcui-language="text" class="language-text" rows="9" cols="24" wrap="soft" style="box-sizing:border-box;max-width:100%;width:100%;resize:vertical;overflow-y:hidden">my-project/
118
- ├── single-exe/
119
- │ ├── assetsHelper.js
120
- │ ├── assetsLoader.mjs
121
- │ ├── compiled.js
122
- │ ├── entry.mjs
123
- │ └── packAssets.sh
124
- └── src/
125
- └── index.js</textarea>
126
- <p><code>assets.tar</code> is generated by <code>packAssets.sh</code>; it does not need to exist before
127
- the first packing step.</p>
128
- </section>
129
- <section>
130
- <h3 id="2-point-entrymjs-at-the-main-program">2. Point <code>entry.mjs</code> at the main program</h3>
131
- <p>Keep the asset loader as the first import and change the path passed to the
132
- final <code>await import()</code> so it points to the other project's real entry module:</p>
133
- <pre><code class="language-js">#!/usr/bin/env bun
134
-
135
- import &quot;./assetsLoader.mjs&quot;;
136
- await globalThis.assetsLoaderPromise;
137
- await import(&quot;../src/index.js&quot;);
138
- </code></pre>
139
- <blockquote>
140
- <p><strong>Important:</strong> Keep the final import dynamic. Do not replace it with a static
141
- <code>import &quot;../src/index.js&quot;</code>, because static dependencies are evaluated before
142
- the asset Promise is awaited.</p>
143
- </blockquote>
144
- <p>Only the Bun single-file executable build should use this bootstrap entry.
145
- Continue to run the regular main module directly when using Node:</p>
146
- <pre><code class="language-shell">node ./src/index.js
147
- </code></pre>
148
- </section>
149
- <section>
150
- <h3 id="3-choose-which-files-to-embed">3. Choose which files to embed</h3>
151
- <p>Edit the resource list in <code>packAssets.sh</code>; the script runs <code>tar</code> for you. It
152
- changes to the project root, writes <code>single-exe/assets.tar</code>, and packs every
153
- listed runtime resource required by the compiled program. Paths stored in the
154
- archive become the lookup keys used by <code>assetsHelper.js</code>; use keys such as
155
- <code>public/app.css</code>, without a leading <code>./</code>. The normal <code>--build-exe</code> and
156
- <code>--build-for</code> flows invoke this script automatically, so you do not need to run
157
- <code>tar</code> yourself.</p>
158
- <p>For example:</p>
159
- <pre><code class="language-sh">#!/bin/sh
160
-
161
- script_dir=$(dirname &quot;$0&quot;)
162
- cd &quot;$script_dir/..&quot; || exit 1
163
-
164
- tar -cvf single-exe/assets.tar public templates README.md
165
- </code></pre>
166
- <p>Do not retain jsmdcui's <code>demos</code>, <code>runtime</code>, <code>src/cui</code>, or <code>testapp.md</code> entries
167
- unless the adopting project actually contains and needs them.</p>
168
- </section>
169
- <section>
170
- <h3 id="4-read-embedded-assets-with-an-external-fallback">4. Read embedded assets with an external fallback</h3>
171
- <p>Import the Node-compatible helpers from <code>assetsHelper.js</code>. They return <code>null</code>
172
- when the embedded store is unavailable or a path is not present:</p>
173
- <pre><code class="language-js">import { readFile } from &quot;node:fs/promises&quot;;
174
- import { resolve } from &quot;node:path&quot;;
175
- import { readInternalAssetText } from &quot;../single-exe/assetsHelper.js&quot;;
176
- import { REPO_ROOT } from &quot;../single-exe/compiled.js&quot;;
177
-
178
- async function readResourceText(path) {
179
- return readInternalAssetText(path) ??
180
- await readFile(resolve(REPO_ROOT, path), &quot;utf8&quot;);
181
- }
182
- </code></pre>
183
- <p>In the source tree, <code>REPO_ROOT</code> is the project root. In a compiled executable,
184
- it is the executable's directory, which is also where <code>--assets-extract</code>
185
- places the external resource tree. Apply the same embedded-first fallback to
186
- every file that must work in both modes. Use <code>readInternalAssetBytes()</code> when
187
- a resource should be returned as bytes instead of decoded text.
188
- Use <code>listInternalAssetPaths()</code> to list embedded asset paths and
189
- <code>listInternalAssetDirs()</code> to list embedded directories.</p>
190
- </section>
191
- <section>
192
- <h3 id="5-add-the-optional-build-commands-to-the-regular-cli">5. Add the optional build commands to the regular CLI</h3>
193
- <p>Before normal argument parsing, call <code>buildEarlyExit</code> from the regular main
194
- module:</p>
195
- <pre><code class="language-js">const compiledHelper = await import(&quot;../single-exe/compiled.js&quot;).catch(() =&gt; null);
196
- await compiledHelper?.buildEarlyExit?.(process.argv, &quot;my-bin&quot;);
197
- </code></pre>
198
- <p>The second argument is the output filename; it defaults to <code>single.exe</code> when
199
- omitted. This enables:</p>
200
- <pre><code class="language-shell">bun ./src/index.js --build-exe
201
- bun ./src/index.js --build-for &lt;target&gt;
202
- </code></pre>
203
- <p>Both commands run <code>packAssets.sh</code> before <code>bun build</code>. The second form passes
204
- <code>&lt;target&gt;</code> through as Bun's <code>--target=&lt;target&gt;</code> value.</p>
205
- <p><code>buildEarlyExit()</code> forwards build arguments to Bun, whose define parser expects
206
- strings to be quoted literals. To accept a bare value such as a path, call the
207
- helper before <code>buildEarlyExit()</code>:</p>
208
- <pre><code class="language-js">const compiledHelper = await import(&quot;../single-exe/compiled.js&quot;).catch(() =&gt; null);
209
- compiledHelper?.stringifyNonPrimitiveDefineValues?.(
210
- process.argv,
211
- &quot;MY_STRING_DEFINE&quot;,
212
- );
213
- await compiledHelper?.buildEarlyExit?.(process.argv, &quot;my-bin&quot;);
214
- </code></pre>
215
- <pre><code class="language-shell"># Wrong: Bun parses the define before the helper runs.
216
- bun --define MY_STRING_DEFINE=../app.md ./src/index.js --build-exe
217
-
218
- # Correct
219
- bun ./src/index.js --build-exe --define MY_STRING_DEFINE=../app.md
220
- </code></pre>
221
- <p>Before normalization:</p>
222
- <pre><code class="language-js">[&quot;--define&quot;, &quot;MY_STRING_DEFINE=../app.md&quot;]
223
- </code></pre>
224
- <p>After normalization:</p>
225
- <pre><code class="language-js">[&quot;--define&quot;, 'MY_STRING_DEFINE=&quot;../app.md&quot;']
226
- </code></pre>
227
- <p>The inline form works too:
228
- <code>--define=MY_STRING_DEFINE=../app.md</code> becomes
229
- <code>--define=MY_STRING_DEFINE=&quot;../app.md&quot;</code>.</p>
230
- <p>Alternatively, build manually from inside <code>single-exe/</code>:</p>
231
- <pre><code class="language-shell">bun ./packAssets.sh
232
- bun build --format=esm --compile --minify --bytecode ./entry.mjs --outfile=my-bin
233
- </code></pre>
234
- </section>
235
- <section>
236
- <h3 id="6-verify-both-execution-paths">6. Verify both execution paths</h3>
237
- <p>Verify the compiled asset archive and then run the program normally:</p>
238
- <pre><code class="language-shell">./my-bin --assets-list
239
- ./my-bin
240
- </code></pre>
241
- <p>Also run the uncompiled main module with Node.js 20.11 or newer to verify that
242
- all required files have a working external fallback:</p>
243
- <pre><code class="language-shell">node ./src/index.js
244
- </code></pre>
245
- <p>Use <code>./my-bin --assets-extract</code> to write the packed tree beside the executable.
246
- After extraction, <code>./my-bin --assets-external</code> skips the embedded store and is
247
- useful for testing the external-resource path.</p>
248
- </section>
249
- <section>
250
- <h3 id="why-assetsloaderpromise-is-used">Why <code>assetsLoaderPromise</code> is used</h3>
251
- <p>A more tightly coupled ESM design could export the assets with top-level
252
- <code>await</code>, then make the main module import <code>assetsLoader.mjs</code> directly. This
253
- project deliberately does not do that because <code>assetsLoader.mjs</code> contains
254
- Bun's compiled-file import:</p>
255
- <pre><code class="language-js">import assets from &quot;./assets.tar&quot; with { type: &quot;file&quot; };
256
- </code></pre>
257
- <p>If the regular main module imported <code>assetsLoader.mjs</code>, that Bun-specific
258
- <code>type: &quot;file&quot;</code> dependency would enter the main module graph. Node would then be
259
- unable to load the program before it could select an external-file fallback.</p>
260
- <p>Instead, only Bun's <code>entry.mjs</code> imports the Bun-specific loader, awaits its
261
- Promise, and then dynamically imports the regular main module. The main module
262
- does not import the loader, inspect the Promise, or contain Bun bootstrap code.
263
- Under the supported Node.js 20.11+ ESM path, Node runs that clean main module
264
- directly with external files. The Promise is therefore an intentional
265
- Node-compatibility boundary inside the Bun-only bootstrap, not a failure to use
266
- a more modern dependency or top-level-<code>await</code> design.</p>
267
- </section>
268
- </section>
269
- <section>
270
- <h2 id="entry-flow">Entry Flow</h2>
271
- <ul>
272
- <li><code>entry.mjs</code> imports <code>assetsLoader.mjs</code> first</li>
273
- <li><code>assetsLoader.mjs</code> loads <code>assets.tar</code> with <code>Bun.Archive</code> and mounts it as <code>globalThis.internalAssets</code></li>
274
- <li><code>assetsLoaderPromise</code> is exposed on <code>globalThis</code></li>
275
- <li><code>entry.mjs</code> awaits <code>assetsLoaderPromise</code></li>
276
- <li><code>entry.mjs</code> dynamically imports the main program after the assets are ready</li>
277
- </ul>
278
- <p>That keeps the main program bootable even if asset loading reports errors.</p>
279
- </section>
280
- <section>
281
- <h2 id="assets-loading">Assets Loading</h2>
282
- <ul>
283
- <li>Bundled assets are loaded sequentially with <code>await file.bytes()</code></li>
284
- <li>Load failures are collected and printed to <code>stderr</code></li>
285
- <li>Asset loading never rejects the bootstrap promise</li>
286
- <li>When loading succeeds, the archive is available through <code>globalThis.internalAssets</code></li>
287
- </ul>
288
- <p>This loader is not zero-copy: it materializes every bundled file in memory and
289
- keeps the resulting bytes in <code>globalThis.internalAssets</code>. Large asset archives
290
- can therefore increase startup and ongoing RAM usage. For workloads where that
291
- matters, I also wrote an experimental Linux-only zero-copy hack:
292
- <a href="https://github.com/jjtseng93/bun-assets-zerocopy">bun-assets-zerocopy</a>.</p>
293
- </section>
294
- <section>
295
- <h2 id="cli-flags">CLI Flags</h2>
296
- <ul>
297
- <li>
298
- <p><code>--assets-list</code></p>
299
- <ul>
300
- <li>Lists all entries inside bundled <code>assets.tar</code></li>
301
- <li>Exits early before the main program starts</li>
302
- </ul>
303
- </li>
304
- <li>
305
- <p><code>--assets-extract</code></p>
306
- <ul>
307
- <li>Extracts bundled assets to the same directory as the executable</li>
308
- <li>Exits early before the main program starts</li>
309
- </ul>
310
- </li>
311
- <li>
312
- <p><code>--assets-external</code></p>
313
- <ul>
314
- <li>Skips loading bundled assets into <code>globalThis.internalAssets</code></li>
315
- <li>Forces the main program and runtime helpers to use the external file tree</li>
316
- <li>Keeps the bootstrap alive while leaving <code>internalAssets</code> falsy</li>
317
- </ul>
318
- </li>
319
- </ul>
320
- </section>
321
- </section>
322
-
323
- <script type="module" src="./README.md.front.js"></script>
324
- </body>
325
- </html>
@@ -1,90 +0,0 @@
1
- const textDecoder = new TextDecoder();
2
- const textEncoder = new TextEncoder();
3
-
4
- export function hasInternalAssets() {
5
- return Boolean(getAssetStore());
6
- }
7
-
8
- export function assetPath(...parts) {
9
- return parts
10
- .flatMap((part) => String(part).split(/[\\/]+/))
11
- .filter(Boolean)
12
- .join("/");
13
- }
14
-
15
- export function listInternalAssetPaths(prefix = "") {
16
- const store = getAssetStore();
17
- if (!store) return [];
18
-
19
- const normalizedPrefix = assetPath(prefix);
20
- const entries = iterateAssetKeys(store);
21
- if (!normalizedPrefix) {
22
- return entries.sort();
23
- }
24
-
25
- const base = `${normalizedPrefix}/`;
26
- return entries.filter((path) => path === normalizedPrefix || path.startsWith(base)).sort();
27
- }
28
-
29
- export function listInternalAssetDirs(prefix = "") {
30
- const normalizedPrefix = assetPath(prefix);
31
- const base = normalizedPrefix ? `${normalizedPrefix}/` : "";
32
- const dirs = new Set();
33
-
34
- for (const path of listInternalAssetPaths(prefix)) {
35
- const rest = normalizedPrefix ? path.slice(base.length) : path;
36
- const [dir] = rest.split("/");
37
- if (dir) dirs.add(dir);
38
- }
39
-
40
- return [...dirs].sort();
41
- }
42
-
43
- export function getInternalAsset(path) {
44
- const store = getAssetStore();
45
- if (!store) return null;
46
- const key = assetPath(path);
47
- if (store instanceof Map) return store.get(key) ?? null;
48
- return store[key] ?? store[path] ?? null;
49
- }
50
-
51
- export function readInternalAssetBytes(path) {
52
- const value = getInternalAsset(path);
53
- if (value == null) return null;
54
- if (value instanceof Uint8Array) return value;
55
- if (ArrayBuffer.isView(value)) {
56
- return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
57
- }
58
- if (value instanceof ArrayBuffer) return new Uint8Array(value);
59
- if (typeof value === "string") return textEncoder.encode(value);
60
- return textEncoder.encode(String(value));
61
- }
62
-
63
- export function readInternalAssetText(path) {
64
- const bytes = readInternalAssetBytes(path);
65
- if (!bytes) return null;
66
- return textDecoder.decode(bytes);
67
- }
68
-
69
- export function internalAssetSource(path) {
70
- return {
71
- name: path.split("/").pop() ?? path,
72
- path,
73
- async text() {
74
- return readInternalAssetText(path) ?? "";
75
- },
76
- };
77
- }
78
-
79
- function getAssetStore() {
80
- const store = globalThis.internalAssets;
81
- if (!store) return null;
82
- if (store instanceof Map) return store;
83
- if (typeof store === "object") return store;
84
- return null;
85
- }
86
-
87
- function iterateAssetKeys(store) {
88
- if (store instanceof Map) return [...store.keys()].map(String);
89
- return Object.keys(store);
90
- }