Skip to content

Commit

Permalink
Merge pull request #43 from HubSpot/add/logging-utils
Browse files Browse the repository at this point in the history
Port logging utils
  • Loading branch information
kemmerle authored Oct 6, 2023
2 parents 3ed4496 + b364897 commit 15d63bb
Show file tree
Hide file tree
Showing 12 changed files with 526 additions and 73 deletions.
2 changes: 1 addition & 1 deletion config/config_DEPRECATED.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,13 @@ export function writeConfig(options: WriteConfigOptions = {}): void {
}

function readConfigFile(): { source?: string; error?: BaseError } {
isConfigPathInGitRepo(_configPath);
let source;
let error;
if (!_configPath) {
return { source, error };
}
try {
isConfigPathInGitRepo(_configPath);
source = fs.readFileSync(_configPath);
} catch (err) {
error = err as BaseError;
Expand Down
16 changes: 16 additions & 0 deletions lang/en.lyaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ en:
success: "Your new {{ type }} has been created in {{ dest }}"
downloadGithubRepoContents:
downloading: "Downloading content piece: {{ contentPiecePath }} from {{ downloadUrl }} to {{ downloadPath }}"
git:
securityIssue: "Security Issue Detected"
configFileTracked: "The HubSpot config file can be tracked by git."
fileName: 'File: "{{ configPath }}"'
remediate: "To remediate:"
moveConfig: "- Move the config file to your home directory: '{{ homeDir }}'"
addGitignore: "- Add gitignore pattern '{{ configPath }}' to a .gitignore file in root of your repository."
noRemote: "- Ensure that the config file has not already been pushed to a remote repository."
checkFailed: "Unable to determine if config file is properly ignored by git."
modules:
createModule:
creatingModule: "Creating module at {{ path }}"
Expand Down Expand Up @@ -124,6 +133,9 @@ en:
creatingPath: "Making {{ path }} if needed"
logging:
creatingFile: "Creating file at {{ path }}"
processFieldsJs:
converting: "Converting \"{{ src }}\" to \"{{ dest }}\"."
converted: "Finished converting \"{{ src }}\" to \"{{ dest }}\"."
errors:
hubdb:
invalidJsonPath: "The HubDB table file must be a '.json' file"
Expand Down Expand Up @@ -229,3 +241,7 @@ en:
initiateSync: "There was an error initiating the sandbox sync."
fetchTaskStatus: "There was an error fetching the task status while syncing sandboxes."
fetchTypes: "There was an error fetching sandbox types."
processFieldsJs:
fieldsJsNotReturnArray: "There was an error loading JS file \"{{ path }}\". Expected type \"Array\". Make sure that your function returns an array"
fieldsJsNotFunction: "There was an error loading JS file \"{{ path }}\". Expected type \"Function\". Make sure that your default export is a function."
invalidMjsFile: ".mjs files are only supported when using Node 13.2.0+"
1 change: 1 addition & 0 deletions lib/cms/handleFieldsJS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class FieldsJs {
rejected: boolean;
fieldOptions: string;
outputPath?: string;
toJSON?: () => JSON;

constructor(
projectDir: string,
Expand Down
115 changes: 115 additions & 0 deletions lib/cms/processFieldsJs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import path from 'path';
import fs from 'fs';
import semver from 'semver';
import { pathToFileURL } from 'url';
import { getExt } from '../path';
import { throwError, throwErrorWithMessage } from '../../errors/standardErrors';
import { FieldsJs } from './handleFieldsJS';
import { i18n } from '../../utils/lang';

const i18nKey = 'processFieldsJs';

const { dirName, fieldOptions, filePath, writeDir } = process.env;
const baseName = path.basename(filePath!);

const FieldErrors = {
IsNotFunction: 'IsNotFunction',
DoesNotReturnArray: 'DoesNotReturnArray',
};

//TODO - Figure out agnostic logging
console.info(
i18n(`${i18nKey}.converting`, {
src: dirName + `/${baseName}`,
dest: dirName + '/fields.json',
})
);

/*
* How this works: dynamicImport() will always return either a Promise or undefined.
* In the case when it's a Promise, its expected that it will resolve to a function.
* This function has optional return type of Promise<Array> | Array. In order to have uniform handling,
* we wrap the return value of the function in a Promise.resolve(), and then process.
*/

const fieldsPromise = dynamicImport(filePath!).catch(e => throwError(e));

fieldsPromise.then(fieldsFunc => {
const fieldsFuncType = typeof fieldsFunc;
if (fieldsFuncType !== 'function') {
throwErrorWithMessage(`${i18nKey}.${FieldErrors.IsNotFunction}`, {
path: filePath!,
});
}
return Promise.resolve(fieldsFunc(fieldOptions)).then(fields => {
if (!Array.isArray(fields)) {
throwErrorWithMessage(`${i18nKey}.${FieldErrors.DoesNotReturnArray}`, {
path: filePath!,
});
}

const finalPath = path.join(writeDir!, '/fields.json');

return fieldsArrayToJson(fields).then(json => {
if (!fs.existsSync(writeDir!)) {
fs.mkdirSync(writeDir!, { recursive: true });
}
fs.writeFileSync(finalPath, json);

//TODO - Figure out agnostic logging
console.log(
i18n(`${i18nKey}.converted`, {
src: dirName + `/${baseName}`,
dest: dirName + '/fields.json',
})
);
if (process) {
process.send!({
action: 'COMPLETE',
finalPath,
});
}
});
});
});

/*
* Polyfill for `Array.flat(Infinity)` since the `flat` is only available for Node v11+
* https://stackoverflow.com/a/15030117
*/
function flattenArray(arr: Array<any>): Array<any> {
return arr.reduce((flat, toFlatten) => {
return flat.concat(
Array.isArray(toFlatten) ? flattenArray(toFlatten) : toFlatten
);
}, []);
}

async function fieldsArrayToJson(fields: Array<FieldsJs>): Promise<string> {
const allFields = await Promise.all(flattenArray(fields));
const jsonFields = allFields.map(field => {
return typeof field['toJSON'] === 'function' ? field.toJSON() : field;
});
return JSON.stringify(jsonFields, null, 2);
}

/**
* Takes in a path to a javascript file and either dynamically imports it or requires it, and returns, depending on node version.
* @param {string} filePath - Path to javascript file
* @returns {Promise | undefined} - Returns _default_ exported content if ESM, or exported module content if CJS, or undefined if node version < 13.2 and file is .mjs.
*/
async function dynamicImport(filePath: string): Promise<any> {
if (semver.gte(process.version, '13.2.0')) {
const exported = await import(pathToFileURL(filePath).toString()).then(
content => content.default
);
return exported;
} else {
if (getExt(filePath) == 'mjs') {
throwErrorWithMessage(`${i18nKey}.invalidMjsFile`);
}
return require(filePath);
}
}
63 changes: 1 addition & 62 deletions lib/gitignore.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,13 @@
import { readFileSync, writeFileSync } from 'fs-extra';
import path from 'path';
import findup from 'findup-sync';

import {
isConfigPathInGitRepo,
configFilenameIsIgnoredByGitignore,
getGitComparisonDir,
makeComparisonDir,
} from '../utils/git';
import { checkGitInclusion } from '../utils/git';
import { DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME } from '../constants/config';
import { throwErrorWithMessage } from '../errors/standardErrors';
import { BaseError } from '../types/Error';

const GITIGNORE_FILE = '.gitignore';

// Get all .gitignore files since they can cascade down directory structures
function getGitignoreFiles(configPath: string): Array<string> {
const gitDir = getGitComparisonDir();
const files: Array<string> = [];
if (!gitDir) {
// Not in git
return files;
}
// Start findup from config dir
let cwd: string | null = configPath && path.dirname(configPath);
while (cwd) {
const ignorePath = findup(GITIGNORE_FILE, { cwd });
const ignorePathComparisonDir = makeComparisonDir(ignorePath);
const gitComparisonDir = makeComparisonDir(gitDir);
if (
ignorePath &&
ignorePathComparisonDir &&
gitComparisonDir &&
ignorePathComparisonDir.startsWith(gitComparisonDir)
) {
const file = path.resolve(ignorePath);
files.push(file);
cwd = path.resolve(path.dirname(file) + '..');
} else {
cwd = null;
}
}
return files;
}

type GitInclusionResult = {
inGit: boolean;
configIgnored: boolean;
gitignoreFiles: Array<string>;
};

function checkGitInclusion(configPath: string): GitInclusionResult {
const result: GitInclusionResult = {
inGit: false,
configIgnored: false,
gitignoreFiles: [],
};

if (isConfigPathInGitRepo(configPath)) {
result.inGit = true;
result.gitignoreFiles = getGitignoreFiles(configPath);

if (configFilenameIsIgnoredByGitignore(result.gitignoreFiles, configPath)) {
// Found ignore statement in .gitignore that matches config filename
result.configIgnored = true;
}
}
return result;
}

export function checkAndAddConfigToGitignore(configPath: string): void {
try {
const { configIgnored, gitignoreFiles } = checkGitInclusion(configPath);
Expand Down
52 changes: 52 additions & 0 deletions lib/loggingUtils/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import fs from 'fs-extra';
import path from 'path';
import os from 'os';
import { checkGitInclusion } from '../../utils/git';
import { logger } from './logger';
import { i18n } from '../../utils/lang';
import { DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME } from '../../constants/config';

const GITIGNORE_FILE = '.gitignore';

const i18nKey = 'debug.git';

export function checkAndWarnGitInclusion(configPath: string): void {
try {
const { inGit, configIgnored } = checkGitInclusion(configPath);

if (!inGit || configIgnored) return;
logger.warn(i18n(`${i18nKey}.securityIssue`));
logger.warn(i18n(`${i18nKey}.configFileTracked`));
logger.warn(i18n(`${i18nKey}.fileName`, { configPath }));
logger.warn(i18n(`${i18nKey}.remediate`));
logger.warn(i18n(`${i18nKey}.moveConfig`, { homeDir: os.homedir() }));
logger.warn(i18n(`${i18nKey}.addGitignore`, { configPath }));
logger.warn(i18n(`${i18nKey}.noRemote`));
} catch (e) {
// fail silently
logger.debug(i18n(`${i18nKey}.checkFailed`));
}
}

export function checkAndUpdateGitignore(configPath: string): void {
try {
const { configIgnored, gitignoreFiles } = checkGitInclusion(configPath);
if (configIgnored) return;

let gitignoreFilePath =
gitignoreFiles && gitignoreFiles.length ? gitignoreFiles[0] : null;

if (!gitignoreFilePath) {
gitignoreFilePath = path.resolve(configPath, GITIGNORE_FILE);

fs.writeFileSync(gitignoreFilePath, '');
}

const gitignoreContents = fs.readFileSync(gitignoreFilePath).toString();
const updatedContents = `${gitignoreContents.trim()}\n\n# HubSpot config file\n${DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME}\n`;
fs.writeFileSync(gitignoreFilePath, updatedContents);
} catch (e) {
// fail silently
logger.debug(i18n(`${i18nKey}.checkFailed`));
}
}
10 changes: 10 additions & 0 deletions lib/loggingUtils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {
checkAndWarnGitInclusion as __checkAndWarnGitInclusion,
checkAndUpdateGitignore as __checkAndUpdateGitignore,
} from './git';
import { outputLogs as __outputLogs } from './logs'

export const checkAndWarnGitInclusion = __checkAndWarnGitInclusion;
export const checkAndUpdateGitignore = __checkAndUpdateGitignore;
export const outputLogs = __outputLogs;

Loading

0 comments on commit 15d63bb

Please sign in to comment.