@synopackageland/cli 0.17.3 → 0.17.5

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 (52) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +37 -1
  3. package/dist/build-spk/compose-diagnosis.d.ts +4 -0
  4. package/dist/build-spk/compose-diagnosis.js +12 -0
  5. package/dist/build-spk/compose-retained-data.d.ts +11 -0
  6. package/dist/build-spk/compose-retained-data.js +34 -0
  7. package/dist/build-spk/delete-app-data.d.ts +1 -1
  8. package/dist/build-spk/delete-app-data.js +1 -1
  9. package/dist/build-spk/uninstall-wizard.d.ts +2 -0
  10. package/dist/build-spk/uninstall-wizard.js +2 -1
  11. package/dist/build-spk/wizard-messages.d.ts +154 -0
  12. package/dist/build-spk/wizard-messages.js +154 -0
  13. package/dist/ctl/cli.js +10 -6
  14. package/dist/ctl/flows.js +1 -1
  15. package/dist/ctl/service.d.ts +4 -1
  16. package/dist/ctl/service.js +20 -6
  17. package/dist/ctl/transfers.d.ts +1 -1
  18. package/dist/ctl/transfers.js +5 -2
  19. package/dist/ctl/transport.d.ts +2 -1
  20. package/dist/ctl/transport.js +7 -3
  21. package/dist/runtime/compose-data.d.ts +10 -0
  22. package/dist/runtime/compose-data.js +20 -0
  23. package/dist/runtime/executor-digests.json +22 -0
  24. package/dist/runtime/executor-policy.d.ts +25 -0
  25. package/dist/runtime/executor-policy.js +10 -1
  26. package/dist/runtime/runtime-budgets.d.ts +24 -0
  27. package/dist/runtime/runtime-budgets.js +13 -0
  28. package/dist/standalone/background.d.ts +3 -3
  29. package/dist/standalone/background.js +12 -11
  30. package/dist/standalone/compose-errors.js +10 -0
  31. package/dist/standalone/compose.d.ts +5 -2
  32. package/dist/standalone/compose.js +19 -2
  33. package/dist/standalone/daemon.d.ts +4 -0
  34. package/dist/standalone/daemon.js +36 -23
  35. package/dist/standalone/dsm-window.js +2 -1
  36. package/dist/standalone/identity-gateway.js +9 -1
  37. package/dist/standalone/inspect-spk.js +87 -15
  38. package/dist/standalone/lifecycle.js +24 -10
  39. package/dist/standalone/manifest.d.ts +1 -0
  40. package/dist/standalone/manifest.js +8 -5
  41. package/dist/standalone/native-gateway.d.ts +17 -0
  42. package/dist/standalone/native-gateway.js +115 -0
  43. package/dist/standalone/resource.d.ts +3 -0
  44. package/dist/standalone/resource.js +3 -1
  45. package/dist/standalone/spk.js +14 -3
  46. package/dist/standalone/uninstall-hook.js +21 -8
  47. package/dist/standalone/uninstall.d.ts +1 -1
  48. package/dist/standalone/uninstall.js +2 -2
  49. package/package.json +2 -2
  50. package/skills/reference/compose.md +53 -0
  51. package/skills/reference/create.md +1 -1
  52. package/skills/reference/lifecycle.md +25 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.5 (2026-10-20)
4
+
5
+ - Run native HTTPS preparation inside the SDK lifecycle steps before App hooks, keeping generated hook wrappers inspectable.
6
+
7
+ - Preserve older SDK gateway/controller JSON and supported timeout limits so their direct-connection fallback remains buildable; reject expanded budgets that require SDK 0.17.5.
8
+
9
+ - Keep Compose named volumes without adding executor/root permissions; warn before uninstall and report their exact undeleted names in nine native locales when delete-data is selected. Clear package-owned data/config/home contents only on explicit uninstall, preserving data on upgrades, stops and keep.
10
+
11
+ - Default supported gateway Apps to a DSM-managed HTTPS origin using native package resources and a private authenticated gateway socket. Preserve direct connections for older SDK/DSM targets with an explanation before opening them.
12
+ - Prepare private connection credentials as the package account after DSM creates package state, and reject modified gateway proxy settings during SPK inspection.
13
+ - Apply `ctl --timeout` to discovery, JSON requests, polling and transfer inactivity without replaying uncertain writes; use a 62-second default transport allowance.
14
+ - Reuse `daemon.timeouts` for native, Compose and background lifecycle work, including declarations that need no daemon executable. Allow lifecycle and App purge budgets up to 3600 seconds while retaining the Compose startup cap.
15
+ - Generate matching gateway, supervisor and approved executor budgets, preserve them through SPK inspection, and report pending uninstall cleanup within bounded TERM/reap reserve.
16
+
17
+ - Pin templates 0.17.8 and SDK/App 0.17.5, including UI 0.12.4 full-page layout. Updated executor and lifecycle payloads require administrator reapproval after upgrading rebuilt Apps.
18
+
19
+ ## 0.17.4 (2026-10-19)
20
+
21
+ - Accept `compose.startup_timeout_seconds` (default 120, integer 2–900), use the SDK plan's value for daemon start, and deliver the same budget to container bootstrap adapters through the SDK-generated environment. Reject a planner that cannot honor an explicitly declared budget. Keep ingress closed until readiness succeeds.
22
+
23
+ - Show actionable image-pull diagnosis in nine locales and direct users to Package Center Repair when a package starts with stale container assets.
24
+ - Pin templates 0.17.6 and SDK/App 0.17.4; keep UI at 0.12.3.
25
+
3
26
  ## 0.17.3 (2026-10-19)
4
27
 
5
28
  - Pin templates 0.17.5, SDK/App 0.17.3 and UI 0.12.3 for generated Apps.
package/README.md CHANGED
@@ -259,6 +259,16 @@ Compose uses schema-2 `dsm-package.yaml` and `ui.launch`; the default is new-pag
259
259
  Classless gateways use type url via the shared contract/app-launch resolver;
