From 12f46121c481832b01636fc792fa22c50d7c1e96 Mon Sep 17 00:00:00 2001 From: prototypa Date: Thu, 27 Jul 2023 21:49:32 -0400 Subject: [PATCH] Add support for config in yaml --- .vscode/astrowind/config-schema.json | 443 +++++++++++++++++++++++++++ .vscode/settings.json | 3 + package.json | 3 + src/config.yaml | 107 +++++++ src/utils/config.ts | 204 ++++++++++++ 5 files changed, 760 insertions(+) create mode 100644 .vscode/astrowind/config-schema.json create mode 100644 src/config.yaml create mode 100644 src/utils/config.ts diff --git a/.vscode/astrowind/config-schema.json b/.vscode/astrowind/config-schema.json new file mode 100644 index 0000000..e596c07 --- /dev/null +++ b/.vscode/astrowind/config-schema.json @@ -0,0 +1,443 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "site": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "site": { + "type": "string" + }, + "base": { + "type": "string" + }, + "trailingSlash": { + "type": "boolean" + }, + "googleSiteVerificationId": { + "type": "string" + } + }, + "required": ["name", "site", "base", "trailingSlash"], + "additionalProperties": false + }, + "metadata": { + "type": "object", + "properties": { + "title": { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "template": { + "type": "string" + } + }, + "required": ["default", "template"] + }, + "description": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index", "follow"] + }, + "openGraph": { + "type": "object", + "properties": { + "siteName": { + "type": "string" + }, + "images": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "width": { + "type": "integer" + }, + "height": { + "type": "integer" + } + }, + "required": ["url", "width", "height"] + } + ] + }, + "type": { + "type": "string" + } + }, + "required": ["siteName", "images", "type"] + }, + "twitter": { + "type": "object", + "properties": { + "handle": { + "type": "string" + }, + "site": { + "type": "string" + }, + "cardType": { + "type": "string" + } + }, + "required": ["handle", "site", "cardType"] + } + }, + "required": ["title", "description", "robots", "openGraph", "twitter"] + }, + "i18n": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "textDirection": { + "type": "string" + } + }, + "required": ["language", "textDirection"] + }, + "apps": { + "type": "object", + "properties": { + "blog": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "postsPerPage": { + "type": "integer" + }, + "post": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "permalink": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index"] + } + }, + "required": ["isEnabled", "permalink", "robots"] + }, + "list": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "pathname": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index"] + } + }, + "required": ["isEnabled", "pathname", "robots"] + }, + "category": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "pathname": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index"] + } + }, + "required": ["isEnabled", "pathname", "robots"] + }, + "tag": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "pathname": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index"] + } + }, + "required": ["isEnabled", "pathname", "robots"] + } + }, + "required": ["isEnabled", "postsPerPage", "post", "list", "category", "tag"] + } + }, + "required": ["blog"] + }, + "analytics": { + "type": "object", + "properties": { + "vendors": { + "type": "object", + "properties": { + "googleAnalytics": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "id": { + "type": "null" + } + }, + "required": ["isEnabled", "id"] + } + }, + "required": ["googleAnalytics"] + } + }, + "required": ["vendors"] + }, + "ui": { + "type": "object", + "properties": { + "theme": { + "type": "string" + }, + "classes": { + "type": "object", + "properties": { + "html": { + "type": "null" + }, + "body": { + "type": "null" + }, + "heading1": { + "type": "null" + }, + "heading2": { + "type": "null" + }, + "button": { + "type": "null" + } + }, + "required": ["html", "body", "heading1", "heading2", "button"] + }, + "tokens": { + "type": "object", + "properties": { + "default": { + "type": "object", + "properties": { + "colors": { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "heading": { + "type": "string" + }, + "muted": { + "type": "string" + }, + "bgPage": { + "type": "string" + }, + "primary": { + "type": "string" + }, + "secondary": { + "type": "string" + }, + "accent": { + "type": "string" + }, + "info": { + "type": "string" + }, + "success": { + "type": "string" + }, + "warning": { + "type": "string" + }, + "error": { + "type": "string" + }, + "link": { + "type": "string" + }, + "linkActive": { + "type": "string" + } + }, + "required": [ + "default", + "heading", + "muted", + "bgPage", + "primary", + "secondary", + "accent", + "info", + "success", + "warning", + "error", + "link", + "linkActive" + ] + }, + "fonts": { + "type": "object", + "properties": { + "sans": { + "type": "string" + }, + "serif": { + "type": "string" + }, + "heading": { + "type": "string" + } + }, + "required": ["sans", "serif", "heading"] + } + }, + "required": ["colors", "fonts"] + }, + "dark": { + "type": "object", + "properties": { + "colors": { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "heading": { + "type": "string" + }, + "muted": { + "type": "string" + }, + "bgPage": { + "type": "string" + }, + "primary": { + "type": "string" + }, + "secondary": { + "type": "string" + }, + "accent": { + "type": "string" + }, + "info": { + "type": "string" + }, + "success": { + "type": "string" + }, + "warning": { + "type": "string" + }, + "error": { + "type": "string" + }, + "link": { + "type": "string" + }, + "linkActive": { + "type": "string" + } + }, + "required": [ + "default", + "heading", + "muted", + "bgPage", + "primary", + "secondary", + "accent", + "info", + "success", + "warning", + "error", + "link", + "linkActive" + ] + }, + "fonts": { + "type": "object" + } + }, + "required": ["colors", "fonts"] + } + }, + "required": ["default", "dark"] + } + }, + "required": ["theme", "classes", "tokens"] + } + }, + "required": ["site", "metadata", "i18n", "apps", "analytics", "ui"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 82d3e34..c0fb9bd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,8 @@ "prettier.documentSelectors": ["**/*.astro"], "[astro]": { "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "yaml.schemas": { + "./.vscode/astrowind/config-schema.json": "/src/config.yml" } } diff --git a/package.json b/package.json index 34c6447..5d09291 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@iconify-json/ri": "^1.1.10", "@iconify-json/tabler": "^1.1.85", "@tailwindcss/typography": "^0.5.9", + "@types/lodash.merge": "^4.6.7", "@typescript-eslint/eslint-plugin": "^6.2.0", "@typescript-eslint/parser": "^6.2.0", "astro": "^2.9.3", @@ -34,7 +35,9 @@ "eslint": "^8.45.0", "eslint-plugin-astro": "^0.27.2", "eslint-plugin-jsx-a11y": "^6.7.1", + "js-yaml": "^4.1.0", "limax": "2.1.0", + "lodash.merge": "^4.6.2", "mdast-util-to-string": "^4.0.0", "prettier": "^3.0.0", "prettier-plugin-astro": "^0.11.0", diff --git a/src/config.yaml b/src/config.yaml new file mode 100644 index 0000000..f41a2f4 --- /dev/null +++ b/src/config.yaml @@ -0,0 +1,107 @@ +site: + name: AstroWind + site: 'https://astrowind.vercel.app' + base: '/' + trailingSlash: false + + googleSiteVerificationId: orcPxI47GSa-cRvY11tUe6iGg2IO_RPvnA1q95iEM3M + +# Default SEO metadata +metadata: + title: + default: AstroWind + template: '%s — AstroWind' + description: "\U0001F680 Suitable for Startups, Small Business, Sass Websites, Professional Portfolios, Marketing Websites, Landing Pages & Blogs." + robots: + index: true + follow: true + openGraph: + siteName: AstroWind + images: + - url: '~/assets/images/default.jpg' + width: 1200 + height: 628 + type: website + twitter: + handle: '@onwidget' + site: '@onwidget' + cardType: summary_large_image + +i18n: + language: en + textDirection: ltr + +apps: + blog: + isEnabled: true + postsPerPage: 6 + + post: + isEnabled: true + permalink: '/%slug%' # Variables: %slug%, %year%, %month%, %day%, %hour%, %minute%, %second%, %category% + robots: + index: true + + list: + isEnabled: true + pathname: 'blog' # Blog main path, you can change this to "articles" (/articles) + robots: + index: true + + category: + isEnabled: true + pathname: 'category' # Category main path /category/some-category, you can change this to "group" (/group/some-category) + robots: + index: true + + tag: + isEnabled: true + pathname: 'tag' # Tag main path /tag/some-tag, you can change this to "topics" (/topics/some-category) + robots: + index: false + +analytics: + vendors: + googleAnalytics: + isEnabled: false + id: null # or "G-XXXXXXXXXX" + +ui: + theme: 'system' # Values: "system" | "light" | "dark" | "light:only" | "dark:only" + + tokens: + default: + fonts: + sans: InterVariable + serif: var(--ph-font-sans) + heading: var(--ph-font-sans) + colors: + default: rgb(16 16 16) + heading: rgb(0 0 0) + muted: rgb(40 40 40) + bgPage: rgb(255 255 255) + primary: rgb(0 124 220) + secondary: rgb(30 58 138) + accent: rgb(109 40 217) + info: rgb(119 182 234) + success: rgb(54 211 153) + warning: rgb(251 189 35) + error: rgb(248 114 114) + link: var(--ph-color-primary) + linkActive: var(--ph-color-link) + dark: + fonts: {} + colors: + default: rgb(247, 248, 248) + heading: rgb(247, 248, 248) + muted: rgb(200, 188, 208) + bgPage: rgb(3 6 32) + primary: rgb(29 78 216) + secondary: rgb(30 58 138) + accent: rgb(135 77 2267) + info: rgb(58 191 248) + success: rgb(54 211 153) + warning: rgb(251 189 35) + error: rgb(248 114 114) + link: var(--ph-color-primary) + linkActive: var(--ph-color-link) diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000..838a583 --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,204 @@ +import fs from 'fs'; +import yaml from 'js-yaml'; +import merge from 'lodash.merge'; + +import type { MetaSEO } from '~/types'; + +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: unknown; +} +export interface AppBlogConfig { + isEnabled: boolean; + postsPerPage: 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: { + isEnabled?: boolean; + id?: string; + }; + }; +} + +const config = yaml.load(fs.readFileSync('src/config.yaml', 'utf8')) as { + site?: SiteConfig; + metadata?: MetaDataConfig; + i18n?: I18NConfig; + apps?: { + blog?: AppBlogConfig; + }; + ui?: unknown; + analytics?: unknown; +}; + +const DEFAULT_SITE_NAME = 'Website'; + +const getSite = () => { + const _default = { + name: DEFAULT_SITE_NAME, + site: undefined, + base: '/', + trailingSlash: false, + + googleSiteVerificationId: '', + }; + + return merge({}, _default, config?.site ?? {}) as SiteConfig; +}; + +const getMetadata = () => { + const siteConfig = getSite(); + + 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 = () => { + const _default = { + language: 'en', + textDirection: 'ltr', + }; + + const value = merge({}, _default, config?.i18n ?? {}); + + return Object.assign(value, { + dateFormatter: new Intl.DateTimeFormat(value.language, { + year: 'numeric', + month: 'short', + day: 'numeric', + timeZone: 'UTC', + }), + }) as I18NConfig; +}; + +const getAppBlog = () => { + const _default = { + isEnabled: false, + postsPerPage: 6, + 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 = () => { + const _default = { + theme: 'system', + classes: {}, + tokens: {}, + }; + + return merge({}, _default, config?.ui ?? {}); +}; + +const getAnalytics = () => { + const _default = { + vendors: { + googleAnalytics: { + isEnabled: false, + id: undefined, + }, + }, + }; + + return merge({}, _default, config?.analytics ?? {}) as AnalyticsConfig; +}; + +export const SITE_CONFIG = getSite(); +export const I18N_CONFIG = getI18N(); +export const METADATA_CONFIG = getMetadata(); +export const APP_BLOG_CONFIG = getAppBlog(); +export const UI_CONFIG = getUI(); +export const ANALYTICS_CONFIG = getAnalytics();