@synopackageland/cli 0.3.10 → 0.4.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 (280) hide show
  1. package/README.md +20 -9
  2. package/dist/bin.js +1 -2
  3. package/dist/build-spk/generator.d.ts +0 -3
  4. package/dist/build-spk/generator.js +135 -287
  5. package/dist/build-spk/native-lifecycle.js +0 -3
  6. package/dist/build-spk/native-payload.js +0 -1
  7. package/dist/cli.js +171 -151
  8. package/dist/compose/analyze.d.ts +1 -2
  9. package/dist/compose/analyze.js +5 -48
  10. package/dist/contract/unsupported-types.d.ts +10 -0
  11. package/dist/contract/unsupported-types.js +49 -0
  12. package/dist/contract/validate.js +25 -2
  13. package/dist/contract/web-launch.js +0 -2
  14. package/dist/deploy/deploy.d.ts +1 -4
  15. package/dist/deploy/deploy.js +45 -156
  16. package/dist/deploy/homes-values.js +1 -1
  17. package/dist/deploy/plan.d.ts +18 -0
  18. package/dist/deploy/plan.js +54 -0
  19. package/dist/deploy/spk-version.d.ts +2 -14
  20. package/dist/deploy/spk-version.js +25 -111
  21. package/dist/dev.d.ts +1 -3
  22. package/dist/dev.js +14 -28
  23. package/dist/error-registry.js +810 -862
  24. package/dist/host/dsm-client.js +12 -272
  25. package/dist/host/dsm-port-profile.d.ts +0 -1
  26. package/dist/host/dsm-port-profile.js +0 -2
  27. package/dist/host/types.d.ts +1 -83
  28. package/dist/index.d.ts +0 -9
  29. package/dist/index.js +0 -6
  30. package/dist/info.d.ts +0 -1
  31. package/dist/info.js +3 -10
  32. package/dist/init.d.ts +11 -1
  33. package/dist/init.js +112 -4
  34. package/dist/log.js +2 -3
  35. package/dist/native/validate.js +0 -1
  36. package/dist/standalone/binary.d.ts +9 -0
  37. package/dist/standalone/binary.js +67 -0
  38. package/dist/standalone/build.d.ts +25 -0
  39. package/dist/standalone/build.js +108 -0
  40. package/dist/standalone/dsm-window.d.ts +26 -0
  41. package/dist/standalone/dsm-window.js +251 -0
  42. package/dist/standalone/helper.d.ts +31 -0
  43. package/dist/standalone/helper.js +104 -0
  44. package/dist/standalone/i18n.d.ts +28 -0
  45. package/dist/standalone/i18n.js +161 -0
  46. package/dist/standalone/inspect-spk.d.ts +21 -0
  47. package/dist/standalone/inspect-spk.js +160 -0
  48. package/dist/standalone/manifest.d.ts +71 -0
  49. package/dist/standalone/manifest.js +276 -0
  50. package/dist/standalone/root-messages.d.ts +7 -0
  51. package/dist/standalone/root-messages.js +250 -0
  52. package/dist/standalone/root-ops.d.ts +42 -0
  53. package/dist/standalone/root-ops.js +101 -0
  54. package/dist/standalone/root-window.d.ts +35 -0
  55. package/dist/standalone/root-window.js +290 -0
  56. package/dist/standalone/spk.d.ts +22 -0
  57. package/dist/standalone/spk.js +205 -0
  58. package/dist/templates/binary/synology-app.yaml +7 -0
  59. package/dist/templates/hello-files/compose.yaml +0 -1
  60. package/dist/templates/hello-web/compose.yaml +13 -10
  61. package/dist/templates/hello-web/html/app.js +1 -23
  62. package/dist/templates/hello-web/html/index.html +1 -3
  63. package/dist/templates/hello-web-page/compose.yaml +13 -10
  64. package/dist/templates/hello-web-page/html/app.js +1 -23
  65. package/dist/templates/hello-web-page/html/index.html +1 -3
  66. package/dist/templates/native-go/README.md +268 -0
  67. package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
  68. package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
  69. package/dist/templates/native-go/backend/go.mod +3 -0
  70. package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
  71. package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
  72. package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
  73. package/dist/templates/native-go/backend/internal/api/server.go +155 -0
  74. package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
  75. package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
  76. package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
  77. package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
  78. package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  79. package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
  80. package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
  81. package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  82. package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  83. package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  84. package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
  85. package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  86. package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
  87. package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
  88. package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
  89. package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  90. package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  91. package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  92. package/dist/templates/native-go/dsm-package.yaml +58 -0
  93. package/dist/templates/native-go/gitignore +1 -0
  94. package/dist/templates/native-go/i18n/glossary.json +662 -0
  95. package/dist/templates/native-go/i18n/messages/de.json +28 -0
  96. package/dist/templates/native-go/i18n/messages/en.json +28 -0
  97. package/dist/templates/native-go/i18n/messages/es.json +28 -0
  98. package/dist/templates/native-go/i18n/messages/fr.json +28 -0
  99. package/dist/templates/native-go/i18n/messages/it.json +28 -0
  100. package/dist/templates/native-go/i18n/messages/ja.json +28 -0
  101. package/dist/templates/native-go/i18n/messages/ko.json +28 -0
  102. package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
  103. package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
  104. package/dist/templates/native-go/ui/app.css +35 -0
  105. package/dist/templates/native-go/ui/app.js +94 -0
  106. package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  107. package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  108. package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
  109. package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
  110. package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
  111. package/dist/templates/native-go-better-auth/node/package.json +1 -0
  112. package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  113. package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  114. package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  115. package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  116. package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
  117. package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  118. package/dist/templates/native-go-root/root/whoami.sh +6 -0
  119. package/dist/templates/native-go-vue/README.md +110 -0
  120. package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
  121. package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
  122. package/dist/templates/native-go-vue/frontend/package.json +6 -0
  123. package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
  124. package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
  125. package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
  126. package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
  127. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  128. package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
  129. package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
  130. package/dist/templates/native-go-vue/gitignore +2 -0
  131. package/dist/types.d.ts +1 -1
  132. package/dist/validate.js +11 -7
  133. package/package.json +3 -7
  134. package/schema/synology-app.schema.json +36 -37
  135. package/skills/SKILL.md +4 -48
  136. package/skills/reference/create.md +11 -83
  137. package/skills/reference/deploy.md +7 -78
  138. package/skills/reference/diagnose.md +4 -101
  139. package/skills/reference/files.md +5 -41
  140. package/skills/reference/identity.md +3 -29
  141. package/skills/reference/wrap.md +0 -2
  142. package/templates/binary/synology-app.yaml +7 -0
  143. package/templates/hello-files/compose.yaml +0 -1
  144. package/templates/hello-web/compose.yaml +13 -10
  145. package/templates/hello-web/html/app.js +1 -23
  146. package/templates/hello-web/html/index.html +1 -3
  147. package/templates/hello-web-page/compose.yaml +13 -10
  148. package/templates/hello-web-page/html/app.js +1 -23
  149. package/templates/hello-web-page/html/index.html +1 -3
  150. package/templates/native-go/README.md +268 -0
  151. package/templates/native-go/backend/cmd/app/main.go +49 -0
  152. package/templates/native-go/backend/cmd/helper/main.go +38 -0
  153. package/templates/native-go/backend/go.mod +3 -0
  154. package/templates/native-go/backend/internal/api/errors.go +33 -0
  155. package/templates/native-go/backend/internal/api/registry.go +94 -0
  156. package/templates/native-go/backend/internal/api/registry_test.go +329 -0
  157. package/templates/native-go/backend/internal/api/server.go +155 -0
  158. package/templates/native-go/backend/internal/app/root_none.go +9 -0
  159. package/templates/native-go/backend/internal/app/routes.go +127 -0
  160. package/templates/native-go/backend/internal/app/routes_test.go +167 -0
  161. package/templates/native-go/backend/internal/dsm/auth.go +118 -0
  162. package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  163. package/templates/native-go/backend/internal/helper/control.go +219 -0
  164. package/templates/native-go/backend/internal/helper/control_test.go +242 -0
  165. package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  166. package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  167. package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  168. package/templates/native-go/backend/internal/helper/process_other.go +5 -0
  169. package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  170. package/templates/native-go/backend/internal/helper/status.go +271 -0
  171. package/templates/native-go/backend/internal/helper/status_test.go +205 -0
  172. package/templates/native-go/backend/internal/helper/supervision.go +84 -0
  173. package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  174. package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  175. package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  176. package/templates/native-go/dsm-package.yaml +58 -0
  177. package/templates/native-go/gitignore +1 -0
  178. package/templates/native-go/i18n/glossary.json +662 -0
  179. package/templates/native-go/i18n/messages/de.json +28 -0
  180. package/templates/native-go/i18n/messages/en.json +28 -0
  181. package/templates/native-go/i18n/messages/es.json +28 -0
  182. package/templates/native-go/i18n/messages/fr.json +28 -0
  183. package/templates/native-go/i18n/messages/it.json +28 -0
  184. package/templates/native-go/i18n/messages/ja.json +28 -0
  185. package/templates/native-go/i18n/messages/ko.json +28 -0
  186. package/templates/native-go/i18n/messages/zh-CN.json +28 -0
  187. package/templates/native-go/i18n/messages/zh-TW.json +28 -0
  188. package/templates/native-go/ui/app.css +35 -0
  189. package/templates/native-go/ui/app.js +94 -0
  190. package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  191. package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  192. package/templates/native-go-better-auth/node/app.mjs +72 -0
  193. package/templates/native-go-better-auth/node/build.mjs +2 -0
  194. package/templates/native-go-better-auth/node/package-lock.json +862 -0
  195. package/templates/native-go-better-auth/node/package.json +1 -0
  196. package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  197. package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  198. package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  199. package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  200. package/templates/native-go-root/backend/internal/app/root.go +107 -0
  201. package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  202. package/templates/native-go-root/root/whoami.sh +6 -0
  203. package/templates/native-go-vue/README.md +110 -0
  204. package/templates/native-go-vue/frontend/DESIGN.md +78 -0
  205. package/templates/native-go-vue/frontend/package-lock.json +2507 -0
  206. package/templates/native-go-vue/frontend/package.json +6 -0
  207. package/templates/native-go-vue/frontend/src/App.vue +132 -0
  208. package/templates/native-go-vue/frontend/src/context.ts +17 -0
  209. package/templates/native-go-vue/frontend/src/main.ts +26 -0
  210. package/templates/native-go-vue/frontend/src/style.css +14 -0
  211. package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  212. package/templates/native-go-vue/frontend/tsconfig.json +1 -0
  213. package/templates/native-go-vue/frontend/vite.config.ts +8 -0
  214. package/templates/native-go-vue/gitignore +2 -0
  215. package/dist/attestation/app-package.d.ts +0 -86
  216. package/dist/attestation/app-package.js +0 -206
  217. package/dist/attestation/install-on-host.d.ts +0 -14
  218. package/dist/attestation/install-on-host.js +0 -52
  219. package/dist/attestation/keyring.d.ts +0 -21
  220. package/dist/attestation/keyring.js +0 -168
  221. package/dist/attestation/platform-service.d.ts +0 -70
  222. package/dist/attestation/platform-service.js +0 -250
  223. package/dist/build-spk/broker-glue.d.ts +0 -16
  224. package/dist/build-spk/broker-glue.js +0 -40
  225. package/dist/deploy/catalog-publish.d.ts +0 -3
  226. package/dist/deploy/catalog-publish.js +0 -7
  227. package/dist/doctor.d.ts +0 -22
  228. package/dist/doctor.js +0 -822
  229. package/dist/error-collector.d.ts +0 -2
  230. package/dist/error-collector.js +0 -46
  231. package/dist/host/broker-paths.d.ts +0 -1
  232. package/dist/host/broker-paths.js +0 -1
  233. package/dist/host/broker-upgrade-preflight.d.ts +0 -61
  234. package/dist/host/broker-upgrade-preflight.js +0 -156
  235. package/dist/host/digests.d.ts +0 -5
  236. package/dist/host/digests.js +0 -63
  237. package/dist/host/fake-nas.d.ts +0 -49
  238. package/dist/host/fake-nas.js +0 -193
  239. package/dist/mock-broker/bootstrap.d.ts +0 -2
  240. package/dist/mock-broker/bootstrap.js +0 -8
  241. package/dist/mock-broker/files.d.ts +0 -60
  242. package/dist/mock-broker/files.js +0 -408
  243. package/dist/mock-broker/oidc.d.ts +0 -31
  244. package/dist/mock-broker/oidc.js +0 -161
  245. package/dist/mock-broker/path.d.ts +0 -9
  246. package/dist/mock-broker/path.js +0 -50
  247. package/dist/mock-broker/server.d.ts +0 -19
  248. package/dist/mock-broker/server.js +0 -350
  249. package/dist/publish/build-ledger.d.ts +0 -22
  250. package/dist/publish/build-ledger.js +0 -26
  251. package/dist/publish/community-path.d.ts +0 -6
  252. package/dist/publish/community-path.js +0 -41
  253. package/dist/publish/curated-release.d.ts +0 -78
  254. package/dist/publish/curated-release.js +0 -883
  255. package/dist/publish/file-build-ledger.d.ts +0 -18
  256. package/dist/publish/file-build-ledger.js +0 -268
  257. package/dist/publish/github-pr.d.ts +0 -25
  258. package/dist/publish/github-pr.js +0 -85
  259. package/dist/publish/github-repo.d.ts +0 -8
  260. package/dist/publish/github-repo.js +0 -21
  261. package/dist/publish/platform-service-release.d.ts +0 -60
  262. package/dist/publish/platform-service-release.js +0 -333
  263. package/dist/publish/preflight.d.ts +0 -24
  264. package/dist/publish/preflight.js +0 -138
  265. package/dist/publish/publish.d.ts +0 -27
  266. package/dist/publish/publish.js +0 -136
  267. package/dist/publish/sync-definition.d.ts +0 -4
  268. package/dist/publish/sync-definition.js +0 -48
  269. package/dist/release-bin.d.ts +0 -2
  270. package/dist/release-bin.js +0 -23
  271. package/dist/templates/hello-web/html/sdk.js +0 -68
  272. package/dist/templates/hello-web-page/html/sdk.js +0 -68
  273. package/dist/templates/native/synology-app.yaml +0 -7
  274. package/dist/test-host.d.ts +0 -15
  275. package/dist/test-host.js +0 -37
  276. package/templates/hello-web/html/sdk.js +0 -68
  277. package/templates/hello-web-page/html/sdk.js +0 -68
  278. package/templates/native/synology-app.yaml +0 -7
  279. /package/dist/templates/{native → binary}/native.yaml +0 -0
  280. /package/templates/{native → binary}/native.yaml +0 -0
