@synopackageland/cli 0.14.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 (114) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +26 -16
  3. package/dist/build-spk/generator.d.ts +3 -2
  4. package/dist/build-spk/generator.js +6 -846
  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/web-launch.d.ts +1 -12
  14. package/dist/contract/web-launch.js +10 -74
  15. package/dist/ctl/cli.d.ts +36 -0
  16. package/dist/ctl/cli.js +218 -0
  17. package/dist/ctl/descriptor.d.ts +81 -0
  18. package/dist/ctl/descriptor.js +93 -0
  19. package/dist/ctl/dsm-auth.d.ts +10 -0
  20. package/dist/ctl/dsm-auth.js +30 -0
  21. package/dist/ctl/errors.d.ts +17 -0
  22. package/dist/ctl/errors.js +32 -0
  23. package/dist/ctl/flows.d.ts +11 -0
  24. package/dist/ctl/flows.js +52 -0
  25. package/dist/ctl/input.d.ts +20 -0
  26. package/dist/ctl/input.js +128 -0
  27. package/dist/ctl/json.d.ts +2 -0
  28. package/dist/ctl/json.js +38 -0
  29. package/dist/ctl/redaction.d.ts +15 -0
  30. package/dist/ctl/redaction.js +198 -0
  31. package/dist/ctl/service.d.ts +38 -0
  32. package/dist/ctl/service.js +156 -0
  33. package/dist/ctl/store.d.ts +51 -0
  34. package/dist/ctl/store.js +93 -0
  35. package/dist/ctl/transfers.d.ts +14 -0
  36. package/dist/ctl/transfers.js +74 -0
  37. package/dist/ctl/transport.d.ts +11 -0
  38. package/dist/ctl/transport.js +79 -0
  39. package/dist/deploy/deploy.d.ts +4 -3
  40. package/dist/deploy/deploy.js +5 -210
  41. package/dist/deploy/plan.js +26 -7
  42. package/dist/dev.js +5 -4
  43. package/dist/error-registry.js +1020 -834
  44. package/dist/info.js +120 -28
  45. package/dist/init.js +188 -64
  46. package/dist/inspect.js +21 -6
  47. package/dist/runtime/executor-digests.json +1 -12
  48. package/dist/skill.js +51 -9
  49. package/dist/standalone/api-description-inspect.d.ts +4 -0
  50. package/dist/standalone/api-description-inspect.js +12 -0
  51. package/dist/standalone/api-description.d.ts +7 -0
  52. package/dist/standalone/api-description.js +73 -0
  53. package/dist/standalone/build.d.ts +3 -0
  54. package/dist/standalone/build.js +140 -33
  55. package/dist/standalone/compose-errors.d.ts +3 -0
  56. package/dist/standalone/compose-errors.js +243 -0
  57. package/dist/standalone/compose.d.ts +97 -10
  58. package/dist/standalone/compose.js +306 -75
  59. package/dist/standalone/daemon.js +111 -29
  60. package/dist/standalone/dsm-window.js +39 -8
  61. package/dist/standalone/identity-gateway.d.ts +9 -2
  62. package/dist/standalone/identity-gateway.js +282 -66
  63. package/dist/standalone/inspect-spk.d.ts +12 -0
  64. package/dist/standalone/inspect-spk.js +666 -136
  65. package/dist/standalone/lifecycle.js +124 -42
  66. package/dist/standalone/manifest.d.ts +8 -1
  67. package/dist/standalone/manifest.js +303 -86
  68. package/dist/standalone/resource.js +6 -2
  69. package/dist/standalone/spk.d.ts +4 -1
  70. package/dist/standalone/spk.js +138 -39
  71. package/dist/template-paths.d.ts +0 -2
  72. package/dist/template-paths.js +0 -5
  73. package/dist/test-command.d.ts +1 -1
  74. package/dist/test-command.js +1 -1
  75. package/dist/validate.d.ts +3 -3
  76. package/dist/validate.js +32 -140
  77. package/package.json +5 -3
  78. package/schema/synopkgland-api-v1.schema.json +423 -0
  79. package/skills/SKILL.md +3 -1
  80. package/skills/reference/compose.md +349 -0
  81. package/skills/reference/create.md +8 -17
  82. package/skills/reference/ctl.md +41 -0
  83. package/skills/reference/deploy.md +1 -1
  84. package/skills/reference/files.md +4 -0
  85. package/skills/reference/identity-gateway.md +8 -144
  86. package/skills/reference/wrap.md +10 -39
  87. package/assets/go/daemon/fixture_linux_test.go +0 -15
  88. package/assets/go/daemon/fixture_other_test.go +0 -7
  89. package/assets/go/daemon/go.mod +0 -5
  90. package/assets/go/daemon/main.go +0 -636
  91. package/assets/go/daemon/main_test.go +0 -315
  92. package/assets/go/daemon/network.go +0 -107
  93. package/assets/go/daemon/network_test.go +0 -86
  94. package/assets/go/daemon/process_linux.go +0 -55
  95. package/assets/go/daemon/process_linux_test.go +0 -161
  96. package/assets/go/daemon/process_other.go +0 -14
  97. package/assets/go/daemon/testdata/child.go +0 -44
  98. package/assets/go/gateway/auth.go +0 -101
  99. package/assets/go/gateway/auth_test.go +0 -216
  100. package/assets/go/gateway/cgi.go +0 -133
  101. package/assets/go/gateway/cgi_test.go +0 -156
  102. package/assets/go/gateway/csrf.go +0 -73
  103. package/assets/go/gateway/csrf_test.go +0 -155
  104. package/assets/go/gateway/directory.go +0 -108
  105. package/assets/go/gateway/directory_test.go +0 -202
  106. package/assets/go/gateway/gateway.go +0 -412
  107. package/assets/go/gateway/gateway_test.go +0 -676
  108. package/assets/go/gateway/go.mod +0 -5
  109. package/assets/go/gateway/groups.go +0 -66
  110. package/assets/go/gateway/main.go +0 -45
  111. package/dist/build-spk/gateway.d.ts +0 -31
  112. package/dist/build-spk/gateway.js +0 -60
  113. package/dist/compose/privilege.d.ts +0 -14
  114. package/dist/compose/privilege.js +0 -102
