arcane-os 0.54.1 → 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 +60 -0
- package/README.md +35 -28
- package/browser-runtime/core/client.mjs +552 -0
- package/browser-runtime/core/contracts.mjs +85 -0
- package/docs/architecture.md +31 -23
- package/docs/compatibility.md +10 -1
- package/docs/consumer-updates.md +82 -0
- package/docs/platform-targets.md +20 -11
- package/docs/publishing.md +41 -18
- package/docs/reference/README.md +19 -7
- package/docs/reference/availability-and-normalization.md +15 -5
- package/docs/reference/behavioral-testing.md +7 -6
- package/docs/reference/cli.md +12 -4
- package/docs/reference/core-client.md +163 -0
- package/docs/reference/core-native-packaging.md +149 -0
- package/docs/reference/core-runtime.md +185 -0
- package/docs/reference/inventory/package-api.json +271 -5
- package/docs/reference/protocols.md +38 -14
- package/docs/reference/pwa.md +17 -0
- package/docs/reference/sdk-api.md +772 -15
- package/package.json +10 -3
- package/schemas/arcane-app.schema.json +10 -0
- package/schemas/arcane-package.schema.json +10 -0
- package/schemas/native-build-plan.schema.json +8 -0
- package/src/app-descriptor.mjs +18 -1
- package/src/app-layout.mjs +6 -2
- package/src/cli/main.mjs +8 -6
- package/src/core/host.mjs +23 -0
- 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 +345 -0
- package/src/core/stdio.mjs +209 -0
- package/src/core-classic-source.mjs +43 -0
- package/src/import-map.mjs +62 -0
- package/src/native/portable-layout.mjs +184 -0
- package/src/native/portable-provider.mjs +211 -0
- package/src/native-plan.mjs +10 -4
- package/src/native-provider-loader.mjs +15 -4
- package/src/packager/core.mjs +131 -32
- package/src/process.mjs +8 -4
- package/src/targets/index.mjs +3 -1
- package/src/toolchain.mjs +23 -6
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';
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
applyPwaEntryReferences,
|
|
21
22
|
inspectImportMapHtml,
|
|
22
23
|
readWorkspaceAssetVersion,
|
|
24
|
+
removePwaEntryReferences,
|
|
23
25
|
resolveAssetReference,
|
|
24
26
|
rewriteAssetReferences,
|
|
25
27
|
versionAssetUrl
|
|
@@ -319,7 +321,7 @@ function normalizeOptionalRecord(value,label){
|
|
|
319
321
|
export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.join(appRelativeRoot(rootConfig,appId),APP_CONFIG_NAME)){
|
|
320
322
|
assertOnlyKeys(value,new Set([
|
|
321
323
|
'schemaVersion','id','displayName','version','entry','strategy','security',
|
|
322
|
-
'localAIModelPolicy','include','exclude','shared','adapter','pwa','outputDirectory','documents'
|
|
324
|
+
'localAIModelPolicy','include','exclude','shared','adapter','pwa','outputDirectory','documents','nativeResources'
|
|
323
325
|
]),`${appId}/${APP_CONFIG_NAME}`);
|
|
324
326
|
if(value.schemaVersion!==1)fail(`${appId}/${APP_CONFIG_NAME}.schemaVersion must be 1.`);
|
|
325
327
|
if(!is.string(value.id)||value.id!==appId||!APP_ID_PATTERN.test(value.id)){
|
|
@@ -334,6 +336,15 @@ export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.j
|
|
|
334
336
|
);
|
|
335
337
|
const include=validatePathList(value.include,`${appId}/${APP_CONFIG_NAME}.include`,{required:true});
|
|
336
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
|
+
}
|
|
337
348
|
if(include.some(allowed=>sameOrDescendant(APP_CONFIG_NAME,allowed))){
|
|
338
349
|
fail(`${appId}/${APP_CONFIG_NAME}.include must not expose the authored package configuration.`);
|
|
339
350
|
}
|
|
@@ -381,6 +392,7 @@ export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.j
|
|
|
381
392
|
)}),
|
|
382
393
|
include,
|
|
383
394
|
exclude,
|
|
395
|
+
...(nativeResources===undefined?{}:{nativeResources}),
|
|
384
396
|
shared:[...value.shared],
|
|
385
397
|
...(adapter===undefined?{}:{adapter}),
|
|
386
398
|
configPath
|
|
@@ -422,7 +434,8 @@ async function assertContainedRealPath(root,candidate,label){
|
|
|
422
434
|
}
|
|
423
435
|
}
|
|
424
436
|
|
|
425
|
-
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)}.`);
|
|
426
439
|
const workspaceRoot=await realDirectory(normalizeWorkspaceRoot(requestedWorkspaceRoot),'Workspace root');
|
|
427
440
|
const rootConfigPath=path.join(workspaceRoot,ROOT_CONFIG_NAME);
|
|
428
441
|
const rootConfig=validateRootConfig(await readJson(rootConfigPath,ROOT_CONFIG_NAME),rootConfigPath);
|
|
@@ -435,14 +448,19 @@ async function loadContext(requestedWorkspaceRoot,appId,{outputDirectory}={}){
|
|
|
435
448
|
if(outputDirectory!==undefined){
|
|
436
449
|
config.outputDirectory=normalizeRelativePath(outputDirectory,'package outputDirectory');
|
|
437
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
|
+
}
|
|
438
455
|
return {
|
|
439
456
|
workspaceRoot,
|
|
440
457
|
rootConfig,
|
|
441
458
|
appsRoot,
|
|
442
459
|
appRoot,
|
|
443
460
|
appId,
|
|
461
|
+
target,
|
|
444
462
|
config,
|
|
445
|
-
outputRoot
|
|
463
|
+
outputRoot
|
|
446
464
|
};
|
|
447
465
|
}
|
|
448
466
|
|
|
@@ -493,6 +511,11 @@ async function assertPackageOutputLocation(context){
|
|
|
493
511
|
for(const selected of context.config.include){
|
|
494
512
|
assertSelectedInput(context.appRoot,selected,context.config.exclude,context.appId);
|
|
495
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
|
+
}
|
|
496
519
|
for(const sharedId of context.config.shared){
|
|
497
520
|
for(const route of context.rootConfig.sharedPayloads[sharedId]){
|
|
498
521
|
const sourceRoot=path.resolve(context.workspaceRoot,route.source);
|
|
@@ -525,6 +548,7 @@ async function collectSelectedPath({
|
|
|
525
548
|
reject,
|
|
526
549
|
records,
|
|
527
550
|
destinations,
|
|
551
|
+
coalescedRecords,
|
|
528
552
|
signal,
|
|
529
553
|
label,
|
|
530
554
|
allowRoot=false
|
|
@@ -554,6 +578,7 @@ async function collectSelectedPath({
|
|
|
554
578
|
reject,
|
|
555
579
|
records,
|
|
556
580
|
destinations,
|
|
581
|
+
coalescedRecords,
|
|
557
582
|
signal,
|
|
558
583
|
label
|
|
559
584
|
});
|
|
@@ -566,9 +591,15 @@ async function collectSelectedPath({
|
|
|
566
591
|
fail(`${label} overlaps the generated release manifest.`);
|
|
567
592
|
}
|
|
568
593
|
const key=pathKey(normalizedDestination);
|
|
569
|
-
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
|
+
}
|
|
570
599
|
destinations.add(key);
|
|
571
|
-
|
|
600
|
+
const record={source:absolute,destination:normalizedDestination};
|
|
601
|
+
records.push(record);
|
|
602
|
+
coalescedRecords?.set(key,record);
|
|
572
603
|
}
|
|
573
604
|
|
|
574
605
|
async function collectPackageRecords(context,{signal}={}){
|
|
@@ -612,6 +643,29 @@ async function collectPackageRecords(context,{signal}={}){
|
|
|
612
643
|
}
|
|
613
644
|
}
|
|
614
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
|
+
}
|
|
615
669
|
records.sort((left,right)=>compareText(left.destination,right.destination));
|
|
616
670
|
if(!records.some(record=>pathKey(record.destination)===pathKey(appPackagePath(context, context.config.entry)))){
|
|
617
671
|
fail(`Package entry is missing from the selected files: ${context.config.entry}.`);
|
|
@@ -670,7 +724,7 @@ async function optionalDescriptor(context){
|
|
|
670
724
|
|
|
671
725
|
async function inspectContext(context,{signal,records}={}){
|
|
672
726
|
const selectedRecords=records??await collectPackageRecords(context,{signal});
|
|
673
|
-
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);
|
|
674
728
|
return {
|
|
675
729
|
appId:context.appId,
|
|
676
730
|
displayName:context.config.displayName,
|
|
@@ -682,6 +736,7 @@ async function inspectContext(context,{signal,records}={}){
|
|
|
682
736
|
...(context.config.pwa===undefined?{}:{pwa:copyJson(context.config.pwa)}),
|
|
683
737
|
include:[...context.config.include],
|
|
684
738
|
exclude:[...context.config.exclude],
|
|
739
|
+
...(context.config.nativeResources===undefined?{}:{nativeResources:copyJson(context.config.nativeResources)}),
|
|
685
740
|
shared:[...context.config.shared],
|
|
686
741
|
...(context.config.security===undefined?{}:{security:copyJson(context.config.security)}),
|
|
687
742
|
...(context.config.localAIModelPolicy===undefined?{}:{
|
|
@@ -723,9 +778,9 @@ export async function discoverApps({workspaceRoot:requestedWorkspaceRoot}={}){
|
|
|
723
778
|
return apps;
|
|
724
779
|
}
|
|
725
780
|
|
|
726
|
-
export async function inspectApp({workspaceRoot,appId,signal}={}){
|
|
781
|
+
export async function inspectApp({workspaceRoot,appId,signal,target,outputDirectory}={}){
|
|
727
782
|
throwIfAborted(signal);
|
|
728
|
-
const context=await loadContext(workspaceRoot,appId);
|
|
783
|
+
const context=await loadContext(workspaceRoot,appId,{target,outputDirectory});
|
|
729
784
|
return inspectContext(context,{signal});
|
|
730
785
|
}
|
|
731
786
|
|
|
@@ -845,7 +900,8 @@ async function replaceDirectory(stagingRoot,outputRoot){
|
|
|
845
900
|
}
|
|
846
901
|
|
|
847
902
|
async function packageWithContext(context,options={}){
|
|
848
|
-
const {signal,onEvent,
|
|
903
|
+
const {signal,onEvent,moduleFormat='import-map',browserPwa:requestedBrowserPwa=true}=options;
|
|
904
|
+
const browserPwa=context.target==='browser'&&requestedBrowserPwa;
|
|
849
905
|
if(!['import-map','native'].includes(moduleFormat)){
|
|
850
906
|
throw new TypeError('packageApp moduleFormat must be "import-map" or "native".');
|
|
851
907
|
}
|
|
@@ -853,7 +909,9 @@ async function packageWithContext(context,options={}){
|
|
|
853
909
|
const appPath=appRelativeRoot(context.rootConfig,context.appId);
|
|
854
910
|
const entryPath=appPackagePath(context,context.config.entry);
|
|
855
911
|
const records=await collectPackageRecords(context,{signal});
|
|
856
|
-
const
|
|
912
|
+
const browserRecords=records.filter(record=>!record.nativeOnly);
|
|
913
|
+
const nativeRecords=records.filter(record=>record.nativeOnly);
|
|
914
|
+
const rootDocument = browserRecords.some(
|
|
857
915
|
function selectedRootIndex(record) {
|
|
858
916
|
return record.destination === 'index.html';
|
|
859
917
|
}
|
|
@@ -900,7 +958,7 @@ async function packageWithContext(context,options={}){
|
|
|
900
958
|
let promoted=false;
|
|
901
959
|
try{
|
|
902
960
|
async function copyBase() {
|
|
903
|
-
await copyRecords(
|
|
961
|
+
await copyRecords(browserRecords,stagingRoot,{signal,onEvent});
|
|
904
962
|
if (!rootDocument) {
|
|
905
963
|
await writePackageLauncher(context, stagingRoot);
|
|
906
964
|
}
|
|
@@ -912,6 +970,8 @@ async function packageWithContext(context,options={}){
|
|
|
912
970
|
workspaceRoot:context.workspaceRoot,
|
|
913
971
|
appRoot:context.appRoot,
|
|
914
972
|
outputRoot:stagingRoot,
|
|
973
|
+
target:context.target,
|
|
974
|
+
browserPwa,
|
|
915
975
|
copyBase,
|
|
916
976
|
signal,
|
|
917
977
|
onEvent
|
|
@@ -920,12 +980,57 @@ async function packageWithContext(context,options={}){
|
|
|
920
980
|
await copyBase();
|
|
921
981
|
}
|
|
922
982
|
let files=await listOutputFiles(stagingRoot,{signal});
|
|
983
|
+
const entryUrl=new URL(packageResourceUrl(entryPath),'http://arcane.invalid/');
|
|
984
|
+
const selectedFiles=new Set(files);
|
|
985
|
+
const explicitDocumentPaths=context.config.documents===undefined?null:new Set(
|
|
986
|
+
inspected.browserDocuments.map(function selectedDocumentPath(document){return document.packagePath;})
|
|
987
|
+
);
|
|
988
|
+
const pwaDocumentPaths=new Set(explicitDocumentPaths??[entryPath]);
|
|
989
|
+
for(const selected of explicitDocumentPaths===null?context.config.include:[]){
|
|
990
|
+
const selectedPath=appPackagePath(context,selected);
|
|
991
|
+
if(/\.html?$/iu.test(selected)&&selectedFiles.has(selectedPath))pwaDocumentPaths.add(selectedPath);
|
|
992
|
+
}
|
|
993
|
+
for(const document of inspected.browserDocuments){
|
|
994
|
+
const appDocument=context.config.include.some(function includesAppDocument(selected){
|
|
995
|
+
return sameOrDescendant(document.path,selected);
|
|
996
|
+
});
|
|
997
|
+
if(appDocument&&(appPath===''||document.managedMaps.length>0)&&selectedFiles.has(document.packagePath)){
|
|
998
|
+
pwaDocumentPaths.add(document.packagePath);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (!pwaEnabled) {
|
|
1002
|
+
// Remove source-generated registration before native processing can follow it.
|
|
1003
|
+
const removals = await Promise.allSettled(
|
|
1004
|
+
[...pwaDocumentPaths].map(
|
|
1005
|
+
async function removeStagedPwaReferences(documentPath) {
|
|
1006
|
+
throwIfAborted(signal);
|
|
1007
|
+
const filePath = path.join(stagingRoot, ...documentPath.split('/'));
|
|
1008
|
+
const source = await readFile(filePath, 'utf8');
|
|
1009
|
+
const content = removePwaEntryReferences(
|
|
1010
|
+
source,
|
|
1011
|
+
{
|
|
1012
|
+
documentUrl: new URL(packageResourceUrl(documentPath), entryUrl.origin),
|
|
1013
|
+
manifestUrl: new URL(`/${PWA_MANIFEST_NAME}`, entryUrl.origin)
|
|
1014
|
+
}
|
|
1015
|
+
);
|
|
1016
|
+
if (content !== source) await writeFile(filePath, content, 'utf8');
|
|
1017
|
+
}
|
|
1018
|
+
)
|
|
1019
|
+
);
|
|
1020
|
+
// Settle every stage write before a failure can trigger stage cleanup.
|
|
1021
|
+
const errors = [];
|
|
1022
|
+
for (const removal of removals) {
|
|
1023
|
+
if (removal.status === 'rejected') errors.push(removal.reason);
|
|
1024
|
+
}
|
|
1025
|
+
if (errors.length === 1) throw errors[0];
|
|
1026
|
+
if (errors.length > 1) throw new AggregateError(errors, 'Selected PWA reference removal failed.');
|
|
1027
|
+
}
|
|
923
1028
|
if(moduleFormat==='native'){
|
|
924
1029
|
files=await materializeNativeModules({
|
|
925
1030
|
stagingRoot,
|
|
926
1031
|
files,
|
|
927
1032
|
documents:inspected.browserDocuments.map(function documentPath(document){return document.packagePath;}),
|
|
928
|
-
sharedFiles:
|
|
1033
|
+
sharedFiles:browserRecords.filter(function sharedRecord(record){return record.appRelativePath===undefined;})
|
|
929
1034
|
.map(function sharedPath(record){return record.destination;}),
|
|
930
1035
|
signal,
|
|
931
1036
|
onEvent
|
|
@@ -937,27 +1042,10 @@ async function packageWithContext(context,options={}){
|
|
|
937
1042
|
const inventory=new Set(files);
|
|
938
1043
|
const offlineInventory=pwaEnabled?new Set(selectPwaFiles(files,context.config.pwa,appPath)):null;
|
|
939
1044
|
const offlineReferences=new Set();
|
|
940
|
-
const entryUrl=new URL(packageResourceUrl(entryPath),'http://arcane.invalid/');
|
|
941
1045
|
const pwaDocumentSources=new Map();
|
|
942
|
-
const explicitDocumentPaths=context.config.documents===undefined?null:new Set(
|
|
943
|
-
inspected.browserDocuments.map(function selectedDocumentPath(document){return document.packagePath;})
|
|
944
|
-
);
|
|
945
1046
|
if(pwaEnabled){
|
|
946
|
-
const documentPaths=new Set(explicitDocumentPaths??[entryPath]);
|
|
947
|
-
for(const selected of explicitDocumentPaths===null?context.config.include:[]){
|
|
948
|
-
const selectedPath=appPackagePath(context,selected);
|
|
949
|
-
if(/\.html?$/iu.test(selected)&&inventory.has(selectedPath))documentPaths.add(selectedPath);
|
|
950
|
-
}
|
|
951
|
-
for(const document of inspected.browserDocuments){
|
|
952
|
-
const appDocument=context.config.include.some(function includesAppDocument(selected){
|
|
953
|
-
return sameOrDescendant(document.path,selected);
|
|
954
|
-
});
|
|
955
|
-
if(appDocument&&(appPath===''||document.managedMaps.length>0)&&inventory.has(document.packagePath)){
|
|
956
|
-
documentPaths.add(document.packagePath);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
1047
|
await Promise.all(
|
|
960
|
-
[...
|
|
1048
|
+
[...pwaDocumentPaths].map(
|
|
961
1049
|
async function readPwaDocument(documentPath) {
|
|
962
1050
|
const source = await readFile(
|
|
963
1051
|
path.join(stagingRoot, ...documentPath.split('/')),
|
|
@@ -978,8 +1066,8 @@ async function packageWithContext(context,options={}){
|
|
|
978
1066
|
const documentUrl=entryDocument?.bases[0]?.href
|
|
979
1067
|
?new URL(entryDocument.bases[0].href,entryUrl):entryUrl;
|
|
980
1068
|
const pending=[{file:entryPath,documentUrl}];
|
|
981
|
-
const sharedFiles=new Set(
|
|
982
|
-
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){
|
|
983
1071
|
return record.appRelativePath!==undefined;
|
|
984
1072
|
}).map(function applicationDestination(record){return record.destination;}));
|
|
985
1073
|
for(const file of files){
|
|
@@ -1103,6 +1191,17 @@ async function packageWithContext(context,options={}){
|
|
|
1103
1191
|
}
|
|
1104
1192
|
files.sort(compareText);
|
|
1105
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);
|
|
1106
1205
|
const manifest=releaseManifest(context,files,pwaArtifacts,rootDocument);
|
|
1107
1206
|
await writeFile(
|
|
1108
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/targets/index.mjs
CHANGED
|
@@ -21,7 +21,7 @@ const DEFINITIONS=[
|
|
|
21
21
|
id:'portable',displayName:'Portable native application',status:'pairing-required',
|
|
22
22
|
platforms:['windows','linux'],architectures:['x64','arm64'],formats:['portable'],
|
|
23
23
|
signingModes:['unsigned-local-test'],
|
|
24
|
-
reason:'Portable
|
|
24
|
+
reason:'Portable app payloads use the SDK package provider by default; explicit provider pairing remains available.'
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
id:'windows-x64',displayName:'Windows x64 executable',status:'pairing-required',
|
|
@@ -226,6 +226,7 @@ export function createNativeTargetAdapter({targetId,nativeBuilder}={}){
|
|
|
226
226
|
appReleaseRoot,
|
|
227
227
|
release,
|
|
228
228
|
appDescriptor,
|
|
229
|
+
selectedSdk,
|
|
229
230
|
dependencyReleases,
|
|
230
231
|
minimumCoreVersion,
|
|
231
232
|
protectedRoots,
|
|
@@ -242,6 +243,7 @@ export function createNativeTargetAdapter({targetId,nativeBuilder}={}){
|
|
|
242
243
|
appReleaseRoot,
|
|
243
244
|
release,
|
|
244
245
|
appDescriptor,
|
|
246
|
+
selectedSdk,
|
|
245
247
|
dependencyReleases,
|
|
246
248
|
minimumCoreVersion,
|
|
247
249
|
protectedRoots,
|
package/src/toolchain.mjs
CHANGED
|
@@ -836,8 +836,11 @@ function sameCanonicalPath(left,right){
|
|
|
836
836
|
return normalize(left)===normalize(right);
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
-
export function assertIntegratedNativeToolchain({
|
|
839
|
+
export function assertIntegratedNativeToolchain({
|
|
840
|
+
workspaceMode,workspaceRoot,toolchainRoot,target,providerSource='arcane-checkout'
|
|
841
|
+
}={}){
|
|
840
842
|
if(workspaceMode!=='integrated')return;
|
|
843
|
+
if(providerSource==='sdk-package'&&target==='portable')return;
|
|
841
844
|
if(!is.string(toolchainRoot)||!sameCanonicalPath(workspaceRoot,toolchainRoot)){
|
|
842
845
|
throw new ArcaneError(
|
|
843
846
|
ERROR_CODES.policyDenied,
|
|
@@ -992,12 +995,15 @@ function pathsOverlap(left,right){
|
|
|
992
995
|
|
|
993
996
|
async function packageNativeRelease(prepared,app,{
|
|
994
997
|
workspaceOperationLease,
|
|
998
|
+
selectedSdk,
|
|
999
|
+
target,
|
|
995
1000
|
signal,
|
|
996
1001
|
onEvent
|
|
997
1002
|
}={}){
|
|
998
1003
|
const release=await packageApp({
|
|
999
1004
|
workspaceRoot:prepared.workspaceRoot,
|
|
1000
1005
|
appId:app.appId,
|
|
1006
|
+
target,
|
|
1001
1007
|
browserPwa:false,
|
|
1002
1008
|
workspaceOperationLease,
|
|
1003
1009
|
signal,
|
|
@@ -1010,6 +1016,7 @@ async function packageNativeRelease(prepared,app,{
|
|
|
1010
1016
|
release,
|
|
1011
1017
|
nativeInput:{
|
|
1012
1018
|
appDescriptor:app.descriptor,
|
|
1019
|
+
...(selectedSdk===undefined?{}:{selectedSdk}),
|
|
1013
1020
|
appReleaseRoot:release.outputRoot??path.resolve(prepared.workspaceRoot,release.output),
|
|
1014
1021
|
release:{manifest:release.manifest,files:[...release.files]}
|
|
1015
1022
|
}
|
|
@@ -1027,7 +1034,8 @@ async function executePairedNativeBuild(options,adapter,{
|
|
|
1027
1034
|
target,
|
|
1028
1035
|
workspaceMode:initialPrepared.workspaceMode,
|
|
1029
1036
|
workspaceRoot:initialPrepared.workspaceRoot,
|
|
1030
|
-
toolchainRoot:options.toolchainRoot
|
|
1037
|
+
toolchainRoot:options.toolchainRoot,
|
|
1038
|
+
providerSource:options.providerSource
|
|
1031
1039
|
});
|
|
1032
1040
|
if(options.dryRun){
|
|
1033
1041
|
throw new ArcaneError(
|
|
@@ -1051,10 +1059,15 @@ async function executePairedNativeBuild(options,adapter,{
|
|
|
1051
1059
|
}
|
|
1052
1060
|
const dependencyApps=await nativeDependencyClosure(initialPrepared,{target,signal,onEvent});
|
|
1053
1061
|
const prepared=initialPrepared;
|
|
1062
|
+
const installedSdk=await readInstalledSdkLayout(prepared.workspaceRoot,prepared.validation.config);
|
|
1063
|
+
const selectedSdk=installedSdk===null?undefined:{
|
|
1064
|
+
packageRoot:installedSdk.packageRoot,
|
|
1065
|
+
packageSource:installedSdk.packageSource
|
|
1066
|
+
};
|
|
1054
1067
|
const packageOutputs=[];
|
|
1055
1068
|
for(const app of [prepared.validation.app,...dependencyApps]){
|
|
1056
1069
|
const releaseRoot=resolvePackageOutputRoot(
|
|
1057
|
-
prepared.workspaceRoot,prepared.validation.config,app.manifest
|
|
1070
|
+
prepared.workspaceRoot,prepared.validation.config,app.manifest,{target}
|
|
1058
1071
|
);
|
|
1059
1072
|
if(pathsOverlap(releaseRoot,outputRoot)
|
|
1060
1073
|
||packageOutputs.some(existing=>pathsOverlap(existing,releaseRoot))){
|
|
@@ -1073,13 +1086,13 @@ async function executePairedNativeBuild(options,adapter,{
|
|
|
1073
1086
|
descriptorSource:prepared.validation.app.descriptorSource,
|
|
1074
1087
|
descriptorPath:prepared.validation.app.descriptorPath,
|
|
1075
1088
|
validation:prepared.validation
|
|
1076
|
-
},{workspaceOperationLease,signal,onEvent});
|
|
1089
|
+
},{workspaceOperationLease,selectedSdk,target,signal,onEvent});
|
|
1077
1090
|
const dependencyReleases=[];
|
|
1078
1091
|
for(const dependency of dependencyApps){
|
|
1079
1092
|
dependencyReleases.push(await packageNativeRelease(
|
|
1080
1093
|
prepared,
|
|
1081
1094
|
dependency,
|
|
1082
|
-
{workspaceOperationLease,signal,onEvent}
|
|
1095
|
+
{workspaceOperationLease,selectedSdk,target,signal,onEvent}
|
|
1083
1096
|
));
|
|
1084
1097
|
}
|
|
1085
1098
|
const toolchain=await adapter.prepare({
|
|
@@ -1094,7 +1107,11 @@ async function executePairedNativeBuild(options,adapter,{
|
|
|
1094
1107
|
toolchainRoot:options.toolchainRoot,
|
|
1095
1108
|
toolchain,
|
|
1096
1109
|
...selectedRelease.nativeInput,
|
|
1097
|
-
dependencyReleases:dependencyReleases.map(item=>
|
|
1110
|
+
dependencyReleases:dependencyReleases.map(item=>({
|
|
1111
|
+
appId:item.appId,
|
|
1112
|
+
releaseRoot:item.nativeInput.appReleaseRoot,
|
|
1113
|
+
release:item.nativeInput.release
|
|
1114
|
+
})),
|
|
1098
1115
|
minimumCoreVersion:ARCANE_MACHINE_BUNDLE_VERSION,
|
|
1099
1116
|
protectedRoots,
|
|
1100
1117
|
outputRoot,
|