@synopackageland/cli 0.34.0 → 0.36.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 +39 -0
- package/README.md +6 -3
- package/assets/go/route-audit/main.go +549 -45
- package/dist/build-spk/install-wizard.d.ts +2 -0
- package/dist/build-spk/install-wizard.js +4 -0
- package/dist/build-spk/wizard-messages.d.ts +22 -0
- package/dist/build-spk/wizard-messages.js +22 -0
- package/dist/cli.js +26 -0
- package/dist/error-registry.js +10 -0
- package/dist/standalone/api-description.js +3 -0
- package/dist/standalone/api-permissions.d.ts +4 -1
- package/dist/standalone/api-permissions.js +10 -4
- package/dist/standalone/build.js +15 -2
- package/dist/standalone/inspect-spk.d.ts +2 -0
- package/dist/standalone/inspect-spk.js +16 -10
- package/dist/standalone/manifest.d.ts +6 -0
- package/dist/standalone/manifest.js +40 -5
- package/dist/standalone/route-audit.d.ts +9 -3
- package/dist/standalone/route-audit.js +6 -2
- package/dist/standalone/shared-folder.d.ts +6 -1
- package/dist/standalone/shared-folder.js +13 -0
- package/dist/standalone/spk.js +5 -6
- package/package.json +1 -1
- package/skills/SKILL.md +43 -3
|
@@ -16,5 +16,7 @@ export interface InstallWizardFiles {
|
|
|
16
16
|
* items, and only the dynamic one adds the reuse notice when data exists.
|
|
17
17
|
*/
|
|
18
18
|
export declare function renderInstallWizardFiles(packageName: string, render: (t: Translate) => InstallWizardStep[]): InstallWizardFiles;
|
|
19
|
+
/** Lists the App's unauthenticated service callbacks for the administrator. */
|
|
20
|
+
export declare function integrationWizardSteps(t: Translate, methods?: readonly string[]): InstallWizardStep[];
|
|
19
21
|
/** The native-go install step: no parameters, only the app-space consent. */
|
|
20
22
|
export declare function renderNativeInstallWizard(t: Translate): InstallWizardStep[];
|
|
@@ -70,6 +70,10 @@ export function renderInstallWizardFiles(packageName, render) {
|
|
|
70
70
|
}
|
|
71
71
|
return { static: staticFiles, dynamic };
|
|
72
72
|
}
|
|
73
|
+
/** Lists the App's unauthenticated service callbacks for the administrator. */
|
|
74
|
+
export function integrationWizardSteps(t, methods = []) {
|
|
75
|
+
return methods.length ? [{ step_title: t("integration.step"), items: [{ desc: t("integration.notice", { methods: methods.join(", ") }) }] }] : [];
|
|
76
|
+
}
|
|
73
77
|
/** The native-go install step: no parameters, only the app-space consent. */
|
|
74
78
|
export function renderNativeInstallWizard(t) {
|
|
75
79
|
return [{ step_title: t("install.step"), items: [{ desc: t("consent.appOwnSpace") }] }];
|
|
@@ -230,6 +230,28 @@ export declare const WIZARD_MESSAGES: {
|
|
|
230
230
|
es: string;
|
|
231
231
|
it: string;
|
|
232
232
|
};
|
|
233
|
+
"integration.step": {
|
|
234
|
+
en: string;
|
|
235
|
+
"zh-TW": string;
|
|
236
|
+
"zh-CN": string;
|
|
237
|
+
ja: string;
|
|
238
|
+
ko: string;
|
|
239
|
+
de: string;
|
|
240
|
+
fr: string;
|
|
241
|
+
es: string;
|
|
242
|
+
it: string;
|
|
243
|
+
};
|
|
244
|
+
"integration.notice": {
|
|
245
|
+
en: string;
|
|
246
|
+
"zh-TW": string;
|
|
247
|
+
"zh-CN": string;
|
|
248
|
+
ja: string;
|
|
249
|
+
ko: string;
|
|
250
|
+
de: string;
|
|
251
|
+
fr: string;
|
|
252
|
+
es: string;
|
|
253
|
+
it: string;
|
|
254
|
+
};
|
|
233
255
|
"install.reuseData": {
|
|
234
256
|
en: string;
|
|
235
257
|
"zh-TW": string;
|
|
@@ -230,6 +230,28 @@ export const WIZARD_MESSAGES = {
|
|
|
230
230
|
es: "Configuración de instalación",
|
|
231
231
|
it: "Impostazioni di installazione",
|
|
232
232
|
},
|
|
233
|
+
"integration.step": {
|
|
234
|
+
en: "Service callbacks",
|
|
235
|
+
"zh-TW": "服務回呼入口",
|
|
236
|
+
"zh-CN": "服务回调入口",
|
|
237
|
+
ja: "サービスコールバック",
|
|
238
|
+
ko: "서비스 콜백",
|
|
239
|
+
de: "Dienst-Callbacks",
|
|
240
|
+
fr: "Rappels de services",
|
|
241
|
+
es: "Devoluciones de llamada de servicios",
|
|
242
|
+
it: "Callback dei servizi",
|
|
243
|
+
},
|
|
244
|
+
"integration.notice": {
|
|
245
|
+
en: "These app entry points accept requests from other services without a DSM sign-in: {methods}. The app verifies each request with its own token, signature or key. It acts only for DSM accounts that are enabled and allowed by Application Privileges, and never with administrator rights.",
|
|
246
|
+
"zh-TW": "這些 App 入口接受其他服務在未登入 DSM 的情況下送來的請求:{methods}。App 會用自己的 token、簽章或金鑰驗證每個請求,只會代表已啟用且 Application Privileges 允許的 DSM 帳號執行,且一律不具管理員權限。",
|
|
247
|
+
"zh-CN": "这些应用入口接受其他服务在未登录 DSM 的情况下发送的请求:{methods}。应用会用自己的 token、签名或密钥验证每个请求,只会代表已启用且 Application Privileges 允许的 DSM 账号执行,且始终不具备管理员权限。",
|
|
248
|
+
ja: "次のアプリの入口は、DSM にサインインしていない他のサービスからの要求を受け付けます:{methods}。アプリは各要求を独自のトークン、署名、またはキーで検証します。有効で、アプリケーション権限で許可された DSM アカウントとしてのみ動作し、管理者権限は使いません。",
|
|
249
|
+
ko: "다음 앱 진입점은 DSM에 로그인하지 않은 다른 서비스의 요청을 받습니다: {methods}. 앱은 자체 토큰, 서명 또는 키로 각 요청을 확인합니다. 활성화되어 있고 애플리케이션 권한이 허용하는 DSM 계정으로만 동작하며, 관리자 권한은 사용하지 않습니다.",
|
|
250
|
+
de: "Diese App-Einstiegspunkte nehmen Anfragen anderer Dienste ohne DSM-Anmeldung an: {methods}. Die App prüft jede Anfrage mit eigenem Token, eigener Signatur oder eigenem Schlüssel. Sie handelt nur für aktivierte DSM-Konten, die die Anwendungsberechtigungen zulassen, und nie mit Administratorrechten.",
|
|
251
|
+
fr: "Ces points d'entrée de l'application acceptent des requêtes d'autres services sans connexion DSM : {methods}. L'application vérifie chaque requête avec son propre jeton, sa signature ou sa clé. Elle n'agit que pour des comptes DSM activés et autorisés par les privilèges d'application, et jamais avec des droits d'administrateur.",
|
|
252
|
+
es: "Estos puntos de entrada de la aplicación aceptan solicitudes de otros servicios sin iniciar sesión en DSM: {methods}. La aplicación verifica cada solicitud con su propio token, firma o clave. Solo actúa en nombre de cuentas de DSM habilitadas y permitidas por los privilegios de aplicación, y nunca con derechos de administrador.",
|
|
253
|
+
it: "Questi punti di ingresso dell'app accettano richieste da altri servizi senza accesso a DSM: {methods}. L'app verifica ogni richiesta con il proprio token, firma o chiave. Agisce solo per account DSM abilitati e consentiti dai privilegi delle applicazioni, mai con diritti di amministratore.",
|
|
254
|
+
},
|
|
233
255
|
"install.reuseData": {
|
|
234
256
|
en: "Data from an earlier installation is still on this NAS (@appdata/{package}), so this install reuses it: saved settings and generated secrets stay. To start empty, cancel and clear that data first — uninstall the app with “Also delete the app data listed above” ticked — then install again.",
|
|
235
257
|
"zh-TW": "這台 NAS 還留有先前安裝的資料(@appdata/{package}),本次安裝會沿用:已存的設定與產生的 secrets 都會保留。若要從空白開始,請先取消並清除該資料——解除安裝時勾選「一併刪除上列 App 資料」——再重新安裝。",
|
package/dist/cli.js
CHANGED
|
@@ -39,6 +39,8 @@ import { checkReproducible } from "./reproducible.js";
|
|
|
39
39
|
import { spkApprovalDiff, formatApprovalDiff } from "./runtime/approval-diff.js";
|
|
40
40
|
import { inspectAnySpk } from "./spk/inspect.js";
|
|
41
41
|
import { terminalSafe } from "./terminal-text.js";
|
|
42
|
+
import { auditGoRoutes } from "./standalone/route-audit.js";
|
|
43
|
+
import { parseApiPermissions } from "./standalone/api-permissions.js";
|
|
42
44
|
function failure(code, message, hint = "Fix the configuration and retry; use --help to see command usage.") {
|
|
43
45
|
return Object.assign(new Error(codedMessage(code, message)), { code, hint });
|
|
44
46
|
}
|
|
@@ -335,6 +337,30 @@ async function runCliWithin(argv) {
|
|
|
335
337
|
.option("--json", "Print a single JSON object")
|
|
336
338
|
.action(async (id, options) => { const { uninstallPackage } = await import('./host/package-commands.js'); const result = await uninstallPackage(id, options); emit('uninstall', result, JSON.stringify(result, null, 2)); });
|
|
337
339
|
program.command("ctl").description("Control registered NAS Apps using described AI commands (ctl --help)");
|
|
340
|
+
program.command("audit-entry <main>")
|
|
341
|
+
.description("Audit one custom Go payload's target import graph without executing it")
|
|
342
|
+
.option("--dir <dir>", "Go module directory", ".")
|
|
343
|
+
.option("--arch <arches>", "Comma-separated Go target architectures", "amd64,arm64,arm")
|
|
344
|
+
.option("--permissions <file>", "App permissions JSON, when the payload registers App methods")
|
|
345
|
+
.option("--json", "Print a single JSON object")
|
|
346
|
+
.action((main, options) => {
|
|
347
|
+
const arches = options.arch.split(",");
|
|
348
|
+
if (!arches.length || arches.some(arch => !["amd64", "arm64", "arm"].includes(arch))) {
|
|
349
|
+
throw new Error("audit-entry: --arch must contain amd64, arm64 or arm");
|
|
350
|
+
}
|
|
351
|
+
const dir = resolve(options.dir);
|
|
352
|
+
const policy = options.permissions
|
|
353
|
+
? parseApiPermissions(readFileSync(resolve(dir, options.permissions)))
|
|
354
|
+
: { schema: 1, default: "administrators", methods: {} };
|
|
355
|
+
const audit = auditGoRoutes(dir, policy, {}, [main], arches);
|
|
356
|
+
const ok = audit.problems.length === 0;
|
|
357
|
+
if (json)
|
|
358
|
+
stdout(JSON.stringify({ ok, ...audit }));
|
|
359
|
+
else
|
|
360
|
+
stdout(terminalSafe(ok ? "Entry audit passed" : audit.problems.join("\n")));
|
|
361
|
+
if (!ok)
|
|
362
|
+
process.exitCode = 1;
|
|
363
|
+
});
|
|
338
364
|
program
|
|
339
365
|
.command("check")
|
|
340
366
|
.description("Validate the manifest, capability combination and Compose")
|
package/dist/error-registry.js
CHANGED
|
@@ -843,6 +843,16 @@ export const ERROR_REGISTRY = {
|
|
|
843
843
|
description: "The API policy is invalid.",
|
|
844
844
|
suggestion: "Fix backend/internal/api/permissions.json.",
|
|
845
845
|
},
|
|
846
|
+
STANDALONE_INTEGRATIONS_INVALID: {
|
|
847
|
+
code: "STANDALONE_INTEGRATIONS_INVALID",
|
|
848
|
+
description: "The integrations list in dsm-package.yaml is invalid or used where an integration callback cannot run.",
|
|
849
|
+
suggestion: "List each integration method once, sorted, as a string; integrations need a Go backend without compose.",
|
|
850
|
+
},
|
|
851
|
+
STANDALONE_INTEGRATIONS_MISMATCH: {
|
|
852
|
+
code: "STANDALONE_INTEGRATIONS_MISMATCH",
|
|
853
|
+
description: "The integrations list does not match the methods permissions.json marks integration.",
|
|
854
|
+
suggestion: "List exactly the integration methods from permissions.json in dsm-package.yaml integrations.",
|
|
855
|
+
},
|
|
846
856
|
CLI_OPTION_UNKNOWN: {
|
|
847
857
|
code: "CLI_OPTION_UNKNOWN",
|
|
848
858
|
description: "Unknown CLI option.",
|
|
@@ -33,6 +33,7 @@ export async function buildApiDescription(m, bridgeMethods, run) {
|
|
|
33
33
|
if (!Array.isArray(methods) || methods.some(item => typeof item.method !== 'string' || typeof item.permission !== 'string'))
|
|
34
34
|
throw new StandaloneBuildError('CTL_DESCRIBE_FAILED', 'Invalid registry snapshot.');
|
|
35
35
|
const registry = new Map(methods.map(item => [item.method, item.permission]));
|
|
36
|
+
const rawHTTP = new Set(methods.filter(item => item.kind === 'http').map(item => item.method));
|
|
36
37
|
// Identity transport routes are outside the business registry but have immutable SDK ownership.
|
|
37
38
|
if (m.capabilities.identity) {
|
|
38
39
|
registry.set('identity.session', 'public');
|
|
@@ -46,6 +47,8 @@ export async function buildApiDescription(m, bridgeMethods, run) {
|
|
|
46
47
|
const names = flowMethods(command.flow);
|
|
47
48
|
let permission = 'public';
|
|
48
49
|
for (const name of names) {
|
|
50
|
+
if (rawHTTP.has(name))
|
|
51
|
+
throw new StandaloneBuildError('CTL_COMMAND_UNREGISTERED', `Command ${id} references raw HTTP route ${name}; ctl commands require RPC methods.`);
|
|
49
52
|
if (!registry.has(name))
|
|
50
53
|
throw new StandaloneBuildError('CTL_COMMAND_UNREGISTERED', `Command ${id} references unregistered method ${name}.`);
|
|
51
54
|
const effective = m.entry === 'admin-only' ? 'administrators' : registry.get(name);
|
|
@@ -5,7 +5,8 @@ export declare const API_PERMISSIONS_FILE: Record<SdkBackend["kind"], string>;
|
|
|
5
5
|
type SdkPermissions = Record<string, Record<string, ApiPermission>>;
|
|
6
6
|
export declare const MAX_PERMISSIONS_BYTES = 65536;
|
|
7
7
|
export declare const MAX_PERMISSION_METHODS = 1024;
|
|
8
|
-
|
|
8
|
+
/** `integration` marks a Go `api.HandleIntegration` callback that arrives without a DSM session. */
|
|
9
|
+
export type ApiPermission = "administrators" | "authenticated" | "public" | "integration";
|
|
9
10
|
export interface ApiPermissions {
|
|
10
11
|
schema: 1;
|
|
11
12
|
default: "administrators";
|
|
@@ -30,4 +31,6 @@ export declare function floorAtAdministrators(policy: ApiPermissions, sdkPermiss
|
|
|
30
31
|
};
|
|
31
32
|
/** Launcher access is derived from the same API policy; no second setting. */
|
|
32
33
|
export declare function permitsOrdinaryUsers(policy?: ApiPermissions): boolean;
|
|
34
|
+
/** Methods declared with the `integration` permission, sorted. */
|
|
35
|
+
export declare function integrationMethods(policy?: ApiPermissions): string[];
|
|
33
36
|
export {};
|
|
@@ -27,7 +27,7 @@ export function parseApiPermissions(raw) {
|
|
|
27
27
|
throw new Error("too many permission methods");
|
|
28
28
|
for (const [method, permission] of entries) {
|
|
29
29
|
const matched = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.exec(method);
|
|
30
|
-
if (method.length > 128 || matched?.[0] !== method || !["administrators", "authenticated", "public"].includes(permission)) {
|
|
30
|
+
if (method.length > 128 || matched?.[0] !== method || !["administrators", "authenticated", "public", "integration"].includes(permission)) {
|
|
31
31
|
throw new Error(`invalid permission for method ${JSON.stringify(method)}`);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -63,11 +63,17 @@ export function readApiPermissions(backend, capabilities = {}) {
|
|
|
63
63
|
export function floorAtAdministrators(policy, sdkPermissions) {
|
|
64
64
|
const sdk = new Set(Object.values(sdkPermissions).flatMap(methods => Object.keys(methods)));
|
|
65
65
|
const methods = Object.fromEntries(Object.entries(policy.methods)
|
|
66
|
-
.map(([method, permission]) => [method, sdk.has(method) && permission === "public" ? permission : "administrators"]));
|
|
67
|
-
|
|
66
|
+
.map(([method, permission]) => [method, sdk.has(method) && permission === "public" || permission === "integration" ? permission : "administrators"]));
|
|
67
|
+
// An integration callback admits no DSM account by itself: api.ActAs refuses
|
|
68
|
+
// every account in an administrator-only App.
|
|
69
|
+
const widened = Object.entries(policy.methods).filter(([method, permission]) => !sdk.has(method) && permission !== "administrators" && permission !== "integration").map(([method]) => method);
|
|
68
70
|
return { policy: { ...policy, methods }, widened };
|
|
69
71
|
}
|
|
70
72
|
/** Launcher access is derived from the same API policy; no second setting. */
|
|
71
73
|
export function permitsOrdinaryUsers(policy) {
|
|
72
|
-
return policy !== undefined && Object.values(policy.methods).some(permission => permission !== "administrators");
|
|
74
|
+
return policy !== undefined && Object.values(policy.methods).some(permission => permission !== "administrators" && permission !== "integration");
|
|
75
|
+
}
|
|
76
|
+
/** Methods declared with the `integration` permission, sorted. */
|
|
77
|
+
export function integrationMethods(policy) {
|
|
78
|
+
return Object.entries(policy?.methods ?? {}).filter(([, permission]) => permission === "integration").map(([method]) => method).sort();
|
|
73
79
|
}
|
package/dist/standalone/build.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildInstallSettings } from './install-settings.js';
|
|
2
|
-
import { auditGoRoutes } from './route-audit.js';
|
|
2
|
+
import { auditGoRoutes, httpRoutesDigest } from './route-audit.js';
|
|
3
3
|
import { PORTAL_REQUIRES_EXECUTOR } from "../portal/alias.js";
|
|
4
4
|
import { buildApiDescription } from "./api-description.js";
|
|
5
5
|
import { goChildEnv } from "../runtime/go-env.js";
|
|
@@ -234,7 +234,7 @@ async function buildGoCgi(manifest, backend, arch, work, run) {
|
|
|
234
234
|
throw new StandaloneBuildError("STANDALONE_API_PERMISSIONS_CHANGED", "API permissions changed during build; check and rebuild.");
|
|
235
235
|
}
|
|
236
236
|
const policy = readApiPermissions(backend, manifest.capabilities);
|
|
237
|
-
const audit = auditGoRoutes(backend.dir, policy.policy, policy.sdk);
|
|
237
|
+
const audit = auditGoRoutes(backend.dir, policy.policy, policy.sdk, [backend.main], [goBuildEnv(arch).GOARCH]);
|
|
238
238
|
if (audit.problems.length || manifest.entry && !audit.entryGate) {
|
|
239
239
|
throw new StandaloneBuildError("STANDALONE_API_REGISTRY_INVALID", audit.problems.join("\n") || "App entry gate was removed during build.");
|
|
240
240
|
}
|
|
@@ -252,6 +252,9 @@ async function buildGoCgi(manifest, backend, arch, work, run) {
|
|
|
252
252
|
`-X ${module}/internal/api.expectedPermissionsDigest=${manifest.apiPermissionsDigest}`,
|
|
253
253
|
`-X ${SDK_MODULE}/api.expectedSDKPermissionsDigest=${manifest.apiSDKPermissionsDigest}`,
|
|
254
254
|
`-X ${SDK_MODULE}/api.expectedPermissionsDigest=${manifest.apiPermissionsDigest}`,
|
|
255
|
+
`-X ${SDK_MODULE}/api.expectedHTTPRoutesDigest=${httpRoutesDigest(audit)}`,
|
|
256
|
+
`-X ${SDK_MODULE}/ipc.packageID=${manifest.package}`,
|
|
257
|
+
`-X ${SDK_MODULE}/ipc.executorTier=none`,
|
|
255
258
|
...(manifest.entry ? [`-X ${SDK_MODULE}/api.expectedEntryMode=${manifest.entry}`] : []),
|
|
256
259
|
...delegateBuildFlags(manifest),
|
|
257
260
|
...(manifest.executor
|
|
@@ -302,11 +305,21 @@ async function buildBackgroundWorker(manifest, arch, work, run) {
|
|
|
302
305
|
if (!manifest.capabilities.jobs || manifest.backend.kind !== "go")
|
|
303
306
|
return undefined;
|
|
304
307
|
const out = join(work, `background-worker-${arch}`);
|
|
308
|
+
const policy = readApiPermissions(manifest.backend, manifest.capabilities);
|
|
309
|
+
const audit = auditGoRoutes(manifest.backend.dir, policy.policy, policy.sdk, [BACKGROUND_WORKER_MAIN], [goBuildEnv(arch).GOARCH]);
|
|
310
|
+
if (audit.problems.length)
|
|
311
|
+
throw new StandaloneBuildError("STANDALONE_API_REGISTRY_INVALID", audit.problems.join("\n"));
|
|
305
312
|
const ldflags = [
|
|
306
313
|
"-s",
|
|
307
314
|
"-w",
|
|
308
315
|
"-buildid=",
|
|
309
316
|
`-X main.packageID=${manifest.package}`,
|
|
317
|
+
`-X ${SDK_MODULE}/ipc.packageID=${manifest.package}`,
|
|
318
|
+
`-X ${SDK_MODULE}/ipc.executorTier=none`,
|
|
319
|
+
`-X ${SDK_MODULE}/api.expectedHTTPRoutesDigest=${httpRoutesDigest(audit)}`,
|
|
320
|
+
`-X ${SDK_MODULE}/api.expectedPermissionsDigest=${manifest.apiPermissionsDigest}`,
|
|
321
|
+
`-X ${SDK_MODULE}/api.expectedSDKPermissionsDigest=${manifest.apiSDKPermissionsDigest}`,
|
|
322
|
+
...(manifest.entry ? [`-X ${SDK_MODULE}/api.expectedEntryMode=${manifest.entry}`] : []),
|
|
310
323
|
...delegateBuildFlags(manifest),
|
|
311
324
|
...(manifest.executor
|
|
312
325
|
? [
|
|
@@ -50,6 +50,8 @@ export interface SpkInspection {
|
|
|
50
50
|
};
|
|
51
51
|
/** Declared `entry` mode; `admin-only` is the administrator floor of every route. */
|
|
52
52
|
entry?: EntryMode;
|
|
53
|
+
/** Service callbacks that skip the DSM session, as listed in the install and upgrade wizards. */
|
|
54
|
+
integrations?: string[];
|
|
53
55
|
/** The declared launch mode verified against the built launcher. */
|
|
54
56
|
launch?: WebLaunchMode;
|
|
55
57
|
/** The App's own scripts/app/<DSM script name>, each called from the generated script of that name. */
|
|
@@ -31,8 +31,8 @@ import { readUninstallRecord, stateItemTexts } from "./uninstall.js";
|
|
|
31
31
|
import { DSM_LANGS } from "./i18n.js";
|
|
32
32
|
import { BACKGROUND_UNIT_DIR, BACKGROUND_WORKER_PAYLOAD, BRIDGE_WORKER_ARGS, backgroundUnitName, renderBackgroundUnit, } from "./background.js";
|
|
33
33
|
import { renderStandaloneResource } from "./resource.js";
|
|
34
|
-
import { recoverSharedFolder,
|
|
35
|
-
import { renderInstallWizardFiles, renderNativeInstallWizard, } from "../build-spk/install-wizard.js";
|
|
34
|
+
import { recoverSharedFolder, renderUpgradeWizardFiles, sharedFolderInstallSteps, SHARED_FOLDER_MARKER, } from "./shared-folder.js";
|
|
35
|
+
import { renderInstallWizardFiles, integrationWizardSteps, renderNativeInstallWizard, } from "../build-spk/install-wizard.js";
|
|
36
36
|
import { renderDelegateScopeSteps, } from "../build-spk/delegate-scope.js";
|
|
37
37
|
import { renderUninstallWizardFiles } from "../build-spk/uninstall-wizard.js";
|
|
38
38
|
import { renderNativePrivilege } from "../build-spk/native-lifecycle.js";
|
|
@@ -125,6 +125,8 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
125
125
|
throw new Error();
|
|
126
126
|
if (raw.operationSeconds !== undefined && (!Number.isInteger(raw.operationSeconds) || raw.operationSeconds < 1 || raw.operationSeconds > 3600))
|
|
127
127
|
throw new Error();
|
|
128
|
+
if (raw.integrations !== undefined && (raw.backend !== 'go' || !Array.isArray(raw.integrations) || !raw.integrations.length || raw.integrations.length > 1024 || raw.integrations.some((m, i) => typeof m !== 'string' || m.length > 128 || !/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(m) || i > 0 && raw.integrations[i - 1] >= m)))
|
|
129
|
+
throw new Error();
|
|
128
130
|
metadata = raw;
|
|
129
131
|
}
|
|
130
132
|
catch {
|
|
@@ -138,6 +140,7 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
138
140
|
const identityGateway = await inspectProviderOnlyAdmission(dir, paths, metadata.provider_only_oidc, findings);
|
|
139
141
|
await inspectExternalGateway(dir, paths, payload, metadata.gateway_external, findings);
|
|
140
142
|
const installSettings = await inspectInstallSettings(dir, payload, metadata, findings);
|
|
143
|
+
const integrations = metadata.integrations ?? [];
|
|
141
144
|
if (metadata.compose !== undefined) {
|
|
142
145
|
const result = await inspectUnifiedCompose(dir, payload, { ...metadata, installSettings }, findings);
|
|
143
146
|
if (installSettings)
|
|
@@ -291,7 +294,7 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
291
294
|
? await inspectLoginStyle(dir, info.package, Boolean(portal || executor?.policy.portal), findings)
|
|
292
295
|
: undefined;
|
|
293
296
|
if (standalone)
|
|
294
|
-
checkSharedFolder(dir, findings, info.package, sharedFolder, executor?.policy.scope === "install-selected", portal, hasExecutor, nativeGateway, info.os_min_ver, installSettings);
|
|
297
|
+
checkSharedFolder(dir, findings, info.package, sharedFolder, executor?.policy.scope === "install-selected", portal, hasExecutor, nativeGateway, info.os_min_ver, installSettings, integrations);
|
|
295
298
|
const icon = inspectPackageIcons(dir, findings);
|
|
296
299
|
const cgiEntry = payload.find((entry) => entry.path === CGI_PAYLOAD_PATH);
|
|
297
300
|
if (standalone && !cgiEntry) {
|
|
@@ -319,6 +322,7 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
319
322
|
}
|
|
320
323
|
: {}),
|
|
321
324
|
...(metadata.entry ? { entry: metadata.entry } : {}),
|
|
325
|
+
...(integrations.length ? { integrations } : {}),
|
|
322
326
|
...(appScripts.length ? { appScripts } : {}),
|
|
323
327
|
...(loginStyle ? { loginStyle } : {}),
|
|
324
328
|
...(standalone
|
|
@@ -1365,7 +1369,7 @@ function checkBackground(dir, info, payload, findings, executor, daemon, bridge
|
|
|
1365
1369
|
* declaration (without one, any present file must be the default native
|
|
1366
1370
|
* install wizard), and no other install/upgrade wizard file may exist.
|
|
1367
1371
|
*/
|
|
1368
|
-
function checkSharedFolder(dir, findings, pkg, folder, selected = false, portal, executor = false, nativeGateway, osMinVer, installSettings) {
|
|
1372
|
+
function checkSharedFolder(dir, findings, pkg, folder, selected = false, portal, executor = false, nativeGateway, osMinVer, installSettings, integrations = []) {
|
|
1369
1373
|
const wizardDir = join(dir, "WIZARD_UIFILES");
|
|
1370
1374
|
const present = (existsSync(wizardDir) ? readdirSync(wizardDir) : [])
|
|
1371
1375
|
.filter((name) => /^(install|upgrade)_uifile(_|\.|$)/.test(name))
|
|
@@ -1391,16 +1395,18 @@ function checkSharedFolder(dir, findings, pkg, folder, selected = false, portal,
|
|
|
1391
1395
|
: {}),
|
|
1392
1396
|
...portalManifest(portal),
|
|
1393
1397
|
};
|
|
1394
|
-
const wizards = expectedWizardFiles(pkg, folder, selected, portal, installSettings);
|
|
1398
|
+
const wizards = expectedWizardFiles(pkg, folder, selected, portal, installSettings, integrations);
|
|
1395
1399
|
const source = selected
|
|
1396
1400
|
? "the install-selected policy"
|
|
1397
1401
|
: folder
|
|
1398
1402
|
? "the install.sharedFolder declaration"
|
|
1399
1403
|
: portal
|
|
1400
1404
|
? "the portal declaration"
|
|
1401
|
-
:
|
|
1405
|
+
: integrations.length
|
|
1406
|
+
? "the integrations declaration"
|
|
1407
|
+
: "the native-go default install wizard";
|
|
1402
1408
|
// The default native wizard is optional; a declared shared folder or portal needs all of its files.
|
|
1403
|
-
for (const name of folder || selected || portal || installSettings
|
|
1409
|
+
for (const name of folder || selected || portal || installSettings || integrations.length
|
|
1404
1410
|
? Object.keys(wizards)
|
|
1405
1411
|
: present.filter((n) => n in wizards)) {
|
|
1406
1412
|
if (readOptional(join(wizardDir, name)) !== wizards[name])
|
|
@@ -1461,7 +1467,7 @@ async function inspectInstallSettings(dir, payload, metadata, findings) {
|
|
|
1461
1467
|
}
|
|
1462
1468
|
return settings;
|
|
1463
1469
|
}
|
|
1464
|
-
function expectedWizardFiles(pkg, folder, selected = false, portal, installSettings) {
|
|
1470
|
+
function expectedWizardFiles(pkg, folder, selected = false, portal, installSettings, integrations = []) {
|
|
1465
1471
|
const steps = selected
|
|
1466
1472
|
? (t) => [
|
|
1467
1473
|
...renderDelegateScopeSteps(t),
|
|
@@ -1470,11 +1476,11 @@ function expectedWizardFiles(pkg, folder, selected = false, portal, installSetti
|
|
|
1470
1476
|
: folder
|
|
1471
1477
|
? sharedFolderInstallSteps(pkg, folder)
|
|
1472
1478
|
: renderNativeInstallWizard;
|
|
1473
|
-
const install = renderInstallWizardFiles(pkg, t => [...steps(t), ...settingsInstallSteps(installSettings)(t), ...(portal ? [renderPortalWizardStep(t, portal.alias)] : [])]);
|
|
1479
|
+
const install = renderInstallWizardFiles(pkg, t => [...steps(t), ...settingsInstallSteps(installSettings)(t), ...(portal ? [renderPortalWizardStep(t, portal.alias)] : []), ...integrationWizardSteps(t, integrations)]);
|
|
1474
1480
|
return {
|
|
1475
1481
|
...install.static,
|
|
1476
1482
|
...install.dynamic,
|
|
1477
|
-
...(
|
|
1483
|
+
...renderUpgradeWizardFiles(pkg, folder, integrations),
|
|
1478
1484
|
};
|
|
1479
1485
|
}
|
|
1480
1486
|
function portalManifest(portal) {
|
|
@@ -98,6 +98,12 @@ export interface StandaloneManifest {
|
|
|
98
98
|
installSettings?: InstallSettings;
|
|
99
99
|
/** Who may use the app at all; absent keeps the route policy alone. */
|
|
100
100
|
entry?: EntryMode;
|
|
101
|
+
/**
|
|
102
|
+
* `integrations`: Go `api.HandleIntegration` methods that arrive without a
|
|
103
|
+
* DSM session, sorted. Exactly the `integration` permissions; the install
|
|
104
|
+
* and upgrade wizards list them for the administrator.
|
|
105
|
+
*/
|
|
106
|
+
integrations?: string[];
|
|
101
107
|
/** `portal`: registers the DSM Application Portal entry; an administrator activates it. */
|
|
102
108
|
portal?: PortalDeclaration;
|
|
103
109
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readInstallSettings } from './install-settings.js';
|
|
2
2
|
import { auditGoRoutes } from './route-audit.js';
|
|
3
|
+
import { BACKGROUND_WORKER_MAIN } from './background.js';
|
|
3
4
|
import { isWebLaunchMode } from "../contract/web-launch.js";
|
|
4
5
|
import { validWebAPIProfiles, } from "../runtime/webapi-profile.js";
|
|
5
6
|
import { validateMetadata } from "../package-metadata.js";
|
|
@@ -20,7 +21,7 @@ import { readAppScripts } from "./app-scripts.js";
|
|
|
20
21
|
import { readBackground } from "./background.js";
|
|
21
22
|
import { DEFAULT_MIN_DSM, isDsmAtLeast, PORTAL_EXECUTOR_MIN_DSM, portalByExecutor, portalMinDsmMessage, } from "../build-spk/dsm-version.js";
|
|
22
23
|
import { readSharedFolder } from "./shared-folder.js";
|
|
23
|
-
import { API_PERMISSIONS_FILE, floorAtAdministrators, permitsOrdinaryUsers, readApiPermissions, } from "./api-permissions.js";
|
|
24
|
+
import { API_PERMISSIONS_FILE, floorAtAdministrators, integrationMethods, permitsOrdinaryUsers, readApiPermissions, } from "./api-permissions.js";
|
|
24
25
|
import { PORTAL_REQUIRES_EXECUTOR, portalEntryProblem, readPortalDeclaration, } from "../portal/alias.js";
|
|
25
26
|
import { renderPackageIcons } from "../build-spk/dsm-icons.js";
|
|
26
27
|
import { LOGIN_COLOR, loginImageProblem, } from "../build-spk/login-style.js";
|
|
@@ -58,6 +59,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
58
59
|
"install",
|
|
59
60
|
"uninstall",
|
|
60
61
|
"entry",
|
|
62
|
+
"integrations",
|
|
61
63
|
"portal",
|
|
62
64
|
"console",
|
|
63
65
|
"icon",
|
|
@@ -222,9 +224,11 @@ function readManifest(raw, c) {
|
|
|
222
224
|
c.fail("STANDALONE_REQUEST_TIMEOUT_SHORT", problem);
|
|
223
225
|
}
|
|
224
226
|
let permissions;
|
|
227
|
+
let integrations;
|
|
225
228
|
if (backend && backend.kind !== "binary") {
|
|
226
229
|
try {
|
|
227
230
|
permissions = readApiPermissions(backend, capabilities);
|
|
231
|
+
integrations = readIntegrations(raw.integrations, backend, permissions.policy, raw.compose !== undefined, c);
|
|
228
232
|
}
|
|
229
233
|
catch (error) {
|
|
230
234
|
// The SDK's own routes come from its module; a failed download or an
|
|
@@ -354,11 +358,18 @@ function readManifest(raw, c) {
|
|
|
354
358
|
}
|
|
355
359
|
if (backend?.kind === "go" && permissions && sdkVersionOrUndefined(backend) && (raw.entry === undefined || entry)) {
|
|
356
360
|
try {
|
|
357
|
-
const
|
|
358
|
-
for (const
|
|
361
|
+
const problems = new Set();
|
|
362
|
+
for (const main of [backend.main, ...(capabilities.jobs ? [BACKGROUND_WORKER_MAIN] : [])]) {
|
|
363
|
+
for (const arch of arches ?? []) {
|
|
364
|
+
const audit = auditGoRoutes(backend.dir, permissions.policy, permissions.sdk, [main], [({ x86_64: 'amd64', aarch64: 'arm64', armv7: 'arm' })[arch]]);
|
|
365
|
+
for (const problem of audit.problems)
|
|
366
|
+
problems.add(problem);
|
|
367
|
+
if (main === backend.main && entry && !audit.entryGate)
|
|
368
|
+
c.fail("STANDALONE_ENTRY_UNSUPPORTED", "App entry requires an actual dsm.NewEntryGate call; comments do not enforce admission.");
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
for (const problem of problems)
|
|
359
372
|
c.fail("STANDALONE_API_REGISTRY_INVALID", problem);
|
|
360
|
-
if (entry && !audit.entryGate)
|
|
361
|
-
c.fail("STANDALONE_ENTRY_UNSUPPORTED", "App entry requires an actual dsm.NewEntryGate call; comments do not enforce admission.");
|
|
362
373
|
}
|
|
363
374
|
catch (error) {
|
|
364
375
|
c.fail("STANDALONE_API_REGISTRY_INVALID", error.message);
|
|
@@ -500,6 +511,7 @@ function readManifest(raw, c) {
|
|
|
500
511
|
...(sharedFolder ? { sharedFolder } : {}),
|
|
501
512
|
...(installSettings ? { installSettings } : {}),
|
|
502
513
|
...(entry ? { entry } : {}),
|
|
514
|
+
...(integrations?.length ? { integrations } : {}),
|
|
503
515
|
...(portal ? { portal } : {}),
|
|
504
516
|
...(dsmConsole ? { console: dsmConsole } : {}),
|
|
505
517
|
...(noWindow ? { sdkWindow: false } : {}),
|
|
@@ -659,6 +671,29 @@ function sdkVersionOrUndefined(backend) {
|
|
|
659
671
|
* SDK gate and main wires it; `-X main.entry` on an older main would be
|
|
660
672
|
* silently dropped and leave a registered app ungated.
|
|
661
673
|
*/
|
|
674
|
+
/**
|
|
675
|
+
* `integrations` repeats, in dsm-package.yaml, every permissions.json method
|
|
676
|
+
* declared `integration`, so a callback that skips the DSM session is never
|
|
677
|
+
* added by the policy file alone. Only Go SDK servers implement it.
|
|
678
|
+
*/
|
|
679
|
+
function readIntegrations(value, backend, policy, compose, c) {
|
|
680
|
+
const declared = integrationMethods(policy);
|
|
681
|
+
if (value === undefined && declared.length === 0)
|
|
682
|
+
return undefined;
|
|
683
|
+
if (value === undefined)
|
|
684
|
+
value = [];
|
|
685
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== "string") || new Set(value).size !== value.length) {
|
|
686
|
+
return c.fail("STANDALONE_INTEGRATIONS_INVALID", "integrations must be a list of unique permissions.json method names.");
|
|
687
|
+
}
|
|
688
|
+
const listed = [...value].sort();
|
|
689
|
+
if (backend.kind !== "go" || compose) {
|
|
690
|
+
return c.fail("STANDALONE_INTEGRATIONS_INVALID", "integrations need a Go backend built with api.HandleIntegration.");
|
|
691
|
+
}
|
|
692
|
+
if (listed.join("\n") !== declared.join("\n")) {
|
|
693
|
+
return c.fail("STANDALONE_INTEGRATIONS_MISMATCH", `integrations must list exactly the permissions.json methods declared "integration" (${declared.join(", ") || "none"}); found ${listed.join(", ") || "none"}.`);
|
|
694
|
+
}
|
|
695
|
+
return listed;
|
|
696
|
+
}
|
|
662
697
|
function readEntry(value, backend, policy, c) {
|
|
663
698
|
if (value !== "admin-only" && value !== "app-privilege") {
|
|
664
699
|
return c.fail("STANDALONE_ENTRY_INVALID", "entry must be admin-only or app-privilege; omit it to keep the existing permissions.json behavior.");
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ApiPermissions } from './api-permissions.js';
|
|
2
|
-
|
|
3
|
-
export declare function auditGoRoutes(dir: string, policy: ApiPermissions, sdk: Record<string, Record<string, string>>): {
|
|
2
|
+
export interface RouteAudit {
|
|
4
3
|
problems: string[];
|
|
5
4
|
entryGate: boolean;
|
|
6
|
-
|
|
5
|
+
httpRoutes: {
|
|
6
|
+
prefix: string;
|
|
7
|
+
method: string;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
export declare function httpRoutesDigest(audit: RouteAudit): string;
|
|
11
|
+
/** Parse Go syntax without executing an App's main, init functions or --describe. */
|
|
12
|
+
export declare function auditGoRoutes(dir: string, policy: ApiPermissions, sdk: Record<string, Record<string, string>>, roots?: string[], arches?: string[]): RouteAudit;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { mkdtempSync, rmSync, realpathSync } from 'node:fs';
|
|
3
4
|
import { tmpdir } from 'node:os';
|
|
4
5
|
import { join } from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import { goChildEnv } from '../runtime/go-env.js';
|
|
7
8
|
let binary;
|
|
9
|
+
export function httpRoutesDigest(audit) {
|
|
10
|
+
return createHash('sha256').update(JSON.stringify(audit.httpRoutes)).digest('hex');
|
|
11
|
+
}
|
|
8
12
|
/** Parse Go syntax without executing an App's main, init functions or --describe. */
|
|
9
|
-
export function auditGoRoutes(dir, policy, sdk) {
|
|
13
|
+
export function auditGoRoutes(dir, policy, sdk, roots = ['.'], arches = ['amd64', 'arm64', 'arm']) {
|
|
10
14
|
const env = goChildEnv({ ...process.env, GOOS: undefined, GOARCH: undefined, GOARM: undefined, GOWORK: 'off', CGO_ENABLED: '0' });
|
|
11
15
|
if (!binary) {
|
|
12
16
|
const work = mkdtempSync(join(tmpdir(), 'synopkgland-route-audit-'));
|
|
@@ -18,7 +22,7 @@ export function auditGoRoutes(dir, policy, sdk) {
|
|
|
18
22
|
throw new Error('Cannot build the Go route auditor; install Go and retry.');
|
|
19
23
|
binary = out;
|
|
20
24
|
}
|
|
21
|
-
const result = spawnSync(binary, [], { env, input: JSON.stringify({ Dir: realpathSync(dir), Permissions: policy.methods, SDK: sdk }), encoding: 'utf8', timeout:
|
|
25
|
+
const result = spawnSync(binary, [], { env, input: JSON.stringify({ Dir: realpathSync(dir), Roots: roots, Arches: arches, Permissions: policy.methods, SDK: sdk }), encoding: 'utf8', timeout: 120_000, maxBuffer: 4 << 20 });
|
|
22
26
|
if (result.error || result.status !== 0)
|
|
23
27
|
throw new Error(`Cannot audit Go routes: ${result.stderr?.trim() || 'auditor unavailable'}`);
|
|
24
28
|
return JSON.parse(result.stdout);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StandaloneError } from "./manifest.js";
|
|
2
2
|
import { type Translate } from "../build-spk/wizard-i18n.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type InstallWizardStep } from "../build-spk/install-wizard.js";
|
|
4
4
|
/**
|
|
5
5
|
* `install.sharedFolder`: the Shared Folder a package keeps its files in.
|
|
6
6
|
*
|
|
@@ -33,6 +33,11 @@ export declare function renderSharedFolderWizardFiles(pkg: string, folder: Share
|
|
|
33
33
|
export declare function sharedFolderInstallSteps(pkg: string, folder: SharedFolder): (t: Translate) => InstallWizardStep[];
|
|
34
34
|
/** The upgrade wizard files that let the administrator keep or change the shared folder. */
|
|
35
35
|
export declare function renderSharedFolderUpgradeWizardFiles(pkg: string, folder: SharedFolder): Record<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* The upgrade wizard: the shared folder choice, then the service callbacks an
|
|
38
|
+
* administrator re-approves on every upgrade. Empty without either.
|
|
39
|
+
*/
|
|
40
|
+
export declare function renderUpgradeWizardFiles(pkg: string, folder: SharedFolder | undefined, integrations?: readonly string[]): Record<string, string>;
|
|
36
41
|
/** `conf/resource` `data-share` entry: create mode only; DSM never deletes the share on uninstall. */
|
|
37
42
|
export declare function sharedFolderResource(pkg: string, folder: SharedFolder): Record<string, unknown> | undefined;
|
|
38
43
|
/** The declaration an SPK's postinst carries, or undefined when it has none or it is no longer valid. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sharedFolderCombobox } from "../build-spk/share-combobox.js";
|
|
2
2
|
import { renderWizardFiles } from "../build-spk/wizard-i18n.js";
|
|
3
|
+
import { integrationWizardSteps } from "../build-spk/install-wizard.js";
|
|
3
4
|
const FIELDS = new Set(["mode", "key", "name", "permission"]);
|
|
4
5
|
const KEY = /^[a-z][a-z0-9_]{0,31}$/;
|
|
5
6
|
const RESERVED_KEY = /^(pkgwizard_|synopkg|change_)/;
|
|
@@ -123,6 +124,18 @@ export function sharedFolderInstallSteps(pkg, folder) {
|
|
|
123
124
|
export function renderSharedFolderUpgradeWizardFiles(pkg, folder) {
|
|
124
125
|
return renderWizardFiles("upgrade_uifile", t => [{ step_title: t("upgrade.step"), items: upgradeItems(t, pkg, folder) }]);
|
|
125
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* The upgrade wizard: the shared folder choice, then the service callbacks an
|
|
129
|
+
* administrator re-approves on every upgrade. Empty without either.
|
|
130
|
+
*/
|
|
131
|
+
export function renderUpgradeWizardFiles(pkg, folder, integrations = []) {
|
|
132
|
+
if (!folder && !integrations.length)
|
|
133
|
+
return {};
|
|
134
|
+
return renderWizardFiles("upgrade_uifile", t => [
|
|
135
|
+
...(folder ? [{ step_title: t("upgrade.step"), items: upgradeItems(t, pkg, folder) }] : []),
|
|
136
|
+
...integrationWizardSteps(t, integrations),
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
126
139
|
/** `conf/resource` `data-share` entry: create mode only; DSM never deletes the share on uninstall. */
|
|
127
140
|
export function sharedFolderResource(pkg, folder) {
|
|
128
141
|
if (folder.mode !== "create")
|
package/dist/standalone/spk.js
CHANGED
|
@@ -13,7 +13,7 @@ import { infoArchValue } from "../build-spk/dsm-arch.js";
|
|
|
13
13
|
import { packageIcons, writeDsmWindowIcons, writePackageCenterIcons, } from "../build-spk/dsm-icons.js";
|
|
14
14
|
import { renderNativePrivilege } from "../build-spk/native-lifecycle.js";
|
|
15
15
|
import { renderUninstallWizardFiles } from "../build-spk/uninstall-wizard.js";
|
|
16
|
-
import { renderInstallWizardFiles, renderNativeInstallWizard, } from "../build-spk/install-wizard.js";
|
|
16
|
+
import { renderInstallWizardFiles, integrationWizardSteps, renderNativeInstallWizard, } from "../build-spk/install-wizard.js";
|
|
17
17
|
import { writeWizardUiFiles, } from "../build-spk/wizard-i18n.js";
|
|
18
18
|
import { renderDelegateScopeSteps } from "../build-spk/delegate-scope.js";
|
|
19
19
|
import { createReproducibleTar } from "../build-spk/reproducible-archive.js";
|
|
@@ -40,7 +40,7 @@ import { portalByExecutor } from "../build-spk/dsm-version.js";
|
|
|
40
40
|
import { PORTAL_JSON_RELPATH, PORTAL_MUSTACHE_RELPATH, renderPortalJson, renderPortalMustache, renderPortalWizardStep, } from "../portal/package.js";
|
|
41
41
|
import { executorPlan, } from "../runtime/executor-build.js";
|
|
42
42
|
import { EXECUTOR_ANCHOR } from "../runtime/executor-inspect.js";
|
|
43
|
-
import {
|
|
43
|
+
import { renderUpgradeWizardFiles, sharedFolderInstallSteps, } from "./shared-folder.js";
|
|
44
44
|
import { stateItemTexts, uninstallExecProblem, uninstallRecord, } from "./uninstall.js";
|
|
45
45
|
const FILE_MODE = 0o644;
|
|
46
46
|
const EXEC_MODE = 0o755;
|
|
@@ -237,6 +237,7 @@ export async function assembleStandaloneSpk(options) {
|
|
|
237
237
|
schema: 2,
|
|
238
238
|
...(moduleVersion ? { ui_module: moduleVersion } : {}),
|
|
239
239
|
...(manifest.installSettings ? { installSettings: manifest.installSettings } : {}),
|
|
240
|
+
...(manifest.integrations ? { integrations: manifest.integrations } : {}),
|
|
240
241
|
...(manifest.lifecycleTimeouts ? { lifecycleTimeouts: manifest.lifecycleTimeouts } : {}),
|
|
241
242
|
...("timeoutSeconds" in manifest.backend && manifest.backend.timeoutSeconds ? { operationSeconds: manifest.backend.timeoutSeconds } : {}),
|
|
242
243
|
...(options.apiDescription ? { ctl: { hash: options.apiDescription.sha256, registry: options.apiRegistry } } : {}),
|
|
@@ -295,13 +296,11 @@ export async function assembleStandaloneSpk(options) {
|
|
|
295
296
|
: folder
|
|
296
297
|
? sharedFolderInstallSteps(manifest.package, folder)
|
|
297
298
|
: renderNativeInstallWizard;
|
|
298
|
-
const install = renderInstallWizardFiles(manifest.package, (t) => [...steps(t), ...settingsInstallSteps(manifest.installSettings)(t), ...(portal ? [renderPortalWizardStep(t, portal.alias)] : [])]);
|
|
299
|
+
const install = renderInstallWizardFiles(manifest.package, (t) => [...steps(t), ...settingsInstallSteps(manifest.installSettings)(t), ...(portal ? [renderPortalWizardStep(t, portal.alias)] : []), ...integrationWizardSteps(t, manifest.integrations)]);
|
|
299
300
|
writeWizardUiFiles(staging, {
|
|
300
301
|
...install.static,
|
|
301
302
|
...install.dynamic,
|
|
302
|
-
...(folder
|
|
303
|
-
? renderSharedFolderUpgradeWizardFiles(manifest.package, folder)
|
|
304
|
-
: {}),
|
|
303
|
+
...renderUpgradeWizardFiles(manifest.package, folder, manifest.integrations),
|
|
305
304
|
...renderUninstallWizardFiles(manifest.package, "native", {
|
|
306
305
|
composeData: manifest.compose?.plan.app_data,
|
|
307
306
|
executor: Boolean(options.executor),
|