From ea3e87a0b17cd87744d57f15d5eb3c08c466cba4 Mon Sep 17 00:00:00 2001 From: Jon Ursenbach Date: Tue, 27 Jul 2021 14:54:01 -0700 Subject: [PATCH 1/4] feat: making the docs command recursive by default --- src/cmds/docs/index.js | 43 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/cmds/docs/index.js b/src/cmds/docs/index.js index 61abf4d89..eb305e142 100644 --- a/src/cmds/docs/index.js +++ b/src/cmds/docs/index.js @@ -28,11 +28,6 @@ exports.args = [ type: String, description: 'Project version', }, - { - name: 'recursive', - type: Boolean, - description: 'Search for files recursively', - }, { name: 'folder', type: String, @@ -41,7 +36,7 @@ exports.args = [ ]; exports.run = async function (opts) { - const { folder, key, version, recursive } = opts; + const { folder, key, version } = opts; if (!key) { return Promise.reject(new Error('No project API key provided. Please use `--key`.')); @@ -55,26 +50,22 @@ exports.run = async function (opts) { return Promise.reject(new Error(`No folder provided. Usage \`${config.cli} ${exports.usage}\`.`)); } - // Find the files to sync, either recursively or not - let allFiles; - if (recursive) { - // A recursive function that returns a list of child file paths - const readdirRecursive = folderToSearch => { - const filesInFolder = fs.readdirSync(folderToSearch, { withFileTypes: true }); - const files = filesInFolder - .filter(fileHandle => fileHandle.isFile()) - .map(fileHandle => path.join(folderToSearch, fileHandle.name)); - const folders = filesInFolder.filter(fileHandle => fileHandle.isDirectory()); - const subFiles = [].concat( - ...folders.map(fileHandle => readdirRecursive(path.join(folderToSearch, fileHandle.name))) - ); - return [...files, ...subFiles]; - }; - // Pull off the leading subdirectory, to keep things consistant with below - allFiles = readdirRecursive(folder).map(file => file.replace(`${folder}${path.sep}`, '')); - } else { - allFiles = fs.readdirSync(folder); - } + // Find the files to sync + const readdirRecursive = folderToSearch => { + const filesInFolder = fs.readdirSync(folderToSearch, { withFileTypes: true }); + const files = filesInFolder + .filter(fileHandle => fileHandle.isFile()) + .map(fileHandle => path.join(folderToSearch, fileHandle.name)); + const folders = filesInFolder.filter(fileHandle => fileHandle.isDirectory()); + const subFiles = [].concat( + ...folders.map(fileHandle => readdirRecursive(path.join(folderToSearch, fileHandle.name))) + ); + return [...files, ...subFiles]; + }; + + // Pull off the leading subdirectory, to keep things consistant with below + const allFiles = readdirRecursive(folder).map(file => file.replace(`${folder}${path.sep}`, '')); + // Strip out non-markdown files const files = allFiles.filter(file => file.endsWith('.md') || file.endsWith('.markdown')); From dcdb03212b5b3204c314174d78f06e361240c4b3 Mon Sep 17 00:00:00 2001 From: Jon Ursenbach Date: Tue, 27 Jul 2021 15:28:25 -0700 Subject: [PATCH 2/4] test: fixing some broken tests, adding one for recursive doc searching --- .../existing-docs/subdir/another-doc.md | 4 + __tests__/cmds/docs.test.js | 115 ++++++++++++------ src/cmds/docs/index.js | 7 +- 3 files changed, 83 insertions(+), 43 deletions(-) create mode 100644 __tests__/__fixtures__/existing-docs/subdir/another-doc.md diff --git a/__tests__/__fixtures__/existing-docs/subdir/another-doc.md b/__tests__/__fixtures__/existing-docs/subdir/another-doc.md new file mode 100644 index 000000000..4e13dff26 --- /dev/null +++ b/__tests__/__fixtures__/existing-docs/subdir/another-doc.md @@ -0,0 +1,4 @@ +--- +title: This is another document title +--- +Another body diff --git a/__tests__/cmds/docs.test.js b/__tests__/cmds/docs.test.js index e68ecaaee..0db28555e 100644 --- a/__tests__/cmds/docs.test.js +++ b/__tests__/cmds/docs.test.js @@ -11,6 +11,19 @@ const docsEdit = require('../../src/cmds/docs/edit'); const fixturesDir = `${__dirname}./../__fixtures__`; const key = 'Xmw4bGctRVIQz7R7dQXqH9nQe5d0SPQs'; const version = '1.0.0'; +const category = '5ae9ece93a685f47efb9a97c'; + +function getNockForVersion(v) { + return nock(config.host, { + reqheaders: { + 'x-readme-version': v, + }, + }); +} + +function hashFileContents(contents) { + return crypto.createHash('sha1').update(contents).digest('hex'); +} describe('rdme docs', () => { beforeAll(() => nock.disableNetConnect()); @@ -39,64 +52,88 @@ describe('rdme docs', () => { it.todo('should error if the folder contains no markdown files'); describe('existing docs', () => { + let simpleDoc; + let anotherDoc; + + beforeEach(() => { + let fileContents = fs.readFileSync(path.join(fixturesDir, `/existing-docs/simple-doc.md`)); + simpleDoc = { + slug: 'simple-doc', + doc: frontMatter(fileContents), + hash: hashFileContents(fileContents), + }; + + fileContents = fs.readFileSync(path.join(fixturesDir, `/existing-docs/subdir/another-doc.md`)); + anotherDoc = { + slug: 'another-doc', + doc: frontMatter(fileContents), + hash: hashFileContents(fileContents), + }; + }); + it('should fetch doc and merge with what is returned', () => { - const slug = 'simple-doc'; - const doc = frontMatter(fs.readFileSync(path.join(fixturesDir, `/existing-docs/${slug}.md`))); - const hash = crypto - .createHash('sha1') - .update(fs.readFileSync(path.join(fixturesDir, `/existing-docs/${slug}.md`))) - .digest('hex'); + expect.assertions(1); - const getMock = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) - .get(`/api/v1/docs/${slug}`) + const getMocks = getNockForVersion(version) + .get(`/api/v1/docs/simple-doc`) .basicAuth({ user: key }) - .reply(200, { category: '5ae9ece93a685f47efb9a97c', slug }); - - const putMock = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) - .put(`/api/v1/docs/${slug}`, { - category: '5ae9ece93a685f47efb9a97c', - slug, - body: doc.content, - lastUpdatedHash: hash, - ...doc.data, + .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' }) + .get(`/api/v1/docs/another-doc`) + .basicAuth({ user: key }) + .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: 'anOldHash' }); + + const updateMocks = getNockForVersion(version) + .put('/api/v1/docs/simple-doc', { + category, + slug: simpleDoc.slug, + body: simpleDoc.doc.content, + lastUpdatedHash: simpleDoc.hash, + ...simpleDoc.doc.data, + }) + .basicAuth({ user: key }) + .reply(200) + .put('/api/v1/docs/another-doc', { + category, + slug: anotherDoc.slug, + body: anotherDoc.doc.content, + lastUpdatedHash: anotherDoc.hash, + ...anotherDoc.doc.data, }) .basicAuth({ user: key }) .reply(200); - return docs.run({ folder: './__tests__/__fixtures__/existing-docs', key, version }).then(() => { - getMock.done(); - putMock.done(); + return docs.run({ folder: './__tests__/__fixtures__/existing-docs', key, version }).then(skippedDocs => { + // All docs should have been updated because their hashes from the GET request were different from what they + // are currently. + expect(skippedDocs).toHaveLength(0); + + getMocks.done(); + updateMocks.done(); }); }); it('should not send requests for docs that have not changed', () => { expect.assertions(1); - const slug = 'simple-doc'; - const hash = crypto - .createHash('sha1') - .update(fs.readFileSync(path.join(fixturesDir, `/existing-docs/${slug}.md`))) - .digest('hex'); - const getMock = nock(config.host, { + const getMocks = nock(config.host, { reqheaders: { 'x-readme-version': version, }, }) - .get(`/api/v1/docs/${slug}`) + .get('/api/v1/docs/simple-doc') .basicAuth({ user: key }) - .reply(200, { category: '5ae9ece93a685f47efb9a97c', slug, lastUpdatedHash: hash }); + .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash }) + .get('/api/v1/docs/another-doc') + .basicAuth({ user: key }) + .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: anotherDoc.hash }); - return docs.run({ folder: './__tests__/__fixtures__/existing-docs', key, version }).then(([message]) => { - expect(message).toBe('`simple-doc` not updated. No changes.'); - getMock.done(); + return docs.run({ folder: './__tests__/__fixtures__/existing-docs', key, version }).then(skippedDocs => { + expect(skippedDocs).toStrictEqual([ + '`simple-doc` was not updated because there were no changes.', + '`another-doc` was not updated because there were no changes.', + ]); + + getMocks.done(); }); }); }); diff --git a/src/cmds/docs/index.js b/src/cmds/docs/index.js index eb305e142..7109777c5 100644 --- a/src/cmds/docs/index.js +++ b/src/cmds/docs/index.js @@ -68,8 +68,7 @@ exports.run = async function (opts) { // Strip out non-markdown files const files = allFiles.filter(file => file.endsWith('.md') || file.endsWith('.markdown')); - - if (files.length === 0) { + if (!files.length) { return Promise.reject(new Error(`We were unable to locate Markdown files in ${folder}.`)); } @@ -93,7 +92,7 @@ exports.run = async function (opts) { function updateDoc(slug, file, hash, existingDoc) { if (hash === existingDoc.lastUpdatedHash) { - return `\`${slug}\` not updated. No changes.`; + return `\`${slug}\` was not updated because there were no changes.`; } return request @@ -110,7 +109,7 @@ exports.run = async function (opts) { const updatedDocs = await Promise.allSettled( files.map(async filename => { - const file = await readFile(path.join(folder, filename), 'utf8'); + const file = await readFile(filename, 'utf8'); const matter = frontMatter(file); // Stripping the subdirectories and markdown extension from the filename and lowercasing to get the default slug. From 7343db7d2555a10f7171b2b14bfb347d120fd8d7 Mon Sep 17 00:00:00 2001 From: Jon Ursenbach Date: Tue, 27 Jul 2021 15:35:44 -0700 Subject: [PATCH 3/4] test: cleaning up the docs test a bit --- __tests__/.eslintrc | 1 + __tests__/cmds/docs.test.js | 116 +++++++++++------------------------- 2 files changed, 36 insertions(+), 81 deletions(-) diff --git a/__tests__/.eslintrc b/__tests__/.eslintrc index 87f009971..727df504a 100644 --- a/__tests__/.eslintrc +++ b/__tests__/.eslintrc @@ -9,6 +9,7 @@ { "assertFunctionNames": [ "expect", + "getNockWithVersionHeader.**.reply", "nock.**.reply" ] } diff --git a/__tests__/cmds/docs.test.js b/__tests__/cmds/docs.test.js index 0db28555e..9c1168096 100644 --- a/__tests__/cmds/docs.test.js +++ b/__tests__/cmds/docs.test.js @@ -9,11 +9,12 @@ const docs = require('../../src/cmds/docs'); const docsEdit = require('../../src/cmds/docs/edit'); const fixturesDir = `${__dirname}./../__fixtures__`; -const key = 'Xmw4bGctRVIQz7R7dQXqH9nQe5d0SPQs'; +const key = 'API_KEY'; const version = '1.0.0'; -const category = '5ae9ece93a685f47efb9a97c'; +const category = 'CATEGORY_ID'; +const apiSetting = 'API_SETTING_ID'; -function getNockForVersion(v) { +function getNockWithVersionHeader(v) { return nock(config.host, { reqheaders: { 'x-readme-version': v, @@ -74,7 +75,7 @@ describe('rdme docs', () => { it('should fetch doc and merge with what is returned', () => { expect.assertions(1); - const getMocks = getNockForVersion(version) + const getMocks = getNockWithVersionHeader(version) .get(`/api/v1/docs/simple-doc`) .basicAuth({ user: key }) .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' }) @@ -82,7 +83,7 @@ describe('rdme docs', () => { .basicAuth({ user: key }) .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: 'anOldHash' }); - const updateMocks = getNockForVersion(version) + const updateMocks = getNockWithVersionHeader(version) .put('/api/v1/docs/simple-doc', { category, slug: simpleDoc.slug, @@ -115,11 +116,7 @@ describe('rdme docs', () => { it('should not send requests for docs that have not changed', () => { expect.assertions(1); - const getMocks = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) + const getMocks = getNockWithVersionHeader(version) .get('/api/v1/docs/simple-doc') .basicAuth({ user: key }) .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash }) @@ -142,16 +139,9 @@ describe('rdme docs', () => { it('should create new doc', () => { const slug = 'new-doc'; const doc = frontMatter(fs.readFileSync(path.join(fixturesDir, `/new-docs/${slug}.md`))); - const hash = crypto - .createHash('sha1') - .update(fs.readFileSync(path.join(fixturesDir, `/new-docs/${slug}.md`))) - .digest('hex'); - - const getMock = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) + const hash = hashFileContents(fs.readFileSync(path.join(fixturesDir, `/new-docs/${slug}.md`))); + + const getMock = getNockWithVersionHeader(version) .get(`/api/v1/docs/${slug}`) .basicAuth({ user: key }) .reply(404, { @@ -161,11 +151,7 @@ describe('rdme docs', () => { help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".', }); - const postMock = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) + const postMock = getNockWithVersionHeader(version) .post(`/api/v1/docs`, { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash }) .basicAuth({ user: key }) .reply(201); @@ -179,25 +165,17 @@ describe('rdme docs', () => { it('should create only valid docs', () => { console.log = jest.fn(); expect.assertions(2); + const slug = 'fail-doc'; const slugTwo = 'new-doc'; + const doc = frontMatter(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slug}.md`))); const docTwo = frontMatter(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slugTwo}.md`))); - const hash = crypto - .createHash('sha1') - .update(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slug}.md`))) - .digest('hex'); - - const hashTwo = crypto - .createHash('sha1') - .update(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slugTwo}.md`))) - .digest('hex'); - - const getMock = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) + + const hash = hashFileContents(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slug}.md`))); + const hashTwo = hashFileContents(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slugTwo}.md`))); + + const getMocks = getNockWithVersionHeader(version) .get(`/api/v1/docs/${slug}`) .basicAuth({ user: key }) .reply(404, { @@ -205,13 +183,7 @@ describe('rdme docs', () => { message: `The doc with the slug '${slug}' couldn't be found`, suggestion: '...a suggestion to resolve the issue...', help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".', - }); - - const getMockTwo = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) + }) .get(`/api/v1/docs/${slugTwo}`) .basicAuth({ user: key }) .reply(404, { @@ -221,24 +193,14 @@ describe('rdme docs', () => { help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".', }); - const postMock = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) - .post(`/api/v1/docs`, { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash }) + const postMocks = getNockWithVersionHeader(version) + .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash }) .basicAuth({ user: key }) .reply(400, { error: 'DOC_INVALID', message: "We couldn't save this doc (Path `category` is required.).", - }); - - const postMockTwo = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) - .post(`/api/v1/docs`, { slug: slugTwo, body: docTwo.content, ...docTwo.data, lastUpdatedHash: hashTwo }) + }) + .post('/api/v1/docs', { slug: slugTwo, body: docTwo.content, ...docTwo.data, lastUpdatedHash: hashTwo }) .basicAuth({ user: key }) .reply(201, { metadata: { image: [], title: '', description: '' }, @@ -247,14 +209,14 @@ describe('rdme docs', () => { url: '', auth: 'required', params: [], - apiSetting: '60ddf83e30681022753e27af', + apiSetting, }, title: 'This is the document title', updates: [], type: 'endpoint', slug: slugTwo, body: 'Body', - category: '5ae122e10fdf4e39bb34db6f', + category, }); return docs.run({ folder: './__tests__/__fixtures__/failure-docs', key, version }).then(message => { @@ -267,20 +229,20 @@ describe('rdme docs', () => { url: '', auth: 'required', params: [], - apiSetting: '60ddf83e30681022753e27af', + apiSetting, }, title: 'This is the document title', updates: [], type: 'endpoint', slug: slugTwo, body: 'Body', - category: '5ae122e10fdf4e39bb34db6f', + category, }, ]); - getMock.done(); - getMockTwo.done(); - postMock.done(); - postMockTwo.done(); + + getMocks.done(); + postMocks.done(); + console.log.mockRestore(); }); }); @@ -316,22 +278,14 @@ describe('rdme docs:edit', () => { const body = 'abcdef'; const edits = 'ghijkl'; - const getMock = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) + const getMock = getNockWithVersionHeader(version) .get(`/api/v1/docs/${slug}`) .basicAuth({ user: key }) - .reply(200, { category: '5ae9ece93a685f47efb9a97c', slug, body }); + .reply(200, { category, slug, body }); - const putMock = nock(config.host, { - reqheaders: { - 'x-readme-version': version, - }, - }) + const putMock = getNockWithVersionHeader(version) .put(`/api/v1/docs/${slug}`, { - category: '5ae9ece93a685f47efb9a97c', + category, slug, body: `${body}${edits}`, }) From c95aac8e3488e15b48919c8654edcf462b0c6592 Mon Sep 17 00:00:00 2001 From: Jon Ursenbach Date: Tue, 27 Jul 2021 18:00:07 -0700 Subject: [PATCH 4/4] fix: removing unnecessary filename rewriting logic --- src/cmds/docs/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cmds/docs/index.js b/src/cmds/docs/index.js index 7109777c5..3ffb23958 100644 --- a/src/cmds/docs/index.js +++ b/src/cmds/docs/index.js @@ -63,11 +63,8 @@ exports.run = async function (opts) { return [...files, ...subFiles]; }; - // Pull off the leading subdirectory, to keep things consistant with below - const allFiles = readdirRecursive(folder).map(file => file.replace(`${folder}${path.sep}`, '')); - // Strip out non-markdown files - const files = allFiles.filter(file => file.endsWith('.md') || file.endsWith('.markdown')); + const files = readdirRecursive(folder).filter(file => file.endsWith('.md') || file.endsWith('.markdown')); if (!files.length) { return Promise.reject(new Error(`We were unable to locate Markdown files in ${folder}.`)); }