arcane-os 0.26.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/src/workspace.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import Is from 'strong-type';
2
+ import {appRelativeRoot, resolveAppRoot, appBaseHref} from './app-layout.mjs';
2
3
  import {readFile,readdir,realpath,stat} from 'node:fs/promises';
3
4
  import path from 'node:path';
4
5
  import {
@@ -228,6 +229,25 @@ async function discoverAppsInRoot(root,config,appId){
228
229
  if(appId!==undefined&&(!is.string(appId)||!APP_ID_PATTERN.test(appId))){
229
230
  fail(`Invalid app id: ${String(appId)}.`,'ARCANE_USAGE');
230
231
  }
232
+ async function readApp(appRoot,id,manifest){
233
+ const configPath=path.join(appRoot,APP_CONFIG_NAME);
234
+ manifest??=await readJson(configPath,`${appRelativeRoot(config,id) || '.'}/${APP_CONFIG_NAME}`);
235
+ const validatedManifest=validatePackagerAppConfig(manifest,id,config,configPath);
236
+ if(!validatedManifest.shared.includes('browser-runtime')){
237
+ fail(`${configPath} must include the browser-runtime shared payload.`);
238
+ }
239
+ const descriptor=await loadAppDescriptor({workspaceRoot:root,appRoot,appId:id,packageManifest:manifest});
240
+ return completeValue({
241
+ appId:id,appRoot,manifest:validatedManifest,
242
+ descriptor:descriptor.descriptor,descriptorSource:descriptor.source,
243
+ descriptorPath:descriptor.descriptorPath
244
+ });
245
+ }
246
+ if(config.appsRoot==='.'){
247
+ const manifest=await readJson(path.join(root,APP_CONFIG_NAME),APP_CONFIG_NAME);
248
+ if(appId!==undefined&&appId!==manifest.id)return [];
249
+ return [await readApp(root,manifest.id,manifest)];
250
+ }
231
251
  const appsRoot=path.join(root,'apps');
232
252
  await assertRealDirectory(appsRoot,'Workspace apps root');
233
253
  const entries=await readdir(appsRoot,{withFileTypes:true});
@@ -240,26 +260,7 @@ async function discoverAppsInRoot(root,config,appId){
240
260
  if(!entry.isDirectory())continue;
241
261
  const appRoot=path.join(appsRoot,entry.name);
242
262
  try{
243
- const configPath=path.join(appRoot,APP_CONFIG_NAME);
244
- const manifest=await readJson(configPath,`apps/${entry.name}/${APP_CONFIG_NAME}`);
245
- const validatedManifest=validatePackagerAppConfig(manifest,entry.name,config,configPath);
246
- if(!validatedManifest.shared.includes('browser-runtime')){
247
- fail(`apps/${entry.name}/${APP_CONFIG_NAME} must include the browser-runtime shared payload.`);
248
- }
249
- const descriptor=await loadAppDescriptor({
250
- workspaceRoot:root,
251
- appRoot,
252
- appId:entry.name,
253
- packageManifest:manifest
254
- });
255
- apps.push(completeValue({
256
- appId:entry.name,
257
- appRoot,
258
- manifest:validatedManifest,
259
- descriptor:descriptor.descriptor,
260
- descriptorSource:descriptor.source,
261
- descriptorPath:descriptor.descriptorPath
262
- }));
263
+ apps.push(await readApp(appRoot,entry.name));
263
264
  }catch(error){
264
265
  if(!String(error?.message).includes('does not exist'))throw error;
265
266
  }
@@ -294,7 +295,7 @@ export async function selectApp(workspaceRoot=process.cwd(),appId){
294
295
  if(!selected)fail(`Unknown app "${appId}".`);
295
296
  return selected;
296
297
  }
297
- if(apps.length===0)fail('No Arcane applications were found under apps/.');
298
+ if(apps.length===0)fail('No Arcane applications were found at the configured appsRoot.');
298
299
  if(apps.length>1){
299
300
  fail(`This workspace contains multiple apps; select one explicitly: ${apps.map(app=>app.appId).join(', ')}.`,'ARCANE_USAGE');
300
301
  }
@@ -313,7 +314,7 @@ export async function resolveWorkspace({workspaceRoot=process.cwd(),appId}={}){
313
314
  }else if(apps.length===1){
314
315
  [app]=apps;
315
316
  }else if(apps.length===0){
316
- fail('No Arcane applications were found under apps/.');
317
+ fail('No Arcane applications were found at the configured appsRoot.');
317
318
  }else{
318
319
  fail(`This workspace contains multiple apps; select one explicitly: ${apps.map(item=>item.appId).join(', ')}.`,'ARCANE_USAGE');
319
320
  }
@@ -363,39 +364,43 @@ export async function resolveInstalledSdkInstallation(workspaceRoot,declaration)
363
364
 
364
365
  function assertHtmlContract(source,appId,{
365
366
  entry='index.html',
367
+ appPath=`apps/${appId}`,
368
+ baseHref='../../',
369
+ runtimePath='arcane',
366
370
  strictStyles=true,
367
371
  allowMissingManagedImportMap=false
368
372
  }={}){
369
- const entryLabel=`apps/${appId}/${entry}`;
373
+ const entryLabel=[appPath,entry].filter(Boolean).join('/');
370
374
  const htmlContract=inspectImportMapHtml(source);
371
375
  const appIdMetadata=htmlContract.metas.filter(meta=>meta.name==='arcane-app-id');
372
376
  if(appIdMetadata.length!==1||appIdMetadata[0].content!==appId){
373
377
  fail(`${entryLabel} must declare exactly one active matching arcane-app-id metadata element.`);
374
378
  }
375
- if(htmlContract.bases.length!==1||htmlContract.bases[0].href!=='../../'){
376
- fail(`${entryLabel} must declare exactly one active <base href="../../">.`);
379
+ if(htmlContract.bases.length!==1||htmlContract.bases[0].href!==baseHref){
380
+ fail(`${entryLabel} must declare exactly one active <base href="${baseHref}">.`);
377
381
  }
378
- const resourcePath=value=>value.split(/[?#]/u,1)[0];
382
+ const resourcePath=value=>value.split(/[?#]/u,1)[0].replace(/^\.\//u,'/');
379
383
  const styles=htmlContract.links.filter(link=>link.rel
380
384
  .split(/[\t\n\f\r ]+/u).includes('stylesheet'));
381
385
  const positionOfStyle=expected=>styles.find(link=>resourcePath(link.href)===expected)?.start??-1;
382
- const theme=positionOfStyle('./arcane/css/theme.css');
383
- const primitives=positionOfStyle('./arcane/css/primitives.css');
384
- const escapedAppId=appId.replace(/[.*+?^${}()|[\]\\]/gu,'\\$&');
386
+ const theme=positionOfStyle(`/${runtimePath}/css/theme.css`);
387
+ const primitives=positionOfStyle(`/${runtimePath}/css/primitives.css`);
388
+ const appPrefix=appPath?`${appPath}/`:'';
389
+ const escapedAppPrefix=appPrefix.replace(/[.*+?^${}()|[\]\\]/gu,'\\$&');
385
390
  const appStyle=styles.find(link=>new RegExp(
386
- `^(?:\\./|/)apps/${escapedAppId}/[^/]+\\.css$`,
391
+ `^(?:\\./|/)${escapedAppPrefix}[^/]+\\.css$`,
387
392
  'u'
388
393
  ).test(resourcePath(link.href)))?.start??-1;
389
394
  const modules=htmlContract.scripts.filter(script=>script.type==='module'&&script.src);
390
395
  const bootstrap=modules.find(script=>resourcePath(script.src)
391
- ==='./arcane/modules/ThemeBootstrap.js')?.start??-1;
396
+ ===`/${runtimePath}/modules/ThemeBootstrap.js`)?.start??-1;
392
397
  if(htmlContract.managedMaps.length>1){
393
398
  fail(`${entryLabel} must contain at most one active managed Arcane import map.`);
394
399
  }
395
400
  const managedImportMap=htmlContract.managedMaps[0]?.start??-1;
396
401
  const firstModule=htmlContract.firstModulePosition;
397
402
  const appModule=modules.find(script=>new RegExp(
398
- `^(?:\\./|/)apps/${escapedAppId}/.+\\.(?:js|mjs)$`,
403
+ `^(?:\\./|/)${escapedAppPrefix}(?!arcane/|node_modules/).+\\.(?:js|mjs)$`,
399
404
  'u'
400
405
  ).test(resourcePath(script.src)))?.start??-1;
401
406
  if(theme<0){
@@ -444,12 +449,6 @@ export async function validateDiscoveredApplication({
444
449
  fail('A discovered Arcane application is required for focused validation.');
445
450
  }
446
451
  const canonicalWorkspaceRoot=await realpath(workspaceRoot);
447
- await assertRealDirectory(path.join(canonicalWorkspaceRoot,'apps'),'Workspace apps root');
448
- const expectedAppRoot=path.join(canonicalWorkspaceRoot,'apps',app.appId);
449
- if(!sameDirectoryPath(app.appRoot,expectedAppRoot)){
450
- fail(`Discovered app ${app.appId} does not belong to the selected workspace.`);
451
- }
452
- const canonicalAppRoot=await realpath(app.appRoot);
453
452
  let config=workspaceConfig;
454
453
  if(!config){
455
454
  const profile=await inspectWorkspaceProfile(canonicalWorkspaceRoot);
@@ -461,6 +460,12 @@ export async function validateDiscoveredApplication({
461
460
  if(!isObject(config?.sharedPayloads)){
462
461
  fail('The selected Arcane workspace configuration is unavailable for focused validation.');
463
462
  }
463
+ const expectedAppRoot=resolveAppRoot(canonicalWorkspaceRoot,config,app.appId);
464
+ await assertRealDirectory(expectedAppRoot,'Selected application root');
465
+ if(!sameDirectoryPath(app.appRoot,expectedAppRoot)){
466
+ fail(`Discovered app ${app.appId} does not belong to the selected workspace.`);
467
+ }
468
+ const canonicalAppRoot=await realpath(app.appRoot);
464
469
  const configPath=path.join(canonicalAppRoot,APP_CONFIG_NAME);
465
470
  const rawManifest=await readJson(
466
471
  configPath,
@@ -492,6 +497,9 @@ export async function validateDiscoveredApplication({
492
497
  }
493
498
  assertHtmlContract(await readFile(entryPath,'utf8'),app.appId,{
494
499
  entry:manifest.entry,
500
+ appPath:appRelativeRoot(config,app.appId),
501
+ baseHref:appBaseHref(canonicalWorkspaceRoot,canonicalAppRoot,manifest.entry),
502
+ runtimePath:config.sharedPayloads['browser-runtime'][0].destination,
495
503
  strictStyles:workspaceMode==='external',
496
504
  allowMissingManagedImportMap
497
505
  });