diff --git a/lib/assets.js b/lib/assets.js index ed93074..d628f6f 100644 --- a/lib/assets.js +++ b/lib/assets.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); const util = require('util'); const readdir = util.promisify(fs.readdir); +const stat = util.promisify(fs.stat); const { generateFileHash } = require('./generate-hash'); @@ -12,16 +13,17 @@ const getAssets = (staticDir, relativeDir, ignoreExtensions) => { const walk = async (staticDir, relativeDir, ignoreExtensions) => { const dir = path.join(process.cwd(), staticDir, relativeDir); let assets = {}; - const files = await readdir(dir, { withFileTypes: true }); + const files = await readdir(dir); const filteredFilters = files.filter( - f => !ignoreExtensions.includes(path.extname(f.name)), + f => !ignoreExtensions.includes(path.extname(f)), ); - for await (const file of filteredFilters) { - const filePath = path.join(dir, file.name); - if (file.isDirectory()) { + for (const file of filteredFilters) { + const filePath = path.join(dir, file); + const fileState = await stat(filePath); + if (fileState.isDirectory()) { const subAssets = await walk( staticDir, - path.join(relativeDir, file.name), + path.join(relativeDir, file), ignoreExtensions ); assets = { @@ -32,7 +34,7 @@ const walk = async (staticDir, relativeDir, ignoreExtensions) => { } const sha = await generateFileHash(filePath); - const relativePath = path.join(relativeDir, file.name); + const relativePath = path.join(relativeDir, file); assets[relativePath] = sha; } return assets; diff --git a/lib/basset.js b/lib/basset.js index aa89981..639b901 100644 --- a/lib/basset.js +++ b/lib/basset.js @@ -7,24 +7,26 @@ const { generateFileHash, generateHash } = require('./generate-hash'); const { getAssets } = require('./assets'); class Basset { - constructor(token, staticDir, bassetUrl, {baseUrl = '', ignoreExtensions = ''}) { + constructor(token, staticDir, bassetUrl, {baseUrl = '', ignoreExtensions = '', type = 'web'}) { this.token = token; this.staticDir = staticDir; this.baseUrl = baseUrl; this.client = new Client(bassetUrl, token); this.ignoreExtensions = ignoreExtensions.split(',').map(e => e.trim()).filter(e => e !== ''); + this.type = type; } async buildStart(compareBranch=null) { const currentAssets = await this.getAssets(); const gitInfo = await getGitInfo(); - const { assets } = await this.client.buildStart({ ...gitInfo, compareBranch, assets: currentAssets, }); - await this.uploadAssets(assets); + if (this.type === 'web') { + await this.uploadAssets(assets); + } } async buildFinish() { @@ -34,7 +36,10 @@ class Basset { return this.client.buildFinish(); } - getAssets() { + async getAssets() { + if (this.type === 'image') { + return []; + } return getAssets(this.staticDir, this.baseUrl, this.ignoreExtensions); } @@ -42,7 +47,7 @@ class Basset { if (!this.client.buildId) { throw new Error('You cannot upload assets without starting a build'); } - for await (const [filePath, sha] of Object.entries(assets)) { + for (const [filePath, sha] of Object.entries(assets)) { const relativePath = path.join(this.baseUrl, filePath); const fileStream = fs.createReadStream( path.join(this.staticDir, filePath), @@ -53,6 +58,9 @@ class Basset { } async uploadSnapshotSource(snapshot, source) { + if (this.type !== 'web') { + throw new Error('Only projects that are type web can upload snapshots'); + } if (!this.client.buildId) { throw new Error('You cannot upload snapshots without starting a build'); } @@ -62,6 +70,9 @@ class Basset { } async uploadSnapshotFile(snapshot, filePath) { + if (this.type !== 'web') { + throw new Error('Only projects that are type web can upload snapshots'); + } if (!this.client.buildId) { throw new Error('You cannot upload snapshots without starting a build'); } @@ -70,6 +81,18 @@ class Basset { const file = fs.createReadStream(filePath); await this.client.uploadSnapshot(snapshot, relativePath, sha, file); } + + async uploadImageFile({ title }, filePath) { + if (!this.type !== 'image') { + throw new Error('Only projects that are type image can upload screenshots'); + } + if (!this.client.buildId) { + throw new Error('You cannot upload screenshots without starting a build'); + } + const sha = await generateFileHash(filePath); + const file = fs.createReadStream(filePath); + await this.client.uploadImage({ title, }, sha, file); + } } module.exports = Basset; diff --git a/lib/client.js b/lib/client.js index c80e321..bddd005 100644 --- a/lib/client.js +++ b/lib/client.js @@ -83,13 +83,35 @@ class Client { browsers: browsers || '', }, }); - const { uploaded } = JSON.parse(body); // body is json + const { uploaded } = JSON.parse(body); if (!uploaded) { throw new Error(`There was a problem uploading this snapshot: ${title}`); } console.log(`Uploaded snapshot: ${title} widths: (${widths})`); } + async uploadImage(snapshot, sha, file) { + const { title } = snapshot; + const { body, statusCode, statusMessage } = await this.request({ + url: `${this.bassetUrl}/build/upload/snapshot`, + method: 'POST', + headers: { + authorization: `Token ${this.token}`, + 'x-build-id': this.buildId, + 'x-sha': sha, + }, + formData: { + image: file, + }, + }); + const { uploaded } = JSON.parse(body); + if (!uploaded) { + throw new Error(`There was a problem uploading this screenshot: ${title}`); + } + console.log(`Uploaded screenshot: ${title}`); + } + + async uploadAsset(relativePath, sha, fileStream) { const { body, statusCode, statusMessage } = await this.request({ url: `${this.bassetUrl}/build/upload/asset`, @@ -104,7 +126,7 @@ class Client { asset: fileStream, }, }); - const { uploaded } = JSON.parse(body); // body is json + const { uploaded } = JSON.parse(body); if (!uploaded) { throw new Error( `There was a problem uploading this asset: ${relativePath}`, diff --git a/tests/assets.spec.js b/tests/assets.spec.js index 7619311..55d995e 100644 --- a/tests/assets.spec.js +++ b/tests/assets.spec.js @@ -1,19 +1,7 @@ -const assets = require('../lib/assets'); +jest.mock('fs'); +const fs = require('fs'); -let mockReaddir = [ - { name: '1.png', isDirectory: () => false }, - { name: '2.png', isDirectory: () => false }, - { name: '3.png', isDirectory: () => false }, - { name: '4.png', isDirectory: () => false }, -]; -jest.mock('fs', () => ({ - readdir: (d, opt, cb) => { - if (d.includes('dir')) { - return cb(null, [{ name: 'test.png', isDirectory: () => false }]); - } - return cb(null, mockReaddir); - }, -})); +const assets = require('../lib/assets'); jest.mock('../lib/generate-hash', () => ({ generateHash: jest.fn(() => 'sha'), @@ -21,6 +9,13 @@ jest.mock('../lib/generate-hash', () => ({ })); test('walk returns a list of assets', async () => { + fs.stat.mockImplementation((p, cb) => (cb(null, { isDirectory: () => false }))); + fs.readdir.mockImplementation((p, cb) => cb(null, [ + '1.png', + '2.png', + '3.png', + '4.png', + ])); const data = await assets.walk('/', 'baseUrl', []); expect(data).toEqual( expect.objectContaining({ @@ -33,7 +28,13 @@ test('walk returns a list of assets', async () => { }); test('walk recurisvely searches directories', async () => { - mockReaddir = [{ name: 'dir', isDirectory: () => true }]; + fs.readdir + .mockImplementationOnce((p, cb) => cb(null, ['dir'])) + .mockImplementationOnce((p, cb) => cb(null, ['test.png'])); + + fs.stat + .mockImplementationOnce((p, cb) => (cb(null, { isDirectory: () => true }))) + .mockImplementationOnce((p, cb) => cb(null, { isDirectory: () => false })); const data = await assets.walk('/', 'baseUrl', []); expect(data).toEqual( expect.objectContaining({ diff --git a/tests/client.spec.js b/tests/client.spec.js index 2fd8560..4a43e69 100644 --- a/tests/client.spec.js +++ b/tests/client.spec.js @@ -98,7 +98,7 @@ test('uploadSnapshot', async () => { body: '{"uploaded": true}', }), ); - const result = await client.uploadSnapshot( + await client.uploadSnapshot( { title: 'title' }, 'relativePath', 'sha', @@ -106,6 +106,35 @@ test('uploadSnapshot', async () => { ); }); + +test('uploadImage', async () => { + const client = new Client('bassetUrl', 'token'); + client.request = jest.fn(() => + Promise.resolve({ + body: '{"uploaded": false}', + }), + ); + try { + await client.uploadImage( + { title: 'title' }, + 'sha', + 'file', + ); + } catch (error) { + expect(error.message).toContain('problem uploading'); + } + client.request = jest.fn(() => + Promise.resolve({ + body: '{"uploaded": true}', + }), + ); + await client.uploadSnapshot( + { title: 'title' }, + 'sha', + 'file', + ); +}); + test('uploadAsset', async () => { const client = new Client('bassetUrl', 'token'); client.request = jest.fn(() =>