@synopackageland/cli 0.9.4 → 0.9.6
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 +94 -0
- package/README.md +1 -1
- package/assets/licenses/MUSL-COPYRIGHT +193 -0
- package/assets/licenses/RUST-COPYRIGHT-library.html +5556 -0
- package/dist/add.js +29 -29
- package/dist/build-spk/dsm-version.d.ts +14 -4
- package/dist/build-spk/dsm-version.js +17 -5
- package/dist/build-spk/notices.d.ts +8 -1
- package/dist/build-spk/notices.js +13 -4
- package/dist/build-spk/uninstall-wizard.d.ts +11 -0
- package/dist/build-spk/uninstall-wizard.js +16 -3
- package/dist/build-spk/wizard-i18n.d.ts +1 -1
- package/dist/build-spk/wizard-i18n.js +1 -1
- package/dist/build-spk/wizard-messages.d.ts +44 -0
- package/dist/build-spk/wizard-messages.js +44 -0
- package/dist/capability-overlays.d.ts +10 -5
- package/dist/capability-overlays.js +32 -10
- package/dist/cli.js +5 -5
- package/dist/deploy/plan.d.ts +1 -1
- package/dist/deploy/plan.js +1 -1
- package/dist/error-registry.js +46 -4
- package/dist/info.js +4 -3
- package/dist/init.js +53 -28
- package/dist/portal/alias.d.ts +8 -0
- package/dist/portal/alias.js +16 -0
- package/dist/runtime/executor-build.d.ts +1 -1
- package/dist/runtime/executor-build.js +2 -1
- package/dist/runtime/executor-digests.json +22 -0
- package/dist/runtime/executor-policy.d.ts +15 -0
- package/dist/runtime/executor-policy.js +32 -6
- package/dist/runtime/sdk-source.d.ts +14 -2
- package/dist/runtime/sdk-source.js +39 -4
- package/dist/runtime/uninstall-cleanup.d.ts +5 -1
- package/dist/runtime/uninstall-cleanup.js +11 -1
- package/dist/standalone/api-permissions.d.ts +4 -2
- package/dist/standalone/api-permissions.js +6 -5
- package/dist/standalone/binary.d.ts +3 -2
- package/dist/standalone/binary.js +17 -5
- package/dist/standalone/build.js +16 -12
- package/dist/standalone/daemon.d.ts +6 -0
- package/dist/standalone/daemon.js +8 -4
- package/dist/standalone/dsm-window.js +16 -3
- package/dist/standalone/inspect-spk.d.ts +1 -1
- package/dist/standalone/inspect-spk.js +46 -13
- package/dist/standalone/lifecycle.js +38 -4
- package/dist/standalone/manifest.d.ts +12 -0
- package/dist/standalone/manifest.js +62 -20
- package/dist/standalone/native-layout.d.ts +4 -7
- package/dist/standalone/native-layout.js +41 -102
- package/dist/standalone/native-root-window.d.ts +2 -1
- package/dist/standalone/native-root-window.js +15 -5
- package/dist/standalone/portal-window.d.ts +4 -2
- package/dist/standalone/portal-window.js +54 -12
- package/dist/standalone/portal.d.ts +4 -1
- package/dist/standalone/portal.js +8 -2
- package/dist/standalone/root-ops.d.ts +2 -0
- package/dist/standalone/root-ops.js +2 -0
- package/dist/standalone/rust-build.d.ts +35 -0
- package/dist/standalone/rust-build.js +127 -0
- package/dist/standalone/sdk-check.d.ts +5 -3
- package/dist/standalone/sdk-check.js +21 -8
- package/dist/standalone/spk.js +15 -3
- package/dist/standalone/toolkit.d.ts +10 -0
- package/dist/standalone/toolkit.js +101 -0
- package/dist/standalone/uninstall-hook.d.ts +10 -0
- package/dist/standalone/uninstall-hook.js +71 -0
- package/dist/standalone/uninstall.d.ts +53 -0
- package/dist/standalone/uninstall.js +136 -0
- package/dist/types.d.ts +1 -1
- package/dist/yaml-edit.d.ts +6 -0
- package/dist/yaml-edit.js +31 -0
- package/package.json +2 -2
- package/skills/reference/portal.md +8 -4
package/dist/add.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { dirname, join, relative } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { parse } from 'yaml';
|
|
5
|
+
import { setTopLevel } from './yaml-edit.js';
|
|
5
6
|
import { capabilityNames, capabilitySettings } from './capabilities.js';
|
|
6
7
|
import { initFromTemplate, nativeCapabilitySettings } from './init.js';
|
|
7
8
|
import { templatesRoot } from './template-paths.js';
|
|
8
|
-
import { borrowedTarget, capabilityOverlays } from './capability-overlays.js';
|
|
9
|
-
import {
|
|
9
|
+
import { borrowedFiles, borrowedTarget, capabilityOverlays } from './capability-overlays.js';
|
|
10
|
+
import { API_PERMISSIONS_FILE } from './standalone/api-permissions.js';
|
|
11
|
+
import { DEFAULT_MIN_DSM, isDsmAtLeast, PORTAL_EXECUTOR_MIN_DSM } from './build-spk/dsm-version.js';
|
|
10
12
|
import { loadStandaloneManifest } from './standalone/manifest.js';
|
|
11
13
|
import { PORTAL_REQUIRES_EXECUTOR } from './portal/alias.js';
|
|
14
|
+
const NATIVE_TEMPLATE = { go: 'native-go', rust: 'native-rust' };
|
|
12
15
|
function files(root, dir = root) {
|
|
13
16
|
if (!existsSync(dir))
|
|
14
17
|
return [];
|
|
@@ -37,13 +40,11 @@ function safePath(root, file) {
|
|
|
37
40
|
}
|
|
38
41
|
return true;
|
|
39
42
|
}
|
|
40
|
-
/** Raise a lower os_min_ver to the floor a capability needs; a higher one stays. Returns the notice. */
|
|
41
|
-
function raiseOsMinVer(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
osMinVer.value = floor;
|
|
46
|
-
return [`os_min_ver raised from ${current} to ${floor}: ${capability} needs DSM ${floor} or later.`];
|
|
43
|
+
/** Raise a lower os_min_ver to the floor a capability needs; a higher one stays. Returns the change and its notice. */
|
|
44
|
+
function raiseOsMinVer(current, floor, capability) {
|
|
45
|
+
if (isDsmAtLeast(current, floor))
|
|
46
|
+
return { values: {}, notes: [] };
|
|
47
|
+
return { values: { os_min_ver: floor }, notes: [`os_min_ver raised from ${current} to ${floor}: ${capability} needs DSM ${floor} or later.`] };
|
|
47
48
|
}
|
|
48
49
|
function withNotes(result, notes) {
|
|
49
50
|
const all = [...(result.notes ?? []), ...notes];
|
|
@@ -51,7 +52,7 @@ function withNotes(result, notes) {
|
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
54
|
* `portal: {}` in dsm-package.yaml; the Go and binary starters already carry the UI and backend side.
|
|
54
|
-
* A lower os_min_ver is raised to PORTAL_MIN_DSM
|
|
55
|
+
* A lower os_min_ver is raised to PORTAL_EXECUTOR_MIN_DSM; below PORTAL_MIN_DSM the executor registers the entry.
|
|
55
56
|
*/
|
|
56
57
|
function addPortal(root, result) {
|
|
57
58
|
const loaded = loadStandaloneManifest(root);
|
|
@@ -62,10 +63,8 @@ function addPortal(root, result) {
|
|
|
62
63
|
if (!safePath(root, 'dsm-package.yaml'))
|
|
63
64
|
throw new Error('[ADD_FILE_CONFLICT] 既有檔案與 SDK overlay 不同:dsm-package.yaml;請先移動或合併衝突,不會覆寫。');
|
|
64
65
|
const manifestPath = join(root, 'dsm-package.yaml');
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
const notes = raiseOsMinVer(doc, loaded.manifest.os_min_ver, PORTAL_MIN_DSM, 'portal');
|
|
68
|
-
writeFileSync(manifestPath, String(doc));
|
|
66
|
+
const { values, notes } = raiseOsMinVer(loaded.manifest.os_min_ver, PORTAL_EXECUTOR_MIN_DSM, 'portal');
|
|
67
|
+
writeFileSync(manifestPath, setTopLevel(readFileSync(manifestPath, 'utf8'), { ...values, portal: {} }));
|
|
69
68
|
return withNotes({ ...result, added: [...result.added, 'portal'] }, notes);
|
|
70
69
|
}
|
|
71
70
|
/** A portal needs the executor tier, already declared or added in the same call; nothing is written otherwise. */
|
|
@@ -90,8 +89,9 @@ export function addCapabilities(root, requested) {
|
|
|
90
89
|
if (!loaded.ok)
|
|
91
90
|
throw new Error(loaded.errors.map(e => `[${e.code}] ${e.message}`).join('\n'));
|
|
92
91
|
const m = loaded.manifest;
|
|
93
|
-
if (m.backend.kind
|
|
94
|
-
throw new Error('[CAPABILITY_UNSUPPORTED] add 需要 backend.kind: go。');
|
|
92
|
+
if (m.backend.kind === 'binary')
|
|
93
|
+
throw new Error('[CAPABILITY_UNSUPPORTED] add 需要 backend.kind: go 或 rust。');
|
|
94
|
+
const template = NATIVE_TEMPLATE[m.backend.kind];
|
|
95
95
|
const existing = Object.entries(capabilitySettings(names)).filter(([key]) => key in m.capabilities).map(([key]) => key);
|
|
96
96
|
const unchanged = names.filter(name => existing.includes(Object.keys(capabilitySettings([name]))[0]));
|
|
97
97
|
const added = names.filter(name => !unchanged.includes(name));
|
|
@@ -106,10 +106,10 @@ export function addCapabilities(root, requested) {
|
|
|
106
106
|
// Generate SDK files using init's identical substitutions, without touching app-owned files.
|
|
107
107
|
const existingNames = Object.keys(m.capabilities).map(name => name === 'identity' ? 'login' : name === 'rootOps' ? 'root-ops' : name);
|
|
108
108
|
const before = join(work, 'before'), generated = join(work, 'generated');
|
|
109
|
-
initFromTemplate(
|
|
110
|
-
initFromTemplate(
|
|
111
|
-
const capability = capabilityOverlays(added);
|
|
112
|
-
const destinations = [...new Set([...capability.overlays.flatMap(overlay => files(join(templatesRoot(), overlay))), ...capability.borrowed.map(borrowedTarget)])].filter(file => existsSync(join(generated, file)));
|
|
109
|
+
initFromTemplate(template, before, { ui: m.ui.build ? 'vue' : 'plain', with: existingNames, packageId: m.package, displayName: m.displayname });
|
|
110
|
+
initFromTemplate(template, generated, { ui: m.ui.build ? 'vue' : 'plain', with: [...existingNames, ...added], packageId: m.package, displayName: m.displayname });
|
|
111
|
+
const capability = capabilityOverlays(template, added);
|
|
112
|
+
const destinations = [...new Set([...capability.overlays.flatMap(overlay => files(join(templatesRoot(), overlay))), ...borrowedFiles(templatesRoot(), capability.borrowed).map(borrowedTarget)])].filter(file => existsSync(join(generated, file)));
|
|
113
113
|
const deletions = capability.removed;
|
|
114
114
|
const conflicts = [];
|
|
115
115
|
for (const file of destinations) {
|
|
@@ -120,21 +120,21 @@ export function addCapabilities(root, requested) {
|
|
|
120
120
|
}
|
|
121
121
|
for (const file of deletions) {
|
|
122
122
|
const path = join(root, file);
|
|
123
|
-
if (!safePath(root, file) || (existsSync(path) && (!lstatSync(path).isFile() || !readFileSync(path).equals(readFileSync(join(templatesRoot(),
|
|
123
|
+
if (!safePath(root, file) || (existsSync(path) && (!lstatSync(path).isFile() || !readFileSync(path).equals(readFileSync(join(templatesRoot(), template, file))))))
|
|
124
124
|
conflicts.push(file);
|
|
125
125
|
}
|
|
126
126
|
const manifestPath = join(root, 'dsm-package.yaml');
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
const permissions = `backend/${API_PERMISSIONS_FILE[m.backend.kind]}`;
|
|
128
|
+
if (added.includes('delegate') && !safePath(root, permissions))
|
|
129
|
+
conflicts.push(permissions);
|
|
129
130
|
if (!safePath(root, 'dsm-package.yaml'))
|
|
130
131
|
conflicts.push('dsm-package.yaml');
|
|
131
132
|
if (conflicts.length)
|
|
132
133
|
throw new Error(`[ADD_FILE_CONFLICT] 既有檔案與 SDK overlay 不同:${conflicts.join(', ')};請先移動或合併衝突,不會覆寫。`);
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
doc.setIn(['capabilities', key], value);
|
|
134
|
+
const text = readFileSync(manifestPath, 'utf8');
|
|
135
|
+
const capabilities = { ...parse(text).capabilities, ...nativeCapabilitySettings(added) };
|
|
136
136
|
// rootOps needs DSM 7.1-42661 or later.
|
|
137
|
-
const notes = added.includes('root-ops') ? raiseOsMinVer(
|
|
137
|
+
const { values, notes } = added.includes('root-ops') ? raiseOsMinVer(m.os_min_ver, DEFAULT_MIN_DSM, 'root-ops') : { values: {}, notes: [] };
|
|
138
138
|
for (const file of destinations) {
|
|
139
139
|
const path = join(root, file);
|
|
140
140
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -142,7 +142,7 @@ export function addCapabilities(root, requested) {
|
|
|
142
142
|
}
|
|
143
143
|
for (const file of deletions)
|
|
144
144
|
rmSync(join(root, file), { force: true });
|
|
145
|
-
writeFileSync(manifestPath,
|
|
145
|
+
writeFileSync(manifestPath, setTopLevel(text, { ...values, capabilities }));
|
|
146
146
|
return withNotes({ added, unchanged }, notes);
|
|
147
147
|
}
|
|
148
148
|
finally {
|
|
@@ -8,11 +8,21 @@ export declare const DEFAULT_MIN_DSM: string;
|
|
|
8
8
|
export declare const CONTAINER_MIN_DSM: string;
|
|
9
9
|
/**
|
|
10
10
|
* DSM registers an Application Portal through the package's web-config
|
|
11
|
-
* app-portal resource. DSM 7.1
|
|
12
|
-
* package (error 320, "internal
|
|
13
|
-
* 7.2
|
|
11
|
+
* app-portal resource. DSM 7.1's package manager marks the web-config worker
|
|
12
|
+
* Synology-only and refuses it in an unsigned package (error 320, "internal
|
|
13
|
+
* worker"); synopkg from DSM 7.2 on does not. That 7.2 floor comes from the
|
|
14
|
+
* source and is not yet verified on a 7.2 NAS.
|
|
14
15
|
*/
|
|
15
|
-
export declare const PORTAL_MIN_DSM
|
|
16
|
+
export declare const PORTAL_MIN_DSM: string;
|
|
17
|
+
/** Why DSM below PORTAL_MIN_DSM refuses a package that declares the web-config portal resource. */
|
|
18
|
+
export declare const PORTAL_MIN_DSM_REASON = "DSM 7.1 \u628A web-config \u5217\u70BA Synology \u5C08\u7528 worker\uFF0C\u4EE5\u932F\u8AA4 320 \u62D2\u88DD\u5E36 portal\uFF08web-config app-portal\uFF09\u7684\u672A\u7C3D\u7AE0\u5957\u4EF6\uFF1B7.2 \u8D77\u958B\u653E\u662F\u4F9D synopkg \u539F\u59CB\u78BC\u5224\u65B7\uFF0C\u5C1A\u672A\u5BE6\u6A5F\u9A57\u8B49";
|
|
19
|
+
/**
|
|
20
|
+
* Below {@link PORTAL_MIN_DSM} the root executor registers the portal entry in
|
|
21
|
+
* DSM's app.d instead; that file format is verified from DSM 7.1 on.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PORTAL_EXECUTOR_MIN_DSM: string;
|
|
24
|
+
/** `true` when a portal package for `osMinVer` has its executor register the entry instead of web-config. */
|
|
25
|
+
export declare function portalByExecutor(osMinVer: string): boolean;
|
|
16
26
|
/** Why a portal package cannot declare `value`, and how to fix it; `field` names the portal key. */
|
|
17
27
|
export declare function portalMinDsmMessage(value: string, field: string): string;
|
|
18
28
|
/** `true` when `value` is not older than `floor`, comparing version parts then build. */
|
|
@@ -15,14 +15,26 @@ export const DEFAULT_MIN_DSM = FIRST_BUILD["7.1"];
|
|
|
15
15
|
export const CONTAINER_MIN_DSM = FIRST_BUILD["7.2.1"];
|
|
16
16
|
/**
|
|
17
17
|
* DSM registers an Application Portal through the package's web-config
|
|
18
|
-
* app-portal resource. DSM 7.1
|
|
19
|
-
* package (error 320, "internal
|
|
20
|
-
* 7.2
|
|
18
|
+
* app-portal resource. DSM 7.1's package manager marks the web-config worker
|
|
19
|
+
* Synology-only and refuses it in an unsigned package (error 320, "internal
|
|
20
|
+
* worker"); synopkg from DSM 7.2 on does not. That 7.2 floor comes from the
|
|
21
|
+
* source and is not yet verified on a 7.2 NAS.
|
|
21
22
|
*/
|
|
22
|
-
export const PORTAL_MIN_DSM = "7.
|
|
23
|
+
export const PORTAL_MIN_DSM = FIRST_BUILD["7.2"];
|
|
24
|
+
/** Why DSM below PORTAL_MIN_DSM refuses a package that declares the web-config portal resource. */
|
|
25
|
+
export const PORTAL_MIN_DSM_REASON = "DSM 7.1 把 web-config 列為 Synology 專用 worker,以錯誤 320 拒裝帶 portal(web-config app-portal)的未簽章套件;7.2 起開放是依 synopkg 原始碼判斷,尚未實機驗證";
|
|
26
|
+
/**
|
|
27
|
+
* Below {@link PORTAL_MIN_DSM} the root executor registers the portal entry in
|
|
28
|
+
* DSM's app.d instead; that file format is verified from DSM 7.1 on.
|
|
29
|
+
*/
|
|
30
|
+
export const PORTAL_EXECUTOR_MIN_DSM = FIRST_BUILD["7.1"];
|
|
31
|
+
/** `true` when a portal package for `osMinVer` has its executor register the entry instead of web-config. */
|
|
32
|
+
export function portalByExecutor(osMinVer) {
|
|
33
|
+
return !isDsmAtLeast(osMinVer, PORTAL_MIN_DSM);
|
|
34
|
+
}
|
|
23
35
|
/** Why a portal package cannot declare `value`, and how to fix it; `field` names the portal key. */
|
|
24
36
|
export function portalMinDsmMessage(value, field) {
|
|
25
|
-
return `${field} 需要 DSM ${
|
|
37
|
+
return `${field} 需要 DSM ${PORTAL_EXECUTOR_MIN_DSM} 以上,os_min_ver 是「${value}」。DSM ${PORTAL_MIN_DSM} 以前由 executor 寫入 DSM 的 app.d 註冊入口,這個格式只在 DSM 7.1 起驗證過。請把 os_min_ver 提高到 ${PORTAL_EXECUTOR_MIN_DSM} 以上,或移除 ${field}。`;
|
|
26
38
|
}
|
|
27
39
|
/** `true` when `value` is not older than `floor`, comparing version parts then build. */
|
|
28
40
|
export function isDsmAtLeast(value, floor) {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { type SdkBackend } from '../runtime/sdk-source.js';
|
|
1
2
|
/** Copy legal text only; never follow a project-supplied symlink. */
|
|
2
3
|
export declare function copyNoticeTree(source: string, target: string): void;
|
|
3
4
|
/** Fixed payload location, independent of app code/binary paths. */
|
|
4
5
|
export declare function copyProjectNotices(root: string, payload: string): void;
|
|
5
|
-
|
|
6
|
+
/** The runtimes linked into the payload's binaries, and the backend whose dependency files name the SDK release. */
|
|
7
|
+
export interface NoticeSources {
|
|
8
|
+
go: boolean;
|
|
9
|
+
rust: boolean;
|
|
10
|
+
sdk?: SdkBackend;
|
|
11
|
+
}
|
|
12
|
+
export declare function copyStandaloneNotices(root: string, uiDir: string, payload: string, { go, rust, sdk: backend }: NoticeSources): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { appSdkVersion, sdkSource } from '../runtime/sdk-source.js';
|
|
4
4
|
/** Copy legal text only; never follow a project-supplied symlink. */
|
|
5
5
|
export function copyNoticeTree(source, target) {
|
|
6
6
|
const stat = lstatSync(source);
|
|
@@ -26,7 +26,7 @@ export function copyProjectNotices(root, payload) {
|
|
|
26
26
|
if (existsSync(join(root, 'LICENSE')))
|
|
27
27
|
copyNoticeTree(join(root, 'LICENSE'), join(dest, 'APP.txt'));
|
|
28
28
|
}
|
|
29
|
-
export function copyStandaloneNotices(root, uiDir, payload, go,
|
|
29
|
+
export function copyStandaloneNotices(root, uiDir, payload, { go, rust, sdk: backend }) {
|
|
30
30
|
copyProjectNotices(root, payload);
|
|
31
31
|
const dest = join(payload, 'LICENSES');
|
|
32
32
|
const shared = new URL('../../assets/licenses/', import.meta.url);
|
|
@@ -34,11 +34,20 @@ export function copyStandaloneNotices(root, uiDir, payload, go, goDir = join(roo
|
|
|
34
34
|
mkdirSync(dest, { recursive: true });
|
|
35
35
|
for (const name of ['GO-LICENSE', 'GO-PATENTS'])
|
|
36
36
|
copyFileSync(new URL(name, shared), join(dest, name));
|
|
37
|
+
const goDir = backend?.kind === 'go' ? backend.dir : join(root, 'backend');
|
|
37
38
|
if (existsSync(join(goDir, 'THIRD_PARTY_NOTICES.txt')))
|
|
38
39
|
copyNoticeTree(join(goDir, 'THIRD_PARTY_NOTICES.txt'), join(dest, 'GO-THIRD_PARTY_NOTICES.txt'));
|
|
39
40
|
}
|
|
40
|
-
//
|
|
41
|
-
|
|
41
|
+
// Rust's standard library and, on x86_64 and aarch64, musl are linked into every Rust binary.
|
|
42
|
+
if (rust && backend) {
|
|
43
|
+
mkdirSync(dest, { recursive: true });
|
|
44
|
+
for (const name of ['RUST-COPYRIGHT-library.html', 'MUSL-COPYRIGHT'])
|
|
45
|
+
copyFileSync(new URL(name, shared), join(dest, name));
|
|
46
|
+
if (existsSync(join(backend.dir, 'THIRD_PARTY_NOTICES.txt')))
|
|
47
|
+
copyNoticeTree(join(backend.dir, 'THIRD_PARTY_NOTICES.txt'), join(dest, 'RUST-THIRD_PARTY_NOTICES.txt'));
|
|
48
|
+
}
|
|
49
|
+
// The SDK embeds the native administrator and AppPriv bridges; their sources and terms ship with them.
|
|
50
|
+
const sdk = backend ? appSdkVersion(backend) : undefined;
|
|
42
51
|
if (sdk) {
|
|
43
52
|
const source = sdkSource(sdk);
|
|
44
53
|
const license = join(source.goDir, 'LICENSE');
|
|
@@ -1,11 +1,22 @@
|
|
|
1
|
+
import type { Locale } from "../standalone/i18n.js";
|
|
1
2
|
/** The key DSM's built-in packages use for their "also remove my data" box. */
|
|
2
3
|
export declare const REMOVE_ADDITIONAL_KEY = "pkgwizard_remove_additional";
|
|
4
|
+
/** uninstall.state's box: the App deletes the state it lists, apart from package data. */
|
|
5
|
+
export declare const DELETE_STATE_KEY = "pkgwizard_delete_state";
|
|
3
6
|
export type UninstallWizardKind = "native" | "container";
|
|
4
7
|
interface UninstallWizardOptions {
|
|
5
8
|
/** The App runs the root executor (rootOps or delegate), which purges DSM's leftovers on uninstall. */
|
|
6
9
|
executor?: boolean;
|
|
7
10
|
/** The App declares a Login Portal alias (web-config app-portal); only the executor tier can. */
|
|
8
11
|
portal?: boolean;
|
|
12
|
+
/** Below DSM 7.2 the executor registered the App's Application Portal entry. */
|
|
13
|
+
portalEntry?: boolean;
|
|
14
|
+
/** uninstall.state: the App's own texts of the items it deletes, per locale. */
|
|
15
|
+
state?: {
|
|
16
|
+
items: (locale: Locale) => string[];
|
|
17
|
+
};
|
|
18
|
+
/** uninstall.purge.root: the executor runs an approved root step at uninstall. */
|
|
19
|
+
rootPurge?: boolean;
|
|
9
20
|
}
|
|
10
21
|
/** `uninstall_uifile` (English) plus one `uninstall_uifile_<dsm language>` per locale. */
|
|
11
22
|
export declare function renderUninstallWizardFiles(packageName: string, kind: UninstallWizardKind, options?: UninstallWizardOptions): Record<string, string>;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { portalEntryCleanupCommands } from "../runtime/uninstall-cleanup.js";
|
|
1
2
|
import { renderWizardFiles } from "./wizard-i18n.js";
|
|
2
3
|
/** The key DSM's built-in packages use for their "also remove my data" box. */
|
|
3
4
|
export const REMOVE_ADDITIONAL_KEY = "pkgwizard_remove_additional";
|
|
5
|
+
/** uninstall.state's box: the App deletes the state it lists, apart from package data. */
|
|
6
|
+
export const DELETE_STATE_KEY = "pkgwizard_delete_state";
|
|
4
7
|
function list(items) {
|
|
5
8
|
return `<ul class="synopkg-ul-list">${items.map((item) => `<li>${item}</li>`).join("")}</ul>`;
|
|
6
9
|
}
|
|
7
|
-
|
|
10
|
+
const html = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
11
|
+
function renderUninstallWizard(t, locale, packageName, kind, { executor = false, portal = false, portalEntry = false, state, rootPurge = false }) {
|
|
8
12
|
const vars = { package: packageName };
|
|
9
13
|
return [{
|
|
10
14
|
step_title: t("uninstall.step"),
|
|
@@ -23,15 +27,24 @@ function renderUninstallWizard(t, packageName, kind, { executor = false, portal
|
|
|
23
27
|
...(executor ? [{ desc: t("uninstall.executor", vars) }] : []),
|
|
24
28
|
{ desc: t(executor ? "uninstall.appPrivilege.auto" : "uninstall.appPrivilege", vars) },
|
|
25
29
|
...(executor && portal ? [{ desc: t("uninstall.portal.auto", vars) }] : []),
|
|
30
|
+
...(executor && portalEntry ? [
|
|
31
|
+
{ desc: t("uninstall.portalEntry", vars) },
|
|
32
|
+
{ desc: portalEntryCleanupCommands(packageName).map((line) => `<code>${html(line)}</code>`).join("<br>") },
|
|
33
|
+
] : []),
|
|
34
|
+
...(executor && rootPurge ? [{ desc: t("uninstall.purge.root", vars) }] : []),
|
|
35
|
+
...(state ? [{ desc: t("uninstall.state.heading") }, { desc: list(state.items(locale).map(html)) }] : []),
|
|
26
36
|
{ desc: t("uninstall.backup") },
|
|
27
37
|
{
|
|
28
38
|
type: "multiselect",
|
|
29
|
-
subitems: [
|
|
39
|
+
subitems: [
|
|
40
|
+
{ key: REMOVE_ADDITIONAL_KEY, desc: t("uninstall.checkbox"), defaultValue: false },
|
|
41
|
+
...(state ? [{ key: DELETE_STATE_KEY, desc: t("uninstall.state.checkbox"), defaultValue: false }] : []),
|
|
42
|
+
],
|
|
30
43
|
},
|
|
31
44
|
],
|
|
32
45
|
}];
|
|
33
46
|
}
|
|
34
47
|
/** `uninstall_uifile` (English) plus one `uninstall_uifile_<dsm language>` per locale. */
|
|
35
48
|
export function renderUninstallWizardFiles(packageName, kind, options = {}) {
|
|
36
|
-
return renderWizardFiles("uninstall_uifile", (t) => renderUninstallWizard(t, packageName, kind, options));
|
|
49
|
+
return renderWizardFiles("uninstall_uifile", (t, locale) => renderUninstallWizard(t, locale, packageName, kind, options));
|
|
37
50
|
}
|
|
@@ -9,7 +9,7 @@ export declare function translator(locale: Locale): Translate;
|
|
|
9
9
|
* every other locale is `<base>_<dsm language>`, which DSM prefers when the
|
|
10
10
|
* administrator's language matches.
|
|
11
11
|
*/
|
|
12
|
-
export declare function renderWizardFiles(base: string, render: (t: Translate) => unknown): Record<string, string>;
|
|
12
|
+
export declare function renderWizardFiles(base: string, render: (t: Translate, locale: Locale) => unknown): Record<string, string>;
|
|
13
13
|
/**
|
|
14
14
|
* Writes `WIZARD_UIFILES/<name>` for every rendered file under an SPK staging
|
|
15
15
|
* directory. A dynamic wizard (`<name>.sh`) is executable: DSM runs it to
|
|
@@ -20,7 +20,7 @@ export function translator(locale) {
|
|
|
20
20
|
export function renderWizardFiles(base, render) {
|
|
21
21
|
return Object.fromEntries(LOCALES.map((locale) => [
|
|
22
22
|
locale === "en" ? base : `${base}_${DSM_LANGS[locale]}`,
|
|
23
|
-
JSON.stringify(render(translator(locale)), null, 2),
|
|
23
|
+
JSON.stringify(render(translator(locale), locale), null, 2),
|
|
24
24
|
]));
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
@@ -417,6 +417,17 @@ export declare const WIZARD_MESSAGES: {
|
|
|
417
417
|
es: string;
|
|
418
418
|
it: string;
|
|
419
419
|
};
|
|
420
|
+
"uninstall.portalEntry": {
|
|
421
|
+
en: string;
|
|
422
|
+
"zh-TW": string;
|
|
423
|
+
"zh-CN": string;
|
|
424
|
+
ja: string;
|
|
425
|
+
ko: string;
|
|
426
|
+
de: string;
|
|
427
|
+
fr: string;
|
|
428
|
+
es: string;
|
|
429
|
+
it: string;
|
|
430
|
+
};
|
|
420
431
|
"uninstall.keep.native": {
|
|
421
432
|
en: string;
|
|
422
433
|
"zh-TW": string;
|
|
@@ -461,6 +472,39 @@ export declare const WIZARD_MESSAGES: {
|
|
|
461
472
|
es: string;
|
|
462
473
|
it: string;
|
|
463
474
|
};
|
|
475
|
+
"uninstall.state.heading": {
|
|
476
|
+
en: string;
|
|
477
|
+
"zh-TW": string;
|
|
478
|
+
"zh-CN": string;
|
|
479
|
+
ja: string;
|
|
480
|
+
ko: string;
|
|
481
|
+
de: string;
|
|
482
|
+
fr: string;
|
|
483
|
+
es: string;
|
|
484
|
+
it: string;
|
|
485
|
+
};
|
|
486
|
+
"uninstall.state.checkbox": {
|
|
487
|
+
en: string;
|
|
488
|
+
"zh-TW": string;
|
|
489
|
+
"zh-CN": string;
|
|
490
|
+
ja: string;
|
|
491
|
+
ko: string;
|
|
492
|
+
de: string;
|
|
493
|
+
fr: string;
|
|
494
|
+
es: string;
|
|
495
|
+
it: string;
|
|
496
|
+
};
|
|
497
|
+
"uninstall.purge.root": {
|
|
498
|
+
en: string;
|
|
499
|
+
"zh-TW": string;
|
|
500
|
+
"zh-CN": string;
|
|
501
|
+
ja: string;
|
|
502
|
+
ko: string;
|
|
503
|
+
de: string;
|
|
504
|
+
fr: string;
|
|
505
|
+
es: string;
|
|
506
|
+
it: string;
|
|
507
|
+
};
|
|
464
508
|
"nativeShare.selectIntro": {
|
|
465
509
|
en: string;
|
|
466
510
|
"zh-TW": string;
|
|
@@ -417,6 +417,17 @@ export const WIZARD_MESSAGES = {
|
|
|
417
417
|
es: "Después de que DSM elimine el alias del Portal de inicio de sesión de {package}, la desinstalación recarga automáticamente el servidor web, de modo que el alias deja de funcionar al instante. Solo si esta recarga falla, como indica la entrada synopkgland-executor del registro del sistema, un administrador debe guardar un alias de aplicación en Panel de control > Portal de inicio de sesión > Aplicaciones, lo que recarga el servidor web, o reiniciar el NAS.",
|
|
418
418
|
it: "Dopo che DSM ha rimosso l’alias del Portale di accesso di {package}, la disinstallazione ricarica automaticamente il server web, così l’alias smette subito di funzionare. Solo se questo ricaricamento non riesce, come segnala la voce synopkgland-executor del registro di sistema, un amministratore deve salvare un alias di applicazione in Pannello di controllo > Portale di accesso > Applicazioni, che ricarica il server web, oppure riavviare il NAS.",
|
|
419
419
|
},
|
|
420
|
+
"uninstall.portalEntry": {
|
|
421
|
+
en: "Uninstall asks the root executor to remove the Application Portal entry of {package}. If the executor is not running or was never approved, uninstall does not remove the entry; an administrator then runs these commands as root:",
|
|
422
|
+
"zh-TW": "解除安裝會由 root executor 移除 {package} 的應用程式入口。如果 executor 未執行或從未核准,解除安裝不會移除應用程式入口,管理員需以 root 執行下列指令:",
|
|
423
|
+
"zh-CN": "卸载会由 root executor 移除 {package} 的应用程序门户入口。如果 executor 未运行或从未核准,卸载不会移除应用程序门户入口,管理员需以 root 执行下列命令:",
|
|
424
|
+
ja: "アンインストール時に root executor が {package} のアプリケーションポータルのエントリを削除します。executor が実行されていない、または承認されていない場合、アンインストールではエントリが削除されないため、管理者が root で次のコマンドを実行してください:",
|
|
425
|
+
ko: "제거 시 root executor가 {package}의 응용 프로그램 포털 항목을 제거합니다. executor가 실행 중이 아니거나 승인된 적이 없으면 제거 과정에서 항목이 제거되지 않으므로 관리자가 root로 다음 명령을 실행하십시오.",
|
|
426
|
+
de: "Bei der Deinstallation entfernt der Root-Executor den Eintrag von {package} im Anwendungsportal. Läuft der Executor nicht oder wurde er nie genehmigt, entfernt die Deinstallation den Eintrag nicht; ein Administrator führt dann diese Befehle als root aus:",
|
|
427
|
+
fr: "La désinstallation demande à l’executor root de supprimer l’entrée de {package} dans le Portail d’application. Si l’executor ne s’exécute pas ou n’a jamais été approuvé, la désinstallation ne supprime pas l’entrée ; un administrateur exécute alors ces commandes en tant que root :",
|
|
428
|
+
es: "La desinstalación pide al executor root que elimine la entrada de {package} del Portal de aplicaciones. Si el executor no se está ejecutando o nunca se aprobó, la desinstalación no elimina la entrada; un administrador debe ejecutar estos comandos como root:",
|
|
429
|
+
it: "La disinstallazione chiede all’executor root di rimuovere la voce di {package} dal Portale applicazioni. Se l’executor non è in esecuzione o non è mai stato approvato, la disinstallazione non rimuove la voce; un amministratore deve eseguire questi comandi come root:",
|
|
430
|
+
},
|
|
420
431
|
"uninstall.keep.native": {
|
|
421
432
|
en: "Never deleted: Shared Folders, the package account, and other apps' data.",
|
|
422
433
|
"zh-TW": "不會被刪除:共用資料夾(Shared Folder)、套件帳號,以及其他 App 的資料。",
|
|
@@ -461,6 +472,39 @@ export const WIZARD_MESSAGES = {
|
|
|
461
472
|
es: "Eliminar también los datos de la aplicación indicados arriba",
|
|
462
473
|
it: "Elimina anche i dati dell'app elencati sopra",
|
|
463
474
|
},
|
|
475
|
+
"uninstall.state.heading": {
|
|
476
|
+
en: "The app deletes its own state only when you tick the second box below; package data is the first box. The app deletes:",
|
|
477
|
+
"zh-TW": "只有勾選下方第二個選項時,App 才會刪除自己的狀態;套件資料是第一個選項。App 會刪除:",
|
|
478
|
+
"zh-CN": "只有勾选下方第二个选项时,应用才会删除自己的状态;套件数据是第一个选项。应用会删除:",
|
|
479
|
+
ja: "下の 2 番目のボックスをオンにした場合にのみ、アプリは自身の状態を削除します。パッケージデータは 1 番目のボックスです。アプリが削除するもの:",
|
|
480
|
+
ko: "아래 두 번째 상자를 선택한 경우에만 앱이 자체 상태를 삭제합니다. 패키지 데이터는 첫 번째 상자입니다. 앱이 삭제하는 항목:",
|
|
481
|
+
de: "Die App löscht ihren eigenen Zustand nur, wenn Sie unten das zweite Kästchen aktivieren; Paketdaten betrifft das erste Kästchen. Die App löscht:",
|
|
482
|
+
fr: "L'application ne supprime son propre état que si vous cochez la deuxième case ci-dessous ; les données du paquet relèvent de la première case. L'application supprime :",
|
|
483
|
+
es: "La aplicación solo elimina su propio estado si marca la segunda casilla de abajo; los datos del paquete corresponden a la primera casilla. La aplicación elimina:",
|
|
484
|
+
it: "L'app elimina il proprio stato solo se si seleziona la seconda casella qui sotto; i dati del pacchetto riguardano la prima casella. L'app elimina:",
|
|
485
|
+
},
|
|
486
|
+
"uninstall.state.checkbox": {
|
|
487
|
+
en: "Also delete the app's own state listed above",
|
|
488
|
+
"zh-TW": "一併刪除上列 App 自身狀態",
|
|
489
|
+
"zh-CN": "同时删除上列应用自身状态",
|
|
490
|
+
ja: "上記のアプリ自身の状態も削除する",
|
|
491
|
+
ko: "위에 나열된 앱 자체 상태도 삭제",
|
|
492
|
+
de: "Oben aufgeführten eigenen Zustand der App ebenfalls löschen",
|
|
493
|
+
fr: "Supprimer aussi l'état propre de l'application listé ci-dessus",
|
|
494
|
+
es: "Eliminar también el estado propio de la aplicación indicado arriba",
|
|
495
|
+
it: "Elimina anche lo stato proprio dell'app elencato sopra",
|
|
496
|
+
},
|
|
497
|
+
"uninstall.purge.root": {
|
|
498
|
+
en: "If an administrator approval of {package}'s executor is still valid, uninstall runs its approved root uninstall step as root. Without a valid approval the step is skipped, and anything it would remove stays for an administrator to clean up.",
|
|
499
|
+
"zh-TW": "若 {package} 的 executor 管理員核准仍有效,解除安裝會以 root 執行其已核准的 root 解除安裝步驟。沒有有效核准時會略過此步驟,原本要移除的項目會留給管理員清理。",
|
|
500
|
+
"zh-CN": "若 {package} 的 executor 管理员批准仍有效,卸载会以 root 执行其已批准的 root 卸载步骤。没有有效批准时会跳过此步骤,原本要移除的内容会留给管理员清理。",
|
|
501
|
+
ja: "{package} の executor に対する管理者の承認がまだ有効な場合、アンインストール時に承認済みの root アンインストール手順が root として実行されます。有効な承認がない場合、この手順はスキップされ、削除されるはずだったものは管理者が後片付けするために残ります。",
|
|
502
|
+
ko: "{package} executor에 대한 관리자 승인이 아직 유효하면, 제거 시 승인된 root 제거 단계가 root로 실행됩니다. 유효한 승인이 없으면 이 단계는 건너뛰며, 제거되었어야 할 항목은 관리자가 정리하도록 남습니다.",
|
|
503
|
+
de: "Ist eine Administratorfreigabe des Executors von {package} noch gültig, führt die Deinstallation dessen freigegebenen Root-Deinstallationsschritt als root aus. Ohne gültige Freigabe wird der Schritt übersprungen, und was er entfernen würde, bleibt zur Bereinigung durch einen Administrator erhalten.",
|
|
504
|
+
fr: "Si une approbation administrateur de l'exécuteur de {package} est toujours valide, la désinstallation exécute en tant que root son étape de désinstallation root approuvée. Sans approbation valide, l'étape est ignorée et ce qu'elle aurait supprimé reste à nettoyer par un administrateur.",
|
|
505
|
+
es: "Si una aprobación de administrador del ejecutor de {package} sigue siendo válida, la desinstalación ejecuta como root su paso de desinstalación root aprobado. Sin una aprobación válida, el paso se omite y lo que habría eliminado queda para que lo limpie un administrador.",
|
|
506
|
+
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.",
|
|
507
|
+
},
|
|
464
508
|
"nativeShare.selectIntro": {
|
|
465
509
|
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.",
|
|
466
510
|
"zh-TW": "此 App 會把檔案存放在你於下方選擇的共用資料夾。DSM 不會自動讓 App 的套件帳號存取這裡選的資料夾:安裝後請到「控制台」>「共用資料夾」,編輯該資料夾,在「權限」>「系統內部使用者」中授予套件帳號 {account}「{permission}」權限。在此之前,App 會回報無法使用該資料夾。",
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* How each native
|
|
3
|
-
* directories copied on top, files borrowed from another overlay, and
|
|
4
|
-
* base files the overlay replaces. init and add both read this table.
|
|
2
|
+
* How each capability of a native template is laid over its base template: the
|
|
3
|
+
* template directories copied on top, files borrowed from another overlay, and
|
|
4
|
+
* the base files the overlay replaces. init and add both read this table.
|
|
5
5
|
*/
|
|
6
6
|
export interface CapabilityOverlay {
|
|
7
7
|
overlays: string[];
|
|
8
8
|
borrowed: string[];
|
|
9
9
|
removed: string[];
|
|
10
10
|
}
|
|
11
|
-
export
|
|
11
|
+
export type NativeTemplate = 'native-go' | 'native-rust';
|
|
12
|
+
export declare const CAPABILITY_OVERLAYS: Record<NativeTemplate, Record<string, CapabilityOverlay>>;
|
|
13
|
+
/** The overlays of a native template's Vue frontend, copied before the capabilities'. */
|
|
14
|
+
export declare const VUE_OVERLAYS: Record<NativeTemplate, string[]>;
|
|
12
15
|
/** The overlays of these capabilities, in table order, each once; a capability without one adds nothing. */
|
|
13
|
-
export declare function capabilityOverlays(names: readonly string[]): CapabilityOverlay;
|
|
16
|
+
export declare function capabilityOverlays(template: NativeTemplate, names: readonly string[]): CapabilityOverlay;
|
|
17
|
+
/** Each file of the borrowed paths, a borrowed folder listed file by file, under `templates`. */
|
|
18
|
+
export declare function borrowedFiles(templates: string, borrowed: readonly string[]): string[];
|
|
14
19
|
/** A borrowed path names its overlay first; this is where it lands in a project. */
|
|
15
20
|
export declare const borrowedTarget: (path: string) => string;
|
|
@@ -1,18 +1,40 @@
|
|
|
1
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
1
3
|
const EXECUTOR_SHELL = ['backend/internal/app/executor.go', 'backend/internal/app/executor_test.go'];
|
|
2
|
-
export const CAPABILITY_OVERLAYS = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export const CAPABILITY_OVERLAYS = { 'native-go': {
|
|
5
|
+
'root-ops': { overlays: ['native-go-root-ops'], borrowed: [], removed: ['backend/internal/app/root_none.go'] },
|
|
6
|
+
jobs: { overlays: ['native-go-jobs'], borrowed: [], removed: ['backend/internal/app/background_none.go'] },
|
|
7
|
+
delegate: {
|
|
8
|
+
overlays: ['native-go-jobs', 'native-go-delegate'],
|
|
9
|
+
borrowed: EXECUTOR_SHELL.map(file => `native-go-root-ops/${file}`),
|
|
10
|
+
removed: ['backend/internal/app/background_none.go', 'backend/internal/app/root_none.go', 'backend/internal/app/delegate_none.go'],
|
|
11
|
+
},
|
|
9
12
|
},
|
|
10
|
-
|
|
13
|
+
// Rust has no build tags: an overlay overwrites the base's same-named files and
|
|
14
|
+
// nothing is removed. The frontend and root/ files come from native-go's overlays.
|
|
15
|
+
'native-rust': {
|
|
16
|
+
'root-ops': { overlays: ['native-rust-root-ops'], borrowed: ['native-go-root-ops/root'], removed: [] },
|
|
17
|
+
jobs: { overlays: ['native-rust-jobs'], borrowed: ['native-go-jobs/frontend'], removed: [] },
|
|
18
|
+
delegate: {
|
|
19
|
+
overlays: ['native-rust-jobs', 'native-rust-delegate'],
|
|
20
|
+
borrowed: ['native-go-jobs/frontend', 'native-go-delegate/frontend', 'native-rust-root-ops/backend/src/app/root_routes.rs'],
|
|
21
|
+
removed: [],
|
|
22
|
+
},
|
|
23
|
+
} };
|
|
24
|
+
/** The overlays of a native template's Vue frontend, copied before the capabilities'. */
|
|
25
|
+
export const VUE_OVERLAYS = { 'native-go': ['native-go-vue'], 'native-rust': ['native-go-vue', 'native-rust-vue'] };
|
|
11
26
|
/** The overlays of these capabilities, in table order, each once; a capability without one adds nothing. */
|
|
12
|
-
export function capabilityOverlays(names) {
|
|
13
|
-
const chosen = Object.entries(CAPABILITY_OVERLAYS).filter(([name]) => names.includes(name)).map(([, overlay]) => overlay);
|
|
27
|
+
export function capabilityOverlays(template, names) {
|
|
28
|
+
const chosen = Object.entries(CAPABILITY_OVERLAYS[template]).filter(([name]) => names.includes(name)).map(([, overlay]) => overlay);
|
|
14
29
|
const unique = (pick) => [...new Set(chosen.flatMap(pick))];
|
|
15
30
|
return { overlays: unique(o => o.overlays), borrowed: unique(o => o.borrowed), removed: unique(o => o.removed) };
|
|
16
31
|
}
|
|
32
|
+
/** Each file of the borrowed paths, a borrowed folder listed file by file, under `templates`. */
|
|
33
|
+
export function borrowedFiles(templates, borrowed) {
|
|
34
|
+
const walk = (path) => statSync(join(templates, path)).isDirectory()
|
|
35
|
+
? readdirSync(join(templates, path)).sort().flatMap(name => walk(`${path}/${name}`))
|
|
36
|
+
: [path];
|
|
37
|
+
return [...new Set(borrowed.flatMap(walk))];
|
|
38
|
+
}
|
|
17
39
|
/** A borrowed path names its overlay first; this is where it lands in a project. */
|
|
18
40
|
export const borrowedTarget = (path) => path.split('/').slice(1).join('/');
|
package/dist/cli.js
CHANGED
|
@@ -271,16 +271,16 @@ export async function runCli(argv) {
|
|
|
271
271
|
.option("-t, --template <name>", `模板:${listTemplates().join(" | ")}`)
|
|
272
272
|
.option("--package <id>", "INFO.package(預設由目錄名產生)")
|
|
273
273
|
.option("--name <display>", "顯示名稱")
|
|
274
|
-
.option("--ui <vue|plain>",
|
|
274
|
+
.option("--ui <vue|plain>", `原生模板 UI,預設 vue(UI ${JSON.parse(readFileSync(join(templatesRoot(), "native-go-vue/frontend/package.json"), "utf8")).dependencies["@synopackageland/ui"]});plain 使用 ui/app.js`)
|
|
275
275
|
.option("--with <caps>", `能力(逗號分隔):${CAPABILITY_NAMES.join(",")}`)
|
|
276
276
|
.option("--from <compose.yaml>", "compose:匯入現有 Compose;省略時建立範例")
|
|
277
277
|
.option("--shared-folder <select|create>", "安裝精靈選擇或建立共用資料夾")
|
|
278
278
|
.option("--shared-folder-name <name>", "建立的共用資料夾名稱")
|
|
279
279
|
.option("--shared-folder-key <key>", "設定鍵,預設 shared")
|
|
280
280
|
.option("--shared-folder-permission <ro|rw>", "套件帳號權限,預設 rw")
|
|
281
|
-
.option("--min-dsm <version>", `最低 DSM 版本(native-go/binary 預設 ${DEFAULT_MIN_DSM},compose 預設 ${CONTAINER_MIN_DSM})`)
|
|
281
|
+
.option("--min-dsm <version>", `最低 DSM 版本(native-go/native-rust/binary 預設 ${DEFAULT_MIN_DSM},compose 預設 ${CONTAINER_MIN_DSM})`)
|
|
282
282
|
.argument("[dir]", "目標目錄", ".")
|
|
283
|
-
.addHelpText("after", "\n原生專案:native-go 與 binary 使用 dsm-package.yaml schema 2。\nCompose 專案:compose 使用 synology-app.yaml;--from 匯入 compose.yaml。\n快速開始:synopkgland init -t native-go --with files,jobs,login my-app\n cd my-app && synopkgland build\n")
|
|
283
|
+
.addHelpText("after", "\n原生專案:native-go、native-rust 與 binary 使用 dsm-package.yaml schema 2。\nCompose 專案:compose 使用 synology-app.yaml;--from 匯入 compose.yaml。\n快速開始:synopkgland init -t native-go --with files,jobs,login my-app\n cd my-app && synopkgland build\n")
|
|
284
284
|
.action((dir, options, command) => {
|
|
285
285
|
if (command.parent?.opts().project)
|
|
286
286
|
throw failure("CLI_OPTION_INVALID", "--project 不適用於 init。");
|
|
@@ -309,8 +309,8 @@ export async function runCli(argv) {
|
|
|
309
309
|
});
|
|
310
310
|
stdout(`已建立 ${options.template} 專案於 ${targetDir}`);
|
|
311
311
|
// --install-links packs a local SDK like the released package; a symlink would load the checkout's own node_modules.
|
|
312
|
-
if (options.template === "native-go")
|
|
313
|
-
stdout(`下一步:\n cd ${dir}\n (cd backend && go test ./...)\n${existsSync(join(targetDir, "frontend", "package.json")) ? ` (cd frontend && npm install${process.env.SYNOPKGLAND_SDK_DIR ? " --install-links" : ""})\n` : ""} synopkgland build\n說明見 README.md。`);
|
|
312
|
+
if (options.template === "native-go" || options.template === "native-rust")
|
|
313
|
+
stdout(`下一步:\n cd ${dir}\n (cd backend && ${options.template === "native-go" ? "go test ./..." : "cargo test"})\n${existsSync(join(targetDir, "frontend", "package.json")) ? ` (cd frontend && npm install${process.env.SYNOPKGLAND_SDK_DIR ? " --install-links" : ""})\n` : ""} synopkgland build\n說明見 README.md。`);
|
|
314
314
|
});
|
|
315
315
|
program
|
|
316
316
|
.command("add <caps...>")
|
package/dist/deploy/plan.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
|
|
|
3
3
|
import type { NasHost } from "../host/types.js";
|
|
4
4
|
export interface DeploymentPlan {
|
|
5
5
|
root: string;
|
|
6
|
-
kind: "native-go" | "binary" | "native-layout" | "container";
|
|
6
|
+
kind: "native-go" | "native-rust" | "binary" | "native-layout" | "container";
|
|
7
7
|
packageId: string;
|
|
8
8
|
version: string;
|
|
9
9
|
arch?: NativeArchFamily;
|
package/dist/deploy/plan.js
CHANGED
|
@@ -29,7 +29,7 @@ export function planDeployment(root, options = {}) {
|
|
|
29
29
|
throw fail("STANDALONE_ARCH_NOT_DECLARED", `Architecture not declared: ${options.arch}; add it to dsm-package.yaml arches to deploy it`);
|
|
30
30
|
return {
|
|
31
31
|
root: standalone,
|
|
32
|
-
kind: m.backend.kind === "
|
|
32
|
+
kind: m.backend.kind === "binary" ? "binary" : `native-${m.backend.kind}`,
|
|
33
33
|
packageId: m.package,
|
|
34
34
|
version: m.spkVersion,
|
|
35
35
|
arch: options.arch,
|