@fontoxml/fontoxml-development-tools
Version:
Development tools for Fonto.
255 lines (226 loc) • 6.77 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { getFileMatchInDirectoryAncestrySync } from './getParentDirectoryContainingFileSync.js';
import Version from './Version.js';
/** @typedef {import('./App').default} App */
/** @typedef {import('./request/FdtCommand').default} FdtCommand */
/** @typedef {import('./request/FdtRequest').default} FdtRequest */
/** @typedef {{ location?: string, configLocation?: string }} Overrides */
/** @typedef {(request: FdtRequest) => Overrides} GetOverrides */
/** @typedef {Map<FdtCommand, GetOverrides>} GetOverridesByCommand */
/**
* @typedef Product
*
* @property {string} productId
* @property {string} manifestFileName
* @property {string} manifestVersionPropertyName
* @property {GetOverridesByCommand} getOverridesByCommand
*/
export default class BackendAppRepository {
/** @type {{[key:string]: Product }} */
static _products = {};
/**
* @param {string} productId
* @param {string} manifestFileName
* @param {string} manifestVersionPropertyName
* @param {GetOverridesByCommand} [getOverridesByCommand=new Map()]
*/
static registerBackendApp(
productId,
manifestFileName,
manifestVersionPropertyName,
getOverridesByCommand = new Map()
) {
this._products[productId] = {
productId,
manifestFileName,
manifestVersionPropertyName,
getOverridesByCommand,
};
}
/**
* @param {string} manifestFilename
*
* @return {string | undefined}
*/
static getProductIdByManifestFilename(manifestFilename) {
const product = Object.values(this._products).find(
(product) => product.manifestFileName === manifestFilename
);
return product ? product.productId : undefined;
}
/**
* @param {string} productId
*
* @return {string | undefined}
*/
static getManifestVersionPropertyNameByProductId(productId) {
const product = this._products[productId];
return product ? product.manifestVersionPropertyName : undefined;
}
/**
* @return {string[]}
*/
static getManifestFileNames() {
return Object.values(this._products).map(
(product) => product.manifestFileName
);
}
/**
* @param {FdtRequest} request
*
* @return {Overrides}
*/
static getOverridesForRequest(request) {
/** @type {GetOverrides | undefined} */
const getOverrides = Object.values(this._products).reduce(
(acc, product) => {
if (acc) {
return acc;
}
return product.getOverridesByCommand.get(request.command);
},
undefined
);
let location;
let configLocation;
if (getOverrides) {
const overrides = getOverrides(request);
location = overrides.location;
configLocation = overrides.configLocation;
}
return { location, configLocation };
}
/**
* @param {App} app
* @param {FdtRequest} request
*
* @return {Promise<void>}
*/
async init(app, request) {
this._app = app;
this.path = null;
this.productId = null;
this.manifestFilename = null;
this.manifestVersionPropertyName = null;
this.config = null;
this.configSource = null;
/** @type {Version | null} */
this.sdkVersion = null;
/** @type {Error | null} */
this.manifestError = null;
const overrides = BackendAppRepository.getOverridesForRequest(request);
const manifestFileNames = BackendAppRepository.getManifestFileNames();
const manifestPath = getFileMatchInDirectoryAncestrySync(
path.resolve(overrides.location || process.cwd()),
manifestFileNames
);
if (!manifestPath) {
return;
}
const manifestFilename = path.basename(manifestPath);
let productId;
try {
productId =
BackendAppRepository.getProductIdByManifestFilename(
manifestFilename
);
if (!productId) {
throw new Error(
`Could not get productId from manifest filename "${manifestFilename}".`
);
}
} catch (error) {
this.manifestError = error;
this._app.logger.notice(
`Could not determine product from "${manifestFilename}".`
);
this._app.logger.notice(
`Please check and fix your "${manifestFilename}" file.`
);
this._app.logger.break();
return;
}
let sdkVersion = null;
let manifestVersionPropertyName = null;
try {
const manifestJson = JSON.parse(
fs.readFileSync(manifestPath, 'utf8')
);
const propertyName =
BackendAppRepository.getManifestVersionPropertyNameByProductId(
productId
);
if (!propertyName) {
throw new Error(
`Could not map product ${productId} to version property name.`
);
}
const version = manifestJson[propertyName];
if (!version) {
throw new Error(
`Manifest does not have a version specified in the ${propertyName} property.`
);
}
manifestVersionPropertyName = propertyName;
sdkVersion = new Version(version);
} catch (error) {
this.manifestError = error;
this._app.logger.notice(`Could not read ${manifestFilename}.`);
this._app.logger.notice(
`Please check and fix your ${productId} ${manifestFilename} file.`
);
this._app.logger.break();
return;
}
this.path = path.dirname(manifestPath);
this.productId = productId;
this.manifestFilename = manifestFilename;
this.manifestVersionPropertyName = manifestVersionPropertyName;
this.sdkVersion = sdkVersion;
try {
const configPath = overrides.configLocation
? path.resolve(overrides.configLocation)
: path.join(this.path, '.env');
this.config = fs
.readFileSync(configPath, 'utf8')
.split(/\r?\n/g)
.reduce((acc, line) => {
if (!line.startsWith('#') && line.includes('=')) {
const [key, ...rest] = line.split('=');
const value = rest.join('=');
acc[key] = value;
}
return acc;
}, {});
this.configSource = configPath;
} catch (_error) {
// No-op
}
}
/**
* @param {string} [productId]
*/
throwIfNotInsideBackendAppRepository(productId) {
if (!this.path) {
const requiredProductId = productId
? `Fonto ${productId}`
: 'Fonto backend app';
throw new this._app.logger.ErrorWithSolution(
`Not running from inside a ${requiredProductId} repository.`,
`This command requires to be run from inside a ${requiredProductId} repository.`,
this.manifestError ? this.manifestError : undefined
);
}
if (productId && this.productId !== productId) {
const currentProductId = this.productId
? `Fonto ${this.productId}`
: 'different Fonto backend app';
throw new this._app.logger.ErrorWithSolution(
`Not running from inside a Fonto ${productId} repository, but from a ${currentProductId} repository.`,
`This command requires to be run from inside a Fonto ${productId} repository.`,
this.manifestError ? this.manifestError : undefined
);
}
}
}