@flight-framework/cli 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -8,7 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/index.ts
10
10
  import { cac } from "cac";
11
- import pc6 from "picocolors";
11
+ import pc7 from "picocolors";
12
12
 
13
13
  // src/version.ts
14
14
  var VERSION = "0.0.1";
@@ -1832,18 +1832,471 @@ ${pc5.cyan("Quick example:")}
1832
1832
  console.log(`${pc5.dim("Docs:")} https://flight.dev/docs/packages/${packageName}`);
1833
1833
  }
1834
1834
 
1835
+ // src/commands/adapter-create.ts
1836
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
1837
+ import { join as join6 } from "path";
1838
+ import pc6 from "picocolors";
1839
+ async function adapterCreateCommand(name, options = {}) {
1840
+ const cwd = process.cwd();
1841
+ const adapterName = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1842
+ const packageName = `@flight-framework/adapter-${adapterName}`;
1843
+ const outputDir = options.outputDir || join6(cwd, "packages", `adapter-${adapterName}`);
1844
+ const type = options.type || "node";
1845
+ const includeValidation = options.validation || false;
1846
+ console.log(pc6.cyan(`
1847
+ Creating adapter: ${packageName}
1848
+ `));
1849
+ if (existsSync7(outputDir)) {
1850
+ console.error(pc6.red(` Error: Directory already exists: ${outputDir}`));
1851
+ process.exit(1);
1852
+ }
1853
+ mkdirSync4(join6(outputDir, "src"), { recursive: true });
1854
+ mkdirSync4(join6(outputDir, "tests"), { recursive: true });
1855
+ const files = generateAdapterFiles(adapterName, packageName, type, includeValidation);
1856
+ for (const [filePath, content] of Object.entries(files)) {
1857
+ const fullPath = join6(outputDir, filePath);
1858
+ writeFileSync5(fullPath, content, "utf-8");
1859
+ console.log(pc6.green(` \u2713 Created ${filePath}`));
1860
+ }
1861
+ console.log(pc6.cyan(`
1862
+ Adapter created successfully!
1863
+
1864
+ Next steps:
1865
+ 1. cd packages/adapter-${adapterName}
1866
+ 2. Implement your adapter logic in src/index.ts
1867
+ 3. Run: pnpm build
1868
+ 4. Test: pnpm test
1869
+
1870
+ Documentation:
1871
+ https://flight.dev/docs/adapters/custom
1872
+ `));
1873
+ }
1874
+ function generateAdapterFiles(name, packageName, type, includeValidation) {
1875
+ const pascalName = name.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
1876
+ return {
1877
+ "package.json": generatePackageJson(packageName, includeValidation),
1878
+ "tsconfig.json": generateTsConfig(),
1879
+ "tsup.config.ts": generateTsupConfig(),
1880
+ "src/index.ts": generateAdapterSource(name, pascalName, type, includeValidation),
1881
+ "tests/adapter.test.ts": generateAdapterTest(name, pascalName),
1882
+ "README.md": generateReadme(name, packageName)
1883
+ };
1884
+ }
1885
+ function generatePackageJson(packageName, includeValidation) {
1886
+ const pkg = {
1887
+ name: packageName,
1888
+ version: "0.1.0",
1889
+ description: `Flight adapter for custom platform`,
1890
+ type: "module",
1891
+ main: "./dist/index.js",
1892
+ types: "./dist/index.d.ts",
1893
+ exports: {
1894
+ ".": {
1895
+ types: "./dist/index.d.ts",
1896
+ import: "./dist/index.js"
1897
+ }
1898
+ },
1899
+ scripts: {
1900
+ build: "tsup",
1901
+ test: "vitest run",
1902
+ "test:watch": "vitest"
1903
+ },
1904
+ dependencies: {
1905
+ "@flight-framework/core": "^0.4.0"
1906
+ },
1907
+ devDependencies: {
1908
+ tsup: "^8.0.0",
1909
+ typescript: "^5.0.0",
1910
+ vitest: "^2.0.0",
1911
+ ...includeValidation ? { zod: "^3.23.0" } : {}
1912
+ },
1913
+ peerDependencies: includeValidation ? {
1914
+ zod: "^3.0.0"
1915
+ } : void 0,
1916
+ peerDependenciesMeta: includeValidation ? {
1917
+ zod: { optional: true }
1918
+ } : void 0
1919
+ };
1920
+ return JSON.stringify(pkg, null, 2);
1921
+ }
1922
+ function generateTsConfig() {
1923
+ return JSON.stringify({
1924
+ extends: "../../tsconfig.base.json",
1925
+ compilerOptions: {
1926
+ outDir: "./dist",
1927
+ rootDir: "./src"
1928
+ },
1929
+ include: ["src"]
1930
+ }, null, 2);
1931
+ }
1932
+ function generateTsupConfig() {
1933
+ return `import { defineConfig } from 'tsup';
1934
+
1935
+ export default defineConfig({
1936
+ entry: ['src/index.ts'],
1937
+ format: ['esm'],
1938
+ dts: true,
1939
+ clean: true,
1940
+ target: 'node20',
1941
+ });
1942
+ `;
1943
+ }
1944
+ function generateAdapterSource(name, pascalName, type, includeValidation) {
1945
+ if (includeValidation) {
1946
+ return `/**
1947
+ * Flight Adapter - ${pascalName}
1948
+ *
1949
+ * Custom deployment adapter with optional Zod validation.
1950
+ */
1951
+
1952
+ import { createValidatedAdapter, type AdapterBuilder } from '@flight-framework/core/adapters';
1953
+ import { z } from 'zod';
1954
+
1955
+ // ============================================================================
1956
+ // Options Schema (Optional - Zod validation)
1957
+ // ============================================================================
1958
+
1959
+ const optionsSchema = z.object({
1960
+ /** Server port */
1961
+ port: z.number().default(3000),
1962
+ /** Enable health check endpoint */
1963
+ healthCheck: z.boolean().default(true),
1964
+ /** Custom environment variables */
1965
+ env: z.record(z.string()).optional(),
1966
+ });
1967
+
1968
+ export type ${pascalName}AdapterOptions = z.infer<typeof optionsSchema>;
1969
+
1970
+ // ============================================================================
1971
+ // Adapter Implementation
1972
+ // ============================================================================
1973
+
1974
+ export default createValidatedAdapter('${name}', optionsSchema, (options) => ({
1975
+ async adapt(builder: AdapterBuilder) {
1976
+ builder.log.info(\`Building for ${pascalName} platform...\`);
1977
+ builder.log.info(\`Port: \${options.port}\`);
1978
+
1979
+ // Generate server entry
1980
+ await generateServerEntry(builder, options);
1981
+
1982
+ // Generate platform-specific config
1983
+ await generatePlatformConfig(builder, options);
1984
+
1985
+ builder.log.info('Build complete!');
1986
+ },
1987
+
1988
+ supports: {
1989
+ node: () => ${type === "node" || type === "container"},
1990
+ edge: () => ${type === "edge" || type === "serverless"},
1991
+ streaming: () => ${type === "node"},
1992
+ websockets: () => ${type === "node"},
1993
+ },
1994
+
1995
+ emulate: () => ({
1996
+ env: {
1997
+ ${name.toUpperCase().replace(/-/g, "_")}_PLATFORM: 'true',
1998
+ PORT: String(options.port),
1999
+ },
2000
+ }),
2001
+ }));
2002
+
2003
+ // ============================================================================
2004
+ // Generator Functions
2005
+ // ============================================================================
2006
+
2007
+ async function generateServerEntry(builder: AdapterBuilder, options: ${pascalName}AdapterOptions): Promise<void> {
2008
+ const serverCode = \`
2009
+ import { createUniversalHandler } from '@flight-framework/core/adapters';
2010
+ import manifest from './manifest.js';
2011
+
2012
+ const handler = createUniversalHandler(manifest);
2013
+
2014
+ const server = Bun.serve({
2015
+ port: \${options.port},
2016
+ fetch: handler,
2017
+ });
2018
+
2019
+ console.log(\\\`Server running at http://localhost:\${options.port}\\\`);
2020
+ \`;
2021
+
2022
+ await builder.writeFile('server.ts', serverCode.trim());
2023
+ }
2024
+
2025
+ async function generatePlatformConfig(builder: AdapterBuilder, options: ${pascalName}AdapterOptions): Promise<void> {
2026
+ const config = {
2027
+ name: '${name}-app',
2028
+ port: options.port,
2029
+ healthCheck: options.healthCheck ? '/health' : null,
2030
+ };
2031
+
2032
+ await builder.writeFile('platform.json', JSON.stringify(config, null, 2));
2033
+ }
2034
+ `;
2035
+ }
2036
+ return `/**
2037
+ * Flight Adapter - ${pascalName}
2038
+ *
2039
+ * Custom deployment adapter for ${pascalName} platform.
2040
+ *
2041
+ * @example
2042
+ * \`\`\`typescript
2043
+ * import ${name.replace(/-/g, "")} from '${name.includes("@") ? name : `@flight-framework/adapter-${name}`}';
2044
+ *
2045
+ * export default defineConfig({
2046
+ * adapter: ${name.replace(/-/g, "")}({
2047
+ * port: 3000,
2048
+ * }),
2049
+ * });
2050
+ * \`\`\`
2051
+ */
2052
+
2053
+ import { createAdapter, type AdapterBuilder } from '@flight-framework/core/adapters';
2054
+
2055
+ // ============================================================================
2056
+ // Options Interface
2057
+ // ============================================================================
2058
+
2059
+ export interface ${pascalName}AdapterOptions {
2060
+ /** Server port (default: 3000) */
2061
+ port?: number;
2062
+ /** Enable health check endpoint (default: true) */
2063
+ healthCheck?: boolean;
2064
+ /** Custom environment variables */
2065
+ env?: Record<string, string>;
2066
+ }
2067
+
2068
+ // ============================================================================
2069
+ // Adapter Implementation
2070
+ // ============================================================================
2071
+
2072
+ export default function ${name.replace(/-/g, "")}Adapter(options: ${pascalName}AdapterOptions = {}) {
2073
+ const {
2074
+ port = 3000,
2075
+ healthCheck = true,
2076
+ env = {},
2077
+ } = options;
2078
+
2079
+ return createAdapter({
2080
+ name: '${name}',
2081
+
2082
+ async adapt(builder: AdapterBuilder) {
2083
+ builder.log.info(\`Building for ${pascalName} platform...\`);
2084
+ builder.log.info(\`Port: \${port}\`);
2085
+
2086
+ // Generate server entry
2087
+ await generateServerEntry(builder, port, healthCheck);
2088
+
2089
+ // Generate platform-specific config
2090
+ await generatePlatformConfig(builder, port, healthCheck);
2091
+
2092
+ builder.log.info('Build complete!');
2093
+ },
2094
+
2095
+ supports: {
2096
+ node: () => ${type === "node" || type === "container"},
2097
+ edge: () => ${type === "edge" || type === "serverless"},
2098
+ streaming: () => ${type === "node"},
2099
+ websockets: () => ${type === "node"},
2100
+ },
2101
+
2102
+ emulate: () => ({
2103
+ env: {
2104
+ ${name.toUpperCase().replace(/-/g, "_")}_PLATFORM: 'true',
2105
+ PORT: String(port),
2106
+ ...env,
2107
+ },
2108
+ }),
2109
+ });
2110
+ }
2111
+
2112
+ // ============================================================================
2113
+ // Generator Functions
2114
+ // ============================================================================
2115
+
2116
+ async function generateServerEntry(
2117
+ builder: AdapterBuilder,
2118
+ port: number,
2119
+ healthCheck: boolean
2120
+ ): Promise<void> {
2121
+ const serverCode = \`
2122
+ import { createServer } from 'node:http';
2123
+ import { createUniversalHandler } from '@flight-framework/core/adapters';
2124
+ import manifest from './manifest.js';
2125
+
2126
+ const handler = createUniversalHandler(manifest);
2127
+
2128
+ const server = createServer(async (req, res) => {
2129
+ const url = req.url || '/';
2130
+
2131
+ // Health check endpoint
2132
+ if (url === '/health' && \${healthCheck}) {
2133
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2134
+ res.end(JSON.stringify({ status: 'ok' }));
2135
+ return;
2136
+ }
2137
+
2138
+ // Handle request with Flight
2139
+ const webReq = new Request(\\\`http://localhost\${url}\\\`, {
2140
+ method: req.method,
2141
+ headers: Object.fromEntries(
2142
+ Object.entries(req.headers).filter(([_, v]) => v != null).map(([k, v]) => [k, String(v)])
2143
+ ),
2144
+ });
2145
+
2146
+ const response = await handler(webReq);
2147
+
2148
+ res.writeHead(response.status, Object.fromEntries(response.headers));
2149
+ res.end(await response.text());
2150
+ });
2151
+
2152
+ server.listen(\${port}, () => {
2153
+ console.log(\\\`Server running at http://localhost:\${port}\\\`);
2154
+ });
2155
+ \`;
2156
+
2157
+ await builder.writeFile('server.mjs', serverCode.trim());
2158
+ }
2159
+
2160
+ async function generatePlatformConfig(
2161
+ builder: AdapterBuilder,
2162
+ port: number,
2163
+ healthCheck: boolean
2164
+ ): Promise<void> {
2165
+ const config = {
2166
+ name: '${name}-app',
2167
+ port,
2168
+ healthCheck: healthCheck ? '/health' : null,
2169
+ };
2170
+
2171
+ await builder.writeFile('platform.json', JSON.stringify(config, null, 2));
2172
+ }
2173
+ `;
2174
+ }
2175
+ function generateAdapterTest(name, pascalName) {
2176
+ return `/**
2177
+ * ${pascalName} Adapter Tests
2178
+ */
2179
+
2180
+ import { describe, it, expect, vi } from 'vitest';
2181
+ import adapter from '../src/index.js';
2182
+
2183
+ describe('adapter-${name}', () => {
2184
+ const createMockBuilder = () => ({
2185
+ manifest: {
2186
+ entries: {},
2187
+ files: [],
2188
+ routes: [],
2189
+ },
2190
+ root: '/test',
2191
+ outDir: '/test/dist',
2192
+ readFile: vi.fn(),
2193
+ writeFile: vi.fn(),
2194
+ copy: vi.fn(),
2195
+ glob: vi.fn(),
2196
+ log: {
2197
+ info: vi.fn(),
2198
+ warn: vi.fn(),
2199
+ error: vi.fn(),
2200
+ },
2201
+ });
2202
+
2203
+ it('should have correct name', () => {
2204
+ const instance = adapter();
2205
+ expect(instance.name).toBe('${name}');
2206
+ });
2207
+
2208
+ it('should generate server entry', async () => {
2209
+ const instance = adapter({ port: 4000 });
2210
+ const builder = createMockBuilder();
2211
+
2212
+ await instance.adapt(builder);
2213
+
2214
+ expect(builder.writeFile).toHaveBeenCalledWith(
2215
+ 'server.mjs',
2216
+ expect.stringContaining('4000')
2217
+ );
2218
+ });
2219
+
2220
+ it('should generate platform config', async () => {
2221
+ const instance = adapter();
2222
+ const builder = createMockBuilder();
2223
+
2224
+ await instance.adapt(builder);
2225
+
2226
+ expect(builder.writeFile).toHaveBeenCalledWith(
2227
+ 'platform.json',
2228
+ expect.any(String)
2229
+ );
2230
+ });
2231
+
2232
+ it('should declare supported features', () => {
2233
+ const instance = adapter();
2234
+
2235
+ expect(instance.supports?.node?.()).toBeDefined();
2236
+ expect(instance.supports?.streaming?.()).toBeDefined();
2237
+ });
2238
+
2239
+ it('should provide emulation environment', () => {
2240
+ const instance = adapter({ port: 5000 });
2241
+ const emulation = instance.emulate?.();
2242
+
2243
+ expect(emulation?.env?.PORT).toBe('5000');
2244
+ expect(emulation?.env?.${name.toUpperCase().replace(/-/g, "_")}_PLATFORM).toBe('true');
2245
+ });
2246
+ });
2247
+ `;
2248
+ }
2249
+ function generateReadme(name, packageName) {
2250
+ return `# ${packageName}
2251
+
2252
+ Flight adapter for ${name} platform.
2253
+
2254
+ ## Installation
2255
+
2256
+ \`\`\`bash
2257
+ npm install ${packageName}
2258
+ \`\`\`
2259
+
2260
+ ## Usage
2261
+
2262
+ \`\`\`typescript
2263
+ import { defineConfig } from '@flight-framework/core';
2264
+ import ${name.replace(/-/g, "")} from '${packageName}';
2265
+
2266
+ export default defineConfig({
2267
+ adapter: ${name.replace(/-/g, "")}({
2268
+ port: 3000,
2269
+ healthCheck: true,
2270
+ }),
2271
+ });
2272
+ \`\`\`
2273
+
2274
+ ## Options
2275
+
2276
+ | Option | Type | Default | Description |
2277
+ |--------|------|---------|-------------|
2278
+ | \`port\` | \`number\` | \`3000\` | Server port |
2279
+ | \`healthCheck\` | \`boolean\` | \`true\` | Enable health check endpoint |
2280
+ | \`env\` | \`Record<string, string>\` | \`{}\` | Custom environment variables |
2281
+
2282
+ ## License
2283
+
2284
+ MIT
2285
+ `;
2286
+ }
2287
+
1835
2288
  // src/index.ts
