arcane-os 0.3.1 → 0.3.3

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 +855 -895
  5. package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
  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 -148
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
  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 +113 -69
  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 +1042 -427
  37. package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
  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 +19 -149
  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 +42 -327
  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 +48 -66
  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 +492 -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 +48 -79
  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 +55 -135
  103. package/src/testing-loader.mjs +164 -0
  104. package/src/testing.mjs +1 -1
  105. package/src/toolchain.mjs +133 -544
  106. package/src/update-check.mjs +26 -64
  107. package/src/workspace-operation-lock.mjs +139 -430
  108. package/src/workspace-runtime.mjs +109 -1558
  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 -185
  120. package/docs/reference/ai/browser-speech-package-authority.json +0 -835
  121. package/docs/reference/ai/browser-speech.md +0 -1295
  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 -719
  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 -2965
  150. package/docs/reference/sdk-api.md +0 -6698
  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,10 @@
1
- import {SDK_NAME,SDK_VERSION} from '../constants.mjs';
2
1
  import {
3
- SDK_BROWSER_RUNTIME_CONTENT_SHA256,
4
- SDK_BROWSER_RUNTIME_MANIFEST_SHA256
5
- } from '../sdk-browser-runtime.mjs';
2
+ ARCANE_PROTOCOL,
3
+ CLI_EVENT_PROTOCOL,
4
+ SDK_NAME,
5
+ SDK_VERSION,
6
+ TARGET_ADAPTER_PROTOCOL
7
+ } from '../constants.mjs';
6
8
 
7
9
  const NPM_PACKAGE_NAME_PATTERN=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
8
10
  const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
@@ -26,11 +28,35 @@ function titleCase(appId){
26
28
  .join(' ');
27
29
  }
28
30
 
