diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 46ccb93c..aeaefaf6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -51,7 +51,7 @@ jobs: run: nx release version --specifier 0.0.0-${{ steps.vars.outputs.sha_short }} --git-tag=false - name: Publish Modules - run: pnpm publish --provenance --filter zudoku --filter create-zudoku-app --tag canary --no-git-checks + run: pnpm publish --provenance --filter zudoku --filter create-zudoku-app --filter config --tag canary --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} @@ -63,5 +63,3 @@ jobs: - name: Has Stats run: echo "exists=${{ hashFiles('./packages/zudoku/stats.html') != '' }}" >> $GITHUB_OUTPUT id: stats-file - - diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 074a4278..f32a3d1a 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -87,7 +87,7 @@ jobs: run: nx release version --specifier 0.0.0-${{ steps.vars.outputs.sha_short }} --git-tag=false - name: Publish Modules - run: pnpm publish --provenance --filter zudoku --filter create-zudoku-app --no-git-checks --tag ${{ github.event_name == 'push' && 'dev' || 'latest' }} + run: pnpm publish --provenance --filter zudoku --filter create-zudoku-app --filter config --no-git-checks --tag ${{ github.event_name == 'push' && 'dev' || 'latest' }} env: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 647e3f9b..90e97a9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,10 +32,10 @@ nx login ## Build -To build all projects run: +To build the project run: ``` -nx run-many -t build +nx run zudoku:build ``` ## Dev diff --git a/nx.json b/nx.json index 95c5b0df..1ba641a5 100644 --- a/nx.json +++ b/nx.json @@ -4,10 +4,10 @@ "parallel": 5, "targetDefaults": { "dev": { - "dependsOn": ["zudoku:build"] + "dependsOn": ["^build"] }, "build": { - "dependsOn": ["zudoku:build"], + "dependsOn": ["^build"], "outputs": ["{projectRoot}/dist"], "cache": false }, @@ -28,7 +28,7 @@ } ], "release": { - "projects": ["zudoku", "create-zudoku-app"], + "projects": ["zudoku", "create-zudoku-app", "config"], "releaseTagPattern": "v{version}", "versionPlans": true, "version": { diff --git a/package.json b/package.json index bbb02701..bbbcf299 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { + "build": "nx run-many -t=build -p zudoku config", "mismatches": "syncpack list-mismatches", "lint": "eslint --cache --fix .", "lint:ci": "eslint .", diff --git a/packages/config/.gitignore b/packages/config/.gitignore new file mode 100644 index 00000000..6ee35cf6 --- /dev/null +++ b/packages/config/.gitignore @@ -0,0 +1,5 @@ +/dist +/lib +/standalone + +stats.html \ No newline at end of file diff --git a/packages/config/package.json b/packages/config/package.json new file mode 100644 index 00000000..b34d7080 --- /dev/null +++ b/packages/config/package.json @@ -0,0 +1,35 @@ +{ + "name": "@zudoku/config", + "type": "module", + "version": "0.14.1", + "keywords": [ + "zudoku" + ], + "description": "Zudoku configuration loader", + "repository": { + "type": "git", + "url": "https://github.com/zuplo/zudoku", + "directory": "packages/config" + }, + "author": "Zuplo ", + "license": "MIT", + "exports": { + ".": "./dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc" + }, + "engines": { + "node": ">=20" + }, + "dependencies": { + "tsx": "4.19.1" + }, + "devDependencies": { + "@types/node": "20.16.11", + "typescript": "5.6.3" + } +} diff --git a/packages/config/project.json b/packages/config/project.json new file mode 100644 index 00000000..edfb69b7 --- /dev/null +++ b/packages/config/project.json @@ -0,0 +1,18 @@ +{ + "name": "config", + "$schema": "../../node_modules/nx/schemas/nx-schema.json", + "targets": { + "build": { + "inputs": [ + "{projectRoot}/**/*.ts", + "{projectRoot}/tsconfig.json", + "{projectRoot}/package.json" + ], + "outputs": ["{projectRoot}/dist"], + "cache": true + }, + "build:ci": { + "dependsOn": ["build"] + } + } +} diff --git a/packages/config/src/config.ts b/packages/config/src/config.ts new file mode 100644 index 00000000..fdfe4825 --- /dev/null +++ b/packages/config/src/config.ts @@ -0,0 +1,74 @@ +import { stat } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { tsImport } from "tsx/esm/api"; + +export const zudokuConfigFiles = [ + "zudoku.config.js", + "zudoku.config.jsx", + "zudoku.config.ts", + "zudoku.config.tsx", + "zudoku.config.mjs", +]; + +const fileExists = (path: string) => + stat(path) + .then(() => true) + .catch(() => false); + +let configPath: string | undefined; + +async function getConfigFilePath(rootDir: string): Promise { + // Also check if file exists, so renaming the file will trigger a restart as well + if (configPath && (await fileExists(configPath))) { + return configPath; + } + + for (const fileName of zudokuConfigFiles) { + const filepath = path.join(rootDir, fileName); + + if (await fileExists(filepath)) { + configPath = filepath; + return filepath; + } + } + configPath = undefined; + throw new Error(`No zudoku config file found in project root.`); +} + +export type ConfigWithMeta = TConfig & { + __meta: { dependencies: string[]; path: string }; +}; + +export async function loadZudokuConfig( + rootDir: string, +): Promise> { + const filepath = await getConfigFilePath(rootDir); + + const configFilePath = pathToFileURL(filepath).href; + + const dependencies: string[] = []; + const loadedConfig = await tsImport(configFilePath, { + parentURL: import.meta.url, + onImport: (file: string) => { + const path = fileURLToPath( + file.startsWith("file://") ? file : pathToFileURL(file).href, + ); + + if (path.startsWith(rootDir)) { + dependencies.push(path); + } + }, + }).then((m) => m.default as TConfig); + + if (!loadedConfig) { + throw new Error(`Failed to load config file: ${filepath}`); + } + + const config: ConfigWithMeta = { + ...loadedConfig, + __meta: { dependencies, path: filepath }, + }; + + return config; +} diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts new file mode 100644 index 00000000..1ee021ae --- /dev/null +++ b/packages/config/src/index.ts @@ -0,0 +1,5 @@ +export { + ConfigWithMeta, + loadZudokuConfig, + zudokuConfigFiles, +} from "./config.js"; diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json new file mode 100644 index 00000000..e5f65a7f --- /dev/null +++ b/packages/config/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["ESNext"], + "module": "NodeNext", + "moduleResolution": "nodenext", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "sourceMap": true, + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/zudoku/package.json b/packages/zudoku/package.json index 65c67f3e..3fb24afa 100644 --- a/packages/zudoku/package.json +++ b/packages/zudoku/package.json @@ -173,6 +173,7 @@ "@types/react": "18.3.11", "@types/react-dom": "18.3.1", "@vitejs/plugin-react": "4.3.1", + "@zudoku/config": "workspace:*", "@zudoku/httpsnippet": "10.0.9", "@zudoku/react-helmet-async": "2.0.4", "autoprefixer": "10.4.20", @@ -219,7 +220,6 @@ "strip-ansi": "7.1.0", "tailwind-merge": "2.5.4", "tailwindcss": "3.4.13", - "tsx": "4.19.1", "ulidx": "2.4.1", "unist-util-visit": "5.0.0", "urql": "4.1.0", diff --git a/packages/zudoku/project.json b/packages/zudoku/project.json index 2a940971..82617f42 100644 --- a/packages/zudoku/project.json +++ b/packages/zudoku/project.json @@ -22,35 +22,45 @@ } }, "build": { + "dependsOn": ["^build"], "outputs": ["{projectRoot}/dist"], "inputs": [ "{projectRoot}/**/*.ts", "{projectRoot}/**/*.tsx", - "!{projectRoot}/**/*.css" + "!{projectRoot}/**/*.css", + "{projectRoot}/tsconfig.json", + "{projectRoot}/package.json" ], "cache": true }, "build:vite": { + "dependsOn": ["^build"], "outputs": ["{projectRoot}/lib", "{projectRoot}/stats.html"], "inputs": [ "{projectRoot}/vite.config.ts", "{projectRoot}/**/*.ts", "{projectRoot}/**/*.tsx", - "!{projectRoot}/**/*.css" + "!{projectRoot}/**/*.css", + "{projectRoot}/tsconfig.json", + "{projectRoot}/package.json" ], "cache": true }, "build:standalone:vite": { + "dependsOn": ["^build"], "outputs": ["{projectRoot}/standalone"], "inputs": [ "{projectRoot}/vite.standalone.config.ts", "{projectRoot}/**/*.ts", "{projectRoot}/**/*.tsx", - "!{projectRoot}/**/*.css" + "!{projectRoot}/**/*.css", + "{projectRoot}/tsconfig.json", + "{projectRoot}/package.json" ], "cache": true }, "build:standalone:html": { + "dependsOn": ["^build"], "dependsOn": ["build:standalone:vite"] }, "codegen": {} diff --git a/packages/zudoku/src/cli/cli.ts b/packages/zudoku/src/cli/cli.ts index bc87537a..a36f1742 100644 --- a/packages/zudoku/src/cli/cli.ts +++ b/packages/zudoku/src/cli/cli.ts @@ -62,8 +62,7 @@ if (gte(process.versions.node, MIN_NODE_VERSION)) { if (err instanceof Error) { Sentry.captureException(err); } - await printCriticalFailureToConsoleAndExit(err.message ?? err); - cli.showHelp(); + throw err; } finally { await shutdownAnalytics(); } diff --git a/packages/zudoku/src/vite/config.ts b/packages/zudoku/src/vite/config.ts index 85da2ae1..088e045a 100644 --- a/packages/zudoku/src/vite/config.ts +++ b/packages/zudoku/src/vite/config.ts @@ -1,11 +1,13 @@ import { vitePluginSsrCss } from "@hiogawa/vite-plugin-ssr-css"; +import { + ConfigWithMeta, + loadZudokuConfig as loadZudokuConfigInner, +} from "@zudoku/config"; import autoprefixer from "autoprefixer"; -import { stat } from "node:fs/promises"; import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; +import { fileURLToPath } from "node:url"; import colors from "picocolors"; import tailwindcss from "tailwindcss"; -import { tsImport } from "tsx/esm/api"; import { type ConfigEnv, type InlineConfig, @@ -20,47 +22,12 @@ import type { ZudokuConfig, ZudokuPluginOptions } from "../config/config.js"; import { validateConfig } from "../config/validators/validate.js"; import vitePlugin from "./plugin.js"; -export const zudokuConfigFiles = [ - "zudoku.config.js", - "zudoku.config.jsx", - "zudoku.config.ts", - "zudoku.config.tsx", - "zudoku.config.mjs", -]; - -const fileExists = (path: string) => - stat(path) - .then(() => true) - .catch(() => false); - -let configPath: string | undefined; - -export async function getConfigFilePath(rootDir: string): Promise { - // Also check if file exists, so renaming the file will trigger a restart as well - if (configPath && (await fileExists(configPath))) { - return configPath; - } - - for (const fileName of zudokuConfigFiles) { - const filepath = path.join(rootDir, fileName); - - if (await fileExists(filepath)) { - configPath = filepath; - return filepath; - } - } - configPath = undefined; - throw new Error(`No zudoku config file found in project root.`); -} - export type ZudokuConfigEnv = ConfigEnv & { mode: "development" | "production"; forceReload?: boolean; }; -export type LoadedConfig = ZudokuConfig & { - __meta: { dependencies: string[]; path: string }; -}; +export type LoadedConfig = ConfigWithMeta; let config: LoadedConfig | undefined; @@ -82,41 +49,25 @@ export async function loadZudokuConfig( return config; } - const filepath = await getConfigFilePath(rootDir); - try { - logger.info(colors.yellow(`loaded config file `) + colors.dim(filepath), { - timestamp: true, - }); - - const configFilePath = pathToFileURL(filepath).href; - - const dependencies: string[] = []; - const loadedConfig = await tsImport(configFilePath, { - parentURL: import.meta.url, - onImport: (file: string) => { - const path = fileURLToPath( - file.startsWith("file://") ? file : pathToFileURL(file).href, - ); + const loadedConfig = await loadZudokuConfigInner(rootDir); - if (path.startsWith(rootDir)) { - dependencies.push(path); - } + logger.info( + colors.yellow(`loaded config file `) + + colors.dim(loadedConfig.__meta.path), + { + timestamp: true, }, - }).then((m) => m.default as ZudokuConfig); - - if (!loadedConfig) { - throw new Error(`Failed to load config file: ${filepath}`); - } + ); - config = { - ...loadedConfig, - __meta: { dependencies, path: filepath }, - }; + config = loadedConfig; - return config; - } catch (e) { - logger.error(e); + return loadedConfig; + } catch (error) { + logger.error(colors.red(`Error loading Zudoku config`), { + timestamp: true, + error, + }); } // Default config @@ -241,7 +192,7 @@ export async function getViteConfig( input: configEnv.command === "build" ? configEnv.isSsrBuild - ? ["zudoku/app/entry.server.tsx", configPath!] + ? ["zudoku/app/entry.server.tsx", config.__meta.path] : "zudoku/app/entry.client.tsx" : undefined, }, diff --git a/packages/zudoku/src/vite/plugin-config.ts b/packages/zudoku/src/vite/plugin-config.ts index 2429c2a9..331ecfe8 100644 --- a/packages/zudoku/src/vite/plugin-config.ts +++ b/packages/zudoku/src/vite/plugin-config.ts @@ -1,14 +1,14 @@ import { type Plugin } from "vite"; -import { getConfigFilePath } from "./config.js"; +import type { ZudokuPluginOptions } from "../config/config.js"; -const viteConfigPlugin = ({ rootDir }: { rootDir: string }): Plugin => { +const viteConfigPlugin = (getConfig: () => ZudokuPluginOptions): Plugin => { const virtualModuleId = "virtual:zudoku-config"; return { name: "zudoku-config-plugin", resolveId(id) { if (id === virtualModuleId) { - return getConfigFilePath(rootDir); + return getConfig().__meta.path; } }, }; diff --git a/packages/zudoku/src/vite/plugin.ts b/packages/zudoku/src/vite/plugin.ts index 2b77dbea..4eb70c67 100644 --- a/packages/zudoku/src/vite/plugin.ts +++ b/packages/zudoku/src/vite/plugin.ts @@ -30,7 +30,7 @@ export default function vitePlugin( return [ viteMdxPlugin(getCurrentConfig), react({ include: /\.(mdx?|jsx?|tsx?)$/ }), - viteConfigPlugin(initialConfig), + viteConfigPlugin(getCurrentConfig), viteApiKeysPlugin(getCurrentConfig), viteCustomPagesPlugin(getCurrentConfig), viteAuthPlugin(getCurrentConfig), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3bb3b5b8..a3cd9aa0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -221,6 +221,19 @@ importers: specifier: workspace:* version: link:../../packages/zudoku + packages/config: + dependencies: + tsx: + specifier: 4.19.1 + version: 4.19.1 + devDependencies: + '@types/node': + specifier: 20.16.11 + version: 20.16.11 + typescript: + specifier: 5.6.3 + version: 5.6.3 + packages/create-zudoku-app: devDependencies: '@types/async-retry': @@ -394,6 +407,9 @@ importers: '@vitejs/plugin-react': specifier: 4.3.1 version: 4.3.1(patch_hash=jb2tifk2jsavdxlrb3gpff2s3i)(vite@5.4.9(@types/node@20.16.11)) + '@zudoku/config': + specifier: workspace:* + version: link:../config '@zudoku/httpsnippet': specifier: 10.0.9 version: 10.0.9 @@ -532,9 +548,6 @@ importers: tailwindcss: specifier: 3.4.13 version: 3.4.13(ts-node@10.9.1(@types/node@20.16.11)(typescript@5.6.3)) - tsx: - specifier: 4.19.1 - version: 4.19.1 ulidx: specifier: 2.4.1 version: 2.4.1