diff --git a/packages/astro-ghostcms/index.ts b/packages/astro-ghostcms/index.ts index 859f8f81..6228b76d 100644 --- a/packages/astro-ghostcms/index.ts +++ b/packages/astro-ghostcms/index.ts @@ -6,7 +6,7 @@ import ghostSitemap from "./src/integrations/sitemap"; import ghostRobots from "./src/integrations/robots-txt"; import { loadEnv } from 'vite'; import { fromZodError } from "zod-validation-error"; -import { viteGhostCMS } from "./src/virtual"; +import { addVirtualImport } from "./src/utils/add-virtual-import"; /** INTERNAL CONSTANTS */ const IC = { @@ -23,7 +23,7 @@ const IC = { /** INTERNAL STRING */ KEY_MISSING:"CONTENT_API_KEY Missing from .env/environment variables", /** INTERNAL STRING */ - URL_MISSING:"CONTENT_API_URL Missing from .env/environment variables", + URL_MISSING:"CONTENT_API_URL Missing from .env/environment variables or ghostURL under the integration settings in `astro.config.mjs`", /** INTERNAL STRING */ IT:"Injecting Theme: ", /** INTERNAL STRING */ @@ -55,7 +55,7 @@ const ENV = loadEnv(IC.MODE, process.cwd(), IC.PREFIXES); * @ For more information and to see the docs check * @see https://astro-ghostcms.xyz */ -export default function GhostCMS(options?: UserConfig): AstroIntegration { +export default function GhostCMS(options: UserConfig): AstroIntegration { return { name: IC.PKG, hooks: { @@ -81,6 +81,7 @@ export default function GhostCMS(options?: UserConfig): AstroIntegration { dCO: GhostConfig.disableConsoleOutput, SM: GhostConfig.sitemap, RTXT: GhostConfig.robotstxt, + gSite: GhostConfig.ghostURL } // Check For ENV Variables @@ -93,8 +94,10 @@ export default function GhostCMS(options?: UserConfig): AstroIntegration { } // CHECK FOR API URL if(ENV.CONTENT_API_URL === undefined){ - logger.error(IC.URL_MISSING); - throw IC.URL_MISSING; + if(GCD.gSite === undefined){ + logger.error(IC.URL_MISSING); + throw IC.URL_MISSING; + } } if(!GCD.dRI){ @@ -189,16 +192,17 @@ export default function GhostCMS(options?: UserConfig): AstroIntegration { // UPDATE ASTRO CONFIG WITH INTEGRATED INTEGRATIONS integrations: [ ghostSitemap( GCD.SM ), ghostRobots( GCD.RTXT ) ], // LOAD VITE AND SETUP viteGhostCMS Configs - vite: { - plugins: [ - viteGhostCMS( GhostConfig, config ) - ] - }, }) } catch ( e ) { logger.error( e as string ); throw e; }; + addVirtualImport({ + name: 'virtual:@matthiesenxyz/astro-ghostcms/config', + content: `export default ${ JSON.stringify(GhostUserConfig.data) }`, + updateConfig + }) + }, 'astro:config:done': async ({ logger }) => { logger.info(IC.CONFSETUPDONE); diff --git a/packages/astro-ghostcms/package.json b/packages/astro-ghostcms/package.json index d58d2bbd..ee9d250b 100644 --- a/packages/astro-ghostcms/package.json +++ b/packages/astro-ghostcms/package.json @@ -42,7 +42,8 @@ ".env.demo", "index.ts", "tsconfig.json", - "types.ts" + "types.ts", + "virtual.d.ts" ], "exports": { ".": "./index.ts", @@ -50,6 +51,7 @@ "./api-core": "./src/api/content-api", "./404.astro": "./src/default-routes/404/404.astro", "./rss.xml.ts": "./src/default-routes/rss.xml.ts", + "./config": "./src/integrations/virtual-config.ts", "./types": "./types.ts" }, "scripts": { diff --git a/packages/astro-ghostcms/src/api/api-functions.ts b/packages/astro-ghostcms/src/api/api-functions.ts index 0a826c4a..d53bb534 100644 --- a/packages/astro-ghostcms/src/api/api-functions.ts +++ b/packages/astro-ghostcms/src/api/api-functions.ts @@ -4,13 +4,17 @@ import { TSGhostContentAPI } from "./content-api"; // LOAD ENVIRONMENT VARIABLES import { loadEnv } from 'vite'; +import config from "../integrations/virtual-config"; + +const CONF_URL = config.ghostURL; + const { CONTENT_API_KEY, CONTENT_API_URL } = loadEnv('all',process.cwd(),'CONTENT_'); const ghostApiKey = CONTENT_API_KEY; -const ghostUrl = CONTENT_API_URL; +const ghostUrl = CONF_URL?CONF_URL:CONTENT_API_URL; const version = "v5.0"; const api = new TSGhostContentAPI(ghostUrl, ghostApiKey, version); diff --git a/packages/astro-ghostcms/src/default-routes/404/404.astro b/packages/astro-ghostcms/src/default-routes/404/404.astro index ef5f18f2..8489240a 100644 --- a/packages/astro-ghostcms/src/default-routes/404/404.astro +++ b/packages/astro-ghostcms/src/default-routes/404/404.astro @@ -3,8 +3,8 @@ import './404.css'; import { getSettings, invariant } from '../../api'; const settings = await getSettings(); invariant(settings, "Settings not found"); ---- +--- 404 | {settings.title} diff --git a/packages/astro-ghostcms/src/integrations/virtual-config.ts b/packages/astro-ghostcms/src/integrations/virtual-config.ts new file mode 100644 index 00000000..0aaf9a97 --- /dev/null +++ b/packages/astro-ghostcms/src/integrations/virtual-config.ts @@ -0,0 +1,6 @@ +import config from 'virtual:@matthiesenxyz/astro-ghostcms/config' +import type { GhostUserConfig } from '../schemas' + +const UserConfig = config as GhostUserConfig + +export default UserConfig; \ No newline at end of file diff --git a/packages/astro-ghostcms/src/schemas/index.ts b/packages/astro-ghostcms/src/schemas/index.ts index 8f4e930d..e8dd0846 100644 --- a/packages/astro-ghostcms/src/schemas/index.ts +++ b/packages/astro-ghostcms/src/schemas/index.ts @@ -3,27 +3,38 @@ import { SitemapSchema } from './sitemap'; import { RobotsTxtSchema } from './robots'; export const UserConfigSchema = z.object({ - /** OPTIONAL - Disable Route Injector - * This option allows the user to disable the route injection system and utilize just the integraions other functions. Such as API, sitemap and robotstxt integrations. */ - disableRouteInjection: z.boolean().default(false), - /** OPTIONAL - Allows the user to disable "info" console output */ - disableConsoleOutput: z.boolean().default(false), - /** OPTIONAL - Theme Selector - * This option allows the user to replace the included theme with an external npm module */ - theme: z.string().default('@matthiesenxyz/astro-ghostcms-theme-default'), - /** OPTIONAL - astrojs/sitemap - * This option allows the user to configure the included integration - * Options shown are the availble options - * REFERENCE https://docs.astro.build/en/guides/integrations-guide/sitemap - */ - sitemap: SitemapSchema.optional(), - /** OPTIONAL - astro-robots-txt - * This option allows the user to configure the included integration - * Options shown are the availble options - * REFERENCE https://www.npmjs.com/package/astro-robots-txt#configuration - */ - robotstxt: RobotsTxtSchema.optional(), - }); + /** OPTIONAL - Either set the URL in your .env or put it here + * @example + * // https://astro.build/config + * export default defineConfig({ + * site: "https://demo.astro-ghostcms.xyz/", + * integrations: [ghostcms({ + * ghostURL: "https://ghostdemo.matthiesen.xyz" + * })], + * }); */ + ghostURL: z.string().optional(), + /** OPTIONAL - Disable Route Injector + * This option allows the user to disable the route injection system and utilize just the integraions other functions. Such as API, sitemap and robotstxt integrations. */ + disableRouteInjection: z.boolean().default(false), + /** OPTIONAL - Allows the user to disable "info" console output */ + disableConsoleOutput: z.boolean().default(false), + /** OPTIONAL - Theme Selector + * This option allows the user to replace the included theme with an external npm module */ + theme: z.string().default('@matthiesenxyz/astro-ghostcms-theme-default'), + /** OPTIONAL - astrojs/sitemap + * This option allows the user to configure the included integration + * Options shown are the availble options + * REFERENCE https://docs.astro.build/en/guides/integrations-guide/sitemap + */ + sitemap: SitemapSchema.optional(), + /** OPTIONAL - astro-robots-txt + * This option allows the user to configure the included integration + * Options shown are the availble options + * REFERENCE https://www.npmjs.com/package/astro-robots-txt#configuration + */ + robotstxt: RobotsTxtSchema.optional(), +}); /** USER CONFIGURATION SCHEMA */ -export type UserConfig = z.infer \ No newline at end of file +export type UserConfig = z.infer +export type GhostUserConfig = z.infer \ No newline at end of file diff --git a/packages/astro-ghostcms/src/utils/add-virtual-import.test.ts b/packages/astro-ghostcms/src/utils/add-virtual-import.test.ts new file mode 100644 index 00000000..e27475b7 --- /dev/null +++ b/packages/astro-ghostcms/src/utils/add-virtual-import.test.ts @@ -0,0 +1,60 @@ +import { afterEach, type Mock, describe, expect, test, vi } from "vitest"; +import { addVirtualImport } from "./add-virtual-import.js"; +import { addVitePlugin } from "./add-vite-plugin.js"; + +vi.mock('./add-vite-plugin') + +const pluginNameStub = (name: T): `vite-plugin-${T}` => `vite-plugin-${name}` + +describe("add-virtual-import", () => { + const name = "test-module"; + const content = "export default {}"; + + afterEach(() => { + vi.clearAllMocks(); + }); + + test("It should call `addVitePlugin`", () => { + const updateConfig = vi.fn(); + + addVirtualImport({ + name, + content, + updateConfig, + }); + + expect(addVitePlugin).toHaveBeenCalled(); + }); + + test("`addVitePlugin` should get called with the correct plugin name", () => { + const updateConfig = vi.fn(); + + addVirtualImport({ + name, + content, + updateConfig, + }); + + const expectedName = pluginNameStub(name) + + const { plugin } = (addVitePlugin as Mock).mock.lastCall[0] + + expect(plugin.name).toEqual(expectedName); + }); + + test("Virtual module should resolve correct name", () => { + const updateConfig = vi.fn(); + + addVirtualImport({ + name, + content, + updateConfig, + }); + + const { plugin } = (addVitePlugin as Mock).mock.lastCall[0] + + const resolvedVirtualModuleId = plugin.resolveId(name) + + expect(resolvedVirtualModuleId).toEqual(`\0${ name }`); + }); +}); \ No newline at end of file diff --git a/packages/astro-ghostcms/src/utils/add-virtual-import.ts b/packages/astro-ghostcms/src/utils/add-virtual-import.ts new file mode 100644 index 00000000..54306799 --- /dev/null +++ b/packages/astro-ghostcms/src/utils/add-virtual-import.ts @@ -0,0 +1,72 @@ +import type { HookParameters } from "astro"; +import type { Plugin } from "vite"; +import { addVitePlugin } from "./add-vite-plugin.js"; + +const resolveVirtualModuleId = (id: T): `\0${T}` => { + return `\0${id}`; +}; + +const createVirtualModule = (name: string, content: string): Plugin => { + const pluginName = `vite-plugin-${name}`; + + return { + name: pluginName, + resolveId(id) { + if (id === name) { + return resolveVirtualModuleId(id); + } + }, + load(id) { + if (id === resolveVirtualModuleId(name)) { + return content; + } + }, + }; +}; + +/** + * Creates a Vite virtual module and updates the Astro config. + * Virtual imports are useful for passing things like config options, or data computed within the integration. + * + * @param {object} params + * @param {string} params.name + * @param {string} params.content + * @param {import("astro").HookParameters<"astro:config:setup">["updateConfig"]} params.updateConfig + * + * @see https://astro-integration-kit.netlify.app/utilities/add-virtual-import/ + * + * @example + * ```ts + * // my-integration/index.ts + * import { addVirtualImport } from "astro-integration-kit"; + * + * addVirtualImport( + * name: 'virtual:my-integration/config', + * content: `export default ${ JSON.stringify({foo: "bar"}) }`, + * updateConfig + * ); + * ``` + * + * This is then readable anywhere else in your integration: + * + * ```ts + * // myIntegration/src/component/layout.astro + * import config from "virtual:my-integration/config"; + * + * console.log(config.foo) // "bar" + * ``` + */ +export const addVirtualImport = ({ + name, + content, + updateConfig, +}: { + name: string; + content: string; + updateConfig: HookParameters<"astro:config:setup">["updateConfig"]; +}) => { + addVitePlugin({ + plugin: createVirtualModule(name, content), + updateConfig, + }); +}; \ No newline at end of file diff --git a/packages/astro-ghostcms/src/utils/add-vite-plugin.ts b/packages/astro-ghostcms/src/utils/add-vite-plugin.ts new file mode 100644 index 00000000..7571dc0b --- /dev/null +++ b/packages/astro-ghostcms/src/utils/add-vite-plugin.ts @@ -0,0 +1,34 @@ +import type { HookParameters } from "astro"; +import type { PluginOption } from "vite"; + +/** + * Adds a [vite plugin](https://vitejs.dev/guide/using-plugins) to the + * Astro config. + * + * @param {Params} params + * @param {import("vite").Plugin} params.plugin + * @param {import("astro").HookParameters<"astro:config:setup">["updateConfig"]} params.updateConfig + * + * @see https://astro-integration-kit.netlify.app/utilities/add-vite-plugin/ + * + * @example + * ```ts + * addVitePlugin({ + * plugin, + * updateConfig + * }) + * ``` + */ +export const addVitePlugin = ({ + plugin, + updateConfig, +}: { + plugin: PluginOption; + updateConfig: HookParameters<"astro:config:setup">["updateConfig"]; +}) => { + updateConfig({ + vite: { + plugins: [plugin], + }, + }); +}; \ No newline at end of file diff --git a/packages/astro-ghostcms/src/virtual/index.ts b/packages/astro-ghostcms/src/virtual/index.ts deleted file mode 100644 index f7481af7..00000000 --- a/packages/astro-ghostcms/src/virtual/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { AstroConfig, ViteUserConfig } from 'astro' -import { resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import type { UserConfig } from '../schemas'; - -function resolveVirtualModuleId(id: T): `\0${T}` { - return `\0${id}` -} - -export function viteGhostCMS( - opts: UserConfig, - { root, }: Pick & { - build: Pick - } -): NonNullable[number] { - const resolveId = (id: string) => - JSON.stringify(id.startsWith('.') ? resolve(fileURLToPath(root), id) : id); - - const modules = { - 'virtual:@matthiesenxyz/astro-ghostcms/config': `export default ${ JSON.stringify(opts) }` - } satisfies Record - - /** Mapping names prefixed with `\0` to their original form. */ - const resolutionMap = Object.fromEntries( - (Object.keys(modules) as (keyof typeof modules)[]).map((key) => [ - resolveVirtualModuleId(key), - key, - ]) - ) - - return { - name: 'vite-plugin-matthiesenxyz-astro-ghostcms-user-config', - resolveId(id): string | undefined { - if (id in modules) return resolveVirtualModuleId(id) - }, - load(id): string | undefined { - const resolution = resolutionMap[id] - if (resolution) return modules[resolution] - }, - } -} \ No newline at end of file diff --git a/packages/astro-ghostcms/types.ts b/packages/astro-ghostcms/types.ts index 9154f8c0..fa8c9574 100644 --- a/packages/astro-ghostcms/types.ts +++ b/packages/astro-ghostcms/types.ts @@ -6,4 +6,5 @@ export type { Settings, Tag, TagsIncludeSchema, Tier, TiersIncludeSchema } from './src/api'; + export type { ContentAPICredentials, APIVersions } from "@ts-ghost/core-api"; \ No newline at end of file diff --git a/packages/astro-ghostcms/src/virtual/virtual.d.ts b/packages/astro-ghostcms/virtual.d.ts similarity index 57% rename from packages/astro-ghostcms/src/virtual/virtual.d.ts rename to packages/astro-ghostcms/virtual.d.ts index 9cfe74a2..0f7bbcd5 100644 --- a/packages/astro-ghostcms/src/virtual/virtual.d.ts +++ b/packages/astro-ghostcms/virtual.d.ts @@ -1,4 +1,4 @@ declare module 'virtual:@matthiesenxyz/astro-ghostcms/config' { - const Config: import('../schemas').UserConfig; + const Config: import('./src/schemas/index').GhostUserConfig; export default Config; } \ No newline at end of file diff --git a/playground/astro.config.mjs b/playground/astro.config.mjs index a0eb4cbc..8890e5d8 100644 --- a/playground/astro.config.mjs +++ b/playground/astro.config.mjs @@ -4,5 +4,7 @@ import ghostcms from "@matthiesenxyz/astro-ghostcms"; // https://astro.build/config export default defineConfig({ site: "https://demo.astro-ghostcms.xyz/", - integrations: [ghostcms()], + integrations: [ ghostcms({ + ghostURL: "https://ghostdemo.matthiesen.xyz" + })], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 701c781f..678d6d4e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,11 +24,11 @@ importers: demo: dependencies: '@matthiesenxyz/astro-ghostcms': - specifier: 3.1.3 + specifier: 3.1.4 version: link:../packages/astro-ghostcms '@matthiesenxyz/astro-ghostcms-theme-default': specifier: 0.1.3 - version: link:../packages/astro-ghostcms-theme-default + version: 0.1.3(astro@4.2.4)(typescript@5.3.3) astro: specifier: ^4.2.4 version: 4.2.4(sass@1.70.0)(typescript@5.3.3) @@ -120,7 +120,7 @@ importers: version: link:../astro-ghostcms astro: specifier: ^4.2.1 - version: 4.2.3(typescript@5.3.3) + version: 4.2.3(sass@1.70.0)(typescript@5.3.3) astro-font: specifier: ^0.0.77 version: 0.0.77 @@ -128,6 +128,9 @@ importers: '@astrojs/check': specifier: ^0.4.1 version: 0.4.1(prettier-plugin-astro@0.13.0)(prettier@3.2.4)(typescript@5.3.3) + sass: + specifier: ^1.70.0 + version: 1.70.0 typescript: specifier: ^5.3.3 version: 5.3.3 @@ -1405,6 +1408,53 @@ packages: read-yaml-file: 1.1.0 dev: true + /@matthiesenxyz/astro-ghostcms-theme-default@0.1.3(astro@4.2.4)(typescript@5.3.3): + resolution: {integrity: sha512-ZWo5L5ZSYTWYcQjauDEQBiyH2nqnmoBs8F/cCttRzE6ms5LrWQ65nMuWQQomp5j/kMBBn2upVIi6bEUgoCxt6w==} + peerDependencies: + astro: ^4.2.1 + dependencies: + '@matthiesenxyz/astro-ghostcms': 3.1.4(astro@4.2.4)(typescript@5.3.3) + astro: 4.2.4(sass@1.70.0)(typescript@5.3.3) + astro-font: 0.0.77 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + - typescript + dev: false + + /@matthiesenxyz/astro-ghostcms@3.1.4(astro@4.2.4)(typescript@5.3.3): + resolution: {integrity: sha512-8jXKKG8IhL1M97+p4rjh8gW+6WM0uzQQEA6SARn68HtB3w7rek3rzfnbf/OsXrP9H8LHgYqAUFvZ1AYMWYVnJw==} + peerDependencies: + astro: ^4.2.3 + dependencies: + '@astrojs/rss': 4.0.3 + '@astrojs/sitemap': 3.0.5 + '@matthiesenxyz/astro-ghostcms-theme-default': 0.1.3(astro@4.2.4)(typescript@5.3.3) + '@ts-ghost/core-api': 5.1.2 + astro: 4.2.4(sass@1.70.0)(typescript@5.3.3) + astro-robots-txt: 1.0.0 + vite: 5.0.12(sass@1.70.0) + vite-tsconfig-paths: 4.3.1(typescript@5.3.3)(vite@5.0.12) + zod: 3.22.4 + zod-validation-error: 3.0.0(zod@3.22.4) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + - typescript + dev: false + /@mdx-js/mdx@3.0.0: resolution: {integrity: sha512-Icm0TBKBLYqroYbNW3BPnzMGn+7mwpQOK310aZ7+fkCtiU3aqv2cdcX+nd0Ydo3wI5Rx8bX2Z2QmGb/XcAClCw==} dependencies: @@ -2176,7 +2226,7 @@ packages: zod: 3.22.4 dev: false - /astro@4.2.3(typescript@5.3.3): + /astro@4.2.3(sass@1.70.0)(typescript@5.3.3): resolution: {integrity: sha512-6bfSogmcwMdaTRAxuhJ7aISGin/T3ovI/69JWPRYOHBkPZxA/EfsNQOI2TiRHFJSF9XtoMnFlgvT+iYapkhOwg==} engines: {node: '>=18.14.1', npm: '>=6.14.0'} hasBin: true