arcane-os 0.25.0 → 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 +16 -0
- package/README.md +11 -2
- package/docs/architecture.md +22 -12
- package/docs/reference/cli.md +24 -7
- package/docs/reference/protocols.md +72 -4
- package/docs/reference/pwa.md +9 -0
- package/docs/reference/sdk-api.md +53 -13
- 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/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,21 @@
|
|
|
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
|
+
|
|
3
19
|
## 0.25.0
|
|
4
20
|
|
|
5
21
|
- Automatically skip configured mail subscription verification when the actual
|
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
|
|
@@ -62,7 +71,7 @@ Create one browser application, install its pinned SDK, and start its source
|
|
|
62
71
|
server:
|
|
63
72
|
|
|
64
73
|
```bash
|
|
65
|
-
npx arcane-os@0.
|
|
74
|
+
npx arcane-os@0.26.0 new hello-speech --path ./hello-speech --target browser
|
|
66
75
|
cd hello-speech
|
|
67
76
|
npm install
|
|
68
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
|
```
|
|
@@ -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
|
|
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/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
|