1836
2289
  var cli = cac("flight");
1837
2290
  var LOGO = `
1838
- ${pc6.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
1839
- ${pc6.cyan(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
1840
- ${pc6.cyan(" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
1841
- ${pc6.cyan(" \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
1842
- ${pc6.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ")}
1843
- ${pc6.cyan(" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
2291
+ ${pc7.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
2292
+ ${pc7.cyan(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
2293
+ ${pc7.cyan(" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
2294
+ ${pc7.cyan(" \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
2295
+ ${pc7.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ")}
2296
+ ${pc7.cyan(" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
1844
2297
 
1845
- ${pc6.dim("The Agnostic Full-Stack Framework")}
1846
- ${pc6.dim("Maximum Flexibility. Zero Lock-in.")}
2298
+ ${pc7.dim("The Agnostic Full-Stack Framework")}
2299
+ ${pc7.dim("Maximum Flexibility. Zero Lock-in.")}
1847
2300
  `;
1848
2301
  function printLogo() {
1849
2302
  console.log(LOGO);
@@ -1857,6 +2310,7 @@ cli.command("build", "Build for production").option("--outDir <dir>", "Output di
1857
2310
  cli.command("preview", "Preview production build").option("-p, --port <port>", "Port to listen on").option("-h, --host <host>", "Host to bind to").option("--open", "Open browser on start").action(previewCommand);
1858
2311
  cli.command("routes:generate", "Generate route manifest from routes directory").option("--routesDir <dir>", "Routes directory", { default: "src/routes" }).option("--outputDir <dir>", "Output directory", { default: "src/.flight" }).action(routesGenerateCommand);
1859
2312
  cli.command("types:generate", "Generate TypeScript types for routes and environment").option("--routesDir <dir>", "Routes directory", { default: "src/routes" }).option("--outputDir <dir>", "Output directory", { default: "src/.flight" }).option("--routes", "Generate route types", { default: true }).option("--env", "Generate environment variable types").option("--watch", "Watch for changes and regenerate").action(typesGenerateCommand);
2313
+ cli.command("adapter:create <name>", "Create a new adapter package").option("-t, --type <type>", "Platform type (node, edge, container, serverless)", { default: "node" }).option("--validation", "Include Zod validation example").option("--outputDir <dir>", "Output directory").action(adapterCreateCommand);
1860
2314
  function run() {
1861
2315
  try {
1862
2316
  cli.parse(process.argv, { run: false });
@@ -1865,7 +2319,7 @@ function run() {
1865
2319
  }
1866
2320
  cli.runMatchedCommand();
1867
2321
  } catch (error) {
1868
- console.error(pc6.red("Error:"), error instanceof Error ? error.message : error);
2322
+ console.error(pc7.red("Error:"), error instanceof Error ? error.message : error);
1869
2323
  process.exit(1);
1870
2324
  }
1871
2325
  }