@synopackageland/cli 0.9.0 → 0.9.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +144 -1
  2. package/README.md +1 -1
  3. package/dist/add.js +12 -0
  4. package/dist/build-spk/dsm-version.d.ts +0 -2
  5. package/dist/build-spk/dsm-version.js +0 -6
  6. package/dist/build-spk/generator.js +4 -23
  7. package/dist/build-spk/resource-worker.js +0 -3
  8. package/dist/build-spk/uninstall-wizard.d.ts +8 -3
  9. package/dist/build-spk/uninstall-wizard.js +4 -2
  10. package/dist/build-spk/wizard-messages.d.ts +33 -0
  11. package/dist/build-spk/wizard-messages.js +33 -0
  12. package/dist/cli.js +3 -3
  13. package/dist/contract/validate.js +4 -21
  14. package/dist/error-registry.js +11 -12
  15. package/dist/host/dsm-client.js +51 -30
  16. package/dist/host/types.d.ts +5 -0
  17. package/dist/info.js +14 -1
  18. package/dist/init.js +1 -1
  19. package/dist/portal/alias.d.ts +6 -4
  20. package/dist/portal/alias.js +6 -11
  21. package/dist/portal/package.d.ts +0 -3
  22. package/dist/portal/package.js +1 -7
  23. package/dist/runtime/executor-digests.json +11 -0
  24. package/dist/runtime/uninstall-cleanup.d.ts +7 -1
  25. package/dist/runtime/uninstall-cleanup.js +7 -1
  26. package/dist/standalone/dsm-window.js +8 -11
  27. package/dist/standalone/inspect-spk.js +8 -7
  28. package/dist/standalone/manifest.js +12 -3
  29. package/dist/standalone/root-window.d.ts +10 -3
  30. package/dist/standalone/root-window.js +54 -29
  31. package/dist/standalone/spk.js +1 -1
  32. package/dist/validate.js +2 -1
  33. package/package.json +3 -3
  34. package/schema/synology-app.schema.json +1 -10
  35. package/skills/SKILL.md +1 -1
  36. package/skills/reference/create.md +7 -2
  37. package/skills/reference/deploy.md +1 -1
  38. package/skills/reference/identity.md +1 -1
  39. package/skills/reference/portal.md +7 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,12 +19,12 @@
19
19
  "scripts": {
20
20
  "build": "rm -rf dist && tsc",
21
21
  "test": "npm run build && node --import tsx --test test/**/*.test.ts",
22
- "test:scripts": "node --test scripts/test/release.test.mjs scripts/test/pack-docker.test.mjs scripts/test/npm-pack.test.mjs scripts/test/executor-bump.test.mjs",
22
+ "test:scripts": "node --test scripts/test/release.test.mjs scripts/test/pack-docker.test.mjs scripts/test/npm-pack.test.mjs scripts/test/executor-bump.test.mjs scripts/test/smoke-exports.test.mjs scripts/test/executor-digests.test.mjs",
23
23
  "gates": "bash scripts/ci/gate.sh npm && bash scripts/ci/gate.sh native-go && bash scripts/ci/gate.sh daemon-gateway",
24
24
  "prepublishOnly": "npm run build"
25
25
  },
26
26
  "dependencies": {
27
- "@synopackageland/templates": "0.9.0",
27
+ "@synopackageland/templates": "0.9.1",
28
28
  "ajv": "^8.17.1",
29
29
  "commander": "^13.1.0",
30
30
  "tar": "^7.4.3",
@@ -151,16 +151,7 @@
151
151
  "description": "DSM group names allowed in; only with access: dsm-groups."
152
152
  },
153
153
  "portal": {
154
- "type": "object",
155
- "properties": {
156
- "alias": {
157
- "type": "string",
158
- "pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
159
- "description": "Default DSM Application Portal alias (https://<nas>/<alias>/). Omit to use the package name. An administrator still applies it."
160
- }
161
- },
162
- "additionalProperties": false,
163
- "description": "Register the App in DSM Control Panel > Login Portal > Applications. Needs launch: dsm-window behind the DSM gateway (access other than public) and os_min_ver 7.3.2-86009 or later. Registration alone exposes nothing."
154
+ "description": "Not available: a Compose App has no executor, and a Login Portal alias needs the root executor to stop nginx serving it after uninstall. validate refuses it with CONTRACT_PORTAL_REQUIRES_EXECUTOR; an administrator can set up a reverse proxy in DSM instead."
164
155
  }
