@synopackageland/cli 0.25.2 → 0.26.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 +7 -0
- package/README.md +10 -0
- package/dist/error-registry.js +16 -1
- package/dist/runtime/executor-digests.json +1 -1
- package/dist/runtime/sdk-source.js +1 -1
- package/dist/standalone/daemon-restart.d.ts +15 -0
- package/dist/standalone/daemon-restart.js +55 -0
- package/dist/standalone/daemon.d.ts +4 -0
- package/dist/standalone/daemon.js +13 -3
- package/dist/standalone/dsm-window.d.ts +2 -0
- package/dist/standalone/dsm-window.js +6 -2
- package/dist/standalone/inspect-spk.js +59 -5
- package/dist/standalone/manifest.d.ts +6 -0
- package/dist/standalone/manifest.js +33 -5
- package/dist/standalone/spk.js +14 -13
- package/package.json +2 -2
- package/schema/daemon-restart.v1.schema.json +16 -0
- package/skills/SKILL.md +6 -0
- package/skills/reference/portal.md +6 -0
- package/skills/reference/ui-vue.md +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.0 (2026-10-22)
|
|
4
|
+
|
|
5
|
+
- Support SDK/App 0.26.x and pin templates 0.26.0, retaining UI 0.14.3 and executor/v0.24.0 with unchanged protected payload digests.
|
|
6
|
+
- Add optional bounded `daemon.restart` crash recovery for native Apps; require SDK 0.26.0 or later, validate the policy and record it for inspection. Initial startup and intentional stops retain their existing behavior.
|
|
7
|
+
|
|
8
|
+
- Add `ui: false` for native Apps with `identity_gateway` and no SDK window. The main menu gets one url launcher to `sso/open.cgi` (new tab) and Package Center's Open goes to the same entry through INFO `adminurl`, as for Compose Apps. check refuses it without a gateway, with `console` or `portal`, or with capabilities whose approval or sign-in needs the SDK window (`STANDALONE_UI_NO_WINDOW_INVALID`); inspect verifies the single launcher and `adminurl`. Apps with `ui.dir` build unchanged.
|
|
9
|
+
|
|
3
10
|
## 0.25.2 (2026-10-22)
|
|
4
11
|
|
|
5
12
|
- Use templates 0.25.1 with the native Rust lockfile pinned to the public SDK 0.25.0 commit. SDK/App support and executor/lifecycle digests are unchanged.
|
package/README.md
CHANGED
|
@@ -278,6 +278,16 @@ sets it for every language. Omit `console` to keep one icon. check refuses
|
|
|
278
278
|
`console` on Compose, without `identity_gateway`, or with `ui.launch` or
|
|
279
279
|
`portal`. A cold DSM sign-in resumes through the url launcher.
|
|
280
280
|
|
|
281
|
+
A native App with `identity_gateway` whose only UI is the wrapped web app
|
|
282
|
+
declares `ui: false` instead of `ui.dir`. It gets no SDK window: the main menu
|
|
283
|
+
has one icon (`displayname`, a `type: "url"` launcher to `sso/open.cgi` that
|
|
284
|
+
opens a new tab), and Package Center's Open goes to the same entry through
|
|
285
|
+
INFO `adminurl`, as for a Compose App. The backend and ctl commands are
|
|
286
|
+
unchanged. check refuses `ui: false` without `identity_gateway`, with `console`
|
|
287
|
+
or `portal`, or with `capabilities.rootOps`, `delegate`, `webapi` or
|
|
288
|
+
`identity`, whose approval or sign-in is shown in the SDK window
|
|
289
|
+
(`STANDALONE_UI_NO_WINDOW_INVALID`). A missing `ui` is still an error.
|
|
290
|
+
|
|
281
291
|
Compose uses schema-2 `dsm-package.yaml` and `ui.launch`; the default is new-page.
|
|
282
292
|
Classless gateways use type url via the shared contract/app-launch resolver;
|
|
283
293
|
AppWindow wrappers use standalone. Headless projects have no launcher.
|
package/dist/error-registry.js
CHANGED
|
@@ -282,6 +282,16 @@ export const ERROR_REGISTRY = {
|
|
|
282
282
|
description: "The package daemon declaration does not match the fixed argv, environment and lifecycle contract.",
|
|
283
283
|
suggestion: "Check daemon.exec, the per-architecture targets, args, env, uninstall_args and integer-second timeouts; remove shell, user and custom conf.",
|
|
284
284
|
},
|
|
285
|
+
STANDALONE_DAEMON_RESTART_INVALID: {
|
|
286
|
+
code: "STANDALONE_DAEMON_RESTART_INVALID",
|
|
287
|
+
description: "daemon.restart does not match the bounded native crash-recovery policy.",
|
|
288
|
+
suggestion: "Use mode off or on-failure, integer delay/attempt/window limits, and max_seconds at least initial_seconds; omit or set limits to zero for SDK defaults and remove unknown fields.",
|
|
289
|
+
},
|
|
290
|
+
STANDALONE_DAEMON_RESTART_SDK_REQUIRED: {
|
|
291
|
+
code: "STANDALONE_DAEMON_RESTART_SDK_REQUIRED",
|
|
292
|
+
description: "The selected controller SDK version or source does not support daemon.restart.",
|
|
293
|
+
suggestion: "Upgrade the selected controller SDK to 0.26.0 or later and use matching sources, including a local SDK override; update the App SDK requirement, identity_gateway.sdk or the templates that supply the default.",
|
|
294
|
+
},
|
|
285
295
|
UNINSTALL_INVALID: {
|
|
286
296
|
code: "UNINSTALL_INVALID",
|
|
287
297
|
description: "The uninstall declaration does not match the fixed exec, argv, environment and timeout contract.",
|
|
@@ -485,7 +495,12 @@ export const ERROR_REGISTRY = {
|
|
|
485
495
|
STANDALONE_UI_INVALID: {
|
|
486
496
|
code: "STANDALONE_UI_INVALID",
|
|
487
497
|
description: "The ui configuration is invalid.",
|
|
488
|
-
suggestion: "Provide ui.dir.",
|
|
498
|
+
suggestion: "Provide ui.dir, or ui: false for a native identity_gateway App without an SDK window.",
|
|
499
|
+
},
|
|
500
|
+
STANDALONE_UI_NO_WINDOW_INVALID: {
|
|
501
|
+
code: "STANDALONE_UI_NO_WINDOW_INVALID",
|
|
502
|
+
description: "ui: false is declared where the App needs an SDK window.",
|
|
503
|
+
suggestion: "ui: false needs identity_gateway and no console, portal, capabilities.rootOps, delegate, webapi or identity; otherwise declare ui.dir.",
|
|
489
504
|
},
|
|
490
505
|
STANDALONE_UI_BUILD_INVALID: {
|
|
491
506
|
code: "STANDALONE_UI_BUILD_INVALID",
|
|
@@ -76,7 +76,7 @@ function goMod(backendDir) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
/** SDK releases that reuse an immutable executor release with identical payload bytes. */
|
|
79
|
-
const EXECUTOR_RELEASES = { '0.21.0': '0.20.0', '0.21.1': '0.20.0', '0.22.0': '0.20.0', '0.23.0': '0.20.0', '0.23.1': '0.20.0', '0.23.2': '0.20.0', '0.24.1': '0.24.0', '0.25.0': '0.24.0' };
|
|
79
|
+
const EXECUTOR_RELEASES = { '0.21.0': '0.20.0', '0.21.1': '0.20.0', '0.22.0': '0.20.0', '0.23.0': '0.20.0', '0.23.1': '0.20.0', '0.23.2': '0.20.0', '0.24.1': '0.24.0', '0.25.0': '0.24.0', '0.26.0': '0.24.0' };
|
|
80
80
|
/**
|
|
81
81
|
* The module directories of one SDK version. SYNOPKGLAND_SDK_DIR names a
|
|
82
82
|
* local sdk checkout (development and gates only); otherwise modules come from
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StandaloneError } from "./manifest.js";
|
|
2
|
+
export declare const DAEMON_RESTART_MIN_SDK = "0.26.0";
|
|
3
|
+
export interface DaemonRestart {
|
|
4
|
+
mode: "off" | "on-failure";
|
|
5
|
+
initial_seconds: number;
|
|
6
|
+
max_seconds: number;
|
|
7
|
+
max_attempts: number;
|
|
8
|
+
window_seconds: number;
|
|
9
|
+
}
|
|
10
|
+
/** Normalize exactly the SDK's zero/omitted defaults; omission itself stays off. */
|
|
11
|
+
export declare function readDaemonRestart(value: unknown, fail: (error: StandaloneError) => void): DaemonRestart | undefined;
|
|
12
|
+
export declare function daemonRestartSdkSupported(sdk: string): boolean;
|
|
13
|
+
export declare function daemonRestartSdkProblem(sdk: string): string | undefined;
|
|
14
|
+
/** Development overrides must contain the decoder as well as the restart code. */
|
|
15
|
+
export declare function requireDaemonRestartSource(dir: string, sdk: string, policy?: DaemonRestart): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Ajv } from "ajv";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { StandaloneBuildError } from "./spk.js";
|
|
5
|
+
export const DAEMON_RESTART_MIN_SDK = "0.26.0";
|
|
6
|
+
let validator;
|
|
7
|
+
function restartValidator() {
|
|
8
|
+
return validator ??= new Ajv().compile(JSON.parse(readFileSync(new URL("../../schema/daemon-restart.v1.schema.json", import.meta.url), "utf8")));
|
|
9
|
+
}
|
|
10
|
+
/** Normalize exactly the SDK's zero/omitted defaults; omission itself stays off. */
|
|
11
|
+
export function readDaemonRestart(value, fail) {
|
|
12
|
+
if (value === undefined)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (restartValidator()(value)) {
|
|
15
|
+
const raw = value;
|
|
16
|
+
const policy = {
|
|
17
|
+
mode: raw.mode,
|
|
18
|
+
initial_seconds: raw.initial_seconds || 1,
|
|
19
|
+
max_seconds: raw.max_seconds || 30,
|
|
20
|
+
max_attempts: raw.max_attempts || 5,
|
|
21
|
+
window_seconds: raw.window_seconds || 300,
|
|
22
|
+
};
|
|
23
|
+
if (policy.max_seconds >= policy.initial_seconds)
|
|
24
|
+
return policy;
|
|
25
|
+
}
|
|
26
|
+
fail({
|
|
27
|
+
code: "STANDALONE_DAEMON_RESTART_INVALID",
|
|
28
|
+
message: "daemon.restart requires mode off or on-failure; delays 1–3600s (max >= initial), attempts 1–100 and window 1–86400s. Omitted or zero limits use SDK defaults; unknown fields are rejected.",
|
|
29
|
+
});
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
export function daemonRestartSdkSupported(sdk) {
|
|
33
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(sdk);
|
|
34
|
+
return Boolean(match && (Number(match[1]) > 0 || Number(match[2]) >= 26));
|
|
35
|
+
}
|
|
36
|
+
export function daemonRestartSdkProblem(sdk) {
|
|
37
|
+
if (!daemonRestartSdkSupported(sdk))
|
|
38
|
+
return `daemon.restart requires SDK ${DAEMON_RESTART_MIN_SDK} or later; selected SDK ${sdk}. Upgrade the App's SDK pin, identity_gateway.sdk or the templates supplying the default controller SDK before rebuilding.`;
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/** Development overrides must contain the decoder as well as the restart code. */
|
|
42
|
+
export function requireDaemonRestartSource(dir, sdk, policy) {
|
|
43
|
+
if (!policy)
|
|
44
|
+
return;
|
|
45
|
+
let main = "", restart = "";
|
|
46
|
+
try {
|
|
47
|
+
main = readFileSync(join(dir, "daemonctl/main.go"), "utf8");
|
|
48
|
+
restart = readFileSync(join(dir, "daemonctl/restart.go"), "utf8");
|
|
49
|
+
}
|
|
50
|
+
catch { /* Fail below before compiling or packaging a mismatched runtime. */ }
|
|
51
|
+
const problem = daemonRestartSdkProblem(sdk);
|
|
52
|
+
if (problem || !/\bRestart\s+\*restartPolicy\s+`json:"restart,omitempty"`/.test(main) ||
|
|
53
|
+
!/^func normalizeRestart\(/m.test(restart) || !/^type restartBudget struct\s*\{/m.test(restart))
|
|
54
|
+
throw new StandaloneBuildError("STANDALONE_DAEMON_RESTART_SDK_REQUIRED", problem ?? `Selected SDK ${sdk} source does not support daemon.restart; use matching SDK ${DAEMON_RESTART_MIN_SDK} or later sources, including for a local development override.`);
|
|
55
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
|
|
2
2
|
import { NODE_PACKAGES } from "./bridge-backend.js";
|
|
3
3
|
import { type DaemonIngress } from "./daemon-ingress.js";
|
|
4
|
+
import { type DaemonRestart } from "./daemon-restart.js";
|
|
4
5
|
import type { CommandRunner } from "./build.js";
|
|
5
6
|
import type { StandaloneError, StandaloneManifest } from "./manifest.js";
|
|
6
7
|
export interface Daemon {
|
|
8
|
+
/** Optional bounded recovery; omitted means off. SDK 0.26.0 or later. */
|
|
9
|
+
restart?: DaemonRestart;
|
|
7
10
|
/** Fixed foreground executable relative to daemon/payload. */
|
|
8
11
|
exec: string;
|
|
9
12
|
/** Architecture-specific project directories, copied without links. */
|
|
@@ -46,6 +49,7 @@ export declare const envKey: (k: string) => boolean;
|
|
|
46
49
|
/** A relative path to a fixed executable, without `.` or `..` parts. */
|
|
47
50
|
export declare const relativeExec: (v: unknown) => v is string;
|
|
48
51
|
export declare function readDaemon(value: unknown, root: string, arches: NativeArchFamily[], fail: (error: StandaloneError) => void): Daemon | undefined;
|
|
52
|
+
export declare function daemonSdkVersion(manifest: Pick<StandaloneManifest, "backend" | "identityGateway">): string;
|
|
49
53
|
export declare function daemonConfig(manifest: StandaloneManifest): string;
|
|
50
54
|
/**
|
|
51
55
|
* The argv and environment daemon/control runs: a Node daemon execs the
|
|
@@ -8,6 +8,7 @@ import { templateSdkVersion } from "../template-paths.js";
|
|
|
8
8
|
import { checkCgiBinary } from "./binary.js";
|
|
9
9
|
import { NODE_PACKAGES } from "./bridge-backend.js";
|
|
10
10
|
import { readDaemonIngress, ingressEnv } from "./daemon-ingress.js";
|
|
11
|
+
import { readDaemonRestart, requireDaemonRestartSource } from "./daemon-restart.js";
|
|
11
12
|
import { composeDaemon, composeNetwork } from "./compose.js";
|
|
12
13
|
import { gatewayDaemon } from "./identity-gateway.js";
|
|
13
14
|
import { StandaloneBuildError } from "./spk.js";
|
|
@@ -41,6 +42,7 @@ const fields = new Set([
|
|
|
41
42
|
"uninstall_args",
|
|
42
43
|
"node",
|
|
43
44
|
"ingress",
|
|
45
|
+
"restart",
|
|
44
46
|
]);
|
|
45
47
|
const record = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
46
48
|
/** A fixed argv or env value: only the package's own directories expand. */
|
|
@@ -110,6 +112,9 @@ export function readDaemon(value, root, arches, fail) {
|
|
|
110
112
|
const timeouts = readLifecycleTimeouts(value.timeouts, fail);
|
|
111
113
|
if (!timeouts)
|
|
112
114
|
return undefined;
|
|
115
|
+
const restart = readDaemonRestart(value.restart, fail);
|
|
116
|
+
if (value.restart !== undefined && !restart)
|
|
117
|
+
return undefined;
|
|
113
118
|
const node = readDaemonNode(value.node, fail);
|
|
114
119
|
if (value.node !== undefined && !node)
|
|
115
120
|
return undefined;
|
|
@@ -129,6 +134,7 @@ export function readDaemon(value, root, arches, fail) {
|
|
|
129
134
|
timeouts: timeouts,
|
|
130
135
|
...(node ? { node } : {}),
|
|
131
136
|
...(ingress ? { ingress } : {}),
|
|
137
|
+
...(restart ? { restart } : {}),
|
|
132
138
|
};
|
|
133
139
|
}
|
|
134
140
|
function readDaemonNode(value, fail) {
|
|
@@ -152,13 +158,13 @@ function readDaemonNode(value, fail) {
|
|
|
152
158
|
}
|
|
153
159
|
return { package: pkg, ...(min ? { min_version: min } : {}) };
|
|
154
160
|
}
|
|
155
|
-
function daemonSdkVersion(manifest) {
|
|
161
|
+
export function daemonSdkVersion(manifest) {
|
|
156
162
|
return manifest.identityGateway?.sdk ??
|
|
157
163
|
(manifest.backend.kind === "go" ? appSdkVersion(manifest.backend) : undefined) ??
|
|
158
164
|
templateSdkVersion();
|
|
159
165
|
}
|
|
160
166
|
export function daemonConfig(manifest) {
|
|
161
|
-
const { exec, args, uninstall_args, env, timeouts } = manifest.compose
|
|
167
|
+
const { exec, args, uninstall_args, env, timeouts, restart } = manifest.compose
|
|
162
168
|
? composeDaemon(manifest)
|
|
163
169
|
: manifest.identityGateway
|
|
164
170
|
? gatewayDaemon(manifest.daemon)
|
|
@@ -168,6 +174,7 @@ export function daemonConfig(manifest) {
|
|
|
168
174
|
exec,
|
|
169
175
|
args,
|
|
170
176
|
...(uninstall_args ? { uninstall_args } : {}),
|
|
177
|
+
...(restart ? { restart } : {}),
|
|
171
178
|
env,
|
|
172
179
|
timeouts,
|
|
173
180
|
...(runtimeBudgetSdkSupported(daemonSdkVersion(manifest))
|
|
@@ -193,6 +200,9 @@ export async function buildDaemonControl(manifest, arch, work, run) {
|
|
|
193
200
|
if (!manifest.daemon && !(manifest.compose && manifest.identityGateway))
|
|
194
201
|
return undefined;
|
|
195
202
|
const out = join(work, `daemon-control-${arch}`);
|
|
203
|
+
const sdk = daemonSdkVersion(manifest);
|
|
204
|
+
const source = runtimeBudgetSource(sdkSource(sdk).goDir, manifest);
|
|
205
|
+
requireDaemonRestartSource(source, sdk, manifest.daemon?.restart);
|
|
196
206
|
const result = await run("go", [
|
|
197
207
|
"build",
|
|
198
208
|
"-trimpath",
|
|
@@ -203,7 +213,7 @@ export async function buildDaemonControl(manifest, arch, work, run) {
|
|
|
203
213
|
out,
|
|
204
214
|
"./daemonctl",
|
|
205
215
|
], {
|
|
206
|
-
cwd:
|
|
216
|
+
cwd: source,
|
|
207
217
|
env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }),
|
|
208
218
|
});
|
|
209
219
|
if (result.missing || result.status !== 0)
|
|
@@ -25,6 +25,8 @@ export declare function windowScriptName(manifest: Pick<StandaloneManifest, "bui
|
|
|
25
25
|
/** The launcher fields an entry mode fixes; inspect checks a built SPK against them. */
|
|
26
26
|
export declare function entryLauncherAccess(entry: EntryMode): Record<string, unknown>;
|
|
27
27
|
export declare function appTitle(manifest: StandaloneManifest): AppTitle;
|
|
28
|
+
/** Whether the App ships the SDK window; Compose and `ui: false` Apps open the gateway instead. */
|
|
29
|
+
export declare function hasSdkWindow(manifest: Pick<StandaloneManifest, "compose" | "sdkWindow">): boolean;
|
|
28
30
|
/** The DSM app class whose launch opens the SDK window: the console's when the App declares one. */
|
|
29
31
|
export declare function windowAppId(manifest: Pick<StandaloneManifest, "package" | "console">): string;
|
|
30
32
|
/**
|
|
@@ -74,7 +74,7 @@ function launcherAccess(manifest) {
|
|
|
74
74
|
return manifest.entry
|
|
75
75
|
? entryLauncherAccess(manifest.entry)
|
|
76
76
|
: {
|
|
77
|
-
allUsers: manifest.compose || manifest.console
|
|
77
|
+
allUsers: manifest.compose || manifest.console || !hasSdkWindow(manifest)
|
|
78
78
|
? manifest.identityGateway?.access !== "dsm-admins"
|
|
79
79
|
: permitsOrdinaryUsers(manifest.apiPermissions),
|
|
80
80
|
};
|
|
@@ -107,6 +107,10 @@ function displayNameCatalogs(manifest) {
|
|
|
107
107
|
}
|
|
108
108
|
return catalogs;
|
|
109
109
|
}
|
|
110
|
+
/** Whether the App ships the SDK window; Compose and `ui: false` Apps open the gateway instead. */
|
|
111
|
+
export function hasSdkWindow(manifest) {
|
|
112
|
+
return !manifest.compose && manifest.sdkWindow !== false;
|
|
113
|
+
}
|
|
110
114
|
/** The DSM app class whose launch opens the SDK window: the console's when the App declares one. */
|
|
111
115
|
export function windowAppId(manifest) {
|
|
112
116
|
return `SYNO.SDS.${manifest.package}.${manifest.console ? "Console" : "Instance"}`;
|
|
@@ -128,7 +132,7 @@ export function renderUiConfig(manifest, icons = packageIcons(manifest.icon), lo
|
|
|
128
132
|
const icon = windowIconTemplate(icons);
|
|
129
133
|
const ns = `SYNO.SDS.${manifest.package}`;
|
|
130
134
|
const titleFields = uiTitleFields(appTitle(manifest));
|
|
131
|
-
if (manifest
|
|
135
|
+
if (!hasSdkWindow(manifest)) {
|
|
132
136
|
if (!manifest.identityGateway)
|
|
133
137
|
return "{}\n";
|
|
134
138
|
return (JSON.stringify({
|
|
@@ -23,6 +23,7 @@ import { ELF_MACHINE } from "./binary.js";
|
|
|
23
23
|
import { CGI_PAYLOAD_PATH, entryLauncherAccess } from "./dsm-window.js";
|
|
24
24
|
import { daemonConfig, readDaemon, readLifecycleTimeouts, renderNodeLauncher, nodeDependency, NODE_LAUNCHER } from "./daemon.js";
|
|
25
25
|
import { DAEMON_INGRESS_CONFIG, daemonIngressMinDsmProblem, readDaemonIngress, renderDaemonIngressConfig } from "./daemon-ingress.js";
|
|
26
|
+
import { daemonRestartSdkProblem, readDaemonRestart } from "./daemon-restart.js";
|
|
26
27
|
import { ROOT_SCRIPT_HINTS } from "./root-ops.js";
|
|
27
28
|
import { DELETE_STATE_KEY } from "../build-spk/uninstall-wizard.js";
|
|
28
29
|
import { readUninstallRecord, stateItemTexts } from "./uninstall.js";
|
|
@@ -114,7 +115,9 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
114
115
|
new Set(raw.capabilities).size !== raw.capabilities.length ||
|
|
115
116
|
(raw.entry !== undefined &&
|
|
116
117
|
!["admin-only", "app-privilege"].includes(raw.entry)) ||
|
|
117
|
-
(raw.console !== undefined && raw.console !== true)
|
|
118
|
+
(raw.console !== undefined && raw.console !== true) ||
|
|
119
|
+
(raw.window !== undefined &&
|
|
120
|
+
(raw.window !== false || raw.console !== undefined)))
|
|
118
121
|
throw new Error();
|
|
119
122
|
if (raw.lifecycleTimeouts !== undefined && (!readLifecycleTimeouts(raw.lifecycleTimeouts, () => { }) || Object.keys(raw.lifecycleTimeouts).length !== 4))
|
|
120
123
|
throw new Error();
|
|
@@ -126,6 +129,9 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
126
129
|
findings.push("sdk-package.json does not match the schema 2 capability inventory.");
|
|
127
130
|
}
|
|
128
131
|
}
|
|
132
|
+
const daemonRestart = recoverDaemonRestart(metadata.daemon_restart, metadata.daemon_sdk, findings);
|
|
133
|
+
if (metadata.daemon_restart !== undefined && (metadata.compose !== undefined || !paths.has("daemon/control")))
|
|
134
|
+
findings.push("DAEMON_RESTART_INVALID daemon restart requires a native daemon controller, without Compose supervision");
|
|
129
135
|
const identityGateway = await inspectProviderOnlyAdmission(dir, paths, metadata.provider_only_oidc, findings);
|
|
130
136
|
const installSettings = await inspectInstallSettings(dir, payload, metadata, findings);
|
|
131
137
|
if (metadata.compose !== undefined) {
|
|
@@ -210,7 +216,7 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
210
216
|
if (standalone && portal)
|
|
211
217
|
findings.push(...inspectPortalInfo(info, hasExecutor));
|
|
212
218
|
const daemon = standalone
|
|
213
|
-
? await checkDaemon(dir, info, paths, payload, findings, hasExecutor, background, combined, nativeGateway, uninstallHook?.uninstall, { ingress: daemonIngress, node: daemonNode })
|
|
219
|
+
? await checkDaemon(dir, info, paths, payload, findings, hasExecutor, background, combined, nativeGateway, uninstallHook?.uninstall, { ingress: daemonIngress, node: daemonNode, restart: daemonRestart })
|
|
214
220
|
: undefined;
|
|
215
221
|
if (background)
|
|
216
222
|
checkBackground(dir, info, payload, findings, hasExecutor, daemon, kind !== "native-go", metadata.lifecycleTimeouts, nativeGateway);
|
|
@@ -276,7 +282,7 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
276
282
|
}
|
|
277
283
|
if (metadata.entry)
|
|
278
284
|
await checkEntryLauncher(dir, info.package, metadata.entry, findings);
|
|
279
|
-
const launch = await checkLaunchConfiguration(dir, info, standalone, metadata.launch, portal?.alias ?? executor?.policy.portal?.alias, findings, metadata.console === true);
|
|
285
|
+
const launch = await checkLaunchConfiguration(dir, info, standalone, metadata.launch, portal?.alias ?? executor?.policy.portal?.alias, findings, metadata.console === true, metadata.window === false);
|
|
280
286
|
const loginStyle = standalone
|
|
281
287
|
? await inspectLoginStyle(dir, info.package, Boolean(portal || executor?.policy.portal), findings)
|
|
282
288
|
: undefined;
|
|
@@ -340,7 +346,7 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
340
346
|
const LAUNCHER_TYPE_MISMATCH = "LAUNCHER_TYPE_MISMATCH";
|
|
341
347
|
const LAUNCHER_PORTAL_MISMATCH = "LAUNCHER_PORTAL_MISMATCH";
|
|
342
348
|
/** Compare the actual launcher and Portal fields to the packaged declaration. */
|
|
343
|
-
async function checkLaunchConfiguration(dir, info, native, declared, alias, findings, console = false) {
|
|
349
|
+
async function checkLaunchConfiguration(dir, info, native, declared, alias, findings, console = false, windowless = false) {
|
|
344
350
|
const target = join(dir, "launch-inspection");
|
|
345
351
|
mkdirSync(target);
|
|
346
352
|
await tar.x({
|
|
@@ -395,6 +401,10 @@ async function checkLaunchConfiguration(dir, info, native, declared, alias, find
|
|
|
395
401
|
checkConsoleLaunchers(target, info, native, findings);
|
|
396
402
|
return launch;
|
|
397
403
|
}
|
|
404
|
+
if (windowless) {
|
|
405
|
+
checkGatewayOnlyLauncher(target, info, native, findings);
|
|
406
|
+
return launch;
|
|
407
|
+
}
|
|
398
408
|
const type = dsmLauncherType(launch, native);
|
|
399
409
|
if (!launcher ||
|
|
400
410
|
launcher.type !== type ||
|
|
@@ -443,6 +453,36 @@ function checkConsoleLaunchers(target, info, native, findings) {
|
|
|
443
453
|
findings.push(`${LAUNCHER_TYPE_MISMATCH} console requires a url launcher at ${ns}.Instance to sso/open.cgi, an administrator-only app launcher at ${ns}.Console and INFO dsmapplaunchname=${ns}.Console; rebuild with synopkgland build`);
|
|
444
454
|
}
|
|
445
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* A `ui: false` App's ui/config: one url launcher at `.Instance` to the
|
|
458
|
+
* gateway, which INFO's adminurl also opens for Package Center's Open, and
|
|
459
|
+
* no SDK window.
|
|
460
|
+
*/
|
|
461
|
+
function checkGatewayOnlyLauncher(target, info, native, findings) {
|
|
462
|
+
const ns = `SYNO.SDS.${info.package}`;
|
|
463
|
+
const entry = `/webman/3rdparty/${info.package}/sso/open.cgi`;
|
|
464
|
+
let config = {};
|
|
465
|
+
try {
|
|
466
|
+
config = JSON.parse(readFileSync(join(target, "ui/config"), "utf8"));
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
/* reported below as a missing launcher */
|
|
470
|
+
}
|
|
471
|
+
const sections = Object.keys(config);
|
|
472
|
+
const instance = config[".url"]?.[`${ns}.Instance`];
|
|
473
|
+
if (!native ||
|
|
474
|
+
sections.length !== 1 ||
|
|
475
|
+
Object.keys(config[".url"] ?? {}).length !== 1 ||
|
|
476
|
+
instance?.type !== "url" ||
|
|
477
|
+
instance.url !== entry ||
|
|
478
|
+
instance.appWindow !== undefined ||
|
|
479
|
+
info.dsmuidir !== "ui" ||
|
|
480
|
+
info.dsmappname !== `${ns}.Instance` ||
|
|
481
|
+
info.dsmapplaunchname !== undefined ||
|
|
482
|
+
info.adminurl !== entry) {
|
|
483
|
+
findings.push(`${LAUNCHER_TYPE_MISMATCH} ui: false requires only a url launcher at ${ns}.Instance to sso/open.cgi and INFO adminurl to the same entry; rebuild with synopkgland build`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
446
486
|
/** The launcher in ui/config must carry exactly the access the declared entry mode fixes. */
|
|
447
487
|
async function checkEntryLauncher(dir, pkg, entry, findings) {
|
|
448
488
|
const target = join(dir, "entry-inspection");
|
|
@@ -877,15 +917,19 @@ async function checkDaemon(dir, info, paths, payload, findings, executor, backgr
|
|
|
877
917
|
"timeouts",
|
|
878
918
|
"operation_seconds",
|
|
879
919
|
"uninstall_args",
|
|
920
|
+
"restart",
|
|
880
921
|
...(combined ? ["network"] : []),
|
|
881
922
|
].includes(key)))
|
|
882
923
|
throw new Error("package/config mismatch");
|
|
924
|
+
if (!isDeepStrictEqual(config.restart, extras.restart))
|
|
925
|
+
throw new Error("daemon restart differs from its build declaration");
|
|
883
926
|
const daemon = readDaemon({
|
|
884
927
|
exec: config.exec,
|
|
885
928
|
args: config.args,
|
|
886
929
|
uninstall_args: config.uninstall_args,
|
|
887
930
|
env: config.env,
|
|
888
931
|
timeouts: config.timeouts,
|
|
932
|
+
restart: config.restart,
|
|
889
933
|
targets: { x86_64: "payload" },
|
|
890
934
|
}, dir, ["x86_64"], () => { });
|
|
891
935
|
if (!daemon)
|
|
@@ -947,8 +991,18 @@ async function checkDaemon(dir, info, paths, payload, findings, executor, backgr
|
|
|
947
991
|
return daemon;
|
|
948
992
|
}
|
|
949
993
|
catch {
|
|
950
|
-
findings.push("daemon/config.json or the payload does not match the fixed daemon contract");
|
|
994
|
+
findings.push("daemon/config.json (including restart) or the payload does not match the fixed daemon contract");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function recoverDaemonRestart(value, sdk, findings) {
|
|
998
|
+
if (value === undefined && sdk === undefined)
|
|
999
|
+
return undefined;
|
|
1000
|
+
const policy = readDaemonRestart(value, () => { });
|
|
1001
|
+
if (!policy || !isDeepStrictEqual(policy, value) || typeof sdk !== "string" || daemonRestartSdkProblem(sdk)) {
|
|
1002
|
+
findings.push("DAEMON_RESTART_INVALID sdk-package.json must record a normalized daemon restart policy and SDK 0.26.0 or later");
|
|
1003
|
+
return undefined;
|
|
951
1004
|
}
|
|
1005
|
+
return policy;
|
|
952
1006
|
}
|
|
953
1007
|
/** INFO retains the localized title inputs used by build's ui/config renderer. */
|
|
954
1008
|
function archivedInfoTranslations(info) {
|
|
@@ -100,6 +100,12 @@ export interface StandaloneManifest {
|
|
|
100
100
|
entry?: EntryMode;
|
|
101
101
|
/** `portal`: registers the DSM Application Portal entry; an administrator activates it. */
|
|
102
102
|
portal?: PortalDeclaration;
|
|
103
|
+
/**
|
|
104
|
+
* `ui: false`: a native identity_gateway App with no SDK window. Its one
|
|
105
|
+
* main-menu icon and Package Center's Open both go to the gateway's web UI,
|
|
106
|
+
* as for a Compose App. Absent when the App has a `ui` directory.
|
|
107
|
+
*/
|
|
108
|
+
sdkWindow?: false;
|
|
103
109
|
/**
|
|
104
110
|
* `console`: the main menu gets two icons, the gateway's native UI in a new
|
|
105
111
|
* tab and the SDK UI in an administrator-only DSM window titled `title`
|
|
@@ -10,7 +10,8 @@ import { isNativeArchFamily, } from "../build-spk/dsm-arch.js";
|
|
|
10
10
|
import { checkGlossary, loadCatalogs, readInfoTranslations, } from "./i18n.js";
|
|
11
11
|
import { loadRootOps, readRootSettings, rootBinaryProblem, ROOT_OPS_DIR, } from "./root-ops.js";
|
|
12
12
|
import { readComposeApp } from "./compose.js";
|
|
13
|
-
import { readDaemon, readLifecycleTimeouts } from "./daemon.js";
|
|
13
|
+
import { daemonSdkVersion, readDaemon, readLifecycleTimeouts } from "./daemon.js";
|
|
14
|
+
import { daemonRestartSdkProblem } from "./daemon-restart.js";
|
|
14
15
|
import { daemonIngressMinDsmProblem } from "./daemon-ingress.js";
|
|
15
16
|
import { readIdentityGateway, } from "./identity-gateway.js";
|
|
16
17
|
import { readUninstall } from "./uninstall.js";
|
|
@@ -283,6 +284,11 @@ function readManifest(raw, c) {
|
|
|
283
284
|
if (problem)
|
|
284
285
|
c.fail("STANDALONE_DAEMON_INGRESS_MIN_DSM", problem);
|
|
285
286
|
}
|
|
287
|
+
if (daemon?.restart && backend) {
|
|
288
|
+
const problem = daemonRestartSdkProblem(daemonSdkVersion({ backend, identityGateway }));
|
|
289
|
+
if (problem)
|
|
290
|
+
c.fail("STANDALONE_DAEMON_RESTART_SDK_REQUIRED", problem);
|
|
291
|
+
}
|
|
286
292
|
if ((daemon?.ingress || daemon?.node) && raw.identity_gateway !== undefined)
|
|
287
293
|
c.fail("STANDALONE_DAEMON_INVALID", "daemon.node and daemon.ingress run the App's own daemon; identity_gateway runs its gateway as the daemon. Use one or the other.");
|
|
288
294
|
if (compose && identityGateway && compose.sdk !== identityGateway.sdk)
|
|
@@ -338,7 +344,10 @@ function readManifest(raw, c) {
|
|
|
338
344
|
}
|
|
339
345
|
permissions.policy = floored.policy;
|
|
340
346
|
}
|
|
341
|
-
const
|
|
347
|
+
const noWindow = raw.compose === undefined && raw.ui === false;
|
|
348
|
+
if (noWindow)
|
|
349
|
+
readNoWindow(raw, capabilities, c);
|
|
350
|
+
const ui = raw.compose !== undefined || noWindow
|
|
342
351
|
? {
|
|
343
352
|
dir: c.root,
|
|
344
353
|
...(isRecord(raw.ui) && raw.ui.launch !== undefined
|
|
@@ -347,8 +356,8 @@ function readManifest(raw, c) {
|
|
|
347
356
|
}
|
|
348
357
|
: readUi(raw.ui, c);
|
|
349
358
|
if (raw.compose !== undefined &&
|
|
350
|
-
|
|
351
|
-
Object.keys(raw.ui).some((k) => k !== "launch"))
|
|
359
|
+
raw.ui !== undefined &&
|
|
360
|
+
(!isRecord(raw.ui) || Object.keys(raw.ui).some((k) => k !== "launch")))
|
|
352
361
|
c.fail("STANDALONE_UI_INVALID", "Compose uses the SDK gateway; ui accepts only launch.");
|
|
353
362
|
if (raw.compose !== undefined &&
|
|
354
363
|
ui?.launch !== undefined &&
|
|
@@ -466,6 +475,7 @@ function readManifest(raw, c) {
|
|
|
466
475
|
...(entry ? { entry } : {}),
|
|
467
476
|
...(portal ? { portal } : {}),
|
|
468
477
|
...(dsmConsole ? { console: dsmConsole } : {}),
|
|
478
|
+
...(noWindow ? { sdkWindow: false } : {}),
|
|
469
479
|
...(icon ? { icon } : {}),
|
|
470
480
|
...(loginStyle ? { loginStyle } : {}),
|
|
471
481
|
...(appScripts ? { appScripts } : {}),
|
|
@@ -539,6 +549,24 @@ function readConsole(raw, launch, c) {
|
|
|
539
549
|
c.fail("STANDALONE_CONSOLE_PORTAL", "console and portal both claim the App's SDK UI; declare one of them.");
|
|
540
550
|
return value.title === undefined ? {} : { title: value.title };
|
|
541
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* `ui: false`: the gateway's web UI is the App's only UI, so the App needs
|
|
554
|
+
* identity_gateway, and nothing may need the SDK window: a console or portal
|
|
555
|
+
* is that window, and root, delegate and WebAPI approvals and the standalone
|
|
556
|
+
* sign-in page are shown in it.
|
|
557
|
+
*/
|
|
558
|
+
function readNoWindow(raw, capabilities, c) {
|
|
559
|
+
const fail = (why) => c.fail("STANDALONE_UI_NO_WINDOW_INVALID", `ui: false ${why}`);
|
|
560
|
+
if (raw.identity_gateway === undefined)
|
|
561
|
+
fail("needs identity_gateway: its web UI is what the main-menu icon and Package Center's Open start.");
|
|
562
|
+
if (raw.console !== undefined)
|
|
563
|
+
fail("leaves no SDK window for the console; remove console, or declare ui.dir.");
|
|
564
|
+
if (raw.portal !== undefined)
|
|
565
|
+
fail("leaves no SDK window for the portal; remove portal, or declare ui.dir.");
|
|
566
|
+
for (const name of ["rootOps", "delegate", "webapi", "identity"])
|
|
567
|
+
if (capabilities[name])
|
|
568
|
+
fail(`cannot use capabilities.${name}: its approval or sign-in is shown in the SDK window; declare ui.dir.`);
|
|
569
|
+
}
|
|
542
570
|
/** `login_style`: a portal's colour, wallpaper (with its thumbnail) and title image, each optional. */
|
|
543
571
|
function readLoginStyle(value, portal, c) {
|
|
544
572
|
const invalid = (message) => c.fail("STANDALONE_LOGIN_STYLE_INVALID", message);
|
|
@@ -736,7 +764,7 @@ function goReadsTimeout(backend, c) {
|
|
|
736
764
|
}
|
|
737
765
|
function readUi(value, c) {
|
|
738
766
|
if (!isRecord(value)) {
|
|
739
|
-
return c.fail("STANDALONE_UI_INVALID", "ui must contain dir.");
|
|
767
|
+
return c.fail("STANDALONE_UI_INVALID", "ui must contain dir; a native identity_gateway App without an SDK window declares ui: false.");
|
|
740
768
|
}
|
|
741
769
|
if (value.launch !== undefined && !isWebLaunchMode(value.launch))
|
|
742
770
|
return c.fail("STANDALONE_UI_LAUNCH_INVALID", "ui.launch must be dsm-window or new-page. Application Portal is a separate opt-in.");
|
package/dist/standalone/spk.js
CHANGED
|
@@ -21,7 +21,7 @@ import { renderStandaloneScript } from "./lifecycle.js";
|
|
|
21
21
|
import { APP_SCRIPTS_DIR, APP_SCRIPTS_SPK_DIR } from "./app-scripts.js";
|
|
22
22
|
import { codedMessage } from "../errors.js";
|
|
23
23
|
import { DSM_LANGS, LOCALES } from "./i18n.js";
|
|
24
|
-
import { appTitle, CGI_PAYLOAD_PATH, renderUiConfig, renderWindowScript, windowScriptName, } from "./dsm-window.js";
|
|
24
|
+
import { appTitle, CGI_PAYLOAD_PATH, hasSdkWindow, renderUiConfig, renderWindowScript, windowScriptName, } from "./dsm-window.js";
|
|
25
25
|
import { writeUiTexts } from "../build-spk/ui-texts.js";
|
|
26
26
|
import { loginStyleAssets } from "../build-spk/login-style.js";
|
|
27
27
|
import { copyStandaloneNotices, copyNoticeTree } from "../build-spk/notices.js";
|
|
@@ -29,7 +29,7 @@ import { copyAppTree } from "./bridge-app.js";
|
|
|
29
29
|
import { isBridgeBackend } from "./bridge-backend.js";
|
|
30
30
|
import { hostScriptName, renderHostAdapterScript } from "./host-adapter.js";
|
|
31
31
|
import { writeComposePayload } from "./compose.js";
|
|
32
|
-
import { copyDaemonPayload, nodeDependency } from "./daemon.js";
|
|
32
|
+
import { copyDaemonPayload, daemonSdkVersion, nodeDependency } from "./daemon.js";
|
|
33
33
|
import { DAEMON_INGRESS_CONFIG, renderDaemonIngressConfig } from "./daemon-ingress.js";
|
|
34
34
|
import { GATEWAY_CGI_PATH, GATEWAY_CONFIG_PATH, GATEWAY_EXEC, identityGatewayConfig, identityGatewayDeclaration, } from "./identity-gateway.js";
|
|
35
35
|
import { BACKGROUND_UNIT_DIR, BACKGROUND_WORKER_PAYLOAD, backgroundUnitName, renderBackgroundUnit, BRIDGE_WORKER_ARGS, } from "./background.js";
|
|
@@ -77,7 +77,7 @@ export async function assembleStandaloneSpk(options) {
|
|
|
77
77
|
const icons = packageIcons(manifest.icon);
|
|
78
78
|
const uiRoot = join(staging, "package", "ui");
|
|
79
79
|
const appDir = join(uiRoot, "app");
|
|
80
|
-
if (
|
|
80
|
+
if (hasSdkWindow(manifest))
|
|
81
81
|
copyUiTree(manifest.ui.dir, appDir);
|
|
82
82
|
else
|
|
83
83
|
mkdirSync(appDir, { recursive: true });
|
|
@@ -88,7 +88,7 @@ export async function assembleStandaloneSpk(options) {
|
|
|
88
88
|
writeFile(join(uiRoot, "config"), renderUiConfig(manifest, icons, login?.fields));
|
|
89
89
|
for (const [path, bytes] of login?.files ?? [])
|
|
90
90
|
writeFile(join(uiRoot, path), bytes);
|
|
91
|
-
if (
|
|
91
|
+
if (hasSdkWindow(manifest))
|
|
92
92
|
writeFile(join(uiRoot, windowScriptName(manifest)), renderWindowScript(manifest, assetVersion, icons, options.portalText));
|
|
93
93
|
writeFile(join(uiRoot, hostScriptName(manifest.build)), renderHostAdapterScript(manifest.package));
|
|
94
94
|
writeDsmWindowIcons(uiRoot, icons);
|
|
@@ -235,6 +235,7 @@ export async function assembleStandaloneSpk(options) {
|
|
|
235
235
|
...(nativeGatewaySupported(manifest) ? { native_gateway: { sdk: manifest.identityGateway.sdk, listen: manifest.identityGateway.listen } } : {}),
|
|
236
236
|
...(manifest.daemon?.ingress ? { daemon_ingress: manifest.daemon.ingress } : {}),
|
|
237
237
|
...(manifest.daemon?.node ? { daemon_node: manifest.daemon.node } : {}),
|
|
238
|
+
...(manifest.daemon?.restart ? { daemon_restart: manifest.daemon.restart, daemon_sdk: daemonSdkVersion(manifest) } : {}),
|
|
238
239
|
launch: resolvedSdkLaunch(manifest),
|
|
239
240
|
capabilities: Object.keys(manifest.capabilities).sort(),
|
|
240
241
|
...(manifest.compose
|
|
@@ -248,6 +249,7 @@ export async function assembleStandaloneSpk(options) {
|
|
|
248
249
|
: {}),
|
|
249
250
|
...(manifest.entry ? { entry: manifest.entry } : {}),
|
|
250
251
|
...(manifest.console ? { console: true } : {}),
|
|
252
|
+
...(manifest.sdkWindow === false ? { window: false } : {}),
|
|
251
253
|
...(manifest.uninstall
|
|
252
254
|
? {
|
|
253
255
|
uninstall: uninstallRecord(manifest.uninstall, manifest.i18n?.catalogs),
|
|
@@ -353,15 +355,14 @@ export function renderInfo(manifest, arch, checksum, executorDigest) {
|
|
|
353
355
|
// Package Center writes INFO as root; root tasks accept only this executor.
|
|
354
356
|
if (executorDigest)
|
|
355
357
|
fields.push([EXECUTOR_ANCHOR, executorDigest]);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
358
|
+
// Package Center's Open goes to the gateway entry when there is no SDK window.
|
|
359
|
+
// A Compose App's entry lets the docker-project worker check availability;
|
|
360
|
+
// never start or repair Container Manager here.
|
|
361
|
+
if (!hasSdkWindow(manifest) && manifest.identityGateway)
|
|
362
|
+
fields.push([
|
|
363
|
+
"adminurl",
|
|
364
|
+
gatewayEntryPath(manifest),
|
|
365
|
+
]);
|
|
365
366
|
const backend = manifest.backend;
|
|
366
367
|
if (isBridgeBackend(backend) && backend.node)
|
|
367
368
|
fields.push(["install_dep_packages", backend.node.package]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Syno Package Land Developer Kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@napi-rs/keyring": "^2.1.1",
|
|
30
|
-
"@synopackageland/templates": "0.
|
|
30
|
+
"@synopackageland/templates": "0.26.0",
|
|
31
31
|
"ajv": "^8.17.1",
|
|
32
32
|
"commander": "^13.1.0",
|
|
33
33
|
"json-canonicalize": "^3.0.1",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://synopkgland.com/schemas/daemon-restart/1",
|
|
4
|
+
"title": "Native daemon restart policy",
|
|
5
|
+
"description": "The daemon.restart declaration in schema-2 dsm-package.yaml. Requires SDK 0.26.0 or later. Omitted or zero numeric limits use SDK defaults; normalized max_seconds must be at least initial_seconds.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["mode"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"mode": { "type": "string", "enum": ["off", "on-failure"] },
|
|
11
|
+
"initial_seconds": { "type": "integer", "minimum": 0, "maximum": 3600, "default": 1 },
|
|
12
|
+
"max_seconds": { "type": "integer", "minimum": 0, "maximum": 3600, "default": 30 },
|
|
13
|
+
"max_attempts": { "type": "integer", "minimum": 0, "maximum": 100, "default": 5 },
|
|
14
|
+
"window_seconds": { "type": "integer", "minimum": 0, "maximum": 86400, "default": 300 }
|
|
15
|
+
}
|
|
16
|
+
}
|
package/skills/SKILL.md
CHANGED
|
@@ -176,6 +176,12 @@ Application Portal 是獨立 opt-in:不想要別名就省略 `portal`,不生
|
|
|
176
176
|
compose App、沒有 gateway 的 App、`ui.launch` 或 `portal` 都會被 check 拒絕。
|
|
177
177
|
冷登入由 url 圖示經 `open.cgi` 續接,`oidc.browser` 需要支援此續接的 SDK。
|
|
178
178
|
|
|
179
|
+
原生 App 搭配 `identity_gateway`、而且唯一的 UI 就是包裝的網頁時,宣告 `ui: false` 取代 `ui.dir`。
|
|
180
|
+
沒有 SDK 視窗:主選單只有一個 `displayname` 圖示,是 `type: "url"`,經 `sso/open.cgi` 在新分頁開原生 UI;
|
|
181
|
+
套件中心「開啟」經 INFO `adminurl` 開同一個入口,與 compose App 相同。backend 與 ctl 指令不受影響。
|
|
182
|
+
沒有 `identity_gateway`、搭配 `console` 或 `portal`、或使用 `capabilities.rootOps`、`delegate`、`webapi`、`identity`
|
|
183
|
+
(這些的核准或登入畫面在 SDK 視窗裡)都會被 check 以 `STANDALONE_UI_NO_WINDOW_INVALID` 拒絕。省略 `ui` 仍是錯誤。
|
|
184
|
+
|
|
179
185
|
Schema 2 Compose 使用 `dsm-package.yaml` 的 `compose.files`、`compose.web` 與
|
|
180
186
|
`identity_gateway`;不使用 file/targets/address_env 或手寫 upstream。
|
|
181
187
|
省略 `ui.launch` 時預設 `new-page`,使用 SDK standalone launcher,不帶 Application Portal。
|
|
@@ -66,6 +66,12 @@ Application Portal 是獨立 opt-in:不想要別名就省略 `portal`,不生
|
|
|
66
66
|
compose App、沒有 gateway 的 App、`ui.launch` 或 `portal` 都會被 check 拒絕。
|
|
67
67
|
冷登入由 url 圖示經 `open.cgi` 續接,`oidc.browser` 需要支援此續接的 SDK。
|
|
68
68
|
|
|
69
|
+
原生 App 搭配 `identity_gateway`、而且唯一的 UI 就是包裝的網頁時,宣告 `ui: false` 取代 `ui.dir`。
|
|
70
|
+
沒有 SDK 視窗:主選單只有一個 `displayname` 圖示,是 `type: "url"`,經 `sso/open.cgi` 在新分頁開原生 UI;
|
|
71
|
+
套件中心「開啟」經 INFO `adminurl` 開同一個入口,與 compose App 相同。backend 與 ctl 指令不受影響。
|
|
72
|
+
沒有 `identity_gateway`、搭配 `console` 或 `portal`、或使用 `capabilities.rootOps`、`delegate`、`webapi`、`identity`
|
|
73
|
+
(這些的核准或登入畫面在 SDK 視窗裡)都會被 check 以 `STANDALONE_UI_NO_WINDOW_INVALID` 拒絕。省略 `ui` 仍是錯誤。
|
|
74
|
+
|
|
69
75
|
Docker/web service App 設定 `integration.web.launch`,內建預設是 `new-page`;
|
|
70
76
|
省略開啟方式時直接開新分頁,不宣告 Application Portal。SDK 原生 App
|
|
71
77
|
省略時使用 `dsm-window`;非 UI SDK 原生 App 必須明確寫 `ui.launch: new-page`。
|
|
@@ -165,6 +165,12 @@ Application Portal 是獨立 opt-in:不想要別名就省略 `portal`,不生
|
|
|
165
165
|
compose App、沒有 gateway 的 App、`ui.launch` 或 `portal` 都會被 check 拒絕。
|
|
166
166
|
冷登入由 url 圖示經 `open.cgi` 續接,`oidc.browser` 需要支援此續接的 SDK。
|
|
167
167
|
|
|
168
|
+
原生 App 搭配 `identity_gateway`、而且唯一的 UI 就是包裝的網頁時,宣告 `ui: false` 取代 `ui.dir`。
|
|
169
|
+
沒有 SDK 視窗:主選單只有一個 `displayname` 圖示,是 `type: "url"`,經 `sso/open.cgi` 在新分頁開原生 UI;
|
|
170
|
+
套件中心「開啟」經 INFO `adminurl` 開同一個入口,與 compose App 相同。backend 與 ctl 指令不受影響。
|
|
171
|
+
沒有 `identity_gateway`、搭配 `console` 或 `portal`、或使用 `capabilities.rootOps`、`delegate`、`webapi`、`identity`
|
|
172
|
+
(這些的核准或登入畫面在 SDK 視窗裡)都會被 check 以 `STANDALONE_UI_NO_WINDOW_INVALID` 拒絕。省略 `ui` 仍是錯誤。
|
|
173
|
+
|
|
168
174
|
Docker/web service App 設定 `integration.web.launch`,內建預設是 `new-page`;
|
|
169
175
|
省略開啟方式時直接開新分頁,不宣告 Application Portal。SDK 原生 App
|
|
170
176
|
省略時使用 `dsm-window`;非 UI SDK 原生 App 必須明確寫 `ui.launch: new-page`。
|