arcane-os 0.14.0 → 0.15.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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.1
4
+
5
+ - Use the published `node-http-server` public lifecycle for the mail gateway.
6
+ Pass original requests and responses through its raw-request hook to the
7
+ existing mail handler, preserving complete content, provider results,
8
+ cancellation, and the returned native listener. Retain the disabled socket
9
+ inactivity timeout and use the module's listener shutdown and malformed-client
10
+ response handling.
11
+
12
+ ## 0.15.0
13
+
14
+ - Add explicit `arcane dev --http` and source API `http:true` for HTTP
15
+ development, including LAN device use. Reuse `node-http-server` and the same
16
+ application, PWA, source-mapping and conditional-response routes without
17
+ certificate setup. Report the actual HTTP endpoint and own one listener's
18
+ readiness, cancellation, errors and shutdown.
19
+ - Preserve HTTPS and its HTTP `308` redirect as the default; packaged browser
20
+ previews continue using HTTPS. Browser settings, certificate validation,
21
+ application content and caching remain unchanged.
22
+
3
23
  ## 0.14.0
4
24
 
5
25
  - Add opt-in `toolText: {name, field}` and `onToolText(text, call, displayId)`
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.13.0` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.15.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
 
@@ -73,7 +73,7 @@ each device. Keep these local files ignored by Git. The same certificate pair
73
73
  works for any selected app in that workspace. The command reports missing TLS
74
74
  files instead of starting an HTTP listener.
75
75
 
76
- Every Arcane app uses HTTPS for development and packaged browser previews.
76
+ HTTPS is the default for development and required for packaged browser previews.
77
77
  Plain `npm run dev` binds to localhost with the same workspace certificate pair.
78
78
  An explicit `--host` overrides the bind address, and `--port` selects the HTTPS port.
79
79
  The paired HTTP listener returns 308 redirects and uses an OS-assigned port
@@ -84,6 +84,17 @@ for public mode. Network reachability depends on the machine's firewall and
84
84
  network. See the [development HTTPS setup](docs/reference/cli.md#development-https-setup)
85
85
  for device trust and DBOPFS's secure-context requirement.
86
86
 
87
+ For explicit HTTP source development, including LAN device testing, use:
88
+
89
+ ```bash
90
+ npm run dev -- --app hello-speech --public --http --port 8000
91
+ ```
92
+
93
+ This selects one HTTP content listener and prints HTTP network URLs. It reads
94
+ no certificate pair and serves the same application and generated PWA routes.
95
+ PWA and storage availability still depend on the browser's secure-context
96
+ rules; see [HTTP development](docs/reference/cli.md#explicit-http-development).
97
+
87
98
  Open the URL printed by the server. The generated page owns its import map and
88
99
  Arcane theme; its application module is `apps/hello-speech/modules/App.js`.
89
100
  `arcane/AI` is a managed **browser import**, not an npm-exported Node inference
@@ -518,7 +529,7 @@ arcane new <id> [--path <directory>] [--display-name <name>] [--target <target>]
518
529
  arcane init [id] [--workspace <directory>] [--display-name <name>] [--target <target>]
519
530
  arcane doctor [--workspace <directory>] [--arcane-root <directory>]
520
531
  arcane import-map [--workspace <directory>] [--app <id>]
521
- arcane dev [--app <id>] [--public] [--https] [--cert <file> --key <file>] [--host <address>] [--port 8000]
532
+ arcane dev [--app <id>] [--public] [--http | --https] [--cert <file> --key <file>] [--host <address>] [--port 8000]
522
533
  arcane test [--app <id>] [--scope app]
523
534
  arcane test --scope shared --test-file <repo-relative.test.mjs>
524
535
  arcane check [--app <id>] [--scope app] [--skip-tests]
@@ -136,8 +136,8 @@ selected source route and the last successful check when evaluating freshness.
136
136
 
137
137
  The shared dev server uses RIAEvangelist's `node-http-server` public interface
138
138
  for HTTPS and conditional responses on those selected routes. The SDK
139
- owns source selection and generated representations. Every Arcane development
140
- server and packaged browser preview serves content on HTTPS and redirects its
139
+ owns source selection and generated representations. By default, source development
140
+ and every packaged browser preview serve content on HTTPS and redirect their
141
141
  paired HTTP listener with status 308 through the public request hook.
142
142
  `arcane dev --public` selects the IPv4 wildcard address;
143
143
  explicit `--host` controls the bind address. CLI startup reads one workspace-local
@@ -151,6 +151,14 @@ redirect listener, defaulting to an OS-assigned port. Existing raw `tls` inputs
151
151
  retain their native HTTPS transport under the SDK, as described by the module's
152
152
  advanced TLS extension guidance; all content uses its public serving methods.
153
153
 
154
+ Explicit `arcane dev --http` or source API `http:true` selects the module's
155
+ single HTTP listener on `port`. It skips certificate resolution and serves the
156
+ same selected source mappings, generated PWA routes, and conditional responses.
157
+ No HTTPS listener or redirect is started in this mode. Listener readiness,
158
+ failure, cancellation and shutdown retain the same operation owner; reported
159
+ URLs use the actual HTTP protocol and bound port. Browser secure-context
160
+ requirements remain browser-owned. Packaged previews retain HTTPS.
161
+
154
162
  Development is an intentionally fast feedback loop. Keep each increment small
155
163
  and independently understandable so its effect has one clear cause and a
156
164
  mistake can be isolated without untangling unrelated work. A development
@@ -50,8 +50,9 @@ meaning and cardinality rules:
50
50
  | `--arcane-root` | directory | `doctor`, native `build`/`run`, `native-doctor`, `native-prepare` |
51
51
  | `--host` / `--port` | host / integer 0–65535 | Browser `dev`/`run` default to HTTPS at `127.0.0.1:8000`; `mail serve` defaults to HTTP at `127.0.0.1:8025` and admits numeric loopback only. |
52
52
  | `--http-port` | integer 0–65535 | Browser `dev`/`run` HTTP redirect listener; defaults to `0`, which selects an available port. |
53
- | `--public` | flag | `dev`; serves HTTPS and binds to `0.0.0.0` unless `--host` explicitly selects another address. |
54
- | `--https` | flag | Browser `dev`/`run`; retained explicitly, while HTTPS is always enabled. |
53
+ | `--public` | flag | `dev`; binds to `0.0.0.0` unless `--host` explicitly selects another address. |
54
+ | `--http` | flag | `dev` only; serves source and PWA routes on one HTTP listener selected by `--port`, without TLS. |
55
+ | `--https` | flag | Browser `dev`/`run`; explicitly selects the default HTTPS transport. |
55
56
  | `--cert` / `--key` | PEM file paths | Browser `dev`/`run`; supply both for an explicit certificate chain and private key. Relative paths resolve from the workspace. |
56
57
  | `--target` | target id | `new`, `init`, native diagnostics, `build`, `run` |
57
58
  | `--format` / `--signing` | target-supported values | Native diagnostics, `build`, `run` |
@@ -313,7 +314,7 @@ npm exec -- arcane upgrade --workspace . --app hello-world
313
314
 
314
315
  Starts one development server for one selected app and maps the exact
315
316
  workspace/runtime routes. It defaults to HTTPS on localhost; `--public` enables access
316
- from other devices on the network over HTTPS.
317
+ from other devices on the network, using HTTPS by default.
317
318
 
318
319
  For an external workspace, the server exposes the selected projected
319
320
  `arcane/` root, including `arcane/sdk` and `arcane/dependencies`, alongside the
@@ -322,7 +323,7 @@ The explicit live-source SDK mapping remains unchanged and does not replace the
322
323
  installed projection.
323
324
 
324
325
  ```text
