arcane-os 0.57.0 → 0.59.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 +37 -0
- package/README.md +6 -2
- package/browser-runtime/ai/core-onnx.mjs +96 -0
- package/browser-runtime/ai/onnx-tensors.mjs +85 -0
- package/browser-runtime/core/client.mjs +152 -17
- package/docs/architecture.md +5 -2
- package/docs/platform-targets.md +30 -13
- package/docs/reference/README.md +22 -3
- package/docs/reference/availability-and-normalization.md +14 -7
- package/docs/reference/cli.md +22 -10
- package/docs/reference/core-client.md +87 -0
- package/docs/reference/core-native-packaging.md +129 -9
- package/docs/reference/core-preferences.md +49 -0
- package/docs/reference/diarization.md +206 -0
- package/docs/reference/git-text-snapshot.md +104 -0
- package/docs/reference/inventory/package-api.json +205 -23
- package/docs/reference/local-ai.md +145 -11
- package/docs/reference/protocols.md +28 -19
- package/docs/reference/pwa.md +74 -4
- package/docs/reference/sdk-api.md +589 -39
- package/package.json +11 -3
- package/schemas/arcane-app.schema.json +3 -3
- package/schemas/arcane-package.schema.json +2 -2
- package/schemas/native-build-plan.schema.json +27 -1
- package/schemas/target-adapter.schema.json +2 -0
- package/src/app-descriptor.mjs +1 -1
- package/src/cli/main.mjs +14 -4
- package/src/constants.mjs +2 -0
- package/src/core/host.mjs +14 -5
- package/src/core/hosts/macos/ArcaneHost.h +86 -0
- package/src/core/hosts/macos/ArcaneHost.m +455 -0
- package/src/core/hosts/macos/ArcaneLauncher.m +432 -0
- package/src/core/hosts/windows/ArcaneLauncher.cs +4 -1
- package/src/core/services/local-ai.mjs +82 -4
- package/src/core/services/packaged-web.mjs +89 -0
- package/src/core/services/preferences.mjs +201 -0
- package/src/core-classic-source.mjs +2 -2
- package/src/dev-server.mjs +2 -5
- package/src/diarization/build.mjs +35 -0
- package/src/diarization/index.mjs +432 -0
- package/src/diarization/native/CMakeLists.txt +9 -0
- package/src/diarization/native/main.cpp +269 -0
- package/src/diarization/service.mjs +129 -0
- package/src/git-text-snapshot.mjs +265 -0
- package/src/import-map.mjs +9 -4
- package/src/index.mjs +1 -0
- package/src/local-ai/config.mjs +1 -1
- package/src/local-ai/install.mjs +134 -19
- package/src/local-ai/onnx-worker.mjs +85 -0
- package/src/local-ai/onnx.mjs +368 -0
- package/src/native/macos-provider.mjs +291 -0
- package/src/native/portable-layout.mjs +10 -2
- package/src/native/portable-provider.mjs +10 -5
- package/src/native-plan.mjs +9 -1
- package/src/native-provider-loader.mjs +3 -1
- package/src/process.mjs +123 -17
- package/src/scaffold.mjs +1 -1
- package/src/targets/index.mjs +11 -1
- package/src/templates/workspace-template.mjs +6 -5
- package/src/toolchain.mjs +42 -13
|
@@ -149,7 +149,7 @@ export async function copyCoreRuntime(root, files, signal, {
|
|
|
149
149
|
return metadata;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
export function coreEntrySource(application, version, services, {localAI, runtimes = []} = {}) {
|
|
152
|
+
export function coreEntrySource(application, version, services, {localAI, runtimes = [], packagedWeb = false} = {}) {
|
|
153
153
|
const imports = services.map(function serviceImport(service, index) {
|
|
154
154
|
const specifier = '../app/' + service.module.split('/').map(encodeURIComponent).join('/');
|
|
155
155
|
return `import createService${index} from ${JSON.stringify(specifier)};`;
|
|
@@ -162,6 +162,10 @@ export function coreEntrySource(application, version, services, {localAI, runtim
|
|
|
162
162
|
imports.push("import {createLocalAIService} from 'arcane-os/core/local-ai';");
|
|
163
163
|
definitions.push(` createLocalAIService(JSON.parse(${JSON.stringify(JSON.stringify(localAI))}), context)`);
|
|
164
164
|
}
|
|
165
|
+
if (packagedWeb) {
|
|
166
|
+
imports.push("import {createPackagedWebService} from 'arcane-os/core/packaged-web';");
|
|
167
|
+
definitions.push(" createPackagedWebService({artifactRoot: fileURLToPath(new URL('../', import.meta.url))}, context)");
|
|
168
|
+
}
|
|
165
169
|
return [
|
|
166
170
|
"import {fileURLToPath} from 'node:url';",
|
|
167
171
|
"import {readCoreLaunchContext, startCoreHost} from 'arcane-os/core/host';",
|
|
@@ -180,7 +184,11 @@ export function coreEntrySource(application, version, services, {localAI, runtim
|
|
|
180
184
|
" appRoot: fileURLToPath(new URL('../app/', import.meta.url)),",
|
|
181
185
|
...(localAI === undefined ? [] : [
|
|
182
186
|
` runtimes: JSON.parse(${JSON.stringify(JSON.stringify(runtimes))}).map(function runtimeLocation(runtime) {`,
|
|
183
|
-
'
|
|
187
|
+
' const resolved = {...runtime};',
|
|
188
|
+
" for (const field of ['root', 'executable', 'modulePath', 'includeDirectory', 'libraryDirectory', 'binaryDirectory', 'cmakeDirectory']) {",
|
|
189
|
+
' if (runtime[field] !== undefined) resolved[field] = runtimePath(runtime[field]);',
|
|
190
|
+
' }',
|
|
191
|
+
' return resolved;',
|
|
184
192
|
' }),'
|
|
185
193
|
]),
|
|
186
194
|
' ...await readCoreLaunchContext()',
|
|
@@ -81,7 +81,7 @@ function eventOwnerPath(release) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/** Package-owned assembly; never imports an OS checkout or application service. */
|
|
84
|
-
export function createPortableNativeProvider({services} = {}) {
|
|
84
|
+
export function createPortableNativeProvider({services, packagedWeb = false, webKitDocumentLifecycle = false} = {}) {
|
|
85
85
|
return {
|
|
86
86
|
protocol: PROTOCOL,
|
|
87
87
|
async describe() {
|
|
@@ -138,7 +138,7 @@ export function createPortableNativeProvider({services} = {}) {
|
|
|
138
138
|
runtimes: localAI.runtimes,
|
|
139
139
|
directory: path.join(output, 'local-ai-runtimes'),
|
|
140
140
|
outputRoot: root,
|
|
141
|
-
platform: target.platform === 'windows' ? 'win32' : target.platform,
|
|
141
|
+
platform: target.platform === 'windows' ? 'win32' : target.platform === 'macos' ? 'darwin' : target.platform,
|
|
142
142
|
architecture: target.architecture,
|
|
143
143
|
signal,
|
|
144
144
|
onEvent
|
|
@@ -183,8 +183,10 @@ export function createPortableNativeProvider({services} = {}) {
|
|
|
183
183
|
}
|
|
184
184
|
await writeOutput(root, 'package.json', '{"type":"module"}\n', files, signal);
|
|
185
185
|
await writeOutput(root, 'runtime/arcane-core.mjs', coreEntrySource(appDescriptor, sdk.version, selectedServices,
|
|
186
|
-
{localAI, runtimes: bundled.runtimes}), files, signal);
|
|
187
|
-
await writeOutput(root, 'runtime/arcane-api.js', await createCoreClassicSource({
|
|
186
|
+
{localAI, runtimes: bundled.runtimes, packagedWeb}), files, signal);
|
|
187
|
+
await writeOutput(root, 'runtime/arcane-api.js', await createCoreClassicSource({
|
|
188
|
+
eventOwnerModuleURL, replayRuntimeState: true, webKitDocumentLifecycle
|
|
189
|
+
}), files, signal);
|
|
188
190
|
const manifest = {
|
|
189
191
|
schemaVersion: 1,
|
|
190
192
|
kind: 'arcane-portable-native',
|
|
@@ -198,7 +200,10 @@ export function createPortableNativeProvider({services} = {}) {
|
|
|
198
200
|
entry: 'runtime/arcane-core.mjs', transport: 'stdio', protocol: 'arcane/1', services: selectedServices,
|
|
199
201
|
...(localAI === undefined ? {} : {localAIRuntimes: bundled.runtimes})
|
|
200
202
|
},
|
|
201
|
-
client: {
|
|
203
|
+
client: {
|
|
204
|
+
source: 'runtime/arcane-api.js', injection: 'document-created', eventOwnerModuleURL, replayRuntimeState: true,
|
|
205
|
+
...(webKitDocumentLifecycle ? {webKitDocumentLifecycle: true} : {})
|
|
206
|
+
},
|
|
202
207
|
dependencies,
|
|
203
208
|
minimumCoreVersion: request.minimumCoreVersion ?? null,
|
|
204
209
|
files: [...files, MANIFEST]
|
package/src/native-plan.mjs
CHANGED
|
@@ -12,7 +12,7 @@ export const NATIVE_BUILDER_PROTOCOL='arcane-native-builder/1';
|
|
|
12
12
|
|
|
13
13
|
const NATIVE_TARGETS={
|
|
14
14
|
portable:{
|
|
15
|
-
platforms:new Set(['windows','linux']),
|
|
15
|
+
platforms:new Set(['windows','linux','macos']),
|
|
16
16
|
architectures:new Set(['x64','arm64']),
|
|
17
17
|
format:'portable',
|
|
18
18
|
signingMode:'unsigned-local-test',
|
|
@@ -22,6 +22,14 @@ const NATIVE_TARGETS={
|
|
|
22
22
|
platforms:new Set(['windows']),architectures:new Set(['x64']),format:'exe',
|
|
23
23
|
signingMode:'unsigned-local-test',signingProfileId:null
|
|
24
24
|
},
|
|
25
|
+
'macos-arm64':{
|
|
26
|
+
platforms:new Set(['macos']),architectures:new Set(['arm64']),format:'app',
|
|
27
|
+
signingMode:'unsigned-local-test',signingProfileId:null
|
|
28
|
+
},
|
|
29
|
+
'macos-x64':{
|
|
30
|
+
platforms:new Set(['macos']),architectures:new Set(['x64']),format:'app',
|
|
31
|
+
signingMode:'unsigned-local-test',signingProfileId:null
|
|
32
|
+
},
|
|
25
33
|
'linux-x64':{
|
|
26
34
|
platforms:new Set(['linux']),architectures:new Set(['x64']),format:'deb',
|
|
27
35
|
signingMode:'unsigned-local-test',signingProfileId:null
|
|
@@ -24,7 +24,9 @@ const FIXED_NATIVE_PROVIDER_PATHS={
|
|
|
24
24
|
|
|
25
25
|
const SDK_NATIVE_PROVIDER_PATHS={
|
|
26
26
|
portable:'./native/portable-provider.mjs',
|
|
27
|
-
'windows-x64':'./native/windows-provider.mjs'
|
|
27
|
+
'windows-x64':'./native/windows-provider.mjs',
|
|
28
|
+
'macos-arm64':'./native/macos-provider.mjs',
|
|
29
|
+
'macos-x64':'./native/macos-provider.mjs'
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
export const ARCANE_NATIVE_PROVIDER_PATHS={
|
package/src/process.mjs
CHANGED
|
@@ -45,10 +45,30 @@ async function emitLines(events,type,text){
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function
|
|
48
|
+
function outputSelected(option,stream){
|
|
49
|
+
return is.boolean(option)?option:option?.[stream]??true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function outputEncodingSelected(option,stream){
|
|
53
|
+
const selected=option!==null&&is.object(option)?option[stream]:option;
|
|
54
|
+
const encoding=selected===undefined?'utf8':selected;
|
|
55
|
+
if(encoding!=='utf8'&&encoding!==null){
|
|
56
|
+
throw new TypeError(`The ${stream} outputEncoding must be 'utf8' or null.`);
|
|
57
|
+
}
|
|
58
|
+
return encoding;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function deliverChunk(stream,events,type,chunk,{onOutput,emitOutput,onFailure}){
|
|
49
62
|
stream.pause();
|
|
50
|
-
return
|
|
51
|
-
|
|
63
|
+
return (async function deliverOutput(){
|
|
64
|
+
try{
|
|
65
|
+
if(onOutput)await onOutput({stream:type==='process.stdout'?'stdout':'stderr',chunk});
|
|
66
|
+
}catch(error){
|
|
67
|
+
onFailure(error);
|
|
68
|
+
}
|
|
69
|
+
if(emitOutput)await emitLines(events,type,chunk);
|
|
70
|
+
})().catch(()=>{
|
|
71
|
+
// The event queue separately owns and propagates observer failures.
|
|
52
72
|
}).finally(()=>{
|
|
53
73
|
if(!stream.destroyed){
|
|
54
74
|
stream.resume();
|
|
@@ -121,6 +141,10 @@ export async function runProcess(command,args=[],{
|
|
|
121
141
|
env,
|
|
122
142
|
signal,
|
|
123
143
|
onEvent,
|
|
144
|
+
onOutput,
|
|
145
|
+
captureOutput=true,
|
|
146
|
+
emitOutputEvents=true,
|
|
147
|
+
outputEncoding='utf8',
|
|
124
148
|
heartbeatMs=5000,
|
|
125
149
|
terminationGraceMs=DEFAULT_TERMINATION_GRACE_MS,
|
|
126
150
|
allowNonzero=false,
|
|
@@ -128,6 +152,23 @@ export async function runProcess(command,args=[],{
|
|
|
128
152
|
cancellationMode='terminate-tree'
|
|
129
153
|
}={}){
|
|
130
154
|
throwIfAborted(signal);
|
|
155
|
+
if(onOutput!==undefined&&!is.function(onOutput)){
|
|
156
|
+
throw new TypeError('onOutput must be a function.');
|
|
157
|
+
}
|
|
158
|
+
const outputEncodings={
|
|
159
|
+
stdout:outputEncodingSelected(outputEncoding,'stdout'),
|
|
160
|
+
stderr:outputEncodingSelected(outputEncoding,'stderr')
|
|
161
|
+
};
|
|
162
|
+
for(const stream of ['stdout','stderr']){
|
|
163
|
+
if(outputEncodings[stream]===null&&(!onOutput
|
|
164
|
+
||outputSelected(captureOutput,stream)!==false
|
|
165
|
+
||outputSelected(emitOutputEvents,stream)!==false)){
|
|
166
|
+
throw new TypeError(`Raw ${stream} requires onOutput, captureOutput:false and emitOutputEvents:false for that stream.`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if(!onOutput&&(!outputSelected(captureOutput,'stdout')||!outputSelected(captureOutput,'stderr'))){
|
|
170
|
+
throw new TypeError('Uncaptured process output requires an onOutput consumer.');
|
|
171
|
+
}
|
|
131
172
|
if(!is.array(args)||args.some(argument=>!is.string(argument))){
|
|
132
173
|
throw new ArcaneError(ERROR_CODES.usage,'Process arguments must be a fixed array of strings.');
|
|
133
174
|
}
|
|
@@ -155,8 +196,8 @@ export async function runProcess(command,args=[],{
|
|
|
155
196
|
throwIfAborted(signal);
|
|
156
197
|
|
|
157
198
|
return new Promise((resolve,reject)=>{
|
|
158
|
-
let stdout='';
|
|
159
|
-
let stderr='';
|
|
199
|
+
let stdout=outputEncodings.stdout!==null&&outputSelected(captureOutput,'stdout')?'':null;
|
|
200
|
+
let stderr=outputEncodings.stderr!==null&&outputSelected(captureOutput,'stderr')?'':null;
|
|
160
201
|
let childClosed=false;
|
|
161
202
|
let closedResult=null;
|
|
162
203
|
let settlementStarted=false;
|
|
@@ -166,12 +207,28 @@ export async function runProcess(command,args=[],{
|
|
|
166
207
|
let escalation=null;
|
|
167
208
|
let spawnError=null;
|
|
168
209
|
let inputError=null;
|
|
210
|
+
let inputIterator=null;
|
|
211
|
+
let inputPump=null;
|
|
212
|
+
let inputReturn=null;
|
|
213
|
+
let inputDone=false;
|
|
214
|
+
const outputErrors=[];
|
|
169
215
|
let child;
|
|
170
216
|
let heartbeat=null;
|
|
171
217
|
const deliveries=new Set();
|
|
172
218
|
|
|
173
219
|
const ownDelivery=(stream,type,chunk)=>{
|
|
174
|
-
const
|
|
220
|
+
const name=type==='process.stdout'?'stdout':'stderr';
|
|
221
|
+
const delivery=deliverChunk(stream,events,type,chunk,{
|
|
222
|
+
onOutput,
|
|
223
|
+
emitOutput:outputEncodings[name]!==null&&outputSelected(emitOutputEvents,name),
|
|
224
|
+
onFailure:function outputFailed(error){
|
|
225
|
+
// Preserve complete failed callback input even when the
|
|
226
|
+
// caller elected not to retain successful protocol output.
|
|
227
|
+
outputErrors.push(new ArcaneError(ERROR_CODES.operationFailed,
|
|
228
|
+
`The ${name} output callback failed.`,{cause:error,details:{stream:name,chunk}}));
|
|
229
|
+
stopTree();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
175
232
|
deliveries.add(delivery);
|
|
176
233
|
void delivery.then(()=>deliveries.delete(delivery));
|
|
177
234
|
};
|
|
@@ -182,6 +239,26 @@ export async function runProcess(command,args=[],{
|
|
|
182
239
|
}
|
|
183
240
|
};
|
|
184
241
|
|
|
242
|
+
function stopInput(){
|
|
243
|
+
if(inputIterator&&!inputDone&&!inputReturn){
|
|
244
|
+
inputReturn=Promise.resolve().then(async function returnInput(){
|
|
245
|
+
await inputIterator.return?.();
|
|
246
|
+
}).catch(function inputReturnFailed(error){
|
|
247
|
+
inputError=inputError&&inputError!==error
|
|
248
|
+
?new AggregateError([inputError,error],'Process input and its cleanup failed.'):error;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
child?.stdin.end();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function drainInput(){
|
|
255
|
+
stopInput();
|
|
256
|
+
// AsyncIterable producers own cancellation of an outstanding next().
|
|
257
|
+
// Their return() must cooperate so owned cleanup can really finish.
|
|
258
|
+
await inputPump;
|
|
259
|
+
await inputReturn;
|
|
260
|
+
}
|
|
261
|
+
|
|
185
262
|
const finish=(callback,value)=>{
|
|
186
263
|
if(settlementStarted){
|
|
187
264
|
return;
|
|
@@ -191,16 +268,17 @@ export async function runProcess(command,args=[],{
|
|
|
191
268
|
clearTimeout(escalation);
|
|
192
269
|
signal?.removeEventListener('abort',abort);
|
|
193
270
|
void (async()=>{
|
|
271
|
+
await drainInput();
|
|
194
272
|
let callbackFailure=null;
|
|
195
273
|
try{
|
|
196
274
|
await events.drain();
|
|
197
275
|
}catch(error){
|
|
198
276
|
callbackFailure=error;
|
|
199
277
|
}
|
|
200
|
-
if(cancellationMode==='close-input'&&closedResult){
|
|
278
|
+
if((cancellationMode==='close-input'&&closedResult)||inputIterator||onOutput){
|
|
201
279
|
const failures=[...new Set([
|
|
202
280
|
callback===reject?value:null,
|
|
203
|
-
callbackFailure,inputError,cancellationError,spawnError
|
|
281
|
+
callbackFailure,inputError,cancellationError,spawnError,...outputErrors
|
|
204
282
|
].filter(error=>error!==null))];
|
|
205
283
|
if(failures.length>0){
|
|
206
284
|
const primary=failures[0];
|
|
@@ -234,10 +312,10 @@ export async function runProcess(command,args=[],{
|
|
|
234
312
|
return;
|
|
235
313
|
}
|
|
236
314
|
terminationRequested=true;
|
|
315
|
+
stopInput();
|
|
237
316
|
if(cancellationMode==='close-input'){
|
|
238
317
|
// The selected host owns shutdown after EOF. Keep observing its
|
|
239
318
|
// complete output and exit; accepted durable work may outlive UI.
|
|
240
|
-
child?.stdin.end();
|
|
241
319
|
return;
|
|
242
320
|
}
|
|
243
321
|
terminateProcessTree(child);
|
|
@@ -306,16 +384,16 @@ export async function runProcess(command,args=[],{
|
|
|
306
384
|
return;
|
|
307
385
|
}
|
|
308
386
|
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
child.stdout.setEncoding(
|
|
312
|
-
child.stderr.setEncoding(
|
|
387
|
+
// Text streams retain split UTF-8 sequences and flush at EOF. Explicit
|
|
388
|
+
// raw streams bypass decoding and deliver native Buffers only to onOutput.
|
|
389
|
+
if(outputEncodings.stdout!==null)child.stdout.setEncoding(outputEncodings.stdout);
|
|
390
|
+
if(outputEncodings.stderr!==null)child.stderr.setEncoding(outputEncodings.stderr);
|
|
313
391
|
child.stdout.on('data',chunk=>{
|
|
314
|
-
stdout=appendOutput(stdout,chunk);
|
|
392
|
+
if(stdout!==null)stdout=appendOutput(stdout,chunk);
|
|
315
393
|
ownDelivery(child.stdout,'process.stdout',chunk);
|
|
316
394
|
});
|
|
317
395
|
child.stderr.on('data',chunk=>{
|
|
318
|
-
stderr=appendOutput(stderr,chunk);
|
|
396
|
+
if(stderr!==null)stderr=appendOutput(stderr,chunk);
|
|
319
397
|
ownDelivery(child.stderr,'process.stderr',chunk);
|
|
320
398
|
});
|
|
321
399
|
child.on('error',error=>{
|
|
@@ -335,6 +413,7 @@ export async function runProcess(command,args=[],{
|
|
|
335
413
|
signal?.removeEventListener('abort',abort);
|
|
336
414
|
clearInterval(heartbeat);
|
|
337
415
|
void (async()=>{
|
|
416
|
+
await drainInput();
|
|
338
417
|
await drainDeliveries();
|
|
339
418
|
if(settlementStarted)return;
|
|
340
419
|
const result={
|
|
@@ -359,7 +438,7 @@ export async function runProcess(command,args=[],{
|
|
|
359
438
|
});
|
|
360
439
|
}
|
|
361
440
|
if(cancellationError)cancellationError.details=result;
|
|
362
|
-
finish(reject,events.error??inputError??cancellationError??new ArcaneError(
|
|
441
|
+
finish(reject,events.error??inputError??outputErrors[0]??cancellationError??new ArcaneError(
|
|
363
442
|
ERROR_CODES.operationFailed,
|
|
364
443
|
`Stopped ${command} after its event callback failed.`
|
|
365
444
|
));
|
|
@@ -379,6 +458,10 @@ export async function runProcess(command,args=[],{
|
|
|
379
458
|
{cause:inputError,details:result}));
|
|
380
459
|
return;
|
|
381
460
|
}
|
|
461
|
+
if(outputErrors.length){
|
|
462
|
+
finish(reject,outputErrors[0]);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
382
465
|
await events.enqueue({
|
|
383
466
|
type:'process.completed',
|
|
384
467
|
message:`${command} exited with code ${String(result.code)}.`,
|
|
@@ -401,7 +484,30 @@ export async function runProcess(command,args=[],{
|
|
|
401
484
|
abort();
|
|
402
485
|
}
|
|
403
486
|
|
|
404
|
-
if(
|
|
487
|
+
if(input&&is.function(input[Symbol.asyncIterator])){
|
|
488
|
+
child.stdin.on('error',error=>{inputError=error;stopTree();});
|
|
489
|
+
inputPump=(async function pumpInput(){
|
|
490
|
+
try{
|
|
491
|
+
inputIterator=input[Symbol.asyncIterator]();
|
|
492
|
+
while(!terminationRequested&&!childClosed){
|
|
493
|
+
const next=await inputIterator.next();
|
|
494
|
+
if(next.done){inputDone=true;break;}
|
|
495
|
+
if(terminationRequested||childClosed)break;
|
|
496
|
+
await new Promise(function writeChunk(resolve,reject){
|
|
497
|
+
child.stdin.write(next.value,function written(error){
|
|
498
|
+
if(error)reject(error);
|
|
499
|
+
else resolve();
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
child.stdin.end();
|
|
504
|
+
}catch(error){
|
|
505
|
+
inputError=inputError&&inputError!==error
|
|
506
|
+
?new AggregateError([inputError,error],'Process input failed.'):error;
|
|
507
|
+
stopTree();
|
|
508
|
+
}
|
|
509
|
+
})();
|
|
510
|
+
}else if(cancellationMode==='close-input'){
|
|
405
511
|
child.stdin.on('error',error=>{inputError=error;stopTree();});
|
|
406
512
|
if(input!==undefined&&!terminationRequested)child.stdin.write(input);
|
|
407
513
|
}else if(input!==undefined){
|
package/src/scaffold.mjs
CHANGED
|
@@ -44,7 +44,7 @@ function validateInputs(appId,displayName){
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function validateScaffoldTarget(target){
|
|
47
|
-
const targets=['browser','portable','windows-x64','linux-x64','linux-arm64','android-arm64'];
|
|
47
|
+
const targets=['browser','portable','windows-x64','macos-arm64','macos-x64','linux-x64','linux-arm64','android-arm64'];
|
|
48
48
|
if(!targets.includes(target)){
|
|
49
49
|
fail(`Invalid scaffold target: ${String(target)}. Expected one of ${targets.join(', ')}.`,'ARCANE_USAGE');
|
|
50
50
|
}
|
package/src/targets/index.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const DEFINITIONS=[
|
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
id:'portable',displayName:'Portable native application',status:'pairing-required',
|
|
22
|
-
platforms:['windows','linux'],architectures:['x64','arm64'],formats:['portable'],
|
|
22
|
+
platforms:['windows','linux','macos'],architectures:['x64','arm64'],formats:['portable'],
|
|
23
23
|
signingModes:['unsigned-local-test'],
|
|
24
24
|
reason:'Portable app payloads use the SDK package provider by default; explicit provider pairing remains available.'
|
|
25
25
|
},
|
|
@@ -28,6 +28,16 @@ const DEFINITIONS=[
|
|
|
28
28
|
platforms:['windows'],architectures:['x64'],formats:['exe'],signingModes:['unsigned-local-test'],
|
|
29
29
|
reason:'Windows output requires an Arcane OS native provider.'
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
id:'macos-arm64',displayName:'macOS Apple silicon application',status:'pairing-required',
|
|
33
|
+
platforms:['macos'],architectures:['arm64'],formats:['app'],signingModes:['unsigned-local-test'],
|
|
34
|
+
reason:'macOS output uses the SDK provider with the selected architecture host asset.'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id:'macos-x64',displayName:'macOS Intel application',status:'pairing-required',
|
|
38
|
+
platforms:['macos'],architectures:['x64'],formats:['app'],signingModes:['unsigned-local-test'],
|
|
39
|
+
reason:'macOS output uses the SDK provider with the selected architecture host asset.'
|
|
40
|
+
},
|
|
31
41
|
{
|
|
32
42
|
id:'linux-x64',displayName:'Linux x64 executable',status:'pairing-required',
|
|
33
43
|
platforms:['linux'],architectures:['x64'],formats:['deb'],signingModes:['unsigned-local-test'],
|
|
@@ -84,7 +84,7 @@ export function workspaceTemplate({
|
|
|
84
84
|
||(!canonicalSpecifier&&!aliasSpecifier)){
|
|
85
85
|
throw new Error('Invalid scaffold SDK installation authority.');
|
|
86
86
|
}
|
|
87
|
-
const supportedTargets=['browser','portable','windows-x64','linux-x64','linux-arm64','android-arm64'];
|
|
87
|
+
const supportedTargets=['browser','portable','windows-x64','macos-arm64','macos-x64','linux-x64','linux-arm64','android-arm64'];
|
|
88
88
|
if(!supportedTargets.includes(target)){
|
|
89
89
|
throw new Error(`Unsupported scaffold target: ${String(target)}.`);
|
|
90
90
|
}
|
|
@@ -105,17 +105,18 @@ export function workspaceTemplate({
|
|
|
105
105
|
const packageInclude=[`${appId}.css`,'index.html'];
|
|
106
106
|
if(native)packageInclude.push('img/icon.png');
|
|
107
107
|
packageInclude.push('manifest.json','modules');
|
|
108
|
+
const macosTarget=target==='macos-arm64'||target==='macos-x64';
|
|
108
109
|
const nativeGuide=native?`
|
|
109
110
|
## ${target} native target
|
|
110
111
|
|
|
111
112
|
This scaffold declares both the browser and ${target} targets and includes the
|
|
112
113
|
required raster application icon.
|
|
113
|
-
Pair it with one explicit Arcane OS checkout:
|
|
114
|
+
${macosTarget?'Use the installed SDK provider and its matching macOS release host asset:':'Pair it with one explicit Arcane OS checkout:'}
|
|
114
115
|
|
|
115
116
|
\`\`\`sh
|
|
116
|
-
npm exec -- arcane native-doctor --target ${target} --arcane-root "<path-to-Arcane-OS>"
|
|
117
|
-
npm run build -- --arcane-root "<path-to-Arcane-OS>"
|
|
118
|
-
${target==='portable'?'':`npm run run -- --arcane-root "<path-to-Arcane-OS>"\n`}
|
|
117
|
+
npm exec -- arcane native-doctor --target ${target}${macosTarget?'':' --arcane-root "<path-to-Arcane-OS>"'}
|
|
118
|
+
npm run build${macosTarget?'':' -- --arcane-root "<path-to-Arcane-OS>"'}
|
|
119
|
+
${target==='portable'?'':`npm run run${macosTarget?'':' -- --arcane-root "<path-to-Arcane-OS>"'}\n`}
|
|
119
120
|
\`\`\`
|
|
120
121
|
|
|
121
122
|
The selected Arcane provider must support this target or the command fails without
|
package/src/toolchain.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Is from 'strong-type';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {resolveAppRoot,resolvePackageOutputRoot} from './app-layout.mjs';
|
|
4
|
-
import {readdir,lstat,realpath,mkdir,readFile,writeFile} from 'node:fs/promises';
|
|
4
|
+
import {readdir,lstat,realpath,mkdir,readFile,writeFile,unlink} from 'node:fs/promises';
|
|
5
5
|
import {createWorkspace,initWorkspace} from './scaffold.mjs';
|
|
6
6
|
import {
|
|
7
7
|
discoverApps as discoverWorkspaceApps,
|
|
@@ -12,8 +12,14 @@ import {
|
|
|
12
12
|
} from './workspace.mjs';
|
|
13
13
|
import {loadArcaneIntegratedProvider} from './integrated-provider-loader.mjs';
|
|
14
14
|
import {collectSourcePwaAssets,startDevServer} from './dev-server.mjs';
|
|
15
|
-
import {applyPwaEntryReferences,generateImportMap,readApplicationTestImportMapContext} from './import-map.mjs';
|
|
16
|
-
import {
|
|
15
|
+
import {applyPwaEntryReferences,generateImportMap,readApplicationTestImportMapContext,removePwaEntryReferences} from './import-map.mjs';
|
|
16
|
+
import {
|
|
17
|
+
createPwaArtifacts,
|
|
18
|
+
PWA_MANIFEST_NAME,
|
|
19
|
+
PWA_OFFLINE_MANIFEST_NAME,
|
|
20
|
+
PWA_WORKER_NAME,
|
|
21
|
+
PWA_BOOTSTRAP_NAME
|
|
22
|
+
} from './pwa.mjs';
|
|
17
23
|
import {readInstalledSdkLayout} from './sdk-runtime-layout.mjs';
|
|
18
24
|
import {withWorkspaceOperationLock} from './workspace-operation-lock.mjs';
|
|
19
25
|
import {refreshAppPackageProjection} from './app-descriptor.mjs';
|
|
@@ -293,7 +299,7 @@ async function validatePreparedRuntime(prepared,{signal}={}){
|
|
|
293
299
|
return validation;
|
|
294
300
|
}
|
|
295
301
|
|
|
296
|
-
async function refreshPreparedImportMap(prepared,{signal,onEvent,workspaceOperationLease}={}){
|
|
302
|
+
async function refreshPreparedImportMap(prepared,{signal,onEvent,workspaceOperationLease}={},retireGeneratedPwa=false){
|
|
297
303
|
const manifest=prepared.validation.app.manifest;
|
|
298
304
|
const inspected=await inspectPackagedApp({
|
|
299
305
|
workspaceRoot:prepared.workspaceRoot,
|
|
@@ -322,12 +328,12 @@ async function refreshPreparedImportMap(prepared,{signal,onEvent,workspaceOperat
|
|
|
322
328
|
onEvent
|
|
323
329
|
});
|
|
324
330
|
if(prepared.validation.config.appsRoot==='.'){
|
|
325
|
-
await refreshRootApplicationFiles(prepared,inspected,importMap,{signal,onEvent});
|
|
331
|
+
await refreshRootApplicationFiles(prepared,inspected,importMap,{signal,onEvent,retireGeneratedPwa});
|
|
326
332
|
}
|
|
327
333
|
return importMap;
|
|
328
334
|
}
|
|
329
335
|
|
|
330
|
-
async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,onEvent}){
|
|
336
|
+
async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,onEvent,retireGeneratedPwa=false}){
|
|
331
337
|
const {workspaceRoot,appId}=prepared;
|
|
332
338
|
const manifest=prepared.validation.app.manifest;
|
|
333
339
|
const installed=await readInstalledSdkLayout(workspaceRoot,prepared.validation.config);
|
|
@@ -385,21 +391,44 @@ async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,
|
|
|
385
391
|
await mkdir(path.dirname(filePath),{recursive:true});
|
|
386
392
|
await writeFile(filePath,file.content,'utf8');
|
|
387
393
|
}
|
|
388
|
-
|
|
394
|
+
const pwaDisabled=manifest.pwa?.enabled===false;
|
|
395
|
+
const retireConfirmedOutput=pwaDisabled&&retireGeneratedPwa===true;
|
|
396
|
+
if(pwa||pwaDisabled){
|
|
389
397
|
for(const document of inspected.browserDocuments){
|
|
390
398
|
throwIfAborted(signal);
|
|
391
399
|
const filePath=path.join(prepared.appRoot,...document.path.split('/'));
|
|
392
400
|
const content=await readFile(filePath,'utf8');
|
|
393
|
-
|
|
401
|
+
const updated=pwa?applyPwaEntryReferences(content,{
|
|
394
402
|
manifestUrl:`/${pwa.entryAssets.manifest}`,
|
|
395
|
-
bootstrapUrl:`/${pwa.entryAssets.bootstrap}
|
|
396
|
-
|
|
403
|
+
bootstrapUrl:`/${pwa.entryAssets.bootstrap}`,
|
|
404
|
+
markGeneratedManifest:true
|
|
405
|
+
}):removePwaEntryReferences(content,{
|
|
406
|
+
documentUrl:new URL(`/${document.path.split('/').map(encodeURIComponent).join('/')}`,sourceOrigin),
|
|
407
|
+
manifestUrl:`/${PWA_MANIFEST_NAME}`,
|
|
408
|
+
markedManifestOnly:!retireConfirmedOutput
|
|
409
|
+
});
|
|
410
|
+
if(updated!==content)await writeFile(filePath,updated,'utf8');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const retiredPwa=[];
|
|
414
|
+
if(retireConfirmedOutput){
|
|
415
|
+
// The caller declares these standard outputs generated, not merely same-named authored files.
|
|
416
|
+
// Remove selected references before retiring their targets; browser storage is untouched.
|
|
417
|
+
for(const relative of [PWA_MANIFEST_NAME,PWA_OFFLINE_MANIFEST_NAME,PWA_WORKER_NAME,PWA_BOOTSTRAP_NAME]){
|
|
418
|
+
throwIfAborted(signal);
|
|
419
|
+
try{
|
|
420
|
+
await unlink(path.join(workspaceRoot,relative));
|
|
421
|
+
retiredPwa.push(relative);
|
|
422
|
+
}catch(error){
|
|
423
|
+
if(error.code!=='ENOENT')throw error;
|
|
424
|
+
}
|
|
397
425
|
}
|
|
398
426
|
}
|
|
399
427
|
await emit(onEvent,{
|
|
400
428
|
type:'import-map.root-files.completed',appId,
|
|
401
429
|
navigation:[],
|
|
402
|
-
pwa:pwa?.files.map(file=>file.path)??[]
|
|
430
|
+
pwa:pwa?.files.map(file=>file.path)??[],
|
|
431
|
+
retiredPwa
|
|
403
432
|
});
|
|
404
433
|
}
|
|
405
434
|
|
|
@@ -408,7 +437,7 @@ async function importMapApplication(options={}){
|
|
|
408
437
|
const prepared=await preparedWorkspace({...options,allowMissingManagedImportMap:true});
|
|
409
438
|
const importMap=await ownedWork(
|
|
410
439
|
'import-map',
|
|
411
|
-
context=>refreshPreparedImportMap(prepared,context),
|
|
440
|
+
context=>refreshPreparedImportMap(prepared,context,options.retireGeneratedPwa===true),
|
|
412
441
|
{...options,workspaceRoot:prepared.workspaceRoot}
|
|
413
442
|
);
|
|
414
443
|
await validatePreparedRuntime(prepared,{signal:options.signal});
|
|
@@ -868,7 +897,7 @@ export function assertIntegratedNativeToolchain({
|
|
|
868
897
|
workspaceMode,workspaceRoot,toolchainRoot,target,providerSource='arcane-checkout'
|
|
869
898
|
}={}){
|
|
870
899
|
if(workspaceMode!=='integrated')return;
|
|
871
|
-
if(providerSource==='sdk-package'&&
|
|
900
|
+
if(providerSource==='sdk-package'&&['portable','windows-x64','macos-arm64','macos-x64'].includes(target))return;
|
|
872
901
|
if(!is.string(toolchainRoot)||!sameCanonicalPath(workspaceRoot,toolchainRoot)){
|
|
873
902
|
throw new ArcaneError(
|
|
874
903
|
ERROR_CODES.policyDenied,
|