From 59cb1c3fcd902a675ece13454744a3d6493a5d7e Mon Sep 17 00:00:00 2001 From: Aral Roca Gomez Date: Sun, 3 Nov 2024 13:15:40 +0100 Subject: [PATCH] fix(windows): fix build error with unnormalized brisa dir (#603) * refactor: create load-constants helper * fix: normalize argv[1] pathname to work on Windows --- packages/brisa/src/constants.ts | 162 +---------------- packages/brisa/src/types/index.d.ts | 45 ++--- .../src/utils/load-constants/index.test.ts | 43 +++++ .../brisa/src/utils/load-constants/index.ts | 168 ++++++++++++++++++ 4 files changed, 243 insertions(+), 175 deletions(-) create mode 100644 packages/brisa/src/utils/load-constants/index.test.ts create mode 100644 packages/brisa/src/utils/load-constants/index.ts diff --git a/packages/brisa/src/constants.ts b/packages/brisa/src/constants.ts index 1219db9a..6b7ccc6b 100644 --- a/packages/brisa/src/constants.ts +++ b/packages/brisa/src/constants.ts @@ -1,110 +1,14 @@ -import path from 'node:path'; -import type { BunPlugin } from 'bun'; -import { version } from '../package.json'; import type { BrisaConstants, I18nConfig } from './types'; -import importFileIfExists from './utils/import-file-if-exists'; import { - blueLog, - cyanLog, - greenLog, - redLog, - yellowLog, -} from './utils/log/log-color'; + internalConstants, + loadProjectConstants, +} from './utils/load-constants'; -const { NODE_ENV } = process.env; - -// Note: process.env.IS_PROD is to be defined in the build process -const IS_PRODUCTION = - Boolean(process.env.IS_PROD) || - NODE_ENV === 'production' || - process.argv.some((t) => t === 'PROD'); - -const CLI_DIR = path.join('brisa', 'out', 'cli'); -const IS_SERVE_PROCESS = - Boolean(process.env.IS_SERVE_PROCESS) || - Boolean(process.argv[1]?.endsWith?.(path.join(CLI_DIR, 'serve', 'index.js'))); - -const IS_STANDALONE_SERVER = Boolean(process.env.IS_STANDALONE_SERVER); - -const IS_BUILD_PROCESS = Boolean( - process.argv[1]?.endsWith?.(path.join(CLI_DIR, 'build.js')), -); - -const BRISA_DIR = process.argv[1]?.replace(new RegExp(`${CLI_DIR}.*`), 'brisa'); - -const rootDir = IS_STANDALONE_SERVER ? import.meta.dirname : process.cwd(); -const staticExportOutputOption = new Set([ - 'static', - 'desktop', - 'android', - 'ios', -]); - -const srcDir = IS_STANDALONE_SERVER - ? import.meta.dirname - : path.resolve(rootDir, 'src'); - -const buildDir = IS_STANDALONE_SERVER - ? import.meta.dirname - : (process.env.BRISA_BUILD_FOLDER ?? path.resolve(rootDir, 'build')); - -const WORKSPACE = IS_BUILD_PROCESS ? srcDir : buildDir; - -const PAGE_404 = '/_404'; -const PAGE_500 = '/_500'; -const OS_CAN_LOAD_BALANCE = - process.platform !== 'darwin' && process.platform !== 'win32'; - -const CACHE_CONTROL = IS_PRODUCTION - ? 'public, max-age=31536000, immutable' - : 'no-store, must-revalidate'; - -const defaultConfig = { - trailingSlash: false, - assetPrefix: '', - basePath: '', - extendPlugins: (plugins: BunPlugin[]) => plugins, - output: 'bun', - clustering: IS_PRODUCTION && OS_CAN_LOAD_BALANCE, - integrations: [], - idleTimeout: 30, -}; +const internal = internalConstants(); let constants = { - JS_RUNTIME: typeof Bun !== 'undefined' ? 'bun' : 'node', - PAGE_404, - PAGE_500, - VERSION: version, - RESERVED_PAGES: [PAGE_404, PAGE_500], - IS_PRODUCTION, - IS_DEVELOPMENT: - process.argv.some((t) => t === 'DEV') || NODE_ENV === 'development', - IS_SERVE_PROCESS, - IS_BUILD_PROCESS, - PORT: Number.parseInt(process.argv[2]) || 3000, - BUILD_DIR: buildDir, - ROOT_DIR: rootDir, - BRISA_DIR, - SRC_DIR: srcDir, - ASSETS_DIR: path.resolve(buildDir, 'public'), - PAGES_DIR: path.resolve(buildDir, 'pages'), - LOG_PREFIX: { - WAIT: cyanLog('[ wait ]') + ' ', - READY: greenLog('[ ready ] ') + ' ', - INFO: blueLog('[ info ] ') + ' ', - ERROR: redLog('[ error ] ') + ' ', - WARN: yellowLog('[ warn ] ') + ' ', - TICK: greenLog('✓ ') + ' ', - }, - REGEX: { - CATCH_ALL: /\[\[\.{3}.*?\]\]/g, - DYNAMIC: /\[.*?\]/g, - REST_DYNAMIC: /\[\.{3}.*?\]/g, - }, - HEADERS: { - CACHE_CONTROL, - }, - ...(await loadDynamicConstants()), + ...internal, + ...(await loadProjectConstants(internal)), } satisfies BrisaConstants; /** @@ -119,63 +23,11 @@ export const getConstants = (): BrisaConstants => ? (globalThis.mockConstants as typeof constants) : constants; -async function loadDynamicConstants() { - const binaryExternalLibs = ['lightningcss']; - const CSS_FILES = - (await importFileIfExists('css-files', buildDir))?.default ?? []; - const integrations = await importFileIfExists( - '_integrations', - path.resolve(buildDir, 'web-components'), - ); - const WEB_CONTEXT_PLUGINS = integrations?.webContextPlugins ?? []; - const I18N_CONFIG = (await importFileIfExists('i18n', WORKSPACE)) - ?.default as I18nConfig; - const CONFIG = { - ...defaultConfig, - ...((await importFileIfExists('brisa.config', rootDir))?.default ?? {}), - }; - const IS_STATIC_EXPORT = staticExportOutputOption.has(CONFIG?.output); - - // Remove trailing slash from pages - if (I18N_CONFIG?.pages) { - I18N_CONFIG.pages = JSON.parse( - JSON.stringify(I18N_CONFIG.pages, (key, value) => - typeof value === 'string' && value.length > 1 - ? value.replace(/\/$/g, '') - : value, - ), - ); - } - - const LOCALES_SET = new Set(I18N_CONFIG?.locales || []) as Set; - - if (CONFIG.basePath && !CONFIG.basePath.startsWith(path.sep)) { - CONFIG.basePath = path.sep + CONFIG.basePath; - } - - // Add external libraries to the list of external libraries - if (!CONFIG.external) CONFIG.external = binaryExternalLibs; - else CONFIG.external = [...CONFIG.external, ...binaryExternalLibs]; - - // This is needed for some helpers like "navigate" to work properly - // in the server side. (For the client-side it's solved during the build process) - globalThis.__BASE_PATH__ = CONFIG.basePath; - - return { - CSS_FILES, - CONFIG, - I18N_CONFIG, - WEB_CONTEXT_PLUGINS, - LOCALES_SET, - IS_STATIC_EXPORT, - }; -} - // Update all that can change during hotreloading export async function reinitConstants() { constants = globalThis.mockConstants = { ...constants, - ...(await loadDynamicConstants()), + ...(await loadProjectConstants(internal)), }; } diff --git a/packages/brisa/src/types/index.d.ts b/packages/brisa/src/types/index.d.ts index 87e0d098..bfef3025 100644 --- a/packages/brisa/src/types/index.d.ts +++ b/packages/brisa/src/types/index.d.ts @@ -28,29 +28,24 @@ declare module 'bun' { } } -/** - * Internal types used by Brisa and output adapters. - */ -export type BrisaConstants = { +export type InternalConstants = { + BRISA_DIR: string; + IS_BUILD_PROCESS: boolean; + IS_SERVE_PROCESS: boolean; + WORKSPACE: string; + SRC_DIR: string; + BUILD_DIR: string; + ROOT_DIR: string; + IS_PRODUCTION: boolean; + IS_DEVELOPMENT: boolean; JS_RUNTIME: 'bun' | 'node'; PAGE_404: string; PAGE_500: string; VERSION: string; - WEB_CONTEXT_PLUGINS: string[]; RESERVED_PAGES: string[]; - IS_PRODUCTION: boolean; - IS_DEVELOPMENT: boolean; - IS_SERVE_PROCESS: boolean; - IS_BUILD_PROCESS: boolean; PORT: number; - BUILD_DIR: string; - BRISA_DIR?: string; - ROOT_DIR: string; - SRC_DIR: string; ASSETS_DIR: string; PAGES_DIR: string; - I18N_CONFIG: I18nConfig; - CSS_FILES: string[]; LOG_PREFIX: { WAIT: string; READY: string; @@ -59,15 +54,27 @@ export type BrisaConstants = { WARN: string; TICK: string; }; - LOCALES_SET: Set; - CONFIG: Configuration; - IS_STATIC_EXPORT: boolean; REGEX: Record; HEADERS: { CACHE_CONTROL: string; }; }; +// Project constants need to be refresed on hot-reload (DEV) +export type ProjectConstants = { + CSS_FILES: string[]; + CONFIG: Configuration; + I18N_CONFIG: I18nConfig; + WEB_CONTEXT_PLUGINS: any[]; + LOCALES_SET: Set; + IS_STATIC_EXPORT: boolean; +}; + +/** + * Internal types used by Brisa and output adapters. + */ +export type BrisaConstants = InternalConstants & ProjectConstants; + /** * Description: * @@ -1398,8 +1405,6 @@ export type WebComponentIntegrations = { }; }; -type ExtendedWebContext = typeof import('@/web-components/_integrations').ExtendedWebContext; - type I18nKey = typeof import('@/i18n').default extends I18nConfig ? Paths : string; diff --git a/packages/brisa/src/utils/load-constants/index.test.ts b/packages/brisa/src/utils/load-constants/index.test.ts new file mode 100644 index 00000000..c58932dd --- /dev/null +++ b/packages/brisa/src/utils/load-constants/index.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'bun:test'; +import { internalConstants } from '.'; + +describe('utils -> load-constants', () => { + describe('internalConstants', () => { + it('should return IS_SERVE_PROCESS as true and IS_BUILD_PROCESS as false', () => { + process.argv[1] = 'brisa/out/cli/serve/index.js'; + const result = internalConstants(); + expect(result.IS_SERVE_PROCESS).toBeTrue(); + expect(result.IS_BUILD_PROCESS).toBeFalse(); + }); + it('should return IS_SERVE_PROCESS as false and IS_BUILD_PROCESS as true', () => { + process.argv[1] = 'brisa/out/cli/build.js'; + const result = internalConstants(); + expect(result.IS_SERVE_PROCESS).toBeFalse(); + expect(result.IS_BUILD_PROCESS).toBeTrue(); + }); + it('should return IS_SERVE_PROCESS as true and IS_BUILD_PROCESS as false (argv Windows format)', () => { + process.argv[1] = 'brisa\\out\\cli\\serve\\index.js'; + const result = internalConstants(); + expect(result.IS_SERVE_PROCESS).toBeTrue(); + expect(result.IS_BUILD_PROCESS).toBeFalse(); + }); + it('should return IS_SERVE_PROCESS as false and IS_BUILD_PROCESS as true (argv Windows format)', () => { + process.argv[1] = 'brisa\\out\\cli\\build.js'; + const result = internalConstants(); + expect(result.IS_SERVE_PROCESS).toBeFalse(); + expect(result.IS_BUILD_PROCESS).toBeTrue(); + }); + + it('should return BRISA_DIR', () => { + process.argv[1] = 'brisa/out/cli/serve/index.js'; + const result = internalConstants(); + expect(result.BRISA_DIR).toBe('brisa'); + }); + + it('should return BRISA_DIR (argv Windows format)', () => { + process.argv[1] = 'brisa\\\\out\\\\cli\\\\serve\\\\index.js'; + const result = internalConstants(); + expect(result.BRISA_DIR).toBe('brisa'); + }); + }); +}); diff --git a/packages/brisa/src/utils/load-constants/index.ts b/packages/brisa/src/utils/load-constants/index.ts new file mode 100644 index 00000000..6842ad08 --- /dev/null +++ b/packages/brisa/src/utils/load-constants/index.ts @@ -0,0 +1,168 @@ +import path from 'node:path'; +import importFileIfExists from '../import-file-if-exists'; +import type { I18nConfig, InternalConstants, ProjectConstants } from '@/types'; +import type { BunPlugin } from 'bun'; +import { + blueLog, + cyanLog, + greenLog, + redLog, + yellowLog, +} from '../log/log-color'; +import { version } from '../../../package.json'; + +const SEPARATORS_REGEX = /[\\|\/]/g; +const PAGE_404 = '/_404'; +const PAGE_500 = '/_500'; +const OS_CAN_LOAD_BALANCE = + process.platform !== 'darwin' && process.platform !== 'win32'; + +const staticExportOutputOption = new Set([ + 'static', + 'desktop', + 'android', + 'ios', +]); + +export function internalConstants(): InternalConstants { + const currentScript = path.normalize( + (process.argv[1] ?? '').replace(SEPARATORS_REGEX, path.sep), + ); + const { NODE_ENV } = process.env; + // Note: process.env.IS_PROD is to be defined in the build process + const IS_PRODUCTION = + Boolean(process.env.IS_PROD) || + NODE_ENV === 'production' || + process.argv.some((t) => t === 'PROD'); + const IS_DEVELOPMENT = + process.argv.some((t) => t === 'DEV') || NODE_ENV === 'development'; + const CLI_DIR = path.join('brisa', 'out', 'cli'); + const IS_SERVE_PROCESS = + Boolean(process.env.IS_SERVE_PROCESS) || + Boolean(currentScript?.endsWith?.(path.join(CLI_DIR, 'serve', 'index.js'))); + + const isStandaloneServer = Boolean(process.env.IS_STANDALONE_SERVER); + const ROOT_DIR = isStandaloneServer ? import.meta.dirname : process.cwd(); + + const IS_BUILD_PROCESS = Boolean( + currentScript?.endsWith?.(path.join(CLI_DIR, 'build.js')), + ); + + const BRISA_DIR = currentScript?.replace(new RegExp(`${CLI_DIR}.*`), 'brisa'); + + const SRC_DIR: string = isStandaloneServer + ? import.meta.dirname + : path.resolve(ROOT_DIR, 'src'); + + const BUILD_DIR: string = isStandaloneServer + ? import.meta.dirname + : (process.env.BRISA_BUILD_FOLDER ?? path.resolve(ROOT_DIR, 'build')); + + const WORKSPACE = IS_BUILD_PROCESS ? SRC_DIR : BUILD_DIR; + + return { + WORKSPACE, + JS_RUNTIME: typeof Bun !== 'undefined' ? 'bun' : 'node', + PAGE_404, + PAGE_500, + VERSION: version, + RESERVED_PAGES: [PAGE_404, PAGE_500], + IS_PRODUCTION, + IS_DEVELOPMENT, + IS_SERVE_PROCESS, + IS_BUILD_PROCESS, + PORT: Number.parseInt(process.argv[2]) || 3000, + BUILD_DIR, + ROOT_DIR, + BRISA_DIR, + SRC_DIR, + ASSETS_DIR: path.resolve(BUILD_DIR, 'public'), + PAGES_DIR: path.resolve(BUILD_DIR, 'pages'), + LOG_PREFIX: { + WAIT: cyanLog('[ wait ]') + ' ', + READY: greenLog('[ ready ] ') + ' ', + INFO: blueLog('[ info ] ') + ' ', + ERROR: redLog('[ error ] ') + ' ', + WARN: yellowLog('[ warn ] ') + ' ', + TICK: greenLog('✓ ') + ' ', + }, + REGEX: { + CATCH_ALL: /\[\[\.{3}.*?\]\]/g, + DYNAMIC: /\[.*?\]/g, + REST_DYNAMIC: /\[\.{3}.*?\]/g, + }, + HEADERS: { + CACHE_CONTROL: IS_PRODUCTION + ? 'public, max-age=31536000, immutable' + : 'no-store, must-revalidate', + }, + }; +} + +export async function loadProjectConstants({ + IS_PRODUCTION, + BUILD_DIR, + WORKSPACE, + ROOT_DIR, +}: InternalConstants): Promise { + const defaultConfig = { + trailingSlash: false, + assetPrefix: '', + basePath: '', + extendPlugins: (plugins: BunPlugin[]) => plugins, + output: 'bun', + clustering: IS_PRODUCTION && OS_CAN_LOAD_BALANCE, + integrations: [], + idleTimeout: 30, + }; + + const binaryExternalLibs = ['lightningcss']; + const CSS_FILES = + (await importFileIfExists('css-files', BUILD_DIR))?.default ?? []; + const integrations = await importFileIfExists( + '_integrations', + path.resolve(BUILD_DIR, 'web-components'), + ); + const WEB_CONTEXT_PLUGINS = integrations?.webContextPlugins ?? []; + const I18N_CONFIG = (await importFileIfExists('i18n', WORKSPACE)) + ?.default as I18nConfig; + const CONFIG = { + ...defaultConfig, + ...((await importFileIfExists('brisa.config', ROOT_DIR))?.default ?? {}), + }; + const IS_STATIC_EXPORT = staticExportOutputOption.has(CONFIG?.output); + + // Remove trailing slash from pages + if (I18N_CONFIG?.pages) { + I18N_CONFIG.pages = JSON.parse( + JSON.stringify(I18N_CONFIG.pages, (key, value) => + typeof value === 'string' && value.length > 1 + ? value.replace(/\/$/g, '') + : value, + ), + ); + } + + const LOCALES_SET = new Set(I18N_CONFIG?.locales || []) as Set; + + if (CONFIG.basePath && !CONFIG.basePath.startsWith(path.sep)) { + CONFIG.basePath = path.sep + CONFIG.basePath; + } + + // Add external libraries to the list of external libraries + if (!CONFIG.external) CONFIG.external = binaryExternalLibs; + else CONFIG.external = [...CONFIG.external, ...binaryExternalLibs]; + + // This is needed for some helpers like "navigate" to work properly + // in the server side. (For the client-side it's solved during the build process) + globalThis.__BASE_PATH__ = CONFIG.basePath; + + return { + CSS_FILES, + CONFIG, + I18N_CONFIG, + WEB_CONTEXT_PLUGINS, + LOCALES_SET, + IS_STATIC_EXPORT, + }; +}