diff --git a/package.json b/package.json index e8d7610..67ae903 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,13 @@ "codebot.templatesFolderPath": { "type": "string", "default": "templates", + "scope": "resource", "markdownDescription": "Path to the templates folder, relative to the workspace root." }, "codebot.templateSettings": { "type": "object", "default": {}, + "scope": "resource", "markdownDescription": "Per-template settings. Keys must match template folder names.", "additionalProperties": { "type": "object", diff --git a/src/__mocks__/vscode.js b/src/__mocks__/vscode.js index c2d7033..58d5248 100644 --- a/src/__mocks__/vscode.js +++ b/src/__mocks__/vscode.js @@ -28,6 +28,20 @@ const workspace = { index: 0, }, ], + getWorkspaceFolder(uri) { + const folders = workspace.workspaceFolders; + if (!folders || folders.length === 0) return undefined; + const sep = require('node:path').sep; + return ( + [...folders] + .sort((a, b) => b.uri.fsPath.length - a.uri.fsPath.length) + .find( + f => + uri.fsPath === f.uri.fsPath || + uri.fsPath.startsWith(f.uri.fsPath + sep), + ) ?? undefined + ); + }, }; const window = { diff --git a/src/__tests__/commands/createComponent.test.ts b/src/__tests__/commands/createComponent.test.ts index b2ee9b1..a62399f 100644 --- a/src/__tests__/commands/createComponent.test.ts +++ b/src/__tests__/commands/createComponent.test.ts @@ -434,6 +434,126 @@ describe('createComponent', () => { }); }); + describe('multi-root workspace', () => { + const appClickedUri = vscode.Uri.file('/test/packages/app/src/components'); + const libClickedUri = vscode.Uri.file('/test/packages/lib/src/components'); + + beforeEach(() => { + ( + vscode.workspace as unknown as Record + ).workspaceFolders = [ + { uri: { fsPath: '/test/packages/app' }, name: 'app', index: 0 }, + { uri: { fsPath: '/test/packages/lib' }, name: 'lib', index: 1 }, + ]; + + const configFactory = ( + _section?: string, + scope?: vscode.ConfigurationScope | null, + ): vscode.WorkspaceConfiguration => { + const fsPath = + scope != null && 'fsPath' in scope + ? (scope as vscode.Uri).fsPath + : undefined; + return { + get: jest.fn().mockImplementation((key: string) => { + if (key === 'templatesFolderPath') return 'templates'; + if (key === 'templateSettings') { + return fsPath?.startsWith('/test/packages/app') + ? { ReactPackage: { nameFormat: 'kebab-case' } } + : {}; + } + return undefined; + }), + update: jest.fn(), + has: jest.fn(), + inspect: jest.fn(), + } as unknown as vscode.WorkspaceConfiguration; + }; + + mockGetConfiguration.mockImplementation(configFactory); + }); + + afterEach(() => { + ( + vscode.workspace as unknown as Record + ).workspaceFolders = [ + { uri: { fsPath: '/test/workspace' }, name: 'workspace', index: 0 }, + ]; + }); + + const setupTemplate = () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ReactPackage', 2]] as never) + .mockResolvedValueOnce([['index.hbs', 1]] as never); + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + }; + + it('applies nameFormat from the workspace folder containing the clicked path', async () => { + setupTemplate(); + mockWindow.showInputBox.mockResolvedValue('my-package'); + + await createComponent(appClickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'my-package' created"), + ); + }); + + it('applies a different config when clicking inside a different workspace folder', async () => { + setupTemplate(); + mockWindow.showInputBox.mockResolvedValue('my-package'); + + await createComponent(libClickedUri); + + // lib has no nameFormat → falls back to pascal-case + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'MyPackage' created"), + ); + }); + + it('passes clickedUri to getConfiguration, not the workspace folder root', async () => { + setupTemplate(); + mockWindow.showInputBox.mockResolvedValue('my-package'); + + await createComponent(appClickedUri); + + expect(mockGetConfiguration).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ fsPath: appClickedUri.fsPath }), + ); + }); + + it('does not confuse a folder whose name is a prefix of another folder', async () => { + ( + vscode.workspace as unknown as Record + ).workspaceFolders = [ + { uri: { fsPath: '/test/packages/app' }, name: 'app', index: 0 }, + { + uri: { fsPath: '/test/packages/application' }, + name: 'application', + index: 1, + }, + ]; + + const applicationUri = vscode.Uri.file( + '/test/packages/application/src/components', + ); + setupTemplate(); + mockWindow.showInputBox.mockResolvedValue('my-package'); + + await createComponent(applicationUri); + + // templates must be looked up under /test/packages/application, not /test/packages/app + const templatesDirCall = mockFs.readDirectory.mock + .calls[0][0] as vscode.Uri; + expect(templatesDirCall.fsPath).toContain('/test/packages/application'); + expect(templatesDirCall.fsPath).not.toMatch(/\/test\/packages\/app\b/); + }); + }); + describe('nameFormat per template', () => { const setupTemplateWithFormat = ( templateName: string, diff --git a/src/__tests__/commands/updateComponent.test.ts b/src/__tests__/commands/updateComponent.test.ts index dddd234..170697a 100644 --- a/src/__tests__/commands/updateComponent.test.ts +++ b/src/__tests__/commands/updateComponent.test.ts @@ -267,6 +267,127 @@ describe('updateComponent', () => { }); }); + describe('multi-root workspace', () => { + const appClickedUri = vscode.Uri.file( + '/test/packages/app/src/components/MyButton', + ); + const libClickedUri = vscode.Uri.file( + '/test/packages/lib/src/components/MyButton', + ); + + beforeEach(() => { + ( + vscode.workspace as unknown as Record + ).workspaceFolders = [ + { uri: { fsPath: '/test/packages/app' }, name: 'app', index: 0 }, + { uri: { fsPath: '/test/packages/lib' }, name: 'lib', index: 1 }, + ]; + + const configFactory = ( + _section?: string, + scope?: vscode.ConfigurationScope | null, + ): vscode.WorkspaceConfiguration => { + const fsPath = + scope != null && 'fsPath' in scope + ? (scope as vscode.Uri).fsPath + : undefined; + return { + get: jest.fn().mockImplementation((key: string) => { + if (key === 'templatesFolderPath') return 'templates'; + if (key === 'templateSettings') { + return fsPath?.startsWith('/test/packages/app') + ? { ReactPackage: { nameFormat: 'kebab-case' } } + : {}; + } + return undefined; + }), + update: jest.fn(), + has: jest.fn(), + inspect: jest.fn(), + } as unknown as vscode.WorkspaceConfiguration; + }; + + mockGetConfiguration.mockImplementation(configFactory); + }); + + afterEach(() => { + ( + vscode.workspace as unknown as Record + ).workspaceFolders = [ + { uri: { fsPath: '/test/workspace' }, name: 'workspace', index: 0 }, + ]; + }); + + const setupTemplate = () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ReactPackage', 2]] as never) + .mockResolvedValueOnce([['index.hbs', 1]] as never); + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + }; + + it('applies nameFormat from the workspace folder containing the clicked path', async () => { + setupTemplate(); + + await updateComponent(appClickedUri); + + // app has kebab-case → 'MyButton' becomes 'my-button' + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'my-button' updated"), + ); + }); + + it('applies a different config when clicking inside a different workspace folder', async () => { + setupTemplate(); + + await updateComponent(libClickedUri); + + // lib has no nameFormat → falls back to pascal-case → 'MyButton' stays + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'MyButton' updated"), + ); + }); + + it('passes clickedUri to getConfiguration, not the workspace folder root', async () => { + setupTemplate(); + + await updateComponent(appClickedUri); + + expect(mockGetConfiguration).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ fsPath: appClickedUri.fsPath }), + ); + }); + + it('does not confuse a folder whose name is a prefix of another folder', async () => { + ( + vscode.workspace as unknown as Record + ).workspaceFolders = [ + { uri: { fsPath: '/test/packages/app' }, name: 'app', index: 0 }, + { + uri: { fsPath: '/test/packages/application' }, + name: 'application', + index: 1, + }, + ]; + + const applicationUri = vscode.Uri.file( + '/test/packages/application/src/components/MyButton', + ); + setupTemplate(); + + await updateComponent(applicationUri); + + // templates must be looked up under /test/packages/application, not /test/packages/app + const templatesDirCall = mockFs.readDirectory.mock + .calls[0][0] as vscode.Uri; + expect(templatesDirCall.fsPath).toContain('/test/packages/application'); + expect(templatesDirCall.fsPath).not.toMatch(/\/test\/packages\/app\b/); + }); + }); + describe('nameFormat per template', () => { const setupTemplateWithFormat = ( templateName: string, diff --git a/src/__tests__/helpers/fs/createFile.test.ts b/src/__tests__/helpers/fs/createFile.test.ts deleted file mode 100644 index 5236116..0000000 --- a/src/__tests__/helpers/fs/createFile.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import { createFile } from '../../../helpers/fs/createFile'; - -describe('createFile', () => { - afterEach(() => jest.restoreAllMocks()); - - it('creates parent directories and writes the file', () => { - const mkdirSpy = jest.spyOn(fs, 'mkdirSync').mockReturnValue(undefined); - const writeSpy = jest.spyOn(fs, 'writeFileSync').mockReturnValue(undefined); - - const result = createFile( - '/project/src/Button/Button.tsx', - 'export default function Button() {}', - ); - - expect(result).toEqual({ success: true, value: undefined }); - expect(mkdirSpy).toHaveBeenCalledWith( - path.dirname('/project/src/Button/Button.tsx'), - { - recursive: true, - }, - ); - expect(writeSpy).toHaveBeenCalledWith( - '/project/src/Button/Button.tsx', - 'export default function Button() {}', - 'utf-8', - ); - }); - - it('returns FILE_WRITE_ERROR when writeFileSync throws', () => { - jest.spyOn(fs, 'mkdirSync').mockReturnValue(undefined); - jest.spyOn(fs, 'writeFileSync').mockImplementation(() => { - throw new Error('Permission denied'); - }); - - const result = createFile('/locked/file.ts', 'content'); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.code).toBe('FILE_WRITE_ERROR'); - expect(result.error.message).toContain('/locked/file.ts'); - } - }); - - it('returns FILE_WRITE_ERROR when mkdirSync throws', () => { - jest.spyOn(fs, 'mkdirSync').mockImplementation(() => { - throw new Error('Read-only filesystem'); - }); - - const result = createFile('/readonly/dir/file.ts', 'content'); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.code).toBe('FILE_WRITE_ERROR'); - } - }); - - it('preserves the original error as cause', () => { - const originalError = new Error('EACCES'); - jest.spyOn(fs, 'mkdirSync').mockReturnValue(undefined); - jest.spyOn(fs, 'writeFileSync').mockImplementation(() => { - throw originalError; - }); - - const result = createFile('/file.ts', ''); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.cause).toBe(originalError); - } - }); -}); diff --git a/src/__tests__/helpers/fs/createFolder.test.ts b/src/__tests__/helpers/fs/createFolder.test.ts deleted file mode 100644 index 440c632..0000000 --- a/src/__tests__/helpers/fs/createFolder.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as fs from 'node:fs'; -import { createFolder } from '../../../helpers/fs/createFolder'; - -describe('createFolder', () => { - afterEach(() => jest.restoreAllMocks()); - - it('creates folder with recursive option', () => { - const mkdirSpy = jest.spyOn(fs, 'mkdirSync').mockReturnValue(undefined); - - const result = createFolder('/project/src/components/Button'); - - expect(result).toEqual({ success: true, value: undefined }); - expect(mkdirSpy).toHaveBeenCalledWith('/project/src/components/Button', { - recursive: true, - }); - }); - - it('returns FOLDER_CREATE_ERROR when mkdirSync throws', () => { - jest.spyOn(fs, 'mkdirSync').mockImplementation(() => { - throw new Error('EACCES: permission denied'); - }); - - const result = createFolder('/readonly/dir'); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.code).toBe('FOLDER_CREATE_ERROR'); - expect(result.error.message).toContain('/readonly/dir'); - } - }); - - it('preserves the original error as cause', () => { - const originalError = new Error('EACCES'); - jest.spyOn(fs, 'mkdirSync').mockImplementation(() => { - throw originalError; - }); - - const result = createFolder('/dir'); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.cause).toBe(originalError); - } - }); -}); diff --git a/src/__tests__/helpers/fs/fileExists.test.ts b/src/__tests__/helpers/fs/fileExists.test.ts deleted file mode 100644 index c495be3..0000000 --- a/src/__tests__/helpers/fs/fileExists.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as fs from 'node:fs'; -import { fileExists } from '../../../helpers/fs/fileExists'; - -describe('fileExists', () => { - afterEach(() => jest.restoreAllMocks()); - - it('returns true when file is accessible', () => { - jest.spyOn(fs, 'accessSync').mockReturnValue(undefined); - - expect(fileExists('/project/file.ts')).toBe(true); - expect(fs.accessSync).toHaveBeenCalledWith( - '/project/file.ts', - fs.constants.F_OK, - ); - }); - - it('returns false when file does not exist', () => { - jest.spyOn(fs, 'accessSync').mockImplementation(() => { - throw new Error('ENOENT'); - }); - - expect(fileExists('/missing/file.ts')).toBe(false); - }); - - it('returns false when access is denied', () => { - jest.spyOn(fs, 'accessSync').mockImplementation(() => { - throw new Error('EACCES'); - }); - - expect(fileExists('/protected/file.ts')).toBe(false); - }); -}); diff --git a/src/__tests__/helpers/fs/folderExists.test.ts b/src/__tests__/helpers/fs/folderExists.test.ts deleted file mode 100644 index 1b80aa6..0000000 --- a/src/__tests__/helpers/fs/folderExists.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as fs from 'node:fs'; -import { folderExists } from '../../../helpers/fs/folderExists'; - -describe('folderExists', () => { - afterEach(() => jest.restoreAllMocks()); - - it('returns true when path is a directory', () => { - jest - .spyOn(fs, 'statSync') - .mockReturnValue({ isDirectory: () => true } as fs.Stats); - - expect(folderExists('/project/src')).toBe(true); - }); - - it('returns false when path is a file, not a directory', () => { - jest - .spyOn(fs, 'statSync') - .mockReturnValue({ isDirectory: () => false } as fs.Stats); - - expect(folderExists('/project/file.ts')).toBe(false); - }); - - it('returns false when path does not exist', () => { - jest.spyOn(fs, 'statSync').mockImplementation(() => { - throw new Error('ENOENT'); - }); - - expect(folderExists('/missing/dir')).toBe(false); - }); -}); diff --git a/src/__tests__/helpers/fs/listFiles.test.ts b/src/__tests__/helpers/fs/listFiles.test.ts deleted file mode 100644 index 0cf38f8..0000000 --- a/src/__tests__/helpers/fs/listFiles.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as fs from 'node:fs'; -import { listFiles } from '../../../helpers/fs/listFiles'; - -describe('listFiles', () => { - afterEach(() => jest.restoreAllMocks()); - - it('returns array of filenames on success', () => { - jest - .spyOn(fs, 'readdirSync') - .mockReturnValue(['Button.tsx', 'index.ts', 'Button.test.tsx'] as never); - - const result = listFiles('/project/src/Button'); - - expect(result).toEqual({ - success: true, - value: ['Button.tsx', 'index.ts', 'Button.test.tsx'], - }); - expect(fs.readdirSync).toHaveBeenCalledWith('/project/src/Button'); - }); - - it('returns empty array for empty directory', () => { - jest.spyOn(fs, 'readdirSync').mockReturnValue([] as never); - - const result = listFiles('/project/empty'); - - expect(result).toEqual({ success: true, value: [] }); - }); - - it('returns FOLDER_LIST_ERROR when directory does not exist', () => { - jest.spyOn(fs, 'readdirSync').mockImplementation(() => { - throw new Error('ENOENT: no such file or directory'); - }); - - const result = listFiles('/missing/dir'); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.code).toBe('FOLDER_LIST_ERROR'); - expect(result.error.message).toContain('/missing/dir'); - } - }); - - it('preserves the original error as cause', () => { - const originalError = new Error('ENOENT'); - jest.spyOn(fs, 'readdirSync').mockImplementation(() => { - throw originalError; - }); - - const result = listFiles('/dir'); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.cause).toBe(originalError); - } - }); -}); diff --git a/src/__tests__/helpers/fs/readFile.test.ts b/src/__tests__/helpers/fs/readFile.test.ts deleted file mode 100644 index f0a5f28..0000000 --- a/src/__tests__/helpers/fs/readFile.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as fs from 'node:fs'; -import { readFile } from '../../../helpers/fs/readFile'; - -describe('readFile', () => { - afterEach(() => jest.restoreAllMocks()); - - it('returns file content on success', () => { - jest.spyOn(fs, 'readFileSync').mockReturnValue('file content' as never); - - const result = readFile('/project/file.ts'); - - expect(result).toEqual({ success: true, value: 'file content' }); - expect(fs.readFileSync).toHaveBeenCalledWith('/project/file.ts', 'utf-8'); - }); - - it('returns FILE_READ_ERROR when file does not exist', () => { - jest.spyOn(fs, 'readFileSync').mockImplementation(() => { - throw new Error('ENOENT: no such file or directory'); - }); - - const result = readFile('/missing/file.ts'); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.code).toBe('FILE_READ_ERROR'); - expect(result.error.message).toContain('/missing/file.ts'); - } - }); - - it('preserves the original error as cause', () => { - const originalError = new Error('ENOENT'); - jest.spyOn(fs, 'readFileSync').mockImplementation(() => { - throw originalError; - }); - - const result = readFile('/file.ts'); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error.cause).toBe(originalError); - } - }); -}); diff --git a/src/__tests__/helpers/vscode/getWorkspaceFolders.test.ts b/src/__tests__/helpers/vscode/getWorkspaceFolders.test.ts deleted file mode 100644 index 3810b8b..0000000 --- a/src/__tests__/helpers/vscode/getWorkspaceFolders.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as vscode from 'vscode'; -import { getWorkspaceFolders } from '../../../helpers/vscode/getWorkspaceFolders'; - -describe('getWorkspaceFolders', () => { - const originalFolders = vscode.workspace.workspaceFolders; - - afterEach(() => { - Object.defineProperty(vscode.workspace, 'workspaceFolders', { - value: originalFolders, - writable: true, - }); - }); - - it('returns mapped workspace folders when folders exist', () => { - Object.defineProperty(vscode.workspace, 'workspaceFolders', { - value: [ - { uri: { fsPath: '/project/root' }, name: 'root', index: 0 }, - { uri: { fsPath: '/project/packages/app' }, name: 'app', index: 1 }, - ], - writable: true, - }); - - const result = getWorkspaceFolders(); - - expect(result).toEqual([ - { name: 'root', path: '/project/root', index: 0 }, - { name: 'app', path: '/project/packages/app', index: 1 }, - ]); - }); - - it('returns undefined when workspaceFolders is undefined', () => { - Object.defineProperty(vscode.workspace, 'workspaceFolders', { - value: undefined, - writable: true, - }); - - expect(getWorkspaceFolders()).toBeUndefined(); - }); - - it('returns undefined when workspaceFolders is empty array', () => { - Object.defineProperty(vscode.workspace, 'workspaceFolders', { - value: [], - writable: true, - }); - - expect(getWorkspaceFolders()).toBeUndefined(); - }); - - it('maps fsPath to path field', () => { - Object.defineProperty(vscode.workspace, 'workspaceFolders', { - value: [{ uri: { fsPath: '/my/project' }, name: 'project', index: 0 }], - writable: true, - }); - - const result = getWorkspaceFolders(); - - expect(result?.[0].path).toBe('/my/project'); - }); -}); diff --git a/src/commands/createComponent.ts b/src/commands/createComponent.ts index 115415b..830ed90 100644 --- a/src/commands/createComponent.ts +++ b/src/commands/createComponent.ts @@ -3,7 +3,6 @@ import * as vscode from 'vscode'; import { getNameFormat, readConfig } from '../helpers/config/readConfig'; import { listTemplates } from '../helpers/template/listTemplates'; import { processTemplateFolder } from '../helpers/template/processTemplateFolder'; -import { getWorkspaceFolders } from '../helpers/vscode/getWorkspaceFolders'; import { promptInput } from '../helpers/vscode/promptInput'; import { promptSelection } from '../helpers/vscode/promptSelection'; import { showError } from '../helpers/vscode/showError'; @@ -14,15 +13,6 @@ import { writeWorkspaceFile } from '../helpers/vscode/workspace/writeFile'; import { formatName } from '../utils/formatName'; import { COMPONENT_NAME_ERROR, canBeFormatted } from '../utils/validation'; -const findWorkspaceFolderUri = ( - clickedUri: vscode.Uri, -): vscode.Uri | undefined => { - const folders = getWorkspaceFolders(); - if (!folders) return undefined; - const match = folders.find(f => clickedUri.fsPath.startsWith(f.path)); - return match ? vscode.Uri.file(match.path) : undefined; -}; - export const createComponent = async ( clickedUri?: vscode.Uri, ): Promise => { @@ -31,7 +21,8 @@ export const createComponent = async ( return; } - const workspaceFolderUri = findWorkspaceFolderUri(clickedUri); + const workspaceFolderUri = + vscode.workspace.getWorkspaceFolder(clickedUri)?.uri; if (!workspaceFolderUri) { showError( 'Could not determine the workspace folder for the selected path.', @@ -39,7 +30,7 @@ export const createComponent = async ( return; } - const config = readConfig(workspaceFolderUri); + const config = readConfig(clickedUri); const templatesUri = vscode.Uri.joinPath( workspaceFolderUri, config.templatesFolderPath, diff --git a/src/commands/updateComponent.ts b/src/commands/updateComponent.ts index 5c17053..b98fde3 100644 --- a/src/commands/updateComponent.ts +++ b/src/commands/updateComponent.ts @@ -3,7 +3,6 @@ import * as vscode from 'vscode'; import { getNameFormat, readConfig } from '../helpers/config/readConfig'; import { listTemplates } from '../helpers/template/listTemplates'; import { processTemplateFolder } from '../helpers/template/processTemplateFolder'; -import { getWorkspaceFolders } from '../helpers/vscode/getWorkspaceFolders'; import { promptSelection } from '../helpers/vscode/promptSelection'; import { showError } from '../helpers/vscode/showError'; import { showInfo } from '../helpers/vscode/showInfo'; @@ -13,15 +12,6 @@ import { writeWorkspaceFile } from '../helpers/vscode/workspace/writeFile'; import { formatName } from '../utils/formatName'; import { canBeFormatted } from '../utils/validation'; -const findWorkspaceFolderUri = ( - clickedUri: vscode.Uri, -): vscode.Uri | undefined => { - const folders = getWorkspaceFolders(); - if (!folders) return undefined; - const match = folders.find(f => clickedUri.fsPath.startsWith(f.path)); - return match ? vscode.Uri.file(match.path) : undefined; -}; - export const updateComponent = async ( clickedUri?: vscode.Uri, ): Promise => { @@ -38,7 +28,8 @@ export const updateComponent = async ( return; } - const workspaceFolderUri = findWorkspaceFolderUri(clickedUri); + const workspaceFolderUri = + vscode.workspace.getWorkspaceFolder(clickedUri)?.uri; if (!workspaceFolderUri) { showError( 'Could not determine the workspace folder for the selected path.', @@ -46,7 +37,7 @@ export const updateComponent = async ( return; } - const config = readConfig(workspaceFolderUri); + const config = readConfig(clickedUri); const templatesUri = vscode.Uri.joinPath( workspaceFolderUri, config.templatesFolderPath, diff --git a/src/helpers/fs/createFile.ts b/src/helpers/fs/createFile.ts deleted file mode 100644 index 38289f1..0000000 --- a/src/helpers/fs/createFile.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import { type AppError, makeError } from '../../types/AppError'; -import { err, ok, type Result } from '../../types/Result'; - -export const createFile = ( - filePath: string, - content: string, -): Result => { - try { - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, content, 'utf-8'); - return ok(undefined); - } catch (cause) { - return err( - makeError( - 'FILE_WRITE_ERROR', - `Failed to create file: ${filePath}`, - cause, - ), - ); - } -}; diff --git a/src/helpers/fs/createFolder.ts b/src/helpers/fs/createFolder.ts deleted file mode 100644 index 5801fbd..0000000 --- a/src/helpers/fs/createFolder.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as fs from 'node:fs'; -import { type AppError, makeError } from '../../types/AppError'; -import { err, ok, type Result } from '../../types/Result'; - -export const createFolder = (folderPath: string): Result => { - try { - fs.mkdirSync(folderPath, { recursive: true }); - return ok(undefined); - } catch (cause) { - return err( - makeError( - 'FOLDER_CREATE_ERROR', - `Failed to create folder: ${folderPath}`, - cause, - ), - ); - } -}; diff --git a/src/helpers/fs/fileExists.ts b/src/helpers/fs/fileExists.ts deleted file mode 100644 index 2fac992..0000000 --- a/src/helpers/fs/fileExists.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as fs from 'node:fs'; - -export const fileExists = (filePath: string): boolean => { - try { - fs.accessSync(filePath, fs.constants.F_OK); - return true; - } catch { - return false; - } -}; diff --git a/src/helpers/fs/folderExists.ts b/src/helpers/fs/folderExists.ts deleted file mode 100644 index bd7b440..0000000 --- a/src/helpers/fs/folderExists.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as fs from 'node:fs'; - -export const folderExists = (folderPath: string): boolean => { - try { - return fs.statSync(folderPath).isDirectory(); - } catch { - return false; - } -}; diff --git a/src/helpers/fs/index.ts b/src/helpers/fs/index.ts deleted file mode 100644 index ab3d852..0000000 --- a/src/helpers/fs/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { createFile } from './createFile'; -export { createFolder } from './createFolder'; -export { fileExists } from './fileExists'; -export { folderExists } from './folderExists'; -export { listFiles } from './listFiles'; -export { readFile } from './readFile'; diff --git a/src/helpers/fs/listFiles.ts b/src/helpers/fs/listFiles.ts deleted file mode 100644 index f7983d4..0000000 --- a/src/helpers/fs/listFiles.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as fs from 'node:fs'; -import { type AppError, makeError } from '../../types/AppError'; -import { err, ok, type Result } from '../../types/Result'; - -export const listFiles = (folderPath: string): Result => { - try { - return ok(fs.readdirSync(folderPath)); - } catch (cause) { - return err( - makeError( - 'FOLDER_LIST_ERROR', - `Failed to list files in: ${folderPath}`, - cause, - ), - ); - } -}; diff --git a/src/helpers/fs/readFile.ts b/src/helpers/fs/readFile.ts deleted file mode 100644 index c20ee36..0000000 --- a/src/helpers/fs/readFile.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as fs from 'node:fs'; -import { type AppError, makeError } from '../../types/AppError'; -import { err, ok, type Result } from '../../types/Result'; - -export const readFile = (filePath: string): Result => { - try { - return ok(fs.readFileSync(filePath, 'utf-8')); - } catch (cause) { - return err( - makeError('FILE_READ_ERROR', `Failed to read file: ${filePath}`, cause), - ); - } -}; diff --git a/src/helpers/index.ts b/src/helpers/index.ts deleted file mode 100644 index 9559890..0000000 --- a/src/helpers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './fs'; -export * from './template'; -export * from './vscode'; diff --git a/src/helpers/vscode/getWorkspaceFolders.ts b/src/helpers/vscode/getWorkspaceFolders.ts deleted file mode 100644 index 7c86ffe..0000000 --- a/src/helpers/vscode/getWorkspaceFolders.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as vscode from 'vscode'; -import type { Option } from '../../types/Option'; - -export type WorkspaceFolder = { - readonly name: string; - readonly path: string; - readonly index: number; -}; - -export const getWorkspaceFolders = (): Option => { - const folders = vscode.workspace.workspaceFolders; - if (!folders || folders.length === 0) return undefined; - return folders.map(folder => ({ - name: folder.name, - path: folder.uri.fsPath, - index: folder.index, - })); -}; diff --git a/src/helpers/vscode/index.ts b/src/helpers/vscode/index.ts index a268233..d9e0534 100644 --- a/src/helpers/vscode/index.ts +++ b/src/helpers/vscode/index.ts @@ -1,5 +1,3 @@ -export type { WorkspaceFolder } from './getWorkspaceFolders'; -export { getWorkspaceFolders } from './getWorkspaceFolders'; export type { InputOptions } from './promptInput'; export { promptInput } from './promptInput'; export type { SelectionOptions } from './promptSelection'; diff --git a/test-workspace/project1/src/components/.gitkeep b/test-workspace/project1/src/components/.gitkeep deleted file mode 100644 index 5d4e1ae..0000000 --- a/test-workspace/project1/src/components/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# This folder will contain generated components \ No newline at end of file diff --git a/test-workspace/project1/templates/component/{{name}}.test.tsx.hbs b/test-workspace/project1/templates/component/{{name}}.test.tsx.hbs deleted file mode 100644 index bc84a80..0000000 --- a/test-workspace/project1/templates/component/{{name}}.test.tsx.hbs +++ /dev/null @@ -1,9 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import { {{name}} } from './{{name}}'; - -describe('{{name}}', () => { - it('should render correctly', () => { - render(<{{name}} />); - expect(screen.getByText('{{name}} Component')).toBeInTheDocument(); - }); -}); \ No newline at end of file diff --git a/test-workspace/project1/templates/component/{{name}}.tsx.hbs b/test-workspace/project1/templates/component/{{name}}.tsx.hbs deleted file mode 100644 index 1de3585..0000000 --- a/test-workspace/project1/templates/component/{{name}}.tsx.hbs +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -interface {{name}}Props { - // Add your props here -} - -export const {{name}}: React.FC<{{name}}Props> = () => { - return ( -
-

{{name}} Component

-
- ); -}; - -export default {{name}}; \ No newline at end of file diff --git a/test-workspace/project2/src/services/.gitkeep b/test-workspace/project2/src/services/.gitkeep deleted file mode 100644 index d30d0ab..0000000 --- a/test-workspace/project2/src/services/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# This folder will contain generated services \ No newline at end of file diff --git a/test-workspace/project2/templates/service/{{name}}.service.ts.hbs b/test-workspace/project2/templates/service/{{name}}.service.ts.hbs deleted file mode 100644 index 9eef259..0000000 --- a/test-workspace/project2/templates/service/{{name}}.service.ts.hbs +++ /dev/null @@ -1,24 +0,0 @@ -export class {{name}}Service { - constructor() { - // Initialize service - } - - async get{{name}}(): Promise { - // Implement your service logic here - return {}; - } - - async create{{name}}(data: any): Promise { - // Implement create logic - return data; - } - - async update{{name}}(id: string, data: any): Promise { - // Implement update logic - return { id, ...data }; - } - - async delete{{name}}(id: string): Promise { - // Implement delete logic - } -} \ No newline at end of file