@synopackageland/cli 0.13.1 → 0.15.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +44 -1
  3. package/dist/build-spk/generator.d.ts +3 -2
  4. package/dist/build-spk/generator.js +6 -850
  5. package/dist/build-spk/resource-worker.d.ts +2 -14
  6. package/dist/build-spk/resource-worker.js +3 -166
  7. package/dist/cli.js +208 -56
  8. package/dist/compose/analyze.d.ts +3 -61
  9. package/dist/compose/analyze.js +35 -776
  10. package/dist/compose/env.d.ts +5 -3
  11. package/dist/compose/env.js +17 -25
  12. package/dist/compose/runner.js +23 -6
  13. package/dist/contract/app-launch.d.ts +11 -0
  14. package/dist/contract/app-launch.js +18 -0
  15. package/dist/contract/web-launch.d.ts +3 -13
  16. package/dist/contract/web-launch.js +14 -81
  17. package/dist/ctl/cli.d.ts +36 -0
  18. package/dist/ctl/cli.js +218 -0
  19. package/dist/ctl/descriptor.d.ts +81 -0
  20. package/dist/ctl/descriptor.js +93 -0
  21. package/dist/ctl/dsm-auth.d.ts +10 -0
  22. package/dist/ctl/dsm-auth.js +30 -0
  23. package/dist/ctl/errors.d.ts +17 -0
  24. package/dist/ctl/errors.js +32 -0
  25. package/dist/ctl/flows.d.ts +11 -0
  26. package/dist/ctl/flows.js +52 -0
  27. package/dist/ctl/input.d.ts +20 -0
  28. package/dist/ctl/input.js +128 -0
  29. package/dist/ctl/json.d.ts +2 -0
  30. package/dist/ctl/json.js +38 -0
  31. package/dist/ctl/redaction.d.ts +15 -0
  32. package/dist/ctl/redaction.js +198 -0
  33. package/dist/ctl/service.d.ts +38 -0
  34. package/dist/ctl/service.js +156 -0
  35. package/dist/ctl/store.d.ts +51 -0
  36. package/dist/ctl/store.js +93 -0
  37. package/dist/ctl/transfers.d.ts +14 -0
  38. package/dist/ctl/transfers.js +74 -0
  39. package/dist/ctl/transport.d.ts +11 -0
  40. package/dist/ctl/transport.js +79 -0
  41. package/dist/deploy/deploy.d.ts +4 -3
  42. package/dist/deploy/deploy.js +5 -210
  43. package/dist/deploy/plan.js +26 -7
  44. package/dist/dev.js +5 -4
  45. package/dist/error-registry.js +1020 -831
  46. package/dist/info.js +120 -28
  47. package/dist/init.js +188 -64
  48. package/dist/inspect.js +21 -6
  49. package/dist/runtime/executor-digests.json +1 -12
  50. package/dist/skill.js +51 -9
  51. package/dist/standalone/api-description-inspect.d.ts +4 -0
  52. package/dist/standalone/api-description-inspect.js +12 -0
  53. package/dist/standalone/api-description.d.ts +7 -0
  54. package/dist/standalone/api-description.js +73 -0
  55. package/dist/standalone/build.d.ts +3 -0
  56. package/dist/standalone/build.js +140 -33
  57. package/dist/standalone/compose-errors.d.ts +3 -0
  58. package/dist/standalone/compose-errors.js +243 -0
  59. package/dist/standalone/compose.d.ts +97 -10
  60. package/dist/standalone/compose.js +306 -75
  61. package/dist/standalone/daemon.js +111 -29
  62. package/dist/standalone/dsm-window.js +55 -9
  63. package/dist/standalone/identity-gateway.d.ts +9 -2
  64. package/dist/standalone/identity-gateway.js +282 -66
  65. package/dist/standalone/inspect-spk.d.ts +15 -0
  66. package/dist/standalone/inspect-spk.js +714 -128
  67. package/dist/standalone/lifecycle.js +124 -42
  68. package/dist/standalone/manifest.d.ts +10 -1
  69. package/dist/standalone/manifest.js +308 -87
  70. package/dist/standalone/resource.js +6 -2
  71. package/dist/standalone/spk.d.ts +4 -1
  72. package/dist/standalone/spk.js +139 -39
  73. package/dist/template-paths.d.ts +0 -2
  74. package/dist/template-paths.js +0 -5
  75. package/dist/test-command.d.ts +1 -1
  76. package/dist/test-command.js +1 -1
  77. package/dist/validate.d.ts +3 -3
  78. package/dist/validate.js +32 -140
  79. package/package.json +5 -3
  80. package/schema/synology-app.schema.json +1 -1
  81. package/schema/synopkgland-api-v1.schema.json +423 -0
  82. package/skills/SKILL.md +34 -1
  83. package/skills/reference/compose.md +349 -0
  84. package/skills/reference/create.md +26 -4
  85. package/skills/reference/ctl.md +41 -0
  86. package/skills/reference/deploy.md +1 -1
  87. package/skills/reference/files.md +4 -0
  88. package/skills/reference/identity-gateway.md +8 -138
  89. package/skills/reference/portal.md +31 -0
  90. package/skills/reference/ui-vue.md +31 -0
  91. package/skills/reference/wrap.md +10 -30
  92. package/assets/go/daemon/fixture_linux_test.go +0 -15
  93. package/assets/go/daemon/fixture_other_test.go +0 -7
  94. package/assets/go/daemon/go.mod +0 -5
  95. package/assets/go/daemon/main.go +0 -636
  96. package/assets/go/daemon/main_test.go +0 -315
  97. package/assets/go/daemon/network.go +0 -107
  98. package/assets/go/daemon/network_test.go +0 -86
  99. package/assets/go/daemon/process_linux.go +0 -55
  100. package/assets/go/daemon/process_linux_test.go +0 -161
  101. package/assets/go/daemon/process_other.go +0 -14
  102. package/assets/go/daemon/testdata/child.go +0 -44
  103. package/assets/go/gateway/auth.go +0 -101
  104. package/assets/go/gateway/auth_test.go +0 -216
  105. package/assets/go/gateway/cgi.go +0 -133
  106. package/assets/go/gateway/cgi_test.go +0 -156
  107. package/assets/go/gateway/csrf.go +0 -73
  108. package/assets/go/gateway/csrf_test.go +0 -155
  109. package/assets/go/gateway/directory.go +0 -108
  110. package/assets/go/gateway/directory_test.go +0 -202
  111. package/assets/go/gateway/gateway.go +0 -412
  112. package/assets/go/gateway/gateway_test.go +0 -676
  113. package/assets/go/gateway/go.mod +0 -5
  114. package/assets/go/gateway/groups.go +0 -66
  115. package/assets/go/gateway/main.go +0 -45
  116. package/dist/build-spk/gateway.d.ts +0 -31
  117. package/dist/build-spk/gateway.js +0 -60
  118. package/dist/compose/privilege.d.ts +0 -14
  119. package/dist/compose/privilege.js +0 -102
