@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
230 lines • 6.7 kB
JavaScript
import fs from 'fs';
import path from 'path';
import inquirer from 'inquirer';
import { logger } from '../../utils/logger.js';
import { normalizeOptionalString } from '../../utils/value.js';
const REQUIRED_VALUE_MESSAGE = 'Enter a value to continue.';
const getTemplateFileReplacements = mergeVariables => [{
filePath: '.firebaserc',
replacements: {
GC_DEV_PROJECT: mergeVariables.developmentProjectId,
GC_PROJECT: mergeVariables.productionProjectId
}
}, {
filePath: 'config.php',
replacements: {
GC_DEV_PROJECT: mergeVariables.developmentProjectId,
GC_PROJECT: mergeVariables.productionProjectId,
HOSTNAME: mergeVariables.hostname,
TITLE: mergeVariables.title
}
}, {
filePath: 'public/index.php',
replacements: {
TITLE: mergeVariables.title
}
}, {
filePath: 'composer.json',
replacements: {
HOSTNAME: mergeVariables.hostname,
TITLE: mergeVariables.title,
VENDOR: mergeVariables.composerVendor
}
}, {
filePath: 'app/package.json',
replacements: {
HOSTNAME: mergeVariables.hostname,
TITLE: mergeVariables.title
}
}, {
filePath: 'functions/base/package.json',
replacements: {
HOSTNAME: mergeVariables.hostname
}
}, {
filePath: 'package.json',
replacements: {
HOSTNAME: mergeVariables.hostname
}
}, {
filePath: 'public/manifest.json',
replacements: {
TITLE: mergeVariables.title
}
}];
const createRequiredValuePrompt = ({
defaultValue,
message,
name
}) => ({
...(defaultValue ? {
default: defaultValue
} : {}),
filter: value => normalizeOptionalString(value) ?? '',
message,
name,
type: 'input',
validate: value => Boolean(normalizeOptionalString(value)) || REQUIRED_VALUE_MESSAGE
});
const resolveTemplateFilePath = (cwd, filePath) => path.join(cwd, filePath);
export const normalizeProjectSlug = value => {
const normalizedValue = normalizeOptionalString(value);
if (!normalizedValue) {
return '';
}
return normalizedValue.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+/g, '').replace(/-+$/g, '');
};
export const replaceTemplateTokens = (content, replacements) => {
let nextContent = content;
Object.entries(replacements).forEach(([key, value]) => {
nextContent = nextContent.replaceAll(`{{ ${key} }}`, value ?? '');
});
return nextContent;
};
const readRequiredTemplateFile = (filePath, dependencies = {}) => {
const {
cwd = process.cwd(),
existsSyncImpl = fs.existsSync,
readFileSyncImpl = fs.readFileSync
} = dependencies;
const resolvedFilePath = resolveTemplateFilePath(cwd, filePath);
if (!existsSyncImpl(resolvedFilePath)) {
throw new Error(`Required init template file not found: ${filePath}`);
}
try {
return readFileSyncImpl(resolvedFilePath, 'utf-8');
} catch (error) {
throw new Error(`Failed to read init template file ${filePath}. ${error.message}`);
}
};
const writeTemplateFile = (filePath, content, dependencies = {}) => {
const {
cwd = process.cwd(),
writeFileSyncImpl = fs.writeFileSync
} = dependencies;
const resolvedFilePath = resolveTemplateFilePath(cwd, filePath);
try {
writeFileSyncImpl(resolvedFilePath, content, 'utf-8');
} catch (error) {
throw new Error(`Failed to write init template file ${filePath}. ${error.message}`);
}
};
const createMergeVariableSummaryRows = mergeVariables => [{
label: 'Client',
value: mergeVariables.clientName,
tone: 'accent'
}, {
label: 'Production project',
value: mergeVariables.productionProjectId,
tone: 'warning'
}, {
label: 'Development project',
value: mergeVariables.developmentProjectId,
tone: 'warning'
}, {
label: 'Hostname',
value: mergeVariables.hostname,
tone: 'accent'
}, {
label: 'Title',
value: mergeVariables.title,
tone: 'accent'
}, {
label: 'Composer vendor',
value: mergeVariables.composerVendor,
tone: 'accent'
}];
export const collectMergeVariables = async (dependencies = {}) => {
const {
loggerImpl = logger,
promptImpl = inquirer.prompt
} = dependencies;
const {
shouldContinue
} = await promptImpl([{
type: 'confirm',
name: 'shouldContinue',
default: true,
message: 'Template variables can only be replaced once. Continue?'
}]);
if (!shouldContinue) {
loggerImpl.warning('Template variable replacement cancelled by the user.');
return null;
}
const {
clientName
} = await promptImpl([createRequiredValuePrompt({
message: 'Enter the client name:',
name: 'clientName'
})]);
const defaultProductionProjectId = normalizeProjectSlug(clientName);
const {
productionProjectId
} = await promptImpl([createRequiredValuePrompt({
defaultValue: defaultProductionProjectId,
message: 'Enter the Google Cloud production project ID:',
name: 'productionProjectId'
})]);
const {
developmentProjectId
} = await promptImpl([createRequiredValuePrompt({
defaultValue: `dev-${productionProjectId}`,
message: 'Enter the Google Cloud development project ID:',
name: 'developmentProjectId'
})]);
const {
hostname
} = await promptImpl([createRequiredValuePrompt({
message: 'Enter the production hostname:',
name: 'hostname'
})]);
const {
title
} = await promptImpl([createRequiredValuePrompt({
defaultValue: hostname,
message: 'Enter the application title:',
name: 'title'
})]);
const {
composerVendor
} = await promptImpl([createRequiredValuePrompt({
defaultValue: defaultProductionProjectId,
message: 'Enter the Composer vendor name:',
name: 'composerVendor'
})]);
const mergeVariables = {
clientName,
composerVendor,
developmentProjectId,
hostname,
productionProjectId,
title
};
loggerImpl.summary('Template variables', createMergeVariableSummaryRows(mergeVariables), {
spacing: 'after'
});
return mergeVariables;
};
export const applyMergeVariables = (mergeVariables, dependencies = {}) => {
for (const templateFile of getTemplateFileReplacements(mergeVariables)) {
const currentContent = readRequiredTemplateFile(templateFile.filePath, dependencies);
const nextContent = replaceTemplateTokens(currentContent, templateFile.replacements);
writeTemplateFile(templateFile.filePath, nextContent, dependencies);
}
};
export default async (dependencies = {}) => {
const {
logger: loggerImpl = logger,
prompt: promptImpl = inquirer.prompt
} = dependencies;
const mergeVariables = await collectMergeVariables({
loggerImpl,
promptImpl
});
if (!mergeVariables) {
return false;
}
applyMergeVariables(mergeVariables, dependencies);
loggerImpl.success('Template variables replaced successfully.');
return true;
};