@synopackageland/templates 0.9.8 → 0.10.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.
Files changed (133) hide show
  1. package/README.md +27 -27
  2. package/package.json +2 -2
  3. package/templates/_shared/AGENTS.md +53 -15
  4. package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
  5. package/templates/native-go/backend/cmd/app/main.go +15 -1
  6. package/templates/native-go/backend/cmd/app/main_test.go +33 -0
  7. package/templates/native-go/backend/go.mod +1 -1
  8. package/templates/native-go/backend/go.sum +2 -2
  9. package/templates/native-go/i18n/glossary.json +3 -1
  10. package/templates/native-go/i18n/messages/de.json +6 -0
  11. package/templates/native-go/i18n/messages/en.json +6 -0
  12. package/templates/native-go/i18n/messages/es.json +6 -0
  13. package/templates/native-go/i18n/messages/fr.json +6 -0
  14. package/templates/native-go/i18n/messages/it.json +6 -0
  15. package/templates/native-go/i18n/messages/ja.json +6 -0
  16. package/templates/native-go/i18n/messages/ko.json +6 -0
  17. package/templates/native-go/i18n/messages/zh-CN.json +6 -0
  18. package/templates/native-go/i18n/messages/zh-TW.json +6 -0
  19. package/templates/native-go/ui/app.js +2 -1
  20. package/templates/native-go-delegate/frontend/test/capability-panels.test.ts +3 -2
  21. package/templates/native-go-jobs/frontend/test/background-page.test.ts +4 -4
  22. package/templates/native-go-vue/README.md +19 -7
  23. package/templates/native-go-vue/frontend/package-lock.json +9 -9
  24. package/templates/native-go-vue/frontend/package.json +2 -2
  25. package/templates/native-go-vue/frontend/src/App.vue +31 -36
  26. package/templates/native-go-vue/frontend/test/apps.test.ts +65 -12
  27. package/templates/native-go-vue/frontend/test/context.ts +8 -0
  28. package/templates/native-go-vue/frontend/test/native-controls.test.ts +3 -4
  29. package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +2 -2
  30. package/templates/native-node/README.md +153 -0
  31. package/templates/native-node/backend/app/package.json +4 -0
  32. package/templates/native-node/backend/app/permissions.json +5 -0
  33. package/templates/native-node/backend/package-lock.json +59 -0
  34. package/templates/native-node/backend/package.json +17 -0
  35. package/templates/native-node/backend/src/delegate.ts +5 -0
  36. package/templates/native-node/backend/src/jobs.ts +5 -0
  37. package/templates/native-node/backend/src/main.ts +4 -0
  38. package/templates/native-node/backend/src/params.ts +11 -0
  39. package/templates/native-node/backend/src/root-ops.ts +5 -0
  40. package/templates/native-node/backend/src/routes.ts +97 -0
  41. package/templates/native-node/backend/src/service.ts +22 -0
  42. package/templates/native-node/backend/test/routes.test.ts +75 -0
  43. package/templates/native-node/backend/test/support.ts +67 -0
  44. package/templates/native-node/backend/tsconfig.build.json +8 -0
  45. package/templates/native-node/backend/tsconfig.json +18 -0
  46. package/templates/native-node/dsm-package.yaml +80 -0
  47. package/templates/native-node/gitignore +4 -0
  48. package/templates/native-node-delegate/backend/src/delegate.ts +35 -0
  49. package/templates/native-node-delegate/backend/test/delegate.test.ts +51 -0
  50. package/templates/native-node-jobs/backend/src/jobs.ts +38 -0
  51. package/templates/native-node-jobs/backend/test/jobs.test.ts +73 -0
  52. package/templates/native-node-root-ops/backend/src/root-ops.ts +16 -0
  53. package/templates/native-node-root-ops/backend/test/root-ops.test.ts +23 -0
  54. package/templates/native-node-vue/README.md +56 -0
  55. package/templates/native-node-vue/gitignore +4 -0
  56. package/templates/native-python/README.md +150 -0
  57. package/templates/native-python/backend/app/delegate.py +5 -0
  58. package/templates/native-python/backend/app/jobs.py +5 -0
  59. package/templates/native-python/backend/app/main.py +5 -0
  60. package/templates/native-python/backend/app/params.py +10 -0
  61. package/templates/native-python/backend/app/permissions.json +5 -0
  62. package/templates/native-python/backend/app/root_ops.py +5 -0
  63. package/templates/native-python/backend/app/routes.py +107 -0
  64. package/templates/native-python/backend/tests/support.py +72 -0
  65. package/templates/native-python/backend/tests/test_routes.py +73 -0
  66. package/templates/native-python/dsm-package.yaml +77 -0
  67. package/templates/native-python/gitignore +2 -0
  68. package/templates/native-python-delegate/backend/app/delegate.py +42 -0
  69. package/templates/native-python-delegate/backend/tests/test_delegate.py +58 -0
  70. package/templates/native-python-jobs/backend/app/jobs.py +40 -0
  71. package/templates/native-python-jobs/backend/tests/test_jobs.py +72 -0
  72. package/templates/native-python-root-ops/backend/app/root_ops.py +18 -0
  73. package/templates/native-python-root-ops/backend/tests/test_root_ops.py +34 -0
  74. package/templates/native-python-vue/README.md +56 -0
  75. package/templates/native-python-vue/gitignore +3 -0
  76. package/templates/native-rust/README.md +74 -296
  77. package/templates/native-rust/backend/Cargo.lock +6 -6
  78. package/templates/native-rust/backend/Cargo.toml +2 -10
  79. package/templates/native-rust/backend/src/delegate.rs +6 -0
  80. package/templates/native-rust/backend/src/jobs.rs +6 -0
  81. package/templates/native-rust/backend/src/main.rs +12 -0
  82. package/templates/native-rust/backend/src/root_ops.rs +6 -0
  83. package/templates/native-rust/backend/src/routes.rs +352 -0
  84. package/templates/native-rust/backend/src/test_support.rs +91 -0
  85. package/templates/native-rust/dsm-package.yaml +10 -21
  86. package/templates/native-rust-delegate/backend/src/delegate.rs +171 -0
  87. package/templates/native-rust-jobs/backend/src/jobs.rs +240 -0
  88. package/templates/native-rust-root-ops/backend/src/root_ops.rs +83 -0
  89. package/templates/native-rust-vue/README.md +35 -34
  90. package/templates/native-rust/backend/THIRD_PARTY_NOTICES.txt +0 -207
  91. package/templates/native-rust/backend/src/app/background/mod.rs +0 -424
  92. package/templates/native-rust/backend/src/app/background/owner.rs +0 -77
  93. package/templates/native-rust/backend/src/app/background/owner_tests.rs +0 -174
  94. package/templates/native-rust/backend/src/app/background/schedule.rs +0 -126
  95. package/templates/native-rust/backend/src/app/background/schedule_tests.rs +0 -108
  96. package/templates/native-rust/backend/src/app/background/tests.rs +0 -505
  97. package/templates/native-rust/backend/src/app/background/work.rs +0 -78
  98. package/templates/native-rust/backend/src/app/background/work_tests.rs +0 -123
  99. package/templates/native-rust/backend/src/app/background_routes.rs +0 -22
  100. package/templates/native-rust/backend/src/app/delegate_config.rs +0 -188
  101. package/templates/native-rust/backend/src/app/delegate_routes.rs +0 -22
  102. package/templates/native-rust/backend/src/app/identity.rs +0 -56
  103. package/templates/native-rust/backend/src/app/migrations.rs +0 -79
  104. package/templates/native-rust/backend/src/app/mod.rs +0 -30
  105. package/templates/native-rust/backend/src/app/portal.rs +0 -317
  106. package/templates/native-rust/backend/src/app/root_routes.rs +0 -23
  107. package/templates/native-rust/backend/src/app/routes.rs +0 -315
  108. package/templates/native-rust/backend/src/app/session_files.rs +0 -19
  109. package/templates/native-rust/backend/src/app/shared_folder.rs +0 -383
  110. package/templates/native-rust/backend/src/app/test_support.rs +0 -202
  111. package/templates/native-rust/backend/src/app/uninstall.rs +0 -38
  112. package/templates/native-rust/backend/src/bin/api.rs +0 -72
  113. package/templates/native-rust/backend/src/bin/background-worker.rs +0 -37
  114. package/templates/native-rust/backend/src/build_env.rs +0 -155
  115. package/templates/native-rust/backend/src/lib.rs +0 -6
  116. package/templates/native-rust/backend/src/permissions.rs +0 -54
  117. package/templates/native-rust/backend/src/process.rs +0 -91
  118. package/templates/native-rust/i18n/glossary.json +0 -663
  119. package/templates/native-rust/i18n/messages/de.json +0 -30
  120. package/templates/native-rust/i18n/messages/en.json +0 -30
  121. package/templates/native-rust/i18n/messages/es.json +0 -30
  122. package/templates/native-rust/i18n/messages/fr.json +0 -30
  123. package/templates/native-rust/i18n/messages/it.json +0 -30
  124. package/templates/native-rust/i18n/messages/ja.json +0 -30
  125. package/templates/native-rust/i18n/messages/ko.json +0 -30
  126. package/templates/native-rust/i18n/messages/zh-CN.json +0 -30
  127. package/templates/native-rust/i18n/messages/zh-TW.json +0 -30
  128. package/templates/native-rust/ui/app.css +0 -45
  129. package/templates/native-rust/ui/app.js +0 -111
  130. package/templates/native-rust-delegate/backend/src/app/delegate_routes.rs +0 -92
  131. package/templates/native-rust-delegate/backend/src/bin/background-worker.rs +0 -52
  132. package/templates/native-rust-jobs/backend/src/app/background_routes.rs +0 -109
  133. package/templates/native-rust-root-ops/backend/src/app/root_routes.rs +0 -475
