@synopackageland/cli 0.10.9 → 0.10.10
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 +22 -10
- package/README.md +5 -4
- package/dist/build-spk/gateway.js +2 -1
- package/dist/error-registry.js +2 -2
- package/dist/init.js +2 -1
- package/dist/runtime/executor-build.js +3 -2
- package/dist/runtime/executor-digests.json +11 -0
- package/dist/runtime/executor-inspect.js +1 -1
- package/dist/runtime/executor-policy-check.js +2 -1
- package/dist/runtime/go-env.d.ts +4 -0
- package/dist/runtime/go-env.js +57 -0
- package/dist/runtime/sdk-session.d.ts +2 -0
- package/dist/runtime/sdk-session.js +23 -0
- package/dist/runtime/sdk-source.d.ts +3 -7
- package/dist/runtime/sdk-source.js +7 -31
- package/dist/standalone/bridge-runtime.js +2 -1
- package/dist/standalone/build.js +3 -2
- package/dist/standalone/daemon.js +2 -1
- package/dist/standalone/dsm-window.js +33 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,28 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.10 (2026-10-15)
|
|
4
|
+
|
|
5
|
+
- Uses SDK 0.10.10, templates 0.10.11 and UI 0.9.9. `init` pins these versions for the generated project, and the executor digest table pins SDK 0.10.10 for x86_64, aarch64 and armv7.
|
|
6
|
+
- The shared DSM window wrapper waits for the SDK session helper before backend and DSM WebAPI calls. A portal window without a desktop SynoToken obtains the current token through a shared same-origin request; cookie, token, principal and original client-address validation remain enforced.
|
|
7
|
+
- Every Go child process merges `github.com/synopackageland/*` into its effective `GOPRIVATE`, `GONOPROXY` and `GONOSUMDB`, preserving environment and `go env -w` settings. Git credentials are needed to fetch the private SDK. Direct `go get` commands supply their own GOPRIVATE setting.
|
|
8
|
+
- SDK approval components explain the requested system privileges with a single consent card, expandable details and inline password errors. `ExecutorApproval`, `ExecutorNotice` and `ExecutorSettings` take `purpose`; background-save settings use one save-folder row per destination key. See the SDK CHANGELOG for translation-key and slot changes.
|
|
9
|
+
- App pins: `@synopackageland/cli` 0.10.10 with its lockfile; SDK 0.10.10 for Go, npm, Cargo and `backend.sdk`; UI 0.9.9. Rebuild the App. An unchanged policy and a matching root-owned INFO anchor preserve an existing approval; policy or capability changes require administrator approval.
|
|
10
|
+
|
|
11
|
+
繁中摘要:使用 SDK 0.10.10、templates 0.10.11、UI 0.9.9 與三架構新 executor digest。portal 缺少 token 時,視窗 wrapper 先等待共用 SDK helper 取得 token,再呼叫後端與 DSM WebAPI,維持驗證。所有 Go 子行程自動合併 private namespace 並保留既有設定。核准畫面使用 consent 卡片,元件可傳入 `purpose`;背景存檔設定每個 key 只有一列存放資料夾。App 更新 exact pin 與 lock 後重建。
|
|
12
|
+
|
|
3
13
|
## 0.10.9 (2026-10-15)
|
|
4
14
|
|
|
5
15
|
Builds with SDK 0.10.9 and templates 0.10.10: `init` writes SDK v0.10.9,
|
|
6
16
|
`@synopackageland/sdk` 0.10.9 and `@synopackageland/ui` 0.9.8, and the root-ops
|
|
7
17
|
template's `ExecutorGate.vue` passes `preload`. The executor digest table adds
|
|
8
18
|
SDK 0.10.8 and 0.10.9 (equal to each other; the executor changed in 0.10.8).
|
|
9
|
-
|
|
10
|
-
|
|
19
|
+
An executor upgrade keeps the existing approval when the policy is unchanged
|
|
20
|
+
and the new executor matches its root-owned INFO anchor. A policy or capability
|
|
21
|
+
change requires an administrator's approval again.
|
|
11
22
|
The CLI still needs SDK 0.10.5 or later: an executor without `tasks` (before
|
|
12
|
-
0.10.8)
|
|
13
|
-
0.10.
|
|
14
|
-
executor setup.
|
|
23
|
+
0.10.8) receives the window's full readback. Without an executor setup,
|
|
24
|
+
SDK 0.10.9's `ExecutorApproval` shows the pages alone, with or without `preload`.
|
|
15
25
|
|
|
16
26
|
To upgrade an App: devDependency `@synopackageland/cli` 0.10.9 (exact) with
|
|
17
27
|
its lockfile; an App that depends on the SDK directly moves to 0.10.9 (Go
|
|
18
28
|
`require github.com/synopackageland/syno-pkg-land-sdk v0.10.9` with go.sum,
|
|
19
29
|
`@synopackageland/sdk` and `@synopackageland/app` 0.10.9, Cargo tag
|
|
20
|
-
`v0.10.9`, `backend.sdk: 0.10.9`); then rebuild
|
|
30
|
+
`v0.10.9`, `backend.sdk: 0.10.9`); then rebuild and upgrade. An unchanged
|
|
31
|
+
policy and matching root-owned INFO anchor preserve the existing approval.
|
|
21
32
|
|
|
22
33
|
繁中摘要:搭配 SDK 0.10.9、templates 0.10.10(UI 0.9.8)。digest 表新增 SDK 0.10.8/0.10.9
|
|
23
|
-
(兩者相同,executor 在 0.10.8
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
(兩者相同,executor 在 0.10.8 改變)。policy 不變且新 executor 符合 root-owned INFO anchor 時,
|
|
35
|
+
升級沿用原核准;policy/能力改變才需管理員重新核准。SDK 下限維持 0.10.5(沒有 `tasks` 的舊 executor 完整核對)。沒有 executor setup 時,SDK 0.10.9 只顯示頁面。
|
|
36
|
+
App 要改:`@synopackageland/cli` 0.10.9+lock;直接依賴 SDK 的 App 升到 0.10.9。
|
|
26
37
|
|
|
27
38
|
- The DSM window shows an administrator the App without reading Task
|
|
28
39
|
Scheduler and EventScheduler when the pinned, running executor's status
|
|
@@ -42,7 +53,8 @@ its lockfile; an App that depends on the SDK directly moves to 0.10.9 (Go
|
|
|
42
53
|
Builds with SDK 0.10.7 and templates 0.10.8, as 0.10.7 did: `init` still
|
|
43
54
|
writes SDK v0.10.7, `@synopackageland/sdk` 0.10.7 and `@synopackageland/ui`
|
|
44
55
|
0.9.8, and the executor digest table is unchanged, so an App rebuilt on it
|
|
45
|
-
keeps its approval
|
|
56
|
+
keeps its approval when the policy is unchanged and the executor matches its
|
|
57
|
+
root-owned INFO anchor. A policy or capability change requires approval again. Only the DSM
|
|
46
58
|
window's `ctx.call` changes. The CLI still needs SDK 0.10.5 or later: the
|
|
47
59
|
SDK's Gate, `useLifecycleRetry`, `appLifecycle` and `ExecutorApproval` already
|
|
48
60
|
read an `APP_STOPPED` that arrives at once and check again; SDK 0.10.7 changed
|
package/README.md
CHANGED
|
@@ -55,10 +55,11 @@ The CLI packages, deploys and checks. The app-side code is the SDK
|
|
|
55
55
|
and checks it against the digests shipped in this CLI. A CLI builds the SDK releases of its own minor version
|
|
56
56
|
(CLI X.Y.z builds SDK X.Y.*) that its digest table records; `EXECUTOR_DIGEST_MISMATCH` names the ones it knows.
|
|
57
57
|
|
|
58
|
-
The SDK repositories are private
|
|
59
|
-
`
|
|
60
|
-
`
|
|
61
|
-
|
|
58
|
+
The SDK repositories are private. The CLI adds `github.com/synopackageland/*` to its Go
|
|
59
|
+
children's `GOPRIVATE`, `GONOPROXY` and `GONOSUMDB`, preserving existing environment and
|
|
60
|
+
`go env -w` settings. Give git a credential that can read
|
|
61
|
+
`https://github.com/synopackageland/syno-pkg-land-sdk` (check with `git ls-remote`);
|
|
62
|
+
a failed fetch reports `SDK_DOWNLOAD_FAILED`.
|
|
62
63
|
|
|
63
64
|
`init` and `add` read their templates from `@synopackageland/templates`, an exact-pinned
|
|
64
65
|
dependency. For development against local checkouts, set `SYNOPKGLAND_TEMPLATES_DIR` to a
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { goChildEnv } from "../runtime/go-env.js";
|
|
1
2
|
import { chmodSync, copyFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { checkCgiBinary } from "../standalone/binary.js";
|
|
@@ -29,7 +30,7 @@ export async function buildGatewayBinary(input) {
|
|
|
29
30
|
const ldflags = ["-s", "-w", "-buildid=", `-X main.packageID=${input.packageId}`, `-X 'main.configJSON=${JSON.stringify(input.config)}'`].join(" ");
|
|
30
31
|
const result = await (input.run ?? runCommand)("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, "-o", out, "."], {
|
|
31
32
|
cwd: goAssetDir("gateway"),
|
|
32
|
-
env: { ...process.env, ...pinnedGoBuildEnv(input.arch) },
|
|
33
|
+
env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(input.arch) }),
|
|
33
34
|
});
|
|
34
35
|
if (result.missing) {
|
|
35
36
|
return { error: "GATEWAY_GO_MISSING", detail: `The go command was not found. Building the DSM verification gateway needs Go 1.21 or later (https://go.dev/dl/). ${PINNED_GO_HINT}` };
|
package/dist/error-registry.js
CHANGED
|
@@ -657,7 +657,7 @@ export const ERROR_REGISTRY = {
|
|
|
657
657
|
"STANDALONE_GO_BUILD_FAILED": {
|
|
658
658
|
"code": "STANDALONE_GO_BUILD_FAILED",
|
|
659
659
|
"description": "go build failed.",
|
|
660
|
-
"suggestion": "Run go build ./... in the backend directory and fix the errors. If github.com/synopackageland/syno-pkg-land-sdk is unreachable,
|
|
660
|
+
"suggestion": "Run go build ./... in the backend directory and fix the errors. If github.com/synopackageland/syno-pkg-land-sdk is unreachable, make sure git can read github.com with credentials (verify with git ls-remote)."
|
|
661
661
|
},
|
|
662
662
|
"STANDALONE_PORTAL_INVALID": {
|
|
663
663
|
"code": "STANDALONE_PORTAL_INVALID",
|
|
@@ -788,7 +788,7 @@ for (const [code, description, suggestion] of [
|
|
|
788
788
|
["SDK_VERSION_MISMATCH", "The @synopackageland/sdk in frontend/package.json differs from the SDK version the backend requires (backend/go.mod or backend/Cargo.lock).", "Make both sides the same version; for a file: dependency the comparison uses that directory's package.json version."],
|
|
789
789
|
["SDK_UNSUPPORTED", "The SDK the backend requires (backend/go.mod or backend/Cargo.lock) is not on this CLI's minor line.", "Use a CLI on the same minor as the SDK, or raise the SDK to a version line the CLI supports (listed in the message)."],
|
|
790
790
|
["SDK_DIR_NOT_READY", "The SDK checkout SYNOPKGLAND_SDK_DIR points at has no go.mod, or the ts package a Vue project needs is not built (no ts/dist).", "Point at a syno-pkg-land-sdk checkout; for a Vue project first run npm ci && npm run build in $SYNOPKGLAND_SDK_DIR/ts (npm does not build file: packages for you)."],
|
|
791
|
-
["SDK_DOWNLOAD_FAILED", "go mod download cannot fetch the SDK version backend/go.mod requires.", "The
|
|
791
|
+
["SDK_DOWNLOAD_FAILED", "go mod download cannot fetch the SDK version backend/go.mod requires.", "The CLI fetches the private SDK with git: make sure git can read github.com with credentials (verify with git ls-remote), then confirm the version exists. Offline, run go mod download once on the same machine first."],
|
|
792
792
|
["TEMPLATES_MISSING", "@synopackageland/templates was not found.", "Reinstall @synopackageland/cli, or point SYNOPKGLAND_TEMPLATES_DIR at a templates checkout."],
|
|
793
793
|
["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."],
|
|
794
794
|
["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."],
|
package/dist/init.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { goChildEnv } from "./runtime/go-env.js";
|
|
1
2
|
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { isMap, isScalar, parse, parseDocument } from "yaml";
|
|
3
4
|
import { setTopLevel } from "./yaml-edit.js";
|
|
@@ -205,7 +206,7 @@ export function initFromTemplate(template, targetDir, options = {}) {
|
|
|
205
206
|
if (template === "binary") {
|
|
206
207
|
mkdirSync(join(targetDir, "prebuilt"), { recursive: true });
|
|
207
208
|
for (const [arch, goarch] of [["x86_64", "amd64"], ["aarch64", "arm64"]]) {
|
|
208
|
-
const result = spawnSync("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", "-s -w -buildid=", "-o", join(targetDir, "prebuilt", `api-${arch}`), "main.go"], { cwd: join(targetDir, "example"), env: { ...process.env, CGO_ENABLED: "0", GOOS: "linux", GOARCH: goarch }, encoding: "utf8" });
|
|
209
|
+
const result = spawnSync("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", "-s -w -buildid=", "-o", join(targetDir, "prebuilt", `api-${arch}`), "main.go"], { cwd: join(targetDir, "example"), env: goChildEnv({ ...process.env, CGO_ENABLED: "0", GOOS: "linux", GOARCH: goarch }), encoding: "utf8" });
|
|
209
210
|
if (result.error || result.status !== 0)
|
|
210
211
|
throw new Error(`[INIT_BINARY_BUILD_FAILED] Install Go and run init again, or provide your own static CGI binary. ${result.stderr ?? result.error}`);
|
|
211
212
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { goChildEnv } from "./go-env.js";
|
|
1
2
|
import { mkdirSync, readFileSync } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem } from '../build-spk/go-toolchain.js';
|
|
@@ -41,14 +42,14 @@ export async function buildExecutor(m, arch, work, run, options = {}) {
|
|
|
41
42
|
throw new StandaloneBuildError('EXECUTOR_BUILD_FAILED', `Executor sources of SDK ${sdk.version} are missing: ${error.message}`);
|
|
42
43
|
}
|
|
43
44
|
const binary = join(dir, 'executor');
|
|
44
|
-
const result = await run('go', [...GO_BUILD, binary, '.'], { cwd: source, env: { ...process.env, ...pinnedGoBuildEnv(arch) } });
|
|
45
|
+
const result = await run('go', [...GO_BUILD, binary, '.'], { cwd: source, env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }) });
|
|
45
46
|
if (result.missing || result.status !== 0)
|
|
46
47
|
throw new StandaloneBuildError('EXECUTOR_BUILD_FAILED', `Executor build failed (${arch}). ${PINNED_GO_HINT}\n${result.stderr}`);
|
|
47
48
|
const problem = checkCgiBinary(work, binary, arch);
|
|
48
49
|
if (problem)
|
|
49
50
|
throw new StandaloneBuildError(problem.code, problem.message);
|
|
50
51
|
const lifecycleBinary = join(dir, 'lifecycle');
|
|
51
|
-
const lifecycleResult = await run('go', [...GO_BUILD, lifecycleBinary, './cmd/lifecycle'], { cwd: source, env: { ...process.env, ...pinnedGoBuildEnv(arch) } });
|
|
52
|
+
const lifecycleResult = await run('go', [...GO_BUILD, lifecycleBinary, './cmd/lifecycle'], { cwd: source, env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }) });
|
|
52
53
|
if (lifecycleResult.missing || lifecycleResult.status !== 0)
|
|
53
54
|
throw new StandaloneBuildError('EXECUTOR_BUILD_FAILED', `Lifecycle helper build failed (${arch}). ${PINNED_GO_HINT}\n${lifecycleResult.stderr}`);
|
|
54
55
|
const lifecycleProblem = checkCgiBinary(work, lifecycleBinary, arch);
|
|
@@ -108,5 +108,16 @@
|
|
|
108
108
|
"armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
|
|
109
109
|
},
|
|
110
110
|
"goToolchain": "go1.26.9"
|
|
111
|
+
},
|
|
112
|
+
"0.10.10": {
|
|
113
|
+
"x86_64": "9c2c6f92edd2536b3061f8b9eef8b500a1f064909deb4c1b44b496465879b90d",
|
|
114
|
+
"aarch64": "175731dafb52b83f771878cd41407cae6353a90628649ec78118d6a1e1ffb9fb",
|
|
115
|
+
"armv7": "27402fa2aa47732a280434cefaa876b0cfe8202a6dc103fd9dd1984e9dc69c81",
|
|
116
|
+
"lifecycle": {
|
|
117
|
+
"x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
|
|
118
|
+
"aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
|
|
119
|
+
"armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
|
|
120
|
+
},
|
|
121
|
+
"goToolchain": "go1.26.9"
|
|
111
122
|
}
|
|
112
123
|
}
|
|
@@ -43,7 +43,7 @@ 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
|
|
46
|
+
// A failed SDK download is this machine's git access, not a defect of the SPK.
|
|
47
47
|
if (e.message.startsWith('[SDK_DOWNLOAD_FAILED]'))
|
|
48
48
|
throw e;
|
|
49
49
|
return { findings: ['Executor inventory invalid: ' + e.message] };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { goChildEnv } from "./go-env.js";
|
|
1
2
|
import { spawnSync } from 'node:child_process';
|
|
2
3
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
4
|
import { tmpdir } from 'node:os';
|
|
@@ -18,7 +19,7 @@ export function executorPolicyRefusal(executorDir, pkg, policyText, env = proces
|
|
|
18
19
|
try {
|
|
19
20
|
const binary = join(dir, 'executor');
|
|
20
21
|
const { GOOS: _os, GOARCH: _arch, ...host } = env;
|
|
21
|
-
const build = spawnSync('go', ['build', '-o', binary, '.'], { cwd: executorDir, env: { ...host, GOTOOLCHAIN: PINNED_GO_TOOLCHAIN, GOFLAGS: '', GOWORK: 'off', CGO_ENABLED: '0' }, encoding: 'utf8' });
|
|
22
|
+
const build = spawnSync('go', ['build', '-o', binary, '.'], { cwd: executorDir, env: goChildEnv({ ...host, GOTOOLCHAIN: PINNED_GO_TOOLCHAIN, GOFLAGS: '', GOWORK: 'off', CGO_ENABLED: '0' }), encoding: 'utf8' });
|
|
22
23
|
if (build.error || build.status !== 0)
|
|
23
24
|
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}`);
|
|
24
25
|
const result = spawnSync(binary, [CHECK_POLICY, '--package', pkg], { input: policyText, encoding: 'utf8' });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Preserve effective Go settings and add the SDK namespace only for this child. */
|
|
2
|
+
export declare function goChildEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
3
|
+
/** Whether a comma-separated GOPRIVATE-style glob list matches a module path or one of its prefixes, as go does. */
|
|
4
|
+
export declare function goPrivateCovers(patterns: string, modulePath: string): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
const SDK_PRIVATE = 'github.com/synopackageland/*';
|
|
4
|
+
const PRIVATE_KEYS = ['GOPRIVATE', 'GONOPROXY', 'GONOSUMDB'];
|
|
5
|
+
/** Preserve effective Go settings and add the SDK namespace only for this child. */
|
|
6
|
+
export function goChildEnv(env = process.env) {
|
|
7
|
+
const configured = readGoEnv(env);
|
|
8
|
+
const child = { ...env };
|
|
9
|
+
const privatePatterns = env.GOPRIVATE || configured.GOPRIVATE || '';
|
|
10
|
+
for (const key of PRIVATE_KEYS) {
|
|
11
|
+
const patterns = env[key] || configured[key] || privatePatterns;
|
|
12
|
+
child[key] = coversSdkNamespace(patterns) ? patterns : [patterns, SDK_PRIVATE].filter(Boolean).join(',');
|
|
13
|
+
}
|
|
14
|
+
return child;
|
|
15
|
+
}
|
|
16
|
+
/** A match for one module (such as `?`) does not cover every SDK module. */
|
|
17
|
+
function coversSdkNamespace(patterns) {
|
|
18
|
+
return patterns.split(',').some(pattern => {
|
|
19
|
+
const glob = pattern.replace(/\/+$/, '');
|
|
20
|
+
const parts = glob.split('/');
|
|
21
|
+
if (parts.length <= 2)
|
|
22
|
+
return goPrivateCovers(glob, 'github.com/synopackageland');
|
|
23
|
+
return parts.length === 3 && /^\*+$/.test(parts[2]) && goPrivateCovers(glob, SDK_PRIVATE);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Bootstrap read: use the original environment to preserve go env -w settings. */
|
|
27
|
+
function readGoEnv(env) {
|
|
28
|
+
const result = spawnSync('go', ['env', '-json', ...PRIVATE_KEYS], {
|
|
29
|
+
cwd: tmpdir(), env: { ...env, GOFLAGS: '', GOTOOLCHAIN: 'local' }, encoding: 'utf8', timeout: 10_000,
|
|
30
|
+
});
|
|
31
|
+
if (result.status !== 0)
|
|
32
|
+
return {};
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(result.stdout);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Whether a comma-separated GOPRIVATE-style glob list matches a module path or one of its prefixes, as go does. */
|
|
41
|
+
export function goPrivateCovers(patterns, modulePath) {
|
|
42
|
+
const elements = modulePath.split('/');
|
|
43
|
+
return patterns.split(',').map(glob => glob.replace(/\/+$/, '')).filter(Boolean).some(glob => {
|
|
44
|
+
const prefix = elements.slice(0, glob.split('/').length).join('/');
|
|
45
|
+
try {
|
|
46
|
+
return globRegExp(glob).test(prefix);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** path.Match syntax: `*` and `?` never cross a slash. */
|
|
54
|
+
function globRegExp(glob) {
|
|
55
|
+
const body = glob.replace(/[.+^${}()|\\]/g, '\\$&').replaceAll('*', '[^/]*').replaceAll('?', '[^/]');
|
|
56
|
+
return new RegExp(`^${body}$`);
|
|
57
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { appSdkVersion, sdkBelowFloor, sdkSource } from './sdk-source.js';
|
|
4
|
+
/** The SDK's plain browser factory, embedded without imposing a TS build on Go Apps. */
|
|
5
|
+
export function appSdkSessionRuntime(backend, env = process.env) {
|
|
6
|
+
if (backend.kind === 'binary')
|
|
7
|
+
return '';
|
|
8
|
+
const version = appSdkVersion(backend);
|
|
9
|
+
if (!version || sdkBelowFloor(version))
|
|
10
|
+
return '';
|
|
11
|
+
const sdk = sdkSource(version, env);
|
|
12
|
+
let source;
|
|
13
|
+
try {
|
|
14
|
+
source = readFileSync(join(sdk.goDir, 'ts/src/dsm/session.js'), 'utf8');
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
// Older supported SDKs retain their desktop-only carrier behavior.
|
|
18
|
+
if (error.code === 'ENOENT')
|
|
19
|
+
return '';
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
return source.replace(/^export function createDsmSession\(/m, 'function createDsmSession(');
|
|
23
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { goPrivateCovers } from './go-env.js';
|
|
1
2
|
export declare const SDK_PACKAGE = "@synopackageland/sdk";
|
|
2
3
|
export declare const SDK_MODULE = "github.com/synopackageland/syno-pkg-land-sdk";
|
|
3
4
|
export declare const EXECUTOR_MODULE = "github.com/synopackageland/syno-pkg-land-sdk/executor";
|
|
@@ -43,10 +44,5 @@ export declare function sdkSource(version: string, env?: NodeJS.ProcessEnv, down
|
|
|
43
44
|
export declare function goModDownload(spec: string, env?: NodeJS.ProcessEnv): string;
|
|
44
45
|
/** The SDK an App builds its executor with; delegate and rootOps cannot build without one. */
|
|
45
46
|
export declare function requiredSdk(backend: SdkBackend, env?: NodeJS.ProcessEnv, download?: (spec: string) => string): SdkSource;
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
* proxy.golang.org and sum.golang.org. Says which of the two is missing.
|
|
49
|
-
*/
|
|
50
|
-
export declare function privateSdkHint(env?: NodeJS.ProcessEnv): string;
|
|
51
|
-
/** Whether a comma-separated GOPRIVATE-style glob list matches a module path or one of its prefixes, as go does. */
|
|
52
|
-
export declare function goPrivateCovers(patterns: string, modulePath: string): boolean;
|
|
47
|
+
/** Go children fetch the private SDK directly; failed fetches need git credentials. */
|
|
48
|
+
export declare function privateSdkHint(): string;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { goChildEnv } from './go-env.js';
|
|
3
|
+
export { goPrivateCovers } from './go-env.js';
|
|
2
4
|
import { readFileSync } from 'node:fs';
|
|
3
5
|
import { tmpdir } from 'node:os';
|
|
4
6
|
import { join } from 'node:path';
|
|
@@ -91,14 +93,14 @@ export function goModDownload(spec, env = process.env) {
|
|
|
91
93
|
const cached = downloaded.get(key);
|
|
92
94
|
if (cached)
|
|
93
95
|
return cached;
|
|
94
|
-
const result = spawnSync('go', ['mod', 'download', '-json', spec], { cwd: tmpdir(), env: { ...env, GOWORK: 'off', GOFLAGS: '' }, encoding: 'utf8' });
|
|
96
|
+
const result = spawnSync('go', ['mod', 'download', '-json', spec], { cwd: tmpdir(), env: goChildEnv({ ...env, GOWORK: 'off', GOFLAGS: '' }), encoding: 'utf8' });
|
|
95
97
|
let info = {};
|
|
96
98
|
try {
|
|
97
99
|
info = JSON.parse(result.stdout);
|
|
98
100
|
}
|
|
99
101
|
catch { /* reported below */ }
|
|
100
102
|
if (result.error || result.status !== 0 || !info.Dir) {
|
|
101
|
-
throw new Error(`[SDK_DOWNLOAD_FAILED] go mod download ${spec} failed: ${info.Error ?? result.stderr ?? result.error?.message}${privateSdkHint(
|
|
103
|
+
throw new Error(`[SDK_DOWNLOAD_FAILED] go mod download ${spec} failed: ${info.Error ?? result.stderr ?? result.error?.message}${privateSdkHint()}`);
|
|
102
104
|
}
|
|
103
105
|
downloaded.set(key, info.Dir);
|
|
104
106
|
return info.Dir;
|
|
@@ -110,33 +112,7 @@ export function requiredSdk(backend, env = process.env, download) {
|
|
|
110
112
|
throw new Error(`[SDK_REQUIRED] capabilities.delegate and rootOps need the SDK: ${SDK_DEPENDENCY[backend.kind]}`);
|
|
111
113
|
return download ? sdkSource(version, env, download) : sdkSource(version, env);
|
|
112
114
|
}
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
*/
|
|
117
|
-
export function privateSdkHint(env = process.env) {
|
|
118
|
-
const result = spawnSync('go', ['env', '-json', 'GONOPROXY', 'GONOSUMDB'], { cwd: tmpdir(), env: { ...env, GOFLAGS: '' }, encoding: 'utf8' });
|
|
119
|
-
let goEnv = {};
|
|
120
|
-
try {
|
|
121
|
-
goEnv = JSON.parse(result.stdout);
|
|
122
|
-
}
|
|
123
|
-
catch { /* treated as not covered */ }
|
|
124
|
-
if (goPrivateCovers(goEnv.GONOPROXY ?? '', SDK_MODULE) && goPrivateCovers(goEnv.GONOSUMDB ?? '', SDK_MODULE)) {
|
|
125
|
-
return ` GOPRIVATE covers the SDK, so Go fetches it with git: check that \`git ls-remote https://${SDK_MODULE}\` works with your git credential.`;
|
|
126
|
-
}
|
|
127
|
-
return ' The SDK repositories are private, and Go looks up modules outside GOPRIVATE on proxy.golang.org and sum.golang.org.'
|
|
128
|
-
+ ' Set GOPRIVATE=github.com/synopackageland/* (for example `go env -w GOPRIVATE=github.com/synopackageland/*`) and give git a credential for github.com.';
|
|
129
|
-
}
|
|
130
|
-
/** Whether a comma-separated GOPRIVATE-style glob list matches a module path or one of its prefixes, as go does. */
|
|
131
|
-
export function goPrivateCovers(patterns, modulePath) {
|
|
132
|
-
const elements = modulePath.split('/');
|
|
133
|
-
return patterns.split(',').map(glob => glob.trim().replace(/\/+$/, '')).filter(Boolean).some(glob => {
|
|
134
|
-
const prefix = elements.slice(0, glob.split('/').length).join('/');
|
|
135
|
-
return globRegExp(glob).test(prefix);
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
/** path.Match syntax: `*` and `?` never cross a slash. */
|
|
139
|
-
function globRegExp(glob) {
|
|
140
|
-
const body = glob.replace(/[.+^${}()|\\]/g, '\\$&').replaceAll('*', '[^/]*').replaceAll('?', '[^/]');
|
|
141
|
-
return new RegExp(`^${body}$`);
|
|
115
|
+
/** Go children fetch the private SDK directly; failed fetches need git credentials. */
|
|
116
|
+
export function privateSdkHint() {
|
|
117
|
+
return ` Check that \`git ls-remote https://${SDK_MODULE}\` works with your git credential.`;
|
|
142
118
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { goChildEnv } from "../runtime/go-env.js";
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem } from "../build-spk/go-toolchain.js";
|
|
3
4
|
import { requiredSdk, SDK_MODULE } from "../runtime/sdk-source.js";
|
|
@@ -29,7 +30,7 @@ export async function buildBridgeRuntime(m, arch, work, run, digests) {
|
|
|
29
30
|
].join(" ");
|
|
30
31
|
const result = await run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, "-o", out, RUNTIME_MAIN], {
|
|
31
32
|
cwd: sdk.goDir,
|
|
32
|
-
env: { ...process.env, ...pinnedGoBuildEnv(arch) },
|
|
33
|
+
env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }),
|
|
33
34
|
});
|
|
34
35
|
if (result.missing)
|
|
35
36
|
throw new StandaloneBuildError("STANDALONE_GO_MISSING", "The go command was not found. The Python, Node and Rust App runtime is compiled from the SDK's Go source; install Go 1.21 or later (https://go.dev/dl/).");
|
package/dist/standalone/build.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { goChildEnv } from "../runtime/go-env.js";
|
|
1
2
|
import { spawn } from "node:child_process";
|
|
2
3
|
import { goModulePath, privateSdkHint, SDK_MODULE, sdkSource } from "../runtime/sdk-source.js";
|
|
3
4
|
import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
@@ -175,7 +176,7 @@ async function buildGoCgi(manifest, backend, arch, work, run) {
|
|
|
175
176
|
].join(" ");
|
|
176
177
|
const result = await run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, "-o", out, backend.main], {
|
|
177
178
|
cwd: backend.dir,
|
|
178
|
-
env: { ...process.env, ...goBuildEnv(arch) },
|
|
179
|
+
env: goChildEnv({ ...process.env, ...goBuildEnv(arch) }),
|
|
179
180
|
});
|
|
180
181
|
if (result.missing) {
|
|
181
182
|
throw new StandaloneBuildError("STANDALONE_GO_MISSING", "The go command was not found. Install Go 1.25 or later (https://go.dev/dl/), or package an existing executable with backend.binary.");
|
|
@@ -196,7 +197,7 @@ async function buildBackgroundWorker(manifest, arch, work, run) {
|
|
|
196
197
|
].join(" ");
|
|
197
198
|
const result = await run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, "-o", out, BACKGROUND_WORKER_MAIN], {
|
|
198
199
|
cwd: manifest.backend.dir,
|
|
199
|
-
env: { ...process.env, ...goBuildEnv(arch) },
|
|
200
|
+
env: goChildEnv({ ...process.env, ...goBuildEnv(arch) }),
|
|
200
201
|
});
|
|
201
202
|
if (result.missing || result.status !== 0) {
|
|
202
203
|
throw new StandaloneBuildError("STANDALONE_BACKGROUND_BUILD_FAILED", `background worker (${BACKGROUND_WORKER_MAIN}) build failed; this project's backend must contain that package.\n${result.stderr}`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { goChildEnv } from "../runtime/go-env.js";
|
|
1
2
|
import { chmodSync, copyFileSync, lstatSync, mkdirSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
4
|
import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem } from "../build-spk/go-toolchain.js";
|
|
@@ -64,7 +65,7 @@ export async function buildDaemonControl(manifest, arch, work, run) {
|
|
|
64
65
|
return undefined;
|
|
65
66
|
const out = join(work, `daemon-control-${arch}`);
|
|
66
67
|
const result = await run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", "-s -w -buildid=", "-o", out, "."], {
|
|
67
|
-
cwd: goAssetDir("daemon"), env: { ...process.env, ...pinnedGoBuildEnv(arch) },
|
|
68
|
+
cwd: goAssetDir("daemon"), env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }),
|
|
68
69
|
});
|
|
69
70
|
if (result.missing || result.status !== 0)
|
|
70
71
|
throw new StandaloneBuildError("STANDALONE_DAEMON_BUILD_FAILED", `daemon controller build failed (${arch}). ${PINNED_GO_HINT}\n${result.stderr}`);
|
|
@@ -10,6 +10,7 @@ import { packageIcons, windowIconFile, windowIconTemplate } from "../build-spk/d
|
|
|
10
10
|
import { DEFAULT_WINDOW, FULL_PAGE_MIN_WIDTH } from "./window-size.js";
|
|
11
11
|
import { requestTimeoutSeconds } from "./request-timeout.js";
|
|
12
12
|
import { appSdkTimeouts } from "../runtime/sdk-timeouts.js";
|
|
13
|
+
import { appSdkSessionRuntime } from "../runtime/sdk-session.js";
|
|
13
14
|
/** Same path the generated wrapper POSTs to, and the payload location of the CGI. */
|
|
14
15
|
export const CGI_PAYLOAD_PATH = "ui/cgi/api.cgi";
|
|
15
16
|
/**
|
|
@@ -136,10 +137,22 @@ export function renderWindowScript(manifest, assetVersion, icons = packageIcons(
|
|
|
136
137
|
const size = manifest.ui.window ?? DEFAULT_WINDOW;
|
|
137
138
|
const requestTimeout = requestTimeoutSeconds(manifest.backend) * 1000;
|
|
138
139
|
const clientMargin = appSdkTimeouts(manifest.backend).browserMarginMs;
|
|
140
|
+
const sessionRuntime = appSdkSessionRuntime(manifest.backend);
|
|
139
141
|
if (manifest.portal && !portalText)
|
|
140
142
|
throw new Error("A portal App's window needs the SDK's portal panel text.");
|
|
141
143
|
return `/* Generated by synopkgland build. Edit your UI in the project's ui folder. */
|
|
142
144
|
Ext.ns(${JSON.stringify(ns)});
|
|
145
|
+
${sessionRuntime ? `
|
|
146
|
+
// The SDK owns recovery; all calls share one bounded, same-origin attempt.
|
|
147
|
+
${ns}.session = (function () {
|
|
148
|
+
${sessionRuntime}
|
|
149
|
+
return createDsmSession({
|
|
150
|
+
read: function () { return SYNO.SDS.Session && SYNO.SDS.Session.SynoToken; },
|
|
151
|
+
write: function (token) { SYNO.SDS.Session = SYNO.SDS.Session || {}; SYNO.SDS.Session.SynoToken = token; },
|
|
152
|
+
fetch: function (url, options) { return fetch(url, options); }
|
|
153
|
+
});
|
|
154
|
+
})();
|
|
155
|
+
` : ''}
|
|
143
156
|
|
|
144
157
|
// The SDK's clientTimeout: past the backend's request deadline
|
|
145
158
|
// (ctx.requestTimeout, from backend.timeout_seconds), so its request_timeout
|
|
@@ -169,8 +182,10 @@ ${ns}.callOnce = function (method, params, options, requestTimeout) {
|
|
|
169
182
|
var done = function () {
|
|
170
183
|
clearTimeout(timer);
|
|
171
184
|
if (signal) signal.removeEventListener("abort", leave);
|
|
185
|
+
if (recoveryCanceled) controller.signal.removeEventListener("abort", recoveryCanceled);
|
|
172
186
|
};
|
|
173
|
-
|
|
187
|
+
var recoveryCanceled;
|
|
188
|
+
var dispatch = function () { return fetch(${JSON.stringify(cgiUrl(pkg))}, {
|
|
174
189
|
method: "POST",
|
|
175
190
|
credentials: "same-origin",
|
|
176
191
|
headers: headers,
|
|
@@ -182,7 +197,20 @@ ${ns}.callOnce = function (method, params, options, requestTimeout) {
|
|
|
182
197
|
});
|
|
183
198
|
}, function () {
|
|
184
199
|
return { success: false, error: failure || { code: "network", message: "The NAS did not answer" } };
|
|
185
|
-
})
|
|
200
|
+
}); };
|
|
201
|
+
var request;
|
|
202
|
+
if (!token && ${ns}.session) {
|
|
203
|
+
var canceledRecovery = new Promise(function (resolve) {
|
|
204
|
+
recoveryCanceled = function () { resolve(undefined); };
|
|
205
|
+
controller.signal.addEventListener("abort", recoveryCanceled);
|
|
206
|
+
});
|
|
207
|
+
request = Promise.race([${ns}.session.ready(), canceledRecovery]).then(function (recovered) {
|
|
208
|
+
if (failure) return { success: false, error: failure };
|
|
209
|
+
if (recovered) headers["X-SYNO-TOKEN"] = recovered;
|
|
210
|
+
return dispatch();
|
|
211
|
+
});
|
|
212
|
+
} else request = dispatch();
|
|
213
|
+
return request.then(function (body) {
|
|
186
214
|
done();
|
|
187
215
|
if (body && body.success === true) {
|
|
188
216
|
return body.data;
|
|
@@ -404,7 +432,7 @@ function renderWebApi(ns) {
|
|
|
404
432
|
return `// DSM WebAPI in the window's own session: resolves data, rejects {code: "dsm_<n>"}.
|
|
405
433
|
${ns}.webApi = function (win) {
|
|
406
434
|
return function (api, method, version, params) {
|
|
407
|
-
return new Promise(function (resolve, reject) {
|
|
435
|
+
var send = function () { return new Promise(function (resolve, reject) {
|
|
408
436
|
win.sendWebAPI({
|
|
409
437
|
api: api, method: method, version: version, params: params, scope: win,
|
|
410
438
|
callback: function (success, response) {
|
|
@@ -412,7 +440,8 @@ ${ns}.webApi = function (win) {
|
|
|
412
440
|
else reject({ code: "dsm_" + ((response && response.code) || "error") });
|
|
413
441
|
}
|
|
414
442
|
});
|
|
415
|
-
});
|
|
443
|
+
}); };
|
|
444
|
+
return ${ns}.session && !${ns}.session.token() ? ${ns}.session.ready().then(send) : send();
|
|
416
445
|
};
|
|
417
446
|
};
|
|
418
447
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.10",
|
|
4
4
|
"description": "Syno Package Land Developer Kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"prepublishOnly": "npm run build"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@synopackageland/templates": "0.10.
|
|
28
|
+
"@synopackageland/templates": "0.10.11",
|
|
29
29
|
"ajv": "^8.17.1",
|
|
30
30
|
"commander": "^13.1.0",
|
|
31
31
|
"tar": "^7.4.3",
|