arcane-os 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +86 -117
  3. package/bin/arcane-test.mjs +170 -46
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +887 -909
  5. package/browser-runtime/ai/browser-speech-providers.mjs +96 -152
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
  7. package/browser-runtime/ai/browser-wasm.mjs +24 -35
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
  9. package/browser-runtime/ai/model-controller.mjs +584 -181
  10. package/browser-runtime/ai/speech-worker-client.mjs +8 -146
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +643 -363
  12. package/browser-runtime/dom-event-instrumentation.mjs +55 -147
  13. package/browser-runtime/event-manager.mjs +239 -624
  14. package/package.json +5 -6
  15. package/runtime/arcane/components/app-bar.html +3 -15
  16. package/runtime/arcane/components/assistant-panel.html +10 -10
  17. package/runtime/arcane/components/calculator.html +1 -1
  18. package/runtime/arcane/components/chat.html +1359 -135
  19. package/runtime/arcane/components/conversation-view.html +2 -2
  20. package/runtime/arcane/components/document-inspector.html +11 -17
  21. package/runtime/arcane/components/file-manager.html +13 -56
  22. package/runtime/arcane/components/markdown-document.html +82 -281
  23. package/runtime/arcane/components/markdown-editor.html +7 -10
  24. package/runtime/arcane/components/media-embed.html +6 -6
  25. package/runtime/arcane/components/screen-capture.html +4 -4
  26. package/runtime/arcane/components/source-explanation.html +2 -2
  27. package/runtime/arcane/components/speech.html +112 -68
  28. package/runtime/arcane/components/terminal-workspace.html +4 -4
  29. package/runtime/arcane/components/theme-editor.html +1 -1
  30. package/runtime/arcane/components/unified-inbox.html +2 -2
  31. package/runtime/arcane/components/voice-transcription.html +31 -21
  32. package/runtime/arcane/entities/Calculation.js +2 -3
  33. package/runtime/arcane/entities/Chat.js +228 -43
  34. package/runtime/arcane/entities/Preference.js +3 -5
  35. package/runtime/arcane/entities/Weather.js +5 -5
  36. package/runtime/arcane/modules/AI.js +1050 -427
  37. package/runtime/arcane/modules/AIProviderRuntime.js +658 -363
  38. package/runtime/arcane/modules/AIResponseLength.js +9 -19
  39. package/runtime/arcane/modules/AIRuntimeState.js +109 -72
  40. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
  41. package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
  42. package/runtime/arcane/modules/CalculatorEngine.js +9 -9
  43. package/runtime/arcane/modules/CommunicationAppController.js +3 -7
  44. package/runtime/arcane/modules/ComponentContracts.js +30 -32
  45. package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
  46. package/runtime/arcane/modules/ConversationActionItems.js +26 -59
  47. package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
  48. package/runtime/arcane/modules/ConversationTimebox.js +27 -15
  49. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
  50. package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
  51. package/runtime/arcane/modules/HTMLImport.js +54 -1
  52. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
  53. package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
  54. package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
  55. package/runtime/arcane/modules/MD.js +1 -45
  56. package/runtime/arcane/modules/Mail.js +51 -103
  57. package/runtime/arcane/modules/MailOutbox.mjs +95 -193
  58. package/runtime/arcane/modules/MailTransport.mjs +36 -57
  59. package/runtime/arcane/modules/ModelDefinition.js +22 -106
  60. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
  61. package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
  62. package/runtime/arcane/modules/PreferenceStore.js +102 -30
  63. package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
  64. package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
  65. package/runtime/arcane/modules/ScreenCapture.js +175 -128
  66. package/runtime/arcane/modules/SpeechPlayback.js +46 -149
  67. package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
  68. package/runtime/arcane/modules/ToolCallRouter.js +25 -12
  69. package/runtime/arcane/modules/YouTubeMedia.js +6 -5
  70. package/schemas/arcane-app-bundle.schema.json +13 -78
  71. package/schemas/arcane-app.schema.json +9 -25
  72. package/schemas/arcane-lock.schema.json +18 -151
  73. package/schemas/arcane-package.schema.json +2 -16
  74. package/schemas/native-build-plan.schema.json +119 -122
  75. package/src/app-descriptor.mjs +75 -132
  76. package/src/application-tests.mjs +200 -0
  77. package/src/cli/main.mjs +27 -46
  78. package/src/constants.mjs +3 -4
  79. package/src/dev-server.mjs +30 -324
  80. package/src/doctor.mjs +92 -154
  81. package/src/dom-event-instrumentation.mjs +55 -147
  82. package/src/errors.mjs +2 -3
  83. package/src/event-manager.mjs +239 -624
  84. package/src/event-queue.mjs +3 -3
  85. package/src/import-map.mjs +273 -1028
  86. package/src/index.mjs +14 -16
  87. package/src/installed-sdk-runtime.mjs +40 -62
  88. package/src/integrated-provider-loader.mjs +53 -382
  89. package/src/mail-api.mjs +0 -2
  90. package/src/mail-server.mjs +224 -580
  91. package/src/mail.mjs +4 -10
  92. package/src/native-plan.mjs +163 -598
  93. package/src/native-provider-loader.mjs +104 -1063
  94. package/src/packager/core.mjs +485 -3229
  95. package/src/process.mjs +5 -10
  96. package/src/release-bundle.mjs +292 -2405
  97. package/src/runtime.mjs +76 -396
  98. package/src/scaffold.mjs +30 -80
  99. package/src/sdk-browser-runtime.mjs +70 -626
  100. package/src/source-server.mjs +588 -0
  101. package/src/targets/index.mjs +78 -188
  102. package/src/templates/workspace-template.mjs +19 -135
  103. package/src/testing-loader.mjs +164 -0
  104. package/src/testing.mjs +1 -1
  105. package/src/toolchain.mjs +131 -544
  106. package/src/update-check.mjs +26 -64
  107. package/src/workspace-operation-lock.mjs +139 -430
  108. package/src/workspace-runtime.mjs +112 -779
  109. package/src/workspace.mjs +40 -302
  110. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
  111. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
  112. package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
  113. package/browser-runtime/ai/internal/sha256.mjs +0 -166
  114. package/docs/architecture.md +0 -344
  115. package/docs/compatibility.md +0 -36
  116. package/docs/event-manager.md +0 -294
  117. package/docs/platform-targets.md +0 -108
  118. package/docs/publishing.md +0 -201
  119. package/docs/reference/README.md +0 -187
  120. package/docs/reference/ai/browser-speech-package-authority.json +0 -835
  121. package/docs/reference/ai/browser-speech.md +0 -1252
  122. package/docs/reference/ai/browser-wasm.md +0 -530
  123. package/docs/reference/arcane-ollama.md +0 -288
  124. package/docs/reference/availability-and-normalization.md +0 -183
  125. package/docs/reference/behavioral-testing.md +0 -133
  126. package/docs/reference/cli.md +0 -779
  127. package/docs/reference/core/README.md +0 -62
  128. package/docs/reference/core/arcane-ai-contracts.md +0 -907
  129. package/docs/reference/core/arcane-api.md +0 -601
  130. package/docs/reference/core/arcane-entities.md +0 -65
  131. package/docs/reference/core/arcane-events.md +0 -134
  132. package/docs/reference/core/ollama-module.md +0 -181
  133. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
  134. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
  135. package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
  136. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
  137. package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
  138. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
  139. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
  140. package/docs/reference/event-manager.md +0 -1511
  141. package/docs/reference/inventory/package-api.json +0 -3284
  142. package/docs/reference/inventory/runtime-components.json +0 -1011
  143. package/docs/reference/inventory/runtime-entities.json +0 -26
  144. package/docs/reference/inventory/runtime-modules.json +0 -1431
  145. package/docs/reference/mail.md +0 -316
  146. package/docs/reference/protocols.md +0 -677
  147. package/docs/reference/runtime-components.md +0 -1366
  148. package/docs/reference/runtime-entities.md +0 -303
  149. package/docs/reference/runtime-modules.md +0 -2960
  150. package/docs/reference/sdk-api.md +0 -6694
  151. package/docs/roadmap.md +0 -79
  152. package/docs/work-amplification.md +0 -129
  153. package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
