Skip to content

Commit c63acd1

Browse files
committed
more abstraction and organization, adding tests
1 parent eec10bd commit c63acd1

File tree

9 files changed

+225
-86
lines changed

9 files changed

+225
-86
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
node_modules
1+
node_modules
2+
.env

jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
setupFiles: ['dotenv/config'],
3+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"node-fetch": "2"
2020
},
2121
"devDependencies": {
22+
"dotenv": "^10.0.0",
2223
"jest": "^27.4.3"
2324
}
2425
}

src/index.js

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
const fs = require('fs').promises;
22
const glob = require('glob');
3-
const { JSDOM } = require('jsdom');
43

5-
const { getCloudinary, getCloudinaryUrl } = require('./lib/cloudinary');
4+
const { getCloudinary, updateHtmlImagesToCloudinary } = require('./lib/cloudinary');
65

76
/**
87
* TODO
@@ -39,45 +38,27 @@ module.exports = {
3938
// Find all HTML source files in the publish directory
4039

4140
const pages = glob.sync(`${PUBLISH_DIR}/**/*.html`);
42-
const errors = [];
43-
44-
for ( const page of pages ) {
45-
const html = await fs.readFile(page, 'utf-8');
46-
const dom = new JSDOM(html);
47-
48-
// Loop through all images found in the DOM and swap the source with
49-
// a Cloudinary URL
50-
51-
const images = Array.from(dom.window.document.querySelectorAll('img'));
52-
53-
for ( const $img of images ) {
54-
let imgSrc = $img.getAttribute('src');
55-
56-
try {
57-
const cloudinarySrc = await getCloudinaryUrl({
58-
apiKey,
59-
apiSecret,
60-
deliveryType,
61-
folder,
62-
path: imgSrc,
63-
publishDir: PUBLISH_DIR,
64-
uploadPreset,
65-
});
66-
67-
$img.setAttribute('src', cloudinarySrc)
68-
} catch(e) {
69-
const { error } = e;
70-
errors.push({
71-
imgSrc,
72-
message: e.message || error.message
73-
});
74-
continue;
75-
}
76-
41+
42+
const results = await Promise.all(pages.map(async page => {
43+
const sourceHtml = await fs.readFile(page, 'utf-8');
44+
45+
const { html, errors } = await updateHtmlImagesToCloudinary(sourceHtml, {
46+
deliveryType,
47+
uploadPreset,
48+
folder,
49+
localDir: PUBLISH_DIR,
50+
remoteHost: process.env.DEPLOY_PRIME_URL
51+
});
52+
53+
await fs.writeFile(page, html);
54+
55+
return {
56+
page,
57+
errors
7758
}
59+
}));
7860

79-
await fs.writeFile(page, dom.serialize());
80-
}
61+
const errors = results.filter(({ errors }) => errors.length > 0);
8162

8263
if ( errors.length > 0) {
8364
console.log(`Done with ${errors.length} errors...`);

src/lib/cloudinary.js

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const crypto = require('crypto');
22
const path = require('path');
33
const fetch = require('node-fetch');
4+
const { JSDOM } = require('jsdom');
45
const cloudinary = require('cloudinary').v2;
56

67
const { isRemoteUrl, determineRemoteUrl } = require('./util');
@@ -15,28 +16,62 @@ function getCloudinary() {
1516

1617
module.exports.getCloudinary = getCloudinary;
1718

19+
/**
20+
* createPublicId
21+
*/
22+
23+
async function createPublicId({ path: filePath } = {}) {
24+
let hash = crypto.createHash('md5');
25+
26+
const { name: imgName } = path.parse(filePath);
27+
28+
if ( !isRemoteUrl(filePath) ) {
29+
hash.update(filePath);
30+
} else {
31+
const response = await fetch(filePath);
32+
const buffer = await response.buffer();
33+
hash.update(buffer);
34+
}
35+
36+
hash = hash.digest('hex');
37+
38+
return `${imgName}-${hash}`
39+
}
40+
41+
module.exports.createPublicId = createPublicId;
42+
1843
/**
1944
* getCloudinaryUrl
2045
*/
2146

2247
async function getCloudinaryUrl(options = {}) {
2348
const {
24-
apiKey,
25-
apiSecret,
2649
deliveryType,
2750
folder,
2851
path: filePath,
29-
publishDir,
52+
localDir,
53+
remoteHost,
3054
uploadPreset,
3155
} = options;
3256

57+
const { cloud_name: cloudName, api_key: apiKey, api_secret: apiSecret } = cloudinary.config();
58+
const canSignUpload = apiKey && apiSecret;
59+
60+
if ( !cloudName ) {
61+
throw new Error('Cloudinary Cloud Name required.');
62+
}
63+
64+
if ( deliveryType === 'upload' && !canSignUpload && !uploadPreset ) {
65+
throw new Error(`To use deliveryType ${deliveryType}, please use an uploadPreset for unsigned requests or an API Key and Secret for signed requests.`);
66+
}
67+
3368
let fileLocation;
3469

3570
if ( deliveryType === 'fetch' ) {
3671
// fetch allows us to pass in a remote URL to the Cloudinary API
3772
// which it will cache and serve from the CDN, but not store
3873

39-
fileLocation = determineRemoteUrl(filePath);
74+
fileLocation = determineRemoteUrl(filePath, remoteHost);
4075
} else if ( deliveryType === 'upload' ) {
4176
// upload will actually store the image in the Cloudinary account
4277
// and subsequently serve that stored image
@@ -48,7 +83,7 @@ async function getCloudinaryUrl(options = {}) {
4883
let fullPath = filePath;
4984

5085
if ( !isRemoteUrl(fullPath) ) {
51-
fullPath = path.join(publishDir, fullPath);
86+
fullPath = path.join(localDir, fullPath);
5287
}
5388

5489
const id = await createPublicId({
@@ -57,28 +92,29 @@ async function getCloudinaryUrl(options = {}) {
5792

5893
const uploadOptions = {
5994
folder,
60-
public_id: id
95+
public_id: id,
96+
overwrite: false
97+
}
98+
99+
if ( uploadPreset ) {
100+
uploadOptions.upload_preset = uploadPreset;
61101
}
62102

63103
let results;
64104

65-
if ( apiKey && apiSecret ) {
105+
if ( canSignUpload ) {
66106
// We need an API Key and Secret to use signed uploading
67107

68108
results = await cloudinary.uploader.upload(fullPath, {
69-
...uploadOptions,
70-
overwrite: false
109+
...uploadOptions
71110
});
72-
} else if ( uploadPreset ) {
111+
} else {
73112
// If we want to avoid signing our uploads, we don't need our API Key and Secret,
74113
// however, we need to provide an uploadPreset
75114

76115
results = await cloudinary.uploader.unsigned_upload(fullPath, uploadPreset, {
77116
...uploadOptions
78-
// Unsigned uploads default to overwrite: false
79117
});
80-
} else {
81-
throw new Error(`To use deliveryType ${deliveryType}, please use an uploadPreset for unsigned requests or an API Key and Secret for signed requests.`);
82118
}
83119

84120
// Finally use the stored public ID to grab the image URL
@@ -104,25 +140,55 @@ async function getCloudinaryUrl(options = {}) {
104140
module.exports.getCloudinaryUrl = getCloudinaryUrl;
105141

106142
/**
107-
* createPublicId
143+
* updateHtmlImagesToCloudinary
108144
*/
109145

110-
async function createPublicId({ path: filePath } = {}) {
111-
let hash = crypto.createHash('md5');
146+
async function updateHtmlImagesToCloudinary(html, options = {}) {
147+
const {
148+
deliveryType,
149+
uploadPreset,
150+
folder,
151+
localDir,
152+
remoteHost
153+
} = options;
112154

113-
const { name: imgName } = path.parse(filePath);
155+
const errors = [];
156+
const dom = new JSDOM(html);
114157

115-
if ( !isRemoteUrl(filePath) ) {
116-
hash.update(filePath);
117-
} else {
118-
const response = await fetch(filePath);
119-
const buffer = await response.buffer();
120-
hash.update(buffer);
121-
}
158+
// Loop through all images found in the DOM and swap the source with
159+
// a Cloudinary URL
122160

123-
hash = hash.digest('hex');
161+
const images = Array.from(dom.window.document.querySelectorAll('img'));
124162

125-
return `${imgName}-${hash}`
163+
for ( const $img of images ) {
164+
let imgSrc = $img.getAttribute('src');
165+
166+
try {
167+
const cloudinarySrc = await getCloudinaryUrl({
168+
deliveryType,
169+
folder,
170+
path: imgSrc,
171+
localDir,
172+
uploadPreset,
173+
remoteHost
174+
});
175+
176+
$img.setAttribute('src', cloudinarySrc)
177+
} catch(e) {
178+
const { error } = e;
179+
errors.push({
180+
imgSrc,
181+
message: e.message || error.message
182+
});
183+
continue;
184+
}
185+
186+
}
187+
188+
return {
189+
html: dom.serialize(),
190+
errors
191+
}
126192
}
127193

128-
module.exports.createPublicId = createPublicId;
194+
module.exports.updateHtmlImagesToCloudinary = updateHtmlImagesToCloudinary;

src/lib/util.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ module.exports.isRemoteUrl = isRemoteUrl;
1212
* determineRemoteUrl
1313
*/
1414

15-
function determineRemoteUrl(url) {
15+
function determineRemoteUrl(url, host) {
1616
if ( isRemoteUrl(url) ) return url;
1717

1818
if ( !url.startsWith('/') ) {
1919
url = `/${url}`;
2020
}
2121

22-
url = `${process.env.DEPLOY_PRIME_URL}${url}`;
22+
url = `${host}${url}`;
2323

2424
return url;
2525
}

0 commit comments

Comments
 (0)