/** * The pattern vocabulary — single source of truth. * * A "pattern" is a whole-application shape (Payload CMS, a Next.js app, a * pre-built static site) that the generator emits, the CDK synthesises, * deploy-core builds and the CLI offers. Before this module the vocabulary was * declared four times — generator schemas, CDK interfaces, the deploy manifest * and the CLI create flow — each with a different membership, so adding a * pattern to one left the others silently wrong. * * `PATTERN_REGISTRY` is the compiler's checklist. It is a `Record`, so a new member of `PATTERN_TYPE_VALUES` fails to * compile until every dispatch decision — which stack, which builder, which * construct id — is stated. Dispatch sites read the descriptor instead of * re-deriving the answer from a ternary chain whose `else` branch would * otherwise absorb the new pattern. */ import type { PolicyResourceKind } from "../posturePolicy/resourceKinds.js"; export declare const PATTERN_TYPE_VALUES: readonly ["payload", "nextjs", "staticsite"]; export type PatternType = (typeof PATTERN_TYPE_VALUES)[number]; export declare const PATTERN_TYPES: ReadonlySet; export declare function isPatternType(value: unknown): value is PatternType; /** * What a pattern's build step produces. Selects the deploy-core builder: * `opennext-lambda` runs the OpenNext build and uploads a Lambda bundle; * `static-assets` runs the site's own build command and syncs a directory to S3. */ export type PatternArtefact = "opennext-lambda" | "static-assets"; /** * Which stack the pattern's constructs are placed into. A static site has no * VPC or compute, so it co-locates in the CDN stack; OpenNext patterns carry a * Lambda (and usually a database) and belong in the compute stack. */ export type PatternStackPlacement = "compute" | "cdn"; /** * Create-time inputs a pattern cannot invent for itself. * * A static site is built from someone else's repository, so Fjall cannot guess * where the sources live, how they build, or which directory to upload — get * `outputDir` wrong and the deploy succeeds onto an empty bucket. Declaring the * set here rather than in the create handler's control flow is what lets the * CLI refuse the command with a list of missing flags instead of surfacing a * raw Zod issue array, and lets `apps detect` prefill exactly the values the * chosen pattern will be asked for. */ export declare const PATTERN_CREATE_INPUT_VALUES: readonly ["source", "buildCommand", "outputDir"]; export type PatternCreateInput = (typeof PATTERN_CREATE_INPUT_VALUES)[number]; /** The CLI flag each input arrives on. Kept beside the vocabulary so error * messages name a flag the user can actually type. */ export declare const PATTERN_CREATE_INPUT_FLAGS: { readonly source: "--source"; readonly buildCommand: "--build-command"; readonly outputDir: "--output-dir"; }; export interface PatternDescriptor { /** Human-facing name, used by the CLI picker and progress output. */ readonly label: string; /** Suffix the generator appends to the PascalCase app name for the construct id. */ readonly constructIdSuffix: string; /** What the build produces — selects the deploy-core framework builder. */ readonly artefact: PatternArtefact; /** Stack the pattern's constructs are placed into. */ readonly stackPlacement: PatternStackPlacement; /** * Whether the pattern can deploy end-to-end. Runtime mirror of the * compile-time gap in `IPatternProps` (components/infrastructure): the CLI * cannot read that union, so it consults this flag to hide and refuse * patterns that would scaffold an app that fails at synth. Flip alongside * adding the construct. */ readonly deployable: boolean; /** * Inputs the caller must supply for this pattern; the create flow refuses * the command when any are absent. Empty for patterns whose scaffold is * generated wholesale and therefore knows its own layout. */ readonly requiredCreateInputs: readonly PatternCreateInput[]; /** * The CloudFormation resource types the pattern's construct synthesises, * at every tier it can be created at, projected onto the posture * registry's vocabulary (`POLICY_RESOURCE_KINDS`): the footprint the * create surfaces hand to `expectedRowsAtDefault`, so a pattern is told * only about the findings its own resources produce. Pinned to the * synthesised templates by the constructs' pattern-footprint test. */ readonly synthesises: readonly PolicyResourceKind[]; } export declare const PATTERN_REGISTRY: { readonly payload: { readonly label: "Payload CMS"; readonly constructIdSuffix: "Payload"; readonly artefact: "opennext-lambda"; readonly stackPlacement: "compute"; readonly deployable: true; readonly requiredCreateInputs: readonly []; readonly synthesises: readonly ["AWS::S3::Bucket", "AWS::KMS::Key"]; }; readonly nextjs: { readonly label: "Next.js"; readonly constructIdSuffix: "Nextjs"; readonly artefact: "opennext-lambda"; readonly stackPlacement: "compute"; readonly deployable: false; readonly requiredCreateInputs: readonly []; readonly synthesises: readonly ["AWS::S3::Bucket", "AWS::KMS::Key"]; }; readonly staticsite: { readonly label: "Static site"; readonly constructIdSuffix: "StaticSite"; readonly artefact: "static-assets"; readonly stackPlacement: "cdn"; readonly deployable: true; readonly requiredCreateInputs: readonly ["source", "buildCommand", "outputDir"]; readonly synthesises: readonly ["AWS::S3::Bucket"]; }; }; /** * The construct id a pattern statement's `XFactory.build` FIRST ARGUMENT * carries: `${toPascalCase(name)}${constructIdSuffix}`. * * This is a coupled emit/locate identity contract with three consumers — * the generator scaffold's pattern emission, the codemod's * `statementIdentity.emitConstructId`, and its `locateCandidates` read half — * and it lives here, beside `PATTERN_REGISTRY`, so no consumer re-derives * the composition. A drifted re-implementation stays green through every * side's own tests and only fails at the seam: scaffold-emitted statements * stop being addressable by `fjall modify/remove pattern`, and duplicate * detection silently re-admits the synth-fatal id collision. */ export declare function patternConstructId(name: string, type: PatternType): string; /** * The buckets an OpenNext pattern's construct builds, keyed as the pattern * statement's `storage` config spells them (`storage.media.versioned`): the * build output, the ISR cache and the uploads. The constructs' props type * and the generator's config schema are both pinned to this tuple, so a * bucket the construct starts building appears in the config vocabulary * before either can compile without it. */ export declare const OPENNEXT_BUCKET_KEYS: readonly ["assets", "cache", "media"]; export type OpenNextBucketKey = (typeof OPENNEXT_BUCKET_KEYS)[number]; export declare function isOpenNextBucketKey(value: unknown): value is OpenNextBucketKey; /** * The construct id of one bucket an OpenNext pattern builds — the FIRST * ARGUMENT of the `StorageFactory.build` call the construct makes for it: * the pattern statement's `name` prop (the workload name the engine lists * as `resourceName`), a hyphen, the bucket key. The bucket's CloudFormation * logical id is derived from this id, so a consumer matching a logical id * back to the statement that owns the bucket derives the same string here * rather than re-composing it; a drifted re-composition agrees for every * kebab-case name and diverges the first time a name carries a case * boundary the id derivation folds. */ export declare function patternBucketConstructId(name: string, bucket: OpenNextBucketKey): string; /** * The patterns that can deploy end-to-end, derived from the registry's own * flag at the type level. `DEPLOYABLE_PATTERN_TYPES` is the value-level twin. */ export type DeployablePatternType = { [K in PatternType]: (typeof PATTERN_REGISTRY)[K]["deployable"] extends true ? K : never; }[PatternType]; /** * Patterns the toolchain may offer for creation, derived from the registry — * pickers and validation reject the rest at create time instead of letting a * scaffold fail at first deploy. Typed to the deployable subset so an enum * built from it (`z.enum(DEPLOYABLE_PATTERN_TYPES)`) infers exactly the * members it accepts at runtime. */ export declare const DEPLOYABLE_PATTERN_TYPES: readonly DeployablePatternType[]; /** * Tier presets a pattern may be created with — the ONE spelling the generator * (`PatternTierSchema`), the CLI's SKILL vocabulary, the MCP protocol's * `ResolvedInputsSchema` and the MCP `plan_app_scaffold` input derive from. * Not the infrastructure tiers (`TIER_NAMES`: tinkerer … enterprise), which * govern `fjall add … --tier` — the two vocabularies were conflated on the MCP * surface until Train 4 of the 2026-09-07 hardening prompt. */ export declare const PATTERN_TIER_VALUES: readonly ["lightweight", "standard", "resilient", "custom"]; export type PatternTier = (typeof PATTERN_TIER_VALUES)[number]; /** * The OpenNext-shaped patterns, derived from the registry at both the type and * the value level — a new OpenNext pattern joins this accept-set by declaring * its artefact, not by being remembered in a second list. (`as const satisfies` * above is what keeps the literal `artefact` types the mapped type reads.) */ export type OpenNextPatternType = { [K in PatternType]: (typeof PATTERN_REGISTRY)[K]["artefact"] extends "opennext-lambda" ? K : never; }[PatternType]; export declare const OPENNEXT_PATTERN_TYPES: readonly OpenNextPatternType[]; export declare function isOpenNextPatternType(value: string | undefined | null): value is OpenNextPatternType; /** * Routing mode for a static site. Shared with the CDN construct and the * generator's `StaticSiteRoutingSchema`, both of which derive from this tuple — * a mode must not be addable to one without the other. * * The first two modes answer the same question — which object does a pretty * URL serve? — for the two layouts a static build emits: `multipage` for * builds that write `about.html` (Astro `build.format: "file"`, Next.js * default export), `directory` for builds that write `about/index.html` * (Astro's default `build.format: "directory"`). Both serve BOTH link shapes * (`/about` and `/about/`) from their layout's object. A single hybrid mode * used to rewrite trailing-slash requests one way and bare requests the * other, which gave every site exactly one working link shape and one broken * one. * * `spa` opts out of rewriting entirely: unknown paths fall back to * `/index.html` for client-side routing. */ export declare const STATIC_SITE_ROUTING_VALUES: readonly ["multipage", "directory", "spa"]; export type StaticSiteRouting = (typeof STATIC_SITE_ROUTING_VALUES)[number]; /** * Human-facing vocabulary for each routing mode, beside the tuple that * governs it (the same shape as `PATTERN_REGISTRY`'s `label`). The CLI * picker, the `--routing` flag help and the agent SKILL all render from * here, so a mode added to the tuple fails to compile until it states what * it serves — and no surface can drift onto its own wording of what a mode * means. */ export interface StaticSiteRoutingDescriptor { /** Short name for pickers (the Ink `Select` label). */ readonly label: string; /** One-line behaviour summary, phrased as what a request serves. */ readonly summary: string; } export declare const STATIC_SITE_ROUTING_INFO: { readonly multipage: { readonly label: "Multi-page (.html files)"; readonly summary: "/about serves /about.html"; }; readonly directory: { readonly label: "Directory index"; readonly summary: "/about serves /about/index.html"; }; readonly spa: { readonly label: "Single-page app"; readonly summary: "unknown paths fall back to /index.html"; }; }; /** * CloudFront price-class vocabulary — shared by the CDN construct's props * (`components/infrastructure`) and the generator's static-site schema, which * must accept exactly what the construct accepts and cannot import it * (generator depends on util only). The values are the CloudFront API's own * literals, resolved to `aws-cloudfront.PriceClass` members by the construct. * * Coverage, for choosing: `PriceClass_100` = NA + Europe (+ Israel) only — * no Oceania/Asia/South-America POPs; `PriceClass_200` adds Asia, but still * no Australia/NZ; `PriceClass_All` is every edge location. At small-site * traffic the price difference is ~zero, so for most sites this is a latency * knob, not a cost knob. */ export declare const CLOUDFRONT_PRICE_CLASS_VALUES: readonly ["PriceClass_100", "PriceClass_200", "PriceClass_All"]; export type CloudFrontPriceClass = (typeof CLOUDFRONT_PRICE_CLASS_VALUES)[number]; /** * The static-site pattern config's key manifest — the field-parity contract. * * Every key of `IStaticSiteProps` (components/infrastructure) appears here, * and every surface that enumerates the config's fields is held to this list: * * - `IStaticSiteProps` itself — compile-time witness beside the interface; * - the generator's `StaticSitePatternConfigSchema` (Zod, `.strict()`); * - the generator's AST parser (`extractStaticSiteConfig` + * `collectUnreadableStaticSiteFields` + `applyPatternConfig`); * - the generator's emitter (`emitStaticSiteFields`) — guarded together with * the parser by a full-config parse→plan→emit→re-parse round-trip test. * * Before this manifest those surfaces agreed only by discipline, and had * already drifted: `zoneName`/`hostedZoneId` were interface-only, so a * codemod round-trip silently erased them from a user's infrastructure.ts. * Add a field to the interface without teaching the other surfaces and the * witness/tests name the surface that is missing it. */ export declare const STATIC_SITE_CONFIG_KEYS: readonly ["type", "name", "source", "build", "routing", "security", "domain", "managedDomain", "zoneName", "hostedZoneId", "forms", "cdn", "accessGate"]; export type StaticSiteConfigKey = (typeof STATIC_SITE_CONFIG_KEYS)[number]; /** * Keys of {@link STATIC_SITE_CONFIG_KEYS} that are DEPLOY-INJECTED, never * authored: the CLI resolves a managed-domain binding at deploy time and * carries it into synth via CDK context (`managedDomainBindings`), so the * generator's schema and parser must NOT accept them — a user writing * `managedDomain:` by hand in infrastructure.ts is outside the authored * surface, and the parity tests exclude these keys rather than teaching the * schema to admit them. */ export declare const STATIC_SITE_DEPLOY_INJECTED_KEYS: readonly ["managedDomain"]; export type StaticSiteDeployInjectedKey = (typeof STATIC_SITE_DEPLOY_INJECTED_KEYS)[number]; /** * Key manifest for the static-site `cdn` sub-config — same parity contract as * {@link STATIC_SITE_CONFIG_KEYS}, one level down (the sub-object is * enumerated field-by-field on the same surfaces). */ export declare const STATIC_SITE_CDN_CONFIG_KEYS: readonly ["behaviours", "priceClass", "invalidateOnDeploy"]; export type StaticSiteCdnConfigKey = (typeof STATIC_SITE_CDN_CONFIG_KEYS)[number];