@@ -0,0 +1,276 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { isAbsolute, join, relative, resolve } from "node:path";
3
+ import { parse } from "yaml";
4
+ import { isNativeArchFamily } from "../build-spk/dsm-arch.js";
5
+ import { checkGlossary, isLocale, loadCatalogs } from "./i18n.js";
6
+ import { loadRootOps, ROOT_OPS_DIR } from "./root-ops.js";
7
+ import { readIntegration } from "./helper.js";
8
+ /**
9
+ * `dsm-package.yaml`: a Standalone Package, i.e. an unsigned native SPK that
10
+ * runs as its own DSM package account with a CGI backend and a DSM window UI.
11
+ */
12
+ export const STANDALONE_MANIFEST = "dsm-package.yaml";
13
+ const KNOWN_FIELDS = new Set([
14
+ "schema", "package", "version", "build", "displayname", "description",
15
+ "maintainer", "os_min_ver", "arches", "backend", "ui", "privilege", "i18n", "integration",
16
+ ]);
17
+ const I18N_FIELDS = new Set(["messages", "glossary", "info"]);
18
+ const INFO_TEXT_FIELDS = new Set(["displayname", "description"]);
19
+ const PACKAGE_ID = /^[A-Za-z][A-Za-z0-9]{1,63}$/;
20
+ const VERSION = /^\d+(\.\d+){0,3}$/;
21
+ const OS_MIN_VER = /^\d+\.\d+(\.\d+)?-\d+$/;
22
+ const GO_MAIN = /^\.\/[A-Za-z0-9_./-]*$/;
23
+ function isRecord(value) {
24
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25
+ }
26
+ class Collector {
27
+ root;
28
+ errors = [];
29
+ constructor(root) {
30
+ this.root = root;
31
+ }
32
+ fail(code, message) {
33
+ this.errors.push({ code, message });
34
+ return undefined;
35
+ }
36
+ /** A project-relative path that stays inside the project. */
37
+ path(value, field) {
38
+ if (typeof value !== "string" || value.trim() === "") {
39
+ return this.fail("STANDALONE_PATH_INVALID", `${field} 必須是專案內的相對路徑。`);
40
+ }
41
+ const resolved = resolve(this.root, value);
42
+ const rel = relative(this.root, resolved);
43
+ if (isAbsolute(value) || rel.startsWith("..") || isAbsolute(rel)) {
44
+ return this.fail("STANDALONE_PATH_OUTSIDE_PROJECT", `${field}「${value}」必須位於專案目錄內。`);
45
+ }
46
+ return resolved;
47
+ }
48
+ text(value, field) {
49
+ if (typeof value !== "string" || value.trim() === "" || /[\r\n"\\]/.test(value)) {
50
+ return this.fail("STANDALONE_TEXT_INVALID", `${field} 必須是單行、非空且不含 " 或 \\ 的文字。`);
51
+ }
52
+ return value;
53
+ }
54
+ }
55
+ export function loadStandaloneManifest(root) {
56
+ const file = join(root, STANDALONE_MANIFEST);
57
+ if (!existsSync(file)) {
58
+ return { ok: false, errors: [{ code: "STANDALONE_MANIFEST_MISSING", message: `找不到 ${STANDALONE_MANIFEST}。` }] };
59
+ }
60
+ let raw;
61
+ try {
62
+ raw = parse(readFileSync(file, "utf8"));
63
+ }
64
+ catch (error) {
65
+ return { ok: false, errors: [{ code: "STANDALONE_MANIFEST_YAML", message: String(error) }] };
66
+ }
67
+ const c = new Collector(root);
68
+ if (!isRecord(raw)) {
69
+ c.fail("STANDALONE_MANIFEST_YAML", `${STANDALONE_MANIFEST} 必須是 YAML 物件。`);
70
+ return { ok: false, errors: c.errors };
71
+ }
72
+ const manifest = readManifest(raw, c);
73
+ return manifest && c.errors.length === 0 ? { ok: true, manifest } : { ok: false, errors: c.errors };
74
+ }
75
+ function readManifest(raw, c) {
76
+ for (const key of Object.keys(raw)) {
77
+ if (!KNOWN_FIELDS.has(key)) {
78
+ c.fail("STANDALONE_FIELD_UNKNOWN", `不支援的欄位「${key}」。Standalone Package 不接受自訂 lifecycle scripts 或 DSM conf。`);
79
+ }
80
+ }
81
+ if (raw.schema !== 1) {
82
+ c.fail("STANDALONE_SCHEMA_INVALID", "schema 必須是 1。");
83
+ }
84
+ const privilege = raw.privilege === "none" || raw.privilege === "root"
85
+ ? raw.privilege
86
+ : c.fail("STANDALONE_PRIVILEGE_UNSUPPORTED", "privilege 只接受 none(只用套件帳號)或 root(由 DSM 管理員核准的 root 工作);請見 README「Root access」。");
87
+ const rootOps = privilege && readRootOps(privilege, c);
88
+ const packageId = typeof raw.package === "string" && PACKAGE_ID.test(raw.package)
89
+ ? raw.package
90
+ : c.fail("STANDALONE_PACKAGE_INVALID", "package 必須以英文字母開頭,只含英數字(2–64 字)。");
91
+ const version = typeof raw.version === "string" && VERSION.test(raw.version)
92
+ ? raw.version
93
+ : c.fail("STANDALONE_VERSION_INVALID", "version 必須是數字版本,例如 1.2.0(build 另外填)。");
94
+ const build = Number.isInteger(raw.build) && raw.build >= 1 && raw.build <= 9999
95
+ ? raw.build
96
+ : c.fail("STANDALONE_BUILD_INVALID", "build 必須是 1–9999 的整數。");
97
+ const osMinVer = typeof raw.os_min_ver === "string" && OS_MIN_VER.test(raw.os_min_ver)
98
+ ? raw.os_min_ver
99
+ : c.fail("STANDALONE_OS_MIN_VER_INVALID", "os_min_ver 格式為 <major>.<minor>-<build>,例如 7.2-64570。");
100
+ const displayname = c.text(raw.displayname, "displayname");
101
+ const description = c.text(raw.description, "description");
102
+ const maintainer = c.text(raw.maintainer, "maintainer");
103
+ const arches = readArches(raw.arches, c);
104
+ const backend = arches && readBackend(raw.backend, arches, c);
105
+ const integration = readIntegration(raw.integration, c.root, e => c.fail(e.code, e.message));
106
+ if (integration && (privilege !== "root" || backend?.kind !== "go" || rootOps?.some(op => op.name.startsWith("helper_")))) {
107
+ c.fail("STANDALONE_INTEGRATION_INVALID", "integration 需要 privilege: root 和 backend.go;helper_ 操作名稱保留給 generator。");
108
+ }
109
+ const ui = readUi(raw.ui, c);
110
+ const i18n = raw.i18n === undefined ? undefined : readI18n(raw.i18n, c);
111
+ if (!packageId || !version || !build || !osMinVer || !displayname || !description || !maintainer
112
+ || !arches || !backend || !ui || !privilege || (privilege === "root" && !rootOps)) {
113
+ return undefined;
114
+ }
115
+ return {
116
+ root: c.root,
117
+ package: packageId,
118
+ version,
119
+ build,
120
+ spkVersion: `${version}-${String(build).padStart(4, "0")}`,
121
+ displayname,
122
+ description,
123
+ maintainer,
124
+ os_min_ver: osMinVer,
125
+ arches,
126
+ backend,
127
+ ui,
128
+ ...(i18n ? { i18n } : {}),
129
+ privilege,
130
+ ...(rootOps ? { rootOps } : {}),
131
+ ...(integration ? { integration } : {}),
132
+ };
133
+ }
134
+ function readRootOps(privilege, c) {
135
+ const dir = join(c.root, ROOT_OPS_DIR);
136
+ if (privilege === "none") {
137
+ return existsSync(dir)
138
+ ? c.fail("STANDALONE_ROOT_OPS_UNUSED", `${ROOT_OPS_DIR}/ 只用在 privilege: root;privilege: none 的套件不會執行它。`)
139
+ : undefined;
140
+ }
141
+ const loaded = loadRootOps(dir);
142
+ if (!loaded.ok) {
143
+ loaded.errors.forEach((e) => c.fail(e.code, e.message));
144
+ return undefined;
145
+ }
146
+ return loaded.ops;
147
+ }
148
+ function readArches(value, c) {
149
+ const ok = Array.isArray(value)
150
+ && value.length > 0
151
+ && value.every((arch) => typeof arch === "string" && isNativeArchFamily(arch))
152
+ && new Set(value).size === value.length;
153
+ return ok
154
+ ? value
155
+ : c.fail("STANDALONE_ARCHES_INVALID", "arches 必須是不重複的 x86_64/aarch64 清單,至少一個。");
156
+ }
157
+ function readBackend(value, arches, c) {
158
+ if (!isRecord(value) || ("go" in value) === ("binary" in value)) {
159
+ return c.fail("STANDALONE_BACKEND_INVALID", "backend 必須恰好有 go 或 binary 其中之一。");
160
+ }
161
+ if ("go" in value) {
162
+ const go = value.go;
163
+ if (!isRecord(go) || typeof go.main !== "string" || !GO_MAIN.test(go.main) || go.main.includes("..")) {
164
+ return c.fail("STANDALONE_BACKEND_INVALID", "backend.go 需要 dir 與以 ./ 開頭的 main(例如 ./cmd/app)。");
165
+ }
166
+ const dir = c.path(go.dir, "backend.go.dir");
167
+ return dir ? { kind: "go", dir, main: go.main } : undefined;
168
+ }
169
+ const binary = value.binary;
170
+ if (!isRecord(binary)) {
171
+ return c.fail("STANDALONE_BACKEND_INVALID", "backend.binary 必須以架構為 key 指向 CGI 執行檔。");
172
+ }
173
+ const files = {};
174
+ for (const arch of arches) {
175
+ if (!(arch in binary)) {
176
+ c.fail("STANDALONE_BINARY_ARCH_MISSING", `arches 宣告了 ${arch},但 backend.binary 沒有它的執行檔。`);
177
+ continue;
178
+ }
179
+ files[arch] = c.path(binary[arch], `backend.binary.${arch}`);
180
+ }
181
+ for (const key of Object.keys(binary)) {
182
+ if (!arches.includes(key)) {
183
+ c.fail("STANDALONE_BACKEND_INVALID", `backend.binary.${key} 不在 arches 內。`);
184
+ }
185
+ }
186
+ return Object.values(files).every(Boolean) ? { kind: "binary", files } : undefined;
187
+ }
188
+ function readUi(value, c) {
189
+ if (!isRecord(value)) {
190
+ return c.fail("STANDALONE_UI_INVALID", "ui 必須包含 dir。");
191
+ }
192
+ const dir = c.path(value.dir, "ui.dir");
193
+ if (value.build === undefined) {
194
+ return dir ? { dir } : undefined;
195
+ }
196
+ const build = value.build;
197
+ const command = isRecord(build) ? build.command : undefined;
198
+ if (!Array.isArray(command) || command.length === 0 || !command.every((arg) => typeof arg === "string" && arg !== "")) {
199
+ return c.fail("STANDALONE_UI_BUILD_INVALID", "ui.build.command 必須是 argv 字串陣列(不經 shell),例如 [npm, run, build]。");
200
+ }
201
+ const cwd = c.path(build.cwd, "ui.build.cwd");
202
+ return dir && cwd ? { dir, build: { cwd, command: command } } : undefined;
203
+ }
204
+ function readI18n(value, c) {
205
+ const invalid = (why) => c.fail("STANDALONE_I18N_INVALID", `i18n ${why}`);
206
+ if (!isRecord(value) || Object.keys(value).some((key) => !I18N_FIELDS.has(key))) {
207
+ return invalid("只接受 messages(訊息目錄)、glossary(用語表)與 info(各語言 INFO 文字)。");
208
+ }
209
+ const info = readInfoText(value.info, c);
210
+ if (value.messages === undefined) {
211
+ return value.glossary === undefined ? info && { info } : invalid("glossary 需要搭配 messages。");
212
+ }
213
+ const dir = c.path(value.messages, "i18n.messages");
214
+ const glossary = value.glossary === undefined ? undefined : c.path(value.glossary, "i18n.glossary");
215
+ if (!dir || (value.glossary !== undefined && !glossary)) {
216
+ return undefined;
217
+ }
218
+ const loaded = loadCatalogs(dir);
219
+ if (!loaded.ok) {
220
+ loaded.errors.forEach((e) => c.fail(e.code, e.message));
221
+ return undefined;
222
+ }
223
+ const drift = glossary ? checkGlossary(glossary, loaded.catalogs) : [];
224
+ drift.forEach((e) => c.fail(e.code, e.message));
225
+ return info && drift.length === 0 ? { catalogs: loaded.catalogs, info } : undefined;
226
+ }
227
+ function readInfoText(value, c) {
228
+ if (value === undefined) {
229
+ return {};
230
+ }
231
+ const invalid = (why) => c.fail("STANDALONE_I18N_INVALID", `i18n.info ${why}`);
232
+ if (!isRecord(value)) {
233
+ return invalid("必須以語言為 key,例如 zh-TW: { displayname, description }。");
234
+ }
235
+ const info = {};
236
+ for (const [locale, text] of Object.entries(value)) {
237
+ if (!isLocale(locale) || locale === "en") {
238
+ return invalid(`的「${locale}」不是支援的翻譯語言;英文寫在頂層 displayname / description。`);
239
+ }
240
+ if (!isRecord(text) || Object.keys(text).some((key) => !INFO_TEXT_FIELDS.has(key))) {
241
+ return invalid(`.${locale} 只接受 displayname 與 description。`);
242
+ }
243
+ const entry = {};
244
+ for (const field of INFO_TEXT_FIELDS) {
245
+ if (text[field] !== undefined) {
246
+ const checked = c.text(text[field], `i18n.info.${locale}.${field}`);
247
+ if (!checked) {
248
+ return undefined;
249
+ }
250
+ entry[field] = checked;
251
+ }
252
+ }
253
+ info[locale] = entry;
254
+ }
255
+ return info;
256
+ }
257
+ /**
258
+ * The nearest directory at or above `start` that holds dsm-package.yaml,
259
+ * unless a Managed Profile project (synology-app.yaml) is found first.
260
+ */
261
+ export function findStandaloneRoot(start) {
262
+ let current = resolve(start);
263
+ while (true) {
264
+ if (existsSync(join(current, "synology-app.yaml"))) {
265
+ return null;
266
+ }
267
+ if (existsSync(join(current, STANDALONE_MANIFEST))) {
268
+ return current;
269
+ }
270
+ const parent = resolve(current, "..");
271
+ if (parent === current) {
272
+ return null;
273
+ }
274
+ current = parent;
275
+ }
276
+ }
@@ -0,0 +1,7 @@
1
+ import type { Catalog, Locale } from "./i18n.js";
2
+ /**
3
+ * Text of the authorization panel every privilege: root package shows, the
4
+ * same in every package. DSM's own words (Task Scheduler, Run, Remove, …) are
5
+ * taken verbatim from DSM 7.4; test/standalone-root-messages.test.ts pins them.
6
+ */
7
+ export declare const ROOT_MESSAGES: Record<Locale, Catalog>;
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Text of the authorization panel every privilege: root package shows, the
3
+ * same in every package. DSM's own words (Task Scheduler, Run, Remove, …) are
4
+ * taken verbatim from DSM 7.4; test/standalone-root-messages.test.ts pins them.
5
+ */
6
+ export const ROOT_MESSAGES = {
7
+ en: {
8
+ title: "Administrator authorization",
9
+ intro: "This package can run the fixed operation \"{op}\" as root. A DSM administrator approves it once with their password. DSM then keeps it as a disabled task in Task Scheduler that runs only when an administrator clicks Run here.",
10
+ status: "Status",
11
+ last_run: "Last run time",
12
+ never: "Not run yet",
13
+ output: "Result",
14
+ "state.checking": "Loading...",
15
+ "state.missing": "Not authorized",
16
+ "state.ready": "Authorized",
17
+ "state.outdated": "The package was updated and needs authorization again",
18
+ "state.conflict": "Another Task Scheduler task already uses the name \"{name}\"",
19
+ authorize: "Authorize",
20
+ run: "Run",
21
+ remove: "Remove",
22
+ retry: "Retry",
23
+ completed: "Completed",
24
+ cancelled: "Authorization was cancelled. Nothing was created.",
25
+ admin_only: "Only administrator accounts can perform this operation.",
26
+ uninstall_hint: "Before uninstalling this package, click Remove to delete its task from Task Scheduler.",
27
+ "error.root_conflict": "Rename or delete the task \"{name}\" in Task Scheduler, then click Retry.",
28
+ "error.root_not_ready": "Authorize this operation first.",
29
+ "error.root_task_failed": "The task ended with an error. Check its history in Task Scheduler, then click Retry.",
30
+ "error.root_timeout": "The task did not finish in time. Check it in Task Scheduler, then click Retry.",
31
+ "error.root_verify_failed": "Task Scheduler does not show the expected task. Click Retry.",
32
+ "error.default": "The operation failed ({code}). Click Retry.",
33
+ },
34
+ "zh-TW": {
35
+ title: "管理員授權",
36
+ intro: "此套件可用 root 身分執行固定操作「{op}」。DSM 管理員以密碼核准一次後,DSM 會在任務排程表中保留一個已停用的任務,只有管理員在此按下「執行」時才會執行。",
37
+ status: "狀態",
38
+ last_run: "最後執行時間",
39
+ never: "尚未執行",
40
+ output: "結果",
41
+ "state.checking": "載入中...",
42
+ "state.missing": "未授權",
43
+ "state.ready": "已授權",
44
+ "state.outdated": "套件已更新,需要重新授權",
45
+ "state.conflict": "任務排程表中已有其他任務使用名稱「{name}」",
46
+ authorize: "授權",
47
+ run: "執行",
48
+ remove: "移除",
49
+ retry: "重試",
50
+ completed: "已完成",
51
+ cancelled: "已取消授權,未建立任何任務。",
52
+ admin_only: "僅限管理員帳號允許執行此操作。",
53
+ uninstall_hint: "解除安裝此套件前,請按「移除」從任務排程表刪除它的任務。",
54
+ "error.root_conflict": "請在任務排程表中重新命名或刪除任務「{name}」,再按「重試」。",
55
+ "error.root_not_ready": "請先授權此操作。",
56
+ "error.root_task_failed": "任務執行失敗。請在任務排程表查看其記錄,再按「重試」。",
57
+ "error.root_timeout": "任務未在時限內完成。請在任務排程表確認後,再按「重試」。",
58
+ "error.root_verify_failed": "任務排程表中沒有預期的任務。請按「重試」。",
59
+ "error.default": "操作失敗({code})。請按「重試」。",
60
+ },
61
+ "zh-CN": {
62
+ title: "管理员授权",
63
+ intro: "此套件可以 root 身份运行固定操作“{op}”。DSM 管理员用密码批准一次后,DSM 会在任务计划中保留一个已禁用的任务,只有管理员在此单击“运行”时才会运行。",
64
+ status: "状态",
65
+ last_run: "最后运行时间",
66
+ never: "尚未运行",
67
+ output: "结果",
68
+ "state.checking": "加载中...",
69
+ "state.missing": "未授权",
70
+ "state.ready": "已授权",
71
+ "state.outdated": "套件已更新,需要重新授权",
72
+ "state.conflict": "任务计划中已有其他任务使用名称“{name}”",
73
+ authorize: "授权",
74
+ run: "运行",
75
+ remove: "删除",
76
+ retry: "重试",
77
+ completed: "已完成",
78
+ cancelled: "已取消授权,未创建任何任务。",
79
+ admin_only: "只有管理员帐户才能执行此操作。",
80
+ uninstall_hint: "卸载此套件前,请单击“删除”从任务计划中删除它的任务。",
81
+ "error.root_conflict": "请在任务计划中重命名或删除任务“{name}”,然后单击“重试”。",
82
+ "error.root_not_ready": "请先授权此操作。",
83
+ "error.root_task_failed": "任务运行失败。请在任务计划中查看其记录,然后单击“重试”。",
84
+ "error.root_timeout": "任务未在时限内完成。请在任务计划中确认后单击“重试”。",
85
+ "error.root_verify_failed": "任务计划中没有预期的任务。请单击“重试”。",
86
+ "error.default": "操作失败({code})。请单击“重试”。",
87
+ },
88
+ ja: {
89
+ title: "管理者の承認",
90
+ intro: "このパッケージは固定の操作「{op}」を root として実行できます。DSM 管理者がパスワードで一度承認すると、DSM はタスク スケジューラーに無効のタスクとして保持し、管理者がここで「実行」をクリックしたときだけ実行します。",
91
+ status: "状態",
92
+ last_run: "最終実行時刻",
93
+ never: "まだ実行されていません",
94
+ output: "結果",
95
+ "state.checking": "読み込み中...",
96
+ "state.missing": "未承認",
97
+ "state.ready": "承認済み",
98
+ "state.outdated": "パッケージが更新されたため、再承認が必要です",
99
+ "state.conflict": "タスク スケジューラーの別のタスクが名前「{name}」を使用しています",
100
+ authorize: "承認",
101
+ run: "実行",
102
+ remove: "削除",
103
+ retry: "再試行",
104
+ completed: "完了",
105
+ cancelled: "承認はキャンセルされました。何も作成されていません。",
106
+ admin_only: "この操作を実行できるのは管理者アカウントのみです。",
107
+ uninstall_hint: "このパッケージをアンインストールする前に、「削除」をクリックしてタスク スケジューラーからタスクを削除してください。",
108
+ "error.root_conflict": "タスク スケジューラーでタスク「{name}」の名前を変更するか削除してから、「再試行」をクリックしてください。",
109
+ "error.root_not_ready": "先にこの操作を承認してください。",
110
+ "error.root_task_failed": "タスクがエラーで終了しました。タスク スケジューラーで履歴を確認してから、「再試行」をクリックしてください。",
111
+ "error.root_timeout": "タスクが時間内に完了しませんでした。タスク スケジューラーで確認してから、「再試行」をクリックしてください。",
112
+ "error.root_verify_failed": "タスク スケジューラーに想定したタスクが見つかりません。「再試行」をクリックしてください。",
113
+ "error.default": "操作に失敗しました ({code})。「再試行」をクリックしてください。",
114
+ },
115
+ ko: {
116
+ title: "관리자 승인",
117
+ intro: "이 패키지는 고정된 작업 \"{op}\"을(를) root로 실행할 수 있습니다. DSM 관리자가 암호로 한 번 승인하면 DSM이 작업 스케줄러에 비활성화된 작업으로 보관하며, 관리자가 여기에서 \"실행\"을 클릭할 때만 실행됩니다.",
118
+ status: "상태",
119
+ last_run: "마지막 실행 시간",
120
+ never: "아직 실행되지 않음",
121
+ output: "결과",
122
+ "state.checking": "로드 중…",
123
+ "state.missing": "승인되지 않음",
124
+ "state.ready": "승인됨",
125
+ "state.outdated": "패키지가 업데이트되어 다시 승인해야 합니다",
126
+ "state.conflict": "작업 스케줄러의 다른 작업이 이미 \"{name}\" 이름을 사용하고 있습니다",
127
+ authorize: "승인",
128
+ run: "실행",
129
+ remove: "제거",
130
+ retry: "재시도",
131
+ completed: "완료됨",
132
+ cancelled: "승인이 취소되었습니다. 아무것도 만들어지지 않았습니다.",
133
+ admin_only: "관리자 계정에서만 이 작업을 수행할 수 있습니다.",
134
+ uninstall_hint: "이 패키지를 제거하기 전에 \"제거\"를 클릭하여 작업 스케줄러에서 작업을 삭제하십시오.",
135
+ "error.root_conflict": "작업 스케줄러에서 작업 \"{name}\"의 이름을 바꾸거나 삭제한 후 \"재시도\"를 클릭하십시오.",
136
+ "error.root_not_ready": "먼저 이 작업을 승인하십시오.",
137
+ "error.root_task_failed": "작업이 오류로 종료되었습니다. 작업 스케줄러에서 기록을 확인한 후 \"재시도\"를 클릭하십시오.",
138
+ "error.root_timeout": "작업이 제시간에 완료되지 않았습니다. 작업 스케줄러에서 확인한 후 \"재시도\"를 클릭하십시오.",
139
+ "error.root_verify_failed": "작업 스케줄러에 예상한 작업이 없습니다. \"재시도\"를 클릭하십시오.",
140
+ "error.default": "작업에 실패했습니다({code}). \"재시도\"를 클릭하십시오.",
141
+ },
142
+ de: {
143
+ title: "Administratorfreigabe",
144
+ intro: "Dieses Paket kann den festen Vorgang „{op}“ als root ausführen. Ein DSM-Administrator genehmigt ihn einmal mit seinem Passwort. DSM behält ihn dann als deaktivierte Aufgabe im Aufgabenplaner, die nur ausgeführt wird, wenn ein Administrator hier auf „Ausführen“ klickt.",
145
+ status: "Status",
146
+ last_run: "Letzte Ausführungszeit",
147
+ never: "Noch nicht ausgeführt",
148
+ output: "Ergebnis",
149
+ "state.checking": "Wird geladen...",
150
+ "state.missing": "Nicht freigegeben",
151
+ "state.ready": "Freigegeben",
152
+ "state.outdated": "Das Paket wurde aktualisiert und muss erneut freigegeben werden",
153
+ "state.conflict": "Eine andere Aufgabe im Aufgabenplaner verwendet bereits den Namen „{name}“",
154
+ authorize: "Freigeben",
155
+ run: "Ausführen",
156
+ remove: "Entfernen",
157
+ retry: "Neuer Versuch",
158
+ completed: "Abgeschlossen",
159
+ cancelled: "Die Freigabe wurde abgebrochen. Es wurde nichts erstellt.",
160
+ admin_only: "Nur Administratorkonten können diesen Vorgang ausführen.",
161
+ uninstall_hint: "Klicken Sie vor der Deinstallation dieses Pakets auf „Entfernen“, um seine Aufgabe aus dem Aufgabenplaner zu löschen.",
162
+ "error.root_conflict": "Benennen Sie die Aufgabe „{name}“ im Aufgabenplaner um oder löschen Sie sie, und klicken Sie dann auf „Neuer Versuch“.",
163
+ "error.root_not_ready": "Geben Sie diesen Vorgang zuerst frei.",
164
+ "error.root_task_failed": "Die Aufgabe wurde mit einem Fehler beendet. Prüfen Sie ihren Verlauf im Aufgabenplaner, und klicken Sie dann auf „Neuer Versuch“.",
165
+ "error.root_timeout": "Die Aufgabe wurde nicht rechtzeitig beendet. Prüfen Sie sie im Aufgabenplaner, und klicken Sie dann auf „Neuer Versuch“.",
166
+ "error.root_verify_failed": "Der Aufgabenplaner zeigt die erwartete Aufgabe nicht. Klicken Sie auf „Neuer Versuch“.",
167
+ "error.default": "Der Vorgang ist fehlgeschlagen ({code}). Klicken Sie auf „Neuer Versuch“.",
168
+ },
169
+ fr: {
170
+ title: "Autorisation de l'administrateur",
171
+ intro: "Ce paquet peut exécuter l'opération fixe « {op} » en tant que root. Un administrateur DSM l'approuve une fois avec son mot de passe. DSM la conserve ensuite comme tâche désactivée dans le Planificateur de tâches, qui ne s'exécute que lorsqu'un administrateur clique ici sur « Exécuter ».",
172
+ status: "Statut",
173
+ last_run: "Dernière heure d'exécution",
174
+ never: "Pas encore exécutée",
175
+ output: "Résultat",
176
+ "state.checking": "Chargement...",
177
+ "state.missing": "Non autorisée",
178
+ "state.ready": "Autorisée",
179
+ "state.outdated": "Le paquet a été mis à jour et doit être autorisé de nouveau",
180
+ "state.conflict": "Une autre tâche du Planificateur de tâches utilise déjà le nom « {name} »",
181
+ authorize: "Autoriser",
182
+ run: "Exécuter",
183
+ remove: "Supprimer",
184
+ retry: "Réessayer",
185
+ completed: "Terminé",
186
+ cancelled: "L'autorisation a été annulée. Rien n'a été créé.",
187
+ admin_only: "Seuls les comptes administrateur peuvent effectuer cette opération.",
188
+ uninstall_hint: "Avant de désinstaller ce paquet, cliquez sur « Supprimer » pour retirer sa tâche du Planificateur de tâches.",
189
+ "error.root_conflict": "Renommez ou supprimez la tâche « {name} » dans le Planificateur de tâches, puis cliquez sur « Réessayer ».",
190
+ "error.root_not_ready": "Autorisez d'abord cette opération.",
191
+ "error.root_task_failed": "La tâche s'est terminée avec une erreur. Consultez son historique dans le Planificateur de tâches, puis cliquez sur « Réessayer ».",
192
+ "error.root_timeout": "La tâche ne s'est pas terminée à temps. Vérifiez-la dans le Planificateur de tâches, puis cliquez sur « Réessayer ».",
193
+ "error.root_verify_failed": "Le Planificateur de tâches n'affiche pas la tâche attendue. Cliquez sur « Réessayer ».",
194
+ "error.default": "L'opération a échoué ({code}). Cliquez sur « Réessayer ».",
195
+ },
196
+ es: {
197
+ title: "Autorización del administrador",
198
+ intro: "Este paquete puede ejecutar la operación fija «{op}» como root. Un administrador de DSM la aprueba una vez con su contraseña. Después, DSM la conserva como una tarea desactivada en el Programador de tareas que solo se ejecuta cuando un administrador hace clic aquí en «Ejecutar».",
199
+ status: "Estado",
200
+ last_run: "Hora de la última ejecución",
201
+ never: "Aún no se ha ejecutado",
202
+ output: "Resultado",
203
+ "state.checking": "Cargando...",
204
+ "state.missing": "No autorizada",
205
+ "state.ready": "Autorizada",
206
+ "state.outdated": "El paquete se actualizó y debe autorizarse de nuevo",
207
+ "state.conflict": "Otra tarea del Programador de tareas ya usa el nombre «{name}»",
208
+ authorize: "Autorizar",
209
+ run: "Ejecutar",
210
+ remove: "Quitar",
211
+ retry: "Volver a intentar",
212
+ completed: "Finalizado",
213
+ cancelled: "Se canceló la autorización. No se creó nada.",
214
+ admin_only: "Solo las cuentas de administrador pueden realizar esta operación.",
215
+ uninstall_hint: "Antes de desinstalar este paquete, haga clic en «Quitar» para eliminar su tarea del Programador de tareas.",
216
+ "error.root_conflict": "Cambie el nombre de la tarea «{name}» o elimínela en el Programador de tareas y, a continuación, haga clic en «Volver a intentar».",
217
+ "error.root_not_ready": "Primero autorice esta operación.",
218
+ "error.root_task_failed": "La tarea terminó con un error. Consulte su historial en el Programador de tareas y, a continuación, haga clic en «Volver a intentar».",
219
+ "error.root_timeout": "La tarea no terminó a tiempo. Revísela en el Programador de tareas y, a continuación, haga clic en «Volver a intentar».",
220
+ "error.root_verify_failed": "El Programador de tareas no muestra la tarea esperada. Haga clic en «Volver a intentar».",
221
+ "error.default": "Error en la operación ({code}). Haga clic en «Volver a intentar».",
222
+ },
223
+ it: {
224
+ title: "Autorizzazione dell'amministratore",
225
+ intro: "Questo pacchetto può eseguire l'operazione fissa \"{op}\" come root. Un amministratore DSM la approva una volta con la propria password. DSM la conserva quindi come attività disattivata in Utilità di pianificazione, che viene eseguita solo quando un amministratore fa clic qui su \"Esegui\".",
226
+ status: "Stato",
227
+ last_run: "Ora ultima esecuzione",
228
+ never: "Non ancora eseguita",
229
+ output: "Risultato",
230
+ "state.checking": "Caricamento...",
231
+ "state.missing": "Non autorizzata",
232
+ "state.ready": "Autorizzata",
233
+ "state.outdated": "Il pacchetto è stato aggiornato e deve essere autorizzato di nuovo",
234
+ "state.conflict": "Un'altra attività in Utilità di pianificazione usa già il nome \"{name}\"",
235
+ authorize: "Autorizza",
236
+ run: "Esegui",
237
+ remove: "Rimuovi",
238
+ retry: "Riprova",
239
+ completed: "Completato",
240
+ cancelled: "Autorizzazione annullata. Non è stato creato nulla.",
241
+ admin_only: "Solo gli account administrator possono eseguire questa operazione.",
242
+ uninstall_hint: "Prima di disinstallare questo pacchetto, fare clic su \"Rimuovi\" per eliminare la sua attività da Utilità di pianificazione.",
243
+ "error.root_conflict": "Rinominare o eliminare l'attività \"{name}\" in Utilità di pianificazione, quindi fare clic su \"Riprova\".",
244
+ "error.root_not_ready": "Autorizzare prima questa operazione.",
245
+ "error.root_task_failed": "L'attività è terminata con un errore. Controllarne la cronologia in Utilità di pianificazione, quindi fare clic su \"Riprova\".",
246
+ "error.root_timeout": "L'attività non è terminata in tempo. Controllarla in Utilità di pianificazione, quindi fare clic su \"Riprova\".",
247
+ "error.root_verify_failed": "Utilità di pianificazione non mostra l'attività prevista. Fare clic su \"Riprova\".",
248
+ "error.default": "Operazione non riuscita ({code}). Fare clic su \"Riprova\".",
249
+ },
250
+ };
@@ -0,0 +1,42 @@
1
+ import type { StandaloneError } from "./manifest.js";
2
+ /**
3
+ * Root operations of a `privilege: root` Standalone Package. Each
4
+ * `root/<name>.sh` becomes the fixed script of one disabled DSM Task
5
+ * Scheduler task owned by root, which a DSM administrator creates through
6
+ * DSM's own password confirmation and runs on demand. The package itself
7
+ * never runs as root and nothing it can write is ever executed as root.
8
+ */
9
+ export interface RootOp {
10
+ name: string;
11
+ body: string;
12
+ /** Internal generator flag for protected helper control output. */
13
+ protectedOutput?: boolean;
14
+ }
15
+ export type RootOpsResult = {
16
+ ok: true;
17
+ ops: RootOp[];
18
+ } | {
19
+ ok: false;
20
+ errors: StandaloneError[];
21
+ };
22
+ export declare const ROOT_OPS_DIR = "root";
23
+ export declare const ROOT_OP_END = "SYNOPKGLAND_ROOT_OP_END";
24
+ export declare function rootTaskName(packageId: string, op: string): string;
25
+ /**
26
+ * The fixed start of an operation's task text. It does not carry the version,
27
+ * so the window can tell its own outdated task from someone else's.
28
+ */
29
+ export declare function rootTaskHeader(packageId: string, op: string): string;
30
+ /** Root-owned tmpfs folder the tasks write to; the package account can only read it. */
31
+ export declare function rootOutputDir(packageId: string): string;
32
+ export declare function loadRootOps(dir: string): RootOpsResult;
33
+ /**
34
+ * The exact text of the DSM task. The body runs from a quoted here-document
35
+ * (no expansion) and its stdout replaces `<op>.out` in the root-owned run
36
+ * folder. The package version is part of the text, so every upgrade changes
37
+ * what the administrator approved and needs approval again.
38
+ */
39
+ export declare function renderRootTaskScript(manifest: {
40
+ package: string;
41
+ spkVersion: string;
42
+ }, op: RootOp): string;