arcane-os 0.27.0 → 0.27.1
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 +12 -0
- package/README.md +1 -1
- package/docs/reference/pwa.md +12 -0
- package/package.json +1 -1
- package/src/dev-server.mjs +12 -6
- package/src/packager/core.mjs +10 -2
- package/src/pwa.mjs +53 -9
- package/src/toolchain.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.27.1
|
|
4
|
+
|
|
5
|
+
- Preserve the previous `apps/<id>/arcane-sw.js` and `arcane-offline.json`
|
|
6
|
+
endpoints when generating a root-layout PWA. Both use the canonical worker
|
|
7
|
+
and current root inventory, including navigation aliases and installed npm
|
|
8
|
+
resource paths; portable output also retains deployment-relative URLs.
|
|
9
|
+
- Serve those previous worker and inventory endpoints directly during SDK
|
|
10
|
+
development instead of redirecting worker-script requests. Retain ordinary
|
|
11
|
+
browser update/activation, caches, application identity and saved data.
|
|
12
|
+
- Keep root layout optional and existing multi-app and Node server behavior
|
|
13
|
+
unchanged. Installed-application execution remains consumer-owned.
|
|
14
|
+
|
|
3
15
|
## 0.27.0
|
|
4
16
|
|
|
5
17
|
- 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.27.
|
|
22
|
+
This checkout defines the `0.27.1` 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
|
|
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
|
package/package.json
CHANGED
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/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/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
|