325
- arcane dev [--app <id>] [--public] [--https] [--cert <file> --key <file>] [--host <address>] [--port 8000] [--http-port 0]
326
+ arcane dev [--app <id>] [--public] [--http | --https] [--cert <file> --key <file>] [--host <address>] [--port 8000] [--http-port 0]
326
327
  ```
327
328
 
328
329
  ### Lifecycle
@@ -344,7 +345,7 @@ device, since `localhost` refers to that device and `0.0.0.0` is a bind address.
344
345
  Network URLs come from one interface snapshot at startup and do not establish
345
346
  remote reachability through the machine's firewall or network.
346
347
 
347
- Every Arcane app uses HTTPS for development and packaged browser previews.
348
+ HTTPS is the default for development and required for packaged browser previews.
348
349
  `--https` remains accepted but is no longer needed to select the transport.
349
350
  `--port` selects the HTTPS application port. A second HTTP listener returns
350
351
  `308` redirects to that HTTPS port, preserving the requested path and query.
@@ -357,9 +358,41 @@ the redirect endpoint.
357
358
  Supplying both `--cert` and `--key` selects an explicit PEM pair. The command
358
359
  does not configure a firewall, router forwarding, or an internet tunnel.
359
360
 
361
+ ### Explicit HTTP development
362
+
363
+ `--http` selects source development over HTTP. Combine it with `--public` or
364
+ `--host` to use a LAN address, and choose the content listener with `--port`:
365
+
366
+ ```bash
367
+ npm run dev -- --app hello-world --public --http --port 8000
368
+ ```
369
+
370
+ The command starts one HTTP listener through `node-http-server`, skips all TLS
371
+ file reads, and serves the same selected source files, generated manifest,
372
+ service worker and offline inventory. PWA configuration and caching are
373
+ unchanged. Startup prints the actual HTTP local and network URLs. Structured
374
+ results contain `protocol:'http:'`; `httpPort`, `httpOrigin`, and `httpUrl`
375
+ identify that content listener and equal `port`, `origin`, and `url`.
376
+ There is no separate redirect endpoint. Cancellation and listener failure
377
+ close the owned HTTP listener and settle the same lifecycle.
378
+
379
+ `--http` is supported only by `dev`. Combining it with `--https`, `--cert`,
380
+ `--key`, or `--http-port` is a usage error. Omitting it preserves HTTPS;
381
+ certificate errors never select HTTP automatically.
382
+
383
+ A LAN HTTP origin does not receive the browser's localhost secure-context
384
+ exception. For Chrome development, Chromium documents
385
+ `chrome://flags/#unsafely-treat-insecure-origin-as-secure` with the exact HTTP
386
+ origin, such as `http://192.0.2.10:8000`; see
387
+ [Chromium's development guidance](https://www.chromium.org/Home/chromium-security/deprecating-powerful-features-on-insecure-origins/).
388
+ The developer owns that browser setting. The SDK does not change it, alter
389
+ certificate validation, or claim a PWA is installable merely because its server
390
+ started. HTTP and HTTPS are distinct origins with separate browser storage and
391
+ registrations; existing HTTPS data is preserved.
392
+
360
393
  ### Development HTTPS setup
361
394
 
362
- Before starting `arcane dev` or a packaged browser preview, place the server's
395
+ Before starting HTTPS `arcane dev` or a packaged browser preview, place the server's
363
396
  PEM certificate chain at `.arcane/dev/server-cert.pem` and its PEM private key at
364
397
  `.arcane/dev/server-key.pem`, relative to the workspace. Alternatively, pass
365
398
  `--cert <file> --key <file>` together. The certificate must cover localhost or the LAN IP
@@ -283,6 +283,12 @@ Non-interactive structured output requires `--app-key-stdin` and redirected
283
283
  stdin. The server binds numeric loopback only; the default is
284
284
  `127.0.0.1:8025/v1/mail`.
285
285
 
286
+ The gateway uses the published `node-http-server` instance lifecycle. Its raw
287
+ request hook hands the original request and response directly to the mail
288
+ handler before body parsing or static routing. Socket inactivity timeout remains
289
+ disabled; caller-selected mail deadlines, cancellation, and complete responses
290
+ remain owned by the mail handler.
291
+
286
292
  The gateway protects the provider credential by requiring:
287
293
 
288
294
  - its exact numeric-loopback `Host` authority and `/v1/mail` route;
@@ -112,9 +112,11 @@ For an enabled application, `arcane dev` serves the generated PWA files at the
112
112
  origin root and starts at the selected app page under `/apps/<id>/`. Use one
113
113
  selected app per development origin. The SDK uses `node-http-server` for source
114
114
  and packaged-preview serving, including conditional resource responses.
115
- Every Arcane development server and packaged browser preview serves HTTPS,
116
- including localhost. Configure the workspace certificate pair before starting
117
- the ordinary command; see [development HTTPS setup](cli.md#development-https-setup).
115
+ Arcane development servers default to HTTPS, including localhost, and packaged
116
+ browser previews require HTTPS. Configure the workspace certificate pair before
117
+ starting the ordinary command; see [development HTTPS setup](cli.md#development-https-setup).
118
+ Explicit source-only `arcane dev --http` serves the same generated PWA routes
119
+ without loading certificates; see [explicit HTTP development](cli.md#explicit-http-development).
118
120
  `--public` selects the IPv4 wildcard bind address; it does not enable PWA
119
121
  configuration, change manifest metadata, or determine browser installability.
120
122
 
@@ -272,8 +274,11 @@ Keep actual icon dimensions in `sizes`. Browser diagnostics about missing
272
274
  and are separate from a usable installation icon.
273
275
 
274
276
  Browser installation requires HTTPS or the browser's localhost/loopback
275
- exception. A device-facing LAN address is not loopback. Arcane's development
276
- server still follows its own HTTPS serving contract above. Browser engagement,
277
+ exception. A device-facing LAN address is not loopback. Selecting HTTP serving
278
+ does not make that LAN origin a secure context. Chromium documents a separate
279
+ [explicit developer origin setting](https://www.chromium.org/Home/chromium-security/deprecating-powerful-features-on-insecure-origins/);
280
+ the SDK does not configure that setting or claim that starting the server proves
281
+ installation eligibility. Browser engagement,
277
282
  installation state and platform support also affect whether native promotion
278
283
  appears; worker cache readiness is not an installation UI prerequisite. See
279
284
  [browser installation requirements](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable).
@@ -3464,7 +3464,7 @@ async function useselectApp(...arguments_) {
3464
3464
 
3465
3465
  Starts one owned browser development server with exact runtime/app route mappings and a caller-selected bind address.
3466
3466
 
3467
- HTTPS serving uses the published `node-http-server` module. The SDK
3467
+ HTTP and HTTPS serving use the published `node-http-server` module. The SDK
3468
3468
  selects source routes and supplies generated representations; the module owns
3469
3469
  static-file conditional GET/HEAD handling and response delivery. The SDK
3470
3470
  retains modification dates for its generated representations. Unchanged
@@ -3480,7 +3480,7 @@ async startDevServer(options={})
3480
3480
  ```
3481
3481
 
3482
3482
  Import it from `arcane-os`. Source mode accepts
3483
- `{workspaceRoot=process.cwd(), appId, mode='source', host='127.0.0.1', port=0, httpPort=0,
3483
+ `{workspaceRoot=process.cwd(), appId, mode='source', host='127.0.0.1', port=0, httpPort=0, http=false,
3484
3484
  certPath, keyPath, tls, signal, onEvent}` and serves one validated
3485
3485
  workspace application plus its complete SDK or integrated runtime. Packaged mode uses
3486
3486
  `{mode:'packaged', releaseRoot, workspaceRoot, host, port, httpPort, certPath, keyPath, tls,
@@ -3494,9 +3494,9 @@ The SDK request hook returns `308` for HTTP with a `Location` pointing to the
3494
3494
  actual HTTPS port while preserving the original request path and query. Both listeners
3495
3495
  use the selected host.
3496
3496
 
3497
- Every source server and packaged browser preview enforces HTTPS, including
3498
- localhost. The legacy `https` option is accepted but cannot disable it.
3499
- Startup reads `.arcane/dev/server-cert.pem` and
3497
+ Source servers default to HTTPS, including localhost; packaged browser previews
3498
+ require HTTPS. The legacy `https` option is accepted but `https:false` and
3499
+ `tls:false` alone do not disable HTTPS. HTTPS startup reads `.arcane/dev/server-cert.pem` and
3500
3500
  `.arcane/dev/server-key.pem` relative to `workspaceRoot` unless explicit
3501
3501
  `certPath` and `keyPath` are supplied together; relative paths resolve from the
3502
3502
  workspace. A direct `tls` object instead supplies Node HTTPS server options, including
@@ -3507,16 +3507,27 @@ handshake; browser trust and address matching are evaluated when a client
3507
3507
  connects. The CLI's `--public` selects the wildcard bind;
3508
3508
  the API's `host` option alone changes only the bind address.
3509
3509
 
3510
- The promise settles after both listeners are ready and resolves to
3510
+ Explicit `http:true` selects one HTTP content listener in source mode and skips
3511
+ certificate loading. `port` selects that listener's port, including `0` for an
3512
+ available port. The same application, runtime and generated PWA routes are
3513
+ served. `http` must be boolean; `http:true` rejects packaged mode, `https:true`,
3514
+ explicit certificate/key paths, a `tls` value other than `undefined`, `null` or
3515
+ `false`, and a nonzero `httpPort`. Browser secure-context and installation
3516
+ requirements remain browser-owned; see [explicit HTTP development](cli.md#explicit-http-development).
3517
+
3518
+ The promise settles after all selected listeners are ready and resolves to
3511
3519
  `{server, protocol, mode, workspaceRoot, appId, host, port, origin, cleanUrl, url,
3512
- networkUrls, httpPort, httpOrigin, httpUrl, close, closed, lifecycle}`. `server` is the raw Node HTTPS
3513
- server; `protocol` is `'https:'`.
3520
+ networkUrls, httpPort, httpOrigin, httpUrl, close, closed, lifecycle}`. `server` is the raw Node
3521
+ server for application content; `protocol` is `'https:'` by default or `'http:'`
3522
+ with explicit HTTP source mode.
3514
3523
  `url` and `cleanUrl` are the same application URL. Wildcard listeners use
3515
3524
  `localhost` in that local URL; `host` retains the actual bound address.
3516
3525
  `httpPort` is the actual HTTP listener port, `httpOrigin` is its HTTP origin,
3517
3526
  and `httpUrl` combines that origin with the application start path. These
3518
- fields identify the redirect endpoint; `origin`, `url`, `cleanUrl`, and
3519
- `networkUrls` identify HTTPS application endpoints.
3527
+ fields identify the redirect endpoint in HTTPS mode; `origin`, `url`, `cleanUrl`,
3528
+ and `networkUrls` identify application endpoints. In HTTP mode, `httpPort`,
3529
+ `httpOrigin`, and `httpUrl` equal `port`, `origin`, and `url`; they identify the
3530
+ single content listener, with no redirect listener.
3520
3531
  `networkUrls` lists application URLs for applicable non-loopback interface
3521
3532
  addresses discovered once at startup. These URLs are connection candidates,
3522
3533
  not evidence of reachability from another device. The server adds no session
@@ -3528,15 +3539,15 @@ certificates or modify trust stores. Each client must trust the issuing CA and o
3528
3539
  server certificate. Lifecycle events and CLI summaries exclude TLS options and
3529
3540
  private key contents. See [development HTTPS setup](cli.md#development-https-setup).
3530
3541
 
3531
- Starting the server opens both selected listeners and emits awaited,
3542
+ Starting the server opens the selected listener or listeners and emits awaited,
3532
3543
  backpressured `server.starting` and `server.started` events. `server.started`
3533
- includes `httpPort`, `httpOrigin`, and `httpUrl` alongside the HTTPS endpoint.
3544
+ includes the selected `protocol`, `httpPort`, `httpOrigin`, and `httpUrl` alongside the application endpoint.
3534
3545
  Request failures emit `server.request.failed`; shutdown emits `server.stopped` after owned
3535
3546
  requests and event delivery drain. Call `await result.close()` in a `finally`
3536
3547
  block, or abort `signal`; `close()` is idempotent and returns the
3537
3548
  same settlement represented by both `closed` and `lifecycle`. Closing the
3538
- operation closes both listeners. An error from either listener or an
3539
- event-callback failure closes both and rejects the lifecycle. Invalid
3549
+ operation closes every selected listener once. A listener error or an
3550
+ event-callback failure closes the operation and rejects the lifecycle. Invalid
3540
3551
  mode/host/port/httpPort, malformed workspace or release content, an occupied port,
3541
3552
  or an already-aborted signal rejects startup.
3542
3553
 
@@ -3931,14 +3942,15 @@ async function usedescribeTargets(...arguments_) {
3931
3942
 
3932
3943
  Starts one owned browser development server for the selected application.
3933
3944
 
3934
- `https`, `certPath`, `keyPath`, and `tls` follow the
3935
- [`startDevServer()` TLS contract](#startdevserver), alongside `host`, `port`, and
3936
- `httpPort`. `port` selects HTTPS and `httpPort` selects the HTTP `308` redirect
3937
- listener; each defaults to an available port.
3945
+ `http`, `https`, `certPath`, `keyPath`, and `tls` follow the
3946
+ [`startDevServer()` transport contract](#startdevserver), alongside `host`, `port`, and
3947
+ `httpPort`. By default, `port` selects HTTPS and `httpPort` selects the HTTP `308`
3948
+ redirect listener; each defaults to an available port. Explicit `http:true`
3949
+ selects one source HTTP content listener at `port`, without certificate loading.
3938
3950
  The operation refreshes the selected authored descriptor's `arcane-package.json`
3939
3951
  projection and managed import maps under one development-refresh lock, then
3940
- releases that lock before opening the HTTPS source listener and its HTTP
3941
- redirect listener. It returns both endpoints and their shared shutdown
3952
+ releases that lock before opening the selected source listener or listeners.
3953
+ It returns their application endpoints and shared shutdown
3942
3954
  lifecycle. Legacy package-only apps remain unchanged. The operation generates
3943
3955
  no packaged output; enabled PWA manifests
3944
3956
  are served directly from the selected source resources.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
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",
package/src/cli/main.mjs CHANGED
@@ -39,6 +39,7 @@ const VALUE_OPTIONS=new Set([
39
39
  const FLAG_OPTIONS=new Set([
40
40
  'git',
41
41
  'public',
42
+ 'http',
42
43
  'https',
43
44
  'skip-tests',
44
45
  'dry-run',
@@ -65,7 +66,7 @@ Usage:
65
66
  ${CLI_NAME} upgrade [--workspace <directory>] [--app <id>]
66
67
  ${CLI_NAME} doctor [--workspace <directory>] [--arcane-root <directory>]
67
68
  ${CLI_NAME} import-map [--workspace <directory>] [--app <id>]
68
- ${CLI_NAME} dev [--app <id>] [--public] [--https] [--cert <pem>] [--key <pem>] [--host <address>] [--port 8000] [--http-port 0] [--sdk-runtime-source <sdk-root>]
69
+ ${CLI_NAME} dev [--app <id>] [--public] [--http | --https] [--cert <pem>] [--key <pem>] [--host <address>] [--port 8000] [--http-port 0] [--sdk-runtime-source <sdk-root>]
69
70
  ${CLI_NAME} test [--app <id>] [--scope app]
70
71
  ${CLI_NAME} test --scope shared --test-file <repo-relative.test.mjs>
71
72
  ${CLI_NAME} check [--app <id>] [--scope app] [--skip-tests]
@@ -88,8 +89,9 @@ Usage:
88
89
  ${CLI_NAME} mail serve --profile <profile> --from <address> --app <id> --origin <origin> [--allow-to <addresses>] [--app-key-stdin] [--host 127.0.0.1] [--port 8025] [--request-timeout <ms>]
89
90
 
90
91
  Development:
91
- --public Serve HTTPS on all IPv4 interfaces (0.0.0.0) and print network URLs.
92
- --https Accepted for compatibility; Arcane browser serving always uses HTTPS.
92
+ --public Bind dev to all IPv4 interfaces (0.0.0.0) and print network URLs.
93
+ --http Dev-only HTTP content on --port; no TLS or HTTPS redirect listener.
94
+ --https Explicitly select the default HTTPS browser transport.
93
95
  --cert <pem> --key <pem> Use an existing certificate pair; paths are relative to the workspace.
94
96
  --host <address> Override the bind address; takes precedence over --public.
95
97
  --http-port <port> Browser dev/run HTTP redirect port; 0 selects an available port (default).
@@ -458,6 +460,14 @@ function operationOptions(command,parsed,cwd){
458
460
  if(flags.has('public')&&command!=='dev'){
459
461
  usage('--public is supported only by dev.');
460
462
  }
463
+ const http = flags.has('http');
464
+ if (http && command !== 'dev') {
465
+ usage('--http is supported only by dev.');
466
+ }
467
+ if (http && (flags.has('https') || values.cert !== undefined
468
+ || values.key !== undefined || values['http-port'] !== undefined)) {
469
+ usage('--http cannot combine --https, --cert, --key, or --http-port; select its listener with --port.');
470
+ }
461
471
  const browserServing = command === 'dev'
462
472
  || (command === 'run' && (values.target ?? 'browser') === 'browser');
463
473
  if ((flags.has('https') || values.cert !== undefined || values.key !== undefined) && !browserServing) {
@@ -470,7 +480,7 @@ function operationOptions(command,parsed,cwd){
470
480
  usage('Arcane HTTPS serving requires --cert and --key together.');
471
481
  }
472
482
  const browserServerOptions = browserServing ? {
473
- https: true,
483
+ ...(http ? {http: true} : {https: true}),
474
484
  httpPort: readPort(values['http-port'], 0),
475
485
  ...(values.cert === undefined ? {} : {
476
486
  certPath: path.resolve(workspaceRoot, values.cert),
@@ -882,7 +892,7 @@ function serverSummary(result){
882
892
  async function waitForServer(result,signal,reporter){
883
893
  const readyMessage=[
884
894
  `Development server ready at ${result.url}`,
885
- ...(result.httpUrl ? [`HTTP redirect: ${result.httpUrl}`] : []),
895
+ ...(result.httpUrl && result.protocol !== 'http:' ? [`HTTP redirect: ${result.httpUrl}`] : []),
886
896
  ...(result.networkUrls??[]).map(function networkAddress(url){return `Network: ${url}`;})
887
897
  ].join('\n');
888
898
  reporter.emit('server.ready',serverSummary(result),readyMessage);
@@ -718,7 +718,7 @@ function deployDevelopmentServer(fileServer, signal, {tlsServer, host, port}) {
718
718
  if (!listeners.every(listener => ready.has(listener))) return;
719
719
  settled = true;
720
720
  cleanupDeployment();
721
- resolve(tlsServer ?? fileServer.secureServer);
721
+ resolve(tlsServer ?? fileServer.secureServer ?? fileServer.server);
722
722
  }
723
723
  try {
724
724
  throwIfAborted(signal);
@@ -804,6 +804,8 @@ async function startOwnedDevServer({
804
804
  host='127.0.0.1',
805
805
  port=0,
806
806
  httpPort=0,
807
+ http = false,
808
+ https: requestedHttps,
807
809
  tls,
808
810
  certPath,
809
811
  keyPath,
@@ -822,22 +824,33 @@ async function startOwnedDevServer({
822
824
  if (!is.integer(httpPort) || httpPort < 0 || httpPort > 65535) {
823
825
  fail('httpPort must be an integer from 0 through 65535.', 'ARCANE_USAGE');
824
826
  }
827
+ if (!is.boolean(http)) {
828
+ fail('http must be a boolean.', 'ARCANE_USAGE');
829
+ }
830
+ if (http && mode !== 'source') {
831
+ fail('HTTP serving is supported only in source development mode.', 'ARCANE_USAGE');
832
+ }
833
+ if (http && (requestedHttps === true || (tls !== undefined && tls !== null && tls !== false)
834
+ || certPath !== undefined || keyPath !== undefined || httpPort !== 0)) {
835
+ fail('HTTP development cannot combine TLS options or a separate httpPort; select its listener with port.', 'ARCANE_USAGE');
836
+ }
837
+ const protocol = http ? 'http:' : 'https:';
825
838
  const requestedRuntimeMode=mode==='source'&&sdkRuntimeSourceRoot!==undefined
826
839
  ?'sdk-source'
827
840
  :null;
828
841
  await events.send({
829
842
  type:'server.starting',
843
+ protocol,
830
844
  mode,
831
845
  host,
832
846
  port,
833
- httpPort,
847
+ httpPort: http ? port : httpPort,
834
848
  appId,
835
849
  ...(requestedRuntimeMode?{runtimeMode:requestedRuntimeMode}:{})
836
850
  });
837
- const selectedTls=await resolveDevelopmentTls({
838
- workspaceRoot,tls,certPath,keyPath,signal
839
- });
840
- const protocol = 'https:';
851
+ const selectedTls = http ? null : await resolveDevelopmentTls(
852
+ {workspaceRoot, tls, certPath, keyPath, signal}
853
+ );
841
854
  throwIfAborted(signal);
842
855
  const routeSet=mode==='source'
843
856
  ?await sourceRoutes(workspaceRoot,appId,{
@@ -1093,7 +1106,7 @@ async function startOwnedDevServer({
1093
1106
  async function serveDevelopmentRequest(request, response) {
1094
1107
  let task;
1095
1108
  async function routeDevelopmentRequest() {
1096
- if (!request.socket.encrypted) {
1109
+ if (!http && !request.socket.encrypted) {
1097
1110
  const address = (tlsServer ?? fileServer.secureServer).address();
1098
1111
  const authority = new URL(`http://${request.headers.host || browserHostname(host)}`);
1099
1112
  authority.protocol = 'https:';
@@ -1262,16 +1275,18 @@ async function startOwnedDevServer({
1262
1275
  {
1263
1276
  root: mappings[0].root,
1264
1277
  host,
1265
- port: httpPort,
1278
+ port: http ? port : httpPort,
1266
1279
  server: {noCache: false, timeout: 0},
1267
- https: {
1268
- only: false,
1269
- port,
1270
- ...(selectedTls.options ? {} : {
1271
- privateKey: selectedTls.privateKeyPath,
1272
- certificate: selectedTls.certificatePath
1273
- })
1274
- }
1280
+ ...(http ? {} : {
1281
+ https: {
1282
+ only: false,
1283
+ port,
1284
+ ...(selectedTls.options ? {} : {
1285
+ privateKey: selectedTls.privateKeyPath,
1286
+ certificate: selectedTls.certificatePath
1287
+ })
1288
+ }
1289
+ })
1275
1290
  }
1276
1291
  );
1277
1292
  fileServer.config.contentType = contentType;
@@ -1279,11 +1294,11 @@ async function startOwnedDevServer({
1279
1294
  // The module's public HTTPS configuration accepts PEM paths. Its HTTPS
1280
1295
  // guide leaves advanced TLS inputs to application code; preserve that
1281
1296
  // existing SDK input while the same public module methods serve all content.
1282
- const tlsServer = selectedTls.options
1297
+ const tlsServer = selectedTls?.options
1283
1298
  ? https.createServer(selectedTls.options, serveDevelopmentRequest)
1284
1299
  : null;
1285
1300
  const server = await deployDevelopmentServer(fileServer, signal, {tlsServer, host, port});
1286
- const listeners = [fileServer.server, server];
1301
+ const listeners = [...new Set([fileServer.server, server])];
1287
1302
  const address=server.address();
1288
1303
  if(!address||is.string(address)){
1289
1304
  await closeDevelopmentListeners(fileServer, tlsServer);
@@ -1,6 +1,6 @@
1
1
  import Is from 'strong-type';
2
2
  import {createHash,randomUUID,timingSafeEqual} from 'node:crypto';
3
- import http from 'node:http';
3
+ import {Server} from 'node-http-server';
4
4
 
5
5
  const is = new Is(false);
6
6
 
@@ -1283,40 +1283,16 @@ export function createResendMailRequestHandler(options={}){
1283
1283
  };
1284
1284
  }
1285
1285
 
1286
- function listen(server,{host,port}){
1286
+ function deployMailServer(mailServer){
1287
1287
  return new Promise(function waitForMailListener(resolve,reject){
1288
- function cleanup(){
1289
- server.removeListener('error',onError);
1290
- server.removeListener('listening',onListening);
1291
- }
1292
1288
  function onError(error){
1293
- cleanup();
1294
1289
  reject(error);
1295
1290
  }
1296
- function onListening(){
1297
- cleanup();
1298
- resolve();
1299
- }
1300
- server.once('error',onError);
1301
- server.once('listening',onListening);
1302
- server.listen({exclusive:true,host,port});
1303
- });
1304
- }
1305
-
1306
- function closeHttpServer(server){
1307
- return new Promise(function waitForHttpServerClose(resolve,reject){
1308
- if(!server.listening){
1309
- resolve();
1310
- return;
1311
- }
1312
- server.close(function finishHttpServerClose(error){
1313
- if(error){
1314
- reject(error);
1315
- }else{
1316
- resolve();
1317
- }
1291
+ mailServer.deploy(function onListening(instance,server){
1292
+ server.removeListener('error',onError);
1293
+ resolve(server);
1318
1294
  });
1319
- server.closeIdleConnections?.();
1295
+ mailServer.server.once('error',onError);
1320
1296
  });
1321
1297
  }
1322
1298
 
@@ -1325,29 +1301,27 @@ export async function startResendMailServer(options={}){
1325
1301
  if(configuration.signal?.aborted){
1326
1302
  throw configuration.signal.reason??new Error('Mail server start was cancelled.');
1327
1303
  }
1328
- const requestHandler=createResendMailRequestHandler(options);
1329
- const server=http.createServer(requestHandler.handle);
1330
- server.on('clientError',function rejectMalformedClient(error,socket){
1331
- if(!socket.writable){
1332
- return;
1333
- }
1334
- socket.end(
1335
- 'HTTP/1.1 400 Bad Request\r\n'
1336
- +'Connection: close\r\n'
1337
- +'Content-Length: 0\r\n'
1338
- +'\r\n'
1339
- );
1304
+ const mailServer=new Server({
1305
+ host:configuration.host,
1306
+ port:configuration.port,
1307
+ server:{timeout:0}
1340
1308
  });
1309
+ const requestHandler=createResendMailRequestHandler(options);
1310
+ mailServer.onRawRequest=function handleMailRequest(request,response){
1311
+ requestHandler.handle(request,response);
1312
+ return true;
1313
+ };
1341
1314
 
1315
+ let server;
1342
1316
  try{
1343
- await listen(server,{host:configuration.host,port:configuration.port});
1317
+ server=await deployMailServer(mailServer);
1344
1318
  }catch(error){
1345
- await requestHandler.close();
1319
+ await Promise.allSettled([mailServer.close(),requestHandler.close()]);
1346
1320
  throw error;
1347
1321
  }
1348
1322
  const address=server.address();
1349
1323
  if(!address||is.string(address)||!isNumericLoopback(address.address)){
1350
- await Promise.allSettled([closeHttpServer(server),requestHandler.close()]);
1324
+ await Promise.allSettled([mailServer.close(),requestHandler.close()]);
1351
1325
  throw configurationError('Mail server did not bind to a numeric loopback address.');
1352
1326
  }
1353
1327
  const displayHost=address.address.includes(':')?`[${address.address}]`:address.address;
@@ -1365,7 +1339,7 @@ export async function startResendMailServer(options={}){
1365
1339
  configuration.signal?.removeEventListener('abort',closeFromSignal);
1366
1340
  const handlerClosing=requestHandler.close();
1367
1341
  try{
1368
- await Promise.all([closeHttpServer(server),handlerClosing]);
1342
+ await Promise.all([mailServer.close(),handlerClosing]);
1369
1343
  resolveLifecycle();
1370
1344
  }catch(error){
1371
1345
  rejectLifecycle(error);
package/src/toolchain.mjs CHANGED
@@ -516,6 +516,7 @@ export async function developApplication(options = {}) {
516
516
  host:options.host,
517
517
  port:options.port,
518
518
  httpPort:options.httpPort,
519
+ http: options.http,
519
520
  https:options.https,
520
521
  tls:options.tls,
521
522
  certPath:options.certPath,