@@ -1,6 +1,6 @@
1
1
  # __DISPLAY_NAME__
2
2
 
3
- A native DSM package (`__PACKAGE__`): a Rust backend that DSM runs as a CGI,
3
+ A native DSM package (`__PACKAGE__`): a Rust App behind the SDK's runtime,
4
4
  and a UI that opens in its own DSM window. It installs as an **unsigned** SPK,
5
5
  runs as its own DSM package account, needs no containers, and has no root by default.
6
6
 
@@ -8,21 +8,19 @@ runs as its own DSM package account, needs no containers, and has no root by def
8
8
  DSM main menu → DSM window (ui/app.js)
9
9
  │ ctx.call("greeting.get") same-origin POST + DSM session
10
10
  ▼
11
- /webman/3rdparty/__PACKAGE__/cgi/api.cgi (backend/, runs as package user)
11
+ /webman/3rdparty/__PACKAGE__/cgi/api.cgi (the SDK runtime, runs as package user)
12
12
  1. transport guards: POST, JSON, same origin, ≤ 1 MiB, bounded JSON complexity
13
- 2. one DSM authentication (unless the route is Public)
14
- 3. route policy: User or Admin → refusal before any handler
15
- 4. your handler (backend/src/app)
13
+ 2. one DSM authentication, the entry gate and permissions.json
14
+ 3. the App binary, cross-compiled from `backend`, runs once per call
15
+ 4. your method, which may ask the runtime for services (files, jobs, executor)
16
16
  ```
17
17
 
18
18
  ## Quick start
19
19
 
20
- Needs Node.js with `synopkgland` installed, and Rust through rustup:
21
- `backend/rust-toolchain.toml` pins 1.97.1 with the three DSM targets, so the
22
- first `cargo` run installs them.
20
+ Needs Node.js with `synopkgland` installed, Rust through rustup, Zig and `cargo-zigbuild`: `backend/rust-toolchain.toml` pins 1.97.1 with the three DSM targets, so the first `cargo` run installs them.
23
21
 
24
22
  ```bash
