Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6ed13af
Updated dependency.
brentrager May 10, 2025
a89fc17
Merge branch 'main' of github.com:SmooAI/utils
brentrager May 10, 2025
990bab6
Merge branch 'main' of github.com:SmooAI/utils
brentrager May 19, 2025
93db6ac
Merge branch 'main' of github.com:SmooAI/utils
brentrager May 19, 2025
3367b22
Merge branch 'main' of github.com:SmooAI/utils
brentrager May 26, 2025
8eabd6d
Merge branch 'main' of github.com:SmooAI/utils
brentrager May 27, 2025
72ec9e0
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jun 2, 2025
ad45f3c
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jun 10, 2025
651fc41
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jun 23, 2025
be3496d
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jun 30, 2025
7acff41
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jul 22, 2025
f0527b0
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jul 22, 2025
afed860
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jul 23, 2025
2feb360
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jul 24, 2025
b62235b
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jul 25, 2025
4a55dd0
Merge branch 'main' of github.com:SmooAI/utils
brentrager Jul 29, 2025
f6ad572
Merge branch 'main' of github.com:SmooAI/utils
brentrager Aug 2, 2025
14bc0f9
Enhance Hono logger with additional context and fix to include reques…
brentrager Aug 2, 2025
1e582f3
Merge branch 'main' of github.com:SmooAI/utils into update-logging-an…
brentrager Aug 22, 2025
e63295e
Add utility for generating git branch names using OpenAI; create bran…
brentrager Aug 22, 2025
bffc865
Added changeset
brentrager Aug 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/breezy-candies-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@smooai/utils': patch
---

Fix hono logger to have more context.
5 changes: 5 additions & 0 deletions .changeset/plenty-donkeys-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@smooai/utils': patch
---

Add utility for using AI to generate branch name, and improve hono logging.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ yarn.lock
# ignore package-lock.json
package-lock.json

.vscode
.vscode

.envrc
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
}
},
"bin": {
"create-entry-points": "./dist/scripts/createEntryPoints.js"
"create-entry-points": "./dist/scripts/createEntryPoints.js",
"generate-git-branch": "./dist/scripts/generateGitBranch.js"
},
"files": [
"dist/**"
Expand All @@ -58,6 +59,7 @@
"ci:publish": "pnpm build && pnpm changeset publish",
"createEntryPoints": "pnpm vite-node ./src/scripts/createEntryPoints.ts -i \"src/**/*.ts\"",
"format": "prettier --write \"**/*.{ts,tsx,md,json,js,cjs,mjs}\"",
"generateGitBranch": "pnpm vite-node ./src/utils/generate-git-branch.ts",
"preinstall": "npx only-allow pnpm",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
Expand All @@ -77,8 +79,11 @@
"hono": "^4.7.2",
"http-status-codes": "^2.3.0",
"libphonenumber-js": "^1.12.4",
"openai": "^5.15.0",
"picocolors": "^1.1.1",
"zod": "^3.24.2",
"zod-validation-error": "^3.4.0"
"zod-validation-error": "^3.4.0",
"zx": "^8.8.1"
},
"devDependencies": {
"@changesets/cli": "^2.28.1",
Expand Down
32 changes: 32 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 20 additions & 7 deletions src/api/hono.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isRunningLocally } from '@/env';
import { HumanReadableSchemaError } from '@/validation/standardSchema';
import AwsServerLogger from '@smooai/logger/AwsServerLogger';
import AwsServerLogger, { HttpResponse } from '@smooai/logger/AwsServerLogger';
import { APIGatewayProxyEventV2, Context } from 'aws-lambda';
import { Hono } from 'hono';
import { handle, LambdaContext, LambdaEvent } from 'hono/aws-lambda';
Expand All @@ -18,14 +18,27 @@ export function addHonoMiddleware(_app: Hono<any>): Hono<any> {
const app = _app ?? new Hono();

app.use(requestId());
app.use(async (c, next) => {
const namespace = `[${c.req.method}] ${c.req.path}`;
logger.addRequestContext(c.req);
logger.addContext({
namespace,
honoRequestId: c.get('requestId'),
});
logger.info(`Request started`);
await next();
});

app.use(async (c, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
logger.addResponseContext(c.res as unknown as HttpResponse);
logger.info(`Request completed in ${duration}ms`);
});

app.use(async (c, next) => {
honoLogger((str, ...rest) => {
const namespace = `[${c.req.method}] ${c.req.path}`;
logger.addRequestContext(c.req);
logger.addContext({
namespace,
honoRequestId: c.get('requestId'),
});
logger.info(str, ...rest);
});
await next();
Expand Down
165 changes: 165 additions & 0 deletions src/utils/generate-git-branch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#!/usr/bin/env tsx
import { createInterface } from 'node:readline';
import OpenAI from 'openai';
import pc from 'picocolors';
import { $$ } from './zx-factory';

interface BranchGenerationOptions {
pullFromMain?: boolean;
}

async function requestUserInput(prompt: string): Promise<string> {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});

return new Promise((resolve) => {
rl.question(pc.cyan(prompt), (answer: string) => {
rl.close();
const trimmedAnswer = answer.trim();
if (trimmedAnswer) {
console.log(pc.green(`→ ${trimmedAnswer}`));
}
resolve(trimmedAnswer);
});
});
}

async function generateBranchName(workDescription: string): Promise<string> {
const openaiApiKey = process.env.OPENAI_API_KEY;

if (!openaiApiKey || typeof openaiApiKey !== 'string') {
throw new Error('OPENAI_API_KEY is not configured');
}

const openai = new OpenAI({
apiKey: openaiApiKey,
});

const prompt = `Generate a git branch name for the following work description.
The branch name should be:
- Descriptive but concise (max 40 characters to leave room for date)
- Use kebab-case (lowercase with hyphens)
- Avoid special characters except hyphens
- Be meaningful and related to the work
- DO NOT include any dates, timestamps, or time-related information
- Focus on the feature/change being implemented

Work description: ${workDescription}

Return only the branch name, nothing else.`;

try {
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'user',
content: prompt,
},
],
max_tokens: 50,
temperature: 0.3,
});

