@synopackageland/cli 0.20.0 → 0.21.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 +10 -0
- package/README.md +17 -0
- package/dist/cli.js +5 -1
- package/dist/runtime/executor-digests.json +24 -1
- package/dist/runtime/sdk-source.d.ts +2 -2
- package/dist/runtime/sdk-source.js +5 -3
- package/dist/standalone/compose-errors.js +5 -0
- package/dist/standalone/compose.d.ts +10 -1
- package/dist/standalone/compose.js +3 -1
- package/dist/standalone/inspect-spk.d.ts +1 -0
- package/dist/standalone/inspect-spk.js +4 -1
- package/dist/standalone/resource.js +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.21.1 (2026-10-21)
|
|
4
|
+
|
|
5
|
+
- Resolve SDK/App 0.21.1 to the verified immutable executor/v0.20.0 release while keeping exact SDK-version digest checks.
|
|
6
|
+
- Pin templates 0.21.1 with SDK/App 0.21.1 and UI 0.13.1 for new projects.
|
|
7
|
+
|
|
8
|
+
## 0.21.0 (2026-10-21)
|
|
9
|
+
|
|
10
|
+
- Validate and preserve `compose.devices.gpu` in check, build and inspect for optional Intel render-node access and software fallback.
|
|
11
|
+
- Pin SDK/templates 0.21.0 and reuse immutable executor/v0.20.0 with verified unchanged executor/lifecycle digests.
|
|
12
|
+
|
|
3
13
|
## 0.20.0 (2026-10-21)
|
|
4
14
|
|
|
5
15
|
- Align CLI/templates and the supported SDK minor line at 0.20; retain published UI 0.12.5.
|
package/README.md
CHANGED
|
@@ -367,3 +367,20 @@ unencrypted-HTTP limitations before opening it. Native resource/startup failures
|
|
|
367
367
|
fail closed; they do not silently expose a direct listener. The native runtime
|
|
368
368
|
requires the corresponding SDK release; this CLI change does not publish that
|
|
369
369
|
release or change existing manifest version pins.
|
|
370
|
+
|
|
371
|
+
Compose Apps may opt in to Intel render devices with
|
|
372
|
+
`compose.devices.gpu.services: [media]`. The matching SDK discovers exact devices
|
|
373
|
+
and the native `videodriver` GID at installation; the CLI emits DSM's
|
|
374
|
+
`video-driver` resource. `check` rejects arbitrary paths/GIDs and elevated GPU
|
|
375
|
+
services. `inspect --json` includes `compose.devices.gpu` with the runtime path
|
|
376
|
+
policy, GID source and software fallback, plus outstanding DSM acceptance
|
|
377
|
+
blockers. Keep the package UID and let the App validate its upstream encoder;
|
|
378
|
+
absence of a GPU produces no device mapping. See the matching SDK's Compose GPU
|
|
379
|
+
documentation for the security boundary and hardware validation limits.
|
|
380
|
+
|
|
381
|
+
DSM's native provider adds NET_BIND_SERVICE to the bounding set for Compose
|
|
382
|
+
packages. The SDK still emits `cap_drop: [ALL]` and requests no `cap_add`; the
|
|
383
|
+
nonroot package process with no-new-privileges has zero effective, permitted and
|
|
384
|
+
ambient capabilities on the tested provider. This general platform behavior is
|
|
385
|
+
not a GPU blocker. Plan metadata describes SDK policy, not an attestation of the
|
|
386
|
+
provider's final container configuration; see the SDK Compose security boundary.
|
package/dist/cli.js
CHANGED
|
@@ -344,6 +344,7 @@ export async function runCli(argv) {
|
|
|
344
344
|
...("compose" in loaded.manifest && loaded.manifest.compose
|
|
345
345
|
? {
|
|
346
346
|
compose: {
|
|
347
|
+
...(loaded.manifest.compose.plan.devices ? { devices: loaded.manifest.compose.plan.devices } : {}),
|
|
347
348
|
images: loaded.manifest.compose.plan.images,
|
|
348
349
|
services: loaded.manifest.compose.plan.services,
|
|
349
350
|
mountsBlocked: loaded.manifest.compose.plan.mounts_blocked,
|
|
@@ -688,15 +689,18 @@ export async function runCli(argv) {
|
|
|
688
689
|
outDir: undefined,
|
|
689
690
|
}),
|
|
690
691
|
});
|
|
692
|
+
const builtManifest = standalone && !isNativeLayout(standalone) ? loadStandaloneManifest(standalone) : undefined;
|
|
693
|
+
const devices = builtManifest && builtManifest.ok ? builtManifest.manifest.compose?.plan.devices : undefined;
|
|
691
694
|
const approval = await Promise.all(outputs.map(async (output) => ({ output, ...await spkApprovalDiff(output, options.baseline && resolve(options.baseline)) })));
|
|
692
695
|
const lines = [
|
|
693
696
|
...approval.map(item => item.output + "\n" + formatApprovalDiff(item)),
|
|
694
697
|
...outputs.map((output) => `Built ${output}`),
|
|
698
|
+
...(devices?.gpu ? [`GPU: ${devices.gpu.paths}; GID source: ${devices.gpu.gid_source}; fallback: ${devices.gpu.fallback}`] : []),
|
|
695
699
|
...(reproducible ? reproducibleLines(reproducible) : []),
|
|
696
700
|
];
|
|
697
701
|
if (reproducible && !reproducible.identical)
|
|
698
702
|
throw Object.assign(failure("BUILD_NOT_REPRODUCIBLE", lines.join("\n"), "Find the payload files that differ between builds (timestamps, random paths, unpinned dependencies) and retry after fixing them."), { report: { outputs, reproducible } });
|
|
699
|
-
emit("build", { outputs, approval, ...(reproducible ? { reproducible } : {}) }, lines.join("\n"));
|
|
703
|
+
emit("build", { outputs, approval, ...(devices ? { compose: { devices } } : {}), ...(reproducible ? { reproducible } : {}) }, lines.join("\n"));
|
|
700
704
|
});
|
|
701
705
|
if (json)
|
|
702
706
|
console.log = (...args) => console.error(...args);
|
|
@@ -1 +1,24 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"0.21.0": {
|
|
3
|
+
"x86_64": "e27cfeb85fed221b06f9c58166f02c0f701a724308cd17c96a43a843a0b5c4aa",
|
|
4
|
+
"aarch64": "908db00cbd37d072f6ffc339e7bca24da5efb8cddd4befcbad02c7398beb7191",
|
|
5
|
+
"armv7": "a0f35d62ec80a1bf3fb49f5e2d1a5f7576eda569a6fd0695084cec6cb8e100bc",
|
|
6
|
+
"lifecycle": {
|
|
7
|
+
"x86_64": "9bb53301670b43cdcd6e21cebac99ab7abd82962d874f32423a81e6cb49d4972",
|
|
8
|
+
"aarch64": "38fe873c86eb102c850baa1f304114a83a31d1df72defd22792bc37610fec9bb",
|
|
9
|
+
"armv7": "256fb348b275da7ac5728a3cb7cb7c65660de0d5c2c59430ab4f5ade1b0a3681"
|
|
10
|
+
},
|
|
11
|
+
"goToolchain": "go1.26.9"
|
|
12
|
+
},
|
|
13
|
+
"0.21.1": {
|
|
14
|
+
"x86_64": "e27cfeb85fed221b06f9c58166f02c0f701a724308cd17c96a43a843a0b5c4aa",
|
|
15
|
+
"aarch64": "908db00cbd37d072f6ffc339e7bca24da5efb8cddd4befcbad02c7398beb7191",
|
|
16
|
+
"armv7": "a0f35d62ec80a1bf3fb49f5e2d1a5f7576eda569a6fd0695084cec6cb8e100bc",
|
|
17
|
+
"lifecycle": {
|
|
18
|
+
"x86_64": "9bb53301670b43cdcd6e21cebac99ab7abd82962d874f32423a81e6cb49d4972",
|
|
19
|
+
"aarch64": "38fe873c86eb102c850baa1f304114a83a31d1df72defd22792bc37610fec9bb",
|
|
20
|
+
"armv7": "256fb348b275da7ac5728a3cb7cb7c65660de0d5c2c59430ab4f5ade1b0a3681"
|
|
21
|
+
},
|
|
22
|
+
"goToolchain": "go1.26.9"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -36,8 +36,8 @@ export declare function sdkRequirement(backendDir: string): string | undefined;
|
|
|
36
36
|
export declare function goModulePath(backendDir: string): string | undefined;
|
|
37
37
|
/**
|
|
38
38
|
* The module directories of one SDK version. SYNOPKGLAND_SDK_DIR names a
|
|
39
|
-
* local sdk checkout (development and gates only); otherwise
|
|
40
|
-
*
|
|
39
|
+
* local sdk checkout (development and gates only); otherwise modules come from
|
|
40
|
+
* `go mod download`; unchanged executor releases are pinned above.
|
|
41
41
|
*/
|
|
42
42
|
export declare function sdkSource(version: string, env?: NodeJS.ProcessEnv, download?: (spec: string) => string): SdkSource;
|
|
43
43
|
/** The module cache directory of module@version; the cache is read-only, so builds copy it first. */
|
|
@@ -75,16 +75,18 @@ function goMod(backendDir) {
|
|
|
75
75
|
return '';
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
/** SDK releases that reuse an immutable executor release with identical payload bytes. */
|
|
79
|
+
const EXECUTOR_RELEASES = { '0.21.0': '0.20.0', '0.21.1': '0.20.0' };
|
|
78
80
|
/**
|
|
79
81
|
* The module directories of one SDK version. SYNOPKGLAND_SDK_DIR names a
|
|
80
|
-
* local sdk checkout (development and gates only); otherwise
|
|
81
|
-
*
|
|
82
|
+
* local sdk checkout (development and gates only); otherwise modules come from
|
|
83
|
+
* `go mod download`; unchanged executor releases are pinned above.
|
|
82
84
|
*/
|
|
83
85
|
export function sdkSource(version, env = process.env, download = (spec) => goModDownload(spec, env)) {
|
|
84
86
|
const dir = env.SYNOPKGLAND_SDK_DIR;
|
|
85
87
|
if (dir)
|
|
86
88
|
return { version, goDir: dir, executorDir: join(dir, 'executor'), local: true };
|
|
87
|
-
return { version, goDir: download(`${SDK_MODULE}@v${version}`), executorDir: download(`${EXECUTOR_MODULE}@v${version}`), local: false };
|
|
89
|
+
return { version, goDir: download(`${SDK_MODULE}@v${version}`), executorDir: download(`${EXECUTOR_MODULE}@v${EXECUTOR_RELEASES[version] ?? version}`), local: false };
|
|
88
90
|
}
|
|
89
91
|
const downloaded = new Map();
|
|
90
92
|
/** The module cache directory of module@version; the cache is read-only, so builds copy it first. */
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/** SDK planner errors accepted at this versioned tool boundary. */
|
|
2
2
|
export const COMPOSE_ERRORS = {
|
|
3
|
+
COMPOSE_GPU_INVALID: {
|
|
4
|
+
code: "COMPOSE_GPU_INVALID",
|
|
5
|
+
description: "The GPU declaration requires bounded package-UID services.",
|
|
6
|
+
suggestion: "Declare compose.devices.gpu.services with distinct active service names. Keep package UID, no added capabilities, and let the SDK discover Intel render devices and the native videodriver GID.",
|
|
7
|
+
},
|
|
3
8
|
COMPOSE_STARTUP_INVALID: {
|
|
4
9
|
code: "COMPOSE_STARTUP_INVALID",
|
|
5
10
|
description: "The container startup budget is invalid.",
|
|
@@ -4,6 +4,15 @@ import { type Daemon } from "./daemon.js";
|
|
|
4
4
|
import type { SharedFolder } from "./shared-folder.js";
|
|
5
5
|
import type { StandaloneError, StandaloneManifest } from "./manifest.js";
|
|
6
6
|
export interface ComposePlan {
|
|
7
|
+
devices?: {
|
|
8
|
+
gpu?: {
|
|
9
|
+
services: string[];
|
|
10
|
+
resource: string;
|
|
11
|
+
gid_source: string;
|
|
12
|
+
paths: string;
|
|
13
|
+
fallback: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
7
16
|
app_data?: ComposeAppData;
|
|
8
17
|
startup_timeout_seconds?: number;
|
|
9
18
|
ingress?: {
|
|
@@ -72,7 +81,7 @@ export interface ComposeApp {
|
|
|
72
81
|
document: Record<string, any>;
|
|
73
82
|
plan: ComposePlan;
|
|
74
83
|
}
|
|
75
|
-
export declare const COMPOSE_FIELDS: readonly ["startup_timeout_seconds", "resource_prefix", "files", "env_files", "profiles", "web", "data", "shares", "network", "services", "helpers", "sdk"];
|
|
84
|
+
export declare const COMPOSE_FIELDS: readonly ["devices", "startup_timeout_seconds", "resource_prefix", "files", "env_files", "profiles", "web", "data", "shares", "network", "services", "helpers", "sdk"];
|
|
76
85
|
export declare function readComposeApp(value: unknown, root: string, arches: NativeArchFamily[], fail: (e: StandaloneError) => void, context?: {
|
|
77
86
|
package?: string;
|
|
78
87
|
sdk?: string;
|
|
@@ -12,6 +12,7 @@ import { goChildEnv } from "../runtime/go-env.js";
|
|
|
12
12
|
import { sdkSource } from "../runtime/sdk-source.js";
|
|
13
13
|
import { readDaemon } from "./daemon.js";
|
|
14
14
|
export const COMPOSE_FIELDS = [
|
|
15
|
+
"devices",
|
|
15
16
|
"startup_timeout_seconds",
|
|
16
17
|
"resource_prefix",
|
|
17
18
|
"files",
|
|
@@ -68,7 +69,7 @@ export function readComposeApp(value, root, arches, fail, context = {}) {
|
|
|
68
69
|
if (!record(value) ||
|
|
69
70
|
Object.keys(value).some((k) => !COMPOSE_FIELDS.includes(k)) ||
|
|
70
71
|
!Array.isArray(value.files))
|
|
71
|
-
return invalid("compose requires files, with optional env_files, profiles, web, data, shares, network, services, helpers and sdk; old file/targets syntax is removed.");
|
|
72
|
+
return invalid("compose requires files, with optional env_files, profiles, web, data, shares, devices, network, services, helpers and sdk; old file/targets syntax is removed.");
|
|
72
73
|
if (value.sdk !== undefined &&
|
|
73
74
|
(typeof value.sdk !== "string" || !/^\d+\.\d+\.\d+$/.test(value.sdk)))
|
|
74
75
|
return invalid("compose.sdk must name the matching SDK release.", "SDK_COMPOSE_UNSUPPORTED");
|
|
@@ -199,6 +200,7 @@ export function writeComposePayload(c, root, target, gateway) {
|
|
|
199
200
|
mkdirSync(target, { recursive: true });
|
|
200
201
|
for (const [name, content] of Object.entries({
|
|
201
202
|
"docker-compose.yml": c.plan.effective,
|
|
203
|
+
...(c.plan.devices?.gpu ? { "gpu.template.yaml": c.plan.effective } : {}),
|
|
202
204
|
"generated.override.yaml": c.plan.override,
|
|
203
205
|
"compose.lock.json": JSON.stringify(c.plan.lock, null, 2) + "\n",
|
|
204
206
|
"request.json": JSON.stringify({ declaration: c.declaration, sdk: c.sdk, gateway }, null, 2) + "\n",
|
|
@@ -608,6 +608,8 @@ async function inspectUnifiedCompose(dir, payload, metadata, findings) {
|
|
|
608
608
|
if (composeSource(c) !==
|
|
609
609
|
readFileSync(join(root, "compose/docker-compose.yml"), "utf8"))
|
|
610
610
|
throw new Error("worker Compose differs from the SDK plan");
|
|
611
|
+
if (c.plan.devices?.gpu && readFileSync(join(root, "compose/gpu.template.yaml"), "utf8") !== c.plan.effective)
|
|
612
|
+
throw new Error("GPU template differs from the SDK plan");
|
|
611
613
|
if (c.plan.override !==
|
|
612
614
|
readFileSync(join(root, "compose/generated.override.yaml"), "utf8"))
|
|
613
615
|
throw new Error("generated override differs from the SDK plan");
|
|
@@ -732,6 +734,7 @@ async function inspectUnifiedCompose(dir, payload, metadata, findings) {
|
|
|
732
734
|
if (storageProblem)
|
|
733
735
|
throw new Error(`[${storageProblem.code}] ${storageProblem.message}`);
|
|
734
736
|
base.compose = {
|
|
737
|
+
...(c.plan.devices ? { devices: c.plan.devices } : {}),
|
|
735
738
|
...(c.plan.ingress ? { ingress: c.plan.ingress } : {}),
|
|
736
739
|
images: c.plan.images,
|
|
737
740
|
services: c.plan.services,
|
|
@@ -1041,7 +1044,7 @@ function checkConf(dir, findings, kind, background, sharedFolder, pkg, portal, c
|
|
|
1041
1044
|
if (!resource["docker-project"])
|
|
1042
1045
|
findings.push("the container is missing the docker-project resource");
|
|
1043
1046
|
for (const key of Object.keys(resource)) {
|
|
1044
|
-
if (!["docker-project", "webservice"].includes(key))
|
|
1047
|
+
if (!["docker-project", "webservice", ...(compose?.plan.devices?.gpu ? ["video-driver"] : [])].includes(key))
|
|
1045
1048
|
findings.push(`Unknown container resource: ${key}`);
|
|
1046
1049
|
}
|
|
1047
1050
|
}
|
|
@@ -11,6 +11,7 @@ export function renderStandaloneResource(m, portal) {
|
|
|
11
11
|
const dataShare = m.sharedFolder && sharedFolderResource(m.package, m.sharedFolder);
|
|
12
12
|
const webConfig = { ...(portal ? portalResource()['web-config'] : {}), ...nativeGatewayResource(m) };
|
|
13
13
|
const resource = {
|
|
14
|
+
...(m.compose?.plan.devices?.gpu && !m.compose.plan.mounts_blocked ? { "video-driver": {} } : {}),
|
|
14
15
|
...(m.compose && !m.compose.plan.mounts_blocked
|
|
15
16
|
? renderDockerProjectResource(m.package, "compose", false, m.compose.plan.project_name)
|
|
16
17
|
: {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "Syno Package Land Developer Kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@napi-rs/keyring": "^2.1.1",
|
|
30
|
-
"@synopackageland/templates": "0.
|
|
30
|
+
"@synopackageland/templates": "0.21.1",
|
|
31
31
|
"ajv": "^8.17.1",
|
|
32
32
|
"commander": "^13.1.0",
|
|
33
33
|
"json-canonicalize": "^3.0.1",
|