@synopackageland/cli 0.3.10 → 0.4.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/README.md +20 -9
- package/dist/bin.js +1 -2
- package/dist/build-spk/generator.d.ts +0 -3
- package/dist/build-spk/generator.js +135 -287
- package/dist/build-spk/native-lifecycle.js +0 -3
- package/dist/build-spk/native-payload.js +0 -1
- package/dist/cli.js +171 -151
- package/dist/compose/analyze.d.ts +1 -2
- package/dist/compose/analyze.js +5 -48
- package/dist/contract/unsupported-types.d.ts +10 -0
- package/dist/contract/unsupported-types.js +49 -0
- package/dist/contract/validate.js +25 -2
- package/dist/contract/web-launch.js +0 -2
- package/dist/deploy/deploy.d.ts +1 -4
- package/dist/deploy/deploy.js +45 -156
- package/dist/deploy/homes-values.js +1 -1
- package/dist/deploy/plan.d.ts +18 -0
- package/dist/deploy/plan.js +54 -0
- package/dist/deploy/spk-version.d.ts +2 -14
- package/dist/deploy/spk-version.js +25 -111
- package/dist/dev.d.ts +1 -3
- package/dist/dev.js +14 -28
- package/dist/error-registry.js +810 -862
- package/dist/host/dsm-client.js +12 -272
- package/dist/host/dsm-port-profile.d.ts +0 -1
- package/dist/host/dsm-port-profile.js +0 -2
- package/dist/host/types.d.ts +1 -83
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -6
- package/dist/info.d.ts +0 -1
- package/dist/info.js +3 -10
- package/dist/init.d.ts +11 -1
- package/dist/init.js +112 -4
- package/dist/log.js +2 -3
- package/dist/native/validate.js +0 -1
- package/dist/standalone/binary.d.ts +9 -0
- package/dist/standalone/binary.js +67 -0
- package/dist/standalone/build.d.ts +25 -0
- package/dist/standalone/build.js +108 -0
- package/dist/standalone/dsm-window.d.ts +26 -0
- package/dist/standalone/dsm-window.js +251 -0
- package/dist/standalone/helper.d.ts +31 -0
- package/dist/standalone/helper.js +104 -0
- package/dist/standalone/i18n.d.ts +28 -0
- package/dist/standalone/i18n.js +161 -0
- package/dist/standalone/inspect-spk.d.ts +21 -0
- package/dist/standalone/inspect-spk.js +160 -0
- package/dist/standalone/manifest.d.ts +71 -0
- package/dist/standalone/manifest.js +276 -0
- package/dist/standalone/root-messages.d.ts +7 -0
- package/dist/standalone/root-messages.js +250 -0
- package/dist/standalone/root-ops.d.ts +42 -0
- package/dist/standalone/root-ops.js +101 -0
- package/dist/standalone/root-window.d.ts +35 -0
- package/dist/standalone/root-window.js +290 -0
- package/dist/standalone/spk.d.ts +22 -0
- package/dist/standalone/spk.js +205 -0
- package/dist/templates/binary/synology-app.yaml +7 -0
- package/dist/templates/hello-files/compose.yaml +0 -1
- package/dist/templates/hello-web/compose.yaml +13 -10
- package/dist/templates/hello-web/html/app.js +1 -23
- package/dist/templates/hello-web/html/index.html +1 -3
- package/dist/templates/hello-web-page/compose.yaml +13 -10
- package/dist/templates/hello-web-page/html/app.js +1 -23
- package/dist/templates/hello-web-page/html/index.html +1 -3
- package/dist/templates/native-go/README.md +268 -0
- package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
- package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/dist/templates/native-go/backend/go.mod +3 -0
- package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
- package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
- package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/dist/templates/native-go/backend/internal/api/server.go +155 -0
- package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
- package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
- package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
- package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/dist/templates/native-go/dsm-package.yaml +58 -0
- package/dist/templates/native-go/gitignore +1 -0
- package/dist/templates/native-go/i18n/glossary.json +662 -0
- package/dist/templates/native-go/i18n/messages/de.json +28 -0
- package/dist/templates/native-go/i18n/messages/en.json +28 -0
- package/dist/templates/native-go/i18n/messages/es.json +28 -0
- package/dist/templates/native-go/i18n/messages/fr.json +28 -0
- package/dist/templates/native-go/i18n/messages/it.json +28 -0
- package/dist/templates/native-go/i18n/messages/ja.json +28 -0
- package/dist/templates/native-go/i18n/messages/ko.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/dist/templates/native-go/ui/app.css +35 -0
- package/dist/templates/native-go/ui/app.js +94 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
- package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
- package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/dist/templates/native-go-better-auth/node/package.json +1 -0
- package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/dist/templates/native-go-root/root/whoami.sh +6 -0
- package/dist/templates/native-go-vue/README.md +110 -0
- package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/dist/templates/native-go-vue/frontend/package.json +6 -0
- package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
- package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/dist/templates/native-go-vue/gitignore +2 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate.js +11 -7
- package/package.json +3 -7
- package/schema/synology-app.schema.json +36 -37
- package/skills/SKILL.md +4 -48
- package/skills/reference/create.md +11 -83
- package/skills/reference/deploy.md +7 -78
- package/skills/reference/diagnose.md +4 -101
- package/skills/reference/files.md +5 -41
- package/skills/reference/identity.md +3 -29
- package/skills/reference/wrap.md +0 -2
- package/templates/binary/synology-app.yaml +7 -0
- package/templates/hello-files/compose.yaml +0 -1
- package/templates/hello-web/compose.yaml +13 -10
- package/templates/hello-web/html/app.js +1 -23
- package/templates/hello-web/html/index.html +1 -3
- package/templates/hello-web-page/compose.yaml +13 -10
- package/templates/hello-web-page/html/app.js +1 -23
- package/templates/hello-web-page/html/index.html +1 -3
- package/templates/native-go/README.md +268 -0
- package/templates/native-go/backend/cmd/app/main.go +49 -0
- package/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/templates/native-go/backend/go.mod +3 -0
- package/templates/native-go/backend/internal/api/errors.go +33 -0
- package/templates/native-go/backend/internal/api/registry.go +94 -0
- package/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/templates/native-go/backend/internal/api/server.go +155 -0
- package/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/templates/native-go/backend/internal/app/routes.go +127 -0
- package/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/templates/native-go/backend/internal/helper/control.go +219 -0
- package/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/templates/native-go/backend/internal/helper/status.go +271 -0
- package/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/templates/native-go/dsm-package.yaml +58 -0
- package/templates/native-go/gitignore +1 -0
- package/templates/native-go/i18n/glossary.json +662 -0
- package/templates/native-go/i18n/messages/de.json +28 -0
- package/templates/native-go/i18n/messages/en.json +28 -0
- package/templates/native-go/i18n/messages/es.json +28 -0
- package/templates/native-go/i18n/messages/fr.json +28 -0
- package/templates/native-go/i18n/messages/it.json +28 -0
- package/templates/native-go/i18n/messages/ja.json +28 -0
- package/templates/native-go/i18n/messages/ko.json +28 -0
- package/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/templates/native-go/ui/app.css +35 -0
- package/templates/native-go/ui/app.js +94 -0
- package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/templates/native-go-better-auth/node/app.mjs +72 -0
- package/templates/native-go-better-auth/node/build.mjs +2 -0
- package/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/templates/native-go-better-auth/node/package.json +1 -0
- package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/templates/native-go-root/root/whoami.sh +6 -0
- package/templates/native-go-vue/README.md +110 -0
- package/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/templates/native-go-vue/frontend/package.json +6 -0
- package/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/templates/native-go-vue/frontend/src/style.css +14 -0
- package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/templates/native-go-vue/gitignore +2 -0
- package/dist/attestation/app-package.d.ts +0 -86
- package/dist/attestation/app-package.js +0 -206
- package/dist/attestation/install-on-host.d.ts +0 -14
- package/dist/attestation/install-on-host.js +0 -52
- package/dist/attestation/keyring.d.ts +0 -21
- package/dist/attestation/keyring.js +0 -168
- package/dist/attestation/platform-service.d.ts +0 -70
- package/dist/attestation/platform-service.js +0 -250
- package/dist/build-spk/broker-glue.d.ts +0 -16
- package/dist/build-spk/broker-glue.js +0 -40
- package/dist/deploy/catalog-publish.d.ts +0 -3
- package/dist/deploy/catalog-publish.js +0 -7
- package/dist/doctor.d.ts +0 -22
- package/dist/doctor.js +0 -822
- package/dist/error-collector.d.ts +0 -2
- package/dist/error-collector.js +0 -46
- package/dist/host/broker-paths.d.ts +0 -1
- package/dist/host/broker-paths.js +0 -1
- package/dist/host/broker-upgrade-preflight.d.ts +0 -61
- package/dist/host/broker-upgrade-preflight.js +0 -156
- package/dist/host/digests.d.ts +0 -5
- package/dist/host/digests.js +0 -63
- package/dist/host/fake-nas.d.ts +0 -49
- package/dist/host/fake-nas.js +0 -193
- package/dist/mock-broker/bootstrap.d.ts +0 -2
- package/dist/mock-broker/bootstrap.js +0 -8
- package/dist/mock-broker/files.d.ts +0 -60
- package/dist/mock-broker/files.js +0 -408
- package/dist/mock-broker/oidc.d.ts +0 -31
- package/dist/mock-broker/oidc.js +0 -161
- package/dist/mock-broker/path.d.ts +0 -9
- package/dist/mock-broker/path.js +0 -50
- package/dist/mock-broker/server.d.ts +0 -19
- package/dist/mock-broker/server.js +0 -350
- package/dist/publish/build-ledger.d.ts +0 -22
- package/dist/publish/build-ledger.js +0 -26
- package/dist/publish/community-path.d.ts +0 -6
- package/dist/publish/community-path.js +0 -41
- package/dist/publish/curated-release.d.ts +0 -78
- package/dist/publish/curated-release.js +0 -883
- package/dist/publish/file-build-ledger.d.ts +0 -18
- package/dist/publish/file-build-ledger.js +0 -268
- package/dist/publish/github-pr.d.ts +0 -25
- package/dist/publish/github-pr.js +0 -85
- package/dist/publish/github-repo.d.ts +0 -8
- package/dist/publish/github-repo.js +0 -21
- package/dist/publish/platform-service-release.d.ts +0 -60
- package/dist/publish/platform-service-release.js +0 -333
- package/dist/publish/preflight.d.ts +0 -24
- package/dist/publish/preflight.js +0 -138
- package/dist/publish/publish.d.ts +0 -27
- package/dist/publish/publish.js +0 -136
- package/dist/publish/sync-definition.d.ts +0 -4
- package/dist/publish/sync-definition.js +0 -48
- package/dist/release-bin.d.ts +0 -2
- package/dist/release-bin.js +0 -23
- package/dist/templates/hello-web/html/sdk.js +0 -68
- package/dist/templates/hello-web-page/html/sdk.js +0 -68
- package/dist/templates/native/synology-app.yaml +0 -7
- package/dist/test-host.d.ts +0 -15
- package/dist/test-host.js +0 -37
- package/templates/hello-web/html/sdk.js +0 -68
- package/templates/hello-web-page/html/sdk.js +0 -68
- package/templates/native/synology-app.yaml +0 -7
- /package/dist/templates/{native → binary}/native.yaml +0 -0
- /package/templates/{native → binary}/native.yaml +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { renderDeleteAppDataBlock } from "./delete-app-data.js";
|
|
2
|
-
import { BROKER_INSTALLER_VARS, renderBrokerStatusProbe, } from "./broker-glue.js";
|
|
3
2
|
/**
|
|
4
3
|
* Escapes a literal string for safe inclusion in POSIX `/bin/sh` single quotes.
|
|
5
4
|
* A single quote is represented as `'\''` (close single quote, literal escaped single quote, reopen single quote).
|
|
@@ -69,7 +68,6 @@ export function renderNativeStartStopStatus(input) {
|
|
|
69
68
|
const envBlock = envLines.length > 0 ? `\n${envLines.join("\n")}` : "";
|
|
70
69
|
const commandLine = [execToken, ...argTokens].join(" ");
|
|
71
70
|
return `#!/bin/sh
|
|
72
|
-
${BROKER_INSTALLER_VARS}
|
|
73
71
|
PKGDEST="${pkgDestFallback}"
|
|
74
72
|
PKGVAR="${pkgVarFallback}"
|
|
75
73
|
PIDFILE="$PKGVAR/${pkg}.pid"
|
|
@@ -129,7 +127,6 @@ case "$1" in
|
|
|
129
127
|
exit 0
|
|
130
128
|
;;
|
|
131
129
|
status)
|
|
132
|
-
${renderBrokerStatusProbe(pkg)}
|
|
133
130
|
if pid_alive; then
|
|
134
131
|
exit 0
|
|
135
132
|
fi
|
|
@@ -27,7 +27,6 @@ export async function materializeNativePayload(options) {
|
|
|
27
27
|
// The payload root IS SYNOPKG_PKGDEST (/var/packages/<pkg>/target): DSM
|
|
28
28
|
// extracts package.tgz straight into it. Nesting a literal `target/`
|
|
29
29
|
// directory here would put the binary at $SYNOPKG_PKGDEST/target/<exec>.
|
|
30
|
-
// Same layout the Broker SPK ships (packages/broker/src/spk/build.ts).
|
|
31
30
|
if (kind === "raw") {
|
|
32
31
|
materializeRaw(sourceBytes, targetDir, rawSourceName(sourceName));
|
|
33
32
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { planDeployment, deployStandalone } from "./deploy/plan.js";
|
|
1
2
|
import { Command } from "commander";
|
|
3
|
+
import { renameSync } from "node:fs";
|
|
2
4
|
import { resolve, dirname } from "node:path";
|
|
3
5
|
import { findProjectRoot } from "./discovery.js";
|
|
4
6
|
import { loadContract } from "./contract/load.js";
|
|
@@ -14,13 +16,11 @@ import { deployProject } from "./deploy/deploy.js";
|
|
|
14
16
|
import { createNasHost } from "./host/dsm-client.js";
|
|
15
17
|
import { projectInfo, formatProjectInfo } from "./info.js";
|
|
16
18
|
import { followProjectLogs } from "./log.js";
|
|
17
|
-
import { testProjectOnHost } from "./test-host.js";
|
|
18
19
|
import { formatSkillList, readSkill, resolveSkillsRoot } from "./skill.js";
|
|
19
|
-
import { publishProject, resolveCommunityAppsRoot } from "./publish/publish.js";
|
|
20
|
-
import { formatPreflight, runPublicReleasePreflight } from "./publish/preflight.js";
|
|
21
|
-
import { createExecGitHubCli, createExecGitOps } from "./publish/github-pr.js";
|
|
22
|
-
import { communityAppsGitHubRepo } from "./publish/github-repo.js";
|
|
23
20
|
import { resolveInstallParameterExtraValues } from "./deploy/param-values.js";
|
|
21
|
+
import { findStandaloneRoot, loadStandaloneManifest } from "./standalone/manifest.js";
|
|
22
|
+
import { buildStandalone } from "./standalone/build.js";
|
|
23
|
+
import { inspectSpk } from "./standalone/inspect-spk.js";
|
|
24
24
|
function collectKvOption(value, previous, flag, example) {
|
|
25
25
|
const separator = value.indexOf("=");
|
|
26
26
|
if (separator <= 0) {
|
|
@@ -44,6 +44,26 @@ function resolveProject(cwd, project) {
|
|
|
44
44
|
const start = project ? resolve(cwd, project) : cwd;
|
|
45
45
|
return findProjectRoot(start);
|
|
46
46
|
}
|
|
47
|
+
function standaloneRoot(cwd, project) {
|
|
48
|
+
return findStandaloneRoot(project ? resolve(cwd, project) : cwd);
|
|
49
|
+
}
|
|
50
|
+
function formatSpkInspection(spk, report) {
|
|
51
|
+
const lines = [
|
|
52
|
+
`SPK:${spk}`,
|
|
53
|
+
`package:${report.package ?? "?"} version:${report.version ?? "?"}`,
|
|
54
|
+
`arch:${report.arch ?? "?"} runtime:${report.kind ?? "?"}`,
|
|
55
|
+
`簽署:${report.signed ? "有 Synology 簽章" : "未簽署(Package Center 需允許任何發行者)"}`,
|
|
56
|
+
`CGI:${report.cgi ?? "無"}`,
|
|
57
|
+
"payload:",
|
|
58
|
+
...report.payload.map((entry) => ` ${entry.mode} ${entry.path}`),
|
|
59
|
+
];
|
|
60
|
+
const integration = report.payload.some(entry => entry.path === "integration/helper");
|
|
61
|
+
lines.push(report.findings.length === 0
|
|
62
|
+
? integration ? "檢查:安裝以套件帳號執行;執行期整合另需管理員核准,此檢查不證明 integration ready。" : "檢查:沒有 root 權限需求或異常。"
|
|
63
|
+
: "問題:");
|
|
64
|
+
lines.push(...report.findings.map((finding) => ` - ${finding}`));
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
47
67
|
function requireProject(cwd, project) {
|
|
48
68
|
const found = resolveProject(cwd, project);
|
|
49
69
|
if (!found) {
|
|
@@ -52,6 +72,30 @@ function requireProject(cwd, project) {
|
|
|
52
72
|
}
|
|
53
73
|
return found;
|
|
54
74
|
}
|
|
75
|
+
async function buildStandaloneCommand(root, arch, options) {
|
|
76
|
+
if (options.output && !arch) {
|
|
77
|
+
console.error("-o 只能搭配 --arch 建置單一架構;多架構請用 --out-dir。");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
const outDir = options.output
|
|
81
|
+
? dirname(resolve(process.cwd(), options.output))
|
|
82
|
+
: options.outDir ? resolve(process.cwd(), options.outDir) : undefined;
|
|
83
|
+
try {
|
|
84
|
+
const { outputs } = await buildStandalone({ root, arches: arch ? [arch] : undefined, outDir });
|
|
85
|
+
for (let output of outputs) {
|
|
86
|
+
if (options.output) {
|
|
87
|
+
const target = resolve(process.cwd(), options.output);
|
|
88
|
+
renameSync(output, target);
|
|
89
|
+
output = target;
|
|
90
|
+
}
|
|
91
|
+
console.log(`已產生 ${output}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
55
99
|
export async function runCli(argv) {
|
|
56
100
|
const program = new Command();
|
|
57
101
|
program
|
|
@@ -59,51 +103,20 @@ export async function runCli(argv) {
|
|
|
59
103
|
.description("Syno Package Land Developer Kit CLI")
|
|
60
104
|
.option("--project <dir>", "App 專案根目錄");
|
|
61
105
|
program
|
|
62
|
-
.command("
|
|
63
|
-
.description("
|
|
64
|
-
.
|
|
65
|
-
.option("--package <id>", "指定診斷的 Package Identity(省略時由合約判定)")
|
|
66
|
-
.option("--dev", "診斷 Development Package Identity({package}.dev)")
|
|
67
|
-
.option("--json", "輸出 machine-readable JSON 診斷報告")
|
|
68
|
-
.action(async (options, command) => {
|
|
106
|
+
.command("validate")
|
|
107
|
+
.description("驗證 App Contract 與 Compose,或 Standalone Package 的 dsm-package.yaml")
|
|
108
|
+
.action((_options, command) => {
|
|
69
109
|
const global = command.parent?.opts() ?? {};
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
console.error(`[${"HOST_CREDENTIALS_MISSING"}] 找不到 NAS 憑證。請設定 synoagentcli profile 或 DSM_URL/DSM_PASSWORD。`);
|
|
110
|
+
const standalone = standaloneRoot(process.cwd(), global.project);
|
|
111
|
+
if (standalone) {
|
|
112
|
+
const loaded = loadStandaloneManifest(standalone);
|
|
113
|
+
if (!loaded.ok) {
|
|
114
|
+
console.error(loaded.errors.map((e) => `[${e.code}] ${e.message}`).join("\n"));
|
|
76
115
|
process.exit(1);
|
|
77
116
|
}
|
|
117
|
+
console.log("驗證通過。");
|
|
118
|
+
return;
|
|
78
119
|
}
|
|
79
|
-
try {
|
|
80
|
-
const { runDoctor, formatDoctorReport } = await import("./doctor.js");
|
|
81
|
-
const report = await runDoctor({
|
|
82
|
-
project,
|
|
83
|
-
host,
|
|
84
|
-
packageId: options.package,
|
|
85
|
-
dev: options.dev,
|
|
86
|
-
});
|
|
87
|
-
if (options.json) {
|
|
88
|
-
console.log(JSON.stringify(report, null, 2));
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
console.log(formatDoctorReport(report));
|
|
92
|
-
}
|
|
93
|
-
if (!report.ok) {
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
program
|
|
103
|
-
.command("validate")
|
|
104
|
-
.description("驗證 App Contract 與 Compose")
|
|
105
|
-
.action((options, command) => {
|
|
106
|
-
const global = command.parent?.opts() ?? {};
|
|
107
120
|
const project = requireProject(process.cwd(), global.project);
|
|
108
121
|
const result = validateProject(project, {
|
|
109
122
|
profile: "personal",
|
|
@@ -119,19 +132,55 @@ export async function runCli(argv) {
|
|
|
119
132
|
});
|
|
120
133
|
program
|
|
121
134
|
.command("inspect")
|
|
122
|
-
.description("檢視 Compose 意圖與 Web entry
|
|
123
|
-
.
|
|
135
|
+
.description("檢視 Compose 意圖與 Web entry 提示;--spk 檢查已產生的 SPK")
|
|
136
|
+
.option("--spk <file>", "不安裝,檢查 SPK 的 identity、檔案 mode 與 root 權限需求")
|
|
137
|
+
.action(async (options, command) => {
|
|
124
138
|
const global = command.parent?.opts() ?? {};
|
|
139
|
+
if (options.spk) {
|
|
140
|
+
const spk = resolve(process.cwd(), options.spk);
|
|
141
|
+
try {
|
|
142
|
+
const report = await inspectSpk(spk);
|
|
143
|
+
console.log(formatSpkInspection(spk, report));
|
|
144
|
+
if (report.findings.length > 0) {
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
125
154
|
const project = requireProject(process.cwd(), global.project);
|
|
126
155
|
const report = inspectProject(project);
|
|
127
156
|
console.log(formatInspectReport(report));
|
|
128
157
|
});
|
|
129
158
|
program
|
|
130
159
|
.command("init")
|
|
131
|
-
.description("從模板建立 App
|
|
160
|
+
.description("從模板建立 App 專案(不覆蓋既有檔案)")
|
|
132
161
|
.option("-t, --template <name>", `模板:${listTemplates().join(" | ")}`)
|
|
133
|
-
.option("--
|
|
162
|
+
.option("--binary", "建立 Binary App:以套件自己的帳號執行預先建置的 binary(等同 -t binary)")
|
|
163
|
+
.option("--package <id>", "native-go:INFO.package(預設由目錄名產生,例如 photo-tagger → PhotoTagger)")
|
|
164
|
+
.option("--name <display>", "native-go:顯示名稱(預設由目錄名產生)")
|
|
165
|
+
.option("--privilege <none|root>", "native-go:root 另加由 DSM 管理員在套件視窗核准的 root 操作(預設 none)")
|
|
166
|
+
.option("--ui <plain|vue>", "native-go:沿用 UI 0.3.1 的 Vue 介面(預設 plain)")
|
|
167
|
+
.option("--integration <go|better-auth>", "native-go --ui vue:每包 helper 與私有範例;better-auth 才加入 Node")
|
|
134
168
|
.argument("[dir]", "目標目錄", ".")
|
|
169
|
+
.addHelpText("after", `
|
|
170
|
+
模板分兩類:
|
|
171
|
+
獨立 Container / Binary App(synology-app.yaml):
|
|
172
|
+
容器(compose.yaml):hello-web、hello-web-page、hello-files、compose-import
|
|
173
|
+
binary(native.yaml):執行一個預先建置的 binary 服務;可用 adminport 在自己的 port 提供網頁
|
|
174
|
+
Standalone Package(未簽署原生 SPK,含 Go CGI 與 DSM 視窗,dsm-package.yaml):
|
|
175
|
+
native-go:Go CGI backend + DSM 視窗 UI,預設需登入、admin 動作另外宣告。
|
|
176
|
+
--privilege root:另加固定的 root 操作(root/<名稱>.sh),由 DSM 管理員在套件視窗
|
|
177
|
+
以密碼核准後才能執行;預設 none,完全不用 root。
|
|
178
|
+
|
|
179
|
+
快速開始(native-go,需要 Go 1.24+):
|
|
180
|
+
synopkgland init -t native-go my-app
|
|
181
|
+
cd my-app && $EDITOR backend/internal/app/routes.go ui/app.js
|
|
182
|
+
synopkgland build-spk # → dist/<Package>-<version>-<arch>.spk
|
|
183
|
+
synopkgland inspect --spk dist/<file>.spk`)
|
|
135
184
|
.action(async (dir, options, command) => {
|
|
136
185
|
const global = command.parent?.opts() ?? {};
|
|
137
186
|
if (global.project) {
|
|
@@ -139,27 +188,45 @@ export async function runCli(argv) {
|
|
|
139
188
|
process.exit(1);
|
|
140
189
|
}
|
|
141
190
|
const targetDir = resolve(process.cwd(), dir);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
console.
|
|
145
|
-
|
|
191
|
+
const template = options.binary ? "binary" : options.template;
|
|
192
|
+
if (!template) {
|
|
193
|
+
console.error(`請指定 -t, --template <name> 或 --binary。可選模板:${listTemplates().join(" | ")}`);
|
|
194
|
+
process.exit(1);
|
|
146
195
|
}
|
|
147
|
-
if (!
|
|
148
|
-
console.error(
|
|
196
|
+
if (!isTemplateName(template)) {
|
|
197
|
+
console.error(`未知模板:${template}`);
|
|
149
198
|
process.exit(1);
|
|
150
199
|
}
|
|
151
|
-
if (
|
|
152
|
-
console.error(
|
|
200
|
+
if ((options.package || options.name || options.privilege) && template !== "native-go") {
|
|
201
|
+
console.error("--package/--name/--privilege 只適用於 -t native-go。");
|
|
153
202
|
process.exit(1);
|
|
154
203
|
}
|
|
155
|
-
|
|
156
|
-
|
|
204
|
+
try {
|
|
205
|
+
initFromTemplate(template, targetDir, {
|
|
206
|
+
packageId: options.package,
|
|
207
|
+
displayName: options.name,
|
|
208
|
+
privilege: options.privilege,
|
|
209
|
+
ui: options.ui,
|
|
210
|
+
integration: options.integration,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
console.log(`已建立 ${template} 專案於 ${targetDir}`);
|
|
218
|
+
if (template === "native-go") {
|
|
219
|
+
console.log(`下一步:
|
|
220
|
+
cd ${dir}
|
|
221
|
+
(cd backend && go test ./...) # 認證與權限政策測試
|
|
222
|
+
synopkgland build-spk # 編譯 Go + 打包每個架構的未簽署 SPK
|
|
223
|
+
說明見 README.md。`);
|
|
224
|
+
}
|
|
157
225
|
});
|
|
158
226
|
program
|
|
159
227
|
.command("dev")
|
|
160
|
-
.description("
|
|
161
|
-
.
|
|
162
|
-
.action(async (options, command) => {
|
|
228
|
+
.description("啟動 App 自己的本機 Compose")
|
|
229
|
+
.action(async (_options, command) => {
|
|
163
230
|
const global = command.parent?.opts() ?? {};
|
|
164
231
|
const project = requireProject(process.cwd(), global.project);
|
|
165
232
|
const validation = validateProject(project);
|
|
@@ -170,10 +237,7 @@ export async function runCli(argv) {
|
|
|
170
237
|
try {
|
|
171
238
|
const handle = await runDev({
|
|
172
239
|
project,
|
|
173
|
-
filesRoot: options.filesRoot,
|
|
174
240
|
});
|
|
175
|
-
console.log(`Mock Broker:${handle.brokerBaseUrl}`);
|
|
176
|
-
console.log(`Bootstrap:${handle.bootstrapPath}`);
|
|
177
241
|
console.log("Compose 已就緒。按 Ctrl+C 結束。");
|
|
178
242
|
const shutdown = async () => {
|
|
179
243
|
await handle.close();
|
|
@@ -191,31 +255,9 @@ export async function runCli(argv) {
|
|
|
191
255
|
program
|
|
192
256
|
.command("test")
|
|
193
257
|
.description("驗證契約並等待 Compose 就緒")
|
|
194
|
-
.
|
|
195
|
-
.option("--dev", "檢查 Development Package Identity({package}.dev)")
|
|
196
|
-
.action(async (options, command) => {
|
|
258
|
+
.action(async (_options, command) => {
|
|
197
259
|
const global = command.parent?.opts() ?? {};
|
|
198
260
|
const project = requireProject(process.cwd(), global.project);
|
|
199
|
-
if (options.host) {
|
|
200
|
-
const host = createNasHost({ host: options.host });
|
|
201
|
-
if (!host) {
|
|
202
|
-
console.error("找不到 NAS 憑證。請設定 synoagentcli profile 或 DSM_URL/DSM_PASSWORD。");
|
|
203
|
-
process.exit(1);
|
|
204
|
-
}
|
|
205
|
-
try {
|
|
206
|
-
const result = await testProjectOnHost({ project, host, dev: options.dev });
|
|
207
|
-
if (!result.ok) {
|
|
208
|
-
console.error(result.message ?? "測試失敗。");
|
|
209
|
-
process.exit(1);
|
|
210
|
-
}
|
|
211
|
-
console.log("NAS smoke 測試通過。");
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
261
|
try {
|
|
220
262
|
const result = await testProject({ project });
|
|
221
263
|
if (!result.ok) {
|
|
@@ -231,16 +273,40 @@ export async function runCli(argv) {
|
|
|
231
273
|
});
|
|
232
274
|
program
|
|
233
275
|
.command("deploy")
|
|
234
|
-
.description("
|
|
235
|
-
.
|
|
276
|
+
.description("建置獨立 SPK 並直接安裝到 DSM")
|
|
277
|
+
.option("--host <name>", "synoagentcli profile 或 NAS 名稱")
|
|
236
278
|
.option("--arch <family>", "native App 的目標架構:x86_64 或 aarch64(省略時由 NAS 判定)")
|
|
237
279
|
.option("--dev", "部署到 Development Package Identity({package}.dev)")
|
|
238
|
-
.option("--replace-curated", "危險:將既有 Curated App Package Attestation 降級為 Local Attestation")
|
|
239
280
|
.option("--share <slot=name>", "此 NAS 上的 Shared Folder 邏輯名(契約 shared_folder 槽,可重複)", collectShareOption)
|
|
240
281
|
.option("--run-as <slot=name>", "此 NAS 上的 DSM 使用者邏輯名(契約 dsm_user 槽)", collectRunAsOption)
|
|
241
282
|
.option("--param <NAME=value>", "契約 string/boolean Install Parameter 答案(可重複)", collectParamOption)
|
|
283
|
+
.option("--dry-run", "只輸出本機部署計畫,不連接 NAS")
|
|
242
284
|
.action(async (options, command) => {
|
|
243
285
|
const global = command.parent?.opts() ?? {};
|
|
286
|
+
if (options.arch !== undefined && !isNativeArchFamily(String(options.arch))) {
|
|
287
|
+
throw new Error('--arch accepts x86_64 or aarch64');
|
|
288
|
+
}
|
|
289
|
+
const plan = planDeployment(resolve(process.cwd(), global.project ?? '.'), {
|
|
290
|
+
arch: options.arch, dev: options.dev,
|
|
291
|
+
});
|
|
292
|
+
if (options.dryRun) {
|
|
293
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!options.host)
|
|
297
|
+
throw new Error('deploy requires --host, or use --dry-run');
|
|
298
|
+
if (plan.kind === 'native-go') {
|
|
299
|
+
if (options.share || options.runAs || options.param)
|
|
300
|
+
throw new Error('native-go does not accept container install parameters');
|
|
301
|
+
const host = createNasHost({ host: options.host });
|
|
302
|
+
if (!host)
|
|
303
|
+
throw new Error('NAS credentials not found');
|
|
304
|
+
const output = await deployStandalone(plan, host);
|
|
305
|
+
console.log(`已部署 ${plan.packageId}: ${output}`);
|
|
306
|
+
for (const note of plan.notes)
|
|
307
|
+
console.log(note);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
244
310
|
const project = requireProject(process.cwd(), global.project);
|
|
245
311
|
const parameterCheck = resolveInstallParameterExtraValues({
|
|
246
312
|
contract: loadContract(project.contractPath).contract,
|
|
@@ -265,7 +331,6 @@ export async function runCli(argv) {
|
|
|
265
331
|
project,
|
|
266
332
|
host,
|
|
267
333
|
dev: options.dev,
|
|
268
|
-
replaceCurated: options.replaceCurated,
|
|
269
334
|
shareValues: options.share,
|
|
270
335
|
runAsValues: options.runAs,
|
|
271
336
|
paramValues: options.param,
|
|
@@ -289,7 +354,7 @@ export async function runCli(argv) {
|
|
|
289
354
|
});
|
|
290
355
|
program
|
|
291
356
|
.command("info")
|
|
292
|
-
.description("顯示
|
|
357
|
+
.description("顯示 DSM 套件狀態與 Open URL")
|
|
293
358
|
.requiredOption("--host <name>", "synoagentcli profile 或 NAS 名稱")
|
|
294
359
|
.option("--dev", "查詢 Development Package Identity")
|
|
295
360
|
.action(async (options, command) => {
|
|
@@ -369,20 +434,27 @@ export async function runCli(argv) {
|
|
|
369
434
|
});
|
|
370
435
|
program
|
|
371
436
|
.command("build-spk")
|
|
372
|
-
.description("
|
|
373
|
-
.option("-o, --output <path>", "輸出 .spk
|
|
374
|
-
.option("--
|
|
437
|
+
.description("產生本機未簽署 SPK:容器 App(synology-app.yaml)或 Standalone Package(dsm-package.yaml,會先編譯 UI 與 Go)")
|
|
438
|
+
.option("-o, --output <path>", "輸出 .spk 路徑(Standalone Package 只在建置單一架構時可用)")
|
|
439
|
+
.option("--out-dir <dir>", "Standalone Package:輸出目錄(預設 dist/)")
|
|
440
|
+
.option("--arch <family>", "目標架構:x86_64 或 aarch64(native App 必填;Standalone Package 預設建置 arches 全部)")
|
|
375
441
|
.action(async (options, command) => {
|
|
376
442
|
const global = command.parent?.opts() ?? {};
|
|
377
|
-
const project = requireProject(process.cwd(), global.project);
|
|
378
443
|
if (options.arch !== undefined && !isNativeArchFamily(String(options.arch))) {
|
|
379
444
|
console.error(`--arch 只接受 x86_64 或 aarch64,收到 ${String(options.arch)}。`);
|
|
380
445
|
process.exit(1);
|
|
381
446
|
}
|
|
447
|
+
const archFamily = options.arch ? String(options.arch) : undefined;
|
|
448
|
+
const standalone = standaloneRoot(process.cwd(), global.project);
|
|
449
|
+
if (standalone) {
|
|
450
|
+
await buildStandaloneCommand(standalone, archFamily, options);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const project = requireProject(process.cwd(), global.project);
|
|
382
454
|
const result = await buildSpk({
|
|
383
455
|
project,
|
|
384
456
|
outputPath: options.output ? resolve(process.cwd(), options.output) : undefined,
|
|
385
|
-
archFamily
|
|
457
|
+
archFamily,
|
|
386
458
|
});
|
|
387
459
|
if (!result.ok) {
|
|
388
460
|
console.error(`建置失敗:${result.errors.join(", ")}`);
|
|
@@ -390,57 +462,5 @@ export async function runCli(argv) {
|
|
|
390
462
|
}
|
|
391
463
|
console.log(`已產生 ${result.outputPath}`);
|
|
392
464
|
});
|
|
393
|
-
program
|
|
394
|
-
.command("public-release-submit")
|
|
395
|
-
.description("送審公開發佈:先跑完本機所有 Curated 檢查,通過才對 Community Apps 建立 Pull Request。"
|
|
396
|
-
+ "不上傳 SPK、不簽署、不發佈——實際發佈由 maintainer 合併後的 trusted CI 執行。")
|
|
397
|
-
.option("--check", "只跑本機檢查,不建立 Pull Request")
|
|
398
|
-
.option("--base <branch>", "PR 目標分支", "master")
|
|
399
|
-
.action(async (options, command) => {
|
|
400
|
-
const global = command.parent?.opts() ?? {};
|
|
401
|
-
const project = requireProject(process.cwd(), global.project);
|
|
402
|
-
const preflight = await runPublicReleasePreflight(project);
|
|
403
|
-
if (!preflight.ok) {
|
|
404
|
-
// A gate's reasons belong on stderr so CI logs and `2>` capture them.
|
|
405
|
-
console.error(formatPreflight(preflight));
|
|
406
|
-
console.error("\n本機檢查未通過,未建立 Pull Request。");
|
|
407
|
-
process.exit(1);
|
|
408
|
-
}
|
|
409
|
-
console.log(formatPreflight(preflight));
|
|
410
|
-
if (options.check) {
|
|
411
|
-
console.log("\n本機檢查全部通過。加上 --check 以外的執行才會送審。");
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const communityAppsRoot = resolveCommunityAppsRoot(project.root);
|
|
415
|
-
if (!communityAppsRoot) {
|
|
416
|
-
console.error("送審需要 Community Apps 定義倉庫的本機副本。請先取得它,再從你的 App 專案目錄重跑:\n"
|
|
417
|
-
+ " git clone https://github.com/synopackageland/synopkgland-community-apps.git\n"
|
|
418
|
-
+ " export SYNOPKGLAND_COMMUNITY_APPS_ROOT=$PWD/synopkgland-community-apps");
|
|
419
|
-
process.exit(1);
|
|
420
|
-
}
|
|
421
|
-
const repoRoot = dirname(communityAppsRoot);
|
|
422
|
-
try {
|
|
423
|
-
const result = await publishProject({
|
|
424
|
-
project,
|
|
425
|
-
communityAppsRoot,
|
|
426
|
-
baseBranch: options.base,
|
|
427
|
-
github: createExecGitHubCli(repoRoot, communityAppsGitHubRepo()),
|
|
428
|
-
git: createExecGitOps(repoRoot),
|
|
429
|
-
});
|
|
430
|
-
if (!result.ok) {
|
|
431
|
-
console.error(result.message ?? result.code ?? "publish 失敗。");
|
|
432
|
-
process.exit(1);
|
|
433
|
-
}
|
|
434
|
-
if (result.noChanges) {
|
|
435
|
-
console.log(result.message ?? "Community Apps 定義已是最新。");
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
console.log(`已建立 PR:${result.prUrl}`);
|
|
439
|
-
}
|
|
440
|
-
catch (error) {
|
|
441
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
442
|
-
process.exit(1);
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
465
|
await program.parseAsync(argv);
|
|
446
466
|
}
|
|
@@ -28,7 +28,7 @@ export interface ResourceWorkerEligibility {
|
|
|
28
28
|
* - any non-headless App without a unique literal published adminport status probe
|
|
29
29
|
*/
|
|
30
30
|
export declare function resourceWorkerEligibility(contract: AppContract, compose: ComposeDocument): ResourceWorkerEligibility;
|
|
31
|
-
export type ComposeRoute = "static-zero-config" | "dynamic" | "
|
|
31
|
+
export type ComposeRoute = "static-zero-config" | "dynamic" | "reject";
|
|
32
32
|
export interface ComposeRouteClassification {
|
|
33
33
|
route: ComposeRoute;
|
|
34
34
|
eligibility: ResourceWorkerEligibility;
|
|
@@ -42,7 +42,6 @@ export interface ComposeRouteClassification {
|
|
|
42
42
|
* placed in the project .env.
|
|
43
43
|
*/
|
|
44
44
|
export declare function classifyComposeRoute(contract: AppContract, parsed: ParsedCompose, composeFile?: string): ComposeRouteClassification;
|
|
45
|
-
export declare function brokerInstallerRouteWarning(compose: ComposeDocument, composeFile?: string): ValidationError;
|
|
46
45
|
export declare function collectComposeInterpolations(compose: ComposeDocument): Set<string>;
|
|
47
46
|
export declare function validateComposeSecurity(compose: ComposeDocument, composeFile: string, options: {
|
|
48
47
|
profile: "personal" | "listing";
|
package/dist/compose/analyze.js
CHANGED
|
@@ -180,7 +180,7 @@ function hasBuildStanza(compose) {
|
|
|
180
180
|
}
|
|
181
181
|
return Object.values(compose.services).some((service) => "build" in service && service.build !== undefined);
|
|
182
182
|
}
|
|
183
|
-
const
|
|
183
|
+
const HEADLESS_UNSUPPORTED_REASONS = new Set([
|
|
184
184
|
"HAS_COMPOSE_SECRETS_OR_CONFIGS",
|
|
185
185
|
"HAS_BUILD_STANZA",
|
|
186
186
|
"HAS_PRIVILEGED_CAPABILITY",
|
|
@@ -204,7 +204,7 @@ export function classifyComposeRoute(contract, parsed, composeFile = "compose.ya
|
|
|
204
204
|
const hasHeadlessUnsupportedParameter = collectInstallParameters(contract).some(({ param }) => !HEADLESS_PACKAGE_ONLY_PARAMETER_TYPES.has(String(param.type)));
|
|
205
205
|
const headlessPackageOnly = eligibility.headless &&
|
|
206
206
|
!hasHeadlessUnsupportedParameter &&
|
|
207
|
-
!eligibility.reasons.some((reason) =>
|
|
207
|
+
!eligibility.reasons.some((reason) => HEADLESS_UNSUPPORTED_REASONS.has(reason));
|
|
208
208
|
const localRoute = contract.adminport !== undefined || headlessPackageOnly;
|
|
209
209
|
const rejectErrors = localRoute
|
|
210
210
|
? collectRouteRejectErrors(contract, parsed, composeFile)
|
|
@@ -225,27 +225,11 @@ export function classifyComposeRoute(contract, parsed, composeFile = "compose.ya
|
|
|
225
225
|
if (eligibility.eligible) {
|
|
226
226
|
return { route: "static-zero-config", eligibility, errors: [] };
|
|
227
227
|
}
|
|
228
|
-
// Keep the route decision in one place. build-spk must not independently
|
|
229
|
-
// reinterpret an ineligible Compose as D1: only a route with a resolved
|
|
230
|
-
// published adminport or an eligible headless Compose, plus at least one
|
|
231
|
-
// declared install parameter (or oidc_compat declaration), has a package-only materializer.
|
|
232
|
-
// Every other buildable fallback is Broker.
|
|
233
228
|
if ((eligibility.adminport !== undefined || headlessPackageOnly) &&
|
|
234
|
-
(collectInstallParameters(contract).length > 0 ||
|
|
229
|
+
(collectInstallParameters(contract).length > 0 || hasInterpolation(parsed.compose))) {
|
|
235
230
|
return { route: "dynamic", eligibility, errors: [] };
|
|
236
231
|
}
|
|
237
|
-
return { route: "
|
|
238
|
-
}
|
|
239
|
-
export function brokerInstallerRouteWarning(compose, composeFile = "compose.yaml") {
|
|
240
|
-
const path = findDynamicComposePath(compose) ?? "services";
|
|
241
|
-
return makeError("CURATED_BROKER_ROUTE_FORBIDDEN", composeSpanForPath(compose, composeFile, path), "此 Compose 會落到 Broker installer 路線;rootless Broker 不支援 Container Manager,無法作為 Curated production runtime。", "請改成零設定 resource-worker:使用 ./html bind 與預先 build 的 image;將 ${SYNOPKG_PKGVAR} 等平台 bind 改成 named volume,或改用 D1 dynamic route 的 string/boolean install 參數。", "warning");
|
|
242
|
-
}
|
|
243
|
-
function hasImmutableServiceImages(compose) {
|
|
244
|
-
const services = compose.services;
|
|
245
|
-
if (!services || Object.keys(services).length === 0) {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
return Object.values(services).every((service) => typeof service.image === "string" && /@sha256:[a-f0-9]{64}$/.test(service.image.trim()));
|
|
232
|
+
return { route: "reject", eligibility, errors: [makeError("COMPOSE_ROUTE_UNSUPPORTED", composeSpanForPath(parsed.compose, composeFile, "services"), `DSM resource worker cannot manage this Compose: ${eligibility.reasons.join(", ")}`, "Use prebuilt images, supported install parameters and DSM docker-project resources.")] };
|
|
249
233
|
}
|
|
250
234
|
function collectRouteRejectErrors(contract, parsed, composeFile) {
|
|
251
235
|
const { compose } = parsed;
|
|
@@ -318,14 +302,10 @@ function collectDynamicRouteError(contract, parsed, composeFile) {
|
|
|
318
302
|
param.type !== "shared_folder" &&
|
|
319
303
|
param.type !== "dsm_user" &&
|
|
320
304
|
param.type !== "posix_all_shares"));
|
|
321
|
-
const brokerOidcInterpolationNames = hasBrokerManagedOidc(contract)
|
|
322
|
-
? BROKER_OIDC_INTERPOLATION_VARS
|
|
323
|
-
: [];
|
|
324
305
|
const supportedInterpolationNames = new Set([
|
|
325
306
|
...supportedParamNames,
|
|
326
307
|
...dsmUserDerivedNames,
|
|
327
308
|
...PLATFORM_BIND_VARS,
|
|
328
|
-
...brokerOidcInterpolationNames,
|
|
329
309
|
]);
|
|
330
310
|
const interpolations = collectComposeInterpolations(compose);
|
|
331
311
|
const hasComposeInterpolation = hasInterpolation(compose);
|
|
@@ -364,7 +344,7 @@ function collectDynamicRouteError(contract, parsed, composeFile) {
|
|
|
364
344
|
: "請只使用 ${NAME} 形式,並在 synology-app.yaml 的 install 宣告對應的 string 或 boolean parameter。";
|
|
365
345
|
return makeError("COMPOSE_DYNAMIC_ROUTE_UNSUPPORTED", composeSpanForPath(compose, composeFile, path), "Compose dynamic route 含有不在 D1/D2 白名單內的輸入(" +
|
|
366
346
|
reasonText +
|
|
367
|
-
");這條 route
|
|
347
|
+
");這條 route 會拒收,不會靜默改用其他執行方式。", undeclaredName
|
|
368
348
|
? interpolationSuggestion
|
|
369
349
|
: `${interpolationSuggestion} secret/config 因 DSM worker 不讀 docker-compose.override.yml 而拒收,請移除後重新執行 validate。`);
|
|
370
350
|
}
|
|
@@ -504,9 +484,6 @@ export function validateComposeSecurity(compose, composeFile, options) {
|
|
|
504
484
|
? route.errors.filter((error) => !HOST_BIND_ERROR_CODES.has(error.code))
|
|
505
485
|
: [];
|
|
506
486
|
errors.push(...routeErrors);
|
|
507
|
-
if (route?.route === "broker-installer" && hasImmutableServiceImages(compose)) {
|
|
508
|
-
errors.push(brokerInstallerRouteWarning(compose, composeFile));
|
|
509
|
-
}
|
|
510
487
|
const routeErrorKeys = new Set(routeErrors.map(validationErrorKey));
|
|
511
488
|
if (!compose.services) {
|
|
512
489
|
return errors;
|
|
@@ -613,26 +590,6 @@ function isForbiddenSocketMount(source) {
|
|
|
613
590
|
* ${NAME} must be declared in contract.install as string or boolean.
|
|
614
591
|
*/
|
|
615
592
|
const PLATFORM_BIND_VARS = new Set(["SYNOPKG_PKGVAR", "SYNO_APP_SECRETS_DIR"]);
|
|
616
|
-
/**
|
|
617
|
-
* Broker-managed OIDC compatibility injects these two names at install time.
|
|
618
|
-
* They are allowed only for an explicitly declared oidc_compat legacy route;
|
|
619
|
-
* the D1 package-only materializer does not write them.
|
|
620
|
-
*/
|
|
621
|
-
const BROKER_OIDC_INTERPOLATION_VARS = new Set([
|
|
622
|
-
"SYNOPKGLAND_OIDC_ISSUER",
|
|
623
|
-
"SYNOPKGLAND_OIDC_CLIENT_ID",
|
|
624
|
-
"SYNOPKGLAND_OIDC_ISSUER_HOST",
|
|
625
|
-
]);
|
|
626
|
-
function hasBrokerManagedOidc(contract) {
|
|
627
|
-
if (!contract.integration || typeof contract.integration !== "object") {
|
|
628
|
-
return false;
|
|
629
|
-
}
|
|
630
|
-
const identity = contract.integration.identity;
|
|
631
|
-
if (!identity || typeof identity !== "object") {
|
|
632
|
-
return false;
|
|
633
|
-
}
|
|
634
|
-
return Boolean(identity.oidc_compat);
|
|
635
|
-
}
|
|
636
593
|
const INTERPOLATED_SOURCE = /^\$\{([A-Z0-9_]+)\}(.*)$/;
|
|
637
594
|
const VALID_SUFFIX_SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
638
595
|
function collectNamedVolumeNames(compose) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AppContract } from "../types.js";
|
|
2
|
+
/** Stable gate code for an install type that only the CLI can materialize. */
|
|
3
|
+
export declare const POSIX_ALL_SHARES_LISTING_UNSUPPORTED_CODE: "CONTRACT_POSIX_ALL_SHARES_LISTING_UNSUPPORTED";
|
|
4
|
+
export interface UnsupportedInstallType {
|
|
5
|
+
name: string;
|
|
6
|
+
type: string;
|
|
7
|
+
code: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function findUnsupportedInstallTypes(contract: AppContract): UnsupportedInstallType[];
|
|
10
|
+
export declare function findListingUnsupportedInstallTypes(contract: AppContract): UnsupportedInstallType[];
|