arcane-os 0.30.0 → 0.31.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 +14 -11
- package/browser-runtime/ai/browser-device-settings.mjs +2 -2
- package/docs/reference/cli.md +25 -25
- package/docs/reference/mail.md +5 -5
- package/docs/reference/protocols.md +17 -26
- package/docs/reference/pwa.md +25 -45
- package/docs/reference/sdk-api.md +9 -10
- package/docs/reviews/mail-server-purpose-review.md +1 -1
- package/package.json +1 -1
- package/src/app-layout.mjs +0 -22
- package/src/cli/main.mjs +1 -1
- package/src/dev-server.mjs +4 -40
- package/src/mail-credentials.mjs +3 -3
- package/src/packager/core.mjs +5 -48
- package/src/pwa.mjs +0 -43
- package/src/scaffold.mjs +3 -2
- package/src/templates/workspace-template.mjs +3 -2
- package/src/toolchain.mjs +4 -39
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.31.0
|
|
4
|
+
|
|
5
|
+
- Standalone applications use their repository root and installed npm package
|
|
6
|
+
paths. New standalone projects default to `appsRoot: "."`; initialization
|
|
7
|
+
preserves an existing configured layout, and explicit multi-app workspaces
|
|
8
|
+
retain their selected application directories.
|
|
9
|
+
- Remove the retired `legacyAppPaths` setting and all SDK-generated nested app
|
|
10
|
+
redirects and duplicate PWA worker/inventory files. Consumers must remove the
|
|
11
|
+
obsolete setting when upgrading. Enabled PWA files remain at the app root;
|
|
12
|
+
public SDK files remain under `node_modules/arcane-os` or the installed alias.
|
|
13
|
+
- Preserve selected authored files, complete URLs and queries, installation
|
|
14
|
+
identity, saved data and caches. Generation does not delete preexisting app
|
|
15
|
+
files; application owners preserve and relocate their content independently.
|
|
16
|
+
- Generated and offline app files are committed for hosting workflows to
|
|
17
|
+
consume. Existing public Node TWiN and browser APIs remain available.
|
|
18
|
+
|
|
3
19
|
## 0.30.0
|
|
4
20
|
|
|
5
21
|
- Add the public `arcane-os/ai/twin-cloud` subpath with stateless `fetchRequest`
|
package/README.md
CHANGED
|
@@ -16,31 +16,34 @@
|
|
|
16
16
|
`arcane-os` is the application SDK and command-line toolchain for Arcane OS. It
|
|
17
17
|
supports two explicit workspace profiles: an external app repository uses the
|
|
18
18
|
version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
19
|
-
`arcane/` runtime. Both profiles
|
|
20
|
-
|
|
19
|
+
`arcane/` runtime. Both profiles share the theme, packaging, event, cancellation,
|
|
20
|
+
and browser run contracts while retaining their selected application layout.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.31.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
|
-
|
|
26
|
+
Standalone browser apps use their repository root and installed npm package directly, without a
|
|
27
27
|
generated workspace `arcane/` directory or `arcane.lock.json`. Select the
|
|
28
28
|
[four installed-package routes](docs/reference/protocols.md#installed-package-browser-routes)
|
|
29
29
|
in `arcane-packager.json`: development, managed import maps, and PWA resources
|
|
30
30
|
read the installed SDK at real `/node_modules/arcane-os/...` browser URLs when
|
|
31
31
|
each destination equals its source. An alias uses its actual installed folder.
|
|
32
|
-
|
|
33
|
-
`arcane
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
`arcane new my-app` defaults to `appsRoot: "."`. After npm installation,
|
|
33
|
+
`arcane import-map` refreshes managed maps and the enabled root PWA files for
|
|
34
|
+
ordinary static hosting. It generates no nested app redirects or duplicate PWA
|
|
35
|
+
files. Remove the retired `legacyAppPaths` setting when upgrading. Commit generated
|
|
36
|
+
and offline app files for hosting workflows to consume. Portable app packages
|
|
37
|
+
still contain their selected runtime.
|
|
36
38
|
Root/direct maps use `arcane-os/modules/<filename>` and
|
|
37
39
|
`arcane-os/entities/<filename>` (including extensions) plus the focused lowercase
|
|
38
40
|
exports, rather than `arcane/*` aliases. These paths resolve to the actual npm
|
|
39
41
|
files, preserving relative component URLs. New root apps put the SDK in runtime
|
|
40
42
|
`dependencies`; init preserves existing runtime/optional declarations and promotes
|
|
41
43
|
a root app's SDK development declaration without changing its other packages.
|
|
42
|
-
|
|
43
|
-
supported
|
|
44
|
+
Explicit multi-app `appsRoot: "apps"`, virtual runtime routes, and existing
|
|
45
|
+
materialized workspaces remain supported; initialization preserves their selected
|
|
46
|
+
layout. Node services continue to
|
|
44
47
|
use the installed CLI or public imports such as `arcane-os/mail`.
|
|
45
48
|
|
|
46
49
|
The [mail gateway](docs/reference/mail.md) serves HTTPS with HTTP/2 on port 4433
|
|
@@ -82,7 +85,7 @@ Create one browser application, install its pinned SDK, and start its source
|
|
|
82
85
|
server:
|
|
83
86
|
|
|
84
87
|
```bash
|
|
85
|
-
npx arcane-os@0.
|
|
88
|
+
npx arcane-os@0.31.0 new hello-speech --path ./hello-speech --target browser
|
|
86
89
|
cd hello-speech
|
|
87
90
|
npm install
|
|
88
91
|
```
|
|
@@ -74,11 +74,11 @@ export function getBrowserDeviceSettings(navigatorObject = globalThis.navigator)
|
|
|
74
74
|
|
|
75
75
|
// Only an explicit browser adapter type or fallback flag establishes its class.
|
|
76
76
|
// Vendor names and powerPreference (including Chromium's echoed request) do not.
|
|
77
|
-
export function describeBrowserGpu(info,
|
|
77
|
+
export function describeBrowserGpu(info, fallbackAdapter) {
|
|
78
78
|
const adapterType = is.string(info?.type) ? info.type : null;
|
|
79
79
|
const isFallbackAdapter = is.boolean(info?.isFallbackAdapter)
|
|
80
80
|
? info.isFallbackAdapter
|
|
81
|
-
: is.boolean(
|
|
81
|
+
: is.boolean(fallbackAdapter) ? fallbackAdapter : null;
|
|
82
82
|
let performanceStatus = 'unknown';
|
|
83
83
|
if (isFallbackAdapter === true || adapterType === 'CPU') performanceStatus = 'fallback';
|
|
84
84
|
else if (adapterType === 'discrete GPU') performanceStatus = 'discrete';
|
package/docs/reference/cli.md
CHANGED
|
@@ -120,8 +120,9 @@ directory as a repository. Native target scaffolds also retain `browser` and
|
|
|
120
120
|
include the required icon. The result reports the workspace, app, descriptor,
|
|
121
121
|
target, and created paths.
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
SDK directly.
|
|
123
|
+
Each standalone app's root is its repository root. The default `--apps-root .`
|
|
124
|
+
uses the installed npm SDK directly. Explicit `--apps-root apps` selects a
|
|
125
|
+
multi-app workspace with each app beneath `apps/<id>`. Root setup
|
|
125
126
|
does not install dependencies or copy a runtime: run `npm install`, then
|
|
126
127
|
`npm run import-map`. Until installation, its result reports the import map as
|
|
127
128
|
pending with reason `sdk-install-required`.
|
|
@@ -152,7 +153,7 @@ idempotent only for files whose existing content satisfies the scaffold
|
|
|
152
153
|
contract.
|
|
153
154
|
|
|
154
155
|
`--apps-root .` selects root setup for a standalone workspace. Omission retains
|
|
155
|
-
the configured layout, or
|
|
156
|
+
the configured layout, or `.` for a new standalone configuration. `init` never moves an
|
|
156
157
|
existing application or converts the integrated Arcane OS layout.
|
|
157
158
|
|
|
158
159
|
### Example
|
|
@@ -217,8 +218,9 @@ the workspace does not already identify exactly one. The command accepts no
|
|
|
217
218
|
positional arguments and supports app scope only. `arcane-os import-map` is the
|
|
218
219
|
identical executable alias.
|
|
219
220
|
|
|
220
|
-
The generated artifact is
|
|
221
|
-
`apps/<id>/modules/arcane.importmap.json
|
|
221
|
+
The generated artifact is `modules/arcane.importmap.json` at a standalone
|
|
222
|
+
app's repository root, or `apps/<id>/modules/arcane.importmap.json` for an
|
|
223
|
+
explicit multi-app workspace. Its exact JSON is also installed in
|
|
222
224
|
the configured entry and every other admitted browser document as `<script
|
|
223
225
|
type="importmap" data-arcane-import-map>` before module loading. The complete
|
|
224
226
|
runtime map derives its entries from the selected runtime and browser-runtime
|
|
@@ -228,15 +230,13 @@ portable runtime subpaths such as `arcane-os/preference-store` and
|
|
|
228
230
|
modules. The result reports the complete map written to the selected
|
|
229
231
|
application; no fixed entry count is a release contract.
|
|
230
232
|
|
|
231
|
-
For `appsRoot: "."`, the
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
[root-only generated output](pwa.md#root-only-generated-output) before changing
|
|
239
|
-
the URLs needed by previously installed apps.
|
|
233
|
+
For `appsRoot: "."`, managed imports use the installed package paths and enabled
|
|
234
|
+
PWA files are written beside the root entry. The SDK generates no nested
|
|
235
|
+
`apps/<id>/` redirects or duplicate PWA files and no repository-root `arcane/`
|
|
236
|
+
projection. App and installation identity, saved data, and selected authored
|
|
237
|
+
files remain unchanged. The same layout applies to `arcane dev` and
|
|
238
|
+
`arcane package`. Generated and offline app files are committed; GitHub Actions
|
|
239
|
+
consume those committed files. See [root generated output](pwa.md#root-generated-output).
|
|
240
240
|
|
|
241
241
|
SDK `0.5.17` preserves the physical workspace route count and ordered include
|
|
242
242
|
list. External and modern integrated routes require `components`, `css`,
|
|
@@ -531,8 +531,9 @@ npm exec -- arcane check --app hello-world
|
|
|
531
531
|
|
|
532
532
|
Creates one complete browser release beneath `dist/<id>/`, preserving the prior
|
|
533
533
|
output until the replacement is complete. It consumes saved source and managed
|
|
534
|
-
import maps,
|
|
535
|
-
shared route destinations.
|
|
534
|
+
import maps, keeps standalone app files at the output root, and retains the
|
|
535
|
+
configured shared route destinations. Explicit multi-app workspaces retain
|
|
536
|
+
their selected app beneath `apps/<id>/`. When selected shared content supplies no root
|
|
536
537
|
`index.html`, the SDK generates one that opens the selected app entry.
|
|
537
538
|
Source document bases and resource URLs therefore retain their development
|
|
538
539
|
layout. Packaging does not run tests or checks automatically.
|
|
@@ -542,11 +543,10 @@ selected SDK content directly from `node_modules`. Only the portable output
|
|
|
542
543
|
receives copies; no workspace `arcane/` projection is required. Its runtime URLs,
|
|
543
544
|
managed import-map targets, and PWA inventory destinations match source serving.
|
|
544
545
|
|
|
545
|
-
With `appsRoot: "."`,
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
packaged installation identity. Omission or `true` preserves existing behavior.
|
|
546
|
+
With `appsRoot: "."`, the planned and actual output retain root-relative app
|
|
547
|
+
files and direct npm package paths. The SDK adds no nested app redirects or
|
|
548
|
+
duplicate PWA worker/inventory files. Explicitly selected authored resources
|
|
549
|
+
and the default packaged installation identity remain unchanged.
|
|
550
550
|
|
|
551
551
|
```text
|
|
552
552
|
arcane package [--app <id>] [--dry-run]
|
|
@@ -898,10 +898,10 @@ Resend key for all its requests. An absent named key never falls back to the
|
|
|
898
898
|
default account. Existing root `RESEND_API_KEY` and
|
|
899
899
|
`MAIL_PROFILES[name].RESEND_API_KEY` remain fallbacks when the corresponding
|
|
900
900
|
nested key property is absent. A nested property containing null or an empty
|
|
901
|
-
string means the selected key is absent and takes precedence over a
|
|
901
|
+
string means the selected key is absent and takes precedence over a root or profile key.
|
|
902
902
|
|
|
903
903
|
Set and delete preserve the file's other settings and profile containers. New
|
|
904
|
-
keys are written to the nested mail member. Existing
|
|
904
|
+
keys are written to the nested mail member. Existing root or profile keys are updated
|
|
905
905
|
in place unless the selected nested key property exists; delete removes both
|
|
906
906
|
representations of only the selected key. Status returns the selected profile,
|
|
907
907
|
`provider:'resend'`, `storage:'.arcane.env.json'`, and `exists`. Delete returns
|
|
@@ -963,7 +963,7 @@ paths. It consumes the configured provider timeout and retry guidance.
|
|
|
963
963
|
|
|
964
964
|
The Resend credential comes from the selected `.arcane.env.json` entry.
|
|
965
965
|
An explicit `--profile` overrides `arcane.config.json.mail.profile`; omitting
|
|
966
|
-
both selects the default `mail.apiKey`, with the
|
|
966
|
+
both selects the default `mail.apiKey`, with the root-key fallback described
|
|
967
967
|
above. Neither the key nor report content is accepted through argv or process
|
|
968
968
|
environment variables.
|
|
969
969
|
|
|
@@ -1006,7 +1006,7 @@ Add the listener's certificate paths to `arcane.config.json`:
|
|
|
1006
1006
|
|
|
1007
1007
|
Supply an existing PEM certificate chain and its private key. Paths resolve
|
|
1008
1008
|
relative to the selected configuration directory, or may be absolute. They
|
|
1009
|
-
belong to the listener regardless of the provider key selected.
|
|
1009
|
+
belong to the listener regardless of the provider key selected. Existing root
|
|
1010
1010
|
`MAIL_TLS_CERT_PATH` and `MAIL_TLS_KEY_PATH` in `.arcane.env.json` remain
|
|
1011
1011
|
fallbacks for omitted config paths. Explicit programmatic `certPath` and
|
|
1012
1012
|
`keyPath` options override those files. Missing TLS settings name the fields
|
package/docs/reference/mail.md
CHANGED
|
@@ -381,7 +381,7 @@ application name and subscriber key. Existing top-level `RESEND_API_KEY` and
|
|
|
381
381
|
`MAIL_PROFILES[profile].RESEND_API_KEY` remain supported. A nested selected
|
|
382
382
|
`apiKey` takes precedence when the property exists, including null or an empty
|
|
383
383
|
string, which means the selected key is absent. Only an absent nested key
|
|
384
|
-
property permits fallback to the corresponding
|
|
384
|
+
property permits fallback to the corresponding root or profile key.
|
|
385
385
|
|
|
386
386
|
Programmatic operations resolve both files from `options.cwd`, then
|
|
387
387
|
`options.workspaceRoot`, then `process.cwd()`, choosing the first supplied
|
|
@@ -397,7 +397,7 @@ Configuration precedence is explicit:
|
|
|
397
397
|
An explicit null retains the option's existing meaning; it does not select
|
|
398
398
|
the file value again.
|
|
399
399
|
2. `arcane.config.json.mail` supplies nonsecret settings absent from those options.
|
|
400
|
-
3.
|
|
400
|
+
3. `.arcane.env.json` root `MAIL_TLS_CERT_PATH` and `MAIL_TLS_KEY_PATH`
|
|
401
401
|
supply certificate paths absent from the selected options and config member.
|
|
402
402
|
4. Remaining settings use the defaults above.
|
|
403
403
|
|
|
@@ -452,7 +452,7 @@ non-interactive alternative and rejects a TTY. Each command accepts an optional
|
|
|
452
452
|
profile argument, defaulting to `mail` independently of `arcane.config.json.mail.profile`.
|
|
453
453
|
Set and delete preserve other JSON settings and profiles; status reports
|
|
454
454
|
existence without returning the key. A new credential is written to the nested
|
|
455
|
-
mail member. An existing
|
|
455
|
+
mail member. An existing root or profile credential is updated at its existing location
|
|
456
456
|
unless the selected nested key property exists, in which case set updates that
|
|
457
457
|
nested property. Delete removes both representations of only the selected key,
|
|
458
458
|
so an older key cannot reappear through fallback. Other settings and profile
|
|
@@ -500,7 +500,7 @@ guidance from the same configuration. It does not require gateway TLS paths.
|
|
|
500
500
|
Send and serve read each required JSON file once, concurrently when both are
|
|
501
501
|
needed, before consuming their settings. An injected `readCredential` remains
|
|
502
502
|
the credential owner and reads once. With that injection, send reads only
|
|
503
|
-
`arcane.config.json`; serve also reads `.arcane.env.json` for
|
|
503
|
+
`arcane.config.json`; serve also reads `.arcane.env.json` for root TLS paths
|
|
504
504
|
without interpreting its unused file credential.
|
|
505
505
|
|
|
506
506
|
Start the gateway:
|
|
@@ -522,7 +522,7 @@ domain, for example `https://mail.example.com:4433/v1/mail`.
|
|
|
522
522
|
|
|
523
523
|
Set `arcane.config.json.mail.certPath` to the PEM certificate chain and
|
|
524
524
|
`mail.keyPath` to its PEM private-key file. These settings belong to the listener
|
|
525
|
-
and apply regardless of the selected provider profile. The
|
|
525
|
+
and apply regardless of the selected provider profile. The root
|
|
526
526
|
`MAIL_TLS_CERT_PATH` and `MAIL_TLS_KEY_PATH` fields in `.arcane.env.json` remain
|
|
527
527
|
fallbacks. Relative certificate paths resolve from the selected configuration
|
|
528
528
|
directory, including explicit programmatic path options; absolute paths are also accepted.
|
|
@@ -157,16 +157,17 @@ the same browser URLs and saved managed import maps.
|
|
|
157
157
|
|
|
158
158
|
Existing `physical-v1` configurations whose first source is `arcane` remain
|
|
159
159
|
supported. The explicit materializer below still refreshes those projections,
|
|
160
|
-
and
|
|
160
|
+
and an explicitly selected multi-app scaffold retains its physical layout. The
|
|
161
161
|
earlier installed routes with virtual `/arcane` destinations also remain
|
|
162
162
|
supported. Selecting `installed-v1`
|
|
163
163
|
does not delete any preexisting workspace files. The separate host-document
|
|
164
164
|
`generateDocumentImportMaps()` API continues to accept an already materialized
|
|
165
165
|
runtime; its input contract is unchanged.
|
|
166
166
|
|
|
167
|
-
###
|
|
167
|
+
### Standalone root application
|
|
168
168
|
|
|
169
|
-
`appsRoot: "."` selects
|
|
169
|
+
Each standalone app's root is its repository root. `appsRoot: "."` selects
|
|
170
|
+
that layout: `arcane-app.json`,
|
|
170
171
|
`arcane-package.json`, entry, and app-owned files occupy the workspace root.
|
|
171
172
|
The declared application ID remains unchanged. `appsRoot: "apps"` continues
|
|
172
173
|
to discover `apps/<id>` and supports the existing integrated and multi-app
|
|
@@ -174,29 +175,19 @@ layouts. Entries and include/exclude paths remain relative to the application.
|
|
|
174
175
|
|
|
175
176
|
Root HTML uses `<base href="./">`; nested navigable documents use their actual
|
|
176
177
|
depth back to the workspace. Managed bare imports remain the public interface;
|
|
177
|
-
their targets follow the selected npm routes. `arcane import-map`
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
restart an existing server after changing this workspace-level choice.
|
|
191
|
-
|
|
192
|
-
This option does not change the application ID, stored data, the existing
|
|
193
|
-
default installation ID or an authored `pwa.manifest.id`. It neither deletes
|
|
194
|
-
existing files nor removes authored resources from the app's include/exclude
|
|
195
|
-
selection. Explicitly included files under `apps/<id>/` still serve and package
|
|
196
|
-
as authored resources. Previously installed launch URLs and worker update URLs
|
|
197
|
-
need their old resources to remain available; retain the default compatibility
|
|
198
|
-
output when those URLs still need SDK support. See the
|
|
199
|
-
[root PWA compatibility boundary](pwa.md#root-only-generated-output).
|
|
178
|
+
their targets follow the selected npm routes. `arcane import-map` generates
|
|
179
|
+
enabled static PWA records at the app root, so normal static hosting needs no
|
|
180
|
+
SDK request handler or runtime copy. Managed refresh, development serving,
|
|
181
|
+
inspection, dry run and packaging add no nested `apps/<id>/` navigation pages,
|
|
182
|
+
redirects, or duplicate PWA worker/inventory files. There is no output-retention
|
|
183
|
+
switch for those retired records.
|
|
184
|
+
|
|
185
|
+
Application identity, stored data, the existing default installation ID and an
|
|
186
|
+
authored `pwa.manifest.id` remain unchanged. Authored resources continue to
|
|
187
|
+
follow the app's include/exclude selection; generation does not delete existing
|
|
188
|
+
files. Published SDK internals stay package-owned under `node_modules`.
|
|
189
|
+
Generated and offline app files are committed, and GitHub Actions consume
|
|
190
|
+
those committed files. See [root generated output](pwa.md#root-generated-output).
|
|
200
191
|
|
|
201
192
|
Direct-installed root maps expose `arcane-os/modules/<filename>` and
|
|
202
193
|
`arcane-os/entities/<filename>` (including extensions), plus the existing
|
package/docs/reference/pwa.md
CHANGED
|
@@ -38,8 +38,9 @@ comes from the selected application entry. The app owns names, icons, colors,
|
|
|
38
38
|
display preference, routes and descriptions. Use real app icons; the SDK does
|
|
39
39
|
not invent branding or claim that a browser has installed the app.
|
|
40
40
|
|
|
41
|
-
Manifest URL fields are relative to the application directory.
|
|
42
|
-
|
|
41
|
+
Manifest URL fields are relative to the application directory. Each standalone
|
|
42
|
+
app's root is its repository root. Explicit multi-app source and packaged
|
|
43
|
+
delivery retain the selected app tree at `apps/<id>/`. For multi-app packaged output,
|
|
43
44
|
the default `start_url` is `./apps/<id>/<entry>`, resolved from the generated
|
|
44
45
|
root manifest. The default `id` and `scope` remain `./`, preserving the existing
|
|
45
46
|
deployment-root installation identity. Authored relative URL fields, including
|
|
@@ -55,51 +56,30 @@ ordinary static host; it does not copy the installed runtime into `arcane/`.
|
|
|
55
56
|
The source root defaults its installation ID to `/apps/<id>/` to preserve the
|
|
56
57
|
previous source identity while its start URL and scope move to the root. An
|
|
57
58
|
authored `manifest.id` remains authoritative. Packaged default identity remains
|
|
58
|
-
`./`.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
`
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
updated files; fixture coverage does not establish a particular installed app's
|
|
72
|
-
actual browser lifecycle.
|
|
73
|
-
|
|
74
|
-
### Root-only generated output
|
|
75
|
-
|
|
76
|
-
For a root application, add `"legacyAppPaths": false` to the workspace's
|
|
77
|
-
`arcane-packager.json`, alongside `"appsRoot": "."`. The default is `true`.
|
|
78
|
-
This selects root-only SDK-generated navigation and PWA output in import-map
|
|
79
|
-
refresh, `arcane dev`, package inspection/dry run, and browser packaging:
|
|
80
|
-
|
|
81
|
-
- The four root PWA files remain generated normally.
|
|
82
|
-
- The SDK generates no `apps/<id>/` navigation pages, legacy worker or legacy
|
|
83
|
-
offline inventory, and adds no legacy navigation aliases or dev redirects.
|
|
59
|
+
`./`. App identity and saved application data remain unchanged.
|
|
60
|
+
|
|
61
|
+
### Root generated output
|
|
62
|
+
|
|
63
|
+
For a standalone application, `appsRoot: "."` places the app at its repository
|
|
64
|
+
root. Import-map refresh, `arcane dev`, package inspection/dry run and browser
|
|
65
|
+
packaging use that layout directly:
|
|
66
|
+
|
|
67
|
+
- The four root PWA files are generated normally when PWA is enabled.
|
|
68
|
+
- The SDK generates no `apps/<id>/` navigation pages, nested worker or duplicate
|
|
69
|
+
offline inventory, and adds no redirects for that path family.
|
|
70
|
+
- Runtime resources use the selected npm package paths; no repository-root
|
|
71
|
+
`arcane/` projection is generated.
|
|
84
72
|
- The default source installation ID remains `/apps/<id>/`, the default
|
|
85
73
|
packaged installation ID remains `./`, and an explicit `manifest.id` remains
|
|
86
|
-
authoritative. An ID
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
An installed app may still launch an old `/apps/<id>/` URL, and an existing
|
|
97
|
-
worker registration may still update its old script/inventory URL. Disabling
|
|
98
|
-
generation does not redirect those installed clients, unregister their worker,
|
|
99
|
-
clear caches or guarantee their next update. Keep compatibility output enabled
|
|
100
|
-
while old URLs still require SDK support, or supply the required resources
|
|
101
|
-
through the application's own declared files and hosting policy. This SDK
|
|
102
|
-
option alone makes no claim about any existing installation's adoption.
|
|
74
|
+
authoritative. An ID identifies the installation; it does not generate a
|
|
75
|
+
directory or a redirect.
|
|
76
|
+
- Authored resources follow their normal include/exclude selection. Generation
|
|
77
|
+
does not delete existing files, stored application data, or worker caches.
|
|
78
|
+
|
|
79
|
+
Generated and offline app files are committed. GitHub Actions consume those
|
|
80
|
+
committed files rather than generating them. Actual multi-app workspaces keep
|
|
81
|
+
their explicit `appsRoot: "apps"` layout. This source/package contract does not
|
|
82
|
+
establish any particular installed application's browser lifecycle.
|
|
103
83
|
|
|
104
84
|
### Offline resource selection
|
|
105
85
|
|
|
@@ -1057,14 +1057,13 @@ async function usevalidateAppConfig(...arguments_) {
|
|
|
1057
1057
|
|
|
1058
1058
|
Validates one root packager mapping and its fixed shared-route boundaries.
|
|
1059
1059
|
|
|
1060
|
-
The schema-1 `arcane-packager.json`
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
[standalone root applications](protocols.md#
|
|
1067
|
-
for configuration, retained-file and existing-installation behavior.
|
|
1060
|
+
The schema-1 `arcane-packager.json` uses `appsRoot: "."` for standalone apps:
|
|
1061
|
+
each app's root is its repository root. Managed-map refresh, development
|
|
1062
|
+
serving, inspection and packaging use root app files and direct npm package
|
|
1063
|
+
paths without generated nested app redirects or duplicate PWA files.
|
|
1064
|
+
Installation identity and authored file selection remain unchanged.
|
|
1065
|
+
Explicit `appsRoot: "apps"` supports real multi-app workspaces. See
|
|
1066
|
+
[standalone root applications](protocols.md#standalone-root-application).
|
|
1068
1067
|
|
|
1069
1068
|
### Signature and result
|
|
1070
1069
|
|
|
@@ -3730,7 +3729,7 @@ actual HTTPS port while preserving the original request path and query. Both lis
|
|
|
3730
3729
|
use the selected host.
|
|
3731
3730
|
|
|
3732
3731
|
Source servers default to HTTPS, including localhost; packaged browser previews
|
|
3733
|
-
require HTTPS. The
|
|
3732
|
+
require HTTPS. The existing `https` option is accepted; `https:false` and
|
|
3734
3733
|
`tls:false` alone do not disable HTTPS. HTTPS startup reads `.arcane/dev/server-cert.pem` and
|
|
3735
3734
|
`.arcane/dev/server-key.pem` relative to `workspaceRoot` unless explicit
|
|
3736
3735
|
`certPath` and `keyPath` are supplied together; relative paths resolve from the
|
|
@@ -4192,7 +4191,7 @@ The operation refreshes the selected authored descriptor's `arcane-package.json`
|
|
|
4192
4191
|
projection and managed import maps under one development-refresh lock, then
|
|
4193
4192
|
releases that lock before opening the selected source listener or listeners.
|
|
4194
4193
|
It returns their application endpoints and shared shutdown
|
|
4195
|
-
lifecycle.
|
|
4194
|
+
lifecycle. Package-only apps remain unchanged. The operation generates
|
|
4196
4195
|
no packaged output; enabled PWA manifests
|
|
4197
4196
|
are served directly from the selected source resources.
|
|
4198
4197
|
|
|
@@ -489,7 +489,7 @@ files. Their explicit precedence and credential compatibility rules are in the
|
|
|
489
489
|
| CLI's early default host, port, and send/serve profile values | Y/Y/Y | Remove premature default assignment. The same defaults remain after file configuration resolves, while explicit CLI values retain priority. Assigning defaults before reading JSON would conceal the deployment's chosen settings. |
|
|
490
490
|
| `origins` string array and list replacement | Y/Y/N | Keep one exact list for multiple caller domains. Explicit options replace the file list, including empty arrays. Existing `origin`, `allowTo`, `errorTo`, and `requestTimeout` aliases remain compatible and win over their canonical programmatic names when both are supplied. No normalization or list-merging helper is needed. |
|
|
491
491
|
| Root provider-key, named-profile, and TLS compatibility | Y/Y/N | Preserve supported inputs using `RESEND_API_KEY`, `MAIL_PROFILES`, `MAIL_TLS_CERT_PATH`, and `MAIL_TLS_KEY_PATH`. Nested selected key presence takes priority even when null or empty. Named profiles never select a different account's default key. Named-profile deployment use is unverified; the retained behavior is a compatibility contract. |
|
|
492
|
-
| Key set/status/delete operations | Y/Y/N | Preserve credential management and secret-free status. New keys use the nested member; existing
|
|
492
|
+
| Key set/status/delete operations | Y/Y/N | Preserve credential management and secret-free status. New keys use the nested member; existing root or profile keys are updated in place unless a nested key exists. Delete removes both selected representations to prevent an old credential reappearing, preserving other keys, settings, and profile containers. Key commands still default to `mail` independently of the serving profile. |
|
|
493
493
|
| Send configuration | Y/Y/N | Use the same profile, sender, provider-timeout, and retry-guidance settings for direct sending. A send needs provider authority and its report, so listener certificates remain a serve-only prerequisite. Reports and idempotency keys remain per-operation inputs. |
|
|
494
494
|
| Explicit runtime dependencies and callbacks | Y/Y/N | Preserve `readCredential`, provider injection, observation, cancellation, and subscription callbacks as programmatic inputs. A function or signal is not JSON configuration. The portable browser mail import retains its existing dependency boundary. |
|
|
495
495
|
| Shared sender omission | Y/Y/N | Preserve per-report `from` and provider-template sender selection when no shared override is configured. One listener can therefore serve independent application sender identities. |
|
package/package.json
CHANGED
package/src/app-layout.mjs
CHANGED
|
@@ -5,12 +5,6 @@ export function appRelativeRoot(config, appId) {
|
|
|
5
5
|
return config.appsRoot === '.' ? '' : `apps/${appId}`;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
// Generated compatibility URLs are separate from installation and storage identity.
|
|
9
|
-
export function rootAppLegacyPath(config, appId) {
|
|
10
|
-
return config.appsRoot === '.' && config.legacyAppPaths !== false
|
|
11
|
-
? `apps/${appId}` : undefined;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
8
|
export function resolveAppRoot(workspaceRoot, config, appId) {
|
|
15
9
|
return path.resolve(workspaceRoot, appRelativeRoot(config, appId));
|
|
16
10
|
}
|
|
@@ -20,19 +14,3 @@ export function appBaseHref(workspaceRoot, appRoot, document = 'index.html') {
|
|
|
20
14
|
const relative = path.relative(directory, workspaceRoot).split(path.sep).join('/');
|
|
21
15
|
return relative ? `${relative}/` : './';
|
|
22
16
|
}
|
|
23
|
-
|
|
24
|
-
export function rootAppNavigation(appId, entry, documents = []) {
|
|
25
|
-
const redirects=new Map();
|
|
26
|
-
for(const document of documents)redirects.set(`apps/${appId}/${document}`,document);
|
|
27
|
-
redirects.set(`apps/${appId}/index.html`,entry);
|
|
28
|
-
return [...redirects].map(([file,target])=>{
|
|
29
|
-
const relative=path.posix.relative(path.posix.dirname(file),target);
|
|
30
|
-
const destination=JSON.stringify(relative).replaceAll('<','\\u003c');
|
|
31
|
-
return {
|
|
32
|
-
path:file,target:`/${target}`,
|
|
33
|
-
content:'<!doctype html>\n<!-- Arcane root application navigation -->\n'
|
|
34
|
-
+'<meta charset="utf-8">\n<title>Opening application</title>\n'
|
|
35
|
-
+`<script>const target=new URL(${destination},location.href);target.search=location.search;target.hash=location.hash;location.replace(target.href);</script>\n`
|
|
36
|
-
};
|
|
37
|
-
});
|
|
38
|
-
}
|
package/src/cli/main.mjs
CHANGED
|
@@ -506,7 +506,7 @@ function operationOptions(command,parsed,cwd){
|
|
|
506
506
|
return {
|
|
507
507
|
targetPath:path.resolve(cwd,values.path??appId),
|
|
508
508
|
appId,
|
|
509
|
-
appsRoot:values['apps-root']??'
|
|
509
|
+
appsRoot:values['apps-root']??'.',
|
|
510
510
|
displayName:values['display-name'],
|
|
511
511
|
target:values.target??'browser',
|
|
512
512
|
initializeGit:flags.has('git')
|
package/src/dev-server.mjs
CHANGED
|
@@ -6,7 +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 {appRelativeRoot
|
|
9
|
+
import {appRelativeRoot} from './app-layout.mjs';
|
|
10
10
|
import {readInstalledSdkLayout} from './sdk-runtime-layout.mjs';
|
|
11
11
|
import {APP_DESCRIPTOR_NAME, projectPackageManifest} from './app-descriptor.mjs';
|
|
12
12
|
import {APP_CONFIG_NAME, validateAppConfig} from './packager/core.mjs';
|
|
@@ -1047,26 +1047,8 @@ async function startOwnedDevServer({
|
|
|
1047
1047
|
const pwaResourceUrls = new Set();
|
|
1048
1048
|
function developmentPwaArtifacts(selectedRoutes, assets = [], version = assetVersion) {
|
|
1049
1049
|
const rootApp = selectedRoutes.config.appsRoot === '.';
|
|
1050
|
-
const legacyAppPath = rootAppLegacyPath(selectedRoutes.config, selectedRoutes.appId);
|
|
1051
1050
|
const appBase = applicationSourcePath(selectedRoutes.config,selectedRoutes.appId);
|
|
1052
1051
|
const navigationAliases = {'/': selectedRoutes.startPath};
|
|
1053
|
-
if (legacyAppPath) {
|
|
1054
|
-
const legacyBase = `/${legacyAppPath}`;
|
|
1055
|
-
navigationAliases[legacyBase] = selectedRoutes.startPath;
|
|
1056
|
-
navigationAliases[`${legacyBase}/`] = selectedRoutes.startPath;
|
|
1057
|
-
for (const asset of [selectedRoutes.startPath,...assets]) {
|
|
1058
|
-
const pathname = new URL(asset,'http://arcane.invalid').pathname;
|
|
1059
|
-
if (!/\.html?$/iu.test(pathname)) continue;
|
|
1060
|
-
const segments = decodeURIComponent(pathname).split('/').filter(Boolean);
|
|
1061
|
-
const mapping = selectedRoutes.mappings.find(function matchingNavigationRoute(route) {
|
|
1062
|
-
return route.prefix.every(function matchingNavigationSegment(segment,index) {
|
|
1063
|
-
return segments[index] === segment;
|
|
1064
|
-
});
|
|
1065
|
-
});
|
|
1066
|
-
if (mapping?.kind === 'app') navigationAliases[`${legacyBase}${pathname}`] = pathname;
|
|
1067
|
-
}
|
|
1068
|
-
navigationAliases[`${legacyBase}/index.html`] = selectedRoutes.startPath;
|
|
1069
|
-
}
|
|
1070
1052
|
return createPwaArtifacts(
|
|
1071
1053
|
{
|
|
1072
1054
|
app: {
|
|
@@ -1085,7 +1067,6 @@ async function startOwnedDevServer({
|
|
|
1085
1067
|
...(rootApp ? {
|
|
1086
1068
|
installationId:`/apps/${routeSet.appId}/`
|
|
1087
1069
|
} : {}),
|
|
1088
|
-
legacyAppPath,
|
|
1089
1070
|
runtimeBase: selectedRoutes.browserRuntimeBase
|
|
1090
1071
|
?? selectedRoutes.installed?.browserRuntimeBase ?? '/arcane/sdk/',
|
|
1091
1072
|
mode: 'development'
|
|
@@ -1177,34 +1158,17 @@ async function startOwnedDevServer({
|
|
|
1177
1158
|
const target=parseRequestTarget(request.url);
|
|
1178
1159
|
if(!target){deny(response,400,'Invalid request path.');return;}
|
|
1179
1160
|
const {segments}=target;
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
const legacyPwaRequest = legacyAppRequest && segments.length === 3
|
|
1183
|
-
&& ['arcane-sw.js', 'arcane-offline.json'].includes(segments[2]);
|
|
1184
|
-
const generatedPwaPath = legacyPwaRequest
|
|
1185
|
-
|| ['/arcane.webmanifest', '/arcane-offline.json', '/arcane-sw.js', '/arcane-pwa.mjs']
|
|
1186
|
-
.includes(target.path);
|
|
1161
|
+
const generatedPwaPath = ['/arcane.webmanifest', '/arcane-offline.json', '/arcane-sw.js', '/arcane-pwa.mjs']
|
|
1162
|
+
.includes(target.path);
|
|
1187
1163
|
const requestedMapping = currentSourceRoutes.mappings.find(function currentRequestMapping(route) {
|
|
1188
1164
|
return route.prefix.every(function currentRequestSegment(segment,index) {
|
|
1189
1165
|
return segments[index] === segment;
|
|
1190
1166
|
});
|
|
1191
1167
|
});
|
|
1192
|
-
const appRequest = requestedMapping?.kind === 'app'
|
|
1168
|
+
const appRequest = requestedMapping?.kind === 'app';
|
|
1193
1169
|
const selectedRoutes = mode === 'source' && (appRequest || segments.length === 0 || generatedPwaPath)
|
|
1194
1170
|
? await refreshSourceRoutes() : currentSourceRoutes;
|
|
1195
1171
|
const pwaEnabled = mode === 'source' ? selectedRoutes.app?.pwa?.enabled === true : routeSet.pwa;
|
|
1196
|
-
const authoredLegacyResource = legacyAppRequest
|
|
1197
|
-
&& sourcePathAllowed(segments, selectedRoutes.app);
|
|
1198
|
-
if (legacyAppRequest
|
|
1199
|
-
&& !(pwaEnabled && legacyPwaRequest)
|
|
1200
|
-
&& !authoredLegacyResource) {
|
|
1201
|
-
const legacyPath = target.pathname.slice(`/apps/${routeSet.appId}`.length);
|
|
1202
|
-
const location = !legacyPath || legacyPath === '/' || legacyPath === '/index.html'
|
|
1203
|
-
? selectedRoutes.startPath : legacyPath;
|
|
1204
|
-
response.writeHead(302,{location:`${location}${target.search}`});
|
|
1205
|
-
response.end();
|
|
1206
|
-
return;
|
|
1207
|
-
}
|
|
1208
1172
|
if (mode === 'source' && pwaEnabled
|
|
1209
1173
|
&& generatedPwaPath) {
|
|
1210
1174
|
const generated = await sourcePwaArtifact(target.path, selectedRoutes);
|
package/src/mail-credentials.mjs
CHANGED
|
@@ -81,9 +81,9 @@ function mailCredentialEntry(settings, location) {
|
|
|
81
81
|
: `mail.profiles[${JSON.stringify(location.profile)}].apiKey`
|
|
82
82
|
};
|
|
83
83
|
if (nestedSettings && Object.hasOwn(nestedSettings, 'apiKey')) return nestedEntry;
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
return {profileSettings
|
|
84
|
+
const profileSettings = mailProfileSettings(settings, location);
|
|
85
|
+
if (profileSettings && Object.hasOwn(profileSettings, 'RESEND_API_KEY')) {
|
|
86
|
+
return {profileSettings, key: 'RESEND_API_KEY', setting: location.setting};
|
|
87
87
|
}
|
|
88
88
|
return nestedEntry;
|
|
89
89
|
}
|
package/src/packager/core.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from 'node:fs/promises';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import {pathToFileURL} from 'node:url';
|
|
15
|
-
import {appRelativeRoot,resolveAppRoot
|
|
15
|
+
import {appRelativeRoot,resolveAppRoot} from '../app-layout.mjs';
|
|
16
16
|
import {readInstalledSdkLayout} from '../sdk-runtime-layout.mjs';
|
|
17
17
|
import {withWorkspaceOperationLock} from '../workspace-operation-lock.mjs';
|
|
18
18
|
import {
|
|
@@ -275,14 +275,11 @@ function validateSharedRoute(route,label){
|
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
export function validateRootConfig(value,configPath=ROOT_CONFIG_NAME){
|
|
278
|
-
assertOnlyKeys(value,new Set(['schemaVersion','appsRoot','distRoot','sharedPayloads'
|
|
278
|
+
assertOnlyKeys(value,new Set(['schemaVersion','appsRoot','distRoot','sharedPayloads']),ROOT_CONFIG_NAME);
|
|
279
279
|
if(value.schemaVersion!==1)fail(`${ROOT_CONFIG_NAME}.schemaVersion must be 1.`);
|
|
280
280
|
if(!['apps','.'].includes(value.appsRoot)||value.distRoot!=='dist'){
|
|
281
281
|
fail(`${ROOT_CONFIG_NAME} must bind appsRoot to "apps" or "." and distRoot to "dist".`);
|
|
282
282
|
}
|
|
283
|
-
if (value.legacyAppPaths !== undefined && !is.boolean(value.legacyAppPaths)) {
|
|
284
|
-
fail(`${ROOT_CONFIG_NAME}.legacyAppPaths must be a boolean.`);
|
|
285
|
-
}
|
|
286
283
|
if(!isPlainObject(value.sharedPayloads)){
|
|
287
284
|
fail(`${ROOT_CONFIG_NAME}.sharedPayloads must be an object.`);
|
|
288
285
|
}
|
|
@@ -297,8 +294,7 @@ export function validateRootConfig(value,configPath=ROOT_CONFIG_NAME){
|
|
|
297
294
|
);
|
|
298
295
|
}
|
|
299
296
|
return {
|
|
300
|
-
schemaVersion:1,appsRoot:value.appsRoot,distRoot:'dist',sharedPayloads,configPath
|
|
301
|
-
legacyAppPaths:value.legacyAppPaths ?? true
|
|
297
|
+
schemaVersion:1,appsRoot:value.appsRoot,distRoot:'dist',sharedPayloads,configPath
|
|
302
298
|
};
|
|
303
299
|
}
|
|
304
300
|
|
|
@@ -581,15 +577,6 @@ async function optionalDescriptor(context){
|
|
|
581
577
|
async function inspectContext(context,{signal}={}){
|
|
582
578
|
const records=await collectPackageRecords(context,{signal});
|
|
583
579
|
const documents=await browserDocuments(records,context.config.entry);
|
|
584
|
-
const navigation=rootAppLegacyPath(context.rootConfig,context.appId)?rootAppNavigation(
|
|
585
|
-
context.appId,context.config.entry,documents.map(document=>document.path)
|
|
586
|
-
):[];
|
|
587
|
-
for(const redirect of navigation){
|
|
588
|
-
const selected=records.find(record=>pathKey(record.destination)===pathKey(redirect.path));
|
|
589
|
-
if(selected&&!(await readFile(selected.source,'utf8')).includes('<!-- Arcane root application navigation -->')){
|
|
590
|
-
fail(`Root application navigation would replace selected content: ${redirect.path}.`);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
580
|
return {
|
|
594
581
|
appId:context.appId,
|
|
595
582
|
displayName:context.config.displayName,
|
|
@@ -607,7 +594,7 @@ async function inspectContext(context,{signal}={}){
|
|
|
607
594
|
...(context.config.adapter===undefined?{}:{adapter:context.config.adapter}),
|
|
608
595
|
descriptor:await optionalDescriptor(context),
|
|
609
596
|
browserDocuments:documents,
|
|
610
|
-
files:[...new Set(['index.html',...records.map(record=>record.destination)
|
|
597
|
+
files:[...new Set(['index.html',...records.map(record=>record.destination)])].sort(compareText),
|
|
611
598
|
output:path.relative(context.workspaceRoot,context.outputRoot).split(path.sep).join('/')
|
|
612
599
|
};
|
|
613
600
|
}
|
|
@@ -763,13 +750,9 @@ async function replaceDirectory(stagingRoot,outputRoot){
|
|
|
763
750
|
async function packageWithContext(context,options={}){
|
|
764
751
|
const {signal,onEvent,browserPwa=true}=options;
|
|
765
752
|
const pwaEnabled=browserPwa&&context.config.pwa?.enabled===true;
|
|
766
|
-
const legacyAppPath=rootAppLegacyPath(context.rootConfig,context.appId);
|
|
767
753
|
const appPath=appRelativeRoot(context.rootConfig,context.appId);
|
|
768
754
|
const entryPath=appPackagePath(context,context.config.entry);
|
|
769
755
|
const inspected=await inspectContext(context,{signal});
|
|
770
|
-
const navigation=legacyAppPath?rootAppNavigation(
|
|
771
|
-
context.appId,context.config.entry,inspected.browserDocuments.map(document=>document.path)
|
|
772
|
-
):[];
|
|
773
756
|
if(options.dryRun){
|
|
774
757
|
return {
|
|
775
758
|
appId:context.appId,
|
|
@@ -782,11 +765,7 @@ async function packageWithContext(context,options={}){
|
|
|
782
765
|
PWA_MANIFEST_NAME,
|
|
783
766
|
PWA_OFFLINE_MANIFEST_NAME,
|
|
784
767
|
PWA_WORKER_NAME,
|
|
785
|
-
PWA_BOOTSTRAP_NAME
|
|
786
|
-
...(legacyAppPath?[
|
|
787
|
-
`${legacyAppPath}/${PWA_WORKER_NAME}`,
|
|
788
|
-
`${legacyAppPath}/${PWA_OFFLINE_MANIFEST_NAME}`
|
|
789
|
-
]:[])
|
|
768
|
+
PWA_BOOTSTRAP_NAME
|
|
790
769
|
]:[])
|
|
791
770
|
].sort(compareText)
|
|
792
771
|
};
|
|
@@ -824,18 +803,6 @@ async function packageWithContext(context,options={}){
|
|
|
824
803
|
}else{
|
|
825
804
|
await copyBase();
|
|
826
805
|
}
|
|
827
|
-
for(const redirect of navigation){
|
|
828
|
-
throwIfAborted(signal);
|
|
829
|
-
const filePath=path.join(stagingRoot,...redirect.path.split('/'));
|
|
830
|
-
try{
|
|
831
|
-
const current=await readFile(filePath,'utf8');
|
|
832
|
-
if(!current.includes('<!-- Arcane root application navigation -->')){
|
|
833
|
-
fail(`Root application navigation would replace package content: ${redirect.path}.`);
|
|
834
|
-
}
|
|
835
|
-
}catch(error){if(error.code!=='ENOENT')throw error;}
|
|
836
|
-
await mkdir(path.dirname(filePath),{recursive:true});
|
|
837
|
-
await writeFile(filePath,redirect.content,'utf8');
|
|
838
|
-
}
|
|
839
806
|
const files=await listOutputFiles(stagingRoot,{signal});
|
|
840
807
|
// Traverse actual browser resources after the adapter finishes. Files
|
|
841
808
|
// included only as application documents retain their original content.
|
|
@@ -957,14 +924,6 @@ async function packageWithContext(context,options={}){
|
|
|
957
924
|
fail(`Package output is missing its entry file: ${context.config.entry}.`);
|
|
958
925
|
}
|
|
959
926
|
const installed=pwaEnabled?await readInstalledSdkLayout(context.workspaceRoot,context.rootConfig):null;
|
|
960
|
-
const navigationAliases=navigation.length?{
|
|
961
|
-
'./':packageResourceUrl(entryPath),
|
|
962
|
-
[`./apps/${context.appId}`]:packageResourceUrl(entryPath),
|
|
963
|
-
[`./apps/${context.appId}/`]:packageResourceUrl(entryPath),
|
|
964
|
-
...Object.fromEntries(navigation.map(redirect=>[
|
|
965
|
-
packageResourceUrl(redirect.path),packageResourceUrl(redirect.target.slice(1))
|
|
966
|
-
]))
|
|
967
|
-
}:undefined;
|
|
968
927
|
const pwaArtifacts=pwaEnabled?createPwaArtifacts({
|
|
969
928
|
app:{
|
|
970
929
|
id:context.appId,
|
|
@@ -973,9 +932,7 @@ async function packageWithContext(context,options={}){
|
|
|
973
932
|
entry:packageResourceUrl(entryPath)
|
|
974
933
|
},
|
|
975
934
|
appPath,
|
|
976
|
-
...(legacyAppPath?{legacyAppPath}:{}),
|
|
977
935
|
...(installed?.direct?{runtimeBase:`.${installed.browserRuntimeBase}`} : {}),
|
|
978
|
-
...(navigationAliases?{navigationAliases}:{}),
|
|
979
936
|
sdkVersion:assetVersion,
|
|
980
937
|
pwa:context.config.pwa,
|
|
981
938
|
files,
|
package/src/pwa.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Is from 'strong-type';
|
|
2
2
|
import {randomUUID} from 'node:crypto';
|
|
3
|
-
import path from 'node:path';
|
|
4
3
|
import {createPwaWorkerScript} from './pwa-worker.mjs';
|
|
5
4
|
import {versionAssetUrl} from './import-map.mjs';
|
|
6
5
|
|
|
@@ -204,7 +203,6 @@ export function createPwaArtifacts(
|
|
|
204
203
|
runtimeBase = './arcane/sdk/',
|
|
205
204
|
appBase,
|
|
206
205
|
installationId,
|
|
207
|
-
legacyAppPath,
|
|
208
206
|
appPath = '',
|
|
209
207
|
navigationAliases,
|
|
210
208
|
revision
|
|
@@ -316,47 +314,6 @@ controller.ready.catch(
|
|
|
316
314
|
},
|
|
317
315
|
{path: PWA_BOOTSTRAP_NAME, content: bootstrap}
|
|
318
316
|
];
|
|
319
|
-
if (legacyAppPath) {
|
|
320
|
-
const directory = legacyAppPath.endsWith('/') ? legacyAppPath : `${legacyAppPath}/`;
|
|
321
|
-
const priorDirectory = new URL(resourceUrl('./', directory), 'https://arcane.invalid/').pathname;
|
|
322
|
-
// Existing registrations continue updating their own script and inventory URLs.
|
|
323
|
-
function priorScopeUrl(value) {
|
|
324
|
-
if (value.startsWith('/') || /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(value)) return value;
|
|
325
|
-
const resolved = new URL(value, 'https://arcane.invalid/');
|
|
326
|
-
let relative = path.posix.relative(priorDirectory, resolved.pathname);
|
|
327
|
-
if (!relative) {
|
|
328
|
-
relative = resolved.pathname.endsWith('/')
|
|
329
|
-
? './'
|
|
330
|
-
: `../${path.posix.basename(resolved.pathname)}`;
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
if (!relative.startsWith('.')) relative = `./${relative}`;
|
|
334
|
-
if (resolved.pathname.endsWith('/') && !relative.endsWith('/')) relative += '/';
|
|
335
|
-
}
|
|
336
|
-
return `${relative}${resolved.search}${resolved.hash}`;
|
|
337
|
-
}
|
|
338
|
-
const legacyOfflineManifest = {
|
|
339
|
-
...offlineManifest,
|
|
340
|
-
assets: [...new Set([
|
|
341
|
-
...offlineManifest.assets.map(priorScopeUrl),
|
|
342
|
-
`./${PWA_OFFLINE_MANIFEST_NAME}`
|
|
343
|
-
])],
|
|
344
|
-
navigationAliases: Object.fromEntries(
|
|
345
|
-
Object.entries(offlineManifest.navigationAliases).map(
|
|
346
|
-
function priorScopeNavigation([alias, target]) {
|
|
347
|
-
return [priorScopeUrl(alias), priorScopeUrl(target)];
|
|
348
|
-
}
|
|
349
|
-
)
|
|
350
|
-
)
|
|
351
|
-
};
|
|
352
|
-
generatedFiles.push(
|
|
353
|
-
{
|
|
354
|
-
path: `${directory}${PWA_WORKER_NAME}`,
|
|
355
|
-
content: createPwaWorkerScript(legacyOfflineManifest, priorScopeUrl(`${runtimeBase}pwa.mjs`))
|
|
356
|
-
},
|
|
357
|
-
{path: `${directory}${PWA_OFFLINE_MANIFEST_NAME}`, content: json(legacyOfflineManifest)}
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
317
|
return {
|
|
361
318
|
manifest,
|
|
362
319
|
offlineManifest,
|
package/src/scaffold.mjs
CHANGED
|
@@ -316,7 +316,7 @@ async function runGitInit(workspaceRoot,signal,onEvent){
|
|
|
316
316
|
export async function createWorkspace({
|
|
317
317
|
targetPath,
|
|
318
318
|
appId,
|
|
319
|
-
appsRoot='
|
|
319
|
+
appsRoot='.',
|
|
320
320
|
displayName,
|
|
321
321
|
target='browser',
|
|
322
322
|
initializeGit=false,
|
|
@@ -400,7 +400,7 @@ export async function initWorkspace({
|
|
|
400
400
|
},async workspaceOperationLease=>{
|
|
401
401
|
const profile=await existingWorkspaceProfile(resolvedRoot);
|
|
402
402
|
const workspaceMode=profile?.workspaceMode??'external';
|
|
403
|
-
const selectedAppsRoot=appsRoot??profile?.config.appsRoot??'
|
|
403
|
+
const selectedAppsRoot=appsRoot??profile?.config.appsRoot??'.';
|
|
404
404
|
if(!['apps','.'].includes(selectedAppsRoot))fail('appsRoot must be apps or .','ARCANE_USAGE');
|
|
405
405
|
if(profile&&selectedAppsRoot!==profile.config.appsRoot){
|
|
406
406
|
fail('appsRoot must match the existing workspace layout; init does not relocate an application.','ARCANE_USAGE');
|
|
@@ -439,6 +439,7 @@ export async function initWorkspace({
|
|
|
439
439
|
const template=workspaceMode==='integrated'
|
|
440
440
|
?workspaceTemplate({
|
|
441
441
|
appId,
|
|
442
|
+
appsRoot:selectedAppsRoot,
|
|
442
443
|
displayName,
|
|
443
444
|
appOnly:true,
|
|
444
445
|
namedImports:true,
|
|
@@ -60,12 +60,12 @@ export function createWorkspaceLockDocument({
|
|
|
60
60
|
|
|
61
61
|
export function workspaceTemplate({
|
|
62
62
|
appId,
|
|
63
|
-
|
|
63
|
+
appOnly=false,
|
|
64
|
+
appsRoot=appOnly?'apps':'.',
|
|
64
65
|
displayName,
|
|
65
66
|
sdkDependencyName=SDK_NAME,
|
|
66
67
|
sdkDependencySpecifier=SDK_VERSION,
|
|
67
68
|
sdkPackageSource=`node_modules/${sdkDependencyName}`,
|
|
68
|
-
appOnly=false,
|
|
69
69
|
namedImports=true,
|
|
70
70
|
minimumCoreVersion='0.8.12',
|
|
71
71
|
target='browser',
|
|
@@ -199,6 +199,7 @@ import map in every directly navigable descriptor-admitted \`.html\`/\`.htm\`
|
|
|
199
199
|
document. HTML component fragments remain package files but do not receive a
|
|
200
200
|
document-level base or managed import map.
|
|
201
201
|
Development, package, and build refresh that shared inventory when the selected operation needs it.
|
|
202
|
+
Commit generated import maps and enabled offline app files. Hosting workflows consume those committed files rather than generating them.
|
|
202
203
|
Named \`${directRuntime?'arcane-os/modules/* and arcane-os/entities/*':'arcane/*'}\` imports resolve through the managed map to the selected SDK files. Packaging copies the complete selected application, runtime, and specifier
|
|
203
204
|
map to \`dist/${appId}\` without running application tests. Run \`verify\` only when
|
|
204
205
|
the user explicitly selects verification or a release artifact that requires it;
|
package/src/toolchain.mjs
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
import {loadArcaneIntegratedProvider} from './integrated-provider-loader.mjs';
|
|
13
13
|
import {startDevServer} from './dev-server.mjs';
|
|
14
14
|
import {applyPwaEntryReferences,generateImportMap,readApplicationTestImportMapContext} from './import-map.mjs';
|
|
15
|
-
import {rootAppLegacyPath,rootAppNavigation} from './app-layout.mjs';
|
|
16
15
|
import {createPwaArtifacts} from './pwa.mjs';
|
|
17
16
|
import {readInstalledSdkLayout} from './sdk-runtime-layout.mjs';
|
|
18
17
|
import {withWorkspaceOperationLock} from './workspace-operation-lock.mjs';
|
|
@@ -327,48 +326,15 @@ async function refreshPreparedImportMap(prepared,{signal,onEvent,workspaceOperat
|
|
|
327
326
|
async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,onEvent}){
|
|
328
327
|
const {workspaceRoot,appId}=prepared;
|
|
329
328
|
const manifest=prepared.validation.app.manifest;
|
|
330
|
-
const legacyAppPath = rootAppLegacyPath(prepared.validation.config, appId);
|
|
331
|
-
const navigation = legacyAppPath ? rootAppNavigation(
|
|
332
|
-
appId,
|
|
333
|
-
manifest.entry,
|
|
334
|
-
inspected.browserDocuments.map(
|
|
335
|
-
function rootBrowserDocumentPath(document) {
|
|
336
|
-
return document.path;
|
|
337
|
-
}
|
|
338
|
-
)
|
|
339
|
-
) : [];
|
|
340
|
-
// These aliases belong to the SDK only after generation; retained app files stay authored.
|
|
341
|
-
for(const redirect of navigation){
|
|
342
|
-
throwIfAborted(signal);
|
|
343
|
-
try{
|
|
344
|
-
const current=await readFile(path.join(workspaceRoot,...redirect.path.split('/')),'utf8');
|
|
345
|
-
if(!current.includes('<!-- Arcane root application navigation -->')){
|
|
346
|
-
throw new ArcaneError(ERROR_CODES.workspaceInvalid,
|
|
347
|
-
`Root application navigation would replace authored content: ${redirect.path}.`);
|
|
348
|
-
}
|
|
349
|
-
}catch(error){if(error.code!=='ENOENT')throw error;}
|
|
350
|
-
}
|
|
351
329
|
const installed=await readInstalledSdkLayout(workspaceRoot,prepared.validation.config);
|
|
352
330
|
const entry=`/${manifest.entry.split('/').map(encodeURIComponent).join('/')}`;
|
|
353
331
|
const navigationAliases={
|
|
354
|
-
'/':entry
|
|
355
|
-
...(legacyAppPath ? {
|
|
356
|
-
[`/${legacyAppPath}`]:entry,
|
|
357
|
-
[`/${legacyAppPath}/`]:entry,
|
|
358
|
-
...Object.fromEntries(
|
|
359
|
-
navigation.map(
|
|
360
|
-
function rootNavigationAlias(redirect) {
|
|
361
|
-
return [`/${redirect.path}`,redirect.target];
|
|
362
|
-
}
|
|
363
|
-
)
|
|
364
|
-
)
|
|
365
|
-
} : {})
|
|
332
|
+
'/':entry
|
|
366
333
|
};
|
|
367
334
|
// The source host serves the installed files in place. There is no runtime projection.
|
|
368
335
|
const files=[...new Set([
|
|
369
336
|
...inspected.files.filter(file=>file!=='index.html'||manifest.include.includes('index.html')),
|
|
370
|
-
importMap.artifactRelativePath
|
|
371
|
-
...navigation.map(redirect=>redirect.path)
|
|
337
|
+
importMap.artifactRelativePath
|
|
372
338
|
])];
|
|
373
339
|
const pwa=installed?.direct&&manifest.pwa?.enabled?createPwaArtifacts({
|
|
374
340
|
app:{id:appId,displayName:manifest.displayName,version:manifest.version,entry},
|
|
@@ -378,12 +344,11 @@ async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,
|
|
|
378
344
|
basePath:'/',
|
|
379
345
|
appBase:'/',
|
|
380
346
|
installationId:`/apps/${appId}/`,
|
|
381
|
-
legacyAppPath,
|
|
382
347
|
runtimeBase:installed.browserRuntimeBase,
|
|
383
348
|
mode:'development',
|
|
384
349
|
navigationAliases
|
|
385
350
|
}):null;
|
|
386
|
-
for(const file of
|
|
351
|
+
for(const file of pwa?.files??[]){
|
|
387
352
|
throwIfAborted(signal);
|
|
388
353
|
const filePath=path.join(workspaceRoot,...file.path.split('/'));
|
|
389
354
|
await mkdir(path.dirname(filePath),{recursive:true});
|
|
@@ -402,7 +367,7 @@ async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,
|
|
|
402
367
|
}
|
|
403
368
|
await emit(onEvent,{
|
|
404
369
|
type:'import-map.root-files.completed',appId,
|
|
405
|
-
navigation:
|
|
370
|
+
navigation:[],
|
|
406
371
|
pwa:pwa?.files.map(file=>file.path)??[]
|
|
407
372
|
});
|
|
408
373
|
}
|