diff --git a/cli.ts b/cli.ts index 6775f46..163cf10 100644 --- a/cli.ts +++ b/cli.ts @@ -2,29 +2,29 @@ import { Command } from "cliffy"; -import * as init from './cli/init.ts'; -import * as deploy from './cli/deploy.ts'; -import * as start from './cli/start.ts'; +import * as init from "./cli/init.ts"; +import * as deploy from "./cli/deploy.ts"; +import * as start from "./cli/start.ts"; -import Version from './version.json' assert { type: "json" } +import Version from "./version.json" assert { type: "json" }; await new Command() - .name("kretes") - .version(Version) - .description("Web Framework for Deno") - .command( - "init [name:string]", - "Create a new Kretes project" - ) - .action(init.action) - .command( - "start", - "Start the project in development mode" - ) - .action(start.action) - .command( - "deploy", - "Deploy the project to Deno Deploy" - ) - .action(deploy.action) - .parse(Deno.args); \ No newline at end of file + .name("kretes") + .version(Version) + .description("Web Framework for Deno") + .command( + "init [name:string]", + "Create a new Kretes project", + ) + .action(init.action) + .command( + "start", + "Start the project in development mode", + ) + .action(start.action) + .command( + "deploy", + "Deploy the project to Deno Deploy", + ) + .action(deploy.action) + .parse(Deno.args); diff --git a/cli/deploy.ts b/cli/deploy.ts index f426dca..32bfc8a 100644 --- a/cli/deploy.ts +++ b/cli/deploy.ts @@ -1,27 +1,26 @@ import { IAction } from "cliffy"; -import { parse } from "path" +import { parse } from "path"; export const action: IAction = async (_options) => { - // TODO brittle as hell :) + // TODO brittle as hell :) - // TODO check if installed - // deno install --allow-read --allow-write --allow-env --allow-net --allow-run --no-check -r -f https://deno.land/x/deploy/deployctl.ts + // TODO check if installed + // deno install --allow-read --allow-write --allow-env --allow-net --allow-run --no-check -r -f https://deno.land/x/deploy/deployctl.ts - // TODO check if root e.g. deno.json + // TODO check if root e.g. deno.json - const { base } = parse(Deno.cwd()); + const { base } = parse(Deno.cwd()); - const p = Deno.run({ - cmd: [ - "deployctl", - "deploy", - `--project=${base}`, - "--import-map=import_map.json", - "main.ts" - ] - }); - - await p.status(); -} + const p = Deno.run({ + cmd: [ + "deployctl", + "deploy", + `--project=${base}`, + "--import-map=import_map.json", + "main.ts", + ], + }); + await p.status(); +}; diff --git a/cli/init.ts b/cli/init.ts index 27a4d76..d5d58ff 100644 --- a/cli/init.ts +++ b/cli/init.ts @@ -1,107 +1,106 @@ import { IAction } from "cliffy"; -import * as path from 'path'; -import * as fs from 'fs'; +import * as path from "path"; +import * as fs from "fs"; export const action: IAction = async (options, name?: string) => { - console.log( - `\n%c 🦋 Kretes %c`, - 'background-color: #60A5FA; color: black; font-weight: bold', - '', - ); - console.log( - 'A programming environment for %cTypeScript%c & Deno\n', - 'color: #60A5FA', - '', - ); - - const projectName = name || 'my-kretes-project'; - const projectPath = path.resolve(projectName); - - try { - const dir = [...Deno.readDirSync(projectPath)]; - const isEmpty = dir.length === 0 || - dir.length === 1 && dir[0].name === '.git'; - if (!isEmpty) { - error('Directory is not empty.'); - } - } catch (err) { - if (!(err instanceof Deno.errors.NotFound)) { - throw err; - } - } - - await Deno.mkdir(projectName, { recursive: true }); - - for (const el of InitTemplate) { - await download(template(el), path.join(projectName, el)); - } - - if (options.vscode) { - await Deno.mkdir(path.join(projectPath, '.vscode'), { recursive: true }); - - const VSCodeSettings = { - 'deno.enable': true, - 'deno.lint': true, - 'editor.defaultFormatter': 'denoland.vscode-deno', - }; - - await Deno.writeTextFile( - path.join(projectPath, '.vscode', 'settings.json'), - JSON.stringify(VSCodeSettings, null, 2) + '\n', - ); - - const VSCodeExtensions = { - recommendations: ['denoland.vscode-deno'], - }; - - await Deno.writeTextFile( - path.join(projectPath, '.vscode', 'extensions.json'), - JSON.stringify(VSCodeExtensions, null, 2) + '\n', - ); - } - - console.log('\n%cProject initialized and ready.\n', 'color: green; font-weight: bold'); - console.log( - `Enter the project directory using %ccd ${projectName}%c.`, - 'color: cyan', - '', - ); - console.log( - 'Run %ckretes start%c or %cdeno task start%c to start the project. %cCTRL-C%c to stop.', - 'color: cyan', - '', - 'color: cyan', - '', - 'color: cyan', - '', - ); -} - + console.log( + `\n%c 🦋 Kretes %c`, + "background-color: #60A5FA; color: black; font-weight: bold", + "", + ); + console.log( + "A programming environment for %cTypeScript%c & Deno\n", + "color: #60A5FA", + "", + ); + + const projectName = name || "my-kretes-project"; + const projectPath = path.resolve(projectName); + + try { + const dir = [...Deno.readDirSync(projectPath)]; + const isEmpty = dir.length === 0 || + dir.length === 1 && dir[0].name === ".git"; + if (!isEmpty) { + error("Directory is not empty."); + } + } catch (err) { + if (!(err instanceof Deno.errors.NotFound)) { + throw err; + } + } + + await Deno.mkdir(projectName, { recursive: true }); + + for (const el of InitTemplate) { + await download(template(el), path.join(projectName, el)); + } + + if (options.vscode) { + await Deno.mkdir(path.join(projectPath, ".vscode"), { recursive: true }); + + const VSCodeSettings = { + "deno.enable": true, + "deno.lint": true, + "editor.defaultFormatter": "denoland.vscode-deno", + }; + + await Deno.writeTextFile( + path.join(projectPath, ".vscode", "settings.json"), + JSON.stringify(VSCodeSettings, null, 2) + "\n", + ); + + const VSCodeExtensions = { + recommendations: ["denoland.vscode-deno"], + }; + + await Deno.writeTextFile( + path.join(projectPath, ".vscode", "extensions.json"), + JSON.stringify(VSCodeExtensions, null, 2) + "\n", + ); + } + + console.log("\n%cProject initialized and ready.\n", "color: green; font-weight: bold"); + console.log( + `Enter the project directory using %ccd ${projectName}%c.`, + "color: cyan", + "", + ); + console.log( + "Run %ckretes start%c or %cdeno task start%c to start the project. %cCTRL-C%c to stop.", + "color: cyan", + "", + "color: cyan", + "", + "color: cyan", + "", + ); +}; const template = (element: string) => - `https://raw.githubusercontent.com/kreteshq/init/main/${element}`; + `https://raw.githubusercontent.com/kreteshq/init/main/${element}`; const InitTemplate = [ - 'components/.gitkeep', - 'routes/index.page.tsx', - 'routes/posts.ts', - 'static/.gitkeep', - '.gitignore', - 'deno.json', - 'import_map.json', - 'main.ts' + "components/.gitkeep", + "routes/index.page.tsx", + "routes/posts.ts", + "static/.gitkeep", + ".gitignore", + "deno.json", + "import_map.json", + "main.ts", ]; const download = async (url: string, path: string) => { - const response = await fetch(url); - const text = await response.text() + const response = await fetch(url); + const text = await response.text(); - await fs.ensureFile(path); + await fs.ensureFile(path); - Deno.writeTextFile(path, text); + Deno.writeTextFile(path, text); }; const error = (message: string) => { - console.error(`%cerror%c: ${message}`, 'color: red; font-weight: bold', ''); - Deno.exit(1); -}; \ No newline at end of file + console.error(`%cerror%c: ${message}`, "color: red; font-weight: bold", ""); + Deno.exit(1); +}; diff --git a/cli/start.ts b/cli/start.ts index 99faeac..d228127 100644 --- a/cli/start.ts +++ b/cli/start.ts @@ -1,33 +1,32 @@ import type { IAction } from "cliffy"; -import { expandGlob } from 'fs' +import { expandGlob } from "fs"; import { Manifest } from "../types.ts"; import { convertFilenameToPathname, generate } from "../util.ts"; export const action: IAction = async (_options) => { - // (re-) generate manifest - - const entries = []; - for await (const entry of expandGlob("routes/**/*.{ts,tsx}")) { - const filename = entry.path.split('routes/').pop()! - const pathname = convertFilenameToPathname(filename); - - entries.push({ filename, pathname, }) - } - - await generate(entries); - - const p = Deno.run({ - cmd: [ - "deno", - "run", - "-A", - "--unstable", - "--watch=static/,routes/", - "main.ts" - ] - }); - - await p.status(); -} - + // (re-) generate manifest + + const entries = []; + for await (const entry of expandGlob("routes/**/*.{ts,tsx}")) { + const filename = entry.path.split("routes/").pop()!; + const pathname = convertFilenameToPathname(filename); + + entries.push({ filename, pathname }); + } + + await generate(entries); + + const p = Deno.run({ + cmd: [ + "deno", + "run", + "-A", + "--unstable", + "--watch=static/,routes/", + "main.ts", + ], + }); + + await p.status(); +}; diff --git a/deno.json b/deno.json index 31765e2..66c8568 100644 --- a/deno.json +++ b/deno.json @@ -7,8 +7,7 @@ "options": { "useTabs": true, "lineWidth": 101, - "indentWidth": 2, - "singleQuote": true + "indentWidth": 2 } } } diff --git a/import_map.json b/import_map.json index f97407b..90e44fe 100644 --- a/import_map.json +++ b/import_map.json @@ -1,16 +1,16 @@ { - "imports": { - "preact": "https://esm.sh/preact@10.11.2", - "preact/": "https://esm.sh/preact@10.11.2/", - "preact-render-to-string": "https://esm.sh/preact-render-to-string@5.2.5?external=preact", - "twind": "https://esm.sh/twind@0.16.17", - "twind/": "https://esm.sh/twind@0.16.17/", - "http/": "https://deno.land/std@0.160.0/http/", - "fs": "https://deno.land/std@0.160.0/fs/mod.ts", - "path": "https://deno.land/std@0.160.0/path/mod.ts", - "wren": "https://deno.land/x/wren@0.5.0/mod.ts", - "wren/": "https://deno.land/x/wren@0.5.0/", - "cliffy": "https://deno.land/x/cliffy@v0.25.2/command/mod.ts", - "cli": "https://deno.land/std@0.160.0/flags/mod.ts" - } + "imports": { + "preact": "https://esm.sh/preact@10.11.2", + "preact/": "https://esm.sh/preact@10.11.2/", + "preact-render-to-string": "https://esm.sh/preact-render-to-string@5.2.5?external=preact", + "twind": "https://esm.sh/twind@0.16.17", + "twind/": "https://esm.sh/twind@0.16.17/", + "http/": "https://deno.land/std@0.160.0/http/", + "fs": "https://deno.land/std@0.160.0/fs/mod.ts", + "path": "https://deno.land/std@0.160.0/path/mod.ts", + "wren": "https://deno.land/x/wren@0.5.0/mod.ts", + "wren/": "https://deno.land/x/wren@0.5.0/", + "cliffy": "https://deno.land/x/cliffy@v0.25.2/command/mod.ts", + "cli": "https://deno.land/std@0.160.0/flags/mod.ts" + } } diff --git a/middleware/page.ts b/middleware/page.ts index a3c5027..253e559 100644 --- a/middleware/page.ts +++ b/middleware/page.ts @@ -1,14 +1,16 @@ import type { Handler, Middleware } from "wren"; import { PageComponent, PageProps } from "../types.ts"; -export const Page = (page: PageComponent): Middleware => (handler: Handler) => (request, connInfo) => { - const { url, params } = request; +export const Page = + (page: PageComponent): Middleware => (handler: Handler) => (request, connInfo) => { + const { url, params } = request; - request.page = (data: T) => page({ - url: new URL(url), - params, - data - } as PageProps) + request.page = (data: T) => + page({ + url: new URL(url), + params, + data, + } as PageProps); - return handler(request, connInfo); -} \ No newline at end of file + return handler(request, connInfo); + }; diff --git a/mod.ts b/mod.ts index a82d2b4..2b04baa 100644 --- a/mod.ts +++ b/mod.ts @@ -1,36 +1,37 @@ -import type { Handler, Pipeline, Routes } from "wren/types.ts"; +import type { Handler, HandlerMapping, Pipeline, Routes } from "wren/types.ts"; import type { Manifest, PageProps } from "./types.ts"; -import * as Response from "wren/response.ts" -import render from 'preact-render-to-string' +import * as Response from "wren/response.ts"; +import render from "preact-render-to-string"; -import * as Middleware from './middleware/page.ts'; +import * as Middleware from "./middleware/page.ts"; export const setup = (manifest: Manifest) => { - const routes: Routes = []; + const routes: Routes = []; - for (const [pathname, { default: page, handler }] of Object.entries(manifest.routes)) { - if (handler && page) { - routes.push([ - pathname, [Middleware.Page(page), handler] as Pipeline - ]); - } else if (handler) { - routes.push([pathname, handler as Handler | Pipeline]); - } else if (page) { - routes.push([pathname, { - GET: (request) => { - const { params } = request; + for (const [pathname, { default: page, handler }] of Object.entries(manifest.routes)) { + if (handler && page) { + routes.push([ + pathname, + [Middleware.Page(page), handler] as Pipeline, + ]); + } else if (handler) { + routes.push([pathname, handler as Handler | HandlerMapping | Pipeline]); + } else if (page) { + routes.push([pathname, { + GET: (request) => { + const { params } = request; - // FIXME - return Response.HTML(render(page({ params } as PageProps)!)); - } - }]); - } else { - // FIXME - } - } + // FIXME + return Response.HTML(render(page({ params } as PageProps)!)); + }, + }]); + } else { + // FIXME + } + } - return routes; -} + return routes; +}; -export * from './types.ts'; \ No newline at end of file +export * from "./types.ts"; diff --git a/types.ts b/types.ts index dc28fd2..803b968 100644 --- a/types.ts +++ b/types.ts @@ -2,19 +2,19 @@ import type { FunctionComponent } from "preact"; import type { Handler, HandlerMapping, Params, Pipeline } from "wren"; export interface PageProps { - url: URL; - params: Params - data: T + url: URL; + params: Params; + data: T; } export interface Manifest { - routes: Record; + routes: Record; } export interface RouteModule { - default?: PageComponent; - handler?: Handler | HandlerMapping | Pipeline + default?: PageComponent; + handler?: Handler | HandlerMapping | Pipeline; } // FIXME ask Michal -export type PageComponent = FunctionComponent>; \ No newline at end of file +export type PageComponent = FunctionComponent>; diff --git a/util.ts b/util.ts index 1f174a7..effcf0b 100644 --- a/util.ts +++ b/util.ts @@ -1,31 +1,29 @@ import { join } from "path"; export const convertFilenameToPathname = (filename: string) => { - const pathname = filename - .replace(/(\.page)?\.(ts|tsx)$/, '') - .replace(/(\/)?index/, '') + const pathname = filename + .replace(/(\.page)?\.(ts|tsx)$/, "") + .replace(/(\/)?index/, ""); - const segments = pathname.split("/"); - const convertedSegments = segments.map(replaceSquareBracketsWithColons).join("/") - const result = `/${convertedSegments}` + const segments = pathname.split("/"); + const convertedSegments = segments.map(replaceSquareBracketsWithColons).join("/"); + const result = `/${convertedSegments}`; - return result; -} + return result; +}; interface Entry { - filename: string; - pathname: string; + filename: string; + pathname: string; } const constructImportStatement = ({ filename }: Entry, index: number) => - `import * as $${index} from "./routes/${filename}";` - -const constructRouteStatement = ({ pathname }: Entry, index: number) => - `'${pathname}': $${index},` + `import * as $${index} from "./routes/${filename}";`; +const constructRouteStatement = ({ pathname }: Entry, index: number) => `'${pathname}': $${index},`; export const generate = async (entries: Entry[]) => { - const output = `// DO NOT EDIT. This file is automatically generated. + const output = `// DO NOT EDIT. This file is automatically generated. import type { Manifest } from 'kretes'; ${entries.map(constructImportStatement).join("\n")} @@ -39,18 +37,17 @@ const manifest: Manifest = { export default manifest; `; - await Deno.writeTextFile('./manifest.g.ts', output); -} - + await Deno.writeTextFile("./manifest.g.ts", output); +}; // PRIVATE const replaceSquareBracketsWithColons = (segment: string) => { - const wildcardMatch = segment.match(/\[\.\.\.(.+)\]/); - if (wildcardMatch) return `:${wildcardMatch[1]}*` + const wildcardMatch = segment.match(/\[\.\.\.(.+)\]/); + if (wildcardMatch) return `:${wildcardMatch[1]}*`; - const regularMatch = segment.match(/\[(.+)\]/); - if (regularMatch) return `:${regularMatch[1]}`; + const regularMatch = segment.match(/\[(.+)\]/); + if (regularMatch) return `:${regularMatch[1]}`; - return segment; -} \ No newline at end of file + return segment; +};