@swell/cli
Version:
Swell's command line interface/utility
268 lines (267 loc) • 10.6 kB
TypeScript
import { GroupInfo } from './table.js';
/**
* Manifest entry shape, matching `extensions[]` in the deployed app record
* and in local `./swell.json`. Mirrors the schema at
* schema-api-server/api/admin/models/apps.json:429-491.
*/
export interface ManifestEntry {
id: string;
type: 'payment' | 'tax' | 'shipping';
name?: string;
description?: string;
setting?: string;
subscriptions?: boolean;
method?: string;
gateway?: string;
method_logo_src?: string;
method_icon_src?: string;
gateway_logo_src?: string;
gateway_icon_src?: string;
carrier?: string;
carrier_logo_src?: string;
carrier_icon_src?: string;
[]: unknown;
}
/** CLI-resolved type. Diverges from raw manifest `type` for payment (card vs alt). */
export type ExtensionType = 'card' | 'alt' | 'shipping' | 'tax';
export type ExtensionStatus = 'not deployed' | 'not activated' | 'app id mismatch' | 'id mismatch' | 'not selected' | 'gateway missing' | 'not enabled' | 'no handler' | 'handler mismatch' | 'activated';
export type ActionOwner = 'dev' | 'merchant' | null;
export interface FunctionRecord {
id?: string;
name?: string;
enabled?: boolean;
app_id?: string;
extension?: string;
model?: {
events?: string[];
};
}
export interface ComponentRecord {
id?: string;
name?: string;
file_path?: string;
values?: {
[]: unknown;
extension?: string;
};
[]: unknown;
}
export interface PaymentMethodRecord {
id?: string;
name?: string;
gateway?: string;
extension_app_id?: string;
extension_config_id?: string;
enabled?: boolean;
activated?: boolean;
[]: unknown;
}
export interface PaymentGatewayRecord {
id?: string;
name?: string;
extension_app_id?: string;
extension_config_id?: string;
[]: unknown;
}
export interface ShippingCarrierRecord {
id?: string;
name?: string;
enabled?: boolean;
extension_app_id?: string;
extension_config_id?: string;
[]: unknown;
}
export interface TaxSettingsRecord {
extension_app_id?: string;
extension_config_id?: string;
[]: unknown;
}
export interface PaymentSettings {
methods?: PaymentMethodRecord[];
gateways?: PaymentGatewayRecord[];
[]: unknown;
}
export interface ShippingSettings {
carriers?: ShippingCarrierRecord[];
[]: unknown;
}
export interface NativeBinding {
path: string;
record: unknown;
field_checks: Record<string, boolean>;
}
export interface BoundFunction {
id?: string;
name?: string;
extension?: string;
enabled?: boolean;
model?: {
events?: string[];
};
}
export interface BoundComponent {
id?: string;
name?: string;
extension?: string;
file_path?: string;
}
export interface LocalDiff {
changed_fields: string[];
local: ManifestEntry;
deployed: ManifestEntry;
}
export interface ExtensionDetail {
status: ExtensionStatus;
action_owner: ActionOwner;
action: string | null;
type: ExtensionType | null;
manifest: ManifestEntry | null;
native_bindings: NativeBinding[];
bound: {
components: BoundComponent[];
functions: BoundFunction[];
};
required_events: string[];
missing_required_events: string[];
local_diff: LocalDiff | null;
}
/** Parsed identifier for `swell inspect extensions <id>`. */
export type ParsedExtensionKey = {
appPart: string;
extId: string;
kind: 'slug';
} | {
kind: 'name';
name: string;
} | {
input: string;
kind: 'invalid';
};
/**
* Parse a `swell inspect extensions` identifier.
*
* Accepts:
* - `app.<slug>.<extId>` → kind 'slug'
* - bare `<extId>` → kind 'name' (requires --app= scope at the call site)
*
* 24-char hex is intentionally NOT a valid form — the synthesized resource has
* no canonical 24-char id. Callers should reject hex shapes upstream so the
* error message can be clear about the cause.
*/
export declare function parseExtensionKey(input: string): ParsedExtensionKey;
/** Build the column-1 paste-back key for a row. */
export declare function formatExtensionKey(slug: string, extId: string): string;
/**
* Resolve a manifest entry to the CLI's type taxonomy. Payment splits on
* `method`: when `method === 'card'` the extension provides a card gateway;
* otherwise it's an alt method. The platform formula at
* schema-api-server/api/admin/models/apps.json:457-458 defaults `method` to
* `id` when unset, so a payment extension whose `id` happens to be `card`
* resolves to `card` even with no explicit `method`.
*/
export declare function resolveExtensionType(entry: Pick<ManifestEntry, 'type' | 'method' | 'id'>): ExtensionType;
/** Apply the platform's `if(method, method, id)` formula. */
export declare function paymentMethodIdFor(entry: Pick<ManifestEntry, 'method' | 'id'>): string;
/** Shipping records use `app_<appId>_<extId>` as both the row id and the carrier id. */
export declare function shippingBindId(appId: string, extId: string): string;
/** Card-gateway records use the same id form as shipping rows. */
export declare function gatewayBindId(appId: string, extId: string): string;
/**
* Required events per extension type, in the `<model>/<event>` form that
* function records actually store.
*
* Verified against:
* schema-api-server/server/vault.js:1818,1894 (payment.create_intent)
* schema-api-server/api/com/features/payments/index.js:701-757
* (payment.charge, payment.refund, dispatcher gate)
* schema-api-server/server/vault.js:2304-2322 (card-gateway intent path)
* schema-api-server/api/com/features/orders/shipping.js:411-417,424-431,452-458
* (shipping dispatch + enabled gate)
* schema-api-server/api/com/features/orders/taxes.js:123-176
* (tax dispatch)
* schema-api-server/api/com/features/orders/extensions.test.js:85,95,179,271,290,303,671,704
* (event-name format fixtures)
*/
export declare function requiredEventsFor(type: ExtensionType): string[];
/**
* Strip `before:` / `after:` hook-type prefix from an event identifier.
*
* Both prefixes are platform-supported on function records; bare events
* default to `after`. For required-event coverage we treat any prefix as
* equivalent so that a function declaring `before:payment.charge` covers a
* required `payments/payment.charge`. Cite:
* schema-api-server/api/com/features/orders/extensions.test.js:95.
*/
export declare function stripHookPrefix(event: string): string;
/**
* Compute the set of bare event identifiers covered by a function record's
* `model.events` list. The function event format is `<model>/<event>` with
* an optional `before:`/`after:` prefix on the bare event part. Returns the
* full `<model>/<event>` string with hook prefixes stripped from the event.
*/
export declare function eventsCoveredByFunctions(functions: Pick<FunctionRecord, 'model'>[]): Set<string>;
/** Required events not covered by any of the bound functions. */
export declare function missingRequiredEvents(type: ExtensionType, functions: Pick<FunctionRecord, 'model'>[]): string[];
/** Lift a component record into the synthesized `BoundComponent` shape. */
export declare function liftBoundComponent(record: ComponentRecord): BoundComponent;
/** Lift a function record into the synthesized `BoundFunction` shape. */
export declare function liftBoundFunction(record: FunctionRecord): BoundFunction;
/**
* Compare a local manifest entry against the deployed entry. Uses deep
* equality for change detection and reports differing top-level keys. When
* the entries are equal, returns `null`.
*/
export declare function diffManifestEntries(local: ManifestEntry, deployed: ManifestEntry): LocalDiff | null;
export interface BuildDetailInput {
appId: string;
appSlug: string;
extId: string;
manifest: ManifestEntry | null;
/** True when the local manifest has the entry but the deployed app record doesn't. */
notDeployed?: boolean;
payments?: PaymentSettings | null;
shipments?: ShippingSettings | null;
taxes?: TaxSettingsRecord | null;
/** Functions in the same app whose top-level `extension` matches this extId. */
functions: FunctionRecord[];
/** Components in the same app whose `values.extension` matches this extId. */
components: ComponentRecord[];
localDiff?: LocalDiff | null;
}
export interface BuildOrphanInput {
appId: string;
appSlug: string;
/** The unresolved extension value taken from the function/component. */
unresolvedId: string;
functions: FunctionRecord[];
components: ComponentRecord[];
}
/** Build the synthesized envelope for a regular (non-orphan) extension row. */
export declare function buildExtensionDetail(input: BuildDetailInput): ExtensionDetail;
/** Build the synthesized envelope for an orphan row. */
export declare function buildOrphanDetail(input: BuildOrphanInput): ExtensionDetail;
/**
* List-mode meta string for one row. Returns `undefined` when the row has
* nothing surface-worthy beyond the type tag (which is always present).
*/
export declare function listMetaFor(detail: ExtensionDetail): string;
/**
* List-mode meta string for an orphan row. The type tag is replaced with
* the status word since there's no manifest to derive type from.
*/
export declare function orphanListMeta(fnCount: number, compCount: number): string;
/**
* `Next steps:` lines per status. Runnable shell commands come first,
* merchant-UI lines come second prefixed with `(merchant)` so an agent can
* filter. Empty array when there are no actionable next steps.
*/
export declare function nextStepLines(detail: ExtensionDetail): string[];
/**
* Section ordering for the list view. Apps render alphabetically; orphan
* rows land in a single trailing `<orphans>` group.
*/
export declare function listGroupForApp(slug: string): GroupInfo;
export declare function listGroupForOrphans(): GroupInfo;
/** True when a function/component "belongs to" an extension by direct match. */
export declare function functionMatchesExtension(fn: FunctionRecord, extId: string): boolean;
export declare function componentMatchesExtension(comp: ComponentRecord, extId: string): boolean;