@synopackageland/cli 0.41.4 → 0.41.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 (39) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/README.md +2 -2
  3. package/dist/build-spk/compose-diagnosis.js +1 -1
  4. package/dist/build-spk/share-validation.d.ts +3 -0
  5. package/dist/build-spk/share-validation.js +5 -0
  6. package/dist/build-spk/wizard-messages.d.ts +99 -0
  7. package/dist/build-spk/wizard-messages.js +108 -9
  8. package/dist/cli.js +1 -1
  9. package/dist/deploy/native-install-values.js +16 -4
  10. package/dist/error-registry.js +2 -0
  11. package/dist/init.d.ts +1 -1
  12. package/dist/init.js +1 -0
  13. package/dist/standalone/compose.d.ts +4 -0
  14. package/dist/standalone/compose.js +2 -1
  15. package/dist/standalone/daemon.js +2 -1
  16. package/dist/standalone/dsm-window.js +1 -2
  17. package/dist/standalone/inspect-spk.js +41 -17
  18. package/dist/standalone/install-ports.d.ts +31 -0
  19. package/dist/standalone/install-ports.js +103 -0
  20. package/dist/standalone/install-settings.d.ts +3 -1
  21. package/dist/standalone/install-settings.js +12 -4
  22. package/dist/standalone/lifecycle.d.ts +1 -1
  23. package/dist/standalone/lifecycle.js +3 -2
  24. package/dist/standalone/manifest.d.ts +1 -0
  25. package/dist/standalone/manifest.js +16 -0
  26. package/dist/standalone/native-gateway.d.ts +1 -0
  27. package/dist/standalone/native-gateway.js +33 -1
  28. package/dist/standalone/resource.d.ts +1 -0
  29. package/dist/standalone/resource.js +1 -0
  30. package/dist/standalone/shared-folder.d.ts +7 -7
  31. package/dist/standalone/shared-folder.js +75 -16
  32. package/dist/standalone/spk.js +15 -4
  33. package/package.json +2 -2
  34. package/skills/SKILL.md +5 -3
  35. package/skills/reference/compose.md +18 -14
  36. package/skills/reference/deploy.md +1 -1
  37. package/skills/reference/files.md +1 -1
  38. package/skills/reference/identity-gateway.md +5 -1
  39. package/skills/reference/install.md +41 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.41.5
4
+
5
+ - Generate dedicated shared folders and guarded existing-folder choices, retain upgrade selections, validate required HTTPS settings before Next, and enable cold sign-in resume for all gateway modes. Pin templates 0.35.3 and SDK 0.38.2.
6
+
7
+ - Select and validate host ports in the DSM wizard; materialize gateway, ingress, service definitions and app environment from one retained selection.
4
8
 
5
9
  ## 0.41.4 (2026-10-30)
6
10
 
package/README.md CHANGED
@@ -88,7 +88,7 @@ alongside npm lockfiles. Do not disable checksum verification for public SDK
88
88
  packages. Private mirrors can use explicitly configured Go authentication and
89
89
  checksum settings; a failed fetch reports `SDK_DOWNLOAD_FAILED`.
90
90
 
91
- CLI 0.41.4 uses templates ~0.35.2, whose default SDK pin is 0.38.1. Compose import and SDK defaults
91
+ CLI 0.41.5 uses templates ~0.35.3, whose default SDK pin is 0.38.2. Compose import and SDK defaults
92
92
  read the SDK pin from the installed templates; explicit App SDK declarations take precedence.
93
93
  Every App method must be declared in the permissions table. Dynamic names and
94
94
  custom HTTP dispatchers are rejected. With SDK 0.33.0, use fixed
@@ -99,7 +99,7 @@ unimported development commands are excluded. Use
99
99
  authentication. See [App entry and registration](docs/app-entry.md).
100
100
 
101
101
  `init` and `add` read their templates from the locally installed
102
- `@synopackageland/templates`. CLI 0.41.4 depends on `~0.35.2`: npm installation or
102
+ `@synopackageland/templates`. CLI 0.41.5 depends on `~0.35.3`: npm installation or
103
103
  update can select the latest templates patch in the 0.35 minor line. Templates
104
104
  patches need only a templates release; update the npm installation to receive them.
105
105
  The CLI never fetches templates at runtime, so offline use works after installation;
@@ -8,5 +8,5 @@ export function composeDiagnosisMessages() {
8
8
  }
9
9
  /** Diagnostics are informational and cannot mask or replace a native result. */
