arcane-os 0.25.0 → 0.27.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 +31 -0
- package/README.md +16 -2
- package/browser-runtime/pwa-install.mjs +3 -3
- package/docs/architecture.md +33 -12
- package/docs/reference/cli.md +39 -11
- package/docs/reference/protocols.md +94 -4
- package/docs/reference/pwa.md +21 -0
- package/docs/reference/sdk-api.md +53 -13
- package/package.json +17 -2
- package/runtime/arcane/components/dashboard-config.html +2 -1
- package/runtime/arcane/components/data-view.html +2 -1
- package/runtime/arcane/components/file-manager.html +7 -6
- package/runtime/arcane/modules/MailApi.mjs +19 -0
- package/src/app-descriptor.mjs +3 -1
- package/src/app-layout.mjs +32 -0
- package/src/application-tests.mjs +3 -1
- package/src/cli/main.mjs +9 -12
- package/src/dev-server.mjs +86 -24
- package/src/import-map.mjs +57 -14
- package/src/mail-api.mjs +2 -19
- package/src/packager/core.mjs +84 -29
- package/src/pwa-worker.mjs +18 -4
- package/src/pwa.mjs +2 -1
- package/src/scaffold.mjs +43 -7
- package/src/sdk-runtime-layout.mjs +112 -0
- package/src/templates/workspace-template.mjs +44 -36
- package/src/toolchain.mjs +75 -3
- package/src/workspace.mjs +54 -38
|
@@ -352,6 +352,7 @@
|
|
|
352
352
|
//importing Document at the end of the file to avoid blocking;
|
|
353
353
|
|
|
354
354
|
const host=this;
|
|
355
|
+
const componentHref=new URL(host.getAttribute('href'),document.baseURI);
|
|
355
356
|
const {default:waitForComponent}=await import('../modules/WaitForComponent.js');
|
|
356
357
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
357
358
|
const events=createArcaneEventSource(host,{source:'arcane.component.file-manager',eventTypes:['file-manager-action','file-manager-select','file-manager-open','file-manager-ready']});
|
|
@@ -830,8 +831,8 @@
|
|
|
830
831
|
icon.className='tree-entry-icon';
|
|
831
832
|
icon.alt='';
|
|
832
833
|
icon.src=entry.kind==='directory'
|
|
833
|
-
?new URL('
|
|
834
|
-
:new URL('
|
|
834
|
+
?new URL('../img/folder.svg',componentHref).href
|
|
835
|
+
:new URL('../img/doc.svg',componentHref).href;
|
|
835
836
|
name.className='tree-entry-name';
|
|
836
837
|
name.innerText=entry.name;
|
|
837
838
|
count.className='tree-file-count';
|
|
@@ -1409,7 +1410,7 @@
|
|
|
1409
1410
|
deleteButton.innerText='Delete File';
|
|
1410
1411
|
deleteIcon.alt='';
|
|
1411
1412
|
deleteIcon.className='action-icon';
|
|
1412
|
-
deleteIcon.src=new URL('
|
|
1413
|
+
deleteIcon.src=new URL('../img/trash.svg',componentHref).href;
|
|
1413
1414
|
deleteButton.prepend(deleteIcon);
|
|
1414
1415
|
deleteButton.addEventListener(
|
|
1415
1416
|
'click',
|
|
@@ -1615,7 +1616,7 @@
|
|
|
1615
1616
|
|
|
1616
1617
|
fileIcon.alt='';
|
|
1617
1618
|
fileIcon.className='icon file-icon';
|
|
1618
|
-
fileIcon.src=new URL('
|
|
1619
|
+
fileIcon.src=new URL('../img/doc.svg',componentHref).href;
|
|
1619
1620
|
fileDetails.className='file-details';
|
|
1620
1621
|
fileLabel.className='file-name';
|
|
1621
1622
|
fileLabel.innerText=fileName;
|
|
@@ -1638,7 +1639,7 @@
|
|
|
1638
1639
|
|
|
1639
1640
|
deleteIcon.alt='';
|
|
1640
1641
|
deleteIcon.className='action-icon';
|
|
1641
|
-
deleteIcon.src=new URL('
|
|
1642
|
+
deleteIcon.src=new URL('../img/trash.svg',componentHref).href;
|
|
1642
1643
|
|
|
1643
1644
|
openButton.addEventListener(
|
|
1644
1645
|
'click',
|
|
@@ -1938,7 +1939,7 @@
|
|
|
1938
1939
|
directory.dataset.directory=tableName;
|
|
1939
1940
|
folder.className='file folder';
|
|
1940
1941
|
folderIcon.className='icon';
|
|
1941
|
-
folderIcon.src=new URL('
|
|
1942
|
+
folderIcon.src=new URL('../img/folder.svg',componentHref).href;
|
|
1942
1943
|
folderName.innerText=tableName;
|
|
1943
1944
|
fileCount.className='file-count';
|
|
1944
1945
|
fileCount.innerText=`${files.length} ${files.length===1?'file':'files'}`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
default,
|
|
3
|
+
default as Mail,
|
|
4
|
+
resolveMailConfig
|
|
5
|
+
} from './Mail.js';
|
|
6
|
+
export {
|
|
7
|
+
MAIL_OUTBOX_IDEMPOTENCY_WINDOW_MS,
|
|
8
|
+
MAIL_OUTBOX_PROTOCOL,
|
|
9
|
+
MAIL_OUTBOX_STATES,
|
|
10
|
+
MAIL_OUTBOX_TABLE,
|
|
11
|
+
MailOutbox,
|
|
12
|
+
createMailOutbox
|
|
13
|
+
} from './MailOutbox.mjs';
|
|
14
|
+
export {
|
|
15
|
+
MailTransportError,
|
|
16
|
+
normalizeMailEndpoint,
|
|
17
|
+
sendMailReport,
|
|
18
|
+
serializeMailReport
|
|
19
|
+
} from './MailTransport.mjs';
|
package/src/app-descriptor.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Is from 'strong-type';
|
|
2
|
+
import {resolveAppRoot} from './app-layout.mjs';
|
|
2
3
|
import {isDeepStrictEqual} from 'node:util';
|
|
3
4
|
import {readFile, writeFile} from 'node:fs/promises';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -427,7 +428,8 @@ async function readJsonFile(filePath,label,{optional=false}={}){
|
|
|
427
428
|
|
|
428
429
|
export async function refreshAppPackageProjection({workspaceRoot, appId, signal, onEvent}) {
|
|
429
430
|
throwIfAborted(signal);
|
|
430
|
-
const
|
|
431
|
+
const rootConfig=await readJsonFile(path.join(workspaceRoot,'arcane-packager.json'),'arcane-packager.json');
|
|
432
|
+
const appRoot = resolveAppRoot(workspaceRoot,rootConfig,appId);
|
|
431
433
|
const descriptorPath = path.join(appRoot, APP_DESCRIPTOR_NAME);
|
|
432
434
|
const authored = await readJsonFile(
|
|
433
435
|
descriptorPath,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
// The app identity is independent of whether its files occupy a workspace root.
|
|
4
|
+
export function appRelativeRoot(config, appId) {
|
|
5
|
+
return config.appsRoot === '.' ? '' : `apps/${appId}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function resolveAppRoot(workspaceRoot, config, appId) {
|
|
9
|
+
return path.resolve(workspaceRoot, appRelativeRoot(config, appId));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function appBaseHref(workspaceRoot, appRoot, document = 'index.html') {
|
|
13
|
+
const directory = path.dirname(path.resolve(appRoot, document));
|
|
14
|
+
const relative = path.relative(directory, workspaceRoot).split(path.sep).join('/');
|
|
15
|
+
return relative ? `${relative}/` : './';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function rootAppNavigation(appId, entry, documents = []) {
|
|
19
|
+
const redirects=new Map();
|
|
20
|
+
for(const document of documents)redirects.set(`apps/${appId}/${document}`,document);
|
|
21
|
+
redirects.set(`apps/${appId}/index.html`,entry);
|
|
22
|
+
return [...redirects].map(([file,target])=>{
|
|
23
|
+
const relative=path.posix.relative(path.posix.dirname(file),target);
|
|
24
|
+
const destination=JSON.stringify(relative).replaceAll('<','\\u003c');
|
|
25
|
+
return {
|
|
26
|
+
path:file,target:`/${target}`,
|
|
27
|
+
content:'<!doctype html>\n<!-- Arcane root application navigation -->\n'
|
|
28
|
+
+'<meta charset="utf-8">\n<title>Opening application</title>\n'
|
|
29
|
+
+`<script>const target=new URL(${destination},location.href);target.search=location.search;target.hash=location.hash;location.replace(target.href);</script>\n`
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -124,7 +124,9 @@ async function selectedTestFiles({workspaceRoot,workspaceMode,appRoot,signal}){
|
|
|
124
124
|
if(workspaceMode==='external'){
|
|
125
125
|
await collectOptionalTests(path.join(workspaceRoot,'test'),workspaceRoot,files,signal);
|
|
126
126
|
}
|
|
127
|
-
if(appRoot
|
|
127
|
+
if(appRoot&&(workspaceMode!=='external'||!samePath(workspaceRoot,appRoot))){
|
|
128
|
+
await collectOptionalTests(path.join(appRoot,'test'),appRoot,files,signal);
|
|
129
|
+
}
|
|
128
130
|
return [...files].sort();
|
|
129
131
|
}
|
|
130
132
|
|
package/src/cli/main.mjs
CHANGED
|
@@ -14,6 +14,7 @@ const VALUE_OPTIONS=new Set([
|
|
|
14
14
|
'display-name',
|
|
15
15
|
'workspace',
|
|
16
16
|
'app',
|
|
17
|
+
'apps-root',
|
|
17
18
|
'sdk-runtime-source',
|
|
18
19
|
'arcane-root',
|
|
19
20
|
'host',
|
|
@@ -60,8 +61,8 @@ const MAX_NODE_TIMER_DELAY_MS=2_147_483_647;
|
|
|
60
61
|
export const HELP_TEXT=`Arcane OS application SDK ${SDK_VERSION}
|
|
61
62
|
|
|
62
63
|
Usage:
|
|
63
|
-
${CLI_NAME} new <id> [--path <directory>] [--display-name <name>] [--target <target>] [--git]
|
|
64
|
-
${CLI_NAME} init [id] [--workspace <directory>] [--display-name <name>] [--target <target>]
|
|
64
|
+
${CLI_NAME} new <id> [--path <directory>] [--apps-root apps|.] [--display-name <name>] [--target <target>] [--git]
|
|
65
|
+
${CLI_NAME} init [id] [--workspace <directory>] [--apps-root apps|.] [--display-name <name>] [--target <target>]
|
|
65
66
|
${CLI_NAME} upgrade [--workspace <directory>] [--app <id>]
|
|
66
67
|
${CLI_NAME} doctor [--workspace <directory>] [--arcane-root <directory>]
|
|
67
68
|
${CLI_NAME} import-map [--workspace <directory>] [--app <id>]
|
|
@@ -421,15 +422,6 @@ function readScope(value){
|
|
|
421
422
|
return scope;
|
|
422
423
|
}
|
|
423
424
|
|
|
424
|
-
function inferredAppId(workspaceRoot){
|
|
425
|
-
const id=path.basename(workspaceRoot)
|
|
426
|
-
.normalize('NFKD')
|
|
427
|
-
.toLowerCase()
|
|
428
|
-
.replace(/[^a-z0-9]+/gu,'-')
|
|
429
|
-
.replace(/^-+|-+$/gu,'');
|
|
430
|
-
return id||'arcane-app';
|
|
431
|
-
}
|
|
432
|
-
|
|
433
425
|
function noExtraPositionals(command,positionals,expected=0){
|
|
434
426
|
if(positionals.length>expected){
|
|
435
427
|
usage(`Unexpected argument for ${command}.`);
|
|
@@ -503,6 +495,9 @@ function operationOptions(command,parsed,cwd){
|
|
|
503
495
|
if(command!=='mail'&&flags.has('report-stdin')){
|
|
504
496
|
usage('--report-stdin is supported only by mail send.');
|
|
505
497
|
}
|
|
498
|
+
if(values['apps-root']!==undefined&&!['new','init'].includes(command)){
|
|
499
|
+
usage('--apps-root is supported only by new and init.');
|
|
500
|
+
}
|
|
506
501
|
|
|
507
502
|
if(command==='new'){
|
|
508
503
|
const appId=positionals[0];
|
|
@@ -511,6 +506,7 @@ function operationOptions(command,parsed,cwd){
|
|
|
511
506
|
return {
|
|
512
507
|
targetPath:path.resolve(cwd,values.path??appId),
|
|
513
508
|
appId,
|
|
509
|
+
appsRoot:values['apps-root']??'apps',
|
|
514
510
|
displayName:values['display-name'],
|
|
515
511
|
target:values.target??'browser',
|
|
516
512
|
initializeGit:flags.has('git')
|
|
@@ -520,7 +516,8 @@ function operationOptions(command,parsed,cwd){
|
|
|
520
516
|
noExtraPositionals(command,positionals,1);
|
|
521
517
|
return {
|
|
522
518
|
workspaceRoot,
|
|
523
|
-
appId:positionals[0]??values.app
|
|
519
|
+
appId:positionals[0]??values.app,
|
|
520
|
+
appsRoot:values['apps-root'],
|
|
524
521
|
displayName:values['display-name'],
|
|
525
522
|
target:values.target??'browser'
|
|
526
523
|
};
|
package/src/dev-server.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import https from 'node:https';
|
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import {resolveWorkspace} from './workspace.mjs';
|
|
9
|
+
import {appRelativeRoot} from './app-layout.mjs';
|
|
10
|
+
import {readInstalledSdkLayout} from './sdk-runtime-layout.mjs';
|
|
9
11
|
import {APP_DESCRIPTOR_NAME, projectPackageManifest} from './app-descriptor.mjs';
|
|
10
12
|
import {APP_CONFIG_NAME, validateAppConfig} from './packager/core.mjs';
|
|
11
13
|
import {createEventQueue} from './event-queue.mjs';
|
|
@@ -96,7 +98,7 @@ function parseRequestTarget(rawUrl){
|
|
|
96
98
|
let parsed;
|
|
97
99
|
try{parsed=new URL(raw,'http://127.0.0.1');}
|
|
98
100
|
catch{return null;}
|
|
99
|
-
return {segments,path:decoded,searchParams:parsed.searchParams};
|
|
101
|
+
return {segments,path:decoded,pathname:parsed.pathname,search:parsed.search,searchParams:parsed.searchParams};
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
function resolveInside(root,segments){
|
|
@@ -436,6 +438,7 @@ function sharedPathAllowed(relative,route){
|
|
|
436
438
|
});
|
|
437
439
|
if(excluded)return false;
|
|
438
440
|
return route.include.some(item=>{
|
|
441
|
+
if(item==='.')return true;
|
|
439
442
|
const candidate=inventoryKey(item);
|
|
440
443
|
return comparable===candidate||comparable.startsWith(`${candidate}/`);
|
|
441
444
|
});
|
|
@@ -448,7 +451,8 @@ async function sourceRoutes(workspaceRoot,appId,{
|
|
|
448
451
|
}){
|
|
449
452
|
const resolved=await resolveWorkspace({workspaceRoot,appId});
|
|
450
453
|
const appMapping={
|
|
451
|
-
|
|
454
|
+
kind:'app',
|
|
455
|
+
prefix:appRelativeRoot(resolved.config,resolved.appId).split('/').filter(Boolean),
|
|
452
456
|
root:resolved.appRoot,
|
|
453
457
|
include:resolved.app.manifest.include,
|
|
454
458
|
allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
|
|
@@ -460,25 +464,39 @@ async function sourceRoutes(workspaceRoot,appId,{
|
|
|
460
464
|
resolved.appId,
|
|
461
465
|
{signal,onEvent}
|
|
462
466
|
);
|
|
467
|
+
const installedRoutes=resolved.config.browserRuntimeLayout==='installed-v1'
|
|
468
|
+
?resolved.config.sharedPayloads['browser-runtime']:null;
|
|
469
|
+
const sourceMappings=installedRoutes?sdkSource.mappings.map(
|
|
470
|
+
function selectLiveSourceDestination(mapping,index){
|
|
471
|
+
const route=installedRoutes[[0,2,1][index]];
|
|
472
|
+
return {...mapping,prefix:route.destination.split('/')};
|
|
473
|
+
}
|
|
474
|
+
):sdkSource.mappings;
|
|
463
475
|
return {
|
|
464
476
|
workspaceRoot:resolved.workspaceRoot,
|
|
465
477
|
workspaceMode:resolved.config.workspaceMode,
|
|
466
478
|
config:resolved.config,
|
|
467
479
|
appId:resolved.appId,
|
|
468
480
|
app:resolved.app.manifest,
|
|
469
|
-
startPath
|
|
481
|
+
startPath:applicationSourcePath(resolved.config,resolved.appId,resolved.app.manifest.entry),
|
|
470
482
|
runtime:sdkSource.runtime,
|
|
471
|
-
|
|
483
|
+
...(installedRoutes?{browserRuntimeBase:`/${installedRoutes[1].destination}/`}:{}),
|
|
484
|
+
mappings:[appMapping,...sourceMappings]
|
|
472
485
|
};
|
|
473
486
|
}
|
|
474
|
-
if(resolved.config.workspaceMode==='integrated'
|
|
487
|
+
if(resolved.config.workspaceMode==='integrated'
|
|
488
|
+
||resolved.config.browserRuntimeLayout==='installed-v1'){
|
|
489
|
+
const installed=resolved.config.browserRuntimeLayout==='installed-v1'
|
|
490
|
+
?await readInstalledSdkLayout(resolved.workspaceRoot,resolved.config)
|
|
491
|
+
:null;
|
|
475
492
|
return {
|
|
476
493
|
workspaceRoot:resolved.workspaceRoot,
|
|
477
|
-
workspaceMode:
|
|
494
|
+
workspaceMode:resolved.config.workspaceMode,
|
|
478
495
|
config:resolved.config,
|
|
496
|
+
...(installed?{installed}:{}),
|
|
479
497
|
appId:resolved.appId,
|
|
480
498
|
app:resolved.app.manifest,
|
|
481
|
-
startPath
|
|
499
|
+
startPath:applicationSourcePath(resolved.config,resolved.appId,resolved.app.manifest.entry),
|
|
482
500
|
mappings:[
|
|
483
501
|
appMapping,
|
|
484
502
|
...resolved.config.sharedPayloads['browser-runtime'].map(route=>({
|
|
@@ -497,7 +515,7 @@ async function sourceRoutes(workspaceRoot,appId,{
|
|
|
497
515
|
config:resolved.config,
|
|
498
516
|
appId:resolved.appId,
|
|
499
517
|
app:resolved.app.manifest,
|
|
500
|
-
startPath
|
|
518
|
+
startPath:applicationSourcePath(resolved.config,resolved.appId,resolved.app.manifest.entry),
|
|
501
519
|
mappings:[
|
|
502
520
|
appMapping,
|
|
503
521
|
{
|
|
@@ -510,6 +528,11 @@ async function sourceRoutes(workspaceRoot,appId,{
|
|
|
510
528
|
};
|
|
511
529
|
}
|
|
512
530
|
|
|
531
|
+
function applicationSourcePath(config,appId,relative='') {
|
|
532
|
+
const root=appRelativeRoot(config,appId);
|
|
533
|
+
return `/${root?`${root}/`:''}${relative}`;
|
|
534
|
+
}
|
|
535
|
+
|
|
513
536
|
async function packagedRoutes(releaseRoot){
|
|
514
537
|
if(!is.string(releaseRoot)||!releaseRoot.trim())fail('releaseRoot is required in packaged mode.','ARCANE_USAGE');
|
|
515
538
|
const requested=path.resolve(releaseRoot);
|
|
@@ -571,15 +594,14 @@ async function sourcePwaAssets(routeSet, mappings, signal, resourceUrls, resourc
|
|
|
571
594
|
} else if (info.isFile()) {
|
|
572
595
|
const segments = [...mapping.prefix, ...relative];
|
|
573
596
|
const url = `/${segments.map(encodeURIComponent).join('/')}`;
|
|
574
|
-
const appResource = mapping.
|
|
575
|
-
&& mapping.prefix[1] === routeSet.appId;
|
|
597
|
+
const appResource = mapping.kind === 'app';
|
|
576
598
|
const logical = (appResource ? relative : segments).join('/');
|
|
577
599
|
records.set(logical, url);
|
|
578
600
|
resources.set(url, {mapping, relative});
|
|
579
601
|
}
|
|
580
602
|
}
|
|
581
603
|
for (const selected of mapping.include ?? ['']) {
|
|
582
|
-
await visitSourceResource(selected ? selected.split('/') : []);
|
|
604
|
+
await visitSourceResource(selected && selected!=='.' ? selected.split('/') : []);
|
|
583
605
|
}
|
|
584
606
|
}
|
|
585
607
|
const origin = 'http://arcane.invalid';
|
|
@@ -590,7 +612,7 @@ async function sourcePwaAssets(routeSet, mappings, signal, resourceUrls, resourc
|
|
|
590
612
|
for (const selected of routeSet.app.include) {
|
|
591
613
|
if (!/\.html?$/iu.test(selected)) continue;
|
|
592
614
|
const url = new URL(
|
|
593
|
-
|
|
615
|
+
applicationSourcePath(routeSet.config,routeSet.appId,selected.split('/').map(encodeURIComponent).join('/')),
|
|
594
616
|
origin
|
|
595
617
|
);
|
|
596
618
|
pending.push({url, documentUrl: url});
|
|
@@ -629,8 +651,8 @@ async function sourcePwaAssets(routeSet, mappings, signal, resourceUrls, resourc
|
|
|
629
651
|
const documentUrl = authoredBase ? new URL(authoredBase, current.url) : current.documentUrl;
|
|
630
652
|
if (!runtimeRootsAdded && pathname === entryUrl.pathname) {
|
|
631
653
|
runtimeRootsAdded = true;
|
|
632
|
-
for (const url of resources
|
|
633
|
-
if (
|
|
654
|
+
for (const [url, resource] of resources) {
|
|
655
|
+
if ((resource.mapping.kind !== 'app' && /\.(?:m?js|html?|css)$/iu.test(url))
|
|
634
656
|
|| path.posix.basename(url) === 'arcane.importmap.json') {
|
|
635
657
|
pending.push({url: new URL(url, origin), documentUrl});
|
|
636
658
|
}
|
|
@@ -866,7 +888,7 @@ async function startOwnedDevServer({
|
|
|
866
888
|
const mappings=deterministicMappings(routeSet.mappings);
|
|
867
889
|
const versionPath = mode === 'source'
|
|
868
890
|
? sdkRuntimeSourceRoot === undefined
|
|
869
|
-
? path.join(routeSet.workspaceRoot, 'arcane.lock.json')
|
|
891
|
+
? routeSet.installed?.versionPath ?? path.join(routeSet.workspaceRoot, 'arcane.lock.json')
|
|
870
892
|
: path.join(routeSet.runtime.sourceRoot, 'package.json')
|
|
871
893
|
: undefined;
|
|
872
894
|
const [generatorInputs, initialAssetVersion, versionInput] = await Promise.all(
|
|
@@ -897,6 +919,9 @@ async function startOwnedDevServer({
|
|
|
897
919
|
);
|
|
898
920
|
async function selectedAssetVersion(){
|
|
899
921
|
if(mode!=='source')return undefined;
|
|
922
|
+
if(routeSet.installed)return JSON.parse(await readFile(
|
|
923
|
+
routeSet.installed.versionPath,'utf8'
|
|
924
|
+
)).version;
|
|
900
925
|
return sdkRuntimeSourceRoot===undefined
|
|
901
926
|
?readWorkspaceAssetVersion(routeSet.workspaceRoot)
|
|
902
927
|
:JSON.parse(await readFile(
|
|
@@ -956,7 +981,7 @@ async function startOwnedDevServer({
|
|
|
956
981
|
let sourceManifestTask;
|
|
957
982
|
const appMapping = mappings.find(
|
|
958
983
|
function selectedApplicationMapping(mapping) {
|
|
959
|
-
return mapping.
|
|
984
|
+
return mapping.kind === 'app';
|
|
960
985
|
}
|
|
961
986
|
);
|
|
962
987
|
async function refreshSourceRoutes() {
|
|
@@ -1007,7 +1032,7 @@ async function startOwnedDevServer({
|
|
|
1007
1032
|
currentSourceRoutes = {
|
|
1008
1033
|
...routeSet,
|
|
1009
1034
|
app: manifest,
|
|
1010
|
-
startPath:
|
|
1035
|
+
startPath: applicationSourcePath(routeSet.config,routeSet.appId,manifest.entry),
|
|
1011
1036
|
mappings: mappings.map(
|
|
1012
1037
|
function currentSourceMapping(mapping) {
|
|
1013
1038
|
return mapping === appMapping ? currentAppMapping : mapping;
|
|
@@ -1021,6 +1046,26 @@ async function startOwnedDevServer({
|
|
|
1021
1046
|
let currentPwaState;
|
|
1022
1047
|
const pwaResourceUrls = new Set();
|
|
1023
1048
|
function developmentPwaArtifacts(selectedRoutes, assets = [], version = assetVersion) {
|
|
1049
|
+
const rootApp = selectedRoutes.config.appsRoot === '.';
|
|
1050
|
+
const appBase = applicationSourcePath(selectedRoutes.config,selectedRoutes.appId);
|
|
1051
|
+
const navigationAliases = {'/': selectedRoutes.startPath};
|
|
1052
|
+
if (rootApp) {
|
|
1053
|
+
const legacyBase = `/apps/${selectedRoutes.appId}`;
|
|
1054
|
+
navigationAliases[legacyBase] = selectedRoutes.startPath;
|
|
1055
|
+
navigationAliases[`${legacyBase}/`] = selectedRoutes.startPath;
|
|
1056
|
+
for (const asset of [selectedRoutes.startPath,...assets]) {
|
|
1057
|
+
const pathname = new URL(asset,'http://arcane.invalid').pathname;
|
|
1058
|
+
if (!/\.html?$/iu.test(pathname)) continue;
|
|
1059
|
+
const segments = decodeURIComponent(pathname).split('/').filter(Boolean);
|
|
1060
|
+
const mapping = selectedRoutes.mappings.find(function matchingNavigationRoute(route) {
|
|
1061
|
+
return route.prefix.every(function matchingNavigationSegment(segment,index) {
|
|
1062
|
+
return segments[index] === segment;
|
|
1063
|
+
});
|
|
1064
|
+
});
|
|
1065
|
+
if (mapping?.kind === 'app') navigationAliases[`${legacyBase}${pathname}`] = pathname;
|
|
1066
|
+
}
|
|
1067
|
+
navigationAliases[`${legacyBase}/index.html`] = selectedRoutes.startPath;
|
|
1068
|
+
}
|
|
1024
1069
|
return createPwaArtifacts(
|
|
1025
1070
|
{
|
|
1026
1071
|
app: {
|
|
@@ -1033,10 +1078,12 @@ async function startOwnedDevServer({
|
|
|
1033
1078
|
pwa: selectedRoutes.app.pwa,
|
|
1034
1079
|
files: [],
|
|
1035
1080
|
assets,
|
|
1036
|
-
navigationAliases
|
|
1081
|
+
navigationAliases,
|
|
1037
1082
|
basePath: '/',
|
|
1038
|
-
appBase
|
|
1039
|
-
|
|
1083
|
+
appBase,
|
|
1084
|
+
...(rootApp ? {installationId:`/apps/${routeSet.appId}/`} : {}),
|
|
1085
|
+
runtimeBase: selectedRoutes.browserRuntimeBase
|
|
1086
|
+
?? selectedRoutes.installed?.browserRuntimeBase ?? '/arcane/sdk/',
|
|
1040
1087
|
mode: 'development'
|
|
1041
1088
|
}
|
|
1042
1089
|
);
|
|
@@ -1128,10 +1175,25 @@ async function startOwnedDevServer({
|
|
|
1128
1175
|
const {segments}=target;
|
|
1129
1176
|
const generatedPwaPath = ['/arcane.webmanifest', '/arcane-offline.json', '/arcane-sw.js', '/arcane-pwa.mjs']
|
|
1130
1177
|
.includes(target.path);
|
|
1131
|
-
const
|
|
1178
|
+
const legacyAppRequest = mode === 'source' && routeSet.config.appsRoot === '.'
|
|
1179
|
+
&& segments[0] === 'apps' && segments[1] === routeSet.appId;
|
|
1180
|
+
const requestedMapping = currentSourceRoutes.mappings.find(function currentRequestMapping(route) {
|
|
1181
|
+
return route.prefix.every(function currentRequestSegment(segment,index) {
|
|
1182
|
+
return segments[index] === segment;
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
const appRequest = requestedMapping?.kind === 'app' || legacyAppRequest;
|
|
1132
1186
|
const selectedRoutes = mode === 'source' && (appRequest || segments.length === 0 || generatedPwaPath)
|
|
1133
1187
|
? await refreshSourceRoutes() : currentSourceRoutes;
|
|
1134
1188
|
const pwaEnabled = mode === 'source' ? selectedRoutes.app?.pwa?.enabled === true : routeSet.pwa;
|
|
1189
|
+
if (legacyAppRequest) {
|
|
1190
|
+
const legacyPath = target.pathname.slice(`/apps/${routeSet.appId}`.length);
|
|
1191
|
+
const location = !legacyPath || legacyPath === '/' || legacyPath === '/index.html'
|
|
1192
|
+
? selectedRoutes.startPath : legacyPath;
|
|
1193
|
+
response.writeHead(302,{location:`${location}${target.search}`});
|
|
1194
|
+
response.end();
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1135
1197
|
if (mode === 'source' && pwaEnabled
|
|
1136
1198
|
&& generatedPwaPath) {
|
|
1137
1199
|
const generated = await sourcePwaArtifact(target.path, selectedRoutes);
|
|
@@ -1153,7 +1215,7 @@ async function startOwnedDevServer({
|
|
|
1153
1215
|
return;
|
|
1154
1216
|
}
|
|
1155
1217
|
if(segments.length===0){
|
|
1156
|
-
response.writeHead(302,{location
|
|
1218
|
+
response.writeHead(302,{location:`${selectedRoutes.startPath}${target.search}`});
|
|
1157
1219
|
response.end();
|
|
1158
1220
|
return;
|
|
1159
1221
|
}
|
|
@@ -1185,7 +1247,7 @@ async function startOwnedDevServer({
|
|
|
1185
1247
|
const extension=path.extname(opened.candidate).toLowerCase();
|
|
1186
1248
|
const html=extension==='.html'||extension==='.htm';
|
|
1187
1249
|
const selectedPwaDocument = mode === 'source' && pwaEnabled && html
|
|
1188
|
-
&& mapping.
|
|
1250
|
+
&& mapping.kind === 'app'
|
|
1189
1251
|
&& selectedRoutes.app.include.includes(relative.join('/'));
|
|
1190
1252
|
const selectedDocument = target.path === selectedRoutes.startPath || selectedPwaDocument;
|
|
1191
1253
|
const managedDocument = !selectedDocument && html && await isManagedDocument(opened);
|
|
@@ -1194,7 +1256,7 @@ async function startOwnedDevServer({
|
|
|
1194
1256
|
// A live server can span an SDK upgrade. Refresh the small
|
|
1195
1257
|
// version record on navigation, not on each resource request.
|
|
1196
1258
|
if(entryDocument||managedMap)rememberAssetVersion(await selectedAssetVersion());
|
|
1197
|
-
const runtimeResource=
|
|
1259
|
+
const runtimeResource=mapping.kind!=='app';
|
|
1198
1260
|
const browserResource=['script','style','worker','sharedworker','serviceworker']
|
|
1199
1261
|
.includes(request.headers['sec-fetch-dest']);
|
|
1200
1262
|
const rewrite=runtimeResource||entryDocument||browserResource||managedMap
|
package/src/import-map.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import Is from 'strong-type';
|
|
2
|
+
import {appBaseHref,resolveAppRoot} from './app-layout.mjs';
|
|
2
3
|
import {lstat,mkdir,readFile as readFileFromDisk,readdir,realpath,writeFile} from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import {pathToFileURL} from 'node:url';
|
|
5
6
|
import {SDK_VERSION} from './constants.mjs';
|
|
6
7
|
import {listRuntimeFiles} from './runtime.mjs';
|
|
8
|
+
import {readInstalledSdkLayout,installedRuntimeFiles,installedRuntimeTarget} from './sdk-runtime-layout.mjs';
|
|
7
9
|
|
|
8
10
|
const is = new Is(false);
|
|
9
11
|
|
|
@@ -19,7 +21,23 @@ const SDK_BROWSER_AI_ENTRY='sdk/ai/browser-wasm.mjs';
|
|
|
19
21
|
const SDK_BROWSER_SPEECH_ENTRY='sdk/ai/browser-speech.mjs';
|
|
20
22
|
const STATIC_RUNTIME_PACKAGE_IMPORTS=new Map([
|
|
21
23
|
['arcane-os/preference-store','modules/PreferenceStore.js'],
|
|
22
|
-
['arcane-os/speech-playback','modules/SpeechPlayback.js']
|
|
24
|
+
['arcane-os/speech-playback','modules/SpeechPlayback.js'],
|
|
25
|
+
['arcane-os/ai','modules/AI.js'],
|
|
26
|
+
['arcane-os/ai-preference-tuple','modules/AIPreferenceTuple.js'],
|
|
27
|
+
['arcane-os/ai-preference-runtime','modules/AIPreferenceRuntime.js'],
|
|
28
|
+
['arcane-os/ai-provider-runtime','modules/AIProviderRuntime.js'],
|
|
29
|
+
['arcane-os/ai-runtime-state','modules/AIRuntimeState.js'],
|
|
30
|
+
['arcane-os/model-definition','modules/ModelDefinition.js'],
|
|
31
|
+
['arcane-os/conversation-timebox','modules/ConversationTimebox.js'],
|
|
32
|
+
['arcane-os/conversation-action-items','modules/ConversationActionItems.js'],
|
|
33
|
+
['arcane-os/conversation-closing-report','modules/ConversationClosingReport.js'],
|
|
34
|
+
['arcane-os/chat-records','modules/ChatRecords.js'],
|
|
35
|
+
['arcane-os/app-data-scope','modules/AppDataScope.js'],
|
|
36
|
+
['arcane-os/core-local-model-catalog','modules/CoreLocalModelCatalog.js'],
|
|
37
|
+
['arcane-os/dbopfs-document-library','modules/DBOPFSDocumentLibrary.js'],
|
|
38
|
+
['arcane-os/local-ai-readiness','modules/LocalAIReadiness.js'],
|
|
39
|
+
['arcane-os/ollama-model-identifier','modules/OllamaModelIdentifier.js'],
|
|
40
|
+
['arcane-os/mail','modules/MailApi.mjs']
|
|
23
41
|
]);
|
|
24
42
|
const SDK_BROWSER_SELF_IMPORTS=new Map([
|
|
25
43
|
['arcane-os/event-manager',SDK_BROWSER_ENTRY],
|
|
@@ -1436,16 +1454,33 @@ async function physicalRuntime(workspaceRoot,signal){
|
|
|
1436
1454
|
}
|
|
1437
1455
|
|
|
1438
1456
|
async function managedImportMapBuild(resolvedWorkspace,signal,pwaEnabled=false){
|
|
1457
|
+
const installed=await readInstalledSdkLayout(resolvedWorkspace);
|
|
1439
1458
|
const [runtime,version]=await Promise.all([
|
|
1440
|
-
physicalRuntime(resolvedWorkspace,signal),
|
|
1441
|
-
pwaEnabled?null:readWorkspaceAssetVersion(resolvedWorkspace)
|
|
1459
|
+
installed?installedRuntimeFiles(resolvedWorkspace,installed,signal):physicalRuntime(resolvedWorkspace,signal),
|
|
1460
|
+
pwaEnabled?null:installed?.version??readWorkspaceAssetVersion(resolvedWorkspace)
|
|
1442
1461
|
]);
|
|
1443
1462
|
const built=await buildImportMap({files:runtime.files,signal,version});
|
|
1463
|
+
if(installed?.direct){
|
|
1464
|
+
const imports={};
|
|
1465
|
+
function installedBrowserUrl(value){
|
|
1466
|
+
return value.startsWith('./')
|
|
1467
|
+
?`./${installedRuntimeTarget(value.slice(2),installed,{browser:true})}`:value;
|
|
1468
|
+
}
|
|
1469
|
+
for(const [specifier,target] of Object.entries(built.imports)){
|
|
1470
|
+
const selected=installedBrowserUrl(target);
|
|
1471
|
+
imports[specifier]=selected;
|
|
1472
|
+
// Relative module imports and bare names must resolve to the same instance.
|
|
1473
|
+
imports[installedBrowserUrl(specifier)]=selected;
|
|
1474
|
+
}
|
|
1475
|
+
built.imports=imports;
|
|
1476
|
+
}
|
|
1444
1477
|
const json=`${JSON.stringify({imports:built.imports},null,2).replaceAll('<','\\u003c')}\n`;
|
|
1445
1478
|
return {built,json,version};
|
|
1446
1479
|
}
|
|
1447
1480
|
|
|
1448
1481
|
export async function readWorkspaceAssetVersion(workspaceRoot){
|
|
1482
|
+
const installed=await readInstalledSdkLayout(workspaceRoot);
|
|
1483
|
+
if(installed)return installed.version;
|
|
1449
1484
|
let source;
|
|
1450
1485
|
try{source=await readFileFromDisk(path.join(workspaceRoot,'arcane.lock.json'),'utf8');}
|
|
1451
1486
|
catch(error){
|
|
@@ -2158,12 +2193,6 @@ export function inspectImportMapHtml(html){
|
|
|
2158
2193
|
};
|
|
2159
2194
|
}
|
|
2160
2195
|
|
|
2161
|
-
function documentBaseHref(relative){
|
|
2162
|
-
const directory=path.posix.dirname(relative);
|
|
2163
|
-
const depth=directory==='.'?0:directory.split('/').length;
|
|
2164
|
-
return '../'.repeat(depth+2);
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
2196
|
function renderManagedHtml(html,json,baseHref='../../'){
|
|
2168
2197
|
const structure=scanHtmlStructure(html);
|
|
2169
2198
|
const activeBases=structure.bases.map(base=>({
|
|
@@ -2320,11 +2349,18 @@ async function writeGeneratedFiles({root,files,signal,onEvent}){
|
|
|
2320
2349
|
return eventError;
|
|
2321
2350
|
}
|
|
2322
2351
|
|
|
2323
|
-
function resolvedAppRoot(workspaceRoot,appId,appRoot){
|
|
2352
|
+
async function resolvedAppRoot(workspaceRoot,appId,appRoot){
|
|
2324
2353
|
if(!is.string(appId)||appId.trim()===''){
|
|
2325
2354
|
throw new TypeError('Import-map app id must be a nonempty string.');
|
|
2326
2355
|
}
|
|
2327
|
-
|
|
2356
|
+
let selected=appRoot;
|
|
2357
|
+
if(selected===undefined){
|
|
2358
|
+
let config={appsRoot:'apps'};
|
|
2359
|
+
try{config=JSON.parse(await readFileFromDisk(path.join(workspaceRoot,'arcane-packager.json'),'utf8'));}
|
|
2360
|
+
catch(error){if(error?.code!=='ENOENT')throw error;}
|
|
2361
|
+
selected=resolveAppRoot(workspaceRoot,config,appId);
|
|
2362
|
+
}
|
|
2363
|
+
const resolved=path.resolve(selected);
|
|
2328
2364
|
if(!pathInside(workspaceRoot,resolved))fail('Import-map application root must stay inside the workspace.');
|
|
2329
2365
|
return resolved;
|
|
2330
2366
|
}
|
|
@@ -2434,10 +2470,17 @@ export async function readApplicationTestImportMapContext({
|
|
|
2434
2470
|
||is.array(document.imports)){
|
|
2435
2471
|
fail('Application test import-map artifact must contain an imports object.');
|
|
2436
2472
|
}
|
|
2473
|
+
const installed=await readInstalledSdkLayout(resolvedWorkspaceRoot);
|
|
2474
|
+
const imports=installed?Object.fromEntries(Object.entries(document.imports).map(
|
|
2475
|
+
function installedApplicationTestTarget([specifier,target]){
|
|
2476
|
+
return [specifier,is.string(target)&&target.startsWith('./')
|
|
2477
|
+
?`./${installedRuntimeTarget(target.slice(2),installed)}`:target];
|
|
2478
|
+
}
|
|
2479
|
+
)):document.imports;
|
|
2437
2480
|
return createApplicationTestImportMapContext({
|
|
2438
2481
|
applicationRoot:resolvedWorkspaceRoot,
|
|
2439
2482
|
boundary:'source',
|
|
2440
|
-
imports
|
|
2483
|
+
imports,
|
|
2441
2484
|
signal
|
|
2442
2485
|
});
|
|
2443
2486
|
}
|
|
@@ -2762,7 +2805,7 @@ async function generateImportMapUnlocked({
|
|
|
2762
2805
|
}
|
|
2763
2806
|
throwIfAborted(signal);
|
|
2764
2807
|
const resolvedWorkspace=path.resolve(workspaceRoot);
|
|
2765
|
-
const resolvedApp=resolvedAppRoot(resolvedWorkspace,appId,appRoot);
|
|
2808
|
+
const resolvedApp=await resolvedAppRoot(resolvedWorkspace,appId,appRoot);
|
|
2766
2809
|
await physicalDirectory(resolvedWorkspace,resolvedApp);
|
|
2767
2810
|
const safeEntry=safeRelativePath(entry,'application entry');
|
|
2768
2811
|
const safeDocuments=normalizedDocumentPaths(safeEntry,documents);
|
|
@@ -2792,7 +2835,7 @@ async function generateImportMapUnlocked({
|
|
|
2792
2835
|
const html=await readPhysicalTextFile(resolvedApp,documentPath,label);
|
|
2793
2836
|
throwIfAborted(signal);
|
|
2794
2837
|
// Reject malformed application structure before traversing the runtime inventory.
|
|
2795
|
-
const baseHref=
|
|
2838
|
+
const baseHref=appBaseHref(resolvedWorkspace,resolvedApp,safeDocuments[index]);
|
|
2796
2839
|
renderManagedHtml(html,'{"imports":{}}\n',baseHref);
|
|
2797
2840
|
documentStates.push({filePath:documentPath,html,label,baseHref});
|
|
2798
2841
|
}
|
package/src/mail-api.mjs
CHANGED
|
@@ -1,19 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
default as Mail,
|
|
4
|
-
resolveMailConfig
|
|
5
|
-
} from '../runtime/arcane/modules/Mail.js';
|
|
6
|
-
export {
|
|
7
|
-
MAIL_OUTBOX_IDEMPOTENCY_WINDOW_MS,
|
|
8
|
-
MAIL_OUTBOX_PROTOCOL,
|
|
9
|
-
MAIL_OUTBOX_STATES,
|
|
10
|
-
MAIL_OUTBOX_TABLE,
|
|
11
|
-
MailOutbox,
|
|
12
|
-
createMailOutbox
|
|
13
|
-
} from '../runtime/arcane/modules/MailOutbox.mjs';
|
|
14
|
-
export {
|
|
15
|
-
MailTransportError,
|
|
16
|
-
normalizeMailEndpoint,
|
|
17
|
-
sendMailReport,
|
|
18
|
-
serializeMailReport
|
|
19
|
-
} from '../runtime/arcane/modules/MailTransport.mjs';
|
|
1
|
+
export {default} from '../runtime/arcane/modules/MailApi.mjs';
|
|
2
|
+
export * from '../runtime/arcane/modules/MailApi.mjs';
|