@synopackageland/cli 0.24.1 → 0.25.1
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 +13 -0
- package/README.md +10 -0
- package/dist/build-spk/ui-texts.d.ts +14 -2
- package/dist/build-spk/ui-texts.js +16 -4
- package/dist/contract/app-launch.d.ts +4 -1
- package/dist/contract/app-launch.js +2 -1
- package/dist/error-registry.js +35 -0
- package/dist/runtime/executor-digests.json +1 -12
- package/dist/runtime/sdk-source.js +1 -1
- package/dist/standalone/daemon-ingress.d.ts +21 -0
- package/dist/standalone/daemon-ingress.js +61 -0
- package/dist/standalone/daemon.d.ts +26 -0
- package/dist/standalone/daemon.js +87 -4
- package/dist/standalone/dsm-window.d.ts +6 -1
- package/dist/standalone/dsm-window.js +58 -10
- package/dist/standalone/inspect-spk.js +103 -16
- package/dist/standalone/lifecycle.d.ts +3 -0
- package/dist/standalone/lifecycle.js +35 -10
- package/dist/standalone/manifest.d.ts +9 -0
- package/dist/standalone/manifest.js +36 -0
- package/dist/standalone/resource.d.ts +4 -0
- package/dist/standalone/resource.js +5 -1
- package/dist/standalone/spk.d.ts +5 -0
- package/dist/standalone/spk.js +25 -5
- package/package.json +2 -2
- package/skills/SKILL.md +21 -0
- package/skills/reference/portal.md +9 -0
- package/skills/reference/ui-json.md +29 -0
- package/skills/reference/ui-vue.md +41 -0
- package/skills/reference/upstream-fork.md +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.25.1 (2026-10-22)
|
|
4
|
+
|
|
5
|
+
- Fix upgrades that first add a `daemon`: the generated `preupgrade` ran the old payload's missing `daemon/control` and DSM rolled back with error 4541. The hook now exits 0 when the outgoing payload has no `daemon/` (plain, executor and delegated-worker variants); inspect still accepts the released unguarded hook.
|
|
6
|
+
- Add `daemon.node` (`package: Node.js_v18|v20|v22`, optional `min_version`): the daemon's `exec` is a JavaScript entry run by the Package Center Node.js package through a generated launcher, with INFO `install_dep_packages`. The SPK bundles no Node. See docs/daemon.md for availability and dependency behaviour.
|
|
7
|
+
- Add `daemon.ingress` (`path`, optional `timeout`, default 3600 s): a same-origin path in DSM's nginx through web-config `nginx-static-config` `type: dsm`, proxied unbuffered to `/tmp/synopkgland-ingress-<package>.sock`, with nginx reloads in INFO. check requires os_min_ver 7.2-64570 or later (`STANDALONE_DAEMON_INGRESS_MIN_DSM`); inspect verifies the location, resource, INFO and daemon environment.
|
|
8
|
+
|
|
9
|
+
## 0.25.0 (2026-10-22)
|
|
10
|
+
|
|
11
|
+
- Add the opt-in `console` declaration for native Apps with `identity_gateway`: a native UI URL launcher and an administrator-only DSM console, selected by Package Center Open. Validate both entries and refuse incompatible launch declarations.
|
|
12
|
+
- Fix installation-settings inspection when native identity-gateway diagnostics precede the settings hook, retaining full lifecycle validation.
|
|
13
|
+
- Document punctuation rules for owned UI copy in the development skill.
|
|
14
|
+
- Support SDK/App 0.25.0 and templates 0.25.0, with UI 0.14.3 in Vue scaffolds. Reuse executor/v0.24.0 with unchanged protected executor/lifecycle payloads and Go 1.26.10. OIDC browser cold-login continuation requires SDK 0.25.0. No NAS acceptance is claimed.
|
|
15
|
+
|
|
3
16
|
## 0.24.1 (2026-10-22)
|
|
4
17
|
|
|
5
18
|
- Build SDK/App 0.24.1 with the verified immutable executor/v0.24.0 release and its exact executor/lifecycle digests; retain SDK/App 0.24.0 support. SDK 0.24.1 adds AppShell brand navigation to the first page and supports UI 0.14.3.
|
package/README.md
CHANGED
|
@@ -268,6 +268,16 @@ A full page does not need Application Portal. Omit `portal` unless an alias is
|
|
|
268
268
|
explicitly wanted; only that opt-in adds `defaultAlias`, login styling, Portal
|
|
269
269
|
panels and capabilities. No Portal is a healthy default for inspect/info/doctor.
|
|
270
270
|
|
|
271
|
+
A native App with `identity_gateway` can declare `console` to get two main-menu
|
|
272
|
+
icons: the native UI (`displayname`, a `type: "url"` launcher to `sso/open.cgi`
|
|
273
|
+
that opens a new tab) and an administrator-only DSM window running the SDK UI
|
|
274
|
+
from `ui.dir`, named "<displayname> for DSM" by default. Package Center's Open
|
|
275
|
+
(INFO `dsmapplaunchname`) starts the console, and Application Privileges lists
|
|
276
|
+
only the native icon. `console: {}` keeps the default name; `console: { title }`
|
|
277
|
+
sets it for every language. Omit `console` to keep one icon. check refuses
|
|
278
|
+
`console` on Compose, without `identity_gateway`, or with `ui.launch` or
|
|
279
|
+
`portal`. A cold DSM sign-in resumes through the url launcher.
|
|
280
|
+
|
|
271
281
|
Compose uses schema-2 `dsm-package.yaml` and `ui.launch`; the default is new-page.
|
|
272
282
|
Classless gateways use type url via the shared contract/app-launch resolver;
|
|
273
283
|
AppWindow wrappers use standalone. Headless projects have no launcher.
|
|
@@ -18,6 +18,18 @@ export declare function uiTitleFields(app: AppTitle): {
|
|
|
18
18
|
texts?: string;
|
|
19
19
|
preloadTexts?: string[];
|
|
20
20
|
};
|
|
21
|
+
/** The console's name: the declared title in every language, else "<displayname> for DSM". */
|
|
22
|
+
export declare function consoleTitle(displayname: string, console: {
|
|
23
|
+
title?: string;
|
|
24
|
+
}): string;
|
|
25
|
+
/** {@link uiTitleFields} for the console window, which shares the App's description. */
|
|
26
|
+
export declare function uiConsoleTitleFields(app: AppTitle, console: {
|
|
27
|
+
title?: string;
|
|
28
|
+
}): ReturnType<typeof uiTitleFields>;
|
|
21
29
|
/** `ui/texts/<dsm lang>/strings` for English and every translated language; none without translations. */
|
|
22
|
-
export declare function renderUiTexts(app: AppTitle
|
|
23
|
-
|
|
30
|
+
export declare function renderUiTexts(app: AppTitle, console?: {
|
|
31
|
+
title?: string;
|
|
32
|
+
}): Map<string, string>;
|
|
33
|
+
export declare function writeUiTexts(uiRoot: string, app: AppTitle, console?: {
|
|
34
|
+
title?: string;
|
|
35
|
+
}): void;
|
|
@@ -4,6 +4,7 @@ import { DSM_LANGS, LOCALES } from "../standalone/i18n.js";
|
|
|
4
4
|
const SECTION = "synopkgland";
|
|
5
5
|
const TITLE = `${SECTION}:app_title`;
|
|
6
6
|
const DESC = `${SECTION}:app_desc`;
|
|
7
|
+
const CONSOLE_TITLE = `${SECTION}:console_title`;
|
|
7
8
|
/** `ui/` subdirectory with the strings; a `ui/config` entry's `texts` names it relative to `ui/`. */
|
|
8
9
|
const TEXTS_DIR = "texts";
|
|
9
10
|
function translated(app) {
|
|
@@ -21,8 +22,18 @@ export function uiTitleFields(app) {
|
|
|
21
22
|
? { title: TITLE, desc: DESC, texts: TEXTS_DIR, preloadTexts: [TITLE, DESC] }
|
|
22
23
|
: { title: app.displayname, desc: app.description };
|
|
23
24
|
}
|
|
25
|
+
/** The console's name: the declared title in every language, else "<displayname> for DSM". */
|
|
26
|
+
export function consoleTitle(displayname, console) {
|
|
27
|
+
return console.title ?? `${displayname} for DSM`;
|
|
28
|
+
}
|
|
29
|
+
/** {@link uiTitleFields} for the console window, which shares the App's description. */
|
|
30
|
+
export function uiConsoleTitleFields(app, console) {
|
|
31
|
+
return translated(app)
|
|
32
|
+
? { title: CONSOLE_TITLE, desc: DESC, texts: TEXTS_DIR, preloadTexts: [CONSOLE_TITLE, DESC] }
|
|
33
|
+
: { title: consoleTitle(app.displayname, console), desc: app.description };
|
|
34
|
+
}
|
|
24
35
|
/** `ui/texts/<dsm lang>/strings` for English and every translated language; none without translations. */
|
|
25
|
-
export function renderUiTexts(app) {
|
|
36
|
+
export function renderUiTexts(app, console) {
|
|
26
37
|
const files = new Map();
|
|
27
38
|
if (!translated(app))
|
|
28
39
|
return files;
|
|
@@ -32,12 +43,13 @@ export function renderUiTexts(app) {
|
|
|
32
43
|
continue;
|
|
33
44
|
const title = text?.displayname ?? app.displayname;
|
|
34
45
|
const desc = text?.description ?? app.description;
|
|
35
|
-
|
|
46
|
+
const consoleLine = console ? `console_title="${consoleTitle(title, console)}"\n` : "";
|
|
47
|
+
files.set(DSM_LANGS[locale], `[${SECTION}]\napp_title="${title}"\napp_desc="${desc}"\n${consoleLine}`);
|
|
36
48
|
}
|
|
37
49
|
return files;
|
|
38
50
|
}
|
|
39
|
-
export function writeUiTexts(uiRoot, app) {
|
|
40
|
-
for (const [lang, strings] of renderUiTexts(app)) {
|
|
51
|
+
export function writeUiTexts(uiRoot, app, console) {
|
|
52
|
+
for (const [lang, strings] of renderUiTexts(app, console)) {
|
|
41
53
|
mkdirSync(join(uiRoot, TEXTS_DIR, lang), { recursive: true });
|
|
42
54
|
const path = join(uiRoot, TEXTS_DIR, lang, "strings");
|
|
43
55
|
writeFileSync(path, strings, "utf8");
|
|
@@ -6,6 +6,9 @@ export declare const DEFAULT_WEB_LAUNCH: AppLaunchMode;
|
|
|
6
6
|
export declare function isAppLaunchMode(value: unknown): value is AppLaunchMode;
|
|
7
7
|
/** Both Compose routes use the same web default; a valid explicit choice wins. */
|
|
8
8
|
export declare function resolveAppLaunch(value: unknown, webService: boolean): AppLaunchMode | undefined;
|
|
9
|
-
|
|
9
|
+
/** A console always opens the SDK UI in a DSM window. */
|
|
10
|
+
export declare function resolvedSdkLaunch(manifest: Pick<StandaloneManifest, 'ui' | 'compose' | 'identityGateway'> & {
|
|
11
|
+
console?: unknown;
|
|
12
|
+
}): AppLaunchMode;
|
|
10
13
|
/** DSM rewrites standalone URLs to launchApp; only SDK wrappers provide that class. */
|
|
11
14
|
export declare function dsmLauncherType(launch: AppLaunchMode, sdkWindow: boolean): 'standalone' | 'app' | 'url';
|
|
@@ -9,8 +9,9 @@ export function resolveAppLaunch(value, webService) {
|
|
|
9
9
|
return value === undefined ? webService ? DEFAULT_WEB_LAUNCH : DEFAULT_NATIVE_LAUNCH
|
|
10
10
|
: isAppLaunchMode(value) ? value : undefined;
|
|
11
11
|
}
|
|
12
|
+
/** A console always opens the SDK UI in a DSM window. */
|
|
12
13
|
export function resolvedSdkLaunch(manifest) {
|
|
13
|
-
return resolveAppLaunch(manifest.ui.launch, Boolean(manifest.compose || manifest.identityGateway));
|
|
14
|
+
return resolveAppLaunch(manifest.console ? 'dsm-window' : manifest.ui.launch, Boolean(manifest.compose || manifest.identityGateway));
|
|
14
15
|
}
|
|
15
16
|
/** DSM rewrites standalone URLs to launchApp; only SDK wrappers provide that class. */
|
|
16
17
|
export function dsmLauncherType(launch, sdkWindow) {
|
package/dist/error-registry.js
CHANGED
|
@@ -322,6 +322,21 @@ export const ERROR_REGISTRY = {
|
|
|
322
322
|
description: "The package daemon's Go supervisor could not be built.",
|
|
323
323
|
suggestion: "Install Go 1.21 or later (builds pin go1.26.10 and need it offline) and check the compiler stderr; the NAS does not need a Go toolchain.",
|
|
324
324
|
},
|
|
325
|
+
STANDALONE_DAEMON_NODE_INVALID: {
|
|
326
|
+
code: "STANDALONE_DAEMON_NODE_INVALID",
|
|
327
|
+
description: "daemon.node does not name a Package Center Node.js package and a version of its major.",
|
|
328
|
+
suggestion: "Set daemon.node.package to Node.js_v18, Node.js_v20 or Node.js_v22, and min_version (optional) to a version of that major such as 22.19.0.",
|
|
329
|
+
},
|
|
330
|
+
STANDALONE_DAEMON_INGRESS_INVALID: {
|
|
331
|
+
code: "STANDALONE_DAEMON_INGRESS_INVALID",
|
|
332
|
+
description: "daemon.ingress does not declare one same-origin path and a bounded proxy timeout.",
|
|
333
|
+
suggestion: "Set daemon.ingress.path to /<name>/ with lowercase letters, digits and -, not a DSM path, and timeout (optional) to 1–3600 seconds.",
|
|
334
|
+
},
|
|
335
|
+
STANDALONE_DAEMON_INGRESS_MIN_DSM: {
|
|
336
|
+
code: "STANDALONE_DAEMON_INGRESS_MIN_DSM",
|
|
337
|
+
description: "daemon.ingress uses DSM's web-config worker, which DSM 7.1 refuses in an unsigned package.",
|
|
338
|
+
suggestion: "Raise os_min_ver to 7.2-64570 or later, or remove daemon.ingress.",
|
|
339
|
+
},
|
|
325
340
|
STANDALONE_DAEMON_PAYLOAD_INVALID: {
|
|
326
341
|
code: "STANDALONE_DAEMON_PAYLOAD_INVALID",
|
|
327
342
|
description: "The daemon payload is not a bounded, link-free directory inside the project.",
|
|
@@ -532,6 +547,26 @@ export const ERROR_REGISTRY = {
|
|
|
532
547
|
description: "The portal format or alias in dsm-package.yaml is invalid.",
|
|
533
548
|
suggestion: "Use portal: {} or portal: { alias: my-app }; alias is 1-64 lowercase alphanumerics or -, and not a DSM reserved path.",
|
|
534
549
|
},
|
|
550
|
+
STANDALONE_CONSOLE_INVALID: {
|
|
551
|
+
code: "STANDALONE_CONSOLE_INVALID",
|
|
552
|
+
description: "The console format or title in dsm-package.yaml is invalid.",
|
|
553
|
+
suggestion: 'Use console: {} for "<displayname> for DSM", or console: { title: My App Admin } with 1-64 characters and no quotes, backslashes or control characters.',
|
|
554
|
+
},
|
|
555
|
+
STANDALONE_CONSOLE_REQUIRES_GATEWAY: {
|
|
556
|
+
code: "STANDALONE_CONSOLE_REQUIRES_GATEWAY",
|
|
557
|
+
description: "console is declared on a Compose App or an App without identity_gateway.",
|
|
558
|
+
suggestion: "Declare console only on a native App whose web UI the identity gateway serves; otherwise remove console and keep one icon.",
|
|
559
|
+
},
|
|
560
|
+
STANDALONE_CONSOLE_LAUNCH: {
|
|
561
|
+
code: "STANDALONE_CONSOLE_LAUNCH",
|
|
562
|
+
description: "console is declared together with ui.launch.",
|
|
563
|
+
suggestion: "Remove ui.launch: the console always opens in a DSM window and the native icon in a new tab.",
|
|
564
|
+
},
|
|
565
|
+
STANDALONE_CONSOLE_PORTAL: {
|
|
566
|
+
code: "STANDALONE_CONSOLE_PORTAL",
|
|
567
|
+
description: "console is declared together with portal.",
|
|
568
|
+
suggestion: "Declare either console or portal, not both.",
|
|
569
|
+
},
|
|
535
570
|
STANDALONE_PORTAL_REQUIRES_EXECUTOR: {
|
|
536
571
|
code: "STANDALONE_PORTAL_REQUIRES_EXECUTOR",
|
|
537
572
|
description: "An App targeting DSM below 7.2-64570 declares portal without an executor.",
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"0.
|
|
3
|
-
"x86_64": "de78684faac211c24a7cda8a4541ab1b138d37f6d580936fe354bb9bd0d78a0f",
|
|
4
|
-
"aarch64": "8ef7c736203f6c111816b76f970b7204fa6f8d871ff3222fd7c0a27d0075b847",
|
|
5
|
-
"armv7": "a38703119554b5f2a8bf4810ec2a6c8f1d793bfbb1ca96803eb1b39a26ffd96c",
|
|
6
|
-
"lifecycle": {
|
|
7
|
-
"x86_64": "c6fbb377ecd5ec7ee5b96439f22091d9fa5965dfd616d510ca9445722f436721",
|
|
8
|
-
"aarch64": "a9ca1750d4d5cff4727dc29789e6eaf0a365cb0e674f1bb796971d82f2d4fa27",
|
|
9
|
-
"armv7": "6d25fc972eba12e3ac68f8b6a95a45e9e5706ebe19ffc1ff774fc318acbeb90f"
|
|
10
|
-
},
|
|
11
|
-
"goToolchain": "go1.26.10"
|
|
12
|
-
},
|
|
13
|
-
"0.24.1": {
|
|
2
|
+
"0.25.0": {
|
|
14
3
|
"x86_64": "de78684faac211c24a7cda8a4541ab1b138d37f6d580936fe354bb9bd0d78a0f",
|
|
15
4
|
"aarch64": "8ef7c736203f6c111816b76f970b7204fa6f8d871ff3222fd7c0a27d0075b847",
|
|
16
5
|
"armv7": "a38703119554b5f2a8bf4810ec2a6c8f1d793bfbb1ca96803eb1b39a26ffd96c",
|
|
@@ -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' };
|
|
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' };
|
|
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,21 @@
|
|
|
1
|
+
import type { StandaloneError } from "./manifest.js";
|
|
2
|
+
/** `daemon.ingress`: one path in DSM's own server blocks, proxied to the daemon. */
|
|
3
|
+
export interface DaemonIngress {
|
|
4
|
+
/** `/<name>/`, served on DSM's ports and custom domains, so same-origin with DSM. */
|
|
5
|
+
path: string;
|
|
6
|
+
/** nginx proxy_read_timeout and proxy_send_timeout in seconds. */
|
|
7
|
+
timeout: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const DAEMON_INGRESS_CONFIG = "daemon/ingress-nginx.conf";
|
|
10
|
+
export declare function readDaemonIngress(value: unknown, fail: (error: StandaloneError) => void): DaemonIngress | undefined;
|
|
11
|
+
/** DSM before 7.2 rejects an unsigned web-config, the worker this path uses. */
|
|
12
|
+
export declare function daemonIngressMinDsmProblem(osMinVer: string | undefined): string | undefined;
|
|
13
|
+
export declare const daemonIngressSocket: (pkg: string) => string;
|
|
14
|
+
/** What the daemon learns: where to listen and which path prefix it serves. */
|
|
15
|
+
export declare function ingressEnv(pkg: string, ingress: DaemonIngress): Record<string, string>;
|
|
16
|
+
export declare function daemonIngressResource(): Record<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Included in DSM's own server blocks. Streaming responses (SSE) pass
|
|
19
|
+
* unbuffered; nginx replaces any client X-Real-IP with the peer address.
|
|
20
|
+
*/
|
|
21
|
+
export declare function renderDaemonIngressConfig(pkg: string, ingress: DaemonIngress): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { isDsmAtLeast, PORTAL_MIN_DSM } from "../build-spk/dsm-version.js";
|
|
2
|
+
import { RESERVED_PORTAL_ALIASES } from "../portal/alias.js";
|
|
3
|
+
export const DAEMON_INGRESS_CONFIG = "daemon/ingress-nginx.conf";
|
|
4
|
+
const PATH = /^\/[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?\/$/;
|
|
5
|
+
const reserved = (path) => RESERVED_PORTAL_ALIASES.includes(path.slice(1, -1));
|
|
6
|
+
export function readDaemonIngress(value, fail) {
|
|
7
|
+
if (value === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
const v = value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
10
|
+
const timeout = v?.timeout ?? 3600;
|
|
11
|
+
if (!v ||
|
|
12
|
+
Object.keys(v).some((k) => k !== "path" && k !== "timeout") ||
|
|
13
|
+
typeof v.path !== "string" ||
|
|
14
|
+
!PATH.test(v.path) ||
|
|
15
|
+
reserved(v.path) ||
|
|
16
|
+
!Number.isInteger(timeout) ||
|
|
17
|
+
timeout < 1 ||
|
|
18
|
+
timeout > 3600) {
|
|
19
|
+
fail({
|
|
20
|
+
code: "STANDALONE_DAEMON_INGRESS_INVALID",
|
|
21
|
+
message: `daemon.ingress needs path "/<name>/" (lowercase letters, digits and -, not a DSM path such as /webapi/) and an optional timeout of 1–3600 seconds.`,
|
|
22
|
+
});
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return { path: v.path, timeout: timeout };
|
|
26
|
+
}
|
|
27
|
+
/** DSM before 7.2 rejects an unsigned web-config, the worker this path uses. */
|
|
28
|
+
export function daemonIngressMinDsmProblem(osMinVer) {
|
|
29
|
+
if (osMinVer && isDsmAtLeast(osMinVer, PORTAL_MIN_DSM))
|
|
30
|
+
return undefined;
|
|
31
|
+
return `daemon.ingress needs os_min_ver ${PORTAL_MIN_DSM} or later; os_min_ver is "${osMinVer ?? ""}". DSM 7.1 rejects the unsigned web-config worker that adds the path to DSM's nginx. Raise os_min_ver or remove daemon.ingress.`;
|
|
32
|
+
}
|
|
33
|
+
export const daemonIngressSocket = (pkg) => `/tmp/synopkgland-ingress-${pkg}.sock`;
|
|
34
|
+
/** What the daemon learns: where to listen and which path prefix it serves. */
|
|
35
|
+
export function ingressEnv(pkg, ingress) {
|
|
36
|
+
return { SYNOPKGLAND_INGRESS_PATH: ingress.path, SYNOPKGLAND_INGRESS_SOCKET: daemonIngressSocket(pkg) };
|
|
37
|
+
}
|
|
38
|
+
export function daemonIngressResource() {
|
|
39
|
+
return { "nginx-static-config": { enable: [{ type: "dsm", relpath: DAEMON_INGRESS_CONFIG }] } };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Included in DSM's own server blocks. Streaming responses (SSE) pass
|
|
43
|
+
* unbuffered; nginx replaces any client X-Real-IP with the peer address.
|
|
44
|
+
*/
|
|
45
|
+
export function renderDaemonIngressConfig(pkg, ingress) {
|
|
46
|
+
return `location ^~ ${ingress.path} {
|
|
47
|
+
proxy_pass http://unix:${daemonIngressSocket(pkg)}:;
|
|
48
|
+
proxy_http_version 1.1;
|
|
49
|
+
proxy_set_header Host $http_host;
|
|
50
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
51
|
+
proxy_set_header Connection "";
|
|
52
|
+
proxy_buffering off;
|
|
53
|
+
proxy_request_buffering off;
|
|
54
|
+
proxy_cache off;
|
|
55
|
+
gzip off;
|
|
56
|
+
proxy_read_timeout ${ingress.timeout}s;
|
|
57
|
+
proxy_send_timeout ${ingress.timeout}s;
|
|
58
|
+
client_max_body_size 8m;
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
|
|
2
|
+
import { NODE_PACKAGES } from "./bridge-backend.js";
|
|
3
|
+
import { type DaemonIngress } from "./daemon-ingress.js";
|
|
2
4
|
import type { CommandRunner } from "./build.js";
|
|
3
5
|
import type { StandaloneError, StandaloneManifest } from "./manifest.js";
|
|
4
6
|
export interface Daemon {
|
|
@@ -17,7 +19,26 @@ export interface Daemon {
|
|
|
17
19
|
status: number;
|
|
18
20
|
uninstall: number;
|
|
19
21
|
};
|
|
22
|
+
/** `exec` is a JavaScript entry run by the node of a Package Center Node.js package. */
|
|
23
|
+
node?: DaemonNode;
|
|
24
|
+
/** A same-origin DSM path that nginx proxies to the daemon's Unix socket. */
|
|
25
|
+
ingress?: DaemonIngress;
|
|
20
26
|
}
|
|
27
|
+
export interface DaemonNode {
|
|
28
|
+
package: (typeof NODE_PACKAGES)[number];
|
|
29
|
+
/** The lowest package version INFO install_dep_packages accepts. */
|
|
30
|
+
min_version?: string;
|
|
31
|
+
}
|
|
32
|
+
/** The CLI-generated launcher; the payload must not ship its own directory. */
|
|
33
|
+
export declare const NODE_LAUNCHER = "_synopkgland/node";
|
|
34
|
+
export declare const nodeBinary: (pkg: string) => string;
|
|
35
|
+
/** INFO install_dep_packages for a Node daemon. */
|
|
36
|
+
export declare function nodeDependency(node: DaemonNode): string;
|
|
37
|
+
/**
|
|
38
|
+
* Runs the dependency's node in place of the shell, so daemon/control
|
|
39
|
+
* supervises node itself. A removed dependency fails start with a clear line.
|
|
40
|
+
*/
|
|
41
|
+
export declare function renderNodeLauncher(node: DaemonNode): string;
|
|
21
42
|
/** A fixed argv or env value: only the package's own directories expand. */
|
|
22
43
|
export declare const token: (v: unknown) => v is string;
|
|
23
44
|
/** An application variable that cannot steer the loader, the shell or DSM. */
|
|
@@ -26,6 +47,11 @@ export declare const envKey: (k: string) => boolean;
|
|
|
26
47
|
export declare const relativeExec: (v: unknown) => v is string;
|
|
27
48
|
export declare function readDaemon(value: unknown, root: string, arches: NativeArchFamily[], fail: (error: StandaloneError) => void): Daemon | undefined;
|
|
28
49
|
export declare function daemonConfig(manifest: StandaloneManifest): string;
|
|
50
|
+
/**
|
|
51
|
+
* The argv and environment daemon/control runs: a Node daemon execs the
|
|
52
|
+
* launcher with its entry first, and ingress names the socket to listen on.
|
|
53
|
+
*/
|
|
54
|
+
export declare function runnableDaemon(pkg: string, daemon: Daemon): Daemon;
|
|
29
55
|
export declare function buildDaemonControl(manifest: StandaloneManifest, arch: NativeArchFamily, work: string, run: CommandRunner): Promise<string | undefined>;
|
|
30
56
|
export declare function copyDaemonPayload(manifest: StandaloneManifest, arch: NativeArchFamily, target: string, controller: string): void;
|
|
31
57
|
/** Copies a bounded, link-free package-account payload tree. */
|
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
import { operationBudgetSeconds, runtimeBudgetSdkSupported } from "../runtime/runtime-budgets.js";
|
|
2
2
|
import { goChildEnv } from "../runtime/go-env.js";
|
|
3
3
|
import { chmodSync, existsSync, copyFileSync, lstatSync, mkdirSync, readdirSync, realpathSync, writeFileSync, } from "node:fs";
|
|
4
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem, } from "../build-spk/go-toolchain.js";
|
|
6
6
|
import { sdkSource, appSdkVersion } from "../runtime/sdk-source.js";
|
|
7
7
|
import { templateSdkVersion } from "../template-paths.js";
|
|
8
8
|
import { checkCgiBinary } from "./binary.js";
|
|
9
|
+
import { NODE_PACKAGES } from "./bridge-backend.js";
|
|
10
|
+
import { readDaemonIngress, ingressEnv } from "./daemon-ingress.js";
|
|
9
11
|
import { composeDaemon, composeNetwork } from "./compose.js";
|
|
10
12
|
import { gatewayDaemon } from "./identity-gateway.js";
|
|
11
13
|
import { StandaloneBuildError } from "./spk.js";
|
|
14
|
+
/** The CLI-generated launcher; the payload must not ship its own directory. */
|
|
15
|
+
export const NODE_LAUNCHER = "_synopkgland/node";
|
|
16
|
+
export const nodeBinary = (pkg) => `/var/packages/${pkg}/target/usr/local/bin/node`;
|
|
17
|
+
/** INFO install_dep_packages for a Node daemon. */
|
|
18
|
+
export function nodeDependency(node) {
|
|
19
|
+
return node.min_version ? `${node.package}>=${node.min_version}` : node.package;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs the dependency's node in place of the shell, so daemon/control
|
|
23
|
+
* supervises node itself. A removed dependency fails start with a clear line.
|
|
24
|
+
*/
|
|
25
|
+
export function renderNodeLauncher(node) {
|
|
26
|
+
return `#!/bin/sh
|
|
27
|
+
node=${nodeBinary(node.package)}
|
|
28
|
+
if [ ! -x "$node" ]; then
|
|
29
|
+
echo "${node.package} is not installed or is incomplete; install it in Package Center." >&2
|
|
30
|
+
exit 127
|
|
31
|
+
fi
|
|
32
|
+
exec "$node" "$@"
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
12
35
|
const fields = new Set([
|
|
13
36
|
"exec",
|
|
14
37
|
"targets",
|
|
@@ -16,6 +39,8 @@ const fields = new Set([
|
|
|
16
39
|
"env",
|
|
17
40
|
"timeouts",
|
|
18
41
|
"uninstall_args",
|
|
42
|
+
"node",
|
|
43
|
+
"ingress",
|
|
19
44
|
]);
|
|
20
45
|
const record = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
21
46
|
/** A fixed argv or env value: only the package's own directories expand. */
|
|
@@ -34,6 +59,8 @@ export const relativeExec = (v) => typeof v === "string" &&
|
|
|
34
59
|
v.length <= 200 &&
|
|
35
60
|
/^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(v) &&
|
|
36
61
|
!v.split("/").some((p) => p === "." || p === "..");
|
|
62
|
+
/** A Node entry such as `server.mjs` or `dist/main.js`, inside the payload. */
|
|
63
|
+
const relativeEntry = (v) => typeof v === "string" && relativeExec(`_/${v}`);
|
|
37
64
|
export function readDaemon(value, root, arches, fail) {
|
|
38
65
|
if (value === undefined)
|
|
39
66
|
return undefined;
|
|
@@ -46,7 +73,7 @@ export function readDaemon(value, root, arches, fail) {
|
|
|
46
73
|
};
|
|
47
74
|
if (!record(value) || Object.keys(value).some((k) => !fields.has(k)))
|
|
48
75
|
return invalid();
|
|
49
|
-
if (!relativeExec(value.exec))
|
|
76
|
+
if (!(value.node === undefined ? relativeExec(value.exec) : relativeEntry(value.exec)))
|
|
50
77
|
return invalid();
|
|
51
78
|
if (!record(value.targets) ||
|
|
52
79
|
Object.keys(value.targets).length !== arches.length)
|
|
@@ -83,6 +110,14 @@ export function readDaemon(value, root, arches, fail) {
|
|
|
83
110
|
const timeouts = readLifecycleTimeouts(value.timeouts, fail);
|
|
84
111
|
if (!timeouts)
|
|
85
112
|
return undefined;
|
|
113
|
+
const node = readDaemonNode(value.node, fail);
|
|
114
|
+
if (value.node !== undefined && !node)
|
|
115
|
+
return undefined;
|
|
116
|
+
const ingress = readDaemonIngress(value.ingress, fail);
|
|
117
|
+
if (value.ingress !== undefined && !ingress)
|
|
118
|
+
return undefined;
|
|
119
|
+
if (ingress && Object.keys(env).some((key) => key in ingressEnv("", ingress)))
|
|
120
|
+
return invalid();
|
|
86
121
|
return {
|
|
87
122
|
exec: value.exec,
|
|
88
123
|
targets,
|
|
@@ -92,8 +127,31 @@ export function readDaemon(value, root, arches, fail) {
|
|
|
92
127
|
.sort()
|
|
93
128
|
.map((key) => [key, env[key]])),
|
|
94
129
|
timeouts: timeouts,
|
|
130
|
+
...(node ? { node } : {}),
|
|
131
|
+
...(ingress ? { ingress } : {}),
|
|
95
132
|
};
|
|
96
133
|
}
|
|
134
|
+
function readDaemonNode(value, fail) {
|
|
135
|
+
if (value === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
const pkg = record(value) ? value.package : undefined;
|
|
138
|
+
const min = record(value) ? value.min_version : undefined;
|
|
139
|
+
const major = typeof pkg === "string" ? pkg.replace("Node.js_v", "") : "";
|
|
140
|
+
if (!record(value) ||
|
|
141
|
+
Object.keys(value).some((k) => k !== "package" && k !== "min_version") ||
|
|
142
|
+
!NODE_PACKAGES.includes(pkg) ||
|
|
143
|
+
(min !== undefined &&
|
|
144
|
+
(typeof min !== "string" ||
|
|
145
|
+
!/^[0-9]{1,3}(\.[0-9]{1,4}){0,2}$/.test(min) ||
|
|
146
|
+
min.split(".")[0] !== major))) {
|
|
147
|
+
fail({
|
|
148
|
+
code: "STANDALONE_DAEMON_NODE_INVALID",
|
|
149
|
+
message: `daemon.node needs package (${NODE_PACKAGES.join(", ")}) and an optional min_version of that major, such as "22.19.0".`,
|
|
150
|
+
});
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return { package: pkg, ...(min ? { min_version: min } : {}) };
|
|
154
|
+
}
|
|
97
155
|
function daemonSdkVersion(manifest) {
|
|
98
156
|
return manifest.identityGateway?.sdk ??
|
|
99
157
|
(manifest.backend.kind === "go" ? appSdkVersion(manifest.backend) : undefined) ??
|
|
@@ -104,7 +162,7 @@ export function daemonConfig(manifest) {
|
|
|
104
162
|
? composeDaemon(manifest)
|
|
105
163
|
: manifest.identityGateway
|
|
106
164
|
? gatewayDaemon(manifest.daemon)
|
|
107
|
-
: manifest.daemon;
|
|
165
|
+
: runnableDaemon(manifest.package, manifest.daemon);
|
|
108
166
|
return (JSON.stringify({
|
|
109
167
|
package: manifest.package,
|
|
110
168
|
exec,
|
|
@@ -117,6 +175,20 @@ export function daemonConfig(manifest) {
|
|
|
117
175
|
...(manifest.compose ? { network: composeNetwork(manifest) } : {}),
|
|
118
176
|
}) + "\n");
|
|
119
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* The argv and environment daemon/control runs: a Node daemon execs the
|
|
180
|
+
* launcher with its entry first, and ingress names the socket to listen on.
|
|
181
|
+
*/
|
|
182
|
+
export function runnableDaemon(pkg, daemon) {
|
|
183
|
+
const entry = daemon.node && [`\${SYNOPKG_PKGDEST}/daemon/payload/${daemon.exec}`];
|
|
184
|
+
const env = daemon.ingress ? { ...daemon.env, ...ingressEnv(pkg, daemon.ingress) } : daemon.env;
|
|
185
|
+
return {
|
|
186
|
+
...daemon,
|
|
187
|
+
...(entry ? { exec: NODE_LAUNCHER, args: [...entry, ...daemon.args] } : {}),
|
|
188
|
+
...(entry && daemon.uninstall_args ? { uninstall_args: [...entry, ...daemon.uninstall_args] } : {}),
|
|
189
|
+
env: Object.fromEntries(Object.keys(env).sort().map((key) => [key, env[key]])),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
120
192
|
export async function buildDaemonControl(manifest, arch, work, run) {
|
|
121
193
|
if (!manifest.daemon && !(manifest.compose && manifest.identityGateway))
|
|
122
194
|
return undefined;
|
|
@@ -151,7 +223,18 @@ export function copyDaemonPayload(manifest, arch, target, controller) {
|
|
|
151
223
|
copyPayloadTree(manifest.root, source, join(target, "payload"));
|
|
152
224
|
else
|
|
153
225
|
mkdirSync(join(target, "payload"), { recursive: true });
|
|
154
|
-
if (!manifest.compose) {
|
|
226
|
+
if (!manifest.compose && manifest.daemon?.node) {
|
|
227
|
+
const exec = join(source, daemon.exec);
|
|
228
|
+
if (!existsSync(exec) || !lstatSync(exec).isFile())
|
|
229
|
+
throw new StandaloneBuildError("STANDALONE_DAEMON_PAYLOAD_INVALID", `daemon.exec must name the Node entry file inside the payload: ${exec}`);
|
|
230
|
+
if (existsSync(join(source, NODE_LAUNCHER.split("/")[0])))
|
|
231
|
+
throw new StandaloneBuildError("STANDALONE_DAEMON_PAYLOAD_INVALID", `The daemon payload must not contain ${NODE_LAUNCHER.split("/")[0]}/; the CLI writes its Node launcher there.`);
|
|
232
|
+
mkdirSync(join(target, "payload", dirname(NODE_LAUNCHER)), { recursive: true });
|
|
233
|
+
chmodSync(join(target, "payload", dirname(NODE_LAUNCHER)), 0o755);
|
|
234
|
+
writeFileSync(join(target, "payload", NODE_LAUNCHER), renderNodeLauncher(manifest.daemon.node));
|
|
235
|
+
chmodSync(join(target, "payload", NODE_LAUNCHER), 0o755);
|
|
236
|
+
}
|
|
237
|
+
else if (!manifest.compose) {
|
|
155
238
|
const exec = join(source, daemon.exec);
|
|
156
239
|
const problem = checkCgiBinary(manifest.root, exec, arch);
|
|
157
240
|
if (problem)
|
|
@@ -25,8 +25,13 @@ 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
|
+
/** The DSM app class whose launch opens the SDK window: the console's when the App declares one. */
|
|
29
|
+
export declare function windowAppId(manifest: Pick<StandaloneManifest, "package" | "console">): string;
|
|
28
30
|
/**
|
|
29
|
-
* `ui/config`: registers the DSM main-menu app and its window class.
|
|
31
|
+
* `ui/config`: registers the DSM main-menu app and its window class. With a
|
|
32
|
+
* console, `.Instance` is the gateway's url launcher (new tab) and `.Console`
|
|
33
|
+
* the administrator-only window; DSM lists only `.Instance` under Application
|
|
34
|
+
* Privileges. A portal
|
|
30
35
|
* App's launcher also carries the Login Portal style and its icon as the
|
|
31
36
|
* login page icon (DSM shows `loginIcon`, never `icon`, there).
|
|
32
37
|
*/
|
|
@@ -9,7 +9,7 @@ import { HOST_ADAPTER_RUNTIME } from "./host-adapter.js";
|
|
|
9
9
|
import { permitsOrdinaryUsers } from "./api-permissions.js";
|
|
10
10
|
import { portalRuntime } from "./portal-window.js";
|
|
11
11
|
import { portalByExecutor } from "../build-spk/dsm-version.js";
|
|
12
|
-
import { uiTitleFields } from "../build-spk/ui-texts.js";
|
|
12
|
+
import { consoleTitle, uiConsoleTitleFields, uiTitleFields } from "../build-spk/ui-texts.js";
|
|
13
13
|
import { packageIcons, windowIconFile, windowIconTemplate, } from "../build-spk/dsm-icons.js";
|
|
14
14
|
import { DEFAULT_WINDOW, FULL_PAGE_MIN_WIDTH } from "./window-size.js";
|
|
15
15
|
import { requestTimeoutSeconds } from "./request-timeout.js";
|
|
@@ -74,7 +74,7 @@ function launcherAccess(manifest) {
|
|
|
74
74
|
return manifest.entry
|
|
75
75
|
? entryLauncherAccess(manifest.entry)
|
|
76
76
|
: {
|
|
77
|
-
allUsers: manifest.compose
|
|
77
|
+
allUsers: manifest.compose || manifest.console
|
|
78
78
|
? manifest.identityGateway?.access !== "dsm-admins"
|
|
79
79
|
: permitsOrdinaryUsers(manifest.apiPermissions),
|
|
80
80
|
};
|
|
@@ -92,19 +92,35 @@ export function appTitle(manifest) {
|
|
|
92
92
|
info: manifest.i18n?.info,
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* The window's name by locale, as `ui/texts` names it: `createI18n(lang,
|
|
97
|
+
* ...).t("app_title")`. A console window carries the console's name.
|
|
98
|
+
*/
|
|
96
99
|
function displayNameCatalogs(manifest) {
|
|
100
|
+
const name = (displayname) => manifest.console ? consoleTitle(displayname, manifest.console) : displayname;
|
|
97
101
|
const catalogs = {
|
|
98
|
-
en: { app_title: manifest.displayname },
|
|
102
|
+
en: { app_title: name(manifest.displayname) },
|
|
99
103
|
};
|
|
100
104
|
for (const [locale, text] of Object.entries(manifest.i18n?.info ?? {})) {
|
|
101
105
|
if (text?.displayname)
|
|
102
|
-
catalogs[locale] = { app_title: text.displayname };
|
|
106
|
+
catalogs[locale] = { app_title: name(text.displayname) };
|
|
103
107
|
}
|
|
104
108
|
return catalogs;
|
|
105
109
|
}
|
|
110
|
+
/** The DSM app class whose launch opens the SDK window: the console's when the App declares one. */
|
|
111
|
+
export function windowAppId(manifest) {
|
|
112
|
+
return `SYNO.SDS.${manifest.package}.${manifest.console ? "Console" : "Instance"}`;
|
|
113
|
+
}
|
|
114
|
+
/** Whether the window itself resumes a cold DSM sign-in; with a console the url launcher does. */
|
|
115
|
+
function windowResumesOIDC(manifest) {
|
|
116
|
+
const oidc = manifest.identityGateway?.oidc;
|
|
117
|
+
return Boolean((oidc?.native_login || oidc?.browser) && !manifest.console);
|
|
118
|
+
}
|
|
106
119
|
/**
|
|
107
|
-
* `ui/config`: registers the DSM main-menu app and its window class.
|
|
120
|
+
* `ui/config`: registers the DSM main-menu app and its window class. With a
|
|
121
|
+
* console, `.Instance` is the gateway's url launcher (new tab) and `.Console`
|
|
122
|
+
* the administrator-only window; DSM lists only `.Instance` under Application
|
|
123
|
+
* Privileges. A portal
|
|
108
124
|
* App's launcher also carries the Login Portal style and its icon as the
|
|
109
125
|
* login page icon (DSM shows `loginIcon`, never `icon`, there).
|
|
110
126
|
*/
|
|
@@ -127,6 +143,38 @@ export function renderUiConfig(manifest, icons = packageIcons(manifest.icon), lo
|
|
|
127
143
|
},
|
|
128
144
|
}, null, 2) + "\n");
|
|
129
145
|
}
|
|
146
|
+
if (manifest.console) {
|
|
147
|
+
const consoleFields = uiConsoleTitleFields(appTitle(manifest), manifest.console);
|
|
148
|
+
return `${JSON.stringify({
|
|
149
|
+
".url": {
|
|
150
|
+
[`${ns}.Instance`]: {
|
|
151
|
+
type: "url",
|
|
152
|
+
...titleFields,
|
|
153
|
+
icon,
|
|
154
|
+
...launcherAccess(manifest),
|
|
155
|
+
url: gatewayEntryPath(manifest),
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
[windowScriptName(manifest)]: {
|
|
159
|
+
[`${ns}.Console`]: {
|
|
160
|
+
type: "app",
|
|
161
|
+
...consoleFields,
|
|
162
|
+
icon,
|
|
163
|
+
appWindow: `${ns}.MainWindow`,
|
|
164
|
+
allowMultiInstance: false,
|
|
165
|
+
allUsers: false,
|
|
166
|
+
depend: [`${ns}.MainWindow`],
|
|
167
|
+
},
|
|
168
|
+
[`${ns}.MainWindow`]: {
|
|
169
|
+
type: "lib",
|
|
170
|
+
title: consoleFields.title,
|
|
171
|
+
texts: consoleFields.texts,
|
|
172
|
+
icon,
|
|
173
|
+
depend: [],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
}, null, 2)}\n`;
|
|
177
|
+
}
|
|
130
178
|
return `${JSON.stringify({
|
|
131
179
|
[windowScriptName(manifest)]: {
|
|
132
180
|
[`${ns}.Instance`]: {
|
|
@@ -379,7 +427,7 @@ ${ns}.prepareFullPage = function (title) {
|
|
|
379
427
|
favicon.href = ${ns}.appIcon;
|
|
380
428
|
};
|
|
381
429
|
|
|
382
|
-
${(manifest
|
|
430
|
+
${windowResumesOIDC(manifest) ? `${ns}.resumeOIDC = ${oidcResumeRuntime(pkg, Object.values(manifest.identityGateway.listen).filter((p) => typeof p === "number"))};` : ""}
|
|
383
431
|
|
|
384
432
|
// DSM reopens a window at the size it was last left at, even one saved on a
|
|
385
433
|
// larger screen, and only moves a window that does not fit: keep it inside.
|
|
@@ -390,7 +438,7 @@ ${ns}.fitDesktop = function (size) {
|
|
|
390
438
|
return size;
|
|
391
439
|
};
|
|
392
440
|
|
|
393
|
-
Ext.define(${JSON.stringify(
|
|
441
|
+
Ext.define(${JSON.stringify(windowAppId(manifest))}, {
|
|
394
442
|
extend: "SYNO.SDS.AppInstance",
|
|
395
443
|
appWindowName: ${JSON.stringify(`${ns}.MainWindow`)}
|
|
396
444
|
});
|
|
@@ -408,7 +456,7 @@ Ext.define(${JSON.stringify(`${ns}.MainWindow`)}, {
|
|
|
408
456
|
},
|
|
409
457
|
onOpen: function (param) {
|
|
410
458
|
this.callParent(arguments);
|
|
411
|
-
${(manifest
|
|
459
|
+
${windowResumesOIDC(manifest) ? `if (${ns}.resumeOIDC(param)) return;` : ""}
|
|
412
460
|
var me = this;
|
|
413
461
|
if (me.mounted) {
|
|
414
462
|
me.reopenEvents.emit();
|
|
@@ -471,7 +519,7 @@ ${root ? root.mount : ""} ${ns}.loadApp(function (app) {
|
|
|
471
519
|
// with that launch's param. Before the App mounts, it mounts with the latest.
|
|
472
520
|
onRequest: function (param) {
|
|
473
521
|
this.callParent(arguments);
|
|
474
|
-
${(manifest
|
|
522
|
+
${windowResumesOIDC(manifest) ? `if (${ns}.resumeOIDC(param)) return;` : ""}
|
|
475
523
|
var params = ${ns}.launchParams(param);
|
|
476
524
|
if (!this.mounted) {
|
|
477
525
|
if (this.mountPending && Object.keys(params).length) this.launchParams = params;
|