@synopackageland/cli 0.9.6 → 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 (111) hide show
  1. package/CHANGELOG.md +144 -5
  2. package/README.md +5 -0
  3. package/dist/add.js +7 -8
  4. package/dist/build-spk/dsm-arch.js +1 -1
  5. package/dist/build-spk/dsm-icons.d.ts +8 -1
  6. package/dist/build-spk/dsm-icons.js +26 -12
  7. package/dist/build-spk/dsm-version.d.ts +1 -1
  8. package/dist/build-spk/dsm-version.js +4 -4
  9. package/dist/build-spk/gateway.js +2 -2
  10. package/dist/build-spk/generator.js +1 -1
  11. package/dist/build-spk/png.js +16 -16
  12. package/dist/capabilities.js +1 -1
  13. package/dist/capability-overlays.d.ts +16 -5
  14. package/dist/capability-overlays.js +30 -16
  15. package/dist/cli.js +107 -107
  16. package/dist/compose/analyze.js +23 -23
  17. package/dist/compose/privilege.js +12 -12
  18. package/dist/contract/i18n.js +3 -3
  19. package/dist/contract/icon.js +2 -2
  20. package/dist/contract/posix-all-shares.js +2 -2
  21. package/dist/contract/schema-validate.js +3 -3
  22. package/dist/contract/validate.js +16 -16
  23. package/dist/contract/web-launch.d.ts +2 -1
  24. package/dist/contract/web-launch.js +3 -2
  25. package/dist/deploy/deploy.js +8 -8
  26. package/dist/deploy/homes-values.js +1 -1
  27. package/dist/deploy/identity-shared.js +1 -1
  28. package/dist/deploy/native-install-values.js +1 -1
  29. package/dist/deploy/param-values.js +4 -4
  30. package/dist/deploy/plan.d.ts +1 -1
  31. package/dist/deploy/port-preflight.js +3 -3
  32. package/dist/deploy/run-as-values.js +2 -2
  33. package/dist/deploy/share-permission-preflight.js +6 -6
  34. package/dist/deploy/share-values.js +3 -3
  35. package/dist/error-registry.js +369 -346
  36. package/dist/errors.js +1 -1
  37. package/dist/host/dsm-port-profile.d.ts +1 -1
  38. package/dist/host/dsm-port-profile.js +1 -1
  39. package/dist/info.js +11 -11
  40. package/dist/init.d.ts +2 -0
  41. package/dist/init.js +85 -38
  42. package/dist/inspect.js +5 -5
  43. package/dist/portal/alias.d.ts +1 -1
  44. package/dist/portal/alias.js +4 -4
  45. package/dist/runtime/executor-build.js +12 -1
  46. package/dist/runtime/executor-digests.json +8 -85
  47. package/dist/runtime/executor-inspect.js +11 -4
  48. package/dist/runtime/executor-policy.d.ts +9 -2
  49. package/dist/runtime/executor-policy.js +27 -8
  50. package/dist/runtime/root-programs.d.ts +47 -0
  51. package/dist/runtime/root-programs.js +153 -0
  52. package/dist/runtime/sdk-source.d.ts +5 -4
  53. package/dist/runtime/sdk-source.js +8 -4
  54. package/dist/skill.js +14 -14
  55. package/dist/spk/check-info.js +17 -17
  56. package/dist/spk/check-metadata.js +18 -18
  57. package/dist/spk/check-payload.js +10 -10
  58. package/dist/spk/rules.d.ts +57 -57
  59. package/dist/spk/rules.js +50 -50
  60. package/dist/spk/validate-spk.js +5 -5
  61. package/dist/standalone/api-permissions.js +1 -1
  62. package/dist/standalone/background.d.ts +3 -1
  63. package/dist/standalone/background.js +8 -4
  64. package/dist/standalone/binary.js +7 -7
  65. package/dist/standalone/bridge-app.d.ts +23 -0
  66. package/dist/standalone/bridge-app.js +141 -0
  67. package/dist/standalone/bridge-backend.d.ts +40 -0
  68. package/dist/standalone/bridge-backend.js +95 -0
  69. package/dist/standalone/bridge-build.d.ts +21 -0
  70. package/dist/standalone/bridge-build.js +62 -0
  71. package/dist/standalone/bridge-json.d.ts +16 -0
  72. package/dist/standalone/bridge-json.js +59 -0
  73. package/dist/standalone/bridge-runtime.d.ts +16 -0
  74. package/dist/standalone/bridge-runtime.js +46 -0
  75. package/dist/standalone/bridge-rust.d.ts +16 -0
  76. package/dist/standalone/bridge-rust.js +89 -0
  77. package/dist/standalone/build.js +18 -14
  78. package/dist/standalone/daemon.js +6 -6
  79. package/dist/standalone/dsm-window.d.ts +10 -5
  80. package/dist/standalone/dsm-window.js +64 -16
  81. package/dist/standalone/i18n.js +18 -18
  82. package/dist/standalone/inspect-bridge.d.ts +31 -0
  83. package/dist/standalone/inspect-bridge.js +111 -0
  84. package/dist/standalone/inspect-spk.d.ts +1 -1
  85. package/dist/standalone/inspect-spk.js +77 -69
  86. package/dist/standalone/lifecycle.js +22 -3
  87. package/dist/standalone/manifest.d.ts +17 -9
  88. package/dist/standalone/manifest.js +143 -65
  89. package/dist/standalone/portal-window.js +14 -7
  90. package/dist/standalone/request-timeout.d.ts +9 -0
  91. package/dist/standalone/request-timeout.js +13 -0
  92. package/dist/standalone/root-ops.d.ts +23 -2
  93. package/dist/standalone/root-ops.js +118 -13
  94. package/dist/standalone/sdk-check.js +4 -4
  95. package/dist/standalone/shared-folder.js +7 -7
  96. package/dist/standalone/spk.d.ts +2 -0
  97. package/dist/standalone/spk.js +30 -11
  98. package/dist/standalone/uninstall-hook.js +22 -4
  99. package/dist/standalone/uninstall.js +7 -7
  100. package/dist/standalone/window-size.d.ts +17 -0
  101. package/dist/standalone/window-size.js +31 -0
  102. package/dist/types.d.ts +1 -1
  103. package/dist/validate.js +13 -13
  104. package/package.json +2 -2
  105. package/schema/bridge-manifest.v1.schema.json +86 -0
  106. package/skills/reference/create.md +76 -2
  107. package/skills/reference/ui-json.md +2 -1
  108. package/skills/reference/ui-vue.md +9 -3
  109. package/skills/reference/wrap.md +1 -1
  110. package/dist/standalone/rust-build.d.ts +0 -35
  111. package/dist/standalone/rust-build.js +0 -127
@@ -56,26 +56,26 @@ async function scanPayload(payloadPath, wanted, findings) {
56
56
  entries = 0;
57
57
  }
58
58
  if (entries === 0) {
59
- findings.add("SPK_PAYLOAD_UNREADABLE", "package.tgz 不是可讀的 tar、tar.gz 封存,或是空的。", "package.tgz");
59
+ findings.add("SPK_PAYLOAD_UNREADABLE", "package.tgz is not a readable tar or tar.gz archive, or is empty.", "package.tgz");
60
60
  return undefined;
61
61
  }
62
62
  return files;
63
63
  }
