Skip to content

Commit e10cffa

Browse files
authored
feat: Configure Multiple Image Paths (#75)
# Description This sets up the ability to allow for more than one path to be configured for images. Instead of only being able to: ``` imagesPath = "/my-path" ``` You can now add: ``` imagesPath = [ "/my-path", "/my-other-path" ] ``` ## Issue Ticket Number <!-- Specifiy which issue this fixes by referencing the issue number (`#11`) or issue URL. --> <!-- Example: Fixes #1 --> Fixes #61 ## Type of change <!-- Please select all options that are applicable. --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update # Checklist <!-- These must all be followed and checked. --> - [ ] I have followed the contributing guidelines of this project as mentioned in [CONTRIBUTING.md](/CONTRIBUTING.md) - [ ] I have created an [issue](https://github.com/colbyfayock/netlify-plugin-cloudinary/issues) ticket for this PR - [ ] I have checked to ensure there aren't other open [Pull Requests](https://github.com/colbyfayock/netlify-plugin-cloudinary/pulls) for the same update/change? - [ ] I have performed a self-review of my own code - [ ] I have run tests locally to ensure they all pass - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes needed to the documentation
1 parent fcc6c15 commit e10cffa

File tree

12 files changed

+181
-57
lines changed

12 files changed

+181
-57
lines changed

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,16 @@ npm install netlify-plugin-cloudinary
8686

8787
### Plugin Inputs
8888

89-
| Name | Required | Example | Description |
90-
|-----------------|----------|-----------| ------------|
91-
| cloudName | No* | mycloud | Cloudinary Cloud Name |
92-
| cname | No | domain.com | The custom domain name (CNAME) to use for building URLs (Advanced Plan Users) |
93-
| deliveryType | No | fetch | The method by which Cloudinary stores and delivers images (Ex: fetch, upload) |
94-
| folder | No | myfolder | Folder all media will be stored in. Defaults to Netlify site name |
95-
| imagesPath | No | /assets | Local path application serves image assets from |
96-
| loadingStrategy | No | eager | The method in which in which images are loaded (Ex: lazy, eager) |
97-
| privateCdn | No | true | Enables Private CDN Distribution (Advanced Plan Users) |
98-
| uploadPreset | No | my-preset | Defined set of asset upload defaults in Cloudinary |
89+
| Name | Type |Required | Example | Description |
90+
|-----------------|---------|---------|-----------| ------------|
91+
| cloudName | string | No* | mycloud | Cloudinary Cloud Name |
92+
| cname | string | No | domain.com | The custom domain name (CNAME) to use for building URLs (Advanced Plan Users) |
93+
| deliveryType | string | No | fetch | The method by which Cloudinary stores and delivers images (Ex: fetch, upload) |
94+
| folder | string | No | myfolder | Folder all media will be stored in. Defaults to Netlify site name |
95+
| imagesPath | string/Array | No | /assets | Local path application serves image assets from |
96+
| loadingStrategy | string | No | eager | The method in which in which images are loaded (Ex: lazy, eager) |
97+
| privateCdn | boolean | No | true | Enables Private CDN Distribution (Advanced Plan Users) |
98+
| uploadPreset | string | No | my-preset | Defined set of asset upload defaults in Cloudinary |
9999

100100
*Cloud Name must be set as an environment variable if not as an input
101101

@@ -197,6 +197,7 @@ Inside your Netlify config:
197197
[plugins.inputs]
198198
cloudName = "[Your Cloudinary Cloud Name]"
199199
imagesPath = "/my-path"
200+
# or imagesPath = [ "/my-path", "/my-other-path" ]
200201
```
201202

202203
## 🕵️‍♀️ Common Questions & Issues
299 KB
Loading

docs/src/pages/configuration.mdx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ import OgImage from '../components/OgImage';
1919

2020
## Plugin Inputs
2121

22-
| Name | Required | Example | Description |
23-
|-----------------|----------|-----------| ------------|
24-
| cloudName | No* | mycloud | Cloudinary Cloud Name |
25-
| cname | No | domain.com | The custom domain name (CNAME) to use for building URLs (Advanced Plan Users) |
26-
| deliveryType | No | fetch | The method by which Cloudinary stores and delivers images (Ex: fetch, upload) |
27-
| folder | No | myfolder | Folder all media will be stored in. Defaults to Netlify site name |
28-
| imagesPath | No | /assets | Local path application serves image assets from |
29-
| loadingStrategy | No | eager | The method in which in which images are loaded (Ex: lazy, eager) |
30-
| privateCdn | No | true | Enables Private CDN Distribution (Advanced Plan Users) |
31-
| uploadPreset | No | my-preset | Defined set of asset upload defaults in Cloudinary |
22+
| Name | Type |Required | Example | Description |
23+
|-----------------|---------|---------|-----------| ------------|
24+
| cloudName | string | No* | mycloud | Cloudinary Cloud Name |
25+
| cname | string | No | domain.com | The custom domain name (CNAME) to use for building URLs (Advanced Plan Users) |
26+
| deliveryType | string | No | fetch | The method by which Cloudinary stores and delivers images (Ex: fetch, upload) |
27+
| folder | string | No | myfolder | Folder all media will be stored in. Defaults to Netlify site name |
28+
| imagesPath | string/Array | No | /assets | Local path application serves image assets from |
29+
| loadingStrategy | string | No | eager | The method in which in which images are loaded (Ex: lazy, eager) |
30+
| privateCdn | boolean | No | true | Enables Private CDN Distribution (Advanced Plan Users) |
31+
| uploadPreset | string | No | my-preset | Defined set of asset upload defaults in Cloudinary |
3232

3333
<Callout emoji={false}>
3434
Cloud Name must be set as an environment variable if not as an input

docs/src/pages/guides/images-path.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,10 @@ Inside your Netlify config, add the following input configurations under your ne
2929
[plugins.inputs]
3030
cloudName = "[Your Cloudinary Cloud Name]"
3131
imagesPath = "/my-path"
32+
```
33+
34+
If you'd like to specify multiple locations for your images, you can configure `imagesPath` as an array:
35+
36+
```toml
37+
imagesPath = [ "/my-path", "/my-other-path" ]
3238
```

docs/src/pages/installation.mdx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,16 @@ import OgImage from '../components/OgImage';
2424

2525
The Cloudinary Build Plugin for Netlify is easily available as an integration right inside of Netlify.
2626

27-
You can enable the plugin from the Integration page:
28-
29-
https://www.netlify.com/integrations/cloudinary/
27+
You can enable the plugin from the Integration page: https://www.netlify.com/integrations/cloudinary/
28+
29+
<p className="mt-6">
30+
<img
31+
width="864"
32+
height="311"
33+
src="/screenshots/netlify-cloudinary-install-website.png"
34+
alt="Netlify Logo"
35+
/>
36+
</p>
3037

3138
Or you can find it in the Netlify dashboard by navigating to your site, selecting Integrations, searching for Cloudinary, then enabling the integration.
3239

netlify-plugin-cloudinary/manifest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ inputs:
1515
description: "Folder all media will be stored in. Defaults to Netlify site name"
1616
- name: imagesPath
1717
required: false
18-
description: "Local path application serves image assets from"
18+
description: "Local path(s) application serves image assets from"
1919
default: "/images"
2020
- name: loadingStrategy
2121
required: false
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
export const ERROR_CLOUD_NAME_REQUIRED =
22
'A Cloudinary Cloud Name is required. Please set cloudName input or use the environment variable CLOUDINARY_CLOUD_NAME'
3-
export const ERROR_NETLIFY_HOST_UNKNOWN =
4-
'Cannot determine Netlify host, can not proceed with plugin.'
3+
export const ERROR_INVALID_IMAGES_PATH = 'Invalid asset path. Please make sure your imagesPath is defined.'
54
export const ERROR_NETLIFY_HOST_CLI_SUPPORT =
65
'Note: The Netlify CLI does not currently support the ability to determine the host locally, try deploying on Netlify.'
7-
export const ERROR_SITE_NAME_REQUIRED = 'Cannot determine the site name, can not proceed with plugin'
6+
export const ERROR_NETLIFY_HOST_UNKNOWN =
7+
'Cannot determine Netlify host, can not proceed with plugin.'
8+
export const ERROR_SITE_NAME_REQUIRED = 'Cannot determine the site name, can not proceed with plugin'

netlify-plugin-cloudinary/src/index.ts

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import {
88
getCloudinaryUrl,
99
Assets,
1010
} from './lib/cloudinary';
11+
import { findAssetsByPath } from './lib/util';
12+
1113
import { PUBLIC_ASSET_PATH } from './data/cloudinary';
1214
import {
1315
ERROR_CLOUD_NAME_REQUIRED,
14-
ERROR_NETLIFY_HOST_UNKNOWN,
16+
ERROR_INVALID_IMAGES_PATH,
1517
ERROR_NETLIFY_HOST_CLI_SUPPORT,
18+
ERROR_NETLIFY_HOST_UNKNOWN,
1619
ERROR_SITE_NAME_REQUIRED,
1720
} from './data/errors';
1821

@@ -67,7 +70,7 @@ type Inputs = {
6770
cname: string;
6871
deliveryType: string;
6972
folder: string;
70-
imagesPath: string;
73+
imagesPath: string | Array<string>;
7174
loadingStrategy: string;
7275
privateCdn: boolean;
7376
uploadPreset: string;
@@ -173,11 +176,19 @@ export async function onBuild({
173176
privateCdn,
174177
});
175178

179+
180+
176181
// Look for any available images in the provided imagesPath to collect
177182
// asset details and to grab a Cloudinary URL to use later
178183

179-
const imagesDirectory = glob.sync(`${PUBLISH_DIR}/${imagesPath}/**/*`);
180-
const imagesFiles = imagesDirectory.filter(file => !!path.extname(file));
184+
if ( typeof imagesPath === 'undefined' ) {
185+
throw new Error(ERROR_INVALID_IMAGES_PATH);
186+
}
187+
188+
const imagesFiles = findAssetsByPath({
189+
baseDir: PUBLISH_DIR,
190+
path: imagesPath
191+
})
181192

182193
if (imagesFiles.length === 0) {
183194
console.warn(`[Cloudinary] No image files found in ${imagesPath}`);
@@ -226,7 +237,6 @@ export async function onBuild({
226237
// @ts-expect-error what are the expected mediaTypes that will be stored in _cloudinaryAssets
227238
return _cloudinaryAssets[mediaType].map(async asset => {
228239
const { publishPath, cloudinaryUrl } = asset;
229-
230240
netlifyConfig.redirects.unshift({
231241
from: `${publishPath}*`,
232242
to: cloudinaryUrl,
@@ -246,31 +256,41 @@ export async function onBuild({
246256
await Promise.all(
247257
CLOUDINARY_ASSET_DIRECTORIES.map(
248258
async ({ inputKey, path: defaultPath }) => {
249-
const mediaPath = inputs[inputKey as keyof Inputs] || defaultPath;
250-
// @ts-ignore Unsure how to type the above so that Inputs['privateCdn'] doesnt mess up types here
251-
const cldAssetPath = `/${path.join(PUBLIC_ASSET_PATH, mediaPath)}`;
252-
const cldAssetUrl = `${host}${cldAssetPath}`;
253-
254-
const { cloudinaryUrl: assetRedirectUrl } = await getCloudinaryUrl({
255-
deliveryType: 'fetch',
256-
folder,
257-
path: `${cldAssetUrl}/:splat`,
258-
uploadPreset,
259-
});
260-
261-
netlifyConfig.redirects.unshift({
262-
from: `${cldAssetPath}/*`,
263-
to: `${mediaPath}/:splat`,
264-
status: 200,
265-
force: true,
266-
});
267-
268-
netlifyConfig.redirects.unshift({
269-
from: `${mediaPath}/*`,
270-
to: assetRedirectUrl,
271-
status: 302,
272-
force: true,
273-
});
259+
let mediaPaths = inputs[inputKey as keyof Inputs] || defaultPath;
260+
261+
// Unsure how to type the above so that Inputs['privateCdn'] doesnt mess up types here
262+
263+
if ( !Array.isArray(mediaPaths) && typeof mediaPaths !== 'string' ) return;
264+
265+
if ( !Array.isArray(mediaPaths) ) {
266+
mediaPaths = [mediaPaths];
267+
}
268+
269+
mediaPaths.forEach(async mediaPath => {
270+
const cldAssetPath = `/${path.join(PUBLIC_ASSET_PATH, mediaPath)}`;
271+
const cldAssetUrl = `${host}${cldAssetPath}`;
272+
273+
const { cloudinaryUrl: assetRedirectUrl } = await getCloudinaryUrl({
274+
deliveryType: 'fetch',
275+
folder,
276+
path: `${cldAssetUrl}/:splat`,
277+
uploadPreset,
278+
});
279+
280+
netlifyConfig.redirects.unshift({
281+
from: `${cldAssetPath}/*`,
282+
to: `${mediaPath}/:splat`,
283+
status: 200,
284+
force: true,
285+
});
286+
287+
netlifyConfig.redirects.unshift({
288+
from: `${mediaPath}/*`,
289+
to: assetRedirectUrl,
290+
status: 302,
291+
force: true,
292+
});
293+
})
274294
},
275295
),
276296
);

netlify-plugin-cloudinary/src/lib/util.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import path from 'node:path';
2+
import { glob } from 'glob';
3+
14
/**
25
* isRemoteUrl
36
*/
@@ -48,3 +51,22 @@ export function getQueryParams(url: string) {
4851
return params
4952
}
5053

54+
/**
55+
* findAssetsByPath
56+
*/
57+
58+
interface FindAssetsByPath {
59+
baseDir: string;
60+
path: string | Array<string>;
61+
}
62+
63+
export function findAssetsByPath(options: FindAssetsByPath) {
64+
if ( !Array.isArray(options.path) ) {
65+
options.path = [options.path];
66+
}
67+
68+
return options.path.flatMap(assetsPath => {
69+
const assetsDirectory = glob.sync(path.join(options.baseDir, assetsPath, '/**/*'));
70+
return assetsDirectory.filter(file => !!path.extname(file));
71+
})
72+
}
47.7 KB
Loading

0 commit comments

Comments
 (0)