Skip to content

Commit c70b032

Browse files
committed
* feat(launch.json): add launch configuration for vscode-jest-tests.v2.insert-file-tag
* feat(calltree-snippet.txt): add call tree generation prompt * feat(source.txt): add source file with "Hello World!" content * feat(isValidPath.test.js): add test for isValidPath function * feat(processAdnSaveFileWithTags.test.js): add tests for processAndSaveFileWithTags function * fix(source-processor.js): fix typo in function name * feat(source-processor.js): add processAndSaveFileWithTags function * feat(source-processor.js): add updateInsertTagsSafely function * feat(source-processor.js): add isValidPath function * feat(source-processor.js): export all functions in source-processor module * feat(target.txt): add target file with "Hello World!" content * feat(test.txt): add test file with "Hello World!" content
1 parent 3739284 commit c70b032

File tree

8 files changed

+256
-13
lines changed

8 files changed

+256
-13
lines changed

.vscode/launch.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
8+
{
9+
"type": "node",
10+
"name": "vscode-jest-tests.v2.insert-file-tag",
11+
"request": "launch",
12+
"args": [
13+
"--runInBand",
14+
"--watchAll=false",
15+
"--testNamePattern",
16+
"${jest.testNamePattern}",
17+
"--runTestsByPath",
18+
"${jest.testFile}"
19+
],
20+
"cwd": "${workspaceFolder}",
21+
"console": "integratedTerminal",
22+
"internalConsoleOptions": "neverOpen",
23+
"disableOptimisticBPs": true,
24+
"program": "${workspaceFolder}/node_modules/.bin/jest",
25+
"windows": {
26+
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
27+
}
28+
}
29+
]
30+
}

calltree-snippet.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Generate a call tree for the JavaScript file, showing the dependencies between functions. The root node should be the filename, followed by functions that don't call other functions within the file, and then their descendants based on function calls. Format the output as a tree using indentation and└─ to indicate parent-child relationships. Enclose the output within a JavaScript block comment.
2+
3+
4+
What mechanism of retrieving this prompt do you recommend? I want to give this prompt a name, and later I give you the prompt name and you will execute the prompt.

source.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello World!

src/isValidPath.test.js

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const { processAndSaveFileWithTags } = require('./source-processor');
4+
5+
describe('processAndSaveFileWithTags', () => {
6+
it('throws Error when sourceFilename is not provided', () => {
7+
expect(() => processAndSaveFileWithTags()).toThrow(Error);
8+
});
9+
10+
it('throws TypeError when sourceFilename is not a string', () => {
11+
expect(() => processAndSaveFileWithTags(123)).toThrow(TypeError);
12+
});
13+
14+
it('throws Error when targetFilename is not provided', () => {
15+
expect(() => processAndSaveFileWithTags('source.txt')).toThrow(Error);
16+
});
17+
18+
it('throws TypeError when targetFilename is not a string', () => {
19+
expect(() => processAndSaveFileWithTags('source.txt', 123)).toThrow(TypeError);
20+
});
21+
22+
// it('throws Error when file path is invalid', () => {
23+
// expect(() => processAndSaveFileWithTags('source.txt', '../invalid/path.txt')).toThrow(Error);
24+
// });
25+
26+
// it('processes and saves file correctly', () => {
27+
// const sourceFilename = 'source.txt';
28+
// const targetFilename = 'target.txt';
29+
// const sourceContent = 'Hello World!';
30+
// fs.writeFileSync(sourceFilename, sourceContent, 'utf8');
31+
// processAndSaveFileWithTags(sourceFilename, targetFilename);
32+
// const targetContent = fs.readFileSync(targetFilename, 'utf8');
33+
// expect(targetContent).toContain(sourceContent);
34+
// fs.unlinkSync(sourceFilename);
35+
// fs.unlinkSync(targetFilename);
36+
// });
37+
38+
// it('handles error while reading or writing file', () => {
39+
// const sourceFilename = 'source.txt';
40+
// const targetFilename = 'target.txt';
41+
// const sourceContent = 'Hello World!';
42+
// fs.writeFileSync(sourceFilename, sourceContent, 'utf8');
43+
// fs.chmodSync(sourceFilename, 0o000); // make the file unreadable
44+
// expect(() => processAndSaveFileWithTags(sourceFilename, targetFilename)).toThrow(Error);
45+
// fs.chmodSync(sourceFilename, 0o644); // restore the file permissions
46+
// fs.unlinkSync(sourceFilename);
47+
// });
48+
});

