genaicode 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +130 -0
  3. package/bin/genaicode.cjs +8 -0
  4. package/bin/genaicode.js +5 -0
  5. package/bin/vertex-monkey-patch.cjs +33 -0
  6. package/media/demo-for-readme.gif +0 -0
  7. package/media/logo-dark.png +0 -0
  8. package/media/logo.png +0 -0
  9. package/package.json +58 -0
  10. package/src/ai-service/anthropic.js +91 -0
  11. package/src/ai-service/chat-gpt.js +96 -0
  12. package/src/ai-service/common.js +40 -0
  13. package/src/ai-service/dall-e.js +29 -0
  14. package/src/ai-service/function-calling.js +320 -0
  15. package/src/ai-service/vertex-ai-claude.js +95 -0
  16. package/src/ai-service/vertex-ai.js +155 -0
  17. package/src/cli/cli-options.js +106 -0
  18. package/src/cli/cli-options.test.js +25 -0
  19. package/src/cli/cli-params.js +84 -0
  20. package/src/cli/cli-params.test.js +43 -0
  21. package/src/cli/service-autodetect.js +11 -0
  22. package/src/cli/service-autodetect.test.js +43 -0
  23. package/src/cli/validate-cli-params.js +90 -0
  24. package/src/cli/validate-cli-params.test.js +103 -0
  25. package/src/files/find-files.js +148 -0
  26. package/src/files/read-files.js +41 -0
  27. package/src/files/update-files.js +118 -0
  28. package/src/main/codegen.js +113 -0
  29. package/src/main/codegen.test.js +186 -0
  30. package/src/prompt/limits.js +23 -0
  31. package/src/prompt/limits.test.js +40 -0
  32. package/src/prompt/prompt-codegen.js +106 -0
  33. package/src/prompt/prompt-codegen.test.js +116 -0
  34. package/src/prompt/prompt-consts.js +1 -0
  35. package/src/prompt/prompt-service.js +211 -0
  36. package/src/prompt/prompt-service.test.js +486 -0
  37. package/src/prompt/systemprompt.js +32 -0
  38. package/src/prompt/systemprompt.test.js +60 -0
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { printHelpMessage, cliOptions } from './cli-options.js';
3
+
4
+ describe('CLI Options', () => {
5
+ describe('printHelpMessage', () => {
6
+ it('should print the help message with all CLI options', () => {
7
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
8
+
9
+ printHelpMessage();
10
+
11
+ expect(consoleSpy).toHaveBeenCalledWith('GenAIcode - AI-powered code generation tool');
12
+ expect(consoleSpy).toHaveBeenCalledWith('\nUsage: npx genaicode [options]');
13
+ expect(consoleSpy).toHaveBeenCalledWith('\nOptions:');
14
+
15
+ cliOptions.forEach((option) => {
16
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining(option.name));
17
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining(option.description));
18
+ });
19
+
20
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('For more information'));
21
+
22
+ consoleSpy.mockRestore();
23
+ });
24
+ });
25
+ });
@@ -0,0 +1,84 @@
1
+ import fs from 'fs';
2
+
3
+ import { serviceAutoDetect } from './service-autodetect.js';
4
+ import { rcConfig } from '../files/find-files.js';
5
+
6
+ const params = process.argv.slice(2);
7
+
8
+ export const dryRun = params.includes('--dry-run');
9
+ export const considerAllFiles = params.includes('--consider-all-files');
10
+ export const allowFileCreate = params.includes('--allow-file-create');
11
+ export const allowFileDelete = params.includes('--allow-file-delete');
12
+ export const allowDirectoryCreate = params.includes('--allow-directory-create');
13
+ export const allowFileMove = params.includes('--allow-file-move');
14
+ export let chatGpt = params.includes('--chat-gpt');
15
+ export let anthropic = params.includes('--anthropic');
16
+ export let vertexAi = params.includes('--vertex-ai');
17
+ export let vertexAiClaude = params.includes('--vertex-ai-claude');
18
+ export const dependencyTree = params.includes('--dependency-tree');
19
+ export const verbosePrompt = params.includes('--verbose-prompt');
20
+ export let explicitPrompt = params.find((param) => param.startsWith('--explicit-prompt'))?.split('=')[1];
21
+ export const disableContextOptimization = params.includes('--disable-context-optimization');
22
+ export const taskFile = params.find((param) => param.startsWith('--task-file'))?.split('=')[1];
23
+ export const requireExplanations = params.includes('--require-explanations');
24
+ export const geminiBlockNone = params.includes('--gemini-block-none');
25
+ export const disableInitialLint = params.includes('--disable-initial-lint');
26
+ export const vision = params.includes('--vision');
27
+ export const imagen = params.includes('--imagen');
28
+
29
+ // Add support for --help option
30
+ export const helpRequested = params.includes('--help');
31
+
32
+ // Export the lintCommand from rcConfig
33
+ export const lintCommand = rcConfig.lintCommand || null;
34
+
35
+ // Temperature parameter
36
+ export const temperature = parseFloat(
37
+ params.find((param) => param.startsWith('--temperature='))?.split('=')[1] || '0.7',
38
+ ); // Default temperature value: 0.7
39
+
40
+ if (taskFile) {
41
+ if (explicitPrompt) {
42
+ throw new Error('The --task-file option is exclusive with the --explicit-prompt option');
43
+ }
44
+ if (!fs.existsSync(taskFile)) {
45
+ throw new Error(`The task file ${taskFile} does not exist`);
46
+ }
47
+ explicitPrompt = `I want you to perform a coding task. The task is described in the ${taskFile} file. Use those instructions.`;
48
+ }
49
+
50
+ if (considerAllFiles && dependencyTree) {
51
+ throw new Error('--consider-all-files and --dependency-tree are exclusive.');
52
+ }
53
+
54
+ if ([chatGpt, anthropic, vertexAi, vertexAiClaude].filter(Boolean).length > 1) {
55
+ throw new Error('--chat-gpt, --anthropic, --vertex-ai, and --vertex-ai-claude are mutually exclusive.');
56
+ }
57
+
58
+ if (!chatGpt && !anthropic && !vertexAi && !vertexAiClaude && !helpRequested) {
59
+ const detected = serviceAutoDetect();
60
+ if (detected === 'anthropic') {
61
+ console.log('Autodetected --anthropic');
62
+ anthropic = true;
63
+ } else if (detected === 'chat-gpt') {
64
+ console.log('Autodetected --chat-gpt');
65
+ chatGpt = true;
66
+ } else if (detected === 'vertex-ai') {
67
+ console.log('Autodetected --vertex-ai');
68
+ vertexAi = true;
69
+ } else {
70
+ throw new Error('Missing --chat-gpt, --anthropic, --vertex-ai, or --vertex-ai-claude');
71
+ }
72
+ }
73
+
74
+ if (lintCommand) {
75
+ console.log(`Lint command detected: ${lintCommand}`);
76
+ }
77
+
78
+ if (temperature) {
79
+ console.log(`Temperature value: ${temperature}`);
80
+ }
81
+
82
+ if (imagen) {
83
+ console.log('Image generation functionality enabled');
84
+ }
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { serviceAutoDetect } from './service-autodetect';
3
+
4
+ describe('serviceAutoDetect', () => {
5
+ afterEach(() => {
6
+ // Clean up environment variables after each test
7
+ delete process.env.ANTHROPIC_API_KEY;
8
+ delete process.env.OPENAI_API_KEY;
9
+ delete process.env.GOOGLE_CLOUD_PROJECT;
10
+ });
11
+
12
+ it('should return "anthropic" when ANTHROPIC_API_KEY is set', () => {
13
+ process.env.ANTHROPIC_API_KEY = 'test-key';
14
+ expect(serviceAutoDetect()).toBe('anthropic');
15
+ });
16
+
17
+ it('should return "chat-gpt" when OPENAI_API_KEY is set', () => {
18
+ process.env.OPENAI_API_KEY = 'test-key';
19
+ expect(serviceAutoDetect()).toBe('chat-gpt');
20
+ });
21
+
22
+ it('should return "vertex-ai" when GOOGLE_CLOUD_PROJECT is set', () => {
23
+ process.env.GOOGLE_CLOUD_PROJECT = 'test-project';
24
+ expect(serviceAutoDetect()).toBe('vertex-ai');
25
+ });
26
+
27
+ it('should return null when no service is configured', () => {
28
+ expect(serviceAutoDetect()).toBeNull();
29
+ });
30
+
31
+ it('should prioritize anthropic over other services', () => {
32
+ process.env.ANTHROPIC_API_KEY = 'test-key';
33
+ process.env.OPENAI_API_KEY = 'test-key';
34
+ process.env.GOOGLE_CLOUD_PROJECT = 'test-project';
35
+ expect(serviceAutoDetect()).toBe('anthropic');
36
+ });
37
+
38
+ it('should prioritize chat-gpt over vertex-ai', () => {
39
+ process.env.OPENAI_API_KEY = 'test-key';
40
+ process.env.GOOGLE_CLOUD_PROJECT = 'test-project';
41
+ expect(serviceAutoDetect()).toBe('chat-gpt');
42
+ });
43
+ });
@@ -0,0 +1,11 @@
1
+ /** Detects if one of ai services is configured */
2
+ export function serviceAutoDetect() {
3
+ if (process.env.ANTHROPIC_API_KEY) {
4
+ return 'anthropic';
5
+ } else if (process.env.OPENAI_API_KEY) {
6
+ return 'chat-gpt';
7
+ } else if (process.env.GOOGLE_CLOUD_PROJECT) {
8
+ return 'vertex-ai';
9
+ }
10
+ return null;
11
+ }
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { serviceAutoDetect } from './service-autodetect';
3
+
4
+ describe('serviceAutoDetect', () => {
5
+ afterEach(() => {
6
+ // Clean up environment variables after each test
7
+ delete process.env.ANTHROPIC_API_KEY;
8
+ delete process.env.OPENAI_API_KEY;
9
+ delete process.env.GOOGLE_CLOUD_PROJECT;
10
+ });
11
+
12
+ it('should return "anthropic" when ANTHROPIC_API_KEY is set', () => {
13
+ process.env.ANTHROPIC_API_KEY = 'test-key';
14
+ expect(serviceAutoDetect()).toBe('anthropic');
15
+ });
16
+
17
+ it('should return "chat-gpt" when OPENAI_API_KEY is set', () => {
18
+ process.env.OPENAI_API_KEY = 'test-key';
19
+ expect(serviceAutoDetect()).toBe('chat-gpt');
20
+ });
21
+
22
+ it('should return "vertex-ai" when GOOGLE_CLOUD_PROJECT is set', () => {
23
+ process.env.GOOGLE_CLOUD_PROJECT = 'test-project';
24
+ expect(serviceAutoDetect()).toBe('vertex-ai');
25
+ });
26
+
27
+ it('should return null when no service is configured', () => {
28
+ expect(serviceAutoDetect()).toBeNull();
29
+ });
30
+
31
+ it('should prioritize anthropic over other services', () => {
32
+ process.env.ANTHROPIC_API_KEY = 'test-key';
33
+ process.env.OPENAI_API_KEY = 'test-key';
34
+ process.env.GOOGLE_CLOUD_PROJECT = 'test-project';
35
+ expect(serviceAutoDetect()).toBe('anthropic');
36
+ });
37
+
38
+ it('should prioritize chat-gpt over vertex-ai', () => {
39
+ process.env.OPENAI_API_KEY = 'test-key';
40
+ process.env.GOOGLE_CLOUD_PROJECT = 'test-project';
41
+ expect(serviceAutoDetect()).toBe('chat-gpt');
42
+ });
43
+ });
@@ -0,0 +1,90 @@
1
+ // List of allowed CLI parameters
2
+ const allowedParameters = [
3
+ '--dry-run',
4
+ '--consider-all-files',
5
+ '--allow-file-create',
6
+ '--allow-file-delete',
7
+ '--allow-directory-create',
8
+ '--allow-file-move',
9
+ '--chat-gpt',
10
+ '--vertex-ai',
11
+ '--vertex-ai-claude',
12
+ '--anthropic',
13
+ '--explicit-prompt=',
14
+ '--task-file=',
15
+ '--dependency-tree',
16
+ '--verbose-prompt',
17
+ '--require-explanations',
18
+ '--disable-context-optimization',
19
+ '--gemini-block-none',
20
+ '--disable-initial-lint',
21
+ '--temperature=',
22
+ '--vision',
23
+ '--imagen',
24
+ '--help',
25
+ ];
26
+
27
+ /**
28
+ * Validate CLI parameters according to those mentioned in README.md
29
+ * Fail the process if not valid, or if an unknown parameter is passed
30
+ * @throws {Error} If an invalid parameter is provided
31
+ */
32
+ export function validateCliParams() {
33
+ const providedParameters = process.argv.slice(2);
34
+
35
+ // Check if --help is present
36
+ const helpRequested = providedParameters.includes('--help');
37
+
38
+ if (helpRequested) {
39
+ // If --help is present, no other parameters should be allowed
40
+ if (providedParameters.length > 1) {
41
+ console.error('The --help option cannot be used with other parameters.');
42
+ process.exit(1);
43
+ }
44
+ return; // Exit the function early as no further validation is needed
45
+ }
46
+
47
+ providedParameters.forEach((param) => {
48
+ if (!param.startsWith('--')) {
49
+ console.error(`Invalid parameter: ${param}, all parameters must start with --`);
50
+ process.exit(1);
51
+ }
52
+ if (!allowedParameters.some((p) => (p.endsWith('=') && param.startsWith(p)) || param === p)) {
53
+ console.error(`Invalid parameter: ${param}, allowed parameters are: ${allowedParameters.join(', ')}`);
54
+ process.exit(1);
55
+ }
56
+ });
57
+
58
+ // Validate temperature parameter, it must be a number between 0.0 and 2.0
59
+ const temperatureParam = providedParameters.find((param) => param.startsWith('--temperature='));
60
+ if (temperatureParam) {
61
+ const temperatureValue = parseFloat(temperatureParam.split('=')[1]);
62
+ if (isNaN(temperatureValue) || temperatureValue < 0.0 || temperatureValue > 2.0) {
63
+ console.error('Invalid temperature value. It must be a number between 0.0 and 2.0.');
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ if (providedParameters.includes('--vision') && providedParameters.includes('--vertex-ai')) {
69
+ throw new Error('--vision and --vertex-ai are currently not supported together.');
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Get the value of a CLI parameter
75
+ * @param {string} paramName - The name of the parameter to get the value for
76
+ * @returns {string|null} The value of the parameter, or null if not found
77
+ */
78
+ export function getCliParamValue(paramName) {
79
+ const param = process.argv.find((arg) => arg.startsWith(`${paramName}=`));
80
+ return param ? param.split('=')[1] : null;
81
+ }
82
+
83
+ /**
84
+ * Check if a CLI parameter is present
85
+ * @param {string} paramName - The name of the parameter to check
86
+ * @returns {boolean} True if the parameter is present, false otherwise
87
+ */
88
+ export function hasCliParam(paramName) {
89
+ return process.argv.includes(paramName) || !!getCliParamValue(paramName);
90
+ }
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { validateCliParams, getCliParamValue, hasCliParam } from './validate-cli-params.js';
3
+
4
+ describe('validateCliParams', () => {
5
+ const originalArgv = process.argv;
6
+
7
+ beforeEach(() => {
8
+ process.argv = ['node', 'script.js'];
9
+ });
10
+
11
+ afterEach(() => {
12
+ process.argv = originalArgv;
13
+ });
14
+
15
+ it('should not throw for valid parameters', () => {
16
+ process.argv.push('--dry-run', '--consider-all-files');
17
+ expect(() => validateCliParams()).not.toThrow();
18
+ });
19
+
20
+ it('should throw for invalid parameters', () => {
21
+ process.argv.push('--invalid-param');
22
+ expect(() => validateCliParams()).toThrow();
23
+ });
24
+
25
+ it('should throw for parameters without --', () => {
26
+ process.argv.push('invalid-param');
27
+ expect(() => validateCliParams()).toThrow();
28
+ });
29
+
30
+ it('should not throw for valid --temperature parameter', () => {
31
+ process.argv.push('--temperature=0.5');
32
+ expect(() => validateCliParams()).not.toThrow();
33
+ });
34
+
35
+ it('should throw for invalid --temperature parameter', () => {
36
+ process.argv.push('--temperature=invalid');
37
+ expect(() => validateCliParams()).toThrow();
38
+ });
39
+
40
+ it('should throw an error when both vision and vertexAi flags are true', () => {
41
+ process.argv.push('--vision');
42
+ process.argv.push('--vertex-ai');
43
+
44
+ expect(() => validateCliParams()).toThrow('--vision and --vertex-ai are currently not supported together.');
45
+ });
46
+ });
47
+
48
+ describe('getCliParamValue', () => {
49
+ const originalArgv = process.argv;
50
+
51
+ beforeEach(() => {
52
+ process.argv = ['node', 'script.js'];
53
+ });
54
+
55
+ afterEach(() => {
56
+ process.argv = originalArgv;
57
+ });
58
+
59
+ it('should return the value for a parameter with a value', () => {
60
+ process.argv.push('--explicit-prompt=Test prompt');
61
+ expect(getCliParamValue('--explicit-prompt')).toBe('Test prompt');
62
+ });
63
+
64
+ it('should return null for a parameter without a value', () => {
65
+ process.argv.push('--dry-run');
66
+ expect(getCliParamValue('--dry-run')).toBeNull();
67
+ });
68
+
69
+ it('should return null for a non-existent parameter', () => {
70
+ expect(getCliParamValue('--non-existent')).toBeNull();
71
+ });
72
+
73
+ it('should return the value for --temperature parameter', () => {
74
+ process.argv.push('--temperature=0.5');
75
+ expect(getCliParamValue('--temperature')).toBe('0.5');
76
+ });
77
+ });
78
+
79
+ describe('hasCliParam', () => {
80
+ const originalArgv = process.argv;
81
+
82
+ beforeEach(() => {
83
+ process.argv = ['node', 'script.js'];
84
+ });
85
+
86
+ afterEach(() => {
87
+ process.argv = originalArgv;
88
+ });
89
+
90
+ it('should return true for an existing parameter', () => {
91
+ process.argv.push('--dry-run');
92
+ expect(hasCliParam('--dry-run')).toBe(true);
93
+ });
94
+
95
+ it('should return false for a non-existent parameter', () => {
96
+ expect(hasCliParam('--non-existent')).toBe(false);
97
+ });
98
+
99
+ it('should return true for --temperature parameter', () => {
100
+ process.argv.push('--temperature=0.5');
101
+ expect(hasCliParam('--temperature')).toBe(true);
102
+ });
103
+ });
@@ -0,0 +1,148 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import assert from 'node:assert';
4
+
5
+ // This file contains project codegen configuration
6
+ const CODEGENRC_FILENAME = '.genaicoderc';
7
+
8
+ const cwd = process.cwd();
9
+
10
+ // List of possible extensions for dependency resolution
11
+ const POSSIBLE_DEPENDENCY_EXTENSIONS = ['.ts', '.js', '.tsx', '.jsx'];
12
+
13
+ // Find .genaicoderc file
14
+ let rcFilePath = cwd;
15
+ while (!fs.existsSync(path.join(rcFilePath, CODEGENRC_FILENAME))) {
16
+ const parentDir = path.dirname(rcFilePath);
17
+ if (parentDir === rcFilePath) {
18
+ throw new Error(`${CODEGENRC_FILENAME} not found in any parent directory`);
19
+ }
20
+ rcFilePath = parentDir;
21
+ }
22
+ rcFilePath = path.join(rcFilePath, CODEGENRC_FILENAME);
23
+
24
+ assert(fs.existsSync(rcFilePath), `${CODEGENRC_FILENAME} not found`);
25
+
26
+ // Read rootDir and extensions from .genaicoderc
27
+ export const rcConfig = JSON.parse(fs.readFileSync(rcFilePath, 'utf-8'));
28
+ export const rootDir = path.resolve(path.dirname(rcFilePath), rcConfig.rootDir);
29
+
30
+ assert(rootDir, 'Root dir not configured');
31
+ assert(isAncestorDirectory(path.dirname(rcFilePath), rootDir), 'Root dir is not located inside project directory');
32
+
33
+ console.log('Detected codegen configuration', rcConfig);
34
+ console.log('Root dir:', rootDir);
35
+
36
+ // Default extensions if not specified in .genaicoderc
37
+ const DEFAULT_EXTENSIONS = [
38
+ '.md',
39
+ '.js',
40
+ '.ts',
41
+ '.tsx',
42
+ '.css',
43
+ '.scss',
44
+ '.py',
45
+ '.go',
46
+ '.c',
47
+ '.h',
48
+ '.cpp',
49
+ '.txt',
50
+ '.html',
51
+ '.txt',
52
+ '.json',
53
+ ];
54
+
55
+ // Use extensions from .genaicoderc if available, otherwise use default
56
+ const sourceExtensions = rcConfig.extensions || DEFAULT_EXTENSIONS;
57
+
58
+ // Image extensions (driven by ai service limitations)
59
+ const IMAGE_ASSET_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp'];
60
+
61
+ // A list of paths that are ignored by default
62
+ const DEFAULT_IGNORE_PATHS = ['node_modules', 'build', 'dist', 'package-lock.json', 'coverage'];
63
+
64
+ function findFiles(dir, recursive, extensions) {
65
+ const files = [];
66
+ const items = fs.readdirSync(dir);
67
+ for (const item of items) {
68
+ const fullPath = path.join(dir, item);
69
+
70
+ if ((rcConfig.ignorePaths ?? DEFAULT_IGNORE_PATHS).some((ignorePath) => fullPath.endsWith(ignorePath))) {
71
+ continue;
72
+ }
73
+
74
+ if (fs.statSync(fullPath).isDirectory()) {
75
+ if (recursive) {
76
+ files.push(...findFiles(fullPath, true, extensions));
77
+ }
78
+ } else if (extensions.includes(path.extname(fullPath))) {
79
+ files.push(fullPath);
80
+ }
81
+ }
82
+ return files;
83
+ }
84
+
85
+ function getDependencies(filePath) {
86
+ const content = fs.readFileSync(filePath, 'utf-8');
87
+ const dependencyRegex = /import\s+.+?\s+from\s+['"](.+?\/?[^'"]+)['"]/g;
88
+ const dependencies = [];
89
+ let match;
90
+ while ((match = dependencyRegex.exec(content)) !== null) {
91
+ const dependencyPath = match[1];
92
+ // Resolve relative paths from the file's directory
93
+ let resolvedPath = path.resolve(path.dirname(filePath), dependencyPath);
94
+
95
+ // Only add the dependency if it's a local file and not a module
96
+ if (fs.existsSync(resolvedPath)) {
97
+ dependencies.push(resolvedPath);
98
+ } else {
99
+ const possibleExtensions = POSSIBLE_DEPENDENCY_EXTENSIONS;
100
+ for (const ext of possibleExtensions) {
101
+ const extendedPath = resolvedPath + ext;
102
+ if (fs.existsSync(extendedPath)) {
103
+ dependencies.push(extendedPath);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ return dependencies;
109
+ }
110
+
111
+ /** Generates a dependency list for given file */
112
+ export function getDependencyList(entryFile) {
113
+ const visitedFiles = new Set();
114
+ const result = new Set();
115
+
116
+ function traverse(file) {
117
+ if (visitedFiles.has(file)) return;
118
+ visitedFiles.add(file);
119
+ const dependencies = getDependencies(file);
120
+ dependencies.forEach((dependency) => result.add(dependency));
121
+ dependencies.forEach(traverse);
122
+ }
123
+
124
+ result.add(path.resolve(entryFile));
125
+ traverse(entryFile);
126
+
127
+ return Array.from(result);
128
+ }
129
+
130
+ const sourceFiles = findFiles(rootDir, true, sourceExtensions);
131
+
132
+ /** Get source files of the application */
133
+ export function getSourceFiles() {
134
+ return [...sourceFiles];
135
+ }
136
+
137
+ const imageAssetFiles = findFiles(rootDir, true, IMAGE_ASSET_EXTENSIONS);
138
+
139
+ /** Get source files of the application */
140
+ export function getImageAssetFiles() {
141
+ return [...imageAssetFiles];
142
+ }
143
+
144
+ /** Check if directory is ancestor of given directory */
145
+ export function isAncestorDirectory(parent, dir) {
146
+ const relative = path.relative(parent, dir);
147
+ return parent === dir || (relative && !relative.startsWith('..') && !path.isAbsolute(relative));
148
+ }
@@ -0,0 +1,41 @@
1
+ import fs from 'fs';
2
+ import mime from 'mime-types';
3
+ import sizeOf from 'image-size';
4
+
5
+ import { getSourceFiles, getImageAssetFiles } from './find-files.js';
6
+ import { verifySourceCodeLimit } from '../prompt/limits.js';
7
+
8
+ /**
9
+ * Read contents of source files and create a map with file path as key and file content as value
10
+ */
11
+ function readSourceFiles(filterPaths) {
12
+ const sourceCode = {};
13
+ for (const file of getSourceFiles()) {
14
+ if (!filterPaths || filterPaths.includes(file)) {
15
+ sourceCode[file] = fs.readFileSync(file, 'utf-8');
16
+ }
17
+ }
18
+ return sourceCode;
19
+ }
20
+
21
+ /** Print source code of all source files */
22
+ export function getSourceCode(filterPaths) {
23
+ const sourceCode = readSourceFiles(filterPaths);
24
+ verifySourceCodeLimit(JSON.stringify(sourceCode));
25
+ return sourceCode;
26
+ }
27
+
28
+ /** Get image asset files summary */
29
+ export function getImageAssets() {
30
+ const imageAssets = {};
31
+ for (const file of getImageAssetFiles()) {
32
+ const dimensions = sizeOf(file);
33
+ imageAssets[file] = {
34
+ mimeType: mime.lookup(file),
35
+ width: dimensions.width,
36
+ height: dimensions.height,
37
+ };
38
+ }
39
+ verifySourceCodeLimit(JSON.stringify(imageAssets));
40
+ return imageAssets;
41
+ }