@synopackageland/cli 0.29.0 → 0.30.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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.30.0 (2026-10-24)
4
+
5
+ - Package and forward `identity_gateway.external_access.single_origin`; use SDK 0.30.0 for one-URL Apps, provider-aware DSM menu launches and launch probes.
6
+ - Pin templates 0.30.x and its SDK/App 0.30.0 defaults; keep immutable executor/v0.27.0.
7
+
8
+ ## Unreleased
9
+
10
+ - Accept and package `identity_gateway.external_access.single_origin` (boolean) for one-URL upstreams. Build requires SDK sources with `single_origin` support, and inspect accepts the packaged `single_origin: true` declaration.
11
+
3
12
  ## 0.29.0 (2026-10-24)
4
13
 
5
14
  - Depend on templates `~0.29.0` with SDK/App 0.29.0 and UI 0.14.10. SDK 0.29.0 reuses executor/v0.27.0 through executor manifest format 1; no executor digests change.
package/README.md CHANGED
@@ -66,7 +66,7 @@ CLI-only patch releases can differ from the SDK patch. Compose import and SDK de
66
66
  read the SDK pin from the installed templates; explicit App SDK declarations take precedence.
67
67
 
68
68
  `init` and `add` read their templates from the locally installed
69
- `@synopackageland/templates`. CLI 0.29.0 depends on `~0.29.0`: npm installation or
69
+ `@synopackageland/templates`. CLI 0.30.0 depends on `~0.30.0`: npm installation or
70
70
  update can select the latest templates patch in the 0.29 minor line. Templates
71
71
  patches need only a templates release; update the npm installation to receive them.
72
72
  The CLI never fetches templates at runtime, so offline use works after installation;
@@ -308,7 +308,8 @@ and default to `external_access: {connect: optional, origin_change: none}`.
308
308
  Declare `identity_gateway.external_access.connect: none` to ignore Connect's
309
309
  public URL projection; manual URLs remain available without Connect. Use
310
310
  `origin_change: confirm` for Apps that need confirmation when their public
311
- origin changes. `connect: required` is rejected, and declaring `external_access`
311
+ origin changes, and `single_origin: true` (SDK 0.30.0+) for an upstream that
312
+ works on one URL only. `connect: required` is rejected, and declaring `external_access`
312
313
  with SDK older than 0.29.0 is rejected. Compose and gateway SDK pins must match.