64
64
  function checkEntryPath(path, type, linkpath, findings) {
65
65
  if (path.startsWith("/") || posix.normalize(path).split("/")[0] === "..") {
66
- findings.add("SPK_PAYLOAD_PATH_UNSAFE", `${path} 會解開到套件目錄之外。`, at(path));
66
+ findings.add("SPK_PAYLOAD_PATH_UNSAFE", `${path} extracts outside the package directory.`, at(path));
67
67
  return;
68
68
  }
69
69
  if (type === "SymbolicLink" && linkpath) {
70
70
  if (linkpath.startsWith("/")) {
71
- findings.add("SPK_PAYLOAD_LINK_ABSOLUTE", `${path} 是指向 ${linkpath} 的絕對路徑 symlink;目標由 NAS 決定,不在套件內。`, at(path));
71
+ findings.add("SPK_PAYLOAD_LINK_ABSOLUTE", `${path} is a symlink to the absolute path ${linkpath}; the target is determined by the NAS, not inside the package.`, at(path));
72
72
  }
73
73
  else if (posix.normalize(posix.join(posix.dirname(path), linkpath)).split("/")[0] === "..") {
74
- findings.add("SPK_PAYLOAD_LINK_ESCAPE", `${path} 的 symlink 目標 ${linkpath} 跳出套件目錄。`, at(path));
74
+ findings.add("SPK_PAYLOAD_LINK_ESCAPE", `The symlink target ${linkpath} of ${path} escapes the package directory.`, at(path));
75
75
  }
76
76
  }
77
77
  if (type === "Link" && linkpath && (linkpath.startsWith("/") || posix.normalize(linkpath).split("/")[0] === "..")) {
78
- findings.add("SPK_PAYLOAD_LINK_ESCAPE", `${path} 的 hard link 目標 ${linkpath} 不在套件內。`, at(path));
78
+ findings.add("SPK_PAYLOAD_LINK_ESCAPE", `The hard link target ${linkpath} of ${path} is not inside the package.`, at(path));
79
79
  }
80
80
  }
81
81
  /** Sections the package defines itself in its `texts/<lang>/strings` files. */
@@ -101,7 +101,7 @@ function checkUiConfig(file, sections, findings) {
101
101
  }
102
102
  catch { /* reported below */ }
103
103
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
104
- findings.add("SPK_UI_CONFIG_INVALID", `${file.path} 不是 JSON 物件。`, at(file.path));
104
+ findings.add("SPK_UI_CONFIG_INVALID", `${file.path} is not a JSON object.`, at(file.path));
105
105
  return;
106
106
  }
107
107
  for (const group of Object.values(config)) {
@@ -116,7 +116,7 @@ function checkUiConfig(file, sections, findings) {
116
116
  if (!ref)
117
117
  continue;
118
118
  const own = ref[1] === "synopkgland" || sections.has(ref[1]);
119
- findings.add(own ? "SPK_UI_TEXTS_MISSING" : "SPK_UI_TEXTS_UNDECLARED", `${file.path} 的 ${id}.${field}「${value}」是字串參照,但這個 entry 沒有宣告 "texts";${own ? "DSM 會直接顯示這個 key" : "除非是 DSM 內建的 section,否則 DSM 會直接顯示這個 key"}。`, at(file.path));
119
+ findings.add(own ? "SPK_UI_TEXTS_MISSING" : "SPK_UI_TEXTS_UNDECLARED", `${file.path} has ${id}.${field} "${value}" as a string reference, but the entry does not declare "texts"; ${own ? "DSM shows this key directly" : "unless it is a DSM built-in section, DSM shows this key directly"}.`, at(file.path));
120
120
  }
121
121
  }
122
122
  }
@@ -132,16 +132,16 @@ function checkElf(files, arch, findings) {
132
132
  continue;
133
133
  const machine = file.head[5] === 2 ? file.head.readUInt16BE(18) : file.head.readUInt16LE(18);
134
134
  if (noarch) {
135
- findings.add("SPK_NOARCH_ELF", `${file.path} 是 ELF 執行檔,但 INFO arch 是 noarch;noarch 套件只能有腳本,否則在其他 CPU 的 NAS 上無法執行。`, at(file.path));
135
+ findings.add("SPK_NOARCH_ELF", `${file.path} is an ELF executable, but INFO arch is noarch; a noarch package may contain only scripts, otherwise it cannot run on NAS models with another CPU.`, at(file.path));
136
136
  }
137
137
  else if (checkable && !machines.has(machine)) {
138
- findings.add("SPK_ELF_ARCH_MISMATCH", `${file.path} 的 ELF machine 是 ${machine},與 INFO arch「${arch}」的 CPU 不符。`, at(file.path));
138
+ findings.add("SPK_ELF_ARCH_MISMATCH", `The ELF machine of ${file.path} is ${machine}, which does not match the CPU of INFO arch "${arch}".`, at(file.path));
139
139
  }
140
140
  }
141
141
  }