25
- (cd backend && cargo test --locked) # auth policy + route tests
23
+ (cd backend && cargo test --locked) # method tests
26
24
  synopkgland build # → dist/__PACKAGE__-0.1.0-0001-{x86_64,aarch64}.spk
27
25
  synopkgland inspect dist/__PACKAGE__-0.1.0-0001-x86_64.spk
28
26
  ```
@@ -33,90 +31,60 @@ then Manual Install with the SPK for your NAS's CPU. Open
33
31
  as, what the backend reports, and (for administrators) a form that stores a
34
32
  greeting in the package's data folder.
35
33
 
36
- ## Capabilities and schema 2
37
-
38
- `dsm-package.yaml` requires `schema: 2` and `backend: {kind: rust, dir: backend, package: app}`.
39
- Declare `capabilities.files: session` for interactive File Station access,
40
- `capabilities.jobs: true` for persistent package-account jobs,
41
- `capabilities.identity: standalone` for independent DSM login, and
42
- `capabilities.rootOps: true` for administrator-approved fixed executor operations.
43
- `init --with files,jobs,root-ops` supports plain and Vue UI; `login` and
44
- `delegate` need the Vue UI. `daemon` cannot be combined with `jobs`, but it can
45
- be combined with an until-revoked `delegate`.
46
-
47
- ```sh
48
- synopkgland add files jobs login
49
- synopkgland check --json
50
- synopkgland status --host my-nas --json
51
- ```
52
-
53
- `add` writes SDK overlay files and updates capabilities. Your handlers/pages and
54
- application entry files remain app-owned. Differing existing overlay files and
55
- modified SDK placeholders cause `ADD_FILE_CONFLICT` with the conflicting paths;
56
- resolve them before retrying. Already-declared capabilities are a no-op.
57
- `check`, `build`, `inspect`, `deploy`, `status` emit one stdout object with
58
- `--json`; failures include `code`, `message`, `hint`.
59
-
60
34
  ## Project layout
61
35
 
62
36
  | Path | What it is | Edit it? |
63
37
  | --- | --- | --- |
64
38
  | `dsm-package.yaml` | Package identity, version/build, arches, where backend and UI come from | Yes |
65
39
  | `ui/app.js`, `ui/app.css` | The app inside the DSM window | Yes — your UI |
66
- | `ui/…` | Any other assets; served from `/webman/3rdparty/__PACKAGE__/app/` | Yes |
67
40
  | `i18n/messages/<locale>.json` | UI text in nine languages | Yes |
68
41
  | `i18n/glossary.json` | DSM's own UI terms, and which messages must use them | Add terms |
69
- | `backend/src/app/` | Your API: routes and handlers | Yes — your backend |
42
+ | `backend/src/routes.rs` | Your methods: `build` registers them | Yes — your backend |
43
+ | `backend/src/jobs.rs`, `root_ops.rs`, `delegate.rs` | Capability examples (stubs unless `init --with` added them) | Yes |
70
44
  | `backend/permissions.json` | Who may call each of your methods | Yes |
71
- | `backend/src/bin/api.rs` | Wires your routes to the SDK server | Rarely |
72
- | `backend/Cargo.toml`, `backend/Cargo.lock` | Your crate; the `syno-pkg-land-sdk` dependency's `tag` pins the SDK, and the committed lock file pins everything else | SDK upgrades |
45
+ | `backend/src/main.rs` | Runs the App on the runtime's channel | Rarely |
46
+ | `backend/src/test_support.rs` | `call` plays the runtime in `cargo test` | Rarely |
47
+ | `backend/Cargo.toml`, `backend/Cargo.lock` | Your crate; the `syno-pkg-land-app` dependency's `tag` pins the SDK, and the committed lock file pins everything else | SDK upgrades |
73
48
  | `backend/rust-toolchain.toml` | The Rust release and DSM targets the SPK is built with | Toolchain upgrades |
74
49
  | `dist/` | Built SPKs (ignored by git) | No |
75
50
 
76
51
  `build` generates everything else: `INFO`, `conf/privilege`
77
- (`run-as: package`), no-op lifecycle scripts, icons, the DSM window wrapper
78
- (`ui/config`, `ui/window-<build>.js`) and the CGI at `ui/cgi/api.cgi`.
52
+ (`run-as: package`), lifecycle scripts, icons, the DSM window wrapper and the
53
+ runtime CGI at `ui/cgi/api.cgi`.
79
54
 
80
- ## Add an API operation
55
+ ## Add a method
81
56
 
82
- Add a handler in `backend/src/app/routes.rs`. Every method defaults to
83
- DSM administrator membership, resolved by its native SDK. All access rules live in
84
- `backend/permissions.json`, including launcher access:
57
+ Register it in `build`. Every method defaults to DSM administrator membership;
58
+ all access rules live in `backend/permissions.json`:
85
59
 
86
60
  ```rust
