@synopackageland/cli 0.41.3 → 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.
- package/CHANGELOG.md +13 -1
- package/README.md +2 -2
- package/dist/build-spk/compose-diagnosis.js +1 -1
- package/dist/build-spk/share-validation.d.ts +3 -0
- package/dist/build-spk/share-validation.js +5 -0
- package/dist/build-spk/wizard-messages.d.ts +99 -0
- package/dist/build-spk/wizard-messages.js +108 -9
- package/dist/cli.js +1 -1
- package/dist/deploy/native-install-values.js +16 -4
- package/dist/error-registry.js +2 -0
- package/dist/init.d.ts +1 -1
- package/dist/init.js +1 -0
- package/dist/runtime/webapi-profile.js +1 -1
- package/dist/standalone/compose.d.ts +4 -0
- package/dist/standalone/compose.js +2 -1
- package/dist/standalone/daemon.js +2 -1
- package/dist/standalone/dsm-window.js +1 -2
- package/dist/standalone/inspect-spk.js +41 -17
- package/dist/standalone/install-ports.d.ts +31 -0
- package/dist/standalone/install-ports.js +103 -0
- package/dist/standalone/install-settings.d.ts +3 -1
- package/dist/standalone/install-settings.js +12 -4
- package/dist/standalone/lifecycle.d.ts +1 -1
- package/dist/standalone/lifecycle.js +3 -2
- package/dist/standalone/manifest.d.ts +1 -0
- package/dist/standalone/manifest.js +16 -0
- package/dist/standalone/native-gateway.d.ts +1 -0
- package/dist/standalone/native-gateway.js +33 -1
- package/dist/standalone/resource.d.ts +1 -0
- package/dist/standalone/resource.js +1 -0
- package/dist/standalone/shared-folder.d.ts +7 -7
- package/dist/standalone/shared-folder.js +75 -16
- package/dist/standalone/spk.js +15 -4
- package/package.json +2 -2
- package/skills/SKILL.md +5 -3
- package/skills/reference/compose.md +18 -14
- package/skills/reference/deploy.md +1 -1
- package/skills/reference/files.md +1 -1
- package/skills/reference/identity-gateway.md +5 -1
- package/skills/reference/install.md +41 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
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.
|
|
8
|
+
|
|
9
|
+
## 0.41.4 (2026-10-30)
|
|
10
|
+
|
|
11
|
+
- Allow `webapi` raw profiles to set `raw_max_bytes` up to 2 GiB, matching
|
|
12
|
+
SDK/App 0.38.2. That SDK removes Go `JobWebAPI.CallRaw` and
|
|
13
|
+
`RawWebAPIResult.Body`; raw consumers must move to `CallRawTo(..., io.Writer)`.
|
|
14
|
+
Templates ~0.35.2 still pin SDK 0.38.1; declare SDK 0.38.2 to use larger raw
|
|
15
|
+
downloads.
|
|
4
16
|
|
|
5
17
|
## 0.41.3 (2026-10-30)
|
|
6
18
|
|
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.
|
|
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.
|
|
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: "
|
|
686
|
-
"zh-TW": "
|
|
687
|
-
"zh-CN": "
|
|
688
|
-
ja: "
|
|
689
|
-
ko: "
|
|
690
|
-
de: "
|
|
691
|
-
fr: "
|
|
692
|
-
es: "
|
|
693
|
-
it: "
|
|
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>", "
|
|
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
|
|
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.");
|
package/dist/error-registry.js
CHANGED
|
@@ -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
|
}
|
|
@@ -8,7 +8,7 @@ export function validWebAPIProfiles(value) {
|
|
|
8
8
|
for (const [name, p] of Object.entries(value)) {
|
|
9
9
|
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || name === 'home' || !record(p) || Object.keys(p).some(k => !['purpose', 'apis', 'raw_max_bytes'].includes(k)) || typeof p.purpose !== 'string' || !p.purpose.trim() || Buffer.byteLength(p.purpose) > 1024 || /[\x00-\x1f\x7f]/.test(p.purpose) || !record(p.apis) || !Object.keys(p.apis).length)
|
|
10
10
|
return false;
|
|
11
|
-
if (p.raw_max_bytes !== undefined && (!Number.isSafeInteger(p.raw_max_bytes) || Number(p.raw_max_bytes) < 0 || Number(p.raw_max_bytes) >
|
|
11
|
+
if (p.raw_max_bytes !== undefined && (!Number.isSafeInteger(p.raw_max_bytes) || Number(p.raw_max_bytes) < 0 || Number(p.raw_max_bytes) > 2147483648))
|
|
12
12
|
return false;
|
|
13
13
|
for (const [api, methods] of Object.entries(p.apis)) {
|
|
14
14
|
if (!/^SYNO\.[A-Za-z][A-Za-z0-9]*(\.[A-Za-z][A-Za-z0-9]*)*$/.test(api) || api.toLowerCase().split('.').slice(1).some(x => blocked.has(x)) || !record(methods) || !Object.keys(methods).length)
|
|
@@ -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
|
|
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
|
-
|
|
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
|