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
JavaScript
;
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