diff --git a/.eleventy.js b/.eleventy.js
index d35683b..4dcae32 100644
--- a/.eleventy.js
+++ b/.eleventy.js
@@ -42,7 +42,7 @@ module.exports = function (eleventyConfig, defaultOptions) {
log(`⭐️ Starting ${blue('eleventy-plugin-cloudcannon')}`);
const context = ctx.getAll();
const config = readConfig(context, options);
- const info = getInfo(context, config, options);
+ const info = getInfo(context, config);
const json = stringifyJson(info);
log(`🏁 Generated ${bold('_cloudcannon/info.json')} ${green('successfully')}`);
return json;
diff --git a/.eslintrc.json b/.eslintrc.json
index 654a711..4ca260a 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -3,7 +3,15 @@
"eslint:recommended"
],
"rules": {
- "quotes": ["warn", "single"]
+ "indent": ["warn", "tab"],
+ "no-else-return": ["warn"],
+ "guard-for-in": ["warn"],
+ "eqeqeq": ["warn"],
+ "no-self-compare": ["warn"],
+ "no-duplicate-imports": ["warn"],
+ "no-lonely-if": ["warn"],
+ "quotes": ["warn", "single"],
+ "semi": ["warn", "always"]
},
"env": {
"es6": true,
diff --git a/README.md b/README.md
index 2d88ca9..70af590 100644
--- a/README.md
+++ b/README.md
@@ -8,25 +8,42 @@ An Eleventy (11ty) plugin that creates [CloudCannon](https://cloudcannon.com/) b
***
- [Installation](#installation)
+- [Configuration](#configuration)
- [Plugin options](#plugin-options)
-- [Site configuration](#site-configuration)
-- [Manual installation](#manual-installation)
- [License](#license)
***
## Installation
-CloudCannon automatically injects this plugin before your site is built. This means you'll get new
-features and fixes as they are released. You can [disable this](#manual-installation) if you need
-to maintain the plugin versioning manually.
+**You don't have to install anything** when building on CloudCannon. This plugin is automatically
+installed before your site is built. This gives you the latest support, new features, and fixes
+as they are released.
-If you use custom paths for your site, you'll need to pass them to the plugin, either one of:
+Although **not recommended**, you can disable the automatic installation and install the plugin
+manually.
-- Returning from `.eleventy.js`
-- Setting `eleventyConfig.cloudcannonOptions`
+
+Manual installation steps
+
+
+
+When installing manually, you'll have to upgrade when new versions are released.
+You could also follow these steps to debug an integration issue locally.
+
+Start by enabling the "Manage eleventy-plugin-cloudcannon plugin manually" option in CloudCannon
+for your site in *Site Settings / Build*.
+
+```bash
+npm install eleventy-plugin-cloudcannon --save
+```
+
+Add the following `addPlugin` call to your `.eleventy.js` file.
+The second parameter is optional, and used to pass [plugin options](#plugin-options).
```javascript
+const pluginCloudCannon = require('eleventy-plugin-cloudcannon');
+
module.exports = function (eleventyConfig) {
const config = {
pathPrefix: '/',
@@ -35,159 +52,176 @@ module.exports = function (eleventyConfig) {
data: '_my-custom-data',
layouts: '_layouts',
includes: '_my-includes'
- },
- markdownItOptions: {
- html: true
}
};
- // Either plugin looks here for plugin options
- eleventyConfig.cloudcannonOptions = config;
-
- // Or plugin looks at what you return for plugin options
+ eleventyConfig.addPlugin(pluginCloudCannon, config);
return config;
};
```
-If you set your custom input path with the `--input` CLI option, CloudCannon reads it automatically.
-
-## Plugin options
-
-The options are either set on the `eleventyConfig.cloudcannonOptions` key within your
-`.eleventy.js` file, or passed to the `addPlugin` call as a second argument if you are adding the
-plugin manually.
+
+
-Should match the return value from your `.eleventy.js` (https://www.11ty.dev/docs/config/) file.
+## Configuration
-
- pathPrefix
+This plugin uses a your `cloudcannon.config.*` configuration file as a base to generate
+`_cloudcannon/info.json`.
-> The `pathPrefix` setting your site uses. Defaults to: `'/'`
+Configuration files must be in the same directory you run `npx @11ty/eleventy`. The first file
+found is used, the files supported are:
-
+- `cloudcannon.config.json`
+- `cloudcannon.config.yaml`
+- `cloudcannon.config.yml`
+- `cloudcannon.config.js`
+- `cloudcannon.config.cjs`
-
- dir
+Example content for `cloudcannon.config.cjs`:
-> Custom paths your site uses. Defaults to:
->
-> ```javascript
-> {
-> input: '.',
-> data: '_data',
-> includes: '_includes',
-> layouts: '_includes'
-> }
-> ```
+```javascript
+module.exports = {
+ // Global CloudCannon configuration
+ _inputs: {
+ title: {
+ type: 'text',
+ comment: 'The title of your page.'
+ }
+ },
+ _select_data: {
+ colors: ['Red', 'Green', 'Blue']
+ },
-
+ // Base path to your site source files, same as input for Eleventy
+ source: 'src',
-
- markdownItOptions
+ // The subpath your built output files are mounted at
+ base_url: '/documentation',
-> Options passed to markdown-it. Defaults to `{ html: true }`.
+ // Populates collections for navigation and metadata in the editor
+ collections_config: {
+ people: {
+ // Base path for files in this collection, relative to source
+ path: 'content/people',
-
+ // Whether this collection produces output files or not
+ output: true
-## Site configuration
-
-This plugin reads data from `cloudcannon` if available (i.e. `_data/cloudcannon.json` or
-`_data/cloudcannon.js`).
-
-Details on each property here are listed in the relevant parts of the
-[CloudCannon documentation](https://cloudcannon.com/documentation/).
-
-The following is an empty template as an example.
-
-```json
-{
- "timezone": "",
- "collections": {
- "projects": {
- "path": "",
- "name": "",
- "title": "",
- "output": false,
- "_sort_key": "",
- "_subtext_key": "",
- "_image_key": "",
- "_image_size": "",
- "_singular_name": "",
- "_singular_key": "",
- "_disable_add": false,
- "_icon": "",
- "_enabled_editors": null,
- "_add_options": []
+ // Collection-level configuration
+ name: 'Personnel',
+ _enabled_editors: ['data']
+ },
+ posts: {
+ path: '_posts',
+ output: true
+ },
+ pages: {
+ name: 'Main pages'
}
},
- "_comments": {},
- "_options": {},
- "_editor": {},
- "_collection_groups": null,
- "_source_editor": {},
- "_array_structures": {},
- "_enabled_editors": null,
- "_select_data": {}
-}
+
+ // Generates the data for select and multiselect inputs matching these names
+ data_config: {
+ // Populates data with authors from an data file with the matching name
+ authors: true,
+ offices: true
+ },
+
+ paths: {
+ // The default location for newly uploaded files, relative to source
+ uploads: 'assets/uploads',
+
+ // The path to site data files, relative to source
+ data: 'data', // defaults to _data
+
+ // The path to site layout files, relative to source
+ layouts: '_layouts', // defaults to _includes
+
+ // The path to site include files, relative to source
+ includes: '_partials' // defaults to _includes
+ }
+};
```
-## Manual installation
+See the [CloudCannon documentation](https://cloudcannon.com/documentation/) for more information
+on the available features you can configure.
-Manually managing this plugin means you'll have to upgrade when new versions are released. You can
-also follow these steps to try it or debug locally.
+## Plugin options
-
-Manual installation steps
+Configuration is set in `cloudcannon.config.*`, but the plugin also automatically
+reads the following from Eleventy if unset:
-
+- `paths` from `dir` in `.eleventy.js` options
+- `base_url` from `pathPrefix` in `.eleventy.js` options
+- `source` from the `--input` CLI option or `dir.input` in `.eleventy.js` options
-Start by enabling the "Manage eleventy-plugin-cloudcannon plugin manually" option in CloudCannon
-for your site in *Site Settings / Build*.
+These options match Eleventy's [configuration format](https://www.11ty.dev/docs/config/) and are
+set in one of three ways:
-```bash
-npm install eleventy-plugin-cloudcannon --save
-```
+
+ Returning from .eleventy.js
with automatic installation
-Add the following `addPlugin` call to the `module.exports` function in your `.eleventy.js` file:
+**Requires automatic installation**.
-```javascript
-const pluginCloudCannon = require('eleventy-plugin-cloudcannon');
+> ```javascript
+> module.exports = function (eleventyConfig) {
+> return {
+> pathPrefix: '/',
+> dir: {
+> input: '.',
+> data: '_my-custom-data',
+> layouts: '_layouts',
+> includes: '_my-includes'
+> }
+> };
+> };
+> ```
-module.exports = function (eleventyConfig) {
- eleventyConfig.addPlugin(pluginCloudCannon);
-};
-```
+
-If you use custom paths for your site, pass them to the plugin as well:
+
+ Setting eleventyConfig.cloudcannonOptions
-```javascript
-const pluginCloudCannon = require('eleventy-plugin-cloudcannon');
+**Requires automatic installation or needs to be before the call to `addPlugin`**.
-module.exports = function (eleventyConfig) {
- const config = {
- pathPrefix: '/',
- dir: {
- input: '.',
- data: '_my-custom-data',
- layouts: '_layouts',
- includes: '_my-includes'
- }
- };
+> ```javascript
+> module.exports = function (eleventyConfig) {
+> eleventyConfig.cloudcannonOptions = {
+> pathPrefix: '/',
+> dir: {
+> input: '.',
+> data: '_my-custom-data',
+> layouts: '_layouts',
+> includes: '_my-includes'
+> }
+> };
+> };
+> ```
- eleventyConfig.addPlugin(pluginCloudCannon, config);
- return config;
-};
-```
+
-Disabling the automatic injection prevents the `info.json` template being copied. In order to
-retain this, add the following to your `.cloudcannon/prebuild` build hook:
+
+ Through addPlugin
with manual installation
-```bash
-rm -rf cloudcannon
-cp -R node_modules/eleventy-plugin-cloudcannon/cloudcannon .
-```
+> ```javascript
+> const pluginCloudCannon = require('eleventy-plugin-cloudcannon');
+>
+> module.exports = function (eleventyConfig) {
+> const config = {
+> pathPrefix: '/',
+> dir: {
+> input: '.',
+> data: '_my-custom-data',
+> layouts: '_layouts',
+> includes: '_my-includes'
+> }
+> };
+>
+> eleventyConfig.addPlugin(pluginCloudCannon, config);
+> return config;
+> };
+> ```
-
## License
diff --git a/integration-tests/1.0.0-beta.8/site/_data/cloudcannon.json b/integration-tests/1.0.0-beta.8/site/_data/cloudcannon.json
deleted file mode 100644
index ff107e6..0000000
--- a/integration-tests/1.0.0-beta.8/site/_data/cloudcannon.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "timezone": "Etc/UTC",
- "_inputs": {
- "layout": {
- "hidden": "true"
- }
- },
- "collections": {
- "authors": {
- "name": "The Authors",
- "path": "authors",
- "output": false
- }
- }
-}
\ No newline at end of file
diff --git a/integration-tests/1.0.0-beta.8/site/cloudcannon.config.js b/integration-tests/1.0.0-beta.8/site/cloudcannon.config.js
new file mode 100644
index 0000000..0767e56
--- /dev/null
+++ b/integration-tests/1.0.0-beta.8/site/cloudcannon.config.js
@@ -0,0 +1,15 @@
+module.exports = {
+ timezone: 'Etc/UTC',
+ _inputs: {
+ layout: {
+ hidden: 'true'
+ }
+ },
+ collections_config: {
+ authors: {
+ name: 'The Authors',
+ path: 'authors',
+ output: false
+ }
+ }
+};
diff --git a/src/config.js b/src/config.js
index 58d90b0..7e9713e 100644
--- a/src/config.js
+++ b/src/config.js
@@ -1,7 +1,7 @@
const { relative } = require('path');
const { cosmiconfigSync } = require('cosmiconfig');
-const { red, bold } = require('chalk');
-const { log } = require('./util/logger.js');
+const { red, bold, yellow } = require('chalk');
+const { log, logError } = require('./util/logger.js');
function readFileSync(configPath) {
const moduleName = 'cloudcannon';
@@ -27,15 +27,15 @@ function readFileSync(configPath) {
}
} catch (e) {
if (e.code === 'ENOENT') {
- log(`⚠️ ${red('No config file found at')} ${red.bold(configPath)}`);
+ log(red(`⚠️ No config file found at ${bold(configPath)}`));
return false;
- } else {
- log(`⚠️ ${red('Error reading config file')}`, 'error');
- throw e;
}
+
+ logError(red('⚠️ Error reading config file'));
+ throw e;
}
- log('⚙️ No config file found');
+ log(`⚙️ No config file found at ${bold('cloudcannon.config.*')}`);
return false;
}
@@ -47,6 +47,10 @@ function rewriteKey(object, oldKey, newKey) {
}
function getLegacyConfig(context) {
+ if (context.cloudcannon) {
+ log(yellow(`⚙️ Falling back to ${bold('cloudcannon')} site data for config`));
+ }
+
const legacy = context.cloudcannon || {};
rewriteKey(legacy, 'data', 'data_config');
@@ -74,8 +78,8 @@ function getLegacyConfig(context) {
}
function readConfig(context, options = {}) {
+ const file = readFileSync() || {}; // TODO custom config path here
const legacy = getLegacyConfig(context);
- const file = readFileSync() || {};
const baseUrl = file.base_url || options.pathPrefix || '';
@@ -95,6 +99,16 @@ function readConfig(context, options = {}) {
}
};
+ if (options.markdownItOptions) {
+ config.generator = {
+ ...config.generator,
+ metadata: {
+ markdown: 'markdown-it',
+ 'markdown-it': options.markdownItOptions
+ }
+ };
+ }
+
rewriteKey(config, '_source_editor', 'source_editor');
rewriteKey(config, '_editor', 'editor');
rewriteKey(config, '_collection_groups', 'collection_groups');
diff --git a/src/generators/generator.js b/src/generators/generator.js
index 4ed0087..ad70458 100644
--- a/src/generators/generator.js
+++ b/src/generators/generator.js
@@ -1,4 +1,4 @@
-function getGenerator(context, options) {
+function getGenerator(context, config) {
const eleventyVersion = context.pkg?.dependencies?.['@11ty/eleventy']
|| context.pkg?.devDependencies?.['@11ty/eleventy']
|| '';
@@ -7,10 +7,12 @@ function getGenerator(context, options) {
name: 'eleventy',
version: eleventyVersion,
environment: process.env.ELEVENTY_ENV || '',
+ ...config.generator,
metadata: {
markdown: 'markdown-it',
- 'markdown-it': options?.markdownItOptions || { html: true }
- }
+ 'markdown-it': { html: true },
+ ...config.generator?.metadata
+ },
};
}
diff --git a/src/generators/info.js b/src/generators/info.js
index 8a31eb2..46e9940 100644
--- a/src/generators/info.js
+++ b/src/generators/info.js
@@ -8,7 +8,7 @@ const cloudcannon = {
version: module.exports.version
};
-function getInfo(context, config, options) {
+function getInfo(context, config) {
const collectionsConfig = getCollectionsConfig(context, config);
const collections = getCollections(collectionsConfig, context, config);
@@ -18,7 +18,7 @@ function getInfo(context, config, options) {
collections: collections,
collections_config: collectionsConfig,
data: getData(context, config),
- generator: getGenerator(context, options),
+ generator: getGenerator(context, config),
time: new Date().toISOString(),
version: '0.0.3' // schema version
};
diff --git a/src/util/logger.js b/src/util/logger.js
index 0577d89..3973a19 100644
--- a/src/util/logger.js
+++ b/src/util/logger.js
@@ -2,6 +2,11 @@ function log(str) {
console.log(`[cloudcannon] ${str}`);
}
+function logError(str) {
+ console.error(`[cloudcannon] ${str}`);
+}
+
module.exports = {
- log
+ log,
+ logError
};
diff --git a/tests/config.test.js b/tests/config.test.js
index bd3b3c6..754cf3f 100644
--- a/tests/config.test.js
+++ b/tests/config.test.js
@@ -2,6 +2,13 @@ const test = require('ava');
const { readConfig } = require('../src/config.js');
test('reads legacy config', (t) => {
+ const options = {
+ markdownItOptions: {
+ html: true,
+ linkify: true
+ }
+ };
+
const context = {
cloudcannon: {
data: {
@@ -109,9 +116,18 @@ test('reads legacy config', (t) => {
source_editor: {
theme: 'dawn',
},
+ generator: {
+ metadata: {
+ markdown: 'markdown-it',
+ 'markdown-it': {
+ html: true,
+ linkify: true
+ }
+ }
+ }
};
- const config = readConfig(context);
+ const config = readConfig(context, options);
t.deepEqual(config, expected);
});
diff --git a/tests/generators/generator.test.js b/tests/generators/generator.test.js
index 42347b8..0e9bd55 100644
--- a/tests/generators/generator.test.js
+++ b/tests/generators/generator.test.js
@@ -1,15 +1,15 @@
const test = require('ava');
const { getGenerator } = require('../../src/generators/generator.js');
-const options = {
- dir: {
- input: '.',
- data: '_data',
- layouts: '_includes'
- },
- markdownItOptions: {
- html: true,
- linkify: true
+const config = {
+ generator: {
+ metadata: {
+ markdown: 'markdown-it',
+ 'markdown-it': {
+ html: true,
+ linkify: true
+ }
+ }
}
};
@@ -31,18 +31,18 @@ test('gets generator', (t) => {
pkg: { dependencies: { '@11ty/eleventy': '1' } }
};
- t.deepEqual(getGenerator(context, options), processedGenerator);
+ t.deepEqual(getGenerator(context, config), processedGenerator);
const contextDev = {
pkg: { devDependencies: { '@11ty/eleventy': '2' } }
};
- t.deepEqual(getGenerator(contextDev, options), {
+ t.deepEqual(getGenerator(contextDev, config), {
...processedGenerator,
version: '2'
});
- t.deepEqual(getGenerator({}, options), {
+ t.deepEqual(getGenerator({}, config), {
...processedGenerator,
version: ''
});
diff --git a/tests/generators/info.test.js b/tests/generators/info.test.js
index 39515c2..de83bfe 100644
--- a/tests/generators/info.test.js
+++ b/tests/generators/info.test.js
@@ -4,19 +4,16 @@ require('pkginfo')(module, 'version');
const version = module.exports.version || '';
-const options = {
- dir: {
- input: '.',
- data: '_data',
- layouts: '_includes'
- },
- markdownItOptions: {
- html: true,
- linkify: true
- }
-};
-
const config = {
+ generator: {
+ metadata: {
+ markdown: 'markdown-it',
+ 'markdown-it': {
+ html: true,
+ linkify: true
+ }
+ }
+ },
_select_data: {
shrutes: ['Dwight', 'Mose']
},
@@ -160,7 +157,7 @@ test('gets info', (t) => {
}
};
- const result = getInfo(context, config, options);
+ const result = getInfo(context, config);
t.deepEqual({ ...result, time: null }, processedInfo);
const time = result.time.substring(0, 10);