javascript-refactoring
Instructions for refactoring JavaScript code into separate files
Install
mkdir -p .claude/skills/javascript-refactoring && curl -L -o skill.zip "https://mcp.directory/api/skills/download/6200" && unzip -o skill.zip -d .claude/skills/javascript-refactoring && rm skill.zipInstalls to .claude/skills/javascript-refactoring
About this skill
JavaScript Code Refactoring Guide
This guide explains how to refactor JavaScript code into a separate .cjs file in the gh-aw repository. Follow these steps when extracting shared functionality or creating new JavaScript modules.
Overview
The gh-aw project uses CommonJS modules (.cjs files) for JavaScript code that runs in GitHub Actions workflows. These files are:
- Embedded in the Go binary using
//go:embeddirectives - Bundled using a custom JavaScript bundler that inlines local
require()calls - Executed in GitHub Actions using
actions/github-script@v8
Top-Level Script Pattern
Top-level .cjs scripts (those that are executed directly in workflows) follow a specific pattern:
✅ Correct Pattern - Export main, but don't call it:
async function main() {
// Script logic here
core.info("Running the script");
}
module.exports = { main };
❌ Incorrect Pattern - Don't call main in the file:
async function main() {
// Script logic here
core.info("Running the script");
}
await main(); // ❌ Don't do this!
module.exports = { main };
Why this pattern?
- The bundler automatically injects
await main()during inline execution in GitHub Actions - This allows the script to be both imported (for testing) and executed (in workflows)
- It provides a clean separation between module definition and execution
- It enables better testing by allowing tests to import and call
main()with mocks
Examples of top-level scripts:
create_issue.cjs- Creates GitHub issuesadd_comment.cjs- Adds comments to issues/PRsadd_labels.cjs- Adds labels to issues/PRsupdate_project.cjs- Updates GitHub Projects
All of these files export main but do not call it directly.
Step 1: Create the New .cjs File
Create your new file in /home/runner/work/gh-aw/gh-aw/pkg/workflow/js/ with a descriptive name:
File naming convention:
- Use snake_case for filenames (e.g.,
sanitize_content.cjs,load_agent_output.cjs) - Use
.cjsextension (CommonJS module) - Choose names that clearly describe the module's purpose
Example file structure:
// @ts-check
/// <reference types="@actions/github-script" />
/**
* Brief description of what this module does
*/
/**
* Function documentation
* @param {string} input - Description of parameter
* @returns {string} Description of return value
*/
function myFunction(input) {
// Implementation
return input;
}
// Export the function(s)
module.exports = {
myFunction,
};
Key points:
- Include
// @ts-checkfor TypeScript checking - Include
/// <reference types="@actions/github-script" />for GitHub Actions types - Use JSDoc comments for documentation
- Export functions using
module.exports = { ... } - Do NOT import
@actions/coreor@actions/github- these are available globally in GitHub Actions
Step 2: Add Tests
Create a test file with the same base name plus .test.cjs:
Example: pkg/workflow/js/my_module.test.cjs
import { describe, it, expect, beforeEach, vi } from "vitest";
// Mock the global objects that GitHub Actions provides
const mockCore = {
debug: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
setFailed: vi.fn(),
setOutput: vi.fn(),
summary: {
addRaw: vi.fn().mockReturnThis(),
write: vi.fn().mockResolvedValue(),
},
};
// Set up global mocks before importing the module
global.core = mockCore;
describe("myFunction", () => {
beforeEach(() => {
// Reset mocks before each test
vi.clearAllMocks();
});
it("should handle basic input", async () => {
// Import the module to test
const { myFunction } = await import("./my_module.cjs");
const result = myFunction("test input");
expect(result).toBe("expected output");
});
it("should handle edge cases", async () => {
const { myFunction } = await import("./my_module.cjs");
const result = myFunction("");
expect(result).toBe("");
});
});
Testing guidelines:
- Use vitest for testing framework
- Mock
coreandgithubglobals as needed - Use dynamic imports (
await import()) to allow mocking before module load - Clear mocks in
beforeEachto ensure test isolation - Test both success cases and error handling
- Follow existing test patterns in
pkg/workflow/js/*.test.cjsfiles
Run tests:
make test-js
Step 3: Add Embedded Variable in Go
Add an //go:embed directive and variable in the appropriate Go file:
For shared utility functions (used by multiple scripts):
Add to pkg/workflow/js.go:
//go:embed js/my_module.cjs
var myModuleScript string
Then add to the GetJavaScriptSources() function:
func GetJavaScriptSources() map[string]string {
return map[string]string{
"sanitize_content.cjs": sanitizeContentScript,
"sanitize_label_content.cjs": sanitizeLabelContentScript,
"sanitize_workflow_name.cjs": sanitizeWorkflowNameScript,
"load_agent_output.cjs": loadAgentOutputScript,
"staged_preview.cjs": stagedPreviewScript,
"is_truthy.cjs": isTruthyScript,
"my_module.cjs": myModuleScript, // Add this line
}
}
For main scripts (top-level scripts that use bundling):
Add to pkg/workflow/scripts.go:
//go:embed js/my_script.cjs
var myScriptSource string
Then create a getter function with bundling:
var (
myScript string
myScriptOnce sync.Once
)
// getMyScript returns the bundled my_script script
// Bundling is performed on first access and cached for subsequent calls
func getMyScript() string {
myScriptOnce.Do(func() {
sources := GetJavaScriptSources()
bundled, err := BundleJavaScriptFromSources(myScriptSource, sources, "")
if err != nil {
scriptsLog.Printf("Bundling failed for my_script, using source as-is: %v", err)
// If bundling fails, use the source as-is
myScript = myScriptSource
} else {
myScript = bundled
}
})
return myScript
}
Important:
- Variables in
js.goare for shared utilities that get bundled into other scripts - Variables in
scripts.goare for main scripts that use the bundler to inline dependencies - Use
sync.Oncepattern for lazy bundling inscripts.go - The bundler will inline all local
require()calls at runtime
Step 4: Register in the Bundler (if creating a shared utility)
If you're creating a shared utility that will be used by other scripts via require(), it's automatically available through the GetJavaScriptSources() map (Step 3).
The bundler will:
- Detect
require('./my_module.cjs')in any script - Look up the file in the
GetJavaScriptSources()map - Inline the required module's content
- Remove the
require()statement - Deduplicate if the same module is required multiple times
No additional bundler registration needed - just ensure the file is in the GetJavaScriptSources() map.
Step 5: Use Local Require in Other JavaScript Files
To use your new module in other JavaScript files, use CommonJS require():
Example usage in another .cjs file:
// @ts-check
/// <reference types="@actions/github-script" />
const { myFunction } = require("./my_module.cjs");
async function main() {
const result = myFunction("some input");
core.info(`Result: ${result}`);
}
module.exports = { main };
Important: Top-level scripts should export main but NOT call it directly. The bundler injects await main() during inline execution in GitHub Actions.
Require guidelines:
- Use relative paths starting with
./ - Include the
.cjsextension - Use destructuring to import specific functions
- The bundler will inline the required module at compile time
Multiple requires example:
const { sanitizeContent } = require("./sanitize_content.cjs");
const { loadAgentOutput } = require("./load_agent_output.cjs");
const { generateStagedPreview } = require("./staged_preview.cjs");
Complete Example: Creating a New Utility Module
Let's walk through creating a new format_timestamp.cjs utility:
1. Create the file: pkg/workflow/js/format_timestamp.cjs
// @ts-check
/// <reference types="@actions/github-script" />
/**
* Formats a timestamp to ISO 8601 format
* @param {Date|string|number} timestamp - Timestamp to format
* @returns {string} ISO 8601 formatted timestamp
*/
function formatTimestamp(timestamp) {
const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
return date.toISOString();
}
/**
* Formats a timestamp to a human-readable string
* @param {Date|string|number} timestamp - Timestamp to format
* @returns {string} Human-readable timestamp
*/
function formatTimestampHuman(timestamp) {
const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
return date.toLocaleString('en-US', {
dateStyle: 'medium',
timeStyle: 'short'
});
}
module.exports = {
formatTimestamp,
formatTimestampHuman,
};
2. Create tests: pkg/workflow/js/format_timestamp.test.cjs
import { describe, it, expect } from "vitest";
describe("formatTimestamp", () => {
it("should format Date object to ISO 8601", async () => {
const { formatTimestamp } = await import("./format_timestamp.cjs");
const date = new Date('2024-01-15T12:30:00Z');
const result = formatTimestamp(date);
expect(result).toBe('2024-01-15T12:30:00.000Z');
});
it("should format timestamp number to ISO 8601", async () => {
const { formatTimestamp } = await import("./format_timestamp.cjs");
const timestamp = 1705323000000; // Jan 15, 2024 12:30:00 UTC
const result = formatTimestamp(timestamp);
expect(result).toBe('2024-01-15T12:30:00.000Z');
});
});
describe("formatTimestampHuman", () => {
it("should format Date object to human-readable string", async () => {
const { formatTimestampHuman }
---
*Content truncated.*
More by githubnext
View all skills by githubnext →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
fastapi-templates
wshobson
Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
Related MCP Servers
Browse all serversData Extractor converts JavaScript and TypeScript code into JSON configuration files using JSON stringify for better mai
TypeScript Refactoring offers advanced TypeScript/JavaScript code analysis and intelligent refactoring for seamless and
Leverage advanced web scraping in Node.js using Playwright and BeautifulSoup for robust JavaScript web scraping and cont
Unlock AI-ready web data with Firecrawl: scrape any website, handle dynamic content, and automate web scraping for resea
Boost your AI code assistant with Context7: inject real-time API documentation from OpenAPI specification sources into y
Extend your developer tools with GitHub MCP Server for advanced automation, supporting GitHub Student and student packag
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.