260
260
  AppWindow wrappers use standalone. Headless projects have no launcher.
261
261
 
262
+ Rebuilt container Apps include informational **Registry check** diagnostics in
263
+ preinst, postinst, postupgrade and prestart. Messages use the existing nine-language
264
+ catalog and end with the native recovery step: fix the cause, then use Package
265
+ Center **Repair**. Repair re-runs the install worker and downloads missing images;
266
+ it is not an SDK Retry action. Checks use only immutable image references from the
267
+ packaged lock, never the Docker socket or registry credentials. They do not block
268
+ cached/offline startup or establish daemon disk space, image existence or pull
269
+ success. Native wrapper codes alone never identify a cause. SDK and CLI changes
270
+ must ship together and Apps must be rebuilt to receive these hooks and catalogs.
271
+
262
272
  See [Compose](skills/reference/compose.md) for multiple upstream files,
263
273
  generated override/worker artifacts, image proofs and the support matrix.
264
274
  Containers use a fixed package identity and do not inherit a signed-in user's
@@ -305,7 +315,11 @@ App is absent from discovery results; no business operation runs.
305
315
  `ctl host list/show` can also succeed while reporting `authenticated:false` and
306
316
  exit 0. Check discovery `apps`/`diagnostics` and session fields as well as the exit
307
317
  code when deciding whether an App is ready.
308
- After a network or job timeout, query the App's state before retrying a write.
318
+ `ctl --timeout <seconds>` applies to discovery, JSON calls, polling and transfer
319
+ inactivity. The transport default is 62 seconds; a supplied 1–3600-second budget
320
+ is shared across the operation. Jobs retain their described wait default when no
321
+ override is supplied. After a network or job timeout, query the App's state before
322
+ retrying a write.
309
323
 
310
324
  Read `synopkgland skill show ctl` for the AI workflow, batch discovery approval,
311
325
  cache refresh, host removal/revocation and explicit `ctl.commands` authoring.
@@ -318,3 +332,25 @@ share a request; results keep input order and retain per-method errors. A batch
318
332
  uses the same token recovery, request deadline and cancellation as `ctx.call`,
319
333
  and is never replayed automatically. Older SDKs and binary backends retain the
320
334
  single-call client. Rebuild the window with the current CLI/SDK to adopt it.
335
+
336
+ ### DSM-managed gateway HTTPS
337
+
338
+ With gateway SDK 0.17.5 or later and a package target of DSM 7.2-64570 or later,
339
+ the build uses DSM's native web-config worker for a separate HTTPS App origin.
340
+ No Web Station dependency or executor capability is added. nginx inherits DSM's
341
+ system default certificate, which must be trusted by the browser and cover the
342
+ App hostname. The package never reads DSM's private key.
343
+
344
+ The declared HTTPS port is served by nginx. If only an HTTP port was declared,
345
+ that port becomes the HTTPS entry; use the generated gateway launcher. A second
346
+ HTTP port redirects to HTTPS. nginx connects to a private, mutually authenticated
347
+ gateway socket and preserves the session, client IP, CSRF and WebSocket checks.
348
+ The package-account post-install/upgrade hooks prepare private connection
349
+ credentials with DSM's OpenSSL after DSM creates package state and before
350
+ package start activates nginx.
351
+
352
+ Older SDK/DSM targets retain the direct listener and explain its certificate or
353
+ unencrypted-HTTP limitations before opening it. Native resource/startup failures
354
+ fail closed; they do not silently expose a direct listener. The native runtime
355
+ requires the corresponding SDK release; this CLI change does not publish that
356
+ release or change existing manifest version pins.
@@ -0,0 +1,4 @@
1
+ /** Packaged data for compose/control; one source of native text in all locales. */
2
+ export declare function composeDiagnosisMessages(): Record<string, Record<string, string>>;
3
+ /** Diagnostics are informational and cannot mask or replace a native result. */
4
+ export declare function renderComposeDiagnosis(project: string, phase: "preinst" | "postinst" | "postupgrade" | "prestart"): string;
@@ -0,0 +1,12 @@
1
+ import { LOCALES } from "../standalone/i18n.js";
2
+ import { WIZARD_MESSAGES } from "./wizard-i18n.js";
3
+ /** Packaged data for compose/control; one source of native text in all locales. */
4
+ export function composeDiagnosisMessages() {
5
+ return Object.fromEntries(LOCALES.map((locale) => [locale, Object.fromEntries(Object.entries(WIZARD_MESSAGES)
6
+ .filter(([key]) => key.startsWith("compose.diagnosis."))
7
+ .map(([key, texts]) => [key.slice("compose.diagnosis.".length), texts[locale]]))]));
8
+ }
9
+ /** Diagnostics are informational and cannot mask or replace a native result. */
10
+ export function renderComposeDiagnosis(project, phase) {
11
+ return `"${project}/control" diagnose --project "${project}" --phase ${phase} || :\n`;
12
+ }
@@ -0,0 +1,11 @@
1
+ import { type ComposeAppData } from "../runtime/compose-data.js";
2
+ import { type Locale } from "../standalone/i18n.js";
3
+ export declare const COMPOSE_DATA_RETAINED = "COMPOSE_DATA_RETAINED";
4
+ /** Same segment spacing as the SDK image-pull diagnosis; support code goes last. */
5
+ export declare function composeRetainedMessage(data: ComposeAppData, locale: Locale): string;
6
+ /**
7
+ * Embedded in the native hook: postuninst need not find an already removed
8
+ * payload/helper. Names describe the plan, not a privileged Docker readback.
9
+ * Reporting is informational and must never prevent package removal.
10
+ */
11
+ export declare function renderComposeRetainedData(data?: ComposeAppData): string;
@@ -0,0 +1,34 @@
1
+ import { COMPOSE_DELETE_CHOICE } from "../runtime/compose-data.js";
2
+ import { DSM_LANGS, LOCALES } from "../standalone/i18n.js";
3
+ import { translator } from "./wizard-i18n.js";
4
+ export const COMPOSE_DATA_RETAINED = "COMPOSE_DATA_RETAINED";
5
+ /** Same segment spacing as the SDK image-pull diagnosis; support code goes last. */
6
+ export function composeRetainedMessage(data, locale) {
7
+ const t = translator(locale);
8
+ const separator = ["zh-TW", "zh-CN", "ja"].includes(locale) ? "" : " ";
9
+ return t("compose.retained.message", { volumes: data.volumes.map(v => v.name).join(", ") }) +
10
+ separator + t("compose.diagnosis.error_code", { code: COMPOSE_DATA_RETAINED });
11
+ }
12
+ const quote = (text) => `'${text.replaceAll("'", "'\\''")}'`;
13
+ /**
14
+ * Embedded in the native hook: postuninst need not find an already removed
15
+ * payload/helper. Names describe the plan, not a privileged Docker readback.
16
+ * Reporting is informational and must never prevent package removal.
17
+ */
18
+ export function renderComposeRetainedData(data) {
19
+ if (!data?.volumes.length)
20
+ return "";
21
+ const cases = LOCALES.map(locale => ` ${locale}|${DSM_LANGS[locale]}) compose_data_message=${quote(composeRetainedMessage(data, locale))} ;;`).join("\n");
22
+ return `${COMPOSE_DELETE_CHOICE}if [ "$compose_remove_data" -eq 1 ]; then
23
+ case "\${SYNOPKG_DSM_LANGUAGE:-enu}" in
24
+ ${cases}
25
+ *) compose_data_message=${quote(composeRetainedMessage(data, "en"))} ;;
26
+ esac
27
+ if [ -n "\${SYNOPKG_TEMP_LOGFILE:-}" ] && [ ! -L "$SYNOPKG_TEMP_LOGFILE" ]; then
28
+ if [ ! -e "$SYNOPKG_TEMP_LOGFILE" ] || [ -f "$SYNOPKG_TEMP_LOGFILE" ]; then
29
+ (printf '%s\\n' "$compose_data_message" >> "$SYNOPKG_TEMP_LOGFILE") 2>/dev/null || :
30
+ fi
31
+ fi
32
+ fi
33
+ `;
34
+ }
@@ -8,7 +8,7 @@
8
8
  * pre-0.5 key `delete_app_data` and DSM's `wizard_` mirror of either key are
