arcane-os 0.3.1 → 0.3.2
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 +14 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +855 -895
- package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
- package/browser-runtime/ai/browser-wasm.mjs +24 -35
- package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
- package/browser-runtime/ai/model-controller.mjs +584 -181
- package/browser-runtime/ai/speech-worker-client.mjs +8 -148
- package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
- package/browser-runtime/dom-event-instrumentation.mjs +55 -147
- package/browser-runtime/event-manager.mjs +239 -624
- package/package.json +5 -6
- package/runtime/arcane/components/app-bar.html +3 -15
- package/runtime/arcane/components/assistant-panel.html +10 -10
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chat.html +1359 -135
- package/runtime/arcane/components/conversation-view.html +2 -2
- package/runtime/arcane/components/document-inspector.html +11 -17
- package/runtime/arcane/components/file-manager.html +13 -56
- package/runtime/arcane/components/markdown-document.html +82 -281
- package/runtime/arcane/components/markdown-editor.html +7 -10
- package/runtime/arcane/components/media-embed.html +6 -6
- package/runtime/arcane/components/screen-capture.html +4 -4
- package/runtime/arcane/components/source-explanation.html +2 -2
- package/runtime/arcane/components/speech.html +112 -68
- package/runtime/arcane/components/terminal-workspace.html +4 -4
- package/runtime/arcane/components/theme-editor.html +1 -1
- package/runtime/arcane/components/unified-inbox.html +2 -2
- package/runtime/arcane/components/voice-transcription.html +31 -21
- package/runtime/arcane/entities/Calculation.js +2 -3
- package/runtime/arcane/entities/Chat.js +228 -43
- package/runtime/arcane/entities/Preference.js +3 -5
- package/runtime/arcane/entities/Weather.js +5 -5
- package/runtime/arcane/modules/AI.js +1042 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
- package/runtime/arcane/modules/AIResponseLength.js +9 -19
- package/runtime/arcane/modules/AIRuntimeState.js +109 -72
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
- package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
- package/runtime/arcane/modules/CalculatorEngine.js +9 -9
- package/runtime/arcane/modules/CommunicationAppController.js +3 -7
- package/runtime/arcane/modules/ComponentContracts.js +30 -32
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
- package/runtime/arcane/modules/ConversationActionItems.js +26 -59
- package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
- package/runtime/arcane/modules/ConversationTimebox.js +27 -15
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
- package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
- package/runtime/arcane/modules/HTMLImport.js +54 -1
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
- package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
- package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
- package/runtime/arcane/modules/MD.js +1 -45
- package/runtime/arcane/modules/Mail.js +51 -103
- package/runtime/arcane/modules/MailOutbox.mjs +95 -193
- package/runtime/arcane/modules/MailTransport.mjs +36 -57
- package/runtime/arcane/modules/ModelDefinition.js +22 -106
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
- package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
- package/runtime/arcane/modules/PreferenceStore.js +102 -30
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
- package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
- package/runtime/arcane/modules/ScreenCapture.js +175 -128
- package/runtime/arcane/modules/SpeechPlayback.js +46 -149
- package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
- package/runtime/arcane/modules/ToolCallRouter.js +25 -12
- package/runtime/arcane/modules/YouTubeMedia.js +6 -5
- package/schemas/arcane-app-bundle.schema.json +13 -78
- package/schemas/arcane-app.schema.json +9 -25
- package/schemas/arcane-lock.schema.json +18 -151
- package/schemas/arcane-package.schema.json +2 -16
- package/schemas/native-build-plan.schema.json +119 -122
- package/src/app-descriptor.mjs +75 -132
- package/src/application-tests.mjs +200 -0
- package/src/cli/main.mjs +27 -46
- package/src/constants.mjs +3 -4
- package/src/dev-server.mjs +30 -324
- package/src/doctor.mjs +92 -154
- package/src/dom-event-instrumentation.mjs +55 -147
- package/src/errors.mjs +2 -3
- package/src/event-manager.mjs +239 -624
- package/src/event-queue.mjs +3 -3
- package/src/import-map.mjs +273 -1028
- package/src/index.mjs +14 -16
- package/src/installed-sdk-runtime.mjs +27 -67
- package/src/integrated-provider-loader.mjs +53 -382
- package/src/mail-api.mjs +0 -2
- package/src/mail-server.mjs +224 -580
- package/src/mail.mjs +4 -10
- package/src/native-plan.mjs +163 -598
- package/src/native-provider-loader.mjs +104 -1063
- package/src/packager/core.mjs +485 -3229
- package/src/process.mjs +5 -10
- package/src/release-bundle.mjs +292 -2405
- package/src/runtime.mjs +76 -396
- package/src/scaffold.mjs +30 -80
- package/src/sdk-browser-runtime.mjs +70 -626
- package/src/source-server.mjs +588 -0
- package/src/targets/index.mjs +78 -188
- package/src/templates/workspace-template.mjs +19 -135
- package/src/testing-loader.mjs +164 -0
- package/src/testing.mjs +1 -1
- package/src/toolchain.mjs +131 -544
- package/src/update-check.mjs +26 -64
- package/src/workspace-operation-lock.mjs +139 -430
- package/src/workspace-runtime.mjs +109 -1558
- package/src/workspace.mjs +40 -302
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
- package/browser-runtime/ai/internal/sha256.mjs +0 -166
- package/docs/architecture.md +0 -344
- package/docs/compatibility.md +0 -36
- package/docs/event-manager.md +0 -294
- package/docs/platform-targets.md +0 -108
- package/docs/publishing.md +0 -201
- package/docs/reference/README.md +0 -185
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1295
- package/docs/reference/ai/browser-wasm.md +0 -530
- package/docs/reference/arcane-ollama.md +0 -288
- package/docs/reference/availability-and-normalization.md +0 -183
- package/docs/reference/behavioral-testing.md +0 -133
- package/docs/reference/cli.md +0 -779
- package/docs/reference/core/README.md +0 -62
- package/docs/reference/core/arcane-ai-contracts.md +0 -907
- package/docs/reference/core/arcane-api.md +0 -601
- package/docs/reference/core/arcane-entities.md +0 -65
- package/docs/reference/core/arcane-events.md +0 -134
- package/docs/reference/core/ollama-module.md +0 -181
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
- package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
- package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
- package/docs/reference/event-manager.md +0 -1511
- package/docs/reference/inventory/package-api.json +0 -3284
- package/docs/reference/inventory/runtime-components.json +0 -1011
- package/docs/reference/inventory/runtime-entities.json +0 -26
- package/docs/reference/inventory/runtime-modules.json +0 -1431
- package/docs/reference/mail.md +0 -316
- package/docs/reference/protocols.md +0 -719
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2965
- package/docs/reference/sdk-api.md +0 -6698
- package/docs/roadmap.md +0 -79
- package/docs/work-amplification.md +0 -129
- package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
package/src/workspace.mjs
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {lstat,readFile,readdir,realpath} from 'node:fs/promises';
|
|
1
|
+
import {readFile,readdir,realpath,stat} from 'node:fs/promises';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import {
|
|
5
4
|
normalizeRelativePath,
|
|
6
5
|
validateAppConfig as validatePackagerAppConfig,
|
|
7
6
|
validateRootConfig as validatePackagerRootConfig
|
|
8
7
|
} from './packager/core.mjs';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
SDK_NAME as EXPECTED_SDK_NAME,
|
|
12
|
-
SDK_VERSION as EXPECTED_SDK_VERSION
|
|
13
|
-
} from './constants.mjs';
|
|
8
|
+
import {loadAppDescriptor} from './app-descriptor.mjs';
|
|
9
|
+
import {SDK_NAME as EXPECTED_SDK_NAME} from './constants.mjs';
|
|
14
10
|
import {inspectImportMapHtml} from './import-map.mjs';
|
|
15
|
-
import {
|
|
16
|
-
SDK_BROWSER_RUNTIME_CONTENT_SHA256,
|
|
17
|
-
SDK_BROWSER_RUNTIME_MANIFEST_SHA256
|
|
18
|
-
} from './sdk-browser-runtime.mjs';
|
|
19
11
|
|
|
20
12
|
const APP_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
21
|
-
const SHA256_PATTERN=/^[a-f0-9]{64}$/;
|
|
22
|
-
const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
|
|
23
13
|
const NPM_PACKAGE_NAME_PATTERN=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
|
|
24
|
-
const EXACT_SDK_ALIAS=`npm:${EXPECTED_SDK_NAME}@${EXPECTED_SDK_VERSION}`;
|
|
25
14
|
const ROOT_CONFIG_NAME='arcane-packager.json';
|
|
26
15
|
const APP_CONFIG_NAME='arcane-package.json';
|
|
16
|
+
const completeValue=value=>value;
|
|
27
17
|
|
|
28
18
|
function fail(message,code='ARCANE_WORKSPACE_INVALID'){
|
|
29
19
|
const error=new Error(message);
|
|
@@ -47,14 +37,12 @@ function throwIfAborted(signal){
|
|
|
47
37
|
}
|
|
48
38
|
|
|
49
39
|
async function emit(onEvent,event){
|
|
50
|
-
if(typeof onEvent==='function')await onEvent(
|
|
40
|
+
if(typeof onEvent==='function')await onEvent(event);
|
|
51
41
|
}
|
|
52
42
|
|
|
53
43
|
async function readJson(filePath,label){
|
|
54
44
|
let text;
|
|
55
45
|
try{
|
|
56
|
-
const info=await lstat(filePath);
|
|
57
|
-
if(info.isSymbolicLink()||!info.isFile())fail(`${label} must be a real file.`);
|
|
58
46
|
text=await readFile(filePath,'utf8');
|
|
59
47
|
}catch(error){
|
|
60
48
|
if(error?.code==='ENOENT')fail(`${label} does not exist.`);
|
|
@@ -66,16 +54,12 @@ async function readJson(filePath,label){
|
|
|
66
54
|
|
|
67
55
|
async function assertRealDirectory(directory,label){
|
|
68
56
|
let info;
|
|
69
|
-
try{info=await
|
|
57
|
+
try{info=await stat(directory);}
|
|
70
58
|
catch(error){
|
|
71
59
|
if(error?.code==='ENOENT')fail(`${label} does not exist: ${directory}.`);
|
|
72
60
|
throw error;
|
|
73
61
|
}
|
|
74
|
-
if(info.
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function sameDirectoryIdentity(left,right){
|
|
78
|
-
return left.device===right.device&&left.inode===right.inode;
|
|
62
|
+
if(!info.isDirectory())fail(`${label} must be a directory: ${directory}.`);
|
|
79
63
|
}
|
|
80
64
|
|
|
81
65
|
function sameDirectoryPath(left,right){
|
|
@@ -86,68 +70,6 @@ function sameDirectoryPath(left,right){
|
|
|
86
70
|
return normalize(left)===normalize(right);
|
|
87
71
|
}
|
|
88
72
|
|
|
89
|
-
async function captureRealDirectoryIdentity(directory,label){
|
|
90
|
-
const requested=path.resolve(directory);
|
|
91
|
-
let requestedInfo;
|
|
92
|
-
try{requestedInfo=await lstat(requested,{bigint:true});}
|
|
93
|
-
catch(error){
|
|
94
|
-
if(error?.code==='ENOENT')fail(`${label} does not exist: ${requested}.`);
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
if(requestedInfo.isSymbolicLink()||!requestedInfo.isDirectory()){
|
|
98
|
-
fail(`${label} must be a real directory: ${requested}.`);
|
|
99
|
-
}
|
|
100
|
-
const canonical=await realpath(requested);
|
|
101
|
-
const canonicalInfo=await lstat(canonical,{bigint:true});
|
|
102
|
-
const requestedIdentity=Object.freeze({
|
|
103
|
-
device:requestedInfo.dev,
|
|
104
|
-
inode:requestedInfo.ino
|
|
105
|
-
});
|
|
106
|
-
const canonicalIdentity=Object.freeze({
|
|
107
|
-
device:canonicalInfo.dev,
|
|
108
|
-
inode:canonicalInfo.ino
|
|
109
|
-
});
|
|
110
|
-
if(canonicalInfo.isSymbolicLink()||!canonicalInfo.isDirectory()
|
|
111
|
-
||!sameDirectoryIdentity(requestedIdentity,canonicalIdentity)){
|
|
112
|
-
fail(`${label} must resolve to one physical directory: ${requested}.`);
|
|
113
|
-
}
|
|
114
|
-
return Object.freeze({
|
|
115
|
-
requested,
|
|
116
|
-
requestedIdentity,
|
|
117
|
-
canonical,
|
|
118
|
-
identity:canonicalIdentity
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function assertRealDirectoryIdentity(captured,label){
|
|
123
|
-
let canonical;
|
|
124
|
-
let requestedInfo;
|
|
125
|
-
let canonicalInfo;
|
|
126
|
-
try{
|
|
127
|
-
[canonical,requestedInfo,canonicalInfo]=await Promise.all([
|
|
128
|
-
realpath(captured.requested),
|
|
129
|
-
lstat(captured.requested,{bigint:true}),
|
|
130
|
-
lstat(captured.canonical,{bigint:true})
|
|
131
|
-
]);
|
|
132
|
-
}catch(error){
|
|
133
|
-
if(error?.code==='ENOENT'){
|
|
134
|
-
fail(`${label} changed while focused validation was active.`,
|
|
135
|
-
'ARCANE_INTEGRITY_FAILED');
|
|
136
|
-
}
|
|
137
|
-
throw error;
|
|
138
|
-
}
|
|
139
|
-
const requestedIdentity={device:requestedInfo.dev,inode:requestedInfo.ino};
|
|
140
|
-
const canonicalIdentity={device:canonicalInfo.dev,inode:canonicalInfo.ino};
|
|
141
|
-
if(!sameDirectoryPath(canonical,captured.canonical)
|
|
142
|
-
||requestedInfo.isSymbolicLink()||!requestedInfo.isDirectory()
|
|
143
|
-
||canonicalInfo.isSymbolicLink()||!canonicalInfo.isDirectory()
|
|
144
|
-
||!sameDirectoryIdentity(requestedIdentity,captured.requestedIdentity)
|
|
145
|
-
||!sameDirectoryIdentity(requestedIdentity,captured.identity)
|
|
146
|
-
||!sameDirectoryIdentity(canonicalIdentity,captured.identity)){
|
|
147
|
-
fail(`${label} changed while focused validation was active.`,'ARCANE_INTEGRITY_FAILED');
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
73
|
function sdkPackageSourceForDependency(dependencyName){
|
|
152
74
|
if(typeof dependencyName!=='string'||dependencyName.length>214
|
|
153
75
|
||!NPM_PACKAGE_NAME_PATTERN.test(dependencyName)){
|
|
@@ -172,12 +94,6 @@ function dependencyNameForSdkPackageSource(source){
|
|
|
172
94
|
}
|
|
173
95
|
}
|
|
174
96
|
|
|
175
|
-
function supportedCanonicalSdkDeclaration(value){
|
|
176
|
-
return value===EXPECTED_SDK_VERSION
|
|
177
|
-
||(typeof value==='string'&&LOCAL_TARBALL_PATTERN.test(value)
|
|
178
|
-
&&!/[\x00-\x1f\x7f]/.test(value));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
97
|
export function resolveSdkPackageDeclaration(rootPackage,{
|
|
182
98
|
allowMissing=false,
|
|
183
99
|
packageSource
|
|
@@ -194,17 +110,10 @@ export function resolveSdkPackageDeclaration(rootPackage,{
|
|
|
194
110
|
&&(specifier===`npm:${EXPECTED_SDK_NAME}`
|
|
195
111
|
||specifier.startsWith(`npm:${EXPECTED_SDK_NAME}@`));
|
|
196
112
|
if(!canonicalName&&!aliasTarget)continue;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
:specifier===EXACT_SDK_ALIAS;
|
|
200
|
-
if(!supported){
|
|
201
|
-
fail(
|
|
202
|
-
`package.json ${groupName}.${dependencyName} must be ${EXPECTED_SDK_VERSION}, `
|
|
203
|
-
+`a local file: tarball under ${EXPECTED_SDK_NAME}, or the exact npm alias `
|
|
204
|
-
+`${EXACT_SDK_ALIAS} under a distinct dependency key.`
|
|
205
|
-
);
|
|
113
|
+
if(typeof specifier!=='string'||!specifier.trim()){
|
|
114
|
+
fail(`package.json ${groupName}.${dependencyName} must declare an SDK package version or source.`);
|
|
206
115
|
}
|
|
207
|
-
candidates.push(
|
|
116
|
+
candidates.push(completeValue({
|
|
208
117
|
dependencyName,
|
|
209
118
|
dependencyGroup:groupName,
|
|
210
119
|
packageSource:sdkPackageSourceForDependency(dependencyName),
|
|
@@ -218,8 +127,7 @@ export function resolveSdkPackageDeclaration(rootPackage,{
|
|
|
218
127
|
if(candidates.length===0){
|
|
219
128
|
if(allowMissing&&packageSource===undefined)return null;
|
|
220
129
|
fail(
|
|
221
|
-
`package.json must declare exactly one ${EXPECTED_SDK_NAME} installation
|
|
222
|
-
+`${EXPECTED_SDK_VERSION}, a local file: tarball, or the exact npm alias ${EXACT_SDK_ALIAS}.`
|
|
130
|
+
`package.json must declare exactly one ${EXPECTED_SDK_NAME} installation.`
|
|
223
131
|
);
|
|
224
132
|
}
|
|
225
133
|
const [declaration]=candidates;
|
|
@@ -232,13 +140,6 @@ export function resolveSdkPackageDeclaration(rootPackage,{
|
|
|
232
140
|
return declaration;
|
|
233
141
|
}
|
|
234
142
|
|
|
235
|
-
function sdkManifestPaths(packageSource){
|
|
236
|
-
return Object.freeze({
|
|
237
|
-
runtimeManifest:`${packageSource}/runtime/ARCANE_RUNTIME_RELEASE.json`,
|
|
238
|
-
browserRuntimeManifest:`${packageSource}/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json`
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
143
|
function classifyRootConfig(config){
|
|
243
144
|
const validated=validatePackagerRootConfig(config,ROOT_CONFIG_NAME);
|
|
244
145
|
const routes=validated.sharedPayloads['browser-runtime'];
|
|
@@ -299,7 +200,7 @@ function classifyRootConfig(config){
|
|
|
299
200
|
}else{
|
|
300
201
|
fail(`${ROOT_CONFIG_NAME} browser-runtime routes must match the external SDK or integrated Arcane workspace contract.`);
|
|
301
202
|
}
|
|
302
|
-
return
|
|
203
|
+
return completeValue({
|
|
303
204
|
...validated,
|
|
304
205
|
workspaceMode,
|
|
305
206
|
browserRuntimeLayout,
|
|
@@ -330,7 +231,7 @@ async function discoverAppsInRoot(root,config){
|
|
|
330
231
|
appId:entry.name,
|
|
331
232
|
packageManifest:manifest
|
|
332
233
|
});
|
|
333
|
-
apps.push(
|
|
234
|
+
apps.push(completeValue({
|
|
334
235
|
appId:entry.name,
|
|
335
236
|
appRoot,
|
|
336
237
|
manifest:validatedManifest,
|
|
@@ -342,7 +243,7 @@ async function discoverAppsInRoot(root,config){
|
|
|
342
243
|
if(!String(error?.message).includes('does not exist'))throw error;
|
|
343
244
|
}
|
|
344
245
|
}
|
|
345
|
-
return
|
|
246
|
+
return completeValue(apps);
|
|
346
247
|
}
|
|
347
248
|
|
|
348
249
|
export async function discoverApps(workspaceRoot=process.cwd()){
|
|
@@ -357,7 +258,7 @@ export async function inspectWorkspaceProfile(workspaceRoot=process.cwd()){
|
|
|
357
258
|
const config=classifyRootConfig(
|
|
358
259
|
await readJson(path.join(canonicalRoot,ROOT_CONFIG_NAME),ROOT_CONFIG_NAME)
|
|
359
260
|
);
|
|
360
|
-
return
|
|
261
|
+
return completeValue({
|
|
361
262
|
workspaceRoot:canonicalRoot,
|
|
362
263
|
workspaceMode:config.workspaceMode,
|
|
363
264
|
config
|
|
@@ -400,82 +301,16 @@ export async function resolveWorkspace({workspaceRoot=process.cwd(),appId}={}){
|
|
|
400
301
|
}else{
|
|
401
302
|
fail(`This workspace contains multiple apps; select one explicitly: ${apps.map(item=>item.appId).join(', ')}.`,'ARCANE_USAGE');
|
|
402
303
|
}
|
|
403
|
-
return
|
|
304
|
+
return completeValue({
|
|
404
305
|
workspaceRoot:canonicalRoot,
|
|
405
306
|
config,
|
|
406
307
|
app,
|
|
407
308
|
appId:app.appId,
|
|
408
309
|
appRoot:app.appRoot,
|
|
409
|
-
appIds:
|
|
310
|
+
appIds:completeValue(apps.map(item=>item.appId))
|
|
410
311
|
});
|
|
411
312
|
}
|
|
412
313
|
|
|
413
|
-
function validateLock(lock,sdkDeclaration){
|
|
414
|
-
const browser=lock?.sdkBrowserRuntime;
|
|
415
|
-
const browserSource=browser?.source;
|
|
416
|
-
const dependencies=browserSource?.dependencies;
|
|
417
|
-
const exactKeys=(value,keys)=>isObject(value)
|
|
418
|
-
&&Object.keys(value).sort().join('\0')===[...keys].sort().join('\0');
|
|
419
|
-
const expectedDependencies=[
|
|
420
|
-
{
|
|
421
|
-
name:'event-pubsub',
|
|
422
|
-
version:'6.1.0',
|
|
423
|
-
resolved:'https://registry.npmjs.org/event-pubsub/-/event-pubsub-6.1.0.tgz',
|
|
424
|
-
integrity:'sha512-FEMlhTxwqGM0hztTixG6FhVFXqp7Eq1ltk5mSreK6Mhy3xWWpLAzEUR6OMvMdNqT3jgSxA8JDhnhyAG3X4Xy7Q=='
|
|
425
|
-
},
|
|
426
|
-
{
|
|
427
|
-
name:'strong-type',
|
|
428
|
-
version:'2.0.0',
|
|
429
|
-
resolved:'https://registry.npmjs.org/strong-type/-/strong-type-2.0.0.tgz',
|
|
430
|
-
integrity:'sha512-HHrY9qYC7yn+5mlewiI3k9RQM9gZqGQsqbomZcd10Ks0h4RlX01nnkWbCe4AsVPCI6KaFvpkWm1nHMD+Ykup6g=='
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
name:'@wllama/wllama',
|
|
434
|
-
version:'3.6.0',
|
|
435
|
-
resolved:'https://registry.npmjs.org/@wllama/wllama/-/wllama-3.6.0.tgz',
|
|
436
|
-
integrity:'sha512-NN3ZBXqaaUwGXTQubkNvsCaLPjN2XVa0bVS40OYCE8zquYmRc2W3oHYEgwvuSWWDB8aUqTLyMioySCXNkcnD1w=='
|
|
437
|
-
}
|
|
438
|
-
];
|
|
439
|
-
const dependenciesMatch=Array.isArray(dependencies)&&dependencies.length===expectedDependencies.length
|
|
440
|
-
&&dependencies.every((actual,index)=>{
|
|
441
|
-
const expected=expectedDependencies[index];
|
|
442
|
-
return exactKeys(actual,['name','version','resolved','integrity'])
|
|
443
|
-
&&actual.name===expected.name&&actual.version===expected.version
|
|
444
|
-
&&actual.resolved===expected.resolved&&actual.integrity===expected.integrity;
|
|
445
|
-
});
|
|
446
|
-
const manifests=sdkManifestPaths(sdkDeclaration.packageSource);
|
|
447
|
-
if(!exactKeys(lock,['schemaVersion','sdk','runtime','sdkBrowserRuntime','protocols'])
|
|
448
|
-
||lock.schemaVersion!==1
|
|
449
|
-
||!exactKeys(lock.sdk,['name','version'])
|
|
450
|
-
||lock.sdk.name!==EXPECTED_SDK_NAME||lock.sdk.version!==EXPECTED_SDK_VERSION
|
|
451
|
-
||!exactKeys(lock.runtime,['manifest','contentSha256','upstreamCommit'])
|
|
452
|
-
||!SHA256_PATTERN.test(lock.runtime.contentSha256)
|
|
453
|
-
||!/^([a-f0-9]{40})$/.test(lock.runtime.upstreamCommit)
|
|
454
|
-
||lock.runtime.manifest!==manifests.runtimeManifest
|
|
455
|
-
||!exactKeys(browser,[
|
|
456
|
-
'manifest','manifestSha256','contentSha256','builder','sdkVersion','source'
|
|
457
|
-
])
|
|
458
|
-
||browser.manifest!==manifests.browserRuntimeManifest
|
|
459
|
-
||browser.manifestSha256!==SDK_BROWSER_RUNTIME_MANIFEST_SHA256
|
|
460
|
-
||browser.contentSha256!==SDK_BROWSER_RUNTIME_CONTENT_SHA256
|
|
461
|
-
||browser.builder!=='arcane-sdk-browser-runtime-v1'
|
|
462
|
-
||browser.sdkVersion!==EXPECTED_SDK_VERSION
|
|
463
|
-
||!exactKeys(browserSource,[
|
|
464
|
-
'authority','repository','protocol','browserEntry','dependencies'
|
|
465
|
-
])||browserSource.authority!=='arcane-os-sdk'
|
|
466
|
-
||browserSource.repository!=='https://github.com/TheWizardNexus/arcane-os-sdk.git'
|
|
467
|
-
||browserSource.protocol!=='arcane-sdk-browser-runtime/1'
|
|
468
|
-
||browserSource.browserEntry!=='arcane-os/event-manager'
|
|
469
|
-
||!dependenciesMatch
|
|
470
|
-
||!exactKeys(lock.protocols,['arcane','cliEvents','targetAdapter'])
|
|
471
|
-
||lock.protocols.arcane!=='arcane/1'
|
|
472
|
-
||lock.protocols.cliEvents!=='arcane-cli-events/1'
|
|
473
|
-
||lock.protocols.targetAdapter!=='arcane-target-adapter/1'){
|
|
474
|
-
fail('arcane.lock.json is incompatible with this SDK. Run arcane init only after reviewing missing files; existing locks are never overwritten.');
|
|
475
|
-
}
|
|
476
|
-
return lock;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
314
|
export async function resolveInstalledSdkInstallation(workspaceRoot,declaration){
|
|
480
315
|
let current=workspaceRoot;
|
|
481
316
|
for(const segment of declaration.packageSource.split('/')){
|
|
@@ -490,8 +325,8 @@ export async function resolveInstalledSdkInstallation(workspaceRoot,declaration)
|
|
|
490
325
|
path.join(canonicalPackageRoot,'package.json'),
|
|
491
326
|
'installed SDK package manifest'
|
|
492
327
|
);
|
|
493
|
-
if(installedPackage.name!==EXPECTED_SDK_NAME||installedPackage.version!==
|
|
494
|
-
fail(`Installed SDK package must identify
|
|
328
|
+
if(installedPackage.name!==EXPECTED_SDK_NAME||typeof installedPackage.version!=='string'){
|
|
329
|
+
fail(`Installed SDK package must identify as ${EXPECTED_SDK_NAME}.`);
|
|
495
330
|
}
|
|
496
331
|
const runtimeRoot=path.join(canonicalPackageRoot,'runtime');
|
|
497
332
|
const browserRuntimeRoot=path.join(canonicalPackageRoot,'browser-runtime');
|
|
@@ -499,40 +334,14 @@ export async function resolveInstalledSdkInstallation(workspaceRoot,declaration)
|
|
|
499
334
|
assertRealDirectory(runtimeRoot,'Installed SDK runtime root'),
|
|
500
335
|
assertRealDirectory(browserRuntimeRoot,'Installed SDK browser runtime root')
|
|
501
336
|
]);
|
|
502
|
-
|
|
503
|
-
return Object.freeze({
|
|
337
|
+
return completeValue({
|
|
504
338
|
dependencyName:declaration.dependencyName,
|
|
505
339
|
packageSource:declaration.packageSource,
|
|
506
340
|
canonicalPackageRoot,
|
|
507
341
|
packageName:installedPackage.name,
|
|
508
342
|
packageVersion:installedPackage.version,
|
|
509
343
|
runtimeRoot,
|
|
510
|
-
browserRuntimeRoot
|
|
511
|
-
runtimeManifest:manifests.runtimeManifest,
|
|
512
|
-
browserRuntimeManifest:manifests.browserRuntimeManifest
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function sameBrowserRuntimeSource(actual,pinned){
|
|
517
|
-
const exactKeys=(value,keys)=>isObject(value)
|
|
518
|
-
&&Object.keys(value).sort().join('\0')===[...keys].sort().join('\0');
|
|
519
|
-
const keys=['authority','repository','protocol','browserEntry','dependencies'];
|
|
520
|
-
if(!exactKeys(actual,keys)||!exactKeys(pinned,keys)
|
|
521
|
-
||actual.authority!==pinned.authority
|
|
522
|
-
||actual.repository!==pinned.repository
|
|
523
|
-
||actual.protocol!==pinned.protocol
|
|
524
|
-
||actual.browserEntry!==pinned.browserEntry
|
|
525
|
-
||!Array.isArray(actual.dependencies)||!Array.isArray(pinned.dependencies)
|
|
526
|
-
||actual.dependencies.length!==pinned.dependencies.length){
|
|
527
|
-
return false;
|
|
528
|
-
}
|
|
529
|
-
const dependencyKeys=['name','version','resolved','integrity'];
|
|
530
|
-
return actual.dependencies.every((dependency,index)=>{
|
|
531
|
-
const expected=pinned.dependencies[index];
|
|
532
|
-
return exactKeys(dependency,dependencyKeys)&&exactKeys(expected,dependencyKeys)
|
|
533
|
-
&&dependency.name===expected.name&&dependency.version===expected.version
|
|
534
|
-
&&dependency.resolved===expected.resolved
|
|
535
|
-
&&dependency.integrity===expected.integrity;
|
|
344
|
+
browserRuntimeRoot
|
|
536
345
|
});
|
|
537
346
|
}
|
|
538
347
|
|
|
@@ -618,21 +427,13 @@ export async function validateDiscoveredApplication({
|
|
|
618
427
|
||!app.manifest||!app.descriptor){
|
|
619
428
|
fail('A discovered Arcane application is required for focused validation.');
|
|
620
429
|
}
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
const capturedAppsRoot=await captureRealDirectoryIdentity(
|
|
624
|
-
path.join(canonicalWorkspaceRoot,'apps'),
|
|
625
|
-
'Workspace apps root'
|
|
626
|
-
);
|
|
430
|
+
const canonicalWorkspaceRoot=await realpath(workspaceRoot);
|
|
431
|
+
await assertRealDirectory(path.join(canonicalWorkspaceRoot,'apps'),'Workspace apps root');
|
|
627
432
|
const expectedAppRoot=path.join(canonicalWorkspaceRoot,'apps',app.appId);
|
|
628
|
-
|
|
629
|
-
captureRealDirectoryIdentity(expectedAppRoot,`apps/${app.appId}`),
|
|
630
|
-
captureRealDirectoryIdentity(app.appRoot,`Discovered app ${app.appId}`)
|
|
631
|
-
]);
|
|
632
|
-
if(!sameDirectoryIdentity(capturedExpectedApp.identity,capturedDiscoveredApp.identity)){
|
|
433
|
+
if(!sameDirectoryPath(app.appRoot,expectedAppRoot)){
|
|
633
434
|
fail(`Discovered app ${app.appId} does not belong to the selected workspace.`);
|
|
634
435
|
}
|
|
635
|
-
const canonicalAppRoot=
|
|
436
|
+
const canonicalAppRoot=await realpath(app.appRoot);
|
|
636
437
|
let config=workspaceConfig;
|
|
637
438
|
if(!config){
|
|
638
439
|
const profile=await inspectWorkspaceProfile(canonicalWorkspaceRoot);
|
|
@@ -660,13 +461,7 @@ export async function validateDiscoveredApplication({
|
|
|
660
461
|
packageManifest:rawManifest
|
|
661
462
|
});
|
|
662
463
|
const descriptor=loadedDescriptor.descriptor;
|
|
663
|
-
|
|
664
|
-
fail(
|
|
665
|
-
`The canonical descriptor for ${app.appId} changed after application discovery.`,
|
|
666
|
-
'ARCANE_INTEGRITY_FAILED'
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
const freshApp=Object.freeze({
|
|
464
|
+
const freshApp=completeValue({
|
|
670
465
|
appId:app.appId,
|
|
671
466
|
appRoot:canonicalAppRoot,
|
|
672
467
|
manifest,
|
|
@@ -675,23 +470,16 @@ export async function validateDiscoveredApplication({
|
|
|
675
470
|
descriptorPath:loadedDescriptor.descriptorPath
|
|
676
471
|
});
|
|
677
472
|
const entryPath=path.join(canonicalAppRoot,manifest.entry);
|
|
678
|
-
const info=await
|
|
679
|
-
if(info.
|
|
680
|
-
fail(`apps/${app.appId}/${manifest.entry} must be a
|
|
473
|
+
const info=await stat(entryPath);
|
|
474
|
+
if(!info.isFile()){
|
|
475
|
+
fail(`apps/${app.appId}/${manifest.entry} must be a file.`);
|
|
681
476
|
}
|
|
682
477
|
assertHtmlContract(await readFile(entryPath,'utf8'),app.appId,{
|
|
683
478
|
entry:manifest.entry,
|
|
684
479
|
strictStyles:workspaceMode==='external',
|
|
685
480
|
allowMissingManagedImportMap
|
|
686
481
|
});
|
|
687
|
-
const
|
|
688
|
-
assertRealDirectoryIdentity(capturedWorkspace,'Workspace'),
|
|
689
|
-
assertRealDirectoryIdentity(capturedAppsRoot,'Workspace apps root'),
|
|
690
|
-
assertRealDirectoryIdentity(capturedExpectedApp,`apps/${app.appId}`),
|
|
691
|
-
assertRealDirectoryIdentity(capturedDiscoveredApp,`Discovered app ${app.appId}`)
|
|
692
|
-
]);
|
|
693
|
-
await assertCapturedDirectories();
|
|
694
|
-
const receipt=Object.freeze({
|
|
482
|
+
const result=completeValue({
|
|
695
483
|
valid:true,
|
|
696
484
|
workspaceRoot:canonicalWorkspaceRoot,
|
|
697
485
|
workspaceMode,
|
|
@@ -704,8 +492,7 @@ export async function validateDiscoveredApplication({
|
|
|
704
492
|
workspaceRoot:canonicalWorkspaceRoot,
|
|
705
493
|
appId:app.appId
|
|
706
494
|
});
|
|
707
|
-
|
|
708
|
-
return receipt;
|
|
495
|
+
return result;
|
|
709
496
|
}
|
|
710
497
|
|
|
711
498
|
export async function validateWorkspace({
|
|
@@ -722,10 +509,9 @@ export async function validateWorkspace({
|
|
|
722
509
|
const add=async(name,operation)=>{
|
|
723
510
|
throwIfAborted(signal);
|
|
724
511
|
await operation();
|
|
725
|
-
checks.push(
|
|
512
|
+
checks.push(completeValue({name,ok:true}));
|
|
726
513
|
await emit(onEvent,{type:'workspace.validate.check',name,ok:true});
|
|
727
514
|
};
|
|
728
|
-
let lock;
|
|
729
515
|
let sdkDeclaration;
|
|
730
516
|
let sdkInstallation;
|
|
731
517
|
let validatedApplication;
|
|
@@ -740,18 +526,6 @@ export async function validateWorkspace({
|
|
|
740
526
|
fail('The selected Arcane application descriptor does not match the app id.');
|
|
741
527
|
}
|
|
742
528
|
});
|
|
743
|
-
if(workspaceMode==='external'){
|
|
744
|
-
await add('lock',async()=>{
|
|
745
|
-
sdkDeclaration=resolveSdkPackageDeclaration(
|
|
746
|
-
await readJson(path.join(resolved.workspaceRoot,'package.json'),'package.json'),
|
|
747
|
-
{packageSource:resolved.config.sdkPackageSource}
|
|
748
|
-
);
|
|
749
|
-
lock=validateLock(
|
|
750
|
-
await readJson(path.join(resolved.workspaceRoot,'arcane.lock.json'),'arcane.lock.json'),
|
|
751
|
-
sdkDeclaration
|
|
752
|
-
);
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
529
|
await add('package',async()=>{
|
|
756
530
|
const rootPackage=await readJson(path.join(resolved.workspaceRoot,'package.json'),'package.json');
|
|
757
531
|
if(workspaceMode==='integrated'){
|
|
@@ -763,19 +537,13 @@ export async function validateWorkspace({
|
|
|
763
537
|
const declaration=resolveSdkPackageDeclaration(rootPackage,{
|
|
764
538
|
packageSource:resolved.config.sdkPackageSource
|
|
765
539
|
});
|
|
540
|
+
sdkDeclaration=declaration;
|
|
766
541
|
if(rootPackage?.private!==true||rootPackage?.type!=='module'){
|
|
767
542
|
fail(
|
|
768
|
-
`package.json must be private, use modules, and declare one
|
|
543
|
+
`package.json must be private, use modules, and declare one `
|
|
769
544
|
+`${EXPECTED_SDK_NAME} installation.`
|
|
770
545
|
);
|
|
771
546
|
}
|
|
772
|
-
if(declaration.dependencyName!==sdkDeclaration.dependencyName
|
|
773
|
-
||declaration.dependencyGroup!==sdkDeclaration.dependencyGroup
|
|
774
|
-
||declaration.packageSource!==sdkDeclaration.packageSource
|
|
775
|
-
||declaration.specifier!==sdkDeclaration.specifier){
|
|
776
|
-
fail('The declared SDK installation changed after workspace profile inspection.',
|
|
777
|
-
'ARCANE_INTEGRITY_FAILED');
|
|
778
|
-
}
|
|
779
547
|
});
|
|
780
548
|
if(workspaceMode==='external'){
|
|
781
549
|
await add('installed-runtime',async()=>{
|
|
@@ -783,35 +551,6 @@ export async function validateWorkspace({
|
|
|
783
551
|
resolved.workspaceRoot,
|
|
784
552
|
sdkDeclaration
|
|
785
553
|
);
|
|
786
|
-
const installed=await readJson(
|
|
787
|
-
path.join(sdkInstallation.runtimeRoot,'ARCANE_RUNTIME_RELEASE.json'),
|
|
788
|
-
'installed SDK runtime manifest'
|
|
789
|
-
);
|
|
790
|
-
if(installed.contentSha256!==lock.runtime.contentSha256
|
|
791
|
-
||installed.source?.legacyProjection?.commit!==lock.runtime.upstreamCommit){
|
|
792
|
-
fail('Installed SDK runtime does not match arcane.lock.json.');
|
|
793
|
-
}
|
|
794
|
-
const browserManifestPath=path.join(
|
|
795
|
-
sdkInstallation.browserRuntimeRoot,
|
|
796
|
-
'ARCANE_SDK_BROWSER_RELEASE.json'
|
|
797
|
-
);
|
|
798
|
-
const browserBytes=await readFile(browserManifestPath);
|
|
799
|
-
let installedBrowser;
|
|
800
|
-
try{installedBrowser=JSON.parse(browserBytes.toString('utf8'));}
|
|
801
|
-
catch(error){
|
|
802
|
-
fail(`Installed SDK browser runtime manifest is not valid JSON: ${error.message}`);
|
|
803
|
-
}
|
|
804
|
-
if(createHash('sha256').update(browserBytes).digest('hex')
|
|
805
|
-
!==lock.sdkBrowserRuntime.manifestSha256
|
|
806
|
-
||installedBrowser.contentSha256!==lock.sdkBrowserRuntime.contentSha256
|
|
807
|
-
||installedBrowser.builder!==lock.sdkBrowserRuntime.builder
|
|
808
|
-
||installedBrowser.sdkVersion!==lock.sdkBrowserRuntime.sdkVersion
|
|
809
|
-
||!sameBrowserRuntimeSource(
|
|
810
|
-
installedBrowser.source,
|
|
811
|
-
lock.sdkBrowserRuntime.source
|
|
812
|
-
)){
|
|
813
|
-
fail('Installed SDK browser runtime does not match arcane.lock.json.');
|
|
814
|
-
}
|
|
815
554
|
});
|
|
816
555
|
}else{
|
|
817
556
|
await add('workspace-runtime',async()=>{
|
|
@@ -822,9 +561,9 @@ export async function validateWorkspace({
|
|
|
822
561
|
['arcane','Integrated Arcane runtime'],
|
|
823
562
|
[strongType,'Integrated strong-type runtime']
|
|
824
563
|
]){
|
|
825
|
-
const info=await
|
|
826
|
-
if(info.
|
|
827
|
-
fail(`${label} must be a
|
|
564
|
+
const info=await stat(path.join(resolved.workspaceRoot,relative));
|
|
565
|
+
if(!info.isDirectory()){
|
|
566
|
+
fail(`${label} must be a directory.`);
|
|
828
567
|
}
|
|
829
568
|
}
|
|
830
569
|
});
|
|
@@ -840,7 +579,7 @@ export async function validateWorkspace({
|
|
|
840
579
|
onEvent
|
|
841
580
|
});
|
|
842
581
|
});
|
|
843
|
-
const
|
|
582
|
+
const result=completeValue({
|
|
844
583
|
valid:true,
|
|
845
584
|
workspaceMode,
|
|
846
585
|
workspaceRoot:resolved.workspaceRoot,
|
|
@@ -848,10 +587,9 @@ export async function validateWorkspace({
|
|
|
848
587
|
appRoot:resolved.appRoot,
|
|
849
588
|
config:resolved.config,
|
|
850
589
|
app:validatedApplication.app,
|
|
851
|
-
lock,
|
|
852
590
|
...(sdkInstallation?{sdkInstallation}:{}),
|
|
853
|
-
checks:
|
|
591
|
+
checks:completeValue(checks)
|
|
854
592
|
});
|
|
855
593
|
await emit(onEvent,{type:'workspace.validate.completed',workspaceRoot:resolved.workspaceRoot,appId:resolved.appId,checks});
|
|
856
|
-
return
|
|
594
|
+
return result;
|
|
857
595
|
}
|