From 2237d699ff0a227db45f1d0807eeec5884f82186 Mon Sep 17 00:00:00 2001 From: RATIU5 Date: Mon, 27 Jan 2025 10:54:34 -0700 Subject: [PATCH 1/2] feat(core): add core module file-based routing --- examples/_test/luxe.config.ts | 8 +- packages/core/package.json | 2 +- packages/core/src/core/cmd/dev.ts | 21 ++--- packages/core/src/core/db/drizzle/model.ts | 2 +- packages/core/src/modules/document/module.ts | 10 +-- .../document/pages/api/document/index.ts | 5 ++ packages/core/src/server/astro/dev.ts | 26 +++--- .../core/src/server/astro/pages/admin.astro | 0 .../src/server/astro/pages/admin/index.astro | 5 ++ packages/core/src/server/astro/pages/api.ts | 0 .../core/src/server/astro/pages/api/index.ts | 5 ++ packages/core/src/server/astro/pages/index.ts | 5 ++ packages/core/src/types/core/config/index.ts | 6 ++ packages/core/src/types/core/module/index.ts | 8 +- packages/core/src/zod/core/config/index.ts | 3 +- packages/core/src/zod/core/module/index.ts | 3 +- packages/luxe-scripts/cmd/build.js | 89 +++++++++++++------ packages/luxe-scripts/package.json | 33 +++---- pnpm-lock.yaml | 24 +++++ 19 files changed, 166 insertions(+), 89 deletions(-) create mode 100644 packages/core/src/modules/document/pages/api/document/index.ts delete mode 100644 packages/core/src/server/astro/pages/admin.astro create mode 100644 packages/core/src/server/astro/pages/admin/index.astro delete mode 100644 packages/core/src/server/astro/pages/api.ts create mode 100644 packages/core/src/server/astro/pages/api/index.ts create mode 100644 packages/core/src/server/astro/pages/index.ts diff --git a/examples/_test/luxe.config.ts b/examples/_test/luxe.config.ts index aa3481a..422c900 100644 --- a/examples/_test/luxe.config.ts +++ b/examples/_test/luxe.config.ts @@ -1,12 +1,12 @@ import { defineConfig } from "@luxecms/core"; -// import { DocumentModule } from "@luxecms/core/modules"; +import { DocumentModule } from "@luxecms/core/modules"; export default defineConfig({ postgresUrl: process.env.POSTGRES_URL ?? "", astro: {}, modules: [ - // DocumentModule({ - // schemas: {}, - // }), + DocumentModule({ + schemas: {}, + }), ], }); diff --git a/packages/core/package.json b/packages/core/package.json index dd9c4d9..57177be 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -4,7 +4,7 @@ "description": "The first truly composable headless CMS built for the modular web.", "type": "module", "scripts": { - "dev": "luxe-scripts dev --dts --astroPages=./core/src/server/astro/pages", + "dev": "luxe-scripts dev --dts --astroPagesPattern=**/src/**/pages/**", "build": "luxe-scripts build --dts", "test": "vitest" }, diff --git a/packages/core/src/core/cmd/dev.ts b/packages/core/src/core/cmd/dev.ts index ee1da60..a2c084c 100644 --- a/packages/core/src/core/cmd/dev.ts +++ b/packages/core/src/core/cmd/dev.ts @@ -1,5 +1,5 @@ import { astroDev } from "../../server/index.js"; -import type { LuxeConfig } from "../../types/index.js"; +import type { LuxeConfig, LuxeRoute } from "../../types/index.js"; import { type LuxeLog, establishLuxeDatabaseConnection, @@ -8,6 +8,12 @@ import { } from "../index.js"; export const dev = async (config: LuxeConfig, logger: LuxeLog) => { + const routes: Array = []; + + const injectRoute = (route: LuxeRoute) => { + routes.push(route); + }; + // this doesn't establish a usable connection await initializeLuxeDatabase(config.postgresUrl); logger.debug("Database initialized successfully"); @@ -16,14 +22,7 @@ export const dev = async (config: LuxeConfig, logger: LuxeLog) => { if (module.hooks?.["luxe:server:init"]) { await module.hooks["luxe:server:init"]({ logger, - // Don't want users to alter the module hooks, so we exclude them - config: { - ...config, - modules: config.modules.map((m) => ({ - ...m, - hooks: undefined, - })), - }, + injectRoute, }); } } @@ -40,7 +39,9 @@ export const dev = async (config: LuxeConfig, logger: LuxeLog) => { logger.debug("Initialized module `luxe:server:before` hooks successfully"); - const devServer = await astroDev(config.astro); + const devServer = await astroDev(config.astro, { + routes, + }); for (const module of config.modules) { if (module.hooks?.["luxe:server:ready"]) { diff --git a/packages/core/src/core/db/drizzle/model.ts b/packages/core/src/core/db/drizzle/model.ts index 11a7bf8..a7000a2 100644 --- a/packages/core/src/core/db/drizzle/model.ts +++ b/packages/core/src/core/db/drizzle/model.ts @@ -1,4 +1,4 @@ -import { pgTable, type PgTableWithColumns } from "drizzle-orm/pg-core"; +import { type PgTableWithColumns, pgTable } from "drizzle-orm/pg-core"; import type { Model } from "../../../types/core/models/index.js"; export const defineModel = (modelName: string, fields: Model["fields"]) => { diff --git a/packages/core/src/modules/document/module.ts b/packages/core/src/modules/document/module.ts index 002179a..b0e8885 100644 --- a/packages/core/src/modules/document/module.ts +++ b/packages/core/src/modules/document/module.ts @@ -1,3 +1,4 @@ +import { dirname, join } from "node:path"; import { defineModel, defineModule, field } from "../../core/index.js"; import type { Module } from "../../types/index.js"; import type { DocumentModuleProps } from "../../zod/modules/documents/index.js"; @@ -53,17 +54,8 @@ export const DocumentModule = ({ schemas }: DocumentModuleProps): Module => { "luxe:migrate:error": async (ctx) => { ctx.logger.info("Document module migrate error hook!"); }, - "luxe:server:init": async (ctx) => { ctx.logger.info("Document module started!"); - // ctx.routes.push({ - // method: "GET", - // path: "/documents", - // handler: async (req, res) => { - // const documents = await ctx.db.query(`SELECT * FROM documents;`); - // res.status(200).json(documents); - // }, - // }); }, "luxe:server:before": async (ctx) => { ctx.logger.info("Document module server before hook!"); diff --git a/packages/core/src/modules/document/pages/api/document/index.ts b/packages/core/src/modules/document/pages/api/document/index.ts new file mode 100644 index 0000000..0289d6d --- /dev/null +++ b/packages/core/src/modules/document/pages/api/document/index.ts @@ -0,0 +1,5 @@ +import type { APIRoute } from "astro"; + +export const GET: APIRoute = async ({ params }) => { + return new Response("Hello, documents!"); +}; diff --git a/packages/core/src/server/astro/dev.ts b/packages/core/src/server/astro/dev.ts index 165c61c..a6688d8 100644 --- a/packages/core/src/server/astro/dev.ts +++ b/packages/core/src/server/astro/dev.ts @@ -1,11 +1,16 @@ -import { dev } from "astro"; -import { LuxeError } from "../../core/errors/index.js"; -import type { LuxeConfig } from "../../types/index.js"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; +import { dev } from "astro"; +import { LuxeError } from "../../core/errors/index.js"; +import type { LuxeConfig, LuxeRoute } from "../../types/index.js"; + +export type LuxeAstroConfig = { + routes: Array; +}; export const astroDev = async ( astroConfig: LuxeConfig["astro"], + luxeAstroConfig: LuxeAstroConfig, ): ReturnType => { try { const currentDir = dirname(fileURLToPath(import.meta.url)) @@ -21,14 +26,13 @@ export const astroDev = async ( name: "luxe-server", hooks: { "astro:config:setup": async (config) => { - config.injectRoute({ - pattern: "/admin/[...slug]", - entrypoint: join(currentDir, "pages", "admin.astro"), - }); - config.injectRoute({ - pattern: "/api/[...slug]", - entrypoint: join(currentDir, "pages", "api.ts"), - }); + for (const route of luxeAstroConfig.routes) { + console.log(route); + config.injectRoute({ + pattern: `${route.type}/${route.pattern}`, + entrypoint: route.entrypoint, + }); + } }, }, }, diff --git a/packages/core/src/server/astro/pages/admin.astro b/packages/core/src/server/astro/pages/admin.astro deleted file mode 100644 index e69de29..0000000 diff --git a/packages/core/src/server/astro/pages/admin/index.astro b/packages/core/src/server/astro/pages/admin/index.astro new file mode 100644 index 0000000..f5dcc96 --- /dev/null +++ b/packages/core/src/server/astro/pages/admin/index.astro @@ -0,0 +1,5 @@ +--- + +--- + +