@@ -1,37 +1,63 @@
1
1
  import { goChildEnv } from "../runtime/go-env.js";
2
- import { chmodSync, copyFileSync, lstatSync, mkdirSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
2
+ import { chmodSync, copyFileSync, lstatSync, mkdirSync, readdirSync, realpathSync, writeFileSync, } from "node:fs";
3
3
  import { isAbsolute, join, relative, resolve } from "node:path";
4
- import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem } from "../build-spk/go-toolchain.js";
5
- import { goAssetDir } from "../template-paths.js";
4
+ import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem, } from "../build-spk/go-toolchain.js";
5
+ import { sdkSource, appSdkVersion } from "../runtime/sdk-source.js";
6
+ import { CLI_VERSION } from "../cli-version.js";
6
7
  import { checkCgiBinary } from "./binary.js";
7
8
  import { composeDaemon, composeNetwork } from "./compose.js";
8
9
  import { gatewayDaemon } from "./identity-gateway.js";
9
10
  import { StandaloneBuildError } from "./spk.js";
10
- const fields = new Set(["exec", "targets", "args", "env", "timeouts", "uninstall_args"]);
11
+ const fields = new Set([
12
+ "exec",
13
+ "targets",
14
+ "args",
15
+ "env",
16
+ "timeouts",
17
+ "uninstall_args",
18
+ ]);
11
19
  const record = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
