@synopackageland/cli 0.27.1 → 0.28.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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.28.0 (2026-10-24)
4
+
5
+ - Depend on templates `~0.28.0` with SDK/App 0.28.0 and UI 0.14.10. SDK 0.28.0 reuses executor/v0.27.0 through executor manifest format 1; no executor digests change.
6
+ - Default the Compose startup budget to 900 seconds and document slow-NAS readiness; the SDK owns health timing and the Starting status. No per-App option is added.
7
+ - Keep UI CGI scripts executable in built SPKs, and let external OIDC resume tokens reach only the fixed DSM CGI.
8
+ - The release gate checks SDK 0.27.1+ executor builds against the SDK executor manifest instead of requiring a frozen legacy table entry.
9
+
3
10
  ## 0.27.1 (2026-10-23)
4
11
 
5
12
  - Read executor release mappings, toolchain and six payload pins from SDK executor manifest format 1. Accept compatible SDK minors independently of CLI versions; keep the frozen 0.26.0/0.27.0 fallback. Remove the CLI digest-recording script.
package/README.md CHANGED
@@ -66,8 +66,8 @@ 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.27.1 depends on `~0.27.0`: npm installation or
70
- update can select the latest templates patch in the 0.27 minor line. Templates
69
+ `@synopackageland/templates`. CLI 0.28.0 depends on `~0.28.0`: npm installation or
70
+ update can select the latest templates patch in the 0.28 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;
73
73
  commit a lockfile and use `npm ci` to reproduce a chosen dependency tree.