@@ -0,0 +1,79 @@
1
+ import { strictJson } from './json.js';
2
+ import http from 'node:http';
3
+ import https from 'node:https';
4
+ import { CtlError } from './errors.js';
5
+ export const APP_COOKIE = Symbol("ctlAppCookie");
6
+ export function canonicalOrigin(input, tunnel = false) {
7
+ let url;
8
+ try {
9
+ url = new URL(input);
10
+ }
11
+ catch {
12
+ throw new CtlError('CTL_URL_INVALID', 'A NAS origin URL is required.', 'run: synopkgland ctl host add <name> --url https://<nas>:5001');
13
+ }
14
+ if (url.username || url.password || url.search || url.hash || url.pathname !== '/' || !(url.protocol === 'https:' || (tunnel && url.protocol === 'http:' && ['127.0.0.1', '[::1]'].includes(url.hostname))))
15
+ throw new CtlError('CTL_URL_INVALID', 'Use HTTPS, or explicit --ssh-tunnel for a loopback HTTP tunnel.', 'run: synopkgland ctl host add <name> --url https://<nas>:5001');
16
+ return url.origin;
17
+ }
18
+ export function createRequest(origin, timeoutMs = 10000, ca) {
19
+ return async (path, body, session) => {
20
+ if (!path.startsWith('/') || path.startsWith('//') || path.includes('?') || path.includes('#') || path.includes('\\') || path.includes('%') || path.split('/').some(p => p === '.' || p === '..'))
21
+ throw new CtlError('CTL_PATH_INVALID', 'Unsafe request path.', 'run: synopkgland ctl list --refresh');
22
+ const url = new URL(path, origin);
23
+ if (url.origin !== origin || (session && session.origin !== origin))
24
+ throw new CtlError('CTL_ORIGIN_MISMATCH', 'Credentials cannot cross NAS origins.', 'run: synopkgland ctl host list', 4);
25
+ const form = body instanceof URLSearchParams;
26
+ const payload = Buffer.from(form ? body.toString() : JSON.stringify(body));
27
+ if (payload.length > 1048576)
28
+ throw new CtlError('CTL_INPUT_TOO_LARGE', 'Request exceeds 1 MiB.', 'Reduce the input.');
29
+ return new Promise((resolve, reject) => {
30
+ const headers = { 'content-type': form ? 'application/x-www-form-urlencoded' : 'application/json', accept: 'application/json', 'content-length': String(payload.length), origin };
31
+ if (session) {
32
+ headers.cookie = session.cookie;
33
+ headers['X-SYNO-TOKEN'] = session.token;
34
+ }
35
+ const req = (url.protocol === 'https:' ? https : http).request(url, { method: 'POST', headers, timeout: timeoutMs, ...(ca ? { ca } : {}) }, res => {
36
+ if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
37
+ res.resume();
38
+ reject(new CtlError('CTL_HTTP_ERROR', `HTTP ${res.statusCode}; redirects are never followed.`, 'Check the NAS URL and connection.', 6));
39
+ return;
40
+ }
41
+ if (!res.headers['content-type']?.includes('application/json')) {
42
+ res.resume();
43
+ reject(new CtlError('CTL_RESPONSE_INVALID', 'NAS returned non-JSON data.', 'Check the endpoint and installed package.', 5));
44
+ return;
45
+ }
46
+ const chunks = [];
47
+ let length = 0;
48
+ res.on('data', (c) => { length += c.length; if (length > 1048576) {
49
+ res.destroy();
50
+ reject(new CtlError('CTL_RESPONSE_TOO_LARGE', 'Response exceeds 1 MiB.', 'Use a smaller operation.', 5));
51
+ }
52
+ else
53
+ chunks.push(c); });
54
+ res.on('error', () => reject(new CtlError('CTL_NETWORK', 'Response interrupted.', 'Check the NAS connection; state-changing requests are not replayed.', 6)));
55
+ res.on('end', () => {
56
+ try {
57
+ const body = strictJson(Buffer.concat(chunks));
58
+ if (!form && body?.success === true && body.data && path.startsWith('/webman/3rdparty/') && url.protocol === 'https:') {
59
+ const cookies = (res.headers['set-cookie'] ?? []).filter(line => /;\s*Secure(?:;|$)/i.test(line) && /;\s*HttpOnly(?:;|$)/i.test(line) && !/;\s*Domain=/i.test(line));
60
+ const own = path.slice(0, path.lastIndexOf('/cgi/')) + '/';
61
+ const valid = cookies.filter(line => { const match = /;\s*Path=([^;]+)/i.exec(line); return match?.[1] === own; }).map(line => line.split(';')[0]).filter(pair => !pair.startsWith('id='));
62
+ if (valid.length)
63
+ Object.defineProperty(body, APP_COOKIE, { value: valid.join('; '), enumerable: false });
64
+ }
65
+ resolve(body);
66
+ }
67
+ catch {
68
+ reject(new CtlError('CTL_RESPONSE_INVALID', 'Invalid JSON response.', 'Check the installed package.', 5));
69
+ }
70
+ });
71
+ });
72
+ const wallTimer = setTimeout(() => req.destroy(), timeoutMs);
73
+ req.once('close', () => clearTimeout(wallTimer));
74
+ req.on('timeout', () => req.destroy());
75
+ req.on('error', () => reject(new CtlError('CTL_NETWORK', 'NAS request failed or timed out.', 'Check the NAS connection and trusted CA; requests are not replayed.', 6)));
76
+ req.end(payload);
77
+ });
78
+ };
79
+ }
@@ -1,8 +1,8 @@
1
1
  import type { CommandRunner } from "../standalone/build.js";
