├── .circleci └── config.yml ├── .editorconfig ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── content │ └── posts │ │ └── 2016-07-28-FirstPost.md ├── netlify-cms │ └── config.yml ├── nuxt.config.js ├── package.json ├── pages │ ├── _post.vue │ ├── archives.vue │ └── index.vue └── yarn.lock ├── greenkeeper.json ├── index.js ├── lib ├── entry.js ├── hmr.client.js └── template │ └── index.html ├── package.json ├── src ├── configManager.js ├── module.js ├── utils │ ├── __mocks__ │ │ └── yaml.js │ ├── cms.config.file.js │ └── yaml.js └── webpack.config.js ├── test ├── __snapshots__ │ ├── config.test.js.snap │ ├── dev.test.js.snap │ ├── generate.test.js.snap │ └── module.test.js.snap ├── config.test.js ├── dev.test.js ├── fixture │ ├── netlify-cms.yml.template │ ├── nuxt.config.js │ └── pages │ │ └── index.vue ├── generate.test.js ├── module.test.js └── nuxt.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | defaults: &defaults 4 | working_directory: /usr/src/app 5 | docker: 6 | - image: node:10 7 | 8 | jobs: 9 | build-job: 10 | <<: *defaults 11 | steps: 12 | # Checkout repository 13 | - checkout 14 | 15 | # Restore cache 16 | - restore_cache: 17 | key: yarn-{{ checksum "yarn.lock" }} 18 | 19 | # Disable yarn progress bar for perf 20 | - run: 21 | command: yarn config set no-progress 22 | 23 | # Disable yarn engine compatibility errors 24 | - run: 25 | command: yarn config set ignore-engines true 26 | 27 | # Greenkeeper-lockfile 28 | - run: 29 | name: Installing Greenkeeper-lockfile 30 | command: yarn global add greenkeeper-lockfile@1 31 | 32 | # Install dependencies 33 | - run: 34 | name: Installing Dependencies 35 | command: yarn 36 | 37 | # Keep cache 38 | - save_cache: 39 | key: yarn-{{ checksum "yarn.lock" }} 40 | paths: 41 | - "node_modules" 42 | 43 | # Update yarn.lock 44 | - run: 45 | name: Updating lockfile 46 | command: greenkeeper-lockfile-update 47 | 48 | # Test 49 | - run: 50 | name: Testing 51 | command: yarn test 52 | 53 | # Build 54 | - run: 55 | name: Uploading test coverage 56 | command: yarn codecov 57 | 58 | # Upload yarn.lock 59 | - run: 60 | name: Uploading lockfile 61 | command: greenkeeper-lockfile-upload 62 | 63 | - persist_to_workspace: 64 | root: /usr/src/app 65 | paths: 66 | - . 67 | 68 | deploy-job: 69 | <<: *defaults 70 | steps: 71 | - attach_workspace: 72 | # Must be absolute path or relative path from working_directory 73 | at: /usr/src/app 74 | 75 | - run: 76 | name: Authenticating with registry 77 | command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc 78 | 79 | - run: 80 | name: Publishing package 81 | command: npm publish 82 | 83 | workflows: 84 | version: 2 85 | build: 86 | jobs: 87 | - build-job: 88 | filters: 89 | tags: 90 | only: /.*/ 91 | - deploy-job: 92 | requires: 93 | - build-job 94 | filters: 95 | tags: 96 | only: /^v.*/ 97 | branches: 98 | ignore: /.*/ 99 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.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 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # nuxt transient files 61 | .nuxt 62 | .dist 63 | 64 | # build dir 65 | dist/ 66 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | src/ 3 | .nyc_output/ 4 | coverage/ 5 | test/ 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | # [4.0.0](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v3.1.0...v4.0.0) (2019-02-21) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * **package:** update style-loader to version 0.23.0 ([#75](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/75)) ([004f9eb](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/004f9eb)) 11 | 12 | 13 | ### Features 14 | 15 | * **module:** compatibility with `nuxt` v2 ([#111](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/111)) ([41c450e](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/41c450e)), closes [nuxt-community/nuxtent-module#190](https://github.com/nuxt-community/nuxtent-module/issues/190) 16 | 17 | 18 | ### BREAKING CHANGES 19 | 20 | * **module:** This module is no more compatible with nuxt versions older than v2.0.0 21 | Update `nuxt` devDependency to v.2.0.0 22 | 23 | Compatibility with webpack v4 24 | Add `FriendlyErrorsWebpackPlugin` 25 | Add `@nuxt/friendly-errors-webpack-plugin`, `extract-css-chunks-webpack-plugin` 26 | Use `consola` instead of debug for logging, improve messages 27 | Add `webpackbar` in production, improve logging 28 | Upgrade husky, move config to its own key 29 | Remove `.yarnrc` engine compatibility fix 30 | 31 | 32 | 33 | 34 | # [3.1.0](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v3.0.2...v3.1.0) (2018-08-07) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * **module:** fix compatibility with `nuxt generate` ([c518a10](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/c518a10)), closes [#59](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/59) 40 | 41 | Thanks @jmcmullen & @easherma! 42 | 43 | ### Features 44 | 45 | * **module:** compatibility with `netlify-cms` v2 ([6800ea4](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/6800ea4)), closes [#64](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/64) 46 | 47 | 48 | 49 | 50 | ## [3.0.2](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v3.0.1...v3.0.2) (2018-08-04) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * use `cms-config-url` html link to determine proper netlify cms `config.yml` path ([1b07d51](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/1b07d51)) 56 | 57 | 58 | 59 | 60 | ## [3.0.1](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v3.0.0...v3.0.1) (2018-04-29) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * **package:** fix `regeneratorRuntime` error ([f96d5fe](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/f96d5fe)), closes [#51](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/51) 66 | 67 | * **docs:** fix update readme.md links to match Netlify's docs 68 | 69 | Thanks gangsthub ! 70 | 71 | 72 | 73 | 74 | # [3.0.0](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v2.0.1...v3.0.0) (2018-04-22) 75 | 76 | 77 | ### Features 78 | 79 | * **module:** compatibility with `nuxt` v1 ([416737e](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/416737e)) 80 | 81 | 82 | ### BREAKING CHANGES 83 | 84 | * **module:** This module is no more compatible with nuxt versions older than v1.0.0 85 | Update `nuxt` devDependency to v.1.4.0 86 | 87 | 88 | 89 | 90 | ## [2.0.1](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v2.0.0...v2.0.1) (2017-09-22) 91 | 92 | 93 | ### Bug Fixes 94 | 95 | * **module:** properly move the CMS build to the `dist` folder on `nuxt generate` ([dd4b970](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/dd4b970)), closes [#23](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/23) 96 | 97 | 98 | 99 | 100 | # [2.0.0](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v1.2.1...v2.0.0) (2017-09-21) 101 | 102 | 103 | ### Bug Fixes 104 | 105 | * **package:** eslint compliance on commonjs index ([55a8f07](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/55a8f07)) 106 | 107 | 108 | ### Chores 109 | 110 | * **package:** re-export module in commonjs format ([1bba36c](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/1bba36c)), closes [#19](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/19) 111 | 112 | 113 | ### Features 114 | 115 | * **config:** enforce a single `netlify-cms` folder ([3fcbc2c](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/3fcbc2c)), closes [#21](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/21) 116 | 117 | 118 | ### BREAKING CHANGES 119 | 120 | * **config:** the cms config file should be placed in the "netlify-cms" folder and named "config.yml" along with the extensions js files 121 | * **config:** remove `extensionsDir` option 122 | * **package:** remove having to use `.default` while requiring this module 123 | 124 | 125 | 126 | 127 | ## [1.2.1](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v1.2.0...v1.2.1) (2017-09-18) 128 | 129 | 130 | ### Bug Fixes 131 | 132 | * **cms-config:** properly prepend `file` paths with nuxt `srcDir` ([c189b0f](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/c189b0f)), closes [#18](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/18) 133 | 134 | 135 | 136 | 137 | # [1.2.0](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v1.1.0...v1.2.0) (2017-09-09) 138 | 139 | 140 | ### Bug Fixes 141 | 142 | * **build:** fix various rebuild errors ([229a257](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/229a257)), closes [#7](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/7) [#9](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/9) [#10](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/10) 143 | * **package:** remove duplicate `lodash.omit` dependency ([d5e1017](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/d5e1017)) 144 | 145 | 146 | ### Features 147 | 148 | * **build:** add a notification of bundle rebuilding and completion ([6c51d10](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/6c51d10)), closes [#11](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/11) 149 | * **build:** add webpack hot reloading ([05748d7](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/05748d7)), closes [#15](https://github.com/medfreeman/nuxt-netlify-cms-module/issues/15) 150 | * **build:** show pretty build status ([c4107f2](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/c4107f2)) 151 | 152 | 153 | 154 | 155 | # [1.1.0](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v1.0.0...v1.1.0) (2017-09-08) 156 | 157 | 158 | ### Bug Fixes 159 | 160 | * **core:** avoid calling file existsSync before reading ([312c14b](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/312c14b)) 161 | 162 | 163 | ### Features 164 | 165 | * **core:** add support for netlify-cms extensions ([f670cf3](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/f670cf3)) 166 | 167 | 168 | 169 | 170 | # [1.0.0](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v0.1.4...v1.0.0) (2017-09-04) 171 | 172 | 173 | ### Features 174 | 175 | * **package:** allow end-user to specify `netlify-cms` version ([78bda84](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/78bda84)) 176 | 177 | 178 | ### BREAKING CHANGES 179 | 180 | * **package:** Move `netlify-cms` from dependencies to devDependencies and peerDependencies 181 | Update readme 182 | 183 | 184 | 185 | 186 | ## [0.1.4](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v0.1.3...v0.1.4) (2017-09-04) 187 | 188 | 189 | ### Bug Fixes 190 | 191 | * **README:** add missing module require instructions ([3192aae](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/3192aae)) 192 | 193 | 194 | 195 | 196 | ## [0.1.3](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v0.1.2...v0.1.3) (2017-09-04) 197 | 198 | 199 | ### Bug Fixes 200 | 201 | * **core:** transpile utils to allow module to work ([141c677](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/141c677)) 202 | 203 | 204 | 205 | 206 | ## [0.1.2](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/v0.1.1...v0.1.2) (2017-09-04) 207 | 208 | 209 | 210 | 211 | ## [0.1.1](https://github.com/medfreeman/nuxt-netlify-cms-module/compare/0.1.0...0.1.1) (2017-09-04) 212 | 213 | 214 | ### Bug Fixes 215 | 216 | * **core:** transpile webpack config to allow module to work ([afdd102](https://github.com/medfreeman/nuxt-netlify-cms-module/commit/afdd102)) 217 | 218 | 219 | 220 | # 0.1.0 - 2017-08-02 221 | 222 | - Initial release 223 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mehdi Lahlou 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuxt-netlify-cms-module 2 | 3 | [![npm version](https://img.shields.io/npm/v/nuxt-netlify-cms.svg)](https://www.npmjs.com/package/nuxt-netlify-cms) 4 | [![npm](https://img.shields.io/npm/dt/nuxt-netlify-cms.svg?style=flat-square)](https://npmjs.com/package/nuxt-netlify-cms) 5 | [![circleci](https://badgen.net/circleci/github/medfreeman/nuxt-netlify-cms-module/master)](https://circleci.com/gh/medfreeman/nuxt-netlify-cms-module) 6 | [![Codecov](https://img.shields.io/codecov/c/github/medfreeman/nuxt-netlify-cms-module.svg?style=flat-square)](https://codecov.io/gh/medfreeman/nuxt-netlify-cms-module) 7 | [![Greenkeeper badge](https://badges.greenkeeper.io/medfreeman/nuxt-netlify-cms-module.svg)](https://greenkeeper.io/) 8 | [![Dependencies](https://img.shields.io/david/medfreeman/nuxt-netlify-cms-module.svg)](https://david-dm.org/medfreeman/nuxt-netlify-cms-module) 9 | [![devDependencies](https://img.shields.io/david/dev/medfreeman/nuxt-netlify-cms-module.svg)](https://david-dm.org/medfreeman/nuxt-netlify-cms-module?type=dev) 10 | 11 | [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 12 | 13 | > Easy [Netlify CMS](https://www.netlifycms.org/) integration with nuxt.js 14 | 15 | [📖 **Release Notes**](./CHANGELOG.md) 16 | 17 | ## Features 18 | 19 | - Automatically build Netlify CMS through a seamless integration with nuxt.js webpack instance 20 | - [Automatically serve Netlify CMS to a chosen path](#adminpath), in development and production builds 21 | - [Support Netlify CMS config.yml](#netlify-cms-configyml), with automatic rebuild on change 22 | - [Meant to be used with nuxtent-module](https://github.com/nuxt-community/nuxtent-module), that allows nuxt to work with static content files 23 | 24 | ## Setup 25 | - Add `nuxt-netlify-cms` and `netlify-cms` devDependencies using yarn or npm to your project 26 | 27 | `npm i -D nuxt-netlify-cms netlify-cms` OR `yarn add -D nuxt-netlify-cms netlify-cms` 28 | 29 | - Add `nuxt-netlify-cms` to `modules` section of `nuxt.config.js` 30 | 31 | ```js 32 | { 33 | modules: [ 34 | // Simple usage 35 | "nuxt-netlify-cms", 36 | 37 | // With options 38 | ["nuxt-netlify-cms", { adminPath: "secure" }], 39 | ], 40 | 41 | // You can optionally use global options instead of inline form 42 | netlifyCms: { 43 | adminPath: "secure" 44 | } 45 | } 46 | ``` 47 | 48 | ## Usage 49 | 50 | ### Netlify CMS module config folder 51 | 52 | This module will look for the Netlify CMS config file and extensions in the following folder: `[nuxt.js srcDir]/netlify-cms`. 53 | 54 | :information_source: The nuxt.js [srcDir](https://nuxtjs.org/api/configuration-srcdir/) is set to the project root folder by default. If you don't change this value in nuxt config, you'll just have to create the "netlify-cms" directory at your project root folder. 55 | 56 | :information_source: If you don't use any of the following two features, there's no need to create this folder. But since `netlify-cms` needs a configuration specific to your repository, you'll have to specify it through [options](#cmsconfig). 57 | 58 | #### Netlify CMS `config.yml` 59 | 60 | You can specify a [custom configuration](https://www.netlifycms.org/docs/add-to-your-site/#configuration), that will be parsed and merged with the module's [netlify CMS options](#cmsconfig). 61 | 62 | You have to place the file in your Netlify CMS module config folder and name it `config.yml`. 63 | 64 | :information_source: Note that each path in the file (`media_folder`, collections `folder` fields and collections `file` fields) will be rewritten to prepend nuxt.js [srcDir](https://nuxtjs.org/api/configuration-srcdir/), so please specify each path relative to this folder. 65 | 66 | This file can be changed while `nuxt dev` is running, and Netlify CMS will be updated automatically. 67 | 68 | #### Netlify CMS customizations 69 | 70 | This module will look for Netlify CMS customizations in \*.js files contained in Netlify CMS module config folder and subfolders, and include them in the CMS build. 71 | 72 | These are of two kinds, [Custom Previews](https://www.netlifycms.org/docs/customization/) and [Custom Widgets](https://www.netlifycms.org/docs/custom-widgets/). 73 | 74 | :information_source: The global variable `CMS` is available to these javascript files to reference the CMS object. 75 | 76 | :information_source: The contents of this directory and subdirectories can be changed while `nuxt dev` is running, and Netlify CMS will be updated automatically. 77 | 78 | ## Options 79 | You can pass options using module options or `netlifyCms` section in `nuxt.config.js`. 80 | 81 | ### `adminPath` 82 | - Default: `"admin"` 83 | 84 | adminPath defines the path where Netlify CMS will be served. 85 | 86 | With nuxt default configuration, it will be served to `http://localhost:3000/admin/` in development. 87 | 88 | ### `adminTitle` 89 | - Default: `"Content Manager"` 90 | 91 | adminTitle defines the html title of the page where Netlify CMS will be served. 92 | 93 | ### `cmsConfig` 94 | - Default: `{ 95 | media_folder: "static/uploads" 96 | }` 97 | 98 | cmsConfig wholly reflects [Netlify CMS config.yml](#netlify-cms-configyml), in js object format. 99 | 100 | :information_source: The order of precedence for the cms configuration is `defaults` < `netlify-cms.yml` < `module options` 101 | 102 | :information_source: The paths are also rewritten according to nuxt.js [srcDir](https://nuxtjs.org/api/configuration-srcdir/) 103 | 104 | ## CONTRIBUTING 105 | 106 | * ⇄ Pull requests and ★ Stars are always welcome. 107 | * For bugs and feature requests, please create an issue. 108 | * Pull requests must be accompanied by passing automated tests (`$ yarn test`). 109 | 110 | ## License 111 | 112 | [MIT License](./LICENSE) 113 | 114 | Copyright (c) Mehdi Lahlou 115 | -------------------------------------------------------------------------------- /example/content/posts/2016-07-28-FirstPost.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My First Post 3 | --- 4 | 5 | This is my first post! -------------------------------------------------------------------------------- /example/netlify-cms/config.yml: -------------------------------------------------------------------------------- 1 | backend: 2 | name: github 3 | repo: username/repo # Change to your repo 4 | # url: http://localhost:3000 5 | 6 | media_folder: "static/uploads" 7 | 8 | collections: # A list of collections the CMS should be able to edit 9 | - name: "posts" # Used in routes, ie.: /admin/collections/:slug/edit 10 | label: "Post" # Used in the UI, ie.: "New Post" 11 | folder: "content/posts" 12 | slug: "{{year}}-{{slug}}" 13 | create: true # Allow users to create new documents in this collection 14 | fields: # The fields each document in this collection have 15 | - {label: "Title", name: "title", widget: "string", tagname: "h1"} 16 | - {label: "Publish Date", name: "date", widget: "datetime", format: "YYYY-MM-DD hh:mma"} 17 | - {label: "Cover Image", name: "image", widget: "image", required: false, tagname: ""} 18 | - {label: "Body", name: "body", widget: "markdown"} 19 | meta: 20 | - {label: "SEO Description", name: "description", widget: "text"} 21 | -------------------------------------------------------------------------------- /example/nuxt.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | modules: ["@dinamomx/nuxtent", "nuxt-netlify-cms"], 3 | nuxtent: { 4 | content: { 5 | page: "/_post", 6 | permalink: ":year/:slug", 7 | generate: [ 8 | // assets to generate static build 9 | "get", 10 | "getAll" 11 | ] 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-netlify-cms-module-example", 3 | "version": "1.0.0", 4 | "description": "Easy Netlify CMS integration with nuxt.js example", 5 | "dependencies": { 6 | "@nuxtjs/axios": "^5.3.1", 7 | "nuxt": "latest", 8 | "@dinamomx/nuxtent": "latest", 9 | "nuxt-netlify-cms": "file:../" 10 | }, 11 | "scripts": { 12 | "dev": "nuxt", 13 | "build": "nuxt build", 14 | "start": "nuxt start", 15 | "generate": "nuxt generate" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/pages/_post.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /example/pages/archives.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /example/pages/index.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "default": { 4 | "packages": [ 5 | "example/package.json", 6 | "package.json" 7 | ] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const netlifyCmsModule = require("./dist/module.js"); 2 | 3 | module.exports = netlifyCmsModule.default; 4 | module.exports.meta = netlifyCmsModule.meta; 5 | -------------------------------------------------------------------------------- /lib/entry.js: -------------------------------------------------------------------------------- 1 | /* global REQUIRE_EXTENSIONS, REQUIRE_CSS, CSS_FILE */ 2 | /* eslint-disable import/order */ 3 | 4 | function requireAll(r) { 5 | r.keys().forEach(r); 6 | } 7 | 8 | const CMS = require("netlify-cms"); 9 | 10 | if (REQUIRE_EXTENSIONS) { 11 | requireAll(require.context("extensions/", true, /\.js$/)); 12 | } 13 | 14 | if (REQUIRE_CSS) { 15 | require(CSS_FILE); 16 | } 17 | 18 | module.exports = { 19 | CMS 20 | }; 21 | -------------------------------------------------------------------------------- /lib/hmr.client.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | const webpackHotMiddlewareClient = require(`webpack-hot-middleware/client?name=client&reload=true&timeout=3000&dynamicPublicPath=true&path=__webpack_hmr`); 4 | 5 | webpackHotMiddlewareClient.subscribe(function(payload) { 6 | if (payload.action === "reload" || payload.reload === true) { 7 | window.location.reload(); 8 | } 9 | }); 10 | 11 | module.exports = webpackHotMiddlewareClient; 12 | })(); 13 | -------------------------------------------------------------------------------- /lib/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= htmlWebpackPlugin.options.title %> 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-netlify-cms", 3 | "description": "Easy Netlify CMS integration with nuxt.js", 4 | "version": "4.0.0", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "author": "Mehdi Lahlou ", 8 | "repository": "https://github.com/medfreeman/nuxt-netlify-cms-module.git", 9 | "scripts": { 10 | "lint": "eslint --ignore-path .gitignore --fix \"**/*.js\"", 11 | "test": "cross-env NODE_ENV=test DEBUG=nuxt:netlify-cms jest --runInBand --forceExit", 12 | "pretest": "yarn lint", 13 | "build": "del ./dist && babel --ignore src/**/__tests__/,src/**/__mocks__/ -d ./dist ./src", 14 | "watch": "del ./dist && babel -w --ignore src/**/__tests__/,src/**/__mocks__/ -d ./dist ./src", 15 | "release": "standard-version; git push --follow-tags", 16 | "prepare": "yarn build" 17 | }, 18 | "files": [ 19 | "lib", 20 | "dist" 21 | ], 22 | "dependencies": { 23 | "@nuxt/friendly-errors-webpack-plugin": "^2.4.0", 24 | "extract-css-chunks-webpack-plugin": "^3.3.2", 25 | "js-yaml": "^3.10.0", 26 | "style-loader": "^0.23.0" 27 | }, 28 | "peerDependencies": { 29 | "netlify-cms": ">=0.4.0" 30 | }, 31 | "devDependencies": { 32 | "@babel/cli": "^7.2.3", 33 | "@babel/plugin-proposal-object-rest-spread": "^7.3.2", 34 | "@babel/plugin-transform-runtime": "^7.2.0", 35 | "@babel/preset-env": "^7.3.1", 36 | "@nuxt/common": "^2.3.4", 37 | "babel-eslint": "^10.0.1", 38 | "codecov": "^3.0.0", 39 | "cross-env": "^5.0.5", 40 | "del-cli": "^1.1.0", 41 | "eslint": "^5.0.1", 42 | "eslint-config-i-am-meticulous": "^11.0.0", 43 | "eslint-config-prettier": "^4.0.0", 44 | "eslint-plugin-babel": "^5.0.0", 45 | "eslint-plugin-import": "^2.13.0", 46 | "eslint-plugin-jest": "^22.0.0", 47 | "eslint-plugin-prettier": "^3.0.1", 48 | "husky": "^1.1.3", 49 | "jest": "^24.1.0", 50 | "koa": "^2.3.0", 51 | "koa-static": "^5.0.0", 52 | "lint-staged": "^8.0.4", 53 | "netlify-cms": "2.4.2", 54 | "nuxt": "^2.0.0", 55 | "prettier": "^1.7.0", 56 | "request-promise-native": "^1.0.4", 57 | "standard-version": "^5.0.0" 58 | }, 59 | "husky": { 60 | "hooks": { 61 | "precommit": "lint-staged" 62 | } 63 | }, 64 | "lint-staged": { 65 | "*.js": [ 66 | "eslint --ignore-path .gitignore --fix", 67 | "git add" 68 | ] 69 | }, 70 | "babel": { 71 | "presets": [ 72 | "@babel/preset-env" 73 | ], 74 | "plugins": [ 75 | "@babel/plugin-transform-runtime", 76 | [ 77 | "@babel/plugin-proposal-object-rest-spread", 78 | { 79 | "useBuiltIns": true 80 | } 81 | ] 82 | ], 83 | "env": { 84 | "test": { 85 | "sourceMaps": "both" 86 | } 87 | } 88 | }, 89 | "jest": { 90 | "testEnvironment": "node", 91 | "coverageDirectory": "./coverage/", 92 | "collectCoverage": true 93 | }, 94 | "eslintConfig": { 95 | "root": true, 96 | "parser": "babel-eslint", 97 | "parserOptions": { 98 | "ecmaVersion": 8, 99 | "sourceType": "module" 100 | }, 101 | "env": { 102 | "node": true, 103 | "es6": true, 104 | "jest": true 105 | }, 106 | "plugins": [ 107 | "babel", 108 | "jest", 109 | "prettier" 110 | ], 111 | "extends": [ 112 | "eslint-config-i-am-meticulous", 113 | "plugin:jest/recommended", 114 | "eslint-config-prettier" 115 | ], 116 | "rules": { 117 | "import/max-dependencies": [ 118 | 2, 119 | { 120 | "max": 20 121 | } 122 | ] 123 | }, 124 | "globals": { 125 | "jest/globals": true, 126 | "jasmine": true 127 | } 128 | }, 129 | "prettier": { 130 | "printWidth": 80, 131 | "parser": "babel" 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/configManager.js: -------------------------------------------------------------------------------- 1 | import { join, relative } from "path"; 2 | 3 | /* eslint-disable import/no-extraneous-dependencies */ 4 | /* covered by nuxt */ 5 | import _ from "lodash"; 6 | 7 | import CmsConfigFile from "./utils/cms.config.file"; 8 | 9 | const NUXT_CONFIG_KEY = "netlifyCms"; 10 | const NUXT_DIST_DIR = "dist"; 11 | const CMS_CONFIG_KEY = "cmsConfig"; 12 | const CMS_CONFIG_DIR = "netlify-cms"; 13 | const CMS_CONFIG_FILENAME = "config.yml"; 14 | 15 | // Defaults 16 | const DEFAULTS = { 17 | adminPath: "admin", 18 | adminTitle: "Content Manager", 19 | cmsConfig: { 20 | media_folder: "static/uploads" 21 | } 22 | }; 23 | 24 | class ConfigManager { 25 | constructor(nuxtOptions, moduleOptions) { 26 | this._relativeSrcDir = relative(nuxtOptions.rootDir, nuxtOptions.srcDir); 27 | 28 | this._userOptions = { 29 | ...nuxtOptions[NUXT_CONFIG_KEY], 30 | ...moduleOptions 31 | }; 32 | 33 | const options = _.omit( 34 | { 35 | ...DEFAULTS, 36 | ...this._userOptions 37 | }, 38 | CMS_CONFIG_KEY 39 | ); 40 | options.adminPath = options.adminPath.replace(/\/?$/, "/"); 41 | options.buildDir = join( 42 | nuxtOptions.buildDir, 43 | NUXT_DIST_DIR, 44 | options.adminPath 45 | ); 46 | options.moduleConfigDir = join(nuxtOptions.srcDir, CMS_CONFIG_DIR); 47 | this._config = Object.freeze(options); 48 | 49 | this._cmsConfigFile = new CmsConfigFile( 50 | join(this._config.moduleConfigDir, CMS_CONFIG_FILENAME) 51 | ); 52 | } 53 | 54 | get config() { 55 | return this._config; 56 | } 57 | 58 | get cmsConfig() { 59 | const config = this.constructor.setConfigPaths( 60 | { 61 | ...DEFAULTS[CMS_CONFIG_KEY], 62 | ...this._cmsConfigFile.config, 63 | ...this._userOptions[CMS_CONFIG_KEY] 64 | }, 65 | this._relativeSrcDir 66 | ); 67 | return config; 68 | } 69 | 70 | get cmsConfigFileName() { 71 | return this._cmsConfigFile.fileName; 72 | } 73 | 74 | readCmsConfigFile() { 75 | return this._cmsConfigFile.readFile(); 76 | } 77 | 78 | static setConfigPaths(configObject, enforcedPath) { 79 | const newConfig = { 80 | collections: [] 81 | }; 82 | 83 | newConfig.media_folder = join(enforcedPath, configObject.media_folder); 84 | 85 | if (configObject.collections) { 86 | configObject.collections.forEach(function(collection) { 87 | if (collection.folder) { 88 | newConfig.collections.push({ 89 | ...collection, 90 | folder: join(enforcedPath, collection.folder) 91 | }); 92 | } else if (collection.files) { 93 | const collectionFiles = []; 94 | collection.files.forEach(function(fileEntry) { 95 | fileEntry.file && 96 | collectionFiles.push({ 97 | ...fileEntry, 98 | file: join(enforcedPath, fileEntry.file) 99 | }); 100 | }); 101 | newConfig.collections.push({ 102 | ...collection, 103 | files: collectionFiles 104 | }); 105 | } 106 | }); 107 | } 108 | 109 | return { 110 | ...configObject, 111 | ...newConfig 112 | }; 113 | } 114 | } 115 | 116 | export default ConfigManager; 117 | -------------------------------------------------------------------------------- /src/module.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { resolve, join } from "path"; 3 | 4 | /* covered by nuxt */ 5 | import { copy } from "fs-extra"; 6 | import _ from "lodash"; 7 | import { r, urlJoin } from "@nuxt/common"; 8 | import consola from "consola"; 9 | import chokidar from "chokidar"; 10 | import env from "std-env"; 11 | import pify from "pify"; 12 | import webpack from "webpack"; 13 | import webpackDevMiddleware from "webpack-dev-middleware"; 14 | import webpackHotMiddleware from "webpack-hot-middleware"; 15 | import WebpackBar from "webpackbar"; 16 | import serveStatic from "serve-static"; 17 | 18 | import pkg from "../package.json"; 19 | 20 | import ConfigManager from "./configManager"; 21 | import getWebpackNetlifyConfig from "./webpack.config"; 22 | import { toYAML } from "./utils/yaml"; 23 | 24 | const logger = consola.withScope("nuxt:netlify-cms"); 25 | 26 | const WEBPACK_CLIENT_COMPILER_NAME = "client"; 27 | const WEBPACK_NETLIFY_COMPILER_NAME = "netlify-cms"; 28 | const NETLIFY_CONFIG_FILE_NAME = "config.yml"; 29 | 30 | export default function NetlifyCmsModule(moduleOptions) { 31 | const configManager = new ConfigManager(this.options, moduleOptions); 32 | const config = configManager.config; 33 | 34 | const emitNetlifyConfig = compilation => { 35 | const netlifyConfigYAML = toYAML(configManager.cmsConfig); 36 | compilation.assets[NETLIFY_CONFIG_FILE_NAME] = { 37 | source: () => netlifyConfigYAML, 38 | size: () => netlifyConfigYAML.length 39 | }; 40 | }; 41 | 42 | // This will be called once when builder started 43 | this.nuxt.hook("build:before", builder => { 44 | const bundleBuilder = builder.bundleBuilder; 45 | 46 | const webpackConfig = getWebpackNetlifyConfig( 47 | WEBPACK_NETLIFY_COMPILER_NAME, 48 | this.options, 49 | config 50 | ); 51 | 52 | webpackConfig.plugins.push({ 53 | apply(compiler) { 54 | compiler.hooks.emit.tapAsync("NetlifyCMSPlugin", (compilation, cb) => { 55 | compilation.hooks.additionalAssets.tapAsync( 56 | "NetlifyCMSPlugin", 57 | callback => { 58 | emitNetlifyConfig(compilation); 59 | callback(); 60 | } 61 | ); 62 | 63 | emitNetlifyConfig(compilation); 64 | cb(); 65 | }); 66 | } 67 | }); 68 | 69 | !this.options.dev && 70 | webpackConfig.plugins.push( 71 | new WebpackBar({ 72 | name: WEBPACK_NETLIFY_COMPILER_NAME, 73 | color: "red", 74 | reporters: ["basic", "fancy", "profile", "stats"], 75 | basic: !this.options.build.quiet && env.minimalCLI, 76 | fancy: !this.options.build.quiet && !env.minimalCLI, 77 | profile: !this.options.build.quiet && this.options.build.profile, 78 | stats: 79 | !this.options.build.quiet && 80 | !this.options.dev && 81 | this.options.build.stats, 82 | reporter: { 83 | change: (_, { shortPath }) => { 84 | this.nuxt.callHook("bundler:change", shortPath); 85 | }, 86 | done: context => { 87 | if (context.hasErrors) { 88 | this.nuxt.callHook("bundler:error"); 89 | } 90 | }, 91 | allDone: () => { 92 | this.nuxt.callHook("bundler:done"); 93 | } 94 | } 95 | }) 96 | ) 97 | 98 | const netlifyCompiler = webpack(webpackConfig); 99 | 100 | // This will be run just before webpack compiler starts 101 | this.nuxt.hook("build:compile", ({ name }) => { 102 | if (name !== WEBPACK_CLIENT_COMPILER_NAME) { 103 | return; 104 | } 105 | 106 | logger.success("Netlify-cms builder initialized"); 107 | 108 | // This will be run just after webpack compiler ends 109 | netlifyCompiler.hooks.done.tapAsync( 110 | "NetlifyCMSPlugin", 111 | async (stats, cb) => { 112 | // Don't reload failed builds 113 | if (stats.hasErrors()) { 114 | /* istanbul ignore next */ 115 | return; 116 | } 117 | 118 | // Show a message inside console when the build is ready 119 | this.options.dev && 120 | logger.info(`Netlify-cms served on: ${config.adminPath}`); 121 | 122 | cb(); 123 | } 124 | ); 125 | 126 | // in development 127 | if (this.options.dev) { 128 | // Use shared filesystem and cache 129 | netlifyCompiler.outputFileSystem = bundleBuilder.mfs; 130 | 131 | // Create webpack dev middleware 132 | const netlifyWebpackDevMiddleware = pify( 133 | webpackDevMiddleware(netlifyCompiler, { 134 | publicPath: "/", 135 | stats: false, 136 | logLevel: "silent", 137 | watchOptions: this.options.watchers.webpack 138 | }) 139 | ); 140 | netlifyWebpackDevMiddleware.close = pify( 141 | netlifyWebpackDevMiddleware.close 142 | ); 143 | 144 | // Create webpack hot middleware 145 | const netlifyWebpackHotMiddleware = pify( 146 | webpackHotMiddleware(netlifyCompiler, { 147 | log: false, 148 | heartbeat: 1000 149 | }) 150 | ); 151 | 152 | // Inject to renderer instance 153 | if (builder.nuxt.renderer) { 154 | builder.nuxt.renderer.netlifyWebpackDevMiddleware = netlifyWebpackDevMiddleware; 155 | builder.nuxt.renderer.netlifyWebpackHotMiddleware = netlifyWebpackHotMiddleware; 156 | } 157 | 158 | // Stop webpack middleware on nuxt.close() 159 | this.nuxt.hook("close", async () => { 160 | await this.nuxt.renderer.netlifyWebpackDevMiddleware.close(); 161 | }); 162 | } else { 163 | // Only run the compiler in production, 164 | // in dev build is started by dev-middleware hooked to client webpack compiler 165 | this.nuxt.hook("build:done", async () => { 166 | await new Promise((resolve, reject) => { 167 | netlifyCompiler.run((err, stats) => { 168 | /* istanbul ignore next */ 169 | if (err) { 170 | return reject(err); 171 | } else if (stats.hasErrors()) { 172 | if (this.options.test) { 173 | err = stats.toString(this.options.build.stats); 174 | } 175 | 176 | return reject(err); 177 | } 178 | 179 | resolve(); 180 | }); 181 | }); 182 | }); 183 | } 184 | }); 185 | }); 186 | 187 | // Serve netlify CMS 188 | if (this.options.dev) { 189 | // Insert webpackDevMiddleware to serve netlify CMS in development 190 | this.addServerMiddleware({ 191 | path: config.adminPath, 192 | handler: async (req, res) => { 193 | if (this.nuxt.renderer.netlifyWebpackDevMiddleware) { 194 | logger.info( 195 | `Netlify-cms requested url: ${urlJoin(config.adminPath, req.url)}` 196 | ); 197 | 198 | await this.nuxt.renderer.netlifyWebpackDevMiddleware(req, res); 199 | } 200 | if (this.nuxt.renderer.netlifyWebpackHotMiddleware) { 201 | await this.nuxt.renderer.netlifyWebpackHotMiddleware(req, res); 202 | } 203 | } 204 | }); 205 | 206 | // Start watching config file 207 | const patterns = [r(configManager.cmsConfigFileName)]; 208 | 209 | const options = { 210 | ...this.options.watchers.chokidar, 211 | ignoreInitial: true 212 | }; 213 | 214 | const refreshFiles = _.debounce(() => { 215 | configManager.readCmsConfigFile(); 216 | this.nuxt.renderer.netlifyWebpackDevMiddleware.invalidate(); 217 | this.nuxt.renderer.netlifyWebpackHotMiddleware.publish({ 218 | action: "reload" 219 | }); 220 | 221 | logger.info("Netlify-cms files refreshed"); 222 | }, 200); 223 | 224 | // Watch for src Files 225 | const fileWatcher = chokidar 226 | .watch(patterns, options) 227 | .on("add", refreshFiles) 228 | .on("change", refreshFiles) 229 | .on("unlink", refreshFiles); 230 | 231 | this.nuxt.renderer.netlifyFileWatcher = fileWatcher; 232 | 233 | // Stop watching on nuxt.close() 234 | this.nuxt.hook("close", () => { 235 | this.nuxt.renderer.netlifyFileWatcher.close(); 236 | }); 237 | } else { 238 | // Statically serve netlify CMS (i.e. .nuxt/dist/admin/) files in production 239 | this.addServerMiddleware({ 240 | path: config.adminPath, 241 | handler: serveStatic(config.buildDir, { 242 | maxAge: "1y" // 1 year in production 243 | }) 244 | }); 245 | } 246 | 247 | // Move cms folder from `.nuxt/dist/admin` folder to `dist/` after nuxt generate 248 | this.nuxt.hook("generate:distCopied", async nuxt => { 249 | await copy( 250 | resolve(nuxt.options.buildDir, "dist", config.adminPath).replace( 251 | /\/$/, 252 | "" 253 | ), 254 | join(nuxt.distPath, config.adminPath).replace(/\/$/, "") 255 | ); 256 | 257 | logger.success("Netlify-cms files copied"); 258 | }); 259 | } 260 | 261 | // REQUIRED if publishing as an NPM package 262 | export { pkg as meta }; 263 | -------------------------------------------------------------------------------- /src/utils/__mocks__/yaml.js: -------------------------------------------------------------------------------- 1 | const module = require.requireActual("../yaml.js"); 2 | 3 | const toYAML = module.toYAML; 4 | 5 | const loadYAMLFile = function() { 6 | const testConfigFile = "test/fixture/netlify-cms.yml.template"; 7 | return module.loadYAMLFile(testConfigFile); 8 | }; 9 | 10 | export { toYAML, loadYAMLFile }; 11 | -------------------------------------------------------------------------------- /src/utils/cms.config.file.js: -------------------------------------------------------------------------------- 1 | import { loadYAMLFile } from "./yaml"; 2 | 3 | class CmsConfig { 4 | constructor(fileName) { 5 | this._fileName = fileName; 6 | this.readFile(); 7 | } 8 | 9 | readFile() { 10 | this._config = loadYAMLFile(this._fileName) || {}; 11 | } 12 | 13 | get config() { 14 | return this._config; 15 | } 16 | 17 | get fileName() { 18 | return this._fileName; 19 | } 20 | } 21 | 22 | export default CmsConfig; 23 | -------------------------------------------------------------------------------- /src/utils/yaml.js: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | 3 | import { safeLoad, safeDump } from "js-yaml"; 4 | /* eslint-disable import/no-extraneous-dependencies */ 5 | /* covered by nuxt */ 6 | import Debug from "debug"; 7 | 8 | const debug = Debug("nuxt:netlify-cms"); 9 | 10 | const toYAML = function(object) { 11 | try { 12 | const yaml = safeDump(object); 13 | return yaml; 14 | } catch (e) { 15 | /* istanbul ignore next */ 16 | debug(e.message, e.name); 17 | return false; 18 | } 19 | }; 20 | 21 | const loadYAMLFile = function(configFile) { 22 | try { 23 | const config = readFileSync(configFile, "utf8"); 24 | const contents = safeLoad(config, { 25 | filename: configFile, 26 | onWarning: debug 27 | }); 28 | return contents; 29 | } catch (e) { 30 | /* istanbul ignore next */ 31 | if (e.code !== "ENOENT") { 32 | debug(e.message, e.name); 33 | } 34 | return false; 35 | } 36 | }; 37 | 38 | export { toYAML, loadYAMLFile }; 39 | -------------------------------------------------------------------------------- /src/webpack.config.js: -------------------------------------------------------------------------------- 1 | import { existsSync } from "fs"; 2 | import { resolve } from "path"; 3 | 4 | import ExtractCssChunksPlugin from "extract-css-chunks-webpack-plugin"; 5 | import FriendlyErrorsWebpackPlugin from "@nuxt/friendly-errors-webpack-plugin"; 6 | /* eslint-disable import/no-extraneous-dependencies */ 7 | /* covered by nuxt */ 8 | import { urlJoin } from "@nuxt/common"; 9 | import webpack from "webpack"; 10 | import HTMLPlugin from "html-webpack-plugin"; 11 | 12 | export default function webpackNetlifyCmsConfig( 13 | name, 14 | nuxtOptions, 15 | moduleConfig 16 | ) { 17 | const BUILD_MODE = nuxtOptions ? "development" : "production"; 18 | const ENTRY = resolve(__dirname, "../lib/entry"); 19 | const BUILD_DIR = moduleConfig.buildDir; 20 | const CHUNK_FILENAME = nuxtOptions.build.filenames.chunk({ 21 | isDev: nuxtOptions.dev, 22 | isModern: nuxtOptions.modern 23 | }); 24 | const PUBLIC_PATH = urlJoin(nuxtOptions.router.base, moduleConfig.adminPath); 25 | const EXTENSIONS_DIR = moduleConfig.moduleConfigDir; 26 | const PAGE_TITLE = moduleConfig.adminTitle; 27 | const PAGE_TEMPLATE = resolve(__dirname, "../lib/template", "index.html"); 28 | const REQUIRE_EXTENSIONS = existsSync(EXTENSIONS_DIR) ? true : false; 29 | const HMR_CLIENT = resolve(__dirname, "../lib/hmr.client"); 30 | const CSS_FILE = "netlify-cms/dist/cms.css"; 31 | const REQUIRE_CSS = existsSync(resolve(__dirname, "node_modules", CSS_FILE)); 32 | const CSS_FILENAME = "style.[contenthash].css"; 33 | 34 | const config = { 35 | name, 36 | mode: BUILD_MODE, 37 | entry: { 38 | app: ENTRY 39 | }, 40 | output: { 41 | path: BUILD_DIR, 42 | filename: "bundle.[hash].js", 43 | chunkFilename: CHUNK_FILENAME, 44 | publicPath: PUBLIC_PATH 45 | }, 46 | module: { 47 | rules: [ 48 | { test: /\.css$/, loader: "style-loader!css-loader" }, 49 | { 50 | test: /\.(eot|svg|ttf|woff|woff2)$/, 51 | loader: "url-loader", 52 | options: { 53 | limit: 1000, // 1 KO 54 | name: "public/fonts/[name].[hash:7].[ext]" 55 | } 56 | } 57 | ] 58 | }, 59 | resolve: { 60 | alias: { 61 | extensions: EXTENSIONS_DIR 62 | } 63 | }, 64 | plugins: [ 65 | // CSS extraction) 66 | ...(nuxtOptions.build.extractCSS 67 | ? [ 68 | new ExtractCssChunksPlugin( 69 | Object.assign( 70 | { 71 | filename: CSS_FILENAME, 72 | chunkFilename: CSS_FILENAME, 73 | // TODO: https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/issues/132 74 | reloadAll: true 75 | }, 76 | nuxtOptions.build.extractCSS 77 | ) 78 | ) 79 | ] 80 | : []), 81 | new HTMLPlugin({ 82 | title: PAGE_TITLE, 83 | filename: "index.html", 84 | template: PAGE_TEMPLATE, 85 | inject: true, 86 | chunksSortMode: "dependency" 87 | }), 88 | new webpack.DefinePlugin({ 89 | REQUIRE_EXTENSIONS, 90 | REQUIRE_CSS, 91 | CSS_FILE 92 | }) 93 | ] 94 | }; 95 | 96 | // -------------------------------------- 97 | // Development specific config 98 | // -------------------------------------- 99 | if (nuxtOptions.dev) { 100 | // Add HMR support 101 | config.entry.app = [HMR_CLIENT, config.entry.app]; 102 | 103 | config.plugins.push( 104 | new webpack.HotModuleReplacementPlugin(), 105 | new webpack.NoEmitOnErrorsPlugin(), 106 | // https://webpack.js.org/plugins/named-modules-plugin 107 | new webpack.NamedModulesPlugin() 108 | ); 109 | 110 | // Add friendly error plugin 111 | if (!nuxtOptions.build.quiet && nuxtOptions.build.friendlyErrors) { 112 | config.plugins.push( 113 | new FriendlyErrorsWebpackPlugin({ 114 | clearConsole: false, 115 | reporter: "consola", 116 | logLevel: "WARNING" 117 | }) 118 | ); 119 | } 120 | } else { 121 | // -------------------------------------- 122 | // Production specific config 123 | // -------------------------------------- 124 | // Minify and optimize the JavaScript 125 | config.plugins.push( 126 | // This is needed in webpack 2 for minify CSS 127 | new webpack.LoaderOptionsPlugin({ 128 | minimize: true 129 | }) 130 | ); 131 | } 132 | 133 | return config; 134 | } 135 | -------------------------------------------------------------------------------- /test/__snapshots__/config.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`config netlify-cms.yml 1`] = ` 4 | "media_folder: test/fixture/static/uploads 5 | backend: 6 | name: github 7 | repo: me/test 8 | branch: master 9 | collections: 10 | - name: pages 11 | label: Page 12 | folder: test/fixture/content/pages 13 | slug: '{{slug}}' 14 | create: true 15 | fields: 16 | - label: Title 17 | name: title 18 | widget: string 19 | tagname: h1 20 | - label: Body 21 | name: body 22 | widget: markdown 23 | meta: 24 | - label: SEO Description 25 | name: description 26 | widget: text 27 | " 28 | `; 29 | -------------------------------------------------------------------------------- /test/__snapshots__/dev.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`module dev mode admin 1`] = ` 4 | " 5 | 6 | 7 | 8 | 9 | 10 | Content Manager 11 | 12 | 13 | 14 | 15 | " 16 | `; 17 | -------------------------------------------------------------------------------- /test/__snapshots__/generate.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`module generate mode admin 1`] = ` 4 | " 5 | 6 | 7 | 8 | 9 | 10 | Content Manager 11 | 12 | 13 | 14 | 15 | " 16 | `; 17 | -------------------------------------------------------------------------------- /test/__snapshots__/module.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`module admin 1`] = ` 4 | " 5 | 6 | 7 | 8 | 9 | 10 | Content Manager 11 | 12 | 13 | 14 | 15 | " 16 | `; 17 | -------------------------------------------------------------------------------- /test/config.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/first */ 2 | jest.mock("../src/utils/yaml"); 3 | 4 | import { get, commonBefore, commonAfter } from "./nuxt"; 5 | 6 | describe("config", () => { 7 | beforeAll(async () => { 8 | await commonBefore(); 9 | }); 10 | 11 | afterAll(async () => { 12 | await commonAfter(); 13 | }); 14 | 15 | test("netlify-cms.yml", async () => { 16 | const config = await get("/admin/config.yml"); 17 | expect(config).toMatchSnapshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/dev.test.js: -------------------------------------------------------------------------------- 1 | import { get, commonBefore, commonAfter } from "./nuxt"; 2 | 3 | describe("module dev mode", () => { 4 | beforeAll(async () => { 5 | await commonBefore({ dev: true }); 6 | }); 7 | 8 | afterAll(async () => { 9 | await commonAfter(); 10 | }); 11 | 12 | test("render", async () => { 13 | const html = await get("/"); 14 | expect(html).toContain("Works!"); 15 | }); 16 | 17 | test("admin", async () => { 18 | const html = await get("/admin/"); 19 | expect(html).toMatchSnapshot(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/fixture/netlify-cms.yml.template: -------------------------------------------------------------------------------- 1 | backend: 2 | name: github 3 | repo: me/test 4 | branch: master 5 | 6 | media_folder: "static/uploads" 7 | 8 | collections: 9 | - name: "pages" 10 | label: "Page" 11 | folder: "content/pages" 12 | slug: "{{slug}}" 13 | create: true 14 | fields: # The fields each document in this collection have 15 | - {label: "Title", name: "title", widget: "string", tagname: "h1"} 16 | - {label: "Body", name: "body", widget: "markdown"} 17 | meta: 18 | - {label: "SEO Description", name: "description", widget: "text"} 19 | -------------------------------------------------------------------------------- /test/fixture/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import module from "../../src/module"; 2 | 3 | export default { 4 | srcDir: __dirname, 5 | buildDir: ".nuxt", 6 | generate: { 7 | dir: ".dist" 8 | }, 9 | dev: false, 10 | render: { 11 | resourceHints: false 12 | }, 13 | modules: [ 14 | { 15 | src: "@@/src/module.js", 16 | options: {}, 17 | handler: module 18 | } 19 | ] 20 | }; 21 | -------------------------------------------------------------------------------- /test/fixture/pages/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /test/generate.test.js: -------------------------------------------------------------------------------- 1 | import { get, generate, commonAfter } from "./nuxt"; 2 | 3 | describe("module generate mode", () => { 4 | beforeAll(async () => { 5 | await generate(); 6 | }); 7 | 8 | afterAll(async () => { 9 | await commonAfter(); 10 | }); 11 | 12 | test("admin", async () => { 13 | const html = await get("/admin/"); 14 | expect(html).toMatchSnapshot(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/module.test.js: -------------------------------------------------------------------------------- 1 | import { get, commonBefore, commonAfter } from "./nuxt"; 2 | 3 | describe("module", () => { 4 | beforeAll(async () => { 5 | await commonBefore(); 6 | }); 7 | 8 | afterAll(async () => { 9 | await commonAfter(); 10 | }); 11 | 12 | test("render", async () => { 13 | const html = await get("/"); 14 | expect(html).toContain("Works!"); 15 | }); 16 | 17 | test("admin", async () => { 18 | const html = await get("/admin/"); 19 | expect(html).toMatchSnapshot(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/nuxt.js: -------------------------------------------------------------------------------- 1 | import { Nuxt, Builder, Generator } from "nuxt"; 2 | import request from "request-promise-native"; 3 | import Koa from "koa"; 4 | import serveStatic from "koa-static"; 5 | 6 | import baseConfig from "./fixture/nuxt.config"; 7 | 8 | jest.setTimeout(60000); 9 | process.env.PORT = process.env.PORT || 5060; 10 | process.env.NODE_ENV = "production"; 11 | 12 | const url = path => `http://localhost:${process.env.PORT}${path}`; 13 | const get = path => request(url(path)); 14 | 15 | let nuxt; 16 | let generator; 17 | let server; 18 | 19 | const serve = (isStatic = false) => { 20 | const app = new Koa(); 21 | 22 | app.use( 23 | !isStatic 24 | ? ctx => { 25 | ctx.status = 200; 26 | ctx.respond = false; 27 | ctx.req.ctx = ctx; 28 | nuxt.render(ctx.req, ctx.res); 29 | } 30 | : serveStatic(generator.distPath) 31 | ); 32 | server = app.listen(process.env.PORT); 33 | }; 34 | 35 | const commonBefore = async (config = {}) => { 36 | const mergedConfig = { 37 | ...baseConfig, 38 | ...config 39 | }; 40 | 41 | // Build a fresh nuxt 42 | nuxt = new Nuxt(mergedConfig); 43 | const builder = new Builder(nuxt); 44 | await builder.build(); 45 | serve(); 46 | }; 47 | 48 | const generate = async (config = {}) => { 49 | const mergedConfig = { 50 | ...baseConfig, 51 | ...config 52 | }; 53 | 54 | // Build a fresh nuxt 55 | nuxt = new Nuxt(mergedConfig); 56 | const builder = new Builder(nuxt); 57 | generator = new Generator(nuxt, builder); 58 | await generator.generate(); 59 | serve(true); 60 | }; 61 | 62 | const commonAfter = async () => { 63 | // Close all opened resources 64 | server.close(); 65 | await nuxt.close(); 66 | }; 67 | 68 | export { get, commonBefore, commonAfter, generate }; 69 | --------------------------------------------------------------------------------