@synopackageland/cli 0.41.2 → 0.41.4

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
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.41.4 (2026-10-30)
6
+
7
+ - Allow `webapi` raw profiles to set `raw_max_bytes` up to 2 GiB, matching
8
+ SDK/App 0.38.2. That SDK removes Go `JobWebAPI.CallRaw` and
9
+ `RawWebAPIResult.Body`; raw consumers must move to `CallRawTo(..., io.Writer)`.
10
+ Templates ~0.35.2 still pin SDK 0.38.1; declare SDK 0.38.2 to use larger raw
11
+ downloads.
12
+
13
+ ## 0.41.3 (2026-10-30)
14
+
15
+ - Build Portal Apps without an executor on DSM 7.2-64570+ using the App SDK
16
+ pin for the panel text. Keep executor SDK selection unchanged.
17
+ - Reject a no-executor Portal without an SDK during both check and build
18
+ (`STANDALONE_PORTAL_SDK_REQUIRED`); cover the Portal/executor/SDK/DSM matrix
19
+ and a real Go CGI-to-SPK build.
20
+ - Complete diagnostics for invalid external caller declarations and entry policies.
21
+
5
22
  ## 0.41.2 (2026-10-29)
6
23
 
7
24
  - Require templates ~0.35.2 with SDK/App 0.38.1 and clean registry metadata.
package/README.md CHANGED
@@ -76,13 +76,19 @@ and checks both payloads against the resolved SDK's `executor-manifest.json`.
76
76
  The manifest declares format 1, the exact executor release, its Go toolchain and
77
77
  six SHA-256 digests. SDK minor versions are independent of CLI minor versions.
78
78
 
79
+ Portal panel text comes from the App's SDK even when it has no executor: the
80
+ existing `identity_gateway.sdk` pin, or otherwise the backend SDK requirement
81
+ (`backend/go.mod`, `backend.sdk`, Rust SDK tag, or project `go.mod` for a binary).
82
+ A Portal without an SDK is rejected by both `check` and `build` with
83
+ `STANDALONE_PORTAL_SDK_REQUIRED`. DSM 7.2-64570+ needs no executor for Portal.
84
+
79
85
  SDK module downloads preserve normal Go checksum verification and the user's Go
80
86
  settings; Go Apps also check their existing `backend/go.sum`. Commit that file
81
87
  alongside npm lockfiles. Do not disable checksum verification for public SDK
82
88
  packages. Private mirrors can use explicitly configured Go authentication and
83
89
  checksum settings; a failed fetch reports `SDK_DOWNLOAD_FAILED`.
84
90
 
85
- CLI 0.41.2 uses templates ~0.35.2, whose default SDK pin is 0.38.1. Compose import and SDK defaults
91
+ CLI 0.41.4 uses templates ~0.35.2, whose default SDK pin is 0.38.1. Compose import and SDK defaults
86
92
  read the SDK pin from the installed templates; explicit App SDK declarations take precedence.
87
93
  Every App method must be declared in the permissions table. Dynamic names and
88
94
  custom HTTP dispatchers are rejected. With SDK 0.33.0, use fixed
@@ -93,7 +99,7 @@ unimported development commands are excluded. Use
93
99
  authentication. See [App entry and registration](docs/app-entry.md).
94
100
 
95
101
  `init` and `add` read their templates from the locally installed
96
- `@synopackageland/templates`. CLI 0.41.2 depends on `~0.35.2`: npm installation or
102
+ `@synopackageland/templates`. CLI 0.41.4 depends on `~0.35.2`: npm installation or
97
103
  update can select the latest templates patch in the 0.35 minor line. Templates
98
104
  patches need only a templates release; update the npm installation to receive them.
99
105
  The CLI never fetches templates at runtime, so offline use works after installation;
@@ -38,6 +38,21 @@ export const ERROR_REGISTRY = {
38
38
  description: "The launcher has Portal fields without a matching explicit Portal declaration.",
39
39
  suggestion: "Remove defaultAlias and login fields, or explicitly enable portal and rebuild.",
40
40
  },
