diff --git a/__tests__/main.test.js b/__tests__/main.test.js index 82e689a..cfa411a 100644 --- a/__tests__/main.test.js +++ b/__tests__/main.test.js @@ -1,6 +1,6 @@ -const core = require('@actions/core'); -const main = require('../src/main'); -const fs = require('fs'); +const core = require('@actions/core') +const main = require('../src/main') +const fs = require('fs') // Mock fs module with constants and promises jest.mock('fs', () => ({ @@ -28,16 +28,16 @@ jest.mock('fs', () => ({ W_OK: 2, X_OK: 1 } -})); +})) -const getInputMock = jest.spyOn(core, 'getInput').mockImplementation(); -const setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation(); -const infoMock = jest.spyOn(core, 'info').mockImplementation(); +const getInputMock = jest.spyOn(core, 'getInput').mockImplementation() +const setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation() +const infoMock = jest.spyOn(core, 'info').mockImplementation() describe('devcontainer-validator', () => { beforeEach(() => { - jest.clearAllMocks(); - }); + jest.clearAllMocks() + }) describe('validateExtensions', () => { it('should return empty array when all extensions are present', () => { @@ -47,12 +47,15 @@ describe('devcontainer-validator', () => { extensions: ['ext1', 'ext2', 'ext3'] } } - }; - const requiredExtensions = ['ext1', 'ext2']; - - const result = main.validateExtensions(devcontainerContent, requiredExtensions); - expect(result).toEqual([]); - }); + } + const requiredExtensions = ['ext1', 'ext2'] + + const result = main.validateExtensions( + devcontainerContent, + requiredExtensions + ) + expect(result).toEqual([]) + }) it('should return missing extensions when some are not configured', () => { const devcontainerContent = { @@ -61,12 +64,14 @@ describe('devcontainer-validator', () => { extensions: ['ext1'] } } - }; - const requiredExtensions = ['ext1', 'ext2', 'ext3']; - - const result = main.validateExtensions(devcontainerContent, requiredExtensions); - expect(result).toEqual(['ext2', 'ext3']); - }); + } + const requiredExtensions = ['ext1', 'ext2', 'ext3'] + const result = main.validateExtensions( + devcontainerContent, + requiredExtensions + ) + expect(result).toEqual(['ext2', 'ext3']) + }) it('should handle empty extensions configuration', () => { const devcontainerContent = { @@ -75,13 +80,16 @@ describe('devcontainer-validator', () => { extensions: [] } } - }; - const requiredExtensions = ['ext1', 'ext2']; - - const result = main.validateExtensions(devcontainerContent, requiredExtensions); - expect(result).toEqual(['ext1', 'ext2']); - }); - }); + } + const requiredExtensions = ['ext1', 'ext2'] + + const result = main.validateExtensions( + devcontainerContent, + requiredExtensions + ) + expect(result).toEqual(['ext1', 'ext2']) + }) + }) describe('validateTasks', () => { it('should return null when all required tasks are present', () => { @@ -91,18 +99,18 @@ describe('devcontainer-validator', () => { test: 'go test ./...', run: 'go run .' } - }; - - const result = main.validateTasks(devcontainerContent); - expect(result).toBeNull(); - }); + } + + const result = main.validateTasks(devcontainerContent) + expect(result).toBeNull() + }) it('should return error when tasks property is missing', () => { - const devcontainerContent = {}; - - const result = main.validateTasks(devcontainerContent); - expect(result).toBe("'tasks' property is missing"); - }); + const devcontainerContent = {} + + const result = main.validateTasks(devcontainerContent) + expect(result).toBe("'tasks' property is missing") + }) it('should return error when required tasks are missing', () => { const devcontainerContent = { @@ -110,11 +118,11 @@ describe('devcontainer-validator', () => { build: 'go build .', test: 'go test ./...' } - }; - - const result = main.validateTasks(devcontainerContent); - expect(result).toBe('Missing or invalid required tasks: run'); - }); + } + + const result = main.validateTasks(devcontainerContent) + expect(result).toBe('Missing or invalid required tasks: run') + }) it('should return error when tasks are not strings', () => { const devcontainerContent = { @@ -123,12 +131,12 @@ describe('devcontainer-validator', () => { test: 'go test ./...', run: 'go run .' } - }; - - const result = main.validateTasks(devcontainerContent); - expect(result).toBe('Missing or invalid required tasks: build'); - }); - }); + } + + const result = main.validateTasks(devcontainerContent) + expect(result).toBe('Missing or invalid required tasks: build') + }) + }) describe('run', () => { it('should pass when all extensions are present', async () => { @@ -138,45 +146,47 @@ describe('devcontainer-validator', () => { extensions: ['ext1', 'ext2'] } } - }; + } getInputMock.mockImplementation(name => { switch (name) { case 'extensions-list': - return 'ext1,ext2'; + return 'ext1,ext2' case 'validate-tasks': - return 'false'; + return 'false' default: - return '.devcontainer/devcontainer.json'; + return '.devcontainer/devcontainer.json' } - }); + }) - fs.existsSync.mockReturnValue(true); - fs.readFileSync.mockReturnValue(JSON.stringify(mockDevcontainer)); + fs.existsSync.mockReturnValue(true) + fs.readFileSync.mockReturnValue(JSON.stringify(mockDevcontainer)) - await main.run(); - expect(infoMock).toHaveBeenCalledWith('All validations passed successfully'); - }); + await main.run() + expect(infoMock).toHaveBeenCalledWith( + 'All validations passed successfully' + ) + }) it('should fail when devcontainer.json is not found', async () => { getInputMock.mockImplementation(name => { switch (name) { case 'extensions-list': - return 'ext1,ext2'; + return 'ext1,ext2' case 'validate-tasks': - return 'false'; + return 'false' default: - return '.devcontainer/devcontainer.json'; + return '.devcontainer/devcontainer.json' } - }); + }) - fs.existsSync.mockReturnValue(false); + fs.existsSync.mockReturnValue(false) - await main.run(); + await main.run() expect(setFailedMock).toHaveBeenCalledWith( expect.stringContaining('devcontainer.json not found') - ); - }); + ) + }) it('should fail when required extensions are missing', async () => { const mockDevcontainer = { @@ -185,27 +195,27 @@ describe('devcontainer-validator', () => { extensions: ['ext1'] } } - }; + } getInputMock.mockImplementation(name => { switch (name) { case 'extensions-list': - return 'ext1,ext2'; + return 'ext1,ext2' case 'validate-tasks': - return 'false'; + return 'false' default: - return '.devcontainer/devcontainer.json'; + return '.devcontainer/devcontainer.json' } - }); + }) - fs.existsSync.mockReturnValue(true); - fs.readFileSync.mockReturnValue(JSON.stringify(mockDevcontainer)); + fs.existsSync.mockReturnValue(true) + fs.readFileSync.mockReturnValue(JSON.stringify(mockDevcontainer)) - await main.run(); + await main.run() expect(setFailedMock).toHaveBeenCalledWith( expect.stringContaining('Missing required extensions: ext2') - ); - }); + ) + }) it('should validate tasks when validate-tasks is true', async () => { const mockDevcontainer = { @@ -219,25 +229,27 @@ describe('devcontainer-validator', () => { test: 'test cmd', run: 'run cmd' } - }; + } getInputMock.mockImplementation(name => { switch (name) { case 'extensions-list': - return 'ext1'; + return 'ext1' case 'validate-tasks': - return 'true'; + return 'true' default: - return '.devcontainer/devcontainer.json'; + return '.devcontainer/devcontainer.json' } - }); + }) - fs.existsSync.mockReturnValue(true); - fs.readFileSync.mockReturnValue(JSON.stringify(mockDevcontainer)); + fs.existsSync.mockReturnValue(true) + fs.readFileSync.mockReturnValue(JSON.stringify(mockDevcontainer)) - await main.run(); - expect(infoMock).toHaveBeenCalledWith('All validations passed successfully'); - }); + await main.run() + expect(infoMock).toHaveBeenCalledWith( + 'All validations passed successfully' + ) + }) it('should fail when tasks validation fails', async () => { const mockDevcontainer = { @@ -249,26 +261,26 @@ describe('devcontainer-validator', () => { tasks: { build: 'build cmd' } - }; + } getInputMock.mockImplementation(name => { switch (name) { case 'extensions-list': - return 'ext1'; + return 'ext1' case 'validate-tasks': - return 'true'; + return 'true' default: - return '.devcontainer/devcontainer.json'; + return '.devcontainer/devcontainer.json' } - }); + }) - fs.existsSync.mockReturnValue(true); - fs.readFileSync.mockReturnValue(JSON.stringify(mockDevcontainer)); + fs.existsSync.mockReturnValue(true) + fs.readFileSync.mockReturnValue(JSON.stringify(mockDevcontainer)) - await main.run(); + await main.run() expect(setFailedMock).toHaveBeenCalledWith( expect.stringContaining('Missing or invalid required tasks: test, run') - ); - }); - }); -}); + ) + }) + }) +}) diff --git a/action.yml b/action.yml index 9b6f88d..9723445 100644 --- a/action.yml +++ b/action.yml @@ -17,5 +17,5 @@ inputs: default: 'false' runs: - using: node22 + using: node20 main: dist/index.js diff --git a/src/main.js b/src/main.js index 811b790..c13e56d 100644 --- a/src/main.js +++ b/src/main.js @@ -1,58 +1,70 @@ -const core = require('@actions/core'); -const fs = require('fs'); +const core = require('@actions/core') +const fs = require('fs') function validateExtensions(devcontainerContent, requiredExtensions) { - const configuredExtensions = devcontainerContent?.customizations?.vscode?.extensions || []; + const configuredExtensions = + devcontainerContent?.customizations?.vscode?.extensions || [] const missingExtensions = requiredExtensions.filter( required => !configuredExtensions.includes(required) - ); - return missingExtensions; + ) + return missingExtensions } function validateTasks(devcontainerContent) { - const tasks = devcontainerContent.tasks; + const tasks = devcontainerContent.tasks if (!tasks) { - return "'tasks' property is missing"; + return "'tasks' property is missing" } - const requiredTasks = ['build', 'test', 'run']; - const missingTasks = requiredTasks.filter(task => !tasks[task] || typeof tasks[task] !== 'string'); - + const requiredTasks = ['build', 'test', 'run'] + const missingTasks = requiredTasks.filter( + task => !tasks[task] || typeof tasks[task] !== 'string' + ) + if (missingTasks.length > 0) { - return `Missing or invalid required tasks: ${missingTasks.join(', ')}`; + return `Missing or invalid required tasks: ${missingTasks.join(', ')}` } - - return null; + + return null } async function run() { try { - const extensionsList = core.getInput('extensions-list', { required: true }); - const devcontainerPath = core.getInput('devcontainer-path', { required: false }) || '.devcontainer/devcontainer.json'; - const shouldValidateTasks = core.getInput('validate-tasks') === 'true'; + const extensionsList = core.getInput('extensions-list', { required: true }) + const devcontainerPath = + core.getInput('devcontainer-path', { required: false }) || + '.devcontainer/devcontainer.json' + const shouldValidateTasks = core.getInput('validate-tasks') === 'true' if (!fs.existsSync(devcontainerPath)) { - throw new Error(`devcontainer.json not found at ${devcontainerPath}`); + throw new Error(`devcontainer.json not found at ${devcontainerPath}`) } - const devcontainerContent = JSON.parse(fs.readFileSync(devcontainerPath, 'utf8')); - const requiredExtensions = extensionsList.split(',').map(ext => ext.trim()); - const missingExtensions = validateExtensions(devcontainerContent, requiredExtensions); + const devcontainerContent = JSON.parse( + fs.readFileSync(devcontainerPath, 'utf8') + ) + const requiredExtensions = extensionsList.split(',').map(ext => ext.trim()) + const missingExtensions = validateExtensions( + devcontainerContent, + requiredExtensions + ) if (missingExtensions.length > 0) { - throw new Error(`Missing required extensions: ${missingExtensions.join(', ')}`); + throw new Error( + `Missing required extensions: ${missingExtensions.join(', ')}` + ) } if (shouldValidateTasks) { - const tasksError = validateTasks(devcontainerContent); + const tasksError = validateTasks(devcontainerContent) if (tasksError) { - throw new Error(tasksError); + throw new Error(tasksError) } } - core.info('All validations passed successfully'); + core.info('All validations passed successfully') } catch (error) { - core.setFailed(error.message); + core.setFailed(error.message) } }