UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

154 lines (139 loc) 4.66 kB
import { $inject, $state, Alepha, AlephaError } from "alepha"; import { Asker } from "alepha/command"; import { $logger } from "alepha/logger"; import { FileSystemProvider } from "alepha/system"; import { type EnvironmentConfig, platformOptions, } from "../atoms/platformOptions.ts"; import { NamingService, type Tenancy } from "./NamingService.ts"; export interface ResolvedPlatformConfig { project: string; defaultEnv: string; /** App tenancy mode (undefined ⇒ "none"). */ tenancy?: Tenancy; environments: Record<string, EnvironmentConfig>; } /** * Reads platform config and resolves project topology. * * Validates project name and environment configuration. Does NOT * introspect app code for resources — that happens at deploy time via * ViteBuildProvider. * * Each app self-declares its platform topology via its own * `alepha.config.ts`. Run `alepha platform <op>` from the app's * directory; no monorepo-root orchestration here. */ export class PlatformInspector { protected readonly log = $logger(); protected readonly alepha = $inject(Alepha); protected readonly fs = $inject(FileSystemProvider); protected readonly asker = $inject(Asker); protected readonly options = $state(platformOptions); protected readonly naming = $inject(NamingService); /** * Resolve and validate the full platform configuration. * * Source priority: * 1. `platformOptions` atom (set by `alepha.config.ts` during the * configure hook) — local dev / from-source deploys. * 2. `dist/manifest.json` (written by `alepha build`) — pre-built * deploys via Alepha Rocket or any `--prebuilt` consumer that * ships only the build artifact without `alepha.config.ts`. */ public async resolveConfig(root: string): Promise<ResolvedPlatformConfig> { if (this.options) { const opts = this.options; const project = await this.resolveProjectName(root, opts.name); return { project: this.naming.slugify(project), defaultEnv: opts.default ?? "production", tenancy: opts.tenancy, environments: opts.environments as Record<string, EnvironmentConfig>, }; } // Fallback: read dist/manifest.json. Lets prebuilt artifacts deploy // without shipping alepha.config.ts in the tarball. const manifest = await this.readManifest(root); if (manifest) { return { project: this.naming.slugify(manifest.project), defaultEnv: manifest.defaultEnv ?? "production", tenancy: manifest.tenancy, environments: manifest.environments as Record< string, EnvironmentConfig >, }; } this.log.warn(` alepha.config.ts not found or missing platform config. Please add a "platform" section to alepha.config.ts: export default defineConfig({ platform: { environments: { production: { adapter: "cloudflare" }, }, }, }); `); throw new AlephaError("Missing platform configuration."); } /** * Read `dist/manifest.json` if present. Returns null on any error so * callers fall back to the strict alepha.config.ts path. */ protected async readManifest(root: string): Promise<{ project: string; defaultEnv?: string; tenancy?: Tenancy; environments?: Record<string, unknown>; } | null> { try { const fs = await import("node:fs/promises"); const path = await import("node:path"); const raw = await fs.readFile( path.join(root, "dist", "manifest.json"), "utf-8", ); return JSON.parse(raw); } catch { return null; } } /** * Resolve a specific environment, validating it exists. */ public async resolveEnvironment( root: string, envName: string, ): Promise<EnvironmentConfig> { const config = await this.resolveConfig(root); const envConfig = config.environments[envName]; if (!envConfig) { const available = Object.keys(config.environments).join(", "); throw new AlephaError( `Unknown environment "${envName}". Available: ${available}`, ); } return envConfig; } protected async resolveProjectName( root: string, configName?: string, ): Promise<string> { if (configName) { return configName; } try { const pkgPath = this.fs.join(root, "package.json"); const pkg = await this.fs.readJsonFile<{ name?: string }>(pkgPath); if (pkg.name) { return pkg.name; } } catch {} throw new AlephaError( 'Missing project name. Set "name" in alepha.config.ts or add a "name" field to package.json.', ); } }