src/source-processor.js

Lines changed: 152 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,24 @@ const fs = require('fs');
88
*/
99

1010
/**
11-
* Testing Order:
11+
* source-processor.js
12+
* └─ isValidPath
13+
* └─ processSourceCode
14+
* └─ processFilename
15+
* └─ processAndSaveFileWithTags
16+
* └─ updateInsertTagsSafely
17+
*/
18+
19+
/**
20+
* The order of testing the functions in this file is as follows:
1221
*
13-
* 1. `processSourceCode`
22+
* 1. `isValidPath`
1423
* 2. `processFilename`
15-
*
16-
* This order is important because `processFilename` depends on
17-
* `processMarkdown` and we want to make sure that all the dependant
18-
* functions are tested first.
24+
* 3. `processSourceCode`
25+
* 4. `processAndSaveFileWithTags`
26+
* 5. `updateInsertTagsSafely`
1927
*/
2028

21-
22-
23-
2429
/**
2530
* Processes the given source code by replacing insert/include tags with the content
2631
* of the specified files. The file paths are resolved relative to the provided base path.
@@ -69,7 +74,6 @@ ${fileContent}
6974
});
7075
}
7176

72-
7377
/**
7478
* Reads the content of a file and processes it by inserting content of other
7579
* files based on insert/include tags. If the file does not exist or is empty,
@@ -82,6 +86,9 @@ ${fileContent}
8286
* @throws {Error} If an error occurs while reading the file
8387
*/
8488
function processFilename(filename) {
89+
/*
90+
* Validate the filename
91+
*/
8592
if (!filename) {
8693
throw new Error("File name is required");
8794
}
@@ -96,20 +103,152 @@ function processFilename(filename) {
96103
// Read the content of the file
97104
fileContent = fs.readFileSync(filename, 'utf8');
98105
} catch (error) {
106+
// If the file does not exist, return null
99107
if (error.code !== 'ENOENT') {
100108
throw error;
101109
}
110+
111+
return null;
102112
}
103113

104114
if (!fileContent || fileContent.trim() === '') {
115+
// If the file is empty, return null
105116
return null;
106117
}
107118

108-
// Process the markdown content by inserting content of other files
119+
// Process the source content by inserting content of other files
109120
return processSourceCode(fileContent, path.dirname(filename));
110121
}
111122

