arcane-os 0.11.3 → 0.12.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 +18 -0
- package/NOTICE +1 -0
- package/README.md +24 -13
- package/browser-runtime/pwa.mjs +129 -0
- package/docs/architecture.md +28 -12
- package/docs/reference/cli.md +42 -16
- package/docs/reference/inventory/package-api.json +1 -1
- package/docs/reference/pwa.md +87 -22
- package/docs/reference/sdk-api.md +61 -27
- package/package.json +2 -1
- package/src/app-descriptor.mjs +43 -1
- package/src/cli/main.mjs +29 -13
- package/src/dev-server.mjs +387 -136
- package/src/pwa-worker.mjs +362 -136
- package/src/pwa.mjs +4 -1
- package/src/targets/index.mjs +5 -1
- package/src/toolchain.mjs +62 -12
|
@@ -3459,6 +3459,15 @@ async function useselectApp(...arguments_) {
|
|
|
3459
3459
|
|
|
3460
3460
|
Starts one owned browser development server with exact runtime/app route mappings and a caller-selected bind address.
|
|
3461
3461
|
|
|
3462
|
+
HTTPS serving uses the published `node-http-server` module. The SDK
|
|
3463
|
+
selects source routes and supplies generated representations; the module owns
|
|
3464
|
+
static-file conditional GET/HEAD handling and response delivery. The SDK
|
|
3465
|
+
retains modification dates for its generated representations. Unchanged
|
|
3466
|
+
resources can return `304` with no body while changed resources return their
|
|
3467
|
+
complete current representation. PEM-path listeners use module deployment;
|
|
3468
|
+
explicit raw `tls` options retain native HTTPS transport with the same module
|
|
3469
|
+
response and static-file operations.
|
|
3470
|
+
|
|
3462
3471
|
### Signature, modes, and result
|
|
3463
3472
|
|
|
3464
3473
|
```text
|
|
@@ -3466,53 +3475,64 @@ async startDevServer(options={})
|
|
|
3466
3475
|
```
|
|
3467
3476
|
|
|
3468
3477
|
Import it from `arcane-os`. Source mode accepts
|
|
3469
|
-
`{workspaceRoot=process.cwd(), appId, mode='source', host='127.0.0.1', port=0,
|
|
3470
|
-
|
|
3478
|
+
`{workspaceRoot=process.cwd(), appId, mode='source', host='127.0.0.1', port=0, httpPort=0,
|
|
3479
|
+
certPath, keyPath, tls, signal, onEvent}` and serves one validated
|
|
3471
3480
|
workspace application plus its complete SDK or integrated runtime. Packaged mode uses
|
|
3472
|
-
`{mode:'packaged', releaseRoot, host, port,
|
|
3473
|
-
complete selected release files.
|
|
3481
|
+
`{mode:'packaged', releaseRoot, workspaceRoot, host, port, httpPort, certPath, keyPath, tls,
|
|
3482
|
+
signal, onEvent}` and serves the complete selected release files.
|
|
3483
|
+
`host` defaults to `127.0.0.1` and accepts an
|
|
3474
3484
|
explicit network address or hostname. Use `0.0.0.0` for all IPv4 interfaces or
|
|
3475
|
-
`::` for the platform's IPv6 wildcard
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
`
|
|
3485
|
+
`::` for the platform's IPv6 wildcard listeners. `port` selects the HTTPS
|
|
3486
|
+
application port, and `httpPort` selects the paired HTTP redirect port. Each
|
|
3487
|
+
defaults to `0`, which asks the operating system for an available port.
|
|
3488
|
+
The SDK request hook returns `308` for HTTP with a `Location` pointing to the
|
|
3489
|
+
actual HTTPS port while preserving the original request path and query. Both listeners
|
|
3490
|
+
use the selected host.
|
|
3491
|
+
|
|
3492
|
+
Every source server and packaged browser preview enforces HTTPS, including
|
|
3493
|
+
localhost. The legacy `https` option is accepted but cannot disable it.
|
|
3494
|
+
Startup reads `.arcane/dev/server-cert.pem` and
|
|
3479
3495
|
`.arcane/dev/server-key.pem` relative to `workspaceRoot` unless explicit
|
|
3480
|
-
`certPath` and `keyPath` are supplied together
|
|
3481
|
-
|
|
3482
|
-
direct `tls` object instead supplies Node HTTPS server options, including
|
|
3496
|
+
`certPath` and `keyPath` are supplied together; relative paths resolve from the
|
|
3497
|
+
workspace. A direct `tls` object instead supplies Node HTTPS server options, including
|
|
3483
3498
|
`cert` and `key`, without reading certificate files. Keep private material
|
|
3484
3499
|
server-side. Missing PEM files and Node certificate/key parse errors reject
|
|
3485
3500
|
startup without falling back to HTTP. Node owns TLS option handling and the
|
|
3486
3501
|
handshake; browser trust and address matching are evaluated when a client
|
|
3487
|
-
connects. The CLI's `--public` selects
|
|
3502
|
+
connects. The CLI's `--public` selects the wildcard bind;
|
|
3488
3503
|
the API's `host` option alone changes only the bind address.
|
|
3489
3504
|
|
|
3490
|
-
The promise settles after
|
|
3505
|
+
The promise settles after both listeners are ready and resolves to
|
|
3491
3506
|
`{server, protocol, mode, workspaceRoot, appId, host, port, origin, cleanUrl, url,
|
|
3492
|
-
networkUrls, close, closed, lifecycle}`. `server` is the raw Node
|
|
3493
|
-
server; `protocol` is `'
|
|
3507
|
+
networkUrls, httpPort, httpOrigin, httpUrl, close, closed, lifecycle}`. `server` is the raw Node HTTPS
|
|
3508
|
+
server; `protocol` is `'https:'`.
|
|
3494
3509
|
`url` and `cleanUrl` are the same application URL. Wildcard listeners use
|
|
3495
3510
|
`localhost` in that local URL; `host` retains the actual bound address.
|
|
3511
|
+
`httpPort` is the actual HTTP listener port, `httpOrigin` is its HTTP origin,
|
|
3512
|
+
and `httpUrl` combines that origin with the application start path. These
|
|
3513
|
+
fields identify the redirect endpoint; `origin`, `url`, `cleanUrl`, and
|
|
3514
|
+
`networkUrls` identify HTTPS application endpoints.
|
|
3496
3515
|
`networkUrls` lists application URLs for applicable non-loopback interface
|
|
3497
3516
|
addresses discovered once at startup. These URLs are connection candidates,
|
|
3498
3517
|
not evidence of reachability from another device. The server adds no session
|
|
3499
3518
|
capability or authentication. In packaged mode, `workspaceRoot` and `appId`
|
|
3500
3519
|
are `null`.
|
|
3501
3520
|
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
stores. Each client must trust the issuing CA and open an address covered by the
|
|
3521
|
+
PEM-path startup reads the selected pair. The server does not create
|
|
3522
|
+
certificates or modify trust stores. Each client must trust the issuing CA and open an address covered by the
|
|
3505
3523
|
server certificate. Lifecycle events and CLI summaries exclude TLS options and
|
|
3506
3524
|
private key contents. See [development HTTPS setup](cli.md#development-https-setup).
|
|
3507
3525
|
|
|
3508
|
-
Starting the server opens
|
|
3509
|
-
backpressured `server.starting` and `server.started` events.
|
|
3510
|
-
|
|
3526
|
+
Starting the server opens both selected listeners and emits awaited,
|
|
3527
|
+
backpressured `server.starting` and `server.started` events. `server.started`
|
|
3528
|
+
includes `httpPort`, `httpOrigin`, and `httpUrl` alongside the HTTPS endpoint.
|
|
3529
|
+
Request failures emit `server.request.failed`; shutdown emits `server.stopped` after owned
|
|
3511
3530
|
requests and event delivery drain. Call `await result.close()` in a `finally`
|
|
3512
3531
|
block, or abort `signal`; `close()` is idempotent and returns the
|
|
3513
|
-
same settlement represented by both `closed` and `lifecycle`.
|
|
3514
|
-
|
|
3515
|
-
|
|
3532
|
+
same settlement represented by both `closed` and `lifecycle`. Closing the
|
|
3533
|
+
operation closes both listeners. An error from either listener or an
|
|
3534
|
+
event-callback failure closes both and rejects the lifecycle. Invalid
|
|
3535
|
+
mode/host/port/httpPort, malformed workspace or release content, an occupied port,
|
|
3516
3536
|
or an already-aborted signal rejects startup.
|
|
3517
3537
|
|
|
3518
3538
|
### Availability and normalization
|
|
@@ -3907,9 +3927,16 @@ async function usedescribeTargets(...arguments_) {
|
|
|
3907
3927
|
Starts one owned browser development server for the selected application.
|
|
3908
3928
|
|
|
3909
3929
|
`https`, `certPath`, `keyPath`, and `tls` follow the
|
|
3910
|
-
[`startDevServer()` TLS contract](#startdevserver), alongside `host` and
|
|
3911
|
-
|
|
3912
|
-
|
|
3930
|
+
[`startDevServer()` TLS contract](#startdevserver), alongside `host`, `port`, and
|
|
3931
|
+
`httpPort`. `port` selects HTTPS and `httpPort` selects the HTTP `308` redirect
|
|
3932
|
+
listener; each defaults to an available port.
|
|
3933
|
+
The operation refreshes the selected authored descriptor's `arcane-package.json`
|
|
3934
|
+
projection and managed import maps under one development-refresh lock, then
|
|
3935
|
+
releases that lock before opening the HTTPS source listener and its HTTP
|
|
3936
|
+
redirect listener. It returns both endpoints and their shared shutdown
|
|
3937
|
+
lifecycle. Legacy package-only apps remain unchanged. The operation generates
|
|
3938
|
+
no packaged output; enabled PWA manifests
|
|
3939
|
+
are served directly from the selected source resources.
|
|
3913
3940
|
|
|
3914
3941
|
### Signature and result
|
|
3915
3942
|
|
|
@@ -6075,6 +6102,13 @@ registration and returns a synchronous owner with `ready`, `state`, `subscribe`,
|
|
|
6075
6102
|
`update` and `dispose`. It does not block page rendering or load models.
|
|
6076
6103
|
Unsupported environments receive an explicit unsupported state.
|
|
6077
6104
|
|
|
6105
|
+
The generated page bootstrap also starts the SDK's background resource check.
|
|
6106
|
+
It stores one `lastChecked` timestamp per app/cache in DBOPFS, advancing it only
|
|
6107
|
+
after the full resource check succeeds, and revalidates on page load after
|
|
6108
|
+
120 seconds in development or 15 minutes in packaged delivery. Complete
|
|
6109
|
+
responses remain cached without SDK expiration; `304` retains the response
|
|
6110
|
+
and a successful replacement updates it. See the [PWA guide](pwa.md).
|
|
6111
|
+
|
|
6078
6112
|
### Example
|
|
6079
6113
|
|
|
6080
6114
|
```javascript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
},
|
|
106
106
|
"dependencies": {
|
|
107
107
|
"event-pubsub": "6.1.0",
|
|
108
|
+
"node-http-server": "9.1.1",
|
|
108
109
|
"strong-type": "2.0.1",
|
|
109
110
|
"vanilla-test": "2.1.3"
|
|
110
111
|
},
|
package/src/app-descriptor.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import Is from 'strong-type';
|
|
2
2
|
import {isDeepStrictEqual} from 'node:util';
|
|
3
|
-
import {readFile} from 'node:fs/promises';
|
|
3
|
+
import {readFile, writeFile} from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import {throwIfAborted} from './errors.mjs';
|
|
5
6
|
import {normalizePwaConfig} from './pwa.mjs';
|
|
6
7
|
import {
|
|
7
8
|
ARCANE_MACHINE_BUNDLE_VERSION,
|
|
@@ -424,6 +425,47 @@ async function readJsonFile(filePath,label,{optional=false}={}){
|
|
|
424
425
|
}
|
|
425
426
|
}
|
|
426
427
|
|
|
428
|
+
export async function refreshAppPackageProjection({workspaceRoot, appId, signal, onEvent}) {
|
|
429
|
+
throwIfAborted(signal);
|
|
430
|
+
const appRoot = path.join(workspaceRoot, 'apps', appId);
|
|
431
|
+
const descriptorPath = path.join(appRoot, APP_DESCRIPTOR_NAME);
|
|
432
|
+
const authored = await readJsonFile(
|
|
433
|
+
descriptorPath,
|
|
434
|
+
`apps/${appId}/${APP_DESCRIPTOR_NAME}`,
|
|
435
|
+
{optional: true}
|
|
436
|
+
);
|
|
437
|
+
if (authored === null) return {updated: false};
|
|
438
|
+
|
|
439
|
+
validateAppDescriptor(
|
|
440
|
+
authored,
|
|
441
|
+
{appId}
|
|
442
|
+
);
|
|
443
|
+
const projection = projectPackageManifest(authored);
|
|
444
|
+
const packagePath = path.join(appRoot, 'arcane-package.json');
|
|
445
|
+
const packageManifest = await readJsonFile(
|
|
446
|
+
packagePath,
|
|
447
|
+
`apps/${appId}/arcane-package.json`
|
|
448
|
+
);
|
|
449
|
+
const packageProjection = packageManifest.pwa === undefined ? packageManifest : {
|
|
450
|
+
...packageManifest,
|
|
451
|
+
pwa: normalizePwaConfig(packageManifest.pwa)
|
|
452
|
+
};
|
|
453
|
+
if (isDeepStrictEqual(projection, packageProjection)) return {updated: false};
|
|
454
|
+
|
|
455
|
+
// Development updates the generated projection before strict workspace readers run.
|
|
456
|
+
throwIfAborted(signal);
|
|
457
|
+
await writeFile(packagePath, `${JSON.stringify(projection, null, 2)}\n`, 'utf8');
|
|
458
|
+
await onEvent?.(
|
|
459
|
+
{
|
|
460
|
+
type: 'workspace.application.projection.updated',
|
|
461
|
+
workspaceRoot,
|
|
462
|
+
appId,
|
|
463
|
+
path: packagePath
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
return {updated: true};
|
|
467
|
+
}
|
|
468
|
+
|
|
427
469
|
export async function loadAppDescriptor({workspaceRoot,appRoot,appId,packageManifest}){
|
|
428
470
|
const descriptorPath=path.join(appRoot,APP_DESCRIPTOR_NAME);
|
|
429
471
|
const authored=await readJsonFile(descriptorPath,`apps/${appId}/${APP_DESCRIPTOR_NAME}`,{optional:true});
|
package/src/cli/main.mjs
CHANGED
|
@@ -18,6 +18,7 @@ const VALUE_OPTIONS=new Set([
|
|
|
18
18
|
'arcane-root',
|
|
19
19
|
'host',
|
|
20
20
|
'port',
|
|
21
|
+
'http-port',
|
|
21
22
|
'cert',
|
|
22
23
|
'key',
|
|
23
24
|
'target',
|
|
@@ -64,7 +65,7 @@ Usage:
|
|
|
64
65
|
${CLI_NAME} upgrade [--workspace <directory>] [--app <id>]
|
|
65
66
|
${CLI_NAME} doctor [--workspace <directory>] [--arcane-root <directory>]
|
|
66
67
|
${CLI_NAME} import-map [--workspace <directory>] [--app <id>]
|
|
67
|
-
${CLI_NAME} dev [--app <id>] [--public] [--https] [--cert <pem>] [--key <pem>] [--host <address>] [--port 8000] [--sdk-runtime-source <sdk-root>]
|
|
68
|
+
${CLI_NAME} dev [--app <id>] [--public] [--https] [--cert <pem>] [--key <pem>] [--host <address>] [--port 8000] [--http-port 0] [--sdk-runtime-source <sdk-root>]
|
|
68
69
|
${CLI_NAME} test [--app <id>] [--scope app]
|
|
69
70
|
${CLI_NAME} test --scope shared --test-file <repo-relative.test.mjs>
|
|
70
71
|
${CLI_NAME} check [--app <id>] [--scope app] [--skip-tests]
|
|
@@ -76,7 +77,7 @@ Usage:
|
|
|
76
77
|
${CLI_NAME} native-doctor --target <native-target> --arcane-root <directory>
|
|
77
78
|
${CLI_NAME} native-prepare --target <native-target> --arcane-root <directory>
|
|
78
79
|
${CLI_NAME} build --target <target> [--arcane-root <directory>] [--output-root <directory>] [--format <format>] [--signing <mode>]
|
|
79
|
-
${CLI_NAME} run [--target <target>] [--app <id>] [--arcane-root <directory>] [--output-root <directory>] [--format <format>] [--signing <mode>]
|
|
80
|
+
${CLI_NAME} run [--target <target>] [--app <id>] [--cert <pem>] [--key <pem>] [--port 8000] [--http-port 0] [--arcane-root <directory>] [--output-root <directory>] [--format <format>] [--signing <mode>]
|
|
80
81
|
${CLI_NAME} update-check
|
|
81
82
|
${CLI_NAME} targets
|
|
82
83
|
${CLI_NAME} repo status|pull|push
|
|
@@ -88,9 +89,10 @@ Usage:
|
|
|
88
89
|
|
|
89
90
|
Development:
|
|
90
91
|
--public Serve HTTPS on all IPv4 interfaces (0.0.0.0) and print network URLs.
|
|
91
|
-
--https
|
|
92
|
+
--https Accepted for compatibility; Arcane browser serving always uses HTTPS.
|
|
92
93
|
--cert <pem> --key <pem> Use an existing certificate pair; paths are relative to the workspace.
|
|
93
94
|
--host <address> Override the bind address; takes precedence over --public.
|
|
95
|
+
--http-port <port> Browser dev/run HTTP redirect port; 0 selects an available port (default).
|
|
94
96
|
--sdk-runtime-source <sdk-root> Dev-only live SDK checkout; omitted preserves the workspace runtime mode.
|
|
95
97
|
|
|
96
98
|
Global:
|
|
@@ -456,9 +458,25 @@ function operationOptions(command,parsed,cwd){
|
|
|
456
458
|
if(flags.has('public')&&command!=='dev'){
|
|
457
459
|
usage('--public is supported only by dev.');
|
|
458
460
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
+
const browserServing = command === 'dev'
|
|
462
|
+
|| (command === 'run' && (values.target ?? 'browser') === 'browser');
|
|
463
|
+
if ((flags.has('https') || values.cert !== undefined || values.key !== undefined) && !browserServing) {
|
|
464
|
+
usage('--https, --cert, and --key are supported only by dev and run --target browser.');
|
|
461
465
|
}
|
|
466
|
+
if (values['http-port'] !== undefined && !browserServing) {
|
|
467
|
+
usage('--http-port is supported only by dev and run --target browser.');
|
|
468
|
+
}
|
|
469
|
+
if (browserServing && (values.cert === undefined) !== (values.key === undefined)) {
|
|
470
|
+
usage('Arcane HTTPS serving requires --cert and --key together.');
|
|
471
|
+
}
|
|
472
|
+
const browserServerOptions = browserServing ? {
|
|
473
|
+
https: true,
|
|
474
|
+
httpPort: readPort(values['http-port'], 0),
|
|
475
|
+
...(values.cert === undefined ? {} : {
|
|
476
|
+
certPath: path.resolve(workspaceRoot, values.cert),
|
|
477
|
+
keyPath: path.resolve(workspaceRoot, values.key)
|
|
478
|
+
})
|
|
479
|
+
} : {};
|
|
462
480
|
if(flags.has('overwrite')&&command!=='bundle'){
|
|
463
481
|
usage('--overwrite is supported only by bundle.');
|
|
464
482
|
}
|
|
@@ -518,18 +536,11 @@ function operationOptions(command,parsed,cwd){
|
|
|
518
536
|
}
|
|
519
537
|
if(command==='dev'){
|
|
520
538
|
noExtraPositionals(command,positionals);
|
|
521
|
-
if((values.cert===undefined)!==(values.key===undefined)){
|
|
522
|
-
usage('HTTPS development requires --cert and --key together.');
|
|
523
|
-
}
|
|
524
539
|
return {
|
|
525
540
|
...common,
|
|
541
|
+
...browserServerOptions,
|
|
526
542
|
host:values.host??(flags.has('public')?'0.0.0.0':'127.0.0.1'),
|
|
527
543
|
port:readPort(values.port,8000),
|
|
528
|
-
https:flags.has('public')||flags.has('https')||values.cert!==undefined,
|
|
529
|
-
...(values.cert===undefined?{}:{
|
|
530
|
-
certPath:path.resolve(workspaceRoot,values.cert),
|
|
531
|
-
keyPath:path.resolve(workspaceRoot,values.key)
|
|
532
|
-
}),
|
|
533
544
|
...(values['sdk-runtime-source']===undefined?{}:{
|
|
534
545
|
sdkRuntimeSourceRoot:path.resolve(cwd,values['sdk-runtime-source'])
|
|
535
546
|
})
|
|
@@ -609,6 +620,7 @@ function operationOptions(command,parsed,cwd){
|
|
|
609
620
|
noExtraPositionals(command,positionals);
|
|
610
621
|
return {
|
|
611
622
|
...common,
|
|
623
|
+
...browserServerOptions,
|
|
612
624
|
target:values.target??'browser',
|
|
613
625
|
arcaneRoot:values['arcane-root']?path.resolve(cwd,values['arcane-root']):undefined,
|
|
614
626
|
outputRoot:values['output-root']?path.resolve(cwd,values['output-root']):undefined,
|
|
@@ -853,6 +865,9 @@ function serverSummary(result){
|
|
|
853
865
|
host:result.host,
|
|
854
866
|
port:result.port,
|
|
855
867
|
url:result.url,
|
|
868
|
+
...(result.httpPort===undefined?{}:{httpPort:result.httpPort}),
|
|
869
|
+
...(result.httpOrigin===undefined?{}:{httpOrigin:result.httpOrigin}),
|
|
870
|
+
...(result.httpUrl===undefined?{}:{httpUrl:result.httpUrl}),
|
|
856
871
|
...(result.protocol===undefined?{}:{protocol:result.protocol}),
|
|
857
872
|
...(result.networkUrls===undefined?{}:{networkUrls:result.networkUrls}),
|
|
858
873
|
...(result.callerAuthentication
|
|
@@ -867,6 +882,7 @@ function serverSummary(result){
|
|
|
867
882
|
async function waitForServer(result,signal,reporter){
|
|
868
883
|
const readyMessage=[
|
|
869
884
|
`Development server ready at ${result.url}`,
|
|
885
|
+
...(result.httpUrl ? [`HTTP redirect: ${result.httpUrl}`] : []),
|
|
870
886
|
...(result.networkUrls??[]).map(function networkAddress(url){return `Network: ${url}`;})
|
|
871
887
|
].join('\n');
|
|
872
888
|
reporter.emit('server.ready',serverSummary(result),readyMessage);
|