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,1335 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {readdir,lstat,realpath} from 'node:fs/promises';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
import {createWorkspace,initWorkspace} from './scaffold.mjs';
|
|
5
|
+
import {
|
|
6
|
+
discoverApps as discoverWorkspaceApps,
|
|
7
|
+
inspectWorkspaceProfile,
|
|
8
|
+
resolveWorkspace,
|
|
9
|
+
validateDiscoveredApplication,
|
|
10
|
+
validateWorkspace
|
|
11
|
+
} from './workspace.mjs';
|
|
12
|
+
import {loadArcaneIntegratedProvider} from './integrated-provider-loader.mjs';
|
|
13
|
+
import {startDevServer} from './dev-server.mjs';
|
|
14
|
+
import {authenticateRuntimeReceipt,loadRuntimeRelease,verifyRuntime} from './runtime.mjs';
|
|
15
|
+
import {
|
|
16
|
+
authenticateSharedPayloadSnapshot,
|
|
17
|
+
packageApp,
|
|
18
|
+
prepareSharedPayloadSnapshot,
|
|
19
|
+
verifyApp
|
|
20
|
+
} from './packager/core.mjs';
|
|
21
|
+
import {assertNativeToolchainCompatibility} from './native-plan.mjs';
|
|
22
|
+
import {runDoctor} from './doctor.mjs';
|
|
23
|
+
import {runProcess} from './process.mjs';
|
|
24
|
+
import {
|
|
25
|
+
buildTarget,
|
|
26
|
+
createNativeTargetAdapter,
|
|
27
|
+
getTargetAdapter,
|
|
28
|
+
listTargets,
|
|
29
|
+
runTarget
|
|
30
|
+
} from './targets/index.mjs';
|
|
31
|
+
import {runRepositoryAction} from './repository.mjs';
|
|
32
|
+
import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
|
|
33
|
+
import {createEventQueue} from './event-queue.mjs';
|
|
34
|
+
import {ARCANE_MACHINE_BUNDLE_VERSION} from './constants.mjs';
|
|
35
|
+
import {checkForSdkUpdate} from './update-check.mjs';
|
|
36
|
+
import {
|
|
37
|
+
APP_BUNDLE_EXTENSION,
|
|
38
|
+
createAppReleaseBundle,
|
|
39
|
+
verifyAppReleaseBundle
|
|
40
|
+
} from './release-bundle.mjs';
|
|
41
|
+
|
|
42
|
+
const TEST_RUNNER_PATH=fileURLToPath(new URL('../bin/arcane-test.mjs',import.meta.url));
|
|
43
|
+
const MAX_TEST_FAILURE_STREAM_BYTES=32*1024;
|
|
44
|
+
const MAX_TEST_FAILURE_AGGREGATE_BYTES=256*1024;
|
|
45
|
+
const MAX_TEST_FAILURE_MESSAGE_BYTES=24*1024;
|
|
46
|
+
const TEST_OUTPUT_TRUNCATION_NOTICE='[... earlier test output omitted ...]\n';
|
|
47
|
+
|
|
48
|
+
function normalizedTestOutput(value){
|
|
49
|
+
return typeof value==='string'?value.trim():'';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function boundedTestOutputTail(value,maxBytes){
|
|
53
|
+
const source=normalizedTestOutput(value);
|
|
54
|
+
const sourceBytes=Buffer.byteLength(source,'utf8');
|
|
55
|
+
if(sourceBytes<=maxBytes){
|
|
56
|
+
return {text:source,bytes:sourceBytes,truncated:false};
|
|
57
|
+
}
|
|
58
|
+
const noticeBytes=Buffer.byteLength(TEST_OUTPUT_TRUNCATION_NOTICE,'utf8');
|
|
59
|
+
if(maxBytes<noticeBytes){
|
|
60
|
+
return {text:'',bytes:0,truncated:sourceBytes>0};
|
|
61
|
+
}
|
|
62
|
+
const buffer=Buffer.from(source,'utf8');
|
|
63
|
+
let start=buffer.length-(maxBytes-noticeBytes);
|
|
64
|
+
while(start<buffer.length&&(buffer[start]&0xc0)===0x80){
|
|
65
|
+
start+=1;
|
|
66
|
+
}
|
|
67
|
+
const text=`${TEST_OUTPUT_TRUNCATION_NOTICE}${buffer.subarray(start).toString('utf8')}`;
|
|
68
|
+
return {text,bytes:Buffer.byteLength(text,'utf8'),truncated:true};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function failedTestOutputBudgets(stderr,stdout,maxBytes){
|
|
72
|
+
const desiredStderr=Math.min(
|
|
73
|
+
MAX_TEST_FAILURE_STREAM_BYTES,
|
|
74
|
+
Buffer.byteLength(normalizedTestOutput(stderr),'utf8')
|
|
75
|
+
);
|
|
76
|
+
const desiredStdout=Math.min(
|
|
77
|
+
MAX_TEST_FAILURE_STREAM_BYTES,
|
|
78
|
+
Buffer.byteLength(normalizedTestOutput(stdout),'utf8')
|
|
79
|
+
);
|
|
80
|
+
if(desiredStderr+desiredStdout<=maxBytes){
|
|
81
|
+
return {stderr:desiredStderr,stdout:desiredStdout};
|
|
82
|
+
}
|
|
83
|
+
const shared=Math.floor(maxBytes/2);
|
|
84
|
+
let stderrBudget=Math.min(desiredStderr,shared);
|
|
85
|
+
let stdoutBudget=Math.min(desiredStdout,shared);
|
|
86
|
+
let remaining=maxBytes-stderrBudget-stdoutBudget;
|
|
87
|
+
const additionalStderr=Math.min(remaining,desiredStderr-stderrBudget);
|
|
88
|
+
stderrBudget+=additionalStderr;
|
|
89
|
+
remaining-=additionalStderr;
|
|
90
|
+
stdoutBudget+=Math.min(remaining,desiredStdout-stdoutBudget);
|
|
91
|
+
return {stderr:stderrBudget,stdout:stdoutBudget};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function captureFailedTest(testFile,result,workspaceRoot,maxBytes){
|
|
95
|
+
const budgets=failedTestOutputBudgets(result.stderr,result.stdout,maxBytes);
|
|
96
|
+
const stderr=boundedTestOutputTail(result.stderr,budgets.stderr);
|
|
97
|
+
const stdout=boundedTestOutputTail(result.stdout,budgets.stdout);
|
|
98
|
+
return {
|
|
99
|
+
failure:Object.freeze({
|
|
100
|
+
testFile:path.relative(workspaceRoot,testFile).replaceAll('\\','/'),
|
|
101
|
+
exitCode:result.code,
|
|
102
|
+
signal:result.signal,
|
|
103
|
+
stdout:stdout.text,
|
|
104
|
+
stderr:stderr.text,
|
|
105
|
+
stdoutTruncated:stdout.truncated,
|
|
106
|
+
stderrTruncated:stderr.truncated
|
|
107
|
+
}),
|
|
108
|
+
bytes:stdout.bytes+stderr.bytes
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function failedTestMessage(failures){
|
|
113
|
+
const summary=`${String(failures.length)} isolated test file${failures.length===1?'':'s'} failed.`;
|
|
114
|
+
const actionable=failures.find(failure=>failure.stderr||failure.stdout);
|
|
115
|
+
if(!actionable){
|
|
116
|
+
return summary;
|
|
117
|
+
}
|
|
118
|
+
const heading=`\n\n${actionable.testFile}:\n`;
|
|
119
|
+
const available=Math.max(
|
|
120
|
+
0,
|
|
121
|
+
MAX_TEST_FAILURE_MESSAGE_BYTES-Buffer.byteLength(`${summary}${heading}`,'utf8')
|
|
122
|
+
);
|
|
123
|
+
const diagnostic=boundedTestOutputTail(
|
|
124
|
+
actionable.stderr||actionable.stdout,
|
|
125
|
+
available
|
|
126
|
+
).text;
|
|
127
|
+
return diagnostic?`${summary}${heading}${diagnostic}`:summary;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function emit(onEvent,event){
|
|
131
|
+
await onEvent?.(event);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function nativeArtifactReceipt(result){
|
|
135
|
+
return result?.artifactReceipt??result?.built?.artifactReceipt??null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function withEventDeliveryFailure(result,error){
|
|
139
|
+
const eventDelivery=Object.freeze({
|
|
140
|
+
status:'degraded',
|
|
141
|
+
errorCode:'ARCANE_EVENT_DELIVERY_FAILED',
|
|
142
|
+
message:String(error?.message??error)
|
|
143
|
+
});
|
|
144
|
+
if(result?.built?.artifactReceipt){
|
|
145
|
+
return {...result,built:{...result.built,eventDelivery}};
|
|
146
|
+
}
|
|
147
|
+
return {...result,eventDelivery};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isNativeCommitEvent(_label,event){
|
|
151
|
+
return event?.type==='native.build.committed'
|
|
152
|
+
||(event?.phase==='publish'&&event?.status==='completed');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function ownedWork(label,work,{signal,onEvent,heartbeatMs=5000}={}){
|
|
156
|
+
throwIfAborted(signal);
|
|
157
|
+
const controller=new AbortController();
|
|
158
|
+
const forwardAbort=()=>controller.abort(signal?.reason);
|
|
159
|
+
signal?.addEventListener('abort',forwardAbort,{once:true});
|
|
160
|
+
if(signal?.aborted){
|
|
161
|
+
forwardAbort();
|
|
162
|
+
}
|
|
163
|
+
throwIfAborted(controller.signal);
|
|
164
|
+
let committed=false;
|
|
165
|
+
const events=createEventQueue(onEvent,{
|
|
166
|
+
onFailure:error=>{
|
|
167
|
+
if(!committed||label==='run')controller.abort(error);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
const forwardEvent=async event=>{
|
|
171
|
+
if(isNativeCommitEvent(label,event))committed=true;
|
|
172
|
+
try{
|
|
173
|
+
await events.send(event);
|
|
174
|
+
}catch(error){
|
|
175
|
+
if(!committed||label==='run')throw error;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const abort=()=>{
|
|
179
|
+
void events.enqueue({
|
|
180
|
+
type:`${label}.cancellation-pending`,
|
|
181
|
+
message:`${label} will stop at the next safe boundary.`
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
let heartbeat=null;
|
|
185
|
+
let producersStopped=false;
|
|
186
|
+
const stopProducers=()=>{
|
|
187
|
+
if(producersStopped){
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
producersStopped=true;
|
|
191
|
+
clearInterval(heartbeat);
|
|
192
|
+
controller.signal.removeEventListener('abort',abort);
|
|
193
|
+
signal?.removeEventListener('abort',forwardAbort);
|
|
194
|
+
};
|
|
195
|
+
try{
|
|
196
|
+
await events.send({type:`${label}.started`,message:`${label} started.`});
|
|
197
|
+
controller.signal.addEventListener('abort',abort,{once:true});
|
|
198
|
+
heartbeat=setInterval(()=>{
|
|
199
|
+
void events.enqueue(
|
|
200
|
+
{type:`${label}.heartbeat`,message:`${label} is still running.`},
|
|
201
|
+
{coalesce:`${label}.heartbeat`}
|
|
202
|
+
);
|
|
203
|
+
},Math.max(1000,heartbeatMs));
|
|
204
|
+
heartbeat.unref?.();
|
|
205
|
+
const result=await work({
|
|
206
|
+
signal:controller.signal,
|
|
207
|
+
onEvent:forwardEvent
|
|
208
|
+
});
|
|
209
|
+
if(nativeArtifactReceipt(result))committed=true;
|
|
210
|
+
stopProducers();
|
|
211
|
+
if(!committed||label==='run')throwIfAborted(controller.signal);
|
|
212
|
+
try{
|
|
213
|
+
await events.send({type:`${label}.completed`,message:`${label} completed.`});
|
|
214
|
+
await events.drain();
|
|
215
|
+
}catch(error){
|
|
216
|
+
if(committed){
|
|
217
|
+
if(label==='run')throwIfAborted(controller.signal);
|
|
218
|
+
return withEventDeliveryFailure(result,error);
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
if(!committed||label==='run')throwIfAborted(controller.signal);
|
|
223
|
+
if(events.error&&committed)return withEventDeliveryFailure(result,events.error);
|
|
224
|
+
return result;
|
|
225
|
+
}catch(error){
|
|
226
|
+
stopProducers();
|
|
227
|
+
try{
|
|
228
|
+
await events.drain();
|
|
229
|
+
}catch(callbackFailure){
|
|
230
|
+
throw callbackFailure;
|
|
231
|
+
}
|
|
232
|
+
throw error;
|
|
233
|
+
}finally{
|
|
234
|
+
stopProducers();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function collectTests(root,files,signal){
|
|
239
|
+
let entries;
|
|
240
|
+
try{
|
|
241
|
+
entries=await readdir(root,{withFileTypes:true});
|
|
242
|
+
}catch(error){
|
|
243
|
+
if(error?.code==='ENOENT'){
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
for(const entry of entries.sort((left,right)=>left.name.localeCompare(right.name,'en'))){
|
|
249
|
+
throwIfAborted(signal);
|
|
250
|
+
const absolute=path.join(root,entry.name);
|
|
251
|
+
if(entry.isSymbolicLink()){
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if(entry.isDirectory()){
|
|
255
|
+
await collectTests(absolute,files,signal);
|
|
256
|
+
}else if(entry.isFile()&&/\.test\.(?:mjs|cjs|js)$/u.test(entry.name)){
|
|
257
|
+
const info=await lstat(absolute);
|
|
258
|
+
if(info.isFile()&&!info.isSymbolicLink()){
|
|
259
|
+
files.add(absolute);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function selectedWorkspace(options){
|
|
266
|
+
const resolved=await resolveWorkspace({
|
|
267
|
+
workspaceRoot:options.workspaceRoot,
|
|
268
|
+
appId:options.appId
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
workspaceRoot:resolved.workspaceRoot,
|
|
272
|
+
workspaceMode:resolved.config.workspaceMode,
|
|
273
|
+
appId:resolved.appId??resolved.app?.id??resolved.app?.appId,
|
|
274
|
+
appRoot:resolved.appRoot??resolved.app?.appRoot
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function operationScope(options){
|
|
279
|
+
const scope=options.scope??'app';
|
|
280
|
+
if(!['app','shared'].includes(scope)){
|
|
281
|
+
throw new ArcaneError(
|
|
282
|
+
ERROR_CODES.usage,
|
|
283
|
+
`Unsupported Arcane development scope: ${String(scope)}. Expected app or shared.`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return scope;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function assertApplicationScope(options,operation){
|
|
290
|
+
if(operationScope(options)!=='app'){
|
|
291
|
+
throw new ArcaneError(
|
|
292
|
+
ERROR_CODES.usage,
|
|
293
|
+
`${operation} supports only --scope app. Shared development cannot package or build application output.`
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function executeIntegratedSharedOperation(operation,options){
|
|
299
|
+
const label=operation==='focused-test'?'shared.test':'shared.check';
|
|
300
|
+
return ownedWork(
|
|
301
|
+
label,
|
|
302
|
+
async({signal,onEvent})=>{
|
|
303
|
+
const profile=await inspectWorkspaceProfile(options.workspaceRoot);
|
|
304
|
+
if(profile.workspaceMode!=='integrated'){
|
|
305
|
+
throw new ArcaneError(
|
|
306
|
+
ERROR_CODES.policyDenied,
|
|
307
|
+
`--scope shared is available only in an integrated Arcane OS workspace.`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const provider=await loadArcaneIntegratedProvider({
|
|
311
|
+
arcaneRoot:profile.workspaceRoot,
|
|
312
|
+
signal,
|
|
313
|
+
onEvent,
|
|
314
|
+
run:options.processRunner??runProcess
|
|
315
|
+
});
|
|
316
|
+
const result=await provider.execute({
|
|
317
|
+
operation,
|
|
318
|
+
...(operation==='focused-test'?{testFile:options.testFile}:{}),
|
|
319
|
+
signal,
|
|
320
|
+
onEvent
|
|
321
|
+
});
|
|
322
|
+
return Object.freeze({
|
|
323
|
+
scope:'shared',
|
|
324
|
+
workspaceMode:'integrated',
|
|
325
|
+
workspaceRoot:profile.workspaceRoot,
|
|
326
|
+
providerGeneration:provider.providerGeneration,
|
|
327
|
+
result
|
|
328
|
+
});
|
|
329
|
+
},
|
|
330
|
+
options
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function preparedWorkspace(options){
|
|
335
|
+
const workspace=await validateWorkspace({
|
|
336
|
+
workspaceRoot:options.workspaceRoot,
|
|
337
|
+
appId:options.appId,
|
|
338
|
+
signal:options.signal,
|
|
339
|
+
onEvent:options.onEvent
|
|
340
|
+
});
|
|
341
|
+
const external=workspace.workspaceMode==='external';
|
|
342
|
+
const runtimeRoot=external
|
|
343
|
+
?path.join(workspace.workspaceRoot,'node_modules','arcane-os','runtime')
|
|
344
|
+
:workspace.workspaceRoot;
|
|
345
|
+
const runtimeReceipt=external&&!options.deferRuntimeVerification
|
|
346
|
+
?await verifyRuntime({
|
|
347
|
+
runtimeRoot,
|
|
348
|
+
signal:options.signal,
|
|
349
|
+
onEvent:options.onEvent
|
|
350
|
+
})
|
|
351
|
+
:null;
|
|
352
|
+
return {
|
|
353
|
+
runtimeReceipt,
|
|
354
|
+
runtimeRoot,
|
|
355
|
+
workspaceMode:workspace.workspaceMode,
|
|
356
|
+
descriptor:workspace.app.descriptor,
|
|
357
|
+
descriptorSource:workspace.app.descriptorSource,
|
|
358
|
+
workspaceRoot:workspace.workspaceRoot,
|
|
359
|
+
appId:workspace.appId,
|
|
360
|
+
appRoot:workspace.appRoot,
|
|
361
|
+
validation:workspace
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function compareRuntimeInventory(left,right){
|
|
366
|
+
return left.path.localeCompare(right.path,'en');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function externalRuntimeSnapshotReceipt(prepared,sharedPayloadSnapshot,sharedPayloadIds,{signal,onEvent}={}){
|
|
370
|
+
throwIfAborted(signal);
|
|
371
|
+
await authenticateSharedPayloadSnapshot(sharedPayloadSnapshot,{
|
|
372
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
373
|
+
sharedPayloadIds,
|
|
374
|
+
signal
|
|
375
|
+
});
|
|
376
|
+
const release=await loadRuntimeRelease({runtimeRoot:prepared.runtimeRoot});
|
|
377
|
+
const runtimePrefix='node_modules/arcane-os/runtime/';
|
|
378
|
+
const files=sharedPayloadSnapshot.files
|
|
379
|
+
.filter(file=>file.source.startsWith(runtimePrefix))
|
|
380
|
+
.map(file=>Object.freeze({
|
|
381
|
+
path:file.source.slice(runtimePrefix.length),
|
|
382
|
+
bytes:file.bytes,
|
|
383
|
+
sha256:file.sha256
|
|
384
|
+
}))
|
|
385
|
+
.sort(compareRuntimeInventory);
|
|
386
|
+
const expected=[...release.files].sort(compareRuntimeInventory);
|
|
387
|
+
if(JSON.stringify(files)!==JSON.stringify(expected)){
|
|
388
|
+
throw new ArcaneError(
|
|
389
|
+
ERROR_CODES.integrityFailed,
|
|
390
|
+
'The retained shared payload does not match the installed Arcane runtime manifest.'
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const receipt=Object.freeze({
|
|
394
|
+
schemaVersion:1,
|
|
395
|
+
kind:'arcane-sdk-runtime-shared-payload',
|
|
396
|
+
canonicalLocation:prepared.runtimeRoot,
|
|
397
|
+
source:Object.freeze({...release.source}),
|
|
398
|
+
files:Object.freeze(files),
|
|
399
|
+
fileCount:release.fileCount,
|
|
400
|
+
totalBytes:release.totalBytes,
|
|
401
|
+
contentSha256:release.contentSha256,
|
|
402
|
+
sharedPayloadContentSha256:sharedPayloadSnapshot.contentSha256
|
|
403
|
+
});
|
|
404
|
+
await emit(onEvent,{
|
|
405
|
+
type:'runtime.snapshot.verified',
|
|
406
|
+
contentSha256:receipt.contentSha256,
|
|
407
|
+
fileCount:receipt.fileCount,
|
|
408
|
+
totalBytes:receipt.totalBytes
|
|
409
|
+
});
|
|
410
|
+
return receipt;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function validatePreparedRuntime(prepared,{signal}={}){
|
|
414
|
+
if(prepared.workspaceMode==='external'){
|
|
415
|
+
return authenticateRuntimeReceipt(prepared.runtimeReceipt,{
|
|
416
|
+
runtimeRoot:prepared.runtimeRoot,
|
|
417
|
+
signal
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
const validation=await validateWorkspace({
|
|
421
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
422
|
+
appId:prepared.appId,
|
|
423
|
+
signal
|
|
424
|
+
});
|
|
425
|
+
if(validation.workspaceMode!=='integrated'){
|
|
426
|
+
throw new ArcaneError(ERROR_CODES.workspaceInvalid,'The integrated Arcane workspace profile changed during the operation.');
|
|
427
|
+
}
|
|
428
|
+
return validation;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export async function createApplication(options={}){
|
|
432
|
+
return createWorkspace(options);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export async function initializeApplication(options={}){
|
|
436
|
+
return initWorkspace(options);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export async function doctorApplication(options={}){
|
|
440
|
+
const result=await runDoctor(options);
|
|
441
|
+
if(!result.ok){
|
|
442
|
+
throw new ArcaneError(
|
|
443
|
+
ERROR_CODES.prerequisiteMissing,
|
|
444
|
+
'One or more required Arcane development prerequisites failed.',
|
|
445
|
+
{details:result}
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export async function testApplication(options={}){
|
|
452
|
+
throwIfAborted(options.signal);
|
|
453
|
+
if(operationScope(options)==='shared'){
|
|
454
|
+
if(typeof options.testFile!=='string'||!options.testFile){
|
|
455
|
+
throw new ArcaneError(
|
|
456
|
+
ERROR_CODES.usage,
|
|
457
|
+
'Shared testing requires one exact repo-relative .test.mjs file.'
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
return executeIntegratedSharedOperation('focused-test',options);
|
|
461
|
+
}
|
|
462
|
+
const workspace=options.workspaceRoot&&options.appId&&options.appRoot
|
|
463
|
+
?{
|
|
464
|
+
workspaceRoot:options.workspaceRoot,
|
|
465
|
+
workspaceMode:options.workspaceMode
|
|
466
|
+
??(await inspectWorkspaceProfile(options.workspaceRoot)).workspaceMode,
|
|
467
|
+
appId:options.appId,
|
|
468
|
+
appRoot:options.appRoot
|
|
469
|
+
}
|
|
470
|
+
:await selectedWorkspace(options);
|
|
471
|
+
const files=new Set();
|
|
472
|
+
if(workspace.workspaceMode==='external'){
|
|
473
|
+
await collectTests(path.join(workspace.workspaceRoot,'test'),files,options.signal);
|
|
474
|
+
}
|
|
475
|
+
if(workspace.appRoot){
|
|
476
|
+
await collectTests(path.join(workspace.appRoot,'test'),files,options.signal);
|
|
477
|
+
}
|
|
478
|
+
const testFiles=[...files].sort();
|
|
479
|
+
if(testFiles.length===0){
|
|
480
|
+
await emit(options.onEvent,{
|
|
481
|
+
type:'test.skipped',
|
|
482
|
+
message:'No JavaScript test files were found.',
|
|
483
|
+
data:{workspaceRoot:workspace.workspaceRoot,appId:workspace.appId}
|
|
484
|
+
});
|
|
485
|
+
return {...workspace,passed:true,skipped:true,testFiles:[]};
|
|
486
|
+
}
|
|
487
|
+
const outputs=[];
|
|
488
|
+
const failures=[];
|
|
489
|
+
let failureOutputBytes=0;
|
|
490
|
+
const perFileFailureOutputBytes=Math.min(
|
|
491
|
+
MAX_TEST_FAILURE_STREAM_BYTES*2,
|
|
492
|
+
Math.floor(MAX_TEST_FAILURE_AGGREGATE_BYTES/testFiles.length)
|
|
493
|
+
);
|
|
494
|
+
for(const testFile of testFiles){
|
|
495
|
+
throwIfAborted(options.signal);
|
|
496
|
+
const result=await runProcess(
|
|
497
|
+
process.execPath,
|
|
498
|
+
[TEST_RUNNER_PATH,testFile],
|
|
499
|
+
{
|
|
500
|
+
cwd:workspace.workspaceRoot,
|
|
501
|
+
signal:options.signal,
|
|
502
|
+
onEvent:options.onEvent,
|
|
503
|
+
allowNonzero:true
|
|
504
|
+
}
|
|
505
|
+
);
|
|
506
|
+
if(result.code===0){
|
|
507
|
+
if(result.stdout.trim())outputs.push(result.stdout.trim());
|
|
508
|
+
}else if(result.code===1){
|
|
509
|
+
const captured=captureFailedTest(
|
|
510
|
+
testFile,
|
|
511
|
+
result,
|
|
512
|
+
workspace.workspaceRoot,
|
|
513
|
+
Math.min(
|
|
514
|
+
perFileFailureOutputBytes,
|
|
515
|
+
MAX_TEST_FAILURE_AGGREGATE_BYTES-failureOutputBytes
|
|
516
|
+
)
|
|
517
|
+
);
|
|
518
|
+
failures.push(captured.failure);
|
|
519
|
+
failureOutputBytes+=captured.bytes;
|
|
520
|
+
}else if(result.code!==0){
|
|
521
|
+
throw new ArcaneError(
|
|
522
|
+
ERROR_CODES.operationFailed,
|
|
523
|
+
`The isolated test runner exited with code ${String(result.code)} for ${path.basename(testFile)}.`,
|
|
524
|
+
{details:{testFile,result}}
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if(failures.length>0){
|
|
529
|
+
throw new ArcaneError(
|
|
530
|
+
ERROR_CODES.operationFailed,
|
|
531
|
+
failedTestMessage(failures),
|
|
532
|
+
{
|
|
533
|
+
details:{
|
|
534
|
+
testFiles:failures.map(failure=>failure.testFile),
|
|
535
|
+
failures,
|
|
536
|
+
outputBytes:failureOutputBytes,
|
|
537
|
+
outputLimitBytes:MAX_TEST_FAILURE_AGGREGATE_BYTES,
|
|
538
|
+
outputTruncated:failures.some(
|
|
539
|
+
failure=>failure.stdoutTruncated||failure.stderrTruncated
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
...workspace,
|
|
547
|
+
passed:true,
|
|
548
|
+
skipped:false,
|
|
549
|
+
testFiles:testFiles.map(file=>path.relative(workspace.workspaceRoot,file).replaceAll('\\','/')),
|
|
550
|
+
output:outputs.join('\n')
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export async function checkApplication(options={}){
|
|
555
|
+
if(operationScope(options)==='shared'){
|
|
556
|
+
if(options.skipTests){
|
|
557
|
+
throw new ArcaneError(ERROR_CODES.usage,'Shared check does not accept skipTests.');
|
|
558
|
+
}
|
|
559
|
+
return executeIntegratedSharedOperation('development-check',options);
|
|
560
|
+
}
|
|
561
|
+
const prepared=await preparedWorkspace(options);
|
|
562
|
+
const tests=options.skipTests
|
|
563
|
+
?{passed:true,skipped:true,testFiles:[]}
|
|
564
|
+
:await testApplication({...options,...prepared});
|
|
565
|
+
await validatePreparedRuntime(prepared,{signal:options.signal});
|
|
566
|
+
return {
|
|
567
|
+
ok:true,
|
|
568
|
+
workspaceMode:prepared.workspaceMode,
|
|
569
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
570
|
+
appId:prepared.appId,
|
|
571
|
+
descriptorSource:prepared.descriptorSource,
|
|
572
|
+
runtime:prepared.runtimeReceipt
|
|
573
|
+
?{
|
|
574
|
+
mode:'sdk',
|
|
575
|
+
manifestSha256:prepared.runtimeReceipt.manifestSha256,
|
|
576
|
+
contentSha256:prepared.runtimeReceipt.contentSha256,
|
|
577
|
+
fileCount:prepared.runtimeReceipt.fileCount
|
|
578
|
+
}
|
|
579
|
+
:{
|
|
580
|
+
mode:'workspace',
|
|
581
|
+
sourceRoot:'arcane'
|
|
582
|
+
},
|
|
583
|
+
checks:prepared.validation.checks,
|
|
584
|
+
tests
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export async function developApplication(options={}){
|
|
589
|
+
assertApplicationScope(options,'Development serving');
|
|
590
|
+
const prepared=await preparedWorkspace(options);
|
|
591
|
+
const server=await startDevServer({
|
|
592
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
593
|
+
appId:prepared.appId,
|
|
594
|
+
mode:'source',
|
|
595
|
+
runtimeReceipt:prepared.runtimeReceipt,
|
|
596
|
+
workspaceMode:prepared.workspaceMode,
|
|
597
|
+
host:options.host,
|
|
598
|
+
port:options.port,
|
|
599
|
+
signal:options.signal,
|
|
600
|
+
onEvent:options.onEvent
|
|
601
|
+
});
|
|
602
|
+
return {...server,appId:prepared.appId,mode:'source'};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export async function packageApplication(options={}){
|
|
606
|
+
assertApplicationScope(options,'Packaging');
|
|
607
|
+
const prepared=await preparedWorkspace(options);
|
|
608
|
+
const release=await ownedWork(
|
|
609
|
+
'package',
|
|
610
|
+
({signal,onEvent})=>packageApp({
|
|
611
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
612
|
+
appId:prepared.appId,
|
|
613
|
+
dryRun:Boolean(options.dryRun),
|
|
614
|
+
signal,
|
|
615
|
+
onEvent,
|
|
616
|
+
validateSourceState:({signal}={})=>validatePreparedRuntime(prepared,{signal})
|
|
617
|
+
}),
|
|
618
|
+
options
|
|
619
|
+
);
|
|
620
|
+
return {
|
|
621
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
622
|
+
workspaceMode:prepared.workspaceMode,
|
|
623
|
+
appId:prepared.appId,
|
|
624
|
+
runtimeContentSha256:prepared.runtimeReceipt?.contentSha256??null,
|
|
625
|
+
release
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export async function verifyApplication(options={}){
|
|
630
|
+
assertApplicationScope(options,'Package verification');
|
|
631
|
+
const prepared=await preparedWorkspace(options);
|
|
632
|
+
const release=await ownedWork(
|
|
633
|
+
'verify',
|
|
634
|
+
({signal,onEvent})=>verifyApp({
|
|
635
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
636
|
+
appId:prepared.appId,
|
|
637
|
+
signal,
|
|
638
|
+
onEvent
|
|
639
|
+
}),
|
|
640
|
+
options
|
|
641
|
+
);
|
|
642
|
+
await validatePreparedRuntime(prepared,{signal:options.signal});
|
|
643
|
+
return {
|
|
644
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
645
|
+
workspaceMode:prepared.workspaceMode,
|
|
646
|
+
appId:prepared.appId,
|
|
647
|
+
runtimeContentSha256:prepared.runtimeReceipt?.contentSha256??null,
|
|
648
|
+
release
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export async function bundleApplication(options={}){
|
|
653
|
+
assertApplicationScope(options,'Release bundling');
|
|
654
|
+
if(options.overwrite!==undefined&&typeof options.overwrite!=='boolean'){
|
|
655
|
+
throw new ArcaneError(ERROR_CODES.usage,'overwrite must be a literal boolean.');
|
|
656
|
+
}
|
|
657
|
+
const prepared=await preparedWorkspace(options);
|
|
658
|
+
const bundle=await ownedWork(
|
|
659
|
+
'bundle',
|
|
660
|
+
async({signal,onEvent})=>{
|
|
661
|
+
const verified=await verifyApp({
|
|
662
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
663
|
+
appId:prepared.appId,
|
|
664
|
+
signal,
|
|
665
|
+
onEvent
|
|
666
|
+
});
|
|
667
|
+
await validatePreparedRuntime(prepared,{signal});
|
|
668
|
+
const releaseRoot=verified.receipt.canonicalLocation;
|
|
669
|
+
const outputPath=options.artifactPath??path.join(
|
|
670
|
+
path.dirname(releaseRoot),
|
|
671
|
+
`${prepared.appId}-${verified.version}${APP_BUNDLE_EXTENSION}`
|
|
672
|
+
);
|
|
673
|
+
return createAppReleaseBundle({
|
|
674
|
+
receipt:verified.receipt,
|
|
675
|
+
releaseRoot,
|
|
676
|
+
outputPath,
|
|
677
|
+
overwrite:options.overwrite,
|
|
678
|
+
signal,
|
|
679
|
+
onEvent
|
|
680
|
+
});
|
|
681
|
+
},
|
|
682
|
+
options
|
|
683
|
+
);
|
|
684
|
+
return {
|
|
685
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
686
|
+
workspaceMode:prepared.workspaceMode,
|
|
687
|
+
appId:prepared.appId,
|
|
688
|
+
runtimeContentSha256:prepared.runtimeReceipt?.contentSha256??null,
|
|
689
|
+
bundle
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export async function verifyBundleApplication(options={}){
|
|
694
|
+
return ownedWork(
|
|
695
|
+
'verify-bundle',
|
|
696
|
+
({signal,onEvent})=>verifyAppReleaseBundle({
|
|
697
|
+
bundlePath:options.artifactPath,
|
|
698
|
+
signal,
|
|
699
|
+
onEvent
|
|
700
|
+
}),
|
|
701
|
+
options
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function targetSelection(options){
|
|
706
|
+
const target=options.target??options.targetRequest?.target;
|
|
707
|
+
const registered=getTargetAdapter(target);
|
|
708
|
+
if(target!=='browser'&&options.nativeBuilder!=null){
|
|
709
|
+
return {
|
|
710
|
+
target,
|
|
711
|
+
adapter:createNativeTargetAdapter({
|
|
712
|
+
targetId:target,
|
|
713
|
+
nativeBuilder:options.nativeBuilder
|
|
714
|
+
})
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
return {target,adapter:registered};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export async function planApplication(options={}){
|
|
721
|
+
assertApplicationScope(options,'Target planning');
|
|
722
|
+
const {target,adapter}=targetSelection(options);
|
|
723
|
+
const targetDescription=await adapter.describe();
|
|
724
|
+
if(targetDescription.status!=='available'){
|
|
725
|
+
return adapter.plan({...options,target});
|
|
726
|
+
}
|
|
727
|
+
if(target==='browser'){
|
|
728
|
+
const prepared=await preparedWorkspace(options);
|
|
729
|
+
return ownedWork(
|
|
730
|
+
'plan',
|
|
731
|
+
({signal,onEvent})=>adapter.plan({
|
|
732
|
+
...options,
|
|
733
|
+
...prepared,
|
|
734
|
+
target,
|
|
735
|
+
signal,
|
|
736
|
+
onEvent
|
|
737
|
+
}),
|
|
738
|
+
options
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
return ownedWork(
|
|
742
|
+
'plan',
|
|
743
|
+
({signal,onEvent})=>adapter.plan({...options,target,signal,onEvent}),
|
|
744
|
+
options
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
export async function doctorNativeTarget(options={}){
|
|
749
|
+
const {target,adapter}=targetSelection(options);
|
|
750
|
+
const targetDescription=await adapter.describe();
|
|
751
|
+
if(targetDescription.status!=='available'){
|
|
752
|
+
return adapter.doctor({...options,target});
|
|
753
|
+
}
|
|
754
|
+
if(target==='browser'){
|
|
755
|
+
throw new ArcaneError(
|
|
756
|
+
ERROR_CODES.targetUnavailable,
|
|
757
|
+
'Native target doctor requires one explicitly selected native target.'
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
return ownedWork(
|
|
761
|
+
'native.doctor',
|
|
762
|
+
({signal,onEvent})=>adapter.doctor({...options,target,signal,onEvent}),
|
|
763
|
+
options
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export async function prepareNativeTarget(options={}){
|
|
768
|
+
const {target,adapter}=targetSelection(options);
|
|
769
|
+
const targetDescription=await adapter.describe();
|
|
770
|
+
if(targetDescription.status!=='available'){
|
|
771
|
+
return adapter.prepare({...options,target});
|
|
772
|
+
}
|
|
773
|
+
if(target==='browser'){
|
|
774
|
+
throw new ArcaneError(
|
|
775
|
+
ERROR_CODES.targetUnavailable,
|
|
776
|
+
'Native target preparation requires one explicitly selected native target.'
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
return ownedWork(
|
|
780
|
+
'native.prepare',
|
|
781
|
+
({signal,onEvent})=>adapter.prepare({...options,target,signal,onEvent}),
|
|
782
|
+
options
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function sameOrDescendant(parent,candidate){
|
|
787
|
+
const relative=path.relative(path.resolve(parent),path.resolve(candidate));
|
|
788
|
+
return relative===''||Boolean(relative&&!relative.startsWith('..')&&!path.isAbsolute(relative));
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
export function resolveNativeBuildOutputRoot({target,workspaceMode,workspaceRoot,outputRoot}={}){
|
|
792
|
+
if(typeof target!=='string'||!target||target==='browser'){
|
|
793
|
+
throw new ArcaneError(ERROR_CODES.usage,'A native output root requires one explicit native target.');
|
|
794
|
+
}
|
|
795
|
+
if(workspaceMode==='integrated'&&!outputRoot){
|
|
796
|
+
throw new ArcaneError(
|
|
797
|
+
ERROR_CODES.usage,
|
|
798
|
+
`An integrated ${target} build requires --output-root outside the Arcane OS checkout.`
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
const resolved=path.resolve(outputRoot??path.join(workspaceRoot,'build',target));
|
|
802
|
+
if(workspaceMode==='integrated'&&sameOrDescendant(workspaceRoot,resolved)){
|
|
803
|
+
throw new ArcaneError(
|
|
804
|
+
ERROR_CODES.policyDenied,
|
|
805
|
+
`An integrated ${target} output root must be outside the Arcane OS checkout.`
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
if(workspaceMode==='external'&&sameOrDescendant(workspaceRoot,resolved)){
|
|
809
|
+
const buildRoot=path.resolve(workspaceRoot,'build');
|
|
810
|
+
if(!sameOrDescendant(buildRoot,resolved)){
|
|
811
|
+
throw new ArcaneError(
|
|
812
|
+
ERROR_CODES.policyDenied,
|
|
813
|
+
`An external ${target} output inside the app workspace must remain under its dedicated build/ namespace.`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return resolved;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export function resolvePortableBuildOutputRoot(options={}){
|
|
821
|
+
return resolveNativeBuildOutputRoot({...options,target:'portable'});
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function sameCanonicalPath(left,right){
|
|
825
|
+
const normalize=value=>{
|
|
826
|
+
const normalized=path.normalize(path.resolve(value));
|
|
827
|
+
return process.platform==='win32'?normalized.toLowerCase():normalized;
|
|
828
|
+
};
|
|
829
|
+
return normalize(left)===normalize(right);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
export function assertIntegratedNativeToolchain({workspaceMode,workspaceRoot,toolchainRoot,target}={}){
|
|
833
|
+
if(workspaceMode!=='integrated')return;
|
|
834
|
+
if(typeof toolchainRoot!=='string'||!sameCanonicalPath(workspaceRoot,toolchainRoot)){
|
|
835
|
+
throw new ArcaneError(
|
|
836
|
+
ERROR_CODES.policyDenied,
|
|
837
|
+
`An integrated ${target??'native'} build must use the same Arcane OS checkout for --workspace and --arcane-root.`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
export function assertIntegratedPortableToolchain(options={}){
|
|
843
|
+
return assertIntegratedNativeToolchain({...options,target:'portable'});
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
export function assertNativeApplicationToolchainCompatibility({prepared,toolchainReceipt}={}){
|
|
847
|
+
return assertNativeToolchainCompatibility({
|
|
848
|
+
appDescriptor:prepared?.descriptor,
|
|
849
|
+
toolchainReceipt,
|
|
850
|
+
minimumCoreVersion:prepared?.runtimeReceipt?.source?.bundleVersion
|
|
851
|
+
??ARCANE_MACHINE_BUNDLE_VERSION
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
export function assertPortableToolchainCompatibility(options={}){
|
|
856
|
+
return assertNativeApplicationToolchainCompatibility(options);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export async function verifyNativeArtifact(options={}){
|
|
860
|
+
const {target,adapter}=targetSelection(options);
|
|
861
|
+
const targetDescription=await adapter.describe();
|
|
862
|
+
if(targetDescription.status!=='available'){
|
|
863
|
+
return adapter.verify({...options,target});
|
|
864
|
+
}
|
|
865
|
+
if(target==='browser'){
|
|
866
|
+
throw new ArcaneError(
|
|
867
|
+
ERROR_CODES.targetUnavailable,
|
|
868
|
+
'Native artifact verification requires one explicitly selected native target.'
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
return ownedWork(
|
|
872
|
+
'native.verify',
|
|
873
|
+
({signal,onEvent})=>adapter.verify({...options,target,signal,onEvent}),
|
|
874
|
+
options
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function nativeDependencyError(message,code=ERROR_CODES.workspaceInvalid){
|
|
879
|
+
throw new ArcaneError(code,message);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
async function nativeDependencyClosure(prepared,{target,signal,onEvent}={}){
|
|
883
|
+
throwIfAborted(signal);
|
|
884
|
+
const apps=await discoverWorkspaceApps(prepared.workspaceRoot);
|
|
885
|
+
const byId=new Map(apps.map(app=>[app.appId,app]));
|
|
886
|
+
const selected=byId.get(prepared.appId);
|
|
887
|
+
if(!selected){
|
|
888
|
+
nativeDependencyError(`The selected app ${prepared.appId} disappeared during native build preflight.`);
|
|
889
|
+
}
|
|
890
|
+
if(JSON.stringify(selected.descriptor)!==JSON.stringify(prepared.descriptor)){
|
|
891
|
+
nativeDependencyError(
|
|
892
|
+
`The selected app ${prepared.appId} descriptor changed during native build preflight.`,
|
|
893
|
+
ERROR_CODES.integrityFailed
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
if(!prepared.descriptor.targets.includes(target)){
|
|
897
|
+
nativeDependencyError(
|
|
898
|
+
`App ${prepared.appId} does not declare target ${target}.`,
|
|
899
|
+
ERROR_CODES.targetUnavailable
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const visiting=new Set([prepared.appId]);
|
|
904
|
+
const visited=new Set();
|
|
905
|
+
const discovered=new Set();
|
|
906
|
+
const dependencies=[];
|
|
907
|
+
function visit(descriptor){
|
|
908
|
+
for(const dependencyId of descriptor.native.bundledApps){
|
|
909
|
+
if(visiting.has(dependencyId)){
|
|
910
|
+
nativeDependencyError(`Bundled app dependency cycle includes ${dependencyId}.`);
|
|
911
|
+
}
|
|
912
|
+
if(visited.has(dependencyId))continue;
|
|
913
|
+
const dependency=byId.get(dependencyId);
|
|
914
|
+
if(!dependency){
|
|
915
|
+
nativeDependencyError(
|
|
916
|
+
`Bundled app dependency ${dependencyId} must be present in the same Arcane workspace.`
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
if(!dependency.descriptor.targets.includes(target)){
|
|
920
|
+
nativeDependencyError(
|
|
921
|
+
`Bundled app dependency ${dependencyId} does not declare target ${target}.`,
|
|
922
|
+
ERROR_CODES.targetUnavailable
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
if(discovered.size>=64){
|
|
926
|
+
nativeDependencyError('A native app dependency closure may contain at most 64 apps.');
|
|
927
|
+
}
|
|
928
|
+
discovered.add(dependencyId);
|
|
929
|
+
visiting.add(dependencyId);
|
|
930
|
+
visit(dependency.descriptor);
|
|
931
|
+
visiting.delete(dependencyId);
|
|
932
|
+
visited.add(dependencyId);
|
|
933
|
+
dependencies.push(dependency);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
visit(prepared.descriptor);
|
|
937
|
+
dependencies.sort((left,right)=>left.appId.localeCompare(right.appId,'en'));
|
|
938
|
+
|
|
939
|
+
const validated=[];
|
|
940
|
+
for(const dependency of dependencies){
|
|
941
|
+
throwIfAborted(signal);
|
|
942
|
+
const validation=await validateDiscoveredApplication({
|
|
943
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
944
|
+
workspaceMode:prepared.workspaceMode,
|
|
945
|
+
app:dependency,
|
|
946
|
+
signal,
|
|
947
|
+
onEvent
|
|
948
|
+
});
|
|
949
|
+
validated.push(Object.freeze({
|
|
950
|
+
...dependency,
|
|
951
|
+
descriptor:validation.app.descriptor,
|
|
952
|
+
validation
|
|
953
|
+
}));
|
|
954
|
+
}
|
|
955
|
+
return Object.freeze(validated);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
async function externalWorkspaceProtectedRoots(prepared,{signal}={}){
|
|
959
|
+
if(prepared.workspaceMode!=='external')return [await realpath(prepared.workspaceRoot)];
|
|
960
|
+
const protectedRoots=[];
|
|
961
|
+
const entries=await readdir(prepared.workspaceRoot,{withFileTypes:true});
|
|
962
|
+
for(const entry of entries.sort((left,right)=>left.name.localeCompare(right.name,'en'))){
|
|
963
|
+
throwIfAborted(signal);
|
|
964
|
+
if(entry.name==='build')continue;
|
|
965
|
+
if(entry.isDirectory()||entry.isSymbolicLink()){
|
|
966
|
+
const candidate=path.join(prepared.workspaceRoot,entry.name);
|
|
967
|
+
const info=await lstat(candidate);
|
|
968
|
+
if(info.isSymbolicLink()){
|
|
969
|
+
const canonical=await realpath(candidate);
|
|
970
|
+
const canonicalInfo=await lstat(canonical);
|
|
971
|
+
if(canonicalInfo.isDirectory())protectedRoots.push(canonical);
|
|
972
|
+
}else if(info.isDirectory()){
|
|
973
|
+
protectedRoots.push(await realpath(candidate));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return protectedRoots;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async function canonicalNativeOutputRoot(outputRoot,{signal}={}){
|
|
981
|
+
let cursor=path.resolve(outputRoot);
|
|
982
|
+
const missing=[];
|
|
983
|
+
while(true){
|
|
984
|
+
throwIfAborted(signal);
|
|
985
|
+
try{
|
|
986
|
+
const info=await lstat(cursor);
|
|
987
|
+
if(info.isSymbolicLink()||!info.isDirectory()){
|
|
988
|
+
throw new ArcaneError(
|
|
989
|
+
ERROR_CODES.policyDenied,
|
|
990
|
+
'The native output root must not use a linked or non-directory ancestor.'
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
return path.join(await realpath(cursor),...missing.reverse());
|
|
994
|
+
}catch(error){
|
|
995
|
+
if(error?.code!=='ENOENT')throw error;
|
|
996
|
+
const parent=path.dirname(cursor);
|
|
997
|
+
if(parent===cursor)throw error;
|
|
998
|
+
missing.push(path.basename(cursor));
|
|
999
|
+
cursor=parent;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function pathsOverlap(left,right){
|
|
1005
|
+
return sameOrDescendant(left,right)||sameOrDescendant(right,left);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
async function packageNativeRelease(prepared,app,{sharedPayloadSnapshot,signal,onEvent}={}){
|
|
1009
|
+
const sourceState={
|
|
1010
|
+
...prepared,
|
|
1011
|
+
appId:app.appId,
|
|
1012
|
+
appRoot:app.appRoot,
|
|
1013
|
+
descriptor:app.descriptor,
|
|
1014
|
+
validation:app.validation??prepared.validation
|
|
1015
|
+
};
|
|
1016
|
+
const release=await packageApp({
|
|
1017
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
1018
|
+
appId:app.appId,
|
|
1019
|
+
sharedPayloadSnapshot,
|
|
1020
|
+
signal,
|
|
1021
|
+
onEvent,
|
|
1022
|
+
validateSourceState:({signal:validationSignal}={})=>validateDiscoveredApplication({
|
|
1023
|
+
workspaceRoot:sourceState.workspaceRoot,
|
|
1024
|
+
workspaceMode:sourceState.workspaceMode,
|
|
1025
|
+
workspaceConfig:prepared.validation.config,
|
|
1026
|
+
app,
|
|
1027
|
+
signal:validationSignal
|
|
1028
|
+
})
|
|
1029
|
+
});
|
|
1030
|
+
return Object.freeze({
|
|
1031
|
+
appId:app.appId,
|
|
1032
|
+
appRoot:app.appRoot,
|
|
1033
|
+
descriptor:app.descriptor,
|
|
1034
|
+
release,
|
|
1035
|
+
nativeInput:Object.freeze({
|
|
1036
|
+
appDescriptor:app.descriptor,
|
|
1037
|
+
appReleaseRoot:path.resolve(prepared.workspaceRoot,release.output),
|
|
1038
|
+
appReleaseReceipt:release.receipt
|
|
1039
|
+
})
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
async function executePairedNativeBuild(options,adapter,{signal,onEvent}={}){
|
|
1044
|
+
const target=options.target??options.targetRequest?.target;
|
|
1045
|
+
const initialPrepared=await preparedWorkspace({
|
|
1046
|
+
...options,
|
|
1047
|
+
deferRuntimeVerification:true,
|
|
1048
|
+
signal,
|
|
1049
|
+
onEvent
|
|
1050
|
+
});
|
|
1051
|
+
assertIntegratedNativeToolchain({
|
|
1052
|
+
target,
|
|
1053
|
+
workspaceMode:initialPrepared.workspaceMode,
|
|
1054
|
+
workspaceRoot:initialPrepared.workspaceRoot,
|
|
1055
|
+
toolchainRoot:options.toolchainRoot
|
|
1056
|
+
});
|
|
1057
|
+
if(options.dryRun){
|
|
1058
|
+
throw new ArcaneError(
|
|
1059
|
+
ERROR_CODES.usage,
|
|
1060
|
+
`${target} native build does not support --dry-run because verified app release receipts are required.`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
const requestedOutputRoot=resolveNativeBuildOutputRoot({
|
|
1064
|
+
target,
|
|
1065
|
+
workspaceMode:initialPrepared.workspaceMode,
|
|
1066
|
+
workspaceRoot:initialPrepared.workspaceRoot,
|
|
1067
|
+
outputRoot:options.outputRoot
|
|
1068
|
+
});
|
|
1069
|
+
const protectedRoots=await externalWorkspaceProtectedRoots(initialPrepared,{signal});
|
|
1070
|
+
const outputRoot=await canonicalNativeOutputRoot(requestedOutputRoot,{signal});
|
|
1071
|
+
if(protectedRoots.some(root=>pathsOverlap(root,outputRoot))){
|
|
1072
|
+
throw new ArcaneError(
|
|
1073
|
+
ERROR_CODES.policyDenied,
|
|
1074
|
+
'The native output root must not overlap an application workspace source or control directory.'
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
const dependencyApps=await nativeDependencyClosure(initialPrepared,{target,signal,onEvent});
|
|
1078
|
+
const sharedPayloadIds=[...new Set([
|
|
1079
|
+
...initialPrepared.validation.app.manifest.shared,
|
|
1080
|
+
...dependencyApps.flatMap(app=>app.manifest.shared)
|
|
1081
|
+
])].sort((left,right)=>left.localeCompare(right,'en'));
|
|
1082
|
+
const sharedPayloadSnapshot=await prepareSharedPayloadSnapshot({
|
|
1083
|
+
workspaceRoot:initialPrepared.workspaceRoot,
|
|
1084
|
+
sharedPayloadIds,
|
|
1085
|
+
signal,
|
|
1086
|
+
onEvent
|
|
1087
|
+
});
|
|
1088
|
+
const runtimeReceipt=initialPrepared.workspaceMode==='external'
|
|
1089
|
+
?await externalRuntimeSnapshotReceipt(
|
|
1090
|
+
initialPrepared,
|
|
1091
|
+
sharedPayloadSnapshot,
|
|
1092
|
+
sharedPayloadIds,
|
|
1093
|
+
{signal,onEvent}
|
|
1094
|
+
)
|
|
1095
|
+
:null;
|
|
1096
|
+
const prepared={...initialPrepared,runtimeReceipt};
|
|
1097
|
+
const selectedRelease=await packageNativeRelease(prepared,{
|
|
1098
|
+
appId:prepared.appId,
|
|
1099
|
+
appRoot:prepared.appRoot,
|
|
1100
|
+
manifest:prepared.validation.app.manifest,
|
|
1101
|
+
descriptor:prepared.descriptor,
|
|
1102
|
+
descriptorSource:prepared.validation.app.descriptorSource,
|
|
1103
|
+
descriptorPath:prepared.validation.app.descriptorPath,
|
|
1104
|
+
validation:prepared.validation
|
|
1105
|
+
},{sharedPayloadSnapshot,signal,onEvent});
|
|
1106
|
+
const dependencyReleases=[];
|
|
1107
|
+
for(const dependency of dependencyApps){
|
|
1108
|
+
dependencyReleases.push(await packageNativeRelease(
|
|
1109
|
+
prepared,
|
|
1110
|
+
dependency,
|
|
1111
|
+
{sharedPayloadSnapshot,signal,onEvent}
|
|
1112
|
+
));
|
|
1113
|
+
}
|
|
1114
|
+
const toolchainReceipt=await adapter.prepare({
|
|
1115
|
+
toolchainRoot:options.toolchainRoot,
|
|
1116
|
+
targetRequest:options.targetRequest,
|
|
1117
|
+
signal,
|
|
1118
|
+
onEvent
|
|
1119
|
+
});
|
|
1120
|
+
protectedRoots.push(...(options.protectedRoots??[]));
|
|
1121
|
+
const built=await adapter.build({
|
|
1122
|
+
nativeBuilder:options.nativeBuilder,
|
|
1123
|
+
toolchainRoot:options.toolchainRoot,
|
|
1124
|
+
toolchainReceipt,
|
|
1125
|
+
...selectedRelease.nativeInput,
|
|
1126
|
+
dependencyReleases:dependencyReleases.map(item=>item.nativeInput),
|
|
1127
|
+
providerGeneration:options.providerGeneration??options.nativeBuilder?.providerGeneration,
|
|
1128
|
+
minimumCoreVersion:prepared.runtimeReceipt?.source?.bundleVersion
|
|
1129
|
+
??ARCANE_MACHINE_BUNDLE_VERSION,
|
|
1130
|
+
protectedRoots,
|
|
1131
|
+
outputRoot,
|
|
1132
|
+
targetRequest:options.targetRequest,
|
|
1133
|
+
signal,
|
|
1134
|
+
onEvent
|
|
1135
|
+
});
|
|
1136
|
+
return Object.freeze({
|
|
1137
|
+
prepared,
|
|
1138
|
+
outputRoot,
|
|
1139
|
+
toolchainReceipt,
|
|
1140
|
+
selectedRelease,
|
|
1141
|
+
dependencyReleases:Object.freeze(dependencyReleases),
|
|
1142
|
+
built
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function nativeBuildResult(assembly){
|
|
1147
|
+
const {prepared}=assembly;
|
|
1148
|
+
return {
|
|
1149
|
+
...assembly.built,
|
|
1150
|
+
workspaceRoot:prepared.workspaceRoot,
|
|
1151
|
+
workspaceMode:prepared.workspaceMode,
|
|
1152
|
+
appId:prepared.appId,
|
|
1153
|
+
runtimeContentSha256:prepared.runtimeReceipt?.contentSha256??null,
|
|
1154
|
+
release:assembly.selectedRelease.release,
|
|
1155
|
+
dependencyReleases:assembly.dependencyReleases.map(item=>Object.freeze({
|
|
1156
|
+
appId:item.appId,
|
|
1157
|
+
release:item.release
|
|
1158
|
+
}))
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function usesWorkspaceNativeAssembly(options){
|
|
1163
|
+
return options.nativeBuilder!=null
|
|
1164
|
+
&&typeof options.workspaceRoot==='string'
|
|
1165
|
+
&&options.appReleaseReceipt==null
|
|
1166
|
+
&&options.appReleaseRoot==null
|
|
1167
|
+
&&options.appDescriptor==null;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
export async function buildApplication(options={}){
|
|
1171
|
+
assertApplicationScope(options,'Building');
|
|
1172
|
+
const {target,adapter}=targetSelection(options);
|
|
1173
|
+
const targetDescription=await adapter.describe();
|
|
1174
|
+
if(targetDescription.status!=='available'){
|
|
1175
|
+
return adapter.build({...options,target});
|
|
1176
|
+
}
|
|
1177
|
+
if(target!=='browser'){
|
|
1178
|
+
if(usesWorkspaceNativeAssembly(options)&&options.nativePlan==null){
|
|
1179
|
+
const assembly=await ownedWork(
|
|
1180
|
+
'build',
|
|
1181
|
+
context=>executePairedNativeBuild(options,adapter,context),
|
|
1182
|
+
options
|
|
1183
|
+
);
|
|
1184
|
+
return nativeBuildResult(assembly);
|
|
1185
|
+
}
|
|
1186
|
+
return ownedWork(
|
|
1187
|
+
'build',
|
|
1188
|
+
({signal,onEvent})=>adapter.build({...options,target,signal,onEvent}),
|
|
1189
|
+
options
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
const prepared=await preparedWorkspace(options);
|
|
1193
|
+
const result=await ownedWork(
|
|
1194
|
+
'build',
|
|
1195
|
+
({signal,onEvent})=>buildTarget({
|
|
1196
|
+
...options,
|
|
1197
|
+
...prepared,
|
|
1198
|
+
signal,
|
|
1199
|
+
onEvent,
|
|
1200
|
+
validateSourceState:({signal}={})=>validatePreparedRuntime(prepared,{signal})
|
|
1201
|
+
}),
|
|
1202
|
+
options
|
|
1203
|
+
);
|
|
1204
|
+
return {
|
|
1205
|
+
...result,
|
|
1206
|
+
workspaceMode:prepared.workspaceMode,
|
|
1207
|
+
runtimeContentSha256:prepared.runtimeReceipt?.contentSha256??null
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
export async function runApplication(options={}){
|
|
1212
|
+
assertApplicationScope(options,'Running');
|
|
1213
|
+
const selectedOptions=options.target==null&&options.targetRequest?.target==null
|
|
1214
|
+
?{...options,target:'browser'}
|
|
1215
|
+
:options;
|
|
1216
|
+
const {target,adapter}=targetSelection(selectedOptions);
|
|
1217
|
+
if(target==='portable'){
|
|
1218
|
+
throw new ArcaneError(
|
|
1219
|
+
ERROR_CODES.nativeRunUnsupported,
|
|
1220
|
+
'The portable target is a verified directory payload and cannot be launched. Select an executable target such as windows-x64 or linux-x64.'
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
const targetDescription=await adapter.describe();
|
|
1224
|
+
if(targetDescription.status!=='available'){
|
|
1225
|
+
return adapter.run({...selectedOptions,target});
|
|
1226
|
+
}
|
|
1227
|
+
if(target!=='browser'){
|
|
1228
|
+
if(usesWorkspaceNativeAssembly(selectedOptions)&&selectedOptions.artifactReceipt==null){
|
|
1229
|
+
return ownedWork(
|
|
1230
|
+
'run',
|
|
1231
|
+
async context=>{
|
|
1232
|
+
const assembly=await executePairedNativeBuild(selectedOptions,adapter,context);
|
|
1233
|
+
const launched=await adapter.run({
|
|
1234
|
+
toolchainRoot:selectedOptions.toolchainRoot,
|
|
1235
|
+
toolchainReceipt:assembly.toolchainReceipt,
|
|
1236
|
+
artifactReceipt:assembly.built.artifactReceipt,
|
|
1237
|
+
targetRequest:selectedOptions.targetRequest,
|
|
1238
|
+
signal:context.signal,
|
|
1239
|
+
onEvent:context.onEvent
|
|
1240
|
+
});
|
|
1241
|
+
return {...nativeBuildResult(assembly),run:launched};
|
|
1242
|
+
},
|
|
1243
|
+
selectedOptions
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
return ownedWork(
|
|
1247
|
+
'run',
|
|
1248
|
+
({signal,onEvent})=>adapter.run({...selectedOptions,target,signal,onEvent}),
|
|
1249
|
+
selectedOptions
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
const prepared=await preparedWorkspace(selectedOptions);
|
|
1253
|
+
return runTarget({...selectedOptions,...prepared,target});
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
export async function describeTargets(options={}){
|
|
1257
|
+
const targets=listTargets();
|
|
1258
|
+
const pairedTarget=options.target??options.targetRequest?.target;
|
|
1259
|
+
if(options.nativeBuilder==null||pairedTarget==null||pairedTarget==='browser'){
|
|
1260
|
+
return {protocol:'arcane-target-adapter/1',targets};
|
|
1261
|
+
}
|
|
1262
|
+
const pairedAdapter=createNativeTargetAdapter({
|
|
1263
|
+
targetId:pairedTarget,
|
|
1264
|
+
nativeBuilder:options.nativeBuilder
|
|
1265
|
+
});
|
|
1266
|
+
await pairedAdapter.describe();
|
|
1267
|
+
return {
|
|
1268
|
+
protocol:'arcane-target-adapter/1',
|
|
1269
|
+
targets:targets.map(target=>target.id===pairedTarget
|
|
1270
|
+
?{...target,status:'available',reason:null}
|
|
1271
|
+
:target)
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
export async function repositoryApplication(options={}){
|
|
1276
|
+
return runRepositoryAction(options.action,options);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
export async function checkSdkUpdate(options={}){
|
|
1280
|
+
return checkForSdkUpdate(options);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
export async function executeOperation(command,options={}){
|
|
1284
|
+
const operations={
|
|
1285
|
+
new:createApplication,
|
|
1286
|
+
init:initializeApplication,
|
|
1287
|
+
doctor:doctorApplication,
|
|
1288
|
+
dev:developApplication,
|
|
1289
|
+
test:testApplication,
|
|
1290
|
+
check:checkApplication,
|
|
1291
|
+
package:packageApplication,
|
|
1292
|
+
verify:verifyApplication,
|
|
1293
|
+
bundle:bundleApplication,
|
|
1294
|
+
'verify-bundle':verifyBundleApplication,
|
|
1295
|
+
'native-doctor':doctorNativeTarget,
|
|
1296
|
+
'native-prepare':prepareNativeTarget,
|
|
1297
|
+
'native-verify':verifyNativeArtifact,
|
|
1298
|
+
plan:planApplication,
|
|
1299
|
+
build:buildApplication,
|
|
1300
|
+
run:runApplication,
|
|
1301
|
+
'update-check':checkSdkUpdate,
|
|
1302
|
+
targets:async options=>describeTargets(options),
|
|
1303
|
+
repo:repositoryApplication
|
|
1304
|
+
};
|
|
1305
|
+
const operation=operations[command];
|
|
1306
|
+
if(!operation){
|
|
1307
|
+
throw new ArcaneError(ERROR_CODES.usage,`Unknown Arcane command: ${String(command)}.`);
|
|
1308
|
+
}
|
|
1309
|
+
return operation(options);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
export function createToolchain(defaults={}){
|
|
1313
|
+
return Object.freeze({
|
|
1314
|
+
execute:(command,options={})=>executeOperation(command,{...defaults,...options}),
|
|
1315
|
+
create:options=>createApplication({...defaults,...options}),
|
|
1316
|
+
init:options=>initializeApplication({...defaults,...options}),
|
|
1317
|
+
doctor:options=>doctorApplication({...defaults,...options}),
|
|
1318
|
+
dev:options=>developApplication({...defaults,...options}),
|
|
1319
|
+
test:options=>testApplication({...defaults,...options}),
|
|
1320
|
+
check:options=>checkApplication({...defaults,...options}),
|
|
1321
|
+
package:options=>packageApplication({...defaults,...options}),
|
|
1322
|
+
verify:options=>verifyApplication({...defaults,...options}),
|
|
1323
|
+
bundle:options=>bundleApplication({...defaults,...options}),
|
|
1324
|
+
verifyBundle:options=>verifyBundleApplication({...defaults,...options}),
|
|
1325
|
+
doctorNative:options=>doctorNativeTarget({...defaults,...options}),
|
|
1326
|
+
prepareNative:options=>prepareNativeTarget({...defaults,...options}),
|
|
1327
|
+
verifyNative:options=>verifyNativeArtifact({...defaults,...options}),
|
|
1328
|
+
plan:options=>planApplication({...defaults,...options}),
|
|
1329
|
+
build:options=>buildApplication({...defaults,...options}),
|
|
1330
|
+
run:options=>runApplication({...defaults,...options}),
|
|
1331
|
+
updateCheck:options=>checkSdkUpdate({...defaults,...options}),
|
|
1332
|
+
targets:options=>describeTargets({...defaults,...options}),
|
|
1333
|
+
repository:options=>repositoryApplication({...defaults,...options})
|
|
1334
|
+
});
|
|
1335
|
+
}
|