arcane-os 0.1.0-dev.5 → 0.1.1
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/NOTICE +10 -0
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +162 -0
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +69 -0
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +1151 -0
- package/browser-runtime/ai/browser-wasm.mjs +44 -0
- package/browser-runtime/ai/browser-wllama-runtime.mjs +390 -0
- package/browser-runtime/ai/internal/sha256.mjs +166 -0
- package/browser-runtime/ai/model-controller.mjs +581 -0
- package/browser-runtime/ai/wllama/LICENCE +21 -0
- package/browser-runtime/ai/wllama/index.mjs +3494 -0
- package/browser-runtime/ai/wllama/llama.cpp-LICENSE +21 -0
- package/browser-runtime/ai/wllama/wllama.wasm +0 -0
- package/browser-runtime/dependencies/event-pubsub/index.js +141 -0
- package/browser-runtime/dependencies/event-pubsub/licence +21 -0
- package/browser-runtime/dependencies/event-pubsub/package.json +59 -0
- package/browser-runtime/dependencies/strong-type/index.js +1151 -0
- package/browser-runtime/dependencies/strong-type/licence +21 -0
- package/browser-runtime/dependencies/strong-type/package.json +61 -0
- package/browser-runtime/dom-event-instrumentation.mjs +594 -0
- package/browser-runtime/event-manager.mjs +1342 -0
- package/docs/publishing.md +65 -67
- package/docs/reference/README.md +2 -1
- package/docs/reference/cli.md +86 -3
- package/docs/reference/event-manager.md +20 -11
- package/docs/reference/inventory/package-api.json +1 -1
- package/docs/reference/protocols.md +112 -14
- package/docs/reference/sdk-api.md +40 -11
- package/docs/work-amplification.md +4 -3
- package/package.json +15 -8
- package/runtime/ARCANE_RUNTIME_RELEASE.json +1 -1
- package/schemas/arcane-lock.schema.json +97 -1
- package/src/cli/main.mjs +5 -0
- package/src/dev-server.mjs +78 -34
- package/src/doctor.mjs +77 -3
- package/src/import-map.mjs +2352 -0
- package/src/packager/core.mjs +607 -29
- package/src/scaffold.mjs +122 -5
- package/src/sdk-browser-runtime.mjs +702 -0
- package/src/targets/index.mjs +31 -4
- package/src/templates/workspace-template.mjs +141 -23
- package/src/toolchain.mjs +288 -55
- package/src/workspace-operation-lock.mjs +716 -0
- package/src/workspace-runtime.mjs +841 -0
- package/src/workspace.mjs +292 -37
package/src/workspace.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
1
2
|
import {lstat,readFile,readdir,realpath} from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import {
|
|
@@ -9,6 +10,11 @@ import {
|
|
|
9
10
|
SDK_NAME as EXPECTED_SDK_NAME,
|
|
10
11
|
SDK_VERSION as EXPECTED_SDK_VERSION
|
|
11
12
|
} from './constants.mjs';
|
|
13
|
+
import {inspectImportMapHtml} from './import-map.mjs';
|
|
14
|
+
import {
|
|
15
|
+
SDK_BROWSER_RUNTIME_CONTENT_SHA256,
|
|
16
|
+
SDK_BROWSER_RUNTIME_MANIFEST_SHA256
|
|
17
|
+
} from './sdk-browser-runtime.mjs';
|
|
12
18
|
|
|
13
19
|
const APP_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
14
20
|
const SHA256_PATTERN=/^[a-f0-9]{64}$/;
|
|
@@ -65,20 +71,89 @@ async function assertRealDirectory(directory,label){
|
|
|
65
71
|
if(info.isSymbolicLink()||!info.isDirectory())fail(`${label} must be a real directory: ${directory}.`);
|
|
66
72
|
}
|
|
67
73
|
|
|
74
|
+
function sameDirectoryIdentity(left,right){
|
|
75
|
+
return left.device===right.device&&left.inode===right.inode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sameDirectoryPath(left,right){
|
|
79
|
+
const normalize=value=>{
|
|
80
|
+
const resolved=path.resolve(value);
|
|
81
|
+
return process.platform==='win32'?resolved.toLocaleLowerCase('en-US'):resolved;
|
|
82
|
+
};
|
|
83
|
+
return normalize(left)===normalize(right);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function captureRealDirectoryIdentity(directory,label){
|
|
87
|
+
const requested=path.resolve(directory);
|
|
88
|
+
let requestedInfo;
|
|
89
|
+
try{requestedInfo=await lstat(requested,{bigint:true});}
|
|
90
|
+
catch(error){
|
|
91
|
+
if(error?.code==='ENOENT')fail(`${label} does not exist: ${requested}.`);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
if(requestedInfo.isSymbolicLink()||!requestedInfo.isDirectory()){
|
|
95
|
+
fail(`${label} must be a real directory: ${requested}.`);
|
|
96
|
+
}
|
|
97
|
+
const canonical=await realpath(requested);
|
|
98
|
+
const canonicalInfo=await lstat(canonical,{bigint:true});
|
|
99
|
+
const requestedIdentity=Object.freeze({
|
|
100
|
+
device:requestedInfo.dev,
|
|
101
|
+
inode:requestedInfo.ino
|
|
102
|
+
});
|
|
103
|
+
const canonicalIdentity=Object.freeze({
|
|
104
|
+
device:canonicalInfo.dev,
|
|
105
|
+
inode:canonicalInfo.ino
|
|
106
|
+
});
|
|
107
|
+
if(canonicalInfo.isSymbolicLink()||!canonicalInfo.isDirectory()
|
|
108
|
+
||!sameDirectoryIdentity(requestedIdentity,canonicalIdentity)){
|
|
109
|
+
fail(`${label} must resolve to one physical directory: ${requested}.`);
|
|
110
|
+
}
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
requested,
|
|
113
|
+
requestedIdentity,
|
|
114
|
+
canonical,
|
|
115
|
+
identity:canonicalIdentity
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function assertRealDirectoryIdentity(captured,label){
|
|
120
|
+
let canonical;
|
|
121
|
+
let requestedInfo;
|
|
122
|
+
let canonicalInfo;
|
|
123
|
+
try{
|
|
124
|
+
[canonical,requestedInfo,canonicalInfo]=await Promise.all([
|
|
125
|
+
realpath(captured.requested),
|
|
126
|
+
lstat(captured.requested,{bigint:true}),
|
|
127
|
+
lstat(captured.canonical,{bigint:true})
|
|
128
|
+
]);
|
|
129
|
+
}catch(error){
|
|
130
|
+
if(error?.code==='ENOENT'){
|
|
131
|
+
fail(`${label} changed while focused validation was active.`,
|
|
132
|
+
'ARCANE_INTEGRITY_FAILED');
|
|
133
|
+
}
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
const requestedIdentity={device:requestedInfo.dev,inode:requestedInfo.ino};
|
|
137
|
+
const canonicalIdentity={device:canonicalInfo.dev,inode:canonicalInfo.ino};
|
|
138
|
+
if(!sameDirectoryPath(canonical,captured.canonical)
|
|
139
|
+
||requestedInfo.isSymbolicLink()||!requestedInfo.isDirectory()
|
|
140
|
+
||canonicalInfo.isSymbolicLink()||!canonicalInfo.isDirectory()
|
|
141
|
+
||!sameDirectoryIdentity(requestedIdentity,captured.requestedIdentity)
|
|
142
|
+
||!sameDirectoryIdentity(requestedIdentity,captured.identity)
|
|
143
|
+
||!sameDirectoryIdentity(canonicalIdentity,captured.identity)){
|
|
144
|
+
fail(`${label} changed while focused validation was active.`,'ARCANE_INTEGRITY_FAILED');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
68
148
|
function classifyRootConfig(config){
|
|
69
149
|
const validated=validatePackagerRootConfig(config,ROOT_CONFIG_NAME);
|
|
70
150
|
const routes=validated.sharedPayloads['browser-runtime'];
|
|
71
151
|
if(!Array.isArray(routes))fail(`${ROOT_CONFIG_NAME} must define browser-runtime routes.`);
|
|
72
152
|
const external=[
|
|
73
153
|
{
|
|
74
|
-
source:'
|
|
154
|
+
source:'arcane',
|
|
75
155
|
destination:'arcane',
|
|
76
|
-
include:['components','css','entities','img','modules','security']
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
source:'node_modules/arcane-os/runtime/strong-type',
|
|
80
|
-
destination:'node_modules/strong-type',
|
|
81
|
-
include:['index.js','licence','package.json']
|
|
156
|
+
include:['components','css','dependencies','entities','img','modules','sdk','security']
|
|
82
157
|
},
|
|
83
158
|
{
|
|
84
159
|
source:'node_modules/arcane-os',
|
|
@@ -87,6 +162,13 @@ function classifyRootConfig(config){
|
|
|
87
162
|
}
|
|
88
163
|
];
|
|
89
164
|
const integrated=[
|
|
165
|
+
{
|
|
166
|
+
source:'arcane',
|
|
167
|
+
destination:'arcane',
|
|
168
|
+
include:['components','css','dependencies','entities','img','modules','sdk','security']
|
|
169
|
+
}
|
|
170
|
+
];
|
|
171
|
+
const integratedLegacy=[
|
|
90
172
|
{
|
|
91
173
|
source:'arcane',
|
|
92
174
|
destination:'arcane',
|
|
@@ -106,14 +188,20 @@ function classifyRootConfig(config){
|
|
|
106
188
|
&&Array.isArray(route.exclude)&&route.exclude.length===0;
|
|
107
189
|
});
|
|
108
190
|
let workspaceMode;
|
|
191
|
+
let browserRuntimeLayout;
|
|
109
192
|
if(matches(external)){
|
|
110
193
|
workspaceMode='external';
|
|
194
|
+
browserRuntimeLayout='physical-v1';
|
|
111
195
|
}else if(matches(integrated)){
|
|
112
196
|
workspaceMode='integrated';
|
|
197
|
+
browserRuntimeLayout='physical-v1';
|
|
198
|
+
}else if(matches(integratedLegacy)){
|
|
199
|
+
workspaceMode='integrated';
|
|
200
|
+
browserRuntimeLayout='integrated-legacy';
|
|
113
201
|
}else{
|
|
114
202
|
fail(`${ROOT_CONFIG_NAME} browser-runtime routes must match the external SDK or integrated Arcane workspace contract.`);
|
|
115
203
|
}
|
|
116
|
-
return Object.freeze({...validated,workspaceMode});
|
|
204
|
+
return Object.freeze({...validated,workspaceMode,browserRuntimeLayout});
|
|
117
205
|
}
|
|
118
206
|
|
|
119
207
|
async function discoverAppsInRoot(root,config){
|
|
@@ -220,12 +308,63 @@ export async function resolveWorkspace({workspaceRoot=process.cwd(),appId}={}){
|
|
|
220
308
|
}
|
|
221
309
|
|
|
222
310
|
function validateLock(lock){
|
|
223
|
-
|
|
311
|
+
const browser=lock?.sdkBrowserRuntime;
|
|
312
|
+
const browserSource=browser?.source;
|
|
313
|
+
const dependencies=browserSource?.dependencies;
|
|
314
|
+
const exactKeys=(value,keys)=>isObject(value)
|
|
315
|
+
&&Object.keys(value).sort().join('\0')===[...keys].sort().join('\0');
|
|
316
|
+
const expectedDependencies=[
|
|
317
|
+
{
|
|
318
|
+
name:'event-pubsub',
|
|
319
|
+
version:'6.1.0',
|
|
320
|
+
resolved:'https://registry.npmjs.org/event-pubsub/-/event-pubsub-6.1.0.tgz',
|
|
321
|
+
integrity:'sha512-FEMlhTxwqGM0hztTixG6FhVFXqp7Eq1ltk5mSreK6Mhy3xWWpLAzEUR6OMvMdNqT3jgSxA8JDhnhyAG3X4Xy7Q=='
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name:'strong-type',
|
|
325
|
+
version:'2.0.0',
|
|
326
|
+
resolved:'https://registry.npmjs.org/strong-type/-/strong-type-2.0.0.tgz',
|
|
327
|
+
integrity:'sha512-HHrY9qYC7yn+5mlewiI3k9RQM9gZqGQsqbomZcd10Ks0h4RlX01nnkWbCe4AsVPCI6KaFvpkWm1nHMD+Ykup6g=='
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name:'@wllama/wllama',
|
|
331
|
+
version:'3.6.0',
|
|
332
|
+
resolved:'https://registry.npmjs.org/@wllama/wllama/-/wllama-3.6.0.tgz',
|
|
333
|
+
integrity:'sha512-NN3ZBXqaaUwGXTQubkNvsCaLPjN2XVa0bVS40OYCE8zquYmRc2W3oHYEgwvuSWWDB8aUqTLyMioySCXNkcnD1w=='
|
|
334
|
+
}
|
|
335
|
+
];
|
|
336
|
+
const dependenciesMatch=Array.isArray(dependencies)&&dependencies.length===expectedDependencies.length
|
|
337
|
+
&&dependencies.every((actual,index)=>{
|
|
338
|
+
const expected=expectedDependencies[index];
|
|
339
|
+
return exactKeys(actual,['name','version','resolved','integrity'])
|
|
340
|
+
&&actual.name===expected.name&&actual.version===expected.version
|
|
341
|
+
&&actual.resolved===expected.resolved&&actual.integrity===expected.integrity;
|
|
342
|
+
});
|
|
343
|
+
if(!exactKeys(lock,['schemaVersion','sdk','runtime','sdkBrowserRuntime','protocols'])
|
|
344
|
+
||lock.schemaVersion!==1
|
|
345
|
+
||!exactKeys(lock.sdk,['name','version'])
|
|
224
346
|
||lock.sdk.name!==EXPECTED_SDK_NAME||lock.sdk.version!==EXPECTED_SDK_VERSION
|
|
225
|
-
||!
|
|
347
|
+
||!exactKeys(lock.runtime,['manifest','contentSha256','upstreamCommit'])
|
|
348
|
+
||!SHA256_PATTERN.test(lock.runtime.contentSha256)
|
|
226
349
|
||!/^([a-f0-9]{40})$/.test(lock.runtime.upstreamCommit)
|
|
227
350
|
||lock.runtime.manifest!=='node_modules/arcane-os/runtime/ARCANE_RUNTIME_RELEASE.json'
|
|
228
|
-
||!
|
|
351
|
+
||!exactKeys(browser,[
|
|
352
|
+
'manifest','manifestSha256','contentSha256','builder','sdkVersion','source'
|
|
353
|
+
])
|
|
354
|
+
||browser.manifest!=='node_modules/arcane-os/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json'
|
|
355
|
+
||browser.manifestSha256!==SDK_BROWSER_RUNTIME_MANIFEST_SHA256
|
|
356
|
+
||browser.contentSha256!==SDK_BROWSER_RUNTIME_CONTENT_SHA256
|
|
357
|
+
||browser.builder!=='arcane-sdk-browser-runtime-v1'
|
|
358
|
+
||browser.sdkVersion!==EXPECTED_SDK_VERSION
|
|
359
|
+
||!exactKeys(browserSource,[
|
|
360
|
+
'authority','repository','protocol','browserEntry','dependencies'
|
|
361
|
+
])||browserSource.authority!=='arcane-os-sdk'
|
|
362
|
+
||browserSource.repository!=='https://github.com/TheWizardNexus/arcane-os-sdk.git'
|
|
363
|
+
||browserSource.protocol!=='arcane-sdk-browser-runtime/1'
|
|
364
|
+
||browserSource.browserEntry!=='arcane-os/event-manager'
|
|
365
|
+
||!dependenciesMatch
|
|
366
|
+
||!exactKeys(lock.protocols,['arcane','cliEvents','targetAdapter'])
|
|
367
|
+
||lock.protocols.arcane!=='arcane/1'
|
|
229
368
|
||lock.protocols.cliEvents!=='arcane-cli-events/1'
|
|
230
369
|
||lock.protocols.targetAdapter!=='arcane-target-adapter/1'){
|
|
231
370
|
fail('arcane.lock.json is incompatible with this SDK. Run arcane init only after reviewing missing files; existing locks are never overwritten.');
|
|
@@ -233,33 +372,94 @@ function validateLock(lock){
|
|
|
233
372
|
return lock;
|
|
234
373
|
}
|
|
235
374
|
|
|
236
|
-
function
|
|
375
|
+
function sameBrowserRuntimeSource(actual,pinned){
|
|
376
|
+
const exactKeys=(value,keys)=>isObject(value)
|
|
377
|
+
&&Object.keys(value).sort().join('\0')===[...keys].sort().join('\0');
|
|
378
|
+
const keys=['authority','repository','protocol','browserEntry','dependencies'];
|
|
379
|
+
if(!exactKeys(actual,keys)||!exactKeys(pinned,keys)
|
|
380
|
+
||actual.authority!==pinned.authority
|
|
381
|
+
||actual.repository!==pinned.repository
|
|
382
|
+
||actual.protocol!==pinned.protocol
|
|
383
|
+
||actual.browserEntry!==pinned.browserEntry
|
|
384
|
+
||!Array.isArray(actual.dependencies)||!Array.isArray(pinned.dependencies)
|
|
385
|
+
||actual.dependencies.length!==pinned.dependencies.length){
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
const dependencyKeys=['name','version','resolved','integrity'];
|
|
389
|
+
return actual.dependencies.every((dependency,index)=>{
|
|
390
|
+
const expected=pinned.dependencies[index];
|
|
391
|
+
return exactKeys(dependency,dependencyKeys)&&exactKeys(expected,dependencyKeys)
|
|
392
|
+
&&dependency.name===expected.name&&dependency.version===expected.version
|
|
393
|
+
&&dependency.resolved===expected.resolved
|
|
394
|
+
&&dependency.integrity===expected.integrity;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function assertHtmlContract(source,appId,{
|
|
399
|
+
entry='index.html',
|
|
400
|
+
strictStyles=true,
|
|
401
|
+
allowMissingManagedImportMap=false
|
|
402
|
+
}={}){
|
|
237
403
|
const entryLabel=`apps/${appId}/${entry}`;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
const
|
|
404
|
+
const htmlContract=inspectImportMapHtml(source);
|
|
405
|
+
const appIdMetadata=htmlContract.metas.filter(meta=>meta.name==='arcane-app-id');
|
|
406
|
+
if(appIdMetadata.length!==1||appIdMetadata[0].content!==appId){
|
|
407
|
+
fail(`${entryLabel} must declare exactly one active matching arcane-app-id metadata element.`);
|
|
408
|
+
}
|
|
409
|
+
if(htmlContract.bases.length!==1||htmlContract.bases[0].href!=='../../'){
|
|
410
|
+
fail(`${entryLabel} must declare exactly one active <base href="../../">.`);
|
|
411
|
+
}
|
|
412
|
+
const resourcePath=value=>value.split(/[?#]/u,1)[0];
|
|
413
|
+
const styles=htmlContract.links.filter(link=>link.rel
|
|
414
|
+
.split(/[\t\n\f\r ]+/u).includes('stylesheet'));
|
|
415
|
+
const positionOfStyle=expected=>styles.find(link=>resourcePath(link.href)===expected)?.start??-1;
|
|
416
|
+
const theme=positionOfStyle('./arcane/css/theme.css');
|
|
417
|
+
const primitives=positionOfStyle('./arcane/css/primitives.css');
|
|
418
|
+
const escapedAppId=appId.replace(/[.*+?^${}()|[\]\\]/gu,'\\$&');
|
|
419
|
+
const appStyle=styles.find(link=>new RegExp(
|
|
420
|
+
`^(?:\\./|/)apps/${escapedAppId}/[^/]+\\.css$`,
|
|
421
|
+
'u'
|
|
422
|
+
).test(resourcePath(link.href)))?.start??-1;
|
|
423
|
+
const modules=htmlContract.scripts.filter(script=>script.type==='module'&&script.src);
|
|
424
|
+
const bootstrap=modules.find(script=>resourcePath(script.src)
|
|
425
|
+
==='./arcane/modules/ThemeBootstrap.js')?.start??-1;
|
|
426
|
+
if(htmlContract.managedMaps.length>1){
|
|
427
|
+
fail(`${entryLabel} must contain at most one active managed Arcane import map.`);
|
|
428
|
+
}
|
|
429
|
+
const managedImportMap=htmlContract.managedMaps[0]?.start??-1;
|
|
430
|
+
const firstModule=htmlContract.firstModulePosition;
|
|
431
|
+
const appModule=modules.find(script=>new RegExp(
|
|
432
|
+
`^(?:\\./|/)apps/${escapedAppId}/.+\\.(?:js|mjs)$`,
|
|
433
|
+
'u'
|
|
434
|
+
).test(resourcePath(script.src)))?.start??-1;
|
|
251
435
|
if(theme<0){
|
|
252
436
|
fail(`${entryLabel} must load the shared Arcane theme.css.`);
|
|
253
437
|
}
|
|
438
|
+
if(appModule<0){
|
|
439
|
+
fail(`${entryLabel} must load an active app-local module script.`);
|
|
440
|
+
}
|
|
254
441
|
if(strictStyles&&(primitives<=theme||appStyle<=primitives)){
|
|
255
442
|
fail(`${entryLabel} must load theme.css, primitives.css, and app CSS in that order.`);
|
|
256
443
|
}
|
|
257
444
|
if(!strictStyles&&((primitives>=0&&primitives<=theme)||(appStyle>=0&&appStyle<=theme))){
|
|
258
445
|
fail(`${entryLabel} must load shared and app CSS after theme.css.`);
|
|
259
446
|
}
|
|
260
|
-
if(bootstrap
|
|
447
|
+
if(bootstrap>=0&&appModule>=0&&appModule<=bootstrap){
|
|
261
448
|
fail(`${entryLabel} must load ThemeBootstrap.js before app-local module scripts.`);
|
|
262
449
|
}
|
|
450
|
+
if((managedImportMap>=0&&htmlContract.bases[0].end>managedImportMap)
|
|
451
|
+
||(firstModule>=0&&htmlContract.bases[0].end>firstModule)){
|
|
452
|
+
fail(`${entryLabel} must place its base element before import maps and module loads.`);
|
|
453
|
+
}
|
|
454
|
+
if(bootstrap<0&&(
|
|
455
|
+
(managedImportMap<0&&!allowMissingManagedImportMap)
|
|
456
|
+
||(managedImportMap>=0&&appModule>=0&&appModule<=managedImportMap)
|
|
457
|
+
)){
|
|
458
|
+
fail(
|
|
459
|
+
`${entryLabel} must install its managed Arcane import map before app-local `
|
|
460
|
+
+'module scripts when ThemeBootstrap.js is imported by name.'
|
|
461
|
+
);
|
|
462
|
+
}
|
|
263
463
|
}
|
|
264
464
|
|
|
265
465
|
export async function validateDiscoveredApplication({
|
|
@@ -267,19 +467,31 @@ export async function validateDiscoveredApplication({
|
|
|
267
467
|
workspaceMode,
|
|
268
468
|
workspaceConfig,
|
|
269
469
|
app,
|
|
470
|
+
allowMissingManagedImportMap=false,
|
|
270
471
|
signal,
|
|
271
472
|
onEvent
|
|
272
473
|
}={}){
|
|
273
474
|
throwIfAborted(signal);
|
|
274
|
-
if(!app||typeof app.appId!=='string'
|
|
475
|
+
if(!app||typeof app.appId!=='string'||!APP_ID_PATTERN.test(app.appId)
|
|
476
|
+
||typeof app.appRoot!=='string'
|
|
275
477
|
||!app.manifest||!app.descriptor){
|
|
276
478
|
fail('A discovered Arcane application is required for focused validation.');
|
|
277
479
|
}
|
|
278
|
-
const
|
|
480
|
+
const capturedWorkspace=await captureRealDirectoryIdentity(workspaceRoot,'Workspace');
|
|
481
|
+
const canonicalWorkspaceRoot=capturedWorkspace.canonical;
|
|
482
|
+
const capturedAppsRoot=await captureRealDirectoryIdentity(
|
|
483
|
+
path.join(canonicalWorkspaceRoot,'apps'),
|
|
484
|
+
'Workspace apps root'
|
|
485
|
+
);
|
|
279
486
|
const expectedAppRoot=path.join(canonicalWorkspaceRoot,'apps',app.appId);
|
|
280
|
-
|
|
487
|
+
const [capturedExpectedApp,capturedDiscoveredApp]=await Promise.all([
|
|
488
|
+
captureRealDirectoryIdentity(expectedAppRoot,`apps/${app.appId}`),
|
|
489
|
+
captureRealDirectoryIdentity(app.appRoot,`Discovered app ${app.appId}`)
|
|
490
|
+
]);
|
|
491
|
+
if(!sameDirectoryIdentity(capturedExpectedApp.identity,capturedDiscoveredApp.identity)){
|
|
281
492
|
fail(`Discovered app ${app.appId} does not belong to the selected workspace.`);
|
|
282
493
|
}
|
|
494
|
+
const canonicalAppRoot=capturedExpectedApp.canonical;
|
|
283
495
|
let config=workspaceConfig;
|
|
284
496
|
if(!config){
|
|
285
497
|
const profile=await inspectWorkspaceProfile(canonicalWorkspaceRoot);
|
|
@@ -291,7 +503,7 @@ export async function validateDiscoveredApplication({
|
|
|
291
503
|
if(!isObject(config?.sharedPayloads)){
|
|
292
504
|
fail('The selected Arcane workspace configuration is unavailable for focused validation.');
|
|
293
505
|
}
|
|
294
|
-
const configPath=path.join(
|
|
506
|
+
const configPath=path.join(canonicalAppRoot,APP_CONFIG_NAME);
|
|
295
507
|
const rawManifest=await readJson(
|
|
296
508
|
configPath,
|
|
297
509
|
`apps/${app.appId}/${APP_CONFIG_NAME}`
|
|
@@ -302,7 +514,7 @@ export async function validateDiscoveredApplication({
|
|
|
302
514
|
}
|
|
303
515
|
const loadedDescriptor=await loadAppDescriptor({
|
|
304
516
|
workspaceRoot:canonicalWorkspaceRoot,
|
|
305
|
-
appRoot:
|
|
517
|
+
appRoot:canonicalAppRoot,
|
|
306
518
|
appId:app.appId,
|
|
307
519
|
packageManifest:rawManifest
|
|
308
520
|
});
|
|
@@ -315,27 +527,35 @@ export async function validateDiscoveredApplication({
|
|
|
315
527
|
}
|
|
316
528
|
const freshApp=Object.freeze({
|
|
317
529
|
appId:app.appId,
|
|
318
|
-
appRoot:
|
|
530
|
+
appRoot:canonicalAppRoot,
|
|
319
531
|
manifest,
|
|
320
532
|
descriptor,
|
|
321
533
|
descriptorSource:loadedDescriptor.source,
|
|
322
534
|
descriptorPath:loadedDescriptor.descriptorPath
|
|
323
535
|
});
|
|
324
|
-
const entryPath=path.join(
|
|
536
|
+
const entryPath=path.join(canonicalAppRoot,manifest.entry);
|
|
325
537
|
const info=await lstat(entryPath);
|
|
326
538
|
if(info.isSymbolicLink()||!info.isFile()){
|
|
327
539
|
fail(`apps/${app.appId}/${manifest.entry} must be a real file.`);
|
|
328
540
|
}
|
|
329
541
|
assertHtmlContract(await readFile(entryPath,'utf8'),app.appId,{
|
|
330
542
|
entry:manifest.entry,
|
|
331
|
-
strictStyles:workspaceMode==='external'
|
|
543
|
+
strictStyles:workspaceMode==='external',
|
|
544
|
+
allowMissingManagedImportMap
|
|
332
545
|
});
|
|
546
|
+
const assertCapturedDirectories=()=>Promise.all([
|
|
547
|
+
assertRealDirectoryIdentity(capturedWorkspace,'Workspace'),
|
|
548
|
+
assertRealDirectoryIdentity(capturedAppsRoot,'Workspace apps root'),
|
|
549
|
+
assertRealDirectoryIdentity(capturedExpectedApp,`apps/${app.appId}`),
|
|
550
|
+
assertRealDirectoryIdentity(capturedDiscoveredApp,`Discovered app ${app.appId}`)
|
|
551
|
+
]);
|
|
552
|
+
await assertCapturedDirectories();
|
|
333
553
|
const receipt=Object.freeze({
|
|
334
554
|
valid:true,
|
|
335
555
|
workspaceRoot:canonicalWorkspaceRoot,
|
|
336
556
|
workspaceMode,
|
|
337
557
|
appId:app.appId,
|
|
338
|
-
appRoot:
|
|
558
|
+
appRoot:canonicalAppRoot,
|
|
339
559
|
app:freshApp
|
|
340
560
|
});
|
|
341
561
|
await emit(onEvent,{
|
|
@@ -343,10 +563,17 @@ export async function validateDiscoveredApplication({
|
|
|
343
563
|
workspaceRoot:canonicalWorkspaceRoot,
|
|
344
564
|
appId:app.appId
|
|
345
565
|
});
|
|
566
|
+
await assertCapturedDirectories();
|
|
346
567
|
return receipt;
|
|
347
568
|
}
|
|
348
569
|
|
|
349
|
-
export async function validateWorkspace({
|
|
570
|
+
export async function validateWorkspace({
|
|
571
|
+
workspaceRoot=process.cwd(),
|
|
572
|
+
appId,
|
|
573
|
+
allowMissingManagedImportMap=false,
|
|
574
|
+
signal,
|
|
575
|
+
onEvent
|
|
576
|
+
}={}){
|
|
350
577
|
throwIfAborted(signal);
|
|
351
578
|
const resolved=await resolveWorkspace({workspaceRoot,appId});
|
|
352
579
|
await emit(onEvent,{type:'workspace.validate.started',workspaceRoot:resolved.workspaceRoot,appId:resolved.appId});
|
|
@@ -409,12 +636,39 @@ export async function validateWorkspace({workspaceRoot=process.cwd(),appId,signa
|
|
|
409
636
|
||installed.source?.commit!==lock.runtime.upstreamCommit){
|
|
410
637
|
fail('Installed SDK runtime does not match arcane.lock.json.');
|
|
411
638
|
}
|
|
639
|
+
const browserManifestPath=path.join(
|
|
640
|
+
resolved.workspaceRoot,
|
|
641
|
+
'node_modules',
|
|
642
|
+
'arcane-os',
|
|
643
|
+
'browser-runtime',
|
|
644
|
+
'ARCANE_SDK_BROWSER_RELEASE.json'
|
|
645
|
+
);
|
|
646
|
+
const browserBytes=await readFile(browserManifestPath);
|
|
647
|
+
let installedBrowser;
|
|
648
|
+
try{installedBrowser=JSON.parse(browserBytes.toString('utf8'));}
|
|
649
|
+
catch(error){
|
|
650
|
+
fail(`Installed SDK browser runtime manifest is not valid JSON: ${error.message}`);
|
|
651
|
+
}
|
|
652
|
+
if(createHash('sha256').update(browserBytes).digest('hex')
|
|
653
|
+
!==lock.sdkBrowserRuntime.manifestSha256
|
|
654
|
+
||installedBrowser.contentSha256!==lock.sdkBrowserRuntime.contentSha256
|
|
655
|
+
||installedBrowser.builder!==lock.sdkBrowserRuntime.builder
|
|
656
|
+
||installedBrowser.sdkVersion!==lock.sdkBrowserRuntime.sdkVersion
|
|
657
|
+
||!sameBrowserRuntimeSource(
|
|
658
|
+
installedBrowser.source,
|
|
659
|
+
lock.sdkBrowserRuntime.source
|
|
660
|
+
)){
|
|
661
|
+
fail('Installed SDK browser runtime does not match arcane.lock.json.');
|
|
662
|
+
}
|
|
412
663
|
});
|
|
413
664
|
}else{
|
|
414
665
|
await add('workspace-runtime',async()=>{
|
|
666
|
+
const strongType=resolved.config.browserRuntimeLayout==='integrated-legacy'
|
|
667
|
+
?path.join('node_modules','strong-type')
|
|
668
|
+
:path.join('arcane','dependencies','strong-type');
|
|
415
669
|
for(const [relative,label] of [
|
|
416
670
|
['arcane','Integrated Arcane runtime'],
|
|
417
|
-
[
|
|
671
|
+
[strongType,'Integrated strong-type runtime']
|
|
418
672
|
]){
|
|
419
673
|
const info=await lstat(path.join(resolved.workspaceRoot,relative));
|
|
420
674
|
if(info.isSymbolicLink()||!info.isDirectory()){
|
|
@@ -429,6 +683,7 @@ export async function validateWorkspace({workspaceRoot=process.cwd(),appId,signa
|
|
|
429
683
|
workspaceMode,
|
|
430
684
|
workspaceConfig:resolved.config,
|
|
431
685
|
app:resolved.app,
|
|
686
|
+
allowMissingManagedImportMap,
|
|
432
687
|
signal,
|
|
433
688
|
onEvent
|
|
434
689
|
});
|