arcane-os 0.51.2 → 0.52.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 +15 -0
- package/README.md +1 -1
- package/docs/reference/cli.md +24 -18
- package/docs/reference/protocols.md +48 -10
- package/docs/reference/pwa.md +30 -7
- package/docs/reference/sdk-api.md +24 -9
- package/package.json +1 -1
- package/schemas/arcane-app.schema.json +5 -0
- package/schemas/arcane-package.schema.json +10 -0
- package/src/app-descriptor.mjs +6 -1
- package/src/dev-server.mjs +140 -22
- package/src/packager/core.mjs +43 -6
- package/src/templates/workspace-template.mjs +14 -4
- package/src/toolchain.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.52.0
|
|
4
|
+
|
|
5
|
+
- Add optional `package.documents` to select managed application HTML separately
|
|
6
|
+
from files retained by `include` and `exclude`. The schema-1 projection uses
|
|
7
|
+
top-level `documents`. The entry is always first, `[]` selects the entry only,
|
|
8
|
+
and omission preserves automatic discovery. Secondary paths retain existing
|
|
9
|
+
deterministic ordering while the authored declaration remains unchanged.
|
|
10
|
+
- Use that selection across ordinary inspection, import-map generation,
|
|
11
|
+
development serving, packaging and PWA document processing. Explicitly
|
|
12
|
+
unselected HTML originals retain their content, including iframe-referenced
|
|
13
|
+
documents, while remaining included, served, packaged and offline-eligible
|
|
14
|
+
under the existing resource selection. HTML-import components and shared
|
|
15
|
+
runtime resources retain their existing processing. No dependency, migration,
|
|
16
|
+
default selection change or extra CLI flag is introduced.
|
|
17
|
+
|
|
3
18
|
## 0.51.2
|
|
4
19
|
|
|
5
20
|
- Forward caller-selected `temperature` through the focused TWiN Cloud
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
|
|
|
20
20
|
share the theme, packaging, event, cancellation,
|
|
21
21
|
and browser run contracts while retaining their selected application layout.
|
|
22
22
|
|
|
23
|
-
This checkout defines the `0.
|
|
23
|
+
This checkout defines the `0.52.0` SDK contract. Applications pin one exact npm
|
|
24
24
|
version and lockfile; registry state is deliberately not baked into application
|
|
25
25
|
artifacts.
|
|
26
26
|
|
package/docs/reference/cli.md
CHANGED
|
@@ -197,19 +197,24 @@ npm exec -- arcane doctor --workspace . --arcane-root "../Arcane OS"
|
|
|
197
197
|
|
|
198
198
|
### Overview
|
|
199
199
|
|
|
200
|
-
Refreshes one selected application's browser runtime map, generates
|
|
201
|
-
|
|
202
|
-
`.html`/`.htm`
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
200
|
+
Refreshes one selected application's browser runtime map, generates its standard
|
|
201
|
+
browser import map, and prepares the map artifact plus the selected managed
|
|
202
|
+
`.html`/`.htm` documents before writing them in order. Optional
|
|
203
|
+
`package.documents` in `arcane-app.json` selects literal app-root-relative HTML
|
|
204
|
+
paths that are already included and not excluded. The entry is always first;
|
|
205
|
+
`[]` selects only the entry. Omitting the field retains automatic selection of
|
|
206
|
+
the entry and included secondary pages with an active `<base>`. An app identity
|
|
207
|
+
meta tag does not select a page. The renderer requires one path-correct active
|
|
208
|
+
base for each selected document.
|
|
209
|
+
|
|
210
|
+
The schema-1 package projection carries this field as `documents`, preserving
|
|
211
|
+
the distinction between omission and `[]`. With explicit selection, other
|
|
212
|
+
included HTML documents keep their exact content, including originals referenced
|
|
213
|
+
through iframes. Selection does not change serving, packaging or offline resource
|
|
214
|
+
membership; component processing and shared runtime-resource traversal retain
|
|
215
|
+
their existing behavior. Inspection, packaging and development use the same
|
|
216
|
+
[document-selection contract](protocols.md#application-document-selection).
|
|
217
|
+
Packaging consumes the saved managed import maps in selected source pages.
|
|
213
218
|
|
|
214
219
|
```text
|
|
215
220
|
arcane import-map [--workspace <directory>] [--app <id>]
|
|
@@ -279,11 +284,12 @@ Success returns the normal selected-workspace wrapper:
|
|
|
279
284
|
}
|
|
280
285
|
```
|
|
281
286
|
|
|
282
|
-
For the direct CLI command, `documentPaths` contains the configured entry first
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
+
For the direct CLI command, `documentPaths` contains the configured entry first.
|
|
288
|
+
Other selected paths follow in deterministic lexical order without duplicates.
|
|
289
|
+
The authored `documents` declaration remains as supplied, including `[]`.
|
|
290
|
+
The generated artifact is written first, followed by the selected documents.
|
|
291
|
+
A write failure or cancellation can leave earlier writes in place; the operation
|
|
292
|
+
does not restore them automatically. After successful
|
|
287
293
|
writes, an observer failure preserves delivery with
|
|
288
294
|
`eventDelivery.status === 'degraded'` and `ARCANE_EVENT_DELIVERY_FAILED`; it
|
|
289
295
|
does not roll back complete application content.
|
|
@@ -51,16 +51,12 @@ App-scoped managed browser imports have three supported control-plane entrypoint
|
|
|
51
51
|
uses `arcane import-map`; Node callers use
|
|
52
52
|
`executeOperation('import-map', options)` or
|
|
53
53
|
`createToolchain(defaults).importMap(options)`. These are three routes to the
|
|
54
|
-
same app-scoped operation, not three import-map formats. Each route
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
artifact plus every
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
explicit identity fails, and the selected page then must pass exact path-relative
|
|
61
|
-
base validation.
|
|
62
|
-
Included HTML with neither signal is a component fragment and remains a
|
|
63
|
-
complete package file.
|
|
54
|
+
same app-scoped operation, not three import-map formats. Each route uses the
|
|
55
|
+
[application document selection](#application-document-selection) shared by
|
|
56
|
+
inspection, development and packaging, and writes one canonical map to the
|
|
57
|
+
artifact plus every selected document. Each selected page retains the existing
|
|
58
|
+
path-relative base requirement. Included component fragments remain complete
|
|
59
|
+
package files and keep their existing component processing.
|
|
64
60
|
Public lowercase runtime subpaths resolve directly to canonical runtime-module
|
|
65
61
|
namespaces from both Node package exports and managed browser keys. These
|
|
66
62
|
include `arcane-os/preference-store`, `arcane-os/speech-playback`,
|
|
@@ -98,6 +94,48 @@ materializes the complete required SDK runtime,
|
|
|
98
94
|
browser-runtime, and managed-import-map closure inside that app's own artifact.
|
|
99
95
|
No application polls for SDK changes.
|
|
100
96
|
|
|
97
|
+
## Application document selection
|
|
98
|
+
|
|
99
|
+
An authored schema-2 `arcane-app.json` may select the HTML pages that receive
|
|
100
|
+
application-document processing through `package.documents`:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"package": {
|
|
105
|
+
"entry": "index.html",
|
|
106
|
+
"include": ["index.html", "pages", "originals", "modules", "css"],
|
|
107
|
+
"documents": ["index.html", "pages/viewer.html"]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This is a package excerpt. Each `documents` member is a literal app-root-relative
|
|
113
|
+
`.html` or `.htm` file path, not a directory or glob. Every selected document
|
|
114
|
+
must already be present in the app's included, non-excluded files. The entry is
|
|
115
|
+
always selected first, followed by the other selected paths in deterministic
|
|
116
|
+
lexical order without duplicates. An empty list selects only the entry. The
|
|
117
|
+
authored `documents` declaration remains as supplied, including `[]`.
|
|
118
|
+
|
|
119
|
+
Omitting `documents` preserves automatic discovery: the SDK selects the entry
|
|
120
|
+
and included secondary HTML pages with an active `<base>`. A
|
|
121
|
+
`meta[name="arcane-app-id"]` marker does not select a page. Selected pages retain
|
|
122
|
+
the existing requirement for one path-correct active base.
|
|
123
|
+
|
|
124
|
+
The schema-1 `arcane-package.json` field is `documents` at the top level.
|
|
125
|
+
Descriptor projection and synthesis preserve an omitted property as omitted
|
|
126
|
+
and an authored empty list as `[]`. Package-only applications may author that
|
|
127
|
+
schema-1 field directly. Ordinary inspect, import-map, dev and package operations
|
|
128
|
+
use the same declaration; there is no document-selection CLI flag.
|
|
129
|
+
|
|
130
|
+
Document selection controls application-document scanning, base handling,
|
|
131
|
+
managed import maps and PWA injection. It does not change `include`/`exclude`,
|
|
132
|
+
serving, package membership or offline resource selection. With an explicit
|
|
133
|
+
list, other included HTML documents retain their exact content, even when a
|
|
134
|
+
selected page references them through an iframe. They remain available as
|
|
135
|
+
included resources without receiving application-document processing.
|
|
136
|
+
Component processing and shared runtime-resource traversal retain their existing
|
|
137
|
+
behavior. Offline inclusion still follows the app's PWA resource selection.
|
|
138
|
+
|
|
101
139
|
## App-selected package output
|
|
102
140
|
|
|
103
141
|
An app may set `"outputDirectory": "ai"` in its schema-1
|
package/docs/reference/pwa.md
CHANGED
|
@@ -58,6 +58,27 @@ previous source identity while its start URL and scope move to the root. An
|
|
|
58
58
|
authored `manifest.id` remains authoritative. Packaged default identity remains
|
|
59
59
|
`./`. App identity and saved application data remain unchanged.
|
|
60
60
|
|
|
61
|
+
### Application pages and preserved HTML resources
|
|
62
|
+
|
|
63
|
+
Optional `package.documents` in `arcane-app.json` selects the app-root-relative
|
|
64
|
+
`.html`/`.htm` pages that receive managed import maps and PWA links/bootstrap.
|
|
65
|
+
Its schema-1 projection is `arcane-package.json`'s top-level `documents`.
|
|
66
|
+
For example, `"documents": ["index.html"]` selects the entry while other
|
|
67
|
+
included HTML documents retain their exact content. The entry is always selected;
|
|
68
|
+
`[]` also selects only the entry. Omitting the property preserves automatic
|
|
69
|
+
discovery of the entry and included secondary pages with an active `<base>`.
|
|
70
|
+
Selected pages retain the existing path-correct base requirement.
|
|
71
|
+
|
|
72
|
+
This declaration does not add or remove resources from `package.include`,
|
|
73
|
+
`package.exclude`, serving, packaging or the offline inventory. Included
|
|
74
|
+
originals remain available and can be cached under the normal offline selection,
|
|
75
|
+
including when a selected page displays them in an iframe. With an explicit
|
|
76
|
+
list, those unselected documents are not scanned as application pages or given
|
|
77
|
+
managed import maps, base handling or PWA injection. HTML-import components and
|
|
78
|
+
shared runtime-resource traversal keep their existing processing. See
|
|
79
|
+
[application document selection](protocols.md#application-document-selection)
|
|
80
|
+
for ordering and the complete declaration contract.
|
|
81
|
+
|
|
61
82
|
### Root generated output
|
|
62
83
|
|
|
63
84
|
For a standalone application, `appsRoot: "."` places the app at its repository
|
|
@@ -136,16 +157,18 @@ their app and SDK versions match. It is not a content measurement.
|
|
|
136
157
|
|
|
137
158
|
The package owns its selected inventory once, after any app adapter finishes.
|
|
138
159
|
It follows actual resource references to include meaningful query variants.
|
|
139
|
-
|
|
160
|
+
Selected application pages receive a manifest link and an `async` module
|
|
140
161
|
marked `data-arcane-pwa`. Existing application scripts retain their order.
|
|
141
162
|
PWA registration does not wait for models, storage, preferences or page rendering.
|
|
142
163
|
The same bootstrap starts one initially hidden `pwa-install.html` component with
|
|
143
164
|
the generated manifest's app name. Component loading and worker registration
|
|
144
165
|
proceed independently.
|
|
145
166
|
|
|
146
|
-
|
|
147
|
-
from actual local resource references,
|
|
148
|
-
|
|
167
|
+
SDK processing of selected application pages, components and runtime resources
|
|
168
|
+
removes only SDK-owned `arcaneVersion` fields from actual local resource references,
|
|
169
|
+
including the managed import map. Explicitly unselected HTML documents keep their
|
|
170
|
+
exact content. Authored fields such as `v`, encoded and repeated fields, empty
|
|
171
|
+
query segments, fragments,
|
|
149
172
|
source spelling and unrelated payloads are preserved. The offline
|
|
150
173
|
manifest carries release information. Non-PWA and native packaging use the same
|
|
151
174
|
[clean local resource URLs](asset-versioning.md). Native packaging does not
|
|
@@ -179,9 +202,9 @@ If `package.pwa.offline.include` is nonempty, the resource must also
|
|
|
179
202
|
match that offline selection and must not match `offline.exclude`. Adding a
|
|
180
203
|
path only to the offline selection does not add it to the app's resources.
|
|
181
204
|
The running source server reads changed app descriptors before the next app,
|
|
182
|
-
root-navigation or generated-PWA request. Include/exclude rules, the entry
|
|
183
|
-
PWA settings share the current selection; no restart or
|
|
184
|
-
write is needed. Edits to selected source files are picked up by the next due
|
|
205
|
+
root-navigation or generated-PWA request. Include/exclude rules, the entry,
|
|
206
|
+
document selection and PWA settings share the current selection; no restart or
|
|
207
|
+
package projection write is needed. Edits to selected source files are picked up by the next due
|
|
185
208
|
page-load check while the server remains running.
|
|
186
209
|
|
|
187
210
|
For an enabled application, `arcane dev` serves the generated PWA files at the
|
|
@@ -779,12 +779,22 @@ async inspectApp({workspaceRoot, appId, signal}={})
|
|
|
779
779
|
```
|
|
780
780
|
|
|
781
781
|
Import it from `arcane-os` or `arcane-os/packager`. The result includes the
|
|
782
|
-
configured `entry`, `include`, and `exclude` values
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
782
|
+
configured `entry`, `include`, and `exclude` values, the optional `documents`
|
|
783
|
+
declaration when authored, and `browserDocuments`, the entry-first ordered
|
|
784
|
+
`.html`/`.htm` paths shared by import-map refresh, development and packaging.
|
|
785
|
+
`package.documents` in an authored descriptor, or `documents` in a schema-1
|
|
786
|
+
package, selects literal app-root-relative paths already included and not
|
|
787
|
+
excluded. Other selected paths follow the entry in deterministic lexical order
|
|
788
|
+
without duplicates. The authored `documents` declaration remains as supplied,
|
|
789
|
+
including `[]`, which selects only the entry. Omitting the property retains
|
|
790
|
+
automatic discovery of the entry and included secondary pages with an active `<base>`. App identity
|
|
791
|
+
meta tags do not select documents.
|
|
792
|
+
|
|
793
|
+
Selected pages retain their existing base requirements. Explicit selection
|
|
794
|
+
leaves other included HTML documents unchanged, including iframe-referenced
|
|
795
|
+
originals, while preserving package, serving and offline resource membership.
|
|
796
|
+
Component processing and shared runtime-resource traversal retain their existing
|
|
797
|
+
behavior. See [application document selection](protocols.md#application-document-selection).
|
|
788
798
|
|
|
789
799
|
`entry` and each app browser document's `path` stay relative to the app
|
|
790
800
|
directory. Each browser document also reports `packagePath`, the emitted path
|
|
@@ -2111,6 +2121,11 @@ async function useprojectNativeDescriptor(...arguments_) {
|
|
|
2111
2121
|
|
|
2112
2122
|
Projects a schema-2 descriptor into the compatible schema-1 application package contract.
|
|
2113
2123
|
|
|
2124
|
+
Optional `package.documents` becomes top-level `documents`. Omission stays
|
|
2125
|
+
omitted and an authored empty list stays `[]`, so projection preserves automatic
|
|
2126
|
+
discovery versus entry-only selection. See
|
|
2127
|
+
[application document selection](protocols.md#application-document-selection).
|
|
2128
|
+
|
|
2114
2129
|
### Signature and result
|
|
2115
2130
|
|
|
2116
2131
|
```text
|
|
@@ -4277,9 +4292,9 @@ async function usedevelopApplication(...arguments_) {
|
|
|
4277
4292
|
|
|
4278
4293
|
Dispatches one named headless SDK operation with normalized acceptance, events, cancellation, and failure.
|
|
4279
4294
|
|
|
4280
|
-
The exact command `'import-map'` dispatches one app-scoped refresh
|
|
4281
|
-
|
|
4282
|
-
|
|
4295
|
+
The exact command `'import-map'` dispatches one app-scoped refresh across the
|
|
4296
|
+
`.html`/`.htm` documents selected by
|
|
4297
|
+
[`package.documents` or automatic discovery](protocols.md#application-document-selection), and returns
|
|
4283
4298
|
`{workspaceRoot, workspaceMode, appId,
|
|
4284
4299
|
importMap}`. The `importMap` value reports the generated imports and complete
|
|
4285
4300
|
ordered `documentPaths`. This route mutates the map artifact and selected managed HTML
|
package/package.json
CHANGED
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"required": ["entry", "strategy", "include", "exclude", "shared"],
|
|
39
39
|
"properties": {
|
|
40
40
|
"entry": { "$ref": "#/$defs/relativePath" },
|
|
41
|
+
"documents": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"description": "Application-relative HTML/HTM pages managed by import-map and PWA processing. Entry is always first; an empty list selects entry only. Omission preserves automatic discovery. File inclusion and serving remain controlled by include/exclude.",
|
|
44
|
+
"items": { "allOf": [{ "$ref": "#/$defs/relativePath" }, { "pattern": "\\.[Hh][Tt][Mm][Ll]?$" }] }
|
|
45
|
+
},
|
|
41
46
|
"strategy": { "enum": ["static", "adapter"] },
|
|
42
47
|
"localAIModelPolicy": { "$ref": "#/$defs/localAIModelPolicy" },
|
|
43
48
|
"pwa": { "$ref": "#/$defs/pwa" },
|
|
@@ -34,6 +34,16 @@
|
|
|
34
34
|
"entry": {
|
|
35
35
|
"$ref": "#/$defs/relativePath"
|
|
36
36
|
},
|
|
37
|
+
"documents": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"description": "Application-relative HTML/HTM pages managed by import-map and PWA processing. Entry is always first; an empty list selects entry only. Omission preserves automatic discovery. File inclusion and serving remain controlled by include/exclude.",
|
|
40
|
+
"items": {
|
|
41
|
+
"allOf": [
|
|
42
|
+
{ "$ref": "#/$defs/relativePath" },
|
|
43
|
+
{ "pattern": "\\.[Hh][Tt][Mm][Ll]?$" }
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
37
47
|
"outputDirectory": {
|
|
38
48
|
"$ref": "#/$defs/relativePath",
|
|
39
49
|
"description": "Final package directory relative to the workspace, with no appended app ID. Omission preserves dist/<id>. This directory is replaced when packaging succeeds; source layout and app identity remain unchanged."
|
package/src/app-descriptor.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from './constants.mjs';
|
|
13
13
|
import {
|
|
14
14
|
normalizeRelativePath,
|
|
15
|
+
normalizeAppDocuments,
|
|
15
16
|
parseSemver,
|
|
16
17
|
validateAppConfig as validateAppPackageConfig
|
|
17
18
|
} from './packager/core.mjs';
|
|
@@ -110,9 +111,10 @@ function validateLocalAIModelPolicy(value,label){
|
|
|
110
111
|
|
|
111
112
|
function validatePackage(value,appId){
|
|
112
113
|
assertOnlyKeys(value,new Set([
|
|
113
|
-
'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy','pwa'
|
|
114
|
+
'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy','pwa','documents'
|
|
114
115
|
]),'descriptor.package');
|
|
115
116
|
const entry=normalizeRelativePath(value.entry,'descriptor.package.entry');
|
|
117
|
+
const documents=normalizeAppDocuments(value.documents,'descriptor.package.documents');
|
|
116
118
|
if(!['static','adapter'].includes(value.strategy))fail('descriptor.package.strategy must be static or adapter.');
|
|
117
119
|
const include=relativePaths(value.include,'descriptor.package.include',{required:true});
|
|
118
120
|
const exclude=relativePaths(value.exclude??[],'descriptor.package.exclude');
|
|
@@ -134,6 +136,7 @@ function validatePackage(value,appId){
|
|
|
134
136
|
}
|
|
135
137
|
return completeValue({
|
|
136
138
|
entry,
|
|
139
|
+
...(documents===undefined?{}:{documents}),
|
|
137
140
|
strategy:value.strategy,
|
|
138
141
|
include,
|
|
139
142
|
exclude,
|
|
@@ -317,6 +320,7 @@ export function projectPackageManifest(descriptor){
|
|
|
317
320
|
displayName:value.displayName,
|
|
318
321
|
version:value.version,
|
|
319
322
|
entry:value.package.entry,
|
|
323
|
+
...(value.package.documents===undefined?{}:{documents:[...value.package.documents]}),
|
|
320
324
|
strategy:value.package.strategy,
|
|
321
325
|
...(hasAuthoredSecurity?{security:{
|
|
322
326
|
connectOrigins:[...value.security.connectOrigins],
|
|
@@ -381,6 +385,7 @@ function synthesizedDescriptor(packageManifest,nativeDescriptor){
|
|
|
381
385
|
publisher:{id:'publisher-undeclared',name:'Publisher not declared'},
|
|
382
386
|
package:{
|
|
383
387
|
entry:packageManifest.entry,
|
|
388
|
+
...(packageManifest.documents===undefined?{}:{documents:[...packageManifest.documents]}),
|
|
384
389
|
strategy:packageManifest.strategy,
|
|
385
390
|
...(packageManifest.localAIModelPolicy?{localAIModelPolicy:packageManifest.localAIModelPolicy}:{}),
|
|
386
391
|
...(packageManifest.pwa===undefined?{}:{pwa:packageManifest.pwa}),
|
package/src/dev-server.mjs
CHANGED
|
@@ -324,7 +324,8 @@ async function serveGeneratedRepresentation(fileServer, request, response, {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
async function serveSourceFile(fileServer, request, response, opened, {
|
|
327
|
-
assetVersion, revalidate = false, onReference, pwaEntry = false, lastModified
|
|
327
|
+
assetVersion, revalidate = false, onReference, pwaEntry = false, lastModified,
|
|
328
|
+
representationTracked = false
|
|
328
329
|
} = {}) {
|
|
329
330
|
const extension = path.extname(opened.candidate).toLowerCase();
|
|
330
331
|
const rewrite = assetVersion !== undefined && /\.(?:m?js|html?|css|json)$/iu.test(extension);
|
|
@@ -336,9 +337,27 @@ async function serveSourceFile(fileServer, request, response, opened, {
|
|
|
336
337
|
response.writeHead = function writeDevelopmentHeaders(...arguments_) {
|
|
337
338
|
this.removeHeader('ETag');
|
|
338
339
|
this.removeHeader('X-Content-Type-Options');
|
|
340
|
+
if (representationTracked) {
|
|
341
|
+
if (lastModified) this.setHeader('Last-Modified', lastModified.toUTCString());
|
|
342
|
+
else this.removeHeader('Last-Modified');
|
|
343
|
+
}
|
|
339
344
|
return writeHead.apply(this, arguments_);
|
|
340
345
|
};
|
|
341
|
-
|
|
346
|
+
const modifiedSince = request.headers['if-modified-since'];
|
|
347
|
+
const range = request.headers.range;
|
|
348
|
+
if (representationTracked) {
|
|
349
|
+
const currentTime = lastModified ? Date.parse(lastModified.toUTCString()) : NaN;
|
|
350
|
+
if (!(Date.parse(modifiedSince) >= currentTime)) delete request.headers['if-modified-since'];
|
|
351
|
+
if (request.headers['if-range'] && !(Date.parse(request.headers['if-range']) >= currentTime)) {
|
|
352
|
+
delete request.headers.range;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
await fileServer.serveFile(opened.candidate, request, response);
|
|
357
|
+
} finally {
|
|
358
|
+
if (modifiedSince !== undefined) request.headers['if-modified-since'] = modifiedSince;
|
|
359
|
+
if (range !== undefined) request.headers.range = range;
|
|
360
|
+
}
|
|
342
361
|
return;
|
|
343
362
|
}
|
|
344
363
|
await serveGeneratedRepresentation(
|
|
@@ -514,6 +533,10 @@ function applicationSourcePath(config,appId,relative='') {
|
|
|
514
533
|
return `/${root?`${root}/`:''}${relative}`;
|
|
515
534
|
}
|
|
516
535
|
|
|
536
|
+
function selectedApplicationDocument(app,relative){
|
|
537
|
+
return app.documents===undefined||relative===app.entry||app.documents.includes(relative);
|
|
538
|
+
}
|
|
539
|
+
|
|
517
540
|
async function packagedRoutes(releaseRoot){
|
|
518
541
|
if(!is.string(releaseRoot)||!releaseRoot.trim())fail('releaseRoot is required in packaged mode.','ARCANE_USAGE');
|
|
519
542
|
const requested=path.resolve(releaseRoot);
|
|
@@ -548,7 +571,7 @@ async function packagedRoutes(releaseRoot){
|
|
|
548
571
|
};
|
|
549
572
|
}
|
|
550
573
|
|
|
551
|
-
async function sourcePwaAssets(routeSet, mappings, signal, resourceUrls, resourcePaths) {
|
|
574
|
+
async function sourcePwaAssets(routeSet, mappings, signal, resourceUrls, resourcePaths, runtimeDocuments = new Set()) {
|
|
552
575
|
const records = new Map();
|
|
553
576
|
const resources = new Map();
|
|
554
577
|
for (const mapping of mappings) {
|
|
@@ -590,7 +613,7 @@ async function sourcePwaAssets(routeSet, mappings, signal, resourceUrls, resourc
|
|
|
590
613
|
const pending = [{url: entryUrl, documentUrl: entryUrl}];
|
|
591
614
|
const visited = new Set();
|
|
592
615
|
const referencesByPath = new Map();
|
|
593
|
-
for (const selected of routeSet.app.include) {
|
|
616
|
+
for (const selected of routeSet.app.documents ?? routeSet.app.include) {
|
|
594
617
|
if (!/\.html?$/iu.test(selected)) continue;
|
|
595
618
|
const url = new URL(
|
|
596
619
|
applicationSourcePath(routeSet.config,routeSet.appId,selected.split('/').map(encodeURIComponent).join('/')),
|
|
@@ -609,6 +632,9 @@ async function sourcePwaAssets(routeSet, mappings, signal, resourceUrls, resourc
|
|
|
609
632
|
if (!/\.(?:m?js|html?|css)$/iu.test(pathname) && !managedMap) continue;
|
|
610
633
|
visited.add(context);
|
|
611
634
|
resourcePaths.add(decodeURIComponent(pathname));
|
|
635
|
+
if (record.mapping.kind !== 'app' && /\.html?$/iu.test(pathname)) {
|
|
636
|
+
runtimeDocuments.add(decodeURIComponent(pathname));
|
|
637
|
+
}
|
|
612
638
|
let references = referencesByPath.get(pathname);
|
|
613
639
|
if (!references) {
|
|
614
640
|
const opened = await openSafeFile(record.mapping.root, record.relative);
|
|
@@ -654,6 +680,9 @@ async function sourcePwaAssets(routeSet, mappings, signal, resourceUrls, resourc
|
|
|
654
680
|
}
|
|
655
681
|
if (target.origin !== origin || !resources.has(target.pathname)) continue;
|
|
656
682
|
resourceUrls.add(`${target.pathname}${target.search}`);
|
|
683
|
+
const targetRecord = resources.get(target.pathname);
|
|
684
|
+
if (kind === 'document' && targetRecord.mapping.kind === 'app'
|
|
685
|
+
&& !selectedApplicationDocument(routeSet.app, targetRecord.relative.join('/'))) continue;
|
|
657
686
|
const traversable = kind !== 'fetch'
|
|
658
687
|
&& (kind !== 'asset' || /\.css(?:[?#]|$)/iu.test(url));
|
|
659
688
|
if (traversable) {
|
|
@@ -942,21 +971,33 @@ async function startOwnedDevServer({
|
|
|
942
971
|
);
|
|
943
972
|
return managed;
|
|
944
973
|
}
|
|
945
|
-
function
|
|
974
|
+
function sourceRepresentation(opened, version, pwaEntry, rewritten) {
|
|
946
975
|
const modifiedAt = opened.modifiedAt.getTime();
|
|
947
976
|
const previous = representationMetadata.get(opened.candidate);
|
|
948
977
|
if (previous?.modifiedAt === modifiedAt
|
|
949
|
-
&& previous.version === version && previous.pwaEntry === pwaEntry
|
|
950
|
-
|
|
978
|
+
&& previous.version === version && previous.pwaEntry === pwaEntry
|
|
979
|
+
&& previous.rewritten === rewritten) {
|
|
980
|
+
if (!previous.lastModified && Date.now() >= previous.ambiguousThrough + 1000) {
|
|
981
|
+
previous.lastModified = new Date();
|
|
982
|
+
}
|
|
983
|
+
return previous;
|
|
951
984
|
}
|
|
952
985
|
const lastModified = new Date(
|
|
953
986
|
Math.max(modifiedAt, generatorModifiedAt, assetVersionModifiedAt, previous ? Date.now() : 0)
|
|
954
987
|
);
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
988
|
+
const previousTime = previous?.lastModified
|
|
989
|
+
? Date.parse(previous.lastModified.toUTCString()) : previous?.ambiguousThrough;
|
|
990
|
+
// HTTP dates cannot distinguish two representations within one second.
|
|
991
|
+
// Omit that ambiguous validator until a later request can date it honestly.
|
|
992
|
+
const ambiguous = previousTime !== undefined
|
|
993
|
+
&& Date.parse(lastModified.toUTCString()) <= previousTime;
|
|
994
|
+
const record = {
|
|
995
|
+
modifiedAt, version, pwaEntry, rewritten,
|
|
996
|
+
lastModified: ambiguous ? undefined : lastModified,
|
|
997
|
+
ambiguousThrough: ambiguous ? previousTime : undefined
|
|
998
|
+
};
|
|
999
|
+
representationMetadata.set(opened.candidate, record);
|
|
1000
|
+
return record;
|
|
960
1001
|
}
|
|
961
1002
|
for(const mapping of mappings){
|
|
962
1003
|
const info=await lstat(mapping.root);
|
|
@@ -1089,7 +1130,7 @@ async function startOwnedDevServer({
|
|
|
1089
1130
|
const generatedAt = new Date();
|
|
1090
1131
|
state.inventoryTask = Promise.all(
|
|
1091
1132
|
[
|
|
1092
|
-
sourcePwaAssets(selectedRoutes, selectedRoutes.mappings, signal, pwaResourceUrls, resourcePaths),
|
|
1133
|
+
sourcePwaAssets(selectedRoutes, selectedRoutes.mappings, signal, pwaResourceUrls, resourcePaths, runtimeDocuments),
|
|
1093
1134
|
selectedAssetVersion().then(
|
|
1094
1135
|
function rememberCurrentInventoryVersion(version) {
|
|
1095
1136
|
if (selectedRoutes === currentSourceRoutes) rememberAssetVersion(version);
|
|
@@ -1124,6 +1165,66 @@ async function startOwnedDevServer({
|
|
|
1124
1165
|
// Remember actual resource edges as their owners are served, not every
|
|
1125
1166
|
// HTML/JS/CSS file in an application's document or attachment corpus.
|
|
1126
1167
|
const resourcePaths=new Set([routeSet.startPath]);
|
|
1168
|
+
const runtimeDocuments=new Set();
|
|
1169
|
+
const componentReferences=new Map();
|
|
1170
|
+
async function isCurrentComponent(targetPath, selectedRoutes) {
|
|
1171
|
+
// Retain parsed edges, not lifetime classifications. Only current page
|
|
1172
|
+
// roots and observed runtime documents can make an original a component.
|
|
1173
|
+
const pending = [selectedRoutes.startPath, ...runtimeDocuments];
|
|
1174
|
+
for (const document of selectedRoutes.app.documents) {
|
|
1175
|
+
pending.push(applicationSourcePath(selectedRoutes.config, selectedRoutes.appId, document));
|
|
1176
|
+
}
|
|
1177
|
+
const visited = new Set();
|
|
1178
|
+
for (const ownerPath of pending) {
|
|
1179
|
+
throwIfAborted(signal);
|
|
1180
|
+
if (visited.has(ownerPath)) continue;
|
|
1181
|
+
visited.add(ownerPath);
|
|
1182
|
+
const target = parseRequestTarget(ownerPath.split('/').map(encodeURIComponent).join('/'));
|
|
1183
|
+
if (!target) continue;
|
|
1184
|
+
const mapping = selectedRoutes.mappings.find(function componentOwnerMapping(route) {
|
|
1185
|
+
return route.prefix.every(function componentOwnerSegment(segment, index) {
|
|
1186
|
+
return target.segments[index] === segment;
|
|
1187
|
+
});
|
|
1188
|
+
});
|
|
1189
|
+
if (!mapping) continue;
|
|
1190
|
+
const relative = target.segments.slice(mapping.prefix.length);
|
|
1191
|
+
if (!relative.length || (mapping.allow && !mapping.allow(relative))) continue;
|
|
1192
|
+
if (!/\.html?$/iu.test(ownerPath)) continue;
|
|
1193
|
+
const opened = await openSafeFile(mapping.root, relative, {readContent: false});
|
|
1194
|
+
if (!opened) continue;
|
|
1195
|
+
const modifiedAt = opened.modifiedAt.getTime();
|
|
1196
|
+
let record = componentReferences.get(opened.candidate);
|
|
1197
|
+
if (record?.modifiedAt !== modifiedAt) {
|
|
1198
|
+
const references = [];
|
|
1199
|
+
rewriteAssetReferences(await readFile(opened.candidate, 'utf8'), {
|
|
1200
|
+
filePath: opened.candidate,
|
|
1201
|
+
version: null,
|
|
1202
|
+
onReference: function currentComponentReference(reference) {
|
|
1203
|
+
if (reference.kind === 'component') references.push(reference);
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
record = {modifiedAt, references};
|
|
1207
|
+
componentReferences.set(opened.candidate, record);
|
|
1208
|
+
}
|
|
1209
|
+
const ownerUrl = new URL(target.pathname, 'http://arcane.invalid');
|
|
1210
|
+
for (const reference of record.references) {
|
|
1211
|
+
let component;
|
|
1212
|
+
try {
|
|
1213
|
+
component = resolveAssetReference(reference, {ownerUrl});
|
|
1214
|
+
} catch {
|
|
1215
|
+
// Non-URL values retain their authored behavior.
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (component.origin !== ownerUrl.origin) continue;
|
|
1219
|
+
const componentTarget = parseRequestTarget(component.pathname);
|
|
1220
|
+
if (!componentTarget) continue;
|
|
1221
|
+
const componentPath = componentTarget.path;
|
|
1222
|
+
if (componentPath === targetPath) return true;
|
|
1223
|
+
pending.push(componentPath);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
return false;
|
|
1227
|
+
}
|
|
1127
1228
|
const requestTasks=new Set();
|
|
1128
1229
|
const runFileWork=createFileWorkLimiter();
|
|
1129
1230
|
async function serveDevelopmentRequest(request, response) {
|
|
@@ -1208,21 +1309,31 @@ async function startOwnedDevServer({
|
|
|
1208
1309
|
}
|
|
1209
1310
|
const extension=path.extname(opened.candidate).toLowerCase();
|
|
1210
1311
|
const html=extension==='.html'||extension==='.htm';
|
|
1312
|
+
const explicitDocuments = mode === 'source' && mapping.kind === 'app'
|
|
1313
|
+
&& selectedRoutes.app.documents !== undefined;
|
|
1314
|
+
const explicitDocument = explicitDocuments && html
|
|
1315
|
+
&& selectedApplicationDocument(selectedRoutes.app, relative.join('/'));
|
|
1211
1316
|
const selectedPwaDocument = mode === 'source' && pwaEnabled && html
|
|
1212
1317
|
&& mapping.kind === 'app'
|
|
1213
|
-
&& selectedRoutes.app.include.includes(relative.join('/'));
|
|
1214
|
-
const selectedDocument = target.path === selectedRoutes.startPath || selectedPwaDocument;
|
|
1215
|
-
const managedDocument = !selectedDocument && html && await isManagedDocument(opened);
|
|
1318
|
+
&& !explicitDocuments && selectedRoutes.app.include.includes(relative.join('/'));
|
|
1319
|
+
const selectedDocument = target.path === selectedRoutes.startPath || explicitDocument || selectedPwaDocument;
|
|
1320
|
+
const managedDocument = !selectedDocument && !explicitDocuments && html && await isManagedDocument(opened);
|
|
1216
1321
|
const entryDocument = selectedDocument || managedDocument;
|
|
1217
1322
|
const managedMap=path.basename(opened.candidate)==='arcane.importmap.json';
|
|
1218
1323
|
// A live server can span an SDK upgrade. Refresh the small
|
|
1219
1324
|
// version record on navigation, not on each resource request.
|
|
1220
1325
|
if(entryDocument||managedMap)rememberAssetVersion(await selectedAssetVersion());
|
|
1221
1326
|
const runtimeResource=mapping.kind!=='app';
|
|
1327
|
+
if (runtimeResource && html) runtimeDocuments.add(target.path);
|
|
1222
1328
|
const browserResource=['script','style','worker','sharedworker','serviceworker']
|
|
1223
1329
|
.includes(request.headers['sec-fetch-dest']);
|
|
1224
|
-
|
|
1225
|
-
|
|
1330
|
+
// Selecting application pages does not turn retained originals
|
|
1331
|
+
// into resources. HTML components remain owned by html-import.
|
|
1332
|
+
const componentDocument=explicitDocuments&&html&&!selectedDocument
|
|
1333
|
+
&&await isCurrentComponent(target.path, selectedRoutes);
|
|
1334
|
+
const retainedDocument=explicitDocuments&&html&&!selectedDocument&&!componentDocument;
|
|
1335
|
+
const rewrite=!retainedDocument&&(runtimeResource||entryDocument||browserResource||managedMap
|
|
1336
|
+
||componentDocument||resourcePaths.has(target.path));
|
|
1226
1337
|
const onReference = function observeServedResource(reference) {
|
|
1227
1338
|
const {url,kind,baseKind}=reference;
|
|
1228
1339
|
if (pwaEnabled && kind !== 'fetch'
|
|
@@ -1247,7 +1358,13 @@ async function startOwnedDevServer({
|
|
|
1247
1358
|
const resource=resolveAssetReference(reference,{ownerUrl:documentUrl});
|
|
1248
1359
|
if(resource.origin===documentUrl.origin
|
|
1249
1360
|
&&/\.(?:m?js|html?|css)$/iu.test(resource.pathname)){
|
|
1250
|
-
|
|
1361
|
+
const resourcePath=decodeURIComponent(resource.pathname);
|
|
1362
|
+
const appPrefix=applicationSourcePath(selectedRoutes.config,selectedRoutes.appId);
|
|
1363
|
+
const appRelative=resourcePath.startsWith(appPrefix)?resourcePath.slice(appPrefix.length):null;
|
|
1364
|
+
if(kind==='document'&&mode==='source'&&appRelative!==null
|
|
1365
|
+
&&sourcePathAllowed(appRelative.split('/'),selectedRoutes.app)
|
|
1366
|
+
&&!selectedApplicationDocument(selectedRoutes.app,appRelative))return;
|
|
1367
|
+
resourcePaths.add(resourcePath);
|
|
1251
1368
|
}
|
|
1252
1369
|
}catch{ /* Non-URL values remain under their existing owner. */ }
|
|
1253
1370
|
};
|
|
@@ -1255,6 +1372,8 @@ async function startOwnedDevServer({
|
|
|
1255
1372
|
const pwaEntry = mode === 'source' && pwaEnabled && entryDocument;
|
|
1256
1373
|
const transformed = pwaEntry || (selectedVersion !== undefined
|
|
1257
1374
|
&& /\.(?:m?js|html?|css|json)$/iu.test(extension));
|
|
1375
|
+
const representation = transformed || retainedDocument
|
|
1376
|
+
? sourceRepresentation(opened, assetVersion, pwaEntry, transformed) : undefined;
|
|
1258
1377
|
await serveSourceFile(
|
|
1259
1378
|
fileServer,
|
|
1260
1379
|
request,
|
|
@@ -1265,9 +1384,8 @@ async function startOwnedDevServer({
|
|
|
1265
1384
|
revalidate: pwaEnabled || entryDocument || managedMap,
|
|
1266
1385
|
pwaEntry,
|
|
1267
1386
|
onReference,
|
|
1268
|
-
lastModified:
|
|
1269
|
-
|
|
1270
|
-
: undefined
|
|
1387
|
+
lastModified: representation?.lastModified,
|
|
1388
|
+
representationTracked: representation !== undefined
|
|
1271
1389
|
}
|
|
1272
1390
|
);
|
|
1273
1391
|
});
|
package/src/packager/core.mjs
CHANGED
|
@@ -166,6 +166,16 @@ function validatePathList(value,label,{required=false,allowRoot=false}={}){
|
|
|
166
166
|
return normalized;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
export function normalizeAppDocuments(value,label='documents'){
|
|
170
|
+
if(value===undefined)return undefined;
|
|
171
|
+
if(!is.array(value))fail(`${label} must be an array of application-relative HTML paths.`);
|
|
172
|
+
return value.map(function normalizeDocumentPath(entry,index){
|
|
173
|
+
const document=normalizeRelativePath(entry,`${label}[${index}]`);
|
|
174
|
+
if(!/\.html?$/iu.test(document))fail(`${label}[${index}] must name an HTML or HTM file.`);
|
|
175
|
+
return document;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
169
179
|
function isAlwaysForbidden(relative){
|
|
170
180
|
return relative.split('/').some(segment=>{
|
|
171
181
|
const key=pathKey(segment);
|
|
@@ -308,7 +318,7 @@ function normalizeOptionalRecord(value,label){
|
|
|
308
318
|
export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.join(appRelativeRoot(rootConfig,appId),APP_CONFIG_NAME)){
|
|
309
319
|
assertOnlyKeys(value,new Set([
|
|
310
320
|
'schemaVersion','id','displayName','version','entry','strategy','security',
|
|
311
|
-
'localAIModelPolicy','include','exclude','shared','adapter','pwa','outputDirectory'
|
|
321
|
+
'localAIModelPolicy','include','exclude','shared','adapter','pwa','outputDirectory','documents'
|
|
312
322
|
]),`${appId}/${APP_CONFIG_NAME}`);
|
|
313
323
|
if(value.schemaVersion!==1)fail(`${appId}/${APP_CONFIG_NAME}.schemaVersion must be 1.`);
|
|
314
324
|
if(!is.string(value.id)||value.id!==appId||!APP_ID_PATTERN.test(value.id)){
|
|
@@ -317,6 +327,7 @@ export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.j
|
|
|
317
327
|
const displayName=assertPresentationText(value.displayName,`${appId}/${APP_CONFIG_NAME}.displayName`);
|
|
318
328
|
parseSemver(value.version);
|
|
319
329
|
const entry=normalizeRelativePath(value.entry,`${appId}/${APP_CONFIG_NAME}.entry`);
|
|
330
|
+
const documents=normalizeAppDocuments(value.documents,`${appId}/${APP_CONFIG_NAME}.documents`);
|
|
320
331
|
const outputDirectory=value.outputDirectory===undefined?undefined:normalizeRelativePath(
|
|
321
332
|
value.outputDirectory,`${appId}/${APP_CONFIG_NAME}.outputDirectory`
|
|
322
333
|
);
|
|
@@ -355,6 +366,7 @@ export function validateAppConfig(value,appId,rootConfig,configPath=path.posix.j
|
|
|
355
366
|
displayName,
|
|
356
367
|
version:value.version,
|
|
357
368
|
entry,
|
|
369
|
+
...(documents===undefined?{}:{documents}),
|
|
358
370
|
...(outputDirectory===undefined?{}:{outputDirectory}),
|
|
359
371
|
strategy:value.strategy,
|
|
360
372
|
...(value.pwa===undefined?{}:{pwa:normalizePwaConfig(value.pwa)}),
|
|
@@ -603,7 +615,23 @@ async function collectPackageRecords(context,{signal}={}){
|
|
|
603
615
|
return records;
|
|
604
616
|
}
|
|
605
617
|
|
|
606
|
-
async function browserDocuments(records,entry){
|
|
618
|
+
async function browserDocuments(records,entry,selected){
|
|
619
|
+
if(selected!==undefined){
|
|
620
|
+
const appRecords=new Map(records.filter(function applicationRecord(record){
|
|
621
|
+
return record.appRelativePath!==undefined;
|
|
622
|
+
}).map(function applicationRecordPath(record){
|
|
623
|
+
return [record.appRelativePath,record];
|
|
624
|
+
}));
|
|
625
|
+
const documentPaths=[entry,...[...new Set(selected)].filter(function secondaryDocument(document){
|
|
626
|
+
return document!==entry;
|
|
627
|
+
}).sort(compareText)];
|
|
628
|
+
return Promise.all(documentPaths.map(async function inspectSelectedDocument(documentPath){
|
|
629
|
+
const record=appRecords.get(documentPath);
|
|
630
|
+
if(!record)fail(`Application document is missing from the selected files: ${documentPath}.`);
|
|
631
|
+
const inspected=inspectImportMapHtml(await readFile(record.source,'utf8'),{documentPath});
|
|
632
|
+
return {path:documentPath,packagePath:record.destination,...copyJson(inspected)};
|
|
633
|
+
}));
|
|
634
|
+
}
|
|
607
635
|
let entryDocument=null;
|
|
608
636
|
const documents=[];
|
|
609
637
|
for(const record of records){
|
|
@@ -638,12 +666,13 @@ async function optionalDescriptor(context){
|
|
|
638
666
|
|
|
639
667
|
async function inspectContext(context,{signal,records}={}){
|
|
640
668
|
const selectedRecords=records??await collectPackageRecords(context,{signal});
|
|
641
|
-
const documents=await browserDocuments(selectedRecords,context.config.entry);
|
|
669
|
+
const documents=await browserDocuments(selectedRecords,context.config.entry,context.config.documents);
|
|
642
670
|
return {
|
|
643
671
|
appId:context.appId,
|
|
644
672
|
displayName:context.config.displayName,
|
|
645
673
|
version:context.config.version,
|
|
646
674
|
entry:context.config.entry,
|
|
675
|
+
...(context.config.documents===undefined?{}:{documents:[...context.config.documents]}),
|
|
647
676
|
...(context.config.outputDirectory===undefined?{}:{outputDirectory:context.config.outputDirectory}),
|
|
648
677
|
strategy:context.config.strategy,
|
|
649
678
|
...(context.config.pwa===undefined?{}:{pwa:copyJson(context.config.pwa)}),
|
|
@@ -888,9 +917,12 @@ async function packageWithContext(context,options={}){
|
|
|
888
917
|
const offlineReferences=new Set();
|
|
889
918
|
const entryUrl=new URL(packageResourceUrl(entryPath),'http://arcane.invalid/');
|
|
890
919
|
const pwaDocumentSources=new Map();
|
|
920
|
+
const explicitDocumentPaths=context.config.documents===undefined?null:new Set(
|
|
921
|
+
inspected.browserDocuments.map(function selectedDocumentPath(document){return document.packagePath;})
|
|
922
|
+
);
|
|
891
923
|
if(pwaEnabled){
|
|
892
|
-
const documentPaths=new Set([entryPath]);
|
|
893
|
-
for(const selected of context.config.include){
|
|
924
|
+
const documentPaths=new Set(explicitDocumentPaths??[entryPath]);
|
|
925
|
+
for(const selected of explicitDocumentPaths===null?context.config.include:[]){
|
|
894
926
|
const selectedPath=appPackagePath(context,selected);
|
|
895
927
|
if(/\.html?$/iu.test(selected)&&inventory.has(selectedPath))documentPaths.add(selectedPath);
|
|
896
928
|
}
|
|
@@ -925,6 +957,9 @@ async function packageWithContext(context,options={}){
|
|
|
925
957
|
?new URL(entryDocument.bases[0].href,entryUrl):entryUrl;
|
|
926
958
|
const pending=[{file:entryPath,documentUrl}];
|
|
927
959
|
const sharedFiles=new Set(records.filter(record=>record.appRelativePath===undefined).map(record=>record.destination));
|
|
960
|
+
const applicationFiles=new Set(records.filter(function applicationRecord(record){
|
|
961
|
+
return record.appRelativePath!==undefined;
|
|
962
|
+
}).map(function applicationDestination(record){return record.destination;}));
|
|
928
963
|
for(const file of files){
|
|
929
964
|
if((sharedFiles.has(file)||/^arcane\/(?:modules|entities|components|css|sdk|dependencies)\//u.test(file))
|
|
930
965
|
&&/\.(?:m?js|html?|css)$/iu.test(file))pending.push({file,documentUrl});
|
|
@@ -985,7 +1020,9 @@ async function packageWithContext(context,options={}){
|
|
|
985
1020
|
if(pwaEnabled&&offlineInventory.has(file)){
|
|
986
1021
|
offlineReferences.add(versionAssetUrl(`.${target.pathname}${target.search}`,null));
|
|
987
1022
|
}
|
|
988
|
-
|
|
1023
|
+
const selectedDocument=kind!=='document'||!applicationFiles.has(file)||explicitDocumentPaths===null
|
|
1024
|
+
||explicitDocumentPaths.has(file);
|
|
1025
|
+
if(traversable&&selectedDocument)pending.push({
|
|
989
1026
|
file,
|
|
990
1027
|
documentUrl:kind==='document'?target
|
|
991
1028
|
:baseHref?new URL(baseHref,ownerUrl):current.documentUrl
|
|
@@ -195,10 +195,20 @@ npm run run
|
|
|
195
195
|
|
|
196
196
|
The explicit \`import-map\` command refreshes
|
|
197
197
|
\`${appPrefix}modules/arcane.importmap.json\` and the managed inline browser
|
|
198
|
-
import map in
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
import map in each selected application \`.html\`/\`.htm\` document.
|
|
199
|
+
Optional \`package.documents\` in \`arcane-app.json\` selects literal app-root-relative
|
|
200
|
+
HTML paths already included and not excluded, for example \`["index.html"]\`.
|
|
201
|
+
The entry is always selected first; \`[]\` selects only the entry. Omission
|
|
202
|
+
retains automatic discovery of the entry and included secondary pages with an
|
|
203
|
+
active base. The schema-1 package projection preserves this as \`documents\`,
|
|
204
|
+
including the distinction between omission and an empty list.
|
|
205
|
+
Selected pages retain their existing path-correct base requirement. With an
|
|
206
|
+
explicit list, other included HTML documents retain their exact content, even
|
|
207
|
+
when referenced by an iframe. Document selection leaves serving, packaging and
|
|
208
|
+
offline resource membership unchanged. HTML component processing and shared
|
|
209
|
+
runtime-resource traversal retain their existing behavior.
|
|
210
|
+
Development startup refreshes the selected managed maps; package and build
|
|
211
|
+
consume the saved source maps and the same document selection.
|
|
202
212
|
Commit generated import maps and enabled offline app files. Hosting workflows consume those committed files rather than generating them.
|
|
203
213
|
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
|
|
204
214
|
map to \`dist/${appId}\` without running application tests. Run \`verify\` only when
|
package/src/toolchain.mjs
CHANGED
|
@@ -299,6 +299,7 @@ async function refreshPreparedImportMap(prepared,{signal,onEvent,workspaceOperat
|
|
|
299
299
|
signal
|
|
300
300
|
});
|
|
301
301
|
if(inspected.entry!==manifest.entry
|
|
302
|
+
||JSON.stringify(inspected.documents)!==JSON.stringify(manifest.documents)
|
|
302
303
|
||JSON.stringify(inspected.include)!==JSON.stringify(manifest.include)
|
|
303
304
|
||JSON.stringify(inspected.exclude)!==JSON.stringify(manifest.exclude)){
|
|
304
305
|
throw new ArcaneError(
|