Hello, admin!

diff --git a/packages/core/src/server/astro/pages/api.ts b/packages/core/src/server/astro/pages/api.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/core/src/server/astro/pages/api/index.ts b/packages/core/src/server/astro/pages/api/index.ts new file mode 100644 index 0000000..c3d49e4 --- /dev/null +++ b/packages/core/src/server/astro/pages/api/index.ts @@ -0,0 +1,5 @@ +import type { APIRoute } from "astro"; + +export const GET: APIRoute = async ({ params }) => { + return new Response("status: 200"); +}; diff --git a/packages/core/src/server/astro/pages/index.ts b/packages/core/src/server/astro/pages/index.ts new file mode 100644 index 0000000..c7302c3 --- /dev/null +++ b/packages/core/src/server/astro/pages/index.ts @@ -0,0 +1,5 @@ +import type { APIRoute } from "astro"; + +export const GET: APIRoute = async ({ params }) => { + return new Response("Hello, Luxe!"); +}; diff --git a/packages/core/src/types/core/config/index.ts b/packages/core/src/types/core/config/index.ts index 906b06f..0c5c302 100644 --- a/packages/core/src/types/core/config/index.ts +++ b/packages/core/src/types/core/config/index.ts @@ -23,3 +23,9 @@ export type LuxeConfig = { }; export type LuxeUserConfig = Partial; + +export type LuxeRoute = { + type: "api" | "admin"; + pattern: string; + entrypoint: string; +}; diff --git a/packages/core/src/types/core/module/index.ts b/packages/core/src/types/core/module/index.ts index 28bde9a..a4e326e 100644 --- a/packages/core/src/types/core/module/index.ts +++ b/packages/core/src/types/core/module/index.ts @@ -1,10 +1,10 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import type { AddressInfo } from "node:net"; +import type { PgColumn, PgTableWithColumns } from "drizzle-orm/pg-core"; import type { FSWatcher } from "vite"; import type { LuxeError, LuxeLog, luxeQuery } from "../../../core/index.js"; -import type { LuxeConfig } from "../config/index.js"; +import type { LuxeConfig, LuxeRoute } from "../config/index.js"; import type { Model } from "../models/index.js"; -import type { PgColumn, PgTableWithColumns } from "drizzle-orm/pg-core"; /* * The reason we have a type for Module and a Zod type is so we can add comments to the types. @@ -111,10 +111,10 @@ export type Module = { */ "luxe:server:init"?: ({ logger, - config, + injectRoute, }: { logger: LuxeLog; - config: LuxeConfig; + injectRoute: (route: LuxeRoute) => void; }) => void | Promise; /** diff --git a/packages/core/src/zod/core/config/index.ts b/packages/core/src/zod/core/config/index.ts index 113acaf..cd9221a 100644 --- a/packages/core/src/zod/core/config/index.ts +++ b/packages/core/src/zod/core/config/index.ts @@ -2,7 +2,7 @@ import type { AstroUserConfig } from "astro"; import type postgres from "postgres"; import { z } from "zod"; import { LuxeErrors } from "../../../core/index.js"; -import { baseModuleSchema, moduleSchema } from "../module/index.js"; +import { moduleSchema } from "../module/index.js"; export const baseConfigSchema = z.object({ postgresUrl: z @@ -16,7 +16,6 @@ export const baseConfigSchema = z.object({ astro: z .custom>() .optional(), - modules: z.array(baseModuleSchema), }); export const configSchema = baseConfigSchema.extend({ diff --git a/packages/core/src/zod/core/module/index.ts b/packages/core/src/zod/core/module/index.ts index 95527ab..a8a0356 100644 --- a/packages/core/src/zod/core/module/index.ts +++ b/packages/core/src/zod/core/module/index.ts @@ -7,7 +7,6 @@ import { LuxeErrors, type luxeQuery, } from "../../../core/index.js"; -import { baseConfigSchema } from "../config/index.js"; import { loggerSchema } from "../logger/index.js"; export const baseModuleSchema = z.object({ @@ -77,7 +76,7 @@ export const lifecycleHooksSchema = z.object({ .args( z.object({ logger: loggerSchema, - config: baseConfigSchema, + injectRoute: z.function().args(z.any()).returns(z.void()), }), ) .returns(z.void().or(z.promise(z.void()))) diff --git a/packages/luxe-scripts/cmd/build.js b/packages/luxe-scripts/cmd/build.js index 1774343..b30dd33 100644 --- a/packages/luxe-scripts/cmd/build.js +++ b/packages/luxe-scripts/cmd/build.js @@ -1,9 +1,10 @@ -import { default as libPath } from "node:path"; import fs from "node:fs"; +import { default as libPath } from "node:path"; import * as esbuild from "esbuild"; import { glob } from "glob"; import kleur from "kleur"; import { x } from "tinyexec"; +import fg from "fast-glob"; async function build(...args) { const isDev = args.includes("--dev"); @@ -11,10 +12,10 @@ async function build(...args) { const dts = args.includes("--dts"); const runServer = args.includes("--server"); const joinedArgs = args.join(" "); - const astroBuild = /--astroPages=[^ ]*/ + const astroPattern = /--astroPagesPattern=[^ ]*/ .exec(joinedArgs)?.[0] .trim() - .replace("--astroPages=", ""); + .replace("--astroPagesPattern=", ""); const path = /--path=[^ ]*/ .exec(joinedArgs)?.[0] .trim() @@ -73,21 +74,36 @@ async function build(...args) { return; } - if (astroBuild) { - const astroPagesPath = libPath.join( - libPath.dirname(process.cwd()), - astroBuild, - ); - if (!fs.existsSync(astroPagesPath)) { - log.error("Astro pages path does not exist"); - } else { - const distPath = libPath.join( - libPath.dirname(process.cwd()), - astroBuild.split("/")[1], - "dist", + if (astroPattern) { + const files = await fg.glob([ + `${astroPattern}/*.ts`, + `${astroPattern}/*.astro`, + ]); + const distPath = libPath.join(process.cwd(), "dist", "pages"); + if (!fs.existsSync(distPath)) { + await x("mkdir", ["-p", distPath]); + await x("rm", ["-rf", distPath]); + } + + for (const file of files) { + const filePath = file.split("pages")[1]; + // If file already exists in destination folder, show a warning + if (fs.existsSync(libPath.join(distPath, filePath))) { + log.warn( + `File ${libPath.join( + distPath, + filePath, + )} already exists in destination folder; overwriting`, + ); + } + const distFolder = libPath.join( + distPath, + libPath.dirname(file).split("pages")[1], ); - await x("cp", ["-r", astroPagesPath, distPath]); + await x("mkdir", ["-p", distFolder]); + await x("cp", [file, distFolder]); } + log.debug("Building Astro"); } @@ -140,21 +156,36 @@ async function build(...args) { ...(path ? { entryPoints: [`${path}/**/*.ts`] } : {}), }), (async () => { - if (astroBuild) { - const astroPagesPath = libPath.join( - libPath.dirname(process.cwd()), - astroBuild, - ); - if (!fs.existsSync(astroPagesPath)) { - log.error("Astro pages path does not exist"); - } else { - const distPath = libPath.join( - libPath.dirname(process.cwd()), - astroBuild.split("/")[1], - "dist", + if (astroPattern) { + const files = await fg.glob([ + `${astroPattern}/*.ts`, + `${astroPattern}/*.astro`, + ]); + const distPath = libPath.join(process.cwd(), "dist", "pages"); + if (!fs.existsSync(distPath)) { + await x("mkdir", ["-p", distPath]); + await x("rm", ["-rf", distPath]); + } + + for (const file of files) { + const filePath = file.split("pages")[1]; + // If file already exists in destination folder, show a warning + if (fs.existsSync(libPath.join(distPath, filePath))) { + log.warn( + `File ${libPath.join( + distPath, + filePath, + )} already exists in destination folder; overwriting`, + ); + } + const distFolder = libPath.join( + distPath, + libPath.dirname(file).split("pages")[1], ); - await x("cp", ["-r", astroPagesPath, distPath]); + await x("mkdir", ["-p", distFolder]); + await x("cp", [file, distFolder]); } + log.debug("Building Astro"); } if (dts) { diff --git a/packages/luxe-scripts/package.json b/packages/luxe-scripts/package.json index 9de09d3..db89522 100644 --- a/packages/luxe-scripts/package.json +++ b/packages/luxe-scripts/package.json @@ -1,18 +1,19 @@ { - "name": "luxe-scripts", - "version": "0.0.1", - "description": "Internal scripts for LuxeCMS", - "private": true, - "type": "module", - "main": "index.js", - "bin": { - "luxe-scripts": "./index.js" - }, - "dependencies": { - "@types/glob": "^8.1.0", - "esbuild": "^0.24.0", - "glob": "^11.0.0", - "kleur": "^4.1.5", - "tinyexec": "^0.3.1" - } + "name": "luxe-scripts", + "version": "0.0.1", + "description": "Internal scripts for LuxeCMS", + "private": true, + "type": "module", + "main": "index.js", + "bin": { + "luxe-scripts": "./index.js" + }, + "dependencies": { + "@types/glob": "^8.1.0", + "esbuild": "^0.24.0", + "fast-glob": "^3.3.3", + "glob": "^11.0.0", + "kleur": "^4.1.5", + "tinyexec": "^0.3.1" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4ac822..3120153 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,15 @@ importers: specifier: ^5.7.2 version: 5.7.2 + examples/_test: + dependencies: + '@luxecms/app': + specifier: workspace:* + version: link:../../packages/app + '@luxecms/core': + specifier: workspace:* + version: link:../../packages/core + packages/app: dependencies: '@luxecms/core': @@ -138,6 +147,9 @@ importers: esbuild: specifier: ^0.24.0 version: 0.24.0 + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 glob: specifier: ^11.0.0 version: 11.0.0 @@ -1899,6 +1911,10 @@ packages: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -4753,6 +4769,14 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fastq@1.17.1: dependencies: reusify: 1.0.4 From f1829ded941e803f5bcb15ceb9392e05b24343c5 Mon Sep 17 00:00:00 2001 From: RATIU5 Date: Mon, 28 Jul 2025 07:03:31 -0600 Subject: [PATCH 2/2] edit: module work --- packages/core/src/modules/document/module.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/modules/document/module.ts b/packages/core/src/modules/document/module.ts index b0e8885..24d674c 100644 --- a/packages/core/src/modules/document/module.ts +++ b/packages/core/src/modules/document/module.ts @@ -1,4 +1,3 @@ -import { dirname, join } from "node:path"; import { defineModel, defineModule, field } from "../../core/index.js"; import type { Module } from "../../types/index.js"; import type { DocumentModuleProps } from "../../zod/modules/documents/index.js";