arcane-os 0.24.1 → 0.26.0
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 +29 -0
- package/README.md +16 -3
- package/docs/architecture.md +22 -12
- package/docs/reference/cli.md +35 -9
- package/docs/reference/mail.md +45 -8
- package/docs/reference/protocols.md +72 -4
- package/docs/reference/pwa.md +9 -0
- package/docs/reference/sdk-api.md +53 -13
- package/docs/reviews/mail-server-purpose-review.md +60 -0
- package/package.json +17 -2
- package/runtime/arcane/modules/MailApi.mjs +19 -0
- package/src/dev-server.mjs +14 -4
- package/src/import-map.mjs +31 -4
- package/src/mail-api.mjs +2 -19
- package/src/mail-server.mjs +10 -2
- package/src/packager/core.mjs +15 -10
- package/src/sdk-runtime-layout.mjs +99 -0
- package/src/workspace.mjs +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.0
|
|
4
|
+
|
|
5
|
+
- Add explicit installed-package browser runtime routes. Source development,
|
|
6
|
+
managed import maps, application test-map resolution, and PWA resource
|
|
7
|
+
selection can use the npm dependency directly without a generated workspace
|
|
8
|
+
runtime or lock. Keep existing browser URLs, aliases, and materialized layouts.
|
|
9
|
+
- Let a shared package route select its complete source directory with
|
|
10
|
+
`include: ["."]`. Portable app output includes the selected runtime,
|
|
11
|
+
dependencies, assets, and notices without requiring the SDK at deployment.
|
|
12
|
+
- Expose lowercase public module subpaths for shared AI preferences, provider
|
|
13
|
+
state, model definitions, conversation helpers, application data, document
|
|
14
|
+
libraries, and local AI readiness. Browser maps and Node package resolution
|
|
15
|
+
select the same canonical implementations; browser runtime requirements remain.
|
|
16
|
+
- Share the existing portable Mail aggregation with browser import maps while
|
|
17
|
+
preserving the `arcane-os/mail` API and Node mail CLI used by server consumers.
|
|
18
|
+
|
|
19
|
+
## 0.25.0
|
|
20
|
+
|
|
21
|
+
- Automatically skip configured mail subscription verification when the actual
|
|
22
|
+
requester connection IP equals the server-side IP of that connection. This
|
|
23
|
+
lets services sharing that IP and local development send without a subscription
|
|
24
|
+
key. Other requester IPs, including other intranet machines, still use the
|
|
25
|
+
configured verification callback.
|
|
26
|
+
- Use native connection addresses for HTTP/1 and HTTP/2, with no environment
|
|
27
|
+
setting, domain lookup, forwarded-header interpretation, or special loopback rule.
|
|
28
|
+
Preserve CORS, report handling, provider delivery, and existing caller APIs.
|
|
29
|
+
- Document the IP rule and its purpose gates; update focused test source for
|
|
30
|
+
the exemption and the remaining subscription-verification paths.
|
|
31
|
+
|
|
3
32
|
## 0.24.1
|
|
4
33
|
|
|
5
34
|
- Add a mail CLI parameter table with command scope, purpose, and defaults.
|
package/README.md
CHANGED
|
@@ -19,10 +19,19 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.26.0` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
26
|
+
External browser apps can use the installed npm package directly, without a
|
|
27
|
+
generated workspace `arcane/` directory or `arcane.lock.json`. Select the
|
|
28
|
+
[four installed-package routes](docs/reference/protocols.md#installed-package-browser-routes)
|
|
29
|
+
in `arcane-packager.json`: development, managed import maps, and PWA resources
|
|
30
|
+
read the installed SDK while keeping `/arcane/...` browser URLs. Portable app
|
|
31
|
+
packages still contain their selected runtime. Existing materialized workspaces
|
|
32
|
+
and the scaffold's current layout remain supported. Node services continue to
|
|
33
|
+
use the installed CLI or public imports such as `arcane-os/mail`.
|
|
34
|
+
|
|
26
35
|
The [mail gateway](docs/reference/mail.md) serves HTTPS with HTTP/2 on port 4433
|
|
27
36
|
by default. Configure its host, port, origin list, certificate paths, and other
|
|
28
37
|
mail settings in `arcane.config.json.mail`; keep provider keys in the ignored
|
|
@@ -31,7 +40,11 @@ Existing root credential, profile, and TLS settings remain supported. Browser
|
|
|
31
40
|
mail defaults to `/v1/mail` on the current domain, and multiple applications can
|
|
32
41
|
share one server with explicit allowed origins. Subscription verification is disabled
|
|
33
42
|
until a `verifySubscription` callback is configured; that callback receives the
|
|
34
|
-
application name and bearer subscription key before
|
|
43
|
+
application name and bearer subscription key before provider attempts from other
|
|
44
|
+
requester IPs. Requests whose actual connection source IP equals the server-side
|
|
45
|
+
IP of that connection automatically skip subscription verification, including
|
|
46
|
+
local development. Other intranet machines still need verification. The exception
|
|
47
|
+
requires no environment setting or domain lookup.
|
|
35
48
|
See the [method and action gate report](docs/reviews/mail-server-purpose-review.md)
|
|
36
49
|
for the removals, retained responsibilities, and naming decisions.
|
|
37
50
|
|
|
@@ -58,7 +71,7 @@ Create one browser application, install its pinned SDK, and start its source
|
|
|
58
71
|
server:
|
|
59
72
|
|
|
60
73
|
```bash
|
|
61
|
-
npx arcane-os@0.
|
|
74
|
+
npx arcane-os@0.26.0 new hello-speech --path ./hello-speech --target browser
|
|
62
75
|
cd hello-speech
|
|
63
76
|
npm install
|
|
64
77
|
```
|
package/docs/architecture.md
CHANGED
|
@@ -84,13 +84,22 @@ runtime materialization, serving, and coordination with other writers. This
|
|
|
84
84
|
path requires no application descriptor or `apps/<id>/` layout and leaves the
|
|
85
85
|
app-scoped toolchain operation unchanged.
|
|
86
86
|
|
|
87
|
-
An external workspace
|
|
88
|
-
|
|
87
|
+
An external workspace can select `installed-v1` routes directly from its
|
|
88
|
+
`node_modules/arcane-os` installation, without a workspace `arcane/` tree or
|
|
89
|
+
`arcane.lock.json`. The SDK reads the installed package version and maps its
|
|
90
|
+
runtime, browser runtime, runtime dependency, and licenses to the established
|
|
91
|
+
browser destinations. Existing `physical-v1` workspaces and the explicit
|
|
92
|
+
materializer remain supported; scaffolding retains its existing physical
|
|
93
|
+
layout. See [installed-package routes](reference/protocols.md#installed-package-browser-routes)
|
|
94
|
+
for the configuration and npm-alias form.
|
|
95
|
+
|
|
96
|
+
An Arcane OS checkout is an integrated SDK consumer, not the owner
|
|
89
97
|
of portable runtime source. For live shared development, the explicit
|
|
90
98
|
development-only SDK source mount maps the canonical SDK runtime and dependency
|
|
91
|
-
paths into that consumer. Without the mount,
|
|
92
|
-
projection
|
|
93
|
-
|
|
99
|
+
paths into that consumer. Without the mount, integrated workspaces retain their
|
|
100
|
+
physical projection; external workspaces select either layout above. The development server,
|
|
101
|
+
import-map generator, packager, and PWA inventory consume the same route
|
|
102
|
+
destinations, so app imports do not change. Integrated
|
|
94
103
|
initialization creates only app-owned files and never rewrites Arcane OS or SDK
|
|
95
104
|
root configuration.
|
|
96
105
|
|
|
@@ -235,13 +244,14 @@ exact schema-1 `arcane-package.json` for current consumers. Existing Arcane
|
|
|
235
244
|
apps synthesize that descriptor from their schema-1 package plus the current
|
|
236
245
|
native registry during migration.
|
|
237
246
|
|
|
238
|
-
An external app
|
|
239
|
-
the installed SDK runtime to `/arcane`,
|
|
240
|
-
`/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
An external app can use four `installed-v1` shared routes in
|
|
248
|
+
`arcane-packager.json`: the installed SDK's `runtime/arcane` maps to `/arcane`,
|
|
249
|
+
`browser-runtime` to `/arcane/sdk`, `runtime/strong-type` to
|
|
250
|
+
`/arcane/dependencies/strong-type`, and its three license files to
|
|
251
|
+
`/licenses/arcane-os`. Source serving and import-map generation read those
|
|
252
|
+
installed files without creating a workspace runtime copy. Distribution copies
|
|
253
|
+
the selected routes completely inside the portable artifact, preserving the
|
|
254
|
+
same URLs. Existing physical `arcane/` routes retain their behavior.
|
|
245
255
|
|
|
246
256
|
Release schema 1 and builder identity `arcane-app-packager-v1` remain unchanged
|
|
247
257
|
because current Arcane native consumers treat them as public contracts. Native
|
package/docs/reference/cli.md
CHANGED
|
@@ -184,7 +184,7 @@ npm exec -- arcane doctor --workspace . --arcane-root "../Arcane OS"
|
|
|
184
184
|
|
|
185
185
|
### Overview
|
|
186
186
|
|
|
187
|
-
Refreshes one selected application's
|
|
187
|
+
Refreshes one selected application's browser runtime map, generates
|
|
188
188
|
its standard browser import map, discovers every directly navigable
|
|
189
189
|
`.html`/`.htm` document admitted by the selected descriptor's existing
|
|
190
190
|
include/exclude rules, and commits the map artifact plus those managed documents
|
|
@@ -211,10 +211,10 @@ The generated artifact is
|
|
|
211
211
|
`apps/<id>/modules/arcane.importmap.json`. Its exact JSON is also installed in
|
|
212
212
|
the configured entry and every other admitted browser document as `<script
|
|
213
213
|
type="importmap" data-arcane-import-map>` before module loading. The complete
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
runtime map derives its entries from the selected runtime and browser-runtime
|
|
215
|
+
inventories. It intentionally has no package-root mapping;
|
|
216
216
|
portable runtime subpaths such as `arcane-os/preference-store` and
|
|
217
|
-
`arcane-os/speech-playback` instead map directly to their canonical
|
|
217
|
+
`arcane-os/speech-playback` instead map directly to their canonical runtime
|
|
218
218
|
modules. The result reports the complete map written to the selected
|
|
219
219
|
application; no fixed entry count is a release contract.
|
|
220
220
|
|
|
@@ -224,6 +224,15 @@ list. External and modern integrated routes require `components`, `css`,
|
|
|
224
224
|
may omit only an optional trailing `security` include. The external license
|
|
225
225
|
route remains separate and second.
|
|
226
226
|
|
|
227
|
+
External applications can instead select the four ordered
|
|
228
|
+
[`installed-v1` routes](protocols.md#installed-package-browser-routes) in
|
|
229
|
+
`arcane-packager.json`. This reads directly from the installed SDK package and
|
|
230
|
+
requires neither a workspace `arcane/` directory nor `arcane.lock.json`.
|
|
231
|
+
The generated browser destinations remain `arcane/`, `arcane/sdk/`, and
|
|
232
|
+
`arcane/dependencies/strong-type/`; an npm alias changes package source paths,
|
|
233
|
+
not those browser URLs. The SDK version comes from that installed package.
|
|
234
|
+
Existing physical workspaces and scaffold output remain supported unchanged.
|
|
235
|
+
|
|
227
236
|
### Result and safety
|
|
228
237
|
|
|
229
238
|
Success returns the normal selected-workspace wrapper:
|
|
@@ -308,9 +317,12 @@ Starts one development server for one selected app and maps the exact
|
|
|
308
317
|
workspace/runtime routes. It defaults to HTTPS on localhost; `--public` enables access
|
|
309
318
|
from other devices on the network, using HTTPS by default.
|
|
310
319
|
|
|
311
|
-
For an external workspace, the server exposes the selected
|
|
312
|
-
|
|
313
|
-
|
|
320
|
+
For an external workspace, the server exposes the selected SDK routes alongside
|
|
321
|
+
the application. `installed-v1` serves directly from the installed package
|
|
322
|
+
under `/arcane`, `/arcane/sdk`, and `/arcane/dependencies/strong-type`, without
|
|
323
|
+
creating a workspace runtime directory. Existing `physical-v1` workspaces serve
|
|
324
|
+
their projected `arcane/` root. Integrated workspaces retain their configured
|
|
325
|
+
physical routes.
|
|
314
326
|
The explicit live-source SDK mapping remains unchanged and does not replace the
|
|
315
327
|
installed projection.
|
|
316
328
|
|
|
@@ -504,6 +516,11 @@ shared route destinations. When selected shared content supplies no root
|
|
|
504
516
|
Source document bases and resource URLs therefore retain their development
|
|
505
517
|
layout. Packaging does not run tests or checks automatically.
|
|
506
518
|
|
|
519
|
+
For `installed-v1`, the same four configured source routes supply the complete
|
|
520
|
+
selected SDK content directly from `node_modules`. Only the portable output
|
|
521
|
+
receives copies; no workspace `arcane/` projection is required. Its runtime URLs,
|
|
522
|
+
managed import-map targets, and PWA inventory destinations match source serving.
|
|
523
|
+
|
|
507
524
|
```text
|
|
508
525
|
arcane package [--app <id>] [--dry-run]
|
|
509
526
|
```
|
|
@@ -985,7 +1002,16 @@ application with `Authorization: Bearer <subscription_key>`.
|
|
|
985
1002
|
Subscription verification is disabled when no callback is configured. The
|
|
986
1003
|
programmatic `createToolchain().mail({action: 'serve', ...})` path accepts
|
|
987
1004
|
`verifySubscription({appName, subscriptionKey, signal})`; supplying that callback
|
|
988
|
-
enables verification
|
|
1005
|
+
enables verification for requests whose actual connection source IP differs
|
|
1006
|
+
from the server-side IP of that connection. The gateway compares the native
|
|
1007
|
+
`request.socket.remoteAddress` with `request.socket.localAddress`. A nonempty,
|
|
1008
|
+
equal requester IP skips subscription verification and needs neither a bearer
|
|
1009
|
+
key nor an application header for that check. No environment setting or domain
|
|
1010
|
+
lookup is involved. Local development qualifies under the same rule; another
|
|
1011
|
+
machine on an intranet still needs verification. Missing connection metadata
|
|
1012
|
+
does not qualify. CORS and the remaining mail requirements still apply.
|
|
1013
|
+
|
|
1014
|
+
For requests requiring verification, the callback must resolve to `true`
|
|
989
1015
|
to accept the request. An invalid subscription receives 401; verifier service
|
|
990
1016
|
failure receives retryable 503; cancellation stops verification before sending.
|
|
991
1017
|
The callback connects the actual TWiN Stripe endpoint when its contract is
|
|
@@ -1016,7 +1042,7 @@ deadline. `mail.bodyTimeoutMs` selects the optional request-body deadline.
|
|
|
1016
1042
|
After binding, `server.ready` reports lifecycle fields such as
|
|
1017
1043
|
protocol, optional app label, bind address, port, URL, and `callerAuthentication`
|
|
1018
1044
|
(`none` or `subscription`). Human output states whether verification is disabled
|
|
1019
|
-
or configured.
|
|
1045
|
+
or configured; configured verification includes the automatic same-IP exception.
|
|
1020
1046
|
The command owns the server until its lifecycle ends or `SIGINT`/`SIGTERM`
|
|
1021
1047
|
cancels it. The server's Resend credential remains outside results and events;
|
|
1022
1048
|
per-request observer events preserve the complete delivery, report, provider
|
package/docs/reference/mail.md
CHANGED
|
@@ -115,7 +115,8 @@ An absent key, `null`, or an empty string omits the Authorization header; the
|
|
|
115
115
|
transport does not block initial setup because a key is missing. Explicit
|
|
116
116
|
`subscriptionKey: null` or `subscriptionKey: ''` also skips User lookup.
|
|
117
117
|
A supplied value of another type is rejected. A gateway with subscription
|
|
118
|
-
verification configured owns rejection of requests without a usable key
|
|
118
|
+
verification configured owns rejection of requests without a usable key,
|
|
119
|
+
subject to its [automatic same-IP exception](#automatic-same-ip-exception).
|
|
119
120
|
|
|
120
121
|
For a caller-owned key and shared endpoint:
|
|
121
122
|
|
|
@@ -586,10 +587,40 @@ Subscription verification is disabled during initial setup when
|
|
|
586
587
|
subscription key. Starting the ordinary CLI gateway uses this mode. This does
|
|
587
588
|
not claim that a subscription was checked.
|
|
588
589
|
|
|
590
|
+
### Automatic same-IP exception
|
|
591
|
+
|
|
592
|
+
The gateway automatically skips subscription verification when the incoming
|
|
593
|
+
connection's nonempty `request.socket.remoteAddress` equals
|
|
594
|
+
`request.socket.localAddress`. This also applies when a verifier is configured
|
|
595
|
+
and the request has no subscription key or supplies an invalid one. The
|
|
596
|
+
verification callback is not called for that request.
|
|
597
|
+
|
|
598
|
+
No `.arcane.env.json` setting or domain allowlist is needed. The comparison uses
|
|
599
|
+
the actual source and destination IP addresses exposed by Node's request socket
|
|
600
|
+
for HTTP/1.1 and HTTP/2. It adds no DNS lookup, configuration read, cache, or
|
|
601
|
+
interface scan. `Origin`, `Host`, `Forwarded`, and `X-Forwarded-For` do not
|
|
602
|
+
establish the connection's IP equality.
|
|
603
|
+
|
|
604
|
+
| Caller connection | Subscription behavior with a verifier configured |
|
|
605
|
+
| --- | --- |
|
|
606
|
+
| The verification service connects to mail using the same source and destination IP | Skips subscription checking, allowing its service-generated emails without a subscriber key. |
|
|
607
|
+
| A local development request has equal source and destination IPs, including an ordinary loopback connection | Uses the same automatic exception. There is no special loopback rule. |
|
|
608
|
+
| Another machine on the same intranet connects from a different IP | Uses the configured subscription verifier. Sharing a network or subnet supplies no exception. |
|
|
609
|
+
| Two services on one machine connect through different interface IPs | Uses the configured subscription verifier when the actual addresses differ. Machine identity alone supplies no exception. |
|
|
610
|
+
| The request has no usable socket source address | Uses the configured subscription verifier; absent addresses do not count as equal. |
|
|
611
|
+
|
|
612
|
+
The exception skips only subscription checking. Existing CORS, route, method,
|
|
613
|
+
idempotency-key, report, recipient, and provider handling still apply. The gateway
|
|
614
|
+
continues to require its Resend provider credential and reports acceptance only
|
|
615
|
+
after the provider accepts the email.
|
|
616
|
+
|
|
617
|
+
### Verification callback contract
|
|
618
|
+
|
|
589
619
|
The hosting process enables verification by supplying the programmatic
|
|
590
620
|
`verifySubscription` function through
|
|
591
621
|
`createToolchain().mail({action:'serve', verifySubscription, ...options})`.
|
|
592
|
-
The server then reports `callerAuthentication: 'subscription'
|
|
622
|
+
The server then reports `callerAuthentication: 'subscription'`, identifying the
|
|
623
|
+
configured mode while retaining the same-IP exception above. The callback
|
|
593
624
|
contract is:
|
|
594
625
|
|
|
595
626
|
```javascript
|
|
@@ -602,13 +633,15 @@ The gateway also treats any other returned value as invalid. `appName` is the
|
|
|
602
633
|
exact incoming `X-Mail-App` value, `subscriptionKey` is the incoming Bearer
|
|
603
634
|
key, and `signal` follows the request lifecycle. These control fields stay
|
|
604
635
|
separate from the mail report and Resend payload. The verifier runs for each
|
|
605
|
-
POST request before any provider attempt; results
|
|
636
|
+
POST request outside the same-IP exception before any provider attempt; results
|
|
637
|
+
are not cached.
|
|
606
638
|
|
|
607
639
|
`X-Mail-App` identifies the originating app so the verifier can select that
|
|
608
640
|
app's subscription account, such as the appropriate Stripe account for `BOSS`
|
|
609
641
|
or `TWiN`. It is separate from the gateway's optional `--app` event label.
|
|
610
642
|
The missing-header and Bearer errors below apply only when `verifySubscription`
|
|
611
|
-
is configured
|
|
643
|
+
is configured and the same-IP exception does not apply. Ordinary `mail serve`
|
|
644
|
+
does not require either header.
|
|
612
645
|
|
|
613
646
|
| Configured-verifier outcome | Gateway response |
|
|
614
647
|
| --- | --- |
|
|
@@ -654,10 +687,14 @@ error report, and trigger another mail event. Subscriber failures cannot change
|
|
|
654
687
|
an already committed mail result, though global capture can report them as new
|
|
655
688
|
errors. This is a source-level possibility, not evidence of a deployed loop.
|
|
656
689
|
|
|
657
|
-
For a verification service
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
690
|
+
For a verification service's own emails sent through the same IP, the gateway
|
|
691
|
+
skips its subscription callback and proceeds to provider delivery. That removes
|
|
692
|
+
the circular dependency `mail -> verify subscription -> send mail -> verify
|
|
693
|
+
subscription` for this connection path. Connections from a different IP still
|
|
694
|
+
use the configured callback, whose owning service must preserve a verification
|
|
695
|
+
path that can return without recursively depending on another verification.
|
|
696
|
+
The exception does not change the separate browser event-subscriber behavior
|
|
697
|
+
described above.
|
|
661
698
|
|
|
662
699
|
## Gateway request lifecycle
|
|
663
700
|
|
|
@@ -61,13 +61,19 @@ explicit identity fails, and the selected page then must pass exact path-relativ
|
|
|
61
61
|
base validation.
|
|
62
62
|
Included HTML with neither signal is a component fragment and remains a
|
|
63
63
|
complete package file.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
Public lowercase runtime subpaths resolve directly to canonical runtime-module
|
|
65
|
+
namespaces from both Node package exports and managed browser keys. These
|
|
66
|
+
include `arcane-os/preference-store`, `arcane-os/speech-playback`,
|
|
67
|
+
`arcane-os/ai-preference-tuple`, `arcane-os/ai-preference-runtime`,
|
|
68
|
+
`arcane-os/ai-provider-runtime`, `arcane-os/ai-runtime-state`,
|
|
69
|
+
`arcane-os/model-definition`, `arcane-os/conversation-timebox`,
|
|
70
|
+
`arcane-os/conversation-action-items`, `arcane-os/conversation-closing-report`,
|
|
71
|
+
and `arcane-os/chat-records`.
|
|
67
72
|
`arcane/PreferenceStore` and `arcane/SpeechPlayback` remain their established
|
|
68
73
|
browser import-map names. The additional portable `arcane-os/speech-text`
|
|
69
74
|
subpath owns shared speech-input cleanup and has the same
|
|
70
|
-
managed browser key.
|
|
75
|
+
managed browser key. `arcane-os/mail` remains the existing Mail aggregation.
|
|
76
|
+
There is
|
|
71
77
|
no exported `importMapApplication()` function, `generateImportMap()` function, or
|
|
72
78
|
`arcane-os/import-map` package subpath.
|
|
73
79
|
Explicit host document lists use the separate root-exported
|
|
@@ -90,6 +96,68 @@ materializes the complete required SDK runtime,
|
|
|
90
96
|
browser-runtime, and managed-import-map closure inside that app's own artifact.
|
|
91
97
|
No application polls for SDK changes.
|
|
92
98
|
|
|
99
|
+
## Installed-package browser routes
|
|
100
|
+
|
|
101
|
+
An external application can serve and package its installed SDK directly,
|
|
102
|
+
without creating a top-level `arcane/` directory or requiring
|
|
103
|
+
`arcane.lock.json`. Select `installed-v1` through these four ordered routes in
|
|
104
|
+
the existing schema-1 `arcane-packager.json`; no additional layout field is
|
|
105
|
+
needed:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"schemaVersion": 1,
|
|
110
|
+
"appsRoot": "apps",
|
|
111
|
+
"distRoot": "dist",
|
|
112
|
+
"sharedPayloads": {
|
|
113
|
+
"browser-runtime": [
|
|
114
|
+
{
|
|
115
|
+
"source": "node_modules/arcane-os/runtime/arcane",
|
|
116
|
+
"destination": "arcane",
|
|
117
|
+
"include": ["components", "css", "entities", "img", "modules"],
|
|
118
|
+
"exclude": []
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"source": "node_modules/arcane-os/browser-runtime",
|
|
122
|
+
"destination": "arcane/sdk",
|
|
123
|
+
"include": ["."],
|
|
124
|
+
"exclude": []
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"source": "node_modules/arcane-os/runtime/strong-type",
|
|
128
|
+
"destination": "arcane/dependencies/strong-type",
|
|
129
|
+
"include": ["."],
|
|
130
|
+
"exclude": []
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"source": "node_modules/arcane-os",
|
|
134
|
+
"destination": "licenses/arcane-os",
|
|
135
|
+
"include": ["LICENSE", "COMMERCIAL-LICENSE.md", "NOTICE"],
|
|
136
|
+
"exclude": []
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The first include list also accepts a final `security` entry. An npm alias such
|
|
144
|
+
as `arcane-sdk` substitutes `node_modules/arcane-sdk` for the package prefix in
|
|
145
|
+
all four source paths. Destinations remain unchanged. Package selection and SDK
|
|
146
|
+
version come from the installed dependency, not a generated runtime lock.
|
|
147
|
+
|
|
148
|
+
`arcane import-map`, `arcane dev`, packaging, and generated PWA inventories use
|
|
149
|
+
the same logical destinations. Source serving and map generation read installed
|
|
150
|
+
files without materializing them into the workspace. A selected portable
|
|
151
|
+
package copies its complete selected resources inside the artifact, preserving
|
|
152
|
+
the same browser URLs and saved managed import maps.
|
|
153
|
+
|
|
154
|
+
Existing `physical-v1` configurations whose first source is `arcane` remain
|
|
155
|
+
supported. The explicit materializer below still refreshes those projections,
|
|
156
|
+
and scaffolding retains its existing physical layout. Selecting `installed-v1`
|
|
157
|
+
does not delete any preexisting workspace files. The separate host-document
|
|
158
|
+
`generateDocumentImportMaps()` API continues to accept an already materialized
|
|
159
|
+
runtime; its input contract is unchanged.
|
|
160
|
+
|
|
93
161
|
## Installed SDK runtime materialization
|
|
94
162
|
|
|
95
163
|
`materializeInstalledSdkRuntime()` is the Node entrypoint for refreshing one
|
package/docs/reference/pwa.md
CHANGED
|
@@ -108,6 +108,15 @@ app's `arcane-app.json`. Startup refreshes that app's generated
|
|
|
108
108
|
maps and starting the server. No packaging or `dist` output is required.
|
|
109
109
|
Package-only applications retain their existing descriptor workflow.
|
|
110
110
|
|
|
111
|
+
With the external [`installed-v1` routes](protocols.md#installed-package-browser-routes),
|
|
112
|
+
the server inventories the selected SDK directly in `node_modules`. Runtime
|
|
113
|
+
resources retain their public `arcane/`, `arcane/sdk/`, and
|
|
114
|
+
`arcane/dependencies/strong-type/` URLs, so offline selections use those logical
|
|
115
|
+
paths rather than package filesystem paths. No workspace `arcane/` tree or
|
|
116
|
+
`arcane.lock.json` is needed. Packaging uses the same route selection and copies
|
|
117
|
+
the selected resources into the portable output; existing physical projections
|
|
118
|
+
remain supported.
|
|
119
|
+
|
|
111
120
|
Add a file or directory to `package.include` to make it part of the app's
|
|
112
121
|
resources. A new file inside an already included directory needs no separate
|
|
113
122
|
entry. `package.include` is an application resource selection, not a file list
|
|
@@ -5,11 +5,12 @@ The npm package exposes a Node.js ESM control plane, the portable
|
|
|
5
5
|
`arcane-os/preference-store`, `arcane-os/speech-playback`,
|
|
6
6
|
`arcane-os/speech-text`, `arcane-os/ai/tool-text-stream`, and `arcane-os/browser-device` entrypoints, and the browser-only
|
|
7
7
|
`arcane-os/pwa`, `arcane-os/ai/browser-wasm` and `arcane-os/ai/browser-speech` entrypoints.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`arcane/
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
Lowercase runtime subpaths also expose existing module namespaces through both
|
|
9
|
+
Node package exports and managed browser imports, as listed below. Established
|
|
10
|
+
browser names such as `arcane/AIProviderRuntime`, `arcane/AIRuntimeState`, and
|
|
11
|
+
`arcane/ThemeBootstrap` remain managed-map names rather than Node package
|
|
12
|
+
entrypoints. Applications call `globalThis.Arcane` for capability-gated host
|
|
13
|
+
behavior.
|
|
13
14
|
|
|
14
15
|
This page is the canonical inventory for every JavaScript name reachable through `package.json#exports`. The same binding can appear at the root and a focused subpath; those entrypoints are listed together. The root workspace `discoverApps` and the low-level packager `discoverApps` are intentionally separate records because they are different functions.
|
|
15
16
|
|
|
@@ -19,7 +20,8 @@ This table is the Node `package.json#exports` map: it defines package
|
|
|
19
20
|
entrypoints for SDK/tooling code. It is distinct from the generated browser
|
|
20
21
|
import map that resolves application-facing `arcane/*` modules and the focused
|
|
21
22
|
EventManager entry. See [browser runtime delivery](protocols.md#browser-runtime-delivery)
|
|
22
|
-
for the
|
|
23
|
+
for the shared browser destinations used by installed-package and physical
|
|
24
|
+
runtime layouts.
|
|
23
25
|
|
|
24
26
|
| Specifier | Purpose |
|
|
25
27
|
| --- | --- |
|
|
@@ -36,6 +38,21 @@ for the installed-inventory-derived physical-runtime contract in SDK `0.5.18`.
|
|
|
36
38
|
| `arcane-os/event-manager` | Central synchronous events, complete time-travel history, playback, and optional DOM instrumentation. |
|
|
37
39
|
| `arcane-os/logging` | Shared console diagnostics controlled by the existing `user.developer` preference. |
|
|
38
40
|
| `arcane-os/preference-store` | Portable preference records and injected storage adapters. |
|
|
41
|
+
| `arcane-os/ai` | Existing `AI.js` provider-neutral inference and speech namespace. |
|
|
42
|
+
| `arcane-os/ai-preference-tuple` | Existing `AIPreferenceTuple.js` module namespace. |
|
|
43
|
+
| `arcane-os/ai-preference-runtime` | Existing `AIPreferenceRuntime.js` module namespace. |
|
|
44
|
+
| `arcane-os/ai-provider-runtime` | Existing `AIProviderRuntime.js` provider selection and lifecycle namespace. |
|
|
45
|
+
| `arcane-os/ai-runtime-state` | Existing `AIRuntimeState.js` state and event namespace. |
|
|
46
|
+
| `arcane-os/model-definition` | Existing `ModelDefinition.js` module namespace. |
|
|
47
|
+
| `arcane-os/conversation-timebox` | Existing `ConversationTimebox.js` module namespace. |
|
|
48
|
+
| `arcane-os/conversation-action-items` | Existing `ConversationActionItems.js` module namespace. |
|
|
49
|
+
| `arcane-os/conversation-closing-report` | Existing `ConversationClosingReport.js` module namespace. |
|
|
50
|
+
| `arcane-os/chat-records` | Existing `ChatRecords.js` module namespace. |
|
|
51
|
+
| `arcane-os/app-data-scope` | Existing `AppDataScope.js` application storage scope namespace. |
|
|
52
|
+
| `arcane-os/core-local-model-catalog` | Existing `CoreLocalModelCatalog.js` local model catalog namespace. |
|
|
53
|
+
| `arcane-os/dbopfs-document-library` | Existing `DBOPFSDocumentLibrary.js` document storage namespace. |
|
|
54
|
+
| `arcane-os/local-ai-readiness` | Existing `LocalAIReadiness.js` local AI readiness namespace. |
|
|
55
|
+
| `arcane-os/ollama-model-identifier` | Existing `OllamaModelIdentifier.js` model identifier namespace. |
|
|
39
56
|
| `arcane-os/speech-playback` | Portable speech preparation, playback state, and injected media adapters. |
|
|
40
57
|
| `arcane-os/speech-text` | Speech-input formatting cleanup for complete text and streamed chunks. |
|
|
41
58
|
| `arcane-os/browser-device` | Synchronous mobile or desktop identity hints for application-owned settings. |
|
|
@@ -45,6 +62,18 @@ for the installed-inventory-derived physical-runtime contract in SDK `0.5.18`.
|
|
|
45
62
|
| `arcane-os/ai/browser-speech` | Caller-selected browser-local Whisper STT and Kokoro TTS provider mechanisms, ordinary upstream assets, materialized/native routing, Workers, and cancellation. |
|
|
46
63
|
| `arcane-os/mail` | Portable Mail runtime, durable outbox, complete transport responses, and provider-neutral acceptance contracts. |
|
|
47
64
|
|
|
65
|
+
These lowercase runtime-module entrypoints expose their existing exports; they
|
|
66
|
+
do not duplicate the module implementations or change their platform and
|
|
67
|
+
lifecycle requirements. Node resolves them inside the installed package, while
|
|
68
|
+
the managed browser map resolves the same names to the selected runtime URLs.
|
|
69
|
+
Node package resolution does not make browser-only operations available in
|
|
70
|
+
Node: a module still needs its documented browser globals, storage, media,
|
|
71
|
+
Worker, or host capabilities when its initialization or operation uses them.
|
|
72
|
+
Use `arcane-os/mail` for the existing Mail aggregation rather than importing a
|
|
73
|
+
private runtime file. Its package facade and managed browser entry both expose
|
|
74
|
+
the canonical `MailApi.mjs` aggregation and the same Mail, outbox, and transport
|
|
75
|
+
bindings.
|
|
76
|
+
|
|
48
77
|
Eight JSON schemas and `package.json` are data-only export subpaths. In Node ESM, import JSON with `with {type: 'json'}`, or resolve and read it explicitly.
|
|
49
78
|
|
|
50
79
|
### Managed AI narration
|
|
@@ -1496,6 +1525,11 @@ After full runtime replacement, it writes or replaces semantic
|
|
|
1496
1525
|
`arcane.lock.json` from the actual installed dependency name, package name,
|
|
1497
1526
|
package version, alias source, and projected roots.
|
|
1498
1527
|
|
|
1528
|
+
This operation remains available for physical workspace projections. External
|
|
1529
|
+
applications using [installed-package routes](protocols.md#installed-package-browser-routes)
|
|
1530
|
+
read directly from their installed dependency and do not need to call it or
|
|
1531
|
+
create `arcane.lock.json` before serving, refreshing app import maps, or packaging.
|
|
1532
|
+
|
|
1499
1533
|
### Signature and result
|
|
1500
1534
|
|
|
1501
1535
|
```text
|
|
@@ -3643,6 +3677,10 @@ certPath, keyPath, tls, signal, onEvent}` and serves one validated
|
|
|
3643
3677
|
workspace application plus its complete SDK or integrated runtime. Packaged mode uses
|
|
3644
3678
|
`{mode:'packaged', releaseRoot, workspaceRoot, host, port, httpPort, certPath, keyPath, tls,
|
|
3645
3679
|
signal, onEvent}` and serves the complete selected release files.
|
|
3680
|
+
External source mode accepts both the installed-package route layout and the
|
|
3681
|
+
existing physical runtime layout. Installed-package mode serves the configured
|
|
3682
|
+
SDK resources directly from `node_modules` at their logical browser URLs;
|
|
3683
|
+
it does not create a workspace `arcane/` directory or require `arcane.lock.json`.
|
|
3646
3684
|
`host` defaults to `127.0.0.1` and accepts an
|
|
3647
3685
|
explicit network address or hostname. Use `0.0.0.0` for all IPv4 interfaces or
|
|
3648
3686
|
`::` for the platform's IPv6 wildcard listeners. `port` selects the HTTPS
|
|
@@ -3866,8 +3904,10 @@ async validateWorkspace({
|
|
|
3866
3904
|
|
|
3867
3905
|
Import it from `arcane-os`. It resolves to a validation result with
|
|
3868
3906
|
`valid`, `workspaceMode`, `workspaceRoot`, `appId`, `appRoot`, the selected
|
|
3869
|
-
configuration/application, lock data
|
|
3870
|
-
|
|
3907
|
+
configuration/application, lock data where the layout uses it, and completed
|
|
3908
|
+
checks. External `installed-v1` selection reads package metadata directly and
|
|
3909
|
+
does not require a workspace `arcane.lock.json` or runtime projection. For an
|
|
3910
|
+
external workspace the result additionally includes the installed package:
|
|
3871
3911
|
|
|
3872
3912
|
```javascript
|
|
3873
3913
|
{
|
|
@@ -3876,17 +3916,17 @@ workspace it additionally returns the exact installed package authority:
|
|
|
3876
3916
|
packageSource,
|
|
3877
3917
|
canonicalPackageRoot,
|
|
3878
3918
|
packageName: 'arcane-os',
|
|
3879
|
-
packageVersion
|
|
3919
|
+
packageVersion,
|
|
3880
3920
|
runtimeRoot,
|
|
3881
3921
|
browserRuntimeRoot
|
|
3882
3922
|
}
|
|
3883
3923
|
}
|
|
3884
3924
|
```
|
|
3885
3925
|
|
|
3886
|
-
The dependency can be named `arcane-os` or
|
|
3887
|
-
`
|
|
3888
|
-
|
|
3889
|
-
|
|
3926
|
+
The dependency can be named `arcane-os` or use an npm alias for the selected
|
|
3927
|
+
`arcane-os` version. The configured package source must match that declaration;
|
|
3928
|
+
`packageVersion` reports the installed package's version. Duplicate
|
|
3929
|
+
canonical/alias declarations reject.
|
|
3890
3930
|
`allowMissingManagedImportMap` is an internal packaging/development seam. An
|
|
3891
3931
|
ordinary caller should leave it `false`.
|
|
3892
3932
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
[Roshi's Codex PRIME] The gateway review and its portable credential follow-up apply the same three purpose gates. The gateway removals preserve complete delivery outcomes, configured CORS and recipient policy, and the optional subscription verifier. Mail credentials now belong in the user-selected `.arcane.env.json`, with one portable Node implementation for Windows, Linux, and macOS and a compatible Node host as the Android adaptation boundary. The original gateway inventory below uses baseline `f055e05`; the credential follow-up reviews the Windows storage implementation present at SDK `0.18.0`. Commit, package, publication, and actual platform execution outcomes belong to the delivery record rather than to this source-review report.
|
|
2
2
|
|
|
3
|
+
The [same-IP subscription exception follow-up](#same-ip-subscription-exception-follow-up)
|
|
4
|
+
records the later user-selected change to when the optional verifier runs. The
|
|
5
|
+
earlier inventories retain their stated historical scope; current verification
|
|
6
|
+
skips requests whose actual connection source and destination IPs are equal.
|
|
7
|
+
|
|
3
8
|
| Decision | Behavior | Why it matters | Source status at this review |
|
|
4
9
|
| --- | --- | --- | --- |
|
|
5
10
|
| Remove | Loopback-only admission, manual header reconstruction, address/origin normalization, local payload grammar, manual content-length scan, dead queue knobs | These restrict supported use, rewrite supplied values, or repeat work already owned elsewhere. | Removed from the inspected source; naming-only changes carry no runtime claim. |
|
|
@@ -541,3 +546,58 @@ global error handler; it is not an automatic server response to an ordinary
|
|
|
541
546
|
mail failure. This is a static source-path finding, not an observed production
|
|
542
547
|
incident or an executed reproduction. This documentation clarification changes
|
|
543
548
|
no error, subscription, retry, or delivery behavior.
|
|
549
|
+
|
|
550
|
+
## Same-IP subscription exception follow-up
|
|
551
|
+
|
|
552
|
+
The user selected an automatic same-IP exception so the verification service can
|
|
553
|
+
send its own emails without a subscriber key. This supersedes the proposed
|
|
554
|
+
domain exception in `.arcane.env.json`; neither an environment setting nor a
|
|
555
|
+
domain allowlist is needed. The reusable decision belongs in the SDK's existing
|
|
556
|
+
`handleMailRequest`, immediately at its subscription-verification branch.
|
|
557
|
+
Application-owned messages, sender choices, subscription accounts, and the
|
|
558
|
+
verification callback remain with their existing owners.
|
|
559
|
+
|
|
560
|
+
The condition is a nonempty `request.socket.remoteAddress` equal to
|
|
561
|
+
`request.socket.localAddress`. Matching requests skip `verifyMailSubscription`
|
|
562
|
+
and the configured `verifySubscription` callback, including when the request
|
|
563
|
+
has no subscriber key or supplies an invalid one. Requests with different or
|
|
564
|
+
unavailable socket addresses retain the configured verification path. Omitting
|
|
565
|
+
the callback continues to select the existing no-verification setup.
|
|
566
|
+
|
|
567
|
+
| Method or action | Gate 1: Do we care? | Gate 2: Why is it worth the work? | Gate 3: Can we remove it without losing the required result? | Decision and concrete effect |
|
|
568
|
+
| --- | --- | --- | --- | --- |
|
|
569
|
+
| Automatic source/destination IP equality at `handleMailRequest` | Yes. The verifier needs to send service mail without a subscriber key. | It identifies the exact connection case the user selected using already-available socket fields. | No. Removing the exception would restore the verifier's dependency on subscription checking for its own mail. | Keep one inline equality decision with a nonempty source address. No new helper or configuration owner. |
|
|
570
|
+
| Subscription checking for a matching connection | The subscription-verification capability matters; this particular call does not. | Requiring it for the verifier's own email adds an unnecessary dependency and can create a circular service call. | Yes. The user expressly permits these senders regardless of their subscription. | Skip the callback and its asynchronous wait for matching requests. A callback that contacts the verification service makes no such request on this path. |
|
|
571
|
+
| Proposed `.arcane.env.json` domain exception, DNS resolution, address cache, and interface enumeration | No. The selected rule is actual IP equality and requires no setup. | These would introduce configuration, I/O, retained state, or a broader machine/domain interpretation absent from the selected outcome. | Yes. Native request socket fields already supply both values. | Add none. Secret files, named provider profiles, and current configuration loading remain unchanged. |
|
|
572
|
+
| `Origin`, `Host`, `Forwarded`, or `X-Forwarded-For` as evidence of matching connection IPs | No. These request fields do not define the native socket endpoints. | Reading them for this exception would change its meaning and duplicate unrelated routing or CORS concerns. | Yes. The actual socket exposes the relevant addresses directly. | Keep header handling at its existing owner; none of these headers selects the exception. |
|
|
573
|
+
| Special loopback, intranet, subnet, or same-machine rules | No. The selected condition is equal IPs. | A larger address policy would grant a different exception and add work. | Yes. Equal local-development addresses already use the ordinary equality path. | Add none. Another intranet machine with a different IP still uses configured verification. Services on one machine using different interface IPs may also require it. |
|
|
574
|
+
| CORS, routing, methods, idempotency, report/recipient handling, provider result, and cancellation | Yes. These continue to determine the actual mail outcome. | Skipping subscription checking does not change the requested delivery or lifecycle contracts. | No. Their retained behavior supports complete delivery and honest results. | Preserve these owners and their current ordering. Provider acceptance remains separate from the subscription decision. |
|
|
575
|
+
|
|
576
|
+
The operation graph adds one synchronous connection-address comparison at the
|
|
577
|
+
existing request owner. A matching request avoids subscription header checking,
|
|
578
|
+
callback invocation, and its asynchronous verification wait, then uses the
|
|
579
|
+
ordinary provider path. There is no new file read, DNS request, timer, polling,
|
|
580
|
+
cache, helper, shared-state lock, startup barrier, or per-platform process.
|
|
581
|
+
Requests continue independently under the existing handler lifecycle. These are
|
|
582
|
+
source-level operation changes, not measured timing improvements.
|
|
583
|
+
|
|
584
|
+
The selected `node-http-server` raw-request hook supplies the native Node request.
|
|
585
|
+
HTTP/1.1 and HTTP/2 both expose the needed address properties; the same Node
|
|
586
|
+
contract applies on Windows, Linux, and macOS, with Android requiring a compatible
|
|
587
|
+
Node host. The local socket address is the actual destination used by the
|
|
588
|
+
connection, including when the listener binds `0.0.0.0`.
|
|
589
|
+
[Node socket addresses](https://nodejs.org/api/net.html#socketlocaladdress) and
|
|
590
|
+
[HTTP/2 request sockets](https://nodejs.org/api/http2.html#requestsocket)
|
|
591
|
+
document these properties.
|
|
592
|
+
|
|
593
|
+
For equal connection IPs, the verification service's own mail proceeds without
|
|
594
|
+
calling that service again. This removes its circular subscription dependency
|
|
595
|
+
on this path. It does not claim that arbitrary callback code or a repeatedly
|
|
596
|
+
failing browser mail-event subscriber cannot create a separate feedback loop.
|
|
597
|
+
The existing error and retry distinctions remain documented in the
|
|
598
|
+
[mail reference](../reference/mail.md#error-reports-and-retries).
|
|
599
|
+
|
|
600
|
+
This follow-up records the selected behavior and source review. No local test,
|
|
601
|
+
check, build, server launch, live mail send, or platform execution was performed
|
|
602
|
+
by this documentation author. Release and runtime evidence remain with the
|
|
603
|
+
corresponding operation's owner.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -17,6 +17,21 @@
|
|
|
17
17
|
"./browser-device": "./browser-runtime/browser-device.mjs",
|
|
18
18
|
"./pwa": "./browser-runtime/pwa.mjs",
|
|
19
19
|
"./preference-store": "./runtime/arcane/modules/PreferenceStore.js",
|
|
20
|
+
"./ai": "./runtime/arcane/modules/AI.js",
|
|
21
|
+
"./ai-preference-tuple": "./runtime/arcane/modules/AIPreferenceTuple.js",
|
|
22
|
+
"./ai-preference-runtime": "./runtime/arcane/modules/AIPreferenceRuntime.js",
|
|
23
|
+
"./ai-provider-runtime": "./runtime/arcane/modules/AIProviderRuntime.js",
|
|
24
|
+
"./ai-runtime-state": "./runtime/arcane/modules/AIRuntimeState.js",
|
|
25
|
+
"./model-definition": "./runtime/arcane/modules/ModelDefinition.js",
|
|
26
|
+
"./conversation-timebox": "./runtime/arcane/modules/ConversationTimebox.js",
|
|
27
|
+
"./conversation-action-items": "./runtime/arcane/modules/ConversationActionItems.js",
|
|
28
|
+
"./conversation-closing-report": "./runtime/arcane/modules/ConversationClosingReport.js",
|
|
29
|
+
"./chat-records": "./runtime/arcane/modules/ChatRecords.js",
|
|
30
|
+
"./app-data-scope": "./runtime/arcane/modules/AppDataScope.js",
|
|
31
|
+
"./core-local-model-catalog": "./runtime/arcane/modules/CoreLocalModelCatalog.js",
|
|
32
|
+
"./dbopfs-document-library": "./runtime/arcane/modules/DBOPFSDocumentLibrary.js",
|
|
33
|
+
"./local-ai-readiness": "./runtime/arcane/modules/LocalAIReadiness.js",
|
|
34
|
+
"./ollama-model-identifier": "./runtime/arcane/modules/OllamaModelIdentifier.js",
|
|
20
35
|
"./speech-playback": "./runtime/arcane/modules/SpeechPlayback.js",
|
|
21
36
|
"./speech-text": "./browser-runtime/speech-text.mjs",
|
|
22
37
|
"./ai/browser-wasm": "./browser-runtime/ai/browser-wasm.mjs",
|
|
@@ -69,7 +84,7 @@
|
|
|
69
84
|
"test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
|
|
70
85
|
"test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
|
|
71
86
|
"test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
|
|
72
|
-
"test:integration": "node ./bin/arcane-test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
|
|
87
|
+
"test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
|
|
73
88
|
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
74
89
|
"check": "node tools/check-source.mjs && npm test",
|
|
75
90
|
"check:release": "node tools/check-source.mjs --package-only && npm run test:release",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
default,
|
|
3
|
+
default as Mail,
|
|
4
|
+
resolveMailConfig
|
|
5
|
+
} from './Mail.js';
|
|
6
|
+
export {
|
|
7
|
+
MAIL_OUTBOX_IDEMPOTENCY_WINDOW_MS,
|
|
8
|
+
MAIL_OUTBOX_PROTOCOL,
|
|
9
|
+
MAIL_OUTBOX_STATES,
|
|
10
|
+
MAIL_OUTBOX_TABLE,
|
|
11
|
+
MailOutbox,
|
|
12
|
+
createMailOutbox
|
|
13
|
+
} from './MailOutbox.mjs';
|
|
14
|
+
export {
|
|
15
|
+
MailTransportError,
|
|
16
|
+
normalizeMailEndpoint,
|
|
17
|
+
sendMailReport,
|
|
18
|
+
serializeMailReport
|
|
19
|
+
} from './MailTransport.mjs';
|
package/src/dev-server.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import https from 'node:https';
|
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import {resolveWorkspace} from './workspace.mjs';
|
|
9
|
+
import {readInstalledSdkLayout} from './sdk-runtime-layout.mjs';
|
|
9
10
|
import {APP_DESCRIPTOR_NAME, projectPackageManifest} from './app-descriptor.mjs';
|
|
10
11
|
import {APP_CONFIG_NAME, validateAppConfig} from './packager/core.mjs';
|
|
11
12
|
import {createEventQueue} from './event-queue.mjs';
|
|
@@ -436,6 +437,7 @@ function sharedPathAllowed(relative,route){
|
|
|
436
437
|
});
|
|
437
438
|
if(excluded)return false;
|
|
438
439
|
return route.include.some(item=>{
|
|
440
|
+
if(item==='.')return true;
|
|
439
441
|
const candidate=inventoryKey(item);
|
|
440
442
|
return comparable===candidate||comparable.startsWith(`${candidate}/`);
|
|
441
443
|
});
|
|
@@ -471,11 +473,16 @@ async function sourceRoutes(workspaceRoot,appId,{
|
|
|
471
473
|
mappings:[appMapping,...sdkSource.mappings]
|
|
472
474
|
};
|
|
473
475
|
}
|
|
474
|
-
if(resolved.config.workspaceMode==='integrated'
|
|
476
|
+
if(resolved.config.workspaceMode==='integrated'
|
|
477
|
+
||resolved.config.browserRuntimeLayout==='installed-v1'){
|
|
478
|
+
const installed=resolved.config.browserRuntimeLayout==='installed-v1'
|
|
479
|
+
?await readInstalledSdkLayout(resolved.workspaceRoot,resolved.config)
|
|
480
|
+
:null;
|
|
475
481
|
return {
|
|
476
482
|
workspaceRoot:resolved.workspaceRoot,
|
|
477
|
-
workspaceMode:
|
|
483
|
+
workspaceMode:resolved.config.workspaceMode,
|
|
478
484
|
config:resolved.config,
|
|
485
|
+
...(installed?{installed}:{}),
|
|
479
486
|
appId:resolved.appId,
|
|
480
487
|
app:resolved.app.manifest,
|
|
481
488
|
startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
|
|
@@ -579,7 +586,7 @@ async function sourcePwaAssets(routeSet, mappings, signal, resourceUrls, resourc
|
|
|
579
586
|
}
|
|
580
587
|
}
|
|
581
588
|
for (const selected of mapping.include ?? ['']) {
|
|
582
|
-
await visitSourceResource(selected ? selected.split('/') : []);
|
|
589
|
+
await visitSourceResource(selected && selected!=='.' ? selected.split('/') : []);
|
|
583
590
|
}
|
|
584
591
|
}
|
|
585
592
|
const origin = 'http://arcane.invalid';
|
|
@@ -866,7 +873,7 @@ async function startOwnedDevServer({
|
|
|
866
873
|
const mappings=deterministicMappings(routeSet.mappings);
|
|
867
874
|
const versionPath = mode === 'source'
|
|
868
875
|
? sdkRuntimeSourceRoot === undefined
|
|
869
|
-
? path.join(routeSet.workspaceRoot, 'arcane.lock.json')
|
|
876
|
+
? routeSet.installed?.versionPath ?? path.join(routeSet.workspaceRoot, 'arcane.lock.json')
|
|
870
877
|
: path.join(routeSet.runtime.sourceRoot, 'package.json')
|
|
871
878
|
: undefined;
|
|
872
879
|
const [generatorInputs, initialAssetVersion, versionInput] = await Promise.all(
|
|
@@ -897,6 +904,9 @@ async function startOwnedDevServer({
|
|
|
897
904
|
);
|
|
898
905
|
async function selectedAssetVersion(){
|
|
899
906
|
if(mode!=='source')return undefined;
|
|
907
|
+
if(routeSet.installed)return JSON.parse(await readFile(
|
|
908
|
+
routeSet.installed.versionPath,'utf8'
|
|
909
|
+
)).version;
|
|
900
910
|
return sdkRuntimeSourceRoot===undefined
|
|
901
911
|
?readWorkspaceAssetVersion(routeSet.workspaceRoot)
|
|
902
912
|
:JSON.parse(await readFile(
|
package/src/import-map.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import {pathToFileURL} from 'node:url';
|
|
5
5
|
import {SDK_VERSION} from './constants.mjs';
|
|
6
6
|
import {listRuntimeFiles} from './runtime.mjs';
|
|
7
|
+
import {readInstalledSdkLayout,installedRuntimeFiles,installedRuntimeTarget} from './sdk-runtime-layout.mjs';
|
|
7
8
|
|
|
8
9
|
const is = new Is(false);
|
|
9
10
|
|
|
@@ -19,7 +20,23 @@ const SDK_BROWSER_AI_ENTRY='sdk/ai/browser-wasm.mjs';
|
|
|
19
20
|
const SDK_BROWSER_SPEECH_ENTRY='sdk/ai/browser-speech.mjs';
|
|
20
21
|
const STATIC_RUNTIME_PACKAGE_IMPORTS=new Map([
|
|
21
22
|
['arcane-os/preference-store','modules/PreferenceStore.js'],
|
|
22
|
-
['arcane-os/speech-playback','modules/SpeechPlayback.js']
|
|
23
|
+
['arcane-os/speech-playback','modules/SpeechPlayback.js'],
|
|
24
|
+
['arcane-os/ai','modules/AI.js'],
|
|
25
|
+
['arcane-os/ai-preference-tuple','modules/AIPreferenceTuple.js'],
|
|
26
|
+
['arcane-os/ai-preference-runtime','modules/AIPreferenceRuntime.js'],
|
|
27
|
+
['arcane-os/ai-provider-runtime','modules/AIProviderRuntime.js'],
|
|
28
|
+
['arcane-os/ai-runtime-state','modules/AIRuntimeState.js'],
|
|
29
|
+
['arcane-os/model-definition','modules/ModelDefinition.js'],
|
|
30
|
+
['arcane-os/conversation-timebox','modules/ConversationTimebox.js'],
|
|
31
|
+
['arcane-os/conversation-action-items','modules/ConversationActionItems.js'],
|
|
32
|
+
['arcane-os/conversation-closing-report','modules/ConversationClosingReport.js'],
|
|
33
|
+
['arcane-os/chat-records','modules/ChatRecords.js'],
|
|
34
|
+
['arcane-os/app-data-scope','modules/AppDataScope.js'],
|
|
35
|
+
['arcane-os/core-local-model-catalog','modules/CoreLocalModelCatalog.js'],
|
|
36
|
+
['arcane-os/dbopfs-document-library','modules/DBOPFSDocumentLibrary.js'],
|
|
37
|
+
['arcane-os/local-ai-readiness','modules/LocalAIReadiness.js'],
|
|
38
|
+
['arcane-os/ollama-model-identifier','modules/OllamaModelIdentifier.js'],
|
|
39
|
+
['arcane-os/mail','modules/MailApi.mjs']
|
|
23
40
|
]);
|
|
24
41
|
const SDK_BROWSER_SELF_IMPORTS=new Map([
|
|
25
42
|
['arcane-os/event-manager',SDK_BROWSER_ENTRY],
|
|
@@ -1436,9 +1453,10 @@ async function physicalRuntime(workspaceRoot,signal){
|
|
|
1436
1453
|
}
|
|
1437
1454
|
|
|
1438
1455
|
async function managedImportMapBuild(resolvedWorkspace,signal,pwaEnabled=false){
|
|
1456
|
+
const installed=await readInstalledSdkLayout(resolvedWorkspace);
|
|
1439
1457
|
const [runtime,version]=await Promise.all([
|
|
1440
|
-
physicalRuntime(resolvedWorkspace,signal),
|
|
1441
|
-
pwaEnabled?null:readWorkspaceAssetVersion(resolvedWorkspace)
|
|
1458
|
+
installed?installedRuntimeFiles(resolvedWorkspace,installed,signal):physicalRuntime(resolvedWorkspace,signal),
|
|
1459
|
+
pwaEnabled?null:installed?.version??readWorkspaceAssetVersion(resolvedWorkspace)
|
|
1442
1460
|
]);
|
|
1443
1461
|
const built=await buildImportMap({files:runtime.files,signal,version});
|
|
1444
1462
|
const json=`${JSON.stringify({imports:built.imports},null,2).replaceAll('<','\\u003c')}\n`;
|
|
@@ -1446,6 +1464,8 @@ async function managedImportMapBuild(resolvedWorkspace,signal,pwaEnabled=false){
|
|
|
1446
1464
|
}
|
|
1447
1465
|
|
|
1448
1466
|
export async function readWorkspaceAssetVersion(workspaceRoot){
|
|
1467
|
+
const installed=await readInstalledSdkLayout(workspaceRoot);
|
|
1468
|
+
if(installed)return installed.version;
|
|
1449
1469
|
let source;
|
|
1450
1470
|
try{source=await readFileFromDisk(path.join(workspaceRoot,'arcane.lock.json'),'utf8');}
|
|
1451
1471
|
catch(error){
|
|
@@ -2434,10 +2454,17 @@ export async function readApplicationTestImportMapContext({
|
|
|
2434
2454
|
||is.array(document.imports)){
|
|
2435
2455
|
fail('Application test import-map artifact must contain an imports object.');
|
|
2436
2456
|
}
|
|
2457
|
+
const installed=await readInstalledSdkLayout(resolvedWorkspaceRoot);
|
|
2458
|
+
const imports=installed?Object.fromEntries(Object.entries(document.imports).map(
|
|
2459
|
+
function installedApplicationTestTarget([specifier,target]){
|
|
2460
|
+
return [specifier,is.string(target)&&target.startsWith('./')
|
|
2461
|
+
?`./${installedRuntimeTarget(target.slice(2),installed)}`:target];
|
|
2462
|
+
}
|
|
2463
|
+
)):document.imports;
|
|
2437
2464
|
return createApplicationTestImportMapContext({
|
|
2438
2465
|
applicationRoot:resolvedWorkspaceRoot,
|
|
2439
2466
|
boundary:'source',
|
|
2440
|
-
imports
|
|
2467
|
+
imports,
|
|
2441
2468
|
signal
|
|
2442
2469
|
});
|
|
2443
2470
|
}
|
package/src/mail-api.mjs
CHANGED
|
@@ -1,19 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
default as Mail,
|
|
4
|
-
resolveMailConfig
|
|
5
|
-
} from '../runtime/arcane/modules/Mail.js';
|
|
6
|
-
export {
|
|
7
|
-
MAIL_OUTBOX_IDEMPOTENCY_WINDOW_MS,
|
|
8
|
-
MAIL_OUTBOX_PROTOCOL,
|
|
9
|
-
MAIL_OUTBOX_STATES,
|
|
10
|
-
MAIL_OUTBOX_TABLE,
|
|
11
|
-
MailOutbox,
|
|
12
|
-
createMailOutbox
|
|
13
|
-
} from '../runtime/arcane/modules/MailOutbox.mjs';
|
|
14
|
-
export {
|
|
15
|
-
MailTransportError,
|
|
16
|
-
normalizeMailEndpoint,
|
|
17
|
-
sendMailReport,
|
|
18
|
-
serializeMailReport
|
|
19
|
-
} from '../runtime/arcane/modules/MailTransport.mjs';
|
|
1
|
+
export {default} from '../runtime/arcane/modules/MailApi.mjs';
|
|
2
|
+
export * from '../runtime/arcane/modules/MailApi.mjs';
|
package/src/mail-server.mjs
CHANGED
|
@@ -899,8 +899,16 @@ function createConfiguredMailHandler(configuration){
|
|
|
899
899
|
if(request.method!=='POST'){
|
|
900
900
|
throw new MailGatewayFault('mail_method_not_allowed',{statusCode:405});
|
|
901
901
|
}
|
|
902
|
-
if(configuration.verifySubscription){
|
|
903
|
-
|
|
902
|
+
if (configuration.verifySubscription) {
|
|
903
|
+
const requesterIp = request.socket?.remoteAddress;
|
|
904
|
+
const serverIp = request.socket?.localAddress;
|
|
905
|
+
if (!requesterIp || requesterIp !== serverIp) {
|
|
906
|
+
appId = await verifyMailSubscription(
|
|
907
|
+
request,
|
|
908
|
+
configuration,
|
|
909
|
+
requestController.signal
|
|
910
|
+
);
|
|
911
|
+
}
|
|
904
912
|
}
|
|
905
913
|
idempotencyKey=requireRequestHeader(request,'idempotency-key');
|
|
906
914
|
const requestText=await readRequestBodyText(request,{
|
package/src/packager/core.mjs
CHANGED
|
@@ -136,12 +136,13 @@ function isGlobLike(value){
|
|
|
136
136
|
return /[*?\[\]{}]/u.test(value);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
function validatePathList(value,label,{required=false}={}){
|
|
139
|
+
function validatePathList(value,label,{required=false,allowRoot=false}={}){
|
|
140
140
|
if(!is.array(value)||(required&&value.length===0)){
|
|
141
141
|
fail(`${label} must be ${required?'a non-empty':'an'} array of literal relative paths.`);
|
|
142
142
|
}
|
|
143
143
|
const normalized=value.map((entry,index)=>{
|
|
144
|
-
const item=
|
|
144
|
+
const item=allowRoot?normalizeRelativeRoot(entry,`${label}[${index}]`)
|
|
145
|
+
:normalizeRelativePath(entry,`${label}[${index}]`);
|
|
145
146
|
if(isGlobLike(item))fail(`${label}[${index}] must be literal; directories include descendants.`);
|
|
146
147
|
return item;
|
|
147
148
|
});
|
|
@@ -151,7 +152,8 @@ function validatePathList(value,label,{required=false}={}){
|
|
|
151
152
|
if(required){
|
|
152
153
|
for(let left=0;left<normalized.length;left+=1){
|
|
153
154
|
for(let right=left+1;right<normalized.length;right+=1){
|
|
154
|
-
if(
|
|
155
|
+
if(normalized[left]==='.'||normalized[right]==='.'
|
|
156
|
+
||sameOrDescendant(normalized[left],normalized[right])
|
|
155
157
|
||sameOrDescendant(normalized[right],normalized[left])){
|
|
156
158
|
fail(`${label} has overlapping paths: ${normalized[left]} and ${normalized[right]}`);
|
|
157
159
|
}
|
|
@@ -256,7 +258,7 @@ function validateSharedRoute(route,label){
|
|
|
256
258
|
assertOnlyKeys(route,new Set(['source','destination','include','exclude']),label);
|
|
257
259
|
const source=normalizeRelativeRoot(route.source,`${label}.source`);
|
|
258
260
|
const destination=normalizeRelativeRoot(route.destination,`${label}.destination`);
|
|
259
|
-
const include=validatePathList(route.include,`${label}.include`,{required:true});
|
|
261
|
+
const include=validatePathList(route.include,`${label}.include`,{required:true,allowRoot:true});
|
|
260
262
|
const exclude=validatePathList(route.exclude??[],`${label}.exclude`);
|
|
261
263
|
if(source==='.'||source==='apps'||source.startsWith('apps/')
|
|
262
264
|
||source==='dist'||source.startsWith('dist/')||source==='node_modules'
|
|
@@ -421,7 +423,7 @@ async function loadContext(requestedWorkspaceRoot,appId){
|
|
|
421
423
|
}
|
|
422
424
|
|
|
423
425
|
function destinationJoin(root,relative){
|
|
424
|
-
return root==='.'?relative:`${root}/${relative}`;
|
|
426
|
+
return relative==='.'?root:root==='.'?relative:`${root}/${relative}`;
|
|
425
427
|
}
|
|
426
428
|
|
|
427
429
|
function appPackagePath(context, relative) {
|
|
@@ -442,12 +444,13 @@ async function collectSelectedPath({
|
|
|
442
444
|
records,
|
|
443
445
|
destinations,
|
|
444
446
|
signal,
|
|
445
|
-
label
|
|
447
|
+
label,
|
|
448
|
+
allowRoot=false
|
|
446
449
|
}){
|
|
447
450
|
throwIfAborted(signal);
|
|
448
451
|
if(isExcluded(selected,excludes))return;
|
|
449
452
|
if(reject(selected))fail(`${label} selects a reserved private or generated path: ${selected}.`);
|
|
450
|
-
const absolute=resolveInside(sourceRoot,selected,label);
|
|
453
|
+
const absolute=resolveInside(sourceRoot,selected,label,{allowRoot});
|
|
451
454
|
let info;
|
|
452
455
|
try{info=await lstat(absolute);}
|
|
453
456
|
catch(error){
|
|
@@ -455,15 +458,16 @@ async function collectSelectedPath({
|
|
|
455
458
|
throw error;
|
|
456
459
|
}
|
|
457
460
|
if(info.isSymbolicLink())fail(`${label} contains a symbolic link or junction: ${selected}.`);
|
|
461
|
+
if(selected==='.'&&!info.isDirectory())fail(`${label} root selection must be a directory.`);
|
|
458
462
|
if(info.isDirectory()){
|
|
459
463
|
const entries=await readdir(absolute,{withFileTypes:true});
|
|
460
464
|
entries.sort((left,right)=>compareText(left.name,right.name));
|
|
461
465
|
for(const entry of entries){
|
|
462
|
-
const child
|
|
466
|
+
const child=destinationJoin(selected,entry.name);
|
|
463
467
|
await collectSelectedPath({
|
|
464
468
|
sourceRoot,
|
|
465
469
|
selected:child,
|
|
466
|
-
destination
|
|
470
|
+
destination:destinationJoin(destination,entry.name),
|
|
467
471
|
excludes,
|
|
468
472
|
reject,
|
|
469
473
|
records,
|
|
@@ -515,7 +519,8 @@ async function collectPackageRecords(context,{signal}={}){
|
|
|
515
519
|
records,
|
|
516
520
|
destinations,
|
|
517
521
|
signal,
|
|
518
|
-
label:`sharedPayloads.${sharedId}
|
|
522
|
+
label:`sharedPayloads.${sharedId}`,
|
|
523
|
+
allowRoot:true
|
|
519
524
|
});
|
|
520
525
|
}
|
|
521
526
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
import {readFile,readdir} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const is=new Is(false);
|
|
6
|
+
|
|
7
|
+
// The browser URLs stay the same; only the source of their files changes.
|
|
8
|
+
export function installedSdkRoutes(packageSource,{security=false}={}){
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
source:`${packageSource}/runtime/arcane`,destination:'arcane',
|
|
12
|
+
include:['components','css','entities','img','modules',...(security?['security']:[])],exclude:[]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
source:`${packageSource}/browser-runtime`,destination:'arcane/sdk',
|
|
16
|
+
include:['.'],exclude:[]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
source:`${packageSource}/runtime/strong-type`,destination:'arcane/dependencies/strong-type',
|
|
20
|
+
include:['.'],exclude:[]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
source:packageSource,destination:'licenses/arcane-os',
|
|
24
|
+
include:['LICENSE','COMMERCIAL-LICENSE.md','NOTICE'],exclude:[]
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function installedSdkPackageSource(config){
|
|
30
|
+
const routes=config?.sharedPayloads?.['browser-runtime'];
|
|
31
|
+
if(!is.array(routes)||routes.length!==4)return null;
|
|
32
|
+
const source=routes[3]?.source;
|
|
33
|
+
if(!is.string(source)||!/^node_modules\/(?:@[a-z0-9._-]+\/)?[a-z0-9][a-z0-9._-]*$/u.test(source))return null;
|
|
34
|
+
const expected=installedSdkRoutes(source,{security:routes[0]?.include?.at(-1)==='security'});
|
|
35
|
+
return routes.every(function matchesInstalledRoute(route,index){
|
|
36
|
+
const wanted=expected[index];
|
|
37
|
+
return route?.source===wanted.source&&route?.destination===wanted.destination
|
|
38
|
+
&&is.array(route.include)&&route.include.length===wanted.include.length
|
|
39
|
+
&&route.include.every(function matchesSelectedPath(value,item){return value===wanted.include[item];})
|
|
40
|
+
&&(route.exclude===undefined||(is.array(route.exclude)&&route.exclude.length===0));
|
|
41
|
+
})?source:null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function readInstalledSdkLayout(workspaceRoot,config){
|
|
45
|
+
if(config===undefined){
|
|
46
|
+
try{config=JSON.parse(await readFile(path.join(workspaceRoot,'arcane-packager.json'),'utf8'));}
|
|
47
|
+
catch(error){if(error.code==='ENOENT')return null;throw error;}
|
|
48
|
+
}
|
|
49
|
+
const packageSource=installedSdkPackageSource(config);
|
|
50
|
+
if(packageSource===null)return null;
|
|
51
|
+
const packageRoot=path.join(workspaceRoot,...packageSource.split('/'));
|
|
52
|
+
const versionPath=path.join(packageRoot,'package.json');
|
|
53
|
+
const manifest=JSON.parse(await readFile(versionPath,'utf8'));
|
|
54
|
+
if(manifest.name!=='arcane-os'||!is.string(manifest.version)||!manifest.version){
|
|
55
|
+
const error=new Error('The installed SDK runtime must identify its arcane-os package version.');
|
|
56
|
+
error.code='ARCANE_WORKSPACE_INVALID';
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
return {packageSource,packageRoot,versionPath,version:manifest.version,routes:config.sharedPayloads['browser-runtime']};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function installedRuntimeFiles(workspaceRoot,layout,signal){
|
|
63
|
+
const files=[];
|
|
64
|
+
async function visit(directory,logical){
|
|
65
|
+
signal?.throwIfAborted();
|
|
66
|
+
const entries=await readdir(directory,{withFileTypes:true});
|
|
67
|
+
for(const entry of entries){
|
|
68
|
+
signal?.throwIfAborted();
|
|
69
|
+
const relative=logical?`${logical}/${entry.name}`:entry.name;
|
|
70
|
+
if(entry.isDirectory())await visit(path.join(directory,entry.name),relative);
|
|
71
|
+
else if(entry.isFile())files.push(relative);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for(const route of layout.routes){
|
|
75
|
+
if(route.destination!=='arcane'&&!route.destination.startsWith('arcane/'))continue;
|
|
76
|
+
const prefix=route.destination==='arcane'?'':route.destination.slice('arcane/'.length);
|
|
77
|
+
for(const selected of route.include){
|
|
78
|
+
const suffix=selected==='.'?'':selected;
|
|
79
|
+
await visit(
|
|
80
|
+
path.join(workspaceRoot,...route.source.split('/'),suffix),
|
|
81
|
+
[prefix,suffix].filter(Boolean).join('/')
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {files:files.sort()};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function installedRuntimeTarget(relative,layout){
|
|
89
|
+
for(const route of [...layout.routes].sort(function longestDestinationFirst(left,right){
|
|
90
|
+
return right.destination.length-left.destination.length;
|
|
91
|
+
})){
|
|
92
|
+
if(!relative.startsWith(`${route.destination}/`))continue;
|
|
93
|
+
const suffix=relative.slice(route.destination.length+1);
|
|
94
|
+
if(route.include.some(function includesRuntimeTarget(selected){
|
|
95
|
+
return selected==='.'||suffix===selected||suffix.startsWith(`${selected}/`);
|
|
96
|
+
}))return `${route.source}/${suffix}`;
|
|
97
|
+
}
|
|
98
|
+
return relative;
|
|
99
|
+
}
|
package/src/workspace.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import {loadAppDescriptor} from './app-descriptor.mjs';
|
|
10
10
|
import {SDK_NAME as EXPECTED_SDK_NAME} from './constants.mjs';
|
|
11
11
|
import {inspectImportMapHtml} from './import-map.mjs';
|
|
12
|
+
import {installedSdkPackageSource} from './sdk-runtime-layout.mjs';
|
|
12
13
|
|
|
13
14
|
const is = new Is(false);
|
|
14
15
|
|
|
@@ -159,6 +160,13 @@ function classifyRootConfig(config){
|
|
|
159
160
|
const validated=validatePackagerRootConfig(config,ROOT_CONFIG_NAME);
|
|
160
161
|
const routes=validated.sharedPayloads['browser-runtime'];
|
|
161
162
|
if(!is.array(routes))fail(`${ROOT_CONFIG_NAME} must define browser-runtime routes.`);
|
|
163
|
+
const installedPackageSource=installedSdkPackageSource(validated);
|
|
164
|
+
if(installedPackageSource!==null){
|
|
165
|
+
return {
|
|
166
|
+
...validated,workspaceMode:'external',browserRuntimeLayout:'installed-v1',
|
|
167
|
+
sdkPackageSource:installedPackageSource
|
|
168
|
+
};
|
|
169
|
+
}
|
|
162
170
|
const externalPackageSource=routes.length===2
|
|
163
171
|
&&dependencyNameForSdkPackageSource(routes[1]?.source)!==null
|
|
164
172
|
?routes[1].source
|