arcane-os 0.55.0 → 0.56.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 +29 -0
- package/README.md +5 -3
- package/browser-runtime/core/client.mjs +75 -14
- package/browser-runtime/core/contracts.mjs +2 -1
- package/docs/consumer-updates.md +82 -0
- package/docs/publishing.md +41 -18
- package/docs/reference/README.md +11 -1
- package/docs/reference/availability-and-normalization.md +11 -3
- package/docs/reference/core-client.md +45 -2
- package/docs/reference/core-runtime.md +10 -0
- package/docs/reference/inventory/package-api.json +22 -8
- package/docs/reference/protocols.md +22 -13
- package/docs/reference/pwa.md +17 -0
- package/package.json +1 -1
- package/schemas/arcane-app.schema.json +10 -0
- package/schemas/arcane-package.schema.json +10 -0
- package/src/app-descriptor.mjs +18 -1
- package/src/app-layout.mjs +6 -2
- package/src/core/hosts/arcane-core-loader.cjs +2 -0
- package/src/core/hosts/linux/arcane_core_process.c +568 -0
- package/src/core/hosts/linux/arcane_core_process.h +127 -0
- package/src/core/hosts/sea-launcher.cjs +17 -0
- package/src/core/hosts/windows/ArcaneCoreProcess.cs +416 -0
- package/src/core/runtime.mjs +15 -3
- package/src/core-classic-source.mjs +2 -2
- package/src/packager/core.mjs +84 -14
- package/src/process.mjs +8 -4
- package/src/toolchain.mjs +5 -3
package/src/packager/core.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import {pathToFileURL} from 'node:url';
|
|
15
15
|
import {appRelativeRoot,resolveAppRoot,resolvePackageOutputRoot} from '../app-layout.mjs';
|
|
16
|
+
import {TARGET_IDS} from '../constants.mjs';
|
|
16
17
|
import {readInstalledSdkLayout} from '../sdk-runtime-layout.mjs';
|
|
17
18
|
import {withWorkspaceOperationLock} from '../workspace-operation-lock.mjs';
|
|
18
19
|
import {materializeNativeModules} from './native-modules.mjs';
|
|
@@ -320,7 +321,7 @@ function normalizeOptionalRecord(value,label){
|
|
|
320
321
|
export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.join(appRelativeRoot(rootConfig,appId),APP_CONFIG_NAME)){
|
|
321
322
|
assertOnlyKeys(value,new Set([
|
|
322
323
|
'schemaVersion','id','displayName','version','entry','strategy','security',
|
|
323
|
-
'localAIModelPolicy','include','exclude','shared','adapter','pwa','outputDirectory','documents'
|
|
324
|
+
'localAIModelPolicy','include','exclude','shared','adapter','pwa','outputDirectory','documents','nativeResources'
|
|
324
325
|
]),`${appId}/${APP_CONFIG_NAME}`);
|
|
325
326
|
if(value.schemaVersion!==1)fail(`${appId}/${APP_CONFIG_NAME}.schemaVersion must be 1.`);
|
|
326
327
|
if(!is.string(value.id)||value.id!==appId||!APP_ID_PATTERN.test(value.id)){
|
|
@@ -335,6 +336,15 @@ export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.j
|
|
|
335
336
|
);
|
|
336
337
|
const include=validatePathList(value.include,`${appId}/${APP_CONFIG_NAME}.include`,{required:true});
|
|
337
338
|
const exclude=validatePathList(value.exclude??[],`${appId}/${APP_CONFIG_NAME}.exclude`);
|
|
339
|
+
let nativeResources;
|
|
340
|
+
if(value.nativeResources!==undefined){
|
|
341
|
+
const label=`${appId}/${APP_CONFIG_NAME}.nativeResources`;
|
|
342
|
+
assertOnlyKeys(value.nativeResources,new Set(['include','exclude']),label);
|
|
343
|
+
nativeResources={
|
|
344
|
+
include:validatePathList(value.nativeResources.include,`${label}.include`),
|
|
345
|
+
exclude:validatePathList(value.nativeResources.exclude??[],`${label}.exclude`)
|
|
346
|
+
};
|
|
347
|
+
}
|
|
338
348
|
if(include.some(allowed=>sameOrDescendant(APP_CONFIG_NAME,allowed))){
|
|
339
349
|
fail(`${appId}/${APP_CONFIG_NAME}.include must not expose the authored package configuration.`);
|
|
340
350
|
}
|
|
@@ -382,6 +392,7 @@ export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.j
|
|
|
382
392
|
)}),
|
|
383
393
|
include,
|
|
384
394
|
exclude,
|
|
395
|
+
...(nativeResources===undefined?{}:{nativeResources}),
|
|
385
396
|
shared:[...value.shared],
|
|
386
397
|
...(adapter===undefined?{}:{adapter}),
|
|
387
398
|
configPath
|
|
@@ -423,7 +434,8 @@ async function assertContainedRealPath(root,candidate,label){
|
|
|
423
434
|
}
|
|
424
435
|
}
|
|
425
436
|
|
|
426
|
-
async function loadContext(requestedWorkspaceRoot,appId,{outputDirectory}={}){
|
|
437
|
+
async function loadContext(requestedWorkspaceRoot,appId,{outputDirectory,target='browser'}={}){
|
|
438
|
+
if(!TARGET_IDS.includes(target))fail(`Unsupported package target: ${String(target)}.`);
|
|
427
439
|
const workspaceRoot=await realDirectory(normalizeWorkspaceRoot(requestedWorkspaceRoot),'Workspace root');
|
|
428
440
|
const rootConfigPath=path.join(workspaceRoot,ROOT_CONFIG_NAME);
|
|
429
441
|
const rootConfig=validateRootConfig(await readJson(rootConfigPath,ROOT_CONFIG_NAME),rootConfigPath);
|
|
@@ -436,14 +448,19 @@ async function loadContext(requestedWorkspaceRoot,appId,{outputDirectory}={}){
|
|
|
436
448
|
if(outputDirectory!==undefined){
|
|
437
449
|
config.outputDirectory=normalizeRelativePath(outputDirectory,'package outputDirectory');
|
|
438
450
|
}
|
|
451
|
+
const outputRoot=resolvePackageOutputRoot(workspaceRoot,rootConfig,config,{target,outputDirectory});
|
|
452
|
+
if(target!=='browser'&&config.nativeResources?.include.length>0){
|
|
453
|
+
config.outputDirectory=path.relative(workspaceRoot,outputRoot).split(path.sep).join('/');
|
|
454
|
+
}
|
|
439
455
|
return {
|
|
440
456
|
workspaceRoot,
|
|
441
457
|
rootConfig,
|
|
442
458
|
appsRoot,
|
|
443
459
|
appRoot,
|
|
444
460
|
appId,
|
|
461
|
+
target,
|
|
445
462
|
config,
|
|
446
|
-
outputRoot
|
|
463
|
+
outputRoot
|
|
447
464
|
};
|
|
448
465
|
}
|
|
449
466
|
|
|
@@ -494,6 +511,11 @@ async function assertPackageOutputLocation(context){
|
|
|
494
511
|
for(const selected of context.config.include){
|
|
495
512
|
assertSelectedInput(context.appRoot,selected,context.config.exclude,context.appId);
|
|
496
513
|
}
|
|
514
|
+
if(context.target!=='browser'&&context.config.nativeResources){
|
|
515
|
+
for(const selected of context.config.nativeResources.include){
|
|
516
|
+
assertSelectedInput(context.appRoot,selected,context.config.nativeResources.exclude,`${context.appId} native`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
497
519
|
for(const sharedId of context.config.shared){
|
|
498
520
|
for(const route of context.rootConfig.sharedPayloads[sharedId]){
|
|
499
521
|
const sourceRoot=path.resolve(context.workspaceRoot,route.source);
|
|
@@ -526,6 +548,7 @@ async function collectSelectedPath({
|
|
|
526
548
|
reject,
|
|
527
549
|
records,
|
|
528
550
|
destinations,
|
|
551
|
+
coalescedRecords,
|
|
529
552
|
signal,
|
|
530
553
|
label,
|
|
531
554
|
allowRoot=false
|
|
@@ -555,6 +578,7 @@ async function collectSelectedPath({
|
|
|
555
578
|
reject,
|
|
556
579
|
records,
|
|
557
580
|
destinations,
|
|
581
|
+
coalescedRecords,
|
|
558
582
|
signal,
|
|
559
583
|
label
|
|
560
584
|
});
|
|
@@ -567,9 +591,15 @@ async function collectSelectedPath({
|
|
|
567
591
|
fail(`${label} overlaps the generated release manifest.`);
|
|
568
592
|
}
|
|
569
593
|
const key=pathKey(normalizedDestination);
|
|
570
|
-
if(destinations.has(key))
|
|
594
|
+
if(destinations.has(key)){
|
|
595
|
+
const existing=coalescedRecords?.get(key);
|
|
596
|
+
if(existing?.source===absolute&&existing.destination===normalizedDestination)return;
|
|
597
|
+
fail(`Package destination collision: ${normalizedDestination}.`);
|
|
598
|
+
}
|
|
571
599
|
destinations.add(key);
|
|
572
|
-
|
|
600
|
+
const record={source:absolute,destination:normalizedDestination};
|
|
601
|
+
records.push(record);
|
|
602
|
+
coalescedRecords?.set(key,record);
|
|
573
603
|
}
|
|
574
604
|
|
|
575
605
|
async function collectPackageRecords(context,{signal}={}){
|
|
@@ -613,6 +643,29 @@ async function collectPackageRecords(context,{signal}={}){
|
|
|
613
643
|
}
|
|
614
644
|
}
|
|
615
645
|
}
|
|
646
|
+
if(context.target!=='browser'&&context.config.nativeResources){
|
|
647
|
+
const nativeRecords=[];
|
|
648
|
+
const coalescedRecords=new Map(records.map(record=>[pathKey(record.destination),record]));
|
|
649
|
+
for(const selected of context.config.nativeResources.include){
|
|
650
|
+
await collectSelectedPath({
|
|
651
|
+
sourceRoot:context.appRoot,
|
|
652
|
+
selected,
|
|
653
|
+
destination:appPackagePath(context,selected),
|
|
654
|
+
excludes:context.config.nativeResources.exclude,
|
|
655
|
+
reject:isAppSourceForbidden,
|
|
656
|
+
records:nativeRecords,
|
|
657
|
+
destinations,
|
|
658
|
+
coalescedRecords,
|
|
659
|
+
signal,
|
|
660
|
+
label:`${context.appId} nativeResources`
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
for(const record of nativeRecords){
|
|
664
|
+
record.appRelativePath=path.relative(context.appRoot,record.source).split(path.sep).join('/');
|
|
665
|
+
record.nativeOnly=true;
|
|
666
|
+
records.push(record);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
616
669
|
records.sort((left,right)=>compareText(left.destination,right.destination));
|
|
617
670
|
if(!records.some(record=>pathKey(record.destination)===pathKey(appPackagePath(context, context.config.entry)))){
|
|
618
671
|
fail(`Package entry is missing from the selected files: ${context.config.entry}.`);
|
|
@@ -671,7 +724,7 @@ async function optionalDescriptor(context){
|
|
|
671
724
|
|
|
672
725
|
async function inspectContext(context,{signal,records}={}){
|
|
673
726
|
const selectedRecords=records??await collectPackageRecords(context,{signal});
|
|
674
|
-
const documents=await browserDocuments(selectedRecords,context.config.entry,context.config.documents);
|
|
727
|
+
const documents=await browserDocuments(selectedRecords.filter(record=>!record.nativeOnly),context.config.entry,context.config.documents);
|
|
675
728
|
return {
|
|
676
729
|
appId:context.appId,
|
|
677
730
|
displayName:context.config.displayName,
|
|
@@ -683,6 +736,7 @@ async function inspectContext(context,{signal,records}={}){
|
|
|
683
736
|
...(context.config.pwa===undefined?{}:{pwa:copyJson(context.config.pwa)}),
|
|
684
737
|
include:[...context.config.include],
|
|
685
738
|
exclude:[...context.config.exclude],
|
|
739
|
+
...(context.config.nativeResources===undefined?{}:{nativeResources:copyJson(context.config.nativeResources)}),
|
|
686
740
|
shared:[...context.config.shared],
|
|
687
741
|
...(context.config.security===undefined?{}:{security:copyJson(context.config.security)}),
|
|
688
742
|
...(context.config.localAIModelPolicy===undefined?{}:{
|
|
@@ -724,9 +778,9 @@ export async function discoverApps({workspaceRoot:requestedWorkspaceRoot}={}){
|
|
|
724
778
|
return apps;
|
|
725
779
|
}
|
|
726
780
|
|
|
727
|
-
export async function inspectApp({workspaceRoot,appId,signal}={}){
|
|
781
|
+
export async function inspectApp({workspaceRoot,appId,signal,target,outputDirectory}={}){
|
|
728
782
|
throwIfAborted(signal);
|
|
729
|
-
const context=await loadContext(workspaceRoot,appId);
|
|
783
|
+
const context=await loadContext(workspaceRoot,appId,{target,outputDirectory});
|
|
730
784
|
return inspectContext(context,{signal});
|
|
731
785
|
}
|
|
732
786
|
|
|
@@ -846,7 +900,8 @@ async function replaceDirectory(stagingRoot,outputRoot){
|
|
|
846
900
|
}
|
|
847
901
|
|
|
848
902
|
async function packageWithContext(context,options={}){
|
|
849
|
-
const {signal,onEvent,
|
|
903
|
+
const {signal,onEvent,moduleFormat='import-map',browserPwa:requestedBrowserPwa=true}=options;
|
|
904
|
+
const browserPwa=context.target==='browser'&&requestedBrowserPwa;
|
|
850
905
|
if(!['import-map','native'].includes(moduleFormat)){
|
|
851
906
|
throw new TypeError('packageApp moduleFormat must be "import-map" or "native".');
|
|
852
907
|
}
|
|
@@ -854,7 +909,9 @@ async function packageWithContext(context,options={}){
|
|
|
854
909
|
const appPath=appRelativeRoot(context.rootConfig,context.appId);
|
|
855
910
|
const entryPath=appPackagePath(context,context.config.entry);
|
|
856
911
|
const records=await collectPackageRecords(context,{signal});
|
|
857
|
-
const
|
|
912
|
+
const browserRecords=records.filter(record=>!record.nativeOnly);
|
|
913
|
+
const nativeRecords=records.filter(record=>record.nativeOnly);
|
|
914
|
+
const rootDocument = browserRecords.some(
|
|
858
915
|
function selectedRootIndex(record) {
|
|
859
916
|
return record.destination === 'index.html';
|
|
860
917
|
}
|
|
@@ -901,7 +958,7 @@ async function packageWithContext(context,options={}){
|
|
|
901
958
|
let promoted=false;
|
|
902
959
|
try{
|
|
903
960
|
async function copyBase() {
|
|
904
|
-
await copyRecords(
|
|
961
|
+
await copyRecords(browserRecords,stagingRoot,{signal,onEvent});
|
|
905
962
|
if (!rootDocument) {
|
|
906
963
|
await writePackageLauncher(context, stagingRoot);
|
|
907
964
|
}
|
|
@@ -913,6 +970,8 @@ async function packageWithContext(context,options={}){
|
|
|
913
970
|
workspaceRoot:context.workspaceRoot,
|
|
914
971
|
appRoot:context.appRoot,
|
|
915
972
|
outputRoot:stagingRoot,
|
|
973
|
+
target:context.target,
|
|
974
|
+
browserPwa,
|
|
916
975
|
copyBase,
|
|
917
976
|
signal,
|
|
918
977
|
onEvent
|
|
@@ -971,7 +1030,7 @@ async function packageWithContext(context,options={}){
|
|
|
971
1030
|
stagingRoot,
|
|
972
1031
|
files,
|
|
973
1032
|
documents:inspected.browserDocuments.map(function documentPath(document){return document.packagePath;}),
|
|
974
|
-
sharedFiles:
|
|
1033
|
+
sharedFiles:browserRecords.filter(function sharedRecord(record){return record.appRelativePath===undefined;})
|
|
975
1034
|
.map(function sharedPath(record){return record.destination;}),
|
|
976
1035
|
signal,
|
|
977
1036
|
onEvent
|
|
@@ -1007,8 +1066,8 @@ async function packageWithContext(context,options={}){
|
|
|
1007
1066
|
const documentUrl=entryDocument?.bases[0]?.href
|
|
1008
1067
|
?new URL(entryDocument.bases[0].href,entryUrl):entryUrl;
|
|
1009
1068
|
const pending=[{file:entryPath,documentUrl}];
|
|
1010
|
-
const sharedFiles=new Set(
|
|
1011
|
-
const applicationFiles=new Set(
|
|
1069
|
+
const sharedFiles=new Set(browserRecords.filter(record=>record.appRelativePath===undefined).map(record=>record.destination));
|
|
1070
|
+
const applicationFiles=new Set(browserRecords.filter(function applicationRecord(record){
|
|
1012
1071
|
return record.appRelativePath!==undefined;
|
|
1013
1072
|
}).map(function applicationDestination(record){return record.destination;}));
|
|
1014
1073
|
for(const file of files){
|
|
@@ -1132,6 +1191,17 @@ async function packageWithContext(context,options={}){
|
|
|
1132
1191
|
}
|
|
1133
1192
|
files.sort(compareText);
|
|
1134
1193
|
}
|
|
1194
|
+
// Native resources bypass browser rewriting, module projection and PWA generation.
|
|
1195
|
+
// The app's browser selection remains authoritative wherever both select a file.
|
|
1196
|
+
const finalDestinations=new Set(files.map(pathKey));
|
|
1197
|
+
for(const record of nativeRecords){
|
|
1198
|
+
if(finalDestinations.has(pathKey(record.destination))){
|
|
1199
|
+
fail(`Native resource overlaps generated package content: ${record.destination}.`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
await copyRecords(nativeRecords,stagingRoot,{signal,onEvent});
|
|
1203
|
+
files.push(...nativeRecords.map(record=>record.destination));
|
|
1204
|
+
files.sort(compareText);
|
|
1135
1205
|
const manifest=releaseManifest(context,files,pwaArtifacts,rootDocument);
|
|
1136
1206
|
await writeFile(
|
|
1137
1207
|
path.join(stagingRoot,RELEASE_MANIFEST_NAME),
|
package/src/process.mjs
CHANGED
|
@@ -33,8 +33,8 @@ function platformArguments(command,args,platform=process.platform){
|
|
|
33
33
|
return [cliPath,...args];
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function appendOutput(current,
|
|
37
|
-
return current+
|
|
36
|
+
function appendOutput(current,text){
|
|
37
|
+
return current+text;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function emitLines(events,type,text){
|
|
@@ -45,9 +45,9 @@ async function emitLines(events,type,text){
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function deliverChunk(stream,events,type,
|
|
48
|
+
function deliverChunk(stream,events,type,text){
|
|
49
49
|
stream.pause();
|
|
50
|
-
return emitLines(events,type,
|
|
50
|
+
return emitLines(events,type,text).catch(()=>{
|
|
51
51
|
// The queue owns and propagates the first callback failure.
|
|
52
52
|
}).finally(()=>{
|
|
53
53
|
if(!stream.destroyed){
|
|
@@ -263,6 +263,10 @@ export async function runProcess(command,args=[],{
|
|
|
263
263
|
return;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
// Each readable retains split UTF-8 sequences and flushes at EOF.
|
|
267
|
+
// Capture and events consume the same decoded text.
|
|
268
|
+
child.stdout.setEncoding('utf8');
|
|
269
|
+
child.stderr.setEncoding('utf8');
|
|
266
270
|
child.stdout.on('data',chunk=>{
|
|
267
271
|
stdout=appendOutput(stdout,chunk);
|
|
268
272
|
ownDelivery(child.stdout,'process.stdout',chunk);
|
package/src/toolchain.mjs
CHANGED
|
@@ -996,12 +996,14 @@ function pathsOverlap(left,right){
|
|
|
996
996
|
async function packageNativeRelease(prepared,app,{
|
|
997
997
|
workspaceOperationLease,
|
|
998
998
|
selectedSdk,
|
|
999
|
+
target,
|
|
999
1000
|
signal,
|
|
1000
1001
|
onEvent
|
|
1001
1002
|
}={}){
|
|
1002
1003
|
const release=await packageApp({
|
|
1003
1004
|
workspaceRoot:prepared.workspaceRoot,
|
|
1004
1005
|
appId:app.appId,
|
|
1006
|
+
target,
|
|
1005
1007
|
browserPwa:false,
|
|
1006
1008
|
workspaceOperationLease,
|
|
1007
1009
|
signal,
|
|
@@ -1065,7 +1067,7 @@ async function executePairedNativeBuild(options,adapter,{
|
|
|
1065
1067
|
const packageOutputs=[];
|
|
1066
1068
|
for(const app of [prepared.validation.app,...dependencyApps]){
|
|
1067
1069
|
const releaseRoot=resolvePackageOutputRoot(
|
|
1068
|
-
prepared.workspaceRoot,prepared.validation.config,app.manifest
|
|
1070
|
+
prepared.workspaceRoot,prepared.validation.config,app.manifest,{target}
|
|
1069
1071
|
);
|
|
1070
1072
|
if(pathsOverlap(releaseRoot,outputRoot)
|
|
1071
1073
|
||packageOutputs.some(existing=>pathsOverlap(existing,releaseRoot))){
|
|
@@ -1084,13 +1086,13 @@ async function executePairedNativeBuild(options,adapter,{
|
|
|
1084
1086
|
descriptorSource:prepared.validation.app.descriptorSource,
|
|
1085
1087
|
descriptorPath:prepared.validation.app.descriptorPath,
|
|
1086
1088
|
validation:prepared.validation
|
|
1087
|
-
},{workspaceOperationLease,selectedSdk,signal,onEvent});
|
|
1089
|
+
},{workspaceOperationLease,selectedSdk,target,signal,onEvent});
|
|
1088
1090
|
const dependencyReleases=[];
|
|
1089
1091
|
for(const dependency of dependencyApps){
|
|
1090
1092
|
dependencyReleases.push(await packageNativeRelease(
|
|
1091
1093
|
prepared,
|
|
1092
1094
|
dependency,
|
|
1093
|
-
{workspaceOperationLease,selectedSdk,signal,onEvent}
|
|
1095
|
+
{workspaceOperationLease,selectedSdk,target,signal,onEvent}
|
|
1094
1096
|
));
|
|
1095
1097
|
}
|
|
1096
1098
|
const toolchain=await adapter.prepare({
|