@synopackageland/cli 0.3.4 → 0.3.6

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.
@@ -17,7 +17,7 @@ import { BROKER_INSTALLER_VARS, BROKER_SOCKET_REQUIRED_SCRIPT, renderBrokerStatu
17
17
  import { loadNative } from "../native/load.js";
18
18
  import { materializeNativePayload } from "./native-payload.js";
19
19
  import { renderNativePrivilege, renderNativeStartStopStatus, renderNativePostuninst, } from "./native-lifecycle.js";
20
- import { renderResourceConfig, renderResourceWorkerStartStopStatus } from "./resource-worker.js";
20
+ import { renderHeadlessResourceMarker, renderResourceConfig, renderResourceWorkerStartStopStatus, } from "./resource-worker.js";
21
21
  const FILE_MODE = 0o644;
22
22
  const SCRIPT_MODE = 0o755;
23
23
  const INSTALL_UIFILE = "install_uifile";
@@ -161,7 +161,7 @@ export async function buildSpk(options) {
161
161
  mkdirSync(join(staging, "scripts"), { recursive: true });
162
162
  for (const name of SCRIPT_NAMES) {
163
163
  const scriptPath = join(staging, "scripts", name);
164
- writeFileSync(scriptPath, renderManagedScript(name, contract, undefined, isResourceWorkerRoute, rwEligibility?.adminport, isDynamicRoute), "utf8");
164
+ writeFileSync(scriptPath, renderManagedScript(name, contract, undefined, isResourceWorkerRoute, rwEligibility?.adminport, isDynamicRoute, rwEligibility?.headless === true), "utf8");
165
165
  chmodSync(scriptPath, SCRIPT_MODE);
166
166
  }
167
167
  mkdirSync(join(staging, "package"), { recursive: true });
@@ -192,6 +192,11 @@ export async function buildSpk(options) {
192
192
  copyFileSync(options.project.composePath, join(resourceProjectDir, "docker-compose.yml"));
193
193
  }
194
194
  chmodSync(join(resourceProjectDir, "docker-compose.yml"), FILE_MODE);
195
+ const routeMarkerDir = join(staging, "package", ".synopkgland");
196
+ mkdirSync(routeMarkerDir, { recursive: true });
197
+ const routeMarker = join(routeMarkerDir, "route");
198
+ writeFileSync(routeMarker, "package-only", "utf8");
199
+ chmodSync(routeMarker, FILE_MODE);
195
200
  }
196
201
  const htmlDir = join(options.project.root, "html");
197
202
  if (existsSync(htmlDir)) {
@@ -580,6 +585,7 @@ function renderUninstallWizard() {
580
585
  ],
581
586
  }];
582
587
  }
