Skip to content

Commit

Permalink
feat: update addToGitignore functionality
Browse files Browse the repository at this point in the history
- Modularize isInGitignore functionality
- Move inquirer confirm question to inside addToGitignore
- Check isInGitignore before ask user to add to .gitignore
- Check last file character and add new line if it is necessary
- Move add to gitignore call for rc file after saving the config file

About testing:
- Add tests
- Modify check-string-in-file exporting an object, so content can be stubbed
- Add a inquirer wrapper in cli file for same previous reason: allow to stub inquirer calls.

Closes #21
  • Loading branch information
javier-sierra-sngular committed Oct 30, 2023
1 parent 32612de commit 4389ce7
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 42 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
node_modules/
dist-types/
.api-mock-runner
.api-mock-runner/
.apimockrc
coverage/
.DS_Store
3 changes: 2 additions & 1 deletion src/services/check-string-in-file.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as fs from 'fs';
import * as readline from 'readline';

export default async function (stringToCheck, filePath) {
async function check(stringToCheck, filePath) {
const reader = readline.createInterface({ input: fs.createReadStream(filePath) });
let exists = false;

Expand All @@ -14,3 +14,4 @@ export default async function (stringToCheck, filePath) {

return exists;
}
export const checkStringInFile = { check };
16 changes: 16 additions & 0 deletions src/services/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as inquirer from '@inquirer/prompts';

/**
* Confirm through CLI adding a file to .gitignore
* @async
* @function confirmAddToGitignore
* @param {string} fileName - The file name to add to .gitignore
* @returns {Promise<boolean>} True if the user confirms, false otherwise
*/
async function confirmAddToGitignore(fileName) {
return await inquirer.confirm({
message: `Add ${fileName} to .gitignore?`,
});
}

export const cli = { confirmAddToGitignore };
43 changes: 43 additions & 0 deletions src/services/gitignore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import fs from 'node:fs';
import path from 'node:path';
import { checkStringInFile } from './check-string-in-file.js';
import { cli } from './cli.js';

export const GITIGNORE_PATH = path.join(process.cwd(), '.gitignore');

/**
* Append a newline with file or folder name to .gitignore.
* If .gitignore does not exist, it will be created.
* If the file or folder name is already in .gitignore, it will not be added again.
* Any action is confirmed through the CLI by the user.
* @async
* @function addToGitignore
* @param {string} fileName - The file or folder name to append to .gitignore
* @returns {Promise<void>}
*/
export default async function addToGitignore(fileName) {
const existsGitignoreFile = fs.existsSync(GITIGNORE_PATH);
if ((!existsGitignoreFile || !(await isInGitignore(fileName))) && (await cli.confirmAddToGitignore(fileName))) {
const leadingCharacter = existsGitignoreFile ? getLeadingCharacter() : '';
fs.appendFileSync(GITIGNORE_PATH, `${leadingCharacter}${fileName}\n`);
}
}

/**
* Check if a string is in .gitignore
* @async
* @function isInGitignore
* @param {string} textToCheck - The text to check
* @returns {Promise<boolean>} True if the text is in .gitignore, false otherwise
*/
async function isInGitignore(textToCheck) {
const result = await checkStringInFile.check(textToCheck, GITIGNORE_PATH);
return result;
}

function getLeadingCharacter() {
let leadingCharacter = '';
const lastFileCharacter = fs.readFileSync(GITIGNORE_PATH, 'utf8').slice(-1);
leadingCharacter = lastFileCharacter === '\n' ? '' : '\n';
return leadingCharacter;
}
25 changes: 4 additions & 21 deletions src/services/user-flow-steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as fs from 'node:fs';
import { OpenApiSchemaNotFoundError } from '../errors/openapi-schema-not-found-error.js';
import cloneGitRepository from '../services/clone-git-repository.js';
import findOasFromDir from '../services/find-oas-from-dir.js';
import addToGitignore from './gitignore.js';
import { originValidator, portValidator } from './inquirer-validators.js';
import { RC_FILE_NAME, TEMP_FOLDER_NAME, addToGitignore, verifyRemoteOrigin } from './utils.js';

import { RC_FILE_NAME, TEMP_FOLDER_NAME, verifyRemoteOrigin } from './utils.js';
/**
* @typedef {Object} Config
* @property {string} schemasOrigin - The origin of the schemas (local or remote)
Expand All @@ -28,24 +28,6 @@ async function initWithConfigFile() {
return useExistingConfig ? existingConfig : await init();
}

/**
* first step when the config file doesn't exist
* @async
* @function initNoConfigFile
* @returns {Promise<string>} path or url of the schemas
*/
async function startNewFlow() {
const schemasOrigin = await getOrigin();
const addRcFileToGitignore = await confirm({
message: `Add ${RC_FILE_NAME} to .gitignore?`,
});
if (addRcFileToGitignore) {
await addToGitignore(RC_FILE_NAME);
}

return schemasOrigin;
}

/**
* Get the schemas from the origin
* @async
Expand Down Expand Up @@ -90,7 +72,7 @@ async function getOrigin() {
* @throws {OpenApiSchemaNotFoundError} When no schemas are found in the given directory
*/
async function init({ origin, schemaPaths, ports } = {}) {
const schemasOrigin = origin || (await startNewFlow());
const schemasOrigin = origin || (await getOrigin());
const schemas = await getSchemas(schemasOrigin);
if (!schemas.length) {
throw new OpenApiSchemaNotFoundError();
Expand All @@ -114,6 +96,7 @@ async function init({ origin, schemaPaths, ports } = {}) {

fs.writeFileSync(`${process.cwd()}/${RC_FILE_NAME}`, JSON.stringify(config, null, '\t'));
console.log(config);
await addToGitignore(RC_FILE_NAME);

return config;
}
Expand Down
22 changes: 3 additions & 19 deletions src/services/utils.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
import * as fs from 'node:fs';
import checkStringInFile from './check-string-in-file.js';

/**
* The name of the config file
* @constant
* @type {string}
* @default
*/
export const RC_FILE_NAME = '.apimockrc';

/**
* The name of the temporary folder
* @constant
* @type {string}
* @default
*/
export const TEMP_FOLDER_NAME = '.api-mock-runner';
/**
* Append text to .gitignore
* @async
* @function addToGitignore
* @param {string} textToAppend - The text to append to .gitignore
* @returns {Promise<boolean>}
*/
export async function addToGitignore(textToAppend) {
if (!(await checkStringInFile(textToAppend, `${process.cwd()}/.gitignore`))) {
fs.appendFileSync(`${process.cwd()}/.gitignore`, `\n${textToAppend}`);
return true;
}
return false;
}
export const TEMP_FOLDER_NAME = '.api-mock-runner/';

/**
* Verify if the origin is remote
* @function verifyRemoteOrigin
Expand All @@ -51,6 +36,5 @@ export function verifyRemoteOrigin(origin) {
export default {
RC_FILE_NAME,
TEMP_FOLDER_NAME,
addToGitignore,
verifyRemoteOrigin,
};
84 changes: 84 additions & 0 deletions test/unit/services/gitignore.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect, use } from 'chai';
import fs from 'node:fs';
import { restore, stub } from 'sinon';
import sinonChai from 'sinon-chai';
import { checkStringInFile } from '../../../src/services/check-string-in-file.js';
import { cli } from '../../../src/services/cli.js';
import addToGitignore, { GITIGNORE_PATH } from '../../../src/services/gitignore.js';
use(sinonChai);

describe('unit:addToGitignore', () => {
const gitignoreContentNoNewline = 'fileContentTest';
const fileNameTest = 'fileNameTest';
const lineToAdd = `${fileNameTest}\n`;
let appendFileSyncStub;
let checkStringInFileStub;
let confirmStub;
let existsSyncStub;
let readFileSyncStub;

beforeEach(() => {
appendFileSyncStub = stub(fs, 'appendFileSync');
checkStringInFileStub = stub(checkStringInFile, 'check');
confirmStub = stub(cli, 'confirmAddToGitignore');
existsSyncStub = stub(fs, 'existsSync');
readFileSyncStub = stub(fs, 'readFileSync');
});

afterEach(() => {
restore();
});

it('should not add filename when already is in it', async () => {
existsSyncStub.returns(true);
checkStringInFileStub.returns(true);
await addToGitignore(fileNameTest);
expect(existsSyncStub).to.have.been.calledWith(GITIGNORE_PATH);
expect(checkStringInFileStub).to.have.been.calledWith(fileNameTest, GITIGNORE_PATH);
expect(confirmStub).to.not.have.been.called;
expect(readFileSyncStub).to.not.have.been.called;
expect(appendFileSyncStub).to.not.have.been.called;
});

it('should not add filename when user refuses', async () => {
existsSyncStub.returns(false);
confirmStub.returns(false);
await addToGitignore(fileNameTest);
expect(existsSyncStub).to.have.been.called;
expect(confirmStub).to.have.been.called;
expect(readFileSyncStub).to.not.have.been.called;
expect(appendFileSyncStub).to.not.have.been.called;
});

it('should add newline and filename to existing .gitignore when user accepts', async () => {
existsSyncStub.returns(true);
confirmStub.returns(true);
readFileSyncStub.returns(gitignoreContentNoNewline);
await addToGitignore(fileNameTest);
expect(existsSyncStub).to.have.been.calledOnceWith(GITIGNORE_PATH);
expect(confirmStub).to.have.been.calledOnceWith(fileNameTest);
expect(readFileSyncStub).to.have.been.calledOnceWith(GITIGNORE_PATH, 'utf8');
expect(appendFileSyncStub).to.have.been.calledOnceWith(GITIGNORE_PATH, `\n${lineToAdd}`);
});

it('should add filename to existing .gitignore when user accepts', async () => {
existsSyncStub.returns(true);
confirmStub.returns(true);
readFileSyncStub.returns(`${gitignoreContentNoNewline}\n`);
await addToGitignore(fileNameTest);
expect(existsSyncStub).to.have.been.calledOnceWith(GITIGNORE_PATH);
expect(confirmStub).to.have.been.calledOnceWith(fileNameTest);
expect(readFileSyncStub).to.have.been.calledOnceWith(GITIGNORE_PATH, 'utf8');
expect(appendFileSyncStub).to.have.been.calledOnceWith(GITIGNORE_PATH, `${lineToAdd}`);
});

it('should add filename to missing .gitignore when user accepts', async () => {
existsSyncStub.returns(false);
confirmStub.returns(true);
await addToGitignore(fileNameTest);
expect(existsSyncStub).to.have.been.calledOnceWith(GITIGNORE_PATH);
expect(confirmStub).to.have.been.calledOnceWith(fileNameTest);
expect(readFileSyncStub).to.not.have.been.called;
expect(appendFileSyncStub).to.have.been.calledOnceWith(GITIGNORE_PATH, `${lineToAdd}`);
});
});

0 comments on commit 4389ce7

Please # to comment.