165
156
  },
166
157
  "additionalProperties": true
package/skills/SKILL.md CHANGED
@@ -13,7 +13,7 @@ Never install, uninstall, publish or modify NAS data without task authorization.
13
13
 
14
14
  Native projects use schema 2 capabilities and `--with files,jobs,login,root-ops,delegate`; use `add` for existing projects. Prefer `check --json`, `build --json`, `inspect <spk> --json`, `deploy --dry-run --json`, and `status --host <profile> --json` for structured results. Read the conflict policy in reference/create.md before adding overlays.
15
15
 
16
- Read reference/portal.md before declaring or changing a DSM Application Portal alias (`add portal`); DSM owns the saved setting. Read reference/files.md and reference/identity.md for capability boundaries. RootOps and delegate share one-time protected executor boot/control setup with digest approval. Existing native products can explicitly use the separate authorization-sdk build-time adapter; do not mix its tasks with generated executor capabilities. App sessions alone do not grant NAS file authority.
16
+ Read reference/portal.md before declaring or changing a DSM Application Portal alias (`add portal`); DSM owns the saved setting, and only an App with rootOps or delegate may declare one. Read reference/files.md and reference/identity.md for capability boundaries. RootOps and delegate share one-time protected executor boot/control setup with digest approval. Existing native products can explicitly use the separate authorization-sdk build-time adapter; do not mix its tasks with generated executor capabilities. App sessions alone do not grant NAS file authority.
17
17
 
18
18
  Read [reference/ui-vue.md](reference/ui-vue.md) when editing Vue components in the native-go Vue template; use the installed SDK contracts, keep DSM embedding boundaries, and verify the actual app.
19
19
 
@@ -16,7 +16,7 @@ synopkgland init -t compose --from ./compose.yaml upstream-app
16
16
 
17
17
  ## 容器存取控制
18
18
 
19
- 有 adminport 的容器 App 預設經 DSM 驗證 gateway(`integration.web.access`:dsm-users 預設、dsm-admins、dsm-groups+groups、public)。Compose published port 必須綁 loopback:`127.0.0.1:<adminport>:<port>`,否則 check 失敗(`COMPOSE_GATEWAY_PORT_EXPOSED`)。gateway 是編譯的 Go,建置需要 Go 與 `--arch`。`public` 才保留匿名 Web Station portal,inspect 會以 `CONTAINER_ANONYMOUS_PORTAL` 提醒。
19
+ 有 adminport 的容器 App 預設經 DSM 驗證 gateway(`integration.web.access`:dsm-users 預設、dsm-admins、dsm-groups+groups、public)。Compose published port 必須綁 loopback:`127.0.0.1:<adminport>:<port>`,否則 check 失敗(`COMPOSE_GATEWAY_PORT_EXPOSED`)。gateway 是編譯的 Go,建置需要 Go 與 `--arch`。`public` 才保留匿名 Web Station portal,inspect 會以 `CONTAINER_ANONYMOUS_PORTAL` 提醒。Compose App 沒有 executor,不能宣告 `integration.web.portal`(`CONTRACT_PORTAL_REQUIRES_EXECUTOR`)。
20
20
 
21
21
  ## 建置與檢查
22
22
 
