arcane-os 0.5.12 → 0.5.13
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 +11 -0
- package/docs/reference/asset-versioning.md +69 -0
- package/package.json +1 -1
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chart.html +2 -7
- package/runtime/arcane/components/dashboard-config.html +3 -8
- package/runtime/arcane/components/file-manager.html +6 -6
- package/runtime/arcane/components/markdown-document.html +1 -6
- package/runtime/arcane/components/markdown-editor.html +2 -7
- package/runtime/arcane/components/media-embed.html +1 -1
- package/runtime/arcane/components/screen-capture.html +1 -1
- package/runtime/arcane/components/voice-transcription.html +4 -9
- package/runtime/arcane/modules/HTMLImport.js +184 -13
- package/src/dev-server.mjs +72 -5
- package/src/import-map.mjs +548 -16
- package/src/installed-sdk-runtime.mjs +1 -0
- package/src/packager/core.mjs +60 -1
- package/src/workspace-runtime.mjs +19 -7
package/src/dev-server.mjs
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import {constants as FS_CONSTANTS} from 'node:fs';
|
|
2
|
-
import {lstat,open,realpath} from 'node:fs/promises';
|
|
2
|
+
import {lstat,open,readFile,realpath} from 'node:fs/promises';
|
|
3
3
|
import http from 'node:http';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {resolveWorkspace} from './workspace.mjs';
|
|
6
6
|
import {createEventQueue} from './event-queue.mjs';
|
|
7
|
+
import {
|
|
8
|
+
inspectImportMapHtml,MANAGED_IMPORT_MAP_ATTRIBUTE,
|
|
9
|
+
readWorkspaceAssetVersion,rewriteAssetReferences
|
|
10
|
+
} from './import-map.mjs';
|
|
7
11
|
|
|
8
12
|
const MIME_TYPES=new Map([
|
|
9
13
|
['.css','text/css; charset=utf-8'],
|
|
10
14
|
['.gif','image/gif'],
|
|
11
15
|
['.html','text/html; charset=utf-8'],
|
|
16
|
+
['.htm','text/html; charset=utf-8'],
|
|
12
17
|
['.ico','image/x-icon'],
|
|
13
18
|
['.jpeg','image/jpeg'],
|
|
14
19
|
['.jpg','image/jpeg'],
|
|
@@ -308,11 +313,21 @@ async function openSafeFile(root,segments){
|
|
|
308
313
|
}
|
|
309
314
|
}
|
|
310
315
|
|
|
311
|
-
async function sendFile(response,opened,{head=false}={}){
|
|
316
|
+
async function sendFile(response,opened,{head=false,assetVersion,revalidate=false,onReference}={}){
|
|
312
317
|
const extension=path.extname(opened.candidate).toLowerCase();
|
|
318
|
+
const content=assetVersion&&/\.(?:m?js|html?|css)$/iu.test(extension)
|
|
319
|
+
?Buffer.from(rewriteAssetReferences(opened.content.toString('utf8'),{
|
|
320
|
+
filePath:opened.candidate,
|
|
321
|
+
version:assetVersion,
|
|
322
|
+
onReference
|
|
323
|
+
}),'utf8')
|
|
324
|
+
:opened.content;
|
|
325
|
+
// Entry documents must discover the current generated graph on navigation.
|
|
326
|
+
// no-cache permits storage and revalidation; ordinary asset caching is unchanged.
|
|
313
327
|
response.writeHead(200,{
|
|
314
328
|
'content-type':MIME_TYPES.get(extension)||'application/octet-stream',
|
|
315
|
-
'content-length':
|
|
329
|
+
'content-length':content.byteLength,
|
|
330
|
+
...(revalidate?{'cache-control':'no-cache'}:{})
|
|
316
331
|
});
|
|
317
332
|
await new Promise((resolve,reject)=>{
|
|
318
333
|
let settled=false;
|
|
@@ -336,7 +351,7 @@ async function sendFile(response,opened,{head=false}={}){
|
|
|
336
351
|
response.once('error',failed);
|
|
337
352
|
response.once('finish',completed);
|
|
338
353
|
response.once('close',completed);
|
|
339
|
-
response.end(head?undefined:
|
|
354
|
+
response.end(head?undefined:content);
|
|
340
355
|
});
|
|
341
356
|
}
|
|
342
357
|
|
|
@@ -509,11 +524,23 @@ async function startOwnedDevServer({
|
|
|
509
524
|
})
|
|
510
525
|
:await packagedRoutes(releaseRoot);
|
|
511
526
|
const mappings=deterministicMappings(routeSet.mappings);
|
|
527
|
+
async function selectedAssetVersion(){
|
|
528
|
+
if(mode!=='source')return undefined;
|
|
529
|
+
return sdkRuntimeSourceRoot===undefined
|
|
530
|
+
?readWorkspaceAssetVersion(routeSet.workspaceRoot)
|
|
531
|
+
:JSON.parse(await readFile(
|
|
532
|
+
path.join(routeSet.runtime.sourceRoot,'package.json'),'utf8'
|
|
533
|
+
)).version;
|
|
534
|
+
}
|
|
535
|
+
let assetVersion=await selectedAssetVersion();
|
|
512
536
|
for(const mapping of mappings){
|
|
513
537
|
const info=await lstat(mapping.root);
|
|
514
538
|
if(info.isSymbolicLink()||!info.isDirectory())fail(`Server route root must be a real directory: ${mapping.root}.`);
|
|
515
539
|
mapping.root=await realpath(mapping.root);
|
|
516
540
|
}
|
|
541
|
+
// Remember actual resource edges as their owners are served, not every
|
|
542
|
+
// HTML/JS/CSS file in an application's document or attachment corpus.
|
|
543
|
+
const resourcePaths=new Set([routeSet.startPath]);
|
|
517
544
|
const requestTasks=new Set();
|
|
518
545
|
const runFileWork=createFileWorkLimiter();
|
|
519
546
|
const server=http.createServer((request,response)=>{
|
|
@@ -541,7 +568,47 @@ async function startOwnedDevServer({
|
|
|
541
568
|
await runFileWork(async()=>{
|
|
542
569
|
const opened=await openSafeFile(mapping.root,relative);
|
|
543
570
|
if(!opened){deny(response,404,'Not found.');return;}
|
|
544
|
-
|
|
571
|
+
const extension=path.extname(opened.candidate).toLowerCase();
|
|
572
|
+
const html=extension==='.html'||extension==='.htm';
|
|
573
|
+
let managedDocument=false;
|
|
574
|
+
if(html&&opened.content.includes(MANAGED_IMPORT_MAP_ATTRIBUTE)){
|
|
575
|
+
try{
|
|
576
|
+
managedDocument=inspectImportMapHtml(
|
|
577
|
+
opened.content.toString('utf8')
|
|
578
|
+
).managedMaps.length>0;
|
|
579
|
+
}catch{
|
|
580
|
+
// An unrelated document is not a source validation surface.
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const entryDocument=target.path===routeSet.startPath||managedDocument;
|
|
584
|
+
const managedMap=path.basename(opened.candidate)==='arcane.importmap.json';
|
|
585
|
+
// A live server can span an SDK upgrade. Refresh the small
|
|
586
|
+
// version record on navigation, not on each resource request.
|
|
587
|
+
if(entryDocument||managedMap)assetVersion=await selectedAssetVersion();
|
|
588
|
+
const runtimeResource=segments[0]==='arcane';
|
|
589
|
+
const browserResource=['script','style','worker','sharedworker','serviceworker']
|
|
590
|
+
.includes(request.headers['sec-fetch-dest']);
|
|
591
|
+
const rewrite=runtimeResource||entryDocument||browserResource
|
|
592
|
+
||resourcePaths.has(target.path);
|
|
593
|
+
const onReference=({url,kind,baseHref,baseKind})=>{
|
|
594
|
+
if(baseKind==='document'||kind==='fetch'||(kind==='asset'&&!/\.css(?:[?#]|$)/iu.test(url))
|
|
595
|
+
||(kind==='import'&&!/^(?:\.{1,2}\/|\/)/u.test(url)))return;
|
|
596
|
+
try{
|
|
597
|
+
const documentUrl=new URL(target.path,'http://arcane.invalid');
|
|
598
|
+
const base=baseHref?new URL(baseHref,documentUrl):documentUrl;
|
|
599
|
+
const resource=new URL(url,base);
|
|
600
|
+
if(resource.origin===documentUrl.origin
|
|
601
|
+
&&/\.(?:m?js|html?|css)$/iu.test(resource.pathname)){
|
|
602
|
+
resourcePaths.add(decodeURIComponent(resource.pathname));
|
|
603
|
+
}
|
|
604
|
+
}catch{ /* Non-URL values remain under their existing owner. */ }
|
|
605
|
+
};
|
|
606
|
+
await sendFile(response,opened,{
|
|
607
|
+
head:request.method==='HEAD',
|
|
608
|
+
assetVersion:rewrite?assetVersion:undefined,
|
|
609
|
+
revalidate:entryDocument||managedMap,
|
|
610
|
+
onReference
|
|
611
|
+
});
|
|
545
612
|
});
|
|
546
613
|
})().catch(async error=>{
|
|
547
614
|
await events.enqueue({type:'server.request.failed',message:error.message});
|