@synopackageland/cli 0.9.7 → 0.10.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 +121 -0
- package/README.md +5 -0
- package/dist/add.js +7 -8
- package/dist/build-spk/dsm-arch.js +1 -1
- package/dist/build-spk/dsm-icons.d.ts +8 -1
- package/dist/build-spk/dsm-icons.js +26 -12
- package/dist/build-spk/dsm-version.d.ts +1 -1
- package/dist/build-spk/dsm-version.js +4 -4
- package/dist/build-spk/gateway.js +2 -2
- package/dist/build-spk/generator.js +1 -1
- package/dist/build-spk/png.js +16 -16
- package/dist/capabilities.js +1 -1
- package/dist/capability-overlays.d.ts +16 -5
- package/dist/capability-overlays.js +30 -16
- package/dist/cli.js +107 -107
- package/dist/compose/analyze.js +23 -23
- package/dist/compose/privilege.js +12 -12
- package/dist/contract/i18n.js +3 -3
- package/dist/contract/icon.js +2 -2
- package/dist/contract/posix-all-shares.js +2 -2
- package/dist/contract/schema-validate.js +3 -3
- package/dist/contract/validate.js +16 -16
- package/dist/contract/web-launch.d.ts +2 -1
- package/dist/contract/web-launch.js +3 -2
- package/dist/deploy/deploy.js +8 -8
- package/dist/deploy/homes-values.js +1 -1
- package/dist/deploy/identity-shared.js +1 -1
- package/dist/deploy/native-install-values.js +1 -1
- package/dist/deploy/param-values.js +4 -4
- package/dist/deploy/plan.d.ts +1 -1
- package/dist/deploy/port-preflight.js +3 -3
- package/dist/deploy/run-as-values.js +2 -2
- package/dist/deploy/share-permission-preflight.js +6 -6
- package/dist/deploy/share-values.js +3 -3
- package/dist/error-registry.js +369 -346
- package/dist/errors.js +1 -1
- package/dist/host/dsm-port-profile.d.ts +1 -1
- package/dist/host/dsm-port-profile.js +1 -1
- package/dist/info.js +11 -11
- package/dist/init.d.ts +2 -0
- package/dist/init.js +85 -38
- package/dist/inspect.js +5 -5
- package/dist/portal/alias.d.ts +1 -1
- package/dist/portal/alias.js +4 -4
- package/dist/runtime/executor-build.js +12 -1
- package/dist/runtime/executor-digests.json +8 -96
- package/dist/runtime/executor-inspect.js +11 -4
- package/dist/runtime/executor-policy.d.ts +9 -2
- package/dist/runtime/executor-policy.js +27 -8
- package/dist/runtime/root-programs.d.ts +47 -0
- package/dist/runtime/root-programs.js +153 -0
- package/dist/runtime/sdk-source.d.ts +5 -4
- package/dist/runtime/sdk-source.js +8 -4
- package/dist/skill.js +14 -14
- package/dist/spk/check-info.js +17 -17
- package/dist/spk/check-metadata.js +18 -18
- package/dist/spk/check-payload.js +10 -10
- package/dist/spk/rules.d.ts +57 -57
- package/dist/spk/rules.js +50 -50
- package/dist/spk/validate-spk.js +5 -5
- package/dist/standalone/api-permissions.js +1 -1
- package/dist/standalone/background.d.ts +3 -1
- package/dist/standalone/background.js +8 -4
- package/dist/standalone/binary.js +7 -7
- package/dist/standalone/bridge-app.d.ts +23 -0
- package/dist/standalone/bridge-app.js +141 -0
- package/dist/standalone/bridge-backend.d.ts +40 -0
- package/dist/standalone/bridge-backend.js +95 -0
- package/dist/standalone/bridge-build.d.ts +21 -0
- package/dist/standalone/bridge-build.js +62 -0
- package/dist/standalone/bridge-json.d.ts +16 -0
- package/dist/standalone/bridge-json.js +59 -0
- package/dist/standalone/bridge-runtime.d.ts +16 -0
- package/dist/standalone/bridge-runtime.js +46 -0
- package/dist/standalone/bridge-rust.d.ts +16 -0
- package/dist/standalone/bridge-rust.js +89 -0
- package/dist/standalone/build.js +18 -14
- package/dist/standalone/daemon.js +6 -6
- package/dist/standalone/dsm-window.d.ts +10 -5
- package/dist/standalone/dsm-window.js +64 -16
- package/dist/standalone/i18n.js +18 -18
- package/dist/standalone/inspect-bridge.d.ts +31 -0
- package/dist/standalone/inspect-bridge.js +111 -0
- package/dist/standalone/inspect-spk.d.ts +1 -1
- package/dist/standalone/inspect-spk.js +77 -69
- package/dist/standalone/lifecycle.js +22 -3
- package/dist/standalone/manifest.d.ts +17 -9
- package/dist/standalone/manifest.js +143 -65
- package/dist/standalone/portal-window.js +14 -7
- package/dist/standalone/request-timeout.d.ts +9 -0
- package/dist/standalone/request-timeout.js +13 -0
- package/dist/standalone/root-ops.d.ts +23 -2
- package/dist/standalone/root-ops.js +118 -13
- package/dist/standalone/sdk-check.js +4 -4
- package/dist/standalone/shared-folder.js +7 -7
- package/dist/standalone/spk.d.ts +2 -0
- package/dist/standalone/spk.js +30 -11
- package/dist/standalone/uninstall-hook.js +22 -4
- package/dist/standalone/uninstall.js +7 -7
- package/dist/standalone/window-size.d.ts +17 -0
- package/dist/standalone/window-size.js +31 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate.js +13 -13
- package/package.json +2 -2
- package/schema/bridge-manifest.v1.schema.json +86 -0
- package/skills/reference/create.md +76 -2
- package/skills/reference/ui-json.md +2 -1
- package/skills/reference/ui-vue.md +9 -3
- package/skills/reference/wrap.md +1 -1
- package/dist/standalone/rust-build.d.ts +0 -35
- package/dist/standalone/rust-build.js +0 -127
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { codedMessage, formatErrors } from "./errors.js";
|
|
|
9
9
|
import { getErrorCode } from "./error-registry.js";
|
|
10
10
|
import { formatInspectReport, inspectProject } from "./inspect.js";
|
|
11
11
|
import { formatPinReport, pinProjectImages } from "./images/pin.js";
|
|
12
|
-
import { initFromTemplate, isTemplateName, listTemplates } from "./init.js";
|
|
12
|
+
import { initFromTemplate, initNextSteps, isTemplateName, listTemplates } from "./init.js";
|
|
13
13
|
import { templatesRoot } from "./template-paths.js";
|
|
14
14
|
import { addCapabilities } from "./add.js";
|
|
15
15
|
import { CAPABILITY_NAMES } from "./capabilities.js";
|
|
@@ -33,13 +33,13 @@ import { buildStandalone, runnerFor } from "./standalone/build.js";
|
|
|
33
33
|
import { checkJobs, commitOutputs, forEachArch } from "./standalone/arch-jobs.js";
|
|
34
34
|
import { checkReproducible } from "./reproducible.js";
|
|
35
35
|
import { inspectAnySpk } from "./spk/inspect.js";
|
|
36
|
-
function failure(code, message, hint = "
|
|
36
|
+
function failure(code, message, hint = "Fix the configuration and retry; use --help to see command usage.") {
|
|
37
37
|
return Object.assign(new Error(codedMessage(code, message)), { code, hint });
|
|
38
38
|
}
|
|
39
39
|
function requireProject(cwd, project) {
|
|
40
40
|
const found = findProjectRoot(project ? resolve(cwd, project) : cwd);
|
|
41
41
|
if (!found)
|
|
42
|
-
throw failure("PROJECT_NOT_FOUND", "
|
|
42
|
+
throw failure("PROJECT_NOT_FOUND", "synology-app.yaml or dsm-package.yaml was not found.", "Run from the project root, or pass --project.");
|
|
43
43
|
return found;
|
|
44
44
|
}
|
|
45
45
|
function standaloneRoot(cwd, project) {
|
|
@@ -48,7 +48,7 @@ function standaloneRoot(cwd, project) {
|
|
|
48
48
|
function collectKv(value, previous, flag) {
|
|
49
49
|
const separator = value.indexOf("=");
|
|
50
50
|
if (separator <= 0)
|
|
51
|
-
throw failure("CLI_OPTION_INVALID", `${flag}
|
|
51
|
+
throw failure("CLI_OPTION_INVALID", `${flag} expects the format SLOT=value.`);
|
|
52
52
|
return {
|
|
53
53
|
...previous,
|
|
54
54
|
[value.slice(0, separator)]: value.slice(separator + 1),
|
|
@@ -58,14 +58,14 @@ function collectKv(value, previous, flag) {
|
|
|
58
58
|
function formatIconReport(report, manifestFile) {
|
|
59
59
|
return {
|
|
60
60
|
lines: report.source === "custom"
|
|
61
|
-
? [
|
|
61
|
+
? [`Icon: custom ${report.path}`]
|
|
62
62
|
: report.source === "layout"
|
|
63
|
-
? ["
|
|
64
|
-
: ["
|
|
65
|
-
...(report.solid ? { warning: "warning
|
|
63
|
+
? ["Icon: provided by the layout metadata's PACKAGE_ICON*.PNG (if present)", `info: set icon: in ${manifestFile} to have the CLI generate the Package Center icon.`]
|
|
64
|
+
: ["Icon: SDK default icon", `info: set icon: in ${manifestFile} to <a square PNG (at least 256x256) or an icon_<size>.png directory> to use your own icon.`],
|
|
65
|
+
...(report.solid ? { warning: "warning: the icon is a single colour, so Package Center and the DSM main menu show a flat block; use an icon with a graphic." } : {}),
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
|
-
const LABEL = { error: "
|
|
68
|
+
const LABEL = { error: "error", warning: "warning" };
|
|
69
69
|
/** One line per finding: severity, stable code, where, and what to fix. */
|
|
70
70
|
function formatFindings(findings) {
|
|
71
71
|
return findings.map((f) => `${LABEL[f.severity]} ${f.code}${f.path ? ` ${f.path}` : ""}: ${f.message}`);
|
|
@@ -74,17 +74,17 @@ function formatFindings(findings) {
|
|
|
74
74
|
function formatSpkChecks(report) {
|
|
75
75
|
const { errors, warnings } = report.dsm;
|
|
76
76
|
return [
|
|
77
|
-
"DSM
|
|
77
|
+
"DSM format:",
|
|
78
78
|
...formatFindings([...errors, ...warnings]).map((line) => ` ${line}`),
|
|
79
|
-
` ${report.dsm.ok ? "
|
|
79
|
+
` ${report.dsm.ok ? "matches" : "does not match"} the DSM 7 package format: ${errors.length} errors, ${warnings.length} warnings.`,
|
|
80
80
|
...(report.synopkgland
|
|
81
81
|
? [
|
|
82
|
-
"synopkgland
|
|
82
|
+
"synopkgland contract:",
|
|
83
83
|
report.findings.length
|
|
84
|
-
? "
|
|
84
|
+
? " Problems:"
|
|
85
85
|
: report.executor
|
|
86
|
-
? "
|
|
87
|
-
: "
|
|
86
|
+
? " Check: no problems. After an administrator approves, it creates root executor tasks (boot and control task) that run this SPK's fixed executor and policy as root."
|
|
87
|
+
: " Check: no root privilege requirement or problem.",
|
|
88
88
|
...report.findings.map((f) => ` - ${f}`),
|
|
89
89
|
]
|
|
90
90
|
: []),
|
|
@@ -92,25 +92,25 @@ function formatSpkChecks(report) {
|
|
|
92
92
|
}
|
|
93
93
|
function formatSpkReport(spk, report) {
|
|
94
94
|
return [
|
|
95
|
-
`SPK
|
|
96
|
-
`package
|
|
97
|
-
report.synopkgland ? `arch
|
|
98
|
-
|
|
99
|
-
...(report.synopkgland ? [`CGI
|
|
100
|
-
...(report.icon ? [
|
|
101
|
-
...(report.entry === "admin-only" ? ["
|
|
102
|
-
...(report.entry === "app-privilege" ? ["
|
|
103
|
-
...(report.portal ? [`Application Portal
|
|
104
|
-
...report.capabilities.map((cap) =>
|
|
105
|
-
"payload
|
|
106
|
-
...(report.uninstallCleanup ?? []).map(item => `Uninstall cleanup (if needed)
|
|
95
|
+
`SPK: ${spk}`,
|
|
96
|
+
`package: ${report.package ?? "?"} version: ${report.version ?? "?"}`,
|
|
97
|
+
report.synopkgland ? `arch: ${report.arch ?? "?"} runtime: ${report.kind ?? "?"}` : `arch: ${report.arch ?? "?"} build: not synopkgland (DSM format only)`,
|
|
98
|
+
`Signature: ${report.signed ? "Synology-signed" : "unsigned (Package Center must allow any publisher)"}`,
|
|
99
|
+
...(report.synopkgland ? [`CGI: ${report.cgi ?? "none"}`] : []),
|
|
100
|
+
...(report.icon ? [`Icon: ${report.icon.source === "default" ? "SDK default icon" : report.icon.source === "custom" ? `custom (PACKAGE_ICON_256 sha256 ${report.icon.sha256.slice(0, 12)}…)` : "no PACKAGE_ICON"}`] : []),
|
|
101
|
+
...(report.entry === "admin-only" ? ["Entry: entry: admin-only; only a DSM administrator can open it and call any route (including SDK routes)"] : []),
|
|
102
|
+
...(report.entry === "app-privilege" ? ["Entry: entry: app-privilege; DSM Control Panel > Application Privileges decides who can use it"] : []),
|
|
103
|
+
...(report.portal ? [`Application Portal: default alias /${report.portal.alias}/ (${report.portal.appId}), active after an administrator applies it`] : []),
|
|
104
|
+
...report.capabilities.map((cap) => `Capability ${cap.name}: ${cap.requiresAdminApproval ? "needs DSM administrator approval" : "ready after install"}`),
|
|
105
|
+
"payload:",
|
|
106
|
+
...(report.uninstallCleanup ?? []).map(item => `Uninstall cleanup (if needed): ${item.target} — ${item.condition} ${item.action}`),
|
|
107
107
|
...report.payload.map((e) => ` ${e.mode} ${e.path}`),
|
|
108
108
|
...formatSpkChecks(report),
|
|
109
109
|
].join("\n");
|
|
110
110
|
}
|
|
111
111
|
async function standaloneBuild(root, arches, options) {
|
|
112
112
|
if (options.output && arches?.length !== 1)
|
|
113
|
-
throw failure("CLI_OUTPUT_ARCH_REQUIRED", "-o
|
|
113
|
+
throw failure("CLI_OUTPUT_ARCH_REQUIRED", "-o requires a single --arch; use --out-dir for multiple architectures.");
|
|
114
114
|
const outDir = options.output
|
|
115
115
|
? dirname(resolve(options.output))
|
|
116
116
|
: options.outDir
|
|
@@ -146,10 +146,10 @@ function reproducibleTarget(standalone, project, arches) {
|
|
|
146
146
|
}
|
|
147
147
|
function reproducibleLines(report) {
|
|
148
148
|
if (report.skipped)
|
|
149
|
-
return [
|
|
149
|
+
return [`Reproducible check: the dependency fingerprint ${report.fingerprint.slice(0, 12)} matches the last passing run, skipping.`];
|
|
150
150
|
return report.results.map((result) => result.identical
|
|
151
|
-
?
|
|
152
|
-
:
|
|
151
|
+
? `Reproducible check: ${basename(result.output)} is identical (sha256 ${result.sha256[0].slice(0, 12)})`
|
|
152
|
+
: `Reproducible check: ${basename(result.output)} differs (sha256 ${result.sha256[0].slice(0, 12)} ≠ ${result.sha256[1].slice(0, 12)})\n` +
|
|
153
153
|
[...result.payload.map((file) => ` payload ${file}`), ...result.metadata.map((file) => ` metadata ${file}`)].join("\n"));
|
|
154
154
|
}
|
|
155
155
|
const ARCH_REQUIRED = [
|
|
@@ -160,12 +160,12 @@ async function containerBuild(project, arches, options) {
|
|
|
160
160
|
const jobs = checkJobs(options.jobs);
|
|
161
161
|
const unsupported = arches?.find((arch) => !isContainerArchFamily(arch));
|
|
162
162
|
if (unsupported)
|
|
163
|
-
throw failure("CONTAINER_ARCH_UNSUPPORTED", `Container Manager
|
|
163
|
+
throw failure("CONTAINER_ARCH_UNSUPPORTED", `Container Manager Apps support only ${CONTAINER_ARCH_FAMILIES.join(", ")}; DSM does not provide Container Manager for ${unsupported}.`);
|
|
164
164
|
const output = options.output ? resolve(options.output) : undefined;
|
|
165
165
|
const build = async (archFamily, outputPath = output) => {
|
|
166
166
|
const result = await buildSpk({ project, archFamily, outputPath, minDsm: options.minDsm, gatewayRun: archFamily && runnerFor(undefined, jobs, archFamily) });
|
|
167
167
|
if (!result.ok && !(archFamily === undefined && result.errors.every((code) => ARCH_REQUIRED.includes(code))))
|
|
168
|
-
throw failure(result.errors[0] ?? "BUILD_FAILED",
|
|
168
|
+
throw failure(result.errors[0] ?? "BUILD_FAILED", `Build failed: ${result.errors.join(", ")}`);
|
|
169
169
|
return result;
|
|
170
170
|
};
|
|
171
171
|
if (arches?.length === 1)
|
|
@@ -174,7 +174,7 @@ async function containerBuild(project, arches, options) {
|
|
|
174
174
|
if (first.ok)
|
|
175
175
|
return [first.outputPath];
|
|
176
176
|
if (output)
|
|
177
|
-
throw failure("CLI_OUTPUT_ARCH_REQUIRED", "-o
|
|
177
|
+
throw failure("CLI_OUTPUT_ARCH_REQUIRED", "-o requires a single --arch.");
|
|
178
178
|
const packageId = String(loadContract(project.contractPath).contract.package);
|
|
179
179
|
return commitOutputs(options.outDir ? resolve(options.outDir) : project.root, (stage) => forEachArch(arches ?? CONTAINER_ARCH_FAMILIES, jobs, async (arch) => (await build(arch, join(stage, `${packageId}-${arch}.spk`))).outputPath));
|
|
180
180
|
}
|
|
@@ -186,21 +186,21 @@ export async function runCli(argv) {
|
|
|
186
186
|
const program = new Command()
|
|
187
187
|
.name("synopkgland")
|
|
188
188
|
.description("Syno Package Land Developer Kit CLI")
|
|
189
|
-
.option("--project <dir>", "App
|
|
189
|
+
.option("--project <dir>", "App project root")
|
|
190
190
|
.exitOverride();
|
|
191
191
|
const rootOf = (command) => resolve(process.cwd(), command.parent?.opts().project ?? ".");
|
|
192
192
|
const hostOf = (host) => {
|
|
193
193
|
if (!host)
|
|
194
|
-
throw failure("HOST_REQUIRED", "
|
|
194
|
+
throw failure("HOST_REQUIRED", "--host is required.", "Set a synoagentcli profile, then use --host <name>.");
|
|
195
195
|
const nas = createNasHost({ host });
|
|
196
196
|
if (!nas)
|
|
197
|
-
throw failure("NAS_CREDENTIALS_MISSING", "
|
|
197
|
+
throw failure("NAS_CREDENTIALS_MISSING", "NAS credentials were not found.", "Set a synoagentcli profile or DSM_URL/DSM_PASSWORD.");
|
|
198
198
|
return nas;
|
|
199
199
|
};
|
|
200
200
|
program
|
|
201
201
|
.command("check")
|
|
202
|
-
.description("
|
|
203
|
-
.option("--json", "
|
|
202
|
+
.description("Validate the manifest, capability combination and Compose")
|
|
203
|
+
.option("--json", "Print a single JSON object")
|
|
204
204
|
.action((_options, command) => {
|
|
205
205
|
const validation = validateProject(requireProject(rootOf(command)), {
|
|
206
206
|
profile: "personal",
|
|
@@ -211,7 +211,7 @@ export async function runCli(argv) {
|
|
|
211
211
|
if (standalone) {
|
|
212
212
|
const loaded = isNativeLayout(standalone) ? loadNativeLayout(standalone) : loadStandaloneManifest(standalone);
|
|
213
213
|
if (!loaded.ok)
|
|
214
|
-
throw failure(loaded.errors[0].code, loaded.errors.map((e) => `[${e.code}] ${e.message}`).join("\n"), "
|
|
214
|
+
throw failure(loaded.errors[0].code, loaded.errors.map((e) => `[${e.code}] ${e.message}`).join("\n"), "Update dsm-package.yaml to schema: 2, fix the declaration and retry.");
|
|
215
215
|
const declared = "icon" in loaded.manifest && loaded.manifest.icon ? resolve(standalone, loaded.manifest.icon) : undefined;
|
|
216
216
|
const icon = isNativeLayout(standalone) && !declared ? { source: "layout", solid: false } : iconReport(standalone, declared);
|
|
217
217
|
const iconText = formatIconReport(icon, "dsm-package.yaml");
|
|
@@ -221,7 +221,7 @@ export async function runCli(argv) {
|
|
|
221
221
|
package: loaded.manifest.package,
|
|
222
222
|
capabilities: "capabilities" in loaded.manifest ? loaded.manifest.capabilities : {},
|
|
223
223
|
icon,
|
|
224
|
-
}, ["
|
|
224
|
+
}, ["Validation passed.", ...iconText.lines].join("\n"));
|
|
225
225
|
return;
|
|
226
226
|
}
|
|
227
227
|
const project = requireProject(rootOf(command));
|
|
@@ -235,26 +235,26 @@ export async function runCli(argv) {
|
|
|
235
235
|
const iconText = formatIconReport(icon, "synology-app.yaml");
|
|
236
236
|
if (iconText.warning)
|
|
237
237
|
console.error(iconText.warning);
|
|
238
|
-
emit("check", { report, warnings: result.warnings ?? [], icon }, [
|
|
238
|
+
emit("check", { report, warnings: result.warnings ?? [], icon }, [`Validation passed.`, formatInspectReport(report), ...iconText.lines].join("\n"));
|
|
239
239
|
});
|
|
240
240
|
program
|
|
241
241
|
.command("inspect <spk>")
|
|
242
|
-
.description("
|
|
243
|
-
.option("--json", "
|
|
242
|
+
.description("Inspect any SPK against the DSM 7 package format without installing; for a synopkgland-built SPK also check the build contract and root privilege requirements")
|
|
243
|
+
.option("--json", "Print a single JSON object")
|
|
244
244
|
.action(async (input) => {
|
|
245
245
|
const spk = resolve(input);
|
|
246
246
|
if (existsSync(spk) && statSync(spk).isDirectory())
|
|
247
|
-
throw failure("SPK_INSPECTION_FAILED", `${spk}
|
|
247
|
+
throw failure("SPK_INSPECTION_FAILED", `${spk} is a directory; inspect checks .spk files.`, "Use synopkgland check to inspect project settings.");
|
|
248
248
|
const report = await inspectAnySpk(spk);
|
|
249
249
|
if (!report.dsm.ok || report.findings.length)
|
|
250
|
-
throw Object.assign(failure("SPK_INSPECTION_FAILED", formatSpkChecks(report).join("\n"), "
|
|
250
|
+
throw Object.assign(failure("SPK_INSPECTION_FAILED", formatSpkChecks(report).join("\n"), "Fix the SPK by error code and repackage; see reference/errors for the codes; do not install an SPK that fails inspection."), { report: { spk, ...report } });
|
|
251
251
|
emit("inspect", { spk, ...report }, formatSpkReport(spk, report));
|
|
252
252
|
});
|
|
253
|
-
const images = program.command("images").description("Container image
|
|
253
|
+
const images = program.command("images").description("Container image tools");
|
|
254
254
|
images
|
|
255
255
|
.command("pin")
|
|
256
|
-
.description("
|
|
257
|
-
.option("--check", "
|
|
256
|
+
.description("Resolve compose.yaml image tags to digests and write them back (anonymous registry lookup)")
|
|
257
|
+
.option("--check", "Check only, do not write; fail when unpinned or stale")
|
|
258
258
|
.action(async (options, command) => {
|
|
259
259
|
const project = requireProject(process.cwd(), command.parent?.parent?.opts().project);
|
|
260
260
|
const result = await pinProjectImages({
|
|
@@ -263,31 +263,31 @@ export async function runCli(argv) {
|
|
|
263
263
|
});
|
|
264
264
|
stdout(formatPinReport(result, { check: options.check === true }));
|
|
265
265
|
if (options.check && result.findings.length)
|
|
266
|
-
throw failure("IMAGE_PIN_REQUIRED", "Container images
|
|
266
|
+
throw failure("IMAGE_PIN_REQUIRED", "Container images are unpinned or stale.");
|
|
267
267
|
});
|
|
268
268
|
program
|
|
269
269
|
.command("init")
|
|
270
|
-
.description("
|
|
271
|
-
.option("-t, --template <name>",
|
|
272
|
-
.option("--package <id>", "INFO.package
|
|
273
|
-
.option("--name <display>", "
|
|
274
|
-
.option("--ui <vue|plain>",
|
|
275
|
-
.option("--with <caps>",
|
|
276
|
-
.option("--from <compose.yaml>", "compose
|
|
277
|
-
.option("--shared-folder <select|create>", "
|
|
278
|
-
.option("--shared-folder-name <name>", "
|
|
279
|
-
.option("--shared-folder-key <key>", "
|
|
280
|
-
.option("--shared-folder-permission <ro|rw>", "
|
|
281
|
-
.option("--min-dsm <version>",
|
|
282
|
-
.argument("[dir]", "
|
|
283
|
-
.addHelpText("after", "\
|
|
270
|
+
.description("Create an App project from a template (never overwrites existing files)")
|
|
271
|
+
.option("-t, --template <name>", `Template: ${listTemplates().join(" | ")}`)
|
|
272
|
+
.option("--package <id>", "INFO.package (defaults to the directory name)")
|
|
273
|
+
.option("--name <display>", "Display name")
|
|
274
|
+
.option("--ui <vue|plain>", `Native UI ${JSON.parse(readFileSync(join(templatesRoot(), "native-go-vue/frontend/package.json"), "utf8")).dependencies["@synopackageland/ui"]} by default (vue); plain uses ui/app.js`)
|
|
275
|
+
.option("--with <caps>", `Capabilities (comma-separated): ${CAPABILITY_NAMES.join(",")}`)
|
|
276
|
+
.option("--from <compose.yaml>", "compose: import an existing Compose; omit to create an example")
|
|
277
|
+
.option("--shared-folder <select|create>", "Select or create a shared folder in the install wizard")
|
|
278
|
+
.option("--shared-folder-name <name>", "Name of the shared folder to create")
|
|
279
|
+
.option("--shared-folder-key <key>", "Settings key, defaults to shared")
|
|
280
|
+
.option("--shared-folder-permission <ro|rw>", "Package account permission, defaults to rw")
|
|
281
|
+
.option("--min-dsm <version>", `Minimum DSM version (native templates and binary default to ${DEFAULT_MIN_DSM}, compose to ${CONTAINER_MIN_DSM})`)
|
|
282
|
+
.argument("[dir]", "Target directory", ".")
|
|
283
|
+
.addHelpText("after", "\nNative projects: native-go, native-python, native-node, native-rust and binary use dsm-package.yaml schema 2.\nCompose projects: compose uses synology-app.yaml; --from imports compose.yaml.\nQuick start: synopkgland init -t native-go --with files,jobs,login my-app\n cd my-app && synopkgland build\n")
|
|
284
284
|
.action((dir, options, command) => {
|
|
285
285
|
if (command.parent?.opts().project)
|
|
286
|
-
throw failure("CLI_OPTION_INVALID", "--project
|
|
286
|
+
throw failure("CLI_OPTION_INVALID", "--project does not apply to init.");
|
|
287
287
|
if (!options.template)
|
|
288
|
-
throw failure("INIT_TEMPLATE_REQUIRED",
|
|
288
|
+
throw failure("INIT_TEMPLATE_REQUIRED", `Specify -t, --template <name>. Available templates: ${listTemplates().join(" | ")}`);
|
|
289
289
|
if (!isTemplateName(options.template))
|
|
290
|
-
throw failure("INIT_TEMPLATE_UNKNOWN",
|
|
290
|
+
throw failure("INIT_TEMPLATE_UNKNOWN", `Unknown template: ${options.template}`);
|
|
291
291
|
const targetDir = resolve(dir);
|
|
292
292
|
initFromTemplate(options.template, targetDir, {
|
|
293
293
|
packageId: options.package,
|
|
@@ -307,30 +307,30 @@ export async function runCli(argv) {
|
|
|
307
307
|
permission: options.sharedFolderPermission,
|
|
308
308
|
},
|
|
309
309
|
});
|
|
310
|
-
stdout(
|
|
311
|
-
|
|
312
|
-
if (
|
|
313
|
-
stdout(
|
|
310
|
+
stdout(`Created the ${options.template} project at ${targetDir}`);
|
|
311
|
+
const next = initNextSteps(options.template, dir, existsSync(join(targetDir, "frontend", "package.json")), Boolean(process.env.SYNOPKGLAND_SDK_DIR));
|
|
312
|
+
if (next)
|
|
313
|
+
stdout(next);
|
|
314
314
|
});
|
|
315
315
|
program
|
|
316
316
|
.command("add <caps...>")
|
|
317
|
-
.description(
|
|
317
|
+
.description(`Add capabilities (${CAPABILITY_NAMES.join(", ")}) or a portal and SDK-owned overlay; never overwrite existing app files`)
|
|
318
318
|
.action((caps, _options, command) => {
|
|
319
319
|
const result = addCapabilities(rootOf(command), caps);
|
|
320
320
|
for (const note of result.notes ?? [])
|
|
321
|
-
console.error(`[
|
|
322
|
-
stdout(
|
|
321
|
+
console.error(`[note] ${note}`);
|
|
322
|
+
stdout(`Added: ${result.added.join(", ") || "none"}; already present: ${result.unchanged.join(", ") || "none"}`);
|
|
323
323
|
});
|
|
324
324
|
program
|
|
325
325
|
.command("dev")
|
|
326
|
-
.description("
|
|
326
|
+
.description("Start the local Compose")
|
|
327
327
|
.action(async (_options, command) => {
|
|
328
328
|
const project = requireProject(rootOf(command));
|
|
329
329
|
const validation = validateProject(project);
|
|
330
330
|
if (!validation.ok)
|
|
331
331
|
throw failure(validation.errors[0].code, formatErrors(validation.errors));
|
|
332
332
|
const handle = await runDev({ project });
|
|
333
|
-
stdout("Compose
|
|
333
|
+
stdout("Compose is ready. Press Ctrl+C to stop.");
|
|
334
334
|
const shutdown = async () => {
|
|
335
335
|
await handle.close();
|
|
336
336
|
process.exit(0);
|
|
@@ -341,26 +341,26 @@ export async function runCli(argv) {
|
|
|
341
341
|
});
|
|
342
342
|
program
|
|
343
343
|
.command("test")
|
|
344
|
-
.description("
|
|
344
|
+
.description("Validate the contract and wait for Compose to be ready")
|
|
345
345
|
.action(async (_options, command) => {
|
|
346
346
|
const result = await testProject({
|
|
347
347
|
project: requireProject(rootOf(command)),
|
|
348
348
|
});
|
|
349
349
|
if (!result.ok)
|
|
350
|
-
throw failure(result.errors[0] ?? "TEST_FAILED",
|
|
351
|
-
stdout("
|
|
350
|
+
throw failure(result.errors[0] ?? "TEST_FAILED", `Test failed: ${result.errors.join(", ")}`);
|
|
351
|
+
stdout("Tests passed.");
|
|
352
352
|
});
|
|
353
353
|
program
|
|
354
354
|
.command("deploy")
|
|
355
|
-
.description("
|
|
356
|
-
.option("--json", "
|
|
355
|
+
.description("Build the SPK and install it into DSM")
|
|
356
|
+
.option("--json", "Print a single JSON object")
|
|
357
357
|
.option("--host <name>", "NAS profile")
|
|
358
358
|
.option("--arch <family>", NATIVE_ARCH_FAMILIES.join(" | "))
|
|
359
359
|
.option("--dev", "Development Package Identity")
|
|
360
|
-
.option("--share <slot=name>", "Compose
|
|
361
|
-
.option("--run-as <slot=name>", "Compose DSM
|
|
362
|
-
.option("--param <NAME=value>", "
|
|
363
|
-
.option("--dry-run", "
|
|
360
|
+
.option("--share <slot=name>", "Compose shared folder", (value, previous) => collectKv(value, previous, "--share"))
|
|
361
|
+
.option("--run-as <slot=name>", "Compose DSM user", (value, previous) => collectKv(value, previous, "--run-as"))
|
|
362
|
+
.option("--param <NAME=value>", "Install parameter (Compose) or install wizard value (native-go/binary)", (value, previous) => collectKv(value, previous, "--param"))
|
|
363
|
+
.option("--dry-run", "Print the local deploy plan only, without connecting to the NAS")
|
|
364
364
|
.action(async (options, command) => {
|
|
365
365
|
if (options.arch !== undefined &&
|
|
366
366
|
!isNativeArchFamily(String(options.arch)))
|
|
@@ -371,7 +371,7 @@ export async function runCli(argv) {
|
|
|
371
371
|
});
|
|
372
372
|
if (plan.kind !== "container" &&
|
|
373
373
|
(options.share || options.runAs))
|
|
374
|
-
throw failure("CLI_OPTION_INVALID", "native-go
|
|
374
|
+
throw failure("CLI_OPTION_INVALID", "native-go/binary do not accept --share/--run-as; pass install wizard values with --param.");
|
|
375
375
|
if (options.dryRun) {
|
|
376
376
|
emit("deploy", plan, JSON.stringify(plan, null, 2));
|
|
377
377
|
return;
|
|
@@ -380,7 +380,7 @@ export async function runCli(argv) {
|
|
|
380
380
|
const host = hostOf(options.host);
|
|
381
381
|
const output = await deployStandalone(plan, host, { params: options.param });
|
|
382
382
|
const status = await standaloneInfo({ root: plan.root, host });
|
|
383
|
-
emit("deploy", { ...plan, output, status },
|
|
383
|
+
emit("deploy", { ...plan, output, status }, `Deployed ${plan.packageId}: ${output}\n${formatProjectInfo(status)}${plan.notes.length ? "\n" + plan.notes.join("\n") : ""}`);
|
|
384
384
|
return;
|
|
385
385
|
}
|
|
386
386
|
const project = requireProject(rootOf(command));
|
|
@@ -400,15 +400,15 @@ export async function runCli(argv) {
|
|
|
400
400
|
paramValues: options.param,
|
|
401
401
|
});
|
|
402
402
|
if (!result.ok)
|
|
403
|
-
throw failure(result.code ?? "DEPLOY_FAILED", result.message ?? "
|
|
403
|
+
throw failure(result.code ?? "DEPLOY_FAILED", result.message ?? "Deployment failed.");
|
|
404
404
|
for (const note of result.notes ?? [])
|
|
405
|
-
console.error(`[
|
|
406
|
-
emit("deploy", result,
|
|
405
|
+
console.error(`[note] ${note}`);
|
|
406
|
+
emit("deploy", result, `Deployed ${result.packageId}${result.openUrl ? "\nOpen URL: " + result.openUrl : ""}`);
|
|
407
407
|
});
|
|
408
408
|
program
|
|
409
409
|
.command("status")
|
|
410
|
-
.description("DSM
|
|
411
|
-
.option("--json", "
|
|
410
|
+
.description("DSM package status, Open URL and capability readiness")
|
|
411
|
+
.option("--json", "Print a single JSON object")
|
|
412
412
|
.option("--host <name>", "NAS profile")
|
|
413
413
|
.option("--dev", "Development Package Identity")
|
|
414
414
|
.action(async (options, command) => {
|
|
@@ -426,7 +426,7 @@ export async function runCli(argv) {
|
|
|
426
426
|
});
|
|
427
427
|
program
|
|
428
428
|
.command("logs")
|
|
429
|
-
.description("
|
|
429
|
+
.description("Status diagnosis: DSM package status and masked diagnostics; does not stream application logs")
|
|
430
430
|
.requiredOption("--host <name>", "NAS profile")
|
|
431
431
|
.option("--dev", "Development Package Identity")
|
|
432
432
|
.action(async (options, command) => {
|
|
@@ -445,32 +445,32 @@ export async function runCli(argv) {
|
|
|
445
445
|
dev: options.dev,
|
|
446
446
|
});
|
|
447
447
|
if (!result.ok)
|
|
448
|
-
throw failure("NOT_INSTALLED", "
|
|
448
|
+
throw failure("NOT_INSTALLED", "The package is not installed. Run synopkgland deploy --host first.");
|
|
449
449
|
for (const line of result.lines)
|
|
450
450
|
stdout(line);
|
|
451
451
|
if (result.diagnostics)
|
|
452
452
|
stdout(JSON.stringify(result.diagnostics, null, 2));
|
|
453
453
|
});
|
|
454
|
-
const skill = program.command("skill").description("
|
|
454
|
+
const skill = program.command("skill").description("Bundled Agent Skill Pack");
|
|
455
455
|
skill.command("path").action(() => stdout(resolveSkillsRoot()));
|
|
456
456
|
skill.command("list").action(() => stdout(formatSkillList()));
|
|
457
457
|
skill.command("show <id>").action((id) => stdout(readSkill(id)));
|
|
458
458
|
program
|
|
459
459
|
.command("build")
|
|
460
|
-
.description("
|
|
461
|
-
.option("--json", "
|
|
462
|
-
.option("-o, --output <path>", "
|
|
463
|
-
.option("--out-dir <dir>", "
|
|
464
|
-
.option("--arch <families>",
|
|
465
|
-
.option("--min-dsm <version>", "
|
|
466
|
-
.option("--jobs <n>", "
|
|
467
|
-
.option("--verify-reproducible [when]", "
|
|
460
|
+
.description("Build an SPK from synology-app.yaml or dsm-package.yaml")
|
|
461
|
+
.option("--json", "Print a single JSON object")
|
|
462
|
+
.option("-o, --output <path>", "Output SPK; use with a single --arch")
|
|
463
|
+
.option("--out-dir <dir>", "Output directory; native projects default to dist/")
|
|
464
|
+
.option("--arch <families>", `Architectures, comma-separated or repeated: ${NATIVE_ARCH_FAMILIES.join(",")} (armv7 is for native-go and binary, not for Container Manager Apps); native layout also supports armv8`, (value, previous) => [...(previous ?? []), value])
|
|
465
|
+
.option("--min-dsm <version>", "Minimum DSM version; uses the manifest value when omitted")
|
|
466
|
+
.option("--jobs <n>", "Number of architectures to build in parallel, default 1 (sequential)", Number)
|
|
467
|
+
.option("--verify-reproducible [when]", "Build again from a clean state and compare; when: changed (default, skips when the dependency fingerprint is unchanged) or always")
|
|
468
468
|
.action(async (options, command) => {
|
|
469
469
|
const root = rootOf(command);
|
|
470
470
|
const standalone = standaloneRoot(root);
|
|
471
471
|
const verify = options.verifyReproducible === true ? "changed" : options.verifyReproducible;
|
|
472
472
|
if (verify !== undefined && verify !== "changed" && verify !== "always")
|
|
473
|
-
throw failure("CLI_OPTION_INVALID", `--verify-reproducible
|
|
473
|
+
throw failure("CLI_OPTION_INVALID", `--verify-reproducible accepts only changed or always, got ${verify}.`);
|
|
474
474
|
let arches;
|
|
475
475
|
try {
|
|
476
476
|
arches =
|
|
@@ -501,9 +501,9 @@ export async function runCli(argv) {
|
|
|
501
501
|
? build({ output: undefined, outDir })
|
|
502
502
|
: build({ output: join(outDir, basename(outputs[0])), outDir: undefined }),
|
|
503
503
|
});
|
|
504
|
-
const lines = [...outputs.map((output) =>
|
|
504
|
+
const lines = [...outputs.map((output) => `Built ${output}`), ...(reproducible ? reproducibleLines(reproducible) : [])];
|
|
505
505
|
if (reproducible && !reproducible.identical)
|
|
506
|
-
throw Object.assign(failure("BUILD_NOT_REPRODUCIBLE", lines.join("\n"), "
|
|
506
|
+
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 } });
|
|
507
507
|
emit("build", { outputs, ...(reproducible ? { reproducible } : {}) }, lines.join("\n"));
|
|
508
508
|
});
|
|
509
509
|
if (json)
|
|
@@ -532,7 +532,7 @@ export async function runCli(argv) {
|
|
|
532
532
|
ok: false,
|
|
533
533
|
code,
|
|
534
534
|
message,
|
|
535
|
-
hint: detail.hint ?? getErrorCode(code)?.suggestion ?? "
|
|
535
|
+
hint: detail.hint ?? getErrorCode(code)?.suggestion ?? "Fix the configuration and retry; use --help to see command usage.",
|
|
536
536
|
...(detail.report ? { data: detail.report } : {}),
|
|
537
537
|
}));
|
|
538
538
|
}
|