@synopackageland/cli 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +44 -1
- package/dist/build-spk/generator.d.ts +3 -2
- package/dist/build-spk/generator.js +6 -850
- package/dist/build-spk/resource-worker.d.ts +2 -14
- package/dist/build-spk/resource-worker.js +3 -166
- package/dist/cli.js +208 -56
- package/dist/compose/analyze.d.ts +3 -61
- package/dist/compose/analyze.js +35 -776
- package/dist/compose/env.d.ts +5 -3
- package/dist/compose/env.js +17 -25
- package/dist/compose/runner.js +23 -6
- package/dist/contract/app-launch.d.ts +11 -0
- package/dist/contract/app-launch.js +18 -0
- package/dist/contract/web-launch.d.ts +3 -13
- package/dist/contract/web-launch.js +14 -81
- package/dist/ctl/cli.d.ts +36 -0
- package/dist/ctl/cli.js +218 -0
- package/dist/ctl/descriptor.d.ts +81 -0
- package/dist/ctl/descriptor.js +93 -0
- package/dist/ctl/dsm-auth.d.ts +10 -0
- package/dist/ctl/dsm-auth.js +30 -0
- package/dist/ctl/errors.d.ts +17 -0
- package/dist/ctl/errors.js +32 -0
- package/dist/ctl/flows.d.ts +11 -0
- package/dist/ctl/flows.js +52 -0
- package/dist/ctl/input.d.ts +20 -0
- package/dist/ctl/input.js +128 -0
- package/dist/ctl/json.d.ts +2 -0
- package/dist/ctl/json.js +38 -0
- package/dist/ctl/redaction.d.ts +15 -0
- package/dist/ctl/redaction.js +198 -0
- package/dist/ctl/service.d.ts +38 -0
- package/dist/ctl/service.js +156 -0
- package/dist/ctl/store.d.ts +51 -0
- package/dist/ctl/store.js +93 -0
- package/dist/ctl/transfers.d.ts +14 -0
- package/dist/ctl/transfers.js +74 -0
- package/dist/ctl/transport.d.ts +11 -0
- package/dist/ctl/transport.js +79 -0
- package/dist/deploy/deploy.d.ts +4 -3
- package/dist/deploy/deploy.js +5 -210
- package/dist/deploy/plan.js +26 -7
- package/dist/dev.js +5 -4
- package/dist/error-registry.js +1020 -831
- package/dist/info.js +120 -28
- package/dist/init.js +188 -64
- package/dist/inspect.js +21 -6
- package/dist/runtime/executor-digests.json +1 -12
- package/dist/skill.js +51 -9
- package/dist/standalone/api-description-inspect.d.ts +4 -0
- package/dist/standalone/api-description-inspect.js +12 -0
- package/dist/standalone/api-description.d.ts +7 -0
- package/dist/standalone/api-description.js +73 -0
- package/dist/standalone/build.d.ts +3 -0
- package/dist/standalone/build.js +140 -33
- package/dist/standalone/compose-errors.d.ts +3 -0
- package/dist/standalone/compose-errors.js +243 -0
- package/dist/standalone/compose.d.ts +97 -10
- package/dist/standalone/compose.js +306 -75
- package/dist/standalone/daemon.js +111 -29
- package/dist/standalone/dsm-window.js +55 -9
- package/dist/standalone/identity-gateway.d.ts +9 -2
- package/dist/standalone/identity-gateway.js +282 -66
- package/dist/standalone/inspect-spk.d.ts +15 -0
- package/dist/standalone/inspect-spk.js +714 -128
- package/dist/standalone/lifecycle.js +124 -42
- package/dist/standalone/manifest.d.ts +10 -1
- package/dist/standalone/manifest.js +308 -87
- package/dist/standalone/resource.js +6 -2
- package/dist/standalone/spk.d.ts +4 -1
- package/dist/standalone/spk.js +139 -39
- package/dist/template-paths.d.ts +0 -2
- package/dist/template-paths.js +0 -5
- package/dist/test-command.d.ts +1 -1
- package/dist/test-command.js +1 -1
- package/dist/validate.d.ts +3 -3
- package/dist/validate.js +32 -140
- package/package.json +5 -3
- package/schema/synology-app.schema.json +1 -1
- package/schema/synopkgland-api-v1.schema.json +423 -0
- package/skills/SKILL.md +34 -1
- package/skills/reference/compose.md +349 -0
- package/skills/reference/create.md +26 -4
- package/skills/reference/ctl.md +41 -0
- package/skills/reference/deploy.md +1 -1
- package/skills/reference/files.md +4 -0
- package/skills/reference/identity-gateway.md +8 -138
- package/skills/reference/portal.md +31 -0
- package/skills/reference/ui-vue.md +31 -0
- package/skills/reference/wrap.md +10 -30
- package/assets/go/daemon/fixture_linux_test.go +0 -15
- package/assets/go/daemon/fixture_other_test.go +0 -7
- package/assets/go/daemon/go.mod +0 -5
- package/assets/go/daemon/main.go +0 -636
- package/assets/go/daemon/main_test.go +0 -315
- package/assets/go/daemon/network.go +0 -107
- package/assets/go/daemon/network_test.go +0 -86
- package/assets/go/daemon/process_linux.go +0 -55
- package/assets/go/daemon/process_linux_test.go +0 -161
- package/assets/go/daemon/process_other.go +0 -14
- package/assets/go/daemon/testdata/child.go +0 -44
- package/assets/go/gateway/auth.go +0 -101
- package/assets/go/gateway/auth_test.go +0 -216
- package/assets/go/gateway/cgi.go +0 -133
- package/assets/go/gateway/cgi_test.go +0 -156
- package/assets/go/gateway/csrf.go +0 -73
- package/assets/go/gateway/csrf_test.go +0 -155
- package/assets/go/gateway/directory.go +0 -108
- package/assets/go/gateway/directory_test.go +0 -202
- package/assets/go/gateway/gateway.go +0 -412
- package/assets/go/gateway/gateway_test.go +0 -676
- package/assets/go/gateway/go.mod +0 -5
- package/assets/go/gateway/groups.go +0 -66
- package/assets/go/gateway/main.go +0 -45
- package/dist/build-spk/gateway.d.ts +0 -31
- package/dist/build-spk/gateway.js +0 -60
- package/dist/compose/privilege.d.ts +0 -14
- package/dist/compose/privilege.js +0 -102
|
@@ -9,10 +9,14 @@ import { portalResource } from "../portal/package.js";
|
|
|
9
9
|
export function renderStandaloneResource(m, portal) {
|
|
10
10
|
const dataShare = m.sharedFolder && sharedFolderResource(m.package, m.sharedFolder);
|
|
11
11
|
const resource = {
|
|
12
|
-
...(m.compose
|
|
12
|
+
...(m.compose && !m.compose.plan.mounts_blocked
|
|
13
|
+
? renderDockerProjectResource(m.package, "compose", false, m.compose.plan.project_name)
|
|
14
|
+
: {}),
|
|
13
15
|
...(dataShare ? { "data-share": dataShare } : {}),
|
|
14
16
|
...(m.capabilities.jobs ? { "systemd-user-unit": {} } : {}),
|
|
15
17
|
...(portal ? portalResource() : {}),
|
|
16
18
|
};
|
|
17
|
-
return Object.keys(resource).length > 0
|
|
19
|
+
return Object.keys(resource).length > 0
|
|
20
|
+
? JSON.stringify(resource)
|
|
21
|
+
: undefined;
|
|
18
22
|
}
|
package/dist/standalone/spk.d.ts
CHANGED
|
@@ -11,10 +11,11 @@ export interface AssembleOptions {
|
|
|
11
11
|
manifest: StandaloneManifest;
|
|
12
12
|
arch: NativeArchFamily;
|
|
13
13
|
/** The compiled or prebuilt CGI executable, already checked. */
|
|
14
|
-
cgiPath
|
|
14
|
+
cgiPath?: string;
|
|
15
15
|
outputPath: string;
|
|
16
16
|
executor?: ExecutorArtifact;
|
|
17
17
|
daemonControl?: string;
|
|
18
|
+
composeControl?: string;
|
|
18
19
|
/** The SDK identity gateway, required exactly when the manifest declares identity_gateway. */
|
|
19
20
|
identityGateway?: string;
|
|
20
21
|
/** The compiled background worker, required exactly when capabilities.jobs is enabled. */
|
|
@@ -23,6 +24,8 @@ export interface AssembleOptions {
|
|
|
23
24
|
app?: string;
|
|
24
25
|
/** The SDK's portal panel text, required exactly when the manifest declares a portal. */
|
|
25
26
|
portalText?: PortalCatalogs;
|
|
27
|
+
apiRegistry?: Record<string, string>;
|
|
28
|
+
apiDescription?: import("../ctl/descriptor.js").Descriptor;
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* Packs an unsigned Standalone Package SPK. DSM runs installation/lifecycle
|
package/dist/standalone/spk.js
CHANGED
|
@@ -1,39 +1,41 @@
|
|
|
1
|
+
import { sdkSource } from "../runtime/sdk-source.js";
|
|
2
|
+
import { resolvedSdkLaunch } from "../contract/app-launch.js";
|
|
1
3
|
import { metadataInfoFields } from "../package-metadata.js";
|
|
2
4
|
import { createHash } from "node:crypto";
|
|
3
5
|
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
4
6
|
import { tmpdir } from "node:os";
|
|
5
7
|
import { dirname, join } from "node:path";
|
|
6
8
|
import { infoArchValue } from "../build-spk/dsm-arch.js";
|
|
7
|
-
import { packageIcons, writeDsmWindowIcons, writePackageCenterIcons } from "../build-spk/dsm-icons.js";
|
|
9
|
+
import { packageIcons, writeDsmWindowIcons, writePackageCenterIcons, } from "../build-spk/dsm-icons.js";
|
|
8
10
|
import { renderNativePrivilege } from "../build-spk/native-lifecycle.js";
|
|
9
11
|
import { renderUninstallWizardFiles } from "../build-spk/uninstall-wizard.js";
|
|
10
|
-
import { renderInstallWizardFiles, renderNativeInstallWizard } from "../build-spk/install-wizard.js";
|
|
11
|
-
import { writeWizardUiFiles } from "../build-spk/wizard-i18n.js";
|
|
12
|
-
import { renderDelegateScopeSteps } from
|
|
12
|
+
import { renderInstallWizardFiles, renderNativeInstallWizard, } from "../build-spk/install-wizard.js";
|
|
13
|
+
import { writeWizardUiFiles, } from "../build-spk/wizard-i18n.js";
|
|
14
|
+
import { renderDelegateScopeSteps } from "../build-spk/delegate-scope.js";
|
|
13
15
|
import { createReproducibleTar } from "../build-spk/reproducible-archive.js";
|
|
14
16
|
import { renderStandaloneScript } from "./lifecycle.js";
|
|
15
17
|
import { APP_SCRIPTS_DIR, APP_SCRIPTS_SPK_DIR } from "./app-scripts.js";
|
|
16
18
|
import { codedMessage } from "../errors.js";
|
|
17
19
|
import { DSM_LANGS, LOCALES } from "./i18n.js";
|
|
18
|
-
import { appTitle, CGI_PAYLOAD_PATH, renderUiConfig, renderWindowScript, windowScriptName } from "./dsm-window.js";
|
|
20
|
+
import { appTitle, CGI_PAYLOAD_PATH, renderUiConfig, renderWindowScript, windowScriptName, } from "./dsm-window.js";
|
|
19
21
|
import { writeUiTexts } from "../build-spk/ui-texts.js";
|
|
20
22
|
import { loginStyleAssets } from "../build-spk/login-style.js";
|
|
21
|
-
import { copyStandaloneNotices } from "../build-spk/notices.js";
|
|
23
|
+
import { copyStandaloneNotices, copyNoticeTree } from "../build-spk/notices.js";
|
|
22
24
|
import { copyAppTree } from "./bridge-app.js";
|
|
23
25
|
import { isBridgeBackend } from "./bridge-backend.js";
|
|
24
26
|
import { hostScriptName, renderHostAdapterScript } from "./host-adapter.js";
|
|
25
|
-
import {
|
|
27
|
+
import { writeComposePayload } from "./compose.js";
|
|
26
28
|
import { copyDaemonPayload } from "./daemon.js";
|
|
27
|
-
import { GATEWAY_CGI_PATH, GATEWAY_CONFIG_PATH, GATEWAY_EXEC, identityGatewayConfig } from "./identity-gateway.js";
|
|
28
|
-
import { BACKGROUND_UNIT_DIR, BACKGROUND_WORKER_PAYLOAD, backgroundUnitName, renderBackgroundUnit, BRIDGE_WORKER_ARGS } from "./background.js";
|
|
29
|
+
import { GATEWAY_CGI_PATH, GATEWAY_CONFIG_PATH, GATEWAY_EXEC, identityGatewayConfig, identityGatewayDeclaration, } from "./identity-gateway.js";
|
|
30
|
+
import { BACKGROUND_UNIT_DIR, BACKGROUND_WORKER_PAYLOAD, backgroundUnitName, renderBackgroundUnit, BRIDGE_WORKER_ARGS, } from "./background.js";
|
|
29
31
|
import { renderStandaloneResource } from "./resource.js";
|
|
30
32
|
import { nativePortal } from "./portal.js";
|
|
31
33
|
import { portalByExecutor } from "../build-spk/dsm-version.js";
|
|
32
|
-
import { PORTAL_JSON_RELPATH, PORTAL_MUSTACHE_RELPATH, renderPortalJson, renderPortalMustache, renderPortalWizardStep } from "../portal/package.js";
|
|
33
|
-
import { executorPlan } from "../runtime/executor-build.js";
|
|
34
|
+
import { PORTAL_JSON_RELPATH, PORTAL_MUSTACHE_RELPATH, renderPortalJson, renderPortalMustache, renderPortalWizardStep, } from "../portal/package.js";
|
|
35
|
+
import { executorPlan, } from "../runtime/executor-build.js";
|
|
34
36
|
import { EXECUTOR_ANCHOR } from "../runtime/executor-inspect.js";
|
|
35
|
-
import { renderSharedFolderUpgradeWizardFiles, sharedFolderInstallSteps } from "./shared-folder.js";
|
|
36
|
-
import { stateItemTexts, uninstallExecProblem, uninstallRecord } from "./uninstall.js";
|
|
37
|
+
import { renderSharedFolderUpgradeWizardFiles, sharedFolderInstallSteps, } from "./shared-folder.js";
|
|
38
|
+
import { stateItemTexts, uninstallExecProblem, uninstallRecord, } from "./uninstall.js";
|
|
37
39
|
const FILE_MODE = 0o644;
|
|
38
40
|
const EXEC_MODE = 0o755;
|
|
39
41
|
const MAX_UI_BYTES = 64 * 1024 * 1024;
|
|
@@ -62,26 +64,39 @@ export async function assembleStandaloneSpk(options) {
|
|
|
62
64
|
const { manifest } = options;
|
|
63
65
|
const staging = mkdtempSync(join(tmpdir(), "syno-standalone-spk-"));
|
|
64
66
|
try {
|
|
65
|
-
if ((manifest.capabilities.rootOps ||
|
|
67
|
+
if ((manifest.capabilities.rootOps ||
|
|
68
|
+
manifest.capabilities.delegate ||
|
|
69
|
+
manifest.capabilities.webapi) &&
|
|
70
|
+
!options.executor)
|
|
66
71
|
throw new StandaloneBuildError("EXECUTOR_PIN_REQUIRED", "Executor artifact required.");
|
|
67
72
|
const icons = packageIcons(manifest.icon);
|
|
68
73
|
const uiRoot = join(staging, "package", "ui");
|
|
69
74
|
const appDir = join(uiRoot, "app");
|
|
70
|
-
|
|
75
|
+
if (!manifest.compose)
|
|
76
|
+
copyUiTree(manifest.ui.dir, appDir);
|
|
77
|
+
else
|
|
78
|
+
mkdirSync(appDir, { recursive: true });
|
|
71
79
|
const assetVersion = digestTree(appDir).slice(0, 12);
|
|
72
|
-
const login = manifest.portal
|
|
80
|
+
const login = manifest.portal
|
|
81
|
+
? loginStyleAssets(manifest.loginStyle)
|
|
82
|
+
: undefined;
|
|
73
83
|
writeFile(join(uiRoot, "config"), renderUiConfig(manifest, icons, login?.fields));
|
|
74
84
|
for (const [path, bytes] of login?.files ?? [])
|
|
75
85
|
writeFile(join(uiRoot, path), bytes);
|
|
76
|
-
|
|
86
|
+
if (!manifest.compose)
|
|
87
|
+
writeFile(join(uiRoot, windowScriptName(manifest)), renderWindowScript(manifest, assetVersion, icons, options.portalText));
|
|
77
88
|
writeFile(join(uiRoot, hostScriptName(manifest.build)), renderHostAdapterScript(manifest.package));
|
|
78
89
|
writeDsmWindowIcons(uiRoot, icons);
|
|
79
90
|
writeUiTexts(uiRoot, appTitle(manifest));
|
|
91
|
+
if (options.apiDescription)
|
|
92
|
+
writeFile(join(staging, "package", "synopkgland-api.json"), JSON.stringify(options.apiDescription) + "\n");
|
|
80
93
|
const cgiTarget = join(staging, "package", CGI_PAYLOAD_PATH);
|
|
81
94
|
mkdirSync(dirname(cgiTarget), { recursive: true });
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
if (options.cgiPath) {
|
|
96
|
+
copyFileSync(options.cgiPath, cgiTarget);
|
|
97
|
+
chmodSync(cgiTarget, EXEC_MODE);
|
|
98
|
+
}
|
|
99
|
+
if (manifest.daemon || (manifest.compose && manifest.identityGateway)) {
|
|
85
100
|
if (!options.daemonControl)
|
|
86
101
|
throw new StandaloneBuildError("STANDALONE_DAEMON_ARTIFACT_REQUIRED", "daemon controller artifact required.");
|
|
87
102
|
copyDaemonPayload(manifest, options.arch, join(staging, "package", "daemon"), options.daemonControl);
|
|
@@ -89,7 +104,11 @@ export async function assembleStandaloneSpk(options) {
|
|
|
89
104
|
if (manifest.identityGateway) {
|
|
90
105
|
if (!options.identityGateway)
|
|
91
106
|
throw new StandaloneBuildError("STANDALONE_IDENTITY_GATEWAY_ARTIFACT_REQUIRED", "identity gateway artifact required.");
|
|
92
|
-
for (const path of [
|
|
107
|
+
for (const path of [
|
|
108
|
+
GATEWAY_CGI_PATH,
|
|
109
|
+
"ui/sso/open.cgi",
|
|
110
|
+
`daemon/payload/${GATEWAY_EXEC}`,
|
|
111
|
+
]) {
|
|
93
112
|
const target = join(staging, "package", path);
|
|
94
113
|
mkdirSync(dirname(target), { recursive: true });
|
|
95
114
|
copyFileSync(options.identityGateway, target);
|
|
@@ -112,10 +131,33 @@ export async function assembleStandaloneSpk(options) {
|
|
|
112
131
|
copyAppTree(options.app, join(staging, "package", "app"));
|
|
113
132
|
}
|
|
114
133
|
// A daemon is a Go program of the project's backend directory.
|
|
115
|
-
const sdk = backend.kind !==
|
|
116
|
-
|
|
134
|
+
const sdk = backend.kind !== "binary"
|
|
135
|
+
? backend
|
|
136
|
+
: manifest.daemon
|
|
137
|
+
? { kind: "go", dir: join(manifest.root, "backend") }
|
|
138
|
+
: undefined;
|
|
139
|
+
copyStandaloneNotices(manifest.root, manifest.ui.dir, join(staging, "package"), {
|
|
140
|
+
go: backend.kind !== "binary" ||
|
|
141
|
+
Boolean(manifest.daemon || manifest.compose),
|
|
142
|
+
rust: backend.kind === "rust",
|
|
143
|
+
sdk,
|
|
144
|
+
});
|
|
145
|
+
if (manifest.compose) {
|
|
146
|
+
const source = sdkSource(manifest.compose.sdk);
|
|
147
|
+
copyNoticeTree(join(source.goDir, "LICENSE"), join(staging, "package/LICENSES/SDK.txt"));
|
|
148
|
+
copyNoticeTree(join(source.goDir, "compose/THIRD_PARTY_NOTICES.txt"), join(staging, "package/LICENSES/COMPOSE-THIRD_PARTY_NOTICES.txt"));
|
|
149
|
+
const nativeAdmin = join(source.goDir, "dsm/native");
|
|
150
|
+
for (const name of [
|
|
151
|
+
"admin_check.c",
|
|
152
|
+
"apppriv_check.c",
|
|
153
|
+
"build.sh",
|
|
154
|
+
"provenance.json",
|
|
155
|
+
"licenses",
|
|
156
|
+
])
|
|
157
|
+
copyNoticeTree(join(nativeAdmin, name), join(staging, "package/LICENSES/native-admin", name));
|
|
158
|
+
}
|
|
117
159
|
if (manifest.daemon)
|
|
118
|
-
copyFileSync(new URL(
|
|
160
|
+
copyFileSync(new URL("../../assets/licenses/TEMPLATE.txt", import.meta.url), join(staging, "package", "LICENSES", "DAEMON-LICENSE.txt"));
|
|
119
161
|
const portal = nativePortal(manifest);
|
|
120
162
|
if (portal) {
|
|
121
163
|
writeFile(join(staging, "package", PORTAL_JSON_RELPATH), renderPortalJson(portal));
|
|
@@ -137,16 +179,48 @@ export async function assembleStandaloneSpk(options) {
|
|
|
137
179
|
copyExecutable(op.binary[options.arch].path, join(staging, "package", "root", op.name));
|
|
138
180
|
}
|
|
139
181
|
}
|
|
140
|
-
const hook = manifest.uninstall &&
|
|
182
|
+
const hook = manifest.uninstall &&
|
|
183
|
+
uninstallExecProblem(join(staging, "package"), manifest.uninstall.purge);
|
|
141
184
|
if (hook)
|
|
142
185
|
throw new StandaloneBuildError(hook.code, hook.message);
|
|
143
|
-
if (manifest.compose)
|
|
144
|
-
|
|
186
|
+
if (manifest.compose) {
|
|
187
|
+
const target = join(staging, "package/compose");
|
|
188
|
+
writeComposePayload(manifest.compose, manifest.root, target, identityGatewayDeclaration(manifest.identityGateway));
|
|
189
|
+
if (!options.composeControl)
|
|
190
|
+
throw new StandaloneBuildError("SDK_COMPOSE_BUILD_FAILED", "Compose runtime artifact required.");
|
|
191
|
+
copyFileSync(options.composeControl, join(target, "control"));
|
|
192
|
+
chmodSync(join(target, "control"), EXEC_MODE);
|
|
193
|
+
}
|
|
145
194
|
const packageTgz = join(staging, "package.tgz");
|
|
146
|
-
await createReproducibleTar(join(staging, "package"), packageTgz, {
|
|
195
|
+
await createReproducibleTar(join(staging, "package"), packageTgz, {
|
|
196
|
+
gzip: true,
|
|
197
|
+
});
|
|
147
198
|
rmSync(join(staging, "package"), { recursive: true, force: true });
|
|
148
|
-
const checksum = createHash("md5")
|
|
149
|
-
|
|
199
|
+
const checksum = createHash("md5")
|
|
200
|
+
.update(readFileSync(packageTgz))
|
|
201
|
+
.digest("hex");
|
|
202
|
+
writeFile(join(staging, "sdk-package.json"), JSON.stringify({
|
|
203
|
+
schema: 2,
|
|
204
|
+
...(options.apiDescription ? { ctl: { hash: options.apiDescription.sha256, registry: options.apiRegistry } } : {}),
|
|
205
|
+
backend: manifest.backend.kind,
|
|
206
|
+
launch: resolvedSdkLaunch(manifest),
|
|
207
|
+
capabilities: Object.keys(manifest.capabilities).sort(),
|
|
208
|
+
...(manifest.compose
|
|
209
|
+
? {
|
|
210
|
+
compose: {
|
|
211
|
+
sdk: manifest.compose.sdk,
|
|
212
|
+
declaration: manifest.compose.declaration,
|
|
213
|
+
gateway: identityGatewayDeclaration(manifest.identityGateway),
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
: {}),
|
|
217
|
+
...(manifest.entry ? { entry: manifest.entry } : {}),
|
|
218
|
+
...(manifest.uninstall
|
|
219
|
+
? {
|
|
220
|
+
uninstall: uninstallRecord(manifest.uninstall, manifest.i18n?.catalogs),
|
|
221
|
+
}
|
|
222
|
+
: {}),
|
|
223
|
+
}));
|
|
150
224
|
writeFile(join(staging, "INFO"), renderInfo(manifest, options.arch, checksum, options.executor?.executorDigest));
|
|
151
225
|
mkdirSync(join(staging, "conf"));
|
|
152
226
|
writeFile(join(staging, "conf", "privilege"), renderNativePrivilege());
|
|
@@ -164,18 +238,37 @@ export async function assembleStandaloneSpk(options) {
|
|
|
164
238
|
for (const name of manifest.appScripts ?? [])
|
|
165
239
|
copyExecutable(join(manifest.root, APP_SCRIPTS_DIR, name), join(staging, APP_SCRIPTS_SPK_DIR, name));
|
|
166
240
|
const folder = manifest.sharedFolder;
|
|
167
|
-
const steps = manifest.capabilities.delegate?.scope ===
|
|
168
|
-
? (t) => [
|
|
169
|
-
|
|
170
|
-
|
|
241
|
+
const steps = manifest.capabilities.delegate?.scope === "install-selected"
|
|
242
|
+
? (t) => [
|
|
243
|
+
...renderDelegateScopeSteps(t),
|
|
244
|
+
...(folder
|
|
245
|
+
? sharedFolderInstallSteps(manifest.package, folder)(t)
|
|
246
|
+
: []),
|
|
247
|
+
]
|
|
248
|
+
: folder
|
|
249
|
+
? sharedFolderInstallSteps(manifest.package, folder)
|
|
250
|
+
: renderNativeInstallWizard;
|
|
251
|
+
const install = renderInstallWizardFiles(manifest.package, portal
|
|
252
|
+
? (t) => [...steps(t), renderPortalWizardStep(t, portal.alias)]
|
|
253
|
+
: steps);
|
|
171
254
|
writeWizardUiFiles(staging, {
|
|
172
255
|
...install.static,
|
|
173
256
|
...install.dynamic,
|
|
174
|
-
...(folder
|
|
257
|
+
...(folder
|
|
258
|
+
? renderSharedFolderUpgradeWizardFiles(manifest.package, folder)
|
|
259
|
+
: {}),
|
|
175
260
|
...renderUninstallWizardFiles(manifest.package, "native", {
|
|
176
|
-
executor: Boolean(options.executor),
|
|
261
|
+
executor: Boolean(options.executor),
|
|
262
|
+
portal: Boolean(portal),
|
|
263
|
+
portalEntry: Boolean(manifest.portal && portalByExecutor(manifest.os_min_ver)),
|
|
177
264
|
rootPurge: Boolean(manifest.uninstall?.purge.root),
|
|
178
|
-
...(manifest.uninstall?.state
|
|
265
|
+
...(manifest.uninstall?.state
|
|
266
|
+
? {
|
|
267
|
+
state: {
|
|
268
|
+
items: (locale) => stateItemTexts(manifest.uninstall, manifest.i18n?.catalogs, locale),
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
: {}),
|
|
179
272
|
}),
|
|
180
273
|
});
|
|
181
274
|
writePackageCenterIcons(staging, icons);
|
|
@@ -218,8 +311,15 @@ function renderInfo(manifest, arch, checksum, executorDigest) {
|
|
|
218
311
|
// Package Center writes INFO as root; root tasks accept only this executor.
|
|
219
312
|
if (executorDigest)
|
|
220
313
|
fields.push([EXECUTOR_ANCHOR, executorDigest]);
|
|
221
|
-
if (manifest.compose)
|
|
222
|
-
|
|
314
|
+
if (manifest.compose) {
|
|
315
|
+
// DSM dependency status can fail while the official docker-project worker is available.
|
|
316
|
+
// Let that worker check availability; never start or repair Container Manager here.
|
|
317
|
+
if (manifest.identityGateway)
|
|
318
|
+
fields.push([
|
|
319
|
+
"adminurl",
|
|
320
|
+
`/webman/3rdparty/${manifest.package}/sso/open.cgi`,
|
|
321
|
+
]);
|
|
322
|
+
}
|
|
223
323
|
const backend = manifest.backend;
|
|
224
324
|
if (isBridgeBackend(backend) && backend.node)
|
|
225
325
|
fields.push(["install_dep_packages", backend.node.package]);
|
package/dist/template-paths.d.ts
CHANGED
|
@@ -3,5 +3,3 @@
|
|
|
3
3
|
* package; SYNOPKGLAND_TEMPLATES_DIR points at a templates checkout instead.
|
|
4
4
|
*/
|
|
5
5
|
export declare function templatesRoot(env?: NodeJS.ProcessEnv, from?: string): string;
|
|
6
|
-
/** Go sources the CLI builds into every SPK that needs them; they are not scaffolded. */
|
|
7
|
-
export declare function goAssetDir(name: "daemon" | "gateway"): string;
|
package/dist/template-paths.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
3
|
/**
|
|
5
4
|
* The scaffold templates come from the exactly pinned @synopackageland/templates
|
|
6
5
|
* package; SYNOPKGLAND_TEMPLATES_DIR points at a templates checkout instead.
|
|
@@ -15,7 +14,3 @@ export function templatesRoot(env = process.env, from = import.meta.url) {
|
|
|
15
14
|
throw new Error("[TEMPLATES_MISSING] @synopackageland/templates is not installed; reinstall @synopackageland/cli, or set SYNOPKGLAND_TEMPLATES_DIR to a templates checkout.");
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
|
-
/** Go sources the CLI builds into every SPK that needs them; they are not scaffolded. */
|
|
19
|
-
export function goAssetDir(name) {
|
|
20
|
-
return fileURLToPath(new URL(`../assets/go/${name}`, import.meta.url));
|
|
21
|
-
}
|
package/dist/test-command.d.ts
CHANGED
|
@@ -10,4 +10,4 @@ export interface TestProjectResult {
|
|
|
10
10
|
errors: string[];
|
|
11
11
|
}
|
|
12
12
|
export declare function testProject(options: TestProjectOptions): Promise<TestProjectResult>;
|
|
13
|
-
export { materializeComposePaths, composeEnvDirsExist, prepareComposeEnvironment } from "./compose/env.js";
|
|
13
|
+
export { materializeComposePaths, composeEnvDirsExist, prepareComposeEnvironment, } from "./compose/env.js";
|
package/dist/test-command.js
CHANGED
|
@@ -23,4 +23,4 @@ export async function testProject(options) {
|
|
|
23
23
|
}
|
|
24
24
|
return { ok: true, composeInvoked: true, errors: [] };
|
|
25
25
|
}
|
|
26
|
-
export { materializeComposePaths, composeEnvDirsExist, prepareComposeEnvironment } from "./compose/env.js";
|
|
26
|
+
export { materializeComposePaths, composeEnvDirsExist, prepareComposeEnvironment, } from "./compose/env.js";
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import type { ProjectRoot } from "./discovery.js";
|
|
1
2
|
import type { ValidationResult } from "./types.js";
|
|
2
|
-
import { type ProjectRoot } from "./discovery.js";
|
|
3
3
|
export type AnalyzerProfile = "personal" | "listing";
|
|
4
|
-
|
|
4
|
+
/** All shipped Compose and native projects use the schema-2 build path. */
|
|
5
|
+
export declare function validateProject(project: ProjectRoot, _options?: {
|
|
5
6
|
profile?: AnalyzerProfile;
|
|
6
7
|
}): ValidationResult;
|
|
7
8
|
export { findProjectRoot } from "./discovery.js";
|
|
8
|
-
export type { ProjectRoot } from "./discovery.js";
|
package/dist/validate.js
CHANGED
|
@@ -1,149 +1,41 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { contractSpan, makeError } from "./errors.js";
|
|
4
3
|
import { getErrorCode } from "./error-registry.js";
|
|
5
|
-
import { collectInstallParameters, getValuePosition, loadContract } from "./contract/load.js";
|
|
6
|
-
import { validateContract } from "./contract/validate.js";
|
|
7
|
-
import { findListingUnsupportedInstallTypes } from "./contract/unsupported-types.js";
|
|
8
|
-
import { classifyComposeRoute, collectComposeInterpolations, collectVolumeSourceInterpolations, loadCompose, validateAdminPortMapping, validateComposeHostBinds, validateComposeNumericCommandArgs, validateComposeRunAs, validateComposeSecurity, validateGatewayPortLoopback, } from "./compose/analyze.js";
|
|
9
|
-
import { usesAuthGateway } from "./contract/web-launch.js";
|
|
10
|
-
import { isNativeLayout, loadNativeLayout } from "./standalone/native-layout.js";
|
|
11
4
|
import { loadStandaloneManifest } from "./standalone/manifest.js";
|
|
12
|
-
import {
|
|
5
|
+
import { isNativeLayout, loadNativeLayout, } from "./standalone/native-layout.js";
|
|
13
6
|
import { manifestPolicyProblem } from "./runtime/executor-policy-check.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
code: "PROJECT_RUNTIME_EXCLUSIVE",
|
|
27
|
-
span: { file: DSM_PACKAGE_FILENAME, line: 1, column: 1 },
|
|
28
|
-
message: "The project has both compose.yaml and dsm-package.yaml, which are mutually exclusive.",
|
|
29
|
-
suggestion: "Choose compose.yaml (container App) or dsm-package.yaml (native App) and delete the other file.",
|
|
30
|
-
}] };
|
|
31
|
-
}
|
|
32
|
-
if (hasStandalone) {
|
|
33
|
-
const loaded = isNativeLayout(project.root) ? loadNativeLayout(project.root) : loadStandaloneManifest(project.root);
|
|
34
|
-
const policy = loaded.ok && "capabilities" in loaded.manifest ? manifestPolicyProblem(loaded.manifest) : undefined;
|
|
35
|
-
const problems = loaded.ok ? (policy ? [policy] : []) : loaded.errors;
|
|
36
|
-
return !problems.length ? { ok: true, errors: [] } : { ok: false, errors: problems.map(error => ({
|
|
37
|
-
...error, span: { file: DSM_PACKAGE_FILENAME, line: 1, column: 1 },
|
|
38
|
-
suggestion: getErrorCode(error.code)?.suggestion ?? "Fix the schema 2 fields and capability declarations in dsm-package.yaml.",
|
|
39
|
-
})) };
|
|
40
|
-
}
|
|
41
|
-
const parsed = loadContract(project.contractPath);
|
|
42
|
-
errors.push(...validateContract(parsed, contractFile));
|
|
43
|
-
const capabilities = parsed.contract.capabilities;
|
|
44
|
-
if (capabilities !== undefined) {
|
|
45
|
-
errors.push(makeError("CAPABILITY_UNSUPPORTED", contractSpan(contractFile, 1, 1, "capabilities"), "A compose project does not support capabilities.", "Remove capabilities, or use a dsm-package.yaml native-go project."));
|
|
46
|
-
}
|
|
47
|
-
readAppScripts(project.root, e => errors.push({ ...e, span: { file: APP_SCRIPTS_DIR, line: 1, column: 1 }, suggestion: getErrorCode(e.code).suggestion }));
|
|
48
|
-
if (profile === "listing") {
|
|
49
|
-
for (const { name, type, code } of findListingUnsupportedInstallTypes(parsed.contract)) {
|
|
50
|
-
const pos = getValuePosition(parsed.doc, ["install", name, "type"]) ??
|
|
51
|
-
getValuePosition(parsed.doc, ["install", name]) ??
|
|
52
|
-
{ line: 1, column: 1 };
|
|
53
|
-
errors.push(makeError(code, contractSpan(contractFile, pos.line, pos.column, `install.${name}.type`), `The Install Parameter "${name}" with type: ${type} can only be snapshotted by synopkgland deploy with an administrator session at install time; Package Center and Package Source listing do not support it.`, "Use synopkgland deploy --host <name> so an administrator session takes the Share list snapshot at install time; this type cannot ship through Package Center or Package Source listing."));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (!hasCompose) {
|
|
57
|
-
errors.push({
|
|
58
|
-
code: "COMPOSE_MISSING",
|
|
59
|
-
span: { file: composeFile, line: 1, column: 1 },
|
|
60
|
-
message: "compose.yaml or dsm-package.yaml was not found.",
|
|
61
|
-
suggestion: "Create compose.yaml or a schema 2 dsm-package.yaml in the project root.",
|
|
62
|
-
});
|
|
63
|
-
return { ok: false, errors };
|
|
64
|
-
}
|
|
65
|
-
const parsedCompose = loadCompose(project.composePath);
|
|
66
|
-
if (!parsedCompose) {
|
|
67
|
-
return { ok: false, errors };
|
|
68
|
-
}
|
|
69
|
-
errors.push(...validateComposeSecurity(parsedCompose.compose, composeFile, {
|
|
70
|
-
profile,
|
|
71
|
-
declaredPrivilege: declaredPrivilegeFlags(parsed.contract),
|
|
72
|
-
}));
|
|
73
|
-
if (classifyComposeRoute(parsed.contract, parsedCompose, composeFile).route !== "reject") {
|
|
74
|
-
errors.push(...validateComposeNumericCommandArgs(parsedCompose.compose, composeFile));
|
|
75
|
-
}
|
|
76
|
-
const installParams = collectInstallParameters(parsed.contract);
|
|
77
|
-
const sharedFolderParams = new Set(installParams.filter(({ param }) => param.type === "shared_folder").map(({ name }) => name));
|
|
78
|
-
const homesParams = new Set(installParams.filter(({ param }) => param.type === "homes").map(({ name }) => name));
|
|
79
|
-
const posixAllSharesParams = new Set(installParams
|
|
80
|
-
.filter(({ param }) => param.type === "posix_all_shares")
|
|
81
|
-
.map(({ name }) => name));
|
|
82
|
-
const installParamNames = new Set(installParams.map(({ name }) => name));
|
|
83
|
-
errors.push(...validateComposeHostBinds(parsedCompose.compose, composeFile, {
|
|
84
|
-
sharedFolderParams,
|
|
85
|
-
homesParams,
|
|
86
|
-
posixAllSharesParams,
|
|
87
|
-
installParamNames,
|
|
88
|
-
}));
|
|
89
|
-
if (typeof parsed.contract.adminport === "number") {
|
|
90
|
-
errors.push(...validateAdminPortMapping(parsed.contract.adminport, parsedCompose.compose, composeFile));
|
|
91
|
-
}
|
|
92
|
-
if (typeof parsed.contract.adminport === "number" && usesAuthGateway(parsed.contract)) {
|
|
93
|
-
errors.push(...validateGatewayPortLoopback(parsed.contract.adminport, parsedCompose.compose, composeFile));
|
|
94
|
-
}
|
|
95
|
-
const interpolations = collectComposeInterpolations(parsedCompose.compose);
|
|
96
|
-
const volumeSourceVars = collectVolumeSourceInterpolations(parsedCompose.compose);
|
|
97
|
-
const dsmUserParams = installParams.filter(({ param }) => param.type === "dsm_user");
|
|
98
|
-
const hasSharedFolder = sharedFolderParams.size > 0;
|
|
99
|
-
if (dsmUserParams.length > 1) {
|
|
100
|
-
errors.push({
|
|
101
|
-
code: "CONTRACT_DSM_USER_DUPLICATE",
|
|
102
|
-
span: { file: contractFile, line: 1, column: 1, path: "install" },
|
|
103
|
-
message: "Each App has at most one dsm_user.",
|
|
104
|
-
suggestion: "Keep only one Container Run-as parameter.",
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
if (hasSharedFolder && dsmUserParams.length === 0) {
|
|
108
|
-
errors.push({
|
|
109
|
-
code: "DSM_USER_REQUIRED",
|
|
110
|
-
span: { file: contractFile, line: 1, column: 1, path: "install" },
|
|
111
|
-
message: "With a Shared Folder, declare exactly one dsm_user as the Container Run-as.",
|
|
112
|
-
suggestion: "Add install.<NAME>.type: dsm_user and reference ${NAME_UID}:${NAME_GID} in Compose.",
|
|
113
|
-
severity: profile === "listing" ? "error" : "warning",
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
errors.push(...validateComposeRunAs(parsedCompose.compose, composeFile, {
|
|
117
|
-
hasSharedFolder,
|
|
118
|
-
hasDsmUser: dsmUserParams.length > 0,
|
|
119
|
-
}).map((item) => item.code === "COMPOSE_USER_ROOT_FORBIDDEN"
|
|
120
|
-
? { ...item, severity: (profile === "listing" ? "error" : "warning") }
|
|
121
|
-
: item));
|
|
122
|
-
for (const { name, param } of installParams) {
|
|
123
|
-
const referenced = param.type === "shared_folder" || param.type === "homes" || param.type === "posix_all_shares"
|
|
124
|
-
? volumeSourceVars.has(name)
|
|
125
|
-
: interpolations.has(name);
|
|
126
|
-
if ((param.type === "string" ||
|
|
127
|
-
param.type === "boolean" ||
|
|
128
|
-
param.type === "shared_folder" ||
|
|
129
|
-
param.type === "homes" ||
|
|
130
|
-
param.type === "posix_all_shares") &&
|
|
131
|
-
!referenced) {
|
|
132
|
-
errors.push({
|
|
133
|
-
code: "INSTALL_PARAMETER_UNREFERENCED",
|
|
134
|
-
span: {
|
|
135
|
-
file: contractFile,
|
|
136
|
-
line: 1,
|
|
137
|
-
column: 1,
|
|
138
|
-
path: `install.${name}`,
|
|
7
|
+
/** All shipped Compose and native projects use the schema-2 build path. */
|
|
8
|
+
export function validateProject(project, _options = {}) {
|
|
9
|
+
const file = "dsm-package.yaml";
|
|
10
|
+
if (!existsSync(join(project.root, file)))
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
errors: [
|
|
14
|
+
{
|
|
15
|
+
code: "COMPOSE_LEGACY_MANIFEST_REMOVED",
|
|
16
|
+
span: { file: "synology-app.yaml", line: 1, column: 1 },
|
|
17
|
+
message: "The legacy synology-app.yaml Compose route has been removed.",
|
|
18
|
+
suggestion: "Use init -t compose and declare DSM differences in schema-2 dsm-package.yaml; keep upstream Compose unchanged.",
|
|
139
19
|
},
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
const loaded = isNativeLayout(project.root)
|
|
23
|
+
? loadNativeLayout(project.root)
|
|
24
|
+
: loadStandaloneManifest(project.root);
|
|
25
|
+
const policy = loaded.ok && "capabilities" in loaded.manifest
|
|
26
|
+
? manifestPolicyProblem(loaded.manifest)
|
|
27
|
+
: undefined;
|
|
28
|
+
const problems = loaded.ok ? (policy ? [policy] : []) : loaded.errors;
|
|
29
|
+
return problems.length
|
|
30
|
+
? {
|
|
31
|
+
ok: false,
|
|
32
|
+
errors: problems.map((error) => ({
|
|
33
|
+
...error,
|
|
34
|
+
span: { file, line: 1, column: 1 },
|
|
35
|
+
suggestion: getErrorCode(error.code)?.suggestion ??
|
|
36
|
+
"Correct the schema-2 declaration; see the SDK Compose support matrix.",
|
|
37
|
+
})),
|
|
143
38
|
}
|
|
144
|
-
|
|
145
|
-
const warnings = errors.filter((item) => item.severity === "warning");
|
|
146
|
-
const hardErrors = errors.filter((item) => item.severity !== "warning");
|
|
147
|
-
return { ok: hardErrors.length === 0, errors: hardErrors, warnings };
|
|
39
|
+
: { ok: true, errors: [] };
|
|
148
40
|
}
|
|
149
41
|
export { findProjectRoot } from "./discovery.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Syno Package Land Developer Kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,16 +18,18 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "rm -rf dist && tsc",
|
|
21
|
-
"test": "npm run build && node --import tsx --test test
|
|
21
|
+
"test": "npm run build && node --import tsx --test test/*.test.ts test/ctl/*.test.ts",
|
|
22
22
|
"test:scripts": "node --test scripts/test/gate.test.mjs scripts/test/release.test.mjs scripts/test/pack-docker.test.mjs scripts/test/npm-pack.test.mjs scripts/test/executor-bump.test.mjs scripts/test/smoke-exports.test.mjs scripts/test/executor-digests.test.mjs scripts/test/parallel.test.mjs scripts/test/scaffold-matrix.test.mjs scripts/test/frontend-deps.test.mjs scripts/test/docker-gate.test.mjs scripts/test/gate-plan.test.mjs scripts/test/gates.test.mjs",
|
|
23
23
|
"gates": "bash scripts/ci/gates.sh",
|
|
24
24
|
"gates:fast": "bash scripts/ci/gates.sh --fast",
|
|
25
25
|
"prepublishOnly": "npm run build"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@
|
|
28
|
+
"@napi-rs/keyring": "^2.1.1",
|
|
29
|
+
"@synopackageland/templates": "0.15.0",
|
|
29
30
|
"ajv": "^8.17.1",
|
|
30
31
|
"commander": "^13.1.0",
|
|
32
|
+
"json-canonicalize": "^3.0.1",
|
|
31
33
|
"tar": "^7.4.3",
|
|
32
34
|
"yaml": "^2.7.0"
|
|
33
35
|
},
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"dsm-window",
|
|
123
123
|
"new-page"
|
|
124
124
|
],
|
|
125
|
-
"description": "Web App Launch Mode. dsm-window opens a DSM Application Window at the Trusted App Entry; new-page
|
|
125
|
+
"description": "Web App Launch Mode. dsm-window opens a DSM Application Window at the Trusted App Entry; new-page opens a new tab from the main menu and Package Center. Omit for new-page. Headless apps omit this field."
|
|
126
126
|
},
|
|
127
127
|
"cookies": {
|
|
128
128
|
"type": "array",
|