12
20
  /** A fixed argv or env value: only the package's own directories expand. */
13
- export const token = (v) => typeof v === "string" && Buffer.byteLength(v) <= 4096 && !v.includes("\0")
14
- && !v.replaceAll("${SYNOPKG_PKGDEST}", "").replaceAll("${SYNOPKG_PKGVAR}", "").includes("${");
21
+ export const token = (v) => typeof v === "string" &&
22
+ Buffer.byteLength(v) <= 4096 &&
23
+ !v.includes("\0") &&
24
+ !v
25
+ .replaceAll("${SYNOPKG_PKGDEST}", "")
26
+ .replaceAll("${SYNOPKG_PKGVAR}", "")
27
+ .includes("${");
15
28
  /** An application variable that cannot steer the loader, the shell or DSM. */
16
- export const envKey = (k) => /^[A-Z][A-Z0-9_]{0,63}$/.test(k)
17
- && !/^(PATH|HOME|TMPDIR|ENV|BASH_ENV|SHELLOPTS|BASHOPTS|GODEBUG|GOTRACEBACK|LD_.*|DYLD_.*|SYNOPKG_.*)$/.test(k);
29
+ export const envKey = (k) => /^[A-Z][A-Z0-9_]{0,63}$/.test(k) &&
30
+ !/^(PATH|HOME|TMPDIR|ENV|BASH_ENV|SHELLOPTS|BASHOPTS|GODEBUG|GOTRACEBACK|LD_.*|DYLD_.*|SYNOPKG_.*)$/.test(k);
18
31
  /** A relative path to a fixed executable, without `.` or `..` parts. */