313
314
  See [external access configuration](skills/reference/identity-gateway.md#external-access)
314
315
  for the manifest, settings entry, URL rules and cold-login handoff.
@@ -365,7 +365,7 @@ export const ERROR_REGISTRY = {
365
365
  STANDALONE_IDENTITY_GATEWAY_EXTERNAL_INVALID: {
366
366
  code: "STANDALONE_IDENTITY_GATEWAY_EXTERNAL_INVALID",
367
367
  description: "The external access declaration does not match the gateway contract.",
368
- suggestion: "Set identity_gateway.external_access.connect to optional or none, and origin_change to none or confirm; required is not supported.",
368
+ suggestion: "Set identity_gateway.external_access.connect to optional or none, origin_change to none or confirm, and single_origin to a boolean; required is not supported.",
369
369
  },
370
370
  STANDALONE_IDENTITY_GATEWAY_EXTERNAL_SDK_REQUIRED: {
371
371
  code: "STANDALONE_IDENTITY_GATEWAY_EXTERNAL_SDK_REQUIRED",
@@ -16,6 +16,8 @@ export type GatewayMode = "none" | "oidc" | "header" | "token";
16
16
  export interface GatewayExternalAccess {
17
17
  connect: "optional" | "none";
18
18
  origin_change: "none" | "confirm";
19
+ /** One-URL upstream: DSM launch and local navigations go to the primary external origin. */
20
+ single_origin?: true;
19
21
  }
20
22
  export declare const EXTERNAL_ACCESS_MIN_SDK = "0.29.0";
21
23
  /** SDK 0.28 has the diagnostic ping only; older gateways reject external JSON. */
@@ -167,13 +167,14 @@ export function readIdentityGateway(value, daemon, fail, packagedPayload = false
167
167
  let externalAccess;
168
168
  if (v.external_access !== undefined) {
169
169
  const external = v.external_access;
170
- if (!record(external) || !only(external, ["connect", "origin_change"]) ||
170
+ if (!record(external) || !only(external, ["connect", "origin_change", "single_origin"]) ||
171
171
  (external.connect !== undefined && external.connect !== "optional" && external.connect !== "none") ||
172
- (external.origin_change !== undefined && external.origin_change !== "none" && external.origin_change !== "confirm")) {
173
- fail({ code: "STANDALONE_IDENTITY_GATEWAY_EXTERNAL_INVALID", message: "identity_gateway.external_access accepts only connect (optional or none) and origin_change (none or confirm); required is not supported." });
172
+ (external.origin_change !== undefined && external.origin_change !== "none" && external.origin_change !== "confirm") ||
173
+ (external.single_origin !== undefined && typeof external.single_origin !== "boolean")) {
174
+ fail({ code: "STANDALONE_IDENTITY_GATEWAY_EXTERNAL_INVALID", message: "identity_gateway.external_access accepts only connect (optional or none), origin_change (none or confirm) and single_origin (boolean); required is not supported." });
174
175
  return undefined;
175
176
  }
176
- externalAccess = { connect: external.connect ?? "optional", origin_change: external.origin_change ?? "none" };
177
+ externalAccess = { connect: external.connect ?? "optional", origin_change: external.origin_change ?? "none", ...(external.single_origin ? { single_origin: true } : {}) };
177
178
  }
178
179
  if (v.launch_path !== undefined) {
179
180
  try {
@@ -531,6 +532,10 @@ export async function buildIdentityGateway(m, arch, work, run) {
531
532
  if (!existsSync(config) || !/`json:"external(?:,omitempty)?"`/.test(readFileSync(config, "utf8")) ||
532
533
  !existsSync(main) || !/["']\/?settings\.cgi["']/.test(readFileSync(main, "utf8")))
533
534
  throw new StandaloneBuildError("STANDALONE_IDENTITY_GATEWAY_EXTERNAL_SDK_REQUIRED", `The selected SDK source lacks external access or settings.cgi support; use SDK ${EXTERNAL_ACCESS_MIN_SDK} or later with matching sources.`);
535
+ const external = join(sdk.goDir, "idgateway/external.go");
536
+ if (gatewayExternalAccess(m.identityGateway)?.single_origin &&
537
+ (!existsSync(external) || !/`json:"single_origin(?:,omitempty)?"`/.test(readFileSync(external, "utf8"))))
538
+ throw new StandaloneBuildError("STANDALONE_IDENTITY_GATEWAY_EXTERNAL_SDK_REQUIRED", "The selected SDK source lacks external_access.single_origin support; use a matching SDK release or an explicit development source override.");
534
539
  }
535
540
  if (m.identityGateway.oidc?.browser_redirect) {
536
541
  const capability = join(sdk.goDir, "idgateway/browser_redirect.go");
@@ -1135,8 +1135,9 @@ async function inspectExternalGateway(dir, paths, payload, saved, findings) {
1135
1135
  return;
1136
1136
  const external = saved;
1137
1137
  if (!external || typeof external !== "object" || Array.isArray(external) ||
1138
- Object.keys(external).length !== 2 || (external.connect !== "optional" && external.connect !== "none") ||
1138
+ Object.keys(external).length !== (external.single_origin === undefined ? 2 : 3) || (external.connect !== "optional" && external.connect !== "none") ||
1139
1139
  (external.origin_change !== "none" && external.origin_change !== "confirm") ||
1140
+ (external.single_origin !== undefined && external.single_origin !== true) ||
1140
1141
  !isDeepStrictEqual(cfg.external, external) || cfg.package !== info.package)
1141
1142
  throw new Error("external configuration differs from its package declaration");
1142
1143
  const arch = info.arch.split(/\s+/).map(familyOfArchToken).find(Boolean) ?? "x86_64";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@napi-rs/keyring": "^2.1.1",
30
- "@synopackageland/templates": "~0.29.0",
30
+ "@synopackageland/templates": "~0.30.0",
31
31
  "ajv": "^8.17.1",
32
32
  "commander": "^13.1.0",
33
33
  "json-canonicalize": "^3.0.1",
@@ -2,15 +2,16 @@
2
2
 
3
3
  ## External access
4
4
 
5
- Use SDK 0.29.0 or later with matching gateway sources. Add this to the existing
5
+ Use SDK 0.30.0 or later with matching gateway sources. Add this to the existing
6
6
  complete gateway declaration; keep the App's upstream, listener and auth policy:
7
7
 
8
8
  ```yaml
9
9
  identity_gateway:
10
- sdk: 0.29.0
10
+ sdk: 0.30.0
11
11
  external_access:
12
12
  connect: optional # optional | none
13
13
  origin_change: none # none | confirm
14
+ single_origin: false # true for an upstream that works on one URL only
14
15
  ```
15
16
 
16
17
  Omitting the block or either member uses `connect: optional` and
@@ -38,11 +39,18 @@ installation. It edits only `manual_origin` and `dsm_origin` in private
38
39
  URL fields.
39
40
 
40
41
  Public origins must use HTTPS and a DNS hostname. Port 443 is normalized;
41
- other ports, IP addresses, paths, queries and user information are rejected,
42
+ other ports (except a single-origin App's `manual_origin`), IP addresses, paths, queries and user information are rejected,
42
43
  as is an origin equal to the DSM origin. Manual takes precedence, then the
43
44
  Connect projection when `connect: optional`; the first effective origin is
44
45
  primary. `connect: none` ignores that projection. Private URLs are display-only.
45
46
  Use `origin_change: confirm` for an App that must confirm public-origin changes.
47
+ Declare `single_origin: true` (with `origin_change: confirm`) for an App whose
48
+ upstream sign-in works on one fixed URL, such as Vaultwarden's `DOMAIN`. While
49
+ an external origin is set, the DSM launcher opens the primary origin and local
50
+ browser navigations redirect to it; the settings page says that only that
51
+ address can sign in. Without an external origin nothing changes. The generated
52
+ `external` object then carries `"single_origin":true`, and build rejects a
53
+ development SDK source without `single_origin` support.
46
54
 
47
55
  Cold DSM login preserves external resume tokens
48
56
  `<txn>:x<n>:<r1>.<r2>` in the fixed `authorize.cgi?resume=<encoded token>`
@@ -260,3 +268,5 @@ control characters are rejected. Caller query values cannot override it.
260
268
  Use the upstream's supported entry route; this does not modify upstream UI,
261
269
  produce an App session, or unlock encrypted data. HTTPS and App-specific
262
270
  fragment/query semantics still require deployment acceptance.
271
+
272
+ Single-origin Apps require SDK 0.30.0 or later. Their `manual_origin` may include an explicit HTTPS port (for example, `https://example.ts.net:29443`). The DSM launcher resolves the destination for the provider used to open DSM, probes `/_spl/ping` for up to eight seconds, and offers **Open anyway** if the probe fails. A single-origin App always uses its configured primary origin. Other Apps and the `external-origin` install setting continue to require HTTPS DNS origins on port 443.