10
10
  export function renderComposeDiagnosis(project, phase) {
11
- return `"${project}/control" diagnose --project "${project}" --phase ${phase} || :\n`;
11
+ return `"${project}/control" diagnose --project "${project}" --phase ${phase} >/dev/null || :\n`;
12
12
  }
@@ -0,0 +1,3 @@
1
+ /** Read-only DSM wizard validation. Permission metadata is advisory; runtime
2
+ * repeats the kernel check as the package account before opening any mount. */
3
+ export declare function existingShareValidator(account: string, permission: 'ro' | 'rw', reason: string, blank?: boolean, dedicatedName?: string): string;
@@ -0,0 +1,5 @@
1
+ /** Read-only DSM wizard validation. Permission metadata is advisory; runtime
2
+ * repeats the kernel check as the package account before opening any mount. */
3
+ export function existingShareValidator(account, permission, reason, blank = false, dedicatedName = '') {
4
+ return `{var name=arguments[0];if(!name)return ${blank ? 'true' : JSON.stringify(reason)};if(typeof name!=='string'||name.length>32||/[\\x00-\\x1f\\x7f/\\\\]/.test(name)||/^(homes?|users|ftp|root|admin|guest|system|volume[0-9]*|(usb|sata|sd)share[0-9]*|ActiveBackupforBusiness|surveillance|docker|web_packages)$/i.test(name))return ${JSON.stringify(reason)};try{var login=new XMLHttpRequest();login.open('GET','/webman/login.cgi?enable_syno_token=yes',false);login.send();var token=JSON.parse(login.responseText).SynoToken;if(!token)throw new Error();function call(api,method,params){var x=new XMLHttpRequest();x.open('POST','/webapi/entry.cgi',false);x.setRequestHeader('Content-Type','application/x-www-form-urlencoded');x.setRequestHeader('X-SYNO-TOKEN',token);var data={api:api,version:1,method:method};Object.keys(params).forEach(function(k){data[k]=params[k]});x.send(Object.keys(data).map(function(k){return encodeURIComponent(k)+'='+encodeURIComponent(data[k])}).join('&'));if(x.status!==200)throw new Error();var j=JSON.parse(x.responseText);if(j.success!==true){var e=new Error();e.code=j.error&&j.error.code;throw e;}return j.data}var share;try{share=call('SYNO.Core.Share','get',{name:name,additional:JSON.stringify(['encryption','is_aclmode','is_force_readonly','is_share_moving','is_missing_share','is_offline_share','is_c2_share','load_worm_attr'])});}catch(e){if(name===${JSON.stringify(dedicatedName)}&&e.code===402)return true;throw e;}if(!share||share.name!==name||share.is_usb_share||share.is_aclmode!==true||share.encryption===2||share.is_force_readonly||share.is_share_moving||share.is_missing_share||share.is_offline_share||share.is_c2_share||share.is_worm_share||share.worm_state) return ${JSON.stringify(reason)};if(name===${JSON.stringify(dedicatedName)})return true;var rows=call('SYNO.Core.Share.Permission','list_by_user',{name:${JSON.stringify(account)},user_group_type:'local_user',with_inherit:true,limit:-1,offset:0}).shares;var row=rows&&rows.filter(function(r){return r.name===name})[0];if(!row||row.is_deny||row.is_custom||row.is_mask||!${permission === 'rw' ? 'row.is_writable' : '(row.is_readonly||row.is_writable)'})return ${JSON.stringify(reason)};return true}catch(e){return ${JSON.stringify(reason)}}}`;
5
+ }
@@ -1,4 +1,103 @@
1
1
  export declare const WIZARD_MESSAGES: {
2
+ "ports.http": {
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
+ "ports.https": {
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
+ "ports.signin": {
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
+ "ports.title": {
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
+ "ports.invalid": {
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
+ "ports.originInvalid": {
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
+ "nativeShare.optionalExisting": {
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
+ "nativeShare.selectionInvalid": {
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
+ "settings.originInvalid": {
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
+ };
2
101
  "uninstall.composeRetained": {
3
102
  en: string;
4
103
  "zh-TW": string;
@@ -1,4 +1,103 @@
1
1
  export const WIZARD_MESSAGES = {
2
+ "ports.http": {
3
+ "en": "HTTP port",
4
+ "zh-TW": "HTTP 連接埠",
5
+ "zh-CN": "HTTP 端口",
6
+ "ja": "HTTP ポート",
7
+ "ko": "HTTP 포트",
8
+ "de": "HTTP-Port",
9
+ "fr": "Port HTTP",
10
+ "es": "Puerto HTTP",
11
+ "it": "Porta HTTP"
12
+ },
13
+ "ports.https": {
14
+ "en": "HTTPS port",
15
+ "zh-TW": "HTTPS 連接埠",
16
+ "zh-CN": "HTTPS 端口",
17
+ "ja": "HTTPS ポート",
18
+ "ko": "HTTPS 포트",
19
+ "de": "HTTPS-Port",
20
+ "fr": "Port HTTPS",
21
+ "es": "Puerto HTTPS",
22
+ "it": "Porta HTTPS"
23
+ },
24
+ "ports.signin": {
25
+ "en": "Local sign-in port",
26
+ "zh-TW": "本機登入連接埠",
27
+ "zh-CN": "本地登录端口",
28
+ "ja": "ローカルサインインポート",
29
+ "ko": "로컬 로그인 포트",
30
+ "de": "Lokaler Anmeldeport",
31
+ "fr": "Port de connexion local",
32
+ "es": "Puerto de inicio de sesión local",
33
+ "it": "Porta di accesso locale"
34
+ },
35
+ "ports.title": {
36
+ "en": "App ports",
37
+ "zh-TW": "應用程式連接埠",
38
+ "zh-CN": "应用程序端口",
39
+ "ja": "アプリのポート",
40
+ "ko": "앱 포트",
41
+ "de": "App-Ports",
42
+ "fr": "Ports de l’application",
43
+ "es": "Puertos de la aplicación",
44
+ "it": "Porte dell’app"
45
+ },
46
+ "ports.invalid": {
47
+ "en": "Choose a port from 1024 to 65535 that is not reserved or used by another service; DSM must be able to verify it",
48
+ "zh-TW": "請選擇 1024 至 65535 間未保留、未被其他服務使用的連接埠;DSM 必須能確認其狀態",
49
+ "zh-CN": "请选择 1024 至 65535 之间未保留、未被其他服务使用的端口;DSM 必须能确认其状态",
50
+ "ja": "1024~65535 の予約済みでも他サービスが使用中でもないポートを選択してください(DSM による確認が必要)",
51
+ "ko": "1024~65535 사이에서 예약되지 않고 다른 서비스가 사용하지 않는 포트를 선택하십시오 (DSM 확인 필요)",
52
+ "de": "Wählen Sie einen nicht reservierten, unbenutzten Port von 1024 bis 65535, den DSM prüfen kann",
53
+ "fr": "Choisissez un port de 1024 à 65535 non réservé et non utilisé par un autre service, vérifiable par DSM",
54
+ "es": "Seleccionar un puerto entre 1024 y 65535 que no esté reservado ni en uso y que DSM pueda verificar",
55
+ "it": "Selezionare una porta da 1024 a 65535 non riservata né usata da altri servizi e verificabile da DSM"
56
+ },
57
+ "ports.originInvalid": {
58
+ "en": "Enter a root HTTPS origin using the HTTPS port selected in this wizard",
59
+ "zh-TW": "請輸入根 HTTPS 來源,並使用此精靈所選的 HTTPS 連接埠",
60
+ "zh-CN": "请输入根 HTTPS 源,并使用此向导所选的 HTTPS 端口",
61
+ "ja": "このウィザードで選択した HTTPS ポートを使うルート HTTPS オリジンを入力してください",
62
+ "ko": "이 마법사에서 선택한 HTTPS 포트의 루트 HTTPS 원본을 입력하십시오",
63
+ "de": "Geben Sie einen HTTPS-Ursprung mit dem hier gewählten HTTPS-Port ein",
64
+ "fr": "Saisissez une origine HTTPS racine avec le port HTTPS choisi dans cet assistant",
65
+ "es": "Introducir un origen HTTPS raíz con el puerto HTTPS elegido en este asistente",
66
+ "it": "Inserire un’origine HTTPS radice con la porta HTTPS selezionata in questa procedura"
67
+ },
68
+ "nativeShare.optionalExisting": {
69
+ "en": "Optional: choose an accessible existing shared folder, or leave empty to create {name}",
70
+ "zh-TW": "選填:選擇已有權限的共用資料夾,留空則建立 {name}",
71
+ "zh-CN": "可选:选择已有权限的共享文件夹,留空则创建 {name}",
72
+ "ja": "アクセス可能な既存の共有フォルダを選択するか、空欄で {name} を作成",
73
+ "ko": "접근 가능한 기존 공유 폴더를 선택하거나 비워 두어 {name} 생성",
74
+ "de": "Optional: zugänglichen vorhandenen gemeinsamen Ordner wählen oder leer lassen, um {name} zu erstellen",
75
+ "fr": "Facultatif : choisissez un dossier partagé accessible ou laissez vide pour créer {name}",
76
+ "es": "Opcional: elija una carpeta compartida accesible o deje vacío para crear {name}",
77
+ "it": "Facoltativo: scegli una cartella condivisa accessibile o lascia vuoto per creare {name}"
78
+ },
79
+ "nativeShare.selectionInvalid": {
80
+ "en": "Cannot verify the required package-account access to this folder, or the folder is unsupported; choose the dedicated folder or ask an administrator to grant the declared RO/RW permission",
81
+ "zh-TW": "無法確認套件帳號對此資料夾具有所需權限,或不支援此資料夾;請使用專用資料夾,或請管理員授予宣告的 RO/RW 權限",
82
+ "zh-CN": "无法确认套件账户对此文件夹具有所需权限,或不支持此文件夹;请使用专用文件夹,或请管理员授予声明的 RO/RW 权限",
83
+ "ja": "必要なパッケージアカウント権限を確認できないか、このフォルダは未対応です。専用フォルダを使用するか、管理者に宣言された RO/RW 権限を依頼してください",
84
+ "ko": "필요한 패키지 계정 권한을 확인할 수 없거나 지원하지 않는 폴더입니다. 전용 폴더를 사용하거나 관리자에게 선언된 RO/RW 권한을 요청하십시오",
85
+ "de": "Erforderlicher Paketzugriff nicht bestätigt oder Ordner nicht unterstützt; verwenden Sie den eigenen Ordner oder bitten Sie den Administrator um die deklarierten RO/RW-Rechte",
86
+ "fr": "Accès du compte du paquet non vérifié ou dossier non pris en charge ; utilisez le dossier dédié ou demandez les droits RO/RW déclarés à l’administrateur",
87
+ "es": "No se puede verificar el acceso de la cuenta del paquete o la carpeta no es compatible; use la carpeta dedicada o solicite los permisos RO/RW declarados al administrador",
88
+ "it": "Accesso dell’account del pacchetto non verificato o cartella non supportata; usa la cartella dedicata o chiedi all’amministratore i permessi RO/RW dichiarati"
89
+ },
90
+ "settings.originInvalid": {
91
+ "en": "Enter an HTTPS origin such as https://app.example{port}, without a path, credentials, query or fragment",
92
+ "zh-TW": "請輸入 HTTPS 來源,例如 https://app.example{port},不可含路徑、帳密、查詢或片段",
93
+ "zh-CN": "请输入 HTTPS 源,例如 https://app.example{port},不可包含路径、凭据、查询或片段",
94
+ "ja": "https://app.example{port} の形式で HTTPS オリジンを入力してください(パス、認証情報、クエリ、フラグメントは不可)",
95
+ "ko": "https://app.example{port} 형식의 HTTPS 원본을 입력하십시오 (경로, 자격 증명, 쿼리, 프래그먼트 제외)",
96
+ "de": "Geben Sie einen HTTPS-Ursprung wie https://app.example{port} ohne Pfad, Anmeldedaten, Abfrage oder Fragment ein",
97
+ "fr": "Saisissez une origine HTTPS telle que https://app.example{port}, sans chemin, identifiants, requête ni fragment",
98
+ "es": "Introducir un origen HTTPS como https://app.example{port}, sin rutas ni credenciales ni parámetros ni fragmentos",
99
+ "it": "Inserisci un’origine HTTPS come https://app.example{port}, senza percorso, credenziali, query o frammento"
100
+ },
2
101
  "uninstall.composeRetained": {
3
102
  "en": "Container data volumes are kept, even when you select delete-data: {volumes}. Their data will NOT be deleted.",
4
103
  "zh-TW": "即使勾選刪除資料,容器資料磁碟區會保留:{volumes}。其中的資料不會刪除。",
@@ -682,15 +781,15 @@ export const WIZARD_MESSAGES = {
682
781
  it: "Se un'approvazione dell'amministratore dell'executor di {package} è ancora valida, la disinstallazione esegue come root il passaggio di disinstallazione root approvato. Senza un'approvazione valida il passaggio viene saltato e ciò che avrebbe rimosso resta da pulire per un amministratore.",
683
782
  },
684
783
  "nativeShare.selectIntro": {
685
- en: "This app keeps its files in a Shared Folder you select below. DSM does not give the app's package account access to a folder chosen here: after installing, open Control Panel > Shared Folder, edit the folder, and under Permissions > System internal user give the package account {account} {permission} access. Until then the app reports that it cannot use the folder.",
686
- "zh-TW": "此 App 會把檔案存放在你於下方選擇的共用資料夾。DSM 不會自動讓 App 的套件帳號存取這裡選的資料夾:安裝後請到「控制台」>「共用資料夾」,編輯該資料夾,在「權限」>「系統內部使用者」中授予套件帳號 {account}「{permission}」權限。在此之前,App 會回報無法使用該資料夾。",
687
- "zh-CN": "此应用会把文件存放在你在下方选择的共享文件夹。DSM 不会自动让应用的套件账户访问此处所选的文件夹:安装后请前往“控制面板”>“共享文件夹”,编辑该文件夹,在“权限”>“系统内部用户”中授予套件账户 {account}“{permission}”权限。在此之前,应用会报告无法使用该文件夹。",
688
- ja: "このアプリは、以下で選択した共有フォルダにファイルを保存します。DSM は、ここで選択したフォルダへのアクセス権をアプリのパッケージアカウントに自動では付与しません。インストール後に [コントロールパネル] > [共有フォルダ] でそのフォルダを編集し、[権限] > [システム内部ユーザー] でパッケージアカウント {account} に「{permission}」権限を付与してください。付与するまで、アプリはこのフォルダを使用できないと報告します。",
689
- ko: "이 앱은 아래에서 선택한 공유 폴더에 파일을 저장합니다. DSM은 여기서 선택한 폴더에 대한 접근 권한을 앱의 패키지 계정에 자동으로 부여하지 않습니다. 설치 후 [제어판] > [공유 폴더]에서 해당 폴더를 편집하고 [권한] > [시스템 내부 사용자]에서 패키지 계정 {account}에 '{permission}' 권한을 부여하십시오. 그때까지 앱은 이 폴더를 사용할 수 없다고 보고합니다.",
690
- de: "Diese App speichert ihre Dateien in einem freigegebenen Ordner, den Sie unten auswählen. DSM gewährt dem Paketkonto der App keinen automatischen Zugriff auf hier gewählte Ordner: Öffnen Sie nach der Installation Systemsteuerung > Gemeinsamer Ordner, bearbeiten Sie den Ordner und geben Sie dem Paketkonto {account} unter Berechtigungen > Interner Systembenutzer die Berechtigung „{permission}“. Bis dahin meldet die App, dass sie den Ordner nicht verwenden kann.",
691
- fr: "Cette application enregistre ses fichiers dans un dossier partagé que vous sélectionnez ci-dessous. DSM n'accorde pas automatiquement au compte de paquet de l'application l'accès à un dossier choisi ici : après l'installation, ouvrez Panneau de configuration > Dossier partagé, modifiez le dossier et, sous Autorisations > Utilisateur système interne, accordez au compte de paquet {account} l'accès « {permission} ». D'ici là, l'application signale qu'elle ne peut pas utiliser le dossier.",
692
- es: "Esta aplicación guarda sus archivos en una carpeta compartida que seleccione a continuación. DSM no concede automáticamente a la cuenta de paquete de la aplicación acceso a una carpeta elegida aquí: después de instalar, abra Panel de control > Carpeta compartida, edite la carpeta y, en Permisos > Usuario interno del sistema, conceda a la cuenta de paquete {account} el acceso «{permission}». Hasta entonces, la aplicación informará de que no puede usar la carpeta.",
693
- it: "Questa app salva i propri file in una cartella condivisa selezionata qui sotto. DSM non concede automaticamente all'account del pacchetto dell'app l'accesso a una cartella scelta qui: dopo l'installazione aprire Pannello di controllo > Cartella condivisa, modificare la cartella e, in Autorizzazioni > Utente interno del sistema, concedere all'account del pacchetto {account} l'accesso «{permission}». Fino ad allora l'app segnalerà di non poter usare la cartella.",
784
+ "en": "Choose an existing shared folder that already gives package account {account} {permission} access; the wizard verifies it before installation",
785
+ "zh-TW": "選擇已授予套件帳號 {account} {permission} 權限的既有共用資料夾;精靈會在安裝前驗證",
786
+ "zh-CN": "选择已授予套件账户 {account} {permission} 权限的现有共享文件夹;向导会在安装前验证",
787
+ "ja": "パッケージアカウント {account} に {permission} 権限がある既存の共有フォルダを選択してください(インストール前に確認します)",
788
+ "ko": "패키지 계정 {account}에 {permission} 권한이 있는 기존 공유 폴더를 선택하십시오 (설치 전 확인)",
789
+ "de": "Wählen Sie einen vorhandenen gemeinsamen Ordner mit {permission}-Zugriff für Paketkonto {account}; der Assistent prüft dies vor der Installation",
790
+ "fr": "Choisissez un dossier partagé existant avec les droits {permission} pour le compte du paquet {account} ; l’assistant les vérifie avant l’installation",
791
+ "es": "Seleccionar una carpeta compartida existente con acceso {permission} para la cuenta del paquete {account}; el asistente lo verifica antes de instalar",
792
+ "it": "Selezionare una cartella condivisa esistente con accesso {permission} per l’account del pacchetto {account}; la procedura verifica prima dell’installazione"
694
793
  },
695
794
  "nativeShare.createIntro": {
696
795
  en: "This app creates the Shared Folder {name} and gives its package account {permission} access to it. If {name} already exists, it is kept and only the package account's permission is set. Uninstalling the app never deletes this Shared Folder or the files in it.",
package/dist/cli.js CHANGED
@@ -516,7 +516,7 @@ async function runCliWithin(argv) {
516
516
  .option("--ui <vue|plain>", `Native UI ${JSON.parse(readFileSync(join(templatesRoot(), "native-go-vue/frontend/package.json"), "utf8")).dependencies["@synopackageland/ui"]} by default (vue); plain uses ui/app.js`)
517
517
  .option("--with <caps>", `Capabilities (comma-separated): ${CAPABILITY_NAMES.join(",")}`)
518
518
  .option("--from <compose.yaml>", "compose: import an existing Compose; omit to create an example")
519
- .option("--shared-folder <select|create>", "Select or create a shared folder in the install wizard")
519
+ .option("--shared-folder <dedicated|select|create>", "Use a dedicated shared folder by default, or select an existing folder")
520
520
  .option("--shared-folder-name <name>", "Name of the shared folder to create")
521
521
  .option("--shared-folder-key <key>", "Settings key, defaults to shared")
522
522
  .option("--shared-folder-permission <ro|rw>", "Package account permission, defaults to rw")
@@ -1,3 +1,4 @@
1
+ import { portDefaults } from "../standalone/install-ports.js";
1
2
  const USAGE = "synopkgland deploy --param delegate_share=<shared folder>, or --param delegate_home=true";
2
3
  /** Mirrors the preinst draft check in build-spk/delegate-scope.ts. */
3
4
  function validShareName(name) {
@@ -13,14 +14,15 @@ function validShareName(name) {
13
14
  export function resolveNativeInstallValues(manifest, provided, state) {
14
15
  const fail = (code, message) => ({ ok: false, code, message });
15
16
  const selected = manifest.capabilities.delegate?.scope === "install-selected";
16
- const folder = manifest.sharedFolder?.mode === "select" ? manifest.sharedFolder : undefined;
17
+ const folder = manifest.sharedFolder && manifest.sharedFolder.mode !== "create" ? manifest.sharedFolder : undefined;
17
18
  const key = folder?.key;
18
19
  const changeKey = key ? `CHANGE_${key}` : undefined;
19
20
  const settingNames = Object.keys(manifest.installSettings ?? {}).map(key => `pkgwizard_setting_${key}`);
21
+ const portNames = Object.keys(portDefaults(manifest)).map(key => "pkgwizard_port_" + key);
20
22
  const names = Object.keys(provided);
21
- if (!selected && !folder && !settingNames.length && names.length)
23
+ if (!selected && !folder && !settingNames.length && !portNames.length && names.length)
22
24
  return fail("DEPLOY_PARAM_UNDECLARED", `${manifest.package} has no install wizard values; remove --param ${names[0]}.`);
23
- const unknown = names.find((name) => !(selected && (name === "delegate_share" || name === "delegate_home")) && name !== key && name !== changeKey && !settingNames.includes(name));
25
+ const unknown = names.find((name) => !(selected && (name === "delegate_share" || name === "delegate_home")) && name !== key && name !== changeKey && !settingNames.includes(name) && !portNames.includes(name));
24
26
  if (unknown)
25
27
  return fail("DEPLOY_PARAM_UNDECLARED", `--param ${unknown} is not an install wizard value. Use: ${folder ? `--param ${folder.key}=<shared folder>` : USAGE}.`);
26
28
  if (key && changeKey) {
@@ -29,11 +31,21 @@ export function resolveNativeInstallValues(manifest, provided, state) {
29
31
  return fail("DEPLOY_PARAM_TYPE_INVALID", `--param ${changeKey} must be true or false.`);
30
32
  if (name !== undefined && (name === "" || /[/\\"\x00-\x1f\x7f]/.test(name) || name.includes("..") || /^[.@]/.test(name)))
31
33
  return fail("DEPLOY_PARAM_TYPE_INVALID", `--param ${key} must be one DSM Shared Folder name, never a path.`);
32
- if ((!state.installed || change === "true") && name === undefined)
34
+ if ((!state.installed || change === "true") && name === undefined && folder?.mode !== "dedicated")
33
35
  return fail("DEPLOY_PARAM_REQUIRED", `Select the native Shared Folder with --param ${key}=<shared folder>.`);
34
36
  if (state.installed && name !== undefined && change !== "true")
35
37
  return fail("DEPLOY_PARAM_REQUIRED", `Changing the selected folder requires --param ${changeKey}=true and --param ${key}=<shared folder>.`);
36
38
  }
39
+ for (const name of portNames) {
40
+ const raw = provided[name];
41
+ if (raw === undefined)
42
+ continue;
43
+ const port = Number(raw);
44
+ if (state.installed)
45
+ return fail('DEPLOY_PARAM_TYPE_INVALID', 'Changing selected ports requires reinstall');
46
+ if (!/^[1-9][0-9]{3,4}$/.test(raw) || port < 1024 || port > 65535 || [1900, 5000, 5001, 5005, 5006, 5353, 6690].includes(port))
47
+ return fail('DEPLOY_PARAM_TYPE_INVALID', 'Choose non-reserved ports from 1024 to 65535');
48
+ }
37
49
  const share = selected ? provided.delegate_share : undefined, home = selected ? provided.delegate_home : undefined;
38
50
  if (home !== undefined && home !== "true" && home !== "false")
39
51
  return fail("DEPLOY_PARAM_TYPE_INVALID", "--param delegate_home must be true or false.");
@@ -13,6 +13,8 @@ export const ERROR_REGISTRY = {
13
13
  INIT_PUBLISHER_INVALID: { code: "INIT_PUBLISHER_INVALID", description: "The attribution name or publisher URL is invalid.", suggestion: "Use plain single-line project/publisher names and an http(s) publisher URL without credentials." },
14
14
  UPSTREAM_ATTRIBUTION: { code: "UPSTREAM_ATTRIBUTION", description: "Upstream App attribution is incomplete or names the distributor as maintainer.", suggestion: "Name the original project/team or owner as maintainer; set metadata.maintainer_url to the upstream repository and metadata.distributor/distributor_url to your publisher; resolve TODOs before release." },
15
15
  INSTALL_SETTINGS_INVALID: { "code": "INSTALL_SETTINGS_INVALID", "description": "The installation setting declaration is invalid.", "suggestion": "Declare 1–32 lowercase identifier keys in install.settings, each with type https-origin, a plain label and an explicit boolean required; omit defaults and values." },
16
+ INSTALL_PORT_INVALID: { code: "INSTALL_PORT_INVALID", description: "The installation port declaration or selected value is invalid", suggestion: "Enable install.ports for a declared identity gateway and choose distinct available non-reserved ports between 1024 and 65535" },
17
+ SDK_PORT_SETTINGS_UNSUPPORTED: { code: "SDK_PORT_SETTINGS_UNSUPPORTED", description: "The selected SDK does not provide installer-selected ports", suggestion: "Pin SDK 0.38.2 or a matching supported release and rebuild with the matching CLI" },
16
18
  SDK_INSTALL_SETTINGS_UNSUPPORTED: { "code": "SDK_INSTALL_SETTINGS_UNSUPPORTED", "description": "The selected SDK does not provide native installation settings.", "suggestion": "Pin SDK 0.24.0 or a matching supported release with installsettings/cmd/control, then rebuild with the matching CLI." },
17
19
  SDK_INSTALL_SETTINGS_BUILD_FAILED: { "code": "SDK_INSTALL_SETTINGS_BUILD_FAILED", "description": "The SDK installation settings controller could not be built.", "suggestion": "Install the pinned Go toolchain, inspect the compiler diagnostics and rebuild with a complete matching SDK release." },
18
20
  BACKUP_INVALID: { code: "BACKUP_INVALID", description: "The Hyper Backup application backup declaration or its packaged scripts are invalid.", suggestion: "Use backup.hyper_backup: false to opt out, or only online, data_version (\"major.minor\") and hook; rebuild so scripts/backup holds exactly the fixed export, import, info and version files." },
package/dist/init.d.ts CHANGED
@@ -26,7 +26,7 @@ export interface InitOptions {
26
26
  healthStatus?: number;
27
27
  /** native-go: `install.sharedFolder` (select from the NAS, or create one). key defaults to "shared", permission to "rw". */
28
28
  sharedFolder?: {
29
- mode: "select" | "create";
29
+ mode: "select" | "create" | "dedicated";
30
30
  name?: string;
31
31
  key?: string;
32
32
  permission?: "ro" | "rw";
package/dist/init.js CHANGED
@@ -401,6 +401,7 @@ function importCompose(from, targetDir) {
401
401
  else {
402
402
  doc.deleteIn(["compose", "web"]);
403
403
  doc.delete("identity_gateway");
404
+ doc.deleteIn(["install", "ports"]);
404
405
  } // Ambiguous ingress requires an explicit developer decision.
405
406
  writeFileSync(path, String(doc));
406
407
  }
@@ -94,6 +94,10 @@ export declare function readComposeApp(value: unknown, root: string, arches: Nat
94
94
  composeMounts?: boolean;
95
95
  sharedFolder?: SharedFolder;
96
96
  installSettings?: InstallSettings;
97
+ installPorts?: Record<string, {
98
+ default: number;
99
+ protocol: "tcp";
100
+ }>;
97
101
  resolveImages?: boolean;
98
102
  architecture?: NativeArchFamily;
99
103
  imageProofs?: unknown;
@@ -109,8 +109,9 @@ export function readComposeApp(value, root, arches, fail, context = {}) {
109
109
  package: context.package ?? "ComposeApp",
110
110
  identity_mode: context.mode ?? "none",
111
111
  compose_mounts: context.composeMounts === true,
112
- ...(context.sharedFolder?.mode === "select" ? { install_shared_folder: context.sharedFolder } : {}),
112
+ ...(context.sharedFolder && context.sharedFolder.mode !== "create" ? { install_shared_folder: context.sharedFolder } : {}),
113
113
  install_settings: context.installSettings,
114
+ install_ports: context.installPorts,
114
115
  resolve_images: context.resolveImages === true,
115
116
  registry_mirrors: context.resolveImages &&
116
117
  process.env.SYNOPKGLAND_COMPOSE_REGISTRY_MIRRORS
@@ -1,3 +1,4 @@
1
+ import { portDefaults } from "./install-ports.js";
1
2
  import { operationBudgetSeconds, runtimeBudgetSdkSupported } from "../runtime/runtime-budgets.js";
2
3
  import { goChildEnv } from "../runtime/go-env.js";
3
4
  import { chmodSync, existsSync, copyFileSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, writeFileSync, } from "node:fs";
@@ -185,7 +186,7 @@ export function daemonConfig(manifest) {
185
186
  args,
186
187
  ...(uninstall_args ? { uninstall_args } : {}),
187
188
  ...(restart ? { restart } : {}),
188
- env,
189
+ env: { ...env, ...Object.fromEntries(Object.entries(portDefaults(manifest)).map(([key, p]) => ["SYNOPKGLAND_PORT_" + key.toUpperCase(), String(p.default)])) },
189
190
  timeouts,
190
191
  ...(runtimeBudgetSdkSupported(daemonSdkVersion(manifest))
191
192
  ? { operation_seconds: operationBudgetSeconds(manifest) } : {}),
@@ -117,8 +117,7 @@ export function windowAppId(manifest) {
117
117
  }
118
118
  /** Whether the window itself resumes a cold DSM sign-in; with a console the url launcher does. */
119
119
  function windowResumesOIDC(manifest) {
120
- const oidc = manifest.identityGateway?.oidc;
121
- return Boolean((oidc?.native_login || oidc?.browser) && !manifest.console);
120
+ return Boolean(manifest.identityGateway && !manifest.console);
122
121
  }
123
122
  /**
124
123
  * `ui/config`: registers the DSM main-menu app and its window class. With a