@@ -1,8 +1,4 @@
1
1
  import {SDK_NAME,SDK_VERSION} from '../constants.mjs';
2
- import {
3
- SDK_BROWSER_RUNTIME_CONTENT_SHA256,
4
- SDK_BROWSER_RUNTIME_MANIFEST_SHA256
5
- } from '../sdk-browser-runtime.mjs';
6
2
 
7
3
  const NPM_PACKAGE_NAME_PATTERN=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
8
4
  const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
@@ -29,8 +25,6 @@ function titleCase(appId){
29
25
  export function workspaceTemplate({
30
26
  appId,
31
27
  displayName,
32
- runtimeRelease,
33
- sdkBrowserRuntimeRelease,
34
28
  sdkDependencyName=SDK_NAME,
35
29
  sdkDependencySpecifier=SDK_VERSION,
36
30
  sdkPackageSource=`node_modules/${sdkDependencyName}`,
@@ -65,31 +59,6 @@ export function workspaceTemplate({
65
59
  }
66
60
  const name=displayName||`Arcane ${titleCase(appId)}`;
67
61
  const packageName=`arcane-${appId}`;
68
- const runtimeContentSha256=runtimeRelease?.contentSha256;
69
- const upstreamCommit=runtimeRelease?.source?.legacyProjection?.commit;
70
- const browserManifestSha256=sdkBrowserRuntimeRelease?.manifestSha256;
71
- const browserContentSha256=sdkBrowserRuntimeRelease?.contentSha256;
72
- const browserSource=sdkBrowserRuntimeRelease?.source;
73
- const expectedBrowserDependencies=[
74
- {
75
- name:'event-pubsub',
76
- version:'6.1.0',
77
- resolved:'https://registry.npmjs.org/event-pubsub/-/event-pubsub-6.1.0.tgz',
78
- integrity:'sha512-FEMlhTxwqGM0hztTixG6FhVFXqp7Eq1ltk5mSreK6Mhy3xWWpLAzEUR6OMvMdNqT3jgSxA8JDhnhyAG3X4Xy7Q=='
79
- },
80
- {
81
- name:'strong-type',
82
- version:'2.0.0',
83
- resolved:'https://registry.npmjs.org/strong-type/-/strong-type-2.0.0.tgz',
84
- integrity:'sha512-HHrY9qYC7yn+5mlewiI3k9RQM9gZqGQsqbomZcd10Ks0h4RlX01nnkWbCe4AsVPCI6KaFvpkWm1nHMD+Ykup6g=='
85
- },
86
- {
87
- name:'@wllama/wllama',
88
- version:'3.6.0',
89
- resolved:'https://registry.npmjs.org/@wllama/wllama/-/wllama-3.6.0.tgz',
90
- integrity:'sha512-NN3ZBXqaaUwGXTQubkNvsCaLPjN2XVa0bVS40OYCE8zquYmRc2W3oHYEgwvuSWWDB8aUqTLyMioySCXNkcnD1w=='
91
- }
92
- ];
93
62
  const packageInclude=[`${appId}.css`,'index.html'];
94
63
  if(native)packageInclude.push('img/icon.png');
95
64
  packageInclude.push('manifest.json','modules');
@@ -114,34 +83,6 @@ the SDK's license terms; replace it with your own raster application icon when
114
83
  appropriate.
115
84
  `:'';
116
85
 
117
- if(!appOnly&&(typeof runtimeContentSha256!=='string'||!/^[a-f0-9]{64}$/.test(runtimeContentSha256))){
118
- throw new Error('The SDK runtime release does not contain a valid contentSha256.');
119
- }
120
- if(!appOnly&&(typeof upstreamCommit!=='string'||!/^[a-f0-9]{40}$/.test(upstreamCommit))){
121
- throw new Error('The SDK runtime release does not contain a valid upstream commit.');
122
- }
123
- if(!appOnly&&(
124
- browserManifestSha256!==SDK_BROWSER_RUNTIME_MANIFEST_SHA256
125
- ||browserContentSha256!==SDK_BROWSER_RUNTIME_CONTENT_SHA256
126
- ||sdkBrowserRuntimeRelease?.builder!=='arcane-sdk-browser-runtime-v1'
127
- ||sdkBrowserRuntimeRelease?.sdkVersion!==SDK_VERSION
128
- ||browserSource?.protocol!=='arcane-sdk-browser-runtime/1'
129
- ||browserSource?.authority!=='arcane-os-sdk'
130
- ||browserSource?.repository!=='https://github.com/TheWizardNexus/arcane-os-sdk.git'
131
- ||browserSource?.browserEntry!=='arcane-os/event-manager'
132
- ||!Array.isArray(browserSource?.dependencies)
133
- ||browserSource.dependencies.length!==expectedBrowserDependencies.length
134
- ||browserSource.dependencies.some((actual,index)=>{
135
- const expected=expectedBrowserDependencies[index];
136
- return !actual||Object.keys(actual).sort().join('\0')
137
- !==['name','version','resolved','integrity'].sort().join('\0')
138
- ||actual.name!==expected.name||actual.version!==expected.version
139
- ||actual.resolved!==expected.resolved||actual.integrity!==expected.integrity;
140
- })
141
- )){
142
- throw new Error('The SDK browser runtime release does not contain a valid trusted identity.');
143
- }
144
-
145
86
  const importMapMarkup=namedImports?` <script type="importmap" data-arcane-import-map>
146
87
  {
147
88
  "imports": {}
@@ -176,28 +117,29 @@ appropriate.
176
117
  - Keep \`arcane/css/theme.css\` before app styles and import \`arcane/ThemeBootstrap\` before app code runs.
177
118
  - Use \`rgb(...)\` or \`rgba(...)\` for new CSS colors.
178
119
  - Build one named app and one explicit target at a time. Native targets may be unavailable until their adapters are installed.
120
+ - Preserve complete application, model, document, message, log, diagnostic, process, and tool content. Do not truncate, clip, tail, elide, or silently discard it.
121
+ - Do not make ordinary application behavior depend on byte counts, byte limits, byte identities, hashes, digests, or byte-based admission.
122
+ - Optional hardening must remain inactive unless the user expressly selects secure: true for the exact operation. The ordinary path must remain fully functional.
179
123
  - Run tests and checks only when the user explicitly selects verification or a distribution artifact requires it.
180
124
  `);
181
125
  files.set('README.md',`# ${name}
182
126
 
183
- This repository contains the portable Arcane application \`${appId}\`. It embeds immutable SDK runtime bytes for distribution and has no runtime dependency on an Arcane OS source checkout.
127
+ This repository contains the portable Arcane application \`${appId}\`. It includes the selected SDK runtime for distribution and has no runtime dependency on an Arcane OS source checkout.
184
128
 
185
129
  ## Start
186
130
 
187
131
  \`\`\`sh
188
132
  npm install
189
- npm run doctor
190
133
  npm run dev
191
134
  \`\`\`
192
135
 
193
- Open the loopback URL printed by the development server. The source server exposes this app and its authenticated physical \`arcane/\` runtime; it does not expose an Ollama HTTP endpoint.
136
+ Open the loopback URL printed by the development server. The source server exposes this app and its physical \`arcane/\` runtime; it does not expose an Ollama HTTP endpoint.
194
137
 
195
138
  Commit the generated \`package-lock.json\` after dependency installation. CI intentionally uses \`npm ci\` and therefore requires that lock. Before the SDK is published, install a locally packed \`${SDK_NAME}\` \`.tgz\` with \`npm install --save-dev --save-exact <path-to-tarball>\`; keep that tarball at the lock file's relative path for repeatable local \`npm ci\` runs.
196
139
 
197
- ## Check and release the browser package
140
+ ## Optional browser release commands
198
141
 
199
142
  \`\`\`sh
200
- npm run check
201
143
  npm run import-map
202
144
  npm run package
203
145
  npm run verify
@@ -207,12 +149,16 @@ npm run run
207
149
 
208
150
  The explicit \`import-map\` command refreshes
209
151
  \`apps/${appId}/modules/arcane.importmap.json\` and the managed inline browser
210
- import map in \`index.html\`. Development, package, and build refresh it
211
- automatically. Named \`arcane/*\` imports resolve against the physical workspace
212
- \`arcane/\` tree. Packaging copies the same authenticated bytes and specifier map
213
- to \`dist/${appId}\`, \`verify\` authenticates that directory, \`bundle\` creates
214
- its distributable archive, and \`run\` launches the verified packaged browser
215
- release.
152
+ import map in every directly navigable descriptor-admitted \`.html\`/\`.htm\`
153
+ document. HTML component fragments remain package files but do not receive a
154
+ document-level base or managed import map.
155
+ Development, package, and build refresh that shared inventory when the selected operation needs it.
156
+ Named \`arcane/*\` imports resolve against the physical workspace \`arcane/\`
157
+ tree. Packaging copies the complete selected application, runtime, and specifier
158
+ map to \`dist/${appId}\` without running application tests. Run \`verify\` only when
159
+ the user explicitly selects verification or a release artifact that requires it;
160
+ \`bundle\` creates the distributable archive and \`run\` launches the selected
161
+ packaged browser release.
216
162
 
217
163
  Native targets are provider-supplied and must be scaffolded and selected
218
164
  explicitly; this browser workflow does not imply a standalone native executable.
@@ -254,7 +200,7 @@ Every browser release also carries Arcane OS licensing material under \`licenses
254
200
  {
255
201
  source:'arcane',
256
202
  destination:'arcane',
257
- include:['components','css','dependencies','entities','img','modules','sdk','security'],
203
+ include:['components','css','dependencies','entities','img','modules','sdk'],
258
204
  exclude:[]
259
205
  },
260
206
  {
@@ -266,54 +212,6 @@ Every browser release also carries Arcane OS licensing material under \`licenses
266
212
  ]
267
213
  }
268
214
  }));
269
- files.set('arcane.lock.json',json({
270
- schemaVersion:1,
271
- sdk:{name:SDK_NAME,version:SDK_VERSION},
272
- runtime:{
273
- manifest:`${sdkPackageSource}/runtime/ARCANE_RUNTIME_RELEASE.json`,
274
- contentSha256:runtimeContentSha256,
275
- upstreamCommit
276
- },
277
- sdkBrowserRuntime:{
278
- manifest:`${sdkPackageSource}/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json`,
279
- manifestSha256:browserManifestSha256,
280
- contentSha256:browserContentSha256,
281
- builder:sdkBrowserRuntimeRelease?.builder,
282
- sdkVersion:sdkBrowserRuntimeRelease?.sdkVersion,
283
- source:sdkBrowserRuntimeRelease?.source
284
- },
285
- protocols:{
286
- arcane:'arcane/1',
287
- cliEvents:'arcane-cli-events/1',
288
- targetAdapter:'arcane-target-adapter/1'
289
- }
290
- }));
291
- files.set('.github/workflows/check.yml',`name: check
292
-
293
- on:
294
- push:
295
- branches: [main]
296
- pull_request:
297
-
298
- permissions:
299
- contents: read
300
-
301
- jobs:
302
- check:
303
- runs-on: \${{ matrix.os }}
304
- strategy:
305
- fail-fast: false
306
- matrix:
307
- os: [ubuntu-latest, windows-latest]
308
- steps:
309
- - uses: actions/checkout@v6
310
- - uses: actions/setup-node@v6
311
- with:
312
- node-version: 24
313
- package-manager-cache: false
314
- - run: npm ci --ignore-scripts
315
- - run: npm run check
316
- `);
317
215
  files.set(`apps/${appId}/arcane-app.json`,json({
318
216
  schemaVersion:2,
319
217
  id:appId,
@@ -331,15 +229,6 @@ jobs:
331
229
  exclude:[],
332
230
  shared:['browser-runtime']
333
231
  },
334
- permissions:{
335
- capabilities:[],
336
- methods:[]
337
- },
338
- security:{
339
- connectOrigins:[],
340
- frameOrigins:[],
341
- mediaOrigins:[]
342
- },
343
232
  native:{
344
233
  type:'app',
345
234
  icon:native?'img/icon.png':null,
@@ -348,7 +237,7 @@ jobs:
348
237
  },
349
238
  requirements:{
350
239
  arcaneProtocol:'arcane/1',
351
- minimumCoreVersion,
240
+ ...(native?{minimumCoreVersion}:{}),
352
241
  features:[]
353
242
  },
354
243
  targets:native?['browser',target].sort():['browser']
@@ -360,18 +249,13 @@ jobs:
360
249
  version:'0.1.0',
361
250
  entry:'index.html',
362
251
  strategy:'static',
363
- security:{
364
- connectOrigins:[],
365
- frameOrigins:[],
366
- mediaOrigins:[]
367
- },
368
252
  include:packageInclude,
369
253
  exclude:[],
370
254
  shared:['browser-runtime']
371
255
  }));
372
256
  files.set(`apps/${appId}/manifest.json`,json({
373
257
  name,
374
- short_name:titleCase(appId).slice(0,30),
258
+ short_name:titleCase(appId),
375
259
  start_url:'./index.html',
376
260
  display:'standalone',
377
261
  background_color:'rgb(13, 18, 32)',
@@ -1,13 +1,177 @@
1
+ import {lstatSync,realpathSync} from 'node:fs';
2
+ import path from 'node:path';
3
+ import {fileURLToPath} from 'node:url';
4
+
1
5
  const TESTING_SPECIFIER='arcane-os/testing';
2
6
  const TESTING_URL=new URL('./testing.mjs',import.meta.url).href;
3
7
  const RUNTIME_STRONG_TYPE_SPECIFIER='../../node_modules/strong-type/index.js';
4
8
  const RUNTIME_ARCANE_URL=new URL('../runtime/arcane/',import.meta.url).href;
5
9
  const STRONG_TYPE_URL=new URL('../runtime/strong-type/index.js',import.meta.url).href;
10
+ const MANAGED_IMPORT_MAP_PROTOCOL='arcane-test-import-map/1';
11
+ const MANAGED_IMPORT_MAP_BOUNDARIES=new Set(['source','dist','test']);
12
+
13
+ let managedImports=null;
14
+ let managedUrlImports=null;
15
+
16
+ function loaderFailure(message,code='ARCANE_IMPORT_MAP_INVALID'){
17
+ const error=new Error(message);
18
+ error.code=code;
19
+ throw error;
20
+ }
21
+
22
+ function urlLikeSpecifier(specifier){
23
+ return specifier.startsWith('./')||specifier.startsWith('../')
24
+ ||specifier.startsWith('/')||/^[A-Za-z][A-Za-z0-9+.-]*:/u.test(specifier);
25
+ }
26
+
27
+ function safeManagedTarget(target){
28
+ return typeof target==='string'
29
+ &&target.startsWith('./')
30
+ &&target.length>2
31
+ &&!/[\\%?#\u0000-\u001f\u007f]/u.test(target)
32
+ &&target.slice(2).split('/').every(
33
+ segment=>segment!==''&&segment!=='.'&&segment!=='..'
34
+ );
35
+ }
36
+
37
+ function samePath(left,right){
38
+ const a=path.resolve(left);
39
+ const b=path.resolve(right);
40
+ return process.platform==='win32'?a.toLowerCase()===b.toLowerCase():a===b;
41
+ }
42
+
43
+ function pathInside(root,candidate){
44
+ const relative=path.relative(root,candidate);
45
+ return relative===''||(!path.isAbsolute(relative)&&relative!=='..'
46
+ &&!relative.startsWith(`..${path.sep}`));
47
+ }
48
+
49
+ function physicalDirectory(baseURL){
50
+ let parsed;
51
+ try{parsed=new URL(baseURL);}
52
+ catch{loaderFailure('Managed test import-map base URL is invalid.');}
53
+ if(parsed.protocol!=='file:'||parsed.username||parsed.password||parsed.search||parsed.hash
54
+ ||!parsed.pathname.endsWith('/')){
55
+ loaderFailure('Managed test import-map base URL must select one application directory.');
56
+ }
57
+ const root=fileURLToPath(parsed);
58
+ let info;
59
+ let canonical;
60
+ try{
61
+ info=lstatSync(root);
62
+ canonical=realpathSync(root);
63
+ }catch(error){
64
+ loaderFailure(`Managed test import-map application directory is unavailable: ${error.message}`);
65
+ }
66
+ if(info.isSymbolicLink()||!info.isDirectory()||!samePath(root,canonical)){
67
+ loaderFailure('Managed test import-map base must be one real application directory.');
68
+ }
69
+ return {url:parsed,root:canonical};
70
+ }
71
+
72
+ function physicalManagedTarget(target,specifier,boundary,boundaryName){
73
+ const relative=target.slice(2);
74
+ if(boundaryName==='source'&&/^(?:dist|test)\//u.test(relative)){
75
+ loaderFailure(
76
+ `Managed source import-map target selects a different application boundary: ${specifier}.`,
77
+ 'ARCANE_IMPORT_MAP_UNRESOLVED'
78
+ );
79
+ }
80
+ let targetURL;
81
+ try{targetURL=new URL(target,boundary.url);}
82
+ catch{loaderFailure(`Managed test import-map target is invalid: ${specifier}.`);}
83
+ if(targetURL.protocol!=='file:'||targetURL.username||targetURL.password
84
+ ||targetURL.search||targetURL.hash){
85
+ loaderFailure(`Managed test import-map target is invalid: ${specifier}.`);
86
+ }
87
+ const targetPath=fileURLToPath(targetURL);
88
+ let info;
89
+ let canonical;
90
+ try{
91
+ info=lstatSync(targetPath);
92
+ canonical=realpathSync(targetPath);
93
+ }catch(error){
94
+ loaderFailure(
95
+ `Managed test import-map target is unavailable for ${specifier}: ${error.message}`,
96
+ 'ARCANE_IMPORT_MAP_UNRESOLVED'
97
+ );
98
+ }
99
+ if(info.isSymbolicLink()||!info.isFile()||!samePath(targetPath,canonical)
100
+ ||!pathInside(boundary.root,canonical)){
101
+ loaderFailure(
102
+ `Managed test import-map target leaves its selected application directory: ${specifier}.`,
103
+ 'ARCANE_IMPORT_MAP_UNRESOLVED'
104
+ );
105
+ }
106
+ return targetURL.href;
107
+ }
108
+
109
+ export function initialize({managedImportMap}={}){
110
+ if(managedImportMap==null){
111
+ managedImports=null;
112
+ managedUrlImports=null;
113
+ return;
114
+ }
115
+ const imports=managedImportMap.imports;
116
+ if(managedImportMap.protocol!==MANAGED_IMPORT_MAP_PROTOCOL
117
+ ||!MANAGED_IMPORT_MAP_BOUNDARIES.has(managedImportMap.boundary)
118
+ ||imports===null||typeof imports!=='object'||Array.isArray(imports)){
119
+ loaderFailure('Managed test import-map loader data is malformed.');
120
+ }
121
+ const boundary=physicalDirectory(managedImportMap.baseURL);
122
+ const exact=new Map();
123
+ const urls=new Map();
124
+ for(const [specifier,target] of Object.entries(imports)){
125
+ if(typeof specifier!=='string'||specifier===''||!safeManagedTarget(target)){
126
+ loaderFailure(`Managed test import-map entry is invalid: ${String(specifier)}.`);
127
+ }
128
+ const targetURL=physicalManagedTarget(
129
+ target,
130
+ specifier,
131
+ boundary,
132
+ managedImportMap.boundary
133
+ );
134
+ exact.set(specifier,targetURL);
135
+ if(urlLikeSpecifier(specifier)){
136
+ let keyURL;
137
+ try{keyURL=new URL(specifier,boundary.url).href;}
138
+ catch{loaderFailure(`Managed test import-map URL key is invalid: ${specifier}.`);}
139
+ if(urls.has(keyURL)&&urls.get(keyURL)!==targetURL){
140
+ loaderFailure(`Managed test import-map URL keys collide: ${specifier}.`);
141
+ }
142
+ urls.set(keyURL,targetURL);
143
+ }
144
+ }
145
+ managedImports=exact;
146
+ managedUrlImports=urls;
147
+ }
148
+
149
+ function managedResolution(specifier,context){
150
+ if(managedImports===null)return null;
151
+ if(managedImports.has(specifier))return managedImports.get(specifier);
152
+ if(urlLikeSpecifier(specifier)&&typeof context.parentURL==='string'){
153
+ let requested;
154
+ try{requested=new URL(specifier,context.parentURL).href;}
155
+ catch{requested=null;}
156
+ if(requested!==null&&managedUrlImports.has(requested)){
157
+ return managedUrlImports.get(requested);
158
+ }
159
+ }
160
+ if(specifier.startsWith('arcane/')||specifier.startsWith('#arcane/')){
161
+ loaderFailure(
162
+ `Managed test import map does not expose ${specifier}.`,
163
+ 'ARCANE_IMPORT_MAP_UNRESOLVED'
164
+ );
165
+ }
166
+ return null;
167
+ }
6
168
 
7
169
  export function resolve(specifier,context,nextResolve){
8
170
  if(specifier===TESTING_SPECIFIER){
9
171
  return {url:TESTING_URL,shortCircuit:true};
10
172
  }
173
+ const managed=managedResolution(specifier,context);
174
+ if(managed!==null)return {url:managed,shortCircuit:true};
11
175
  if(specifier===RUNTIME_STRONG_TYPE_SPECIFIER
12
176
  &&context.parentURL?.startsWith(RUNTIME_ARCANE_URL)){
13
177
  return {url:STRONG_TYPE_URL,shortCircuit:true};
package/src/testing.mjs CHANGED
@@ -52,7 +52,7 @@ function definition(name,optionsOrCallback,maybeCallback){
52
52
  if(typeof callback!=='function'){
53
53
  throw new TypeError(`Test "${name}" requires a callback function.`);
54
54
  }
55
- return Object.freeze({name,timeoutMs:testTimeout(options),callback});
55
+ return {name,timeoutMs:testTimeout(options),callback};
56
56
  }
57
57
 
58
58
  export function test(name,optionsOrCallback,maybeCallback){