├── index.js ├── .npmignore ├── .travis.yml ├── .prettierrc ├── getMetaRedirect.js ├── tests ├── __snapshots__ │ ├── gatsby-node.spec.js.snap │ └── getMetaRedirect.spec.js.snap ├── getMetaRedirect.spec.js └── gatsby-node.spec.js ├── .gitignore ├── package.json ├── LICENSE ├── gatsby-node.js └── README.md /index.js: -------------------------------------------------------------------------------- 1 | // noop 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | tests 4 | *.tgz 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 8 6 | - 9 7 | - 10 8 | - 11 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 80 6 | } 7 | -------------------------------------------------------------------------------- /getMetaRedirect.js: -------------------------------------------------------------------------------- 1 | module.exports = function getMetaRedirect(toPath) { 2 | let url = toPath.trim(); 3 | 4 | const hasProtocol = url.includes('://'); 5 | if (!hasProtocol) { 6 | const hasLeadingSlash = url.startsWith('/'); 7 | if (!hasLeadingSlash) { 8 | url = `/${url}`; 9 | } 10 | 11 | const resemblesFile = url.includes('.'); 12 | if (!resemblesFile) { 13 | url = `${url}/`.replace(/\/\/+/g, '/'); 14 | } 15 | } 16 | 17 | return ``; 18 | }; 19 | -------------------------------------------------------------------------------- /tests/__snapshots__/gatsby-node.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`onPostBuild handles external redirects 1`] = `""`; 4 | 5 | exports[`onPostBuild writes deep path redirects 1`] = `""`; 6 | 7 | exports[`onPostBuild writes redirects from root 1`] = `""`; 8 | 9 | exports[`onPostBuild writes redirects to root 1`] = `""`; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Dependency directories 21 | node_modules/ 22 | 23 | # Optional npm cache directory 24 | .npm 25 | 26 | # Optional eslint cache 27 | .eslintcache 28 | 29 | # Optional REPL history 30 | .node_repl_history 31 | 32 | # Output of 'npm pack' 33 | *.tgz 34 | 35 | # Yarn Integrity file 36 | .yarn-integrity 37 | 38 | 39 | yarn.lock 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-meta-redirect", 3 | "version": "1.1.1", 4 | "repository": "git@github.com:getchalk/gatsby-plugin-meta-redirect.git", 5 | "author": "Danny Wilson ", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "engines": { 9 | "node": ">=8" 10 | }, 11 | "keywords": [ 12 | "gatsby", 13 | "gatsby-plugin" 14 | ], 15 | "scripts": { 16 | "clean": "git clean -xdf ./*.js", 17 | "test": "jest" 18 | }, 19 | "dependencies": { 20 | "fs-extra": "^7.0.0" 21 | }, 22 | "devDependencies": { 23 | "jest": "^22.1.4" 24 | }, 25 | "jest": { 26 | "verbose": true, 27 | "testURL": "http://localhost/" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Get Chalk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { exists, writeFile, ensureDir } = require('fs-extra'); 3 | 4 | const getMetaRedirect = require('./getMetaRedirect'); 5 | 6 | async function writeRedirectsFile(redirects, folder, pathPrefix) { 7 | if (!redirects.length) return; 8 | 9 | for (const redirect of redirects) { 10 | const { fromPath, toPath } = redirect; 11 | 12 | const FILE_PATH = path.join( 13 | folder, 14 | fromPath.replace(pathPrefix, ''), 15 | 'index.html' 16 | ); 17 | 18 | const fileExists = await exists(FILE_PATH); 19 | if (!fileExists) { 20 | try { 21 | await ensureDir(path.dirname(FILE_PATH)); 22 | } catch (err) { 23 | // ignore if the directory already exists; 24 | } 25 | 26 | const data = getMetaRedirect(toPath); 27 | await writeFile(FILE_PATH, data); 28 | } 29 | } 30 | } 31 | 32 | exports.onPostBuild = ({ store }) => { 33 | const { redirects, program, config } = store.getState(); 34 | 35 | let pathPrefix = ''; 36 | if (program.prefixPaths) { 37 | pathPrefix = config.pathPrefix; 38 | } 39 | 40 | const folder = path.join(program.directory, 'public'); 41 | return writeRedirectsFile(redirects, folder, pathPrefix); 42 | }; 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Travis][build-badge]][build] 2 | [![npm package][npm-badge]][npm] 3 | 4 | # gatsby-plugin-meta-redirect 5 | 6 | Generates meta redirect html files for redirecting on any static file host. 7 | 8 | ## Install 9 | 10 | ```sh 11 | npm install --save gatsby-plugin-meta-redirect 12 | ``` 13 | 14 | or 15 | 16 | ```sh 17 | yarn add gatsby-plugin-meta-redirect 18 | ``` 19 | 20 | ## How to use 21 | 22 | ```js 23 | // In your gatsby-config.js 24 | plugins: [ 25 | `gatsby-plugin-meta-redirect` // make sure to put last in the array 26 | ]; 27 | ``` 28 | 29 | ### Redirects 30 | 31 | You can create redirects using the [`createRedirect`](https://www.gatsbyjs.org/docs/bound-action-creators/#createRedirect) action. 32 | 33 | An example: 34 | 35 | ```js 36 | createRedirect({ fromPath: '/old-url', toPath: '/new-url', isPermanent: true }); 37 | createRedirect({ fromPath: '/url', toPath: '/zn-CH/url', Language: 'zn' }); 38 | ``` 39 | 40 | That will generate the following html files: 41 | 42 | ### `/old-url/index.html`: 43 | 44 | ```html 45 | 46 | ``` 47 | 48 | and 49 | 50 | ### `/url/index.html`: 51 | 52 | ```html 53 | 54 | ``` 55 | 56 | [build-badge]: https://img.shields.io/travis/getchalk/gatsby-plugin-meta-redirect/master.png?style=flat-square 57 | [build]: https://travis-ci.org/getchalk/gatsby-plugin-meta-redirect 58 | [npm-badge]: https://img.shields.io/npm/v/gatsby-plugin-meta-redirect.png?style=flat-square 59 | [npm]: https://www.npmjs.org/package/gatsby-plugin-meta-redirect 60 | -------------------------------------------------------------------------------- /tests/getMetaRedirect.spec.js: -------------------------------------------------------------------------------- 1 | const getMetaRedirect = require('../getMetaRedirect'); 2 | 3 | describe('getMetaRedirect', () => { 4 | it('wraps path in forward slashes', () => { 5 | expect(getMetaRedirect('toPath')).toMatchSnapshot(); 6 | }); 7 | 8 | it('allows existing leading and trailing forward slashes', () => { 9 | expect(getMetaRedirect('/toPath/')).toMatchSnapshot(); 10 | }); 11 | 12 | it('trims leading and trailing whitespace', () => { 13 | expect(getMetaRedirect(' toPath ')).toMatchSnapshot(); 14 | }); 15 | 16 | it('handles deep paths', () => { 17 | expect(getMetaRedirect('a/b/c/d')).toMatchSnapshot(); 18 | }); 19 | 20 | it('handles offset wrapping forward slashes', () => { 21 | expect(getMetaRedirect('a/b/c/')).toMatchSnapshot(); 22 | }); 23 | 24 | it('replaces duplicate slashes with single slash', () => { 25 | expect(getMetaRedirect('topath//a')).toMatchSnapshot(); 26 | }); 27 | 28 | it('leaves full urls untouched', () => { 29 | expect(getMetaRedirect('http://example.com')).toMatchSnapshot(); 30 | expect(getMetaRedirect('http://example.com/')).toMatchSnapshot(); 31 | expect(getMetaRedirect('http://example.com/a/b/c')).toMatchSnapshot(); 32 | }); 33 | 34 | it('handles redirecting to root', () => { 35 | expect(getMetaRedirect('/')).toMatchSnapshot(); 36 | }); 37 | 38 | it('handles redirecting to a file', () => { 39 | expect(getMetaRedirect('/test.txt')).toMatchSnapshot(); 40 | }); 41 | 42 | it('handles redirecting to a file in a folder', () => { 43 | expect(getMetaRedirect('a/b/test.txt')).toMatchSnapshot(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /tests/__snapshots__/getMetaRedirect.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`getMetaRedirect allows existing leading and trailing forward slashes 1`] = `""`; 4 | 5 | exports[`getMetaRedirect handles deep paths 1`] = `""`; 6 | 7 | exports[`getMetaRedirect handles offset wrapping forward slashes 1`] = `""`; 8 | 9 | exports[`getMetaRedirect handles redirecting to a file 1`] = `""`; 10 | 11 | exports[`getMetaRedirect handles redirecting to a file in a folder 1`] = `""`; 12 | 13 | exports[`getMetaRedirect handles redirecting to root 1`] = `""`; 14 | 15 | exports[`getMetaRedirect leaves full urls untouched 1`] = `""`; 16 | 17 | exports[`getMetaRedirect leaves full urls untouched 2`] = `""`; 18 | 19 | exports[`getMetaRedirect leaves full urls untouched 3`] = `""`; 20 | 21 | exports[`getMetaRedirect replaces duplicate slashes with single slash 1`] = `""`; 22 | 23 | exports[`getMetaRedirect trims leading and trailing whitespace 1`] = `""`; 24 | 25 | exports[`getMetaRedirect wraps path in forward slashes 1`] = `""`; 26 | -------------------------------------------------------------------------------- /tests/gatsby-node.spec.js: -------------------------------------------------------------------------------- 1 | const { exists, remove, readFile } = require('fs-extra'); 2 | const { onPostBuild } = require('../gatsby-node'); 3 | 4 | describe('onPostBuild', () => { 5 | const tempFolderPath = './public'; 6 | 7 | const assertRedirectFile = async (redirects, expectedPath) => { 8 | await onPostBuild({ 9 | store: { 10 | getState: () => ({ 11 | redirects, 12 | program: { 13 | directory: './' 14 | } 15 | }) 16 | } 17 | }); 18 | 19 | expect(await exists(expectedPath)).toBe(true); 20 | expect(await readFile(expectedPath, 'utf-8')).toMatchSnapshot(); 21 | }; 22 | 23 | beforeEach(async () => { 24 | await remove(tempFolderPath); 25 | }); 26 | 27 | // cleanup 28 | afterAll(async () => { 29 | await remove(tempFolderPath); 30 | }); 31 | 32 | it('writes redirects from root', async () => { 33 | await assertRedirectFile( 34 | [ 35 | { 36 | fromPath: '/', 37 | toPath: '/hello' 38 | } 39 | ], 40 | `${tempFolderPath}/index.html` 41 | ); 42 | }); 43 | 44 | it('writes redirects to root', async () => { 45 | await assertRedirectFile( 46 | [ 47 | { 48 | fromPath: '/hello', 49 | toPath: '/' 50 | } 51 | ], 52 | `${tempFolderPath}/hello/index.html` 53 | ); 54 | }); 55 | 56 | it('writes deep path redirects', async () => { 57 | await assertRedirectFile( 58 | [ 59 | { 60 | fromPath: '/a/b/c/d', 61 | toPath: '/x/y/z' 62 | } 63 | ], 64 | `${tempFolderPath}/a/b/c/d/index.html` 65 | ); 66 | }); 67 | 68 | it('handles external redirects', async () => { 69 | await assertRedirectFile( 70 | [ 71 | { 72 | fromPath: '/a/b', 73 | toPath: 'http://example.com/' 74 | } 75 | ], 76 | `${tempFolderPath}/a/b/index.html` 77 | ); 78 | }); 79 | }); 80 | --------------------------------------------------------------------------------