@@ -13,7 +13,7 @@ export const COMPOSE_ERRORS = {
13
13
  COMPOSE_STARTUP_INVALID: {
14
14
  code: "COMPOSE_STARTUP_INVALID",
15
15
  description: "The container startup budget is invalid.",
16
- suggestion: "Set compose.startup_timeout_seconds to an integer from 2 to 900 seconds; the default is 120. Read the same SYNOPKGLAND_STARTUP_BUDGET_SECONDS in the App bootstrap adapter.",
16
+ suggestion: "Set compose.startup_timeout_seconds to an integer from 2 to 900 seconds; the default is 900, which a loaded HDD NAS may need. Read the same SYNOPKGLAND_STARTUP_BUDGET_SECONDS in the App bootstrap adapter.",
17
17
  },
18
18
  COMPOSE_STARTUP_TIMEOUT: {
19
19
  code: "COMPOSE_STARTUP_TIMEOUT",
@@ -82,8 +82,8 @@ export const COMPOSE_ERRORS = {
82
82
  },
83
83
  COMPOSE_HEALTH_NOT_READY: {
84
84
  code: "COMPOSE_HEALTH_NOT_READY",
85
- description: "The App health endpoint did not return the expected status.",
86
- suggestion: "Check the declared App health path/status and upstream health; redirects are not followed.",
85
+ description: "The App health endpoint did not return the expected status within the startup budget.",
86
+ suggestion: "Check the declared App health path/status and upstream health; redirects are not followed. daemon/daemon.log records how long the App stayed unavailable. Keep the default startup budget on slow NAS devices.",
87
87
  },
88
88
  COMPOSE_REQUEST_INVALID: {
89
89
  code: "COMPOSE_REQUEST_INVALID",
@@ -77,7 +77,7 @@ export function readComposeApp(value, root, arches, fail, context = {}) {
77
77
  if (value.startup_timeout_seconds !== undefined &&
78
78
  (!Number.isInteger(value.startup_timeout_seconds) ||
79
79
  value.startup_timeout_seconds < 2 || value.startup_timeout_seconds > 900))
80
- return invalid("compose.startup_timeout_seconds must be an integer from 2 to 900 seconds (default 120).", "COMPOSE_STARTUP_INVALID");
80
+ return invalid("compose.startup_timeout_seconds must be an integer from 2 to 900 seconds (default 900).", "COMPOSE_STARTUP_INVALID");
81
81
  let hostUdp;
82
82
  if (value.host_udp !== undefined) {
83
83
  const ports = new Set();
@@ -178,7 +178,7 @@ export function composeDaemon(m) {
178
178
  args: ["run"],
179
179
  targets: m.compose.targets,
180
180
  env: {},
181
- timeouts: { stop: 30, status: 5, uninstall: 60, ...m.lifecycleTimeouts, start: m.compose.plan.startup_timeout_seconds ?? m.lifecycleTimeouts?.start ?? 120 },
181
+ timeouts: { stop: 30, status: 5, uninstall: 60, ...m.lifecycleTimeouts, start: m.compose.plan.startup_timeout_seconds ?? m.lifecycleTimeouts?.start ?? 900 },
182
182
  };
183
183
  }
184
184
  export function composeNetwork(m) {
@@ -1,2 +1,2 @@
1
- /** No caller-provided URL reaches location: only a transaction and a declared port. */
1
+ /** Only the fixed DSM CGI receives a resume token; external origins are resolved by the SDK. */
2
2
  export declare function oidcResumeRuntime(pkg: string, ports: number[]): string;
@@ -1,4 +1,4 @@
1
- /** No caller-provided URL reaches location: only a transaction and a declared port. */
1
+ /** Only the fixed DSM CGI receives a resume token; external origins are resolved by the SDK. */
2
2
  export function oidcResumeRuntime(pkg, ports) {
3
3
  return `(function(param) {
4
4
  if (typeof param === 'string') {
@@ -14,13 +14,18 @@ export function oidcResumeRuntime(pkg, ports) {
14
14
  }
15
15
  if (!param || typeof param !== 'object' || Array.isArray(param)) return false;
16
16
  var value = param && param.ssoResume;
17
- if (typeof value !== 'string') return false;
17
+ if (typeof value !== 'string' || /\\s/.test(value)) return false;
18
+ var external = /^[A-Za-z0-9_-]{1,128}:x[01]:[0-9]{1,19}\\.[0-9]{1,19}$/.test(value);
18
19
  var parts = /^([A-Za-z0-9_-]{1,128}):([0-9]{1,5})$/.exec(value);
19
- if (!parts || ${JSON.stringify(ports)}.indexOf(Number(parts[2])) < 0) return false;
20
+ if (!external && (!parts || ${JSON.stringify(ports)}.indexOf(Number(parts[2])) < 0)) return false;
20
21
  var target = new URL(${JSON.stringify(`/webman/3rdparty/${pkg}/sso/authorize.cgi`)},location.origin);
21
- var app = new URL(location.origin); app.port = parts[2]; app.pathname = '/_sso/dsm-return';
22
- target.searchParams.set('return',app.href);
23
- target.searchParams.set('txn',parts[1]);
22
+ if (external) {
23
+ target.searchParams.set('resume',value);
24
+ } else {
25
+ var app = new URL(location.origin); app.port = parts[2]; app.pathname = '/_sso/dsm-return';
26
+ target.searchParams.set('return',app.href);
27
+ target.searchParams.set('txn',parts[1]);
28
+ }
24
29
  target.searchParams.set('login_attempt','1');
25
30
  location.replace(target.href);
26
31
  return true;
@@ -410,7 +410,7 @@ function copyUiTree(source, target) {
410
410
  throw new StandaloneBuildError("STANDALONE_UI_TOO_LARGE", "The UI directory exceeds 64 MiB.");
411
411
  }
412
412
  copyFileSync(src, join(to, name));
413
- chmodSync(join(to, name), FILE_MODE);
413
+ chmodSync(join(to, name), name.endsWith(".cgi") ? EXEC_MODE : FILE_MODE);
414
414
  }
415
415
  else {
416
416
  throw new StandaloneBuildError("STANDALONE_UI_SPECIAL_FILE", `The UI directory may contain only regular files and directories: ${src}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.27.1",
3
+ "version": "0.28.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.27.0",
30
+ "@synopackageland/templates": "~0.28.0",
31
31
  "ajv": "^8.17.1",
32
32
  "commander": "^13.1.0",
33
33
  "json-canonicalize": "^3.0.1",
@@ -37,14 +37,21 @@ scripts run as the package account; containers are managed by Container Manager.
37
37
 
38
38
  ## Startup budget
39
39
 
40
- Set `compose.startup_timeout_seconds: 600` for a slow cold start or migration.
41
- Omitting it uses 120 seconds; only integers from 2 through 900 are accepted.
40
+ Omit `compose.startup_timeout_seconds`: the default of 900 seconds lets a loaded
41
+ HDD NAS with 2 GB RAM start the App. Declare a shorter value only with device
42
+ evidence; only integers from 2 through 900 are accepted.
42
43
  Lock waiting, status, readiness, stability and handoff share that deadline; there
43
44
  is no extra fixed handoff allowance. The cap is a policy bound, not a measured
44
45
  DSM lifecycle limit. `daemon: {timeouts: {stop: 120, status: 15, uninstall: 300}}`
45
46
  configures the other lifecycle phases without declaring a daemon payload.
46
- A transient health failure closes new ingress while the running App gets one
47
- operation-budget recovery window; authority or generation failures stop at once.
47
+ One readiness probe may take 30 seconds. A health failure after startup closes
48
+ new ingress and the gateway shows a localized, self-refreshing starting page
49
+ (`app_starting` for API clients) while DSM status stays running; the App gets
50
+ the startup budget to recover before its gateway stops. Authority or generation
51
+ failures stop at once. The SDK raises stricter Compose healthcheck timing to
52
+ timeout 30s, retries 3 and start_period equal to the startup budget; declare the
53
+ health command only, without timing or short in-command limits such as
54
+ `curl --max-time 2`.
48
55
  Use a matching SDK that implements the field and rebuild the App; older planners
49
56
  are rejected for explicit budgets. The SDK plan drives generated daemon start
50
57
  and supplies every container with `SYNOPKGLAND_STARTUP_BUDGET_SECONDS`, overriding
@@ -93,7 +100,7 @@ a build operator trust decision. `check`/`inspect` remain offline.
93
100
 
94
101
  | Feature | Current behavior |
95
102
  | --- | --- |
96
- | Files in one directory, profiles, anchors, depends_on, healthcheck | Reference-loader semantics retained; DSM worker compatibility needs device acceptance |
103
+ | Files in one directory, profiles, anchors, depends_on, healthcheck | Reference-loader semantics retained; healthcheck timing receives the SDK slow-NAS floor; DSM worker compatibility needs device acceptance |
97
104
  | Named local volumes | Stable package namespace, retained across recreation; inspect inventory |
98
105
  | Private bind data | DSM build rejects COMPOSE_PRIVATE_BIND_TIMING_UNSUPPORTED: clean-install preinst precedes pkgvar creation, while worker acquire precedes postinst; no root initialization or automatic volume conversion |
99
106
  | Image VOLUME | Build inventories verified image config and assigns a stable named volume |
@@ -115,7 +115,7 @@ Use existing `daemon.timeouts` for start/stop/status/uninstall (defaults
115
115
  30/30/5/60 seconds). Start/stop/uninstall accept 2–3600 seconds and status accepts
116
116
  1–3600. A `daemon` containing only `timeouts` declares budgets without an
117
117
  executable, including background and Compose Apps; Compose start still uses
118
- `compose.startup_timeout_seconds` (120, 2–900). Background user-manager recovery
118
+ `compose.startup_timeout_seconds` (900, 2–900). Background user-manager recovery
119
119
  uses the start allowance and its generated package user unit uses the stop
120
120
  allowance with `KillMode=control-group`.
121
121