@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 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
- **An App moved from SDK 0.10.7 or earlier and rebuilt is approved once more
10
- by an administrator after the upgrade**, since its executor digest changes.
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) is read back by the window as before. Use SDK 0.10.9, not 0.10.8:
13
- 0.10.8's `ExecutorApproval` kept its loading text beside the pages without an
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, upgrade, and approve once.
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 改變)。自 SDK 0.10.7 以前升級並重建的 App,升級後要由管理員
24
- 重新核准一次。SDK 下限維持 0.10.5(沒有 `tasks` 的舊 executor 照舊完整核對)。請用 SDK 0.10.9,
25
- 不要用 0.10.8。App 要改:`@synopackageland/cli` 0.10.9+lock;直接依賴 SDK 的 App 升到 0.10.9。
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 unless its executor or policy changes. Only the DSM
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 for now, so Go cannot fetch them from proxy.golang.org. Set
59
- `go env -w GOPRIVATE=github.com/synopackageland/*` and give git a credential that can read
60
- `https://github.com/synopackageland/syno-pkg-land-sdk` (check with `git ls-remote`); a failed
61
- fetch reports `SDK_DOWNLOAD_FAILED` with the step that is missing.
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}` };
@@ -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, set GOPRIVATE=github.com/synopackageland/* and make sure git can read github.com with credentials."
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 SDK repo is private and cannot go through proxy.golang.org: set GOPRIVATE=github.com/synopackageland/* (for example go env -w) and 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."],
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 Go setup (GOPRIVATE), not a defect of the SPK.
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,2 @@
1
+ /** The SDK's plain browser factory, embedded without imposing a TS build on Go Apps. */
2
+ export declare function appSdkSessionRuntime(backend: object, env?: NodeJS.ProcessEnv): string;
@@ -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
- * The SDK repositories are private: Go must fetch them with git, not from
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(env)}`);
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
- * The SDK repositories are private: Go must fetch them with git, not from
115
- * proxy.golang.org and sum.golang.org. Says which of the two is missing.
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/).");
@@ -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
- return fetch(${JSON.stringify(cgiUrl(pkg))}, {
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
- }).then(function (body) {
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.9",
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.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",