588
+ const LEGACY_BROKER_STATE_REGISTRY_CODE = "COMPOSE_LEGACY_BROKER_STATE_PRESENT";
583
589
  function renderDynamicPreinstMaterializer(contract) {
584
590
  const allParams = collectInstallParameters(contract);
585
591
  const scalarParams = allParams.filter(({ param }) => param.type === "string" || param.type === "boolean");
@@ -591,6 +597,18 @@ function renderDynamicPreinstMaterializer(contract) {
591
597
  "umask 077",
592
598
  "# D3/D5.4 package-only carrier; values are wizard answers, canonical values, or defaults.",
593
599
  "die() { echo \"dynamic route materialization failed: $1\" >&2; exit 1; }",
600
+ `# registry-code="${LEGACY_BROKER_STATE_REGISTRY_CODE}"`,
601
+ 'APPDATA="${SYNOPKG_PKGDEST_VOL:?}/@appdata/${SYNOPKG_PKGNAME:?}"',
602
+ 'if [ -d "$APPDATA" ]; then',
603
+ ' broker_uid="$(id -u SynoPkgLandService 2>/dev/null || true)"',
604
+ ' if [ -n "$broker_uid" ]; then',
605
+ ' for item in "$APPDATA"/* "$APPDATA"/.[!.]* "$APPDATA"/..?*; do',
606
+ ' [ -e "$item" ] || continue',
607
+ ' item_uid="$(stat -c %u "$item" 2>/dev/null || true)"',
608
+ ' [ "$item_uid" = "$broker_uid" ] && die "MIGRATION_UNSUPPORTED: ${SYNOPKG_PKGNAME} 曾由 Broker installer 安裝;dynamic 路線不支援就地遷移,請以新的 Package Identity 安裝或先完整卸載並清除 App Data"',
609
+ " done",
610
+ " fi",
611
+ "fi",
594
612
  'T="${SYNOPKG_PKGINST_TEMP_DIR:?}"',
595
613
  'STATUS="${SYNOPKG_PKG_STATUS:?}"',
596
614
  'PROJECT="$T/compose"',
@@ -677,12 +695,13 @@ function renderDynamicPreinstMaterializer(contract) {
677
695
  }
678
696
  if (dsmUserParam) {
679
697
  const name = dsmUserParam.name;
680
- lines.push(` append_env "${name}_UID" "$${name}_UID" "string"`, ` append_env "${name}_GID" "$${name}_GID" "string"`, ` idx=1`, ` for g in $${name}_GROUPS; do`, ` append_env "${name}_GROUP_\${idx}" "$g" "string"`, ` idx=$((idx + 1))`, ` done`);
698
+ const rawUserVar = "D54_RAW_USER_" + name;
699
+ lines.push(` append_env "${name}" "$${rawUserVar}" "string"`, ` append_env "${name}_UID" "$${name}_UID" "string"`, ` append_env "${name}_GID" "$${name}_GID" "string"`, ` idx=1`, ` for g in $${name}_GROUPS; do`, ` append_env "${name}_GROUP_\${idx}" "$g" "string"`, ` idx=$((idx + 1))`, ` done`);
681
700
  }
682
701
  lines.push(" } > \"$tmp\"", " mv \"$tmp\" \"$RUNTIME_ENV\"", " trap - EXIT HUP INT TERM", " [ ! -L \"$RUNTIME_ENV\" ] || die \"runtime file became a symlink\"", " [ \"$(stat -c %a \"$RUNTIME_ENV\")\" = \"600\" ] || die \"runtime env mode is not 0600\"", "exit 0");
683
702
  return lines.join("\n") + "\n";
684
703
  }
685
- function renderDynamicCanonicalMirror() {
704
+ function renderDynamicCanonicalMirror(headless = false) {
686
705
  const lines = [
687
706
  "#!/bin/sh",
688
707
  "set -eu",
@@ -719,14 +738,17 @@ function renderDynamicCanonicalMirror() {
719
738
  'trap - EXIT HUP INT TERM',
720
739
  '[ ! -L "$CANONICAL_ENV" ] || die "canonical file became a symlink"',
721
740
  '[ "$(stat -c %a "$CANONICAL_ENV")" = "600" ] || die "canonical env mode is not 0600"',
722
- "exit 0",
723
741
  ];
742
+ if (headless) {
743
+ lines.push('MARKER="${PKGVAR}/.docker-project-managed"', '[ ! -L "$MARKER" ] || die "docker-project marker is a symlink"', 'marker_tmp="$(mktemp "$PKGVAR/.docker-project-managed.tmp.XXXXXX")"', 'printf \'%s\\n\' managed > "$marker_tmp"', 'chmod 600 "$marker_tmp"', 'mv "$marker_tmp" "$MARKER"');
744
+ }
745
+ lines.push("exit 0");
724
746
  return lines.join("\n") + "\n";
725
747
  }
726
748
  function shellQuote(value) {
727
749
  return "'" + value.replace(/'/g, "'\\''") + "'";
728
750
  }
729
- function renderManagedScript(name, contract, nativeContext, isResourceWorker = false, resourceWorkerAdminport, isDynamicRoute = false) {
751
+ function renderManagedScript(name, contract, nativeContext, isResourceWorker = false, resourceWorkerAdminport, isDynamicRoute = false, isHeadlessRoute = false) {
730
752
  if (nativeContext) {
731
753
  const lifecycleInput = {
732
754
  packageIdentity: String(contract.package),
@@ -766,13 +788,16 @@ fi
766
788
  }
767
789
  if (isResourceWorker) {
768
790
  if (name === "start-stop-status") {
769
- return renderResourceWorkerStartStopStatus(resourceWorkerAdminport);
791
+ return renderResourceWorkerStartStopStatus(String(contract.package), resourceWorkerAdminport, isHeadlessRoute);
770
792
  }
771
793
  if (isDynamicRoute && name === "preinst") {
772
794
  return renderDynamicPreinstMaterializer(contract);
773
795
  }
774
796
  if (isDynamicRoute && (name === "postinst" || name === "postupgrade")) {
775
- return renderDynamicCanonicalMirror();
797
+ return renderDynamicCanonicalMirror(isHeadlessRoute);
798
+ }
799
+ if (isHeadlessRoute && (name === "postinst" || name === "postupgrade")) {
800
+ return renderHeadlessResourceMarker();
776
801
  }
777
802
  if (name === "postuninst") {
778
803
  const projectRoot = isDynamicRoute ? 'PROJECT="${SYNOPKG_PKGDEST}/compose"\n' : "";
@@ -11,4 +11,5 @@ export declare function renderResourceConfig(contract: AppContract, eligibility:
11
11
  * the worker timing hooks; the package script must wait for that operation to
12
12
  * leave a reachable service and must not claim running unconditionally.
13
13
  */
14
- export declare function renderResourceWorkerStartStopStatus(adminport?: number): string;
14
+ export declare function renderResourceWorkerStartStopStatus(packageId: string, adminport?: number, headless?: boolean): string;
15
+ export declare function renderHeadlessResourceMarker(): string;
@@ -77,7 +77,50 @@ export function renderResourceConfig(contract, eligibility) {
77
77
  * the worker timing hooks; the package script must wait for that operation to
78
78
  * leave a reachable service and must not claim running unconditionally.
79
79
  */
80
- export function renderResourceWorkerStartStopStatus(adminport) {
80
+ export function renderResourceWorkerStartStopStatus(packageId, adminport, headless = false) {
81
+ if (headless) {
82
+ const projectName = composeProjectName(packageId);
83
+ return `#!/bin/sh
84
+ PKGVAR="\${SYNOPKG_PKGVAR:-/var/packages/${packageId}/var}"
85
+ MARKER="$PKGVAR/.docker-project-managed"
86
+ PROJECT_NAME="${projectName}"
87
+
88
+ project_containers_running() {
89
+ command -v docker >/dev/null 2>&1 || return 2
90
+ containers="$(docker ps -aq --filter "label=com.docker.compose.project=$PROJECT_NAME" 2>/dev/null)" || return 2
91
+ [ -n "$containers" ] || return 1
92
+ for container in $containers; do
93
+ state="$(docker inspect --format '{{.State.Running}}' "$container" 2>/dev/null)" || return 2
94
+ [ "$state" = "true" ] || return 1
95
+ done
96
+ return 0
97
+ }
98
+
99
+ marker_managed() {
100
+ [ -f "$MARKER" ] || return 1
101
+ [ ! -L "$MARKER" ] || return 1
102
+ [ "$(cat "$MARKER" 2>/dev/null || true)" = "managed" ]
103
+ }
104
+
105
+ case "$1" in
106
+ start|status)
107
+ project_containers_running
108
+ probe_status=$?
109
+ if [ "$probe_status" -eq 0 ]; then
110
+ exit 0
111
+ fi
112
+ # Package user may not have a usable Docker inspection path. DSM owns the
113
+ # docker-project lifecycle; the postinst marker records that ownership.
114
+ if [ "$probe_status" -eq 2 ] && marker_managed; then
115
+ exit 0
116
+ fi
117
+ exit 3
118
+ ;;
119
+ stop) exit 0 ;;
120
+ *) exit 1 ;;
121
+ esac
122
+ `;
123
+ }
81
124
  if (adminport === undefined) {
82
125
  return `#!/bin/sh
83
126
  case "$1" in
@@ -138,3 +181,20 @@ case "$1" in
138
181
  esac
139
182
  `;
140
183
  }
184
+ export function renderHeadlessResourceMarker() {
185
+ return `#!/bin/sh
186
+ set -eu
187
+ PKGVAR="\${SYNOPKG_PKGVAR:?}"
188
+ [ -d "$PKGVAR" ] || mkdir -p "$PKGVAR"
189
+ [ ! -L "$PKGVAR" ] || { echo "package var is a symlink" >&2; exit 1; }
190
+ MARKER="$PKGVAR/.docker-project-managed"
191
+ [ ! -L "$MARKER" ] || { echo "docker-project marker is a symlink" >&2; exit 1; }
192
+ tmp="$(mktemp "$PKGVAR/.docker-project-managed.tmp.XXXXXX")"
193
+ trap 'rm -f "$tmp"' EXIT HUP INT TERM
194
+ printf '%s\n' managed > "$tmp"
195
+ chmod 600 "$tmp"
196
+ mv "$tmp" "$MARKER"
197
+ trap - EXIT HUP INT TERM
198
+ exit 0
199
+ `;
200
+ }
@@ -14,6 +14,7 @@ export type ResourceWorkerIneligibleReason = "HAS_INSTALL_PARAMETERS" | "HAS_COM
14
14
  export interface ResourceWorkerEligibility {
15
15
  eligible: boolean;
16
16
  reasons: ResourceWorkerIneligibleReason[];
17
+ headless: boolean;
17
18
  adminport?: number;
18
19
  }
19
20
  /**
@@ -24,7 +25,7 @@ export interface ResourceWorkerEligibility {
24
25
  * - compose secrets or configs
25
26
  * - build: stanzas
26
27
  * - privileged / host network / device / root-like capabilities
27
- * - any App without a unique literal published adminport status probe
28
+ * - any non-headless App without a unique literal published adminport status probe
28
29
  */
29
30
  export declare function resourceWorkerEligibility(contract: AppContract, compose: ComposeDocument): ResourceWorkerEligibility;
30
31
  export type ComposeRoute = "static-zero-config" | "dynamic" | "broker-installer" | "reject";
@@ -112,10 +112,11 @@ export function collectPublishedTcpPorts(compose) {
112
112
  * - compose secrets or configs
113
113
  * - build: stanzas
114
114
  * - privileged / host network / device / root-like capabilities
115
- * - any App without a unique literal published adminport status probe
115
+ * - any non-headless App without a unique literal published adminport status probe
116
116
  */
117
117
  export function resourceWorkerEligibility(contract, compose) {
118
118
  const reasons = [];
119
+ const headless = contract.adminport === undefined && collectPublishedTcpPorts(compose).size === 0;
119
120
  if (contract.install && Object.keys(contract.install).length > 0) {
120
121
  reasons.push("HAS_INSTALL_PARAMETERS");
121
122
  }
@@ -134,7 +135,9 @@ export function resourceWorkerEligibility(contract, compose) {
134
135
  }
135
136
  let resolvedAdminport;
136
137
  if (contract.adminport === undefined) {
137
- reasons.push("WEB_APP_MISSING_OR_AMBIGUOUS_ADMINPORT");
138
+ if (!headless) {
139
+ reasons.push("WEB_APP_MISSING_OR_AMBIGUOUS_ADMINPORT");
140
+ }
138
141
  }
139
142
  else {
140
143
  const rawPort = Number(contract.adminport);
@@ -155,6 +158,7 @@ export function resourceWorkerEligibility(contract, compose) {
155
158
  return {
156
159
  eligible: reasons.length === 0,
157
160
  reasons,
161
+ headless,
158
162
  adminport: resolvedAdminport,
159
163
  };
160
164
  }
@@ -176,6 +180,17 @@ function hasBuildStanza(compose) {
176
180
  }
177
181
  return Object.values(compose.services).some((service) => "build" in service && service.build !== undefined);
178
182
  }
183
+ const HEADLESS_BROKER_FALLBACK_REASONS = new Set([
184
+ "HAS_COMPOSE_SECRETS_OR_CONFIGS",
185
+ "HAS_BUILD_STANZA",
186
+ "HAS_PRIVILEGED_CAPABILITY",
187
+ ]);
188
+ const HEADLESS_PACKAGE_ONLY_PARAMETER_TYPES = new Set([
189
+ "string",
190
+ "boolean",
191
+ "shared_folder",
192
+ "dsm_user",
193
+ ]);
179
194
  /**
180
195
  * Chooses the local route D0 can safely emit. The first dynamic slice is
181
196
  * deliberately small: only string/boolean wizard answers interpolated as
@@ -185,13 +200,18 @@ function hasBuildStanza(compose) {
185
200
  */
186
201
  export function classifyComposeRoute(contract, parsed, composeFile = "compose.yaml") {
187
202
  const eligibility = resourceWorkerEligibility(contract, parsed.compose);
188
- const rejectErrors = contract.adminport !== undefined
203
+ const hasHeadlessUnsupportedParameter = collectInstallParameters(contract).some(({ param }) => !HEADLESS_PACKAGE_ONLY_PARAMETER_TYPES.has(String(param.type)));
204
+ const headlessPackageOnly = eligibility.headless &&
205
+ !hasHeadlessUnsupportedParameter &&
206
+ !eligibility.reasons.some((reason) => HEADLESS_BROKER_FALLBACK_REASONS.has(reason));
207
+ const localRoute = contract.adminport !== undefined || headlessPackageOnly;
208
+ const rejectErrors = localRoute
189
209
  ? collectRouteRejectErrors(contract, parsed, composeFile)
190
210
  : [];
191
211
  if (rejectErrors.length > 0) {
192
212
  return { route: "reject", eligibility, errors: rejectErrors };
193
213
  }
194
- const dynamicError = contract.adminport !== undefined
214
+ const dynamicError = localRoute
195
215
  ? collectDynamicRouteError(contract, parsed, composeFile)
196
216
  : undefined;
197
217
  if (dynamicError) {
@@ -206,9 +226,11 @@ export function classifyComposeRoute(contract, parsed, composeFile = "compose.ya
206
226
  }
207
227
  // Keep the route decision in one place. build-spk must not independently
208
228
  // reinterpret an ineligible Compose as D1: only a route with a resolved
209
- // published adminport and at least one declared install parameter has a
210
- // package-only materializer. Every other buildable fallback is Broker.
211
- if (eligibility.adminport !== undefined && collectInstallParameters(contract).length > 0) {
229
+ // published adminport or an eligible headless Compose, plus at least one
230
+ // declared install parameter, has a package-only materializer. Every other
231
+ // buildable fallback is Broker.
232
+ if ((eligibility.adminport !== undefined || headlessPackageOnly) &&
233
+ collectInstallParameters(contract).length > 0) {
212
234
  return { route: "dynamic", eligibility, errors: [] };
213
235
  }
214
236
  return { route: "broker-installer", eligibility, errors: [] };
@@ -239,7 +261,8 @@ function collectRouteRejectErrors(contract, parsed, composeFile) {
239
261
  return dedupeValidationErrors(errors);
240
262
  }
241
263
  function collectPermanentRouteErrors(contract, parsed, composeFile) {
242
- if (contract.adminport === undefined) {
264
+ const headless = contract.adminport === undefined && collectPublishedTcpPorts(parsed.compose).size === 0;
265
+ if (contract.adminport === undefined && !headless) {
243
266
  return [];
244
267
  }
245
268
  const { compose } = parsed;
package/dist/doctor.js CHANGED
@@ -6,6 +6,19 @@ import { loadContract } from "./contract/load.js";
6
6
  import { getErrorCode } from "./error-registry.js";
7
7
  import { dsmFetch } from "./host/dsm-http.js";
8
8
  import { appOriginFromContract } from "./host/open-url.js";
9
+ function packageRouteFromDiagnostics(diagnostics) {
10
+ switch (diagnostics?.packageRoute) {
11
+ case "package-only":
12
+ case "broker-installer":
13
+ case "unknown":
14
+ return diagnostics.packageRoute;
15
+ default:
16
+ break;
17
+ }
18
+ // Keep compatibility with the boolean-only response emitted by older
19
+ // Brokers, but never infer broker-installer from missing evidence.
20
+ return diagnostics?.isPackageOnly === true ? "package-only" : "unknown";
21
+ }
9
22
  const PROJECT_FOUR_FILES = ["synology-app.yaml", "README.md", "icon.png"];
10
23
  function packageFailureSummary(state) {
11
24
  const fields = [];
@@ -472,14 +485,44 @@ export async function runDoctor(options) {
472
485
  else {
473
486
  const info = await host.getAttestationInfo(targetPackageId);
474
487
  if (info.readiness === "NOT_INSTALLED") {
475
- const errorEntry = getErrorCode("PROJECT_BINDING_NOT_FOUND");
476
- checks.push({
477
- id: "host.package_binding",
478
- status: "fail",
479
- code: "PROJECT_BINDING_NOT_FOUND",
480
- message: `套件 ${targetPackageId} 在 Broker 中無有效 Project Binding`,
481
- suggestion: errorEntry?.suggestion ?? "請執行 synopkgland deploy 重新部署套件。",
482
- });
488
+ // Check the diagnosed route before treating a missing binding
489
+ // as a Broker-installer failure. Unknown is deliberately
490
+ // non-failing because the Broker may not be able to read the
491
+ // DSM package resource.
492
+ let packageRoute = "unknown";
493
+ if (host.getDsmDiagnostics) {
494
+ try {
495
+ const diag = await host.getDsmDiagnostics(targetPackageId);
496
+ packageRoute = packageRouteFromDiagnostics(diag);
497
+ }
498
+ catch {
499
+ // ignore
500
+ }
501
+ }
502
+ if (packageRoute === "package-only") {
503
+ checks.push({
504
+ id: "host.package_binding",
505
+ status: "skip",
506
+ message: "package-only 路線,無 Broker binding(略過)",
507
+ });
508
+ }
509
+ else if (packageRoute === "unknown") {
510
+ checks.push({
511
+ id: "host.package_binding",
512
+ status: "skip",
513
+ message: "Broker 無法判定套件路線,略過 Package Binding 檢查",
514
+ });
515
+ }
516
+ else {
517
+ const errorEntry = getErrorCode("PROJECT_BINDING_NOT_FOUND");
518
+ checks.push({
519
+ id: "host.package_binding",
520
+ status: "fail",
521
+ code: "PROJECT_BINDING_NOT_FOUND",
522
+ message: `套件 ${targetPackageId} 在 Broker 中無有效 Project Binding`,
523
+ suggestion: errorEntry?.suggestion ?? "請執行 synopkgland deploy 重新部署套件。",
524
+ });
525
+ }
483
526
  }
484
527
  else if (info.readiness === "PROJECT_BINDING_DRIFTED") {
485
528
  const errorEntry = getErrorCode("PROJECT_BINDING_DRIFTED");
@@ -116,6 +116,11 @@ export const ERROR_REGISTRY = {
116
116
  description: "Compose dynamic route 超出 D1/D2 白名單,或使用 DSM worker 不會載入的 secret/config 來源。",
117
117
  suggestion: "請只使用 string/boolean wizard answer 的 ${NAME} interpolation;secret/config 因 DSM worker 不讀 docker-compose.override.yml 而拒收。",
118
118
  },
119
+ COMPOSE_LEGACY_BROKER_STATE_PRESENT: {
120
+ code: "COMPOSE_LEGACY_BROKER_STATE_PRESENT",
121
+ description: "偵測到既有 Broker installer App Data,dynamic package-only route 不支援同一 Package Identity 的就地遷移。",
122
+ suggestion: "請改用新的 Package Identity 安裝,或先完整卸載舊套件並清除 App Data。",
123
+ },
119
124
  COMPOSE_BUILD_FORBIDDEN: {
120
125
  code: "COMPOSE_BUILD_FORBIDDEN",
121
126
  description: "D0 Compose route 禁止在 NAS install-time 使用 build:;build capability 永久拒收。",
@@ -1,4 +1,4 @@
1
- import type { AttestationKind, HostInstallRecord, NasHost } from "./types.js";
1
+ import type { AttestationKind, HostInstallRecord, NasHost, PackageRoute } from "./types.js";
2
2
  export interface FakeNasHostOptions {
3
3
  curated?: string[];
4
4
  dsmOrigin?: string;
@@ -8,6 +8,8 @@ export interface FakeNasHostOptions {
8
8
  installedEnv?: Record<string, Record<string, string>>;
9
9
  diagnostics?: {
10
10
  dsmResidue?: import("./types.js").DsmResidueInventory;
11
+ packageRoute?: PackageRoute;
12
+ isPackageOnly?: boolean;
11
13
  [key: string]: unknown;
12
14
  };
13
15
  }
@@ -1,5 +1,6 @@
1
1
  import type { InstalledPackageState } from "./catalog-install.js";
2
2
  export type AttestationKind = "local" | "app-package" | "none";
3
+ export type PackageRoute = "package-only" | "broker-installer" | "unknown";
3
4
  export interface DeployIdentity {
4
5
  packageId: string;
5
6
  displayname: string;
@@ -74,6 +75,8 @@ export interface NasHost {
74
75
  fetchContainerLogs(packageIdentity: string): Promise<string[]>;
75
76
  getDsmDiagnostics?(packageIdentity?: string): Promise<{
76
77
  dsmResidue?: DsmResidueInventory;
78
+ packageRoute?: PackageRoute;
79
+ isPackageOnly?: boolean;
77
80
  [key: string]: unknown;
78
81
  } | undefined>;
79
82
  smokeRequireLogin(input: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {