/** * Starting a project from a stack's skeleton — no model involved. * * A stack is two projects kept in step by hand (see the drift check): the * EXAMPLE, and the SKELETON that is the same architecture with the * example's features removed. Bootstrapping is therefore not a * transformation to be derived, it is a COPY plus a rename: * * fetch stacks///skeleton -> copy -> take the new name * * The rename needs no manifest. The skeleton's own projectName is the * placeholder: every place the skeleton names itself — package * descriptions, app titles, localization catalogs, the devcontainer, the * mobile bundle id — is a place the new project must name itself. Finding * that token is the whole job. */ /** Where the stacks live. Overridable for forks and for testing. */ export declare const FARKETARI_REPO_URL = "https://github.com/redjolr/farketari"; /** The stack a project is started from, and which generation of it. */ export interface StackSelection { /** Directory name under stacks/, e.g. "nextjs-fastify". */ stack: string; /** Generation directory, e.g. "v1". */ version: string; /** Branch, tag or commit of the stacks repository; default "main". */ ref?: string; /** The repository holding the stacks; default FARKETARI_REPO_URL. */ repoUrl?: string; } /** * The generation of a stack a project starts from when none is named. * * There is deliberately no DEFAULT_STACK to match: which stack a project * is built on decides its whole architecture, so `bootstrap` requires * --stack rather than quietly picking one. */ export declare const DEFAULT_STACK_VERSION = "v1"; /** Which half of a stack a URL points at. */ export type StackPart = "example" | "skeleton"; /** The GitHub directory URL for one half of one stack version. */ export declare function stackDirectoryUrl(selection: StackSelection, part: StackPart): string; /** One literal-for-literal substitution applied across the new project. */ export interface IdentityToken { from: string; to: string; } /** * A name with every separator removed — the form reverse-DNS identifiers * need. Android package segments must be valid Java identifiers, so * "com.acme-banking.mobileapp" is not a legal package name while * "com.acmebanking.mobileapp" is. */ export declare function squashedName(name: string): string; /** * The substitutions that turn the skeleton into the new project. The * squashed pair comes FIRST: it is a substring of nothing else, and * applying the hyphenated form first would leave an invalid bundle id * behind. */ export declare function identityTokens(placeholder: string, projectName: string): IdentityToken[]; /** The skeleton names itself here; this is the placeholder to replace. */ export declare function readPlaceholderName(projectRoot: string): Promise; /** * Apply the substitutions across every text file under root. Returns the * repo-relative paths that changed, so the caller can report them. */ export declare function rewriteIdentity(root: string, tokens: readonly IdentityToken[]): Promise; /** True when nothing is listening and we may bind it ourselves. */ export declare function isPortFree(port: number): Promise; export interface PortChoiceOptions { /** Injectable so tests need no real sockets. */ isFree?: (port: number) => Promise; /** Injectable so tests are not at the mercy of the RNG. */ random?: () => number; } /** * A port for each base: random within its window, never reserved, and * proven free before it is claimed. Random rather than sequential because * sequential from a fixed base makes every project try the same numbers in * the same order — maximal contention, minimal spread. */ export declare function choosePorts(bases?: Readonly>, options?: PortChoiceOptions): Promise>; /** * Write the chosen host ports into the new project's global configuration. * Returns what was set, for the bootstrap report. */ export declare function assignProjectPorts(projectRoot: string, options?: PortChoiceOptions): Promise>; /** * Settings a new project must fill in itself, and what each one is for. * * The skeleton ships these EMPTY on purpose. A skeleton that carried the * template author's own GitLab host and group would hand every new project * a configuration that LOOKS complete and points somewhere else — the * failure that costs a day, because nothing errors, it just talks to the * wrong server. Empty is honest: the task that needs one says so. */ export declare const PROJECT_SETTINGS_TO_FILL: ReadonlyArray<{ key: string; what: string; }>; /** * Which of those a freshly bootstrapped project has not filled in yet. * Reported at the end of `bootstrap` so the blanks are seen once, at the * moment the project is created, rather than discovered later by a task * that quietly builds a URL out of an empty string. */ export declare function unsetProjectSettings(projectRoot: string): Promise>; export declare function runnerSetupSteps(projectRoot: string): Promise>; export declare function pipelineScheduleSteps(projectRoot: string): Promise>; /** * The stack's deployment contract, when it ships one: the pipeline's * release stages call dummy tasks the team replaces with its own * infrastructure (deployment/README.md). Reported so nobody discovers the * deploy jobs "succeeding" on main and believes something was deployed. */ export declare function deploymentContract(projectRoot: string): Promise; /** * The stack's secrets runbook, when it ships a sops-encrypted secrets tree * (secrets/README.md). Reported so the first developer creates their user * (`task secrets:create-user`) at bootstrap time — before the first token-gated * task fails for want of a devcontainer.env. The skeleton ships the LAYOUT * only, never ciphertext: a new project must never inherit the template * author's secrets, the same principle as the runner keys. */ export declare function secretsRunbook(projectRoot: string): Promise; /** * The stack's manual verification checklist, when it ships one * (VERIFICATION.md at the project root): the staged, run-these-in-order * checks that prove a fresh bootstrap works end to end. Reported as the * LAST step — everything else in the report feeds into it. */ export declare function verificationChecklist(projectRoot: string): Promise; /** * Point the new project's config at the stack version it came from, when * that is not already what the skeleton says. It usually IS already * right — the skeleton lives beside its example and names it — so this * only bites when a non-default ref or repository was used, where the * example must be pinned exactly as the skeleton was. */ export declare function alignExampleSource(projectRoot: string, exampleUrl: string): Promise; export interface StackBootstrapResult { /** Where the skeleton was fetched from. */ skeletonUrl: string; /** The stack's example, which the new project's config points at. */ exampleUrl: string; /** The name the skeleton called itself. */ placeholder: string; /** Files the rename touched. */ renamedFiles: string[]; /** Whether exampleProjectSource had to be repointed. */ repointedExample: boolean; /** Host ports moved off the skeleton's defaults, keyed by config field. */ ports: Record; /** Settings the new project still has to fill in before its CI tasks work. */ toFill: ReadonlyArray<{ key: string; what: string; }>; /** Runner machines to set up by hand, each with the README that explains how. */ runnerSetup: ReadonlyArray<{ name: string; readme: string; }>; /** The stack's staged verification checklist, when it ships one. */ verification: string | null; /** The stack's sops-secrets runbook, when it ships an encrypted secrets tree. */ secretsRunbook: string | null; /** The stack's deployment contract, when it ships one (dummy tasks the team replaces). */ deploymentContract: string | null; /** Pipeline schedules the project must create by hand in GitLab, one per schedule-gated job. */ pipelineSchedules: ReadonlyArray; } export interface PipelineScheduleStep { /** The CI variable the schedule must set to "true" for its job to run. */ variable: string; /** The CI include that carries the job, where the full reasoning lives. */ file: string; /** The stack's `# schedule:` hint — cadence and ordering — when the file declares one. */ hint: string | null; } /** * Create a new project from a stack's skeleton. The target must be an * absolute path that does not exist or is empty — bootstrapping never * writes into existing work. */ export declare function bootstrapFromStack(targetDir: string, projectName: string, selection: StackSelection, options?: { cacheRoot?: string; log?: (message: string) => void; }): Promise; /** True when a directory exists and holds anything. */ export declare function isNonEmptyDirectory(path: string): Promise;