9
9
  * still honoured for scripted callers.
10
10
  *
11
- * Deletion never runs on an upgrade: DSM replays postuninst for the old
11
+ * Deletion runs only on exact UNINSTALL, never on an upgrade or stop: DSM replays postuninst for the old
12
12
  * version, and the new version must find its data.
13
13
  *
14
14
  * Every root is resolved first and must land on `/volumeN/@<area>/<package>`
@@ -4,7 +4,7 @@ function shellQuote(value) {
4
4
  export function renderDeleteAppDataBlock(packageName, roots, options = {}) {
5
5
  const calls = roots.map((root) => ` delete_root "$${root.variable}" ${root.area}`).join("\n");
6
6
  return `remove_additional=0
7
- if [ "\${SYNOPKG_PKG_STATUS:-}" != UPGRADE ]; then
7
+ if [ "\${SYNOPKG_PKG_STATUS:-}" = UNINSTALL ]; then
8
8
  for flag in "\${pkgwizard_remove_additional:-}" "\${wizard_pkgwizard_remove_additional:-}" "\${delete_app_data:-}" "\${wizard_delete_app_data:-}"; do
9
9
  case "$flag" in
10
10
  true|True|TRUE|yes|1) remove_additional=1 ;;
@@ -1,3 +1,4 @@
1
+ import type { ComposeAppData } from "../runtime/compose-data.js";
1
2
  import type { Locale } from "../standalone/i18n.js";
2
3
  /** The key DSM's built-in packages use for their "also remove my data" box. */
3
4
  export declare const REMOVE_ADDITIONAL_KEY = "pkgwizard_remove_additional";
@@ -5,6 +6,7 @@ export declare const REMOVE_ADDITIONAL_KEY = "pkgwizard_remove_additional";
5
6
  export declare const DELETE_STATE_KEY = "pkgwizard_delete_state";
6
7
  export type UninstallWizardKind = "native" | "container";
7
8
  interface UninstallWizardOptions {
9
+ composeData?: ComposeAppData;
8
10
  /** The App runs the root executor (rootOps or delegate), which purges DSM's leftovers on uninstall. */
9
11
  executor?: boolean;
10
12
  /** The App declares a Login Portal alias (web-config app-portal); DSM reloads nginx through INFO. */
@@ -8,7 +8,7 @@ function list(items) {
8
8
  return `<ul class="synopkg-ul-list">${items.map((item) => `<li>${item}</li>`).join("")}</ul>`;
9
9
  }
10
10
  const html = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
11
- function renderUninstallWizard(t, locale, packageName, kind, { executor = false, portal = false, portalEntry = false, state, rootPurge = false }) {
11
+ function renderUninstallWizard(t, locale, packageName, kind, { executor = false, portal = false, portalEntry = false, state, rootPurge = false, composeData }) {
12
12
  const vars = { package: packageName };
13
13
  return [{
14
14
  step_title: t("uninstall.step"),
@@ -33,6 +33,7 @@ function renderUninstallWizard(t, locale, packageName, kind, { executor = false,
33
33
  ] : []),
34
34
  ...(executor && rootPurge ? [{ desc: t("uninstall.purge.root", vars) }] : []),
35
35
  ...(state ? [{ desc: t("uninstall.state.heading") }, { desc: list(state.items(locale).map(html)) }] : []),
36
+ ...(composeData?.volumes.length ? [{ desc: t("uninstall.composeRetained", { volumes: composeData.volumes.map(v => v.name).join(", ") }) }] : []),
36
37
  { desc: t("uninstall.backup") },
37
38
  {
38
39
  type: "multiselect",
@@ -1,4 +1,158 @@
1
1
  export declare const WIZARD_MESSAGES: {
2
+ "uninstall.composeRetained": {
3
+ en: string;
4
+ "zh-TW": string;
5
+ "zh-CN": string;
6
+ ja: string;
7
+ ko: string;
8
+ de: string;
9
+ fr: string;
10
+ es: string;
11
+ it: string;
12
+ };
13
+ "compose.retained.message": {
14
+ en: string;
15
+ "zh-TW": string;
16
+ "zh-CN": string;
17
+ ja: string;
18
+ ko: string;
19
+ de: string;
20
+ fr: string;
21
+ es: string;
22
+ it: string;
23
+ };
24
+ "compose.diagnosis.registry_check": {
25
+ en: string;
26
+ "zh-TW": string;
27
+ "zh-CN": string;
28
+ ja: string;
29
+ ko: string;
30
+ de: string;
31
+ fr: string;
32
+ es: string;
33
+ it: string;
34
+ };
35
+ "compose.diagnosis.container_manager": {
36
+ en: string;
37
+ "zh-TW": string;
38
+ "zh-CN": string;
39
+ ja: string;
40
+ ko: string;
41
+ de: string;
42
+ fr: string;
43
+ es: string;
44
+ it: string;
45
+ };
46
+ "compose.diagnosis.tls_trust": {
47
+ en: string;
48
+ "zh-TW": string;
49
+ "zh-CN": string;
50
+ ja: string;
51
+ ko: string;
52
+ de: string;
53
+ fr: string;
54
+ es: string;
55
+ it: string;
56
+ };
57
+ "compose.diagnosis.unreachable": {
58
+ en: string;
59
+ "zh-TW": string;
60
+ "zh-CN": string;
61
+ ja: string;
62
+ ko: string;
63
+ de: string;
64
+ fr: string;
65
+ es: string;
66
+ it: string;
67
+ };
68
+ "compose.diagnosis.dns": {
69
+ en: string;
70
+ "zh-TW": string;
71
+ "zh-CN": string;
72
+ ja: string;
73
+ ko: string;
74
+ de: string;
75
+ fr: string;
76
+ es: string;
77
+ it: string;
78
+ };
79
+ "compose.diagnosis.rate_limit": {
80
+ en: string;
81
+ "zh-TW": string;
82
+ "zh-CN": string;
83
+ ja: string;
84
+ ko: string;
85
+ de: string;
86
+ fr: string;
87
+ es: string;
88
+ it: string;
89
+ };
90
+ "compose.diagnosis.disk_full": {
91
+ en: string;
92
+ "zh-TW": string;
93
+ "zh-CN": string;
94
+ ja: string;
95
+ ko: string;
96
+ de: string;
97
+ fr: string;
98
+ es: string;
99
+ it: string;
100
+ };
101
+ "compose.diagnosis.not_found": {
102
+ en: string;
103
+ "zh-TW": string;
104
+ "zh-CN": string;
105
+ ja: string;
106
+ ko: string;
107
+ de: string;
108
+ fr: string;
109
+ es: string;
110
+ it: string;
111
+ };
112
+ "compose.diagnosis.denied": {
113
+ en: string;
114
+ "zh-TW": string;
115
+ "zh-CN": string;
116
+ ja: string;
117
+ ko: string;
118
+ de: string;
119
+ fr: string;
120
+ es: string;
121
+ it: string;
122
+ };
123
+ "compose.diagnosis.unknown": {
124
+ en: string;
125
+ "zh-TW": string;
126
+ "zh-CN": string;
127
+ ja: string;
128
+ ko: string;
129
+ de: string;
130
+ fr: string;
131
+ es: string;
132
+ it: string;
133
+ };
134
+ "compose.diagnosis.repair": {
135
+ en: string;
136
+ "zh-TW": string;
137
+ "zh-CN": string;
138
+ ja: string;
139
+ ko: string;
140
+ de: string;
141
+ fr: string;
142
+ es: string;
143
+ it: string;
144
+ };
145
+ "compose.diagnosis.error_code": {
146
+ en: string;
147
+ "zh-TW": string;
148
+ "zh-CN": string;
149
+ ja: string;
150
+ ko: string;
151
+ de: string;
152
+ fr: string;
153
+ es: string;
154
+ it: string;
155
+ };
2
156
  "delegate.preselection": {
3
157
  en: string;
4
158
  "zh-TW": string;
@@ -1,4 +1,158 @@
1
1
  export const WIZARD_MESSAGES = {
2
+ "uninstall.composeRetained": {
3
+ "en": "Container data volumes are kept, even when you select delete-data: {volumes}. Their data will NOT be deleted.",
4
+ "zh-TW": "即使勾選刪除資料,容器資料磁碟區會保留:{volumes}。其中的資料不會刪除。",
5
+ "zh-CN": "即使勾选删除数据,容器数据卷也会保留:{volumes}。其中的数据不会删除。",
6
+ "ja": "データ削除を選択しても、コンテナのデータボリュームは保持されます:{volumes}。その中のデータは削除されません。",
7
+ "ko": "데이터 삭제를 선택해도 컨테이너 데이터 볼륨은 보존됩니다: {volumes}. 볼륨의 데이터는 삭제되지 않습니다.",
8
+ "de": "Container-Datenvolumes bleiben erhalten, auch wenn Sie die Datenlöschung auswählen: {volumes}. Die darin gespeicherten Daten werden NICHT gelöscht.",
9
+ "fr": "Les volumes de données des conteneurs sont conservés, même si vous choisissez de supprimer les données : {volumes}. Leurs données ne seront PAS supprimées.",
10
+ "es": "Los volúmenes de datos de los contenedores se conservan aunque seleccione eliminar datos: {volumes}. Sus datos NO se eliminarán.",
11
+ "it": "I volumi dati dei container vengono conservati anche se si seleziona l’eliminazione dei dati: {volumes}. I dati al loro interno NON verranno eliminati."
12
+ },
13
+ "compose.retained.message": {
14
+ "en": "Container data volumes were NOT deleted: {volumes}. Their data is kept.",
15
+ "zh-TW": "容器資料磁碟區並未刪除:{volumes}。其中的資料已保留。",
16
+ "zh-CN": "容器数据卷并未删除:{volumes}。其中的数据已保留。",
17
+ "ja": "コンテナのデータボリュームは削除されていません:{volumes}。その中のデータは保持されます。",
18
+ "ko": "컨테이너 데이터 볼륨이 삭제되지 않았습니다: {volumes}. 볼륨의 데이터는 보존됩니다.",
19
+ "de": "Container-Datenvolumes wurden NICHT gelöscht: {volumes}. Die darin gespeicherten Daten bleiben erhalten.",
20
+ "fr": "Les volumes de données des conteneurs n’ont PAS été supprimés : {volumes}. Leurs données sont conservées.",
21
+ "es": "Los volúmenes de datos de los contenedores NO se eliminaron: {volumes}. Sus datos se conservan.",
22
+ "it": "I volumi dati dei container NON sono stati eliminati: {volumes}. I dati al loro interno vengono conservati."
23
+ },
24
+ "compose.diagnosis.registry_check": {
25
+ "en": "Registry check",
26
+ "zh-TW": "登錄站檢查",
27
+ "zh-CN": "镜像仓库检查",
28
+ "ja": "レジストリ確認",
29
+ "ko": "레지스트리 확인",
30
+ "de": "Registry-Prüfung",
31
+ "fr": "Vérification du registre",
32
+ "es": "Comprobación del registro",
33
+ "it": "Verifica del registro"
34
+ },
35
+ "compose.diagnosis.container_manager": {
36
+ "en": "Container Manager",
37
+ "zh-TW": "Container Manager 錯誤",
38
+ "zh-CN": "Container Manager 错误",
39
+ "ja": "Container Manager のエラー",
40
+ "ko": "Container Manager 오류",
41
+ "de": "Container Manager-Fehler",
42
+ "fr": "Erreur de Container Manager",
43
+ "es": "Error de Container Manager",
44
+ "it": "Errore di Container Manager"
45
+ },
46
+ "compose.diagnosis.tls_trust": {
47
+ "en": "The registry certificate could not be verified. Check the NAS date and time. Ask your network administrator to configure trusted certificates for Container Manager. Alternatively, ask them to let this registry bypass network certificate checks.",
48
+ "zh-TW": "無法驗證登錄站憑證。請檢查 NAS 日期與時間。若公司網路會攔查加密連線,請網管協助。可為 Container Manager 設定信任憑證,或讓登錄站略過攔查。",
49
+ "zh-CN": "无法验证镜像仓库证书。请检查 NAS 日期和时间。如果公司网络会检查加密连接,请联系网络管理员。可为 Container Manager 配置信任证书,或让镜像仓库绕过此类检查。",
50
+ "ja": "レジストリの証明書を検証できません。NAS の日付と時刻を確認してください。Container Manager で信頼する証明書の設定をネットワーク管理者に依頼してください。または、このレジストリをネットワークの証明書検査から除外してもらってください。",
51
+ "ko": "레지스트리 인증서를 확인할 수 없습니다. NAS 날짜와 시간을 확인하십시오. 네트워크 관리자에게 Container Manager의 신뢰할 수 있는 인증서 설정을 요청하십시오. 또는 이 레지스트리를 네트워크 인증서 검사에서 제외하도록 요청하십시오.",
52
+ "de": "Das Zertifikat der Registry konnte nicht geprüft werden. Prüfen Sie Datum und Uhrzeit des NAS. Bitten Sie Ihre Netzwerkadministration, vertrauenswürdige Zertifikate für Container Manager einzurichten. Alternativ kann sie diese Registry von der Zertifikatsprüfung im Netzwerk ausnehmen.",
53
+ "fr": "Le certificat du registre ne peut pas être vérifié. Vérifiez la date et l’heure du NAS. Demandez à votre administrateur réseau de configurer les certificats de confiance pour Container Manager. Il peut aussi exclure ce registre du contrôle des certificats sur le réseau.",
54
+ "es": "No se pudo verificar el certificado del registro. Compruebe la fecha y la hora del NAS. Solicite a su administrador de red que configure certificados de confianza para Container Manager. Como alternativa, puede excluir este registro de la comprobación de certificados de la red.",
55
+ "it": "Impossibile verificare il certificato del registro. Controllare la data e l’ora del NAS. Chiedere all’amministratore di rete di configurare i certificati attendibili per Container Manager. In alternativa, chiedere di escludere questo registro dai controlli dei certificati di rete."
56
+ },
57
+ "compose.diagnosis.unreachable": {
58
+ "en": "Cannot reach the registry. Check the NAS network, firewall and Container Manager proxy settings.",
59
+ "zh-TW": "無法連線至登錄站。檢查 NAS 網路、防火牆及 Container Manager 的代理伺服器設定。",
60
+ "zh-CN": "无法连接镜像仓库。检查 NAS 网络、防火墙及 Container Manager 的代理服务器设置。",
61
+ "ja": "レジストリに接続できません。NAS のネットワーク、ファイアウォール、Container Manager のプロキシ設定を確認してください。",
62
+ "ko": "레지스트리에 연결할 수 없습니다. NAS 네트워크, 방화벽 및 Container Manager 프록시 설정을 확인하십시오.",
63
+ "de": "Die Registry ist nicht erreichbar. Prüfen Sie Netzwerk, Firewall und die Proxy-Einstellungen von Container Manager.",
64
+ "fr": "Le registre est inaccessible. Vérifiez le réseau du NAS, le pare-feu et les paramètres de proxy de Container Manager.",
65
+ "es": "No se puede acceder al registro. Compruebe la red del NAS, el cortafuegos y la configuración de proxy de Container Manager.",
66
+ "it": "Impossibile raggiungere il registro. Controllare la rete del NAS, il firewall e le impostazioni proxy di Container Manager."
67
+ },
68
+ "compose.diagnosis.dns": {
69
+ "en": "The registry name could not be resolved. Check the NAS DNS settings.",
70
+ "zh-TW": "無法解析登錄站名稱。檢查 NAS 的 DNS 設定。",
71
+ "zh-CN": "无法解析镜像仓库名称。检查 NAS 的 DNS 设置。",
72
+ "ja": "レジストリ名を解決できません。NAS の DNS 設定を確認してください。",
73
+ "ko": "레지스트리 이름을 확인할 수 없습니다. NAS DNS 설정을 확인하십시오.",
74
+ "de": "Der Name der Registry konnte nicht aufgelöst werden. Prüfen Sie die DNS-Einstellungen des NAS.",
75
+ "fr": "Le nom du registre ne peut pas être résolu. Vérifiez les paramètres DNS du NAS.",
76
+ "es": "No se pudo resolver el nombre del registro. Compruebe la configuración DNS del NAS.",
77
+ "it": "Impossibile risolvere il nome del registro. Controllare le impostazioni DNS del NAS."
78
+ },
79
+ "compose.diagnosis.rate_limit": {
80
+ "en": "The registry download limit was reached. Wait before trying again. You can also ask an administrator to check registry sign-in.",
81
+ "zh-TW": "已達登錄站下載上限。請稍後再試,也可請管理員檢查登錄站登入設定。",
82
+ "zh-CN": "已达到镜像仓库下载上限。请稍后再试,也可请管理员检查镜像仓库登录设置。",
83
+ "ja": "レジストリのダウンロード制限に達しました。しばらく待つか、管理者にレジストリのサインイン設定の確認を依頼してください。",
84
+ "ko": "레지스트리 다운로드 한도에 도달했습니다. 기다리거나 관리자에게 레지스트리 로그인 설정 확인을 요청하십시오.",
85
+ "de": "Das Downloadlimit der Registry wurde erreicht. Warten Sie oder bitten Sie einen Administrator, die Anmeldung bei der Registry zu prüfen.",
86
+ "fr": "La limite de téléchargement du registre est atteinte. Patientez ou demandez à un administrateur de vérifier la connexion au registre.",
87
+ "es": "Se alcanzó el límite de descargas del registro. Espere o solicite a un administrador que compruebe el inicio de sesión en el registro.",
88
+ "it": "È stato raggiunto il limite di download del registro. Attendere o chiedere a un amministratore di controllare l’accesso al registro."
89
+ },
90
+ "compose.diagnosis.disk_full": {
91
+ "en": "There is not enough storage for the image. Free space on Container Manager's storage volume.",
92
+ "zh-TW": "儲存空間不足,無法下載映像。請釋出 Container Manager 儲存磁碟區的空間。",
93
+ "zh-CN": "存储空间不足,无法下载映像。请释放 Container Manager 存储卷的空间。",
94
+ "ja": "イメージ用の空き容量が不足しています。Container Manager のストレージボリュームの空き容量を増やしてください。",
95
+ "ko": "이미지를 위한 저장 공간이 부족합니다. Container Manager 저장소 볼륨의 공간을 확보하십시오.",
96
+ "de": "Für das Image ist nicht genug Speicherplatz verfügbar. Geben Sie Speicherplatz auf dem Speichervolume von Container Manager frei.",
97
+ "fr": "L’espace de stockage est insuffisant pour l’image. Libérez de l’espace sur le volume de stockage de Container Manager.",
98
+ "es": "No hay espacio de almacenamiento suficiente para la imagen. Libere espacio en el volumen de almacenamiento de Container Manager.",
99
+ "it": "Lo spazio di archiviazione per l’immagine non è sufficiente. Liberare spazio sul volume di archiviazione di Container Manager."
100
+ },
101
+ "compose.diagnosis.not_found": {
102
+ "en": "The required image version is unavailable. Check for an App update or contact its publisher.",
103
+ "zh-TW": "所需映像版本無法取得。檢查 App 更新或聯絡發佈者。",
104
+ "zh-CN": "无法获取所需映像版本。检查应用更新或联系发布者。",
105
+ "ja": "必要なイメージバージョンを取得できません。アプリの更新を確認するか、公開元に問い合わせてください。",
106
+ "ko": "필요한 이미지 버전을 사용할 수 없습니다. 앱 업데이트를 확인하거나 게시자에게 문의하십시오.",
107
+ "de": "Die erforderliche Image-Version ist nicht verfügbar. Prüfen Sie auf App-Updates oder wenden Sie sich an den Herausgeber.",
108
+ "fr": "La version d’image requise est indisponible. Recherchez une mise à jour de l’application ou contactez son éditeur.",
109
+ "es": "La versión de imagen requerida no está disponible. Busque una actualización de la aplicación o contacte con su editor.",
110
+ "it": "La versione dell’immagine richiesta non è disponibile. Verificare gli aggiornamenti dell’app o contattare l’editore."
111
+ },
112
+ "compose.diagnosis.denied": {
113
+ "en": "The registry denied access. Ask an administrator to check registry sign-in and image permissions.",
114
+ "zh-TW": "登錄站拒絕存取。請管理員檢查登錄站登入設定及映像權限。",
115
+ "zh-CN": "镜像仓库拒绝访问。请管理员检查镜像仓库登录设置及映像权限。",
116
+ "ja": "レジストリにアクセスを拒否されました。管理者にレジストリのサインイン設定とイメージの権限の確認を依頼してください。",
117
+ "ko": "레지스트리가 접근을 거부했습니다. 관리자에게 레지스트리 로그인 설정과 이미지 권한 확인을 요청하십시오.",
118
+ "de": "Die Registry hat den Zugriff verweigert. Bitten Sie einen Administrator, die Anmeldung und die Image-Berechtigungen zu prüfen.",
119
+ "fr": "Le registre a refusé l’accès. Demandez à un administrateur de vérifier la connexion au registre et les autorisations de l’image.",
120
+ "es": "El registro denegó el acceso. Solicite a un administrador que compruebe el inicio de sesión y los permisos de la imagen.",
121
+ "it": "Il registro ha negato l’accesso. Chiedere a un amministratore di controllare l’accesso al registro e le autorizzazioni dell’immagine."
122
+ },
123
+ "compose.diagnosis.unknown": {
124
+ "en": "The check could not determine the cause. Open Container Manager for details or contact the App publisher.",
125
+ "zh-TW": "檢查無法判斷原因。開啟 Container Manager 查看詳細資訊,或聯絡 App 發佈者。",
126
+ "zh-CN": "检查无法确定原因。打开 Container Manager 查看详细信息,或联系应用发布者。",
127
+ "ja": "確認では原因を特定できませんでした。Container Manager で詳細を確認するか、アプリの公開元に問い合わせてください。",
128
+ "ko": "확인으로 원인을 파악할 수 없습니다. Container Manager에서 세부 정보를 확인하거나 앱 게시자에게 문의하십시오.",
129
+ "de": "Die Prüfung konnte die Ursache nicht ermitteln. Öffnen Sie Container Manager für Details oder wenden Sie sich an den App-Herausgeber.",
130
+ "fr": "La vérification ne permet pas de déterminer la cause. Ouvrez Container Manager pour plus de détails ou contactez l’éditeur de l’application.",
131
+ "es": "La comprobación no pudo determinar la causa. Abra Container Manager para ver los detalles o contacte con el editor de la aplicación.",
132
+ "it": "La verifica non ha potuto determinare la causa. Aprire Container Manager per i dettagli o contattare l’editore dell’app."
133
+ },
134
+ "compose.diagnosis.repair": {
135
+ "en": "After fixing the cause, use Package Center \"Repair\". The system will reinstall the package and download missing images.",
136
+ "zh-TW": "修正後,請在套件中心按「修復」,系統會重新安裝並下載缺少的映像。",
137
+ "zh-CN": "修正后,请在套件中心点击“修复”,系统会重新安装并下载缺少的映像。",
138
+ "ja": "原因を解消してから、パッケージセンターで「修復」を実行してください。システムがパッケージを再インストールし、不足しているイメージをダウンロードします。",
139
+ "ko": "원인을 해결한 후 패키지 센터에서 \"복구\"를 선택하십시오. 시스템이 패키지를 다시 설치하고 누락된 이미지를 다운로드합니다.",
140
+ "de": "Beheben Sie die Ursache und wählen Sie im Paket-Zentrum „Reparieren“. Das System installiert das Paket erneut und lädt fehlende Images herunter.",
141
+ "fr": "Corrigez la cause, puis sélectionnez « Réparer » dans le Centre de paquets. Le système réinstallera le paquet et téléchargera les images manquantes.",
142
+ "es": "Corrija la causa y seleccione \"Reparar\" en el Centro de paquetes. El sistema reinstalará el paquete y descargará las imágenes que faltan.",
143
+ "it": "Correggere la causa e selezionare «Ripara» nel Centro pacchetti. Il sistema reinstallerà il pacchetto e scaricherà le immagini mancanti."
144
+ },
145
+ "compose.diagnosis.error_code": {
146
+ "en": "(Error code: {code})",
147
+ "zh-TW": "(錯誤碼:{code})",
148
+ "zh-CN": "(错误代码:{code})",
149
+ "ja": "(エラーコード:{code})",
150
+ "ko": "(오류 코드: {code})",
151
+ "de": "(Fehlercode: {code})",
152
+ "fr": "(Code d’erreur : {code})",
153
+ "es": "(Código de error: {code})",
154
+ "it": "(Codice errore: {code})"
155
+ },
2
156
  "delegate.preselection": {
3
157
  en: "Pre-select the shared folder for background saves. Confirm this selection with your administrator password when first enabling background access in the app. This does not grant the package account access to the share.",
4
158
  "zh-TW": "預選背景存檔使用的共用資料夾。首次在 App 啟用背景存取時,以管理員密碼確認這個選項。這不會授予套件帳號共用資料夾權限。",
package/dist/ctl/cli.js CHANGED
@@ -6,7 +6,7 @@ import { CtlError, errorData, ctlError } from './errors.js';
6
6
  import { CtlStore } from './store.js';
7
7
  import { CtlClient, boundedMap } from './service.js';
8
8
  import { selectHosts, selectApp, selectOperation, flagName, RESERVED_FLAGS } from './input.js';
9
- import { canonicalOrigin, createRequest } from './transport.js';
9
+ import { canonicalOrigin, CTL_TIMEOUT_MS, createRequest } from './transport.js';
10
10
  import { loginDsm, logoutDsm } from './dsm-auth.js';
11
11
  import { publicOperation } from './redaction.js';
12
12
  const USAGE = 'synopkgland ctl host add|list|show|login|logout|remove|revoke; ctl list [--host a,b|--all-hosts] [--app pkg] [--refresh]; ctl <alias|package> <command...> [typed flags|--input file|-] [--dry-run] [--yes]';
@@ -70,7 +70,11 @@ export async function runCtl(argv, io = {}) {
70
70
  emit({ ok: true, data: { usage: USAGE } });
71
71
  return;
72
72
  }
73
- const client = (host) => new CtlClient(store, host, io.requestFor?.(host));
73
+ const timeout = take(args, '--timeout', true);
74
+ if (timeout && (!/^\d+$/.test(timeout) || Number(timeout) < 1 || Number(timeout) > 3600))
75
+ throw new CtlError('CTL_ARGUMENT_INVALID', '--timeout must be 1–3600 seconds.', 'Use --timeout 60.');
76
+ const timeoutMs = timeout ? Number(timeout) * 1000 : CTL_TIMEOUT_MS;
77
+ const client = (host) => new CtlClient(store, host, io.requestFor?.(host), timeoutMs);
74
78
  const discoverOptions = { refresh, app: appFilter, approve, confirm: tty ? async (packages) => /^y(es)?$/i.test(await ask('Allow DSM session to fixed description endpoints for ' + packages.map(p => p.id + '@' + p.version).join(', ') + '? [y/N] ')) : undefined };
75
79
  if (args[0] === 'host') {
76
80
  const action = args[1], name = args[2];
@@ -100,7 +104,7 @@ export async function runCtl(argv, io = {}) {
100
104
  const host = old ?? { name, origin: canonicalOrigin(url, tunnel), username: usernameOption ?? await ask('DSM account: '), approved: {}, ...(tunnel ? { tunnel: true } : {}), ...(ca ? { ca } : {}) };
101
105
  const previous = old ? await store.credentials.get(store.key(host)) : null;
102
106
  const secret = passwordStdin ? readBounded(0, 4096).replace(/[\r\n]+$/, '') : await ask('User password: ', true);
103
- const session = await loginDsm(host.origin, host.username, secret, { remember: !noRemember, deviceId: previous?.deviceId, otp: () => ask('DSM OTP: ', true), request: io.requestFor?.(host), ca: host.ca ? readFileSync(host.ca, 'utf8') : undefined });
107
+ const session = await loginDsm(host.origin, host.username, secret, { remember: !noRemember, deviceId: previous?.deviceId, otp: () => ask('DSM OTP: ', true), request: io.requestFor?.(host) ?? createRequest(host.origin, timeoutMs, host.ca ? readFileSync(host.ca, 'utf8') : undefined), ca: host.ca ? readFileSync(host.ca, 'utf8') : undefined });
104
108
  try {
105
109
  if (old)
106
110
  await store.credentials.set(store.key(host), session);
@@ -108,7 +112,7 @@ export async function runCtl(argv, io = {}) {
108
112
  await store.add(host, session);
109
113
  }
110
114
  catch (e) {
111
- await logoutDsm(session, io.requestFor?.(host) ?? createRequest(host.origin)).catch(() => { });
115
+ await logoutDsm(session, io.requestFor?.(host) ?? createRequest(host.origin, timeoutMs)).catch(() => { });
112
116
  throw e;
113
117
  }
114
118
  host.lastUsed = new Date().toISOString();
@@ -135,7 +139,7 @@ export async function runCtl(argv, io = {}) {
135
139
  let logoutError;
136
140
  if (session?.cookie) {
137
141
  try {
138
- await logoutDsm(session, io.requestFor?.(host) ?? createRequest(host.origin, 10000, host.ca ? readFileSync(host.ca, 'utf8') : undefined));
142
+ await logoutDsm(session, io.requestFor?.(host) ?? createRequest(host.origin, timeoutMs, host.ca ? readFileSync(host.ca, 'utf8') : undefined));
139
143
  loggedOut = true;
140
144
  }
141
145
  catch (error) {
@@ -184,7 +188,7 @@ export async function runCtl(argv, io = {}) {
184
188
  return { host: name, ok: true, data: path.length ? { ...view, commands: [view.commands.find(c => c.id === selectOperation(app, path).id)] } : view };
185
189
  }
186
190
  const tail = args.slice(1);
187
- const noWait = !!take(tail, '--no-wait'), wait = !!take(tail, '--wait'), timeout = take(tail, '--timeout', true);
191
+ const noWait = !!take(tail, '--no-wait'), wait = !!take(tail, '--wait');
188
192
  if (noWait && wait)
189
193
  throw new CtlError('CTL_ARGUMENT_INVALID', 'Use --wait or --no-wait.', 'run: synopkgland ctl list --json');
190
194
  if (timeout && (!/^\d+$/.test(timeout) || Number(timeout) < 1 || Number(timeout) > 3600))
package/dist/ctl/flows.js CHANGED
@@ -32,13 +32,13 @@ export async function runFlow(flow, params, invoke, options) {
32
32
  }
33
33
  throw new CtlError('CTL_FLOW_LIMIT', 'Pagination page limit reached; result is incomplete.', 'Narrow the query.', 5);
34
34
  }
35
+ const until = performance.now() + Math.min(options.timeoutMs ?? flow.timeoutMs, 3600000);
35
36
  const accepted = await invoke(flow.start, params);
36
37
  if (options.wait === false)
37
38
  return { accepted: true, job: accepted };
38
39
  const id = field(accepted, flow.jobId);
39
40
  if (!['string', 'number'].includes(typeof id))
40
41
  throw new CtlError('CTL_FLOW_RESPONSE', 'Start response has no job ID.', 'Check command metadata; start is not retried.', 5);
41
- const until = performance.now() + Math.min(options.timeoutMs ?? flow.timeoutMs, 3600000);
42
42
  while (performance.now() < until) {
43
43
  const poll = await invoke(flow.poll, { [flow.idParam]: id });
44
44
  const state = field(poll, flow.state);
@@ -15,7 +15,9 @@ export declare class CtlClient {
15
15
  store: CtlStore;
16
16
  host: Host;
17
17
  request: JsonRequest;
18
- constructor(store: CtlStore, host: Host, request?: JsonRequest);
18
+ constructor(store: CtlStore, host: Host, request?: JsonRequest, timeoutMs?: number);
19
+ private until?;
20
+ private remaining;
19
21
  valid(): Promise<boolean>;
20
22
  inventory(session: DsmSession): Promise<{
21
23
  id: string;
@@ -34,5 +36,6 @@ export declare class CtlClient {
34
36
  wait?: boolean;
35
37
  timeoutMs?: number;
36
38
  }): Promise<any>;
39
+ private executeWithin;
37
40
  private appCookieSession;
38
41
  }