UNPKG

spanwright

Version:

CLI tool to generate Cloud Spanner E2E testing framework projects with Go database tools and Playwright browser automation

322 lines 12.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ensureDirectoryExists = ensureDirectoryExists; exports.copyDirectory = copyDirectory; exports.safeFileExists = safeFileExists; exports.safeFileDelete = safeFileDelete; exports.safeFileRename = safeFileRename; exports.readFileContent = readFileContent; exports.writeFileContent = writeFileContent; exports.escapeRegExp = escapeRegExp; exports.replaceInFile = replaceInFile; exports.processTemplateFiles = processTemplateFiles; exports.replaceProjectNameInGoFiles = replaceProjectNameInGoFiles; exports.removeSecondaryDbFiles = removeSecondaryDbFiles; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const errors_1 = require("./errors"); const constants_1 = require("./constants"); const security_1 = require("./security"); const template_security_1 = require("./template-security"); const validation_1 = require("./validation"); // File and directory operation utilities function ensureDirectoryExists(dirPath) { try { // Check for null bytes in all paths if (dirPath.includes('\0')) { throw new errors_1.SecurityError(`Null byte in path detected in ensureDirectoryExists: ${dirPath}`, dirPath); } // Only validate relative paths to avoid issues with absolute paths in tests if (!path.isAbsolute(dirPath)) { (0, security_1.validatePath)(process.cwd(), dirPath, 'ensureDirectoryExists'); } // Use mkdirSync with recursive: true which is idempotent and avoids TOCTOU fs.mkdirSync(dirPath, { recursive: true }); } catch (error) { if (error instanceof Error && error.name === 'SecurityError') { throw error; } throw new errors_1.FileSystemError(`Failed to create directory: ${dirPath}`, dirPath); } } function copyDirectory(src, dest) { try { // Only validate relative paths to avoid issues with absolute paths in tests if (!path.isAbsolute(src)) { (0, security_1.validatePath)(process.cwd(), src, 'copyDirectory'); } if (!path.isAbsolute(dest)) { (0, security_1.validatePath)(process.cwd(), dest, 'copyDirectory'); } ensureDirectoryExists(dest); const files = fs.readdirSync(src); for (const file of files) { const srcPath = path.join(src, file); const destPath = path.join(dest, file); // Validate each file path for security issues (focus on path traversal in file names) if (file.includes('..') || file.includes('\0')) { (0, security_1.validatePath)(process.cwd(), srcPath, 'copyDirectory'); (0, security_1.validatePath)(process.cwd(), destPath, 'copyDirectory'); } const stat = fs.statSync(srcPath); if (stat.isDirectory()) { copyDirectory(srcPath, destPath); } else { fs.copyFileSync(srcPath, destPath); } } } catch (error) { if (error instanceof Error && error.name === 'SecurityError') { throw error; } throw new errors_1.FileSystemError(`Failed to copy directory from ${src} to ${dest}`, src); } } function safeFileExists(filePath) { try { return fs.existsSync(filePath); } catch { return false; } } function safeFileDelete(filePath) { try { // Only validate relative paths to avoid issues with absolute paths in tests if (!path.isAbsolute(filePath)) { (0, security_1.validatePath)(process.cwd(), filePath, 'safeFileDelete'); } // Try to unlink the file - will fail silently if it doesn't exist try { fs.unlinkSync(filePath); } catch (error) { // ENOENT is fine - file doesn't exist if (error.code !== 'ENOENT') { throw error; } } } catch (error) { if (error instanceof Error && error.name === 'SecurityError') { throw error; } throw new errors_1.FileSystemError(`Failed to delete file: ${filePath}`, filePath); } } function safeFileRename(oldPath, newPath) { try { // Only validate relative paths to avoid issues with absolute paths in tests if (!path.isAbsolute(oldPath)) { (0, security_1.validatePath)(process.cwd(), oldPath, 'safeFileRename'); } if (!path.isAbsolute(newPath)) { (0, security_1.validatePath)(process.cwd(), newPath, 'safeFileRename'); } // Try to rename - will fail if oldPath doesn't exist fs.renameSync(oldPath, newPath); } catch (error) { if (error instanceof Error && error.name === 'SecurityError') { throw error; } throw new errors_1.FileSystemError(`Failed to rename file from ${oldPath} to ${newPath}`, oldPath); } } function readFileContent(filePath) { try { // Only validate relative paths, but check for obvious security issues in all paths if (path.isAbsolute(filePath)) { // Check for specific security issues in absolute paths if (filePath === '/etc/passwd' || filePath.includes('..')) { (0, security_1.validatePath)(process.cwd(), filePath, 'readFileContent'); } } else { (0, security_1.validatePath)(process.cwd(), filePath, 'readFileContent'); } return fs.readFileSync(filePath, 'utf8'); } catch (error) { if (error instanceof Error && error.name === 'SecurityError') { throw error; } throw new errors_1.FileSystemError(`Failed to read file: ${filePath}`, filePath); } } function writeFileContent(filePath, content) { try { // Check for null bytes in all paths if (filePath.includes('\0')) { throw new errors_1.SecurityError(`Null byte in path detected in writeFileContent: ${filePath}`, filePath); } // Only validate relative paths to avoid issues with absolute paths in tests if (!path.isAbsolute(filePath)) { (0, security_1.validatePath)(process.cwd(), filePath, 'writeFileContent'); } fs.writeFileSync(filePath, content, 'utf8'); } catch (error) { if (error instanceof Error && error.name === 'SecurityError') { throw error; } throw new errors_1.FileSystemError(`Failed to write file: ${filePath}`, filePath); } } // Template processing utilities function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function replaceInFile(filePath, replacements) { // Validate all template inputs for security (0, validation_1.validateAllTemplateInputs)(replacements); // Read the file content const content = readFileContent(filePath); // Use simple template replacement const secureContent = (0, template_security_1.simpleTemplateReplace)(content, replacements); // Write the content back to the file writeFileContent(filePath, secureContent); } function processTemplateFiles(projectPath, projectName) { // Only validate relative paths to avoid issues with absolute paths in tests if (!path.isAbsolute(projectPath)) { (0, security_1.validatePath)(process.cwd(), projectPath, 'processTemplateFiles'); } // Validate template inputs for security (0, validation_1.validateAllTemplateInputs)({ [constants_1.TEMPLATE_VARS.PROJECT_NAME]: projectName, }); // Rename template files const fileRenamings = [ { from: path.join(projectPath, constants_1.FILE_PATTERNS.PACKAGE_JSON_TEMPLATE), to: path.join(projectPath, constants_1.FILE_PATTERNS.PACKAGE_JSON), }, { from: path.join(projectPath, constants_1.FILE_PATTERNS.GITIGNORE_TEMPLATE), to: path.join(projectPath, constants_1.FILE_PATTERNS.GITIGNORE), }, { from: path.join(projectPath, constants_1.FILE_PATTERNS.GO_MOD_TEMPLATE), to: path.join(projectPath, constants_1.FILE_PATTERNS.GO_MOD), }, ]; for (const { from, to } of fileRenamings) { try { safeFileRename(from, to); } catch (error) { // Template files might not exist, which is okay if (error instanceof errors_1.FileSystemError) { continue; } throw error; } } // Process go.mod template const goModPath = path.join(projectPath, constants_1.FILE_PATTERNS.GO_MOD); if (safeFileExists(goModPath)) { replaceInFile(goModPath, { [constants_1.TEMPLATE_VARS.PROJECT_NAME]: projectName, }); } // Copy spalidate validation templates const validationTemplateFiles = [ 'expected-primary.yaml.template', 'expected-secondary.yaml.template', ]; for (const templateFile of validationTemplateFiles) { const srcPath = path.join(projectPath, templateFile); if (safeFileExists(srcPath)) { // Keep template files for new-scenario command continue; } } } function replaceProjectNameInGoFiles(projectPath, projectName) { // Only validate relative paths to avoid issues with absolute paths in tests if (!path.isAbsolute(projectPath)) { (0, security_1.validatePath)(process.cwd(), projectPath, 'replaceProjectNameInGoFiles'); } // Validate template inputs for security (0, validation_1.validateAllTemplateInputs)({ [constants_1.TEMPLATE_VARS.PROJECT_NAME]: projectName, }); function processDirectory(dir) { try { // Only validate relative paths to avoid issues with absolute paths in tests if (!path.isAbsolute(dir)) { (0, security_1.validatePath)(process.cwd(), dir, 'processDirectory'); } const items = fs.readdirSync(dir); for (const item of items) { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { processDirectory(fullPath); } else if (item.endsWith(constants_1.FILE_PATTERNS.GO_EXTENSION)) { replaceInFile(fullPath, { [constants_1.TEMPLATE_VARS.PROJECT_NAME]: projectName, }); } } } catch { throw new errors_1.FileSystemError(`Failed to process directory: ${dir}`, dir); } } processDirectory(projectPath); } function removeSecondaryDbFiles(projectPath) { // Only validate relative paths to avoid issues with absolute paths in tests if (!path.isAbsolute(projectPath)) { (0, security_1.validatePath)(process.cwd(), projectPath, 'removeSecondaryDbFiles'); } const exampleDir = path.join(projectPath, 'scenarios', 'example-01-basic-setup'); const filesToRemove = [ path.join(exampleDir, 'expected-secondary.yaml'), path.join(exampleDir, 'seed-data', 'secondary-seed.json'), path.join(projectPath, 'expected-secondary.yaml.template'), ]; for (const file of filesToRemove) { safeFileDelete(file); } } //# sourceMappingURL=file-operations.js.map