From f1aeaf363e95815f3cecdbe0b5c79ddf90a6d76e Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Fri, 3 Jul 2026 10:35:30 +0200 Subject: [PATCH] feat(deploy): expand DeployTargetPort to the canonical 7-op contract (S0) Additively expand the deploy target port to the Archetype 7 canonical 7-op lifecycle contract, the rebase point for the sibling cloud-adapter slices (#342 Deno Deploy, #343 Aspire) which consume this port. - Add DeployOperation (plan/emit/up/down/status/logs/rollback/secrets) and keep the legacy build/install/uninstall verbs as LegacyDeployOperation aliases; DeployTargetOperation is their union. - Add optional 7-op method signatures on DeployTargetPort via a shared DeployTargetOperationHandler; retain the legacy build/install/uninstall methods so the shipped WindowsServiceDeployTarget stub and command-registry_test stay green untouched (stub evolves at S8). - Reserve the linux-service registry key at the type level via KnownDeployTargetKey (no adapter registered yet; that is S8). - Export the new contract types from extension-points for sibling slices. - rollback/secrets are declared present-but-deferred (bodies land in #341). Pure type-level + registry-shape; no bare-metal bodies, no OsServicePort, no config. Independently mergeable. Refs #339 #340 #327 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_012wKHquACkXnWPDgJYhhFjN --- .../domain/deploy/deploy-target-port.ts | 77 +++++++++++++++++-- .../domain/deploy/deploy-target-port_test.ts | 59 ++++++++++++++ .../deploy/deploy-target-registry-port.ts | 11 +++ packages/cli/src/kernel/extension-points.ts | 8 +- 4 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 packages/cli/src/kernel/domain/deploy/deploy-target-port_test.ts diff --git a/packages/cli/src/kernel/domain/deploy/deploy-target-port.ts b/packages/cli/src/kernel/domain/deploy/deploy-target-port.ts index 1d5792a7f..82d093639 100644 --- a/packages/cli/src/kernel/domain/deploy/deploy-target-port.ts +++ b/packages/cli/src/kernel/domain/deploy/deploy-target-port.ts @@ -1,5 +1,35 @@ +/** + * Canonical deploy lifecycle operations (Archetype 7 — the uniform 7-op + * contract every deploy target adapter conforms to). + * + * A target adapter implements only the subset it supports. The legacy CLI verbs + * map onto the canonical ops as: `build` → `plan`/`emit`, `install` → `up`, + * `uninstall` → `down`. `status`, `logs`, `rollback`, and `secrets` are net-new + * lifecycle ops the legacy 3-op seed did not have. `rollback`/`secrets` bodies + * land with the deployment hardening slice (#341); until then adapters may + * declare them unsupported (omit the method) rather than provide a silent no-op. + */ +export type DeployOperation = + | 'plan' + | 'emit' + | 'up' + | 'down' + | 'status' + | 'logs' + | 'rollback' + | 'secrets'; + +/** + * Legacy CLI deploy verbs retained as thin-router aliases of the canonical + * {@link DeployOperation} set (`build` → `plan`/`emit`, `install` → `up`, + * `uninstall` → `down`). Kept alongside the canonical names so existing adapters + * and the CLI verb surface stay stable; new adapters should prefer the canonical + * operation names. + */ +export type LegacyDeployOperation = 'build' | 'install' | 'uninstall'; + /** Public deploy operation exposed by a deploy target adapter. */ -export type DeployTargetOperation = 'build' | 'install' | 'uninstall'; +export type DeployTargetOperation = DeployOperation | LegacyDeployOperation; /** Request passed to deploy target operations. */ export interface DeployTargetRequest { @@ -19,7 +49,20 @@ export interface DeployTargetResult { readonly message: string; } -/** Deploy target adapter surface used by CLI extension registries. */ +/** Signature shared by every deploy target lifecycle operation. */ +export type DeployTargetOperationHandler = ( + request: DeployTargetRequest, +) => Promise; + +/** + * Deploy target adapter surface used by CLI extension registries. + * + * Every operation method is optional: an adapter declares the subset of the + * canonical {@link DeployOperation} contract it supports (mirrored by + * {@link DeployTargetPort.operations}). The legacy `build`/`install`/`uninstall` + * aliases are retained so the shipped seed and CLI verbs keep working while the + * bare-metal and cloud adapters adopt the canonical op names. + */ export interface DeployTargetPort { /** Stable target identifier. */ readonly key: string; @@ -27,10 +70,28 @@ export interface DeployTargetPort { readonly label: string; /** Supported public deploy operations for the target. */ readonly operations: readonly DeployTargetOperation[]; - /** Build deployment assets for this target. */ - readonly build?: (request: DeployTargetRequest) => Promise; - /** Install deployment assets for this target. */ - readonly install?: (request: DeployTargetRequest) => Promise; - /** Uninstall deployment assets for this target. */ - readonly uninstall?: (request: DeployTargetRequest) => Promise; + + /** Compute the deployment plan/artifact spec for this target. */ + readonly plan?: DeployTargetOperationHandler; + /** Emit deployment artifacts for this target. */ + readonly emit?: DeployTargetOperationHandler; + /** Bring the deployment up (install + enable + start). */ + readonly up?: DeployTargetOperationHandler; + /** Bring the deployment down (stop + disable + uninstall). */ + readonly down?: DeployTargetOperationHandler; + /** Report the current deployment status for this target. */ + readonly status?: DeployTargetOperationHandler; + /** Stream or tail deployment logs for this target. */ + readonly logs?: DeployTargetOperationHandler; + /** Roll the deployment back to a previous revision (bodies → #341). */ + readonly rollback?: DeployTargetOperationHandler; + /** Reconcile deployment secrets for this target (bodies → #341). */ + readonly secrets?: DeployTargetOperationHandler; + + /** Legacy alias of `plan`/`emit`: build deployment assets for this target. */ + readonly build?: DeployTargetOperationHandler; + /** Legacy alias of `up`: install deployment assets for this target. */ + readonly install?: DeployTargetOperationHandler; + /** Legacy alias of `down`: uninstall deployment assets for this target. */ + readonly uninstall?: DeployTargetOperationHandler; } diff --git a/packages/cli/src/kernel/domain/deploy/deploy-target-port_test.ts b/packages/cli/src/kernel/domain/deploy/deploy-target-port_test.ts new file mode 100644 index 000000000..1bd60ab9e --- /dev/null +++ b/packages/cli/src/kernel/domain/deploy/deploy-target-port_test.ts @@ -0,0 +1,59 @@ +import { assertEquals } from 'jsr:@std/assert@^1'; + +import type { DeployTargetOperation, DeployTargetPort } from './deploy-target-port.ts'; +import type { KnownDeployTargetKey } from './deploy-target-registry-port.ts'; +import { DeployTargetRegistry } from '../../application/registries/deploy-target-registry.ts'; + +Deno.test('deploy target contract exposes the canonical 7-op names', () => { + const canonical: readonly DeployTargetOperation[] = [ + 'plan', + 'emit', + 'up', + 'down', + 'status', + 'logs', + 'rollback', + 'secrets', + ]; + + assertEquals(canonical, ['plan', 'emit', 'up', 'down', 'status', 'logs', 'rollback', 'secrets']); +}); + +Deno.test('deploy target contract retains the legacy build/install/uninstall verb aliases', () => { + const legacy: readonly DeployTargetOperation[] = ['build', 'install', 'uninstall']; + + assertEquals(legacy, ['build', 'install', 'uninstall']); +}); + +Deno.test('deploy target port accepts an adapter that implements only the canonical subset', () => { + const target: DeployTargetPort = { + key: 'linux-service', + label: 'Linux service', + operations: ['plan', 'up', 'down', 'status', 'logs'], + up: (request) => + Promise.resolve({ + target: 'linux-service', + operation: 'up', + message: `up for ${request.projectRoot}`, + }), + }; + + assertEquals(target.operations.includes('up'), true); + assertEquals(typeof target.up, 'function'); + assertEquals(target.rollback, undefined); +}); + +Deno.test('deploy target registry reserves the linux-service key at the type level', () => { + const reserved: KnownDeployTargetKey = 'linux-service'; + const registry = new DeployTargetRegistry([]); + const linuxTarget: DeployTargetPort = { + key: reserved, + label: 'Linux service', + operations: ['up', 'down'], + }; + + registry.register(reserved, linuxTarget); + + assertEquals(registry.get(reserved)?.key, 'linux-service'); + assertEquals(registry.entries().map(([key]) => key), ['linux-service']); +}); diff --git a/packages/cli/src/kernel/domain/deploy/deploy-target-registry-port.ts b/packages/cli/src/kernel/domain/deploy/deploy-target-registry-port.ts index e16fc26d8..2353659e6 100644 --- a/packages/cli/src/kernel/domain/deploy/deploy-target-registry-port.ts +++ b/packages/cli/src/kernel/domain/deploy/deploy-target-registry-port.ts @@ -1,5 +1,16 @@ import type { DeployTargetPort } from './deploy-target-port.ts'; +/** + * Well-known deploy target keys reserved by the framework registry shape. + * + * `windows-service` ships today; `linux-service` is reserved for the bare-metal + * Linux (systemd) adapter that is registered at the bare-metal realization slice + * (#339). Adapters may still register under any string key — this union + * documents the first-party reservations so sibling deployment slices agree on + * the canonical keys without registering an adapter here. + */ +export type KnownDeployTargetKey = 'windows-service' | 'linux-service'; + /** Registry surface for deploy target adapters. */ export interface DeployTargetRegistryPort { /** Register or replace a deploy target adapter. */ diff --git a/packages/cli/src/kernel/extension-points.ts b/packages/cli/src/kernel/extension-points.ts index de44fcd58..bd8af7b08 100644 --- a/packages/cli/src/kernel/extension-points.ts +++ b/packages/cli/src/kernel/extension-points.ts @@ -23,9 +23,15 @@ export { PresetRegistry } from './application/registries/preset-registry.ts'; /** Deployment target descriptors supported by deploy commands. */ export { DeployTargetRegistry } from './application/registries/deploy-target-registry.ts'; export type { + DeployOperation, DeployTargetOperation, + DeployTargetOperationHandler, DeployTargetPort, DeployTargetRequest, DeployTargetResult, + LegacyDeployOperation, } from './domain/deploy/deploy-target-port.ts'; -export type { DeployTargetRegistryPort } from './domain/deploy/deploy-target-registry-port.ts'; +export type { + DeployTargetRegistryPort, + KnownDeployTargetKey, +} from './domain/deploy/deploy-target-registry-port.ts';