87
- pub fn routes(cfg: Config) -> Vec<Route> {
88
- let cfg = Arc::new(cfg);
89
- vec![
90
- cfg.handle("app.status", Config::status),
91
- cfg.handle("greeting.get", Config::get_greeting),
92
- cfg.handle("greeting.set", Config::set_greeting),
93
- cfg.handle("notes.list", Config::list_notes), // new: administrators by default
94
- cfg.handle("notes.purge", Config::purge_notes), // new: administrators by default
95
- ]
61
+ pub fn build(cfg: Config) -> App {
62
+ let mut app = App::new();
63
+ ...
64
+ let c = cfg.clone();
65
+ app.method("notes.list", move |ctx, params| list_notes(&c, ctx, params)); // new: administrators by default
66
+ app
67
+ }
68
+
69
+ #[derive(Deserialize)]
70
+ #[serde(deny_unknown_fields)]
71
+ struct ListNotes {
72
+ folder: String,
96
73
  }
97
74
 
98
- impl Config {
99
- fn list_notes(&self, _: &Context, caller: &Caller, params: &[u8]) -> Result<Value, BoxError> {
100
- #[derive(Default, Deserialize)]
101
- #[serde(deny_unknown_fields)]
102
- struct Params {
103
- #[serde(default)]
104
- limit: i64,
105
- }
106
- let _params: Params = decode_params(params)?;
107
- Ok(json!({"owner": caller.user(), "notes": []}))
108
- }
75
+ fn list_notes(_cfg: &Config, ctx: &Context, params: &Params) -> Result<Value, Error> {
76
+ let ListNotes { folder } = params.decode()?; // anything else is bad_request
77
+ Ok(json!({"owner": ctx.user, "folder": folder, "notes": []}))
109
78
  }
110
79
  ```
111
80
 
112
- Then add the method's valid params to `samples()` in `test_support.rs`; the
113
- test fails until you do, and once you do it proves the new route refuses
114
- callers without a session and ordinary users without touching
115
- the data folder.
81
+ List the method in `backend.methods` in dsm-package.yaml, then add a test next to it with `call(build(cfg), "notes.list", json!({"folder": "work"}), "admin", &services)`. Call it from the UI with `ctx.call("notes.list", { folder: "work" })`.
82
+
83
+ Return `Error::new(code, message)` (or `Error::bad_request(message)`) for an error the browser may see; use `routes::internal(e)` for anything unexpected, which logs it and answers `internal`. `ctx.runtime` returns the runtime's refusal as an `Error`, so `?` passes its code on to the browser.
116
84
 
117
- Call it from the UI with `ctx.call("notes.list", { limit: 20 })`. Errors
118
- reject with `{ code, message }`, for example `session_required`,
119
- `admin_required`, `bad_request` or `internal`.
85
+ The runtime refuses before your App runs: `session_required`, `admin_required`,
86
+ `unknown_method`, `request_timeout` and the transport guards' codes. The UI
87
+ shows `error.<code>` from its catalogs.
120
88
 
121
89
  ### Who can call what
122
90
 
@@ -129,253 +97,63 @@ The shipped policy is:
129
97
  The default must remain `administrators`. An intentional exception belongs in
130
98
  `methods`, for example `"notes.list": "authenticated"`; `"public"` explicitly
131
99
  allows anonymous calls. Such exceptions also enable ordinary-user launcher
132
- access. The template ships no exceptions and sets DSM `allUsers` to `false`.
100
+ access.
133
101
 
134
102
  | JSON rule | No session | Ordinary user | Administrators group member |
135
103
  | --- | --- | --- | --- |
136
104
  | absent or `administrators` | `session_required` | `admin_required` | runs |
137
105
  | explicit `authenticated` | `session_required` | runs | runs |
138
106
  | explicit `public` | runs | runs | runs |
139
- | unregistered method | `session_required` | `unknown_method` | `unknown_method` |
107
+ | method the App does not register | `session_required` | `unknown_method` | `unknown_method` |
140
108
 
141
109
  Missing or invalid JSON, duplicate keys, unknown permissions and rules for
142
- unregistered methods fail closed. The CLI validates the file and binds its
143
- SHA-256 digest to the compiled CGI, which checks it at startup before migration.
144
- Changing policy requires rebuilding the SPK; request parameters cannot alter it.
145
- Register routes with `Route::handle`. Binary reads cannot be configured as anonymous.
110
+ methods the App does not register fail closed. Changing policy requires
111
+ rebuilding the SPK.
146
112
 
147
- ## The SDK
113
+ ## Capabilities
148
114
 
149
- Everything outside `backend/src/app`, `backend/permissions.json`
150
- and your UI comes from the SDK: the Rust crate `syno-pkg-land-sdk`
151
- (a git dependency pinned by `tag` in `backend/Cargo.toml`, the single source of the SDK version; `Cargo.lock` records the same tag) and, for the
152
- Vue UI, the npm package `@synopackageland/sdk` at the same version. Transport
153
- guards, DSM authentication, the administrator check, File Station, jobs,
154
- identity, portal, migrations and the executor protocol are documented in the SDK
155
- repository: <https://github.com/synopackageland/syno-pkg-land-sdk>.
156
-
157
- Upgrade the SDK by changing the dependency's `tag` and running `cargo update -p
158
- syno-pkg-land-sdk` (and changing `frontend/package.json`) to the new version; `synopkgland check` refuses mismatched versions.
159
-
160
- ## Customize the UI
161
-
162
- `ui/app.js` must register `window.DsmPackageApps[<package>].mount(root, ctx)`
163
- (the template shows how, using `document.currentScript.dataset.package`).
164
- `ctx.call(method, params)` is the only transport. `ctx.reopen(fn)` runs `fn`
165
- whenever DSM shows the window again and returns an idempotent unsubscribe function. Scope your CSS under your own root class:
166
- every DSM app shares one page.
167
-
168
- Plain JS and Vue receive the same `ctx.host` adapter. `ctx.host.platform()`
169
- returns a fresh frozen snapshot: `web`, `host` (`dsm`, `standalone-web` or
170
- `unavailable`), `dsmStandalone`, `dsmLanguage`, `appLocale`, `systemVersion`,
171
- `fullVersion`, and `theme`. Unknown fields are `null`; `theme` stays unknown.
172
- DSM launch mode is separate from plain-web fallback, and `appLocale` is the
173
- app's catalog locale, which may differ from DSM's UI language code.
174
-
175
- ```js
176
- const platform = ctx.host.platform();
177
- const result = ctx.host.setTitle('My notes');
178
- if (result.status !== 'ok') showTitleStatus(result.status);
179
- ```
115
+ `synopkgland init --with` adds examples that use the runtime's services through
116
+ `ctx.runtime`; each comes with its tests:
180
117
 
181
- `setTitle` enables DSM HTML encoding or assigns standalone `document.title`.
182
- It returns `{status: 'ok', target: 'dsm-window' | 'document'}` on synchronous
183
- completion, or `unsupported`, `unavailable`, `invalid-input`, `failed` statuses.
184
- A missing DSM method is unsupported; it never changes the DSM desktop's title.
185
- Destroy disposes the adapter and clears reopen handlers; retained adapters
186
- return unavailable. Navigation exposes activateWindow/requestClose/openApp/openURL;
187
- requests do not acknowledge completion. `ctx.host.subscribe('window-resize', fn)`
188
- returns `{status: 'subscribed', dispose}` only for a verified DSM window with on/un.
189
- The callback receives no native arguments. Remove your subscriptions on unmount;
190
- wrapper destroy also cleans them. `host-theme-change` and `host-language-change`
191
- return unsupported: no third-party host event is proven. These observations do
192
- not authorize work.
193
-
194
- `ctx.host.openFileManager('/example-share/docs')` requests File Station folder
195
- navigation; `revealFile('/home/report.txt')` requests a file highlight. Both require
196
- a registered, enabled and launchable File Station and report requested plus action,
197
- not location completion or ACL success. Use canonical virtual share paths only;
198
- OS `/volumeN/...`, traversal, repeated/trailing slash, controls and backslash are
199
- invalid. No path conversion or grant is performed. `openFile`, `showFileDetails`
200
- and `openAppSetting` return unsupported; private preview/details/settings flows
201
- have no proven generic contract.
202
-
203
- File reveal adds a best-effort correction using File Station 1.4.x private
204
- structures. Required APIs are feature-detected; absent/throwing APIs silently
205
- cancel and release timers/listeners. One AppLaunch callback binds one receiver;
206
- only its model may be selected once. Interaction, navigation away from the reached
207
- parent, close/destroy, disposal and new requests cancel the correction. The named
208
- 5000 ms total budget, 100 ms checks and 300 ms quiet interval have no device latency
209
- proof. `requested` still does not mean `selected`; actual effectiveness on DSM 7.4 /
210
- File Station 1.4.4 awaits a rebuilt-SPK device matrix. No second launch or broadcast
211
- setter is used, and later reloads may still clear selection.
212
-
213
- For an independent browser shell, use the SPK's generated `ui/host-0001.js`
214
- (the number follows the manifest build). It has no Ext or npm runtime dependency:
215
-
216
- ```html
217
- <script src="./host-0001.js"></script>
218
- ```
219
-
220
- ```js
221
- // shellContext supplies your app's own auth/transport and other context members.
222
- const ctx = {...shellContext, host: window.DsmPackageHosts.__PACKAGE__({locale: 'en'})};
223
- const app = window.DsmPackageApps.__PACKAGE__.mount(root, ctx);
224
- // When your shell unmounts:
225
- ctx.host.dispose();
226
- app.unmount();
227
- ```
118
+ | `--with` | Methods | Runtime services |
119
+ | --- | --- | --- |
120
+ | `jobs` | `hash.submit`, `hash.get`, `hash.cancel` | `files.read` with the caller's session, `bg.submit`, `bg.get`, `bg.cancel` |
121
+ | `root-ops` | `whoami.run` | `root.run` of `root/whoami.sh`, once an administrator approves the executor |
122
+ | `delegate` | `report.save` | `delegate.move_from_staging` into a folder the caller chose |
228
123
 
229
- Serve the generated script with your shell (or reference its same-origin DSM
230
- static path). `ctx.host` does not create a standalone backend, DSM session,
231
- cross-origin iframe bridge, or implementations of `ctx.call/t/root/reopen`.
232
- Standalone title affects the document shared by that shell's mounts.
124
+ A service refuses with its own code (`limit_exceeded`, `read_failed`, the
125
+ executor's refusals); the examples pass it on to the browser. `synopkgland add`
126
+ works on Go backends only: start a Rust App with the capabilities it needs.
233
127
 
234
- To build the UI with Vite, Vue or React, point `ui` at the build output and
235
- give the build command as an argv list (no shell):
128
+ ## The SDK
236
129
 
237
- ```yaml
238
- ui:
239
- dir: web/dist
240
- build:
241
- cwd: web
242
- command: [npm, run, build]
243
- ```
130
+ The `syno-pkg-land-app` git dependency's `tag` in `backend/Cargo.toml` names the SDK release; the build reads it from `Cargo.lock` and ships that release's runtime. A cross-compiled binary cannot run on the build machine, so `backend.methods` in dsm-package.yaml lists every method the App registers; `synopkgland check` refuses a list that differs from the App's own. Upgrade the SDK by changing that version; `synopkgland check`
131
+ refuses versions this CLI does not support. The bridge protocol, the services
132
+ and their codes are documented in the SDK repository:
133
+ <https://github.com/synopackageland/syno-pkg-land-sdk> (`spec/bridge/v1`).
244
134
 
245
- The build must emit `app.js` (one script that registers `mount`) and
246
- `app.css`. The installed `@synopackageland/ui` package gives Vue components with DSM 7 styling;
247
- import its `themes/dsm7-embedded.css` for use inside a DSM window.
248
-
249
- ## Languages
250
-
251
- The UI ships in nine of DSM's languages: `en`, `zh-TW`,
252
- `zh-CN`, `ja`, `ko`, `de`, `fr`, `es` and `it`. DSM's other languages show
253
- English. The window follows DSM's own language setting (`_S("lang")`).
254
-
255
- - **Messages**: `i18n/messages/<locale>.json`, flat `"key": "text"`. Use
256
- `ctx.t("greeting.setBy", { text, user })` in the UI; `{name}` is a
257
- placeholder, and `ctx.t("files", { count: 3 })` picks `files.one` or
258
- `files.other`. Missing keys fall back to English, then to the key itself.
259
- `build` embeds the catalogs in the DSM window script, so there is no
260
- extra request.
261
- - **Errors**: the backend returns a `code`; the UI shows
262
- `error.<code>` from the catalogs and uses the backend's English text only for
263
- codes it has no message for. Add `error.<code>` for every code you return.
264
- - **Glossary**: `i18n/glossary.json` records DSM's own words in all nine
265
- languages, each with its source (`section/key` in DSM 7.4's
266
- `/usr/syno/synoman/webman/texts/<lang>/strings`). A term's `keys` lists the
267
- messages that must contain it; that is how "Save" uses DSM's own word in
268
- every language, the same as the rest of DSM. Product names in `untranslated`
269
- stay in English everywhere.
270
- - **Package Center text**: `i18n.info` in `dsm-package.yaml` writes INFO
271
- `displayname_<lang>` / `description_<lang>` (for example `description_cht`).
272
-
273
- `synopkgland check` and `build` refuse a language file with missing or
274
- extra keys, a translation that drops a `{placeholder}`, an unsupported
275
- language, or a listed message that drifts from the glossary.
276
-
277
- To add a message: add the key to `en.json` and to the other eight files. To
278
- use a DSM term, copy its `text` from the glossary and add your key to the
279
- term's `keys`. A Vue or React UI can keep its own i18n library; `ctx.t` and
280
- `ctx.locale` are there if you want them.
281
-
282
- The Vue shell (`--ui vue`) keeps the app's own text in `frontend/src/i18n/<locale>.json`,
283
- in the same nine languages; SDK components (sign-in, files, background jobs) ship
284
- their own catalogs. Add new text to all nine files rather than writing it into a
285
- `.vue` file.
286
-
287
- ## Package an existing binary
288
-
289
- Keep the Rust backend and call your tool from a handler (recommended: auth
290
- stays central), or replace the CGI with your own executable:
291
-
292
- ```yaml
293
- arches: [x86_64]
294
- backend:
295
- kind: binary
296
- files:
297
- x86_64: prebuilt/api-x86_64
298
- ```
135
+ ## UI and languages
299
136
 
300
- The file must be a regular file in the project, a 64-bit Linux ELF for that
301
- arch, and statically linked (DSM models ship different libc versions). It is
302
- installed as `ui/cgi/api.cgi` (mode 0755) and runs as the package account.
303
- It then owns everything the SDK server does for you: bounded reads,
304
- POST-only JSON, same-origin checks, authentication before any work, and a
305
- refusal for unknown methods.
306
-
307
- ## DSM Application Portal link
308
-
309
- Only an App with the root executor (`rootOps` or `delegate`) can declare a
310
- portal; `synopkgland add root-ops portal` declares both. Uninstall then
311
- removes the App's Application Privileges rules and reloads nginx once DSM has
312
- removed the alias, so it stops answering. `synopkgland add portal` adds
313
- `portal: {}` to dsm-package.yaml so
314
- `https://<nas>/<alias>/` opens this app right after DSM sign-in. The install
315
- wizard proposes the alias (the package name in lowercase by default); an
316
- administrator turns it on from the app's portal panel or Control Panel >
317
- Login Portal > Applications. Ordinary users then see the link; until then
318
- they see "ask an administrator". DSM owns the setting: the backend's
319
- `portal.status` only reads it, and nothing in the package may write it. See
320
- `synopkgland skill show portal`.
321
-
322
- A portal needs DSM 7.3.2-86009 or later, so `add portal` raises a lower
323
- `os_min_ver` and build refuses one below it. DSM 7.1.1 rejects an unsigned
324
- package that registers a portal (Package Center error 320); 7.3.2 and 7.4
325
- install it; 7.2 is untested. To keep supporting older DSM, remove `portal:`.
137
+ The UI is the same as the Go template's. `ui/app.js` registers
138
+ `window.DsmPackageApps[<package>].mount(root, ctx)`; `ctx.call(method, params)`
139
+ is the only transport and `ctx.t(key, values)` reads
140
+ `i18n/messages/<locale>.json` (`en`, `zh-TW`, `zh-CN`, `ja`, `ko`, `de`,
141
+ `fr`, `es`, `it`). Add `error.<code>` for every code your methods return.
142
+ `synopkgland check` and `build` refuse a language file with missing or extra
143
+ keys, a dropped `{placeholder}` or a listed message that drifts from the
144
+ glossary. `--ui vue` gives the Vue shell in `frontend/` instead.
326
145
 
327
146
  ## Data, upgrades, uninstall
328
147
 
329
- The backend stores data in `/var/packages/__PACKAGE__/var` (the package's
330
- own folder, kept across upgrades). Bump `build` for every SPK you hand out;
331
- bump `version` for feature releases. There are no lifecycle scripts to write:
332
- the CGI starts per request, so start/stop do nothing.
333
-
334
- ### What an upgrade keeps
335
-
336
- An upgrade replaces the program files and keeps the data. DSM preserves `var`
337
- (`@appdata`), `home` (`@apphome`) and `etc` (`@appconf`) across an upgrade and
338
- removes `tmp` (`@apptemp`) and `target` (`@appstore`). Keep everything you need
339
- to keep under `var`, `home` or `etc`.
340
-
341
- ### Schema migrations
342
-
343
- The SDK's `datamigrate` module applies ordered, idempotent schema migrations and
344
- records the result in `<var>/data-version`. `backend/src/bin/api.rs` runs `app::migrate`
345
- on every start; the recorded version makes that a cheap no-op.
346
-
347
- - Versions start at 1 and never change meaning. The template ships one empty
348
- example in `backend/src/app/migrations.rs`; replace it with your steps.
349
- - Every step must be idempotent: a crash between the step and the version record,
350
- or two CGI requests racing on the first start after an upgrade, can run one step
351
- twice.
352
- - A step that fails stops the run with the previous version still recorded, and
353
- the next start resumes from there. The helper never deletes or rewrites data
354
- itself, so a failed migration leaves your old data in place.
355
- - `datamigrate::Context::old_pkg_ver` is the version being upgraded from:
356
- `SYNOPKG_OLD_PKGVER` when your environment sets it, otherwise the version the last
357
- successful run recorded. `new_pkg_ver` is the version running now.
358
-
359
- Uninstalling keeps your data: DSM leaves `@appdata` and `@apphome` on the volume,
360
- and reinstalling the same package ID picks them up again. The install wizard is a
361
- dynamic `WIZARD_UIFILES/install_uifile.sh` that checks — read-only — whether
362
- `@appdata/<package>` already exists and, if it does, tells the administrator the
363
- install reuses the old data and how to start clean.
364
-
365
- The generated SPK ships
366
- an uninstall wizard (English, plus eight other languages) with one box, **Also
367
- delete the app data listed above** (`pkgwizard_remove_additional`), unchecked by
368
- default. Ticked, `postuninst` empties `@appdata/<package>`, `@apphome/<package>`
369
- and `@appconf/<package>` and nothing else: not Shared Folders, the package
370
- account or other packages. An upgrade never deletes anything. Do not write your own `postuninst`;
371
- `synopkgland inspect` flags a deleting script that lacks the upgrade guard
372
- or deletes outside the package's own folders.
148
+ The App stores data in `/var/packages/__PACKAGE__/var` (`SYNOPKGLAND_DATA_DIR`,
149
+ kept across upgrades). Bump `build` for every SPK you hand out and `version`
150
+ for feature releases. Uninstalling keeps the data unless the administrator
151
+ ticks **Also delete the app data listed above** in the uninstall wizard.
373
152
 
374
153
  ## Support matrix
375
154
 
376
155
  | | Status |
377
156
  | --- | --- |
378
- | Build hosts | macOS, Linux (any OS with Rust and Node) |
379
- | Target CPUs | x86_64 and aarch64 DSM models (one SPK each). armv7 (DSM 7.2+ platforms `alpine`, `alpine4k`, `armada38x`, `monaco`) is opt-in: add it to `arches`; it covers the Rust CGI (plain or Vue UI), jobs, daemon, root-ops and delegate. armv7 jobs, daemon and executor (root-ops, delegate) are built and checked locally but have not been verified on a NAS |
380
- | DSM | 7.1 or later declared (`os_min_ver`, change it with `init --min-dsm` or `build --min-dsm`). Installed and tested on DSM 7.4 and 7.3.2 (x86_64). armv7 was installed and tested on DSM 7.1.1 (`alpine4k`, Linux 3.10, where the bridge runs from the package tmp fallback) with administrator, AppPriv and unauthenticated requests; `armada38x` and `monaco` have not been run on a NAS. No x86_64 Linux 3.10 model has been tested. aarch64 SPKs are built the same way but have not been run on a NAS. |
381
- | Not supported | armv7 platforms only DSM 7.0/7.1 ship (`armada370`, `armada375`, `armadaxp`, `comcerto2k`); DSM 6, running anything as root without an administrator's approval |
157
+ | Build hosts | macOS, Linux |
158
+ | Target CPUs | x86_64 and aarch64 DSM models (one SPK each); armv7 is opt-in in `arches`, built but not NAS-verified |
159
+ | DSM | 7.1 or later declared (`os_min_ver`) |
@@ -9,7 +9,7 @@ dependencies = [
9
9
  "libc",
10
10
  "serde",
11
11
  "serde_json",
12
- "syno-pkg-land-sdk",
12
+ "syno-pkg-land-app",
13
13
  ]
14
14
 
15
15
  [[package]]
@@ -93,9 +93,9 @@ dependencies = [
93
93
 
94
94
  [[package]]
95
95
  name = "syn"
96
- version = "3.0.8"
96
+ version = "3.0.9"
97
97
  source = "registry+https://github.com/rust-lang/crates.io-index"
98
- checksum = "01016da373cd8f7ef12624f796309f5c31ba8d646dd08856c02cd741d823c622"
98
+ checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
99
99
  dependencies = [
100
100
  "proc-macro2",
101
101
  "quote",
@@ -103,9 +103,9 @@ dependencies = [
103
103
  ]
104
104
 
105
105
  [[package]]
106
- name = "syno-pkg-land-sdk"
107
- version = "0.9.9"
108
- source = "git+https://github.com/synopackageland/syno-pkg-land-sdk?tag=v0.9.9#28da1726b0c06f4428153555314d33c7a3ca8a8b"
106
+ name = "syno-pkg-land-app"
107
+ version = "0.10.0"
108
+ source = "git+https://github.com/synopackageland/syno-pkg-land-sdk?tag=v0.10.0#ed4e0c15fdf91e4058ea795ba0bfae4e24882ed9"
109
109
  dependencies = [
110
110
  "libc",
111
111
  "serde",
@@ -4,21 +4,13 @@ version = "0.1.0"
4
4
  edition = "2024"
5
5
  publish = false
6
6
 
7
- [[bin]]
8
- name = "api"
9
- path = "src/bin/api.rs"
10
-
11
- [[bin]]
12
- name = "background-worker"
13
- path = "src/bin/background-worker.rs"
14
-
15
7
  [dependencies]
16
8
  libc = "0.2"
17
9
  serde = { version = "1", features = ["derive"] }
18
10
  serde_json = "1"
19
11
  # The SDK version is the tag; `synopkgland check` reads it from Cargo.lock.
20
- syno-pkg-land-sdk = { git = "https://github.com/synopackageland/syno-pkg-land-sdk", tag = "v0.9.9" }
12
+ syno-pkg-land-app = { git = "https://github.com/synopackageland/syno-pkg-land-sdk", tag = "v0.10.0" }
21
13
 
22
14
  [profile.release]
23
- # A handler panic becomes an "internal" reply (api::Server); abort would end the CGI instead.
15
+ # A handler panic becomes an "internal" reply; abort would end the App without one.
24
16
  panic = "unwind"
@@ -0,0 +1,6 @@
1
+ //! Saves to a user's destination: `synopkgland init --with delegate` replaces this stub with an example.
2
+
3
+ use crate::routes::Config;
4
+ use syno_pkg_land_app::App;
5
+
6
+ pub fn register(_app: &mut App, _cfg: &Config) {}
@@ -0,0 +1,6 @@
1
+ //! Background jobs: `synopkgland init --with jobs` replaces this stub with an example.
2
+
3
+ use crate::routes::Config;
4
+ use syno_pkg_land_app::App;
5
+
6
+ pub fn register(_app: &mut App, _cfg: &Config) {}
@@ -0,0 +1,12 @@
1
+ //! The App: the runtime starts it once per call (spec/bridge/v1).
2
+
3
+ mod delegate;
4
+ mod jobs;
5
+ mod root_ops;
6
+ mod routes;
7
+ #[cfg(test)]
8
+ mod test_support;
9
+
10
+ fn main() -> std::io::Result<()> {
11
+ routes::build(routes::Config::from_env()).run()
12
+ }
@@ -0,0 +1,6 @@
1
+ //! Root operations: `synopkgland init --with root-ops` replaces this stub with an example.
2
+
3
+ use crate::routes::Config;
4
+ use syno_pkg_land_app::App;
5
+
6
+ pub fn register(_app: &mut App, _cfg: &Config) {}