31
+ export function createWorkspaceLockDocument({
32
+ dependencyName=SDK_NAME,
33
+ packageName=SDK_NAME,
34
+ packageVersion=SDK_VERSION,
35
+ packageSource=`node_modules/${dependencyName}`
36
+ }={}){
37
+ if(packageName!==SDK_NAME||typeof packageVersion!=='string'
38
+ ||!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/u.test(packageVersion)
39
+ ||typeof dependencyName!=='string'||dependencyName.length>214
40
+ ||!NPM_PACKAGE_NAME_PATTERN.test(dependencyName)
41
+ ||packageSource!==`node_modules/${dependencyName}`){
42
+ throw new Error('Invalid workspace SDK lock authority.');
43
+ }
44
+ return {
45
+ schemaVersion:1,
46
+ sdk:{name:packageName,version:packageVersion},
47
+ runtime:{root:`${packageSource}/runtime`},
48
+ sdkBrowserRuntime:{root:`${packageSource}/browser-runtime`},
49
+ protocols:{
50
+ arcane:ARCANE_PROTOCOL,
51
+ cliEvents:CLI_EVENT_PROTOCOL,
52
+ targetAdapter:TARGET_ADAPTER_PROTOCOL
53
+ }
54
+ };
55
+ }
56
+
29
57
  export function workspaceTemplate({
30
58
  appId,
31
59
  displayName,
32
- runtimeRelease,
33
- sdkBrowserRuntimeRelease,
34
60
  sdkDependencyName=SDK_NAME,
35
61
  sdkDependencySpecifier=SDK_VERSION,
36
62
  sdkPackageSource=`node_modules/${sdkDependencyName}`,
@@ -65,31 +91,6 @@ export function workspaceTemplate({
65
91
  }
66
92
  const name=displayName||`Arcane ${titleCase(appId)}`;
67
93
  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
94
  const packageInclude=[`${appId}.css`,'index.html'];
94
95
  if(native)packageInclude.push('img/icon.png');
95
96
  packageInclude.push('manifest.json','modules');
@@ -114,34 +115,6 @@ the SDK's license terms; replace it with your own raster application icon when
114
115
  appropriate.
115
116
  `:'';
116
117
 
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
118
  const importMapMarkup=namedImports?` <script type="importmap" data-arcane-import-map>
146
119
  {
147
120
  "imports": {}
@@ -176,28 +149,29 @@ appropriate.
176
149
  - Keep \`arcane/css/theme.css\` before app styles and import \`arcane/ThemeBootstrap\` before app code runs.
177
150
  - Use \`rgb(...)\` or \`rgba(...)\` for new CSS colors.
178
151
  - Build one named app and one explicit target at a time. Native targets may be unavailable until their adapters are installed.
152
+ - Preserve complete application, model, document, message, log, diagnostic, process, and tool content. Do not truncate, clip, tail, elide, or silently discard it.
153
+ - Do not make ordinary application behavior depend on byte counts, byte limits, byte identities, hashes, digests, or byte-based admission.
154
+ - Optional hardening must remain inactive unless the user expressly selects secure: true for the exact operation. The ordinary path must remain fully functional.
179
155
  - Run tests and checks only when the user explicitly selects verification or a distribution artifact requires it.
180
156
  `);
181
157
  files.set('README.md',`# ${name}
182
158
 
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.
159
+ 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
160
 
185
161
  ## Start
186
162
 
187
163
  \`\`\`sh
188
164
  npm install
189
- npm run doctor
190
165
  npm run dev
191
166
  \`\`\`
192
167
 
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.
168
+ 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
169
 
195
170
  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
171
 
197
- ## Check and release the browser package
172
+ ## Optional browser release commands
198
173
 
199
174
  \`\`\`sh
200
- npm run check
201
175
  npm run import-map
202
176
  npm run package
203
177
  npm run verify
@@ -207,12 +181,16 @@ npm run run
207
181
 
208
182
  The explicit \`import-map\` command refreshes
209
183
  \`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.
184
+ import map in every directly navigable descriptor-admitted \`.html\`/\`.htm\`
185
+ document. HTML component fragments remain package files but do not receive a
186
+ document-level base or managed import map.
187
+ Development, package, and build refresh that shared inventory when the selected operation needs it.
188
+ Named \`arcane/*\` imports resolve against the physical workspace \`arcane/\`
189
+ tree. Packaging copies the complete selected application, runtime, and specifier
190
+ map to \`dist/${appId}\` without running application tests. Run \`verify\` only when
191
+ the user explicitly selects verification or a release artifact that requires it;
192
+ \`bundle\` creates the distributable archive and \`run\` launches the selected
193
+ packaged browser release.
216
194
 
217
195
  Native targets are provider-supplied and must be scaffolded and selected
218
196
  explicitly; this browser workflow does not imply a standalone native executable.
@@ -254,7 +232,7 @@ Every browser release also carries Arcane OS licensing material under \`licenses
254
232
  {
255
233
  source:'arcane',
256
234
  destination:'arcane',
257
- include:['components','css','dependencies','entities','img','modules','sdk','security'],
235
+ include:['components','css','dependencies','entities','img','modules','sdk'],
258
236
  exclude:[]
259
237
  },
260
238
  {
@@ -266,54 +244,10 @@ Every browser release also carries Arcane OS licensing material under \`licenses
266
244
  ]
267
245
  }
268
246
  }));
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
- `);
247
+ files.set('arcane.lock.json',json(createWorkspaceLockDocument({
248
+ dependencyName:sdkDependencyName,
249
+ packageSource:sdkPackageSource
250
+ })));
317
251
  files.set(`apps/${appId}/arcane-app.json`,json({
318
252
  schemaVersion:2,
319
253
  id:appId,
@@ -331,15 +265,6 @@ jobs:
331
265
  exclude:[],
332
266
  shared:['browser-runtime']
333
267
  },
334
- permissions:{
335
- capabilities:[],
336
- methods:[]
337
- },
338
- security:{
339
- connectOrigins:[],
340
- frameOrigins:[],
341
- mediaOrigins:[]
342
- },
343
268
  native:{
344
269
  type:'app',
345
270
  icon:native?'img/icon.png':null,
@@ -348,7 +273,7 @@ jobs:
348
273
  },
349
274
  requirements:{
350
275
  arcaneProtocol:'arcane/1',
351
- minimumCoreVersion,
276
+ ...(native?{minimumCoreVersion}:{}),
352
277
  features:[]
353
278
  },
354
279
  targets:native?['browser',target].sort():['browser']
@@ -360,18 +285,13 @@ jobs:
360
285
  version:'0.1.0',
361
286
  entry:'index.html',
362
287
  strategy:'static',
363
- security:{
364
- connectOrigins:[],
365
- frameOrigins:[],
366
- mediaOrigins:[]
367
- },
368
288
  include:packageInclude,
369
289
  exclude:[],
370
290
  shared:['browser-runtime']
371
291
  }));
372
292
  files.set(`apps/${appId}/manifest.json`,json({
373
293
  name,
374
- short_name:titleCase(appId).slice(0,30),
294
+ short_name:titleCase(appId),
375
295
  start_url:'./index.html',
376
296
  display:'standalone',
377
297
  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){