41
+ CTL_EXTERNAL_TRANSPORT: {
42
+ code: "CTL_EXTERNAL_TRANSPORT",
43
+ description: "External callers require the DSM session transport.",
44
+ suggestion: "Use the DSM session transport for external entries; remove capabilities.identity from an external-enabled App.",
45
+ },
46
+ CTL_EXTERNAL_ENTRY: {
47
+ code: "CTL_EXTERNAL_ENTRY",
48
+ description: "An external entry is not a registered administrator RPC method.",
49
+ suggestion: "Register a fixed RPC method, declare it as administrators in permissions.json, and reference it in ctl.external.",
50
+ },
51
+ CTL_EXTERNAL_DECLARATION_INVALID: {
52
+ code: "CTL_EXTERNAL_DECLARATION_INVALID",
53
+ description: "ctl.external contains an invalid protocol or RPC method declaration.",
54
+ suggestion: "Use protocol names mapped to objects with a fixed method, for example ctl.external.mcp: {method: app.mcp}.",
55
+ },
41
56
  LAUNCHER_TYPE_MISMATCH: {
42
57
  code: "LAUNCHER_TYPE_MISMATCH",
43
58
  description: "The built launcher or INFO entry differs from the declared launch mode.",
@@ -618,6 +633,11 @@ export const ERROR_REGISTRY = {
618
633
  description: "console is declared together with portal.",
619
634
  suggestion: "Declare either console or portal, not both.",
620
635
  },
636
+ STANDALONE_PORTAL_SDK_REQUIRED: {
637
+ code: "STANDALONE_PORTAL_SDK_REQUIRED",
638
+ description: "A Portal App has no SDK pin for its panel text.",
639
+ suggestion: "Declare the App SDK in backend/go.mod (project go.mod for a binary), backend.sdk, the Rust SDK dependency, or identity_gateway.sdk; otherwise remove portal. An executor is not required on DSM 7.2-64570+.",
640
+ },
621
641
  STANDALONE_PORTAL_REQUIRES_EXECUTOR: {
622
642
  code: "STANDALONE_PORTAL_REQUIRES_EXECUTOR",
623
643
  description: "An App targeting DSM below 7.2-64570 declares portal without an executor.",
@@ -8,7 +8,7 @@ export function validWebAPIProfiles(value) {
8
8
  for (const [name, p] of Object.entries(value)) {
9
9
  if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || name === 'home' || !record(p) || Object.keys(p).some(k => !['purpose', 'apis', 'raw_max_bytes'].includes(k)) || typeof p.purpose !== 'string' || !p.purpose.trim() || Buffer.byteLength(p.purpose) > 1024 || /[\x00-\x1f\x7f]/.test(p.purpose) || !record(p.apis) || !Object.keys(p.apis).length)
10
10
  return false;
11
- if (p.raw_max_bytes !== undefined && (!Number.isSafeInteger(p.raw_max_bytes) || Number(p.raw_max_bytes) < 0 || Number(p.raw_max_bytes) > 67108864))
11
+ if (p.raw_max_bytes !== undefined && (!Number.isSafeInteger(p.raw_max_bytes) || Number(p.raw_max_bytes) < 0 || Number(p.raw_max_bytes) > 2147483648))
12
12
  return false;
13
13
  for (const [api, methods] of Object.entries(p.apis)) {
14
14
  if (!/^SYNO\.[A-Za-z][A-Za-z0-9]*(\.[A-Za-z][A-Za-z0-9]*)*$/.test(api) || api.toLowerCase().split('.').slice(1).some(x => blocked.has(x)) || !record(methods) || !Object.keys(methods).length)
@@ -25,6 +25,7 @@ import { isBridgeBackend } from "./bridge-backend.js";
25
25
  import { requestTimeoutSeconds } from "./request-timeout.js";
26
26
  import { buildBridge, prepareBridge, } from "./bridge-build.js";
27
27
  import { portalCatalogs } from "./portal-window.js";
28
+ import { portalSdkVersion } from "./portal.js";
28
29
  /** Argv only, never a shell; output streams to the terminal except stderr, which is also kept, and captured stdout. */
29
30
  export const runCommand = (command, args, options) => new Promise((done) => {
30
31
  const child = spawn(command, args, {
@@ -159,9 +160,9 @@ export async function buildStandalone(options) {
159
160
  const installSettingsControl = await buildInstallSettings(manifest, arch, archWork, run);
160
161
  const appBackupControl = await buildAppBackup(manifest, arch, archWork, run, (w) => warnings.add(w));
161
162
  const composeStorageInit = await buildComposeStorageInit(manifest, arch, archWork, run);
162
- // A portal needs the executor, so the executor's SDK carries the panel text.
163
- const portalText = manifest.portal && executor
164
- ? portalCatalogs(sdkSource(executor.sdk).goDir)
163
+ // Executor Apps keep their executor's SDK; no-root Apps use their own SDK pin.
164
+ const portalText = manifest.portal
165
+ ? portalCatalogs(sdkSource(executor?.sdk ?? portalSdkVersion(manifest)).goDir)
165
166
  : undefined;
166
167
  await assembleStandaloneSpk({
167
168
  manifest: builtManifest,
@@ -30,6 +30,7 @@ import { LOGIN_COLOR, loginImageProblem, } from "../build-spk/login-style.js";
30
30
  import { validateReproducible } from "../reproducible-config.js";
31
31
  import { appSdkVersion, SDK_MODULE, sdkSource, } from "../runtime/sdk-source.js";
32
32
  import { sdkProblems } from "./sdk-check.js";
33
+ import { portalSdkVersion } from "./portal.js";
33
34
  import { readWindowSize } from "./window-size.js";
34
35
  import { appSdkTimeouts } from "../runtime/sdk-timeouts.js";
35
36
  import { requestTimeoutSeconds, rootRunDeadlineProblem, TIMEOUT_USAGE, validTimeoutSeconds, } from "./request-timeout.js";
@@ -466,6 +467,16 @@ function readManifest(raw, c) {
466
467
  if (portal && osMinVer && !isDsmAtLeast(osMinVer, PORTAL_EXECUTOR_MIN_DSM)) {
467
468
  c.fail("STANDALONE_PORTAL_MIN_DSM", portalMinDsmMessage(osMinVer, "portal"));
468
469
  }
470
+ if (portal && backend && osMinVer && !portalByExecutor(osMinVer) &&
471
+ !capabilities.rootOps && !capabilities.delegate && !capabilities.webapi) {
472
+ try {
473
+ if (!portalSdkVersion({ backend, root: c.root, identityGateway }))
474
+ c.fail("STANDALONE_PORTAL_SDK_REQUIRED", "portal needs the App's SDK for its panel text: require github.com/synopackageland/syno-pkg-land-sdk in the Go backend's go.mod (project go.mod for a binary), set backend.sdk for Python/Node, pin the Rust SDK release, or use identity_gateway.sdk. Remove portal if the App has no SDK.");
475
+ }
476
+ catch {
477
+ // sdkProblems reports an invalid backend SDK pin above.
478
+ }
479
+ }
469
480
  const entryProblem = portal && packageId && displayname && osMinVer && portalByExecutor(osMinVer)
470
481
  ? portalEntryProblem(packageId, {
471
482
  application: displayname,
@@ -1,5 +1,7 @@
1
1
  import type { StandaloneManifest } from "./manifest.js";
2
2
  import type { PortalPackage } from "../portal/package.js";
3
+ /** The App's existing gateway pin or backend SDK; never the CLI/templates default. */
4
+ export declare function portalSdkVersion(manifest: Pick<StandaloneManifest, "backend" | "root" | "identityGateway">): string | undefined;
3
5
  /**
4
6
  * A native portal registered through DSM's web-config resource; it opens the
5
7
  * package's own DSM window. Below DSM 7.2 the executor registers it instead.
@@ -1,4 +1,9 @@
1
1
  import { portalByExecutor } from "../build-spk/dsm-version.js";
2
+ import { appSdkVersion } from "../runtime/sdk-source.js";
3
+ /** The App's existing gateway pin or backend SDK; never the CLI/templates default. */
4
+ export function portalSdkVersion(manifest) {
5
+ return manifest.identityGateway?.sdk ?? appSdkVersion(manifest.backend.kind === "binary" ? { kind: "go", dir: manifest.root } : manifest.backend);
6
+ }
2
7
  /**
3
8
  * A native portal registered through DSM's web-config resource; it opens the
4
9
  * package's own DSM window. Below DSM 7.2 the executor registers it instead.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.41.2",
3
+ "version": "0.41.4",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
package/skills/SKILL.md CHANGED
@@ -24,7 +24,7 @@ Use this guide to initialize, build and inspect own applications or upstream OSS
24
24
 
25
25
  For an existing open-source App, first read [reference/upstream-fork.md](reference/upstream-fork.md): upstream fork/history + untouched upstream files + all packaging in `synology/`, official container images pinned by tag/index digest, native official release binaries pinned by SHA-256 or a source build hook, and a single tag-merge upgrade command. Use `init --upstream-fork`; never rewrite the App as a component. Integrated upstream Apps require direct DSM administrator entry without a separate App password or an OIDC/SSO server: supported trusted headers first, disabling native login second; the shipped OpenList pattern uses the existing SDK 0.17.3 token adapter (`mode: token`, `access: dsm-admins`, `entry: admin-only`) with server-side Authorization injection. All DSM admins share one upstream admin; token reset requires package restart; WebDAV retains its native credentials.
26
26
 
27
- Use SDK/App 0.38.1 and CLI 0.41.2 for App entry enforcement. Declare every App method
27
+ Use SDK/App 0.38.2 and CLI 0.41.4 for App entry enforcement. Declare every App method
28
28
  in the permissions table; dynamic names and unregistered HTTP dispatchers are rejected.
29
29
  Use fixed SDK routes and `identity_gateway.upstream_auth_paths` for client API
30
30
  paths that rely on upstream authentication. Read
@@ -2,6 +2,8 @@
2
2
 
3
3
  Tier: a schema-2 App targeting DSM 7.2-64570+ may declare a Portal without an executor, including a unified Compose App. The CLI adds `instuninst_restart_services="nginx.service"` to INFO (merging service names without duplicates), so DSM reloads nginx after install, upgrade and uninstall. App hooks still run as the package account; a no-privilege App never runs root code. Declaring a Portal does not activate an alias or automatically open it: an administrator must apply the alias through DSM.
4
4
 
5
+ Portal needs the App's SDK for its panel text, including the no-executor route. The CLI uses the existing `identity_gateway.sdk` pin when present; otherwise it reads the backend SDK (`backend/go.mod` for Go, `backend.sdk` for Python/Node, the Rust release tag in Cargo.lock, or project `go.mod` for a binary). It does not substitute the CLI/templates SDK default. Without an SDK, `check` and `build` both refuse `STANDALONE_PORTAL_SDK_REQUIRED`: declare the App's SDK or remove Portal. Executor Apps continue using their executor's SDK.
6
+
5
7
  On DSM 7.1 the unsigned web-config worker is unavailable, so entry registration still requires an executor (`rootOps`, `delegate` or `webapi`). `check`/`build` and `add portal` refuse a no-executor App targeting below 7.2 with `STANDALONE_PORTAL_REQUIRES_EXECUTOR`; raise `os_min_ver` to 7.2-64570+, add an executor capability, or omit Portal. `add portal` preserves a supported DSM target; it does not silently upgrade a no-privilege App's target or add root capabilities. The legacy `integration.web.portal` contract is unsupported; use the schema-2 top-level `portal` declaration.
6
8
 
7
9
  Declare: `portal: {}` or `portal: {alias: my-app}` in dsm-package.yaml (`synopkgland add portal` with os_min_ver 7.2-64570+, or `synopkgland add root-ops portal` for the DSM 7.1 executor route). The alias defaults to the package name in lowercase. Aliases are 1–64 lowercase letters, digits or `-`, not starting or ending with `-`, and not a DSM path or Synology App alias (`webman`, `file`, …). The native-layout `backend: {kind: native}` owns its conf/scripts and its own root helper: `portal: {}` or `{alias}` there needs os_min_ver 7.1-42661+ and writes `synopkgland_portal_alias="<alias>"` and the nginx reload declaration into INFO, which Package Center writes as root, so the product's root helper can trust it; the helper registers the app.d entry (application = INFO displayname) and owns its uninstall cleanup and wizard. inspect checks that INFO line against `native-layout.json`.