chore(docs): Thorough tsdoc generation

Bump node v in docs workflow

fix(docs): treat warnings as errors false

fix(docs)Correct docs pattern in gitignore

Add target branch to docs build

fix(docs)Correct docs pattern in gitignore

Add target branch to docs build

Add target branch to docs build
This commit is contained in:
icld 2025-02-27 17:14:35 -08:00
parent 71fac7ffa2
commit 57599b4542
21 changed files with 2574 additions and 2946 deletions

View File

@ -1,34 +1,40 @@
name: Generate and Deploy Documentation
name: Generate and Deploy Docs
on:
push:
branches: [ main, master, develop, develop-qwrk ] # Trigger on push to these branches
workflow_dispatch: # Allow manual triggering
workflow_dispatch: # Allow manual triggering
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for proper versioning
fetch-depth: 0 # Fetch all history for proper branch detection
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: '16'
node-version: '22'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Update TypeDoc config for current branch
run: |
CURRENT_BRANCH=${GITHUB_REF#refs/heads/}
echo "Current branch: $CURRENT_BRANCH"
# Update gitRevision in typedoc.json to use the current branch
sed -i "s|\"gitRevision\": \"qrigin/[^\"]*\"|\"gitRevision\": \"qrigin/$CURRENT_BRANCH\"|" typedoc.mjs
- name: Generate documentation
run: node scripts/generate-docs.js
run: yarn typedoc
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages # The branch the action should deploy to
folder: docs # The folder the action should deploy
clean: true # Automatically remove deleted files from the deploy branch
folder: docs
clean: true
token: ${{ secrets.ACTIONS_ONLY }} # Use the ACTION_ONLY token

2
.gitignore vendored
View File

@ -14,4 +14,4 @@ packages/frontend/dist/
.vscode
# TypeDoc generated documentation
/docs
docs/

View File

@ -5,15 +5,18 @@
"packages/*"
],
"devDependencies": {
"@microsoft/tsdoc": "^0.15.1",
"chalk": "^4.1.2",
"concurrently": "^8.2.0",
"depcheck": "^1.4.2",
"husky": "^8.0.3",
"lerna": "^8.0.0",
"lerna": "^8.2.0",
"patch-package": "^8.0.0",
"rimraf": "^6.0.1",
"tsdoc": "^0.0.4",
"typedoc": "^0.27.9",
"typedoc-plugin-markdown": "^4.4.2"
"typedoc-plugin-markdown": "^4.4.2",
"typedoc-unhoax-theme": "^0.4.6"
},
"scripts": {
"prepare": "husky install",
@ -24,9 +27,8 @@
"start:backend": "yarn workspace backend start",
"start:frontend": "yarn workspace frontend dev",
"kill:ports": "node scripts/kill-ports.js",
"docs": "node scripts/generate-docs.js",
"docs:clean": "node scripts/generate-docs.js --clean",
"docs:watch": "typedoc --watch"
"docs": "yarn typedoc",
"docs:watch": "yarn typedoc --watch"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@ -1,51 +0,0 @@
# Backend server URL
VITE_SERVER_URL='http://localhost:8000'
# Frontend app URL for OAuth callbacks
NEXT_PUBLIC_APP_URL='http://localhost:3000'
# Session configuration
SESSION_SECRET="complex_secret_at_least_32_chars_long"
NEXT_PUBLIC_SESSION_SECRET="complex_secret_at_least_32_chars_long"
# Enable auto-authentication in development (bypasses wallet)
NEXT_PUBLIC_DEV_AUTO_AUTH='true'
# Enable UI-only mode (bypasses all authentication)
NEXT_PUBLIC_UI_ONLY_MODE='true'
# Same as GitHub client id set in backend config local.toml
NEXT_PUBLIC_GITHUB_CLIENT_ID="Ov23li29Afs0s2Hw2VV1"
NEXT_PUBLIC_GITHUB_CLIENT_SECRET="c16bd30c29169bbada5c4ded16d6639a79cf9789"
# GitHub repos in the format <org>/<repo-name>
# Templates from https://github.com/orgs/deploy-laconic-templates/repositories
NEXT_PUBLIC_GITHUB_PWA_TEMPLATE_REPO="deploy-laconic-templates/test-progressive-web-app"
NEXT_PUBLIC_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO="deploy-laconic-templates/image-upload-pwa-example"
NEXT_PUBLIC_GITHUB_NEXT_APP_TEMPLATE_REPO="deploy-laconic-templates/starter.nextjs-react-tailwind"
NEXT_PUBLIC_WALLET_CONNECT_ID="getaidw"
# Chain ID set in backend config
NEXT_PUBLIC_LACONICD_CHAIN_ID="laconic_9000-1"
# Set to URL of locally running wallet https://git.vdb.to/cerc-io/laconic-wallet-web
# Example: http://localhost:3001
NEXT_PUBLIC_WALLET_IFRAME_URL="http://localhost:3001"
# NOTES FOR WALLET CONNECT
# In laconic-wallet-web (https://git.vdb.to/cerc-io/laconic-wallet-web)
# Create a WalletConnect project in https://cloud.reown.com/app (site renamed to reown) and set ID from the project dashboard
REACT_APP_WALLET_CONNECT_PROJECT_ID=
# Set to URL of locally running deploy frontend app
# Example: http://localhost:3000
REACT_APP_DEPLOY_APP_URL=
# The following env vars are not required and corresponding functionality has been commented in code
NEXT_PUBLIC_LIT_RELAY_API_KEY=
NEXT_PUBLIC_BUGSNAG_API_KEY=
NEXT_PUBLIC_PASSKEY_WALLET_RPID=
NEXT_PUBLIC_TURNKEY_API_BASE_URL=

View File

@ -16,8 +16,10 @@
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.local.example
npm-debug.log*
yarn-debug.log*

View File

@ -11,7 +11,7 @@ const THEMES = { light: "", dark: ".dark" } as const
/**
* Configuration type for chart colors and themes.
*
* @typedef {Object} ChartConfig
* @type {Object} Configuration object for chart elements
* @property {Object} [key: string] - Configuration for each chart element
* @property {React.ReactNode} [key.label] - Label for the element
* @property {React.ComponentType} [key.icon] - Icon component for the element

View File

@ -4,12 +4,12 @@ import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import * as React from "react"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { Label } from "@/components/ui/label"
@ -69,8 +69,9 @@ const FormFieldContext = React.createContext<FormFieldContextValue>(
* @template TFieldValues - Type of form values
* @template TName - Name of the field within the form values
*
* @param control - Form control from useForm()
* @param render - Render prop for field implementation
* @param {Object} props - The component props
* @param {Object} props.control - Form control from useForm()
* @param {Function} props.render - Render prop for field implementation
*
* @example
* ```tsx
@ -227,8 +228,8 @@ const FormMessage = React.forwardRef<
FormMessage.displayName = "FormMessage"
export {
Form, FormControl,
FormDescription, FormField, FormItem,
FormLabel, FormMessage, useFormField
Form, FormControl,
FormDescription, FormField, FormItem,
FormLabel, FormMessage, useFormField
}

View File

@ -60,7 +60,7 @@ const SelectGroup = SelectPrimitive.Group
* @component
* @public
*
* @param placeholder - Text to show when no value is selected
* @param {Object} props - The component props
*/
const SelectValue = SelectPrimitive.Value
@ -100,9 +100,8 @@ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
* @component
* @public
*
* @param className - Additional CSS class for styling the scroll up button
* @param props - Additional props are spread to the underlying Radix UI ScrollUpButton component
* @param ref - Ref to attach to the scroll up button element for direct DOM access
* @param {Object} props - The component props
* @param {string} [props.className] - Additional CSS class for styling the scroll up button
*/
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
@ -127,9 +126,8 @@ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
* @component
* @public
*
* @param className - Additional CSS class for styling the scroll down button
* @param props - Additional props are spread to the underlying Radix UI ScrollDownButton component
* @param ref - Ref to attach to the scroll down button element for direct DOM access
* @param {Object} props - The component props
* @param {string} [props.className] - Additional CSS class for styling the scroll down button
*/
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
@ -155,11 +153,10 @@ SelectScrollDownButton.displayName =
* @component
* @public
*
* @param className - Additional CSS class for styling the content container
* @param children - Elements to display within the content container
* @param position - Positioning strategy for the dropdown content
* @param props - Additional props are spread to the underlying Radix UI Content component
* @param ref - Ref to attach to the content element for direct DOM access
* @param {Object} props - The component props
* @param {string} [props.className] - Additional CSS class for styling the content container
* @param {React.ReactNode} props.children - Elements to display within the content container
* @param {string} [props.position="popper"] - Positioning strategy for the dropdown content
*/
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
@ -199,9 +196,8 @@ SelectContent.displayName = "SelectContent"
* @component
* @public
*
* @param className - Additional CSS class for styling the label
* @param props - Additional props are spread to the underlying Radix UI Label component
* @param ref - Ref to attach to the label element for direct DOM access
* @param {Object} props - The component props
* @param {string} [props.className] - Additional CSS class for styling the label
*/
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
@ -221,10 +217,9 @@ SelectLabel.displayName = SelectPrimitive.Label.displayName
* @component
* @public
*
* @param className - Additional CSS class for styling the item
* @param children - Elements to display within the item
* @param props - Additional props are spread to the underlying Radix UI Item component
* @param ref - Ref to attach to the item element for direct DOM access
* @param {Object} props - The component props
* @param {string} [props.className] - Additional CSS class for styling the item
* @param {React.ReactNode} props.children - Elements to display within the item
*/
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
@ -249,14 +244,13 @@ const SelectItem = React.forwardRef<
SelectItem.displayName = "SelectItem"
/**
* Separator for dividing Select items, providing visual separation.
* Visual separator for grouping Select items within the dropdown.
*
* @component
* @public
*
* @param className - Additional CSS class for styling the separator
* @param props - Additional props are spread to the underlying Radix UI Separator component
* @param ref - Ref to attach to the separator element for direct DOM access
* @param {Object} props - The component props
* @param {string} [props.className] - Additional CSS class for styling the separator
*/
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
@ -271,15 +265,15 @@ const SelectSeparator = React.forwardRef<
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue
}

