feat(build): add vite-build-logger (#507)

* feat(build): add vite-build-logger

* chore: fix types

* chore: fix logic

---------

Co-authored-by: Preston-Cook <preston.l.cook@gmail.com>
This commit is contained in:
doprz
2025-02-15 00:05:56 -06:00
committed by GitHub
parent ee4c6ce699
commit 1aa4e8c5fb
3 changed files with 150 additions and 1 deletions

View File

@@ -11,16 +11,19 @@ import {
RadioButton, RadioButton,
Trash, Trash,
} from '@phosphor-icons/react'; } from '@phosphor-icons/react';
import { background } from '@shared/messages';
import type { UserSchedule } from '@shared/types/UserSchedule'; import type { UserSchedule } from '@shared/types/UserSchedule';
import Text from '@views/components/common/Text/Text'; import Text from '@views/components/common/Text/Text';
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit'; import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
import useSchedules from '@views/hooks/useSchedules'; import useSchedules from '@views/hooks/useSchedules';
import { LONGHORN_DEVELOPERS_ADMINS, LONGHORN_DEVELOPERS_SWE } from '@views/lib/getGitHubStats';
import clsx from 'clsx'; import clsx from 'clsx';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { Button } from './Button'; import { Button } from './Button';
import DialogProvider, { usePrompt } from './DialogProvider/DialogProvider'; import DialogProvider, { usePrompt } from './DialogProvider/DialogProvider';
import { ExtensionRootWrapper, styleResetClass } from './ExtensionRoot/ExtensionRoot'; import { ExtensionRootWrapper, styleResetClass } from './ExtensionRoot/ExtensionRoot';
import Link from './Link';
import { SortableListDragHandle } from './SortableListDragHandle'; import { SortableListDragHandle } from './SortableListDragHandle';
/** /**
@@ -32,6 +35,7 @@ interface ScheduleListItemProps {
} }
const IS_STORYBOOK = import.meta.env.STORYBOOK; const IS_STORYBOOK = import.meta.env.STORYBOOK;
const teamMembers = [...LONGHORN_DEVELOPERS_ADMINS, ...LONGHORN_DEVELOPERS_SWE];
/** /**
* This is a reusable dropdown component that can be used to toggle the visiblity of information * This is a reusable dropdown component that can be used to toggle the visiblity of information
@@ -40,6 +44,7 @@ export default function ScheduleListItem({ schedule, onClick }: ScheduleListItem
const [activeSchedule] = useSchedules(); const [activeSchedule] = useSchedules();
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [editorValue, setEditorValue] = useState(schedule.name); const [editorValue, setEditorValue] = useState(schedule.name);
const teamMember = teamMembers[Math.floor(Math.random() * teamMembers.length)];
const showDialog = usePrompt(); const showDialog = usePrompt();
const enforceScheduleLimit = useEnforceScheduleLimit(); const enforceScheduleLimit = useEnforceScheduleLimit();
@@ -65,13 +70,46 @@ export default function ScheduleListItem({ schedule, onClick }: ScheduleListItem
const handleBlur = async () => { const handleBlur = async () => {
if (editorValue.trim() !== '' && editorValue.trim() !== schedule.name) { if (editorValue.trim() !== '' && editorValue.trim() !== schedule.name) {
schedule.name = (await renameSchedule(schedule.id, editorValue.trim())) as string; schedule.name = (await renameSchedule(schedule.id, editorValue.trim())) as string;
if (schedule.name === '404') {
const url = chrome.runtime.getURL('/404.html');
background.openNewTab({ url });
}
if (Math.random() < 0.002) {
showDialog({
title: 'Schedule name already taken',
description: (
<>
<Text>Schedule name</Text>
<Text className='text-ut-burntorange'> {schedule.name} </Text>
<Text>
is already taken.
<br />
<br />
Join the&nbsp;
</Text>
<Link className='link' href='https://discord.gg/7pQDBGdmb7'>
<Text>Discord</Text>
</Link>
<Text> to contact {teamMember?.name as string}.</Text>
</>
),
// eslint-disable-next-line react/no-unstable-nested-components
buttons: close => (
<Button variant='minimal' color='ut-black' onClick={close}>
Go Back
</Button>
),
});
}
} }
setIsEditing(false); setIsEditing(false);
}; };
const handleDelete = () => { const handleDelete = () => {
showDialog({ showDialog({
title: `Are you sure?`, title: 'Are you sure?',
description: ( description: (
<> <>
<Text>Deleting</Text> <Text>Deleting</Text>

View File

@@ -0,0 +1,81 @@
import chalk from 'chalk';
import type { Plugin } from 'vite';
/**
* Options to configure the build logger.
*/
interface BuildLoggerOptions {
includeEnvVars?: string[]; // Specific env vars to display
includeTimestamp?: boolean;
includeBuildTime?: boolean;
customMetadata?: Record<string, () => string | Promise<string>>;
}
/**
* Vite plugin to log build information.
*
* @param Options - to configure the build logger.
*
* @returns Vite plugin object.
*/
export function buildLogger(options: BuildLoggerOptions = {}): Plugin {
const startTime = Date.now();
return {
name: 'vite-build-logger',
enforce: 'post',
async closeBundle() {
console.log(`\n${chalk.bold.cyan('=== Build Information ===')}`);
// Environment
console.log(chalk.yellow('\nBuild Environment:'));
console.log(`🔧 Node Build Mode: ${process.env.NODE_ENV}`);
console.log(`🎯 Browser Target: ${process.env.BROWSER_TARGET}`);
// Timestamp
if (options.includeTimestamp) {
console.log(chalk.yellow('\nBuild Timestamp:'));
console.log(`📅 ${new Date().toISOString()}`);
}
// Build Time
if (options.includeBuildTime) {
const buildTime = Date.now() - startTime;
console.log(chalk.yellow('\nBuild Time:'));
console.log(`⏱️ ${buildTime}ms (${(buildTime / 1000).toFixed(2)}s)`);
}
// Selected Environment Variables
if (options.includeEnvVars?.length) {
console.log(chalk.yellow('\nEnvironment Variables:'));
for (const envVar of options.includeEnvVars) {
if (process.env[envVar]) {
// Mask sensitive variables that might contain tokens or keys
const isSensitive =
envVar.toLowerCase().includes('key') ||
envVar.toLowerCase().includes('token') ||
envVar.toLowerCase().includes('secret');
const value = isSensitive ? '****' : process.env[envVar];
console.log(`${envVar}: ${value}`);
} else {
console.log(`${envVar}: ${chalk.red('Not defined')}`);
}
}
}
// Custom Metadata
if (options.customMetadata) {
console.log(chalk.yellow('\nCustom Metadata:'));
for (const [key, getter] of Object.entries(options.customMetadata)) {
// eslint-disable-next-line no-await-in-loop
const value = await getter();
console.log(`${key}: ${value}`);
}
}
console.log(`\n${chalk.bold.cyan('=====================')}\n`);
},
};
}

