arcane-os 0.1.0-dev.5
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 +154 -0
- package/COMMERCIAL-LICENSE.md +13 -0
- package/LICENSE +661 -0
- package/NOTICE +70 -0
- package/README.md +448 -0
- package/bin/arcane-test.mjs +741 -0
- package/bin/arcane.mjs +5 -0
- package/docs/architecture.md +234 -0
- package/docs/compatibility.md +36 -0
- package/docs/event-manager.md +166 -0
- package/docs/platform-targets.md +108 -0
- package/docs/publishing.md +203 -0
- package/docs/reference/README.md +117 -0
- package/docs/reference/arcane-ollama.md +288 -0
- package/docs/reference/availability-and-normalization.md +123 -0
- package/docs/reference/behavioral-testing.md +86 -0
- package/docs/reference/cli.md +569 -0
- package/docs/reference/core/README.md +62 -0
- package/docs/reference/core/arcane-ai-contracts.md +873 -0
- package/docs/reference/core/arcane-api.md +601 -0
- package/docs/reference/core/arcane-entities.md +65 -0
- package/docs/reference/core/arcane-events.md +134 -0
- package/docs/reference/core/ollama-module.md +181 -0
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
- package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
- package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
- package/docs/reference/event-manager.md +957 -0
- package/docs/reference/inventory/package-api.json +2632 -0
- package/docs/reference/inventory/runtime-components.json +934 -0
- package/docs/reference/inventory/runtime-entities.json +26 -0
- package/docs/reference/inventory/runtime-modules.json +1249 -0
- package/docs/reference/protocols.md +242 -0
- package/docs/reference/runtime-components.md +1098 -0
- package/docs/reference/runtime-entities.md +303 -0
- package/docs/reference/runtime-modules.md +2010 -0
- package/docs/reference/sdk-api.md +4901 -0
- package/docs/roadmap.md +79 -0
- package/docs/work-amplification.md +124 -0
- package/node_modules/event-pubsub/CHANGELOG.md +55 -0
- package/node_modules/event-pubsub/MIGRATION.md +70 -0
- package/node_modules/event-pubsub/README.md +363 -0
- package/node_modules/event-pubsub/SECURITY.md +37 -0
- package/node_modules/event-pubsub/index.js +141 -0
- package/node_modules/event-pubsub/licence +21 -0
- package/node_modules/event-pubsub/package.json +59 -0
- package/node_modules/strong-type/README.md +408 -0
- package/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/strong-type/index.js +1151 -0
- package/node_modules/strong-type/licence +21 -0
- package/node_modules/strong-type/node.js +125 -0
- package/node_modules/strong-type/package.json +61 -0
- package/package.json +95 -0
- package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
- package/runtime/arcane/components/app-bar.html +468 -0
- package/runtime/arcane/components/assistant-panel.html +715 -0
- package/runtime/arcane/components/calculator.html +8 -0
- package/runtime/arcane/components/chart.html +655 -0
- package/runtime/arcane/components/chat.html +1225 -0
- package/runtime/arcane/components/conversation-view.html +13 -0
- package/runtime/arcane/components/dashboard-config.html +341 -0
- package/runtime/arcane/components/data-maintenance.html +112 -0
- package/runtime/arcane/components/data-view.html +92 -0
- package/runtime/arcane/components/directory-picker.html +197 -0
- package/runtime/arcane/components/document-inspector.html +252 -0
- package/runtime/arcane/components/file-drop.html +264 -0
- package/runtime/arcane/components/file-inspector.html +293 -0
- package/runtime/arcane/components/file-manager.html +1715 -0
- package/runtime/arcane/components/header.html +142 -0
- package/runtime/arcane/components/integration-settings.html +14 -0
- package/runtime/arcane/components/local-ai-status.html +360 -0
- package/runtime/arcane/components/markdown-document.html +1048 -0
- package/runtime/arcane/components/markdown-editor.html +360 -0
- package/runtime/arcane/components/media-embed.html +8 -0
- package/runtime/arcane/components/modal.html +402 -0
- package/runtime/arcane/components/output-panel.html +259 -0
- package/runtime/arcane/components/preferences-form.html +135 -0
- package/runtime/arcane/components/record-timeline.html +105 -0
- package/runtime/arcane/components/relationship-board.html +116 -0
- package/runtime/arcane/components/screen-capture.html +8 -0
- package/runtime/arcane/components/source-code-viewer.html +441 -0
- package/runtime/arcane/components/source-explanation.html +124 -0
- package/runtime/arcane/components/speech.html +365 -0
- package/runtime/arcane/components/summary-strip.html +177 -0
- package/runtime/arcane/components/table.html +77 -0
- package/runtime/arcane/components/task-progress.html +282 -0
- package/runtime/arcane/components/terminal-workspace.html +65 -0
- package/runtime/arcane/components/theme-editor.html +41 -0
- package/runtime/arcane/components/theme-switcher.html +46 -0
- package/runtime/arcane/components/unified-inbox.html +20 -0
- package/runtime/arcane/components/voice-transcription.html +476 -0
- package/runtime/arcane/components/weather-widget.html +8 -0
- package/runtime/arcane/components/web-navigator.html +239 -0
- package/runtime/arcane/css/communications.css +1 -0
- package/runtime/arcane/css/dashboard-config.css +45 -0
- package/runtime/arcane/css/document-site.css +981 -0
- package/runtime/arcane/css/layout.css +438 -0
- package/runtime/arcane/css/primitives.css +321 -0
- package/runtime/arcane/css/theme.css +112 -0
- package/runtime/arcane/css/utility-workspace.css +1 -0
- package/runtime/arcane/entities/ApiModelRecord.js +20 -0
- package/runtime/arcane/entities/Calculation.js +13 -0
- package/runtime/arcane/entities/Chat.js +581 -0
- package/runtime/arcane/entities/CommunicationMessage.js +29 -0
- package/runtime/arcane/entities/CommunicationThread.js +21 -0
- package/runtime/arcane/entities/Document.js +10 -0
- package/runtime/arcane/entities/File.js +143 -0
- package/runtime/arcane/entities/Image.js +135 -0
- package/runtime/arcane/entities/IntentEnvelope.js +834 -0
- package/runtime/arcane/entities/Preference.js +83 -0
- package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
- package/runtime/arcane/entities/TerminalSession.js +46 -0
- package/runtime/arcane/entities/Theme.js +107 -0
- package/runtime/arcane/entities/User.js +1046 -0
- package/runtime/arcane/entities/Weather.js +23 -0
- package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
- package/runtime/arcane/img/arrow-left.png +0 -0
- package/runtime/arcane/img/arrow-right.png +0 -0
- package/runtime/arcane/img/doc.svg +5 -0
- package/runtime/arcane/img/folder.svg +4 -0
- package/runtime/arcane/img/image.svg +5 -0
- package/runtime/arcane/img/refresh.png +0 -0
- package/runtime/arcane/img/send.svg +9 -0
- package/runtime/arcane/img/trash.svg +5 -0
- package/runtime/arcane/img/upload.svg +5 -0
- package/runtime/arcane/modules/AI.js +2048 -0
- package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
- package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
- package/runtime/arcane/modules/AIResponseLength.js +42 -0
- package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
- package/runtime/arcane/modules/AnsiText.js +53 -0
- package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
- package/runtime/arcane/modules/AppDataScope.js +245 -0
- package/runtime/arcane/modules/AppearancePreferences.js +28 -0
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
- package/runtime/arcane/modules/AsyncBoundary.js +161 -0
- package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
- package/runtime/arcane/modules/CalculatorEngine.js +20 -0
- package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
- package/runtime/arcane/modules/ChartLibrary.js +35 -0
- package/runtime/arcane/modules/ChatRecords.js +13 -0
- package/runtime/arcane/modules/CommunicationAppController.js +43 -0
- package/runtime/arcane/modules/CommunicationHub.js +16 -0
- package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
- package/runtime/arcane/modules/ComponentContracts.js +586 -0
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
- package/runtime/arcane/modules/ConversationActionItems.js +488 -0
- package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
- package/runtime/arcane/modules/ConversationTimebox.js +527 -0
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
- package/runtime/arcane/modules/DBLS.js +171 -0
- package/runtime/arcane/modules/DBOPFS.js +1154 -0
- package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
- package/runtime/arcane/modules/DataMaintenance.js +91 -0
- package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
- package/runtime/arcane/modules/DirectoryPicker.js +109 -0
- package/runtime/arcane/modules/DocumentNavigation.js +223 -0
- package/runtime/arcane/modules/Errors.js +1025 -0
- package/runtime/arcane/modules/GifEncoder.js +29 -0
- package/runtime/arcane/modules/HTMLImport.js +114 -0
- package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
- package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
- package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
- package/runtime/arcane/modules/MD.js +111 -0
- package/runtime/arcane/modules/Mail.js +352 -0
- package/runtime/arcane/modules/MailTransport.mjs +180 -0
- package/runtime/arcane/modules/Marked.min.js +71 -0
- package/runtime/arcane/modules/MemoryRecords.js +44 -0
- package/runtime/arcane/modules/MessageAdvisory.js +38 -0
- package/runtime/arcane/modules/ModelDefinition.js +189 -0
- package/runtime/arcane/modules/Ollama.js +74 -0
- package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
- package/runtime/arcane/modules/OllamaSettings.js +24 -0
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
- package/runtime/arcane/modules/PreferenceStore.js +109 -0
- package/runtime/arcane/modules/QRCode.min.js +1 -0
- package/runtime/arcane/modules/Questionnaire.js +61 -0
- package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
- package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
- package/runtime/arcane/modules/RecordReviewStore.js +94 -0
- package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
- package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
- package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
- package/runtime/arcane/modules/ScreenCapture.js +20 -0
- package/runtime/arcane/modules/SpeechPlayback.js +581 -0
- package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
- package/runtime/arcane/modules/SystemAppearance.js +20 -0
- package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
- package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
- package/runtime/arcane/modules/TerminalClient.js +52 -0
- package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
- package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
- package/runtime/arcane/modules/ThemeManager.js +131 -0
- package/runtime/arcane/modules/TimeGuard.js +149 -0
- package/runtime/arcane/modules/ToolCallRouter.js +83 -0
- package/runtime/arcane/modules/WaitForComponent.js +102 -0
- package/runtime/arcane/modules/YouTubeMedia.js +16 -0
- package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
- package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
- package/runtime/arcane/modules/uPlot.min.css +1 -0
- package/runtime/arcane/security/arcane-network-policy.json +6 -0
- package/runtime/strong-type/index.js +352 -0
- package/runtime/strong-type/licence +21 -0
- package/runtime/strong-type/package.json +45 -0
- package/schemas/arcane-app-bundle.schema.json +125 -0
- package/schemas/arcane-app.schema.json +329 -0
- package/schemas/arcane-lock.schema.json +86 -0
- package/schemas/arcane-package.schema.json +224 -0
- package/schemas/cli-event.schema.json +122 -0
- package/schemas/event-stack.schema.json +152 -0
- package/schemas/native-build-plan.schema.json +176 -0
- package/schemas/target-adapter.schema.json +117 -0
- package/src/app-descriptor.mjs +500 -0
- package/src/cli/main.mjs +561 -0
- package/src/constants.mjs +31 -0
- package/src/dev-server.mjs +718 -0
- package/src/doctor.mjs +315 -0
- package/src/dom-event-instrumentation.mjs +594 -0
- package/src/errors.mjs +75 -0
- package/src/event-manager.mjs +1342 -0
- package/src/event-queue.mjs +138 -0
- package/src/events.mjs +219 -0
- package/src/index.mjs +177 -0
- package/src/integrated-provider-loader.mjs +432 -0
- package/src/native-plan.mjs +698 -0
- package/src/native-provider-loader.mjs +1126 -0
- package/src/packager/core.mjs +2691 -0
- package/src/process.mjs +353 -0
- package/src/release-bundle.mjs +2523 -0
- package/src/repository.mjs +90 -0
- package/src/runtime.mjs +452 -0
- package/src/scaffold.mjs +380 -0
- package/src/targets/index.mjs +436 -0
- package/src/templates/assets/app-icon.png +0 -0
- package/src/templates/workspace-template.mjs +388 -0
- package/src/testing-loader.mjs +9 -0
- package/src/testing.mjs +427 -0
- package/src/toolchain.mjs +1335 -0
- package/src/update-check.mjs +307 -0
- package/src/workspace.mjs +449 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/TheWizardNexus/arcane-os-sdk/schemas/target-adapter.schema.json",
|
|
4
|
+
"title": "Arcane target adapter descriptor",
|
|
5
|
+
"description": "Stable, serializable description of a build and run target adapter.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"protocol",
|
|
10
|
+
"id",
|
|
11
|
+
"displayName",
|
|
12
|
+
"status",
|
|
13
|
+
"platforms",
|
|
14
|
+
"architectures",
|
|
15
|
+
"formats",
|
|
16
|
+
"signingModes",
|
|
17
|
+
"reason",
|
|
18
|
+
"methods"
|
|
19
|
+
],
|
|
20
|
+
"properties": {
|
|
21
|
+
"protocol": {
|
|
22
|
+
"const": "arcane-target-adapter/1"
|
|
23
|
+
},
|
|
24
|
+
"id": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
|
|
27
|
+
},
|
|
28
|
+
"displayName": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1,
|
|
31
|
+
"maxLength": 128
|
|
32
|
+
},
|
|
33
|
+
"status": {
|
|
34
|
+
"enum": [
|
|
35
|
+
"available",
|
|
36
|
+
"pairing-required",
|
|
37
|
+
"host-unsupported",
|
|
38
|
+
"deferred",
|
|
39
|
+
"incompatible"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"platforms": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"minItems": 1,
|
|
45
|
+
"uniqueItems": true,
|
|
46
|
+
"items": {
|
|
47
|
+
"enum": [
|
|
48
|
+
"any",
|
|
49
|
+
"windows",
|
|
50
|
+
"linux",
|
|
51
|
+
"android"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"architectures": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"minItems": 1,
|
|
58
|
+
"uniqueItems": true,
|
|
59
|
+
"items": {
|
|
60
|
+
"enum": [
|
|
61
|
+
"any",
|
|
62
|
+
"x64",
|
|
63
|
+
"arm64"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"formats": {
|
|
68
|
+
"type": "array",
|
|
69
|
+
"minItems": 1,
|
|
70
|
+
"uniqueItems": true,
|
|
71
|
+
"items": {
|
|
72
|
+
"enum": [
|
|
73
|
+
"directory",
|
|
74
|
+
"portable",
|
|
75
|
+
"exe",
|
|
76
|
+
"appimage",
|
|
77
|
+
"deb",
|
|
78
|
+
"rpm",
|
|
79
|
+
"apk",
|
|
80
|
+
"aab"
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"signingModes": {
|
|
85
|
+
"type": "array",
|
|
86
|
+
"minItems": 1,
|
|
87
|
+
"uniqueItems": true,
|
|
88
|
+
"items": {
|
|
89
|
+
"enum": [
|
|
90
|
+
"none",
|
|
91
|
+
"unsigned-local-test",
|
|
92
|
+
"development",
|
|
93
|
+
"production"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"reason": {
|
|
98
|
+
"type": [
|
|
99
|
+
"string",
|
|
100
|
+
"null"
|
|
101
|
+
],
|
|
102
|
+
"minLength": 1,
|
|
103
|
+
"maxLength": 2048
|
|
104
|
+
},
|
|
105
|
+
"methods": {
|
|
106
|
+
"const": [
|
|
107
|
+
"describe",
|
|
108
|
+
"doctor",
|
|
109
|
+
"prepare",
|
|
110
|
+
"plan",
|
|
111
|
+
"build",
|
|
112
|
+
"verify",
|
|
113
|
+
"run"
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import {isDeepStrictEqual} from 'node:util';
|
|
2
|
+
import {createHash} from 'node:crypto';
|
|
3
|
+
import {lstat,readFile} from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {
|
|
6
|
+
ARCANE_MACHINE_BUNDLE_VERSION,
|
|
7
|
+
ARCANE_PROTOCOL,
|
|
8
|
+
TARGET_IDS
|
|
9
|
+
} from './constants.mjs';
|
|
10
|
+
import {
|
|
11
|
+
normalizeRelativePath,
|
|
12
|
+
parseSemver,
|
|
13
|
+
validateAppConfig as validateAppPackageConfig
|
|
14
|
+
} from './packager/core.mjs';
|
|
15
|
+
|
|
16
|
+
export const APP_DESCRIPTOR_NAME='arcane-app.json';
|
|
17
|
+
export const APP_DESCRIPTOR_SCHEMA_VERSION=2;
|
|
18
|
+
|
|
19
|
+
const APP_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
20
|
+
const CAPABILITY_PATTERN=/^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+$/u;
|
|
21
|
+
const METHOD_PATTERN=/^[a-z][a-z0-9]*(?:\.[a-z][a-zA-Z0-9]*)+$/u;
|
|
22
|
+
const FEATURE_PATTERN=/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/u;
|
|
23
|
+
const PUBLISHER_PATTERN=/^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
24
|
+
const NATIVE_TYPES=new Set(['app']);
|
|
25
|
+
const SAFE_ICON_EXTENSIONS=new Set(['.ico','.jpeg','.jpg','.png','.webp']);
|
|
26
|
+
const REGISTRY_PATH=path.join(
|
|
27
|
+
'machine_bundles',
|
|
28
|
+
'arcane-os-machine-bundle',
|
|
29
|
+
'arcane-apps.json'
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
function fail(message){
|
|
33
|
+
const error=new Error(message);
|
|
34
|
+
error.code='ARCANE_APP_DESCRIPTOR_INVALID';
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isObject(value){
|
|
39
|
+
return value!==null&&typeof value==='object'&&!Array.isArray(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function assertOnlyKeys(value,allowed,label){
|
|
43
|
+
if(!isObject(value))fail(`${label} must be an object.`);
|
|
44
|
+
for(const key of Object.keys(value)){
|
|
45
|
+
if(!allowed.has(key))fail(`${label} contains an unsupported key: ${key}.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function safeText(value,label,{maximum=500}={}){
|
|
50
|
+
if(typeof value!=='string'||value!==value.trim()||!value||value.length>maximum
|
|
51
|
+
||/[<>\u0000-\u001f\u007f]/u.test(value)){
|
|
52
|
+
fail(`${label} must be nonempty, trimmed, bounded presentation text.`);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function uniqueSortedStrings(value,label,{pattern,maximum=256,required=false}={}){
|
|
58
|
+
if(!Array.isArray(value)||(required&&value.length===0)||value.length>maximum){
|
|
59
|
+
fail(`${label} must be ${required?'a nonempty':'an'} array with at most ${maximum} entries.`);
|
|
60
|
+
}
|
|
61
|
+
const normalized=value.map((entry,index)=>{
|
|
62
|
+
if(typeof entry!=='string'||entry!==entry.trim()||!entry
|
|
63
|
+
||(pattern&&!pattern.test(entry))){
|
|
64
|
+
fail(`${label}[${index}] is invalid.`);
|
|
65
|
+
}
|
|
66
|
+
return entry;
|
|
67
|
+
});
|
|
68
|
+
if(new Set(normalized).size!==normalized.length)fail(`${label} must not contain duplicates.`);
|
|
69
|
+
if(JSON.stringify(normalized)!==JSON.stringify([...normalized].sort())){
|
|
70
|
+
fail(`${label} must be sorted for deterministic projection.`);
|
|
71
|
+
}
|
|
72
|
+
return Object.freeze(normalized);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function relativePaths(value,label,{required=false}={}){
|
|
76
|
+
if(!Array.isArray(value)||(required&&value.length===0)||value.length>512){
|
|
77
|
+
fail(`${label} must be ${required?'a nonempty':'an'} array with at most 512 entries.`);
|
|
78
|
+
}
|
|
79
|
+
const normalized=value.map((entry,index)=>normalizeRelativePath(entry,`${label}[${index}]`));
|
|
80
|
+
if(new Set(normalized.map(entry=>entry.toLocaleLowerCase('en-US'))).size!==normalized.length){
|
|
81
|
+
fail(`${label} must not contain duplicate paths.`);
|
|
82
|
+
}
|
|
83
|
+
return Object.freeze(normalized);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateOrigins(value,label,{allowLoopbackHttp=false,allowHttpsScheme=false}={}){
|
|
87
|
+
if(!Array.isArray(value)||value.length>16)fail(`${label} must be an array with at most 16 origins.`);
|
|
88
|
+
const origins=value.map((origin,index)=>{
|
|
89
|
+
if(typeof origin!=='string'||origin!==origin.trim())fail(`${label}[${index}] is invalid.`);
|
|
90
|
+
if(origin==='https:'&&allowHttpsScheme)return origin;
|
|
91
|
+
let parsed;
|
|
92
|
+
try{parsed=new URL(origin);}
|
|
93
|
+
catch{fail(`${label}[${index}] is not a valid URL origin.`);}
|
|
94
|
+
if(parsed.origin!==origin||parsed.username||parsed.password
|
|
95
|
+
||parsed.pathname!=='/'||parsed.search||parsed.hash){
|
|
96
|
+
fail(`${label}[${index}] must be a canonical allowed origin.`);
|
|
97
|
+
}
|
|
98
|
+
if(parsed.protocol==='http:'){
|
|
99
|
+
if(!allowLoopbackHttp||!['127.0.0.1','[::1]'].includes(parsed.hostname)){
|
|
100
|
+
fail(`${label}[${index}] may use HTTP only for a numeric loopback host.`);
|
|
101
|
+
}
|
|
102
|
+
}else if(parsed.protocol!=='https:'){
|
|
103
|
+
fail(`${label}[${index}] must use HTTPS or an approved loopback HTTP origin.`);
|
|
104
|
+
}
|
|
105
|
+
return parsed.origin;
|
|
106
|
+
});
|
|
107
|
+
if(new Set(origins).size!==origins.length)fail(`${label} must not contain duplicates.`);
|
|
108
|
+
if(JSON.stringify(origins)!==JSON.stringify([...origins].sort())){
|
|
109
|
+
fail(`${label} must be sorted for deterministic projection.`);
|
|
110
|
+
}
|
|
111
|
+
return Object.freeze(origins);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function validateLocalAIModelPolicy(value,label){
|
|
115
|
+
if(value===undefined)return undefined;
|
|
116
|
+
assertOnlyKeys(value,new Set(['verified_only','models']),label);
|
|
117
|
+
if(typeof value.verified_only!=='boolean'||!Array.isArray(value.models)||value.models.length>64){
|
|
118
|
+
fail(`${label} is invalid.`);
|
|
119
|
+
}
|
|
120
|
+
const models=value.models.map((model,index)=>{
|
|
121
|
+
const itemLabel=`${label}.models[${index}]`;
|
|
122
|
+
assertOnlyKeys(model,new Set(['name','definition']),itemLabel);
|
|
123
|
+
if(typeof model.name!=='string'||!model.name||model.name.length>256
|
|
124
|
+
||typeof model.definition!=='string'||!model.definition.endsWith('Modelfile')){
|
|
125
|
+
fail(`${itemLabel} is invalid.`);
|
|
126
|
+
}
|
|
127
|
+
return Object.freeze({name:model.name,definition:normalizeRelativePath(model.definition,`${itemLabel}.definition`)});
|
|
128
|
+
});
|
|
129
|
+
return Object.freeze({verified_only:value.verified_only,models:Object.freeze(models)});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function validatePackage(value,appId){
|
|
133
|
+
assertOnlyKeys(value,new Set([
|
|
134
|
+
'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy'
|
|
135
|
+
]),'descriptor.package');
|
|
136
|
+
const entry=normalizeRelativePath(value.entry,'descriptor.package.entry');
|
|
137
|
+
if(!['static','adapter'].includes(value.strategy))fail('descriptor.package.strategy must be static or adapter.');
|
|
138
|
+
const include=relativePaths(value.include,'descriptor.package.include',{required:true});
|
|
139
|
+
const exclude=relativePaths(value.exclude??[],'descriptor.package.exclude');
|
|
140
|
+
if(!include.some(candidate=>entry===candidate||entry.startsWith(`${candidate}/`))){
|
|
141
|
+
fail('descriptor.package.entry must be covered by descriptor.package.include.');
|
|
142
|
+
}
|
|
143
|
+
const shared=uniqueSortedStrings(value.shared,'descriptor.package.shared',{
|
|
144
|
+
pattern:/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u,
|
|
145
|
+
maximum:64,
|
|
146
|
+
required:true
|
|
147
|
+
});
|
|
148
|
+
let adapter;
|
|
149
|
+
if(value.strategy==='adapter'){
|
|
150
|
+
adapter=normalizeRelativePath(value.adapter,'descriptor.package.adapter');
|
|
151
|
+
if(!/^scripts\/[A-Za-z0-9._/-]+\.mjs$/u.test(adapter)){
|
|
152
|
+
fail('descriptor.package.adapter must be an app-local scripts/*.mjs path.');
|
|
153
|
+
}
|
|
154
|
+
}else if(value.adapter!==undefined){
|
|
155
|
+
fail('descriptor.package.adapter is only valid for adapter packages.');
|
|
156
|
+
}
|
|
157
|
+
return Object.freeze({
|
|
158
|
+
entry,
|
|
159
|
+
strategy:value.strategy,
|
|
160
|
+
include,
|
|
161
|
+
exclude,
|
|
162
|
+
shared,
|
|
163
|
+
...(adapter?{adapter}:{}),
|
|
164
|
+
...(value.localAIModelPolicy===undefined?{}:{
|
|
165
|
+
localAIModelPolicy:validateLocalAIModelPolicy(
|
|
166
|
+
value.localAIModelPolicy,
|
|
167
|
+
`descriptor ${appId} localAIModelPolicy`
|
|
168
|
+
)
|
|
169
|
+
})
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function validatePublisher(value){
|
|
174
|
+
assertOnlyKeys(value,new Set(['id','name']),'descriptor.publisher');
|
|
175
|
+
if(typeof value.id!=='string'||!PUBLISHER_PATTERN.test(value.id)){
|
|
176
|
+
fail('descriptor.publisher.id must be a lowercase publisher identifier.');
|
|
177
|
+
}
|
|
178
|
+
return Object.freeze({id:value.id,name:safeText(value.name,'descriptor.publisher.name',{maximum:160})});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function validatePermissions(value){
|
|
182
|
+
assertOnlyKeys(value,new Set(['capabilities','methods']),'descriptor.permissions');
|
|
183
|
+
const capabilities=uniqueSortedStrings(value.capabilities,'descriptor.permissions.capabilities',{
|
|
184
|
+
pattern:CAPABILITY_PATTERN,
|
|
185
|
+
maximum:256
|
|
186
|
+
});
|
|
187
|
+
if(capabilities.includes('external.open')&&capabilities.includes('web.embed')){
|
|
188
|
+
fail('descriptor.permissions.capabilities must not combine external.open with web.embed.');
|
|
189
|
+
}
|
|
190
|
+
return Object.freeze({
|
|
191
|
+
capabilities,
|
|
192
|
+
methods:uniqueSortedStrings(value.methods,'descriptor.permissions.methods',{
|
|
193
|
+
pattern:METHOD_PATTERN,
|
|
194
|
+
maximum:512
|
|
195
|
+
})
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function validateSecurity(value,{appId,capabilities}){
|
|
200
|
+
assertOnlyKeys(value,new Set(['connectOrigins','frameOrigins','mediaOrigins']),'descriptor.security');
|
|
201
|
+
const frameOrigins=validateOrigins(
|
|
202
|
+
value.frameOrigins,
|
|
203
|
+
'descriptor.security.frameOrigins',
|
|
204
|
+
{allowHttpsScheme:appId==='browser'}
|
|
205
|
+
);
|
|
206
|
+
if(frameOrigins.length&&!capabilities.includes('web.embed')){
|
|
207
|
+
fail('descriptor.security.frameOrigins requires the web.embed capability.');
|
|
208
|
+
}
|
|
209
|
+
return Object.freeze({
|
|
210
|
+
connectOrigins:validateOrigins(
|
|
211
|
+
value.connectOrigins,
|
|
212
|
+
'descriptor.security.connectOrigins',
|
|
213
|
+
{allowLoopbackHttp:true}
|
|
214
|
+
),
|
|
215
|
+
frameOrigins,
|
|
216
|
+
mediaOrigins:validateOrigins(value.mediaOrigins,'descriptor.security.mediaOrigins')
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function validateDocumentCatalog(value){
|
|
221
|
+
if(value===undefined)return undefined;
|
|
222
|
+
const label='descriptor.native.documentCatalog';
|
|
223
|
+
if(value.policy==='empty-unpublished'){
|
|
224
|
+
assertOnlyKeys(value,new Set(['policy','destination','manifest']),label);
|
|
225
|
+
const manifest=normalizeRelativePath(value.manifest,`${label}.manifest`);
|
|
226
|
+
if(manifest.includes('/')||path.posix.extname(manifest).toLowerCase()!=='.json'){
|
|
227
|
+
fail(`${label}.manifest must be a JSON filename.`);
|
|
228
|
+
}
|
|
229
|
+
return Object.freeze({
|
|
230
|
+
policy:value.policy,
|
|
231
|
+
destination:normalizeRelativePath(value.destination,`${label}.destination`),
|
|
232
|
+
manifest
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
assertOnlyKeys(value,new Set([
|
|
236
|
+
'policy','release','destination','originals','manifest','expectedCount'
|
|
237
|
+
]),label);
|
|
238
|
+
if(value.policy!=='public-only'||!Number.isInteger(value.expectedCount)
|
|
239
|
+
||value.expectedCount<1||value.expectedCount>512){
|
|
240
|
+
fail(`${label} has unsupported policy or expectedCount.`);
|
|
241
|
+
}
|
|
242
|
+
const destination=normalizeRelativePath(value.destination,`${label}.destination`);
|
|
243
|
+
const originals=normalizeRelativePath(value.originals,`${label}.originals`);
|
|
244
|
+
const manifest=normalizeRelativePath(value.manifest,`${label}.manifest`);
|
|
245
|
+
if(manifest.includes('/')||path.posix.extname(manifest).toLowerCase()!=='.json'){
|
|
246
|
+
fail(`${label}.manifest must be a JSON filename.`);
|
|
247
|
+
}
|
|
248
|
+
if(destination===originals||destination.startsWith(`${originals}/`)
|
|
249
|
+
||originals.startsWith(`${destination}/`)){
|
|
250
|
+
fail(`${label}.destination and originals must not overlap.`);
|
|
251
|
+
}
|
|
252
|
+
return Object.freeze({
|
|
253
|
+
policy:value.policy,
|
|
254
|
+
release:normalizeRelativePath(value.release,`${label}.release`),
|
|
255
|
+
destination,
|
|
256
|
+
originals,
|
|
257
|
+
manifest,
|
|
258
|
+
expectedCount:value.expectedCount
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function validateNative(value,appId){
|
|
263
|
+
assertOnlyKeys(value,new Set(['type','icon','order','bundledApps','documentCatalog']),'descriptor.native');
|
|
264
|
+
if(!NATIVE_TYPES.has(value.type))fail('descriptor.native.type is unsupported.');
|
|
265
|
+
const icon=value.icon===null?null:normalizeRelativePath(value.icon,'descriptor.native.icon');
|
|
266
|
+
if(icon&&(!SAFE_ICON_EXTENSIONS.has(path.posix.extname(icon).toLowerCase())||icon.length>160)){
|
|
267
|
+
fail('descriptor.native.icon must identify a bounded safe raster image or icon file.');
|
|
268
|
+
}
|
|
269
|
+
if(!Number.isInteger(value.order)||value.order<0||value.order>10000){
|
|
270
|
+
fail('descriptor.native.order must be an integer from 0 through 10000.');
|
|
271
|
+
}
|
|
272
|
+
const bundledApps=uniqueSortedStrings(value.bundledApps,'descriptor.native.bundledApps',{
|
|
273
|
+
pattern:APP_ID_PATTERN,
|
|
274
|
+
maximum:16
|
|
275
|
+
});
|
|
276
|
+
if(bundledApps.includes(appId))fail('descriptor.native.bundledApps must not include the app itself.');
|
|
277
|
+
const documentCatalog=validateDocumentCatalog(value.documentCatalog);
|
|
278
|
+
return Object.freeze({
|
|
279
|
+
type:value.type,
|
|
280
|
+
icon,
|
|
281
|
+
order:value.order,
|
|
282
|
+
bundledApps,
|
|
283
|
+
...(documentCatalog?{documentCatalog}:{})
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function validateRequirements(value){
|
|
288
|
+
assertOnlyKeys(value,new Set(['arcaneProtocol','minimumCoreVersion','features']),'descriptor.requirements');
|
|
289
|
+
if(value.arcaneProtocol!==ARCANE_PROTOCOL)fail(`descriptor.requirements.arcaneProtocol must be ${ARCANE_PROTOCOL}.`);
|
|
290
|
+
parseSemver(value.minimumCoreVersion);
|
|
291
|
+
return Object.freeze({
|
|
292
|
+
arcaneProtocol:value.arcaneProtocol,
|
|
293
|
+
minimumCoreVersion:value.minimumCoreVersion,
|
|
294
|
+
features:uniqueSortedStrings(value.features,'descriptor.requirements.features',{
|
|
295
|
+
pattern:FEATURE_PATTERN,
|
|
296
|
+
maximum:128
|
|
297
|
+
})
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function validateAppDescriptor(value,{appId}={}){
|
|
302
|
+
assertOnlyKeys(value,new Set([
|
|
303
|
+
'schemaVersion','id','displayName','description','version','publisher','package',
|
|
304
|
+
'permissions','security','native','requirements','targets'
|
|
305
|
+
]),'Arcane app descriptor');
|
|
306
|
+
if(value.schemaVersion!==APP_DESCRIPTOR_SCHEMA_VERSION){
|
|
307
|
+
fail(`Arcane app descriptor schemaVersion must be ${APP_DESCRIPTOR_SCHEMA_VERSION}.`);
|
|
308
|
+
}
|
|
309
|
+
if(!APP_ID_PATTERN.test(value.id)||(appId&&value.id!==appId)){
|
|
310
|
+
fail('Arcane app descriptor id must match its apps directory.');
|
|
311
|
+
}
|
|
312
|
+
parseSemver(value.version);
|
|
313
|
+
const targets=uniqueSortedStrings(value.targets,'descriptor.targets',{
|
|
314
|
+
pattern:APP_ID_PATTERN,
|
|
315
|
+
maximum:TARGET_IDS.length,
|
|
316
|
+
required:true
|
|
317
|
+
});
|
|
318
|
+
if(targets.some(target=>!TARGET_IDS.includes(target)))fail('descriptor.targets contains an unknown SDK target.');
|
|
319
|
+
const packageDescriptor=validatePackage(value.package,value.id);
|
|
320
|
+
const native=validateNative(value.native,value.id);
|
|
321
|
+
const displayName=safeText(value.displayName,'descriptor.displayName',{maximum:160});
|
|
322
|
+
const description=safeText(value.description,'descriptor.description');
|
|
323
|
+
if(targets.some(target=>target!=='browser')){
|
|
324
|
+
if(displayName.length>80||description.length>240){
|
|
325
|
+
fail('Native app displayName and description must fit the Arcane presentation limits.');
|
|
326
|
+
}
|
|
327
|
+
if(!native.icon){
|
|
328
|
+
fail('descriptor.native.icon is required when a native target is declared.');
|
|
329
|
+
}
|
|
330
|
+
if(!packageDescriptor.include.some(candidate=>
|
|
331
|
+
native.icon===candidate||native.icon.startsWith(`${candidate}/`)
|
|
332
|
+
)){
|
|
333
|
+
fail('descriptor.native.icon must be covered by descriptor.package.include.');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const permissions=validatePermissions(value.permissions);
|
|
337
|
+
return Object.freeze({
|
|
338
|
+
schemaVersion:APP_DESCRIPTOR_SCHEMA_VERSION,
|
|
339
|
+
id:value.id,
|
|
340
|
+
displayName,
|
|
341
|
+
description,
|
|
342
|
+
version:value.version,
|
|
343
|
+
publisher:validatePublisher(value.publisher),
|
|
344
|
+
package:packageDescriptor,
|
|
345
|
+
permissions,
|
|
346
|
+
security:validateSecurity(value.security,{appId:value.id,capabilities:permissions.capabilities}),
|
|
347
|
+
native,
|
|
348
|
+
requirements:validateRequirements(value.requirements),
|
|
349
|
+
targets
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function projectPackageManifest(descriptor){
|
|
354
|
+
const value=validateAppDescriptor(descriptor,{appId:descriptor?.id});
|
|
355
|
+
const projection={
|
|
356
|
+
schemaVersion:1,
|
|
357
|
+
id:value.id,
|
|
358
|
+
displayName:value.displayName,
|
|
359
|
+
version:value.version,
|
|
360
|
+
entry:value.package.entry,
|
|
361
|
+
strategy:value.package.strategy,
|
|
362
|
+
security:{
|
|
363
|
+
connectOrigins:[...value.security.connectOrigins],
|
|
364
|
+
frameOrigins:[...value.security.frameOrigins],
|
|
365
|
+
mediaOrigins:[...value.security.mediaOrigins]
|
|
366
|
+
},
|
|
367
|
+
...(value.package.localAIModelPolicy?{localAIModelPolicy:value.package.localAIModelPolicy}:{}),
|
|
368
|
+
include:[...value.package.include],
|
|
369
|
+
exclude:[...value.package.exclude],
|
|
370
|
+
shared:[...value.package.shared],
|
|
371
|
+
...(value.package.adapter?{adapter:value.package.adapter}:{})
|
|
372
|
+
};
|
|
373
|
+
const sharedPayloads=Object.fromEntries(
|
|
374
|
+
value.package.shared.map(id=>[id,Object.freeze([])])
|
|
375
|
+
);
|
|
376
|
+
validateAppPackageConfig(
|
|
377
|
+
projection,
|
|
378
|
+
value.id,
|
|
379
|
+
{sharedPayloads:Object.freeze(sharedPayloads)},
|
|
380
|
+
`${APP_DESCRIPTOR_NAME} projection`
|
|
381
|
+
);
|
|
382
|
+
return projection;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function appDescriptorSha256(descriptor){
|
|
386
|
+
const value=validateAppDescriptor(descriptor,{appId:descriptor?.id});
|
|
387
|
+
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function projectNativeDescriptor(descriptor,{source}={}){
|
|
391
|
+
const value=validateAppDescriptor(descriptor,{appId:descriptor?.id});
|
|
392
|
+
if(!value.targets.some(target=>target!=='browser')){
|
|
393
|
+
fail('A browser-only descriptor cannot be projected as a native Arcane app.');
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
displayName:value.displayName,
|
|
397
|
+
description:value.description,
|
|
398
|
+
icon:value.native.icon,
|
|
399
|
+
order:value.native.order,
|
|
400
|
+
type:value.native.type,
|
|
401
|
+
source:source??`apps/${value.id}`,
|
|
402
|
+
entry:value.package.entry,
|
|
403
|
+
capabilities:[...value.permissions.capabilities],
|
|
404
|
+
security:{
|
|
405
|
+
connectOrigins:[...value.security.connectOrigins],
|
|
406
|
+
frameOrigins:[...value.security.frameOrigins],
|
|
407
|
+
mediaOrigins:[...value.security.mediaOrigins]
|
|
408
|
+
},
|
|
409
|
+
...(value.native.bundledApps.length?{bundledApps:[...value.native.bundledApps]}:{}),
|
|
410
|
+
...(value.native.documentCatalog?{documentCatalog:value.native.documentCatalog}:{}),
|
|
411
|
+
include:[...value.package.include]
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function synthesizedDescriptor(packageManifest,nativeDescriptor){
|
|
416
|
+
const native=nativeDescriptor??{};
|
|
417
|
+
if(packageManifest.security!==undefined&&native.security!==undefined
|
|
418
|
+
&&!isDeepStrictEqual(packageManifest.security,native.security)){
|
|
419
|
+
fail('Legacy package and native registry security policies do not match.');
|
|
420
|
+
}
|
|
421
|
+
const security=packageManifest.security??native.security??{};
|
|
422
|
+
return validateAppDescriptor({
|
|
423
|
+
schemaVersion:APP_DESCRIPTOR_SCHEMA_VERSION,
|
|
424
|
+
id:packageManifest.id,
|
|
425
|
+
displayName:packageManifest.displayName,
|
|
426
|
+
description:native.description??`${packageManifest.displayName} Arcane application.`,
|
|
427
|
+
version:packageManifest.version,
|
|
428
|
+
publisher:{id:'publisher-undeclared',name:'Publisher not declared'},
|
|
429
|
+
package:{
|
|
430
|
+
entry:packageManifest.entry,
|
|
431
|
+
strategy:packageManifest.strategy,
|
|
432
|
+
...(packageManifest.localAIModelPolicy?{localAIModelPolicy:packageManifest.localAIModelPolicy}:{}),
|
|
433
|
+
include:[...packageManifest.include],
|
|
434
|
+
exclude:[...(packageManifest.exclude??[])],
|
|
435
|
+
shared:[...packageManifest.shared].sort(),
|
|
436
|
+
...(packageManifest.adapter?{adapter:packageManifest.adapter}:{})
|
|
437
|
+
},
|
|
438
|
+
permissions:{
|
|
439
|
+
capabilities:[...(native.capabilities??[])].sort(),
|
|
440
|
+
methods:[]
|
|
441
|
+
},
|
|
442
|
+
security:{
|
|
443
|
+
connectOrigins:[...(security.connectOrigins??[])].sort(),
|
|
444
|
+
frameOrigins:[...(security.frameOrigins??[])].sort(),
|
|
445
|
+
mediaOrigins:[...(security.mediaOrigins??[])].sort()
|
|
446
|
+
},
|
|
447
|
+
native:{
|
|
448
|
+
type:native.type??'app',
|
|
449
|
+
icon:native.icon??null,
|
|
450
|
+
order:Number.isInteger(native.order)?native.order:100,
|
|
451
|
+
bundledApps:[...(native.bundledApps??[])].sort(),
|
|
452
|
+
...(native.documentCatalog?{documentCatalog:native.documentCatalog}:{})
|
|
453
|
+
},
|
|
454
|
+
requirements:{
|
|
455
|
+
arcaneProtocol:ARCANE_PROTOCOL,
|
|
456
|
+
minimumCoreVersion:ARCANE_MACHINE_BUNDLE_VERSION,
|
|
457
|
+
features:[]
|
|
458
|
+
},
|
|
459
|
+
targets:nativeDescriptor!==undefined
|
|
460
|
+
?['android-arm64','browser','linux-arm64','linux-x64','portable','windows-x64']
|
|
461
|
+
:['browser']
|
|
462
|
+
},{appId:packageManifest.id});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async function readJsonFile(filePath,label,{optional=false}={}){
|
|
466
|
+
try{
|
|
467
|
+
const info=await lstat(filePath);
|
|
468
|
+
if(info.isSymbolicLink()||!info.isFile())fail(`${label} must be a real file.`);
|
|
469
|
+
return JSON.parse(await readFile(filePath,'utf8'));
|
|
470
|
+
}catch(error){
|
|
471
|
+
if(optional&&error?.code==='ENOENT')return null;
|
|
472
|
+
if(error instanceof SyntaxError)fail(`${label} is not valid JSON: ${error.message}`);
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export async function loadAppDescriptor({workspaceRoot,appRoot,appId,packageManifest}){
|
|
478
|
+
const descriptorPath=path.join(appRoot,APP_DESCRIPTOR_NAME);
|
|
479
|
+
const authored=await readJsonFile(descriptorPath,`apps/${appId}/${APP_DESCRIPTOR_NAME}`,{optional:true});
|
|
480
|
+
if(authored){
|
|
481
|
+
const descriptor=validateAppDescriptor(authored,{appId});
|
|
482
|
+
const projection=projectPackageManifest(descriptor);
|
|
483
|
+
if(!isDeepStrictEqual(projection,packageManifest)){
|
|
484
|
+
fail(`${APP_DESCRIPTOR_NAME} does not project exactly to arcane-package.json.`);
|
|
485
|
+
}
|
|
486
|
+
return Object.freeze({descriptor,source:'authored',descriptorPath});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const registry=await readJsonFile(
|
|
490
|
+
path.join(workspaceRoot,REGISTRY_PATH),
|
|
491
|
+
'Arcane native app registry',
|
|
492
|
+
{optional:true}
|
|
493
|
+
);
|
|
494
|
+
const nativeDescriptor=isObject(registry?.apps?.[appId])?registry.apps[appId]:undefined;
|
|
495
|
+
return Object.freeze({
|
|
496
|
+
descriptor:synthesizedDescriptor(packageManifest,nativeDescriptor),
|
|
497
|
+
source:nativeDescriptor?'legacy-registry':'legacy-package',
|
|
498
|
+
descriptorPath:null
|
|
499
|
+
});
|
|
500
|
+
}
|