@synopackageland/cli 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +18 -13
- package/dist/error-registry.js +7 -2
- package/dist/runtime/executor-build.js +14 -10
- package/dist/runtime/executor-policy-check.d.ts +2 -2
- package/dist/runtime/executor-policy-check.js +8 -8
- package/dist/runtime/executor-source.d.ts +3 -7
- package/dist/runtime/executor-source.js +4 -2
- package/dist/runtime/sdk-executor-manifest.d.ts +16 -0
- package/dist/runtime/sdk-executor-manifest.js +45 -0
- package/dist/runtime/sdk-source.d.ts +3 -1
- package/dist/runtime/sdk-source.js +15 -6
- package/dist/standalone/compose-errors.js +3 -3
- package/dist/standalone/compose.js +2 -2
- package/dist/standalone/oidc-resume.d.ts +1 -1
- package/dist/standalone/oidc-resume.js +11 -6
- package/dist/standalone/sdk-check.d.ts +2 -2
- package/dist/standalone/sdk-check.js +23 -9
- package/dist/standalone/spk.js +1 -1
- package/package.json +3 -3
- package/skills/SKILL.md +6 -3
- package/skills/reference/compose.md +13 -6
- package/skills/reference/lifecycle.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.28.0 (2026-10-24)
|
|
4
|
+
|
|
5
|
+
- Depend on templates `~0.28.0` with SDK/App 0.28.0 and UI 0.14.10. SDK 0.28.0 reuses executor/v0.27.0 through executor manifest format 1; no executor digests change.
|
|
6
|
+
- Default the Compose startup budget to 900 seconds and document slow-NAS readiness; the SDK owns health timing and the Starting status. No per-App option is added.
|
|
7
|
+
- Keep UI CGI scripts executable in built SPKs, and let external OIDC resume tokens reach only the fixed DSM CGI.
|
|
8
|
+
- The release gate checks SDK 0.27.1+ executor builds against the SDK executor manifest instead of requiring a frozen legacy table entry.
|
|
9
|
+
|
|
10
|
+
## 0.27.1 (2026-10-23)
|
|
11
|
+
|
|
12
|
+
- Read executor release mappings, toolchain and six payload pins from SDK executor manifest format 1. Accept compatible SDK minors independently of CLI versions; keep the frozen 0.26.0/0.27.0 fallback. Remove the CLI digest-recording script.
|
|
13
|
+
|
|
3
14
|
## 0.27.0 (2026-10-23)
|
|
4
15
|
|
|
5
16
|
- Depend on templates `~0.27.0` with SDK/App 0.27.0 and UI 0.14.9. Templates patches can ship independently and resolve during npm installation/update; runtime uses the installed templates without network fetching. SDK minor upgrades still require a CLI release for executor mappings/digests and `SDK_UNSUPPORTED` checks. New scaffolds use browser same-origin DSM file operations and the idle-storage fixes.
|
package/README.md
CHANGED
|
@@ -52,28 +52,33 @@ The CLI packages, deploys and checks. The app-side code is the SDK
|
|
|
52
52
|
`github.com/synopackageland/syno-pkg-land-sdk` and the npm package `@synopackageland/sdk` (Vue components at
|
|
53
53
|
`@synopackageland/sdk/vue`). The `require github.com/synopackageland/syno-pkg-land-sdk` line in
|
|
54
54
|
`backend/go.mod` is the app's only SDK version; `build` compiles the executor of that version
|
|
55
|
-
and checks
|
|
56
|
-
|
|
55
|
+
and checks both payloads against the resolved SDK's `executor-manifest.json`.
|
|
56
|
+
The manifest declares format 1, the exact executor release, its Go toolchain and
|
|
57
|
+
six SHA-256 digests. SDK minor versions are independent of CLI minor versions.
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
a failed fetch reports `SDK_DOWNLOAD_FAILED`.
|
|
59
|
+
SDK module downloads preserve normal Go checksum verification and the user's Go
|
|
60
|
+
settings; Go Apps also check their existing `backend/go.sum`. Commit that file
|
|
61
|
+
alongside npm lockfiles. Do not disable checksum verification for public SDK
|
|
62
|
+
packages. Private mirrors can use explicitly configured Go authentication and
|
|
63
|
+
checksum settings; a failed fetch reports `SDK_DOWNLOAD_FAILED`.
|
|
63
64
|
|
|
64
65
|
CLI-only patch releases can differ from the SDK patch. Compose import and SDK defaults
|
|
65
66
|
read the SDK pin from the installed templates; explicit App SDK declarations take precedence.
|
|
66
67
|
|
|
67
68
|
`init` and `add` read their templates from the locally installed
|
|
68
|
-
`@synopackageland/templates`. CLI 0.
|
|
69
|
-
update can select the latest templates patch in the 0.
|
|
69
|
+
`@synopackageland/templates`. CLI 0.28.0 depends on `~0.28.0`: npm installation or
|
|
70
|
+
update can select the latest templates patch in the 0.28 minor line. Templates
|
|
70
71
|
patches need only a templates release; update the npm installation to receive them.
|
|
71
72
|
The CLI never fetches templates at runtime, so offline use works after installation;
|
|
72
73
|
commit a lockfile and use `npm ci` to reproduce a chosen dependency tree.
|
|
73
|
-
The
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
The CLI accepts SDK executor manifest format 1 across SDK minor versions. The
|
|
75
|
+
exact SDK Go module sum anchors the build manifest; npm integrity anchors the
|
|
76
|
+
identical manifest in `@synopackageland/sdk/executor-manifest.json`. No manifest
|
|
77
|
+
is fetched at NAS runtime. Missing, malformed or unsupported new manifests fail
|
|
78
|
+
closed. SDK 0.26.0 and 0.27.0 retain the CLI's frozen legacy inventory.
|
|
79
|
+
SDK defaults continue to come from the actual installed templates. An SDK minor
|
|
80
|
+
release no longer requires a CLI release; new CLI capabilities or a new manifest
|
|
81
|
+
format can still require one.
|
|
77
82
|
|
|
78
83
|
For development against local checkouts, set `SYNOPKGLAND_TEMPLATES_DIR` to a
|
|
79
84
|
templates checkout and `SYNOPKGLAND_SDK_DIR` to an SDK checkout; SPKs built with
|
package/dist/error-registry.js
CHANGED
|
@@ -909,8 +909,13 @@ export const ERROR_REGISTRY = {
|
|
|
909
909
|
},
|
|
910
910
|
SDK_UNSUPPORTED: {
|
|
911
911
|
code: "SDK_UNSUPPORTED",
|
|
912
|
-
description: "The SDK
|
|
913
|
-
suggestion: "Use
|
|
912
|
+
description: "The SDK has an unsupported executor manifest format or lacks a known legacy inventory.",
|
|
913
|
+
suggestion: "Use an SDK with executor manifest format 1, or update the CLI when a new manifest format is needed.",
|
|
914
|
+
},
|
|
915
|
+
SDK_MANIFEST_INVALID: {
|
|
916
|
+
code: "SDK_MANIFEST_INVALID",
|
|
917
|
+
description: "The resolved SDK executor manifest has invalid versions, toolchain or payload digests.",
|
|
918
|
+
suggestion: "Reinstall the exact locked SDK package and verify its integrity; do not replace its manifest with runtime data.",
|
|
914
919
|
},
|
|
915
920
|
SDK_DIR_NOT_READY: {
|
|
916
921
|
code: "SDK_DIR_NOT_READY",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { goChildEnv } from "./go-env.js";
|
|
2
2
|
import { mkdirSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { PINNED_GO_TOOLCHAIN, pinnedGoBuildEnv, goBinaryVersion } from '../build-spk/go-toolchain.js';
|
|
5
5
|
import { sdkBackend } from '../standalone/manifest.js';
|
|
6
6
|
import { StandaloneBuildError } from '../standalone/spk.js';
|
|
7
7
|
import { checkCgiBinary } from '../standalone/binary.js';
|
|
@@ -11,7 +11,7 @@ import { copyModule, verifyExecutorDigests } from './executor-source.js';
|
|
|
11
11
|
import { requiredSdk } from './sdk-source.js';
|
|
12
12
|
import { rootProgramsUnsupported } from './root-programs.js';
|
|
13
13
|
import { executorPolicyProblem } from './executor-policy-check.js';
|
|
14
|
-
/**
|
|
14
|
+
/** Manifest format 1's reproducible build flags; changing them requires a new format. */
|
|
15
15
|
const GO_BUILD = ['build', '-trimpath', '-buildvcs=false', '-ldflags', '-s -w -buildid=', '-o'];
|
|
16
16
|
function failure(error) {
|
|
17
17
|
const message = error.message;
|
|
@@ -32,6 +32,8 @@ export async function buildExecutor(m, arch, work, run, options = {}) {
|
|
|
32
32
|
catch (error) {
|
|
33
33
|
throw failure(error);
|
|
34
34
|
}
|
|
35
|
+
const toolchain = sdk.manifest?.executorDigests.goToolchain ?? PINNED_GO_TOOLCHAIN;
|
|
36
|
+
const toolchainHint = `These executor binaries need ${toolchain}; install it on PATH or allow Go to download that exact toolchain.`;
|
|
35
37
|
const dir = join(work, `executor-${arch}`);
|
|
36
38
|
mkdirSync(dir);
|
|
37
39
|
const source = join(dir, 'source');
|
|
@@ -42,22 +44,24 @@ export async function buildExecutor(m, arch, work, run, options = {}) {
|
|
|
42
44
|
throw new StandaloneBuildError('EXECUTOR_BUILD_FAILED', `Executor sources of SDK ${sdk.version} are missing: ${error.message}`);
|
|
43
45
|
}
|
|
44
46
|
const binary = join(dir, 'executor');
|
|
45
|
-
const result = await run('go', [...GO_BUILD, binary, '.'], { cwd: source, env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }) });
|
|
47
|
+
const result = await run('go', [...GO_BUILD, binary, '.'], { cwd: source, env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch), GOTOOLCHAIN: toolchain }) });
|
|
46
48
|
if (result.missing || result.status !== 0)
|
|
47
|
-
throw new StandaloneBuildError('EXECUTOR_BUILD_FAILED', `Executor build failed (${arch}). ${
|
|
49
|
+
throw new StandaloneBuildError('EXECUTOR_BUILD_FAILED', `Executor build failed (${arch}). ${toolchainHint}\n${result.stderr}`);
|
|
48
50
|
const problem = checkCgiBinary(work, binary, arch);
|
|
49
51
|
if (problem)
|
|
50
52
|
throw new StandaloneBuildError(problem.code, problem.message);
|
|
51
53
|
const lifecycleBinary = join(dir, 'lifecycle');
|
|
52
|
-
const lifecycleResult = await run('go', [...GO_BUILD, lifecycleBinary, './cmd/lifecycle'], { cwd: source, env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }) });
|
|
54
|
+
const lifecycleResult = await run('go', [...GO_BUILD, lifecycleBinary, './cmd/lifecycle'], { cwd: source, env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch), GOTOOLCHAIN: toolchain }) });
|
|
53
55
|
if (lifecycleResult.missing || lifecycleResult.status !== 0)
|
|
54
|
-
throw new StandaloneBuildError('EXECUTOR_BUILD_FAILED', `Lifecycle helper build failed (${arch}). ${
|
|
56
|
+
throw new StandaloneBuildError('EXECUTOR_BUILD_FAILED', `Lifecycle helper build failed (${arch}). ${toolchainHint}\n${lifecycleResult.stderr}`);
|
|
55
57
|
const lifecycleProblem = checkCgiBinary(work, lifecycleBinary, arch);
|
|
56
58
|
if (lifecycleProblem)
|
|
57
59
|
throw new StandaloneBuildError(lifecycleProblem.code, lifecycleProblem.message);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
for (const file of [binary, lifecycleBinary]) {
|
|
61
|
+
const actual = goBinaryVersion(file);
|
|
62
|
+
if (actual !== toolchain)
|
|
63
|
+
throw new StandaloneBuildError('EXECUTOR_BUILD_FAILED', `${file} was built by ${actual ?? 'an unknown Go release'}, not ${toolchain}.`);
|
|
64
|
+
}
|
|
61
65
|
const executorDigest = sha256(readFileSync(binary));
|
|
62
66
|
let warnings;
|
|
63
67
|
try {
|
|
@@ -79,7 +83,7 @@ export async function buildExecutor(m, arch, work, run, options = {}) {
|
|
|
79
83
|
const policyText = executorPolicyText(policy);
|
|
80
84
|
let refused;
|
|
81
85
|
try {
|
|
82
|
-
refused = executorPolicyProblem(source, sdk.version, m.package, policyText, options.env);
|
|
86
|
+
refused = executorPolicyProblem(source, sdk.version, m.package, policyText, options.env, toolchain);
|
|
83
87
|
}
|
|
84
88
|
catch (error) {
|
|
85
89
|
throw failure(error);
|
|
@@ -4,9 +4,9 @@ import { type StandaloneManifest } from '../standalone/manifest.js';
|
|
|
4
4
|
* undefined when it accepts it. The executor is
|
|
5
5
|
* built for this host from executorDir, so its own rules decide, not a copy in the CLI.
|
|
6
6
|
*/
|
|
7
|
-
export declare function executorPolicyRefusal(executorDir: string, pkg: string, policyText: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
7
|
+
export declare function executorPolicyRefusal(executorDir: string, pkg: string, policyText: string, env?: NodeJS.ProcessEnv, toolchain?: string): string | undefined;
|
|
8
8
|
/** The EXECUTOR_POLICY_REJECTED message for a policy the SDK executor refuses; undefined when it accepts it. */
|
|
9
|
-
export declare function executorPolicyProblem(executorDir: string, sdkVersion: string, pkg: string, policyText: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
9
|
+
export declare function executorPolicyProblem(executorDir: string, sdkVersion: string, pkg: string, policyText: string, env?: NodeJS.ProcessEnv, toolchain?: string): string | undefined;
|
|
10
10
|
/** The policy of each architecture `check` would build, checked by the App's SDK executor; Apps without executor need none. */
|
|
11
11
|
export declare function manifestPolicyProblem(m: StandaloneManifest, env?: NodeJS.ProcessEnv): {
|
|
12
12
|
code: string;
|
|
@@ -3,7 +3,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
3
3
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { PINNED_GO_TOOLCHAIN } from '../build-spk/go-toolchain.js';
|
|
7
7
|
import { sdkBackend } from '../standalone/manifest.js';
|
|
8
8
|
import { executorPolicy, executorPolicyText } from './executor-policy.js';
|
|
9
9
|
import { requiredSdk } from './sdk-source.js';
|
|
@@ -14,14 +14,14 @@ const CHECK_POLICY = 'check-policy';
|
|
|
14
14
|
* undefined when it accepts it. The executor is
|
|
15
15
|
* built for this host from executorDir, so its own rules decide, not a copy in the CLI.
|
|
16
16
|
*/
|
|
17
|
-
export function executorPolicyRefusal(executorDir, pkg, policyText, env = process.env) {
|
|
17
|
+
export function executorPolicyRefusal(executorDir, pkg, policyText, env = process.env, toolchain = PINNED_GO_TOOLCHAIN) {
|
|
18
18
|
const dir = mkdtempSync(join(tmpdir(), 'synopkgland-check-policy-'));
|
|
19
19
|
try {
|
|
20
20
|
const binary = join(dir, 'executor');
|
|
21
21
|
const { GOOS: _os, GOARCH: _arch, ...host } = env;
|
|
22
|
-
const build = spawnSync('go', ['build', '-o', binary, '.'], { cwd: executorDir, env: goChildEnv({ ...host, GOTOOLCHAIN:
|
|
22
|
+
const build = spawnSync('go', ['build', '-o', binary, '.'], { cwd: executorDir, env: goChildEnv({ ...host, GOTOOLCHAIN: toolchain, GOFLAGS: '', GOWORK: 'off', CGO_ENABLED: '0' }), encoding: 'utf8' });
|
|
23
23
|
if (build.error || build.status !== 0)
|
|
24
|
-
throw new Error(`[EXECUTOR_BUILD_FAILED] Building the SDK executor for this host to check the policy failed. ${
|
|
24
|
+
throw new Error(`[EXECUTOR_BUILD_FAILED] Building the SDK executor for this host to check the policy failed. This SDK's executor needs ${toolchain}.\n${build.stderr || build.error?.message}`);
|
|
25
25
|
const result = spawnSync(binary, [CHECK_POLICY, '--package', pkg], { input: policyText, encoding: 'utf8' });
|
|
26
26
|
if (result.status === 0)
|
|
27
27
|
return undefined;
|
|
@@ -43,13 +43,13 @@ const POLICY_FIX = [
|
|
|
43
43
|
[/policy entry/, 'fix entry in dsm-package.yaml'],
|
|
44
44
|
];
|
|
45
45
|
/** The EXECUTOR_POLICY_REJECTED message for a policy the SDK executor refuses; undefined when it accepts it. */
|
|
46
|
-
export function executorPolicyProblem(executorDir, sdkVersion, pkg, policyText, env = process.env) {
|
|
47
|
-
const reason = executorPolicyRefusal(executorDir, pkg, policyText, env);
|
|
46
|
+
export function executorPolicyProblem(executorDir, sdkVersion, pkg, policyText, env = process.env, toolchain = PINNED_GO_TOOLCHAIN) {
|
|
47
|
+
const reason = executorPolicyRefusal(executorDir, pkg, policyText, env, toolchain);
|
|
48
48
|
if (!reason)
|
|
49
49
|
return undefined;
|
|
50
50
|
const fix = POLICY_FIX.find(([pattern]) => pattern.test(reason))?.[1] ?? 'fix the declaration in dsm-package.yaml';
|
|
51
51
|
return `The executor of SDK ${sdkVersion} refuses this App's policy (executor: ${reason}), so the NAS would refuse it at approval: ${fix}.`
|
|
52
|
-
+ ' If dsm-package.yaml already matches the manifest reference,
|
|
52
|
+
+ ' If dsm-package.yaml already matches the manifest reference, check the SDK manifest contract and the CLI capability support.';
|
|
53
53
|
}
|
|
54
54
|
/** The policy of each architecture `check` would build, checked by the App's SDK executor; Apps without executor need none. */
|
|
55
55
|
export function manifestPolicyProblem(m, env = process.env) {
|
|
@@ -58,7 +58,7 @@ export function manifestPolicyProblem(m, env = process.env) {
|
|
|
58
58
|
try {
|
|
59
59
|
const sdk = requiredSdk(sdkBackend(m), env);
|
|
60
60
|
for (const text of new Set(m.arches.map(arch => executorPolicyText(executorPolicy(m, arch))))) {
|
|
61
|
-
const message = executorPolicyProblem(sdk.executorDir, sdk.version, m.package, text, env);
|
|
61
|
+
const message = executorPolicyProblem(sdk.executorDir, sdk.version, m.package, text, env, sdk.manifest?.executorDigests.goToolchain);
|
|
62
62
|
if (message)
|
|
63
63
|
return { code: 'EXECUTOR_POLICY_REJECTED', message };
|
|
64
64
|
}
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import type { NativeArchFamily } from '../build-spk/dsm-arch.js';
|
|
2
2
|
import type { SdkSource } from './sdk-source.js';
|
|
3
|
-
type
|
|
3
|
+
import { type ExecutorDigests } from './sdk-executor-manifest.js';
|
|
4
4
|
/** Released SDK versions: the sha256 of the executor and lifecycle helper built from them, per architecture. */
|
|
5
|
-
export type ExecutorDigestTable = Record<string,
|
|
6
|
-
lifecycle: ArchDigests;
|
|
7
|
-
goToolchain: string;
|
|
8
|
-
}>;
|
|
5
|
+
export type ExecutorDigestTable = Record<string, ExecutorDigests>;
|
|
9
6
|
export declare const SHIPPED_EXECUTOR_DIGESTS: ExecutorDigestTable;
|
|
10
7
|
/** Copy a module directory (the module cache is read-only) so the build owns a writable tree. */
|
|
11
8
|
export declare function copyModule(source: string, target: string): void;
|
|
12
9
|
/**
|
|
13
10
|
* A released SDK's executor is only shipped when its bytes equal the digest
|
|
14
|
-
* table; a local SDK checkout
|
|
11
|
+
* manifest (or frozen legacy table); a local SDK checkout is built unverified.
|
|
15
12
|
* Returns the warnings to show.
|
|
16
13
|
*/
|
|
17
14
|
export declare function verifyExecutorDigests(sdk: SdkSource, arch: NativeArchFamily, built: {
|
|
18
15
|
executor: string;
|
|
19
16
|
lifecycle: string;
|
|
20
17
|
}, table?: ExecutorDigestTable): string[];
|
|
21
|
-
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chmodSync, cpSync, lstatSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import EXECUTOR_DIGESTS from './executor-digests.json' with { type: 'json' };
|
|
4
|
+
import { readExecutorManifest, predatesExecutorManifest } from './sdk-executor-manifest.js';
|
|
4
5
|
export const SHIPPED_EXECUTOR_DIGESTS = EXECUTOR_DIGESTS;
|
|
5
6
|
/** Copy a module directory (the module cache is read-only) so the build owns a writable tree. */
|
|
6
7
|
export function copyModule(source, target) {
|
|
@@ -18,13 +19,14 @@ export function copyModule(source, target) {
|
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
21
|
* A released SDK's executor is only shipped when its bytes equal the digest
|
|
21
|
-
* table; a local SDK checkout
|
|
22
|
+
* manifest (or frozen legacy table); a local SDK checkout is built unverified.
|
|
22
23
|
* Returns the warnings to show.
|
|
23
24
|
*/
|
|
24
25
|
export function verifyExecutorDigests(sdk, arch, built, table = SHIPPED_EXECUTOR_DIGESTS) {
|
|
25
26
|
if (sdk.local)
|
|
26
27
|
return [`[EXECUTOR_DIGEST_UNVERIFIED] executor built from the local SDK checkout SYNOPKGLAND_SDK_DIR (${arch}, sha256 ${built.executor}); the digest table was not checked. Do not publish this SPK.`];
|
|
27
|
-
const
|
|
28
|
+
const manifest = sdk.manifest ?? readExecutorManifest(sdk.goDir, sdk.version);
|
|
29
|
+
const expected = manifest?.executorDigests ?? (predatesExecutorManifest(sdk.version) ? table[sdk.version] : undefined);
|
|
28
30
|
if (!expected)
|
|
29
31
|
throw new Error(`[EXECUTOR_DIGEST_MISMATCH] This CLI has no recorded executor digest for SDK ${sdk.version}; it knows ${Object.keys(table).join(', ') || 'no released SDK'}.`);
|
|
30
32
|
if (expected[arch] !== built.executor)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NativeArchFamily } from '../build-spk/dsm-arch.js';
|
|
2
|
+
export type ArchDigests = Record<NativeArchFamily, string>;
|
|
3
|
+
export type ExecutorDigests = ArchDigests & {
|
|
4
|
+
lifecycle: ArchDigests;
|
|
5
|
+
goToolchain: string;
|
|
6
|
+
};
|
|
7
|
+
export interface ExecutorManifest {
|
|
8
|
+
formatVersion: 1;
|
|
9
|
+
sdkVersion: string;
|
|
10
|
+
executorVersion: string;
|
|
11
|
+
executorDigests: ExecutorDigests;
|
|
12
|
+
}
|
|
13
|
+
/** Read only the resolved, integrity-protected SDK module, never a remote manifest. */
|
|
14
|
+
export declare function readExecutorManifest(goDir: string, version: string): ExecutorManifest | undefined;
|
|
15
|
+
/** Only SDKs released before the manifest contract can use the frozen inventory. */
|
|
16
|
+
export declare function predatesExecutorManifest(version: string): boolean;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const versionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
4
|
+
/** Read only the resolved, integrity-protected SDK module, never a remote manifest. */
|
|
5
|
+
export function readExecutorManifest(goDir, version) {
|
|
6
|
+
let text;
|
|
7
|
+
try {
|
|
8
|
+
text = readFileSync(join(goDir, 'executor-manifest.json'), 'utf8');
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code === 'ENOENT')
|
|
12
|
+
return undefined;
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
const invalid = () => { throw new Error(`[SDK_MANIFEST_INVALID] SDK ${version} has an invalid executor-manifest.json; reinstall the exact locked SDK package.`); };
|
|
16
|
+
let m;
|
|
17
|
+
try {
|
|
18
|
+
m = JSON.parse(text);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return invalid();
|
|
22
|
+
}
|
|
23
|
+
if (!m || typeof m !== 'object' || !Number.isInteger(m.formatVersion))
|
|
24
|
+
return invalid();
|
|
25
|
+
if (m.formatVersion !== 1)
|
|
26
|
+
throw new Error(`[SDK_UNSUPPORTED] SDK ${version} declares executor manifest format ${m.formatVersion}; this CLI understands format 1. Update the CLI to support that format.`);
|
|
27
|
+
if (m.sdkVersion !== version || !versionPattern.test(m.sdkVersion) || typeof m.executorVersion !== 'string' || !versionPattern.test(m.executorVersion))
|
|
28
|
+
return invalid();
|
|
29
|
+
const d = m.executorDigests;
|
|
30
|
+
if (!d || typeof d.goToolchain !== 'string' || !/^go1\.\d+\.\d+$/.test(d.goToolchain))
|
|
31
|
+
return invalid();
|
|
32
|
+
for (const hashes of [d, d.lifecycle])
|
|
33
|
+
for (const arch of ['x86_64', 'aarch64', 'armv7']) {
|
|
34
|
+
if (!hashes || typeof hashes[arch] !== 'string' || !/^[a-f0-9]{64}$/.test(hashes[arch]))
|
|
35
|
+
return invalid();
|
|
36
|
+
}
|
|
37
|
+
return m;
|
|
38
|
+
}
|
|
39
|
+
/** Only SDKs released before the manifest contract can use the frozen inventory. */
|
|
40
|
+
export function predatesExecutorManifest(version) {
|
|
41
|
+
if (!versionPattern.test(version))
|
|
42
|
+
return false;
|
|
43
|
+
const [major, minor, patch] = version.split('.').map(Number);
|
|
44
|
+
return major === 0 && (minor < 27 || (minor === 27 && patch === 0));
|
|
45
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { goPrivateCovers } from './go-env.js';
|
|
2
|
+
import { type ExecutorManifest } from './sdk-executor-manifest.js';
|
|
2
3
|
export declare const SDK_PACKAGE = "@synopackageland/sdk";
|
|
3
4
|
export declare const SDK_MODULE = "github.com/synopackageland/syno-pkg-land-sdk";
|
|
4
5
|
export declare const EXECUTOR_MODULE = "github.com/synopackageland/syno-pkg-land-sdk/executor";
|
|
@@ -19,6 +20,7 @@ export interface SdkSource {
|
|
|
19
20
|
goDir: string;
|
|
20
21
|
executorDir: string;
|
|
21
22
|
local: boolean;
|
|
23
|
+
manifest?: ExecutorManifest;
|
|
22
24
|
}
|
|
23
25
|
/** How each backend declares the SDK. */
|
|
24
26
|
export declare const SDK_DEPENDENCY: Record<SdkBackend['kind'], string>;
|
|
@@ -41,7 +43,7 @@ export declare function goModulePath(backendDir: string): string | undefined;
|
|
|
41
43
|
*/
|
|
42
44
|
export declare function sdkSource(version: string, env?: NodeJS.ProcessEnv, download?: (spec: string) => string): SdkSource;
|
|
43
45
|
/** The module cache directory of module@version; the cache is read-only, so builds copy it first. */
|
|
44
|
-
export declare function goModDownload(spec: string, env?: NodeJS.ProcessEnv): string;
|
|
46
|
+
export declare function goModDownload(spec: string, env?: NodeJS.ProcessEnv, moduleDir?: string): string;
|
|
45
47
|
/** The SDK an App builds its executor with; delegate and rootOps cannot build without one. */
|
|
46
48
|
export declare function requiredSdk(backend: SdkBackend, env?: NodeJS.ProcessEnv, download?: (spec: string) => string): SdkSource;
|
|
47
49
|
/** Go children fetch the private SDK directly; failed fetches need git credentials. */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { goChildEnv } from './go-env.js';
|
|
3
2
|
export { goPrivateCovers } from './go-env.js';
|
|
4
3
|
import { readFileSync } from 'node:fs';
|
|
5
4
|
import { tmpdir } from 'node:os';
|
|
6
5
|
import { join } from 'node:path';
|
|
6
|
+
import { readExecutorManifest, predatesExecutorManifest } from './sdk-executor-manifest.js';
|
|
7
7
|
export const SDK_PACKAGE = '@synopackageland/sdk';
|
|
8
8
|
export const SDK_MODULE = 'github.com/synopackageland/syno-pkg-land-sdk';
|
|
9
9
|
export const EXECUTOR_MODULE = `${SDK_MODULE}/executor`;
|
|
@@ -86,16 +86,23 @@ export function sdkSource(version, env = process.env, download = (spec) => goMod
|
|
|
86
86
|
const dir = env.SYNOPKGLAND_SDK_DIR;
|
|
87
87
|
if (dir)
|
|
88
88
|
return { version, goDir: dir, executorDir: join(dir, 'executor'), local: true };
|
|
89
|
-
|
|
89
|
+
const goDir = download(`${SDK_MODULE}@v${version}`);
|
|
90
|
+
const manifest = readExecutorManifest(goDir, version);
|
|
91
|
+
if (!manifest && !predatesExecutorManifest(version))
|
|
92
|
+
throw new Error(`[SDK_UNSUPPORTED] SDK ${version} is missing executor-manifest.json; install a released SDK with manifest format 1.`);
|
|
93
|
+
return { version, goDir, executorDir: download(`${EXECUTOR_MODULE}@v${manifest?.executorVersion ?? EXECUTOR_RELEASES[version] ?? version}`), local: false, ...(manifest ? { manifest } : {}) };
|
|
90
94
|
}
|
|
91
95
|
const downloaded = new Map();
|
|
92
96
|
/** The module cache directory of module@version; the cache is read-only, so builds copy it first. */
|
|
93
|
-
export function goModDownload(spec, env = process.env) {
|
|
94
|
-
const key = [spec, env.GOPROXY, env.GOMODCACHE, env.GOPATH].join('\0');
|
|
97
|
+
export function goModDownload(spec, env = process.env, moduleDir = tmpdir()) {
|
|
98
|
+
const key = [spec, moduleDir, env.GOPROXY, env.GOMODCACHE, env.GOPATH, env.GOSUMDB, env.GONOSUMDB, env.GOPRIVATE].join('\0');
|
|
95
99
|
const cached = downloaded.get(key);
|
|
96
100
|
if (cached)
|
|
97
101
|
return cached;
|
|
98
|
-
|
|
102
|
+
// Public SDK manifests rely on Go module sums. Do not add the namespace to
|
|
103
|
+
// GOPRIVATE/GONOSUMDB as other legacy build children do. For Go Apps the
|
|
104
|
+
// command also checks the App's existing go.sum in its own module directory.
|
|
105
|
+
const result = spawnSync('go', ['mod', 'download', '-json', spec], { cwd: moduleDir, env: { ...env, GOWORK: 'off', GOFLAGS: '' }, encoding: 'utf8' });
|
|
99
106
|
let info = {};
|
|
100
107
|
try {
|
|
101
108
|
info = JSON.parse(result.stdout);
|
|
@@ -104,6 +111,8 @@ export function goModDownload(spec, env = process.env) {
|
|
|
104
111
|
if (result.error || result.status !== 0 || !info.Dir) {
|
|
105
112
|
throw new Error(`[SDK_DOWNLOAD_FAILED] go mod download ${spec} failed: ${info.Error ?? result.stderr ?? result.error?.message}${privateSdkHint()}`);
|
|
106
113
|
}
|
|
114
|
+
if (readExecutorManifest(info.Dir, spec.split('@v')[1] ?? '') && (typeof info.Sum !== 'string' || !/^h1:[A-Za-z0-9+/]{43}=$/.test(info.Sum)))
|
|
115
|
+
throw new Error(`[SDK_MANIFEST_INVALID] SDK module ${spec} has no verified Go module sum.`);
|
|
107
116
|
downloaded.set(key, info.Dir);
|
|
108
117
|
return info.Dir;
|
|
109
118
|
}
|
|
@@ -112,7 +121,7 @@ export function requiredSdk(backend, env = process.env, download) {
|
|
|
112
121
|
const version = appSdkVersion(backend);
|
|
113
122
|
if (!version)
|
|
114
123
|
throw new Error(`[SDK_REQUIRED] capabilities.delegate and rootOps need the SDK: ${SDK_DEPENDENCY[backend.kind]}`);
|
|
115
|
-
return
|
|
124
|
+
return sdkSource(version, env, download ?? (spec => goModDownload(spec, env, backend.kind === 'go' ? backend.dir : undefined)));
|
|
116
125
|
}
|
|
117
126
|
/** Go children fetch the private SDK directly; failed fetches need git credentials. */
|
|
118
127
|
export function privateSdkHint() {
|
|
@@ -13,7 +13,7 @@ export const COMPOSE_ERRORS = {
|
|
|
13
13
|
COMPOSE_STARTUP_INVALID: {
|
|
14
14
|
code: "COMPOSE_STARTUP_INVALID",
|
|
15
15
|
description: "The container startup budget is invalid.",
|
|
16
|
-
suggestion: "Set compose.startup_timeout_seconds to an integer from 2 to 900 seconds; the default is
|
|
16
|
+
suggestion: "Set compose.startup_timeout_seconds to an integer from 2 to 900 seconds; the default is 900, which a loaded HDD NAS may need. Read the same SYNOPKGLAND_STARTUP_BUDGET_SECONDS in the App bootstrap adapter.",
|
|
17
17
|
},
|
|
18
18
|
COMPOSE_STARTUP_TIMEOUT: {
|
|
19
19
|
code: "COMPOSE_STARTUP_TIMEOUT",
|
|
@@ -82,8 +82,8 @@ export const COMPOSE_ERRORS = {
|
|
|
82
82
|
},
|
|
83
83
|
COMPOSE_HEALTH_NOT_READY: {
|
|
84
84
|
code: "COMPOSE_HEALTH_NOT_READY",
|
|
85
|
-
description: "The App health endpoint did not return the expected status.",
|
|
86
|
-
suggestion: "Check the declared App health path/status and upstream health; redirects are not followed.",
|
|
85
|
+
description: "The App health endpoint did not return the expected status within the startup budget.",
|
|
86
|
+
suggestion: "Check the declared App health path/status and upstream health; redirects are not followed. daemon/daemon.log records how long the App stayed unavailable. Keep the default startup budget on slow NAS devices.",
|
|
87
87
|
},
|
|
88
88
|
COMPOSE_REQUEST_INVALID: {
|
|
89
89
|
code: "COMPOSE_REQUEST_INVALID",
|
|
@@ -77,7 +77,7 @@ export function readComposeApp(value, root, arches, fail, context = {}) {
|
|
|
77
77
|
if (value.startup_timeout_seconds !== undefined &&
|
|
78
78
|
(!Number.isInteger(value.startup_timeout_seconds) ||
|
|
79
79
|
value.startup_timeout_seconds < 2 || value.startup_timeout_seconds > 900))
|
|
80
|
-
return invalid("compose.startup_timeout_seconds must be an integer from 2 to 900 seconds (default
|
|
80
|
+
return invalid("compose.startup_timeout_seconds must be an integer from 2 to 900 seconds (default 900).", "COMPOSE_STARTUP_INVALID");
|
|
81
81
|
let hostUdp;
|
|
82
82
|
if (value.host_udp !== undefined) {
|
|
83
83
|
const ports = new Set();
|
|
@@ -178,7 +178,7 @@ export function composeDaemon(m) {
|
|
|
178
178
|
args: ["run"],
|
|
179
179
|
targets: m.compose.targets,
|
|
180
180
|
env: {},
|
|
181
|
-
timeouts: { stop: 30, status: 5, uninstall: 60, ...m.lifecycleTimeouts, start: m.compose.plan.startup_timeout_seconds ?? m.lifecycleTimeouts?.start ??
|
|
181
|
+
timeouts: { stop: 30, status: 5, uninstall: 60, ...m.lifecycleTimeouts, start: m.compose.plan.startup_timeout_seconds ?? m.lifecycleTimeouts?.start ?? 900 },
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
184
|
export function composeNetwork(m) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Only the fixed DSM CGI receives a resume token; external origins are resolved by the SDK. */
|
|
2
2
|
export declare function oidcResumeRuntime(pkg: string, ports: number[]): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Only the fixed DSM CGI receives a resume token; external origins are resolved by the SDK. */
|
|
2
2
|
export function oidcResumeRuntime(pkg, ports) {
|
|
3
3
|
return `(function(param) {
|
|
4
4
|
if (typeof param === 'string') {
|
|
@@ -14,13 +14,18 @@ export function oidcResumeRuntime(pkg, ports) {
|
|
|
14
14
|
}
|
|
15
15
|
if (!param || typeof param !== 'object' || Array.isArray(param)) return false;
|
|
16
16
|
var value = param && param.ssoResume;
|
|
17
|
-
if (typeof value !== 'string') return false;
|
|
17
|
+
if (typeof value !== 'string' || /\\s/.test(value)) return false;
|
|
18
|
+
var external = /^[A-Za-z0-9_-]{1,128}:x[01]:[0-9]{1,19}\\.[0-9]{1,19}$/.test(value);
|
|
18
19
|
var parts = /^([A-Za-z0-9_-]{1,128}):([0-9]{1,5})$/.exec(value);
|
|
19
|
-
if (!parts || ${JSON.stringify(ports)}.indexOf(Number(parts[2])) < 0) return false;
|
|
20
|
+
if (!external && (!parts || ${JSON.stringify(ports)}.indexOf(Number(parts[2])) < 0)) return false;
|
|
20
21
|
var target = new URL(${JSON.stringify(`/webman/3rdparty/${pkg}/sso/authorize.cgi`)},location.origin);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
if (external) {
|
|
23
|
+
target.searchParams.set('resume',value);
|
|
24
|
+
} else {
|
|
25
|
+
var app = new URL(location.origin); app.port = parts[2]; app.pathname = '/_sso/dsm-return';
|
|
26
|
+
target.searchParams.set('return',app.href);
|
|
27
|
+
target.searchParams.set('txn',parts[1]);
|
|
28
|
+
}
|
|
24
29
|
target.searchParams.set('login_attempt','1');
|
|
25
30
|
location.replace(target.href);
|
|
26
31
|
return true;
|
|
@@ -5,7 +5,7 @@ export interface SdkProblem {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* The backend's dependency files name the App's SDK version; the frontend
|
|
8
|
-
*
|
|
8
|
+
* must agree, and the CLI must understand its manifest. `needsSdk`: the App declares
|
|
9
9
|
* delegate or rootOps.
|
|
10
10
|
*/
|
|
11
|
-
export declare function sdkProblems(backend: SdkBackend, frontendDir: string | undefined, needsSdk: boolean, cliVersion?: string): SdkProblem[];
|
|
11
|
+
export declare function sdkProblems(backend: SdkBackend, frontendDir: string | undefined, needsSdk: boolean, cliVersion?: string, env?: NodeJS.ProcessEnv, download?: (spec: string) => string): SdkProblem[];
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { CLI_VERSION } from "../cli-version.js";
|
|
4
|
-
import { appSdkVersion, SDK_DEPENDENCY, SDK_FLOOR, SDK_PACKAGE, sdkBelowFloor } from "../runtime/sdk-source.js";
|
|
5
|
-
|
|
4
|
+
import { appSdkVersion, SDK_DEPENDENCY, SDK_FLOOR, SDK_PACKAGE, sdkBelowFloor, sdkSource, goModDownload } from "../runtime/sdk-source.js";
|
|
5
|
+
import { predatesExecutorManifest } from '../runtime/sdk-executor-manifest.js';
|
|
6
|
+
import { SHIPPED_EXECUTOR_DIGESTS } from '../runtime/executor-source.js';
|
|
6
7
|
/** The file that names each backend kind's SDK version. */
|
|
7
8
|
const SDK_VERSION_FILE = { go: "backend/go.mod", rust: "Cargo.lock", python: "backend.sdk", node: "backend.sdk" };
|
|
8
9
|
/**
|
|
9
10
|
* The backend's dependency files name the App's SDK version; the frontend
|
|
10
|
-
*
|
|
11
|
+
* must agree, and the CLI must understand its manifest. `needsSdk`: the App declares
|
|
11
12
|
* delegate or rootOps.
|
|
12
13
|
*/
|
|
13
|
-
export function sdkProblems(backend, frontendDir, needsSdk, cliVersion = CLI_VERSION) {
|
|
14
|
+
export function sdkProblems(backend, frontendDir, needsSdk, cliVersion = CLI_VERSION, env = process.env, download) {
|
|
14
15
|
let version;
|
|
15
16
|
try {
|
|
16
17
|
version = appSdkVersion(backend);
|
|
@@ -22,16 +23,29 @@ export function sdkProblems(backend, frontendDir, needsSdk, cliVersion = CLI_VER
|
|
|
22
23
|
throw error;
|
|
23
24
|
}
|
|
24
25
|
if (!version) {
|
|
25
|
-
return needsSdk ? [{ code: "SDK_REQUIRED", message: `Python, Node and Rust Apps, plus capabilities.delegate and rootOps, need the SDK
|
|
26
|
+
return needsSdk ? [{ code: "SDK_REQUIRED", message: `Python, Node and Rust Apps, plus capabilities.delegate and rootOps, need the SDK: ${SDK_DEPENDENCY[backend.kind]}` }] : [];
|
|
26
27
|
}
|
|
27
28
|
const file = SDK_VERSION_FILE[backend.kind];
|
|
28
29
|
const problems = [];
|
|
29
|
-
if (
|
|
30
|
-
problems.push({ code: "SDK_UNSUPPORTED", message: `${file} requires SDK ${version}; CLI ${cliVersion} supports only SDK ${minorLine(cliVersion)}.x. Use the matching CLI, or raise the SDK to ${minorLine(cliVersion)}.x.` });
|
|
31
|
-
}
|
|
32
|
-
else if (sdkBelowFloor(version)) {
|
|
30
|
+
if (sdkBelowFloor(version)) {
|
|
33
31
|
problems.push({ code: "SDK_UNSUPPORTED", message: `${file} requires SDK ${version}; CLI ${cliVersion} supports SDK ${SDK_FLOOR} or later. Raise the SDK to ${SDK_FLOOR} or later.` });
|
|
34
32
|
}
|
|
33
|
+
else if (predatesExecutorManifest(version)) {
|
|
34
|
+
if (!SHIPPED_EXECUTOR_DIGESTS[version])
|
|
35
|
+
problems.push({ code: 'SDK_UNSUPPORTED', message: `${file} requires SDK ${version}; this pre-manifest SDK has no pinned executor inventory. Use SDK 0.26.0, 0.27.0, or a release with executor manifest format 1.` });
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
try {
|
|
39
|
+
sdkSource(version, env, download ?? (spec => goModDownload(spec, env, backend.kind === 'go' ? backend.dir : undefined)));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const message = error.message;
|
|
43
|
+
const tagged = /^\[([A-Z_]+)\] ([\s\S]*)$/.exec(message);
|
|
44
|
+
if (!tagged)
|
|
45
|
+
throw error;
|
|
46
|
+
problems.push({ code: tagged[1], message: tagged[2] });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
35
49
|
const frontend = frontendDir === undefined ? undefined : frontendSdkVersion(frontendDir);
|
|
36
50
|
if (frontend && frontend.version !== version) {
|
|
37
51
|
problems.push({ code: "SDK_VERSION_MISMATCH", message: `frontend/package.json has ${SDK_PACKAGE} ${frontend.spec} (${frontend.version ?? "version unreadable"}), while ${file} requires ${version}; the two must match.` });
|
package/dist/standalone/spk.js
CHANGED
|
@@ -410,7 +410,7 @@ function copyUiTree(source, target) {
|
|
|
410
410
|
throw new StandaloneBuildError("STANDALONE_UI_TOO_LARGE", "The UI directory exceeds 64 MiB.");
|
|
411
411
|
}
|
|
412
412
|
copyFileSync(src, join(to, name));
|
|
413
|
-
chmodSync(join(to, name), FILE_MODE);
|
|
413
|
+
chmodSync(join(to, name), name.endsWith(".cgi") ? EXEC_MODE : FILE_MODE);
|
|
414
414
|
}
|
|
415
415
|
else {
|
|
416
416
|
throw new StandaloneBuildError("STANDALONE_UI_SPECIAL_FILE", `The UI directory may contain only regular files and directories: ${src}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Syno Package Land Developer Kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"build": "rm -rf dist && tsc",
|
|
21
21
|
"test": "npm run build && node --import tsx --test --test-concurrency=${SYNOPKGLAND_GATE_JOBS:-3} test/*.test.ts test/ctl/*.test.ts",
|
|
22
22
|
"test:glossary": "node --test scripts/test/glossary-drift.test.mjs",
|
|
23
|
-
"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/
|
|
23
|
+
"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/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",
|
|
24
24
|
"gates": "bash scripts/ci/gates.sh",
|
|
25
25
|
"gates:fast": "bash scripts/ci/gates.sh --fast",
|
|
26
26
|
"prepublishOnly": "npm run build"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@napi-rs/keyring": "^2.1.1",
|
|
30
|
-
"@synopackageland/templates": "~0.
|
|
30
|
+
"@synopackageland/templates": "~0.28.0",
|
|
31
31
|
"ajv": "^8.17.1",
|
|
32
32
|
"commander": "^13.1.0",
|
|
33
33
|
"json-canonicalize": "^3.0.1",
|
package/skills/SKILL.md
CHANGED
|
@@ -9,9 +9,12 @@ CLI 0.27.x installs templates through `~0.27.0`. A templates patch needs only a
|
|
|
9
9
|
templates release; npm install/update selects a patch in that minor line. The CLI
|
|
10
10
|
uses the locally installed templates and reads their SDK pin for defaults; it
|
|
11
11
|
never downloads templates at runtime. Preserve the lockfile and use `npm ci` for
|
|
12
|
-
reproducible offline-ready installations.
|
|
13
|
-
CLI
|
|
14
|
-
|
|
12
|
+
reproducible offline-ready installations. SDK minor releases are independent of
|
|
13
|
+
the CLI: executor manifest format 1 in the resolved SDK pins the executor release,
|
|
14
|
+
Go toolchain and six payload digests. Preserve npm integrity and Go module sums.
|
|
15
|
+
The CLI verifies bytes before packaging and never fetches a manifest at NAS runtime.
|
|
16
|
+
Unsupported formats require a CLI update; invalid manifests fail closed. Existing
|
|
17
|
+
SDK 0.26.0/0.27.0 use the frozen legacy inventory.
|
|
15
18
|
|
|
16
19
|
Before releasing an executor App, read [reference/approval-upgrades.md](reference/approval-upgrades.md). Run build/inspect with the previous released SPK as `--baseline` and include the approval difference in release notes. Without a comparable baseline, retention is unknown.
|
|
17
20
|
|
|
@@ -37,14 +37,21 @@ scripts run as the package account; containers are managed by Container Manager.
|
|
|
37
37
|
|
|
38
38
|
## Startup budget
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
Omit `compose.startup_timeout_seconds`: the default of 900 seconds lets a loaded
|
|
41
|
+
HDD NAS with 2 GB RAM start the App. Declare a shorter value only with device
|
|
42
|
+
evidence; only integers from 2 through 900 are accepted.
|
|
42
43
|
Lock waiting, status, readiness, stability and handoff share that deadline; there
|
|
43
44
|
is no extra fixed handoff allowance. The cap is a policy bound, not a measured
|
|
44
45
|
DSM lifecycle limit. `daemon: {timeouts: {stop: 120, status: 15, uninstall: 300}}`
|
|
45
46
|
configures the other lifecycle phases without declaring a daemon payload.
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
One readiness probe may take 30 seconds. A health failure after startup closes
|
|
48
|
+
new ingress and the gateway shows a localized, self-refreshing starting page
|
|
49
|
+
(`app_starting` for API clients) while DSM status stays running; the App gets
|
|
50
|
+
the startup budget to recover before its gateway stops. Authority or generation
|
|
51
|
+
failures stop at once. The SDK raises stricter Compose healthcheck timing to
|
|
52
|
+
timeout 30s, retries 3 and start_period equal to the startup budget; declare the
|
|
53
|
+
health command only, without timing or short in-command limits such as
|
|
54
|
+
`curl --max-time 2`.
|
|
48
55
|
Use a matching SDK that implements the field and rebuild the App; older planners
|
|
49
56
|
are rejected for explicit budgets. The SDK plan drives generated daemon start
|
|
50
57
|
and supplies every container with `SYNOPKGLAND_STARTUP_BUDGET_SECONDS`, overriding
|
|
@@ -93,7 +100,7 @@ a build operator trust decision. `check`/`inspect` remain offline.
|
|
|
93
100
|
|
|
94
101
|
| Feature | Current behavior |
|
|
95
102
|
| --- | --- |
|
|
96
|
-
| Files in one directory, profiles, anchors, depends_on, healthcheck | Reference-loader semantics retained; DSM worker compatibility needs device acceptance |
|
|
103
|
+
| Files in one directory, profiles, anchors, depends_on, healthcheck | Reference-loader semantics retained; healthcheck timing receives the SDK slow-NAS floor; DSM worker compatibility needs device acceptance |
|
|
97
104
|
| Named local volumes | Stable package namespace, retained across recreation; inspect inventory |
|
|
98
105
|
| Private bind data | DSM build rejects COMPOSE_PRIVATE_BIND_TIMING_UNSUPPORTED: clean-install preinst precedes pkgvar creation, while worker acquire precedes postinst; no root initialization or automatic volume conversion |
|
|
99
106
|
| Image VOLUME | Build inventories verified image config and assigns a stable named volume |
|
|
@@ -232,7 +239,7 @@ declared availability readiness, LAN isolation/egress, protected grant lifecycle
|
|
|
232
239
|
ACLs, two-user visibility and clean install/upgrade/reboot/stop/uninstall before
|
|
233
240
|
calling the package supported on a NAS. No NAS is accessed by the build gate.
|
|
234
241
|
|
|
235
|
-
The unified Compose contract uses SDK 0.27.x with CLI 0.27.x and locally installed templates from `~0.27.0`. Templates patches can ship independently; npm install/update resolves them, and SDK defaults come from the installed templates. SDK minor upgrades
|
|
242
|
+
The unified Compose contract uses SDK 0.27.x with CLI 0.27.x and locally installed templates from `~0.27.0`. Templates patches can ship independently; npm install/update resolves them, and SDK defaults come from the installed templates. SDK minor upgrades do not require a CLI release when they declare supported executor manifest format 1; the resolved SDK owns the release mapping and verified payload digests. The CLI does not fetch templates at runtime. The legacy packaging route and bridge publish mode are removed.
|
|
236
243
|
|
|
237
244
|
Primary override files, and each included override set, must share a directory. Cross-directory override bases are rejected before loader I/O. Included implicit .env files are confined, inventoried and preserved; symlinks are rejected.
|
|
238
245
|
|
|
@@ -115,7 +115,7 @@ Use existing `daemon.timeouts` for start/stop/status/uninstall (defaults
|
|
|
115
115
|
30/30/5/60 seconds). Start/stop/uninstall accept 2–3600 seconds and status accepts
|
|
116
116
|
1–3600. A `daemon` containing only `timeouts` declares budgets without an
|
|
117
117
|
executable, including background and Compose Apps; Compose start still uses
|
|
118
|
-
`compose.startup_timeout_seconds` (
|
|
118
|
+
`compose.startup_timeout_seconds` (900, 2–900). Background user-manager recovery
|
|
119
119
|
uses the start allowance and its generated package user unit uses the stop
|
|
120
120
|
allowance with `KillMode=control-group`.
|
|
121
121
|
|