@synopackageland/cli 0.3.9 → 0.4.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/README.md +20 -9
- package/dist/bin.js +1 -2
- package/dist/build-spk/generator.d.ts +0 -3
- package/dist/build-spk/generator.js +135 -287
- package/dist/build-spk/native-lifecycle.js +0 -3
- package/dist/build-spk/native-payload.js +0 -1
- package/dist/cli.js +171 -151
- package/dist/compose/analyze.d.ts +1 -2
- package/dist/compose/analyze.js +5 -48
- package/dist/contract/unsupported-types.d.ts +10 -0
- package/dist/contract/unsupported-types.js +49 -0
- package/dist/contract/validate.js +25 -2
- package/dist/contract/web-launch.js +0 -2
- package/dist/deploy/deploy.d.ts +1 -4
- package/dist/deploy/deploy.js +57 -156
- package/dist/deploy/homes-values.js +1 -1
- package/dist/deploy/plan.d.ts +18 -0
- package/dist/deploy/plan.js +54 -0
- package/dist/deploy/port-preflight.d.ts +21 -0
- package/dist/deploy/port-preflight.js +42 -0
- package/dist/deploy/spk-version.d.ts +2 -14
- package/dist/deploy/spk-version.js +25 -111
- package/dist/dev.d.ts +1 -3
- package/dist/dev.js +14 -28
- package/dist/error-registry.js +810 -857
- package/dist/host/dsm-client.js +23 -263
- package/dist/host/dsm-port-profile.d.ts +47 -0
- package/dist/host/dsm-port-profile.js +206 -0
- package/dist/host/port-info-lookup.d.ts +9 -0
- package/dist/host/port-info-lookup.js +87 -0
- package/dist/host/port-info.d.ts +33 -0
- package/dist/host/port-info.js +66 -0
- package/dist/host/types.d.ts +6 -78
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -6
- package/dist/info.d.ts +0 -1
- package/dist/info.js +3 -10
- package/dist/init.d.ts +11 -1
- package/dist/init.js +112 -4
- package/dist/log.js +2 -3
- package/dist/native/validate.js +0 -1
- package/dist/standalone/binary.d.ts +9 -0
- package/dist/standalone/binary.js +67 -0
- package/dist/standalone/build.d.ts +25 -0
- package/dist/standalone/build.js +108 -0
- package/dist/standalone/dsm-window.d.ts +26 -0
- package/dist/standalone/dsm-window.js +251 -0
- package/dist/standalone/helper.d.ts +31 -0
- package/dist/standalone/helper.js +104 -0
- package/dist/standalone/i18n.d.ts +28 -0
- package/dist/standalone/i18n.js +161 -0
- package/dist/standalone/inspect-spk.d.ts +21 -0
- package/dist/standalone/inspect-spk.js +160 -0
- package/dist/standalone/manifest.d.ts +71 -0
- package/dist/standalone/manifest.js +276 -0
- package/dist/standalone/root-messages.d.ts +7 -0
- package/dist/standalone/root-messages.js +250 -0
- package/dist/standalone/root-ops.d.ts +42 -0
- package/dist/standalone/root-ops.js +101 -0
- package/dist/standalone/root-window.d.ts +35 -0
- package/dist/standalone/root-window.js +290 -0
- package/dist/standalone/spk.d.ts +22 -0
- package/dist/standalone/spk.js +205 -0
- package/dist/templates/binary/synology-app.yaml +7 -0
- package/dist/templates/hello-files/compose.yaml +0 -1
- package/dist/templates/hello-web/compose.yaml +13 -10
- package/dist/templates/hello-web/html/app.js +1 -23
- package/dist/templates/hello-web/html/index.html +1 -3
- package/dist/templates/hello-web-page/compose.yaml +13 -10
- package/dist/templates/hello-web-page/html/app.js +1 -23
- package/dist/templates/hello-web-page/html/index.html +1 -3
- package/dist/templates/native-go/README.md +268 -0
- package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
- package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/dist/templates/native-go/backend/go.mod +3 -0
- package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
- package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
- package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/dist/templates/native-go/backend/internal/api/server.go +155 -0
- package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
- package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
- package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
- package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/dist/templates/native-go/dsm-package.yaml +58 -0
- package/dist/templates/native-go/gitignore +1 -0
- package/dist/templates/native-go/i18n/glossary.json +662 -0
- package/dist/templates/native-go/i18n/messages/de.json +28 -0
- package/dist/templates/native-go/i18n/messages/en.json +28 -0
- package/dist/templates/native-go/i18n/messages/es.json +28 -0
- package/dist/templates/native-go/i18n/messages/fr.json +28 -0
- package/dist/templates/native-go/i18n/messages/it.json +28 -0
- package/dist/templates/native-go/i18n/messages/ja.json +28 -0
- package/dist/templates/native-go/i18n/messages/ko.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/dist/templates/native-go/ui/app.css +35 -0
- package/dist/templates/native-go/ui/app.js +94 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
- package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
- package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/dist/templates/native-go-better-auth/node/package.json +1 -0
- package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/dist/templates/native-go-root/root/whoami.sh +6 -0
- package/dist/templates/native-go-vue/README.md +110 -0
- package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/dist/templates/native-go-vue/frontend/package.json +6 -0
- package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
- package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/dist/templates/native-go-vue/gitignore +2 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate.js +11 -7
- package/package.json +3 -7
- package/schema/synology-app.schema.json +36 -37
- package/skills/SKILL.md +4 -48
- package/skills/reference/create.md +11 -83
- package/skills/reference/deploy.md +7 -78
- package/skills/reference/diagnose.md +4 -101
- package/skills/reference/files.md +5 -41
- package/skills/reference/identity.md +3 -29
- package/skills/reference/wrap.md +0 -2
- package/templates/binary/synology-app.yaml +7 -0
- package/templates/hello-files/compose.yaml +0 -1
- package/templates/hello-web/compose.yaml +13 -10
- package/templates/hello-web/html/app.js +1 -23
- package/templates/hello-web/html/index.html +1 -3
- package/templates/hello-web-page/compose.yaml +13 -10
- package/templates/hello-web-page/html/app.js +1 -23
- package/templates/hello-web-page/html/index.html +1 -3
- package/templates/native-go/README.md +268 -0
- package/templates/native-go/backend/cmd/app/main.go +49 -0
- package/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/templates/native-go/backend/go.mod +3 -0
- package/templates/native-go/backend/internal/api/errors.go +33 -0
- package/templates/native-go/backend/internal/api/registry.go +94 -0
- package/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/templates/native-go/backend/internal/api/server.go +155 -0
- package/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/templates/native-go/backend/internal/app/routes.go +127 -0
- package/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/templates/native-go/backend/internal/helper/control.go +219 -0
- package/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/templates/native-go/backend/internal/helper/status.go +271 -0
- package/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/templates/native-go/dsm-package.yaml +58 -0
- package/templates/native-go/gitignore +1 -0
- package/templates/native-go/i18n/glossary.json +662 -0
- package/templates/native-go/i18n/messages/de.json +28 -0
- package/templates/native-go/i18n/messages/en.json +28 -0
- package/templates/native-go/i18n/messages/es.json +28 -0
- package/templates/native-go/i18n/messages/fr.json +28 -0
- package/templates/native-go/i18n/messages/it.json +28 -0
- package/templates/native-go/i18n/messages/ja.json +28 -0
- package/templates/native-go/i18n/messages/ko.json +28 -0
- package/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/templates/native-go/ui/app.css +35 -0
- package/templates/native-go/ui/app.js +94 -0
- package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/templates/native-go-better-auth/node/app.mjs +72 -0
- package/templates/native-go-better-auth/node/build.mjs +2 -0
- package/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/templates/native-go-better-auth/node/package.json +1 -0
- package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/templates/native-go-root/root/whoami.sh +6 -0
- package/templates/native-go-vue/README.md +110 -0
- package/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/templates/native-go-vue/frontend/package.json +6 -0
- package/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/templates/native-go-vue/frontend/src/style.css +14 -0
- package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/templates/native-go-vue/gitignore +2 -0
- package/dist/attestation/app-package.d.ts +0 -86
- package/dist/attestation/app-package.js +0 -206
- package/dist/attestation/install-on-host.d.ts +0 -14
- package/dist/attestation/install-on-host.js +0 -52
- package/dist/attestation/keyring.d.ts +0 -21
- package/dist/attestation/keyring.js +0 -168
- package/dist/attestation/platform-service.d.ts +0 -70
- package/dist/attestation/platform-service.js +0 -250
- package/dist/build-spk/broker-glue.d.ts +0 -16
- package/dist/build-spk/broker-glue.js +0 -40
- package/dist/deploy/catalog-publish.d.ts +0 -3
- package/dist/deploy/catalog-publish.js +0 -7
- package/dist/doctor.d.ts +0 -22
- package/dist/doctor.js +0 -721
- package/dist/error-collector.d.ts +0 -2
- package/dist/error-collector.js +0 -46
- package/dist/host/broker-paths.d.ts +0 -1
- package/dist/host/broker-paths.js +0 -1
- package/dist/host/broker-upgrade-preflight.d.ts +0 -61
- package/dist/host/broker-upgrade-preflight.js +0 -156
- package/dist/host/digests.d.ts +0 -5
- package/dist/host/digests.js +0 -63
- package/dist/host/fake-nas.d.ts +0 -41
- package/dist/host/fake-nas.js +0 -174
- package/dist/mock-broker/bootstrap.d.ts +0 -2
- package/dist/mock-broker/bootstrap.js +0 -8
- package/dist/mock-broker/files.d.ts +0 -60
- package/dist/mock-broker/files.js +0 -408
- package/dist/mock-broker/oidc.d.ts +0 -31
- package/dist/mock-broker/oidc.js +0 -161
- package/dist/mock-broker/path.d.ts +0 -9
- package/dist/mock-broker/path.js +0 -50
- package/dist/mock-broker/server.d.ts +0 -19
- package/dist/mock-broker/server.js +0 -350
- package/dist/publish/build-ledger.d.ts +0 -22
- package/dist/publish/build-ledger.js +0 -26
- package/dist/publish/community-path.d.ts +0 -6
- package/dist/publish/community-path.js +0 -41
- package/dist/publish/curated-release.d.ts +0 -78
- package/dist/publish/curated-release.js +0 -883
- package/dist/publish/file-build-ledger.d.ts +0 -18
- package/dist/publish/file-build-ledger.js +0 -268
- package/dist/publish/github-pr.d.ts +0 -25
- package/dist/publish/github-pr.js +0 -85
- package/dist/publish/github-repo.d.ts +0 -8
- package/dist/publish/github-repo.js +0 -21
- package/dist/publish/platform-service-release.d.ts +0 -60
- package/dist/publish/platform-service-release.js +0 -333
- package/dist/publish/preflight.d.ts +0 -24
- package/dist/publish/preflight.js +0 -138
- package/dist/publish/publish.d.ts +0 -27
- package/dist/publish/publish.js +0 -136
- package/dist/publish/sync-definition.d.ts +0 -4
- package/dist/publish/sync-definition.js +0 -48
- package/dist/release-bin.d.ts +0 -2
- package/dist/release-bin.js +0 -23
- package/dist/templates/hello-web/html/sdk.js +0 -68
- package/dist/templates/hello-web-page/html/sdk.js +0 -68
- package/dist/templates/native/synology-app.yaml +0 -7
- package/dist/test-host.d.ts +0 -15
- package/dist/test-host.js +0 -37
- package/templates/hello-web/html/sdk.js +0 -68
- package/templates/hello-web-page/html/sdk.js +0 -68
- package/templates/native/synology-app.yaml +0 -7
- /package/dist/templates/{native → binary}/native.yaml +0 -0
- /package/templates/{native → binary}/native.yaml +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { checkCgiBinary } from "./binary.js";
|
|
6
|
+
import { loadStandaloneManifest } from "./manifest.js";
|
|
7
|
+
import { assembleStandaloneSpk, StandaloneBuildError } from "./spk.js";
|
|
8
|
+
import { buildHelper } from "./helper.js";
|
|
9
|
+
const GOARCH = { x86_64: "amd64", aarch64: "arm64" };
|
|
10
|
+
/** Argv only, never a shell; output streams to the terminal except stderr, which is also kept. */
|
|
11
|
+
export const runCommand = (command, args, options) => {
|
|
12
|
+
const result = spawnSync(command, args, {
|
|
13
|
+
cwd: options.cwd,
|
|
14
|
+
env: options.env,
|
|
15
|
+
stdio: ["ignore", "inherit", "pipe"],
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
});
|
|
18
|
+
if (result.stderr) {
|
|
19
|
+
process.stderr.write(result.stderr);
|
|
20
|
+
}
|
|
21
|
+
const missing = result.error?.code === "ENOENT";
|
|
22
|
+
return { status: result.status, stderr: result.stderr ?? "", missing };
|
|
23
|
+
};
|
|
24
|
+
/** `build-spk` for a Standalone Package: build the UI, compile the CGI, pack one SPK per arch. */
|
|
25
|
+
export async function buildStandalone(options) {
|
|
26
|
+
const loaded = loadStandaloneManifest(options.root);
|
|
27
|
+
if (!loaded.ok) {
|
|
28
|
+
const [first] = loaded.errors;
|
|
29
|
+
throw new StandaloneBuildError(first.code, loaded.errors.map((e) => `[${e.code}] ${e.message}`).join("\n"));
|
|
30
|
+
}
|
|
31
|
+
const manifest = loaded.manifest;
|
|
32
|
+
const run = options.run ?? runCommand;
|
|
33
|
+
const arches = options.arches ?? manifest.arches;
|
|
34
|
+
for (const arch of arches) {
|
|
35
|
+
if (!manifest.arches.includes(arch)) {
|
|
36
|
+
throw new StandaloneBuildError("STANDALONE_ARCH_NOT_DECLARED", `${arch} 不在 dsm-package.yaml 的 arches 內。`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
buildUi(manifest, run);
|
|
40
|
+
if (manifest.integration?.identity === "better-auth") {
|
|
41
|
+
const result = run("npm", ["run", "build"], { cwd: join(manifest.root, "node"), env: { ...process.env } });
|
|
42
|
+
if (result.missing || result.status !== 0)
|
|
43
|
+
throw new StandaloneBuildError("STANDALONE_IDENTITY_BUILD_FAILED", `Optional Better Auth build failed.\n${result.stderr}`);
|
|
44
|
+
}
|
|
45
|
+
const outDir = options.outDir ?? join(options.root, "dist");
|
|
46
|
+
const work = mkdtempSync(join(tmpdir(), "syno-standalone-build-"));
|
|
47
|
+
try {
|
|
48
|
+
const outputs = [];
|
|
49
|
+
for (const arch of arches) {
|
|
50
|
+
const helper = buildHelper(manifest, arch, work, run);
|
|
51
|
+
if (helper) {
|
|
52
|
+
const problem = checkCgiBinary(work, helper.binary, arch);
|
|
53
|
+
if (problem)
|
|
54
|
+
throw new StandaloneBuildError(problem.code, problem.message);
|
|
55
|
+
}
|
|
56
|
+
const cgiPath = backendFor(manifest, arch, work, run, helper);
|
|
57
|
+
const problem = checkCgiBinary(manifest.backend.kind === "go" ? work : manifest.root, cgiPath, arch);
|
|
58
|
+
if (problem) {
|
|
59
|
+
throw new StandaloneBuildError(problem.code, problem.message);
|
|
60
|
+
}
|
|
61
|
+
const outputPath = join(outDir, `${manifest.package}-${manifest.spkVersion}-${arch}.spk`);
|
|
62
|
+
const builtManifest = helper ? { ...manifest, rootOps: [...(manifest.rootOps ?? []), ...helper.ops] } : manifest;
|
|
63
|
+
await assembleStandaloneSpk({ manifest: builtManifest, arch, cgiPath, outputPath, helper });
|
|
64
|
+
outputs.push(outputPath);
|
|
65
|
+
}
|
|
66
|
+
return { outputs };
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
rmSync(work, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function buildUi(manifest, run) {
|
|
73
|
+
const build = manifest.ui.build;
|
|
74
|
+
if (!build) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const [command, ...args] = build.command;
|
|
78
|
+
const result = run(command, args, { cwd: build.cwd, env: { ...process.env } });
|
|
79
|
+
if (result.missing || result.status !== 0) {
|
|
80
|
+
throw new StandaloneBuildError("STANDALONE_UI_BUILD_FAILED", `UI build(${build.command.join(" ")})失敗。\n${result.stderr}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function backendFor(manifest, arch, work, run, helper) {
|
|
84
|
+
const backend = manifest.backend;
|
|
85
|
+
if (backend.kind === "binary") {
|
|
86
|
+
return backend.files[arch];
|
|
87
|
+
}
|
|
88
|
+
const out = join(work, `api-${arch}.cgi`);
|
|
89
|
+
const ldflags = [
|
|
90
|
+
"-s",
|
|
91
|
+
"-w",
|
|
92
|
+
"-buildid=",
|
|
93
|
+
`-X main.packageID=${manifest.package}`,
|
|
94
|
+
`-X main.version=${manifest.spkVersion}`,
|
|
95
|
+
...(helper ? [`-X 'main.helperInventory=${JSON.stringify(helper.inventory)}'`] : []),
|
|
96
|
+
].join(" ");
|
|
97
|
+
const result = run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, "-o", out, backend.main], {
|
|
98
|
+
cwd: backend.dir,
|
|
99
|
+
env: { ...process.env, CGO_ENABLED: "0", GOOS: "linux", GOARCH: GOARCH[arch] },
|
|
100
|
+
});
|
|
101
|
+
if (result.missing) {
|
|
102
|
+
throw new StandaloneBuildError("STANDALONE_GO_MISSING", "找不到 go 指令。請安裝 Go 1.24 以上版本(https://go.dev/dl/),或改用 backend.binary 打包既有執行檔。");
|
|
103
|
+
}
|
|
104
|
+
if (result.status !== 0) {
|
|
105
|
+
throw new StandaloneBuildError("STANDALONE_GO_BUILD_FAILED", `go build(${arch})失敗。\n${result.stderr}`);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StandaloneManifest } from "./manifest.js";
|
|
2
|
+
/** Same path the generated wrapper POSTs to, and the payload location of the CGI. */
|
|
3
|
+
export declare const CGI_PAYLOAD_PATH = "ui/cgi/api.cgi";
|
|
4
|
+
export declare function cgiUrl(packageId: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* `createI18n(dsmLang, catalogs)` → `{ locale, t }`, plain ES5 so it runs in
|
|
7
|
+
* DSM's desktop as is. The DSM language picks a shipped catalog (else
|
|
8
|
+
* English); missing keys fall back to English, then to the key itself.
|
|
9
|
+
* `t(key, { count })` looks up `key.one` / `key.other` via Intl.PluralRules.
|
|
10
|
+
*/
|
|
11
|
+
export declare const I18N_RUNTIME: string;
|
|
12
|
+
/**
|
|
13
|
+
* The wrapper file name carries the build so DSM never serves a wrapper
|
|
14
|
+
* cached from the previous version after an upgrade.
|
|
15
|
+
*/
|
|
16
|
+
export declare function windowScriptName(manifest: StandaloneManifest): string;
|
|
17
|
+
/** `ui/config`: registers the DSM main-menu app and its window class. */
|
|
18
|
+
export declare function renderUiConfig(manifest: StandaloneManifest): string;
|
|
19
|
+
/**
|
|
20
|
+
* The DSM AppWindow that hosts the project's UI. It loads app/app.js and
|
|
21
|
+
* app/app.css once, then calls the app's mount(root, ctx). `ctx.call` is the
|
|
22
|
+
* only transport: same-origin POST with DSM's session cookie and SynoToken.
|
|
23
|
+
* `ctx.t` / `ctx.locale` translate with the catalogs embedded at build time.
|
|
24
|
+
* `assetVersion` busts browser caches when the UI changes.
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderWindowScript(manifest: StandaloneManifest, assetVersion: string): string;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { DSM_LANGS } from "./i18n.js";
|
|
2
|
+
import { ROOT_MESSAGES } from "./root-messages.js";
|
|
3
|
+
import { ROOT_PANEL, ROOT_RUNTIME, rootTasks } from "./root-window.js";
|
|
4
|
+
/** Same path the generated wrapper POSTs to, and the payload location of the CGI. */
|
|
5
|
+
export const CGI_PAYLOAD_PATH = "ui/cgi/api.cgi";
|
|
6
|
+
export function cgiUrl(packageId) {
|
|
7
|
+
return `/webman/3rdparty/${packageId}/cgi/api.cgi`;
|
|
8
|
+
}
|
|
9
|
+
const LOCALE_BY_DSM_LANG = Object.fromEntries(Object.entries(DSM_LANGS).map(([locale, lang]) => [lang, locale]));
|
|
10
|
+
/**
|
|
11
|
+
* `createI18n(dsmLang, catalogs)` → `{ locale, t }`, plain ES5 so it runs in
|
|
12
|
+
* DSM's desktop as is. The DSM language picks a shipped catalog (else
|
|
13
|
+
* English); missing keys fall back to English, then to the key itself.
|
|
14
|
+
* `t(key, { count })` looks up `key.one` / `key.other` via Intl.PluralRules.
|
|
15
|
+
*/
|
|
16
|
+
export const I18N_RUNTIME = `function createI18n(dsmLang, catalogs) {
|
|
17
|
+
var byLang = ${JSON.stringify(LOCALE_BY_DSM_LANG)};
|
|
18
|
+
var has = function (object, key) { return Object.prototype.hasOwnProperty.call(object, key); };
|
|
19
|
+
var wanted = has(byLang, dsmLang) ? byLang[dsmLang] : "en";
|
|
20
|
+
var locale = has(catalogs, wanted) ? wanted : "en";
|
|
21
|
+
var own = catalogs[locale] || {};
|
|
22
|
+
var en = catalogs.en || {};
|
|
23
|
+
var plural = typeof Intl !== "undefined" && Intl.PluralRules ? new Intl.PluralRules(locale) : null;
|
|
24
|
+
function lookup(key) {
|
|
25
|
+
return has(own, key) ? own[key] : has(en, key) ? en[key] : undefined;
|
|
26
|
+
}
|
|
27
|
+
function t(key, params) {
|
|
28
|
+
var text = lookup(key);
|
|
29
|
+
if (text === undefined && params && typeof params.count === "number") {
|
|
30
|
+
var form = plural ? plural.select(params.count) : params.count === 1 ? "one" : "other";
|
|
31
|
+
text = lookup(key + "." + form);
|
|
32
|
+
if (text === undefined) text = lookup(key + ".other");
|
|
33
|
+
}
|
|
34
|
+
if (text === undefined) return key;
|
|
35
|
+
return text.replace(/\\{(\\w+)\\}/g, function (match, name) {
|
|
36
|
+
return params && has(params, name) ? String(params[name]) : match;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return { locale: locale, t: t };
|
|
40
|
+
}`;
|
|
41
|
+
/**
|
|
42
|
+
* The wrapper file name carries the build so DSM never serves a wrapper
|
|
43
|
+
* cached from the previous version after an upgrade.
|
|
44
|
+
*/
|
|
45
|
+
export function windowScriptName(manifest) {
|
|
46
|
+
return `window-${String(manifest.build).padStart(4, "0")}.js`;
|
|
47
|
+
}
|
|
48
|
+
/** `ui/config`: registers the DSM main-menu app and its window class. */
|
|
49
|
+
export function renderUiConfig(manifest) {
|
|
50
|
+
const ns = `SYNO.SDS.${manifest.package}`;
|
|
51
|
+
return `${JSON.stringify({
|
|
52
|
+
[windowScriptName(manifest)]: {
|
|
53
|
+
[`${ns}.Instance`]: {
|
|
54
|
+
type: "app",
|
|
55
|
+
title: manifest.displayname,
|
|
56
|
+
desc: manifest.description,
|
|
57
|
+
icon: "images/app_{0}.png",
|
|
58
|
+
appWindow: `${ns}.MainWindow`,
|
|
59
|
+
allowMultiInstance: false,
|
|
60
|
+
allUsers: true,
|
|
61
|
+
depend: [`${ns}.MainWindow`],
|
|
62
|
+
},
|
|
63
|
+
[`${ns}.MainWindow`]: {
|
|
64
|
+
type: "lib",
|
|
65
|
+
title: manifest.displayname,
|
|
66
|
+
icon: "images/app_{0}.png",
|
|
67
|
+
depend: [],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}, null, 2)}\n`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The DSM AppWindow that hosts the project's UI. It loads app/app.js and
|
|
74
|
+
* app/app.css once, then calls the app's mount(root, ctx). `ctx.call` is the
|
|
75
|
+
* only transport: same-origin POST with DSM's session cookie and SynoToken.
|
|
76
|
+
* `ctx.t` / `ctx.locale` translate with the catalogs embedded at build time.
|
|
77
|
+
* `assetVersion` busts browser caches when the UI changes.
|
|
78
|
+
*/
|
|
79
|
+
export function renderWindowScript(manifest, assetVersion) {
|
|
80
|
+
const pkg = manifest.package;
|
|
81
|
+
const ns = `SYNO.SDS.${pkg}`;
|
|
82
|
+
const p = JSON.stringify(pkg);
|
|
83
|
+
const root = manifest.rootOps ? renderRootParts(ns, manifest) : undefined;
|
|
84
|
+
return `/* Generated by synopkgland build-spk. Edit your UI in the project's ui folder. */
|
|
85
|
+
Ext.ns(${JSON.stringify(ns)});
|
|
86
|
+
|
|
87
|
+
${ns}.call = function (method, params) {
|
|
88
|
+
var headers = { "Content-Type": "application/json" };
|
|
89
|
+
var token = SYNO.SDS.Session && SYNO.SDS.Session.SynoToken;
|
|
90
|
+
if (token) {
|
|
91
|
+
headers["X-SYNO-TOKEN"] = token;
|
|
92
|
+
}
|
|
93
|
+
return fetch(${JSON.stringify(cgiUrl(pkg))}, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
credentials: "same-origin",
|
|
96
|
+
headers: headers,
|
|
97
|
+
body: JSON.stringify({ method: method, params: params || {} })
|
|
98
|
+
}).then(function (response) {
|
|
99
|
+
return response.json().catch(function () {
|
|
100
|
+
return { success: false, error: { code: "bad_response", message: "HTTP " + response.status } };
|
|
101
|
+
});
|
|
102
|
+
}, function () {
|
|
103
|
+
return { success: false, error: { code: "network", message: "The NAS did not answer" } };
|
|
104
|
+
}).then(function (body) {
|
|
105
|
+
if (body && body.success === true) {
|
|
106
|
+
return body.data;
|
|
107
|
+
}
|
|
108
|
+
throw (body && body.error) || { code: "bad_response", message: "Unexpected response" };
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
${ns}.messages = ${JSON.stringify(manifest.i18n?.catalogs ?? { en: {} })};
|
|
113
|
+
${ns}.createI18n = (function () {
|
|
114
|
+
${I18N_RUNTIME}
|
|
115
|
+
return createI18n;
|
|
116
|
+
})();
|
|
117
|
+
|
|
118
|
+
${root ? root.definitions : ""}${ns}.loadApp = function (done) {
|
|
119
|
+
var apps = window.DsmPackageApps || {};
|
|
120
|
+
if (apps[${p}]) {
|
|
121
|
+
done(apps[${p}]);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
var base = "webman/3rdparty/${pkg}/app/";
|
|
125
|
+
var css = document.createElement("link");
|
|
126
|
+
css.rel = "stylesheet";
|
|
127
|
+
css.href = base + "app.css?v=${assetVersion}";
|
|
128
|
+
document.head.appendChild(css);
|
|
129
|
+
var js = document.createElement("script");
|
|
130
|
+
js.src = base + "app.js?v=${assetVersion}";
|
|
131
|
+
js.dataset.package = ${p};
|
|
132
|
+
js.onload = function () { done((window.DsmPackageApps || {})[${p}]); };
|
|
133
|
+
document.head.appendChild(js);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
Ext.define(${JSON.stringify(`${ns}.Instance`)}, {
|
|
137
|
+
extend: "SYNO.SDS.AppInstance",
|
|
138
|
+
appWindowName: ${JSON.stringify(`${ns}.MainWindow`)}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
Ext.define(${JSON.stringify(`${ns}.MainWindow`)}, {
|
|
142
|
+
extend: "SYNO.SDS.AppWindow",
|
|
143
|
+
constructor: function (config) {
|
|
144
|
+
this.callParent([Ext.apply({
|
|
145
|
+
width: 960, height: 640, minWidth: 640, minHeight: 420, layout: "fit",
|
|
146
|
+
${root ? root.html : `html: '<div class="dsmapp-root" style="height:100%"></div>'`}
|
|
147
|
+
}, config)]);
|
|
148
|
+
},
|
|
149
|
+
onOpen: function () {
|
|
150
|
+
this.callParent(arguments);
|
|
151
|
+
var me = this;
|
|
152
|
+
if (me.mounted) {
|
|
153
|
+
me.reopenHandlers.forEach(function (fn) { fn(); });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
me.reopenHandlers = [];
|
|
157
|
+
var lang = typeof _S === "function" ? _S("lang") : "enu";
|
|
158
|
+
${root ? root.mount : ""} ${ns}.loadApp(function (app) {
|
|
159
|
+
var root = me.body && me.body.dom.querySelector(".dsmapp-root");
|
|
160
|
+
if (app && root && !me.mounted) {
|
|
161
|
+
var i18n = ${ns}.createI18n(lang, ${ns}.messages);
|
|
162
|
+
root.lang = i18n.locale;
|
|
163
|
+
me.mounted = app.mount(root, {
|
|
164
|
+
call: ${ns}.call,
|
|
165
|
+
reopen: function (fn) { me.reopenHandlers.push(fn); },
|
|
166
|
+
appWindow: me,
|
|
167
|
+
lang: lang,
|
|
168
|
+
locale: i18n.locale,
|
|
169
|
+
t: i18n.t${root ? ",\n root: access" : ""}
|
|
170
|
+
}) || {};
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
onDestroy: function () {
|
|
175
|
+
if (this.mounted && this.mounted.unmount) {
|
|
176
|
+
this.mounted.unmount();
|
|
177
|
+
}
|
|
178
|
+
this.mounted = null;
|
|
179
|
+
this.callParent(arguments);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
const ROOT_PANEL_CSS = [
|
|
185
|
+
".dsmapp-rootpanel{margin:16px 20px;padding:12px 16px;border:1px solid #d5dce5;border-radius:4px;background:#f7f9fb;font-size:13px;line-height:1.5}",
|
|
186
|
+
".dsmapp-rootpanel h2{margin:0 0 6px;font-size:14px;font-weight:600}",
|
|
187
|
+
".dsmapp-rootpanel dl{display:grid;grid-template-columns:max-content 1fr;gap:2px 12px;margin:8px 0}",
|
|
188
|
+
".dsmapp-rootpanel dt{color:#526070}.dsmapp-rootpanel dd{margin:0}",
|
|
189
|
+
".dsmapp-rootpanel pre{margin:4px 0 8px;padding:6px 8px;background:#fff;border:1px solid #e1e6ec;white-space:pre-wrap;max-height:160px;overflow:auto}",
|
|
190
|
+
".dsmapp-rootpanel-actions button{margin-right:8px;padding:4px 14px}",
|
|
191
|
+
".dsmapp-rootpanel-primary{background:#057feb;color:#fff;border:1px solid #057feb;border-radius:3px}",
|
|
192
|
+
".dsmapp-rootpanel-message{color:#c0392b}.dsmapp-rootpanel-hint{color:#526070;font-size:12px}",
|
|
193
|
+
].join("");
|
|
194
|
+
/**
|
|
195
|
+
* The privilege: root additions to the window: the exact tasks and panel
|
|
196
|
+
* text, the access runtime wired to DSM's own password confirmation and the
|
|
197
|
+
* AppWindow's WebAPI channel, and the panel above the app.
|
|
198
|
+
*/
|
|
199
|
+
function renderRootParts(ns, manifest) {
|
|
200
|
+
return {
|
|
201
|
+
definitions: `${ns}.rootTasks = ${JSON.stringify(rootTasks(manifest))};
|
|
202
|
+
${ns}.rootMessages = ${JSON.stringify(ROOT_MESSAGES)};
|
|
203
|
+
${ns}.createRootAccess = (function () {
|
|
204
|
+
${ROOT_RUNTIME}
|
|
205
|
+
return createRootAccess;
|
|
206
|
+
})();
|
|
207
|
+
${ns}.mountRootPanel = (function () {
|
|
208
|
+
${ROOT_PANEL}
|
|
209
|
+
return mountRootPanel;
|
|
210
|
+
})();
|
|
211
|
+
|
|
212
|
+
`,
|
|
213
|
+
html: `autoScroll: true,
|
|
214
|
+
html: '<style>${ROOT_PANEL_CSS}</style><div class="dsmapp-rootpanels"></div><div class="dsmapp-root"></div>'`,
|
|
215
|
+
mount: ` var session = function () {
|
|
216
|
+
var current = SYNO.SDS.Session || {};
|
|
217
|
+
return { name: current.user, admin: current.is_admin === true };
|
|
218
|
+
};
|
|
219
|
+
var access = ${ns}.createRootAccess({
|
|
220
|
+
tasks: ${ns}.rootTasks,
|
|
221
|
+
user: session,
|
|
222
|
+
send: function (api, method, version, params) {
|
|
223
|
+
return new Promise(function (resolve, reject) {
|
|
224
|
+
me.sendWebAPI({
|
|
225
|
+
api: api, method: method, version: version, params: params, scope: me,
|
|
226
|
+
callback: function (success, response) {
|
|
227
|
+
if (success) resolve(response);
|
|
228
|
+
else reject({ code: "dsm_" + ((response && response.code) || "error") });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
confirmPassword: function () {
|
|
234
|
+
return new Promise(function (resolve) {
|
|
235
|
+
SYNO.SDS.Utils.PasswordConfirmDialog.openDialog(me, function (confirmation) {
|
|
236
|
+
resolve((confirmation && confirmation.SynoConfirmPWToken) || null);
|
|
237
|
+
}, [], function () { resolve(null); });
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
call: ${ns}.call,
|
|
241
|
+
wait: function (ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); }
|
|
242
|
+
});
|
|
243
|
+
var panels = me.body && me.body.dom.querySelector(".dsmapp-rootpanels");
|
|
244
|
+
if (panels) {
|
|
245
|
+
var rootI18n = ${ns}.createI18n(lang, ${ns}.rootMessages);
|
|
246
|
+
panels.lang = rootI18n.locale;
|
|
247
|
+
${ns}.mountRootPanel(panels, access, rootI18n, ${ns}.rootTasks, session().admin);
|
|
248
|
+
}
|
|
249
|
+
`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
|
|
2
|
+
import type { CommandRunner } from "./build.js";
|
|
3
|
+
import type { StandaloneError, StandaloneManifest } from "./manifest.js";
|
|
4
|
+
import { type RootOp } from "./root-ops.js";
|
|
5
|
+
export interface Integration {
|
|
6
|
+
protocol: number;
|
|
7
|
+
workers: Record<string, string>;
|
|
8
|
+
identity?: "better-auth";
|
|
9
|
+
}
|
|
10
|
+
export interface HelperInventory {
|
|
11
|
+
package: string;
|
|
12
|
+
version: string;
|
|
13
|
+
protocol: number;
|
|
14
|
+
payloadDigest: string;
|
|
15
|
+
registryDigest: string;
|
|
16
|
+
}
|
|
17
|
+
export interface WorkerPayload {
|
|
18
|
+
id: string;
|
|
19
|
+
digest: string;
|
|
20
|
+
bytes: Buffer;
|
|
21
|
+
}
|
|
22
|
+
export interface HelperArtifact {
|
|
23
|
+
binary: string;
|
|
24
|
+
inventory: HelperInventory;
|
|
25
|
+
workers: WorkerPayload[];
|
|
26
|
+
ops: RootOp[];
|
|
27
|
+
}
|
|
28
|
+
export declare function readIntegration(raw: unknown, root: string, fail: (e: StandaloneError) => void): Integration | undefined;
|
|
29
|
+
export declare function assertHelperTaskBudget(script: string, metadata?: string): void;
|
|
30
|
+
export declare function helperOps(m: StandaloneManifest, inventory: HelperInventory): RootOp[];
|
|
31
|
+
export declare function buildHelper(m: StandaloneManifest, arch: NativeArchFamily, work: string, run: CommandRunner): HelperArtifact | undefined;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { renderRootTaskScript } from "./root-ops.js";
|
|
5
|
+
import { StandaloneBuildError } from "./spk.js";
|
|
6
|
+
const sha256 = (b) => createHash("sha256").update(b).digest("hex");
|
|
7
|
+
const record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
8
|
+
export function readIntegration(raw, root, fail) {
|
|
9
|
+
if (raw === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
const invalid = () => { fail({ code: "STANDALONE_INTEGRATION_INVALID", message: "integration 需要 protocol: 1 與 workers: {固定 ID: 專案內檔案};最多 16 個,僅作 inventory,不執行 worker。" }); return undefined; };
|
|
12
|
+
if (!record(raw) || Object.keys(raw).some(k => !["protocol", "workers", "identity"].includes(k)) || raw.protocol !== 1 || !record(raw.workers) || Object.keys(raw.workers).length > 16)
|
|
13
|
+
return invalid();
|
|
14
|
+
if (raw.identity !== undefined && raw.identity !== "better-auth")
|
|
15
|
+
return invalid();
|
|
16
|
+
const workers = {};
|
|
17
|
+
for (const [id, file] of Object.entries(raw.workers)) {
|
|
18
|
+
if (!/^[a-z][a-z0-9_]{0,31}$/.test(id) || typeof file !== "string" || !file || isAbsolute(file) || file.split(/[\\/]/).includes(".."))
|
|
19
|
+
return invalid();
|
|
20
|
+
const path = resolve(root, file), rel = relative(root, path);
|
|
21
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
22
|
+
return invalid();
|
|
23
|
+
workers[id] = path;
|
|
24
|
+
}
|
|
25
|
+
return { protocol: 1, workers, ...(raw.identity === "better-auth" ? { identity: "better-auth" } : {}) };
|
|
26
|
+
}
|
|
27
|
+
function snapshotWorkers(m) {
|
|
28
|
+
let total = 0;
|
|
29
|
+
return Object.entries(m.integration.workers).sort(([a], [b]) => a.localeCompare(b, "en")).map(([id, path]) => {
|
|
30
|
+
const rel = relative(realpathSync(m.root), realpathSync(path));
|
|
31
|
+
if (rel.startsWith("..") || isAbsolute(rel) || !lstatSync(path).isFile())
|
|
32
|
+
throw new StandaloneBuildError("HELPER_WORKER_UNSAFE", `${id} 必須是專案內的一般檔案,不接受 symlink。`);
|
|
33
|
+
const bytes = readFileSync(path);
|
|
34
|
+
total += bytes.length;
|
|
35
|
+
if (total > 1024 * 1024)
|
|
36
|
+
throw new StandaloneBuildError("HELPER_WORKERS_TOO_LARGE", "worker inventory payload 合計不可超過 1 MiB。");
|
|
37
|
+
return { id, digest: sha256(bytes), bytes };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export function assertHelperTaskBudget(script, metadata = JSON.stringify({ script })) {
|
|
41
|
+
if (Buffer.byteLength(script) > 4095)
|
|
42
|
+
throw new StandaloneBuildError("HELPER_TASK_TOO_LARGE", "helper task 超過 DSM 文件的 4095 bytes 保守限制。");
|
|
43
|
+
if (Buffer.byteLength(metadata) > 8191)
|
|
44
|
+
throw new StandaloneBuildError("HELPER_METADATA_TOO_LARGE", "helper scheduler metadata 超過 8191 bytes 有界限制。");
|
|
45
|
+
}
|
|
46
|
+
export function helperOps(m, inventory) {
|
|
47
|
+
return ["enable", "recover", "teardown", "disable"].map(action => {
|
|
48
|
+
const op = { name: `helper_${action}`, protectedOutput: true, body: `#!/bin/sh
|
|
49
|
+
set -eu
|
|
50
|
+
umask 077
|
|
51
|
+
[ "$(id -u)" = 0 ]
|
|
52
|
+
# Verify the installed version as well as the window's exact-script check.
|
|
53
|
+
info=/var/packages/${m.package}/INFO
|
|
54
|
+
[ ! -L "$info" ] && [ "$(stat -c %u "$info")" = 0 ]
|
|
55
|
+
grep -Fqx 'version="${m.spkVersion}"' "$info"
|
|
56
|
+
for p in /var/lib /run; do
|
|
57
|
+
[ ! -L "$p" ] && [ -d "$p" ] && [ "$(stat -c %u "$p")" = 0 ]
|
|
58
|
+
mode=$(stat -c %a "$p")
|
|
59
|
+
[ "$((0$mode & 0022))" = 0 ]
|
|
60
|
+
done
|
|
61
|
+
s=/var/lib/synopkgland-${m.package}
|
|
62
|
+
r=/run/synopkgland-helper-${m.package}
|
|
63
|
+
for p in "$s" "$r"; do
|
|
64
|
+
[ ! -L "$p" ]
|
|
65
|
+
if [ ! -e "$p" ]; then mkdir -m 0755 "$p"; fi
|
|
66
|
+
[ -d "$p" ] && [ "$(stat -c %u:%a "$p")" = 0:755 ]
|
|
67
|
+
done
|
|
68
|
+
# Copy into a root-owned private file FIRST, then verify the copied bytes.
|
|
69
|
+
# The package may change its source during copy; no unverified byte executes.
|
|
70
|
+
tmp=$(mktemp "$s/.payload.XXXXXX")
|
|
71
|
+
trap 'rm -f "$tmp"' EXIT HUP INT TERM
|
|
72
|
+
dd if=/var/packages/${m.package}/target/integration/helper of="$tmp" bs=65536 count=1025 2>/dev/null
|
|
73
|
+
[ "$(stat -c %s "$tmp")" -le 67108864 ]
|
|
74
|
+
chmod 0700 "$tmp"
|
|
75
|
+
[ "$(sha256sum "$tmp" | cut -d ' ' -f 1)" = '${inventory.payloadDigest}' ]
|
|
76
|
+
exe="$s/payload-${inventory.payloadDigest}"
|
|
77
|
+
# Rename only inside the protected directory. Existing executable mappings
|
|
78
|
+
# retain their inode and digest until the controller has verified teardown.
|
|
79
|
+
mv -f "$tmp" "$exe"
|
|
80
|
+
trap - EXIT HUP INT TERM
|
|
81
|
+
exec "$exe" ${action}
|
|
82
|
+
` };
|
|
83
|
+
const script = renderRootTaskScript(m, op);
|
|
84
|
+
assertHelperTaskBudget(script, JSON.stringify({ name: `${m.package} root: ${op.name}`, script, owner: "root", enable: false }));
|
|
85
|
+
return op;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export function buildHelper(m, arch, work, run) {
|
|
89
|
+
if (!m.integration)
|
|
90
|
+
return undefined;
|
|
91
|
+
if (m.backend.kind !== "go")
|
|
92
|
+
throw new StandaloneBuildError("HELPER_GO_REQUIRED", "M1a integration 僅支援 native-go。");
|
|
93
|
+
const workers = snapshotWorkers(m);
|
|
94
|
+
const registry = workers.map(({ id, digest }) => ({ id, digest }));
|
|
95
|
+
const base = { package: m.package, version: m.spkVersion, protocol: m.integration.protocol, payloadDigest: "", registryDigest: sha256(JSON.stringify(registry)) };
|
|
96
|
+
const binary = join(work, `helper-${arch}`);
|
|
97
|
+
const result = run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", `-s -w -buildid= -X 'main.helperInventory=${JSON.stringify(base)}'`, "-o", binary, "./cmd/helper"], {
|
|
98
|
+
cwd: m.backend.dir, env: { ...process.env, CGO_ENABLED: "0", GOOS: "linux", GOARCH: arch === "x86_64" ? "amd64" : "arm64" },
|
|
99
|
+
});
|
|
100
|
+
if (result.missing || result.status !== 0)
|
|
101
|
+
throw new StandaloneBuildError("HELPER_BUILD_FAILED", `Go helper build 失敗:${result.stderr}`);
|
|
102
|
+
const inventory = { ...base, payloadDigest: sha256(readFileSync(binary)) };
|
|
103
|
+
return { binary, inventory, workers, ops: helperOps(m, inventory) };
|
|
104
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { StandaloneError } from "./manifest.js";
|
|
2
|
+
/**
|
|
3
|
+
* UI languages a Standalone Package can ship, as BCP-47 tags (the catalog
|
|
4
|
+
* file names and the `Intl` locale), with the DSM language code each one
|
|
5
|
+
* answers to. Every other DSM language opens the app in English.
|
|
6
|
+
*/
|
|
7
|
+
export declare const LOCALES: readonly ["en", "zh-TW", "zh-CN", "ja", "ko", "de", "fr", "es", "it"];
|
|
8
|
+
export type Locale = (typeof LOCALES)[number];
|
|
9
|
+
export declare const DSM_LANGS: Record<Locale, string>;
|
|
10
|
+
export type Catalog = Record<string, string>;
|
|
11
|
+
export type Catalogs = {
|
|
12
|
+
en: Catalog;
|
|
13
|
+
} & Partial<Record<Locale, Catalog>>;
|
|
14
|
+
export type CatalogsResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
catalogs: Catalogs;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
errors: StandaloneError[];
|
|
20
|
+
};
|
|
21
|
+
export declare function isLocale(value: string): value is Locale;
|
|
22
|
+
/** `<dir>/<locale>.json` for each shipped locale; en.json is required. */
|
|
23
|
+
export declare function loadCatalogs(dir: string): CatalogsResult;
|
|
24
|
+
/**
|
|
25
|
+
* Each glossary term lists the message keys that must use it: in every
|
|
26
|
+
* shipped locale the message has to contain that locale's official term.
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkGlossary(file: string, catalogs: Catalogs): StandaloneError[];
|