const branchName = completion.choices[0]?.message?.content?.trim();

if (!branchName) {
throw new Error('Failed to generate branch name from OpenAI');
}

// Clean up the branch name to ensure it's valid
const cleanedBranchName = branchName
.replace(/[^a-zA-Z0-9\-_/]/g, '-') // Replace invalid characters with hyphens
.replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
.replace(/\d{4}-\d{2}-\d{2}/g, '') // Remove date patterns (YYYY-MM-DD)
.replace(/\d{2}-\d{2}-\d{4}/g, '') // Remove date patterns (MM-DD-YYYY)
.replace(/-+/g, '-') // Replace multiple hyphens again after date removal
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens again
.toLowerCase();

// Add today's date in YYYY-MM-DD format
const today = new Date();
const dateString = today.toISOString().split('T')[0]; // YYYY-MM-DD format

return `${cleanedBranchName}-${dateString}`;
} catch (_error) {
console.error('Error generating branch name:', _error);
throw _error;
}
}

async function createAndSwitchToBranch(branchName: string, options: BranchGenerationOptions = {}): Promise<void> {
const { pullFromMain = true } = options;

console.log(`\n${pc.blue('🔄')} Creating branch: ${pc.bold(branchName)}`);

try {
// Create and switch to the new branch using proper zx syntax
await $$`git checkout -b ${branchName}`;

if (pullFromMain) {
console.log(pc.yellow('📥 Pulling latest changes from main...'));
try {
// First, fetch the latest changes
await $$`git fetch origin`;

// Then merge the latest main into the new branch
await $$`git merge origin/main`;
console.log(pc.green('✅ Successfully pulled and merged latest changes from main'));
} catch (_error) {
console.warn(pc.yellow('⚠️ Warning: Could not pull from main. You may need to resolve conflicts manually.'));
}
}

console.log(`\n${pc.green('🎉')} Successfully created and switched to branch: ${pc.bold(pc.green(branchName))}`);
console.log(`${pc.cyan('📝')} Ready to start working on: ${pc.bold(branchName)}`);
} catch (_error) {
console.error(pc.red(`Git command failed`));
process.exit(1);
}
}

async function main(): Promise<void> {
try {
console.log(pc.bold(pc.blue('🚀 Git Branch Generator')));
console.log(pc.blue('======================\n'));

// Request work description from user
const workDescription = await requestUserInput('Please describe the work you want to do: ');

if (!workDescription) {
console.error(pc.red('❌ Work description cannot be empty'));
process.exit(1);
}

// Ask about pulling from main
const pullFromMainInput = await requestUserInput('\nPull latest changes from main after creating branch? (y/n, default: y): ');
const pullFromMain = pullFromMainInput.toLowerCase() !== 'n' && pullFromMainInput.toLowerCase() !== 'no';

console.log(`\n${pc.magenta('🤖')} Generating branch name with AI...`);

// Generate branch name using OpenAI
const branchName = await generateBranchName(workDescription);

console.log(`${pc.green('✨')} Generated branch name: ${pc.bold(pc.green(branchName))}`);

// Confirm with user
const confirm = await requestUserInput(`\nCreate branch "${pc.bold(branchName)}"? (y/n, default: y): `);

if (confirm.toLowerCase() === 'n' || confirm.toLowerCase() === 'no') {
console.log(pc.red('❌ Branch creation cancelled'));
process.exit(0);
}

// Create and switch to the branch
await createAndSwitchToBranch(branchName, { pullFromMain });
} catch (error) {
console.error(pc.red('❌ Error:'), error);
process.exit(1);
}
}

main();
22 changes: 22 additions & 0 deletions src/utils/zx-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { $ } from 'zx';

// Base zx instance with configuration
export const $$ = $({
verbose: false,
});

// Quiet zx instance for silent operations
export const $$quiet = $({ quiet: true });

// Factory function to create zx instances with custom options
export function createZxInstance(options: { cwd?: string; quiet?: boolean; verbose?: boolean } = {}) {
return $({
verbose: options.verbose ?? false,
quiet: options.quiet ?? false,
...options,
});
}

// Common patterns
export const $$cwd = (cwd: string) => createZxInstance({ cwd });
export const $$quietCwd = (cwd: string) => createZxInstance({ cwd, quiet: true });
2 changes: 2 additions & 0 deletions tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ export default defineConfig((options: Options) => ({
'src/index.ts',
'src/validation/standardSchema.ts',
'src/validation/phoneNumber.ts',
'src/utils/zx-factory.ts',
'src/utils/sleep.ts',
'src/utils/generate-git-branch.ts',
'src/scripts/createEntryPoints.ts',
'src/file/findFile.ts',
'src/error/errorHandler.ts',
Expand Down
Loading