/** * The framework vocabulary and its bridge to the pattern vocabulary. * * Detection answers "what did the author build this with?" (`Framework`). * Creation asks "which shape does Fjall deploy?" (`PatternType`). Those are * different questions with different membership, and until this module nothing * joined them: `apps detect` reported `astro`, the agent skill told the agent to * feed what detect reported straight into `--pattern`, and `--pattern astro` * reached `PATTERN_REGISTRY[...]` as an unchecked index and threw a `TypeError` * that surfaced to the agent as `code: UNKNOWN`. Five of the seven framework * values had no pattern at all; the join existed only in the skill's prose. * * `FRAMEWORK_PATTERN_RECOMMENDATIONS` is the compiler's checklist for that * join. It is a `Record`, so an eighth * framework fails to compile until someone states what it means for * `--pattern` — the same discipline `PATTERN_REGISTRY` applies to patterns. * * The three verdicts are deliberately distinct, because they are three * different instructions to hand an agent mid-onboarding: * * - `recommended` — a deployable pattern fits; proceed with it. * - `blocked` — the pattern exists but cannot deploy yet; stop, and say why. * - `unsupported` — no pattern shape fits this framework; stop, and say so. * * `recommended` and `blocked` are separated at the *type* level rather than by * author discipline: their `pattern` fields are mapped types filtered on the * registry's own `deployable` flag, in the same idiom as `OpenNextPatternType`. * Flipping `nextjs` to `deployable: true` makes `UndeployablePatternType` * uninhabited and breaks the `blocked` entry, forcing it to be moved rather * than left behind as a lie. The reverse holds too: a pattern that loses its * construct cannot stay in `recommended`. */ import { PATTERN_REGISTRY, type PatternType, type PatternCreateInput, type DeployablePatternType } from "./patternTypes.js"; /** * Every framework the detector can name. * * This vocabulary lives here rather than in `@fjall/generator` because the * bridge below needs both halves in one place, and `generator` already depends * on `@fjall/util` (never the reverse). `generator/src/detection` re-exports * these names and builds the Zod schema over them, exactly as it already does * for `STATIC_SITE_ROUTING_VALUES`. * * - `nextjs+payload` is a strict superset of both its parts and must be * matched before either: Payload v3 ships as a Next.js plugin, so both * dependencies appear in a Payload app. * - `unknown` is a real member, not an absence — a repository with a parseable * `package.json` and no recognised signal is still a detection result. */ export declare const FRAMEWORK_VALUES: readonly ["nextjs", "payload", "nextjs+payload", "express", "remix", "astro", "unknown"]; export type Framework = (typeof FRAMEWORK_VALUES)[number]; export declare const FRAMEWORKS: ReadonlySet; export declare function isFramework(value: unknown): value is Framework; /** * The patterns the generator knows but cannot yet synthesise. Uninhabited once * every pattern is deployable — at which point the `blocked` verdict below * stops compiling, which is the intended signal, not a defect. */ export type UndeployablePatternType = { [K in PatternType]: (typeof PATTERN_REGISTRY)[K]["deployable"] extends false ? K : never; }[PatternType]; /** * What a detected framework means for `--pattern`. * * Every arm carries prose because every arm is read aloud to a user by an * agent. A verdict an agent cannot explain is a verdict it will second-guess, * and second-guessing is what the "do not infer the pattern" instruction in the * skill exists to prevent. */ export type PatternRecommendation = { readonly kind: "recommended"; /** Constrained to deployable patterns by the registry's own flag. */ readonly pattern: DeployablePatternType; /** Why this pattern fits, in the agent's own words to the user. */ readonly rationale: string; } | { readonly kind: "blocked"; /** Constrained to patterns the registry marks undeployable. */ readonly pattern: UndeployablePatternType; /** What is missing, so the agent can say more than "no". */ readonly reason: string; } | { readonly kind: "unsupported"; /** Why no pattern fits this framework's shape. */ readonly reason: string; }; /** * The framework → pattern join. * * This is the *evidence-free* verdict: it knows only the framework name. A * detector holding more evidence (an Astro config declaring `output: "server"`, * say) returns the same union with a different arm — that is a refinement of * one contract, not a second framing path. */ export declare const FRAMEWORK_PATTERN_RECOMMENDATIONS: { readonly astro: { readonly kind: "recommended"; readonly pattern: "staticsite"; readonly rationale: "Astro builds to a directory of static assets, which is what the static-site pattern serves from S3 behind CloudFront."; }; readonly payload: { readonly kind: "recommended"; readonly pattern: "payload"; readonly rationale: "Payload CMS runs on OpenNext with a database and migrations, which the payload pattern provisions."; }; readonly "nextjs+payload": { readonly kind: "recommended"; readonly pattern: "payload"; readonly rationale: "Payload v3 ships as a Next.js plugin, so a repository carrying both dependencies is a Payload application and deploys as one."; }; readonly nextjs: { readonly kind: "blocked"; readonly pattern: "nextjs"; readonly reason: "The Next.js pattern has no CDK construct yet — IPatternProps omits it, so a generated app fails at synthesis. Deploy a Payload application, or supply a static export with --pattern staticsite."; }; readonly remix: { readonly kind: "unsupported"; readonly reason: "Remix serves from a Node server at request time; Fjall has no server-rendering pattern beyond OpenNext, and no static-export path from Remix."; }; readonly express: { readonly kind: "unsupported"; readonly reason: "Express is a long-running HTTP server. Fjall's patterns cover OpenNext applications and pre-built static sites; neither shape fits."; }; readonly unknown: { readonly kind: "unsupported"; readonly reason: "No framework signal was found in package.json. If this is a pre-built static site, pass --pattern staticsite explicitly with --source, --build-command and --output-dir."; }; }; /** * The evidence-free verdict for a framework. Total by construction — there is * no undefined branch to guard, which is the whole point of the record above. */ export declare function recommendPatternForFramework(framework: Framework): PatternRecommendation; /** * The frameworks an agent can carry all the way to a deploy today, derived * from the table rather than restated. Used by the skill's vocabulary block so * its prose cannot drift from the join. */ export declare const CREATABLE_FRAMEWORKS: readonly Framework[]; /** * The inputs a framework's recommended pattern still needs from the caller. * Empty for anything not `recommended` — there is nothing to collect for a * pattern that cannot be created. */ export declare function requiredInputsForFramework(framework: Framework): readonly PatternCreateInput[];