@synopackageland/cli 0.15.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -8
- package/README.md +3 -0
- package/assets/init-compose.md +33 -0
- package/assets/init-upstream/build-source.sh +10 -0
- package/assets/init-upstream/update-image.mjs +26 -0
- package/assets/init-upstream/upgrade.sh +47 -0
- package/assets/init-upstream/version.mjs +12 -0
- package/dist/cli.js +16 -2
- package/dist/e2e/dsm.d.ts +2 -2
- package/dist/e2e/dsm.js +3 -3
- package/dist/error-registry.js +4 -0
- package/dist/host/share-permission.d.ts +1 -1
- package/dist/host/share-permission.js +1 -1
- package/dist/init-upstream.d.ts +7 -0
- package/dist/init-upstream.js +64 -0
- package/dist/init.d.ts +4 -0
- package/dist/init.js +37 -1
- package/dist/runtime/dsm-version.d.ts +1 -1
- package/dist/runtime/dsm-version.js +1 -1
- package/dist/runtime/executor-digests.json +1 -23
- package/dist/skill.js +1 -0
- package/dist/standalone/build.js +3 -1
- package/dist/standalone/compose-errors.js +15 -0
- package/dist/standalone/compose.d.ts +8 -0
- package/dist/standalone/compose.js +16 -6
- package/dist/standalone/daemon.js +2 -2
- package/dist/standalone/dsm-window.js +2 -2
- package/dist/standalone/identity-gateway.js +1 -0
- package/dist/standalone/inspect-spk.js +11 -0
- package/dist/standalone/lifecycle.js +4 -1
- package/dist/standalone/manifest.js +6 -1
- package/dist/standalone/portal-window.js +1 -1
- package/dist/standalone/spk.d.ts +1 -0
- package/dist/standalone/spk.js +6 -0
- package/dist/template-paths.d.ts +4 -0
- package/dist/template-paths.js +11 -0
- package/package.json +3 -3
- package/skills/SKILL.md +7 -13
- package/skills/reference/compose.md +25 -8
- package/skills/reference/create.md +2 -2
- package/skills/reference/upstream-fork.md +165 -0
- package/skills/reference/wrap.md +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.0 (2026-10-19)
|
|
4
|
+
|
|
5
|
+
- Pin SDK/templates 0.16.0; generated Shared Folder flows use the shared SDK readback and settings components.
|
|
6
|
+
- Add the upstream-fork recipe/init/check workflow, Container Manager preflight and architecture-matched storage initializer packaging while preserving official images.
|
|
7
|
+
- Remove internal deployment details from published content and scan actual npm tarballs before release.
|
|
8
|
+
|
|
9
|
+
## 0.15.4 (2026-10-18)
|
|
10
|
+
|
|
11
|
+
- Fix Compose import and undeclared SDK defaults after CLI-only patch releases. Resolve the SDK from the exactly pinned templates' Go module instead of assuming the CLI and SDK share a patch version; daemon-control defaults use the same source.
|
|
12
|
+
- Preserve explicit Compose and identity-gateway SDK declarations. Add subprocess regressions with differing CLI/SDK versions and no SDK source override, plus release smoke coverage for real Compose imports and headless manifests.
|
|
13
|
+
- Keep templates/SDK pinned to 0.15.2 and every executor/lifecycle digest entry unchanged. Upgrade from CLI 0.15.3 to 0.15.4 to restore `init -t compose --from`; SDK/helper bytes do not change and no new administrator reapproval requirement is introduced.
|
|
14
|
+
|
|
15
|
+
繁中摘要:修正 CLI 與 SDK patch 版本不同時,Compose 匯入/未宣告 SDK 的預設路徑下載不存在 SDK tag 的問題;改由固定 templates 的 Go module 讀取 SDK pin,daemon-control 預設值亦同步。保留明確 SDK 宣告的優先順序,新增無 source override 的版本拆分回歸測試與正式產物 smoke。templates/SDK 保持 0.15.2,全部 executor/lifecycle digest 不變;請由 CLI 0.15.3 升級至 0.15.4,不新增管理員重新核准要求。
|
|
16
|
+
|
|
3
17
|
## 0.15.3 (2026-10-18)
|
|
4
18
|
|
|
5
19
|
- Pin templates and SDK fixtures/examples to 0.15.2, including UI 0.12.0 scaffolds, concise UI copy/tokens and delegate DTO contract checks.
|
|
@@ -166,8 +180,8 @@ only its protocol README's wording, and nothing in the CLI depends on it.
|
|
|
166
180
|
|
|
167
181
|
- `ctx.call` rejects `APP_STOPPED` at once: a stopped package waits for the
|
|
168
182
|
user to start it in Package Center, so the SDK's stopped copy shows within a
|
|
169
|
-
page's next read instead of 30 s later (Download Hub 29.3 s
|
|
170
|
-
showed its old content or loading
|
|
183
|
+
page's next read instead of 30 s later (Download Hub 29.3 s; Reclaim
|
|
184
|
+
showed its old content or loading). `APP_STARTING` and
|
|
171
185
|
`EXECUTOR_STARTING` repeat for 4 s of waiting (0.5, 1, 2, 0.5 s), so a short
|
|
172
186
|
start still shows the page nothing and the SDK's starting copy follows a
|
|
173
187
|
longer one within its own 4 s recheck. `executor.control`, which the window's
|
|
@@ -217,7 +231,7 @@ e2e 新增 `openUntilLoaded`(`signIn` 遇 `net::ERR_ABORTED` 最多重開約 3
|
|
|
217
231
|
the newest one it read (`lastStatus()`); a `status()` asked for while a read
|
|
218
232
|
runs shares it. The SDK's `PortalSettings` starts from it, so a settings
|
|
219
233
|
page no longer moves when its placeholder rows become the real section
|
|
220
|
-
(Reclaim: 100 px
|
|
234
|
+
(Reclaim: 100 px).
|
|
221
235
|
|
|
222
236
|
## 0.10.6 (2026-10-15)
|
|
223
237
|
|
|
@@ -236,7 +250,7 @@ digest 與 0.10.5 相同;SDK 下限維持 0.10.5。e2e 新增 `openPath`、`ha
|
|
|
236
250
|
|
|
237
251
|
- `signIn`, `openDesktop` and `openPortal` open their page once more when
|
|
238
252
|
the first navigation fails with `net::ERR_ABORTED`, which about one run in
|
|
239
|
-
ten met as DSM's desktop navigated on its own
|
|
253
|
+
ten met as DSM's desktop navigated on its own; any other
|
|
240
254
|
failure, or a second abort, still fails. The helper is exported as
|
|
241
255
|
`openPath(app, path)`.
|
|
242
256
|
- The DSM window's `status()` answers a pinned executor that reports
|
|
@@ -322,7 +336,7 @@ digest 與 0.10.4 相同;新增 `@synopackageland/cli/e2e`。
|
|
|
322
336
|
`authorization.status`, `executor.status`) while the package is stopped or
|
|
323
337
|
starting: they reject with the code at once, and SDK 0.10.5's panels show the
|
|
324
338
|
stopped or starting package and check again on their own. A page no longer
|
|
325
|
-
shows nothing for the first seconds after an upgrade (6.7 s in
|
|
339
|
+
shows nothing for the first seconds after an upgrade (6.7 s in the regression test). Calls
|
|
326
340
|
that change something, approvals and controls among them, still repeat for up
|
|
327
341
|
to 30 s. `ctx.executor.status()` for a user who is not an administrator says
|
|
328
342
|
the package is stopped or starting instead of `admin_required`.
|
|
@@ -354,12 +368,12 @@ before its timeout.
|
|
|
354
368
|
- The window approval of an upgrade (outdated tasks) makes 11 scheduler calls
|
|
355
369
|
instead of 12: the outdated boot task is deleted by the approval's own first
|
|
356
370
|
EventScheduler get, made moments before the password, instead of a second
|
|
357
|
-
get (2.9 s on the test NAS in
|
|
371
|
+
get (2.9 s on the test NAS in the regression test). The control task is still read again by id
|
|
358
372
|
before its delete (spec/scheduler/v1). A fresh approval stays at 8.
|
|
359
373
|
繁中:升級核准的排程呼叫由 12 降為 11(boot 任務不再於刪除前重讀)。
|
|
360
374
|
- A scope approval reads the executor's selection back every second at most
|
|
361
375
|
(it backed off to 2 s), so the window sees the landed scope within a second
|
|
362
|
-
of `scope.confirm` (the Dialog closed 4.8 s after it in
|
|
376
|
+
of `scope.confirm` (the Dialog closed 4.8 s after it in the regression test). Removing
|
|
363
377
|
the temporary selection task still lists Task Scheduler first, as
|
|
364
378
|
spec/scheduler/v1 requires of a delete.
|
|
365
379
|
繁中:範圍核准讀回間隔上限 2 s → 1 s,confirmed 後 1 s 內看到結果。
|
|
@@ -368,7 +382,7 @@ before its timeout.
|
|
|
368
382
|
before any request: the pinned, running executor reads `ready`; anything
|
|
369
383
|
else stays `blocked` with `admin_required`. Task Scheduler is still read only
|
|
370
384
|
for administrators. A user no longer sees an approved executor as waiting
|
|
371
|
-
for approval
|
|
385
|
+
for approval.
|
|
372
386
|
繁中:非管理員的 `ctx.executor.status()` 會向 App 的 `executor.status` 詢問,已核准且
|
|
373
387
|
執行中即為 `ready`,不再一律回 `admin_required`。
|
|
374
388
|
|
package/README.md
CHANGED
|
@@ -61,6 +61,9 @@ children's `GOPRIVATE`, `GONOPROXY` and `GONOSUMDB`, preserving existing environ
|
|
|
61
61
|
`https://github.com/synopackageland/syno-pkg-land-sdk` (check with `git ls-remote`);
|
|
62
62
|
a failed fetch reports `SDK_DOWNLOAD_FAILED`.
|
|
63
63
|
|
|
64
|
+
CLI-only patch releases can differ from the SDK patch. Compose import and SDK defaults
|
|
65
|
+
read the SDK pin from the installed templates; explicit App SDK declarations take precedence.
|
|
66
|
+
|
|
64
67
|
`init` and `add` read their templates from `@synopackageland/templates`, an exact-pinned
|
|
65
68
|
dependency. For development against local checkouts, set `SYNOPKGLAND_TEMPLATES_DIR` to a
|
|
66
69
|
templates checkout and `SYNOPKGLAND_SDK_DIR` to an SDK checkout; SPKs built with
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Compose App development
|
|
2
|
+
|
|
3
|
+
Run `synopkgland skill list`, then `synopkgland skill show orchestrator`,
|
|
4
|
+
`synopkgland skill show compose` and `synopkgland skill show upstream-fork`.
|
|
5
|
+
Prefer `check --json`, `build --json`, `inspect <spk> --json` for reviewable results.
|
|
6
|
+
|
|
7
|
+
Never write `AppPortal.json` or `@appconf` directly, or relay QuickConnect.
|
|
8
|
+
Compose has no native Application Portal alias; use its supported web entry.
|
|
9
|
+
|
|
10
|
+
Use dsm-package.yaml schema 2. Keep upstream Compose bytes unchanged; declare DSM
|
|
11
|
+
policy in compose.files and compose.web. Never use file/targets/address_env or
|
|
12
|
+
identity_gateway.upstream: the SDK planner derives ingress.
|
|
13
|
+
|
|
14
|
+
## Compose health and launch
|
|
15
|
+
|
|
16
|
+
Select compose.web.health.path and its actual expected 2xx status before
|
|
17
|
+
check/build. Redirects are not followed; a 302 to login is unhealthy. check is
|
|
18
|
+
static, not an HTTP or arbitrary-UID/storage probe. Test the selected official image as
|
|
19
|
+
the package UID, including writable volumes, first setup and upgrade.
|
|
20
|
+
Omitted ui.launch means new-page via the SDK standalone launcher, without
|
|
21
|
+
Application Portal. Headless Apps omit compose.web and have no launcher.
|
|
22
|
+
Compose ctl is unsupported.
|
|
23
|
+
|
|
24
|
+
Use supported upstream config files, environment, argv or reverse proxy for
|
|
25
|
+
login/settings. For integrated upstream Apps, select supported trusted-header authentication or disable upstream login behind the DSM admin gate; or use the existing SDK 0.16.0 token adapter: OpenList uses mode: token, access: dsm-admins and entry: admin-only. Its unmodified admin token CLI supplies a package-private 0600 credential; the gateway injects Authorization server-side after DSM admission, without delivering it to the browser. All DSM admins share one upstream admin; token reset requires package restart; WebDAV keeps its own credentials. Verify each App adapter and DSM admission separately.
|
|
26
|
+
Containers use the package account and a dedicated bridge; they do not inherit
|
|
27
|
+
an interactive user's NAS ACL. Do not use root/chown during any App lifecycle
|
|
28
|
+
phase. Preserve licenses and dependency/source provenance. Read the CLI skill
|
|
29
|
+
reference/compose.md and reference/upstream-fork.md for the full recipe.
|
|
30
|
+
|
|
31
|
+
Public npm access does not grant anonymous Go SDK access. Obtain authorised
|
|
32
|
+
source/cache access or a matching SYNOPKGLAND_SDK_DIR checkout first. Never copy
|
|
33
|
+
credentials into the App. Do not install, publish or modify NAS data without task authorization.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
# Native App payload hook. SPL_UPSTREAM_SOURCE is the merged fork root.
|
|
4
|
+
# Stage official release binaries only after verifying pinned SHA-256 values
|
|
5
|
+
# (the OpenList route), or compile the merged source with a source build hook.
|
|
6
|
+
# Stage daemon.targets under synology/; pin toolchains/dependencies for builds.
|
|
7
|
+
# Keep upstream source unchanged.
|
|
8
|
+
# Every App lifecycle phase uses the package UID. Do not add root/chown hooks.
|
|
9
|
+
echo 'SOURCE_BUILD_REQUIRED: implement the native synology/build-source.sh.' >&2
|
|
10
|
+
exit 2
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Use the CLI's existing OCI registry resolver; never build or publish an image.
|
|
3
|
+
import {mkdtempSync, readFileSync, writeFileSync, rmSync} from 'node:fs';
|
|
4
|
+
import {tmpdir} from 'node:os';
|
|
5
|
+
import {join} from 'node:path';
|
|
6
|
+
import {spawnSync} from 'node:child_process';
|
|
7
|
+
const [tag] = process.argv.slice(2);
|
|
8
|
+
const match = /^v?((?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))$/.exec(tag ?? '');
|
|
9
|
+
if (!match) throw new Error('Use the selected stable upstream tag.');
|
|
10
|
+
const config = JSON.parse(readFileSync(new URL('./upstream-recipe.json', import.meta.url), 'utf8'));
|
|
11
|
+
if (config.kind !== 'official-image' || !['tag', 'version'].includes(config.imageTag)) throw new Error('Invalid official image recipe.');
|
|
12
|
+
const imageTag = config.imageTag === 'version' ? match[1] : tag;
|
|
13
|
+
const reference = `${config.repository}:${imageTag}`;
|
|
14
|
+
const temp = mkdtempSync(join(tmpdir(), 'synopkgland-image-pin-'));
|
|
15
|
+
try {
|
|
16
|
+
// images pin edits only compose.yaml; use a bounded temporary project so the
|
|
17
|
+
// App's base Compose and upstream source remain unchanged on lookup failure.
|
|
18
|
+
writeFileSync(join(temp, 'dsm-package.yaml'), 'schema: 2\n');
|
|
19
|
+
writeFileSync(join(temp, 'compose.yaml'), JSON.stringify({services: {[config.service]: {image: reference}}}, null, 2) + '\n');
|
|
20
|
+
const result = spawnSync('synopkgland', ['--project', temp, 'images', 'pin'], {stdio: 'inherit'});
|
|
21
|
+
if (result.error || result.status !== 0) throw new Error('Official image digest lookup failed; no Compose override was changed.');
|
|
22
|
+
const pinned = JSON.parse(readFileSync(join(temp, 'compose.yaml'), 'utf8'));
|
|
23
|
+
const image = pinned.services[config.service].image;
|
|
24
|
+
if (!image.startsWith(`${reference}@sha256:`) || !/@sha256:[0-9a-f]{64}$/.test(image)) throw new Error('Registry lookup did not produce the selected tag plus digest.');
|
|
25
|
+
writeFileSync(new URL('./compose.image.yaml', import.meta.url), JSON.stringify(pinned, null, 2) + '\n');
|
|
26
|
+
} finally {rmSync(temp, {recursive: true, force: true});}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# One explicit stable tag; no latest guessing, push, image publication or NAS access.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
packaging=$(cd -- "$(dirname -- "$0")" && pwd)
|
|
5
|
+
repo=$(cd -- "$packaging/.." && pwd)
|
|
6
|
+
cd "$repo"
|
|
7
|
+
[[ $# == 2 && $1 =~ ^v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || {
|
|
8
|
+
echo 'Usage: bash synology/upgrade.sh <[v]X.Y.Z> <increasing-build-number>' >&2; exit 2;
|
|
9
|
+
}
|
|
10
|
+
[[ $(git rev-parse --show-toplevel) == "$repo" ]] || { echo 'Run inside a source fork.' >&2; exit 2; }
|
|
11
|
+
[[ -z $(git status --porcelain --untracked-files=normal) ]] || { echo 'Commit changes before upgrading.' >&2; exit 2; }
|
|
12
|
+
git remote get-url upstream >/dev/null
|
|
13
|
+
node "$packaging/version.mjs" "$1" "$2" --validate
|
|
14
|
+
# Fetch the named remote tag into a private ref: a local tag can never substitute for it.
|
|
15
|
+
git fetch --no-tags upstream "+refs/tags/$1:refs/synopkgland/selected-upstream"
|
|
16
|
+
commit=$(git rev-parse 'refs/synopkgland/selected-upstream^{commit}')
|
|
17
|
+
[[ -z $(git ls-tree "$commit" -- synology) ]] || { echo 'Upstream owns synology/; choose another integration layout.' >&2; exit 2; }
|
|
18
|
+
previous=$(git describe --tags --abbrev=0 --match '[0-9]*' --match 'v[0-9]*' HEAD 2>/dev/null || true)
|
|
19
|
+
if [[ -f "$packaging/upstream-source.json" ]]; then
|
|
20
|
+
previous=$(node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(process.argv[1])).commit)' "$packaging/upstream-source.json")
|
|
21
|
+
fi
|
|
22
|
+
if [[ -n $previous ]]; then
|
|
23
|
+
git merge-base --is-ancestor "$previous" "$commit" || { echo 'Refusing upstream downgrade/divergent history.' >&2; exit 2; }
|
|
24
|
+
[[ -z $(git diff "$previous" HEAD -- . ':(exclude)synology') ]] || { echo 'Wrapper changed upstream files outside synology/.' >&2; exit 2; }
|
|
25
|
+
else
|
|
26
|
+
# First adoption must already be based on the selected source tag.
|
|
27
|
+
git merge-base --is-ancestor "$commit" HEAD
|
|
28
|
+
[[ -z $(git diff "$commit" HEAD -- . ':(exclude)synology') ]] || { echo 'Initial fork must preserve upstream source.' >&2; exit 2; }
|
|
29
|
+
fi
|
|
30
|
+
git merge --no-ff --no-edit "$commit"
|
|
31
|
+
[[ -z $(git diff "$commit" HEAD -- . ':(exclude)synology') ]] || { echo 'Upstream files differ after merge; resolve before building.' >&2; exit 2; }
|
|
32
|
+
kind=$(node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(process.argv[1])).kind)' "$packaging/upstream-recipe.json")
|
|
33
|
+
if [[ $kind == official-image ]]; then
|
|
34
|
+
node "$packaging/update-image.mjs" "$1"
|
|
35
|
+
elif [[ $kind != native-source ]]; then
|
|
36
|
+
echo 'Unknown upstream recipe kind.' >&2; exit 2
|
|
37
|
+
fi
|
|
38
|
+
node "$packaging/version.mjs" "$1" "$2"
|
|
39
|
+
node -e 'const fs=require("fs");fs.writeFileSync(process.argv[1],JSON.stringify({tag:process.argv[2],commit:process.argv[3],build:Number(process.argv[4])},null,2)+"\n")' "$packaging/upstream-source.json" "$1" "$commit" "$2"
|
|
40
|
+
export SPL_UPSTREAM_SOURCE="$repo"
|
|
41
|
+
export SPL_UPSTREAM_TAG="$1"
|
|
42
|
+
export SPL_UPSTREAM_COMMIT="$commit"
|
|
43
|
+
cd "$packaging"
|
|
44
|
+
if [[ $kind == native-source ]]; then bash ./build-source.sh; fi
|
|
45
|
+
synopkgland check --json
|
|
46
|
+
synopkgland build --json
|
|
47
|
+
# Review version/provenance and commit synology/ before the next upgrade.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Stable tag -> DSM version. Keep all wrapper-owned changes under synology/.
|
|
3
|
+
import {readFileSync, writeFileSync} from 'node:fs';
|
|
4
|
+
const [tag, build, mode] = process.argv.slice(2);
|
|
5
|
+
const match = /^v?((?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))$/.exec(tag ?? '');
|
|
6
|
+
const file = new URL('./dsm-package.yaml', import.meta.url);
|
|
7
|
+
const source = readFileSync(file, 'utf8');
|
|
8
|
+
const current = /^build: (\d+)\s*$/m.exec(source);
|
|
9
|
+
if (!match || !/^[1-9][0-9]{0,3}$/.test(build ?? '') || !current || Number(build) <= Number(current[1]))
|
|
10
|
+
throw new Error('Use a stable [v]X.Y.Z tag and an increasing packaging build (1–9999).');
|
|
11
|
+
if (!/^version: .+$/m.test(source)) throw new Error('Missing scaffold version.');
|
|
12
|
+
if (mode !== '--validate') writeFileSync(file, source.replace(/^version: .+$/m, `version: ${match[1]}`).replace(/^build: \d+\s*$/m, `build: ${Number(build)}`));
|
package/dist/cli.js
CHANGED
|
@@ -421,6 +421,10 @@ export async function runCli(argv) {
|
|
|
421
421
|
.option("-t, --template <name>", `Template: ${listTemplates().join(" | ")}`)
|
|
422
422
|
.option("--package <id>", "INFO.package (defaults to the directory name)")
|
|
423
423
|
.option("--name <display>", "Display name")
|
|
424
|
+
.option("--upstream-image <repository>", "compose fork: official image repository; upgrade pins its matching tag and index digest")
|
|
425
|
+
.option("--upstream-fork", "Create only synology/ inside an upstream source fork (compose or native-go)")
|
|
426
|
+
.option("--health-path <path>", "compose: actual readiness endpoint; redirects are not followed")
|
|
427
|
+
.option("--health-status <status>", "compose: expected 2xx readiness status (no default)", Number)
|
|
424
428
|
.option("--ui <vue|plain>", `Native UI ${JSON.parse(readFileSync(join(templatesRoot(), "native-go-vue/frontend/package.json"), "utf8")).dependencies["@synopackageland/ui"]} by default (vue); plain uses ui/app.js`)
|
|
425
429
|
.option("--with <caps>", `Capabilities (comma-separated): ${CAPABILITY_NAMES.join(",")}`)
|
|
426
430
|
.option("--from <compose.yaml>", "compose: import an existing Compose; omit to create an example")
|
|
@@ -430,7 +434,7 @@ export async function runCli(argv) {
|
|
|
430
434
|
.option("--shared-folder-permission <ro|rw>", "Package account permission, defaults to rw")
|
|
431
435
|
.option("--min-dsm <version>", `Minimum DSM version (native templates and binary default to ${DEFAULT_MIN_DSM}, compose to ${CONTAINER_MIN_DSM})`)
|
|
432
436
|
.argument("[dir]", "Target directory", ".")
|
|
433
|
-
.addHelpText("after", "\nNative projects: native-go, native-python, native-node, native-rust and binary use dsm-package.yaml schema 2.\nCompose projects: compose uses
|
|
437
|
+
.addHelpText("after", "\nNative projects: native-go, native-python, native-node, native-rust and binary use dsm-package.yaml schema 2.\nCompose projects: compose uses dsm-package.yaml schema 2; --from imports compose.yaml.\nQuick start: synopkgland init -t native-go --with files,jobs,login my-app\n cd my-app && synopkgland build\n")
|
|
434
438
|
.action((dir, options, command) => {
|
|
435
439
|
if (command.parent?.opts().project)
|
|
436
440
|
throw failure("CLI_OPTION_INVALID", "--project does not apply to init.");
|
|
@@ -440,6 +444,10 @@ export async function runCli(argv) {
|
|
|
440
444
|
throw failure("INIT_TEMPLATE_UNKNOWN", `Unknown template: ${options.template}`);
|
|
441
445
|
const targetDir = resolve(dir);
|
|
442
446
|
initFromTemplate(options.template, targetDir, {
|
|
447
|
+
upstreamFork: options.upstreamFork,
|
|
448
|
+
upstreamImage: options.upstreamImage,
|
|
449
|
+
healthPath: options.healthPath,
|
|
450
|
+
healthStatus: options.healthStatus,
|
|
443
451
|
packageId: options.package,
|
|
444
452
|
displayName: options.name,
|
|
445
453
|
minDsm: options.minDsm,
|
|
@@ -457,7 +465,13 @@ export async function runCli(argv) {
|
|
|
457
465
|
permission: options.sharedFolderPermission,
|
|
458
466
|
},
|
|
459
467
|
});
|
|
460
|
-
stdout(`Created the ${options.template} project at ${targetDir}`);
|
|
468
|
+
stdout(`Created the ${options.template} project at ${options.upstreamFork ? join(targetDir, "synology") : targetDir}`);
|
|
469
|
+
if (options.template === "compose")
|
|
470
|
+
stdout("Select actual compose.web.health path/status before check/build. Redirects are not followed; check does not probe runtime health.");
|
|
471
|
+
if (options.upstreamFork) {
|
|
472
|
+
stdout("Read synology/AGENTS.md; configure the official image recipe or the native source hook, then run bash synology/upgrade.sh <upstream-tag> <build-number>.");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
461
475
|
const next = initNextSteps(options.template, dir, existsSync(join(targetDir, "frontend", "package.json")), Boolean(process.env.SYNOPKGLAND_SDK_DIR));
|
|
462
476
|
if (next)
|
|
463
477
|
stdout(next);
|
package/dist/e2e/dsm.d.ts
CHANGED
|
@@ -105,7 +105,7 @@ type SessionPage = {
|
|
|
105
105
|
/**
|
|
106
106
|
* Registers the signed-in session's cookies (id, did, _SSID) and SynoToken as
|
|
107
107
|
* e2e secrets, so e2e masks them in its report and output, as a request's
|
|
108
|
-
* Playwright call log carries them
|
|
108
|
+
* Playwright call log carries them. Returns how many were registered.
|
|
109
109
|
*/
|
|
110
110
|
export declare function registerDsmSecrets(page?: SessionPage, ledger?: SecretLedger): Promise<number>;
|
|
111
111
|
/**
|
|
@@ -136,7 +136,7 @@ export declare function dsmSessionLive(page: Pick<SessionPage, "evaluate">): Pro
|
|
|
136
136
|
/**
|
|
137
137
|
* `app.open(path)`, once more after net::ERR_ABORTED: about one first
|
|
138
138
|
* navigation in ten was aborted as DSM's desktop navigated on its own, and the
|
|
139
|
-
* next one loaded
|
|
139
|
+
* next one loaded. Any other failure, or a second abort, is
|
|
140
140
|
* thrown.
|
|
141
141
|
*/
|
|
142
142
|
export declare function openPath(app: Pick<App, "open">, path: string): Promise<void>;
|
package/dist/e2e/dsm.js
CHANGED
|
@@ -135,7 +135,7 @@ export async function runWithTunnel(command, outer = process.env) {
|
|
|
135
135
|
// ---- Secrets in output ----------------------------------------------------
|
|
136
136
|
// e2e redacts only the secrets its config declares. A Playwright call log in a
|
|
137
137
|
// run error lists a request's headers, DSM's session cookie among them, and e2e
|
|
138
|
-
// writes it to the report and stdout
|
|
138
|
+
// writes it to the report and stdout. A value runs to a quote, a
|
|
139
139
|
// backslash or a line end, so a JSON string's escapes end it too.
|
|
140
140
|
const mask = "***";
|
|
141
141
|
// A Cookie or Set-Cookie header, and in it DSM's session (id, _SSID) and device (did) cookies.
|
|
@@ -324,7 +324,7 @@ async function e2eSecretLedger() {
|
|
|
324
324
|
/**
|
|
325
325
|
* Registers the signed-in session's cookies (id, did, _SSID) and SynoToken as
|
|
326
326
|
* e2e secrets, so e2e masks them in its report and output, as a request's
|
|
327
|
-
* Playwright call log carries them
|
|
327
|
+
* Playwright call log carries them. Returns how many were registered.
|
|
328
328
|
*/
|
|
329
329
|
export async function registerDsmSecrets(page = dsmPage(), ledger) {
|
|
330
330
|
const target = ledger ?? await e2eSecretLedger();
|
|
@@ -410,7 +410,7 @@ function accountFile(user, env) {
|
|
|
410
410
|
/**
|
|
411
411
|
* `app.open(path)`, once more after net::ERR_ABORTED: about one first
|
|
412
412
|
* navigation in ten was aborted as DSM's desktop navigated on its own, and the
|
|
413
|
-
* next one loaded
|
|
413
|
+
* next one loaded. Any other failure, or a second abort, is
|
|
414
414
|
* thrown.
|
|
415
415
|
*/
|
|
416
416
|
export async function openPath(app, path) {
|
package/dist/error-registry.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { COMPOSE_ERRORS } from "./standalone/compose-errors.js";
|
|
2
2
|
export const ERROR_REGISTRY = {
|
|
3
3
|
...COMPOSE_ERRORS,
|
|
4
|
+
INIT_HEALTH_INVALID: { code: "INIT_HEALTH_INVALID", description: "The Compose readiness selection is invalid.", suggestion: "Supply both --health-path and --health-status with the actual endpoint and expected 2xx response; redirects are not followed." },
|
|
5
|
+
INIT_UPSTREAM_IMAGE_REQUIRED: { code: "INIT_UPSTREAM_IMAGE_REQUIRED", description: "The official upstream image repository was not selected.", suggestion: "Use init --upstream-fork --upstream-image <official-repository> for Compose." },
|
|
6
|
+
INIT_UPSTREAM_IMAGE_INVALID: { code: "INIT_UPSTREAM_IMAGE_INVALID", description: "The upstream image repository is invalid.", suggestion: "Supply the official repository without a tag or digest; upgrade.sh selects the matching release and resolves its digest." },
|
|
4
7
|
CTL_TRUST_REQUIRED: { "code": "CTL_TRUST_REQUIRED", "description": "App description access requires approval.", "suggestion": "Run synopkgland ctl list with explicit host and approve the candidate batch." },
|
|
8
|
+
COMPOSE_HEALTH_REQUIRED: { code: "COMPOSE_HEALTH_REQUIRED", description: "Compose web readiness was not selected.", suggestion: "Set compose.web.health.path and expected 2xx status for the actual App; redirects are not followed." },
|
|
5
9
|
CTL_UNSUPPORTED_BACKEND: { "code": "CTL_UNSUPPORTED_BACKEND", "description": "Automatic descriptions require a native SDK backend.", "suggestion": "Disable ctl for binary/Compose wrappers or implement the public contract separately." },
|
|
6
10
|
CTL_DESCRIBE_REQUIRED: { "code": "CTL_DESCRIBE_REQUIRED", "description": "The Go backend has no build-only registry exporter.", "suggestion": "Update the native template and implement --describe using api.DescribeRoutes." },
|
|
7
11
|
CTL_DESCRIBE_FAILED: { "code": "CTL_DESCRIBE_FAILED", "description": "The backend registry export failed.", "suggestion": "Check the backend --describe output and build diagnostics." },
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* extra_values directly, and share permission can change after install.
|
|
11
11
|
*
|
|
12
12
|
* The rows are SYNO.Core.Share.Permission v1 output verbatim. Read the tests
|
|
13
|
-
* before changing the predicate: on the test NAS, `
|
|
13
|
+
* before changing the predicate: on the test NAS, `fixtureadmin` has is_writable=false on
|
|
14
14
|
* its own row for `pkgland-p0b` and still writes there, because the
|
|
15
15
|
* `administrators` group row carries the grant. A user-row-only check rejects
|
|
16
16
|
* the only configuration with live proof behind it.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* extra_values directly, and share permission can change after install.
|
|
11
11
|
*
|
|
12
12
|
* The rows are SYNO.Core.Share.Permission v1 output verbatim. Read the tests
|
|
13
|
-
* before changing the predicate: on the test NAS, `
|
|
13
|
+
* before changing the predicate: on the test NAS, `fixtureadmin` has is_writable=false on
|
|
14
14
|
* its own row for `pkgland-p0b` and still writes there, because the
|
|
15
15
|
* `administrators` group row carries the grant. A user-row-only check rejects
|
|
16
16
|
* the only configuration with live proof behind it.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ForkRecipeOptions {
|
|
2
|
+
upstreamImage?: string;
|
|
3
|
+
from?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function validateForkRecipe(template: 'compose' | 'native-go', options: ForkRecipeOptions): void;
|
|
6
|
+
/** CLI-owned packaging recipe; upstream files and history stay untouched. */
|
|
7
|
+
export declare function writeForkScaffold(packaging: string, template: 'compose' | 'native-go', options: ForkRecipeOptions): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { cpSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { parseDocument } from 'yaml';
|
|
5
|
+
import { API_PERMISSIONS_FILE } from './standalone/api-permissions.js';
|
|
6
|
+
import { parseImageReference } from './images/pin.js';
|
|
7
|
+
export function validateForkRecipe(template, options) {
|
|
8
|
+
if (template === 'native-go' && options.upstreamImage)
|
|
9
|
+
throw new Error('[INIT_OPTIONS_UNSUPPORTED] Native forks stage official release binaries or use a source build hook; image options apply only to compose.');
|
|
10
|
+
if (template !== 'compose')
|
|
11
|
+
return;
|
|
12
|
+
if (!options.upstreamImage)
|
|
13
|
+
throw new Error('[INIT_UPSTREAM_IMAGE_REQUIRED] Compose forks use official images: supply --upstream-image <official-repository>.');
|
|
14
|
+
const parsed = parseImageReference(options.upstreamImage);
|
|
15
|
+
if (!parsed || parsed.explicitTag || parsed.digest || !/^[a-z0-9][a-z0-9._:/-]*$/.test(options.upstreamImage) || options.upstreamImage.includes('//'))
|
|
16
|
+
throw new Error('[INIT_UPSTREAM_IMAGE_INVALID] Supply the official image repository without a tag or digest; upgrade.sh selects the matching upstream release tag.');
|
|
17
|
+
}
|
|
18
|
+
/** CLI-owned packaging recipe; upstream files and history stay untouched. */
|
|
19
|
+
export function writeForkScaffold(packaging, template, options) {
|
|
20
|
+
const official = template === 'compose';
|
|
21
|
+
const assets = fileURLToPath(new URL('../assets/init-upstream/', import.meta.url));
|
|
22
|
+
for (const file of ['upgrade.sh', 'version.mjs', official ? 'update-image.mjs' : 'build-source.sh'])
|
|
23
|
+
cpSync(join(assets, file), join(packaging, file));
|
|
24
|
+
const manifest = join(packaging, 'dsm-package.yaml');
|
|
25
|
+
const doc = parseDocument(readFileSync(manifest, 'utf8'));
|
|
26
|
+
const service = official ? (doc.toJS().compose.web?.service ?? 'web') : undefined;
|
|
27
|
+
const recipe = official
|
|
28
|
+
? { kind: 'official-image', repository: options.upstreamImage, service, imageTag: 'tag' }
|
|
29
|
+
: { kind: 'native-source' };
|
|
30
|
+
writeFileSync(join(packaging, 'upstream-recipe.json'), JSON.stringify(recipe, null, 2) + '\n');
|
|
31
|
+
doc.set('entry', 'admin-only');
|
|
32
|
+
if (official) {
|
|
33
|
+
doc.setIn(['identity_gateway', 'access'], 'dsm-admins');
|
|
34
|
+
if (!options.from) {
|
|
35
|
+
const path = join(packaging, 'compose.yaml');
|
|
36
|
+
const base = parseDocument(readFileSync(path, 'utf8'));
|
|
37
|
+
base.deleteIn(['services', service, 'command']);
|
|
38
|
+
base.setIn(['services', service, 'image'], `${options.upstreamImage}:SELECT_UPSTREAM_TAG`);
|
|
39
|
+
writeFileSync(path, String(base));
|
|
40
|
+
}
|
|
41
|
+
doc.setIn(['compose', 'files'], [...doc.toJS().compose.files, 'compose.image.yaml']);
|
|
42
|
+
writeFileSync(join(packaging, 'compose.image.yaml'), JSON.stringify({ services: { [service]: { image: `${options.upstreamImage}:SELECT_UPSTREAM_TAG` } } }, null, 2) + '\n');
|
|
43
|
+
}
|
|
44
|
+
if (!official) {
|
|
45
|
+
const path = join(packaging, 'backend', API_PERMISSIONS_FILE.go);
|
|
46
|
+
const policy = JSON.parse(readFileSync(path, 'utf8'));
|
|
47
|
+
for (const method of Object.keys(policy.methods))
|
|
48
|
+
policy.methods[method] = 'administrators';
|
|
49
|
+
writeFileSync(path, JSON.stringify(policy, null, 2) + '\n');
|
|
50
|
+
}
|
|
51
|
+
writeFileSync(manifest, String(doc));
|
|
52
|
+
const guidance = `
|
|
53
|
+
## Integrating an existing open-source App
|
|
54
|
+
|
|
55
|
+
Keep the upstream fork/history and remote upstream. All wrapper-owned files belong in synology/; never edit upstream files or treat the App as an SDK component. Read synopkgland skill show upstream-fork.
|
|
56
|
+
|
|
57
|
+
Commit the scaffold, then run bash synology/upgrade.sh <upstream-tag> <build-number>. It fetches the exact tag, verifies ancestry/untouched upstream files, merges, derives version/provenance, checks and builds. Commit synology/ before upgrading again. It does not publish or touch a NAS.
|
|
58
|
+
|
|
59
|
+
${official ? 'Default: use the upstream official image. upgrade.sh updates its release tag and resolves an OCI index/manifest digest through synopkgland images pin, writing only synology/compose.image.yaml. Container Manager downloads that pinned official image at install. Configure upstream-recipe.json repository/service and imageTag: tag (exact upstream tag) or version (strip v only when upstream image naming requires it). Review multi-architecture support and upstream source/license correspondence. The SELECT_UPSTREAM_TAG placeholder must be replaced by a successful upgrade before check/build.' : 'Native App: implement build-source.sh as the native payload hook; it deliberately fails until implemented. Stage official release binaries pinned by SHA-256 (the OpenList route), or compile the merged source with a source build hook. Verify the release digest before staging architecture-specific daemon.targets; keep the SDK management shell separate.'}
|
|
60
|
+
|
|
61
|
+
Use DSM administrator identity without an additional App password, an OIDC/SSO server or upstream source changes. Prefer supported trusted-header/proxy auth (FreshRSS), otherwise disable upstream login/enable guest behind the administrator-only gateway after a bounded first bootstrap (Uptime Kuma disableAuth). The third shipped pattern is the existing SDK 0.16.0 token adapter: mode: token, access: dsm-admins, entry: admin-only. OpenList obtains its native API token with the unmodified admin token CLI while stopped, stores it package-private 0600 under a 0700 directory, and the gateway injects Authorization server-side after DSM admission; packaging never delivers it to the browser. All DSM admins share one upstream admin; token reset requires package restart; WebDAV keeps its own native credentials. The upstream admin token/settings APIs remain unchanged. Verify actual DSM admission/revocation separately. Use supported config, environment, argv or proxy adapters. Every App lifecycle phase runs as the package UID. For upstream fixed-UID data directories, currently provide a synology/ startup adapter or writable path without root/chown; replace it with the framework ownership mechanism when available. Preserve licenses and the full source/component inventory.
|
|
62
|
+
`;
|
|
63
|
+
writeFileSync(join(packaging, 'AGENTS.md'), readFileSync(join(packaging, 'AGENTS.md'), 'utf8') + guidance);
|
|
64
|
+
}
|
package/dist/init.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export interface InitOptions {
|
|
|
16
16
|
ui?: "plain" | "vue";
|
|
17
17
|
with?: string[];
|
|
18
18
|
from?: string;
|
|
19
|
+
upstreamFork?: boolean;
|
|
20
|
+
upstreamImage?: string;
|
|
21
|
+
healthPath?: string;
|
|
22
|
+
healthStatus?: number;
|
|
19
23
|
/** native-go: `install.sharedFolder` (select from the NAS, or create one). key defaults to "shared", permission to "rw". */
|
|
20
24
|
sharedFolder?: {
|
|
21
25
|
mode: "select" | "create";
|
package/dist/init.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateForkRecipe, writeForkScaffold } from "./init-upstream.js";
|
|
1
2
|
import { goChildEnv } from "./runtime/go-env.js";
|
|
2
3
|
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { parse, parseDocument } from "yaml";
|
|
@@ -112,6 +113,30 @@ function isTemplateBuildArtifact(path) {
|
|
|
112
113
|
return (name === "node_modules" || name === "dist" || name === ".go-results.json");
|
|
113
114
|
}
|
|
114
115
|
export function initFromTemplate(template, targetDir, options = {}) {
|
|
116
|
+
if (options.upstreamFork) {
|
|
117
|
+
if (template !== "compose" && template !== "native-go")
|
|
118
|
+
throw new Error("[INIT_OPTIONS_UNSUPPORTED] --upstream-fork supports compose or native-go.");
|
|
119
|
+
validateForkRecipe(template, options);
|
|
120
|
+
const packaging = join(targetDir, "synology");
|
|
121
|
+
if (existsSync(packaging))
|
|
122
|
+
throw new Error("[INIT_TARGET_NOT_EMPTY] synology/ already exists.");
|
|
123
|
+
initFromTemplate(template, packaging, {
|
|
124
|
+
...options,
|
|
125
|
+
packageId: options.packageId ?? defaultPackageId(targetDir),
|
|
126
|
+
displayName: options.displayName ?? defaultDisplayName(targetDir),
|
|
127
|
+
upstreamFork: false,
|
|
128
|
+
upstreamImage: undefined,
|
|
129
|
+
});
|
|
130
|
+
writeForkScaffold(packaging, template, options);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (options.upstreamImage)
|
|
134
|
+
throw new Error("[INIT_OPTIONS_UNSUPPORTED] Image recipe options require --upstream-fork.");
|
|
135
|
+
const hasHealth = options.healthPath !== undefined || options.healthStatus !== undefined;
|
|
136
|
+
if (hasHealth && (template !== "compose" || typeof options.healthPath !== "string" ||
|
|
137
|
+
!/^\/(?!\/)[^\s#\\]*$/.test(options.healthPath) || !Number.isInteger(options.healthStatus) ||
|
|
138
|
+
options.healthStatus < 200 || options.healthStatus > 299))
|
|
139
|
+
throw new Error("[INIT_HEALTH_INVALID] Compose health needs both --health-path /actual-endpoint and --health-status 2xx; redirects are not followed.");
|
|
115
140
|
if (!isTemplateName(template))
|
|
116
141
|
throw new Error(`[INIT_TEMPLATE_UNKNOWN] Unknown template: ${template}`);
|
|
117
142
|
const requested = capabilityNames(options.with ?? []);
|
|
@@ -261,6 +286,18 @@ export function initFromTemplate(template, targetDir, options = {}) {
|
|
|
261
286
|
}
|
|
262
287
|
if (template === "compose" && options.from)
|
|
263
288
|
importCompose(options.from, targetDir);
|
|
289
|
+
if (template === "compose") {
|
|
290
|
+
const path = join(targetDir, "dsm-package.yaml");
|
|
291
|
+
const doc = parseDocument(readFileSync(path, "utf8"));
|
|
292
|
+
if (doc.hasIn(["compose", "web"])) {
|
|
293
|
+
doc.deleteIn(["compose", "web", "health"]);
|
|
294
|
+
if (hasHealth)
|
|
295
|
+
doc.setIn(["compose", "web", "health"], { path: options.healthPath, status: options.healthStatus });
|
|
296
|
+
}
|
|
297
|
+
writeFileSync(path, String(doc));
|
|
298
|
+
const guidance = readFileSync(new URL("../assets/init-compose.md", import.meta.url), "utf8");
|
|
299
|
+
writeFileSync(join(targetDir, "AGENTS.md"), guidance);
|
|
300
|
+
}
|
|
264
301
|
if (template === "binary") {
|
|
265
302
|
mkdirSync(join(targetDir, "prebuilt"), { recursive: true });
|
|
266
303
|
for (const [arch, goarch] of [
|
|
@@ -356,7 +393,6 @@ function importCompose(from, targetDir) {
|
|
|
356
393
|
if (candidates.length === 1)
|
|
357
394
|
doc.setIn(["compose", "web"], {
|
|
358
395
|
...candidates[0],
|
|
359
|
-
health: { path: "/", status: 200 },
|
|
360
396
|
});
|
|
361
397
|
else {
|
|
362
398
|
doc.deleteIn(["compose", "web"]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Self-contained: the same function is embedded in the DSM window. True only
|
|
2
2
|
* when DSM refused a request because this method version is unavailable, the
|
|
3
|
-
* one refusal that may be retried with an older version. DSM 7.1.1
|
|
3
|
+
* one refusal that may be retried with an older version. DSM 7.1.1
|
|
4
4
|
* returns 104 for a version above maxVersion and 103 for a method absent at an
|
|
5
5
|
* existing version. Accepts the numeric code and the window's dsm_ form. */
|
|
6
6
|
export declare function versionUnsupported(code: unknown): boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Self-contained: the same function is embedded in the DSM window. True only
|
|
2
2
|
* when DSM refused a request because this method version is unavailable, the
|
|
3
|
-
* one refusal that may be retried with an older version. DSM 7.1.1
|
|
3
|
+
* one refusal that may be retried with an older version. DSM 7.1.1
|
|
4
4
|
* returns 104 for a version above maxVersion and 103 for a method absent at an
|
|
5
5
|
* existing version. Accepts the numeric code and the window's dsm_ form. */
|
|
6
6
|
export function versionUnsupported(code) {
|
|
@@ -1,27 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"0.
|
|
3
|
-
"x86_64": "efe409287770f6b70e7f8465e8767eeb2413b9ab8775020cf4dffb1555d7bf1f",
|
|
4
|
-
"aarch64": "d68c9889ed2391e918b9c278679ea0a01cd04425528c11090700a3b6341fc543",
|
|
5
|
-
"armv7": "9149a05efb9f2d7d86a400707099b3d657382b1c0dbad6588be8c500e90445cc",
|
|
6
|
-
"lifecycle": {
|
|
7
|
-
"x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
|
|
8
|
-
"aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
|
|
9
|
-
"armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
|
|
10
|
-
},
|
|
11
|
-
"goToolchain": "go1.26.9"
|
|
12
|
-
},
|
|
13
|
-
"0.15.1": {
|
|
14
|
-
"x86_64": "4f47fb43f6e60dbad064afb0bbec96dd009fa046670e2c4b590b128a855cb404",
|
|
15
|
-
"aarch64": "c903b09ad657a838f0c62b6688e65e9094572b22b969770fca92fcc3640a5364",
|
|
16
|
-
"armv7": "8178d90de7ea720530bb9496e50ed118086b0e57382d84675aadc68f482d219f",
|
|
17
|
-
"lifecycle": {
|
|
18
|
-
"x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
|
|
19
|
-
"aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
|
|
20
|
-
"armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
|
|
21
|
-
},
|
|
22
|
-
"goToolchain": "go1.26.9"
|
|
23
|
-
},
|
|
24
|
-
"0.15.2": {
|
|
2
|
+
"0.16.0": {
|
|
25
3
|
"x86_64": "4f47fb43f6e60dbad064afb0bbec96dd009fa046670e2c4b590b128a855cb404",
|
|
26
4
|
"aarch64": "c903b09ad657a838f0c62b6688e65e9094572b22b969770fca92fcc3640a5364",
|
|
27
5
|
"armv7": "8178d90de7ea720530bb9496e50ed118086b0e57382d84675aadc68f482d219f",
|
package/dist/skill.js
CHANGED
|
@@ -14,6 +14,7 @@ const SKILL_CATALOG = [
|
|
|
14
14
|
file: "reference/compose.md",
|
|
15
15
|
when: "Upstream Compose with the SDK planner, fixed identity and support boundary",
|
|
16
16
|
},
|
|
17
|
+
{ id: "upstream-fork", file: "reference/upstream-fork.md", when: "Package an upstream source fork without changing upstream files" },
|
|
17
18
|
{
|
|
18
19
|
id: "wrap",
|
|
19
20
|
file: "reference/wrap.md",
|
package/dist/standalone/build.js
CHANGED
|
@@ -10,7 +10,7 @@ import { resolveMinDsm, DEFAULT_MIN_DSM, isDsmAtLeast, PORTAL_EXECUTOR_MIN_DSM,
|
|
|
10
10
|
import { checkCgiBinary } from "./binary.js";
|
|
11
11
|
import { loadStandaloneManifest, } from "./manifest.js";
|
|
12
12
|
import { assembleStandaloneSpk, StandaloneBuildError } from "./spk.js";
|
|
13
|
-
import { buildComposeControl, lockComposeImages, dsmComposeProblem, } from "./compose.js";
|
|
13
|
+
import { buildComposeControl, buildComposeStorageInit, lockComposeImages, dsmComposeProblem, } from "./compose.js";
|
|
14
14
|
import { buildDaemonControl } from "./daemon.js";
|
|
15
15
|
import { buildIdentityGateway } from "./identity-gateway.js";
|
|
16
16
|
import { buildExecutor } from "../runtime/executor-build.js";
|
|
@@ -148,6 +148,7 @@ export async function buildStandalone(options) {
|
|
|
148
148
|
const daemonControl = await buildDaemonControl(manifest, arch, archWork, run);
|
|
149
149
|
const identityGateway = await buildIdentityGateway(manifest, arch, archWork, run);
|
|
150
150
|
const composeControl = await buildComposeControl(manifest, arch, archWork, run);
|
|
151
|
+
const composeStorageInit = await buildComposeStorageInit(manifest, arch, archWork, run);
|
|
151
152
|
// A portal needs the executor, so the executor's SDK carries the panel text.
|
|
152
153
|
const portalText = manifest.portal && executor
|
|
153
154
|
? portalCatalogs(sdkSource(executor.sdk).goDir)
|
|
@@ -160,6 +161,7 @@ export async function buildStandalone(options) {
|
|
|
160
161
|
daemonControl,
|
|
161
162
|
identityGateway,
|
|
162
163
|
composeControl,
|
|
164
|
+
composeStorageInit,
|
|
163
165
|
backgroundWorker,
|
|
164
166
|
executor,
|
|
165
167
|
app,
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
/** SDK planner errors accepted at this versioned tool boundary. */
|
|
2
2
|
export const COMPOSE_ERRORS = {
|
|
3
|
+
COMPOSE_STORAGE_INIT_INVALID: {
|
|
4
|
+
code: "COMPOSE_STORAGE_INIT_INVALID",
|
|
5
|
+
description: "The container storage initialization declaration is invalid.",
|
|
6
|
+
suggestion: "Declare private subdirectories on writable mounts and explicit upstream entrypoint/command argv; keep the service nonroot. The SDK initializer is supplied by the SPK.",
|
|
7
|
+
},
|
|
8
|
+
COMPOSE_STORAGE_NOT_WRITABLE: {
|
|
9
|
+
code: "COMPOSE_STORAGE_NOT_WRITABLE",
|
|
10
|
+
description: "The package UID cannot initialize or write its private container data.",
|
|
11
|
+
suggestion: "Use a writable mount base with a package-owned private subtree; inspect ownership, permissions, read-only storage, quota and free space. Do not add root or chown hooks.",
|
|
12
|
+
},
|
|
13
|
+
COMPOSE_STORAGE_COMMAND_INVALID: {
|
|
14
|
+
code: "COMPOSE_STORAGE_COMMAND_INVALID",
|
|
15
|
+
description: "The application command after storage preflight cannot be executed.",
|
|
16
|
+
suggestion: "Declare the actual upstream executable and arguments. Keep the upstream official image unchanged; the SPK supplies the SDK initializer.",
|
|
17
|
+
},
|
|
3
18
|
COMPOSE_SUBNET_STATE_INVALID: {
|
|
4
19
|
code: "COMPOSE_SUBNET_STATE_INVALID",
|
|
5
20
|
description: "The private ingress history is invalid or has foreign ownership.",
|