arcane-os 0.27.0 → 0.28.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 +30 -0
- package/README.md +7 -1
- package/browser-runtime/pwa-install.mjs +4 -4
- package/browser-runtime/pwa.mjs +1 -1
- package/docs/reference/inventory/package-api.json +4 -0
- package/docs/reference/protocols.md +15 -1
- package/docs/reference/pwa.md +12 -0
- package/docs/reference/sdk-api.md +9 -2
- package/package.json +3 -1
- package/src/dev-server.mjs +12 -6
- package/src/import-map.mjs +8 -3
- package/src/packager/core.mjs +10 -2
- package/src/pwa.mjs +53 -9
- package/src/scaffold.mjs +11 -6
- package/src/templates/workspace-template.mjs +8 -6
- package/src/toolchain.mjs +1 -0
- package/src/workspace.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
- Expose existing runtime modules and entities directly through
|
|
6
|
+
`arcane-os/modules/<filename>` and `arcane-os/entities/<filename>`, including
|
|
7
|
+
extensions. Package exports and managed maps point to the actual files, so
|
|
8
|
+
relative imports, component resources and `import.meta.resolve` keep their
|
|
9
|
+
established directory and lifecycle. Existing lowercase exports remain.
|
|
10
|
+
- Root apps using direct npm routes generate package-namespaced maps without
|
|
11
|
+
the old `arcane/*` or `./arcane/*` keys. Update authored root-app bare imports
|
|
12
|
+
before regenerating the managed map. Installed `runtime/arcane/` directories
|
|
13
|
+
remain unchanged; physical, virtual and nested layouts retain compatibility.
|
|
14
|
+
- Use the package namespace for generated root app code and shared PWA imports,
|
|
15
|
+
preserving parallel startup and the existing storage and installation owners.
|
|
16
|
+
- New root apps declare the SDK as a runtime dependency. Init promotes their
|
|
17
|
+
SDK development declaration and preserves existing runtime or optional
|
|
18
|
+
classification in every layout, including aliases. Existing nested scaffold
|
|
19
|
+
defaults remain unchanged. No server, listener or application policy changes.
|
|
20
|
+
|
|
21
|
+
## 0.27.1
|
|
22
|
+
|
|
23
|
+
- Preserve the previous `apps/<id>/arcane-sw.js` and `arcane-offline.json`
|
|
24
|
+
endpoints when generating a root-layout PWA. Both use the canonical worker
|
|
25
|
+
and current root inventory, including navigation aliases and installed npm
|
|
26
|
+
resource paths; portable output also retains deployment-relative URLs.
|
|
27
|
+
- Serve those previous worker and inventory endpoints directly during SDK
|
|
28
|
+
development instead of redirecting worker-script requests. Retain ordinary
|
|
29
|
+
browser update/activation, caches, application identity and saved data.
|
|
30
|
+
- Keep root layout optional and existing multi-app and Node server behavior
|
|
31
|
+
unchanged. Installed-application execution remains consumer-owned.
|
|
32
|
+
|
|
3
33
|
## 0.27.0
|
|
4
34
|
|
|
5
35
|
- Add optional standalone `appsRoot: "."` discovery and `new`/`init --apps-root .`.
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.28.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
|
|
|
@@ -33,6 +33,12 @@ Select `appsRoot: "."` for a standalone root app, or scaffold one with
|
|
|
33
33
|
`arcane new my-app --apps-root .`. After npm installation, `arcane import-map`
|
|
34
34
|
refreshes managed maps and the root app's static PWA/navigation files for ordinary
|
|
35
35
|
static hosting. Portable app packages still contain their selected runtime.
|
|
36
|
+
Root/direct maps use `arcane-os/modules/<filename>` and
|
|
37
|
+
`arcane-os/entities/<filename>` (including extensions) plus the focused lowercase
|
|
38
|
+
exports, rather than `arcane/*` aliases. These paths resolve to the actual npm
|
|
39
|
+
files, preserving relative component URLs. New root apps put the SDK in runtime
|
|
40
|
+
`dependencies`; init preserves existing runtime/optional declarations and promotes
|
|
41
|
+
a root app's SDK development declaration without changing its other packages.
|
|
36
42
|
Existing `apps/<id>`, virtual `/arcane` routes, and materialized workspaces remain
|
|
37
43
|
supported. Node services continue to
|
|
38
44
|
use the installed CLI or public imports such as `arcane-os/mail`.
|
|
@@ -89,7 +89,7 @@ export function getPwaInstall() {
|
|
|
89
89
|
|
|
90
90
|
async function loadInstallStorage() {
|
|
91
91
|
if (!globalThis.dbopfs) {
|
|
92
|
-
await import('arcane/DBOPFS');
|
|
92
|
+
await import('arcane-os/modules/DBOPFS.js');
|
|
93
93
|
}
|
|
94
94
|
const storage = globalThis.dbopfs;
|
|
95
95
|
if (!storage) {
|
|
@@ -273,8 +273,8 @@ export function mountPwaInstallPrompt({appName = ''} = {}) {
|
|
|
273
273
|
if (!globalThis.document) return null;
|
|
274
274
|
// Both modules may start independently; saved theme loading is not a barrier.
|
|
275
275
|
await Promise.all([
|
|
276
|
-
import('arcane/HTMLImport'),
|
|
277
|
-
import('arcane/ThemeBootstrap')
|
|
276
|
+
import('arcane-os/modules/HTMLImport.js'),
|
|
277
|
+
import('arcane-os/modules/ThemeBootstrap.js')
|
|
278
278
|
]);
|
|
279
279
|
if (owner.state.status === 'disposed') return null;
|
|
280
280
|
if (!document.body) {
|
|
@@ -295,7 +295,7 @@ export function mountPwaInstallPrompt({appName = ''} = {}) {
|
|
|
295
295
|
host.hidden = true;
|
|
296
296
|
host.dataset.appName = String(appName);
|
|
297
297
|
host.dataset.arcanePwaInstall = '';
|
|
298
|
-
host.setAttribute('href', new URL('../components/pwa-install.html', import.meta.resolve('arcane/HTMLImport')).href);
|
|
298
|
+
host.setAttribute('href', new URL('../components/pwa-install.html', import.meta.resolve('arcane-os/modules/HTMLImport.js')).href);
|
|
299
299
|
return new Promise(function waitForInstallComponent(resolve, reject) {
|
|
300
300
|
const observer = new MutationObserver(function observeRemovedInstallComponent() {
|
|
301
301
|
if (!host.isConnected) cancelMount();
|
package/browser-runtime/pwa.mjs
CHANGED
|
@@ -156,7 +156,7 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
|
|
|
156
156
|
|
|
157
157
|
async function loadCheckStorage() {
|
|
158
158
|
if (!globalThis.dbopfs) {
|
|
159
|
-
await import('arcane/DBOPFS');
|
|
159
|
+
await import('arcane-os/modules/DBOPFS.js');
|
|
160
160
|
}
|
|
161
161
|
const storage = globalThis.dbopfs;
|
|
162
162
|
if (!storage) {
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
"moduleSystem": "ESM"
|
|
8
8
|
},
|
|
9
9
|
"memberCount": 210,
|
|
10
|
+
"runtimeSubpathPatterns": {
|
|
11
|
+
"arcane-os/modules/*": "./runtime/arcane/modules/*",
|
|
12
|
+
"arcane-os/entities/*": "./runtime/arcane/entities/*"
|
|
13
|
+
},
|
|
10
14
|
"members": [
|
|
11
15
|
{
|
|
12
16
|
"id": "root:APP_BUNDLE_DESCRIPTOR_NAME",
|
|
@@ -146,7 +146,7 @@ all four source and destination paths. Package selection and SDK
|
|
|
146
146
|
version come from the installed dependency, not a generated runtime lock.
|
|
147
147
|
|
|
148
148
|
`arcane import-map`, `arcane dev`, packaging, and generated PWA inventories use
|
|
149
|
-
the same actual npm destinations. For example, `arcane/HTMLImport` resolves to
|
|
149
|
+
the same actual npm destinations. For example, `arcane-os/modules/HTMLImport.js` resolves to
|
|
150
150
|
`./node_modules/arcane-os/runtime/arcane/modules/HTMLImport.js`, and
|
|
151
151
|
`arcane-os/event-manager` resolves to
|
|
152
152
|
`./node_modules/arcane-os/browser-runtime/event-manager.mjs`. A normal static
|
|
@@ -180,6 +180,20 @@ query strings and fragments. It preserves authored files at those destinations.
|
|
|
180
180
|
For a direct-installed root PWA it generates the static PWA records at the root,
|
|
181
181
|
so normal static hosting needs no SDK request handler or runtime copy.
|
|
182
182
|
|
|
183
|
+
Direct-installed root maps expose `arcane-os/modules/<filename>` and
|
|
184
|
+
`arcane-os/entities/<filename>` (including extensions), plus the existing
|
|
185
|
+
focused lowercase package exports. They omit the old `arcane/*` and
|
|
186
|
+
`./arcane/*` keys. Real installed `runtime/arcane/` directories are unchanged.
|
|
187
|
+
Physical, nested and virtual layouts retain their established aliases and also
|
|
188
|
+
support the package-namespaced module/entity keys. Relative module and component
|
|
189
|
+
URLs still resolve from the actual selected file, not a reexport wrapper.
|
|
190
|
+
|
|
191
|
+
New root apps declare the SDK in `dependencies` because serving their npm
|
|
192
|
+
resources requires the installed package at runtime. Root `init` promotes an
|
|
193
|
+
existing SDK development declaration while preserving other dependencies.
|
|
194
|
+
Existing runtime or optional declarations retain their classification across
|
|
195
|
+
layouts; the default nested scaffold continues to use its development dependency.
|
|
196
|
+
|
|
183
197
|
## Installed SDK runtime materialization
|
|
184
198
|
|
|
185
199
|
`materializeInstalledSdkRuntime()` is the Node entrypoint for refreshing one
|
package/docs/reference/pwa.md
CHANGED
|
@@ -59,6 +59,18 @@ authored `manifest.id` remains authoritative. Packaged default identity remains
|
|
|
59
59
|
fragments and lead to the selected root document. No stored application data is
|
|
60
60
|
rewritten by changing the layout.
|
|
61
61
|
|
|
62
|
+
Root PWA generation also retains `apps/<id>/arcane-sw.js` and
|
|
63
|
+
`apps/<id>/arcane-offline.json`. These are ordinary generated files served at
|
|
64
|
+
their original URLs, including on a static host. The same canonical worker and
|
|
65
|
+
current inventory use root resource/navigation URLs, rebased for the previous
|
|
66
|
+
scope when deployed beneath a directory. An existing worker's inventory refresh
|
|
67
|
+
can learn the new navigation destinations; the browser can update the worker at
|
|
68
|
+
its retained script URL through normal update and activation. No cache deletion,
|
|
69
|
+
forced activation, user-data migration or application-owned server handler is
|
|
70
|
+
introduced. Offline clients require a later successful connection to receive
|
|
71
|
+
updated files; fixture coverage does not establish a particular installed app's
|
|
72
|
+
actual browser lifecycle.
|
|
73
|
+
|
|
62
74
|
`offline.include` and `offline.exclude` select literal paths or directory
|
|
63
75
|
prefixes from the selected emitted inventory. An omitted or empty include list
|
|
64
76
|
selects that inventory; exclusions subtract from it. App files use app-relative
|
|
@@ -5,11 +5,16 @@ The npm package exposes a Node.js ESM control plane, the portable
|
|
|
5
5
|
`arcane-os/preference-store`, `arcane-os/speech-playback`,
|
|
6
6
|
`arcane-os/speech-text`, `arcane-os/ai/tool-text-stream`, and `arcane-os/browser-device` entrypoints, and the browser-only
|
|
7
7
|
`arcane-os/pwa`, `arcane-os/ai/browser-wasm` and `arcane-os/ai/browser-speech` entrypoints.
|
|
8
|
+
The `arcane-os/modules/<filename>` and `arcane-os/entities/<filename>` paths
|
|
9
|
+
resolve directly to the existing runtime files, with their actual extension.
|
|
10
|
+
They are public Node package exports and managed browser keys, without a wrapper.
|
|
11
|
+
For example, `import.meta.resolve('arcane-os/modules/WaitForComponent.js')`
|
|
12
|
+
retains the module directory needed to resolve `../components/modal.html`.
|
|
8
13
|
Lowercase runtime subpaths also expose existing module namespaces through both
|
|
9
14
|
Node package exports and managed browser imports, as listed below. Established
|
|
10
15
|
browser names such as `arcane/AIProviderRuntime`, `arcane/AIRuntimeState`, and
|
|
11
|
-
`arcane/ThemeBootstrap` remain
|
|
12
|
-
|
|
16
|
+
`arcane/ThemeBootstrap` remain compatibility names in physical/nested maps;
|
|
17
|
+
direct-installed root maps use the package namespace instead. Applications call `globalThis.Arcane` for capability-gated host
|
|
13
18
|
behavior.
|
|
14
19
|
|
|
15
20
|
This page is the canonical inventory for every JavaScript name reachable through `package.json#exports`. The same binding can appear at the root and a focused subpath; those entrypoints are listed together. The root workspace `discoverApps` and the low-level packager `discoverApps` are intentionally separate records because they are different functions.
|
|
@@ -36,6 +41,8 @@ runtime layouts.
|
|
|
36
41
|
| `arcane-os/packager` | Low-level browser app packager. |
|
|
37
42
|
| `arcane-os/release-bundle` | Deterministic external release bundles. |
|
|
38
43
|
| `arcane-os/event-manager` | Central synchronous events, complete time-travel history, playback, and optional DOM instrumentation. |
|
|
44
|
+
| `arcane-os/modules/<filename>` | Direct existing runtime module, including its `.js` or `.mjs` extension. |
|
|
45
|
+
| `arcane-os/entities/<filename>` | Direct existing runtime entity, including its extension. |
|
|
39
46
|
| `arcane-os/logging` | Shared console diagnostics controlled by the existing `user.developer` preference. |
|
|
40
47
|
| `arcane-os/preference-store` | Portable preference records and injected storage adapters. |
|
|
41
48
|
| `arcane-os/ai` | Existing `AI.js` provider-neutral inference and speech namespace. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"./toolchain": "./src/toolchain.mjs",
|
|
14
14
|
"./events": "./src/events.mjs",
|
|
15
15
|
"./event-manager": "./src/event-manager.mjs",
|
|
16
|
+
"./modules/*": "./runtime/arcane/modules/*",
|
|
17
|
+
"./entities/*": "./runtime/arcane/entities/*",
|
|
16
18
|
"./logging": "./browser-runtime/logging.mjs",
|
|
17
19
|
"./browser-device": "./browser-runtime/browser-device.mjs",
|
|
18
20
|
"./pwa": "./browser-runtime/pwa.mjs",
|
package/src/dev-server.mjs
CHANGED
|
@@ -1081,7 +1081,10 @@ async function startOwnedDevServer({
|
|
|
1081
1081
|
navigationAliases,
|
|
1082
1082
|
basePath: '/',
|
|
1083
1083
|
appBase,
|
|
1084
|
-
...(rootApp ? {
|
|
1084
|
+
...(rootApp ? {
|
|
1085
|
+
installationId:`/apps/${routeSet.appId}/`,
|
|
1086
|
+
legacyAppPath:`apps/${routeSet.appId}`
|
|
1087
|
+
} : {}),
|
|
1085
1088
|
runtimeBase: selectedRoutes.browserRuntimeBase
|
|
1086
1089
|
?? selectedRoutes.installed?.browserRuntimeBase ?? '/arcane/sdk/',
|
|
1087
1090
|
mode: 'development'
|
|
@@ -1109,8 +1112,8 @@ async function startOwnedDevServer({
|
|
|
1109
1112
|
pwaStates.set(selectedRoutes, state);
|
|
1110
1113
|
if (selectedRoutes === currentSourceRoutes) currentPwaState = state;
|
|
1111
1114
|
}
|
|
1112
|
-
if (targetPath
|
|
1113
|
-
|| targetPath
|
|
1115
|
+
if (targetPath.endsWith('/arcane-sw.js')
|
|
1116
|
+
|| targetPath.endsWith('/arcane-offline.json')) {
|
|
1114
1117
|
if (!state.inventoryTask) {
|
|
1115
1118
|
// Timestamp selection before traversal, so an older snapshot
|
|
1116
1119
|
// finishing later cannot acquire a newer HTTP modification date.
|
|
@@ -1173,10 +1176,13 @@ async function startOwnedDevServer({
|
|
|
1173
1176
|
const target=parseRequestTarget(request.url);
|
|
1174
1177
|
if(!target){deny(response,400,'Invalid request path.');return;}
|
|
1175
1178
|
const {segments}=target;
|
|
1176
|
-
const generatedPwaPath = ['/arcane.webmanifest', '/arcane-offline.json', '/arcane-sw.js', '/arcane-pwa.mjs']
|
|
1177
|
-
.includes(target.path);
|
|
1178
1179
|
const legacyAppRequest = mode === 'source' && routeSet.config.appsRoot === '.'
|
|
1179
1180
|
&& segments[0] === 'apps' && segments[1] === routeSet.appId;
|
|
1181
|
+
const legacyPwaRequest = legacyAppRequest && segments.length === 3
|
|
1182
|
+
&& ['arcane-sw.js', 'arcane-offline.json'].includes(segments[2]);
|
|
1183
|
+
const generatedPwaPath = legacyPwaRequest
|
|
1184
|
+
|| ['/arcane.webmanifest', '/arcane-offline.json', '/arcane-sw.js', '/arcane-pwa.mjs']
|
|
1185
|
+
.includes(target.path);
|
|
1180
1186
|
const requestedMapping = currentSourceRoutes.mappings.find(function currentRequestMapping(route) {
|
|
1181
1187
|
return route.prefix.every(function currentRequestSegment(segment,index) {
|
|
1182
1188
|
return segments[index] === segment;
|
|
@@ -1186,7 +1192,7 @@ async function startOwnedDevServer({
|
|
|
1186
1192
|
const selectedRoutes = mode === 'source' && (appRequest || segments.length === 0 || generatedPwaPath)
|
|
1187
1193
|
? await refreshSourceRoutes() : currentSourceRoutes;
|
|
1188
1194
|
const pwaEnabled = mode === 'source' ? selectedRoutes.app?.pwa?.enabled === true : routeSet.pwa;
|
|
1189
|
-
if (legacyAppRequest) {
|
|
1195
|
+
if (legacyAppRequest && !(pwaEnabled && legacyPwaRequest)) {
|
|
1190
1196
|
const legacyPath = target.pathname.slice(`/apps/${routeSet.appId}`.length);
|
|
1191
1197
|
const location = !legacyPath || legacyPath === '/' || legacyPath === '/index.html'
|
|
1192
1198
|
? selectedRoutes.startPath : legacyPath;
|
package/src/import-map.mjs
CHANGED
|
@@ -1390,6 +1390,9 @@ export async function buildImportMap({files,signal,version=SDK_VERSION,encodePat
|
|
|
1390
1390
|
for(const relative of [...inventory].sort(compareText)){
|
|
1391
1391
|
if(JAVASCRIPT_EXTENSION.test(relative)){
|
|
1392
1392
|
const target=runtimeTarget(relative);
|
|
1393
|
+
if(relative.startsWith('modules/')||relative.startsWith('entities/')){
|
|
1394
|
+
registerSpecifier(namedRegistry,`arcane-os/${relative}`,target);
|
|
1395
|
+
}
|
|
1393
1396
|
registerSpecifier(namedRegistry,target,target);
|
|
1394
1397
|
}
|
|
1395
1398
|
}
|
|
@@ -1453,7 +1456,7 @@ async function physicalRuntime(workspaceRoot,signal){
|
|
|
1453
1456
|
return {files};
|
|
1454
1457
|
}
|
|
1455
1458
|
|
|
1456
|
-
async function managedImportMapBuild(resolvedWorkspace,signal,pwaEnabled=false){
|
|
1459
|
+
async function managedImportMapBuild(resolvedWorkspace,resolvedApp,signal,pwaEnabled=false){
|
|
1457
1460
|
const installed=await readInstalledSdkLayout(resolvedWorkspace);
|
|
1458
1461
|
const [runtime,version]=await Promise.all([
|
|
1459
1462
|
installed?installedRuntimeFiles(resolvedWorkspace,installed,signal):physicalRuntime(resolvedWorkspace,signal),
|
|
@@ -1462,13 +1465,15 @@ async function managedImportMapBuild(resolvedWorkspace,signal,pwaEnabled=false){
|
|
|
1462
1465
|
const built=await buildImportMap({files:runtime.files,signal,version});
|
|
1463
1466
|
if(installed?.direct){
|
|
1464
1467
|
const imports={};
|
|
1468
|
+
const rootApplication=samePath(resolvedWorkspace,resolvedApp);
|
|
1465
1469
|
function installedBrowserUrl(value){
|
|
1466
1470
|
return value.startsWith('./')
|
|
1467
1471
|
?`./${installedRuntimeTarget(value.slice(2),installed,{browser:true})}`:value;
|
|
1468
1472
|
}
|
|
1469
1473
|
for(const [specifier,target] of Object.entries(built.imports)){
|
|
1474
|
+
if(rootApplication&&specifier.startsWith('arcane/'))continue;
|
|
1470
1475
|
const selected=installedBrowserUrl(target);
|
|
1471
|
-
imports[specifier]=selected;
|
|
1476
|
+
if(!rootApplication)imports[specifier]=selected;
|
|
1472
1477
|
// Relative module imports and bare names must resolve to the same instance.
|
|
1473
1478
|
imports[installedBrowserUrl(specifier)]=selected;
|
|
1474
1479
|
}
|
|
@@ -2846,7 +2851,7 @@ async function generateImportMapUnlocked({
|
|
|
2846
2851
|
}catch(error){
|
|
2847
2852
|
if(error?.code!=='ENOENT')throw error;
|
|
2848
2853
|
}
|
|
2849
|
-
const {built,json,version}=await managedImportMapBuild(resolvedWorkspace,signal,pwaEnabled);
|
|
2854
|
+
const {built,json,version}=await managedImportMapBuild(resolvedWorkspace,resolvedApp,signal,pwaEnabled);
|
|
2850
2855
|
const renderedDocuments=documentStates.map(item=>({
|
|
2851
2856
|
...item,
|
|
2852
2857
|
content:rewriteAssetReferences(renderManagedHtml(item.html,json,item.baseHref),{
|
package/src/packager/core.mjs
CHANGED
|
@@ -757,6 +757,7 @@ async function replaceDirectory(stagingRoot,outputRoot){
|
|
|
757
757
|
async function packageWithContext(context,options={}){
|
|
758
758
|
const {signal,onEvent,browserPwa=true}=options;
|
|
759
759
|
const pwaEnabled=browserPwa&&context.config.pwa?.enabled===true;
|
|
760
|
+
const legacyAppPath=context.rootConfig.appsRoot==='.'?`apps/${context.appId}`:undefined;
|
|
760
761
|
const appPath=appRelativeRoot(context.rootConfig,context.appId);
|
|
761
762
|
const entryPath=appPackagePath(context,context.config.entry);
|
|
762
763
|
const inspected=await inspectContext(context,{signal});
|
|
@@ -775,7 +776,11 @@ async function packageWithContext(context,options={}){
|
|
|
775
776
|
PWA_MANIFEST_NAME,
|
|
776
777
|
PWA_OFFLINE_MANIFEST_NAME,
|
|
777
778
|
PWA_WORKER_NAME,
|
|
778
|
-
PWA_BOOTSTRAP_NAME
|
|
779
|
+
PWA_BOOTSTRAP_NAME,
|
|
780
|
+
...(legacyAppPath?[
|
|
781
|
+
`${legacyAppPath}/${PWA_WORKER_NAME}`,
|
|
782
|
+
`${legacyAppPath}/${PWA_OFFLINE_MANIFEST_NAME}`
|
|
783
|
+
]:[])
|
|
779
784
|
]:[])
|
|
780
785
|
].sort(compareText)
|
|
781
786
|
};
|
|
@@ -962,6 +967,7 @@ async function packageWithContext(context,options={}){
|
|
|
962
967
|
entry:packageResourceUrl(entryPath)
|
|
963
968
|
},
|
|
964
969
|
appPath,
|
|
970
|
+
...(legacyAppPath?{legacyAppPath}:{}),
|
|
965
971
|
...(installed?.direct?{runtimeBase:`.${installed.browserRuntimeBase}`} : {}),
|
|
966
972
|
...(navigationAliases?{navigationAliases}:{}),
|
|
967
973
|
sdkVersion:assetVersion,
|
|
@@ -974,7 +980,9 @@ async function packageWithContext(context,options={}){
|
|
|
974
980
|
if(inventory.has(artifact.path)){
|
|
975
981
|
fail(`Package content overlaps generated PWA file: ${artifact.path}.`);
|
|
976
982
|
}
|
|
977
|
-
|
|
983
|
+
const artifactPath=path.join(stagingRoot,...artifact.path.split('/'));
|
|
984
|
+
await mkdir(path.dirname(artifactPath),{recursive:true});
|
|
985
|
+
await writeFile(artifactPath,artifact.content,'utf8');
|
|
978
986
|
files.push(artifact.path);
|
|
979
987
|
}
|
|
980
988
|
for(const [documentPath,document] of pwaDocumentSources){
|
package/src/pwa.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Is from 'strong-type';
|
|
2
2
|
import {randomUUID} from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import {createPwaWorkerScript} from './pwa-worker.mjs';
|
|
4
5
|
import {versionAssetUrl} from './import-map.mjs';
|
|
5
6
|
|
|
@@ -203,6 +204,7 @@ export function createPwaArtifacts(
|
|
|
203
204
|
runtimeBase = './arcane/sdk/',
|
|
204
205
|
appBase,
|
|
205
206
|
installationId,
|
|
207
|
+
legacyAppPath,
|
|
206
208
|
appPath = '',
|
|
207
209
|
navigationAliases,
|
|
208
210
|
revision
|
|
@@ -305,18 +307,60 @@ controller.ready.catch(
|
|
|
305
307
|
const entryAssets = {manifest: PWA_MANIFEST_NAME, bootstrap: PWA_BOOTSTRAP_NAME};
|
|
306
308
|
const manifestHref = resourceUrl(basePath, entryAssets.manifest);
|
|
307
309
|
const bootstrapHref = resourceUrl(basePath, entryAssets.bootstrap);
|
|
310
|
+
const generatedFiles = [
|
|
311
|
+
{path: PWA_MANIFEST_NAME, content: json(manifest)},
|
|
312
|
+
{path: PWA_OFFLINE_MANIFEST_NAME, content: json(offlineManifest)},
|
|
313
|
+
{
|
|
314
|
+
path: PWA_WORKER_NAME,
|
|
315
|
+
content: createPwaWorkerScript(offlineManifest, `${runtimeBase}pwa.mjs`)
|
|
316
|
+
},
|
|
317
|
+
{path: PWA_BOOTSTRAP_NAME, content: bootstrap}
|
|
318
|
+
];
|
|
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
|
+
}
|
|
308
360
|
return {
|
|
309
361
|
manifest,
|
|
310
362
|
offlineManifest,
|
|
311
|
-
files:
|
|
312
|
-
{path: PWA_MANIFEST_NAME, content: json(manifest)},
|
|
313
|
-
{path: PWA_OFFLINE_MANIFEST_NAME, content: json(offlineManifest)},
|
|
314
|
-
{
|
|
315
|
-
path: PWA_WORKER_NAME,
|
|
316
|
-
content: createPwaWorkerScript(offlineManifest, `${runtimeBase}pwa.mjs`)
|
|
317
|
-
},
|
|
318
|
-
{path: PWA_BOOTSTRAP_NAME, content: bootstrap}
|
|
319
|
-
],
|
|
363
|
+
files: generatedFiles,
|
|
320
364
|
entryAssets,
|
|
321
365
|
entryMarkup: `<link rel="manifest" href="${htmlAttribute(manifestHref)}">\n`
|
|
322
366
|
+ `<script type="module" async data-arcane-pwa src="${htmlAttribute(bootstrapHref)}"></script>\n`
|
package/src/scaffold.mjs
CHANGED
|
@@ -192,21 +192,26 @@ async function prepareExistingPackage(workspaceRoot,files,existingPackage,sdkDec
|
|
|
192
192
|
if(conflicts.length){
|
|
193
193
|
fail(`Existing package.json conflicts with Arcane setup; no values were overwritten. Resolve: ${conflicts.join('; ')}.`);
|
|
194
194
|
}
|
|
195
|
+
const dependencyGroup=sdkDeclaration.dependencyGroup==='optionalDependencies'
|
|
196
|
+
?'optionalDependencies'
|
|
197
|
+
:generated.dependencies||sdkDeclaration.dependencyGroup==='dependencies'
|
|
198
|
+
?'dependencies':'devDependencies';
|
|
195
199
|
const merged={
|
|
196
200
|
...existing,
|
|
197
201
|
private:true,
|
|
198
202
|
type:'module',
|
|
199
203
|
scripts:{...generated.scripts,...(existing.scripts||{})},
|
|
200
|
-
|
|
201
|
-
...(existing
|
|
204
|
+
[dependencyGroup]:{
|
|
205
|
+
...(existing[dependencyGroup]||{}),
|
|
202
206
|
[sdkDeclaration.dependencyName]:sdkDeclaration.specifier
|
|
203
207
|
},
|
|
204
208
|
engines:{...generated.engines,...(existing.engines||{})}
|
|
205
209
|
};
|
|
206
|
-
if(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
if(dependencyGroup!=='devDependencies'
|
|
211
|
+
&&existing.devDependencies?.[sdkDeclaration.dependencyName]!==undefined){
|
|
212
|
+
merged.devDependencies={...existing.devDependencies};
|
|
213
|
+
delete merged.devDependencies[sdkDeclaration.dependencyName];
|
|
214
|
+
if(Object.keys(merged.devDependencies).length===0)delete merged.devDependencies;
|
|
210
215
|
}
|
|
211
216
|
if(JSON.stringify(existing)===JSON.stringify(merged)){
|
|
212
217
|
return {exists:true,updated:false};
|
|
@@ -159,7 +159,7 @@ appropriate.
|
|
|
159
159
|
|
|
160
160
|
- Use plain JavaScript, HTML, and CSS; do not introduce TypeScript or TSX.
|
|
161
161
|
- Keep reusable portable mechanisms in the Arcane SDK and app-specific behavior under \`${appPrefix||'./'}\`.
|
|
162
|
-
- Keep \`${runtimePrefix}/css/theme.css\` before app styles and import
|
|
162
|
+
- Keep \`${runtimePrefix}/css/theme.css\` before app styles and import \`${directRuntime?'arcane-os/modules/ThemeBootstrap.js':'arcane/ThemeBootstrap'}\` before app code runs.
|
|
163
163
|
- Use \`rgb(...)\` or \`rgba(...)\` for new CSS colors.
|
|
164
164
|
- Build one named app and one explicit target at a time. Native targets may be unavailable until their adapters are installed.
|
|
165
165
|
- Preserve complete application, model, document, message, log, diagnostic, process, and tool content. Do not truncate, clip, tail, elide, or silently discard it.
|
|
@@ -181,7 +181,7 @@ npm run dev
|
|
|
181
181
|
|
|
182
182
|
Open the loopback URL printed by the development server. ${directRuntime?'This root app reads SDK files directly from its installed npm package; an ordinary static host uses the same resource paths.':'This app uses the existing physical arcane/ runtime layout.'} The SDK server does not expose an Ollama HTTP endpoint.
|
|
183
183
|
|
|
184
|
-
Commit the generated \`package-lock.json\` after dependency installation. CI intentionally uses \`npm ci\` and therefore requires that lock. Before the SDK is published, install a locally packed \`${SDK_NAME}\` \`.tgz\` with \`npm install --save-dev --save-exact <path-to-tarball>\`; keep that tarball at the lock file's relative path for repeatable local \`npm ci\` runs.
|
|
184
|
+
Commit the generated \`package-lock.json\` after dependency installation. CI intentionally uses \`npm ci\` and therefore requires that lock. ${directRuntime?'The SDK is a runtime dependency: keep it installed when serving this app directly from node_modules. ':''}Before the SDK is published, install a locally packed \`${SDK_NAME}\` \`.tgz\` with \`npm install ${directRuntime?'--save-prod':'--save-dev'} --save-exact <path-to-tarball>\`; keep that tarball at the lock file's relative path for repeatable local \`npm ci\` runs.
|
|
185
185
|
|
|
186
186
|
## Optional browser release commands
|
|
187
187
|
|
|
@@ -199,7 +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
|
-
Named
|
|
202
|
+
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
203
|
map to \`dist/${appId}\` without running application tests. Run \`verify\` only when
|
|
204
204
|
the user explicitly selects verification or a release artifact that requires it;
|
|
205
205
|
\`bundle\` creates the distributable archive and \`run\` launches the selected
|
|
@@ -231,7 +231,7 @@ Every browser release also carries Arcane OS licensing material under \`${direct
|
|
|
231
231
|
'run:browser':'arcane run --target browser'
|
|
232
232
|
}:{})
|
|
233
233
|
},
|
|
234
|
-
devDependencies:{
|
|
234
|
+
[directRuntime?'dependencies':'devDependencies']:{
|
|
235
235
|
[sdkDependencyName]:sdkDependencySpecifier
|
|
236
236
|
},
|
|
237
237
|
engines:{node:'>=22.23.2'}
|
|
@@ -369,9 +369,11 @@ ${bootstrapMarkup}</head>
|
|
|
369
369
|
files.set(`${appPrefix}modules/arcane.importmap.json`,json({imports:{}}));
|
|
370
370
|
}
|
|
371
371
|
const themeSpecifier=namedImports
|
|
372
|
-
?'arcane/ThemeBootstrap
|
|
372
|
+
?directRuntime?'arcane-os/modules/ThemeBootstrap.js':'arcane/ThemeBootstrap'
|
|
373
|
+
:`${appsRoot==='.'?'../':'../../../'}${runtimePrefix.slice(2)}/modules/ThemeBootstrap.js`;
|
|
373
374
|
const appDataSpecifier=namedImports
|
|
374
|
-
?'arcane/
|
|
375
|
+
?directRuntime?'arcane-os/app-data-scope':'arcane/AppDataScope'
|
|
376
|
+
:`${appsRoot==='.'?'../':'../../../'}${runtimePrefix.slice(2)}/modules/AppDataScope.js`;
|
|
375
377
|
const strongTypeSpecifier=namedImports
|
|
376
378
|
?'strong-type':directRuntime?`../${sdkPackageSource}/runtime/strong-type/index.js`:'../../../arcane/dependencies/strong-type/index.js';
|
|
377
379
|
files.set(`${appPrefix}modules/App.js`,`import Is from '${strongTypeSpecifier}';
|
package/src/toolchain.mjs
CHANGED
|
@@ -361,6 +361,7 @@ async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,
|
|
|
361
361
|
basePath:'/',
|
|
362
362
|
appBase:'/',
|
|
363
363
|
installationId:`/apps/${appId}/`,
|
|
364
|
+
legacyAppPath:`apps/${appId}`,
|
|
364
365
|
runtimeBase:installed.browserRuntimeBase,
|
|
365
366
|
mode:'development',
|
|
366
367
|
navigationAliases
|
package/src/workspace.mjs
CHANGED
|
@@ -117,7 +117,7 @@ export function resolveSdkPackageDeclaration(rootPackage,{
|
|
|
117
117
|
}={}){
|
|
118
118
|
if(!isObject(rootPackage))fail('package.json must contain a JSON object.');
|
|
119
119
|
const candidates=[];
|
|
120
|
-
for(const groupName of ['devDependencies','dependencies']){
|
|
120
|
+
for(const groupName of ['devDependencies','dependencies','optionalDependencies']){
|
|
121
121
|
const group=rootPackage[groupName];
|
|
122
122
|
if(group===undefined)continue;
|
|
123
123
|
if(!isObject(group))fail(`package.json ${groupName} must be a JSON object.`);
|