@@ -31,7 +31,12 @@ native-go 與 binary 使用 `dsm-package.yaml` 的 `schema: 2`,backend 寫 `ki
31
31
  既有 native-go 專案用 `synopkgland add files jobs login`;已存在能力回報 no-op。overlay 檔案已有不同內容或 stub 被改動時,add 拒絕並列出衝突;不修改業務 routes 或 App.vue。
32
32
  check、inspect、build、deploy、status 支援 `--json`,stdout 只有單一 JSON 物件,錯誤包含 code、message、hint;進度在 stderr。
33
33
 
34
- rootOps 與 delegate 已共用管理員 PasswordConfirm 核准的 protected executor、boot/control tasks;精確 task readback 與 digest health 一致才 ready,pins 變更須重核准。native 產品另可明確 opt-in authorization-sdk adapter,不與 generated executor tasks 混用。SDK off stub 與 routes_test.go 的預設 policy scaffold 只有內容仍等於 bundled base 時才會替換;App 自訂版本拒絕。
34
+ rootOps 與 delegate 已共用管理員 PasswordConfirm 核准的 protected executor、boot/control tasks;精確 task readback 與 digest health 一致才 ready;policy(operations、scope、root 腳本)變更須重核准,policy 不變的 executor 更新會自動 repin,install-selected 的 scope 需管理員確認一次。Vue App 只在 SDK 的 ExecutorSettings 核准;plain UI 由 DSM 視窗面板核准,兩者都在面板內輸入密碼並走 PasswordConfirm。native 產品另可明確 opt-in authorization-sdk adapter,不與 generated executor tasks 混用。SDK off stub 與 routes_test.go 的預設 policy scaffold 只有內容仍等於 bundled base 時才會替換;App 自訂版本拒絕。
35
+
36
+ ## 兩種層級
37
+
38
+ - 無升權 App(沒有 rootOps、delegate;含所有 Compose App):安裝、執行、解除安裝都只用套件帳號,任何階段都不用 root。不能宣告 portal(native `STANDALONE_PORTAL_REQUIRES_EXECUTOR`、Compose `CONTRACT_PORTAL_REQUIRES_EXECUTOR`);需要登入入口別名時由管理員在 DSM 手動設定反向代理。解除安裝後 DSM 保留的「控制台」>「應用程式權限」規則由管理員手動移除,精靈與 cleanup 清單會說明。
39
+ - executor App(rootOps 或 delegate):解除安裝時 root purge 自動刪除 `SYNO.SDS.<package>.Instance` 的應用程式權限規則,宣告 portal 時在 DSM 移除別名後 reload nginx。精靈說明自動清除;cleanup 清單只在自動清除失敗時(系統日誌 `synopkgland-executor`)才需要管理員處理。
35
40
 
36
41
  ## Permanent destination integration
37
42
 
@@ -7,6 +7,6 @@
7
7
 
8
8
  Deployment uploads directly to DSM. It does not publish artifacts. Container install answers use --share, --run-as and --param; omitted upgrade answers are retained by package preinst, which also rejects missing newly required values.
9
9
 
10
- For native-go root-ops, approve fixed operations in DSM after installation. Updates that change the task text require fresh approval. Remove tasks before uninstall. Never delete installed packages or data as part of a code cleanup.
10
+ For native-go root-ops, approve fixed operations in DSM after installation. Updates that change the task text require fresh approval. Uninstall removes the executor tasks, the App's Application Privileges rules and a stale portal alias itself. Never delete installed packages or data as part of a code cleanup.
11
11
 
12
12
  5. After installation run `synopkgland status --host <profile> --json`; inspect every capability state and Open URL. Installation is not readiness. Delegate is implemented and shipped (default `files.moveFromStaging`); full device lifecycle acceptance is still pending for the shared delegate/rootOps executor: install → setup → reboot → upgrade → revoke → uninstall. Delete App Data is not evidence of complete cleanup; verify retained directories/account artifacts.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Native-go validates the caller's DSM session per package. Business route permissions come from backend/internal/api/permissions.json; SDK-owned route policy is protected separately. Capabilities describe execution/resources and do not grant business route access. Never trust a request username as owner.
4
4
 
5
- Entry: omit `entry` for legacy access; `entry: admin-only` admits administrators only and is the floor of every route: SDK routes a capability adds (files, jobs, portal, delegate, rootOps) refuse ordinary users with `admin_required`, the launcher is hidden, and an app route marked `authenticated` or `public` in permissions.json fails validate/build with `STANDALONE_ENTRY_WIDENED` (only the public `identity: standalone` login routes stay public; they admit no one by themselves). The SPK records the mode in `sdk-package.json`; `inspect` prints it and flags `ENTRY_LAUNCHER_MISMATCH`; `entry: app-privilege` also admits accounts DSM Application Privileges allows for `SYNO.SDS.<package>.Instance` (native-go only, needs an `authenticated` route). DSM evaluates user/group/everyone rules, allow/deny precedence and IP ranges; one CGI check covers every route, failures refuse with `app_privilege_denied`, and decisions are cached at most 30 seconds. DSM's install default allows administrators only, so declaring or upgrading never opens the app to everyone. The delegate executor applies the same entry rule to every protected and producer move: once DSM no longer allows a user, new moves under that user's still-valid grant are refused (`APP_DENIED`, grant invalidated); files already saved stay. Guard global settings in shared routes with Go `api.RequireAdmin(caller)` (`admin_required`); the frontend `ctx.user?.isAdmin` only hides controls.
5
+ Entry: omit `entry` for legacy access; `entry: admin-only` admits administrators only and is the floor of every route: SDK routes a capability adds (files, jobs, portal, delegate, rootOps) refuse ordinary users with `admin_required`, the launcher is hidden, and an app route marked `authenticated` or `public` in permissions.json fails validate/build with `STANDALONE_ENTRY_WIDENED` (only the public `identity: standalone` login routes stay public; they admit no one by themselves). The SPK records the mode in `sdk-package.json`; `inspect` prints it and flags `ENTRY_LAUNCHER_MISMATCH`; `entry: app-privilege` also admits accounts DSM Application Privileges allows for `SYNO.SDS.<package>.Instance` (native-go only, needs an `authenticated` route). DSM evaluates user/group/everyone rules, allow/deny precedence and IP ranges; one CGI check covers every route, failures refuse with `app_privilege_denied`, and decisions are cached at most 30 seconds. DSM's install default allows administrators only, so declaring or upgrading never opens the app to everyone. DSM keeps those rules after uninstall and applies them again to a later install under the same name: an App with the executor (rootOps or delegate) removes them automatically at uninstall (failures go to the system log entry `synopkgland-executor`); a no-privilege App never uses root, so an administrator removes them in Control Panel > Application Privileges. The delegate executor applies the same entry rule to every protected and producer move: once DSM no longer allows a user, new moves under that user's still-valid grant are refused (`APP_DENIED`, grant invalidated); files already saved stay. Guard global settings in shared routes with Go `api.RequireAdmin(caller)` (`admin_required`); the frontend `ctx.user?.isAdmin` only hides controls.
6
6
 
7
7
  I capability: init --with login or add login enables Go-native standalone identity and Vue login. Same-host DSM cookies can be reused across ports after backend verification. Missing cookies use an HTTPS DSM account/password/OTP fallback; passwords and DSM credentials are not persisted. Plain UI is unsupported. App sessions last at most 15 minutes; sensitive operations revalidate live DSM cookies and local UID. Password-only sessions permit only basic app.status/greeting.get reads. Missing revalidation returns session_revalidation_required; files return dsm_session_required. Sign in to DSM on the same hostname. Web Station portal/proxy acceptance remains pending.
8
8
 
@@ -1,6 +1,8 @@
1
1
  # DSM Application Portal
2
2
 
3
- Declare: native-go/binary `portal: {}` or `portal: {alias: my-app}` in dsm-package.yaml (`synopkgland add portal`); Compose `integration.web.portal: {}` only for a DSM-window app behind the DSM auth gateway. 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: the SDK does not register a portal for it.
3
+ Tier: only an App with the executor (`capabilities.rootOps` or `capabilities.delegate`) may declare a portal, because after uninstall only root can make nginx stop serving the alias. A no-privilege native App fails check/build with `STANDALONE_PORTAL_REQUIRES_EXECUTOR`, `add portal` refuses it unless the same call adds `root-ops` or `delegate`, and inspect flags such an SPK with `PORTAL_REQUIRES_EXECUTOR`. A Compose App has no executor: `integration.web.portal` always fails with `CONTRACT_PORTAL_REQUIRES_EXECUTOR`. Without the executor, an administrator can set up a reverse proxy in DSM instead.
4
+
5
+ Declare: native-go/binary with rootOps or delegate, `portal: {}` or `portal: {alias: my-app}` in dsm-package.yaml (`synopkgland add root-ops portal`, or `add portal` once the executor is declared). 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: the SDK does not register a portal for it.
4
6
 
5
7
  Build: the SPK carries `etc/portal.json` (every field DSM's app-portal worker needs) and `etc/portal.mustache`, `conf/resource` registers them, and the install wizard adds a prefilled alias step. preinst validates the answer; postinst stores it as the package's own draft and default alias. Nothing is active until an administrator applies it. `inspect <spk> --json` reports `portal: {appId, alias}` and refuses any other portal content.
6
8
 
@@ -8,10 +10,12 @@ Activate: DSM owns the saved setting. Administrators use the app's portal panel
8
10
 
9
11
  Read: ordinary users call the Go backend's `portal.status` (`authenticated`), which reads `/var/packages/<package>/etc/AppPortal.json` read-only, bounded (64 KiB, no symlinks, one JSON object, matching app id). Anything else is `unavailable` and the UI shows "ask an administrator". A binary CGI must implement the same `{state, alias}` answer itself or ordinary users never see the link.
10
12
 
11
- DSM floor: os_min_ver must be 7.3.2-86009 or later. DSM 7.1.1-42962 refuses an unsigned SPK whose conf/resource carries web-config app-portal (Package Center error 320, "non-synology-signed package should not use internal worker"); 7.3.2-86009 and 7.4 install it; 7.2 and 7.3.0/7.3.1 are untested (unknown). validate/build fail with `STANDALONE_PORTAL_MIN_DSM` / `CONTRACT_PORTAL_MIN_DSM`, `--min-dsm` cannot go below it, `add portal` raises a lower os_min_ver, and inspect flags a lower INFO os_min_ver. Fix: raise os_min_ver or remove the portal.
13
+ DSM floor: os_min_ver must be 7.3.2-86009 or later. DSM 7.1.1-42962 refuses an unsigned SPK whose conf/resource carries web-config app-portal (Package Center error 320, "non-synology-signed package should not use internal worker"); 7.3.2-86009 and 7.4 install it; 7.2 and 7.3.0/7.3.1 are untested (unknown). validate/build fail with `STANDALONE_PORTAL_MIN_DSM`, `--min-dsm` cannot go below it, `add portal` raises a lower os_min_ver, and inspect flags a lower INFO os_min_ver. Fix: raise os_min_ver or remove the portal.
12
14
 
13
15
  Capability: beyond that floor, no version gate. The panel needs SYNO.API.Info to list `SYNO.Core.AppPortal` (v2) and the app's registration to exist; otherwise administrators get only the Control Panel shortcut.
14
16
 
15
17
  UI: plain JS calls `ctx.portal && ctx.portal.mountPanel(element)` and disposes it on unmount; Vue renders `portal/PortalSettings.vue`. `ctx.portal.messages` carries the SDK text in all nine languages.
16
18
 
17
- Never: write `AppPortal.json`, `@appconf/<package>` or `/usr/syno/etc/www/app.d`; add a backend route that changes the alias; relay QuickConnect responses or build QuickConnect links; reboot or change other packages' portal entries while testing. DSM keeps the alias across upgrades and removes the entry on uninstall.
19
+ Never: write `AppPortal.json`, `@appconf/<package>` or `/usr/syno/etc/www/app.d`; add a backend route that changes the alias; relay QuickConnect responses or build QuickConnect links; reboot or change other packages' portal entries while testing. DSM keeps the alias across upgrades and removes the entry on uninstall, but it does not reload nginx, and it keeps the App's Application Privileges rules.
20
+
21
+ Uninstall: the root executor's purge deletes the rules of `SYNO.SDS.<package>.Instance` through the Application Privileges API and, once DSM has removed the alias files, reloads nginx (`synosystemctl reload nginx`), so the alias stops at once. Failures are only logged to the system log entry `synopkgland-executor`; then an administrator saves an alias in Control Panel > Login Portal > Applications (which reloads nginx) or restarts the NAS, and removes the rules in Control Panel > Application Privileges. The uninstall wizard and the `status`/`inspect` cleanup list say this in that order: automatic first, manual only as the fallback.