Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port logging utils #43

Merged
merged 11 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -230,3 +242,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!);
kemmerle marked this conversation as resolved.
Show resolved Hide resolved

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

//TODO - Figure out agnostic logging
console.info(
kemmerle marked this conversation as resolved.
Show resolved Hide resolved
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(
kemmerle marked this conversation as resolved.
Show resolved Hide resolved
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
55 changes: 55 additions & 0 deletions lib/loggingUtils/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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.log('');
kemmerle marked this conversation as resolved.
Show resolved Hide resolved
logger.warn(i18n(`${i18nKey}.moveConfig`, { homeDir: os.homedir() }));
logger.log('');
logger.warn(i18n(`${i18nKey}.addGitignore`, { configPath }));
logger.log('');
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'
kemmerle marked this conversation as resolved.
Show resolved Hide resolved

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

Loading
Loading