@synopackageland/cli 0.14.1 → 0.15.1
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 +20 -1
- package/README.md +33 -18
- package/dist/build-spk/generator.d.ts +3 -2
- package/dist/build-spk/generator.js +6 -846
- 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/unsupported-types.js +1 -1
- package/dist/contract/validate.js +2 -2
- package/dist/contract/web-launch.d.ts +1 -12
- package/dist/contract/web-launch.js +10 -74
- 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/e2e/dsm.d.ts +2 -2
- package/dist/e2e/dsm.js +10 -6
- package/dist/error-registry.js +1020 -834
- package/dist/host/share-permission-lookup.d.ts +1 -1
- package/dist/host/share-permission.d.ts +1 -1
- package/dist/host/share-permission.js +1 -1
- package/dist/info.js +120 -28
- package/dist/init.js +188 -64
- package/dist/inspect.js +21 -6
- package/dist/runtime/executor-digests.json +5 -5
- 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 +39 -8
- package/dist/standalone/identity-gateway.d.ts +9 -2
- package/dist/standalone/identity-gateway.js +282 -66
- package/dist/standalone/inspect-spk.d.ts +12 -0
- package/dist/standalone/inspect-spk.js +666 -136
- package/dist/standalone/lifecycle.js +124 -42
- package/dist/standalone/manifest.d.ts +8 -1
- package/dist/standalone/manifest.js +303 -86
- package/dist/standalone/resource.js +6 -2
- package/dist/standalone/spk.d.ts +4 -1
- package/dist/standalone/spk.js +138 -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/synopkgland-api-v1.schema.json +423 -0
- package/skills/SKILL.md +4 -2
- package/skills/reference/compose.md +349 -0
- package/skills/reference/create.md +9 -18
- package/skills/reference/ctl.md +41 -0
- package/skills/reference/deploy.md +1 -1
- package/skills/reference/files.md +5 -1
- package/skills/reference/identity-gateway.md +8 -144
- package/skills/reference/wrap.md +10 -39
- 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,3 +1,4 @@
|
|
|
1
|
+
import { sdkSource } from "../runtime/sdk-source.js";
|
|
1
2
|
import { resolvedSdkLaunch } from "../contract/app-launch.js";
|
|
2
3
|
import { metadataInfoFields } from "../package-metadata.js";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
@@ -5,36 +6,36 @@ import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync,
|
|
|
5
6
|
import { tmpdir } from "node:os";
|
|
6
7
|
import { dirname, join } from "node:path";
|
|
7
8
|
import { infoArchValue } from "../build-spk/dsm-arch.js";
|
|
8
|
-
import { packageIcons, writeDsmWindowIcons, writePackageCenterIcons } from "../build-spk/dsm-icons.js";
|
|
9
|
+
import { packageIcons, writeDsmWindowIcons, writePackageCenterIcons, } from "../build-spk/dsm-icons.js";
|
|
9
10
|
import { renderNativePrivilege } from "../build-spk/native-lifecycle.js";
|
|
10
11
|
import { renderUninstallWizardFiles } from "../build-spk/uninstall-wizard.js";
|
|
11
|
-
import { renderInstallWizardFiles, renderNativeInstallWizard } from "../build-spk/install-wizard.js";
|
|
12
|
-
import { writeWizardUiFiles } from "../build-spk/wizard-i18n.js";
|
|
13
|
-
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";
|
|
14
15
|
import { createReproducibleTar } from "../build-spk/reproducible-archive.js";
|
|
15
16
|
import { renderStandaloneScript } from "./lifecycle.js";
|
|
16
17
|
import { APP_SCRIPTS_DIR, APP_SCRIPTS_SPK_DIR } from "./app-scripts.js";
|
|
17
18
|
import { codedMessage } from "../errors.js";
|
|
18
19
|
import { DSM_LANGS, LOCALES } from "./i18n.js";
|
|
19
|
-
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";
|
|
20
21
|
import { writeUiTexts } from "../build-spk/ui-texts.js";
|
|
21
22
|
import { loginStyleAssets } from "../build-spk/login-style.js";
|
|
22
|
-
import { copyStandaloneNotices } from "../build-spk/notices.js";
|
|
23
|
+
import { copyStandaloneNotices, copyNoticeTree } from "../build-spk/notices.js";
|
|
23
24
|
import { copyAppTree } from "./bridge-app.js";
|
|
24
25
|
import { isBridgeBackend } from "./bridge-backend.js";
|
|
25
26
|
import { hostScriptName, renderHostAdapterScript } from "./host-adapter.js";
|
|
26
|
-
import {
|
|
27
|
+
import { writeComposePayload } from "./compose.js";
|
|
27
28
|
import { copyDaemonPayload } from "./daemon.js";
|
|
28
|
-
import { GATEWAY_CGI_PATH, GATEWAY_CONFIG_PATH, GATEWAY_EXEC, identityGatewayConfig } from "./identity-gateway.js";
|
|
29
|
-
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";
|
|
30
31
|
import { renderStandaloneResource } from "./resource.js";
|
|
31
32
|
import { nativePortal } from "./portal.js";
|
|
32
33
|
import { portalByExecutor } from "../build-spk/dsm-version.js";
|
|
33
|
-
import { PORTAL_JSON_RELPATH, PORTAL_MUSTACHE_RELPATH, renderPortalJson, renderPortalMustache, renderPortalWizardStep } from "../portal/package.js";
|
|
34
|
-
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";
|
|
35
36
|
import { EXECUTOR_ANCHOR } from "../runtime/executor-inspect.js";
|
|
36
|
-
import { renderSharedFolderUpgradeWizardFiles, sharedFolderInstallSteps } from "./shared-folder.js";
|
|
37
|
-
import { stateItemTexts, uninstallExecProblem, uninstallRecord } from "./uninstall.js";
|
|
37
|
+
import { renderSharedFolderUpgradeWizardFiles, sharedFolderInstallSteps, } from "./shared-folder.js";
|
|
38
|
+
import { stateItemTexts, uninstallExecProblem, uninstallRecord, } from "./uninstall.js";
|
|
38
39
|
const FILE_MODE = 0o644;
|
|
39
40
|
const EXEC_MODE = 0o755;
|
|
40
41
|
const MAX_UI_BYTES = 64 * 1024 * 1024;
|
|
@@ -63,26 +64,39 @@ export async function assembleStandaloneSpk(options) {
|
|
|
63
64
|
const { manifest } = options;
|
|
64
65
|
const staging = mkdtempSync(join(tmpdir(), "syno-standalone-spk-"));
|
|
65
66
|
try {
|
|
66
|
-
if ((manifest.capabilities.rootOps ||
|
|
67
|
+
if ((manifest.capabilities.rootOps ||
|
|
68
|
+
manifest.capabilities.delegate ||
|
|
69
|
+
manifest.capabilities.webapi) &&
|
|
70
|
+
!options.executor)
|
|
67
71
|
throw new StandaloneBuildError("EXECUTOR_PIN_REQUIRED", "Executor artifact required.");
|
|
68
72
|
const icons = packageIcons(manifest.icon);
|
|
69
73
|
const uiRoot = join(staging, "package", "ui");
|
|
70
74
|
const appDir = join(uiRoot, "app");
|
|
71
|
-
|
|
75
|
+
if (!manifest.compose)
|
|
76
|
+
copyUiTree(manifest.ui.dir, appDir);
|
|
77
|
+
else
|
|
78
|
+
mkdirSync(appDir, { recursive: true });
|
|
72
79
|
const assetVersion = digestTree(appDir).slice(0, 12);
|
|
73
|
-
const login = manifest.portal
|
|
80
|
+
const login = manifest.portal
|
|
81
|
+
? loginStyleAssets(manifest.loginStyle)
|
|
82
|
+
: undefined;
|
|
74
83
|
writeFile(join(uiRoot, "config"), renderUiConfig(manifest, icons, login?.fields));
|
|
75
84
|
for (const [path, bytes] of login?.files ?? [])
|
|
76
85
|
writeFile(join(uiRoot, path), bytes);
|
|
77
|
-
|
|
86
|
+
if (!manifest.compose)
|
|
87
|
+
writeFile(join(uiRoot, windowScriptName(manifest)), renderWindowScript(manifest, assetVersion, icons, options.portalText));
|
|
78
88
|
writeFile(join(uiRoot, hostScriptName(manifest.build)), renderHostAdapterScript(manifest.package));
|
|
79
89
|
writeDsmWindowIcons(uiRoot, icons);
|
|
80
90
|
writeUiTexts(uiRoot, appTitle(manifest));
|
|
91
|
+
if (options.apiDescription)
|
|
92
|
+
writeFile(join(staging, "package", "synopkgland-api.json"), JSON.stringify(options.apiDescription) + "\n");
|
|
81
93
|
const cgiTarget = join(staging, "package", CGI_PAYLOAD_PATH);
|
|
82
94
|
mkdirSync(dirname(cgiTarget), { recursive: true });
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
if (options.cgiPath) {
|
|
96
|
+
copyFileSync(options.cgiPath, cgiTarget);
|
|
97
|
+
chmodSync(cgiTarget, EXEC_MODE);
|
|
98
|
+
}
|
|
99
|
+
if (manifest.daemon || (manifest.compose && manifest.identityGateway)) {
|
|
86
100
|
if (!options.daemonControl)
|
|
87
101
|
throw new StandaloneBuildError("STANDALONE_DAEMON_ARTIFACT_REQUIRED", "daemon controller artifact required.");
|
|
88
102
|
copyDaemonPayload(manifest, options.arch, join(staging, "package", "daemon"), options.daemonControl);
|
|
@@ -90,7 +104,11 @@ export async function assembleStandaloneSpk(options) {
|
|
|
90
104
|
if (manifest.identityGateway) {
|
|
91
105
|
if (!options.identityGateway)
|
|
92
106
|
throw new StandaloneBuildError("STANDALONE_IDENTITY_GATEWAY_ARTIFACT_REQUIRED", "identity gateway artifact required.");
|
|
93
|
-
for (const path of [
|
|
107
|
+
for (const path of [
|
|
108
|
+
GATEWAY_CGI_PATH,
|
|
109
|
+
"ui/sso/open.cgi",
|
|
110
|
+
`daemon/payload/${GATEWAY_EXEC}`,
|
|
111
|
+
]) {
|
|
94
112
|
const target = join(staging, "package", path);
|
|
95
113
|
mkdirSync(dirname(target), { recursive: true });
|
|
96
114
|
copyFileSync(options.identityGateway, target);
|
|
@@ -113,10 +131,33 @@ export async function assembleStandaloneSpk(options) {
|
|
|
113
131
|
copyAppTree(options.app, join(staging, "package", "app"));
|
|
114
132
|
}
|
|
115
133
|
// A daemon is a Go program of the project's backend directory.
|
|
116
|
-
const sdk = backend.kind !==
|
|
117
|
-
|
|
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
|
+
}
|
|
118
159
|
if (manifest.daemon)
|
|
119
|
-
copyFileSync(new URL(
|
|
160
|
+
copyFileSync(new URL("../../assets/licenses/TEMPLATE.txt", import.meta.url), join(staging, "package", "LICENSES", "DAEMON-LICENSE.txt"));
|
|
120
161
|
const portal = nativePortal(manifest);
|
|
121
162
|
if (portal) {
|
|
122
163
|
writeFile(join(staging, "package", PORTAL_JSON_RELPATH), renderPortalJson(portal));
|
|
@@ -138,16 +179,48 @@ export async function assembleStandaloneSpk(options) {
|
|
|
138
179
|
copyExecutable(op.binary[options.arch].path, join(staging, "package", "root", op.name));
|
|
139
180
|
}
|
|
140
181
|
}
|
|
141
|
-
const hook = manifest.uninstall &&
|
|
182
|
+
const hook = manifest.uninstall &&
|
|
183
|
+
uninstallExecProblem(join(staging, "package"), manifest.uninstall.purge);
|
|
142
184
|
if (hook)
|
|
143
185
|
throw new StandaloneBuildError(hook.code, hook.message);
|
|
144
|
-
if (manifest.compose)
|
|
145
|
-
|
|
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
|
+
}
|
|
146
194
|
const packageTgz = join(staging, "package.tgz");
|
|
147
|
-
await createReproducibleTar(join(staging, "package"), packageTgz, {
|
|
195
|
+
await createReproducibleTar(join(staging, "package"), packageTgz, {
|
|
196
|
+
gzip: true,
|
|
197
|
+
});
|
|
148
198
|
rmSync(join(staging, "package"), { recursive: true, force: true });
|
|
149
|
-
const checksum = createHash("md5")
|
|
150
|
-
|
|
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
|
+
}));
|
|
151
224
|
writeFile(join(staging, "INFO"), renderInfo(manifest, options.arch, checksum, options.executor?.executorDigest));
|
|
152
225
|
mkdirSync(join(staging, "conf"));
|
|
153
226
|
writeFile(join(staging, "conf", "privilege"), renderNativePrivilege());
|
|
@@ -165,18 +238,37 @@ export async function assembleStandaloneSpk(options) {
|
|
|
165
238
|
for (const name of manifest.appScripts ?? [])
|
|
166
239
|
copyExecutable(join(manifest.root, APP_SCRIPTS_DIR, name), join(staging, APP_SCRIPTS_SPK_DIR, name));
|
|
167
240
|
const folder = manifest.sharedFolder;
|
|
168
|
-
const steps = manifest.capabilities.delegate?.scope ===
|
|
169
|
-
? (t) => [
|
|
170
|
-
|
|
171
|
-
|
|
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);
|
|
172
254
|
writeWizardUiFiles(staging, {
|
|
173
255
|
...install.static,
|
|
174
256
|
...install.dynamic,
|
|
175
|
-
...(folder
|
|
257
|
+
...(folder
|
|
258
|
+
? renderSharedFolderUpgradeWizardFiles(manifest.package, folder)
|
|
259
|
+
: {}),
|
|
176
260
|
...renderUninstallWizardFiles(manifest.package, "native", {
|
|
177
|
-
executor: Boolean(options.executor),
|
|
261
|
+
executor: Boolean(options.executor),
|
|
262
|
+
portal: Boolean(portal),
|
|
263
|
+
portalEntry: Boolean(manifest.portal && portalByExecutor(manifest.os_min_ver)),
|
|
178
264
|
rootPurge: Boolean(manifest.uninstall?.purge.root),
|
|
179
|
-
...(manifest.uninstall?.state
|
|
265
|
+
...(manifest.uninstall?.state
|
|
266
|
+
? {
|
|
267
|
+
state: {
|
|
268
|
+
items: (locale) => stateItemTexts(manifest.uninstall, manifest.i18n?.catalogs, locale),
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
: {}),
|
|
180
272
|
}),
|
|
181
273
|
});
|
|
182
274
|
writePackageCenterIcons(staging, icons);
|
|
@@ -219,8 +311,15 @@ function renderInfo(manifest, arch, checksum, executorDigest) {
|
|
|
219
311
|
// Package Center writes INFO as root; root tasks accept only this executor.
|
|
220
312
|
if (executorDigest)
|
|
221
313
|
fields.push([EXECUTOR_ANCHOR, executorDigest]);
|
|
222
|
-
if (manifest.compose)
|
|
223
|
-
|
|
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
|
+
}
|
|
224
323
|
const backend = manifest.backend;
|
|
225
324
|
if (isBridgeBackend(backend) && backend.node)
|
|
226
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.1",
|
|
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.1",
|
|
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
|
},
|