19
- export const relativeExec = (v) => typeof v === "string" && v.length <= 200
20
- && /^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(v) && !v.split("/").some(p => p === "." || p === "..");
32
+ export const relativeExec = (v) => typeof v === "string" &&
33
+ v.length <= 200 &&
34
+ /^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(v) &&
35
+ !v.split("/").some((p) => p === "." || p === "..");
21
36
  export function readDaemon(value, root, arches, fail) {
22
37
  if (value === undefined)
23
38
  return undefined;
24
- const invalid = () => { fail({ code: "STANDALONE_DAEMON_INVALID", message: "daemon accepts only fixed exec, targets, args, env and timeouts; paths must be inside the project, and the environment does not accept loader/shell/system variables." }); return undefined; };
25
- if (!record(value) || Object.keys(value).some(k => !fields.has(k)))
39
+ const invalid = () => {
40
+ fail({
41
+ code: "STANDALONE_DAEMON_INVALID",
42
+ message: "daemon accepts only fixed exec, targets, args, env and timeouts; paths must be inside the project, and the environment does not accept loader/shell/system variables.",
43
+ });
44
+ return undefined;
45
+ };
46
+ if (!record(value) || Object.keys(value).some((k) => !fields.has(k)))
26
47
  return invalid();
27
48
  if (!relativeExec(value.exec))
28
49
  return invalid();
29
- if (!record(value.targets) || Object.keys(value.targets).length !== arches.length)
50
+ if (!record(value.targets) ||
51
+ Object.keys(value.targets).length !== arches.length)
30
52
  return invalid();
31
53
  const targets = {};
32
54
  for (const arch of arches) {
33
55
  const path = value.targets[arch];
34
- if (typeof path !== "string" || !path.trim() || path.includes("\0") || isAbsolute(path) || path.split(/[\\/]/).includes(".."))
56
+ if (typeof path !== "string" ||
57
+ !path.trim() ||
58
+ path.includes("\0") ||
59
+ isAbsolute(path) ||
60
+ path.split(/[\\/]/).includes(".."))
35
61
  return invalid();
36
62
  const resolved = resolve(root, path), rel = relative(root, resolved);
37
63
  if (!rel || rel.startsWith("..") || isAbsolute(rel))
@@ -42,32 +68,81 @@ export function readDaemon(value, root, arches, fail) {
42
68
  if (!Array.isArray(args) || args.length > 64 || !args.every(token))
43
69
  return invalid();
44
70
  const uninstall = value.uninstall_args;
45
- if (uninstall !== undefined && (!Array.isArray(uninstall) || !uninstall.length || uninstall.length > 64 || !uninstall.every(token)))
71
+ if (uninstall !== undefined &&
72
+ (!Array.isArray(uninstall) ||
73
+ !uninstall.length ||
74
+ uninstall.length > 64 ||
75
+ !uninstall.every(token)))
46
76
  return invalid();
47
77
  const env = value.env ?? {};
48
- if (!record(env) || Object.keys(env).length > 64 || Object.entries(env).some(([k, v]) => !envKey(k) || !token(v)))
78
+ if (!record(env) ||
79
+ Object.keys(env).length > 64 ||
80
+ Object.entries(env).some(([k, v]) => !envKey(k) || !token(v)))
49
81
  return invalid();
50
82
  const limits = value.timeouts ?? {};
51
- if (!record(limits) || Object.keys(limits).some(k => !["start", "stop", "status", "uninstall"].includes(k)))
83
+ if (!record(limits) ||
84
+ Object.keys(limits).some((k) => !["start", "stop", "status", "uninstall"].includes(k)))
52
85
  return invalid();
53
86
  // Defaults hold under IO load on a busy NAS; DSM's package unit allows 3600 s.
54
- const timeouts = { start: limits.start ?? 30, stop: limits.stop ?? 30, status: limits.status ?? 5, uninstall: limits.uninstall ?? 60 };
87
+ const timeouts = {
88
+ start: limits.start ?? 30,
89
+ stop: limits.stop ?? 30,
90
+ status: limits.status ?? 5,
91
+ uninstall: limits.uninstall ?? 60,
92
+ };
55
93
  for (const [key, v] of Object.entries(timeouts)) {
56
- if (!Number.isInteger(v) || v < (key === "status" ? 1 : 2) || v > (key === "status" ? 10 : 120))
94
+ if (!Number.isInteger(v) ||
95
+ v < (key === "status" ? 1 : 2) ||
96
+ v > (key === "status" ? 10 : 120))
57
97
  return invalid();
58
98
  }
59
- return { exec: value.exec, targets, args, ...(uninstall ? { uninstall_args: uninstall } : {}), env: Object.fromEntries(Object.keys(env).sort().map(key => [key, env[key]])), timeouts: timeouts };
99
+ return {
100
+ exec: value.exec,
101
+ targets,
102
+ args,
103
+ ...(uninstall ? { uninstall_args: uninstall } : {}),
104
+ env: Object.fromEntries(Object.keys(env)
105
+ .sort()
106
+ .map((key) => [key, env[key]])),
107
+ timeouts: timeouts,
108
+ };
60
109
  }
61
110
  export function daemonConfig(manifest) {
62
- const { exec, args, uninstall_args, env, timeouts } = manifest.compose ? composeDaemon(manifest) : manifest.identityGateway ? gatewayDaemon(manifest.daemon) : manifest.daemon;
63
- return JSON.stringify({ package: manifest.package, exec, args, ...(uninstall_args ? { uninstall_args } : {}), env, timeouts, ...(manifest.compose ? { network: composeNetwork(manifest) } : {}) }) + "\n";
111
+ const { exec, args, uninstall_args, env, timeouts } = manifest.compose
112
+ ? composeDaemon(manifest)
113
+ : manifest.identityGateway
114
+ ? gatewayDaemon(manifest.daemon)
115
+ : manifest.daemon;
116
+ return (JSON.stringify({
117
+ package: manifest.package,
118
+ exec,
119
+ args,
120
+ ...(uninstall_args ? { uninstall_args } : {}),
121
+ env,
122
+ timeouts,
123
+ ...(manifest.compose ? { network: composeNetwork(manifest) } : {}),
124
+ }) + "\n");
64
125
  }
65
126
  export async function buildDaemonControl(manifest, arch, work, run) {
66
- if (!manifest.daemon && !manifest.compose)
127
+ if (!manifest.daemon && !(manifest.compose && manifest.identityGateway))
67
128
  return undefined;
68
129
  const out = join(work, `daemon-control-${arch}`);
69
- const result = await run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", "-s -w -buildid=", "-o", out, "."], {
70
- cwd: goAssetDir("daemon"), env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }),
130
+ const result = await run("go", [
131
+ "build",
132
+ "-trimpath",
133
+ "-buildvcs=false",
134
+ "-ldflags",
135
+ "-s -w -buildid=",
136
+ "-o",
137
+ out,
138
+ "./daemonctl",
139
+ ], {
140
+ cwd: sdkSource(manifest.identityGateway?.sdk ??
141
+ (manifest.backend.kind === "go"
142
+ ? appSdkVersion(manifest.backend)
143
+ : undefined) ??
144
+ CLI_VERSION).goDir,
145
+ env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }),
71
146
  });