142
142
  export async function checkPayload(payloadPath, info, findings) {
143
143
  if (startsWith(payloadPath, XZ_MAGIC)) {
144
- findings.add("SPK_PAYLOAD_XZ", "package.tgz 是 xz 壓縮(Synology toolkit 的格式);inspect 讀不了 xz,沒有檢查 payload 內容。", "package.tgz");
144
+ findings.add("SPK_PAYLOAD_XZ", "package.tgz is xz-compressed (the Synology toolkit format); inspect cannot read xz and did not check the payload contents.", "package.tgz");
145
145
  return;
146
146
  }
147
147
  const dirs = uiDirs(info.dsmuidir);
@@ -13,252 +13,252 @@ export interface SpkRule {
13
13
  export declare const SPK_RULES: {
14
14
  readonly SPK_NOT_TAR: {
15
15
  readonly severity: "error";
16
- readonly description: "檔案不是可讀的 tar 封存;SPK 是未壓縮的 tar。";
16
+ readonly description: "The file is not a readable tar archive; an SPK is an uncompressed tar.";
17
17
  readonly source: "DSM 7 Developer Guide: Package Structure";
18
18
  };
19
19
  readonly SPK_ENTRY_UNSAFE: {
20
20
  readonly severity: "error";
21
- readonly description: "SPK 內的路徑跳出封存根目錄(絕對路徑或 ..),或是連結。";
21
+ readonly description: "A path inside the SPK escapes the archive root (an absolute path or ..), or is a link.";
22
22
  readonly source: "DSM 7 Developer Guide: Package Structure";
23
23
  };
24
24
  readonly SPK_ENTRY_UNKNOWN: {
25
25
  readonly severity: "warning";
26
- readonly description: "SPK 最上層有指南沒有列出的檔案或目錄(檔名區分大小寫)。";
26
+ readonly description: "The SPK top level has a file or directory the guide does not list (file names are case-sensitive).";
27
27
  readonly source: "DSM 7 Developer Guide: Package Structure";
28
28
  };
29
29
  readonly SPK_INFO_MISSING: {
30
30
  readonly severity: "error";
31
- readonly description: "缺少 INFO。";
31
+ readonly description: "INFO is missing.";
32
32
  readonly source: "DSM 7 Developer Guide: Package Structure";
33
33
  };
34
34
  readonly SPK_PAYLOAD_MISSING: {
35
35
  readonly severity: "error";
36
- readonly description: "缺少 package.tgz。";
36
+ readonly description: "package.tgz is missing.";
37
37
  readonly source: "DSM 7 Developer Guide: Package Structure";
38
38
  };
39
39
  readonly SPK_INFO_FIELD_REQUIRED: {
40
40
  readonly severity: "error";
41
- readonly description: "INFO 缺少必填欄位(package、version、os_min_ver、description、arch、maintainer)。";
41
+ readonly description: "INFO is missing a required field (package, version, os_min_ver, description, arch, maintainer).";
42
42
  readonly source: "DSM 7 Developer Guide: INFO Necessary Fields";
43
43
  };
44
44
  readonly SPK_INFO_DISPLAYNAME_MISSING: {
45
45
  readonly severity: "warning";
46
- readonly description: "INFO 沒有 displayname;Package Center 會改顯示 package 值。";
46
+ readonly description: "INFO has no displayname; Package Center shows the package value instead.";
47
47
  readonly source: "DSM 7 Developer Guide: INFO Optional Fields";
48
48
  };
49
49
  readonly SPK_INFO_LINE_INVALID: {
50
50
  readonly severity: "warning";
51
- readonly description: "INFO 有一行不是 key=\"value\"(空行與 # 註解除外)。";
51
+ readonly description: "INFO has a line that is not key=\"value\" (blank lines and # comments excepted).";
52
52
  readonly source: "DSM 7 Developer Guide: INFO";
53
53
  };
54
54
  readonly SPK_INFO_VALUE_UNTERMINATED: {
55
55
  readonly severity: "error";
56
- readonly description: "INFO 欄位值的引號沒有結束。";
56
+ readonly description: "An INFO field value has an unterminated quote.";
57
57
  readonly source: "DSM 7 Developer Guide: INFO";
58
58
  };
59
59
  readonly SPK_INFO_FIELD_DUPLICATE: {
60
60
  readonly severity: "warning";
61
- readonly description: "INFO 同一個欄位設定了兩次。";
61
+ readonly description: "INFO sets the same field twice.";
62
62
  readonly source: "DSM 7 Developer Guide: INFO";
63
63
  };
64
64
  readonly SPK_INFO_PACKAGE_INVALID: {
65
65
  readonly severity: "error";
66
- readonly description: "INFO package 含有 : / > < | = 其中之一。";
66
+ readonly description: "INFO package contains one of : / > < | =.";
67
67
  readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (package)";
68
68
  };
69
69
  readonly SPK_INFO_VERSION_INVALID: {
70
70
  readonly severity: "error";
71
- readonly description: "INFO version 不是以 .、-、_ 分隔的數字,或某段超過 2147483647。";
71
+ readonly description: "INFO version is not numbers separated by . - _, or a segment exceeds 2147483647.";
72
72
  readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (version)";
73
73
  };
74
74
  readonly SPK_INFO_VERSION_NO_BUILD: {
75
75
  readonly severity: "warning";
76
- readonly description: "INFO version 沒有 -<build> 編號(指南格式為 [feature number]-[build number])。";
76
+ readonly description: "INFO version has no -<build> number (the guide format is [feature number]-[build number]).";
77
77
  readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (version)";
78
78
  };
79
79
  readonly SPK_INFO_OS_VER_INVALID: {
80
80
  readonly severity: "error";
81
- readonly description: "INFO os_min_ver 或 os_max_ver 不是 <major>.<minor>-<build>(也接受 <major>.<minor>.<micro>-<build>)。";
81
+ readonly description: "INFO os_min_ver or os_max_ver is not <major>.<minor>-<build> (<major>.<minor>.<micro>-<build> is also accepted).";
82
82
  readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (os_min_ver), Optional Fields (os_max_ver)";
83
83
  };
84
84
  readonly SPK_INFO_OS_MIN_VER_LOW: {
85
85
  readonly severity: "warning";
86
- readonly description: "INFO os_min_ver 低於 7.0-40000。";
86
+ readonly description: "INFO os_min_ver is below 7.0-40000.";
87
87
  readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (os_min_ver)";
88
88
  };
89
89
  readonly SPK_INFO_ARCH_UNKNOWN: {
90
90
  readonly severity: "warning";
91
- readonly description: "INFO arch 或 exclude_arch 含有 Appendix A 與已知 DSM 7 平台都沒有的值。";
91
+ readonly description: "INFO arch or exclude_arch has a value that is in neither Appendix A nor the known DSM 7 platforms.";
92
92
  readonly source: "DSM 7 Developer Guide: Appendix A Platform and Arch Value Mapping Table";
93
93
  };
94
94
  readonly SPK_INFO_ARCH_NOARCH_MIXED: {
95
95
  readonly severity: "warning";
96
- readonly description: "INFO arch 同時有 noarch 與其他值。";
96
+ readonly description: "INFO arch has noarch together with other values.";
97
97
  readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (arch)";
98
98
  };
99
99
  readonly SPK_INFO_BOOLEAN_INVALID: {
100
100
  readonly severity: "error";
101
- readonly description: "INFO 的 yes/no 欄位(例如 startable、silent_install)不是 \"yes\" 或 \"no\"。";
101
+ readonly description: "An INFO yes/no field (for example startable, silent_install) is not \"yes\" or \"no\".";
102
102
  readonly source: "DSM 7 Developer Guide: INFO Optional Fields";
103
103
  };
104
104
  readonly SPK_INFO_LANG_UNKNOWN: {
105
105
  readonly severity: "warning";
106
- readonly description: "displayname_<lang> 或 description_<lang> 的語言碼不在指南清單(enu、cht、chs、jpn…)。";
106
+ readonly description: "The language code of displayname_<lang> or description_<lang> is not in the guide list (enu, cht, chs, jpn...).";
107
107
  readonly source: "DSM 7 Developer Guide: INFO Optional Fields (description_[language])";
108
108
  };
109
109
  readonly SPK_INFO_FIELD_UNKNOWN: {
110
110
  readonly severity: "warning";
111
- readonly description: "INFO 欄位不在指南清單;DSM 不會使用它(拼錯的欄位名稱也會出現在這裡)。";
111
+ readonly description: "An INFO field is not in the guide list; DSM does not use it (misspelled field names also show up here).";
112
112
  readonly source: "DSM 7 Developer Guide: INFO Necessary Fields, Optional Fields";
113
113
  };
114
114
  readonly SPK_INFO_ADMINPORT_INVALID: {
115
115
  readonly severity: "error";
116
- readonly description: "INFO adminport 不是 0–65536 的整數。";
116
+ readonly description: "INFO adminport is not an integer from 0 to 65536.";
117
117
  readonly source: "DSM 7 Developer Guide: INFO Optional Fields (adminport)";
118
118
  };
119
119
  readonly SPK_INFO_CHECKSUM_MISMATCH: {
120
120
  readonly severity: "error";
121
- readonly description: "INFO checksum 不是 package.tgz 的 MD5。";
121
+ readonly description: "INFO checksum is not the MD5 of package.tgz.";
122
122
  readonly source: "DSM 7 Developer Guide: INFO Optional Fields (checksum)";
123
123
  };
124
124
  readonly SPK_PAYLOAD_UNREADABLE: {
125
125
  readonly severity: "error";
126
- readonly description: "package.tgz 不是可讀的 tar 或 tar.gz 封存,或是空的。";
126
+ readonly description: "package.tgz is not a readable tar or tar.gz archive, or is empty.";
127
127
  readonly source: "DSM 7 Developer Guide: Package Structure (package.tgz)";
128
128
  };
129
129
  readonly SPK_PAYLOAD_XZ: {
130
130
  readonly severity: "warning";
131
- readonly description: "package.tgz 是 xz 壓縮(Synology toolkit 的格式);inspect 讀不了 xz,沒有檢查 payload 內容。";
131
+ readonly description: "package.tgz is xz-compressed (the Synology toolkit format); inspect cannot read xz and did not check the payload contents.";
132
132
  readonly source: "DSM 7 Developer Guide: Pack Stage";
133
133
  };
134
134
  readonly SPK_PAYLOAD_PATH_UNSAFE: {
135
135
  readonly severity: "error";
136
- readonly description: "package.tgz 的路徑是絕對路徑或含 ..,會解開到套件目錄之外。";
136
+ readonly description: "A package.tgz path is absolute or contains .., so it extracts outside the package directory.";
137
137
  readonly source: "DSM 7 Developer Guide: Package Structure (package.tgz)";
138
138
  };
139
139
  readonly SPK_PAYLOAD_LINK_ESCAPE: {
140
140
  readonly severity: "error";
141
- readonly description: "package.tgz 的 symlink 或 hard link 指到套件目錄之外。";
141
+ readonly description: "A package.tgz symlink or hard link points outside the package directory.";
142
142
  readonly source: "DSM 7 Developer Guide: Package Structure (package.tgz)";
143
143
  };
144
144
  readonly SPK_PAYLOAD_LINK_ABSOLUTE: {
145
145
  readonly severity: "warning";
146
- readonly description: "package.tgz 的 symlink 指向絕對路徑;目標由 NAS 決定,不在套件內。";
146
+ readonly description: "A package.tgz symlink points at an absolute path; the target is determined by the NAS, not inside the package.";
147
147
  readonly source: "DSM 7 Developer Guide: Package Structure (package.tgz)";
148
148
  };
149
149
  readonly SPK_UI_CONFIG_INVALID: {
150
150
  readonly severity: "error";
151
- readonly description: "dsmuidir 底下的 config 不是 JSON 物件。";
151
+ readonly description: "The config under dsmuidir is not a JSON object.";
152
152
  readonly source: "DSM 7 Developer Guide: Integrate DSM, Application Config";
153
153
  };
154
154
  readonly SPK_UI_TEXTS_MISSING: {
155
155
  readonly severity: "error";
156
- readonly description: "config 的 title/desc 參照套件自己的字串(例如 synopkgland:app_title),但該 entry 沒有宣告 \"texts\";DSM 會直接顯示 key。";
157
- readonly source: "DSM 7.1.1/7.4 實測(DSM 只為宣告 texts 的 entry 載入 texts/<lang>/strings)";
156
+ readonly description: "The config title/desc references a package string (for example synopkgland:app_title), but the entry does not declare \"texts\"; DSM shows the key directly.";
157
+ readonly source: "Measured on DSM 7.1.1/7.4 (DSM loads texts/<lang>/strings only for entries that declare texts)";
158
158
  };
159
159
  readonly SPK_UI_TEXTS_UNDECLARED: {
160
160
  readonly severity: "warning";
161
- readonly description: "config 的 title/desc 是 section:key 字串參照,但沒有宣告 \"texts\";除非是 DSM 內建 section,否則 DSM 會直接顯示 key。";
162
- readonly source: "DSM 7.1.1/7.4 實測";
161
+ readonly description: "The config title/desc is a section:key string reference but does not declare \"texts\"; unless it is a DSM built-in section, DSM shows the key directly.";
162
+ readonly source: "Measured on DSM 7.1.1/7.4";
163
163
  };
164
164
  readonly SPK_NOARCH_ELF: {
165
165
  readonly severity: "error";
166
- readonly description: "INFO arch 是 noarch,但 package.tgz 有 ELF 執行檔。";
167
- readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (arch)、Pack Stage";
166
+ readonly description: "INFO arch is noarch, but package.tgz contains an ELF executable.";
167
+ readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (arch), Pack Stage";
168
168
  };
169
169
  readonly SPK_ELF_ARCH_MISMATCH: {
170
170
  readonly severity: "error";
171
- readonly description: "package.tgz 的 ELF 執行檔與 INFO arch 的 CPU 不符。";
172
- readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (arch)、Appendix A";
171
+ readonly description: "An ELF executable in package.tgz does not match the CPU of INFO arch.";
172
+ readonly source: "DSM 7 Developer Guide: INFO Necessary Fields (arch), Appendix A";
173
173
  };
174
174
  readonly SPK_SCRIPT_MISSING: {
175
175
  readonly severity: "error";
176
- readonly description: "缺少 scripts/start-stop-status;DSM 用它啟動、停止與查詢套件狀態。";
177
- readonly source: "DSM 7 Developer Guide: Package Structure、scripts";
176
+ readonly description: "scripts/start-stop-status is missing; DSM uses it to start, stop and query the package status.";
177
+ readonly source: "DSM 7 Developer Guide: Package Structure, scripts";
178
178
  };
179
179
  readonly SPK_SCRIPT_HOOK_MISSING: {
180
180
  readonly severity: "warning";
181
- readonly description: "缺少 preinst、postinst、preuninst、postuninst、preupgrade 或 postupgrade;指南列為必備,DSM 缺檔時的行為未驗證。";
181
+ readonly description: "preinst, postinst, preuninst, postuninst, preupgrade or postupgrade is missing; the guide lists them as required, and DSM's behavior without them is unverified.";
182
182
  readonly source: "DSM 7 Developer Guide: scripts";
183
183
  };
184
184
  readonly SPK_SCRIPT_NOT_EXECUTABLE: {
185
185
  readonly severity: "error";
186
- readonly description: "scripts/ 的生命週期腳本沒有執行權限。";
186
+ readonly description: "A scripts/ lifecycle script is not executable.";
187
187
  readonly source: "DSM 7 Developer Guide: scripts";
188
188
  };
189
189
  readonly SPK_SCRIPT_NO_INTERPRETER: {
190
190
  readonly severity: "warning";
191
- readonly description: "生命週期腳本開頭沒有 #!(例如 #!/bin/sh),也不是 ELF。";
191
+ readonly description: "A lifecycle script starts without #! (for example #!/bin/sh) and is not an ELF.";
192
192
  readonly source: "DSM 7 Developer Guide: scripts";
193
193
  };
194
194
  readonly SPK_PRIVILEGE_MISSING: {
195
195
  readonly severity: "error";
196
- readonly description: "缺少 conf/privilege;DSM 7 起必須提供。";
197
- readonly source: "DSM 7 Developer Guide: conf、Privilege Config";
196
+ readonly description: "conf/privilege is missing; DSM 7 requires it.";
197
+ readonly source: "DSM 7 Developer Guide: conf, Privilege Config";
198
198
  };
199
199
  readonly SPK_PRIVILEGE_INVALID: {
200
200
  readonly severity: "error";
201
- readonly description: "conf/privilege 不是 JSON 物件、沒有 defaults.run-as,或 run-as 不是 package/root。";
201
+ readonly description: "conf/privilege is not a JSON object, has no defaults.run-as, or run-as is not package/root.";
202
202
  readonly source: "DSM 7 Developer Guide: Privilege Config";
203
203
  };
204
204
  readonly SPK_PRIVILEGE_ROOT: {
205
205
  readonly severity: "warning";
206
- readonly description: "conf/privilege 要求以 root 執行(defaults、ctrl-script 或 executable)。";
206
+ readonly description: "conf/privilege requires running as root (defaults, ctrl-script or executable).";
207
207
  readonly source: "DSM 7 Developer Guide: Privilege Config";
208
208
  };
209
209
  readonly SPK_RESOURCE_INVALID: {
210
210
  readonly severity: "error";
211
- readonly description: "conf/resource 不是 JSON 物件。";
211
+ readonly description: "conf/resource is not a JSON object.";
212
212
  readonly source: "DSM 7 Developer Guide: Resource Config";
213
213
  };
214
214
  readonly SPK_RESOURCE_UNKNOWN: {
215
215
  readonly severity: "warning";
216
- readonly description: "conf/resource 有指南沒有列出的 resource worker id。";
216
+ readonly description: "conf/resource has a resource worker id the guide does not list.";
217
217
  readonly source: "DSM 7 Developer Guide: Available Workers";
218
218
  };
219
219
  readonly SPK_CONF_UNKNOWN: {
220
220
  readonly severity: "warning";
221
- readonly description: "conf/ 有 DSM 不讀取的檔案(已知:privilege、resource、PKG_DEPS、PKG_CONX、systemd/)。";
222
- readonly source: "DSM 7 Developer Guide: conf、Systemd User Unit";
221
+ readonly description: "conf/ has a file DSM does not read (known: privilege, resource, PKG_DEPS, PKG_CONX, systemd/).";
222
+ readonly source: "DSM 7 Developer Guide: conf, Systemd User Unit";
223
223
  };
224
224
  readonly SPK_ICON_MISSING: {
225
225
  readonly severity: "error";
226
- readonly description: "缺少 PACKAGE_ICON.PNG 或 PACKAGE_ICON_256.PNG。";
226
+ readonly description: "PACKAGE_ICON.PNG or PACKAGE_ICON_256.PNG is missing.";
227
227
  readonly source: "DSM 7 Developer Guide: Package Structure";
228
228
  };
229
229
  readonly SPK_ICON_INVALID: {
230
230
  readonly severity: "error";
231
- readonly description: "PACKAGE_ICON.PNG 或 PACKAGE_ICON_256.PNG 不是 PNG。";
231
+ readonly description: "PACKAGE_ICON.PNG or PACKAGE_ICON_256.PNG is not a PNG.";
232
232
  readonly source: "DSM 7 Developer Guide: Package Structure";
233
233
  };
234
234
  readonly SPK_ICON_SIZE: {
235
235
  readonly severity: "warning";
236
- readonly description: "PACKAGE_ICON.PNG 不是 64×64,或 PACKAGE_ICON_256.PNG 不是 256×256。";
236
+ readonly description: "PACKAGE_ICON.PNG is not 64x64, or PACKAGE_ICON_256.PNG is not 256x256.";
237
237
  readonly source: "DSM 7 Developer Guide: Package Structure";
238
238
  };
239
239
  readonly SPK_LICENSE_TOO_LARGE: {
240
240
  readonly severity: "error";
241
- readonly description: "LICENSE 大於或等於 1 MB。";
241
+ readonly description: "LICENSE is 1 MB or larger.";
242
242
  readonly source: "DSM 7 Developer Guide: Package Structure";
243
243
  };
244
244
  readonly SPK_WIZARD_INVALID: {
245
245
  readonly severity: "warning";
246
- readonly description: "WIZARD_UIFILES 的精靈檔不是 JSON 陣列。";
246
+ readonly description: "A WIZARD_UIFILES wizard file is not a JSON array.";
247
247
  readonly source: "DSM 7 Developer Guide: Package Structure (WIZARD_UIFILES)";
248
248
  };
249
249
  readonly SPK_WIZARD_SCRIPT_NOT_EXECUTABLE: {
250
250
  readonly severity: "warning";
251
- readonly description: "WIZARD_UIFILES 的 .sh 精靈產生器沒有執行權限。";
251
+ readonly description: "A WIZARD_UIFILES .sh wizard generator is not executable.";
252
252
  readonly source: "DSM 7 Developer Guide: Package Structure (WIZARD_UIFILES)";
253
253
  };
254
254
  readonly SPK_WIZARD_UNKNOWN: {
255
255
  readonly severity: "warning";
256
- readonly description: "WIZARD_UIFILES 的檔名不是 install/upgrade/uninstall_uifile[_<lang>][.sh]。";
256
+ readonly description: "A WIZARD_UIFILES file name is not install/upgrade/uninstall_uifile[_<lang>][.sh].";
257
257
  readonly source: "DSM 7 Developer Guide: Package Structure (WIZARD_UIFILES)";
258
258
  };
259
259
  readonly SPK_SIGNATURE_INVALID: {
260
260
  readonly severity: "warning";
261
- readonly description: "syno_signature.asc 不是 ASCII-armored PGP 簽章(指南的套件簽章只適用 DSM 6.x,可省略此檔)。";
261
+ readonly description: "syno_signature.asc is not an ASCII-armored PGP signature (the guide's package signing applies only to DSM 6.x, so this file may be omitted).";
262
262
  readonly source: "DSM 7 Developer Guide: Sign Package (only for DSM6.X)";
263
263
  };
264
264
  };
package/dist/spk/rules.js CHANGED
@@ -1,53 +1,53 @@
1
1
  const GUIDE = "DSM 7 Developer Guide";
2
2
  export const SPK_RULES = {
3
- SPK_NOT_TAR: { severity: "error", description: "檔案不是可讀的 tar 封存;SPK 是未壓縮的 tar。", source: `${GUIDE}: Package Structure` },
4
- SPK_ENTRY_UNSAFE: { severity: "error", description: "SPK 內的路徑跳出封存根目錄(絕對路徑或 ..),或是連結。", source: `${GUIDE}: Package Structure` },
5
- SPK_ENTRY_UNKNOWN: { severity: "warning", description: "SPK 最上層有指南沒有列出的檔案或目錄(檔名區分大小寫)。", source: `${GUIDE}: Package Structure` },
6
- SPK_INFO_MISSING: { severity: "error", description: "缺少 INFO。", source: `${GUIDE}: Package Structure` },
7
- SPK_PAYLOAD_MISSING: { severity: "error", description: "缺少 package.tgz。", source: `${GUIDE}: Package Structure` },
8
- SPK_INFO_FIELD_REQUIRED: { severity: "error", description: "INFO 缺少必填欄位(package、version、os_min_ver、description、arch、maintainer)。", source: `${GUIDE}: INFO Necessary Fields` },
9
- SPK_INFO_DISPLAYNAME_MISSING: { severity: "warning", description: "INFO 沒有 displayname;Package Center 會改顯示 package 值。", source: `${GUIDE}: INFO Optional Fields` },
10
- SPK_INFO_LINE_INVALID: { severity: "warning", description: "INFO 有一行不是 key=\"value\"(空行與 # 註解除外)。", source: `${GUIDE}: INFO` },
11
- SPK_INFO_VALUE_UNTERMINATED: { severity: "error", description: "INFO 欄位值的引號沒有結束。", source: `${GUIDE}: INFO` },
12
- SPK_INFO_FIELD_DUPLICATE: { severity: "warning", description: "INFO 同一個欄位設定了兩次。", source: `${GUIDE}: INFO` },
13
- SPK_INFO_PACKAGE_INVALID: { severity: "error", description: "INFO package 含有 : / > < | = 其中之一。", source: `${GUIDE}: INFO Necessary Fields (package)` },
14
- SPK_INFO_VERSION_INVALID: { severity: "error", description: "INFO version 不是以 .、-、_ 分隔的數字,或某段超過 2147483647。", source: `${GUIDE}: INFO Necessary Fields (version)` },
15
- SPK_INFO_VERSION_NO_BUILD: { severity: "warning", description: "INFO version 沒有 -<build> 編號(指南格式為 [feature number]-[build number])。", source: `${GUIDE}: INFO Necessary Fields (version)` },
16
- SPK_INFO_OS_VER_INVALID: { severity: "error", description: "INFO os_min_ver 或 os_max_ver 不是 <major>.<minor>-<build>(也接受 <major>.<minor>.<micro>-<build>)。", source: `${GUIDE}: INFO Necessary Fields (os_min_ver), Optional Fields (os_max_ver)` },
17
- SPK_INFO_OS_MIN_VER_LOW: { severity: "warning", description: "INFO os_min_ver 低於 7.0-40000。", source: `${GUIDE}: INFO Necessary Fields (os_min_ver)` },
18
- SPK_INFO_ARCH_UNKNOWN: { severity: "warning", description: "INFO arch 或 exclude_arch 含有 Appendix A 與已知 DSM 7 平台都沒有的值。", source: `${GUIDE}: Appendix A Platform and Arch Value Mapping Table` },
19
- SPK_INFO_ARCH_NOARCH_MIXED: { severity: "warning", description: "INFO arch 同時有 noarch 與其他值。", source: `${GUIDE}: INFO Necessary Fields (arch)` },
20
- SPK_INFO_BOOLEAN_INVALID: { severity: "error", description: "INFO 的 yes/no 欄位(例如 startable、silent_install)不是 \"yes\" 或 \"no\"。", source: `${GUIDE}: INFO Optional Fields` },
21
- SPK_INFO_LANG_UNKNOWN: { severity: "warning", description: "displayname_<lang> 或 description_<lang> 的語言碼不在指南清單(enu、cht、chs、jpn…)。", source: `${GUIDE}: INFO Optional Fields (description_[language])` },
22
- SPK_INFO_FIELD_UNKNOWN: { severity: "warning", description: "INFO 欄位不在指南清單;DSM 不會使用它(拼錯的欄位名稱也會出現在這裡)。", source: `${GUIDE}: INFO Necessary Fields, Optional Fields` },
23
- SPK_INFO_ADMINPORT_INVALID: { severity: "error", description: "INFO adminport 不是 0–65536 的整數。", source: `${GUIDE}: INFO Optional Fields (adminport)` },
24
- SPK_INFO_CHECKSUM_MISMATCH: { severity: "error", description: "INFO checksum 不是 package.tgz 的 MD5。", source: `${GUIDE}: INFO Optional Fields (checksum)` },
25
- SPK_PAYLOAD_UNREADABLE: { severity: "error", description: "package.tgz 不是可讀的 tar 或 tar.gz 封存,或是空的。", source: `${GUIDE}: Package Structure (package.tgz)` },
26
- SPK_PAYLOAD_XZ: { severity: "warning", description: "package.tgz 是 xz 壓縮(Synology toolkit 的格式);inspect 讀不了 xz,沒有檢查 payload 內容。", source: `${GUIDE}: Pack Stage` },
27
- SPK_PAYLOAD_PATH_UNSAFE: { severity: "error", description: "package.tgz 的路徑是絕對路徑或含 ..,會解開到套件目錄之外。", source: `${GUIDE}: Package Structure (package.tgz)` },
28
- SPK_PAYLOAD_LINK_ESCAPE: { severity: "error", description: "package.tgz 的 symlink 或 hard link 指到套件目錄之外。", source: `${GUIDE}: Package Structure (package.tgz)` },
29
- SPK_PAYLOAD_LINK_ABSOLUTE: { severity: "warning", description: "package.tgz 的 symlink 指向絕對路徑;目標由 NAS 決定,不在套件內。", source: `${GUIDE}: Package Structure (package.tgz)` },
30
- SPK_UI_CONFIG_INVALID: { severity: "error", description: "dsmuidir 底下的 config 不是 JSON 物件。", source: `${GUIDE}: Integrate DSM, Application Config` },
31
- SPK_UI_TEXTS_MISSING: { severity: "error", description: "config 的 title/desc 參照套件自己的字串(例如 synopkgland:app_title),但該 entry 沒有宣告 \"texts\";DSM 會直接顯示 key。", source: "DSM 7.1.1/7.4 實測(DSM 只為宣告 texts 的 entry 載入 texts/<lang>/strings)" },
32
- SPK_UI_TEXTS_UNDECLARED: { severity: "warning", description: "config 的 title/desc 是 section:key 字串參照,但沒有宣告 \"texts\";除非是 DSM 內建 section,否則 DSM 會直接顯示 key。", source: "DSM 7.1.1/7.4 實測" },
33
- SPK_NOARCH_ELF: { severity: "error", description: "INFO arch 是 noarch,但 package.tgz 有 ELF 執行檔。", source: `${GUIDE}: INFO Necessary Fields (arch)、Pack Stage` },
34
- SPK_ELF_ARCH_MISMATCH: { severity: "error", description: "package.tgz 的 ELF 執行檔與 INFO arch 的 CPU 不符。", source: `${GUIDE}: INFO Necessary Fields (arch)、Appendix A` },
35
- SPK_SCRIPT_MISSING: { severity: "error", description: "缺少 scripts/start-stop-status;DSM 用它啟動、停止與查詢套件狀態。", source: `${GUIDE}: Package Structure、scripts` },
36
- SPK_SCRIPT_HOOK_MISSING: { severity: "warning", description: "缺少 preinst、postinst、preuninst、postuninst、preupgrade 或 postupgrade;指南列為必備,DSM 缺檔時的行為未驗證。", source: `${GUIDE}: scripts` },
37
- SPK_SCRIPT_NOT_EXECUTABLE: { severity: "error", description: "scripts/ 的生命週期腳本沒有執行權限。", source: `${GUIDE}: scripts` },
38
- SPK_SCRIPT_NO_INTERPRETER: { severity: "warning", description: "生命週期腳本開頭沒有 #!(例如 #!/bin/sh),也不是 ELF。", source: `${GUIDE}: scripts` },
39
- SPK_PRIVILEGE_MISSING: { severity: "error", description: "缺少 conf/privilege;DSM 7 起必須提供。", source: `${GUIDE}: conf、Privilege Config` },
40
- SPK_PRIVILEGE_INVALID: { severity: "error", description: "conf/privilege 不是 JSON 物件、沒有 defaults.run-as,或 run-as 不是 package/root。", source: `${GUIDE}: Privilege Config` },
41
- SPK_PRIVILEGE_ROOT: { severity: "warning", description: "conf/privilege 要求以 root 執行(defaults、ctrl-script 或 executable)。", source: `${GUIDE}: Privilege Config` },
42
- SPK_RESOURCE_INVALID: { severity: "error", description: "conf/resource 不是 JSON 物件。", source: `${GUIDE}: Resource Config` },
43
- SPK_RESOURCE_UNKNOWN: { severity: "warning", description: "conf/resource 有指南沒有列出的 resource worker id。", source: `${GUIDE}: Available Workers` },
44
- SPK_CONF_UNKNOWN: { severity: "warning", description: "conf/ 有 DSM 不讀取的檔案(已知:privilege、resource、PKG_DEPS、PKG_CONX、systemd/)。", source: `${GUIDE}: conf、Systemd User Unit` },
45
- SPK_ICON_MISSING: { severity: "error", description: "缺少 PACKAGE_ICON.PNG 或 PACKAGE_ICON_256.PNG。", source: `${GUIDE}: Package Structure` },
46
- SPK_ICON_INVALID: { severity: "error", description: "PACKAGE_ICON.PNG 或 PACKAGE_ICON_256.PNG 不是 PNG。", source: `${GUIDE}: Package Structure` },
47
- SPK_ICON_SIZE: { severity: "warning", description: "PACKAGE_ICON.PNG 不是 64×64,或 PACKAGE_ICON_256.PNG 不是 256×256。", source: `${GUIDE}: Package Structure` },
48
- SPK_LICENSE_TOO_LARGE: { severity: "error", description: "LICENSE 大於或等於 1 MB。", source: `${GUIDE}: Package Structure` },
49
- SPK_WIZARD_INVALID: { severity: "warning", description: "WIZARD_UIFILES 的精靈檔不是 JSON 陣列。", source: `${GUIDE}: Package Structure (WIZARD_UIFILES)` },
50
- SPK_WIZARD_SCRIPT_NOT_EXECUTABLE: { severity: "warning", description: "WIZARD_UIFILES 的 .sh 精靈產生器沒有執行權限。", source: `${GUIDE}: Package Structure (WIZARD_UIFILES)` },
51
- SPK_WIZARD_UNKNOWN: { severity: "warning", description: "WIZARD_UIFILES 的檔名不是 install/upgrade/uninstall_uifile[_<lang>][.sh]。", source: `${GUIDE}: Package Structure (WIZARD_UIFILES)` },
52
- SPK_SIGNATURE_INVALID: { severity: "warning", description: "syno_signature.asc 不是 ASCII-armored PGP 簽章(指南的套件簽章只適用 DSM 6.x,可省略此檔)。", source: `${GUIDE}: Sign Package (only for DSM6.X)` },
3
+ SPK_NOT_TAR: { severity: "error", description: "The file is not a readable tar archive; an SPK is an uncompressed tar.", source: `${GUIDE}: Package Structure` },
4
+ SPK_ENTRY_UNSAFE: { severity: "error", description: "A path inside the SPK escapes the archive root (an absolute path or ..), or is a link.", source: `${GUIDE}: Package Structure` },
5
+ SPK_ENTRY_UNKNOWN: { severity: "warning", description: "The SPK top level has a file or directory the guide does not list (file names are case-sensitive).", source: `${GUIDE}: Package Structure` },
6
+ SPK_INFO_MISSING: { severity: "error", description: "INFO is missing.", source: `${GUIDE}: Package Structure` },
7
+ SPK_PAYLOAD_MISSING: { severity: "error", description: "package.tgz is missing.", source: `${GUIDE}: Package Structure` },
8
+ SPK_INFO_FIELD_REQUIRED: { severity: "error", description: "INFO is missing a required field (package, version, os_min_ver, description, arch, maintainer).", source: `${GUIDE}: INFO Necessary Fields` },
9
+ SPK_INFO_DISPLAYNAME_MISSING: { severity: "warning", description: "INFO has no displayname; Package Center shows the package value instead.", source: `${GUIDE}: INFO Optional Fields` },
10
+ SPK_INFO_LINE_INVALID: { severity: "warning", description: "INFO has a line that is not key=\"value\" (blank lines and # comments excepted).", source: `${GUIDE}: INFO` },
11
+ SPK_INFO_VALUE_UNTERMINATED: { severity: "error", description: "An INFO field value has an unterminated quote.", source: `${GUIDE}: INFO` },
12
+ SPK_INFO_FIELD_DUPLICATE: { severity: "warning", description: "INFO sets the same field twice.", source: `${GUIDE}: INFO` },
13
+ SPK_INFO_PACKAGE_INVALID: { severity: "error", description: "INFO package contains one of : / > < | =.", source: `${GUIDE}: INFO Necessary Fields (package)` },
14
+ SPK_INFO_VERSION_INVALID: { severity: "error", description: "INFO version is not numbers separated by . - _, or a segment exceeds 2147483647.", source: `${GUIDE}: INFO Necessary Fields (version)` },
15
+ SPK_INFO_VERSION_NO_BUILD: { severity: "warning", description: "INFO version has no -<build> number (the guide format is [feature number]-[build number]).", source: `${GUIDE}: INFO Necessary Fields (version)` },
16
+ SPK_INFO_OS_VER_INVALID: { severity: "error", description: "INFO os_min_ver or os_max_ver is not <major>.<minor>-<build> (<major>.<minor>.<micro>-<build> is also accepted).", source: `${GUIDE}: INFO Necessary Fields (os_min_ver), Optional Fields (os_max_ver)` },
17
+ SPK_INFO_OS_MIN_VER_LOW: { severity: "warning", description: "INFO os_min_ver is below 7.0-40000.", source: `${GUIDE}: INFO Necessary Fields (os_min_ver)` },
18
+ SPK_INFO_ARCH_UNKNOWN: { severity: "warning", description: "INFO arch or exclude_arch has a value that is in neither Appendix A nor the known DSM 7 platforms.", source: `${GUIDE}: Appendix A Platform and Arch Value Mapping Table` },
19
+ SPK_INFO_ARCH_NOARCH_MIXED: { severity: "warning", description: "INFO arch has noarch together with other values.", source: `${GUIDE}: INFO Necessary Fields (arch)` },
20
+ SPK_INFO_BOOLEAN_INVALID: { severity: "error", description: "An INFO yes/no field (for example startable, silent_install) is not \"yes\" or \"no\".", source: `${GUIDE}: INFO Optional Fields` },
21
+ SPK_INFO_LANG_UNKNOWN: { severity: "warning", description: "The language code of displayname_<lang> or description_<lang> is not in the guide list (enu, cht, chs, jpn...).", source: `${GUIDE}: INFO Optional Fields (description_[language])` },
22
+ SPK_INFO_FIELD_UNKNOWN: { severity: "warning", description: "An INFO field is not in the guide list; DSM does not use it (misspelled field names also show up here).", source: `${GUIDE}: INFO Necessary Fields, Optional Fields` },
23
+ SPK_INFO_ADMINPORT_INVALID: { severity: "error", description: "INFO adminport is not an integer from 0 to 65536.", source: `${GUIDE}: INFO Optional Fields (adminport)` },
24
+ SPK_INFO_CHECKSUM_MISMATCH: { severity: "error", description: "INFO checksum is not the MD5 of package.tgz.", source: `${GUIDE}: INFO Optional Fields (checksum)` },
25
+ SPK_PAYLOAD_UNREADABLE: { severity: "error", description: "package.tgz is not a readable tar or tar.gz archive, or is empty.", source: `${GUIDE}: Package Structure (package.tgz)` },
26
+ SPK_PAYLOAD_XZ: { severity: "warning", description: "package.tgz is xz-compressed (the Synology toolkit format); inspect cannot read xz and did not check the payload contents.", source: `${GUIDE}: Pack Stage` },
27
+ SPK_PAYLOAD_PATH_UNSAFE: { severity: "error", description: "A package.tgz path is absolute or contains .., so it extracts outside the package directory.", source: `${GUIDE}: Package Structure (package.tgz)` },
28
+ SPK_PAYLOAD_LINK_ESCAPE: { severity: "error", description: "A package.tgz symlink or hard link points outside the package directory.", source: `${GUIDE}: Package Structure (package.tgz)` },
29
+ SPK_PAYLOAD_LINK_ABSOLUTE: { severity: "warning", description: "A package.tgz symlink points at an absolute path; the target is determined by the NAS, not inside the package.", source: `${GUIDE}: Package Structure (package.tgz)` },
30
+ SPK_UI_CONFIG_INVALID: { severity: "error", description: "The config under dsmuidir is not a JSON object.", source: `${GUIDE}: Integrate DSM, Application Config` },
31
+ SPK_UI_TEXTS_MISSING: { severity: "error", description: "The config title/desc references a package string (for example synopkgland:app_title), but the entry does not declare \"texts\"; DSM shows the key directly.", source: "Measured on DSM 7.1.1/7.4 (DSM loads texts/<lang>/strings only for entries that declare texts)" },
32
+ SPK_UI_TEXTS_UNDECLARED: { severity: "warning", description: "The config title/desc is a section:key string reference but does not declare \"texts\"; unless it is a DSM built-in section, DSM shows the key directly.", source: "Measured on DSM 7.1.1/7.4" },
33
+ SPK_NOARCH_ELF: { severity: "error", description: "INFO arch is noarch, but package.tgz contains an ELF executable.", source: `${GUIDE}: INFO Necessary Fields (arch), Pack Stage` },
34
+ SPK_ELF_ARCH_MISMATCH: { severity: "error", description: "An ELF executable in package.tgz does not match the CPU of INFO arch.", source: `${GUIDE}: INFO Necessary Fields (arch), Appendix A` },
35
+ SPK_SCRIPT_MISSING: { severity: "error", description: "scripts/start-stop-status is missing; DSM uses it to start, stop and query the package status.", source: `${GUIDE}: Package Structure, scripts` },
36
+ SPK_SCRIPT_HOOK_MISSING: { severity: "warning", description: "preinst, postinst, preuninst, postuninst, preupgrade or postupgrade is missing; the guide lists them as required, and DSM's behavior without them is unverified.", source: `${GUIDE}: scripts` },
37
+ SPK_SCRIPT_NOT_EXECUTABLE: { severity: "error", description: "A scripts/ lifecycle script is not executable.", source: `${GUIDE}: scripts` },
38
+ SPK_SCRIPT_NO_INTERPRETER: { severity: "warning", description: "A lifecycle script starts without #! (for example #!/bin/sh) and is not an ELF.", source: `${GUIDE}: scripts` },
39
+ SPK_PRIVILEGE_MISSING: { severity: "error", description: "conf/privilege is missing; DSM 7 requires it.", source: `${GUIDE}: conf, Privilege Config` },
40
+ SPK_PRIVILEGE_INVALID: { severity: "error", description: "conf/privilege is not a JSON object, has no defaults.run-as, or run-as is not package/root.", source: `${GUIDE}: Privilege Config` },
41
+ SPK_PRIVILEGE_ROOT: { severity: "warning", description: "conf/privilege requires running as root (defaults, ctrl-script or executable).", source: `${GUIDE}: Privilege Config` },
42
+ SPK_RESOURCE_INVALID: { severity: "error", description: "conf/resource is not a JSON object.", source: `${GUIDE}: Resource Config` },
43
+ SPK_RESOURCE_UNKNOWN: { severity: "warning", description: "conf/resource has a resource worker id the guide does not list.", source: `${GUIDE}: Available Workers` },
44
+ SPK_CONF_UNKNOWN: { severity: "warning", description: "conf/ has a file DSM does not read (known: privilege, resource, PKG_DEPS, PKG_CONX, systemd/).", source: `${GUIDE}: conf, Systemd User Unit` },
45
+ SPK_ICON_MISSING: { severity: "error", description: "PACKAGE_ICON.PNG or PACKAGE_ICON_256.PNG is missing.", source: `${GUIDE}: Package Structure` },
46
+ SPK_ICON_INVALID: { severity: "error", description: "PACKAGE_ICON.PNG or PACKAGE_ICON_256.PNG is not a PNG.", source: `${GUIDE}: Package Structure` },
47
+ SPK_ICON_SIZE: { severity: "warning", description: "PACKAGE_ICON.PNG is not 64x64, or PACKAGE_ICON_256.PNG is not 256x256.", source: `${GUIDE}: Package Structure` },
48
+ SPK_LICENSE_TOO_LARGE: { severity: "error", description: "LICENSE is 1 MB or larger.", source: `${GUIDE}: Package Structure` },
49
+ SPK_WIZARD_INVALID: { severity: "warning", description: "A WIZARD_UIFILES wizard file is not a JSON array.", source: `${GUIDE}: Package Structure (WIZARD_UIFILES)` },
50
+ SPK_WIZARD_SCRIPT_NOT_EXECUTABLE: { severity: "warning", description: "A WIZARD_UIFILES .sh wizard generator is not executable.", source: `${GUIDE}: Package Structure (WIZARD_UIFILES)` },
51
+ SPK_WIZARD_UNKNOWN: { severity: "warning", description: "A WIZARD_UIFILES file name is not install/upgrade/uninstall_uifile[_<lang>][.sh].", source: `${GUIDE}: Package Structure (WIZARD_UIFILES)` },
52
+ SPK_SIGNATURE_INVALID: { severity: "warning", description: "syno_signature.asc is not an ASCII-armored PGP signature (the guide's package signing applies only to DSM 6.x, so this file may be omitted).", source: `${GUIDE}: Sign Package (only for DSM6.X)` },
53
53
  };
@@ -22,7 +22,7 @@ export async function validateSpk(spkPath) {
22
22
  checkOuterEntries(outer.entries, findings);
23
23
  info = checkInfo(outer, findings);
24
24
  if (!outer.payloadPath)
25
- findings.add("SPK_PAYLOAD_MISSING", "缺少 package.tgz。", "package.tgz");
25
+ findings.add("SPK_PAYLOAD_MISSING", "package.tgz is missing.", "package.tgz");
26
26
  else
27
27
  await checkPayload(outer.payloadPath, info, findings);
28
28
  checkMetadata({ files: outer.files, modes: new Map(outer.entries.map((entry) => [normalize(entry.path), entry.mode])) }, findings);
@@ -71,7 +71,7 @@ async function readOuter(spkPath, dir, findings) {
71
71
  outer.entries.length = 0;
72
72
  }
73
73
  if (outer.entries.length === 0) {
74
- findings.add("SPK_NOT_TAR", "檔案不是可讀的 tar 封存。");
74
+ findings.add("SPK_NOT_TAR", "The file is not a readable tar archive.");
75
75
  return undefined;
76
76
  }
77
77
  return outer;
@@ -86,19 +86,19 @@ function checkOuterEntries(entries, findings) {
86
86
  for (const entry of entries) {
87
87
  const path = normalize(entry.path);
88
88
  if (!isSafeOuter(entry.path, entry.type)) {
89
- findings.add("SPK_ENTRY_UNSAFE", `${entry.path} 跳出 SPK 根目錄或是連結;DSM 只解開一般檔案與目錄。`, entry.path);
89
+ findings.add("SPK_ENTRY_UNSAFE", `${entry.path} escapes the SPK root or is a link; DSM only extracts regular files and directories.`, entry.path);
90
90
  continue;
91
91
  }
92
92
  const top = path.split("/")[0];
93
93
  if (top && !TOP_LEVEL.has(top)) {
94
- findings.add("SPK_ENTRY_UNKNOWN", `${top} 不是 SPK 最上層的已知檔案(檔名區分大小寫)。`, top);
94
+ findings.add("SPK_ENTRY_UNKNOWN", `${top} is not a known SPK top-level file (file names are case-sensitive).`, top);
95
95
  }
96
96
  }
97
97
  }
98
98
  function checkInfo(outer, findings) {
99
99
  const text = outer.files.get("INFO");
100
100
  if (text === undefined) {
101
- findings.add("SPK_INFO_MISSING", "缺少 INFO。", "INFO");
101
+ findings.add("SPK_INFO_MISSING", "INFO is missing.", "INFO");
102
102
  return {};
103
103
  }
104
104
  const fields = parseInfo(text.toString("utf8"), findings);
@@ -4,7 +4,7 @@ import { join } from "node:path";
4
4
  import { appSdkVersion, sdkSource } from "../runtime/sdk-source.js";
5
5
  import { parseDocument } from "yaml";
6
6
  /** The App route policy inside each backend kind's directory. */
7
- export const API_PERMISSIONS_FILE = { go: "internal/api/permissions.json", rust: "permissions.json" };
7
+ export const API_PERMISSIONS_FILE = { go: "internal/api/permissions.json", rust: "permissions.json", python: "permissions.json", node: "permissions.json" };
8
8
  /** SDK-owned routes per capability, in the Go module of the SDK version the App requires. */
9
9
  const SDK_PERMISSIONS_FILE = "api/sdk-permissions.json";
10
10
  export const MAX_PERMISSIONS_BYTES = 65536;
@@ -16,7 +16,9 @@ export declare function backgroundUnitName(pkg: string): string;
16
16
  export declare function readBackground(value: unknown, fail: (error: StandaloneError) => void): Background | undefined;
17
17
  /** `conf/resource`: only the official user-unit resource. */
18
18
  export declare function renderBackgroundResource(): string;
19
- export declare function renderBackgroundUnit(pkg: string): string;
19
+ /** The worker's arguments: none for a Go App's own worker, `--worker` for the bridge runtime (spec/bridge/v1). */
20
+ export declare const BRIDGE_WORKER_ARGS: readonly ["--worker"];
21
+ export declare function renderBackgroundUnit(pkg: string, args?: readonly string[]): string;
20
22
  /** Shell function that stops the worker in lifecycle hooks; call it as `stop_background_worker`. */
21
23
  export declare function renderBackgroundWorkerStop(pkg: string): string;
22
24
  /**