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.
@@ -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
- https=false, certPath, keyPath, tls, signal, onEvent}` and serves one validated
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, signal, onEvent}` and serves the
3473
- complete selected release files. `host` defaults to `127.0.0.1` and accepts an
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 listener; port `0` asks the operating
3476
- system for an available port.
3477
-
3478
- `https:true` reads `.arcane/dev/server-cert.pem` and
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. The path pair also selects
3481
- HTTPS without `https:true`; relative paths resolve from the workspace. A
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 HTTPS and the wildcard bind;
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 the listener is ready and resolves to
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 HTTP or HTTPS
3493
- server; `protocol` is `'http:'` or `'https:'`.
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
- All returned application URLs use the selected transport's scheme. The server
3503
- reads one PEM pair per startup and does not create certificates or modify trust
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 the selected listener and emits awaited,
3509
- backpressured `server.starting` and `server.started` events. Request failures
3510
- emit `server.request.failed`; shutdown emits `server.stopped` after owned
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`. A listener error
3514
- or event-callback failure closes the server and rejects its lifecycle. Invalid
3515
- mode/host/port, malformed workspace or release content, an occupied port,
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 `port`.
3911
- The operation refreshes the selected app's managed import maps once, then owns
3912
- one source listener and returns its protocol, URLs, and shutdown lifecycle.
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.11.3",
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
  },
@@ -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 Use HTTPS with .arcane/dev/server-cert.pem and server-key.pem.
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
- if((flags.has('https')||values.cert!==undefined||values.key!==undefined)&&command!=='dev'){
460
- usage('--https, --cert, and --key are supported only by dev.');
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);