View File

@ -96,7 +96,9 @@ TabsTrigger.displayName = "TabsTrigger"
* @component
* @public
*
* @param value - Must match corresponding trigger's value
* @param {Object} props - Component props
* @param {string} props.value - Must match corresponding trigger's value
* @param {string} [props.className] - Additional custom classes
*
* @see {@link https://ui.shadcn.com/docs/components/tabs | Shadcn TabsContent Documentation}
* @see {@link https://www.radix-ui.com/primitives/docs/components/tabs#content | Radix TabsContent Primitive}

View File

@ -10,8 +10,9 @@ const GQLClientContext = createContext({} as GQLClient);
/**
* @component GQLClientProvider
* @description Provides the GQLClientContext to its children.
* @param {ReactNode} children - The children to render.
* @param {GQLClient} client - The GQLClient instance.
* @param {Object} props - The component props
* @param {ReactNode} props.children - The children to render.
* @param {GQLClient} props.client - The GQLClient instance.
*/
export const GQLClientProvider = ({
client,

View File

@ -42,8 +42,9 @@ const OctokitContext = createContext<ContextValue>({
/**
* @component OctokitProvider
* @description Provides the OctokitContext to its children.
* @param {ReactNode} children - The children to render.
* @param {Function} [navigate] - Optional navigation function. If not provided, useNavigate will be used.
* @param {Object} props - Component props
* @param {ReactNode} props.children - The children to render.
* @param {Function} [props.navigate] - Optional navigation function. If not provided, useNavigate will be used.
*/
export const OctokitProvider = ({
children,

View File

@ -34,7 +34,8 @@ const WalletContext = createContext<WalletContextType | undefined>(undefined);
/**
* @component WalletProvider
* @description Provides the WalletContext to its children.
* @param {ReactNode} children - The children to render.
* @param {Object} props - Component props
* @param {ReactNode} props.children - The children to render.
*/
export const WalletProvider: React.FC<{ children: ReactNode }> = ({
children,

View File

@ -1,82 +0,0 @@
#!/usr/bin/env node
/**
* Script to generate TypeDoc documentation for the monorepo
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
// Ensure docs directory exists
const docsDir = path.join(__dirname, '..', 'docs');
if (!fs.existsSync(docsDir)) {
fs.mkdirSync(docsDir, { recursive: true });
}
// Run TypeDoc
console.log('Generating documentation...');
try {
// Use npx to run TypeDoc
execSync('npx typedoc', { stdio: 'inherit' });
console.log('Documentation generated successfully in the docs/ directory');
console.log('\nCI/CD Approach for Documentation:');
console.log('1. Add docs/ to your .gitignore file to prevent committing generated docs');
console.log('2. Set up a GitHub Action workflow that:');
console.log(' - Runs on push to main/develop/develop-qwrk branches');
console.log(' - Generates documentation using this script');
console.log(' - Deploys the generated docs to GitHub Pages');
console.log('\nExample GitHub Action workflow (.github/workflows/docs.yml):');
console.log(`
name: Generate and Deploy Docs
on:
push:
branches: [ main, master, develop, develop-qwrk ] # Trigger on these branches
workflow_dispatch: # Allow manual triggering
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Generate documentation
run: node scripts/generate-docs.js
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: docs
clean: true
`);
console.log('\nThis way, your documentation will be:');
console.log('- Generated automatically when you push to main, develop, or develop-qwrk');
console.log('- Deployed to GitHub Pages without cluttering your main branch');
console.log('- Available at https://[username].github.io/[repository]/');
// Clean up docs directory if the --clean flag is provided
if (process.argv.includes('--clean')) {
console.log('\nCleaning up docs directory...');
rimraf.sync(docsDir);
console.log('Docs directory removed. Documentation will only be available through GitHub Pages.');
}
} catch (error) {
console.error('Error generating documentation:', error.message);
process.exit(1);
}

View File

@ -1,303 +0,0 @@
#!/usr/bin/env node
const { spawn, exec } = require('child_process');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs');
const { promisify } = require('util');
const os = require('os');
const execAsync = promisify(exec);
// Configuration
const config = {
rootDir: process.cwd(),
backendDir: path.join(process.cwd(), 'packages/backend'),
frontendDir: path.join(process.cwd(), 'packages/frontend'),
ports: {
backend: 8000,
frontend: 3000,
alternativeFrontend: 3001
}
};
// Helper to check if a port is in use and kill the process if needed
async function ensurePortAvailable(port) {
try {
console.log(chalk.blue(`🔍 Checking if port ${port} is in use...`));
// Different commands for different operating systems
let command;
if (process.platform === 'win32') {
command = `netstat -ano | findstr :${port}`;
} else {
command = `lsof -i :${port} -t`;
}
const { stdout } = await execAsync(command);
if (stdout.trim()) {
console.log(chalk.yellow(`⚠️ Port ${port} is in use. Attempting to terminate processes...`));
let pids;
if (process.platform === 'win32') {
// Extract PIDs from Windows netstat output
pids = stdout.split('\n')
.filter(line => line.includes(`:${port}`))
.map(line => line.trim().split(/\s+/).pop() || '')
.filter(Boolean);
} else {
// Unix lsof already returns PIDs directly
pids = stdout.trim().split('\n').filter(Boolean);
}
// Kill each process
for (const pid of pids) {
try {
const killCommand = process.platform === 'win32'
? `taskkill /F /PID ${pid}`
: `kill -9 ${pid}`;
await execAsync(killCommand);
console.log(chalk.green(`✅ Terminated process ${pid} using port ${port}`));
} catch (error) {
console.error(chalk.red(`Failed to kill process ${pid}: ${error}`));
}
}
} else {
console.log(chalk.green(`✅ Port ${port} is available`));
}
} catch (error) {
// If the command fails (e.g., lsof not found), assume port is available
console.log(chalk.yellow(`⚠️ Could not check port ${port}: ${error}`));
}
}
// Helper to run commands with proper output handling
function runCommand(command, args, cwd, label) {
return new Promise((resolve, reject) => {
console.log(chalk.blue(`🚀 Starting: ${label}...`));
const childProcess = spawn(command, args, {
cwd,
stdio: 'pipe',
shell: true
});
let stdout = '';
let stderr = '';
childProcess.stdout && childProcess.stdout.on('data', (data) => {
const output = data.toString();
stdout += output;
process.stdout.write(chalk.gray(`[${label}] `) + output);
});
childProcess.stderr && childProcess.stderr.on('data', (data) => {
const output = data.toString();
stderr += output;
process.stderr.write(chalk.yellow(`[${label}] `) + output);
});
childProcess.on('error', (error) => {
console.error(chalk.red(`❌ Error in ${label}: ${error.message}`));
reject(error);
});
childProcess.on('close', (code) => {
if (code === 0) {
console.log(chalk.green(`✅ Completed: ${label}`));
resolve();
} else {
console.error(chalk.red(`❌ Failed: ${label} (exit code: ${code})`));
reject(new Error(`Command failed with exit code ${code}: ${stderr}`));
}
});
});
}
// Check if iTerm2 is available (macOS only)
async function isITerm2Available() {
if (process.platform !== 'darwin') return false;
try {
const { stdout } = await execAsync('osascript -e "exists application \\"iTerm2\\""');
return stdout.trim() === 'true';
} catch (error) {
return false;
}
}
// Open split panes in iTerm2
async function openITerm2SplitPanes(backendCommand, frontendCommand) {
const script = `
tell application "iTerm2"
create window with default profile
tell current window
tell current session
set backendSession to (split horizontally with default profile)
set frontendSession to (split vertically with default profile)
select
write text "cd ${config.backendDir} && clear && echo '🚀 BACKEND SERVER' && ${backendCommand}"
set name to "Backend Server"
tell backendSession
select
write text "cd ${config.frontendDir} && clear && echo '🚀 FRONTEND DEV SERVER' && ${frontendCommand}"
set name to "Frontend Dev Server"
end tell
tell frontendSession
select
write text "cd ${config.rootDir} && clear && echo '📊 MONOREPO ROOT'"
set name to "Monorepo Root"
end tell
end tell
end tell
end tell
`;
try {
await execAsync(`osascript -e '${script}'`);
return true;
} catch (error) {
console.error(chalk.yellow(`⚠️ Failed to open iTerm2 with split panes: ${error.message}`));
return false;
}
}
// Open a new terminal based on platform
async function openTerminals(backendCommand, frontendCommand) {
// First check if we can use iTerm2 on macOS
if (process.platform === 'darwin') {
const iTerm2Available = await isITerm2Available();
if (iTerm2Available) {
console.log(chalk.blue('🖥️ Opening iTerm2 with split panes...'));
const success = await openITerm2SplitPanes(backendCommand, frontendCommand);
if (success) return true;
}
}
// Fallback to separate terminal windows
const escapedBackendCommand = backendCommand.replace(/"/g, '\\"');
const escapedFrontendCommand = frontendCommand.replace(/"/g, '\\"');
const escapedBackendDir = config.backendDir.replace(/"/g, '\\"');
const escapedFrontendDir = config.frontendDir.replace(/"/g, '\\"');
// Platform-specific terminal opening commands
if (process.platform === 'darwin') {
// macOS - Terminal.app
const backendScript = `
tell application "Terminal"
do script "cd \\"${escapedBackendDir}\\" && clear && echo '🚀 BACKEND SERVER' && ${escapedBackendCommand}"
set custom title of front window to "Backend Server"
end tell
`;
const frontendScript = `
tell application "Terminal"
do script "cd \\"${escapedFrontendDir}\\" && clear && echo '🚀 FRONTEND DEV SERVER' && ${escapedFrontendCommand}"
set custom title of front window to "Frontend Dev Server"
end tell
`;
await execAsync(`osascript -e '${backendScript}'`);
await execAsync(`osascript -e '${frontendScript}'`);
} else if (process.platform === 'win32') {
// Windows - try Windows Terminal first, fall back to cmd
try {
// Windows Terminal (supports multiple tabs)
await execAsync(`wt -w 0 -d "${escapedBackendDir}" cmd /k "title Backend Server && ${escapedBackendCommand}" ; split-pane -d "${escapedFrontendDir}" cmd /k "title Frontend Dev Server && ${escapedFrontendCommand}"`);
} catch (error) {
// Fallback to regular cmd windows
spawn('cmd.exe', ['/c', 'start', 'cmd.exe', '/K',
`cd /d "${escapedBackendDir}" && title Backend Server && ${escapedBackendCommand}`]);
spawn('cmd.exe', ['/c', 'start', 'cmd.exe', '/K',
`cd /d "${escapedFrontendDir}" && title Frontend Dev Server && ${escapedFrontendCommand}`]);
}
} else {
// Linux terminals with split support
try {
// Try Tilix (supports split screen)
await execAsync(`tilix --window-style=disable-csd-hide-toolbar --maximize --session-file=<(echo '[{"command":"cd ${escapedBackendDir} && ${escapedBackendCommand}","title":"Backend Server"},{"command":"cd ${escapedFrontendDir} && ${escapedFrontendCommand}","title":"Frontend Dev Server"}]')`);
} catch (error) {
try {
// Try Terminator (supports split screen)
await execAsync(`terminator --maximize -e "bash -c 'cd ${escapedBackendDir} && ${escapedBackendCommand}'" -e "bash -c 'cd ${escapedFrontendDir} && ${escapedFrontendCommand}'"`);
} catch (error) {
// Fallback to separate gnome-terminal windows
try {
spawn('gnome-terminal', ['--', 'bash', '-c',
`cd "${escapedBackendDir}" && echo -e "\\033]0;Backend Server\\007" && ${escapedBackendCommand}; exec bash`]);
spawn('gnome-terminal', ['--', 'bash', '-c',
`cd "${escapedFrontendDir}" && echo -e "\\033]0;Frontend Dev Server\\007" && ${escapedFrontendCommand}; exec bash`]);
} catch (error) {
// Last resort: try xterm
spawn('xterm', ['-T', 'Backend Server', '-e',
`cd "${escapedBackendDir}" && ${escapedBackendCommand}; exec bash`]);
spawn('xterm', ['-T', 'Frontend Dev Server', '-e',
`cd "${escapedFrontendDir}" && ${escapedFrontendCommand}; exec bash`]);
}
}
}
}
return true;
}
// Check if directory exists
function checkDirectoryExists(dir, name) {
if (!fs.existsSync(dir)) {
console.error(chalk.red(`❌ Error: ${name} directory not found at ${dir}`));
throw new Error(`Directory not found: ${dir}`);
}
}
// Main execution function
async function runMonorepoWorkflow() {
console.log(chalk.cyan.bold('📦 Monorepo Workflow Script'));
console.log(chalk.cyan('---------------------------'));
try {
// Validate directories
checkDirectoryExists(config.rootDir, 'Root');
checkDirectoryExists(config.backendDir, 'Backend');
checkDirectoryExists(config.frontendDir, 'Frontend');
// Ensure all required ports are available
await ensurePortAvailable(config.ports.backend);
await ensurePortAvailable(config.ports.frontend);
await ensurePortAvailable(config.ports.alternativeFrontend);
// Step 1: Install dependencies at root
await runCommand('yarn', [], config.rootDir, 'Root dependency installation');
// Step 2: Build packages (ignoring frontend)
await runCommand('yarn', ['build', '--ignore', 'frontend'], config.rootDir, 'Building packages');
// Step 3: Start services in split terminal
console.log(chalk.blue('🚀 Opening terminal with services...'));
await openTerminals('yarn start', 'yarn dev');
console.log(chalk.green.bold('✅ Development environment started!'));
console.log(chalk.cyan('Backend running at:') + chalk.yellow(` http://localhost:${config.ports.backend}`));
console.log(chalk.cyan('Frontend running at:') + chalk.yellow(` http://localhost:${config.ports.frontend}`));
console.log(chalk.gray('Check the opened terminal windows for detailed logs.'));
// Exit successfully
process.exit(0);
} catch (error) {
console.error(chalk.red.bold('❌ Workflow failed:'));
console.error(chalk.red(error.message));
process.exit(1);
}
}
// Execute the workflow
runMonorepoWorkflow().catch(error => {
console.error(chalk.red.bold('❌ Unhandled error:'));
console.error(error);
process.exit(1);
});

View File

@ -1,53 +0,0 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs');
const rootDir = process.cwd();
const backendDir = path.join(rootDir, 'packages/backend');
const frontendDir = path.join(rootDir, 'packages/frontend');
// Create shell scripts for each service
const backendScript = path.join(rootDir, 'scripts', 'start-backend.sh');
const frontendScript = path.join(rootDir, 'scripts', 'start-frontend.sh');
// Write shell scripts
fs.writeFileSync(backendScript, `#!/bin/bash
cd "${backendDir}"
echo "🚀 Starting Backend Server..."
yarn start
`, { mode: 0o755 });
fs.writeFileSync(frontendScript, `#!/bin/bash
cd "${frontendDir}"
echo "🚀 Starting Frontend Dev Server..."
yarn dev
`, { mode: 0o755 });
console.log(chalk.cyan.bold('📦 Starting development environment'));
try {
// Clean up ports first
console.log(chalk.blue('🧹 Cleaning up ports...'));
execSync('yarn kill:ports', { stdio: 'inherit' });
// Build packages
console.log(chalk.blue('🔨 Building packages...'));
execSync('yarn build --ignore frontend', { stdio: 'inherit' });
console.log(chalk.green('✅ Build complete'));
console.log(chalk.blue('🚀 Starting services in separate terminals...'));
// Open new terminals with the shell scripts
execSync(`open -a Terminal ${backendScript}`);
execSync(`open -a Terminal ${frontendScript}`);
console.log(chalk.green('✅ Services started in separate terminal windows'));
console.log(chalk.cyan('Backend running at:') + chalk.yellow(' http://localhost:8000'));
console.log(chalk.cyan('Frontend running at:') + chalk.yellow(' http://localhost:3000'));
} catch (error) {
console.error(chalk.red(`❌ Error: ${error.message}`));
process.exit(1);
}

View File

@ -1,49 +0,0 @@
const { exec } = require('child_process');
const path = require('path');
// Function to execute shell commands
const runCommand = (command, cwd) => {
return new Promise((resolve, reject) => {
console.log(`Running command: ${command} in ${cwd}`);
const process = exec(command, { cwd }, (error, stdout, stderr) => {
if (error) {
console.error(`Command failed: ${command}`);
console.error(stderr);
reject(error);
return;
}
console.log(stdout);
resolve(stdout);
});
process.stdout.pipe(process.stdout);
process.stderr.pipe(process.stderr);
});
};
(async () => {
try {
const rootDir = __dirname;
const backendDir = path.join(rootDir, 'packages', 'backend');
const frontendDir = path.join(rootDir, 'packages', 'frontend');
console.log('Starting application setup...');
console.log('Running yarn install...');
await runCommand('yarn', rootDir);
console.log('Running yarn build...');
await runCommand('yarn build', rootDir);
console.log('Starting backend...');
await runCommand('yarn start', backendDir);
console.log('Starting frontend in new terminal...');
exec(`osascript -e 'tell application "Terminal" to do script "cd ${frontendDir} && yarn dev"'`);
console.log('Application started successfully!');
} catch (error) {
console.error('Error during setup:', error);
process.exit(1);
}
})();

View File

@ -1,4 +1,9 @@
{
"extends": [
"./packages/frontend/tsconfig.json",
"./packages/backend/tsconfig.json",
"./packages/gql-client/tsconfig.json"
],
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
@ -9,15 +14,11 @@
"resolveJsonModule": true,
"jsx": "react-jsx"
},
"include": [
"packages/*/src/**/*.ts",
"packages/*/src/**/*.tsx"
],
"include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
"exclude": [
"node_modules",
"packages/*/node_modules",
"packages/*/dist",
"packages/frontend/src/components",
"packages/frontend/src/stories"
]
}
}

18
tsdoc.json Normal file
View File

@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/tsdoc/v0/tsdoc.schema.json",
"extends": ["typedoc/tsdoc.json"],
"tagDefinitions": [
{
"tagName": "@description",
"syntaxKind": "block"
},
{
"tagName": "@component",
"syntaxKind": "block"
},
{
"tagName": "@function",
"syntaxKind": "block"
}
]
}

View File

@ -1,25 +0,0 @@
{
"$schema": "https://typedoc.org/schema.json",
"entryPoints": ["packages/*/src"],
"entryPointStrategy": "expand",
"out": "docs",
"plugin": ["typedoc-plugin-markdown"],
"readme": "docs-readme.md",
"name": "Snowball Tools API Documentation",
"includeVersion": true,
"excludePrivate": true,
"excludeProtected": false,
"excludeExternals": true,
"excludeInternal": true,
"hideGenerator": true,
"disableSources": false,
"categorizeByGroup": true,
"categoryOrder": ["Core", "API", "Utilities", "*"],
"sort": ["source-order"],
"validation": {
"invalidLink": true,
"notDocumented": true
},
"skipErrorChecking": true,
"tsconfig": "./tsconfig.doc.json"
}

44
typedoc.mjs Normal file
View File

@ -0,0 +1,44 @@
import { OptionDefaults } from 'typedoc';
const config = {
$schema: 'https://typedoc.org/schema.json',
entryPoints: [
'./packages/**/src/**/*.{ts,tsx}',
// './packages/frontend/src/**/*.{ts,tsx}',
// './packages/backend/src/**/*.{ts,tsx}',
// './packages/gql-client/src/**/*.{ts,tsx}',
],
entryPointStrategy: 'expand',
out: './docs',
plugin: ['typedoc-plugin-markdown'],
// plugin: ['typedoc-unhoax-theme'],
readme: './README.md',
mergeReadme: true,
name: 'Laconic Deploy FE API Documentation',
includeVersion: true,
githubPages: true,
searchInComments: true,
enumMembersFormat: 'table',
parametersFormat: 'table',
propertiesFormat: 'table',
typeDeclarationFormat: 'table',
indexFormat: 'table',
useCodeBlocks: true,
expandObjects: true,
textContentMappings: {
'breadcrumbs.home': 'Home',
},
validation: {
notDocumented: false,
invalidLink: false,
notExported: true,
},
skipErrorChecking: true,
tsconfig: './tsconfig.doc.json',
gitRevision: 'qrigin/develop-qwrk',
gitRemote: 'qrigin',
blockTags: [...OptionDefaults.blockTags, '@component', '@description'],
};
export default config;

4724
yarn.lock

File diff suppressed because it is too large Load Diff