arcane-os 0.1.0-dev.5 → 0.1.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/src/scaffold.mjs CHANGED
@@ -1,7 +1,11 @@
1
1
  import {lstat,mkdir,readFile,readdir,writeFile} from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import {spawn} from 'node:child_process';
4
- import {loadRuntimeRelease} from './runtime.mjs';
4
+ import {verifyRuntime} from './runtime.mjs';
5
+ import {verifySdkBrowserRuntime} from './sdk-browser-runtime.mjs';
6
+ import {materializeWorkspaceRuntime} from './workspace-runtime.mjs';
7
+ import {generateImportMap} from './import-map.mjs';
8
+ import {withWorkspaceOperationLock} from './workspace-operation-lock.mjs';
5
9
  import {SDK_NAME,SDK_VERSION,workspaceTemplate} from './templates/workspace-template.mjs';
6
10
  import {inspectWorkspaceProfile} from './workspace.mjs';
7
11
  import {parseSemver} from './packager/core.mjs';
@@ -259,6 +263,29 @@ async function writeMissingFiles(workspaceRoot,files,{signal,onEvent}){
259
263
  return {createdFiles,skippedFiles};
260
264
  }
261
265
 
266
+ async function assertExistingLockAdmission(workspaceRoot,files){
267
+ const expected=files.get('arcane.lock.json');
268
+ if(typeof expected!=='string')return;
269
+ const lockPath=path.join(workspaceRoot,'arcane.lock.json');
270
+ let info;
271
+ try{
272
+ info=await lstat(lockPath);
273
+ }catch(error){
274
+ if(error?.code==='ENOENT')return;
275
+ throw error;
276
+ }
277
+ if(info.isSymbolicLink()||!info.isFile()){
278
+ fail('Existing arcane.lock.json must be a real file.');
279
+ }
280
+ if(await readFile(lockPath,'utf8')!==expected){
281
+ fail(
282
+ 'Existing arcane.lock.json does not match the authenticated SDK runtime '
283
+ +'admission. Remove or reconcile it before running arcane init; Arcane never '
284
+ +'overwrites a lock implicitly.'
285
+ );
286
+ }
287
+ }
288
+
262
289
  async function runGitInit(workspaceRoot,signal,onEvent){
263
290
  throwIfAborted(signal);
264
291
  await emit(onEvent,{type:'scaffold.git.started',command:'git init -b main'});
@@ -313,17 +340,54 @@ export async function createWorkspace({
313
340
  const workspaceRoot=path.resolve(targetPath);
314
341
  await emit(onEvent,{type:'scaffold.started',mode:'create',workspaceRoot,appId,target});
315
342
  await assertCreateTarget(workspaceRoot);
316
- const runtimeRelease=await loadRuntimeRelease();
343
+ const receipt=await withWorkspaceOperationLock({
344
+ workspaceRoot,
345
+ operation:'scaffold-create',
346
+ signal,
347
+ onEvent
348
+ },async workspaceOperationLease=>{
349
+ const runtimeReceipt=await verifyRuntime({signal,onEvent});
350
+ const sdkBrowserRuntimeReceipt=await verifySdkBrowserRuntime({signal,onEvent});
317
351
  const template=workspaceTemplate({
318
352
  appId,
319
353
  displayName,
320
- runtimeRelease,
354
+ runtimeRelease:runtimeReceipt,
355
+ sdkBrowserRuntimeRelease:sdkBrowserRuntimeReceipt,
321
356
  target,
322
357
  appIcon:await scaffoldIcon(target)
323
358
  });
324
359
  const result=await writeMissingFiles(workspaceRoot,template.files,{signal,onEvent});
360
+ const workspaceRuntimeReceipt=await materializeWorkspaceRuntime({
361
+ workspaceRoot,
362
+ runtimeRoot:runtimeReceipt.canonicalLocation,
363
+ runtimeReceipt,
364
+ browserRuntimeRoot:sdkBrowserRuntimeReceipt.canonicalLocation,
365
+ sdkBrowserRuntimeReceipt,
366
+ signal,
367
+ onEvent
368
+ });
369
+ const importMap=await generateImportMap({
370
+ workspaceRoot,
371
+ appId,
372
+ appRoot:path.join(workspaceRoot,'apps',appId),
373
+ workspaceRuntimeReceipt,
374
+ workspaceOperationLease,
375
+ signal,
376
+ onEvent
377
+ });
325
378
  if(initializeGit)await runGitInit(workspaceRoot,signal,onEvent);
326
- const receipt={workspaceRoot,appId,displayName:template.name,target,...result,gitInitialized:Boolean(initializeGit)};
379
+ const receipt={
380
+ workspaceRoot,
381
+ appId,
382
+ displayName:template.name,
383
+ target,
384
+ ...result,
385
+ workspaceRuntime:workspaceRuntimeReceipt,
386
+ importMap,
387
+ gitInitialized:Boolean(initializeGit)
388
+ };
389
+ return receipt;
390
+ });
327
391
  await emit(onEvent,{type:'scaffold.completed',...receipt});
328
392
  return receipt;
329
393
  }
@@ -342,13 +406,28 @@ export async function initWorkspace({
342
406
  const resolvedRoot=path.resolve(workspaceRoot);
343
407
  await emit(onEvent,{type:'scaffold.started',mode:'init',workspaceRoot:resolvedRoot,appId,target});
344
408
  await assertInitTarget(resolvedRoot);
409
+ const receipt=await withWorkspaceOperationLock({
410
+ workspaceRoot:resolvedRoot,
411
+ operation:'scaffold-init',
412
+ signal,
413
+ onEvent
414
+ },async workspaceOperationLease=>{
345
415
  const profile=await existingWorkspaceProfile(resolvedRoot);
346
416
  const workspaceMode=profile?.workspaceMode??'external';
417
+ const legacyIntegrated=workspaceMode==='integrated'
418
+ &&profile.config.browserRuntimeLayout==='integrated-legacy';
419
+ const runtimeReceipt=workspaceMode==='external'
420
+ ?await verifyRuntime({signal,onEvent})
421
+ :null;
422
+ const sdkBrowserRuntimeReceipt=workspaceMode==='external'
423
+ ?await verifySdkBrowserRuntime({signal,onEvent})
424
+ :null;
347
425
  const template=workspaceMode==='integrated'
348
426
  ?workspaceTemplate({
349
427
  appId,
350
428
  displayName,
351
429
  appOnly:true,
430
+ namedImports:!legacyIntegrated,
352
431
  minimumCoreVersion:await integratedCoreVersion(resolvedRoot),
353
432
  target,
354
433
  appIcon:await scaffoldIcon(target)
@@ -356,15 +435,49 @@ export async function initWorkspace({
356
435
  :workspaceTemplate({
357
436
  appId,
358
437
  displayName,
359
- runtimeRelease:await loadRuntimeRelease(),
438
+ runtimeRelease:runtimeReceipt,
439
+ sdkBrowserRuntimeRelease:sdkBrowserRuntimeReceipt,
360
440
  target,
361
441
  appIcon:await scaffoldIcon(target)
362
442
  });
363
443
  const packagePlan=workspaceMode==='integrated'
364
444
  ?Object.freeze({exists:true,updated:false})
365
445
  :await prepareExistingPackage(resolvedRoot,template.files);
446
+ if(workspaceMode==='external'){
447
+ await assertExistingLockAdmission(resolvedRoot,template.files);
448
+ }
366
449
  const result=await writeMissingFiles(resolvedRoot,template.files,{signal,onEvent});
367
450
  const packageUpdated=await applyPackageMerge(resolvedRoot,packagePlan,{signal,onEvent});
451
+ const workspaceRuntimeReceipt=workspaceMode==='external'
452
+ ?await materializeWorkspaceRuntime({
453
+ workspaceRoot:resolvedRoot,
454
+ runtimeRoot:runtimeReceipt.canonicalLocation,
455
+ runtimeReceipt,
456
+ browserRuntimeRoot:sdkBrowserRuntimeReceipt.canonicalLocation,
457
+ sdkBrowserRuntimeReceipt,
458
+ signal,
459
+ onEvent
460
+ })
461
+ :null;
462
+ const importMap=legacyIntegrated
463
+ ?Object.freeze({
464
+ appId,
465
+ skipped:true,
466
+ compatibility:'integrated-legacy',
467
+ reason:'The canonical integrated Arcane OS root retains its physical two-route browser runtime.'
468
+ })
469
+ :await generateImportMap({
470
+ workspaceRoot:resolvedRoot,
471
+ appId,
472
+ appRoot:path.join(resolvedRoot,'apps',appId),
473
+ workspaceRuntimeReceipt,
474
+ workspaceOperationLease,
475
+ signal,
476
+ onEvent
477
+ });
478
+ if(legacyIntegrated){
479
+ await emit(onEvent,{type:'import-map.compatibility.skipped',...importMap});
480
+ }
368
481
  const receipt={
369
482
  workspaceRoot:resolvedRoot,
370
483
  workspaceMode,
@@ -373,8 +486,12 @@ export async function initWorkspace({
373
486
  target,
374
487
  ...result,
375
488
  packageUpdated,
489
+ workspaceRuntime:workspaceRuntimeReceipt,
490
+ importMap,
376
491
  gitInitialized:false
377
492
  };
493
+ return receipt;
494
+ });
378
495
  await emit(onEvent,{type:'scaffold.completed',...receipt});
379
496
  return receipt;
380
497
  }