@synopackageland/cli 0.10.1 → 0.10.2
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 +31 -0
- package/dist/error-registry.js +1 -0
- package/dist/runtime/executor-build.js +10 -0
- package/dist/runtime/executor-digests.json +11 -0
- package/dist/runtime/executor-inspect.js +3 -0
- package/dist/runtime/executor-policy-check.d.ts +14 -0
- package/dist/runtime/executor-policy-check.js +72 -0
- package/dist/standalone/dsm-window.js +10 -1
- package/dist/validate.js +4 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.2 (2026-10-14)
|
|
4
|
+
|
|
5
|
+
Builds with SDK 0.10.2 and templates 0.10.2: `init` writes SDK v0.10.2 (Go
|
|
6
|
+
module, Rust crate tag and `backend.sdk`) and `@synopackageland/sdk` 0.10.2.
|
|
7
|
+
The executor digest table adds SDK 0.10.2 and keeps 0.10.0 and 0.10.1. SDK
|
|
8
|
+
0.10.2's executor changed (it adds `check-policy`), so an App rebuilt on it
|
|
9
|
+
carries a new executor digest; when its policy digest does not change, the
|
|
10
|
+
upgrade puts the new executor in place without approval. The lifecycle
|
|
11
|
+
digests equal 0.10.1.
|
|
12
|
+
|
|
13
|
+
繁中摘要:使用 SDK 0.10.2 與 templates 0.10.2;digest 表新增 SDK 0.10.2(保留
|
|
14
|
+
0.10.0、0.10.1)。executor 變更(新增 check-policy),policy digest 不變的 App
|
|
15
|
+
升級時自動換 executor,不需重新核准;lifecycle digest 與 0.10.1 相同。
|
|
16
|
+
|
|
17
|
+
- `check` and `build` have the App's SDK executor validate the executor policy
|
|
18
|
+
(`executor check-policy`, SDK 0.10.2 and later, which `init` now writes; built for this host from the
|
|
19
|
+
App's SDK version, or from `SYNOPKGLAND_SDK_DIR`). A policy the executor
|
|
20
|
+
would refuse fails with `EXECUTOR_POLICY_REJECTED`, the executor's reason and
|
|
21
|
+
the dsm-package.yaml part to fix, instead of failing only at approval on the
|
|
22
|
+
NAS. Apps without delegate or rootOps, and SDKs before check-policy, are not
|
|
23
|
+
checked.
|
|
24
|
+
- Fix: `inspect` reports an SDK it cannot download as `SDK_DOWNLOAD_FAILED`
|
|
25
|
+
with the GOPRIVATE hint. It was `SPK_INSPECTION_FAILED` ("Executor inventory
|
|
26
|
+
invalid", "do not install an SPK that fails inspection"), which blamed a
|
|
27
|
+
valid SPK for the machine's Go setup.
|
|
28
|
+
- Fix: a DSM window (and a Portal or standalone launch) passes the App
|
|
29
|
+
`ctx.displayName` in the DSM language: the translated `i18n.info.<locale>.displayname`,
|
|
30
|
+
the name DSM shows in the window title, else the English `displayname`. It
|
|
31
|
+
was always the English name, so a narrow-window header and the title bar
|
|
32
|
+
disagreed under a translated DSM.
|
|
33
|
+
|
|
3
34
|
## 0.10.1 (2026-10-13)
|
|
4
35
|
|
|
5
36
|
Builds with SDK 0.10.1 and templates 0.10.1: `init` writes SDK v0.10.1 (Go
|
package/dist/error-registry.js
CHANGED
|
@@ -757,6 +757,7 @@ for (const [code, description, suggestion] of [
|
|
|
757
757
|
["TEMPLATES_MISSING", "@synopackageland/templates was not found.", "Reinstall @synopackageland/cli, or point SYNOPKGLAND_TEMPLATES_DIR at a templates checkout."],
|
|
758
758
|
["EXECUTOR_PIN_REQUIRED", "A package that declares rootOps or delegate was built without the executor artifact.", "Use synopkgland build so the CLI compiles and pins the executor; do not call the low-level SPK API directly."],
|
|
759
759
|
["EXECUTOR_POLICY", "capabilities.delegate has invalid operations or scope, or a binary root program lacks the file for this architecture, so the executor policy cannot be generated.", "Use the delegate declaration init/add produces and fix scope and operations against the manifest reference; for binary root programs provide root/<arch>/<name> for every arch in arches."],
|
|
760
|
+
["EXECUTOR_POLICY_REJECTED", "The executor of the App's SDK version refuses the policy the CLI generated, so the NAS would refuse it at approval.", "The reason names the refused part: policy root/stop/purge → root.programs (schema, run, job), root.stop, uninstall.purge; policy scope/operation/destination/move/lifetime → capabilities.delegate; policy portal → portal; policy entry → entry. Fix that part of dsm-package.yaml; if it already matches the manifest reference, use the SDK version this CLI release names."],
|
|
760
761
|
["EXECUTOR_ROOT_PROGRAMS_UNSUPPORTED", "The root settings in dsm-package.yaml need an executor that supports root program settings, which the App's SDK version lacks.", "Use an SDK with root programs, stop and root_state (executor/stop_step.go), or remove the root settings."],
|
|
761
762
|
["EXECUTOR_PACKAGE", "The package name pinned into the root task is invalid.", "package must start with a letter and contain only alphanumerics (2-64 characters)."],
|
|
762
763
|
["EXECUTOR_DIGEST", "The sha256 pin for the executor or policy is malformed.", "Build again so the CLI recomputes the pin; do not hand-edit pins inside the SPK."],
|
|
@@ -9,6 +9,7 @@ import { executorTasks } from './executor-tasks.js';
|
|
|
9
9
|
import { copyModule, verifyExecutorDigests } from './executor-source.js';
|
|
10
10
|
import { requiredSdk } from './sdk-source.js';
|
|
11
11
|
import { rootProgramsUnsupported } from './root-programs.js';
|
|
12
|
+
import { executorPolicyProblem } from './executor-policy-check.js';
|
|
12
13
|
/** CLI-owned build flags; the release digest table is computed the same way, so they must not change within an SDK minor. */
|
|
13
14
|
const GO_BUILD = ['build', '-trimpath', '-buildvcs=false', '-ldflags', '-s -w -buildid=', '-o'];
|
|
14
15
|
function failure(error) {
|
|
@@ -75,6 +76,15 @@ export async function buildExecutor(m, arch, work, run, options = {}) {
|
|
|
75
76
|
if (unsupported)
|
|
76
77
|
throw new StandaloneBuildError('EXECUTOR_ROOT_PROGRAMS_UNSUPPORTED', unsupported);
|
|
77
78
|
const policyText = executorPolicyText(policy);
|
|
79
|
+
let refused;
|
|
80
|
+
try {
|
|
81
|
+
refused = executorPolicyProblem(source, sdk.version, m.package, policyText, options.env);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw failure(error);
|
|
85
|
+
}
|
|
86
|
+
if (refused)
|
|
87
|
+
throw new StandaloneBuildError('EXECUTOR_POLICY_REJECTED', refused);
|
|
78
88
|
const pins = { package: m.package, executorDigest, policyDigest: sha256(policyText) };
|
|
79
89
|
return { ...pins, binary, lifecycleBinary, policy, policyText, tasks: executorTasks(pins, sdk.executorDir), implementation: 'executor', sdk: sdk.version, warnings };
|
|
80
90
|
}
|
|
@@ -20,5 +20,16 @@
|
|
|
20
20
|
"armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
|
|
21
21
|
},
|
|
22
22
|
"goToolchain": "go1.26.9"
|
|
23
|
+
},
|
|
24
|
+
"0.10.2": {
|
|
25
|
+
"x86_64": "bb7aebb120562633e852d590007870489b7839ea2c943db897b7a71dea743fae",
|
|
26
|
+
"aarch64": "2fd8365aabfa2ea2ea7c767f89fa8222d7f4fc04cbdde6b7d510c74002a5bb89",
|
|
27
|
+
"armv7": "6f757614eab802fa07c2601c801bcf2c14ad3076d7c01f9ed20d62744ef13979",
|
|
28
|
+
"lifecycle": {
|
|
29
|
+
"x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
|
|
30
|
+
"aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
|
|
31
|
+
"armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
|
|
32
|
+
},
|
|
33
|
+
"goToolchain": "go1.26.9"
|
|
23
34
|
}
|
|
24
35
|
}
|
|
@@ -43,6 +43,9 @@ export function inspectExecutor(dir, pkg, arch) {
|
|
|
43
43
|
return { executor: { executorDigest, policyDigest, policy, implementation: plan.implementation, sdk: plan.sdk, tasks: tasks.map(task => ({ ...task, state: 'unknown' })) }, findings: [] };
|
|
44
44
|
}
|
|
45
45
|
catch (e) {
|
|
46
|
+
// A failed SDK download is this machine's Go setup (GOPRIVATE), not a defect of the SPK.
|
|
47
|
+
if (e.message.startsWith('[SDK_DOWNLOAD_FAILED]'))
|
|
48
|
+
throw e;
|
|
46
49
|
return { findings: ['Executor inventory invalid: ' + e.message] };
|
|
47
50
|
}
|
|
48
51
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type StandaloneManifest } from '../standalone/manifest.js';
|
|
2
|
+
/**
|
|
3
|
+
* Why the SDK executor would refuse the policy, such as `invalid policy: policy root`;
|
|
4
|
+
* undefined when it accepts it or its SDK predates check-policy. The executor is
|
|
5
|
+
* built for this host from executorDir, so its own rules decide, not a copy in the CLI.
|
|
6
|
+
*/
|
|
7
|
+
export declare function executorPolicyRefusal(executorDir: string, pkg: string, policyText: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
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;
|
|
10
|
+
/** The policy of each architecture `check` would build, checked by the App's SDK executor; Apps without executor need none. */
|
|
11
|
+
export declare function manifestPolicyProblem(m: StandaloneManifest, env?: NodeJS.ProcessEnv): {
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
} | undefined;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { PINNED_GO_HINT, PINNED_GO_TOOLCHAIN } from '../build-spk/go-toolchain.js';
|
|
6
|
+
import { sdkBackend } from '../standalone/manifest.js';
|
|
7
|
+
import { executorPolicy, executorPolicyText } from './executor-policy.js';
|
|
8
|
+
import { requiredSdk } from './sdk-source.js';
|
|
9
|
+
/** The executor subcommand that validates a policy on stdin as load() does (SDK 0.10.2 and later). */
|
|
10
|
+
const CHECK_POLICY = 'check-policy';
|
|
11
|
+
/**
|
|
12
|
+
* Why the SDK executor would refuse the policy, such as `invalid policy: policy root`;
|
|
13
|
+
* undefined when it accepts it or its SDK predates check-policy. The executor is
|
|
14
|
+
* built for this host from executorDir, so its own rules decide, not a copy in the CLI.
|
|
15
|
+
*/
|
|
16
|
+
export function executorPolicyRefusal(executorDir, pkg, policyText, env = process.env) {
|
|
17
|
+
const main = join(executorDir, 'main.go');
|
|
18
|
+
if (!existsSync(main) || !readFileSync(main, 'utf8').includes(`"${CHECK_POLICY}"`))
|
|
19
|
+
return undefined;
|
|
20
|
+
const dir = mkdtempSync(join(tmpdir(), 'synopkgland-check-policy-'));
|
|
21
|
+
try {
|
|
22
|
+
const binary = join(dir, 'executor');
|
|
23
|
+
const { GOOS: _os, GOARCH: _arch, ...host } = env;
|
|
24
|
+
const build = spawnSync('go', ['build', '-o', binary, '.'], { cwd: executorDir, env: { ...host, GOTOOLCHAIN: PINNED_GO_TOOLCHAIN, GOFLAGS: '', GOWORK: 'off', CGO_ENABLED: '0' }, encoding: 'utf8' });
|
|
25
|
+
if (build.error || build.status !== 0)
|
|
26
|
+
throw new Error(`[EXECUTOR_BUILD_FAILED] Building the SDK executor for this host to check the policy failed. ${PINNED_GO_HINT}\n${build.stderr || build.error?.message}`);
|
|
27
|
+
const result = spawnSync(binary, [CHECK_POLICY, '--package', pkg], { input: policyText, encoding: 'utf8' });
|
|
28
|
+
if (result.status === 0)
|
|
29
|
+
return undefined;
|
|
30
|
+
const reason = /^executor: (.*)$/m.exec(result.stderr ?? '')?.[1];
|
|
31
|
+
if (result.status === 1 && reason)
|
|
32
|
+
return reason;
|
|
33
|
+
throw new Error(`[EXECUTOR_BUILD_FAILED] executor ${CHECK_POLICY} did not run: ${result.stderr || result.error?.message}`);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
rmSync(dir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Where in dsm-package.yaml each executor refusal comes from. */
|
|
40
|
+
const POLICY_FIX = [
|
|
41
|
+
[/policy (root|stop|purge)/, 'fix root.programs (schema, run, job), root.stop or uninstall.purge in dsm-package.yaml'],
|
|
42
|
+
[/policy (scope|operation|destination|move|lifetime)/, 'fix capabilities.delegate in dsm-package.yaml'],
|
|
43
|
+
[/policy portal/, 'fix portal in dsm-package.yaml'],
|
|
44
|
+
[/policy entry/, 'fix entry in dsm-package.yaml'],
|
|
45
|
+
];
|
|
46
|
+
/** The EXECUTOR_POLICY_REJECTED message for a policy the SDK executor refuses; undefined when it accepts it. */
|
|
47
|
+
export function executorPolicyProblem(executorDir, sdkVersion, pkg, policyText, env = process.env) {
|
|
48
|
+
const reason = executorPolicyRefusal(executorDir, pkg, policyText, env);
|
|
49
|
+
if (!reason)
|
|
50
|
+
return undefined;
|
|
51
|
+
const fix = POLICY_FIX.find(([pattern]) => pattern.test(reason))?.[1] ?? 'fix the declaration in dsm-package.yaml';
|
|
52
|
+
return `The executor of SDK ${sdkVersion} refuses this App's policy (executor: ${reason}), so the NAS would refuse it at approval: ${fix}.`
|
|
53
|
+
+ ' If dsm-package.yaml already matches the manifest reference, this CLI and the SDK disagree: use the SDK version this CLI release names.';
|
|
54
|
+
}
|
|
55
|
+
/** The policy of each architecture `check` would build, checked by the App's SDK executor; Apps without executor need none. */
|
|
56
|
+
export function manifestPolicyProblem(m, env = process.env) {
|
|
57
|
+
if (!m.capabilities.delegate && !m.capabilities.rootOps)
|
|
58
|
+
return undefined;
|
|
59
|
+
try {
|
|
60
|
+
const sdk = requiredSdk(sdkBackend(m), env);
|
|
61
|
+
for (const text of new Set(m.arches.map(arch => executorPolicyText(executorPolicy(m, arch))))) {
|
|
62
|
+
const message = executorPolicyProblem(sdk.executorDir, sdk.version, m.package, text, env);
|
|
63
|
+
if (message)
|
|
64
|
+
return { code: 'EXECUTOR_POLICY_REJECTED', message };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const coded = /^\[([A-Z0-9_]+)\] ([\s\S]*)$/.exec(error.message);
|
|
69
|
+
return { code: coded?.[1] ?? 'EXECUTOR_BUILD_FAILED', message: coded?.[2] ?? error.message };
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
@@ -72,6 +72,15 @@ export function entryLauncherAccess(entry) {
|
|
|
72
72
|
export function appTitle(manifest) {
|
|
73
73
|
return { displayname: manifest.displayname, description: manifest.description, info: manifest.i18n?.info };
|
|
74
74
|
}
|
|
75
|
+
/** The app name by locale, as `ui/texts` names the window: `createI18n(lang, ...).t("app_title")`. */
|
|
76
|
+
function displayNameCatalogs(manifest) {
|
|
77
|
+
const catalogs = { en: { app_title: manifest.displayname } };
|
|
78
|
+
for (const [locale, text] of Object.entries(manifest.i18n?.info ?? {})) {
|
|
79
|
+
if (text?.displayname)
|
|
80
|
+
catalogs[locale] = { app_title: text.displayname };
|
|
81
|
+
}
|
|
82
|
+
return catalogs;
|
|
83
|
+
}
|
|
75
84
|
/** `ui/config`: registers the DSM main-menu app and its window class. */
|
|
76
85
|
export function renderUiConfig(manifest, icons = packageIcons(manifest.icon)) {
|
|
77
86
|
const icon = windowIconTemplate(icons);
|
|
@@ -268,7 +277,7 @@ ${root ? root.mount : ""} ${ns}.loadApp(function (app) {
|
|
|
268
277
|
root.lang = i18n.locale;
|
|
269
278
|
try {
|
|
270
279
|
me.mounted = app.mount(root, Ext.apply(ctx, {
|
|
271
|
-
displayName: ${JSON.stringify(manifest.
|
|
280
|
+
displayName: ${ns}.createI18n(lang, ${JSON.stringify(displayNameCatalogs(manifest))}).t("app_title"),
|
|
272
281
|
call: call,
|
|
273
282
|
files: {endpoint: ${JSON.stringify(cgiUrl(pkg))}, token: function () { return SYNO.SDS.Session && SYNO.SDS.Session.SynoToken; }},
|
|
274
283
|
reopen: me.reopenEvents.subscribe,
|
package/dist/validate.js
CHANGED
|
@@ -9,6 +9,7 @@ import { classifyComposeRoute, collectComposeInterpolations, collectVolumeSource
|
|
|
9
9
|
import { usesAuthGateway } from "./contract/web-launch.js";
|
|
10
10
|
import { isNativeLayout, loadNativeLayout } from "./standalone/native-layout.js";
|
|
11
11
|
import { loadStandaloneManifest } from "./standalone/manifest.js";
|
|
12
|
+
import { manifestPolicyProblem } from "./runtime/executor-policy-check.js";
|
|
12
13
|
import { COMPOSE_FILENAME, CONTRACT_FILENAME, DSM_PACKAGE_FILENAME, } from "./discovery.js";
|
|
13
14
|
import { declaredPrivilegeFlags } from "./compose/privilege.js";
|
|
14
15
|
export function validateProject(project, options = {}) {
|
|
@@ -29,7 +30,9 @@ export function validateProject(project, options = {}) {
|
|
|
29
30
|
}
|
|
30
31
|
if (hasStandalone) {
|
|
31
32
|
const loaded = isNativeLayout(project.root) ? loadNativeLayout(project.root) : loadStandaloneManifest(project.root);
|
|
32
|
-
|
|
33
|
+
const policy = loaded.ok && "capabilities" in loaded.manifest ? manifestPolicyProblem(loaded.manifest) : undefined;
|
|
34
|
+
const problems = loaded.ok ? (policy ? [policy] : []) : loaded.errors;
|
|
35
|
+
return !problems.length ? { ok: true, errors: [] } : { ok: false, errors: problems.map(error => ({
|
|
33
36
|
...error, span: { file: DSM_PACKAGE_FILENAME, line: 1, column: 1 },
|
|
34
37
|
suggestion: getErrorCode(error.code)?.suggestion ?? "Fix the schema 2 fields and capability declarations in dsm-package.yaml.",
|
|
35
38
|
})) };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Syno Package Land Developer Kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@synopackageland/templates": "0.10.
|
|
27
|
+
"@synopackageland/templates": "0.10.2",
|
|
28
28
|
"ajv": "^8.17.1",
|
|
29
29
|
"commander": "^13.1.0",
|
|
30
30
|
"tar": "^7.4.3",
|