View File

@@ -1,6 +1,7 @@
/// <reference types="vitest" /> /// <reference types="vitest" />
import { crx } from '@crxjs/vite-plugin'; import { crx } from '@crxjs/vite-plugin';
import react from '@vitejs/plugin-react-swc'; import react from '@vitejs/plugin-react-swc';
import { execSync } from 'child_process';
import { resolve } from 'path'; import { resolve } from 'path';
import UnoCSS from 'unocss/vite'; import UnoCSS from 'unocss/vite';
import Icons from 'unplugin-icons/vite'; import Icons from 'unplugin-icons/vite';
@@ -11,17 +12,27 @@ import inspect from 'vite-plugin-inspect';
import packageJson from './package.json'; import packageJson from './package.json';
import manifest from './src/manifest'; import manifest from './src/manifest';
import vitePluginRunCommandOnDemand from './utils/plugins/run-command-on-demand'; import vitePluginRunCommandOnDemand from './utils/plugins/run-command-on-demand';
import { buildLogger } from './utils/plugins/vite-build-logger';
const BROWSER_TARGET = process.env.BROWSER_TARGET || 'chrome';
// Set browser target environment variable default
process.env.BROWSER_TARGET = BROWSER_TARGET;
const root = resolve(__dirname, 'src'); const root = resolve(__dirname, 'src');
const pagesDir = resolve(root, 'pages'); const pagesDir = resolve(root, 'pages');
const assetsDir = resolve(root, 'assets'); const assetsDir = resolve(root, 'assets');
const publicDir = resolve(__dirname, 'public'); const publicDir = resolve(__dirname, 'public');
// Set default environment variables
process.env.PROD = process.env.NODE_ENV === 'production' ? 'true' : 'false';
const isBeta = !!process.env.BETA; const isBeta = !!process.env.BETA;
if (isBeta) { if (isBeta) {
process.env.VITE_BETA_BUILD = 'true'; process.env.VITE_BETA_BUILD = 'true';
} }
process.env.VITE_PACKAGE_VERSION = packageJson.version; process.env.VITE_PACKAGE_VERSION = packageJson.version;
// TODO: Debug this. If PROD is false, VITE_SENTRY_ENVIRONMENT is in production mode
if (process.env.PROD) { if (process.env.PROD) {
process.env.VITE_SENTRY_ENVIRONMENT = 'production'; process.env.VITE_SENTRY_ENVIRONMENT = 'production';
} else if (isBeta) { } else if (isBeta) {
@@ -162,6 +173,23 @@ export default defineConfig({
// afterServerStart: 'pnpm gulp forceDisableUseDynamicUrl', // afterServerStart: 'pnpm gulp forceDisableUseDynamicUrl',
closeBundle: 'pnpm gulp forceDisableUseDynamicUrl', closeBundle: 'pnpm gulp forceDisableUseDynamicUrl',
}), }),
buildLogger({
includeEnvVars: [
'VITE_PACKAGE_VERSION',
'NODE_ENV',
'BROWSER_TARGET',
'PROD',
'VITE_SENTRY_ENVIRONMENT',
'VITE_BETA_BUILD',
],
includeTimestamp: true,
includeBuildTime: true,
customMetadata: {
gitBranch: () => execSync('git rev-parse --abbrev-ref HEAD').toString().trim(),
gitCommit: () => execSync('git rev-parse --short HEAD').toString().trim(),
nodeVersion: () => process.version,
},
}),
], ],
resolve: { resolve: {
alias: { alias: {
@@ -205,6 +233,8 @@ export default defineConfig({
}, },
build: { build: {
target: ['chrome120', 'edge120', 'firefox120'], target: ['chrome120', 'edge120', 'firefox120'],
// NOTE: Eventually we will add this back once we support multiple browsers
// outDir: `dist/${process.env.BROWSER_TARGET || 'chrome'}`,
emptyOutDir: true, emptyOutDir: true,
reportCompressedSize: false, reportCompressedSize: false,
sourcemap: true, sourcemap: true,