2
- import { type NativeArchFamily } from "../build-spk/dsm-arch.js";
2
+ import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
3
3
  import type { ProjectRoot } from "../discovery.js";
4
4
  import type { NasHost } from "../host/types.js";
5
- import { resolveDeploySpkVersion } from "./spk-version.js";
5
+ import type { resolveDeploySpkVersion } from "./spk-version.js";
6
6
  export interface DeployProjectOptions {
7
7
  project: ProjectRoot;
8
8
  /** Gateway architecture. Omitted means "ask the NAS" when needed. */
@@ -27,4 +27,5 @@ export interface DeployProjectResult {
27
27
  /** Non-blocking pre-flight remarks, e.g. a read-only Shared Folder grant. */
28
28
  notes?: string[];
29
29
  }
30
- export declare function deployProject(options: DeployProjectOptions): Promise<DeployProjectResult>;
30
+ /** Removed legacy dispatch; schema-2 deployment uses planDeployment/deployStandalone. */
31
+ export declare function deployProject(_options: DeployProjectOptions): Promise<DeployProjectResult>;
@@ -1,213 +1,8 @@
1
- import { buildSpk } from "../build-spk/generator.js";
2
- import { CONTAINER_ARCH_FAMILIES, isContainerArchFamily } from "../build-spk/dsm-arch.js";
3
- import { loadContract } from "../contract/load.js";
4
- import { usesAuthGateway } from "../contract/web-launch.js";
5
- import { findUnsupportedInstallTypes } from "../contract/unsupported-types.js";
6
- import { appOriginFromContract } from "../host/open-url.js";
7
- import { validateProject } from "../validate.js";
8
- import { resolveDeployIdentity, PACKAGE_IDENTITY_MAX_LENGTH } from "./identity.js";
9
- import { resolveDeploySpkVersion } from "./spk-version.js";
10
- import { resolveSharedFolderExtraValues, sharedFolderParameterNames } from "./share-values.js";
11
- import { resolveDsmUserExtraValues, dsmUserParameterNames } from "./run-as-values.js";
12
- import { runSharePermissionPreflight } from "./share-permission-preflight.js";
13
- import { homesParameterNames, resolveHomesExtraValues } from "./homes-values.js";
14
- import { resolveInstallParameterExtraValues } from "./param-values.js";
15
- import { loadCompose } from "../compose/analyze.js";
16
- import { runPortPreflight } from "./port-preflight.js";
17
- const POSIX_ALL_SHARES_LOOKUP_FAILED = "DEPLOY_POSIX_ALL_SHARES_LOOKUP_FAILED";
18
- export async function deployProject(options) {
19
- const contract = loadContract(options.project.contractPath).contract;
20
- // A45/A46: refuse before anything is built or uploaded. validate only warns,
21
- // so this is the gate that keeps an uninstallable App off a NAS.
22
- const unsupported = findUnsupportedInstallTypes(contract);
23
- if (unsupported.length > 0) {
24
- const first = unsupported[0];
25
- return {
26
- ok: false,
27
- code: first.code,
28
- message: `The Install Parameter "${first.name}" with type: ${first.type} has no usable install path today, ` +
29
- "a Package Center install ends in DSM broken/150, so deploy refuses to deploy." +
30
- (first.type === "homes"
31
- ? "If you need an administrator-chosen folder, use type: shared_folder."
32
- : "Use type: string, or have the App read the file from ${SYNO_APP_SECRETS_DIR}."),
33
- };
34
- }
35
- const validation = validateProject(options.project);
36
- if (!validation.ok) {
37
- return { ok: false, code: "VALIDATION_FAILED", message: validation.errors.map((e) => e.code).join(", ") };
38
- }
39
- const identity = resolveDeployIdentity(options.project, { dev: options.dev });
40
- if (identity.packageId.length > PACKAGE_IDENTITY_MAX_LENGTH) {
41
- return {
42
- ok: false,
43
- code: "PACKAGE_IDENTITY_TOO_LONG",
44
- message: `The final Package Identity "${identity.packageId}" length ${identity.packageId.length} exceeds the DSM limit of ${PACKAGE_IDENTITY_MAX_LENGTH} characters.`,
45
- };
46
- }
47
- const packageInstalled = await options.host.isPackageInstalled(identity.packageId);
48
- const explicitParamNames = new Set(Object.keys(options.paramValues ?? {}));
49
- const paramValues = { ...(options.paramValues ?? {}) };
50
- const posixAllSharesNames = Object.entries(contract.install ?? {})
51
- .filter(([, parameter]) => parameter?.type === "posix_all_shares")
52
- .map(([name]) => name);
53
- for (const name of posixAllSharesNames) {
54
- if (Object.prototype.hasOwnProperty.call(paramValues, name)) {
55
- continue;
56
- }
57
- let shareNames;
58
- try {
59
- shareNames = await options.host.listShareNames?.();
60
- }
61
- catch {
62
- shareNames = undefined;
63
- }
64
- const names = (shareNames ?? []).map((value) => value.trim()).filter(Boolean);
65
- if (names.length === 0) {
66
- return {
67
- ok: false,
68
- code: POSIX_ALL_SHARES_LOOKUP_FAILED,
69
- message: `The Shared Folders of this NAS could not be listed with an administrator session, so the POSIX all-shares parameter "${name}" cannot be filled.`,
70
- };
71
- }
72
- paramValues[name] = names.join(",");
73
- }
74
- const paramResolved = resolveInstallParameterExtraValues({
75
- contract,
76
- provided: paramValues,
77
- });
78
- if (!paramResolved.ok) {
79
- return { ok: false, code: paramResolved.code, message: paramResolved.message };
80
- }
81
- const homesResolved = await resolveHomesExtraValues({
82
- requiredKeys: homesParameterNames(contract),
83
- lookup: async () => options.host.resolveHomesPath?.(),
84
- });
85
- if (!homesResolved.ok) {
86
- return { ok: false, code: homesResolved.code, message: homesResolved.message };
87
- }
88
- const shareResolved = await resolveSharedFolderExtraValues({
89
- requiredKeys: sharedFolderParameterNames(contract).filter((key) => !packageInstalled || options.shareValues?.[key] !== undefined),
90
- provided: options.shareValues ?? {},
91
- lookup: async (logicalName) => options.host.resolveSharePath?.(logicalName),
92
- });
93
- if (!shareResolved.ok) {
94
- return { ok: false, code: shareResolved.code, message: shareResolved.message };
95
- }
96
- const runAsResolved = resolveDsmUserExtraValues({
97
- requiredKeys: dsmUserParameterNames(contract).filter((key) => !packageInstalled || options.runAsValues?.[key] !== undefined),
98
- provided: options.runAsValues ?? {},
99
- });
100
- if (!runAsResolved.ok) {
101
- return { ok: false, code: runAsResolved.code, message: runAsResolved.message };
102
- }
103
- // A42 pre-flight: preinst runs as the package user and can never read ACL, so
104
- // the only place that can catch "selected user cannot reach selected Share" is
105
- // here, where we still hold an administrator session. Refuses before install.
106
- const sharePermission = await runSharePermissionPreflight({
107
- shares: Object.fromEntries(sharedFolderParameterNames(contract)
108
- .map((key) => [key, (options.shareValues ?? {})[key] ?? ""])
109
- .filter(([, value]) => value.trim() !== "")),
110
- runAsUsers: runAsResolved.extraValues,
111
- listSharePermissions: options.host.listSharePermissions
112
- ? (name) => options.host.listSharePermissions(name)
113
- : undefined,
114
- listUserGroups: options.host.listUserGroups
115
- ? (name) => options.host.listUserGroups(name)
116
- : undefined,
117
- });
118
- if (!sharePermission.ok) {
119
- return { ok: false, code: sharePermission.code, message: sharePermission.message };
120
- }
121
- const portPreflight = await runPortPreflight({
122
- compose: loadCompose(options.project.composePath)?.compose,
123
- packageIdentity: identity.packageId,
124
- listPortInfo: options.host.listPortInfo
125
- ? () => options.host.listPortInfo()
126
- : undefined,
127
- });
128
- if (!portPreflight.ok) {
129
- return { ok: false, code: portPreflight.code, message: portPreflight.message };
130
- }
131
- const extraValues = {
132
- ...paramResolved.extraValues,
133
- ...homesResolved.extraValues,
134
- ...shareResolved.extraValues,
135
- ...runAsResolved.extraValues,
136
- };
137
- // The upgrade wizard deliberately ignores auto-filled Share and Run-as controls unless
138
- // the administrator opted into a change. Keep the CLI's explicit upgrade
139
- // flags equivalent to checking those controls.
140
- if (packageInstalled) {
141
- for (const name of sharedFolderParameterNames(contract)) {
142
- if ((options.shareValues?.[name] ?? "").trim() !== "") {
143
- extraValues[`CHANGE_${name}`] = "true";
144
- }
145
- }
146
- for (const name of dsmUserParameterNames(contract)) {
147
- if ((options.runAsValues?.[name] ?? "").trim() !== "") {
148
- extraValues[`CHANGE_${name}`] = "true";
149
- }
150
- }
151
- for (const name of posixAllSharesNames) {
152
- if (explicitParamNames.has(name) && (paramValues[name] ?? "").trim() !== "") {
153
- extraValues[`CHANGE_${name}`] = "true";
154
- }
155
- }
156
- }
157
- const resolveSpkVersion = options.spkVersionResolver ?? resolveDeploySpkVersion;
158
- let spkVersion;
159
- try {
160
- spkVersion = await resolveSpkVersion(options.project, options.host, identity.packageId);
161
- }
162
- catch (error) {
163
- return { ok: false, code: "DEPLOY_VERSION_INVALID",
164
- message: error instanceof Error ? error.message : String(error) };
165
- }
166
- let archFamily = options.archFamily;
167
- // A DSM-authenticated web App ships a native gateway CGI, so it is built per architecture too.
168
- if (archFamily === undefined && usesAuthGateway(contract)) {
169
- archFamily = await options.host.getArchFamily?.();
170
- if (archFamily === undefined) {
171
- return {
172
- ok: false,
173
- code: "NATIVE_ARCH_UNDETERMINED",
174
- message: "The CPU architecture could not be determined from this NAS. Specify --arch x86_64 or --arch aarch64 explicitly.",
175
- };
176
- }
177
- }
178
- if (archFamily !== undefined && !isContainerArchFamily(archFamily)) {
179
- return {
180
- ok: false,
181
- code: "CONTAINER_ARCH_UNSUPPORTED",
182
- message: `Container Manager Apps support only ${CONTAINER_ARCH_FAMILIES.join(", ")}; DSM does not provide Container Manager for ${archFamily}.`,
183
- };
184
- }
185
- const build = await buildSpk({
186
- project: options.project,
187
- packageIdentity: identity.packageId,
188
- displayname: identity.displayname,
189
- spkVersion,
190
- archFamily,
191
- gatewayRun: options.gatewayRun,
192
- });
193
- if (!build.ok || !build.outputPath) {
194
- return { ok: false, code: "BUILD_SPK_FAILED", message: build.errors.join(", ") };
195
- }
196
- try {
197
- await options.host.installFromLocalSpk({
198
- packageId: identity.packageId,
199
- version: spkVersion,
200
- spkPath: build.outputPath,
201
- extraValues,
202
- });
203
- }
204
- catch (error) {
205
- return { ok: false, code: "LOCAL_SPK_INSTALL_FAILED",
206
- message: error instanceof Error ? error.message : String(error) };
207
- }
1
+ /** Removed legacy dispatch; schema-2 deployment uses planDeployment/deployStandalone. */
2
+ export async function deployProject(_options) {
208
3
  return {
209
- ok: true, packageId: identity.packageId, spkPath: build.outputPath,
210
- openUrl: appOriginFromContract(options.host.dsmOrigin, contract),
211
- version: spkVersion, notes: sharePermission.notes,
4
+ ok: false,
5
+ code: "COMPOSE_LEGACY_MANIFEST_REMOVED",
6
+ message: "Use schema-2 dsm-package.yaml and the unified deployment plan; the legacy Compose installer is removed.",
212
7
  };
213
8
  }
@@ -3,7 +3,7 @@ import { loadContract } from "../contract/load.js";
3
3
  import { validateProject } from "../validate.js";
4
4
  import { findStandaloneRoot, loadStandaloneManifest, } from "../standalone/manifest.js";
5
5
  import { buildStandalone } from "../standalone/build.js";
6
- import { buildNativeLayout, isNativeLayout, loadNativeLayout } from "../standalone/native-layout.js";
6
+ import { buildNativeLayout, isNativeLayout, loadNativeLayout, } from "../standalone/native-layout.js";
7
7
  import { resolveDeployIdentity } from "./identity.js";
8
8
  import { nextDeployVersion } from "./spk-version.js";
9
9
  import { resolveNativeInstallValues } from "./native-install-values.js";
@@ -29,17 +29,28 @@ export function planDeployment(root, options = {}) {
29
29
  throw fail("STANDALONE_ARCH_NOT_DECLARED", `Architecture not declared: ${options.arch}; add it to dsm-package.yaml arches to deploy it`);
30
30
  return {
31
31
  root: standalone,
32
- kind: m.backend.kind === "binary" ? "binary" : `native-${m.backend.kind}`,
32
+ kind: m.compose
33
+ ? "container"
34
+ : m.backend.kind === "binary"
35
+ ? "binary"
36
+ : `native-${m.backend.kind}`,
33
37
  packageId: m.package,
34
38
  version: m.spkVersion,
35
39
  arch: options.arch,
36
40
  capabilities: Object.keys(m.capabilities),
37
41
  transport: "dsm-local-spk",
38
- notes: m.capabilities.rootOps || m.capabilities.delegate || m.capabilities.webapi
42
+ notes: m.compose
39
43
  ? [
40
- "Installation does not mean ready. Approve boot and control tasks once in the App window. An executor-only upgrade stays ready without approval (INFO anchors the executor digest); a policy digest change requires approval again.",
44
+ "Containers use the fixed package identity; they do not inherit signed-in File Station ACLs.",
45
+ ...m.compose.plan.acceptance_blockers,
41
46
  ]
42
- : [],
47
+ : m.capabilities.rootOps ||
48
+ m.capabilities.delegate ||
49
+ m.capabilities.webapi
50
+ ? [
51
+ "Installation does not mean ready. Approve boot and control tasks once in the App window. An executor-only upgrade stays ready without approval (INFO anchors the executor digest); a policy digest change requires approval again.",
52
+ ]
53
+ : [],
43
54
  };
44
55
  }
45
56
  const project = findProjectRoot(root);
@@ -100,7 +111,11 @@ async function buildForDeploy(plan, arch, installed, options) {
100
111
  const name = Object.keys(options.params ?? {})[0];
101
112
  if (name)
102
113
  throw fail("DEPLOY_PARAM_UNDECLARED", `${m.package} has no install wizard values; remove --param ${name}.`);
103
- const { outputs } = await buildNativeLayout({ root: plan.root, arches: [layoutArch], run: options.run });
114
+ const { outputs } = await buildNativeLayout({
115
+ root: plan.root,
116
+ arches: [layoutArch],
117
+ run: options.run,
118
+ });
104
119
  return { outputs, extraValues: {} };
105
120
  }
106
121
  const loaded = loadStandaloneManifest(plan.root);
@@ -109,7 +124,11 @@ async function buildForDeploy(plan, arch, installed, options) {
109
124
  const values = resolveNativeInstallValues(loaded.manifest, options.params ?? {}, { installed });
110
125
  if (!values.ok)
111
126
  throw fail(values.code, values.message);
112
- const { outputs } = await buildStandalone({ root: plan.root, arches: [arch], run: options.run });
127
+ const { outputs } = await buildStandalone({
128
+ root: plan.root,
129
+ arches: [arch],
130
+ run: options.run,
131
+ });
113
132
  return { outputs, extraValues: values.extraValues };
114
133
  }
115
134
  export async function deployStandalone(plan, host, options = {}) {
package/dist/dev.js CHANGED
@@ -1,9 +1,6 @@
1
- import { existsSync } from "node:fs";
2
1
  import { createDockerComposeRunner } from "./compose/runner.js";
3
2
  /** Run the application's own Compose project locally. */
4
3
  export async function runDev(options) {
5
- if (!existsSync(options.project.composePath))
6
- throw new Error('dev requires compose.yaml');
7
4
  const runner = options.composeRunner ?? createDockerComposeRunner();
8
5
  try {
9
6
  await runner.up(options.project.root);
@@ -13,5 +10,9 @@ export async function runDev(options) {
13
10
  await runner.down?.(options.project.root);
14
11
  throw error;
15
12
  }
16
- return { close: async () => { await runner.down?.(options.project.root); } };
13
+ return {
14
+ close: async () => {
15
+ await runner.down?.(options.project.root);
16
+ },
17
+ };
17
18
  }