123+
/**
124+
* Processes the source file by replacing insert/include tags with the content
125+
* of the specified files, and then saves the processed content to the target
126+
* file.
127+
*
128+
* @param {string} sourceFilename - The name of the source file to process.
129+
* @param {string} targetFilename - The name of the target file to save the
130+
* processed content to.
131+
* @throws {Error} If the file name is not provided or is not a string.
132+
* @throws {TypeError} If the file name is not a string.
133+
* @throws {Error} If an error occurs while reading or writing the file.
134+
*/
135+
function processAndSaveFileWithTags(sourceFilename, targetFilename) {
136+
// if (!isValidPath(sourceFilename) || !isValidPath(targetFilename)) {
137+
// throw new TypeError('Invalid file path');
138+
// }
139+
if (!sourceFilename) {
140+
throw new Error("Source file name is required");
141+
}
142+
143+
if (typeof sourceFilename !== 'string') {
144+
throw new TypeError("Source file name must be a string");
145+
}
146+
147+
if (!targetFilename) {
148+
throw new Error("Target file name is required");
149+
}
150+
151+
if (typeof targetFilename !== 'string') {
152+
throw new TypeError("Target file name must be a string");
153+
}
154+
155+
// Read the source file content and process it
156+
// const processedSource = processFilename(path.basename(sourceFilename));
157+
const processedSource = processFilename(sourceFilename);
158+
if (!processedSource) {
159+
throw new Error('Filename processing failed.');
160+
}
161+
162+
// Save the processed content to the target file
163+
try {
164+
fs.mkdirSync(path.dirname(targetFilename), { recursive: true });
165+
fs.writeFileSync(targetFilename, processedSource, 'utf8');
166+
} catch (error) {
167+
console.error(`Error processing file: ${error.message}`);
168+
}
169+
}
170+
171+
/**
172+
* Processes the file by replacing insert/include tags with the content of
173+
* other files, and saves the processed content to the same file. This
174+
* function is safe to call concurrently because it uses a temporary file to
175+
* avoid overwriting the original file during processing.
176+
*
177+
* @param {string} filename - The name of the file to process.
178+
* @throws {Error} If the file name is not provided or is not a string.
179+
* @throws {TypeError} If the file name is not a string.
180+
* @throws {Error} If an error occurs while reading or writing the file.
181+
*/
182+
function updateInsertTagsSafely(filename) {
183+
if (!filename) {
184+
throw new Error("File name is required");
185+
}
186+
187+
if (typeof filename !== 'string') {
188+
throw new TypeError("File name must be a string");
189+
}
190+
191+
const tempFilename = filename + '.tmp';
192+
193+
try {
194+
// Process the file and save to a temporary file
195+
processAndSaveFileWithTags(filename, tempFilename);
196+
197+
// Rename the temporary file to the original filename
198+
fs.renameSync(tempFilename, filename);
199+
} catch (error) {
200+
console.error(`Error processing file with temp: ${error.message}`);
201+
// Cleanup: remove the temporary file if it exists
202+
if (fs.existsSync(tempFilename)) {
203+
fs.unlinkSync(tempFilename);
204+
}
205+
}
206+
}
207+
208+
/**
209+
* Checks if the given file path is valid and not maliciously trying to escape
210+
* the base directory.
211+
*
212+
* @param {string} filePath - The file path to check.
213+
* @param {string} [baseDir=process.cwd()] - The base directory to check against.
214+
* @returns {boolean} True if the path is valid, false otherwise.
215+
*/
216+
function isValidPath(filePath, baseDir = process.cwd()) {
217+
const pathRegex = /^[a-zA-Z0-9_\-\/\\]+$/;
218+
if (typeof filePath !== 'string' || !pathRegex.test(filePath)) {
219+
return false
220+
}
221+
222+
// Make sure the base directory exists and is a directory
223+
if (!fs.existsSync(baseDir) || !fs.lstatSync(baseDir).isDirectory()) {
224+
console.error(`Invalid base directory: ${baseDir}`);
225+
return false;
226+
}
227+
228+
// Sanitize the file path and base directory by removing any .. and . references
229+
const sanitizedFilePath = path.normalize(filePath).replace(/(\.\.(\/|\\|$))+/, '');
230+
const sanitizedBaseDir = path.normalize(baseDir).replace(/(\.\.(\/|\\|$))+/, '');
231+
232+
// Make sure the file path is absolute
233+
if (!path.isAbsolute(sanitizedFilePath)) {
234+
return false;
235+
}
236+
237+
try {
238+
// Resolve the file path and check if it's inside the base directory
239+
const resolvedPath = path.join(sanitizedBaseDir, sanitizedFilePath);
240+
const realPath = fs.realpathSync(resolvedPath);
241+
return realPath.startsWith(sanitizedBaseDir);
242+
} catch (error) {
243+
console.error(`Error resolving path: ${error.message}`);
244+
return false;
245+
}
246+
}
247+
112248
module.exports = {
113249
processFilename,
114-
processSourceCode
115-
}
250+
processSourceCode,
251+
processAndSaveFileWithTags,
252+
isValidPath,
253+
updateInsertTagsSafely,
254+
};

target.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello World!

test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello World!

0 commit comments

Comments
 (0)