diff --git a/src/env.d.ts b/src/env.d.ts index c47f586..b0185e3 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -2,3 +2,4 @@ /// /// /// +/// diff --git a/src/integration/README.md b/src/integration/README.md new file mode 100644 index 0000000..b3fd285 --- /dev/null +++ b/src/integration/README.md @@ -0,0 +1,4 @@ +This folder will become an integration for **AstroWind**. + +We are working to allow updates to template instances. +These are changes on the way to new **AstroWind v2** \ No newline at end of file diff --git a/src/integration/index.mjs b/src/integration/index.mjs new file mode 100644 index 0000000..3a12e46 --- /dev/null +++ b/src/integration/index.mjs @@ -0,0 +1,115 @@ +import fs from 'node:fs'; +import os from 'node:os'; + +import yaml from 'js-yaml'; + +import configBuilder from "./utils/configBuilder" + +const tasksIntegration = () => { + let config; + return { + name: 'AstroWind:tasks', + + hooks: { + 'astro:config:setup': async ({ + // command, + config, + // injectRoute, + // isRestart, + logger, + updateConfig, + addWatchFile + }) => { + + const buildLogger = logger.fork("astrowind"); + + const virtualModuleId = 'astrowind:config'; + const resolvedVirtualModuleId = '\0' + virtualModuleId; + + const fileConfig = yaml.load(fs.readFileSync('src/config.yaml', 'utf8')); + const { SITE, I18N, METADATA, APP_BLOG, UI, ANALYTICS } = configBuilder(fileConfig); + + updateConfig({ + site: SITE.site, + base: SITE.base, + + trailingSlash: SITE.trailingSlash ? 'always' : 'never', + + vite: { + plugins: [ + { + name: 'vite-plugin-astrowind-config', + resolveId(id) { + if (id === virtualModuleId) { + return resolvedVirtualModuleId; + } + }, + load(id) { + if (id === resolvedVirtualModuleId) { + return ` + export const SITE = ${JSON.stringify(SITE)}; + export const I18N = ${JSON.stringify(I18N)}; + export const METADATA = ${JSON.stringify(METADATA)}; + export const APP_BLOG = ${JSON.stringify(APP_BLOG)}; + export const UI = ${JSON.stringify(UI)}; + export const ANALYTICS = ${JSON.stringify(ANALYTICS)}; + `; + } + }, + }, + ], + }, + }); + + addWatchFile(new URL('./src/config.yaml', config.root)); + + buildLogger.info("Astrowind `src/config.yaml` has been loaded.") + }, + 'astro:config:done': async ({ config: cfg }) => { + config = cfg; + }, + + 'astro:build:done': async ({ logger }) => { + + const buildLogger = logger.fork("astrowind"); + buildLogger.info("Updating `robots.txt` with `sitemap-index.xml` ...") + + try { + const outDir = config.outDir; + const publicDir = config.publicDir; + const sitemapName = 'sitemap-index.xml'; + const sitemapFile = new URL(sitemapName, outDir); + const robotsTxtFile = new URL('robots.txt', publicDir); + const robotsTxtFileInOut = new URL('robots.txt', outDir); + + const hasIntegration = + Array.isArray(config?.integrations) && + config.integrations?.find((e) => e?.name === '@astrojs/sitemap') !== undefined; + const sitemapExists = fs.existsSync(sitemapFile); + + if (hasIntegration && sitemapExists) { + const robotsTxt = fs.readFileSync(robotsTxtFile, { encoding: 'utf8', flags: 'a+' }); + const sitemapUrl = new URL(sitemapName, String(new URL(config.base, config.site))); + const pattern = /^Sitemap:(.*)$/m; + + if (!pattern.test(robotsTxt)) { + fs.appendFileSync(robotsTxtFileInOut, `${os.EOL}${os.EOL}Sitemap: ${sitemapUrl}`, { + encoding: 'utf8', + flags: 'w', + }); + } else { + fs.writeFileSync(robotsTxtFileInOut, robotsTxt.replace(pattern, `Sitemap: ${sitemapUrl}`), { + encoding: 'utf8', + flags: 'w', + }); + } + } + } catch (err) { + /* empty */ + } + }, + }, + }; +}; + +export default tasksIntegration; diff --git a/src/integration/types.d.ts b/src/integration/types.d.ts new file mode 100644 index 0000000..a8cac31 --- /dev/null +++ b/src/integration/types.d.ts @@ -0,0 +1,10 @@ +declare module 'astrowind:config' { + import type { SiteConfig, I18NConfig, MetaDataConfig, AppBlogConfig, UIConfig, AnalyticsConfig } from "./config" + + export const SITE: SiteConfig; + export const I18N : I18NConfig; + export const METADATA: MetaDataConfig; + export const APP_BLOG : AppBlogConfig; + export const UI : UIConfig; + export const ANALYTICS : AnalyticsConfig; +} \ No newline at end of file diff --git a/src/integration/utils/configBuilder.ts b/src/integration/utils/configBuilder.ts new file mode 100644 index 0000000..18df411 --- /dev/null +++ b/src/integration/utils/configBuilder.ts @@ -0,0 +1,228 @@ +import merge from 'lodash.merge'; + +import type { MetaData } from '~/types'; + +type Config = { + site?: SiteConfig; + metadata?: MetaDataConfig; + i18n?: I18NConfig; + apps?: { + blog?: AppBlogConfig; + }; + ui?: unknown; + analytics?: unknown; +}; + +export interface SiteConfig { + name: string; + site?: string; + base?: string; + trailingSlash?: boolean; + googleSiteVerificationId?: string; +} +export interface MetaDataConfig extends Omit { + title?: { + default: string; + template: string; + }; +} +export interface I18NConfig { + language: string; + textDirection: string; + dateFormatter?: Intl.DateTimeFormat; +} +export interface AppBlogConfig { + isEnabled: boolean; + postsPerPage: number; + isRelatedPostsEnabled: boolean; + relatedPostsCount: number; + post: { + isEnabled: boolean; + permalink: string; + robots: { + index: boolean; + follow: boolean; + }; + }; + list: { + isEnabled: boolean; + pathname: string; + robots: { + index: boolean; + follow: boolean; + }; + }; + category: { + isEnabled: boolean; + pathname: string; + robots: { + index: boolean; + follow: boolean; + }; + }; + tag: { + isEnabled: boolean; + pathname: string; + robots: { + index: boolean; + follow: boolean; + }; + }; +} +export interface AnalyticsConfig { + vendors: { + googleAnalytics: { + id?: string; + partytown?: boolean; + }; + }; +} + +export interface UIConfig {} + +const DEFAULT_SITE_NAME = 'Website'; + +const getSite = (config: Config) => { + const _default = { + name: DEFAULT_SITE_NAME, + site: undefined, + base: '/', + trailingSlash: false, + + googleSiteVerificationId: '', + }; + + return merge({}, _default, config?.site ?? {}) as SiteConfig; +}; + +const getMetadata = (config: Config) => { + const siteConfig = getSite(config); + + const _default = { + title: { + default: siteConfig?.name || DEFAULT_SITE_NAME, + template: '%s', + }, + description: '', + robots: { + index: false, + follow: false, + }, + openGraph: { + type: 'website', + }, + }; + + return merge({}, _default, config?.metadata ?? {}) as MetaDataConfig; +}; + +const getI18N = (config: Config) => { + const _default = { + language: 'en', + textDirection: 'ltr', + }; + + const value = merge({}, _default, config?.i18n ?? {}); + + return value as I18NConfig; +}; + +const getAppBlog = (config: Config) => { + const _default = { + isEnabled: false, + postsPerPage: 6, + isRelatedPostsEnabled: false, + relatedPostsCount: 4, + post: { + isEnabled: true, + permalink: '/blog/%slug%', + robots: { + index: true, + follow: true, + }, + }, + list: { + isEnabled: true, + pathname: 'blog', + robots: { + index: true, + follow: true, + }, + }, + category: { + isEnabled: true, + pathname: 'category', + robots: { + index: true, + follow: true, + }, + }, + tag: { + isEnabled: true, + pathname: 'tag', + robots: { + index: false, + follow: true, + }, + }, + }; + + return merge({}, _default, config?.apps?.blog ?? {}) as AppBlogConfig; +}; + +const getUI = (config: Config) => { + const _default = { + theme: 'system', + classes: {}, + tokens: { + default: { + fonts: {}, + colors: { + default: 'rgb(16 16 16)', + heading: 'rgb(0 0 0)', + muted: 'rgb(16 16 16 / 66%)', + bgPage: 'rgb(255 255 255)', + primary: 'rgb(1 97 239)', + secondary: 'rgb(1 84 207)', + accent: 'rgb(109 40 217)', + }, + }, + dark: { + fonts: {}, + colors: { + default: 'rgb(229 236 246)', + heading: 'rgb(247, 248, 248)', + muted: 'rgb(229 236 246 / 66%)', + bgPage: 'rgb(3 6 32)', + primary: 'rgb(1 97 239)', + secondary: 'rgb(1 84 207)', + accent: 'rgb(109 40 217)', + }, + }, + }, + }; + + return merge({}, _default, config?.ui ?? {}); +}; + +const getAnalytics = (config: Config) => { + const _default = { + vendors: { + googleAnalytics: { + id: undefined, + partytown: true, + }, + }, + }; + + return merge({}, _default, config?.analytics ?? {}) as AnalyticsConfig; +}; + +export default (config: Config) => ({ + SITE: getSite(config), + I18N: getI18N(config), + METADATA: getMetadata(config), + APP_BLOG: getAppBlog(config), + UI: getUI(config), + ANALYTICS: getAnalytics(config), +});