원클릭으로
javascript-refactoring
Split large JavaScript files into maintainable modules safely.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Split large JavaScript files into maintainable modules safely.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Conversational skill that interviews users to design new agentic workflows
Route gh-aw workflow design/create/debug/upgrade requests to the right prompts.
Analyze and reduce token consumption in agentic workflows — guardrail-specific entry points, measurement, and optimization techniques.
Implement secret-safe HTTP headers for MCP transport in gh-aw.
Review code that performs git or gh operations against repository checkouts in gh-aw, checking that the right credentials are available at the right time and that sparseness, shallowness and credential-free factors are properly considered.
Teach Copilot how to plan, address, and respond to pull request review feedback.
| name | javascript-refactoring |
| description | Split large JavaScript files into maintainable modules safely. |
Use this guide to refactor JavaScript into separate .cjs files in gh-aw.
gh-aw uses CommonJS modules (.cjs) for JavaScript in GitHub Actions workflows. These files are:
//go:embed directivesrequire() callsactions/github-script@v8Top-level .cjs scripts executed directly in workflows follow this 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?
await main() during inline execution in GitHub Actionsmain() with mocksExamples 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 ProjectsAll of these files export main but do not call it directly.
Create your new file in /home/runner/work/gh-aw/gh-aw/pkg/workflow/js/ with a descriptive name:
File naming convention:
sanitize_content.cjs, load_agent_output.cjs).cjs extension (CommonJS module)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:
// @ts-check for TypeScript checking/// <reference types="@actions/github-script" /> for GitHub Actions typesmodule.exports = { ... }@actions/core or @actions/github - these are available globally in GitHub ActionsCreate 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:
core and github globals as neededawait import()) to allow mocking before module loadbeforeEach to ensure test isolationpkg/workflow/js/*.test.cjs filesRun tests:
make test-js
Add an //go:embed directive and variable in the appropriate Go file:
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
}
}
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:
js.go are for shared utilities that get bundled into other scriptsscripts.go are for main scripts that use the bundler to inline dependenciessync.Once pattern for lazy bundling in scripts.gorequire() calls at runtimeIf 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:
require('./my_module.cjs') in any scriptGetJavaScriptSources() maprequire() statementNo additional bundler registration needed - just ensure the file is in the GetJavaScriptSources() map.
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:
./.cjs extensionMultiple requires example:
const { sanitizeContent } = require("./sanitize_content.cjs");
const { loadAgentOutput } = require("./load_agent_output.cjs");
const { generateStagedPreview } = require("./staged_preview.cjs");
Let's walk through creating a new format_timestamp.cjs utility:
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,
};
pkg/workflow/js/format_timestamp.test.cjsimport { 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 } = await import("./format_timestamp.cjs");
const date = new Date('2024-01-15T12:30:00Z');
const result = formatTimestampHuman(date);
expect(result).toContain('Jan');
expect(result).toContain('15');
expect(result).toContain('2024');
});
});
pkg/workflow/js.go://go:embed js/format_timestamp.cjs
var formatTimestampScript string
func GetJavaScriptSources() map[string]string {
return map[string]string{
// ... existing entries ...
"format_timestamp.cjs": formatTimestampScript,
}
}
// @ts-check
/// <reference types="@actions/github-script" />
const { formatTimestamp } = require("./format_timestamp.cjs");
async function main() {
const now = new Date();
core.info(`Current time: ${formatTimestamp(now)}`);
}
module.exports = { main };
Note: The script exports main but does not call it. The bundler will inject await main() when the script is executed inline in GitHub Actions.
# Format the code
make fmt-cjs
# Run JavaScript tests
make test-js
# Run Go tests (includes bundler tests)
make test-unit
# Build the binary (embeds JavaScript files)
make build
Before committing your refactored code:
.cjs file created in pkg/workflow/js/.test.cjs filemake test-jspkg/workflow/js.go or pkg/workflow/scripts.goGetJavaScriptSources() mapsync.Oncerequire() statements work correctly in other filesmake fmt-cjsmake lint-cjsmake test-unitmake buildFiles like sanitize_content.cjs, load_agent_output.cjs that provide reusable functions:
js.go with //go:embedGetJavaScriptSources() maprequire() in other scriptsFiles like create_issue.cjs, add_labels.cjs that are top-level scripts:
scripts.go with //go:embed as xxxSource variablesync.Once patternrequire() utilities from GetJavaScriptSources()main function but NOT call it - the bundler injects await main() during executionFiles like parse_claude_log.cjs that parse AI engine logs:
js.go with //go:embedGetLogParserScript() functionCause: File not added to GetJavaScriptSources() map
Solution: Add the file to the map in pkg/workflow/js.go
Cause: Missing global mocks
Solution: Add proper mocks before importing the module:
global.core = mockCore;
global.github = mockGithub;
Cause: File A requires File B which requires File A
Solution: Restructure to break the circular dependency, or combine the modules
Cause: Go build cache not recognizing embedded file changes
Solution:
make clean
make build
pkg/workflow/bundler.gopkg/workflow/js.gopkg/workflow/scripts.gopkg/workflow/js/*.test.cjs