72
147
  if (result.missing || result.status !== 0)
73
148
  throw new StandaloneBuildError("STANDALONE_DAEMON_BUILD_FAILED", `daemon controller build failed (${arch}). ${PINNED_GO_HINT}\n${result.stderr}`);
@@ -82,7 +157,10 @@ export async function buildDaemonControl(manifest, arch, work, run) {
82
157
  export function copyDaemonPayload(manifest, arch, target, controller) {
83
158
  const daemon = manifest.daemon ?? composeDaemon(manifest);
84
159
  const source = daemon.targets[arch];
85
- copyPayloadTree(manifest.root, source, join(target, "payload"));
160
+ if (source)
161
+ copyPayloadTree(manifest.root, source, join(target, "payload"));
162
+ else
163
+ mkdirSync(join(target, "payload"), { recursive: true });
86
164
  if (!manifest.compose) {
87
165
  const exec = join(source, daemon.exec);
88
166
  const problem = checkCgiBinary(manifest.root, exec, arch);
@@ -92,11 +170,15 @@ export function copyDaemonPayload(manifest, arch, target, controller) {
92
170
  }
93
171
  copyFileSync(controller, join(target, "control"));
94
172
  chmodSync(join(target, "control"), 0o755);
95
- writeFileSync(join(target, "config.json"), daemonConfig(manifest), { mode: 0o644 });
173
+ writeFileSync(join(target, "config.json"), daemonConfig(manifest), {
174
+ mode: 0o644,
175
+ });
96
176
  }
97
177
  /** Copies a bounded, link-free package-account payload tree. */
98
178
  export function copyPayloadTree(root, source, target) {
99
- const invalid = (message) => { throw new StandaloneBuildError("STANDALONE_DAEMON_PAYLOAD_INVALID", message); };
179
+ const invalid = (message) => {
180
+ throw new StandaloneBuildError("STANDALONE_DAEMON_PAYLOAD_INVALID", message);
181
+ };
100
182
  let total = 0;
101
183
  const walk = (from, to) => {
102
184
  const stat = lstatSync(from);
@@ -111,7 +193,7 @@ export function copyPayloadTree(root, source, target) {
111
193
  }
112
194
  else if (stat.isFile()) {
113
195
  total += stat.size;
114
- if (total > 512 * 1024 * 1024 || (stat.mode & 0o6000))
196
+ if (total > 512 * 1024 * 1024 || stat.mode & 0o6000)
115
197
  invalid(`The daemon payload does not accept setid bits or more than 512 MiB: ${from}`);
116
198
  copyFileSync(from, to);
117
199
  chmodSync(to, stat.mode & 0o111 ? 0o755 : 0o644);
@@ -1,13 +1,14 @@
1
+ import { resolvedSdkLaunch, dsmLauncherType } from "../contract/app-launch.js";
1
2
  import { REOPEN_EVENTS_RUNTIME } from "./reopen-events.js";
2
3
  import { LAUNCH_PARAMS_RUNTIME } from "./launch-params.js";
3
4
  import { DSM_LANGS } from "./i18n.js";
4
- import { PASSWORD_CONFIRM, ROOT_PANEL, ROOT_RUNTIME, rootPanelCatalogs } from "./root-window.js";
5
+ import { PASSWORD_CONFIRM, ROOT_PANEL, ROOT_RUNTIME, rootPanelCatalogs, } from "./root-window.js";
5
6
  import { HOST_ADAPTER_RUNTIME } from "./host-adapter.js";
6
7
  import { permitsOrdinaryUsers } from "./api-permissions.js";
7
8
  import { portalRuntime } from "./portal-window.js";
8
9
  import { portalByExecutor } from "../build-spk/dsm-version.js";
9
10
  import { uiTitleFields } from "../build-spk/ui-texts.js";
10
- import { packageIcons, windowIconFile, windowIconTemplate } from "../build-spk/dsm-icons.js";
11
+ import { packageIcons, windowIconFile, windowIconTemplate, } from "../build-spk/dsm-icons.js";
11
12
  import { DEFAULT_WINDOW, FULL_PAGE_MIN_WIDTH } from "./window-size.js";
12
13
  import { requestTimeoutSeconds } from "./request-timeout.js";
13
14
  import { appSdkTimeouts } from "../runtime/sdk-timeouts.js";
@@ -67,7 +68,13 @@ export function windowScriptName(manifest) {
67
68
  * IP rules); DSM then filters the launcher and the CGI checks the same rule.
68
69
  */
69
70
  function launcherAccess(manifest) {
70
- return manifest.entry ? entryLauncherAccess(manifest.entry) : { allUsers: permitsOrdinaryUsers(manifest.apiPermissions) };
71
+ return manifest.entry
72
+ ? entryLauncherAccess(manifest.entry)
73
+ : {
74
+ allUsers: manifest.compose
75
+ ? manifest.identityGateway?.access !== "dsm-admins"
76
+ : permitsOrdinaryUsers(manifest.apiPermissions),
77
+ };
71
78
  }
72
79
  /** The launcher fields an entry mode fixes; inspect checks a built SPK against them. */
73
80
  export function entryLauncherAccess(entry) {
@@ -76,11 +83,17 @@ export function entryLauncherAccess(entry) {
76
83
  : { allUsers: true, grantPrivilege: "all", advanceGrantPrivilege: true };
77
84
  }
78
85
  export function appTitle(manifest) {
79
- return { displayname: manifest.displayname, description: manifest.description, info: manifest.i18n?.info };
86
+ return {
87
+ displayname: manifest.displayname,
88
+ description: manifest.description,
89
+ info: manifest.i18n?.info,
90
+ };
80
91
  }
81
92
  /** The app name by locale, as `ui/texts` names the window: `createI18n(lang, ...).t("app_title")`. */
82
93
  function displayNameCatalogs(manifest) {
83
- const catalogs = { en: { app_title: manifest.displayname } };
94
+ const catalogs = {
95
+ en: { app_title: manifest.displayname },
96
+ };
84
97
  for (const [locale, text] of Object.entries(manifest.i18n?.info ?? {})) {
85
98
  if (text?.displayname)
86
99
  catalogs[locale] = { app_title: text.displayname };
@@ -96,10 +109,26 @@ export function renderUiConfig(manifest, icons = packageIcons(manifest.icon), lo
96
109
  const icon = windowIconTemplate(icons);
97
110
  const ns = `SYNO.SDS.${manifest.package}`;
98
111
  const titleFields = uiTitleFields(appTitle(manifest));
112
+ if (manifest.compose) {
113
+ if (!manifest.identityGateway)
114
+ return "{}\n";
115
+ return (JSON.stringify({
116
+ ".url": {
117
+ [`${ns}.Instance`]: {
118
+ type: dsmLauncherType(resolvedSdkLaunch(manifest), false),
119
+ ...titleFields,
120
+ icon,
121
+ ...launcherAccess(manifest),
122
+ url: `/webman/3rdparty/${manifest.package}/sso/open.cgi`,
123
+ },
124
+ },
125
+ }, null, 2) + "\n");
126
+ }
99
127
  return `${JSON.stringify({
100
128
  [windowScriptName(manifest)]: {
101
129
  [`${ns}.Instance`]: {
102
- type: "app",
130
+ type: dsmLauncherType(resolvedSdkLaunch(manifest), true),
131
+ ...(manifest.portal ? { defaultAlias: manifest.portal.alias } : {}),
103
132
  ...titleFields,
104
133
  icon,
105
134
  appWindow: `${ns}.MainWindow`,
@@ -146,7 +175,8 @@ export function renderWindowScript(manifest, assetVersion, icons = packageIcons(
146
175
  throw new Error("A portal App's window needs the SDK's portal panel text.");
147
176
  return `/* Generated by synopkgland build. Edit your UI in the project's ui folder. */
148
177
  Ext.ns(${JSON.stringify(ns)});
149
- ${sessionRuntime ? `
178
+ ${sessionRuntime
179
+ ? `
150
180
  // The SDK owns recovery; all calls share one bounded, same-origin attempt.
151
181
  ${ns}.session = (function () {
152
182
  ${sessionRuntime}
@@ -156,7 +186,8 @@ ${sessionRuntime}
156
186
  fetch: function (url, options) { return fetch(url, options); }
157
187
  });
158
188
  })();
159
- ` : ''}
189
+ `
190
+ : ""}
160
191
 
161
192
  // The SDK's clientTimeout: past the backend's request deadline
162
193
  // (ctx.requestTimeout, from backend.timeout_seconds), so its request_timeout
@@ -316,6 +347,18 @@ ${ns}.fullPage = function () {
316
347
  };
317
348
  ${ns}.appIcon = ${JSON.stringify(`webman/3rdparty/${pkg}/${windowIconFile(icons, 64)}`)};
318
349
 
350
+ // Standalone pages own their title, favicon and device viewport, for any UI.
351
+ ${ns}.prepareFullPage = function (title) {
352
+ if (!${ns}.fullPage()) return;
353
+ document.title = title;
354
+ var viewport = document.querySelector && document.querySelector('meta[name="viewport"]');
355
+ if (!viewport) { viewport = document.createElement("meta"); viewport.name = "viewport"; document.head.appendChild(viewport); }
356
+ viewport.content = "width=device-width, initial-scale=1, viewport-fit=cover";
357
+ var favicon = document.querySelector && document.querySelector('link[rel="icon"]');
358
+ if (!favicon) { favicon = document.createElement("link"); favicon.rel = "icon"; document.head.appendChild(favicon); }
359
+ favicon.href = ${ns}.appIcon;
360
+ };
361
+
319
362
  // DSM reopens a window at the size it was last left at, even one saved on a
320
363
  // larger screen, and only moves a window that does not fit: keep it inside.
321
364
  ${ns}.fitDesktop = function (size) {
@@ -367,6 +410,7 @@ ${root ? root.mount : ""} ${ns}.loadApp(function (app) {
367
410
  var i18n = ${ns}.createI18n(lang, ${ns}.messages);
368
411
  me.hostAccess = ${ns}.createHost({ appWindow: me, locale: i18n.locale });
369
412
  root.lang = i18n.locale;
413
+ ${ns}.prepareFullPage(${ns}.createI18n(lang, ${JSON.stringify(displayNameCatalogs(manifest))}).t("app_title"));
370
414
  try {
371
415
  me.mounted = app.mount(root, Ext.apply(ctx, {
372
416
  displayName: ${ns}.createI18n(lang, ${JSON.stringify(displayNameCatalogs(manifest))}).t("app_title"),
@@ -532,7 +576,9 @@ ${PASSWORD_CONFIRM}
532
576
  call: call,
533
577
  wait: function (ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); }
534
578
  });
535
- ${settingsOnlyApproval(manifest) ? "" : ` var panels = me.body && me.body.dom.querySelector(".dsmapp-rootpanels");
579
+ ${settingsOnlyApproval(manifest)
580
+ ? ""
581
+ : ` var panels = me.body && me.body.dom.querySelector(".dsmapp-rootpanels");
536
582
  if (panels) {
537
583
  ${ns}.mountRootPanel(panels, access, ${ns}.createI18n(lang, ${JSON.stringify(rootPanelCatalogs())}).t, session().admin);
538
584
  }
@@ -1,5 +1,6 @@
1
1
  import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
2
2
  import type { CommandRunner } from "./build.js";
3
+ import { type WebAccessMode } from "../contract/web-launch.js";
3
4
  import { type Daemon } from "./daemon.js";
4
5
  import type { StandaloneError, StandaloneManifest } from "./manifest.js";
5
6
  /** The SDK's identity gateway: the daemon wrapper and the DSM-origin authorize CGI. */
@@ -10,7 +11,7 @@ export declare const GATEWAY_CGI_PATH = "ui/sso/authorize.cgi";
10
11
  export declare const GATEWAY_PAYLOAD_DIR = "_sso";
11
12
  export declare const GATEWAY_EXEC = "_sso/gateway";
12
13
  export declare const GATEWAY_CONFIG_PATH = "sso/gateway.json";
13
- export type GatewayMode = "oidc" | "header" | "token";
14
+ export type GatewayMode = "none" | "oidc" | "header" | "token";
14
15
  export interface GatewayRoles {
15
16
  claim: string;
16
17
  admin: string;
@@ -28,6 +29,10 @@ export interface IdentityGateway {
28
29
  /** The SDK release the gateway is built from. */
29
30
  sdk: string;
30
31
  mode: GatewayMode;
32
+ access?: WebAccessMode;
33
+ groups?: string[];
34
+ max_upload_bytes?: number;
35
+ response_header_timeout_seconds?: number;
31
36
  /** The App's loopback port. */
32
37
  upstream: number;
33
38
  listen: {
@@ -64,10 +69,12 @@ export type DeleteAction = {
64
69
  } | {
65
70
  exec: string[];
66
71
  };
67
- export declare function readIdentityGateway(value: unknown, daemon: Pick<Daemon, "targets"> | undefined, fail: (error: StandaloneError) => void): IdentityGateway | undefined;
72
+ export declare function readIdentityGateway(value: unknown, daemon: Pick<Daemon, "targets"> | undefined, fail: (error: StandaloneError) => void, packagedPayload?: boolean, composeBridge?: boolean): IdentityGateway | undefined;
68
73
  /** target/sso/gateway.json, the SDK gateway's configuration (idgateway.Config). */
69
74
  export declare function identityGatewayConfig(m: StandaloneManifest): string;
70
75
  /** The daemon as the controller runs it: the gateway, with the App as its child. */
71
76
  export declare function gatewayDaemon(daemon: Daemon): Daemon;
72
77
  /** Builds the gateway from the declared SDK release with the pinned Go, binding its config path. */
73
78
  export declare function buildIdentityGateway(m: StandaloneManifest, arch: NativeArchFamily, work: string, run: CommandRunner): Promise<string | undefined>;
79
+ /** The manifest form of a normalized gateway, retained for artifact inspection. */
80
+ export declare function identityGatewayDeclaration(g: IdentityGateway | undefined): unknown;