diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..a5ec49122
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,9 @@
+**/node_modules/**
+node_modules
+**/vendor/**
+vendor
+**/test/**
+**/dist/**
+
+// being refactored / removed
+packages/uikit-workshop/src/scripts/components/styleguide.js
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 000000000..826a1702e
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,86 @@
+module.exports = {
+ root: true,
+ env: {
+ node: true,
+ builtin: true,
+ es6: true,
+ browser: true,
+ },
+ parser: 'babel-eslint',
+ parserOptions: {
+ ecmaVersion: 2018,
+ sourceType: 'module',
+ allowImportExportEverywhere: true,
+ ecmaFeatures: {
+ jsx: true,
+ experimentalDecorators: true,
+ },
+ },
+ globals: {},
+ plugins: ['prettier'],
+ extends: ['eslint-config-prettier'].map(require.resolve),
+ rules: {
+ 'prettier/prettier': 'error',
+ 'block-scoped-var': 0,
+ camelcase: 0,
+ 'consistent-return': 2,
+ curly: [2, 'all'],
+ 'dot-notation': [1, { allowKeywords: true }],
+ eqeqeq: [2, 'allow-null'],
+ 'global-strict': [0, 'never'],
+ 'guard-for-in': 2,
+ 'key-spacing': 0,
+ 'new-cap': 0,
+ 'no-alert': 2,
+ 'no-bitwise': 2,
+ 'no-caller': 2,
+ 'no-cond-assign': [2, 'except-parens'],
+ 'no-debugger': 2,
+ 'no-dupe-args': 2,
+ 'no-dupe-keys': 2,
+ 'no-empty': 2,
+ 'no-eval': 2,
+ 'no-extend-native': 2,
+ 'no-extra-bind': 2,
+ 'no-extra-parens': 0,
+ 'no-func-assign': 2,
+ 'no-implied-eval': 2,
+ 'no-invalid-regexp': 2,
+ 'no-irregular-whitespace': 1,
+ 'no-iterator': 2,
+ 'no-loop-func': 2,
+ 'no-mixed-requires': 0,
+ 'no-multi-str': 2,
+ 'no-native-reassign': 2,
+ 'no-new': 2,
+ 'no-param-reassign': 1,
+ 'no-proto': 2,
+ 'no-redeclare': 0,
+ 'no-script-url': 2,
+ 'no-self-assign': 2,
+ 'no-self-compare': 2,
+ 'no-sequences': 2,
+ 'no-shadow': 2,
+ 'no-undef': 2,
+ 'no-underscore-dangle': 0,
+ 'no-unreachable': 1,
+ 'no-unused-vars': 1,
+ 'no-use-before-define': 1,
+ 'no-useless-call': 2,
+ 'no-useless-concat': 2,
+ 'no-var': 2,
+ 'no-with': 2,
+ quotes: [0, 'single'],
+ radix: 2,
+ strict: 0,
+ 'valid-typeof': 2,
+ 'vars-on-top': 0,
+ 'prefer-const': [
+ 'error',
+ {
+ destructuring: 'any',
+ ignoreReadBeforeAssign: false,
+ },
+ ],
+ },
+};
diff --git a/.eslintrc b/.eslintrc.json
similarity index 67%
rename from .eslintrc
rename to .eslintrc.json
index 1a6fc01c8..335572829 100644
--- a/.eslintrc
+++ b/.eslintrc.json
@@ -1,29 +1,28 @@
{
+ "root": true,
"env": {
"node": true,
- "builtin": true
+ "builtin": true,
+ "es6": true
+ },
+ "parserOptions": {
+ "ecmaVersion": 2017,
+ "sourceType": "module"
},
"globals": {},
+ "extends": ["prettier"],
+ "plugins": ["prettier"],
"rules": {
+ "prettier/prettier": "error",
"block-scoped-var": 0,
"camelcase": 0,
- "comma-spacing": [1, {"before": false, "after": true}],
"consistent-return": 2,
"curly": [2, "all"],
"dot-notation": [1, { "allowKeywords": true }],
"eqeqeq": [2, "allow-null"],
"global-strict": [0, "never"],
"guard-for-in": 2,
- "indent": [1, 2, {"SwitchCase": 1, "VariableDeclarator": 1}],
- "lines-around-comment": [1, {
- "beforeBlockComment": true,
- "beforeLineComment": true,
- "allowBlockStart": true,
- "allowObjectStart": true,
- "allowArrayStart": true
- }],
"key-spacing": 0,
- "keyword-spacing": 1,
"new-cap": 0,
"no-alert": 2,
"no-bitwise": 2,
@@ -37,7 +36,6 @@
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-parens": 0,
- "no-extra-semi": 2,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-invalid-regexp": 2,
@@ -46,7 +44,6 @@
"no-loop-func": 2,
"no-mixed-requires": 0,
"no-multi-str": 2,
- "no-multi-spaces": 1,
"no-native-reassign": 2,
"no-new": 2,
"no-param-reassign": 1,
@@ -64,20 +61,19 @@
"no-use-before-define": 1,
"no-useless-call": 2,
"no-useless-concat": 2,
+ "no-var": 2,
"no-with": 2,
"quotes": [0, "single"],
"radix": 2,
- "semi": [1, "always"],
"strict": 0,
- "space-before-blocks": 1,
- "space-before-function-paren": [1, {
- "anonymous": "always",
- "named": "never"
- }],
- "space-in-parens": [1, "never"],
- "space-infix-ops": 1,
"valid-typeof": 2,
"vars-on-top": 0,
- "wrap-iife": [2, "inside"]
+ "prefer-const": [
+ "error",
+ {
+ "destructuring": "any",
+ "ignoreReadBeforeAssign": false
+ }
+ ]
}
}
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 8598cddb2..477f273af 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -1,15 +1,71 @@
-# Contributing to Patternlab Node
+# Contributing to Pattern Lab Node
+
If you'd like to contribute to Pattern Lab Node, please do so! There is always a lot of ground to cover and something for your wheelhouse.
-No pull request is too small. Check out any [up for grabs issues](https://github.com/pattern-lab/patternlab-node/labels/up%20for%20grabs) as a good way to get your feet wet, or add some more unit tests.
+No pull request is too small. Check out any [help wanted 🆘](https://github.com/pattern-lab/patternlab-node/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted+%3Asos%3A%22) or [good first issues 🎓](https://github.com/pattern-lab/patternlab-node/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue+%3Amortar_board%3A%22)as a good way to get your feet wet, or add some more unit tests.
+
+## Prerequisites
+
+To get started, you'll need Node installed. Managing Node with [nvm](https://github.com/creationix/nvm) is recommended. Once installed, you can target the version of Node we specify within the [`.nvmrc`](https://github.com/pattern-lab/patternlab-node/blob/master/.nvmrc) file.
+
+```sh
+nvm install <>
+nvm use <>
+```
+
+## Developing Locally
+
+The best way to make changes to the Pattern Lab Node core and test them is through an edition.
+
+* Fork this repository on Github.
+* `npm install && npm run bootstrap`
+* Create a new branch in your fork and push your changes in that fork.
+* `cd packages/edition-node`
+* Test your changes with the edition's api
+
+### Cold start testing
+
+To ensure that developers can bootstrap the repo from a fresh clone, do this in your working copy:
+
+```sh
+git reset --hard && git clean -dfx && npm install && npm run bootstrap
+```
+
+This ensures that any changes you've made will still result in a clean and functional developer experience. **Note**: be sure you've committed any outstanding work before doing this -- it will blow away whatever's still outstanding, including anything staged but not commited.
## Guidelines
-1. Please keep your pull requests concise and limited to **ONE** substantive change at a time. This makes reviewing and testing so much easier.
-2. _ALWAYS_ submit pull requests against the [dev branch](https://github.com/pattern-lab/patternlab-node/tree/dev). If this does not occur, I will first, try to redirect you gently, second, port over your contribution manually if time allows, and/or third, close your pull request. If you have a major feature to stabilize over time, talk to @bmuenzenmeyer about making a dedicated `feature-branch`
-3. If you can, add some unit tests using the existing patterns in the `./test` directory
-##Coding style
-Two files combine within the project to define and maintain our coding style.
+* _ALWAYS_ submit pull requests against the [dev branch](https://github.com/pattern-lab/patternlab-node/tree/dev). If this does not occur, I will first, try to redirect you gently, second, attempt to redirect the target branch myself, thirdly, port over your contribution manually if time allows, and/or lastly, close your pull request. If you have a major feature to stabilize over time, talk to @bmuenzenmeyer via an issue about making a dedicated `feature-branch`
+* Keep your pull requests concise and limited to **ONE** substantive change at a time. This makes reviewing and testing so much easier.
+* Commits should reference the issue you are adressing. For any Pull Request that you send, use the template provided.
+* Commits are best formatted using the [conventional commits pattern](https://conventionalcommits.org/).
+* If you can, add some unit tests using the existing patterns in the `.packages/core/test` directory
+* Large enhancements should begin with opening an issue. This will result in a more systematic way for us to review your contribution and determine if a [specifcation discussion](https://github.com/pattern-lab/the-spec/issues) needs to occur.
+* Mention the issue number in commits, so anyone can see to which issue your changes belong to. For instance:
+ * `fix(get): Resolve patterns correctly`
+ * `feat(version): Add ability to ask for version statically`
+
+## Coding style
+
+Formatting is automated via [Prettier](https://prettier.io/), setup to run on precommit. We suggest [editor integration](https://prettier.io/docs/en/editors.html) for this and for eslint. Prettier is further configured within `.prettierrc`. Eslint validates syntax and usage that Prettier doesn't handle. Configuration for both is found within the `.eslintrc.json` file.
+
+The `.editorconfig` controls spaces / tabs within supported editors. Check out their [site](http://editorconfig.org/).
+
+## Tests
+
+Add unit and integration tests if you can. It's always nice if our code coverage improves bit by bit (literally!). We are using [Node Tap](http://www.node-tap.org/) as test framework and [Rewire](https://github.com/jhnns/rewire) for mocking things like file system access.
+
+## Branching Scheme
+
+ Currently Pattern Lab has the following branches:
+
+* **master** contains the latext stable, released version
+* **dev**: for development. _Target pull requests against this branch._
+* **feature-branches** for larger changes. Allows merging all changes into both `dev` easily.
+* **long running branches** for changes that involve major changes to the code, architecture and take a lot of time (i.e. making Pattern Lab async)
+
+New features are typically cut off of `dev` branch. When `dev` is stable cut releases by merging `dev` to `master` and creating a release tag.
+
+# Gitter.im Chat
-* The `.editorconfig` controls spaces / tabs within supported editors. Check out their [site](http://editorconfig.org/).
-* The `.eslintrc` defines our javascript standards. Some editors will evaluate this real-time - otherwise it's run using `grunt|gulp build`
+If you have any questions or you would like to help, feel free to ask on [our Gitter.im channel](https://gitter.im/pattern-lab/node) :smiley:
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..5805c3f03
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+patreon: patternlab
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index dd3871fc4..9e5ad2464 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,12 +1,11 @@
-
-I am using Pattern Lab Node `vX.X.X` on `Windows | Mac | Linux`, with Node `vX.X.X`, using the `Gulp | Grunt ` Edition.
+
-##### Expected Behavior
+I am using Pattern Lab Node `vX.X.X` on `Windows | Mac | Linux`, with Node `vX.X.X`, using a `Gulp | Grunt | Vanilla | Custom` Edition.
+##### Expected Behavior
##### Actual Behavior
-
##### Steps to Reproduce
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 4ddf5e12d..0ca02b82c 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,5 +1,5 @@
-Addresses #
+Closes #
Summary of changes:
diff --git a/.github/branching-scheme.png b/.github/branching-scheme.png
new file mode 100644
index 000000000..9d7b7628a
Binary files /dev/null and b/.github/branching-scheme.png differ
diff --git a/.github/gitgraph/README.md b/.github/gitgraph/README.md
new file mode 100644
index 000000000..02cc14ca4
--- /dev/null
+++ b/.github/gitgraph/README.md
@@ -0,0 +1,8 @@
+Generating a new graph
+======================
+
+This folder uses http://gitgraphjs.com/ for generating the git graph model.
+
+1. Change `patternlab-flow.js` to your needs according to the documentation on http://gitgraphjs.com/
+2. Open branching-scheme.html in browse, right click the graph and "Save as...".
+3. Overwrite `/.github/branching-scheme.png`
diff --git a/.github/gitgraph/branching-scheme.html b/.github/gitgraph/branching-scheme.html
new file mode 100644
index 000000000..32d2ffb80
--- /dev/null
+++ b/.github/gitgraph/branching-scheme.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.github/gitgraph/patternlab-flow.js b/.github/gitgraph/patternlab-flow.js
new file mode 100644
index 000000000..a68910cee
--- /dev/null
+++ b/.github/gitgraph/patternlab-flow.js
@@ -0,0 +1,213 @@
+var graphConfig = new GitGraph.Template({
+ colors: [
+ '#9993FF',
+ '#47E8D4',
+ '#6BDB52',
+ '#F85BB5',
+ '#FFA657',
+ '#FFCCAA',
+ '#F85BB5',
+ ],
+ branch: {
+ lineWidth: 3,
+ spacingX: 60,
+ mergeStyle: 'straight',
+ showLabel: true, // display branch names on graph
+ labelFont: 'normal 10pt Arial',
+ labelRotation: 0,
+ color: 'black',
+ },
+ commit: {
+ spacingY: -30,
+ dot: {
+ size: 8,
+ strokeColor: '#000000',
+ strokeWidth: 4,
+ },
+ tag: {
+ font: 'normal 10pt Arial',
+ color: 'yellow',
+ },
+ message: {
+ color: 'black',
+ font: 'normal 12pt Arial',
+ displayAuthor: false,
+ displayBranch: false,
+ displayHash: false,
+ },
+ },
+ arrow: {
+ size: 8,
+ offset: 3,
+ },
+});
+
+var config = {
+ template: graphConfig,
+ mode: 'extended',
+ orientation: 'horizontal',
+};
+
+var bugFixCommit = {
+ messageAuthorDisplay: false,
+ messageBranchDisplay: false,
+ messageHashDisplay: false,
+ message: 'Bug fix commit(s)',
+};
+
+var stabilizationCommit = {
+ messageAuthorDisplay: false,
+ messageBranchDisplay: false,
+ messageHashDisplay: false,
+ message: 'Release stabilization commit(s)',
+};
+
+// You can manually fix columns to control the display.
+var i = 0;
+var longRunningCol = i++;
+var featureV3Col = i++;
+var developV3Col = i++;
+var featureCol = i++;
+var developCol = i++;
+var releaseCol = i++;
+var masterCol = i++;
+
+var gitgraph = new GitGraph(config);
+
+var master = gitgraph.branch({
+ name: 'master',
+ column: masterCol,
+});
+master.commit('Initial commit');
+
+var develop = gitgraph.branch({
+ parentBranch: master,
+ name: 'dev',
+ column: developCol,
+});
+
+var developV3 = gitgraph.branch({
+ parentBranch: master,
+ name: 'dev-3.0',
+ column: developV3Col,
+});
+
+var longRunning = gitgraph.branch({
+ parentBranch: master,
+ name: 'long-running-improvement',
+ column: longRunningCol,
+});
+
+develop.commit({
+ messageDisplay: false,
+});
+developV3.commit({
+ messageDisplay: false,
+});
+
+longRunning.commit({
+ messageDisplay: false,
+});
+longRunning.merge(developV3);
+
+var feature1 = gitgraph.branch({
+ parentBranch: develop,
+ name: 'feature/1-description',
+ column: featureCol,
+});
+feature1.commit('#1 A feature to go into v2.8.0').commit({
+ messageDisplay: false,
+});
+develop.merge(feature1);
+feature1.commit('Small Bugfix').commit({
+ messageDisplay: false,
+});
+feature1.merge(develop);
+
+var feature3X = gitgraph.branch({
+ parentBranch: developV3,
+ name: 'feature/42-feature-for-3-x-only',
+ column: featureV3Col,
+});
+feature3X.commit('#42 A feature to go into v3.X').commit({
+ messageDisplay: false,
+});
+feature3X.merge(developV3);
+
+var feature2 = gitgraph.branch({
+ parentBranch: develop,
+ name: 'feature/2-description',
+ column: featureCol,
+});
+feature2.commit('#2 Another feature to go into v2.8.0').commit({
+ messageDisplay: false,
+});
+feature2.merge(develop);
+feature2.merge(developV3);
+
+develop.merge(master, {
+ dotStrokeWidth: 10,
+ message: 'Release v2.8.1 tagged',
+ tag: 'v2.8.1',
+});
+
+develop.commit({
+ messageDisplay: false,
+});
+
+longRunning.commit({
+ messageDisplay: false,
+});
+
+developV3.merge(longRunning);
+
+longRunning.commit({
+ messageDisplay: false,
+});
+
+var feature3 = gitgraph.branch({
+ parentBranch: develop,
+ name: 'bugfix/3-description',
+ column: featureCol,
+});
+
+feature3.commit('A feature to go into v2.8.0').commit({
+ messageDisplay: false,
+});
+feature3.merge(develop);
+
+longRunning.merge(developV3);
+
+developV3.commit({
+ messageDisplay: false,
+ dotStrokeWidth: 10,
+});
+
+develop.commit({
+ messageDisplay: false,
+});
+
+develop.commit({
+ messageDisplay: false,
+});
+
+develop.merge(master, {
+ dotStrokeWidth: 10,
+ message: 'Release v2.9.0 tagged',
+ tag: 'v2.9.0',
+});
+
+develop.commit({
+ messageDisplay: false,
+ dotStrokeWidth: 10,
+});
+
+developV3.checkout();
+
+/*
+developV3.merge(master, {
+ dotStrokeWidth: 10,
+ message: "Release v3.0.0 tagged",
+ tag: "v3.0.0"
+});
+*/
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 000000000..5969a91e8
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,17 @@
+# Number of days of inactivity before an issue becomes stale
+daysUntilStale: 60
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 30
+# Issues with these labels will never be considered stale
+exemptLabels:
+ - "staged for next release 🏁"
+ - "pinned 📌"
+ - "triage 😰"
+# Label to use when marking an issue as stale
+staleLabel: "needs response 🤙"
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ It's hard to keep track of everything. This issue has been automatically marked as stale because it has not had recent activity, neither from the team nor the community. It will be closed if no further activity occurs. Please consider adding additional info, volunteering to contribute a fix for this issue, or making a further case that this is important to you, the team, and the project as a whole. Thanks!
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: >
+ Issue closed after going stale. It can be re-opened if still relevant.
diff --git a/.gitignore b/.gitignore
index 74ab03195..96232f92a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,17 @@
node_modules/
+pattern_exports/
.DS_Store
-latest-change.txt
-patternlab.json
-.sass-cache/*
-/sass-cache
Thumbs.db
-source/css/style.css.map
+.nyc_output/
+.vscode/
.idea/
-public
+.env
+packages/core/test/public
+packages/*/public
+!packages/core/test/patterns/public/.gitkeep
+!packages/core/test/patterns/testDependencyGraph.json
+lerna-debug.log
+packages/edition-node-gulp/dependencyGraph.json
+packages/uikit-workshop/dist
+
+yarn-error.log
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 000000000..13dae57b5
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,7 @@
+test/
+.DS_Store
+.travis.yml
+.nyc_output/
+.vscode/
+.idea/
+
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 000000000..43c97e719
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 000000000..7f976a5ae
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+12.12.0
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..f25d454d3
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,15 @@
+**/*.json
+**/README.md
+**/node_modules/
+**/bower_components/
+**/dist/
+**/public/
+**/*.min.js
+packages/core/scripts/api.handlebars
+packages/core/scripts/events.handlebars
+packages/core/test/files/annotations.js
+packages/**/annotations.js
+**/uikit-workshop/src/js/**/*
+*.json
+*.md
+*.scss
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 000000000..c1a6f6671
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "singleQuote": true,
+ "trailingComma": "es5"
+}
diff --git a/.travis.yml b/.travis.yml
index 844871fce..3fb4c3593 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,25 +1,29 @@
language: node_js
-node_js:
- - node
- - 6
- - 5
- - 4
+addons:
+ chrome: stable
before_install:
- - phantomjs --version
+ # version lifted from `.nvmrc`
+ - nvm install
+ - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.19.0
+ - export PATH="$HOME/.yarn/bin:$PATH"
before_script:
- - npm install -g grunt-cli
- - npm install patternengine-node-underscore
- - npm install patternengine-node-handlebars
- - npm install patternengine-node-twig
+ - yarn run setup
+ - npx lerna add @pattern-lab/engine-mustache --scope=@pattern-lab/core
+ - npx lerna add @pattern-lab/engine-handlebars --scope=@pattern-lab/core
+ - npx lerna add @pattern-lab/engine-underscore --scope=@pattern-lab/core
+ - npx lerna add @pattern-lab/engine-liquid --scope=@pattern-lab/core
+ - npx lerna add @pattern-lab/engine-twig --scope=@pattern-lab/core
+ - npx lerna add @pattern-lab/engine-react --scope=@pattern-lab/core
+
+script: travis_wait yarn run test
branches:
only:
- master
- dev
- - issue/438-runAllTestsTravis
notifications:
webhooks:
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..ddc797b61
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,396 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+
+### Bug Fixes
+
+* **plugintabs:** enabling multiple file formats [#1163](https://github.com/pattern-lab/patternlab-node/issues/1163) ([bb5e817](https://github.com/pattern-lab/patternlab-node/commit/bb5e8179e6b8553a6e1af0bede26db412b6c0b68))
+* adjust UIKit Nav updates to account for the noViewAll config variation ([73eac97](https://github.com/pattern-lab/patternlab-node/commit/73eac976461f4e587b0c30668942c4895aea319f))
+* make sure the top-level Dropdown menus always open/close ([7a8b418](https://github.com/pattern-lab/patternlab-node/commit/7a8b418bfcbd200ef8b2802b1a07964a9995bf9f))
+* only allow one top level nav item to be open at a time while rendering as a dropdown menu ([409bef3](https://github.com/pattern-lab/patternlab-node/commit/409bef37165260d9b728013ac33e7aa67541c832))
+* re-try Netlify preview to debug local vs prod rendering differences ([6da41a1](https://github.com/pattern-lab/patternlab-node/commit/6da41a14feea034f891c745dfeb062fa3b196235))
+* Update dependency on twing JS engine ([cfe88c6](https://github.com/pattern-lab/patternlab-node/commit/cfe88c6cdbf2219b9955eaa0ffcfc0e4a7683511))
+* **cli:** fix test script glob ([ff18eb5](https://github.com/pattern-lab/patternlab-node/commit/ff18eb51ce24fc5423b009168e85ede366069139))
+
+
+
+
+
+## [5.9.2](https://github.com/pattern-lab/patternlab-node/compare/v5.9.1...v5.9.2) (2020-04-24)
+
+**Note:** Version bump only for package pl-node
+
+
+
+
+
+## [5.9.1](https://github.com/pattern-lab/patternlab-node/compare/v5.9.0...v5.9.1) (2020-04-24)
+
+
+### Bug Fixes
+
+* **cli:** ensure specified directory exists prior to scaffold ([cc3b696](https://github.com/pattern-lab/patternlab-node/commit/cc3b69624d486c94ee3b1f4b1bbb0334a514fa59))
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+
+### Bug Fixes
+
+* **cli:** set current working directory before scaffolded npm init ([6d2186d](https://github.com/pattern-lab/patternlab-node/commit/6d2186d8e8a74634198a4474ca8ae83221dd70a9))
+* **core:** do not warn about uikit-polyfills ([6bb68e7](https://github.com/pattern-lab/patternlab-node/commit/6bb68e763769969546542bf7aaf6d1f4235c6622))
+* actually exit build when Twig render fails ([5d28a24](https://github.com/pattern-lab/patternlab-node/commit/5d28a24a53011396289c1e29e0a715cd82470185))
+* Update packages/engine-twig-php/lib/engine_twig_php.js ([c67d50e](https://github.com/pattern-lab/patternlab-node/commit/c67d50ebb5d69816b7514e85f129f8ecde984ad3))
+
+
+### Features
+
+* **docs:** yarnify ([5a47dc7](https://github.com/pattern-lab/patternlab-node/commit/5a47dc7b90dc5c43c12a51143b41943dcbd8564c))
+* **README:** add netlify badges ([941df8a](https://github.com/pattern-lab/patternlab-node/commit/941df8a59b6b75bc1255646005f329e40be68106))
+
+
+
+
+
+# [5.8.0](https://github.com/pattern-lab/patternlab-node/compare/v5.7.2...v5.8.0) (2020-04-03)
+
+
+### Bug Fixes
+
+* the namespace notation should not be mixed with PatternLab shorthand pattern naming & name is not defined in the textarea macro ([8250fe8](https://github.com/pattern-lab/patternlab-node/commit/8250fe88231d03735424d597eae40496da2cb48c))
+* Updated the README to reflect which issues are resolved. ([d90c3c4](https://github.com/pattern-lab/patternlab-node/commit/d90c3c4605f9a5bcd1153996e3f4d1a17d58bd92))
+
+
+### Features
+
+* switch engine-twig to use twing rather than node-twig ([daca95c](https://github.com/pattern-lab/patternlab-node/commit/daca95c4ffa48916fb6c67c5184bde9b624acd76))
+
+
+
+
+
+## [5.7.2](https://github.com/pattern-lab/patternlab-node/compare/v5.7.1...v5.7.2) (2020-03-24)
+
+
+### Bug Fixes
+
+* update iframe resizer UI to be hidden when iframe is full width ([9797c1a](https://github.com/pattern-lab/patternlab-node/commit/9797c1a047d746d21b88a1f57b57f618a03a54df))
+
+
+
+
+
+## [5.7.1](https://github.com/pattern-lab/patternlab-node/compare/v5.7.0...v5.7.1) (2020-02-24)
+
+
+### Bug Fixes
+
+* update twig-renderer ([46f53b7](https://github.com/pattern-lab/patternlab-node/commit/46f53b79f8bb0bb64a9c55fd32f29459cea6e28c))
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/patternlab-node/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+
+### Features
+
+* **cli:** make options more user friendly ([ad845b3](https://github.com/pattern-lab/patternlab-node/commit/ad845b394ef81f90895ebb5bc6f12cc608e5e3d4))
+
+
+
+
+
+# [5.6.0](https://github.com/pattern-lab/patternlab-node/compare/v5.5.0...v5.6.0) (2020-01-18)
+
+
+### Bug Fixes
+
+* a11y fix on text contrast ([6d75b22](https://github.com/pattern-lab/patternlab-node/commit/6d75b226ce27228025b2915e5d402f7080faee31))
+* a11y issue on a missing description of that complementary icon ([4f13807](https://github.com/pattern-lab/patternlab-node/commit/4f13807cb93df33435088de3a51170b9c4515889))
+
+
+### Features
+
+* pass additional configuration into twig-php engine ([dff5a78](https://github.com/pattern-lab/patternlab-node/commit/dff5a7830918fa46e2692d9f9daed4121f803461))
+
+
+
+
+
+
+# [5.5.0](https://github.com/pattern-lab/patternlab-node/compare/v5.4.2...v5.5.0) (2019-12-19)
+
+
+### Features
+
+* upgrade Twig to use new filter, map, reduce ([4218a5a](https://github.com/pattern-lab/patternlab-node/commit/4218a5a04b06027548afd9f417486297dd25fef8))
+
+
+
+
+
+## [5.4.2](https://github.com/pattern-lab/patternlab-node/compare/v5.4.1...v5.4.2) (2019-11-27)
+
+**Note:** Version bump only for package pl-node-pr
+
+
+
+
+
+## [5.4.1](https://github.com/pattern-lab/patternlab-node/compare/v5.4.0...v5.4.1) (2019-11-26)
+
+
+### Bug Fixes
+
+* temp workaround to address instance where the latest version of Edge supports ES modules but NOT Custom Elements ([ada3d82](https://github.com/pattern-lab/patternlab-node/commit/ada3d829019345fd33ed949f306972efdcb4fa57))
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/patternlab-node/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+
+### Bug Fixes
+
+* re-add popstate listener ([6dbbd6a](https://github.com/pattern-lab/patternlab-node/commit/6dbbd6aae3709cc17544c12dd10588120eb9e71a))
+* **script:** remove quotes around starterkit ([e4897fb](https://github.com/pattern-lab/patternlab-node/commit/e4897fb6e4d4cd0985ab72397abd03ff04be514b))
+* add a new method to check if PL is currently compiling + add new method to get the config PL is using ([26e886c](https://github.com/pattern-lab/patternlab-node/commit/26e886c93db5d135c91de648724f7278c4d5b3e9))
+* check if dependency graph file exists before trying to remove ([f9af6a9](https://github.com/pattern-lab/patternlab-node/commit/f9af6a95025a22041e7ff8a4bfb19e4727385e98))
+* comment out example config to disable viewAll links ([ddb3fad](https://github.com/pattern-lab/patternlab-node/commit/ddb3fad5770d1d66432c4b583ae9af09a3a47d48))
+
+
+### Features
+
+* add the ability to disable Pattern Lab viewall links in the navigation ([156e609](https://github.com/pattern-lab/patternlab-node/commit/156e609a92e7f7e7ebd8f4f5cd77b5d695db8bad))
+* major improvements to local UIKit workflow ([4dc9173](https://github.com/pattern-lab/patternlab-node/commit/4dc9173a5a44b422e9677824de3728048b7c4f05))
+* test adding cross-env to Twig Edition test ([3f8bb01](https://github.com/pattern-lab/patternlab-node/commit/3f8bb01bc4e96a0aba61c213ea1619c02593defc))
+
+
+
+
+
+## [5.3.3](https://github.com/pattern-lab/patternlab-node/compare/v5.3.2...v5.3.3) (2019-11-22)
+
+
+### Bug Fixes
+
+* simplify overflow fix ([378cf42](https://github.com/pattern-lab/patternlab-node/commit/378cf4282a3e5b4f597287eb538270e3358c8c69))
+* testing potential FF fix for https://github.com/pattern-lab/patternlab-node/issues/1100 ([613bba1](https://github.com/pattern-lab/patternlab-node/commit/613bba104f2082be507938db78f1db7a07f6b8be))
+
+
+
+
+
+
+## [5.3.2](https://github.com/pattern-lab/patternlab-node/compare/v5.3.1...v5.3.2) (2019-11-14)
+
+**Note:** Version bump only for package pl-node
+
+
+
+
+
+## [5.3.1](https://github.com/pattern-lab/patternlab-node/compare/v5.3.0...v5.3.1) (2019-11-13)
+
+
+### Bug Fixes
+
+* CSS fix to properly highlight the correct active page / link in the Nav; improve dropdown open / close animation ([ec4ab84](https://github.com/pattern-lab/patternlab-node/commit/ec4ab84ddc8007796c9012a3f493822d76f039a7))
+* small UI fixes for the sticky Tabs header on smaller screens + drawer content collapsing on smaller screens + better handling of Nav link cleanup when changing pages ([347e2fe](https://github.com/pattern-lab/patternlab-node/commit/347e2fe29a78a1d168005a07c656b4f9f1124c7f))
+* tweak header and drawer padding when viewing on a device with curved edges ([98e9baf](https://github.com/pattern-lab/patternlab-node/commit/98e9baf649eceb9124218a924b6b08097b910e86))
+* uikit fixes and minor CSS updates intended for the v5.3.0 release ([26c4ced](https://github.com/pattern-lab/patternlab-node/commit/26c4ceddaae09fa4fa4873f092c924274498c5da))
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/patternlab-node/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+
+### Bug Fixes
+
+* add PluginTab workaround for Safari ([2fa9367](https://github.com/pattern-lab/patternlab-node/commit/2fa936769be65484af52f242dca2536a3382462c))
+* **core:** re-add cleanPublic fix ([c100bbc](https://github.com/pattern-lab/patternlab-node/commit/c100bbca3f339e9132acb9c482e98c1c8a66b8b5))
+* **plugin-tab:** defensively call addPanels ([b82bd12](https://github.com/pattern-lab/patternlab-node/commit/b82bd129fdbe48de95b62d75fb7fe95cea896b7e))
+* port over missing UIKit Sass that wasn't added in the original PR ([f7659e6](https://github.com/pattern-lab/patternlab-node/commit/f7659e64d0eee13be20921dd5afc48ac20ae93e6))
+
+
+### Features
+
+* port latest UIKit updates + fixes upstream ([d07952c](https://github.com/pattern-lab/patternlab-node/commit/d07952cb07e3792b995dda2e589262ecf4153fdc))
+
+
+
+
+
+# [5.2.0](https://github.com/pattern-lab/patternlab-node/compare/v5.1.0...v5.2.0) (2019-11-12)
+
+
+### Bug Fixes
+
+* **deploy:** add setup command ([7c1d8d1](https://github.com/pattern-lab/patternlab-node/commit/7c1d8d14842a467bb301e2ede2ec83074ff35ae2))
+* add missing $ ([c95a06e](https://github.com/pattern-lab/patternlab-node/commit/c95a06ece78631b068f8721666caf33452e57a7a))
+* address bug causing viewport width to progressively decrease in size when resizing your screen / refreshing on certain devices ([41b11af](https://github.com/pattern-lab/patternlab-node/commit/41b11af8aaaf066fcf99abd2513eae8706122d32))
+* configure the Logo's `altText` config option when used as an HTML attribute ([ade34a2](https://github.com/pattern-lab/patternlab-node/commit/ade34a29435f5112f0449ad020bee7e9dc2c81e1))
+* fix classname typo ([da3c5f1](https://github.com/pattern-lab/patternlab-node/commit/da3c5f144d22b1ac3ad99680a264433d4438ebb2))
+* temp workaround to fix content exceeding the height of drawer container ([435243c](https://github.com/pattern-lab/patternlab-node/commit/435243cbfbd000a7d96a0e9fa7beff1a988ede64))
+* update drawer UI to not collapse content on smaller screen sizes ([7147085](https://github.com/pattern-lab/patternlab-node/commit/71470856b8b389421348366afd247a599d1e9c86))
+* update package.json description in `@pattern-lab/uikit-polyfills` ([22fc44a](https://github.com/pattern-lab/patternlab-node/commit/22fc44a4b3683753a469a98abfcdad8f1234f28a))
+* **engine_twig_php:** Allow additional flexibility with twig namespaces. ([07bfaa3](https://github.com/pattern-lab/patternlab-node/commit/07bfaa35a00ff62fd2016cc9f34e09cf5af36559))
+
+
+### Features
+
+* add lit-element, basic Typescript support to Webpack ([611f705](https://github.com/pattern-lab/patternlab-node/commit/611f705be85eea8a31091169750d64e988798cee))
+* add local copy of new Slotify library till published to NPM ([63b9d83](https://github.com/pattern-lab/patternlab-node/commit/63b9d833908151ce5cb5aa5184c72254125c7ed1))
+* add new component to make Button-like styles more reusable ([5e7b014](https://github.com/pattern-lab/patternlab-node/commit/5e7b0140622eb89154c38925769a6def6d669fb3))
+* add new component ([e8ce2a9](https://github.com/pattern-lab/patternlab-node/commit/e8ce2a927365b8d5316a7d8229c979ff31b04907))
+* add support for auto-closing Nav when clicking inside of the rendered iframe ([9d602fe](https://github.com/pattern-lab/patternlab-node/commit/9d602fe335a5d3b5bca5cac258c2465934d9a46a))
+* add support for optional chaining syntax via Babel plugin ([c8886b6](https://github.com/pattern-lab/patternlab-node/commit/c8886b6d9d91fea246fa3ab7947f289509dc26d5))
+* major refactoring + UI updates to address cross browser support; UI cleanup and conversion of the majority of the remaining components over to lit-element ([2ff8e1c](https://github.com/pattern-lab/patternlab-node/commit/2ff8e1c98cdd02e8077064c48eca5f7754a3db02))
+* refactor + convert pl-toggle-info to lit-element ([85cd9c5](https://github.com/pattern-lab/patternlab-node/commit/85cd9c50ca814066bf999badf2071d84964f00cc))
+* refactor + convert pl-toggle-layout to lit-element ([46009d9](https://github.com/pattern-lab/patternlab-node/commit/46009d91b1cb9ed613baa5a7626cba4f42883465))
+* refactor + convert pl-toggle-theme to lit-element ([95a3b21](https://github.com/pattern-lab/patternlab-node/commit/95a3b21a89dacd2d5b4df8c134ce438d4efdbd04))
+* refactor Drawer to render via lit-element + massively improve rendering performance ([28d47eb](https://github.com/pattern-lab/patternlab-node/commit/28d47eb3cbbce038204203e786e5188b4cefe64f))
+* remove mixin that was causing outlines to be removed from default UI styles ([622ed76](https://github.com/pattern-lab/patternlab-node/commit/622ed76d435b3b2e31e412266c3090506f98051b))
+* temp add unsafe-svg directive till upstream PR merged ([34de61c](https://github.com/pattern-lab/patternlab-node/commit/34de61ccd9c7bb3b48ca5ef386a87efc8e84babc))
+* update the Nav design to not bold the active item in order to not shift the layout ([0eda431](https://github.com/pattern-lab/patternlab-node/commit/0eda4312ba9f4c61afa6322c3ff45f9cda0efc9e))
+* update Webpack config to use the latest Style Loader + new SVG icon system ([2ed70e7](https://github.com/pattern-lab/patternlab-node/commit/2ed70e79d8656c7314d8b3109aa1c34160ad24f9))
+
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/patternlab-node/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+
+### Features
+
+* **config:** add new default pattern export options ([a7487a0](https://github.com/pattern-lab/patternlab-node/commit/a7487a0681cb11e6f3c5c8eaefd62e5648ad5ea3))
+
+
+
+
+
+## [5.0.2](https://github.com/pattern-lab/patternlab-node/compare/v5.0.1...v5.0.2) (2019-10-28)
+
+
+### Bug Fixes
+
+* **uikit-workshop:** add template files to published bundle ([9005fce](https://github.com/pattern-lab/patternlab-node/commit/9005fcee9e129fb41d509f706195e1437bddc710))
+* **uikit-workshop:** add webpack config to published bundle ([060a573](https://github.com/pattern-lab/patternlab-node/commit/060a573cbddce9ee3d270d39337d0c8cac8372fa))
+
+
+
+
+
+## [5.0.1](https://github.com/pattern-lab/patternlab-node/compare/v5.0.0...v5.0.1) (2019-10-28)
+
+
+### Bug Fixes
+
+* add missing “dist” folder to array of files / folders published to NPM ([8829429](https://github.com/pattern-lab/patternlab-node/commit/88294296c438352570befd2eb6b9e1ca2ae3b750))
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Bug Fixes
+
+* **1049:** Treat folders like patterns only if they're subfolders of pattern groupings ([4eb79ab](https://github.com/pattern-lab/patternlab-node/commit/4eb79ab48b335a35b2e5ed3b7053974b8e8bb6b6))
+* **cli:** add custom install logic to edition-node ([f04fd26](https://github.com/pattern-lab/patternlab-node/commit/f04fd266429cd806987dab747e6d69bff9b926a4))
+* **cli:** allow any package to be installed as a starterkit ([d2aa1be](https://github.com/pattern-lab/patternlab-node/commit/d2aa1be810a0a7473dcc52391a2263dacfdda0b8)), closes [#1067](https://github.com/pattern-lab/patternlab-node/issues/1067)
+* **cli:** merge config arrays via overwrite instead of concatenate ([42e5f7b](https://github.com/pattern-lab/patternlab-node/commit/42e5f7b42a26b4fc1f262c68ee4b474b546f2eac))
+* **cli:** proper path resolution to helpers ([a18fe5e](https://github.com/pattern-lab/patternlab-node/commit/a18fe5ef4d1c074a5eba8bfa255ebbee2261bf74))
+* **cli:** re-order and clarify engines ([e39e301](https://github.com/pattern-lab/patternlab-node/commit/e39e301a33306c6615fabf64262f1893ca682b97))
+* **core:** allow plugin resolution to follow normal algorithm ([3f6b83b](https://github.com/pattern-lab/patternlab-node/commit/3f6b83be080c88aec1d8b73bececb76f0f57a79d))
+* **core:** find plugins from config only and with simpler args ([fe7351c](https://github.com/pattern-lab/patternlab-node/commit/fe7351cba346425512cbb2ef3a1b7728ab06ae60))
+* **deploy:** add setup command ([74dd314](https://github.com/pattern-lab/patternlab-node/commit/74dd3142bf48873a9f1ec4e8dccb8aa2fef9001d))
+* **engine_twig_php:** Pseudo patterns Twig PHP ([226aa8b](https://github.com/pattern-lab/patternlab-node/commit/226aa8bbaaf5e418530ccf54a28f6c5657ee6dea)), closes [#1045](https://github.com/pattern-lab/patternlab-node/issues/1045)
+* **engine_twig_php:** Twig incremental rebuilds ([1ade945](https://github.com/pattern-lab/patternlab-node/commit/1ade9451840b2645706a0b01129e2b697dc22d4b)), closes [#1015](https://github.com/pattern-lab/patternlab-node/issues/1015)
+* **engine_twig_php:** Twig incremental rebuilds ([5d33f24](https://github.com/pattern-lab/patternlab-node/commit/5d33f24f156ebe50900701513a855de7de608dcf)), closes [#1015](https://github.com/pattern-lab/patternlab-node/issues/1015)
+* **lerna:** typo in config ([525a47b](https://github.com/pattern-lab/patternlab-node/commit/525a47b51fba91c1bf5b7439735f48eb7dfa073e))
+* **lint:** Use const instead of var ([ad1e782](https://github.com/pattern-lab/patternlab-node/commit/ad1e782ef71295eb610f56d019eaa35499fb3f85))
+* **plugin:** correct spelling error and function locations ([d4abd88](https://github.com/pattern-lab/patternlab-node/commit/d4abd88cb017550002407241b5045a2ad1adb1dc))
+* **plugin-tab:** bump lodash from 4.17.5 to 4.17.15 in /packages/plugin-tab ([#1081](https://github.com/pattern-lab/patternlab-node/issues/1081)) ([3f89dda](https://github.com/pattern-lab/patternlab-node/commit/3f89dda1685874e251f9777f969c0943e0080881))
+* **plugin-tab:** handle params correctly ([d248993](https://github.com/pattern-lab/patternlab-node/commit/d2489939bb0db1a1d67b0e7f47dfb1838b88b0a0))
+* **starterkit:** add css output and build command ([ccb2d35](https://github.com/pattern-lab/patternlab-node/commit/ccb2d3569b741220324a3fa738ab3d4d2eb97ffe))
+* add better pre-rendering support ([8ecd615](https://github.com/pattern-lab/patternlab-node/commit/8ecd6159a89232f42e0a9dc3c688b6e21de8fc30))
+* add eslint fixes ([00d7bbe](https://github.com/pattern-lab/patternlab-node/commit/00d7bbe319ea77a6ee8cc9cd0348856feaaf13ad))
+* add missing @babel/runtime package to address silent error getting thrown on Travis ([1918d04](https://github.com/pattern-lab/patternlab-node/commit/1918d042d7e90cc8aaa2fdfcd8649961c0a5dd50))
+* add missing preact-render-to-string library ([881296a](https://github.com/pattern-lab/patternlab-node/commit/881296a2c256424beac28bd560c5b1a5e1fed005))
+* add repo info to root package.json so Auto knows what repo to configure for ([85142e8](https://github.com/pattern-lab/patternlab-node/commit/85142e8e94549edd7980459e5975d0639c34864d))
+* address unrelated eslint errors from PL core ([6ada00d](https://github.com/pattern-lab/patternlab-node/commit/6ada00d396eb436837f7453664bfa50522a2ec10))
+* correct typo in build logging ([96d989f](https://github.com/pattern-lab/patternlab-node/commit/96d989f8869630ba9f59705bfca66755f20e35ab))
+* fall back to seeing the current pattern's query string to `all` or the defaultPattern value if undefined when the iframe page initially loads ([a368459](https://github.com/pattern-lab/patternlab-node/commit/a3684590fca02cf96b99421b87a0ad0a711893ad))
+* fix incorrect Webpack version in package.json ([9788e89](https://github.com/pattern-lab/patternlab-node/commit/9788e8977921e31fe43f2a1ec19d4684dd4709c5))
+* fix issue with viewport height exceeding the space available ([95cd1cf](https://github.com/pattern-lab/patternlab-node/commit/95cd1cfa57f086ecb84ac2e996ecda81f0c6a1a6))
+* fix Prism.js typo so languages not found / supported don't throw a JS error ([a8c19f9](https://github.com/pattern-lab/patternlab-node/commit/a8c19f9f9b11d4abbdcd9e573fb0cb418d665660))
+* fix Twig Edition examples by adding missing Twig namespaces to config ([b4c20ef](https://github.com/pattern-lab/patternlab-node/commit/b4c20ef88ee0d3010760584c6f05ff7f92b711a6))
+* minor CSS fixes + fresh prod build ([8ac2c1f](https://github.com/pattern-lab/patternlab-node/commit/8ac2c1fa1c7558ed2ac50755f599a438d682ee2a))
+* re-enable displaying the top level `All` link if PL isn't configured to hide this specific link in the ishControlsHide config option. Addresses [#1048](https://github.com/pattern-lab/patternlab-node/issues/1048) ([6bb4e1a](https://github.com/pattern-lab/patternlab-node/commit/6bb4e1ac6f38b47f93030c8c5bca62d5db2132e4))
+* re-enable using the defaultPattern config for the initial iframe page load if defined ([d645ea1](https://github.com/pattern-lab/patternlab-node/commit/d645ea15150061d7ad13741d2dc37b12b9786411))
+* regenerate fresh UIKit build after fixing main JS issues ([9ea34d2](https://github.com/pattern-lab/patternlab-node/commit/9ea34d2efe43cafacb3729ac113121ba51126344))
+* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/commit/74e5af28c4e714fdfc1db535b94c52f3dc14a3a4))
+* squashing minor UI bugs ([a8a606c](https://github.com/pattern-lab/patternlab-node/commit/a8a606cfb224f7041f53ff5026a84e13fa17914c))
+* temporarily disable Random and Disco viewport controls until the full JS logic for these is re-enabled ([14b9a19](https://github.com/pattern-lab/patternlab-node/commit/14b9a19e4dee9462f3784eae28066893cc893624))
+* temporarily downgrade Preact version so tooltip used for displaying viewport sizes renders correctly ([52dcf85](https://github.com/pattern-lab/patternlab-node/commit/52dcf85e756ee171ca993288d98f5b5ef9a0a24b))
+* update autoprefixer browserslist config to address warning messages ([5e52f2b](https://github.com/pattern-lab/patternlab-node/commit/5e52f2b0ed02e2002ca867368636c3c0dc79ff0a))
+* update initial PL iframe path default ([a26fbb9](https://github.com/pattern-lab/patternlab-node/commit/a26fbb956e13901d1751c435b76de65637191ca4))
+* update Javascript to address merge conflict issue with previous PR merge / recent release ([cf2ecc1](https://github.com/pattern-lab/patternlab-node/commit/cf2ecc154383c3e8abd56dc88484370bc58ac30b))
+* update styles for pattern state dots ([7728acc](https://github.com/pattern-lab/patternlab-node/commit/7728accc9a6e5cd83be451f7d74e522dfe721cad))
+* update the default pattern that displays in the Handlebars demo ([ff1d85f](https://github.com/pattern-lab/patternlab-node/commit/ff1d85f2852fc4f210841e8e0aaf14b55165ce58))
+* **starterkit:** remove config file ([f90e38a](https://github.com/pattern-lab/patternlab-node/commit/f90e38aa873dcff0dd08fe4dabc3b71bf95080b6))
+* **starterkit:** use handlebars meta files ([d8f5e12](https://github.com/pattern-lab/patternlab-node/commit/d8f5e12471bd783bd3755626701ecc17669fc761))
+* updates to address eslint / prettier issues ([d945acc](https://github.com/pattern-lab/patternlab-node/commit/d945acc13b8e4e36f3815b017fbc12266c323d1f))
+* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/commit/5b7a057d46ccd16b5832af1441030c7b76f237a8))
+* use 100% of the screen available when JS is disabled / the first time the iframe loads up ([c0c5bff](https://github.com/pattern-lab/patternlab-node/commit/c0c5bff7a63b157d5b81dc2bcecee9e732ecfd4e))
+* **uikit:** clear out "404" responses when loading tabs ([73874b1](https://github.com/pattern-lab/patternlab-node/commit/73874b1b0b66ca6425c2b74331d417efdb529e2e))
+* **uikit-workshop:** fix merge problem ([d245b3b](https://github.com/pattern-lab/patternlab-node/commit/d245b3bca044c29f281052bf2feb95eeffafcf6b))
+
+
+### Features
+
+* **core:** invoke registered plugin hooks ([a54d775](https://github.com/pattern-lab/patternlab-node/commit/a54d7753b6939fe6a58da543f4fb34f64dd8901a))
+* **edition-node:** switch to engine-handlebars ([b481e22](https://github.com/pattern-lab/patternlab-node/commit/b481e22dc1f41ddd4da709621640a15190fba257))
+* **engine-handlebars:** Default location for helpers, like engine-nunjucks ([11c4180](https://github.com/pattern-lab/patternlab-node/commit/11c41805e0c3dbebb7109719c4f3c780d32feab5))
+* **engine-handlebars:** Demonstration of custom Handlebars helper ([f330b5b](https://github.com/pattern-lab/patternlab-node/commit/f330b5bca72f2f34bfafe5c2c64e6b0b8823eb1c))
+* **engine-handlebars:** Document the Helpers feature ([a01e040](https://github.com/pattern-lab/patternlab-node/commit/a01e040429a7f77dfeb28d67c690e835b97881de))
+* **engine-handlebars:** Load Handlebars helpers specified in the config ([a12df36](https://github.com/pattern-lab/patternlab-node/commit/a12df36d2a644dfac8ded1dfd94b987e99c29d79))
+* **engine-nunjucks:** Configurable extension locations; Use usePatternlabConfig() ([e54e3b3](https://github.com/pattern-lab/patternlab-node/commit/e54e3b3d48f934d3a4d44b9f4ff262f742a4aaf9))
+* **engine-react:** set package to private ([3aea881](https://github.com/pattern-lab/patternlab-node/commit/3aea8815f19df5b527cdda0b75cf99a9a8c3bc1e))
+* **plugin-tab:** pivot to using hook functions ([d4b2598](https://github.com/pattern-lab/patternlab-node/commit/d4b25984fc2a2646cc1876a5c635f57593c35f09))
+* **plugin-tab, core:** initial plugin hook exploration ([2f3d39a](https://github.com/pattern-lab/patternlab-node/commit/2f3d39ac6b125ad4c6b872e27ee224ce2ea33a12))
+* **starterkits:** add starterkit-handlebars-demo ([384d2cf](https://github.com/pattern-lab/patternlab-node/commit/384d2cfa3440c1e6f456d39f56ca6381f82f7689))
+* **uikit-workshop:** add plugin-loader ([fc966d6](https://github.com/pattern-lab/patternlab-node/commit/fc966d6b151e24055bc2f4146d6a90b5fb392765))
+* introduce netlify preview ([6c5d332](https://github.com/pattern-lab/patternlab-node/commit/6c5d332479fb6836bd8bd5530a074d13440f8ae4))
+* remove pre-built uikit dist folder and switch to auto-building when bootstrapping OR when publishing to NPM ([b5dd553](https://github.com/pattern-lab/patternlab-node/commit/b5dd5538ee00ddf1da321851865fa1c223cedb43))
+* switch to Yarn + Yarn workspaces ([f4c4ec3](https://github.com/pattern-lab/patternlab-node/commit/f4c4ec33cd30d372c87ffa904fbe7d5b819ee14e))
+* update Node to v12 ([fcbb970](https://github.com/pattern-lab/patternlab-node/commit/fcbb970648cdd775c9a88078f14c1f24c5b62d73))
+
+
+### Reverts
+
+* don't flatten folders containing only one item inside ([77f1f46](https://github.com/pattern-lab/patternlab-node/commit/77f1f46595328bd96fba46347b532295c65802d1))
+
+
+### BREAKING CHANGES
+
+* **core:** plugins now use async functions instead of events
+* **plugin-tab:** event based listeners replaced with functions
+* **cli:** previously, we concatenated arrays, which is unlikely to be intended
+* **edition-node:** use handlebars over mustache
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 000000000..75ecbe287
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,17 @@
+# This is a comment.
+# Each line is a file pattern followed by one or more owners.
+
+# These owners will be the default owners for everything in
+# the repo. Unless a later match takes precedence,
+# @global-owner1 and @global-owner2 will be requested for
+# review when someone opens a pull request.
+@pattern-lab/trusted-committers
+
+# CLI owner
+/packages/cli @raphaelokon
+
+# uikit-workshop owner
+/packages/uikit-workshop @sghoweri
+
+# engine-nunjucks owner
+/packages/engine-nunjucks @danwhite85
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..9f536bcc4
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at brian.muenzenmeyer@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 2d74d0d19..000000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,41 +0,0 @@
-module.exports = function (grunt) {
-
- /******************************
- * Project configuration.
- * Should only be needed if you are developing against core, running tests, linting and want to run tests or increment package numbers
- *****************************/
- grunt.initConfig({
- pkg: grunt.file.readJSON('package.json'),
- concat: {
- options: {
- stripBanners: true,
- banner: '/* \n * <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy") %> \n * \n * <%= pkg.author.name %>, <%= pkg.contributors[0].name %>, and the web community.\n * Licensed under the <%= pkg.license %> license. \n * \n * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. \n *\n */\n\n',
- },
- patternlab: {
- src: './core/lib/patternlab.js',
- dest: './core/lib/patternlab.js'
- }
- },
- nodeunit: {
- all: ['test/*_tests.js']
- },
- eslint: {
- options: {
- configFile: './.eslintrc'
- },
- target: ['./core/lib/*']
- }
- });
-
- // load all grunt tasks
- grunt.loadNpmTasks('grunt-contrib-concat');
- grunt.loadNpmTasks('grunt-eslint');
- grunt.loadNpmTasks('grunt-contrib-nodeunit');
-
- //travis CI task
- grunt.registerTask('travis', ['nodeunit', 'eslint']);
-
- //to be run prior to releasing a version
- grunt.registerTask('build', ['nodeunit', 'eslint', 'concat']);
-
-};
diff --git a/LICENSE b/LICENSE
index 44cbb3e41..c9b8c1daa 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2013 Brad Frost, http://bradfrostweb.com & Dave Olsen, http://dmolsen.com & Brian Muenzenmeyer, http://brianmuenzenmeyer.com
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index fa94173db..cd621433a 100644
--- a/README.md
+++ b/README.md
@@ -1,44 +1,74 @@
-[](https://travis-ci.org/pattern-lab/patternlab-node)   [](https://gitter.im/pattern-lab/node)
+
+
+
-# Pattern Lab Node Core
+# Pattern Lab
-This repository contains the core functionality for Pattern Lab Node. Pattern Lab Core is designed to be included as a dependency within [Node Editions](https://github.com/pattern-lab?utf8=%E2%9C%93&query=edition-node).
-If this looks **REALLY DIFFERENT** from what you expected, check out the [ChangeLog](https://github.com/pattern-lab/patternlab-node/wiki/ChangeLog).
+This monorepo contains the core of Pattern Lab / Node and all related engines, UI kits, plugins and utilities. Pattern Lab helps you and your team build thoughtful, pattern-driven user interfaces using atomic design principles.
-* [Pattern Lab/Node: Gulp Edition](https://github.com/pattern-lab/edition-node-gulp) contains info how to get started within a Gulp task running environment.
-* [Pattern Lab/Node: Grunt Edition](https://github.com/pattern-lab/edition-node-grunt) contains info how to get started within a Grunt task running environment.
+If you'd like to see what a front-end project built with Pattern Lab looks like, check out this [online demo of Pattern Lab output](http://demo.patternlab.io/).
-## Core Team
+[](https://travis-ci.org/pattern-lab/patternlab-node)
+
+
+[](https://coveralls.io/github/pattern-lab/patternlab-node?branch=master)
+[](https://github.com/prettier/prettier)
+[]()
+[](https://gitter.im/pattern-lab/node)
-* [@bmuenzenmeyer](https://github.com/bmuenzenmeyer) - Lead Maintainer
-* [@geoffp](https://github.com/geoffp) - Core Contributor
+Docs @ [](https://app.netlify.com/sites/patternlab-docs-preview/deploys)
-## Upgrading
+Pattern Lab Preview @ [](https://app.netlify.com/sites/patternlab-handlebars-preview/deploys)
-If you find yourself here and are looking to upgrade, check out how to upgrade from version to version of Pattern Lab Node here: [https://github.com/pattern-lab/patternlab-node/wiki/Upgrading](https://github.com/pattern-lab/patternlab-node/wiki/Upgrading)
+## Using Pattern Lab
-## Command Line Interface
+Refer to the [core usage guidelines](https://github.com/pattern-lab/patternlab-node/blob/master/packages/core/README.md#usage)
-The [command line interface](https://github.com/pattern-lab/patternlab-node/wiki/Command-Line-Interface) is documented in the wiki, and already implemented for you within [Node Editions](https://github.com/pattern-lab?utf8=%E2%9C%93&query=edition-node).
+### Installation
-## Contributing
+As of Pattern Lab Node 3.0.0, installation of [Editions](http://patternlab.io/docs/advanced-ecosystem-overview.html) is accomplished via the command line interface.
+
+The below assumes a new directory and project is required. This is likely what you want to do if starting from scratch. You could also run this within an existing project. The CLI will ask you for the installation location.
+
+1. Open a terminal window and following along below:
+ ```bash
+ mkdir new-project
+ cd new-project
+ npm create pattern-lab
+ ```
+ > If you get an error stating that `npx` is not installed, ensure you are on `npm 5.2.0` or later by running `npm -v` or install it globally with `npm install -g npx`. [Learn more about npx.](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b)
+1. Follow the on-screen prompts to choose your Edition and a Starterkit should you want one.
+ - If you chose `edition-node`, new commands in the "scripts" will be added in your `package.json`.
+ - If you chose `edition-node-gulp`, a `gulpfile.js` will be added to your project.
+
+ > Notice that `@pattern-lab/cli` was installed as a depdendency. Learn how to further [use the cli in your own project](https://github.com/pattern-lab/patternlab-node/blob/dev/packages/cli/readme.md#configuring-your-project-to-use-the-cli).
+
+
+## Ecosystem
-If you'd like to contribute to Pattern Lab Node, please do so! There is always a lot of ground to cover and something for your wheelhouse.
+
-No pull request is too small. Check out any [up for grabs issues](https://github.com/pattern-lab/patternlab-node/labels/up%20for%20grabs) as a good way to get your feet wet, or add some more unit tests.
+Core, and Editions, are part of the [Pattern Lab Ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). With this architecture, we encourage people to write and maintain their own Editions, Starterkits, and even PatternEngines.
-## Guidelines
-1. Please keep your pull requests concise and limited to **ONE** substantive change at a time. This makes reviewing and testing so much easier.
-2. _ALWAYS_ submit pull requests against the [dev branch](https://github.com/pattern-lab/patternlab-node/tree/dev). If this does not occur, I will first, try to redirect you gently, second, port over your contribution manually if time allows, and/or third, close your pull request. If you have a major feature to stabilize over time, talk to @bmuenzenmeyer about making a dedicated `feature-branch`
-3. If you can, add some unit tests using the existing patterns in the `./test` directory
-4. To help hack on core from an edition, read [this wiki page](https://github.com/pattern-lab/patternlab-node/wiki/Running-an-Edition-Against-Local-Core)
+## Changelog
-## Coding style
-Two files combine within the project to define and maintain our coding style.
+[Each package within this monorepo](https://github.com/pattern-lab/patternlab-node/tree/master/packages) has its own changelog. Below are the main ones to watch:
-* The `.editorconfig` controls spaces / tabs within supported editors. Check out their [site](http://editorconfig.org/).
-* The `.eslintrc` defines our javascript standards. Some editors will evaluate this real-time - otherwise it's run using `grunt|gulp build`
+* [@pattern-lab/core changelog ](https://github.com/pattern-lab/patternlab-node/blob/master/packages/core/CHANGELOG.md)
+* [@pattern-lab/cli changelog ](https://github.com/pattern-lab/patternlab-node/blob/master/packages/cli/CHANGELOG.md)
-## Gitter
+## Support for Pattern Lab
+
+Pattern Lab / Node wouldn't be what it is today without the support of the community. It will always be free and open source. Continued development is made possible in part from the support of [these wonderful project supporters](https://github.com/pattern-lab/patternlab-node/wiki/Thanks). If you want to learn more about supporting the project, visit the [Pattern Lab / Node Patreon page](https://www.patreon.com/patternlab).
+
+**:100: Thanks for support from the following:**
+
+* **[Brad Frost](http://bradfrost.com/)**
+* Jan Ditze
+* [Marcos Peebles](https://twitter.com/marcospeebles)
+* [Maximilian Franzke](https://twitter.com/maedmaex)
+* [Susan Simkins](https://twitter.com/susanmsimkins)
+
+## Contributing
-The Pattern Lab Node team uses [our gitter.im channel, pattern-lab/node](https://gitter.im/pattern-lab/node) to keep in sync, share updates, and talk shop. Please stop by to say hello or as a first place to turn if stuck. Other channels in the Pattern Lab organization can be found on gitter too.
+Refer to the [contribution guidelines](https://github.com/pattern-lab/patternlab-node/blob/master/.github/CONTRIBUTING.md).
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 000000000..6960ccf61
--- /dev/null
+++ b/babel.config.js
@@ -0,0 +1,27 @@
+module.exports = {
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ targets: {
+ node: 'current',
+ },
+ },
+ ],
+ ],
+ plugins: [
+ /**
+ * 1. Helps with our Web Component Preact renderer
+ */
+ '@babel/plugin-syntax-jsx' /* [1] */,
+ [
+ '@babel/plugin-transform-react-jsx' /* [1] */,
+ {
+ pragma: 'h',
+ pragmaFrag: '"span"',
+ throwIfNamespace: false,
+ useBuiltIns: false,
+ },
+ ],
+ ],
+};
diff --git a/core/lib/annotation_exporter.js b/core/lib/annotation_exporter.js
deleted file mode 100644
index d12054145..000000000
--- a/core/lib/annotation_exporter.js
+++ /dev/null
@@ -1,101 +0,0 @@
-"use strict";
-
-var path = require('path'),
- glob = require('glob'),
- fs = require('fs-extra'),
- JSON5 = require('json5'),
- _ = require('lodash'),
- mp = require('./markdown_parser');
-
-var annotations_exporter = function (pl) {
-
- var paths = pl.config.paths;
-
- /*
- Returns the array of comments that used to be wrapped in raw JS.
- */
- function parseAnnotationsJS() {
- //attempt to read the file
- try {
- var oldAnnotations = fs.readFileSync(path.resolve(paths.source.annotations, 'annotations.js'), 'utf8');
- } catch (ex) {
- if (pl.config.debug) {
- console.log('annotations.js file missing from ' + paths.source.annotations + '. This may be expected.');
- }
- return [];
- }
-
- //parse as JSON by removing the old wrapping js syntax. comments and the trailing semi-colon
- oldAnnotations = oldAnnotations.replace('var comments = ', '');
- oldAnnotations = oldAnnotations.replace('};', '}');
-
- try {
- var oldAnnotationsJSON = JSON5.parse(oldAnnotations);
- } catch (ex) {
- console.log('There was an error parsing JSON for ' + paths.source.annotations + 'annotations.js');
- console.log(ex);
- return [];
- }
- return oldAnnotationsJSON.comments;
- }
-
- function buildAnnotationMD(annotationsYAML, markdown_parser) {
- var annotation = {};
- var markdownObj = markdown_parser.parse(annotationsYAML);
-
- annotation.el = markdownObj.el || markdownObj.selector;
- annotation.title = markdownObj.title;
- annotation.comment = markdownObj.markdown;
- return annotation;
- }
-
- function parseMDFile(annotations, parser) {
- var annotations = annotations;
- var markdown_parser = parser;
-
- return function (filePath) {
- var annotationsMD = fs.readFileSync(path.resolve(filePath), 'utf8');
-
- //take the annotation snippets and split them on our custom delimiter
- var annotationsYAML = annotationsMD.split('~*~');
- for (var i = 0; i < annotationsYAML.length; i++) {
- var annotation = buildAnnotationMD(annotationsYAML[i], markdown_parser);
- annotations.push(annotation);
- }
- return false;
- };
- }
-
- /*
- Converts the *.md file yaml list into an array of annotations
- */
- function parseAnnotationsMD() {
- var markdown_parser = new mp();
- var annotations = [];
- var mdFiles = glob.sync(paths.source.annotations + '/*.md');
-
- mdFiles.forEach(parseMDFile(annotations, markdown_parser));
- return annotations;
- }
-
- function gatherAnnotations() {
- var annotationsJS = parseAnnotationsJS();
- var annotationsMD = parseAnnotationsMD();
- return _.unionBy(annotationsJS, annotationsMD, 'el');
- }
-
- return {
- gather: function () {
- return gatherAnnotations();
- },
- gatherJS: function () {
- return parseAnnotationsJS();
- },
- gatherMD: function () {
- return parseAnnotationsMD();
- }
- };
-
-};
-
-module.exports = annotations_exporter;
diff --git a/core/lib/lineage_hunter.js b/core/lib/lineage_hunter.js
deleted file mode 100644
index e74e4a6c9..000000000
--- a/core/lib/lineage_hunter.js
+++ /dev/null
@@ -1,131 +0,0 @@
-"use strict";
-
-var lineage_hunter = function () {
-
- var pa = require('./pattern_assembler');
-
- function findlineage(pattern, patternlab) {
-
- var pattern_assembler = new pa();
-
- //find the {{> template-name }} within patterns
- var matches = pattern.findPartials();
- if (matches !== null) {
- matches.forEach(function (match) {
- //get the ancestorPattern
- var ancestorPattern = pattern_assembler.getPartial(pattern.findPartial(match), patternlab);
-
- if (ancestorPattern && pattern.lineageIndex.indexOf(ancestorPattern.patternPartial) === -1) {
- //add it since it didnt exist
- pattern.lineageIndex.push(ancestorPattern.patternPartial);
-
- //create the more complex patternLineage object too
- var l = {
- "lineagePattern": ancestorPattern.patternPartial,
- "lineagePath": "../../patterns/" + ancestorPattern.patternLink
- };
- if (ancestorPattern.patternState) {
- l.lineageState = ancestorPattern.patternState;
- }
-
- pattern.lineage.push(l);
-
- //also, add the lineageR entry if it doesn't exist
- if (ancestorPattern.lineageRIndex.indexOf(pattern.patternPartial) === -1) {
- ancestorPattern.lineageRIndex.push(pattern.patternPartial);
-
- //create the more complex patternLineage object in reverse
- var lr = {
- "lineagePattern": pattern.patternPartial,
- "lineagePath": "../../patterns/" + pattern.patternLink
- };
- if (pattern.patternState) {
- lr.lineageState = pattern.patternState;
- }
-
- ancestorPattern.lineageR.push(lr);
- }
- }
- });
- }
- }
-
- function setPatternState(direction, pattern, targetPattern) {
- // if the request came from the past, apply target pattern state to current pattern lineage
- if (direction === 'fromPast') {
- for (var i = 0; i < pattern.lineageIndex.length; i++) {
- if (pattern.lineageIndex[i] === targetPattern.patternPartial) {
- pattern.lineage[i].lineageState = targetPattern.patternState;
- }
- }
- } else {
- //the request came from the future, apply target pattern state to current pattern reverse lineage
- for (var i = 0; i < pattern.lineageRIndex.length; i++) {
- if (pattern.lineageRIndex[i] === targetPattern.patternPartial) {
- pattern.lineageR[i].lineageState = targetPattern.patternState;
- }
- }
- }
- }
-
-
- function cascadePatternStates(patternlab) {
-
- var pattern_assembler = new pa();
-
- for (var i = 0; i < patternlab.patterns.length; i++) {
- var pattern = patternlab.patterns[i];
-
- //for each pattern with a defined state
- if (pattern.patternState) {
-
- if (pattern.lineageIndex && pattern.lineageIndex.length > 0) {
-
- //find all lineage - patterns being consumed by this one
- for (var h = 0; h < pattern.lineageIndex.length; h++) {
- var lineagePattern = pattern_assembler.getPartial(pattern.lineageIndex[h], patternlab);
- setPatternState('fromFuture', lineagePattern, pattern);
- }
- }
-
- if (pattern.lineageRIndex && pattern.lineageRIndex.length > 0) {
-
- //find all reverse lineage - that is, patterns consuming this one
- for (var j = 0; j < pattern.lineageRIndex.length; j++) {
-
- var lineageRPattern = pattern_assembler.getPartial(pattern.lineageRIndex[j], patternlab);
-
- //only set patternState if pattern.patternState "is less than" the lineageRPattern.patternstate
- //or if lineageRPattern.patternstate (the consuming pattern) does not have a state
- //this makes patternlab apply the lowest common ancestor denominator
- if (lineageRPattern.patternState === '' || (patternlab.config.patternStateCascade.indexOf(pattern.patternState)
- < patternlab.config.patternStateCascade.indexOf(lineageRPattern.patternState))) {
-
- if (patternlab.config.debug) {
- console.log('Found a lower common denominator pattern state: ' + pattern.patternState + ' on ' + pattern.patternPartial + '. Setting reverse lineage pattern ' + lineageRPattern.patternPartial + ' from ' + (lineageRPattern.patternState === '' ? '<>' : lineageRPattern.patternState));
- }
-
- lineageRPattern.patternState = pattern.patternState;
-
- //take this opportunity to overwrite the lineageRPattern's lineage state too
- setPatternState('fromPast', lineageRPattern, pattern);
- } else {
- setPatternState('fromPast', pattern, lineageRPattern);
- }
- }
- }
- }
- }
- }
-
- return {
- find_lineage: function (pattern, patternlab) {
- findlineage(pattern, patternlab);
- },
- cascade_pattern_states : function (patternlab) {
- cascadePatternStates(patternlab);
- }
- };
-};
-
-module.exports = lineage_hunter;
diff --git a/core/lib/list_item_hunter.js b/core/lib/list_item_hunter.js
deleted file mode 100644
index be55f35c5..000000000
--- a/core/lib/list_item_hunter.js
+++ /dev/null
@@ -1,138 +0,0 @@
-"use strict";
-
-var list_item_hunter = function () {
-
- var extend = require('util')._extend,
- JSON5 = require('json5'),
- pa = require('./pattern_assembler'),
- smh = require('./style_modifier_hunter'),
- plutils = require('./utilities'),
- Pattern = require('./object_factory').Pattern;
-
- var pattern_assembler = new pa(),
- style_modifier_hunter = new smh(),
- items = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty'];
-
- function processListItemPartials(pattern, patternlab) {
- //find any listitem blocks
- var matches = pattern.findListItems();
-
- if (matches !== null) {
- matches.forEach(function (liMatch) {
-
- if (patternlab.config.debug) {
- console.log('found listItem of size ' + liMatch + ' inside ' + pattern.patternPartial);
- }
-
- //find the boundaries of the block
- var loopNumberString = liMatch.split('.')[1].split('}')[0].trim();
- var end = liMatch.replace('#', '/');
- var patternBlock = pattern.template.substring(pattern.template.indexOf(liMatch) + liMatch.length, pattern.template.indexOf(end)).trim();
-
- //build arrays that repeat the block, however large we need to
- var repeatedBlockTemplate = [];
- var repeatedBlockHtml = '';
- for (var i = 0; i < items.indexOf(loopNumberString); i++) {
- if (patternlab.config.debug) {
- console.log('list item(s) in pattern', pattern.patternPartial, 'adding', patternBlock, 'to repeatedBlockTemplate');
- }
- repeatedBlockTemplate.push(patternBlock);
- }
-
- //check for a local listitems.json file
- var listData;
- try {
- listData = JSON5.parse(JSON5.stringify(patternlab.listitems));
- } catch (err) {
- console.log('There was an error parsing JSON for ' + pattern.relPath);
- console.log(err);
- }
-
- listData = plutils.mergeData(listData, pattern.listitems);
- listData = pattern_assembler.parse_data_links_specific(patternlab, listData, 'listitems.json + any pattern listitems.json');
-
- //iterate over each copied block, rendering its contents along with pattenlab.listitems[i]
- for (var i = 0; i < repeatedBlockTemplate.length; i++) {
-
- var thisBlockTemplate = repeatedBlockTemplate[i];
- var thisBlockHTML = "";
-
- //combine listItem data with pattern data with global data
- var itemData = listData['' + items.indexOf(loopNumberString)]; //this is a property like "2"
- var globalData;
- var localData;
- try {
- globalData = JSON5.parse(JSON5.stringify(patternlab.data));
- localData = JSON5.parse(JSON5.stringify(pattern.jsonFileData));
- } catch (err) {
- console.log('There was an error parsing JSON for ' + pattern.relPath);
- console.log(err);
- }
-
- var allData = plutils.mergeData(globalData, localData);
- allData = plutils.mergeData(allData, itemData !== undefined ? itemData[i] : {}); //itemData could be undefined if the listblock contains no partial, just markup
- allData.link = extend({}, patternlab.data.link);
-
- //check for partials within the repeated block
- var foundPartials = Pattern.createEmpty({'template': thisBlockTemplate}).findPartials();
-
- if (foundPartials && foundPartials.length > 0) {
-
- for (var j = 0; j < foundPartials.length; j++) {
-
- //get the partial
- var partialName = foundPartials[j].match(/([\w\-\.\/~]+)/g)[0];
- var partialPattern = pattern_assembler.getPartial(partialName, patternlab);
-
- //create a copy of the partial so as to not pollute it after the get_pattern_by_key call.
- var cleanPartialPattern;
- try {
- cleanPartialPattern = JSON5.parse(JSON5.stringify(partialPattern));
- } catch (err) {
- console.log('There was an error parsing JSON for ' + pattern.relPath);
- console.log(err);
- }
-
- //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #356
- cleanPartialPattern.extendedTemplate = cleanPartialPattern.template;
-
- //if partial has style modifier data, replace the styleModifier value
- if (foundPartials[j].indexOf(':') > -1) {
- style_modifier_hunter.consume_style_modifier(cleanPartialPattern, foundPartials[j], patternlab);
- }
-
- //replace its reference within the block with the extended template
- thisBlockTemplate = thisBlockTemplate.replace(foundPartials[j], cleanPartialPattern.extendedTemplate);
- }
-
- //render with data
- thisBlockHTML = pattern_assembler.renderPattern(thisBlockTemplate, allData, patternlab.partials);
-
- } else {
- //just render with mergedData
- thisBlockHTML = pattern_assembler.renderPattern(thisBlockTemplate, allData, patternlab.partials);
- }
-
- //add the rendered HTML to our string
- repeatedBlockHtml = repeatedBlockHtml + thisBlockHTML;
- }
-
- //replace the block with our generated HTML
- var repeatingBlock = pattern.extendedTemplate.substring(pattern.extendedTemplate.indexOf(liMatch), pattern.extendedTemplate.indexOf(end) + end.length);
- pattern.extendedTemplate = pattern.extendedTemplate.replace(repeatingBlock, repeatedBlockHtml);
-
- //update the extendedTemplate in the partials object in case this pattern is consumed later
- patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate;
-
- });
- }
- }
-
- return {
- process_list_item_partials: function (pattern, patternlab) {
- processListItemPartials(pattern, patternlab);
- }
- };
-};
-
-module.exports = list_item_hunter;
diff --git a/core/lib/object_factory.js b/core/lib/object_factory.js
deleted file mode 100644
index a03e2b400..000000000
--- a/core/lib/object_factory.js
+++ /dev/null
@@ -1,148 +0,0 @@
-"use strict";
-
-var patternEngines = require('./pattern_engines');
-var path = require('path');
-var extend = require('util')._extend;
-
-// Pattern properties
-
-var Pattern = function (relPath, data, patternlab) {
- // We expect relPath to be the path of the pattern template, relative to the
- // root of the pattern tree. Parse out the path parts and save the useful ones.
- var pathObj = path.parse(path.normalize(relPath));
- this.relPath = path.normalize(relPath); // '00-atoms/00-global/00-colors.mustache'
- this.fileName = pathObj.name; // '00-colors'
- this.subdir = pathObj.dir; // '00-atoms/00-global'
- this.fileExtension = pathObj.ext; // '.mustache'
-
- // this is the unique name, subDir + fileName (sans extension)
- this.name = this.subdir.replace(path.sep, '-') + '-' + this.fileName.replace('~', '-'); // '00-atoms-00-global-00-colors'
-
- // the JSON used to render values in the pattern
- this.jsonFileData = data || {};
-
- // strip leading "00-" from the file name and flip tildes to dashes
- this.patternBaseName = this.fileName.replace(/^\d*\-/, '').replace('~', '-'); // 'colors'
-
- // Fancy name. No idea how this works. 'Colors'
- this.patternName = this.patternBaseName.split('-').reduce(function (val, working) {
- return val.charAt(0).toUpperCase() + val.slice(1) + ' ' + working.charAt(0).toUpperCase() + working.slice(1);
- }, '').trim(); //this is the display name for the ui. strip numeric + hyphen prefixes
-
- // the top-level pattern group this pattern belongs to. 'atoms'
- this.patternGroup = this.subdir.split(path.sep)[0].replace(/^\d*-/, '');
-
- //00-atoms if needed
- this.patternType = this.subdir.split(path.sep)[0];
-
- // the sub-group this pattern belongs to.
- this.patternSubGroup = path.basename(this.subdir).replace(/^\d*-/, ''); // 'global'
-
- //00-colors if needed
- this.patternSubType = path.basename(this.subdir);
-
- // the joined pattern group and subgroup directory
- this.flatPatternPath = this.subdir.replace(/[\/\\]/g, '-'); // '00-atoms-00-global'
-
- // calculated path from the root of the public directory to the generated
- // (rendered!) html file for this pattern, to be shown in the iframe
- this.patternLink = patternlab ? this.getPatternLink(patternlab, 'rendered') : null;
-
- // The canonical "key" by which this pattern is known. This is the callable
- // name of the pattern. UPDATE: this.key is now known as this.patternPartial
- this.patternPartial = this.patternGroup + '-' + this.patternBaseName;
-
- this.isPattern = true;
- this.isFlatPattern = this.patternGroup === this.patternSubGroup;
- this.patternState = '';
- this.template = '';
- this.patternPartialCode = '';
- this.lineage = [];
- this.lineageIndex = [];
- this.lineageR = [];
- this.lineageRIndex = [];
- this.isPseudoPattern = false;
- this.engine = patternEngines.getEngineForPattern(this);
-};
-
-// Pattern methods
-
-Pattern.prototype = {
-
- // render method on oPatterns; this acts as a proxy for the PatternEngine's
- // render function
- render: function (data, partials) {
- if (this.engine) {
- return this.engine.renderPattern(this, data || this.jsonFileData, partials);
- }
- return null;
- },
-
- registerPartial: function () {
- if (this.engine && typeof this.engine.registerPartial === 'function') {
- this.engine.registerPartial(this);
- }
- },
-
- // calculated path from the root of the public directory to the generated html
- // file for this pattern.
- // Should look something like '00-atoms-00-global-00-colors/00-atoms-00-global-00-colors.html'
- getPatternLink: function (patternlab, suffixType, customfileExtension) {
- // if no suffixType is provided, we default to rendered
- var suffixConfig = patternlab.config.outputFileSuffixes;
- var suffix = suffixType ? suffixConfig[suffixType] : suffixConfig.rendered;
-
- if (suffixType === 'rawTemplate') {
- return this.name + path.sep + this.name + suffix + this.fileExtension;
- }
-
- if (suffixType === 'custom') {
- return this.name + path.sep + this.name + customfileExtension;
- }
-
- return this.name + path.sep + this.name + suffix + '.html';
- },
-
- // the finders all delegate to the PatternEngine, which also encapsulates all
- // appropriate regexes
- findPartials: function () {
- return this.engine.findPartials(this);
- },
-
- findPartialsWithStyleModifiers: function () {
- return this.engine.findPartialsWithStyleModifiers(this);
- },
-
- findPartialsWithPatternParameters: function () {
- return this.engine.findPartialsWithPatternParameters(this);
- },
-
- findListItems: function () {
- return this.engine.findListItems(this);
- },
-
- findPartial: function (partialString) {
- return this.engine.findPartial(partialString);
- }
-};
-
-// Pattern static methods
-
-// factory: creates an empty Pattern for miscellaneous internal use, such as
-// by list_item_hunter
-Pattern.createEmpty = function (customProps, patternlab) {
- var pattern = new Pattern('', null, patternlab);
- return extend(pattern, customProps);
-};
-
-// factory: creates an Pattern object on-demand from a hash; the hash accepts
-// parameters that replace the positional parameters that the Pattern
-// constructor takes.
-Pattern.create = function (relPath, data, customProps, patternlab) {
- var newPattern = new Pattern(relPath || '', data || null, patternlab);
- return extend(newPattern, customProps);
-};
-
-module.exports = {
- Pattern: Pattern
-};
diff --git a/core/lib/pattern_assembler.js b/core/lib/pattern_assembler.js
deleted file mode 100644
index 69308215e..000000000
--- a/core/lib/pattern_assembler.js
+++ /dev/null
@@ -1,563 +0,0 @@
-"use strict";
-
-var path = require('path'),
- fs = require('fs-extra'),
- Pattern = require('./object_factory').Pattern,
- pph = require('./pseudopattern_hunter'),
- mp = require('./markdown_parser'),
- plutils = require('./utilities'),
- patternEngines = require('./pattern_engines'),
- lh = require('./lineage_hunter'),
- lih = require('./list_item_hunter'),
- smh = require('./style_modifier_hunter'),
- ph = require('./parameter_hunter'),
- JSON5 = require('json5');
-
-var markdown_parser = new mp();
-
-var pattern_assembler = function () {
- // HELPER FUNCTIONS
-
- function getPartial(partialName, patternlab) {
- //look for exact partial matches
- for (var i = 0; i < patternlab.patterns.length; i++) {
- if (patternlab.patterns[i].patternPartial === partialName) {
- return patternlab.patterns[i];
- }
- }
-
- //else look by verbose syntax
- for (var i = 0; i < patternlab.patterns.length; i++) {
- switch (partialName) {
- case patternlab.patterns[i].relPath:
- case patternlab.patterns[i].subdir + '/' + patternlab.patterns[i].fileName:
- return patternlab.patterns[i];
- }
- }
-
- //return the fuzzy match if all else fails
- for (var i = 0; i < patternlab.patterns.length; i++) {
- var partialParts = partialName.split('-'),
- partialType = partialParts[0],
- partialNameEnd = partialParts.slice(1).join('-');
-
- if (patternlab.patterns[i].patternPartial.split('-')[0] === partialType && patternlab.patterns[i].patternPartial.indexOf(partialNameEnd) > -1) {
- return patternlab.patterns[i];
- }
- }
- if (patternlab.config.debug) {
- console.error('Could not find pattern with partial ' + partialName);
- }
- return undefined;
- }
-
- function buildListItems(container) {
- //combine all list items into one structure
- var list = [];
- for (var item in container.listitems) {
- if (container.listitems.hasOwnProperty(item)) {
- list.push(container.listitems[item]);
- }
- }
- container.listItemArray = plutils.shuffle(list);
-
- for (var i = 1; i <= container.listItemArray.length; i++) {
- var tempItems = [];
- if (i === 1) {
- tempItems.push(container.listItemArray[0]);
- container.listitems['' + i ] = tempItems;
- } else {
- for (var c = 1; c <= i; c++) {
- tempItems.push(container.listItemArray[c - 1]);
- container.listitems['' + i ] = tempItems;
- }
- }
- }
- }
-
- /*
- * Deprecated in favor of .md 'status' frontmatter inside a pattern. Still used for unit tests at this time.
- * Will be removed in future versions
- */
- function setState(pattern, patternlab, displayDeprecatedWarning) {
- if (patternlab.config.patternStates && patternlab.config.patternStates[pattern.patternPartial]) {
-
- if (displayDeprecatedWarning) {
- plutils.logRed("Deprecation Warning: Using patternlab-config.json patternStates object will be deprecated in favor of the state frontmatter key associated with individual pattern markdown files.");
- console.log("This feature will still work in it's current form this release (but still be overridden by the new parsing method), and will be removed in the future.");
- }
-
- pattern.patternState = patternlab.config.patternStates[pattern.patternPartial];
- }
- }
-
- function addPattern(pattern, patternlab) {
-
- //add the link to the global object
- patternlab.data.link[pattern.patternPartial] = '/patterns/' + pattern.patternLink;
-
- //only push to array if the array doesn't contain this pattern
- var isNew = true;
- for (var i = 0; i < patternlab.patterns.length; i++) {
- //so we need the identifier to be unique, which patterns[i].relPath is
- if (pattern.relPath === patternlab.patterns[i].relPath) {
- //if relPath already exists, overwrite that element
- patternlab.patterns[i] = pattern;
- patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate || pattern.template;
- isNew = false;
- break;
- }
- }
-
- // if the pattern is new, we must register it with various data structures!
- if (isNew) {
-
- if (patternlab.config.debug) {
- console.log('found new pattern ' + pattern.patternPartial);
- }
-
- // do global registration
- if (pattern.isPattern) {
- patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate || pattern.template;
-
- // do plugin-specific registration
- pattern.registerPartial();
- } else {
- patternlab.partials[pattern.patternPartial] = pattern.patternDesc;
- }
-
- patternlab.patterns.push(pattern);
-
- }
- }
-
- function addSubtypePattern(subtypePattern, patternlab) {
- patternlab.subtypePatterns[subtypePattern.patternPartial] = subtypePattern;
- }
-
- // Render a pattern on request. Long-term, this should probably go away.
- function renderPattern(pattern, data, partials) {
- // if we've been passed a full Pattern, it knows what kind of template it
- // is, and how to render itself, so we just call its render method
- if (pattern instanceof Pattern) {
- return pattern.render(data, partials);
- } else {
- // otherwise, assume it's a plain mustache template string, and we
- // therefore just need to create a dummpy pattern to be able to render
- // it
- var dummyPattern = Pattern.createEmpty({extendedTemplate: pattern});
- return patternEngines.mustache.renderPattern(dummyPattern, data, partials);
- }
- }
-
- function parsePatternMarkdown(currentPattern, patternlab) {
-
- try {
- var markdownFileName = path.resolve(patternlab.config.paths.source.patterns, currentPattern.subdir, currentPattern.fileName + ".md");
- var markdownFileContents = fs.readFileSync(markdownFileName, 'utf8');
-
- var markdownObject = markdown_parser.parse(markdownFileContents);
- if (!plutils.isObjectEmpty(markdownObject)) {
- //set keys and markdown itself
- currentPattern.patternDescExists = true;
- currentPattern.patternDesc = markdownObject.markdown;
-
- //consider looping through all keys eventually. would need to blacklist some properties and whitelist others
- if (markdownObject.state) {
- currentPattern.patternState = markdownObject.state;
- }
- if (markdownObject.order) {
- currentPattern.order = markdownObject.order;
- }
- if (markdownObject.hidden) {
- currentPattern.hidden = markdownObject.hidden;
- }
- if (markdownObject.excludeFromStyleguide) {
- currentPattern.excludeFromStyleguide = markdownObject.excludeFromStyleguide;
- }
- if (markdownObject.tags) {
- currentPattern.tags = markdownObject.tags;
- }
- if (markdownObject.links) {
- currentPattern.links = markdownObject.links;
- }
- } else {
- if (patternlab.config.debug) {
- console.log('error processing markdown for ' + currentPattern.patternPartial);
- }
- }
-
- if (patternlab.config.debug) {
- console.log('found pattern-specific markdown for ' + currentPattern.patternPartial);
- }
- }
- catch (err) {
- // do nothing when file not found
- if (err.code !== 'ENOENT') {
- console.log('there was an error setting pattern keys after markdown parsing of the companion file for pattern ' + currentPattern.patternPartial);
- console.log(err);
- }
- }
- }
-
- /**
- * A helper that unravels a pattern looking for partials or listitems to unravel.
- * The goal is really to convert pattern.template into pattern.extendedTemplate
- * @param pattern - the pattern to decompose
- * @param patternlab - global data store
- * @param ignoreLineage - whether or not to hunt for lineage for this pattern
- */
- function decomposePattern(pattern, patternlab, ignoreLineage) {
-
- var lineage_hunter = new lh(),
- list_item_hunter = new lih();
-
- pattern.extendedTemplate = pattern.template;
-
- //find how many partials there may be for the given pattern
- var foundPatternPartials = pattern.findPartials();
-
- //find any listItem blocks that within the pattern, even if there are no partials
- list_item_hunter.process_list_item_partials(pattern, patternlab);
-
- // expand any partials present in this pattern; that is, drill down into
- // the template and replace their calls in this template with rendered
- // results
-
- if (pattern.engine.expandPartials && (foundPatternPartials !== null && foundPatternPartials.length > 0)) {
- // eslint-disable-next-line
- expandPartials(foundPatternPartials, list_item_hunter, patternlab, pattern);
-
- // update the extendedTemplate in the partials object in case this
- // pattern is consumed later
- patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate;
- }
-
- //find pattern lineage
- if (!ignoreLineage) {
- lineage_hunter.find_lineage(pattern, patternlab);
- }
-
- //add to patternlab object so we can look these up later.
- addPattern(pattern, patternlab);
- }
-
- function processPatternIterative(relPath, patternlab) {
-
- var relativeDepth = relPath.match(/\w(?=\\)|\w(?=\/)/g || []).length;
- if (relativeDepth > 2) {
- console.log('');
- plutils.logOrange('Warning:');
- plutils.logOrange('A pattern file: ' + relPath + ' was found greater than 2 levels deep from ' + patternlab.config.paths.source.patterns + '.');
- plutils.logOrange('It\'s strongly suggested to not deviate from the following structure under _patterns/');
- plutils.logOrange('[patternType]/[patternSubtype]/[patternName].[patternExtension]');
- console.log('');
- plutils.logOrange('While Pattern Lab may still function, assets may 404 and frontend links may break. Consider yourself warned. ');
- plutils.logOrange('Read More: http://patternlab.io/docs/pattern-organization.html');
- console.log('');
- }
-
- //check if the found file is a top-level markdown file
- var fileObject = path.parse(relPath);
- if (fileObject.ext === '.md') {
- try {
- var proposedDirectory = path.resolve(patternlab.config.paths.source.patterns, fileObject.dir, fileObject.name);
- var proposedDirectoryStats = fs.statSync(proposedDirectory);
- if (proposedDirectoryStats.isDirectory()) {
- var subTypeMarkdownFileContents = fs.readFileSync(proposedDirectory + '.md', 'utf8');
- var subTypeMarkdown = markdown_parser.parse(subTypeMarkdownFileContents);
- var subTypePattern = new Pattern(relPath, null, patternlab);
- subTypePattern.patternSectionSubtype = true;
- subTypePattern.patternLink = subTypePattern.name + '/index.html';
- subTypePattern.patternDesc = subTypeMarkdown.markdown;
- subTypePattern.flatPatternPath = subTypePattern.flatPatternPath + '-' + subTypePattern.fileName;
- subTypePattern.isPattern = false;
- subTypePattern.engine = null;
-
- addSubtypePattern(subTypePattern, patternlab);
- return subTypePattern;
- }
- } catch (err) {
- // no file exists, meaning it's a pattern markdown file
- if (err.code !== 'ENOENT') {
- console.log(err);
- }
- }
-
- }
-
- var pseudopattern_hunter = new pph();
-
- //extract some information
- var filename = fileObject.base;
- var ext = fileObject.ext;
- var patternsPath = patternlab.config.paths.source.patterns;
-
- // skip non-pattern files
- if (!patternEngines.isPatternFile(filename, patternlab)) { return null; }
-
- //make a new Pattern Object
- var currentPattern = new Pattern(relPath, null, patternlab);
-
- //if file is named in the syntax for variants
- if (patternEngines.isPseudoPatternJSON(filename)) {
- return currentPattern;
- }
-
- //can ignore all non-supported files at this point
- if (patternEngines.isFileExtensionSupported(ext) === false) {
- return currentPattern;
- }
-
- //see if this file has a state
- setState(currentPattern, patternlab, true);
-
- //look for a json file for this template
- try {
- var jsonFilename = path.resolve(patternsPath, currentPattern.subdir, currentPattern.fileName + ".json");
- try {
- var jsonFilenameStats = fs.statSync(jsonFilename);
- } catch (err) {
- //not a file
- }
- if (jsonFilenameStats && jsonFilenameStats.isFile()) {
- currentPattern.jsonFileData = fs.readJSONSync(jsonFilename);
- if (patternlab.config.debug) {
- console.log('processPatternIterative: found pattern-specific data.json for ' + currentPattern.patternPartial);
- }
- }
- }
- catch (err) {
- console.log('There was an error parsing sibling JSON for ' + currentPattern.relPath);
- console.log(err);
- }
-
- //look for a listitems.json file for this template
- try {
- var listJsonFileName = path.resolve(patternsPath, currentPattern.subdir, currentPattern.fileName + ".listitems.json");
- try {
- var listJsonFileStats = fs.statSync(listJsonFileName);
- } catch (err) {
- //not a file
- }
- if (listJsonFileStats && listJsonFileStats.isFile()) {
- currentPattern.listitems = fs.readJSONSync(listJsonFileName);
- buildListItems(currentPattern);
- if (patternlab.config.debug) {
- console.log('found pattern-specific listitems.json for ' + currentPattern.patternPartial);
- }
- }
- }
- catch (err) {
- console.log('There was an error parsing sibling listitem JSON for ' + currentPattern.relPath);
- console.log(err);
- }
-
- //look for a markdown file for this template
- parsePatternMarkdown(currentPattern, patternlab);
-
- //add the raw template to memory
- currentPattern.template = fs.readFileSync(path.resolve(patternsPath, relPath), 'utf8');
-
- //find any stylemodifiers that may be in the current pattern
- currentPattern.stylePartials = currentPattern.findPartialsWithStyleModifiers();
-
- //find any pattern parameters that may be in the current pattern
- currentPattern.parameteredPartials = currentPattern.findPartialsWithPatternParameters();
-
- //add currentPattern to patternlab.patterns array
- addPattern(currentPattern, patternlab);
-
- //look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json
- pseudopattern_hunter.find_pseudopatterns(currentPattern, patternlab);
-
- return currentPattern;
- }
-
- function processPatternRecursive(file, patternlab) {
-
- //find current pattern in patternlab object using var file as a partial
- var currentPattern, i;
-
- for (i = 0; i < patternlab.patterns.length; i++) {
- if (patternlab.patterns[i].relPath === file) {
- currentPattern = patternlab.patterns[i];
- }
- }
-
- //return if processing an ignored file
- if (typeof currentPattern === 'undefined') { return; }
-
- //we are processing a markdown only pattern
- if (currentPattern.engine === null) { return; }
-
- //call our helper method to actually unravel the pattern with any partials
- decomposePattern(currentPattern, patternlab);
- }
-
- function expandPartials(foundPatternPartials, list_item_hunter, patternlab, currentPattern) {
-
- var style_modifier_hunter = new smh(),
- parameter_hunter = new ph();
-
- if (patternlab.config.debug) {
- console.log('found partials for ' + currentPattern.patternPartial);
- }
-
- // determine if the template contains any pattern parameters. if so they
- // must be immediately consumed
- parameter_hunter.find_parameters(currentPattern, patternlab);
-
- //do something with the regular old partials
- for (var i = 0; i < foundPatternPartials.length; i++) {
- var partial = currentPattern.findPartial(foundPatternPartials[i]);
- var partialPath;
-
- //identify which pattern this partial corresponds to
- for (var j = 0; j < patternlab.patterns.length; j++) {
- if (patternlab.patterns[j].patternPartial === partial ||
- patternlab.patterns[j].relPath.indexOf(partial) > -1)
- {
- partialPath = patternlab.patterns[j].relPath;
- }
- }
-
- //recurse through nested partials to fill out this extended template.
- processPatternRecursive(partialPath, patternlab);
-
- //complete assembly of extended template
- //create a copy of the partial so as to not pollute it after the getPartial call.
- var partialPattern = getPartial(partial, patternlab);
- var cleanPartialPattern = JSON5.parse(JSON5.stringify(partialPattern));
-
- //if partial has style modifier data, replace the styleModifier value
- if (currentPattern.stylePartials && currentPattern.stylePartials.length > 0) {
- style_modifier_hunter.consume_style_modifier(cleanPartialPattern, foundPatternPartials[i], patternlab);
- }
-
- currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(foundPatternPartials[i], cleanPartialPattern.extendedTemplate);
- }
- }
-
- function parseDataLinksHelper(patternlab, obj, key) {
- var linkRE, dataObjAsString, linkMatches;
-
- //check for link.patternPartial
- linkRE = /link\.[A-z0-9-_]+/g;
-
- //stringify the passed in object
- dataObjAsString = JSON5.stringify(obj);
- if (!dataObjAsString) { return obj; }
-
- //find matches
- linkMatches = dataObjAsString.match(linkRE);
-
- if (linkMatches) {
- for (var i = 0; i < linkMatches.length; i++) {
- var dataLink = linkMatches[i];
- if (dataLink && dataLink.split('.').length >= 2) {
-
- //get the partial the link refers to
- var linkPatternPartial = dataLink.split('.')[1];
- var pattern = getPartial(linkPatternPartial, patternlab);
- if (pattern !== undefined) {
-
- //get the full built link and replace it
- var fullLink = patternlab.data.link[linkPatternPartial];
- if (fullLink) {
- fullLink = path.normalize(fullLink).replace(/\\/g, '/');
- if (patternlab.config.debug) {
- console.log('expanded data link from ' + dataLink + ' to ' + fullLink + ' inside ' + key);
- }
-
- //also make sure our global replace didn't mess up a protocol
- fullLink = fullLink.replace(/:\//g, '://');
- dataObjAsString = dataObjAsString.replace(dataLink, fullLink);
- }
- } else {
- if (patternlab.config.debug) {
- console.log('pattern not found for', dataLink, 'inside', key);
- }
- }
- }
- }
- }
-
- var dataObj;
- try {
- dataObj = JSON5.parse(dataObjAsString);
- } catch (err) {
- console.log('There was an error parsing JSON for ' + key);
- console.log(err);
- }
-
- return dataObj;
- }
-
- //look for pattern links included in data files.
- //these will be in the form of link.* WITHOUT {{}}, which would still be there from direct pattern inclusion
- function parseDataLinks(patternlab) {
- //look for link.* such as link.pages-blog as a value
-
- patternlab.data = parseDataLinksHelper(patternlab, patternlab.data, 'data.json');
-
- //loop through all patterns
- for (var i = 0; i < patternlab.patterns.length; i++) {
- patternlab.patterns[i].jsonFileData = parseDataLinksHelper(patternlab, patternlab.patterns[i].jsonFileData, patternlab.patterns[i].patternPartial);
- }
- }
-
- return {
- find_pattern_partials: function (pattern) {
- return pattern.findPartials();
- },
- find_pattern_partials_with_style_modifiers: function (pattern) {
- return pattern.findPartialsWithStyleModifiers();
- },
- find_pattern_partials_with_parameters: function (pattern) {
- return pattern.findPartialsWithPatternParameters();
- },
- find_list_items: function (pattern) {
- return pattern.findListItems();
- },
- setPatternState: function (pattern, patternlab, displayDeprecatedWarning) {
- setState(pattern, patternlab, displayDeprecatedWarning);
- },
- addPattern: function (pattern, patternlab) {
- addPattern(pattern, patternlab);
- },
- addSubtypePattern: function (subtypePattern, patternlab) {
- addSubtypePattern(subtypePattern, patternlab);
- },
- decomposePattern: function (pattern, patternlab, ignoreLineage) {
- decomposePattern(pattern, patternlab, ignoreLineage);
- },
- renderPattern: function (template, data, partials) {
- return renderPattern(template, data, partials);
- },
- process_pattern_iterative: function (file, patternlab) {
- return processPatternIterative(file, patternlab);
- },
- process_pattern_recursive: function (file, patternlab, additionalData) {
- processPatternRecursive(file, patternlab, additionalData);
- },
- getPartial: function (partial, patternlab) {
- return getPartial(partial, patternlab);
- },
- combine_listItems: function (patternlab) {
- buildListItems(patternlab);
- },
- parse_data_links: function (patternlab) {
- parseDataLinks(patternlab);
- },
- parse_data_links_specific: function (patternlab, data, label) {
- return parseDataLinksHelper(patternlab, data, label);
- },
- parse_pattern_markdown: function (pattern, patternlab) {
- parsePatternMarkdown(pattern, patternlab);
- }
- };
-
-};
-
-module.exports = pattern_assembler;
diff --git a/core/lib/pattern_engines.js b/core/lib/pattern_engines.js
deleted file mode 100644
index 451f8d4fe..000000000
--- a/core/lib/pattern_engines.js
+++ /dev/null
@@ -1,188 +0,0 @@
-// special shoutout to Geoffrey Pursell for single-handedly making Pattern Lab Node Pattern Engines possible!
-'use strict';
-
-var path = require('path');
-var diveSync = require('diveSync');
-var engineMatcher = /^patternengine-node-(.*)$/;
-var enginesDirectories = [
- {
- displayName: 'the core',
- path: path.resolve(__dirname, '..', '..', 'node_modules')
- },
- {
- displayName: 'the edition or test directory',
- path: path.join(process.cwd(), 'node_modules')
- }
-];
-var PatternEngines; // the main export object
-var engineNameForExtension; // generated mapping of extension to engine name
-
-
-// free "private" functions, for internal setup only
-
-// given a path: return the engine name if the path points to a valid engine
-// module directory, or false if it doesn't
-function isEngineModule(filePath) {
- var baseName = path.basename(filePath);
- var engineMatch = baseName.match(engineMatcher);
-
- if (engineMatch) { return engineMatch[1]; }
- return false;
-}
-
-function findEngineModulesInDirectory(dir) {
- var foundEngines = [];
-
- diveSync(dir, {
- recursive: false,
- directories: true
- }, function (err, filePath) {
- if (err) { throw err; }
- var foundEngineName = isEngineModule(filePath);
- if (foundEngineName) {
- foundEngines.push({
- name: foundEngineName,
- modulePath: filePath
- });
- }
- });
-
- return foundEngines;
-}
-
-// Try to load engines! We scan for engines at each path specified above. This
-// function is kind of a big deal.
-function loadAllEngines(enginesObject) {
- console.log('\nLoading engines...');
-
- enginesDirectories.forEach(function (engineDirectory) {
- var enginesInThisDir = findEngineModulesInDirectory(engineDirectory.path);
- console.log("...scanning for engines in", engineDirectory.displayName + "...");
-
- // find all engine-named things in this directory and try to load them,
- // unless it's already been loaded.
- enginesInThisDir.forEach(function (engineDiscovery) {
- var errorMessage;
- var successMessage = "good to go";
-
- try {
- // give it a try! load 'er up. But not if we already have, of course.
- if (enginesObject[engineDiscovery.name]) {
- throw new Error("already loaded, skipping.");
- }
- enginesObject[engineDiscovery.name] = require(engineDiscovery.modulePath);
- } catch (err) {
- errorMessage = err.message;
- } finally {
- // report on the status of the engine, one way or another!
- console.log('-', engineDiscovery.name, 'engine:', errorMessage ? errorMessage : successMessage);
- }
- });
- });
-
- // Complain if for some reason we haven't loaded any engines.
- if (Object.keys(enginesObject).length === 0) {
- throw new Error('No engines loaded! Something is seriously wrong.');
- }
- console.log('...done loading engines.\n');
-}
-
-
-// produce a mapping between file extension and engine name for each of the
-// loaded engines
-function createFileExtensionToEngineNameMap(enginesObject) {
- var mapping = {};
-
- Object.keys(enginesObject).forEach(function (engineName) {
- var extensionForEngine = enginesObject[engineName].engineFileExtension;
- mapping[extensionForEngine] = engineName;
- });
-
- return mapping;
-}
-
-
-//
-// PatternEngines: the main export of this module
-//
-// It's an Object/hash of all loaded pattern engines, empty at first. My
-// intention here is to make this return an object that can be used to obtain
-// any loaded PatternEngine by addressing them like this:
-//
-// var PatternEngines = require('./pattern_engines/pattern_engines');
-// var Mustache = PatternEngines['mustache'];
-//
-// Object.create lets us create an object with a specified prototype. We want
-// this here because we would like the object's "own properties" to include
-// only the engine names so we can easily iterate over them; all the handy
-// methods and properites below should therefore be on its prototype.
-
-PatternEngines = Object.create({
- getEngineNameForPattern: function (pattern) {
- // avoid circular dependency by putting this in here. TODO: is this slow?
- var of = require('./object_factory');
-
- if (pattern instanceof of.Pattern && typeof pattern.fileExtension === 'string' && pattern.fileExtension) {
- return engineNameForExtension[pattern.fileExtension];
- }
-
- // otherwise, assume it's a plain mustache template string and act
- // accordingly
- return 'mustache';
- },
-
- getEngineForPattern: function (pattern) {
- if (pattern.isPseudoPattern) {
- return this.getEngineForPattern(pattern.basePattern);
- } else {
- var engineName = this.getEngineNameForPattern(pattern);
- return this[engineName];
- }
- },
-
- getSupportedFileExtensions: function () {
- var engineNames = Object.keys(PatternEngines);
- return engineNames.map(function (engineName) {
- return PatternEngines[engineName].engineFileExtension;
- });
- },
-
- isFileExtensionSupported: function (fileExtension) {
- var supportedExtensions = PatternEngines.getSupportedFileExtensions();
- return (supportedExtensions.lastIndexOf(fileExtension) !== -1);
- },
-
- // given a filename, return a boolean: whether or not the filename indicates
- // that the file is pseudopattern JSON
- isPseudoPatternJSON: function (filename) {
- var extension = path.extname(filename);
- return (extension === '.json' && filename.indexOf('~') > -1);
- },
-
- // takes a filename string, not a full path; a basename (plus extension)
- // ignore _underscored patterns, dotfiles, and anything not recognized by a
- // loaded pattern engine. Pseudo-pattern .json files ARE considered to be
- // pattern files!
- isPatternFile: function (filename) {
- // skip hidden patterns/files without a second thought
- var extension = path.extname(filename);
- if (filename.charAt(0) === '.' ||
- (extension === '.json' && !PatternEngines.isPseudoPatternJSON(filename))) {
- return false;
- }
-
- // not a hidden pattern, let's dig deeper
- var supportedPatternFileExtensions = PatternEngines.getSupportedFileExtensions();
- return (supportedPatternFileExtensions.lastIndexOf(extension) !== -1 ||
- PatternEngines.isPseudoPatternJSON(filename));
- }
-});
-
-
-// load up the engines we found
-loadAllEngines(PatternEngines);
-
-// mapping of file extensions to engine names, for lookup use
-engineNameForExtension = createFileExtensionToEngineNameMap(PatternEngines);
-
-module.exports = PatternEngines;
diff --git a/core/lib/pattern_exporter.js b/core/lib/pattern_exporter.js
deleted file mode 100644
index c53768420..000000000
--- a/core/lib/pattern_exporter.js
+++ /dev/null
@@ -1,30 +0,0 @@
-"use strict";
-
-var fs = require('fs-extra');
-
-var pattern_exporter = function () {
-
- function exportPatterns(patternlab) {
- //read the config export options
- var exportPartials = patternlab.config.patternExportPatternPartials;
-
- //find the chosen patterns to export
- for (var i = 0; i < exportPartials.length; i++) {
- for (var j = 0; j < patternlab.patterns.length; j++) {
- if (exportPartials[i] === patternlab.patterns[j].patternPartial) {
- //write matches to the desired location
- fs.outputFileSync(patternlab.config.patternExportDirectory + patternlab.patterns[j].patternPartial + '.html', patternlab.patterns[j].patternPartialCode);
- }
- }
- }
- }
-
- return {
- export_patterns: function (patternlab) {
- exportPatterns(patternlab);
- }
- };
-
-};
-
-module.exports = pattern_exporter;
diff --git a/core/lib/patternlab.js b/core/lib/patternlab.js
deleted file mode 100644
index 15da30ce8..000000000
--- a/core/lib/patternlab.js
+++ /dev/null
@@ -1,499 +0,0 @@
-/*
- * patternlab-node - v2.6.0-alpha - 2016
- *
- * Brian Muenzenmeyer, Geoff Pursell, and the web community.
- * Licensed under the MIT license.
- *
- * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice.
- *
- */
-
-"use strict";
-
-var diveSync = require('diveSync'),
- glob = require('glob'),
- _ = require('lodash'),
- path = require('path'),
- cleanHtml = require('js-beautify').html,
- inherits = require('util').inherits,
- pm = require('./plugin_manager'),
- plutils = require('./utilities');
-
-var EventEmitter = require('events').EventEmitter;
-
-function buildPatternData(dataFilesPath, fs) {
- var dataFiles = glob.sync(dataFilesPath + '*.json', {"ignore" : [dataFilesPath + 'listitems.json']});
- var mergeObject = {};
- dataFiles.forEach(function (filePath) {
- var jsonData = fs.readJSONSync(path.resolve(filePath), 'utf8');
- mergeObject = _.merge(mergeObject, jsonData);
- });
- return mergeObject;
-}
-
-// GTP: these two diveSync pattern processors factored out so they can be reused
-// from unit tests to reduce code dupe!
-function processAllPatternsIterative(pattern_assembler, patterns_dir, patternlab) {
- diveSync(
- patterns_dir,
- function (err, file) {
- //log any errors
- if (err) {
- console.log(err);
- return;
- }
- pattern_assembler.process_pattern_iterative(path.relative(patterns_dir, file), patternlab);
- }
- );
-}
-
-function processAllPatternsRecursive(pattern_assembler, patterns_dir, patternlab) {
- diveSync(
- patterns_dir,
- function (err, file) {
- //log any errors
- if (err) {
- console.log(err);
- return;
- }
- pattern_assembler.process_pattern_recursive(path.relative(patterns_dir, file), patternlab);
- }
- );
-}
-
-function checkConfiguration(patternlab) {
- //default the output suffixes if not present
- var outputFileSuffixes = {
- rendered: '.rendered',
- rawTemplate: '',
- markupOnly: '.markup-only'
- };
-
- if (!patternlab.config.outputFileSuffixes) {
- plutils.logOrange('Configuration Object "outputFileSuffixes" not found, and defaulted to the following:');
- console.log(outputFileSuffixes);
- plutils.logOrange('Since Pattern Lab Core 2.3.0 this configuration option is required. Suggest you add it to your patternlab-config.json file.');
- console.log();
- }
- patternlab.config.outputFileSuffixes = _.extend(outputFileSuffixes, patternlab.config.outputFileSuffixes);
-}
-
-/**
- * Finds and calls the main method of any found plugins.
- * @param patternlab - global data store
- */
-function initializePlugins(patternlab) {
- var plugin_manager = new pm(patternlab.config, path.resolve(__dirname, '../../patternlab-config.json'));
- var foundPlugins = plugin_manager.detect_plugins();
-
- if (foundPlugins && foundPlugins.length > 0) {
-
- for (var i = 0; i < foundPlugins.length; i++) {
- var plugin = plugin_manager.load_plugin(foundPlugins[i]);
- plugin(patternlab);
- }
- }
-}
-
-function PatternLabEventEmitter() {
- EventEmitter.call(this);
-}
-inherits(PatternLabEventEmitter, EventEmitter);
-
-var patternlab_engine = function (config) {
- 'use strict';
-
- var JSON5 = require('json5'),
- fs = require('fs-extra'),
- pa = require('./pattern_assembler'),
- pe = require('./pattern_exporter'),
- lh = require('./lineage_hunter'),
- ui = require('./ui_builder'),
- sm = require('./starterkit_manager'),
- Pattern = require('./object_factory').Pattern,
- patternlab = {};
-
- var pattern_assembler = new pa(),
- pattern_exporter = new pe(),
- lineage_hunter = new lh();
-
- patternlab.package = fs.readJSONSync(path.resolve(__dirname, '../../package.json'));
- patternlab.config = config || fs.readJSONSync(path.resolve(__dirname, '../../patternlab-config.json'));
- patternlab.events = new PatternLabEventEmitter();
-
- checkConfiguration(patternlab);
-
- //todo: determine if this is the best place to wire up plugins
- initializePlugins(patternlab);
-
- var paths = patternlab.config.paths;
-
- function getVersion() {
- console.log(patternlab.package.version);
- }
-
- function help() {
-
- console.log('');
-
- console.log('|=======================================|');
- plutils.logGreen(' Pattern Lab Node Help v' + patternlab.package.version);
- console.log('|=======================================|');
-
- console.log('');
- console.log('Command Line Interface - usually consumed by an edition');
- console.log('');
-
- plutils.logGreen(' patternlab:build');
- console.log(' > Compiles the patterns and frontend, outputting to config.paths.public');
- console.log('');
-
- plutils.logGreen(' patternlab:patternsonly');
- console.log(' > Compiles the patterns only, outputting to config.paths.public');
- console.log('');
-
- plutils.logGreen(' patternlab:version');
- console.log(' > Return the version of patternlab-node you have installed');
- console.log('');
-
- plutils.logGreen(' patternlab:help');
- console.log(' > Get more information about patternlab-node, pattern lab in general, and where to report issues.');
- console.log('');
-
- plutils.logGreen(' patternlab:liststarterkits');
- console.log(' > Returns a url with the list of available starterkits hosted on the Pattern Lab organization Github account');
- console.log('');
-
- plutils.logGreen(' patternlab:loadstarterkit');
- console.log(' > Load a starterkit into config.paths.source/*');
- console.log(' > NOTE: Overwrites existing content, and only cleans out existing directory if --clean=true argument is passed.');
- console.log(' > NOTE: In most cases, `npm install starterkit-name` will precede this call.');
- console.log(' > arguments:');
- console.log(' -- kit ');
- console.log(' > the name of the starter kit to load');
- console.log(' -- clean ');
- console.log(' > removes all files from config.paths.source/ prior to load');
- console.log(' > example (gulp):');
- console.log(' `gulp patternlab:loadstarterkit --kit=starterkit-mustache-demo`');
- console.log('');
-
- console.log('===============================');
- console.log('');
- console.log('Visit http://patternlab.io/ for more info about Pattern Lab');
- console.log('Visit https://github.com/pattern-lab/patternlab-node/issues to open an issue.');
- console.log('Visit https://github.com/pattern-lab/patternlab-node/wiki to view the changelog, roadmap, and other info.');
- console.log('');
- console.log('===============================');
- }
-
- function printDebug() {
- // A replacer function to pass to stringify below; this is here to prevent
- // the debug output from blowing up into a massive fireball of circular
- // references. This happens specifically with the Handlebars engine. Remove
- // if you like 180MB log files.
- function propertyStringReplacer(key, value) {
- if (key === 'engine' && value && value.engineName) {
- return '{' + value.engineName + ' engine object}';
- }
- return value;
- }
-
- //debug file can be written by setting flag on patternlab-config.json
- if (patternlab.config.debug) {
- console.log('writing patternlab debug file to ./patternlab.json');
- fs.outputFileSync('./patternlab.json', JSON.stringify(patternlab, propertyStringReplacer, 3));
- }
- }
-
- function setCacheBust() {
- if (patternlab.config.cacheBust) {
- if (patternlab.config.debug) {
- console.log('setting cacheBuster value for frontend assets.');
- }
- patternlab.cacheBuster = new Date().getTime();
- } else {
- patternlab.cacheBuster = 0;
- }
- }
-
- function listStarterkits() {
- var starterkit_manager = new sm(patternlab.config);
- return starterkit_manager.list_starterkits();
- }
-
- function loadStarterKit(starterkitName, clean) {
- var starterkit_manager = new sm(patternlab.config);
- starterkit_manager.load_starterkit(starterkitName, clean);
- }
-
- /**
- * Process the user-defined pattern head and prepare it for rendering
- */
- function processHeadPattern() {
- try {
- var headPath = path.resolve(paths.source.meta, '_00-head.mustache');
- var headPattern = new Pattern(headPath, null, patternlab);
- headPattern.template = fs.readFileSync(headPath, 'utf8');
- headPattern.isPattern = false;
- headPattern.isMetaPattern = true;
- pattern_assembler.decomposePattern(headPattern, patternlab, true);
- patternlab.userHead = headPattern.extendedTemplate;
- }
- catch (ex) {
- plutils.logRed('\nWARNING: Could not find the user-editable header template, currently configured to be at ' + path.join(config.paths.source.meta, '_00-head.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n');
- if (patternlab.config.debug) { console.log(ex); }
- process.exit(1);
- }
- }
-
- /**
- * Process the user-defined pattern footer and prepare it for rendering
- */
- function processFootPattern() {
- try {
- var footPath = path.resolve(paths.source.meta, '_01-foot.mustache');
- var footPattern = new Pattern(footPath, null, patternlab);
- footPattern.template = fs.readFileSync(footPath, 'utf8');
- footPattern.isPattern = false;
- footPattern.isMetaPattern = true;
- pattern_assembler.decomposePattern(footPattern, patternlab, true);
- patternlab.userFoot = footPattern.extendedTemplate;
- }
- catch (ex) {
- plutils.logRed('\nWARNING: Could not find the user-editable footer template, currently configured to be at ' + path.join(config.paths.source.meta, '_01-foot.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n');
- if (patternlab.config.debug) { console.log(ex); }
- process.exit(1);
- }
- }
-
- function buildPatterns(deletePatternDir) {
-
- patternlab.events.emit('patternlab-build-pattern-start', patternlab);
-
- try {
- patternlab.data = buildPatternData(paths.source.data, fs);
- } catch (ex) {
- plutils.logRed('missing or malformed' + paths.source.data + 'data.json Pattern Lab may not work without this file.');
- patternlab.data = {};
- }
- try {
- patternlab.listitems = fs.readJSONSync(path.resolve(paths.source.data, 'listitems.json'));
- } catch (ex) {
- plutils.logOrange('WARNING: missing or malformed ' + paths.source.data + 'listitems.json file. Pattern Lab may not work without this file.');
- patternlab.listitems = {};
- }
- try {
- patternlab.header = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials', 'general-header.mustache'), 'utf8');
- patternlab.footer = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials', 'general-footer.mustache'), 'utf8');
- patternlab.patternSection = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials', 'patternSection.mustache'), 'utf8');
- patternlab.patternSectionSubType = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials', 'patternSectionSubtype.mustache'), 'utf8');
- patternlab.viewAll = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'viewall.mustache'), 'utf8');
- } catch (ex) {
- console.log(ex);
- plutils.logRed('\nERROR: missing an essential file from ' + paths.source.patternlabFiles + '. Pattern Lab won\'t work without this file.\n');
- process.exit(1);
- }
- patternlab.patterns = [];
- patternlab.subtypePatterns = {};
- patternlab.partials = {};
- patternlab.data.link = {};
-
- setCacheBust();
-
- pattern_assembler.combine_listItems(patternlab);
-
- patternlab.events.emit('patternlab-build-global-data-end', patternlab);
-
- // diveSync once to perform iterative populating of patternlab object
- processAllPatternsIterative(pattern_assembler, paths.source.patterns, patternlab);
-
- patternlab.events.emit('patternlab-pattern-iteration-end', patternlab);
-
- //diveSync again to recursively include partials, filling out the
- //extendedTemplate property of the patternlab.patterns elements
- processAllPatternsRecursive(pattern_assembler, paths.source.patterns, patternlab);
-
- //take the user defined head and foot and process any data and patterns that apply
- processHeadPattern();
- processFootPattern();
-
- //now that all the main patterns are known, look for any links that might be within data and expand them
- //we need to do this before expanding patterns & partials into extendedTemplates, otherwise we could lose the data -> partial reference
- pattern_assembler.parse_data_links(patternlab);
-
- //cascade any patternStates
- lineage_hunter.cascade_pattern_states(patternlab);
-
- //delete the contents of config.patterns.public before writing
- if (deletePatternDir) {
- fs.removeSync(paths.public.patterns);
- fs.emptyDirSync(paths.public.patterns);
- }
-
- //set pattern-specific header if necessary
- var head;
- if (patternlab.userHead) {
- head = patternlab.userHead;
- } else {
- head = patternlab.header;
- }
-
- //set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header
- patternlab.data.patternLabHead = pattern_assembler.renderPattern(patternlab.header, {
- cacheBuster: patternlab.cacheBuster
- });
-
- //render all patterns last, so lineageR works
- patternlab.patterns.forEach(function (pattern) {
-
- if (!pattern.isPattern) {
- return false;
- }
-
- //todo move this into lineage_hunter
- pattern.patternLineages = pattern.lineage;
- pattern.patternLineageExists = pattern.lineage.length > 0;
- pattern.patternLineagesR = pattern.lineageR;
- pattern.patternLineageRExists = pattern.lineageR.length > 0;
- pattern.patternLineageEExists = pattern.patternLineageExists || pattern.patternLineageRExists;
-
- //render the pattern, but first consolidate any data we may have
- var allData;
- try {
- allData = JSON5.parse(JSON5.stringify(patternlab.data));
- } catch (err) {
- console.log('There was an error parsing JSON for ' + pattern.relPath);
- console.log(err);
- }
- allData = plutils.mergeData(allData, pattern.jsonFileData);
- allData.cacheBuster = patternlab.cacheBuster;
-
- //re-rendering the headHTML each time allows pattern-specific data to influence the head of the pattern
- pattern.header = head;
- var headHTML = pattern_assembler.renderPattern(pattern.header, allData);
-
- //render the extendedTemplate with all data
- pattern.patternPartialCode = pattern_assembler.renderPattern(pattern, allData);
-
- // stringify this data for individual pattern rendering and use on the styleguide
- // see if patternData really needs these other duped values
- pattern.patternData = JSON.stringify({
- cssEnabled: false,
- patternLineageExists: pattern.patternLineageExists,
- patternLineages: pattern.patternLineages,
- lineage: pattern.patternLineages,
- patternLineageRExists: pattern.patternLineageRExists,
- patternLineagesR: pattern.patternLineagesR,
- lineageR: pattern.patternLineagesR,
- patternLineageEExists: pattern.patternLineageExists || pattern.patternLineageRExists,
- patternDesc: pattern.patternDescExists ? pattern.patternDesc : '',
- patternBreadcrumb:
- pattern.patternGroup === pattern.patternSubGroup ?
- {
- patternType: pattern.patternGroup
- } : {
- patternType: pattern.patternGroup,
- patternSubtype: pattern.patternSubGroup
- },
- patternExtension: pattern.fileExtension.substr(1), //remove the dot because styleguide asset default adds it for us
- patternName: pattern.patternName,
- patternPartial: pattern.patternPartial,
- patternState: pattern.patternState,
- patternEngineName: pattern.engine.engineName,
- extraOutput: {}
- });
-
- //set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer
- var footerPartial = pattern_assembler.renderPattern(patternlab.footer, {
- isPattern: pattern.isPattern,
- patternData: pattern.patternData,
- cacheBuster: patternlab.cacheBuster
- });
-
- var allFooterData;
- try {
- allFooterData = JSON5.parse(JSON5.stringify(patternlab.data));
- } catch (err) {
- console.log('There was an error parsing JSON for ' + pattern.relPath);
- console.log(err);
- }
- allFooterData = plutils.mergeData(allFooterData, pattern.jsonFileData);
- allFooterData.patternLabFoot = footerPartial;
-
- var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, allFooterData);
-
- patternlab.events.emit('patternlab-pattern-write-begin', patternlab, pattern);
-
- //write the compiled template to the public patterns directory
- var patternPage = headHTML + pattern.patternPartialCode + footerHTML;
-
- //beautify the output if configured to do so
- var cleanedPatternPage = config.cleanOutputHtml ? cleanHtml(patternPage, {indent_size: 2}) : patternPage;
- var cleanedPatternPartialCode = config.cleanOutputHtml ? cleanHtml(pattern.patternPartialCode, {indent_size: 2}) : pattern.patternPartialCode;
- var cleanedPatternTemplateCode = config.cleanOutputHtml ? cleanHtml(pattern.template, {indent_size: 2}) : pattern.template;
-
- //write the compiled template to the public patterns directory
- fs.outputFileSync(paths.public.patterns + pattern.getPatternLink(patternlab, 'rendered'), cleanedPatternPage);
-
- //write the mustache file too
- fs.outputFileSync(paths.public.patterns + pattern.getPatternLink(patternlab, 'rawTemplate'), cleanedPatternTemplateCode);
-
- //write the encoded version too
- fs.outputFileSync(paths.public.patterns + pattern.getPatternLink(patternlab, 'markupOnly'), cleanedPatternPartialCode);
-
- patternlab.events.emit('patternlab-pattern-write-end', patternlab, pattern);
-
- return true;
- });
-
- //export patterns if necessary
- pattern_exporter.export_patterns(patternlab);
- }
-
- return {
- version: function () {
- return getVersion();
- },
- build: function (callback, deletePatternDir) {
- if (patternlab && patternlab.isBusy) {
- console.log('Pattern Lab is busy building a previous run - returning early.');
- return;
- }
- patternlab.isBusy = true;
- buildPatterns(deletePatternDir);
- new ui().buildFrontend(patternlab);
- printDebug();
- patternlab.isBusy = false;
- callback();
- },
- help: function () {
- help();
- },
- patternsonly: function (callback, deletePatternDir) {
- if (patternlab && patternlab.isBusy) {
- console.log('Pattern Lab is busy building a previous run - returning early.');
- return;
- }
- patternlab.isBusy = true;
- buildPatterns(deletePatternDir);
- printDebug();
- patternlab.isBusy = false;
- callback();
- },
- liststarterkits: function () {
- return listStarterkits();
- },
- loadstarterkit: function (starterkitName, clean) {
- loadStarterKit(starterkitName, clean);
- }
- };
-};
-
-// export these free functions so they're available without calling the exported
-// function, for use in reducing code dupe in unit tests. At least, until we
-// have a better way to do this
-patternlab_engine.build_pattern_data = buildPatternData;
-patternlab_engine.process_all_patterns_iterative = processAllPatternsIterative;
-patternlab_engine.process_all_patterns_recursive = processAllPatternsRecursive;
-
-module.exports = patternlab_engine;
diff --git a/core/lib/plugin_manager.js b/core/lib/plugin_manager.js
deleted file mode 100644
index fdb331a5b..000000000
--- a/core/lib/plugin_manager.js
+++ /dev/null
@@ -1,79 +0,0 @@
-"use strict";
-
-var plugin_manager = function (config, configPath) {
- var path = require('path'),
- fs = require('fs-extra'),
- util = require('./utilities');
-
- function loadPlugin(pluginName) {
- return require(path.join(process.cwd(), 'node_modules', pluginName));
- }
-
- function installPlugin(pluginName) {
- try {
- var pluginPath = path.resolve(
- path.join(process.cwd(), 'node_modules', pluginName)
- );
- console.log('Attempting to load plugin from', pluginPath);
- try {
- var pluginDirStats = fs.statSync(pluginPath);
- } catch (ex) {
- util.logRed(pluginName + ' not found, please use npm to install it first.');
- util.logRed(pluginName + ' not loaded.');
- return;
- }
- var pluginPathDirExists = pluginDirStats.isDirectory();
- if (pluginPathDirExists) {
-
- //write config entry back
- var diskConfig = fs.readJSONSync(path.resolve(configPath), 'utf8');
- diskConfig[pluginName] = false;
- fs.outputFileSync(path.resolve(configPath), JSON.stringify(diskConfig, null, 2));
-
- util.logGreen('Plugin ' + pluginName + ' installed.');
-
- //todo, tell them how to uninstall or disable
-
- }
- } catch (ex) {
- console.log(ex);
- }
- }
-
- function detectPlugins() {
- var node_modules_path = path.join(process.cwd(), 'node_modules');
- return fs.readdirSync(node_modules_path).filter(function (dir) {
- var module_path = path.join(process.cwd(), 'node_modules', dir);
- return fs.statSync(module_path).isDirectory() && dir.indexOf('plugin-node-') === 0;
- });
- }
-
- function disablePlugin(pluginName) {
- console.log('disablePlugin not implemented yet. No change made to state of plugin', pluginName);
- }
-
- function enablePlugin(pluginName) {
- console.log('enablePlugin not implemented yet. No change made to state of plugin', pluginName);
- }
-
- return {
- install_plugin: function (pluginName) {
- installPlugin(pluginName);
- },
- load_plugin: function (pluginName) {
- return loadPlugin(pluginName);
- },
- detect_plugins: function () {
- return detectPlugins();
- },
- disable_plugin: function (pluginName) {
- disablePlugin(pluginName);
- },
- enable_plugin: function (pluginName) {
- enablePlugin(pluginName);
- }
- };
-
-};
-
-module.exports = plugin_manager;
diff --git a/core/lib/pseudopattern_hunter.js b/core/lib/pseudopattern_hunter.js
deleted file mode 100644
index d695b4f0e..000000000
--- a/core/lib/pseudopattern_hunter.js
+++ /dev/null
@@ -1,81 +0,0 @@
-"use strict";
-
-var pseudopattern_hunter = function () {
-
- function findpseudopatterns(currentPattern, patternlab) {
- var glob = require('glob'),
- fs = require('fs-extra'),
- pa = require('./pattern_assembler'),
- lh = require('./lineage_hunter'),
- Pattern = require('./object_factory').Pattern,
- plutils = require('./utilities'),
- path = require('path');
-
-
- var pattern_assembler = new pa();
- var lineage_hunter = new lh();
- var paths = patternlab.config.paths;
-
- //look for a pseudo pattern by checking if there is a file containing same
- //name, with ~ in it, ending in .json
- var needle = currentPattern.subdir + '/' + currentPattern.fileName + '~*.json';
- var pseudoPatterns = glob.sync(needle, {
- cwd: paths.source.patterns,
- debug: false,
- nodir: true
- });
-
- if (pseudoPatterns.length > 0) {
- for (var i = 0; i < pseudoPatterns.length; i++) {
- if (patternlab.config.debug) {
- console.log('found pseudoPattern variant of ' + currentPattern.patternPartial);
- }
-
- //we want to do everything we normally would here, except instead read the pseudoPattern data
- try {
- var variantFileData = fs.readJSONSync(path.resolve(paths.source.patterns, pseudoPatterns[i]));
- } catch (err) {
- console.log('There was an error parsing pseudopattern JSON for ' + currentPattern.relPath);
- console.log(err);
- }
-
- //extend any existing data with variant data
- variantFileData = plutils.mergeData(currentPattern.jsonFileData, variantFileData);
-
- var variantName = pseudoPatterns[i].substring(pseudoPatterns[i].indexOf('~') + 1).split('.')[0];
- var variantFilePath = path.join(currentPattern.subdir, currentPattern.fileName + '~' + variantName + '.json');
- var patternVariant = Pattern.create(variantFilePath, variantFileData, {
- //use the same template as the non-variant
- template: currentPattern.template,
- fileExtension: currentPattern.fileExtension,
- extendedTemplate: currentPattern.extendedTemplate,
- isPseudoPattern: true,
- basePattern: currentPattern,
- stylePartials: currentPattern.stylePartials,
- parameteredPartials: currentPattern.parameteredPartials,
-
- // use the same template engine as the non-variant
- engine: currentPattern.engine
- }, patternlab);
-
- //process the companion markdown file if it exists
- pattern_assembler.parse_pattern_markdown(patternVariant, patternlab);
-
- //find pattern lineage
- lineage_hunter.find_lineage(patternVariant, patternlab);
-
- //add to patternlab object so we can look these up later.
- pattern_assembler.addPattern(patternVariant, patternlab);
- }
- }
- }
-
- return {
- find_pseudopatterns: function (pattern, patternlab) {
- findpseudopatterns(pattern, patternlab);
- }
- };
-
-};
-
-module.exports = pseudopattern_hunter;
diff --git a/core/lib/starterkit_manager.js b/core/lib/starterkit_manager.js
deleted file mode 100644
index 89ef77ecf..000000000
--- a/core/lib/starterkit_manager.js
+++ /dev/null
@@ -1,106 +0,0 @@
-"use strict";
-
-var starterkit_manager = function (config) {
- var path = require('path'),
- fetch = require('node-fetch'),
- fs = require('fs-extra'),
- util = require('./utilities'),
- paths = config.paths;
-
- function loadStarterKit(starterkitName, clean) {
- try {
- var kitPath = path.resolve(
- path.join(process.cwd(), 'node_modules', starterkitName, config.starterkitSubDir)
- );
- console.log('Attempting to load starterkit from', kitPath);
- try {
- var kitDirStats = fs.statSync(kitPath);
- } catch (ex) {
- util.logRed(starterkitName + ' not found, please use npm to install it first.');
- util.logRed(starterkitName + ' not loaded.');
- return;
- }
- var kitPathDirExists = kitDirStats.isDirectory();
- if (kitPathDirExists) {
-
- if (clean) {
- console.log('Deleting contents of', paths.source.root, 'prior to starterkit load.');
- util.emptyDirectory(paths.source.root);
- } else {
- console.log('Overwriting contents of', paths.source.root, 'during starterkit load.');
- }
-
- fs.copy(kitPath, paths.source.root, function (ex) {
- if (ex) {
- console.error(ex);
- }
- util.logGreen('starterkit ' + starterkitName + ' loaded successfully.');
- });
- }
- } catch (ex) {
- console.log(ex);
- }
- }
-
- /**
- * @func listStarterkits
- * @desc Fetches starterkit repos from GH API that contain 'starterkit' in their name for the user 'pattern-lab'
- * @returns {Promise} Returns an Array<{name,url}> for the starterkit repos
- */
- function listStarterkits() {
- return fetch('https://api.github.com/search/repositories?q=starterkit+in:name+user:pattern-lab&sort=stars&order=desc', {
- method: 'GET',
- headers: {
- 'Accept': 'application/json'
- }
- }).then(function (res) {
- var contentType = res.headers.get('content-type');
- if (contentType && contentType.indexOf('application/json') === -1) {
- throw new TypeError("StarterkitManager->listStarterkits: Not valid JSON");
- }
- return res.json();
- }).then(function (json) {
- if (!json.items || !Array.isArray(json.items)) {
- return false;
- }
- return json.items
- .map(function (repo) {
- return {name: repo.name, url: repo.html_url};
- });
- }).catch(function (err) {
- console.error(err);
- return false;
- });
- }
-
- function packStarterkit() {
-
- }
-
- function detectStarterKits() {
- var node_modules_path = path.join(process.cwd(), 'node_modules');
- var npm_modules = fs.readdirSync(node_modules_path).filter(function (dir) {
- var module_path = path.join(process.cwd(), 'node_modules', dir);
- return fs.statSync(module_path).isDirectory() && dir.indexOf('starterkit-') === 0;
- });
- return npm_modules;
- }
-
- return {
- load_starterkit: function (starterkitName, clean) {
- loadStarterKit(starterkitName, clean);
- },
- list_starterkits: function () {
- return listStarterkits();
- },
- pack_starterkit: function () {
- packStarterkit();
- },
- detect_starterkits: function () {
- return detectStarterKits();
- }
- };
-
-};
-
-module.exports = starterkit_manager;
diff --git a/core/lib/ui_builder.js b/core/lib/ui_builder.js
deleted file mode 100644
index 4a0e87f0a..000000000
--- a/core/lib/ui_builder.js
+++ /dev/null
@@ -1,663 +0,0 @@
-"use strict";
-
-var path = require('path');
-var JSON5 = require('json5');
-var fs = require('fs-extra');
-var ae = require('./annotation_exporter');
-var of = require('./object_factory');
-var Pattern = of.Pattern;
-var pa = require('./pattern_assembler');
-var pattern_assembler = new pa();
-var plutils = require('./utilities');
-var eol = require('os').EOL;
-var _ = require('lodash');
-
-var ui_builder = function () {
-
- /**
- * Registers the pattern to the patternPaths object for the appropriate patternGroup and basename
- * patternGroup + patternBaseName are what comprise the patternPartial (atoms-colors)
- * @param patternlab - global data store
- * @param pattern - the pattern to add
- */
- function addToPatternPaths(patternlab, pattern) {
- if (!patternlab.patternPaths[pattern.patternGroup]) {
- patternlab.patternPaths[pattern.patternGroup] = {};
- }
-
- //only add real patterns
- if (pattern.isPattern && !pattern.isDocPattern) {
- patternlab.patternPaths[pattern.patternGroup][pattern.patternBaseName] = pattern.name;
- }
- }
-
- /**
- * Registers the pattern with the viewAllPaths object for the appropriate patternGroup and patternSubGroup
- * @param patternlab - global data store
- * @param pattern - the pattern to add
- */
- function addToViewAllPaths(patternlab, pattern) {
- if (!patternlab.viewAllPaths[pattern.patternGroup]) {
- patternlab.viewAllPaths[pattern.patternGroup] = {};
- }
-
- if (!patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup]) {
- patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup] = {};
- }
-
- //note these retain any number prefixes if present, because these paths match the filesystem
- patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup] = pattern.patternType + '-' + pattern.patternSubType;
-
- //add all if it does not exist yet
- if (!patternlab.viewAllPaths[pattern.patternGroup].all) {
- patternlab.viewAllPaths[pattern.patternGroup].all = pattern.patternType;
- }
- }
-
- /**
- * Writes a file to disk, with an optional callback
- * @param filePath - the path to write to with filename
- * @param data - the file contents
- * @param callback - an optional callback
- */
- function writeFile(filePath, data, callback) {
- if (callback) {
- fs.outputFileSync(filePath, data, callback);
- } else {
- fs.outputFileSync(filePath, data);
- }
- }
-
- /**
- * Returns whether or not the pattern should be excluded from direct rendering or navigation on the front end
- * @param pattern - the pattern to test for inclusion/exclusion
- * @param patternlab - global data store
- * @returns boolean - whether or not the pattern is excluded
- */
- function isPatternExcluded(pattern, patternlab) {
- var isOmitted;
-
- // skip underscore-prefixed files
- isOmitted = pattern.isPattern && pattern.fileName.charAt(0) === '_';
- if (isOmitted) {
- if (patternlab.config.debug) {
- console.log('Omitting ' + pattern.patternPartial + " from styleguide patterns because it has an underscore suffix.");
- }
- return true;
- }
-
- //this is meant to be a homepage that is not present anywhere else
- isOmitted = pattern.patternPartial === patternlab.config.defaultPattern;
- if (isOmitted) {
- if (patternlab.config.debug) {
- console.log('Omitting ' + pattern.patternPartial + ' from styleguide patterns because it is defined as a defaultPattern.');
- }
- patternlab.defaultPattern = pattern;
- return true;
- }
-
- //this pattern is contained with a directory prefixed with an underscore (a handy way to hide whole directories from the nav
- isOmitted = pattern.relPath.charAt(0) === '_' || pattern.relPath.indexOf('/_') > -1;
- if (isOmitted) {
- if (patternlab.config.debug) {
- console.log('Omitting ' + pattern.patternPartial + ' from styleguide patterns because its contained within an underscored directory.');
- }
- return true;
- }
-
- //this pattern is a head or foot pattern
- isOmitted = pattern.isMetaPattern;
- if (isOmitted) {
- if (patternlab.config.debug) {
- console.log('Omitting ' + pattern.patternPartial + ' from styleguide patterns because its a meta pattern.');
- }
- return true;
- }
-
- //yay, let's include this on the front end
- return isOmitted;
- }
-
- /**
- * For the given pattern, find or construct the view-all pattern block for the group
- * @param pattern - the pattern to derive our documentation pattern from
- * @param patternlab - global data store
- * @param isSubtypePattern - whether or not this is a subtypePattern or a typePattern (typePatterns not supported yet)
- * @returns the found or created pattern object
- */
- function injectDocumentationBlock(pattern, patternlab, isSubtypePattern) {
- //first see if pattern_assembler processed one already
- var docPattern = patternlab.subtypePatterns[pattern.patternGroup + (isSubtypePattern ? '-' + pattern.patternSubGroup : '')];
- if (docPattern) {
- docPattern.isDocPattern = true;
- return docPattern;
- }
-
- //if not, create one now
- docPattern = new Pattern.createEmpty(
- {
- name: pattern.flatPatternPath,
- patternName: isSubtypePattern ? pattern.patternSubGroup : pattern.patternGroup,
- patternDesc: '',
- patternPartial: 'viewall-' + pattern.patternGroup + (isSubtypePattern ? '-' + pattern.patternSubGroup : ''),
- patternSectionSubtype : isSubtypePattern,
- patternLink: pattern.flatPatternPath + path.sep + 'index.html',
- isPattern: false,
- engine: null,
- flatPatternPath: pattern.flatPatternPath,
- isDocPattern: true
- },
- patternlab
- );
- return docPattern;
- }
-
- /**
- * Registers flat patterns with the patternTypes object
- * This is a new menu group like atoms
- * @param patternlab - global data store
- * @param pattern - the pattern to register
- */
- function addPatternType(patternlab, pattern) {
- patternlab.patternTypes.push(
- {
- patternTypeLC: pattern.patternGroup.toLowerCase(),
- patternTypeUC: pattern.patternGroup.charAt(0).toUpperCase() + pattern.patternGroup.slice(1),
- patternType: pattern.patternType,
- patternTypeDash: pattern.patternGroup, //todo verify
- patternTypeItems: []
- }
- );
- }
-
- /**
- * Return the patternType object for the given pattern. Exits application if not found.
- * @param patternlab - global data store
- * @param pattern - the pattern to derive the pattern Type from
- * @returns the found pattern type object
- */
- function getPatternType(patternlab, pattern) {
- var patternType = _.find(patternlab.patternTypes, ['patternType', pattern.patternType]);
-
- if (!patternType) {
- plutils.logRed('Could not find patternType' + pattern.patternType + '. This is a critical error.');
- console.trace();
- process.exit(1);
- }
-
- return patternType;
- }
-
- /**
- * Return the patternSubType object for the given pattern. Exits application if not found.
- * @param patternlab - global data store
- * @param pattern - the pattern to derive the pattern subType from
- * @returns the found patternSubType object
- */
- function getPatternSubType(patternlab, pattern) {
- var patternType = getPatternType(patternlab, pattern);
- var patternSubType = _.find(patternType.patternTypeItems, ['patternSubtype', pattern.patternSubType]);
-
- if (!patternSubType) {
- plutils.logRed('Could not find patternType ' + pattern.patternType + '-' + pattern.patternType + '. This is a critical error.');
- console.trace();
- process.exit(1);
- }
-
- return patternSubType;
- }
-
- /**
- * Registers the pattern with the appropriate patternType.patternTypeItems object
- * This is a new menu group like atoms/global
- * @param patternlab - global data store
- * @param pattern - the pattern to register
- */
- function addPatternSubType(patternlab, pattern) {
- var patternType = getPatternType(patternlab, pattern);
- patternType.patternTypeItems.push(
- {
- patternSubtypeLC: pattern.patternSubGroup.toLowerCase(),
- patternSubtypeUC: pattern.patternSubGroup.charAt(0).toUpperCase() + pattern.patternSubGroup.slice(1),
- patternSubtype: pattern.patternSubType,
- patternSubtypeDash: pattern.patternSubGroup, //todo verify
- patternSubtypeItems: []
- }
- );
- }
-
- /**
- * Creates a patternSubTypeItem object from a pattern
- * This is a menu item you click on
- * @param pattern - the pattern to derive the subtypeitem from
- * @returns {{patternPartial: string, patternName: (*|string), patternState: string, patternSrcPath: string, patternPath: string}}
- */
- function createPatternSubTypeItem(pattern) {
- var patternPath = '';
- if (pattern.isFlatPattern) {
- patternPath = pattern.flatPatternPath + '-' + pattern.fileName + '/' + pattern.flatPatternPath + '-' + pattern.fileName + '.html';
- } else {
- patternPath = pattern.flatPatternPath + '/' + pattern.flatPatternPath + '.html';
- }
-
- return {
- patternPartial: pattern.patternPartial,
- patternName: pattern.patternName,
- patternState: pattern.patternState,
- patternSrcPath: encodeURI(pattern.subdir + '/' + pattern.fileName),
- patternPath: patternPath
- };
- }
-
- /**
- * Registers the pattern with the appropriate patternType.patternSubType.patternSubtypeItems array
- * These are the actual menu items you click on
- * @param patternlab - global data store
- * @param pattern - the pattern to derive the subtypeitem from
- * @param createViewAllVariant - whether or not to create the special view all item
- */
- function addPatternSubTypeItem(patternlab, pattern, createSubtypeViewAllVarient) {
- var patternSubType = getPatternSubType(patternlab, pattern);
- if (createSubtypeViewAllVarient) {
- patternSubType.patternSubtypeItems.push(
- {
- patternPartial: 'viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup,
- patternName: 'View All',
- patternPath: encodeURI(pattern.flatPatternPath + '/index.html'),
- patternType: pattern.patternType,
- patternSubtype: pattern.patternSubtype
- }
- );
- }
- else {
- patternSubType.patternSubtypeItems.push(
- createPatternSubTypeItem(pattern)
- );
- }
- }
-
- /**
- * Registers flat patterns to the appropriate type
- * @param patternlab - global data store
- * @param pattern - the pattern to add
- */
- function addPatternItem(patternlab, pattern, isViewAllVariant) {
- var patternType = getPatternType(patternlab, pattern);
- if (!patternType) {
- plutils.logRed('Could not find patternType' + pattern.patternType + '. This is a critical error.');
- console.trace();
- process.exit(1);
- }
-
- if (!patternType.patternItems) {
- patternType.patternItems = [];
- }
-
- if (isViewAllVariant) {
- if (!pattern.isFlatPattern) {
- //todo: it'd be nice if we could get this into createPatternSubTypeItem someday
- patternType.patternItems.push({
- patternPartial: 'viewall-' + pattern.patternGroup + '-all',
- patternName: 'View All',
- patternPath: encodeURI(pattern.patternType + '/index.html')
- });
- }
-
- } else {
- patternType.patternItems.push(createPatternSubTypeItem(pattern));
- }
- }
-
- // function getPatternItems(patternlab, patternType) {
- // var patternType = _.find(patternlab.patternTypes, ['patternTypeLC', patternType]);
- // if (patternType) {
- // return patternType.patternItems;
- // }
- // return [];
- // }
-
- /**
- * Sorts patterns based on name.
- * Will be expanded to use explicit order in the near future
- * @param patternsArray - patterns to sort
- * @returns sorted patterns
- */
- function sortPatterns(patternsArray) {
- return patternsArray.sort(function (a, b) {
-
- if (a.name > b.name) {
- return 1;
- }
- if (a.name < b.name) {
- return -1;
- }
- return 0;
- });
- }
-
- /**
- * Returns an object representing how the front end styleguide and navigation is structured
- * @param patternlab - global data store
- * @returns ptterns grouped by type -> subtype like atoms -> global -> pattern, pattern, pattern
- */
- function groupPatterns(patternlab) {
- var groupedPatterns = {
- patternGroups: {}
- };
-
- _.forEach(sortPatterns(patternlab.patterns), function (pattern) {
-
- //ignore patterns we can omit from rendering directly
- pattern.omitFromStyleguide = isPatternExcluded(pattern, patternlab);
- if (pattern.omitFromStyleguide) { return; }
-
- if (!groupedPatterns.patternGroups[pattern.patternGroup]) {
-
- groupedPatterns.patternGroups[pattern.patternGroup] = {};
- pattern.isSubtypePattern = false;
- addPatternType(patternlab, pattern);
-
- //todo: Pattern Type View All and Documentation
- //groupedPatterns.patternGroups[pattern.patternGroup]['viewall-' + pattern.patternGroup] = injectDocumentationBlock(pattern, patternlab, false);
- addPatternItem(patternlab, pattern, true);
- }
-
- //continue building navigation for nested patterns
- if (pattern.patternGroup !== pattern.patternSubGroup) {
-
- if (!groupedPatterns.patternGroups[pattern.patternGroup][pattern.patternSubGroup]) {
-
- addPatternSubType(patternlab, pattern);
-
- pattern.isSubtypePattern = !pattern.isPattern;
- groupedPatterns.patternGroups[pattern.patternGroup][pattern.patternSubGroup] = {};
- groupedPatterns.patternGroups[pattern.patternGroup][pattern.patternSubGroup]['viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup] = injectDocumentationBlock(pattern, patternlab, true);
-
- addToViewAllPaths(patternlab, pattern);
- addPatternSubTypeItem(patternlab, pattern, true);
-
- }
-
- groupedPatterns.patternGroups[pattern.patternGroup][pattern.patternSubGroup][pattern.patternBaseName] = pattern;
-
- addToPatternPaths(patternlab, pattern);
- addPatternSubTypeItem(patternlab, pattern);
- } else {
- addPatternItem(patternlab, pattern);
- addToPatternPaths(patternlab, pattern);
- }
-
- });
-
- return groupedPatterns;
- }
-
- /**
- * Builds footer HTML from the general footer and user-defined footer
- * @param patternlab - global data store
- * @param patternPartial - the partial key to build this for, either viewall-patternPartial or a viewall-patternType-all
- * @returns HTML
- */
- function buildFooterHTML(patternlab, patternPartial) {
- //first render the general footer
- var footerPartial = pattern_assembler.renderPattern(patternlab.footer, {
- patternData: JSON.stringify({
- patternPartial: patternPartial,
- }),
- cacheBuster: patternlab.cacheBuster
- });
-
- var allFooterData;
- try {
- allFooterData = JSON5.parse(JSON5.stringify(patternlab.data));
- } catch (err) {
- console.log('There was an error parsing JSON for patternlab.data');
- console.log(err);
- }
- allFooterData.patternLabFoot = footerPartial;
-
- //then add it to the user footer
- var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, allFooterData);
- return footerHTML;
- }
-
- /**
- * Takes a set of patterns and builds a viewall HTML page for them
- * Used by the type and subtype viewall sets
- * @param patternlab - global data store
- * @param patterns - the set of patterns to build the viewall page for
- * @param patternPartial - a key used to identify the viewall page
- * @returns HTML
- */
- function buildViewAllHTML(patternlab, patterns, patternPartial) {
- var viewAllHTML = pattern_assembler.renderPattern(patternlab.viewAll,
- {
- partials: patterns,
- patternPartial: 'viewall-' + patternPartial,
- cacheBuster: patternlab.cacheBuster
- }, {
- patternSection: patternlab.patternSection,
- patternSectionSubtype: patternlab.patternSectionSubType
- });
- return viewAllHTML;
- }
-
- /**
- * Constructs viewall pages for each set of grouped patterns
- * @param mainPageHeadHtml - the already built main page HTML
- * @param patternlab - global data store
- * @param styleguidePatterns - the grouped set of patterns
- * @returns every built pattern and set of viewall patterns, so the styleguide can use it
- */
- function buildViewAllPages(mainPageHeadHtml, patternlab, styleguidePatterns) {
- var paths = patternlab.config.paths;
- var patterns = [];
- var writeViewAllFile = true;
-
- //loop through the grouped styleguide patterns, building at each level
- _.forEach(styleguidePatterns.patternGroups, function (patternTypeObj, patternType) {
-
- var p;
- var typePatterns = [];
- var styleGuideExcludes = patternlab.config.styleGuideExcludes;
-
- _.forOwn(patternTypeObj, function (patternSubtypes, patternSubtype) {
-
- var patternPartial = patternType + '-' + patternSubtype;
-
- //do not create a viewall page for flat patterns
- if (patternType === patternSubtype) {
- writeViewAllFile = false;
- return false;
- }
-
- //render the footer needed for the viewall template
- var footerHTML = buildFooterHTML(patternlab, 'viewall-' + patternPartial);
-
- //render the viewall template
- var subtypePatterns = _.values(patternSubtypes);
-
- //determine if we should write at this time by checking if these are flat patterns or grouped patterns
- p = _.find(subtypePatterns, function (pat) {
- return pat.isDocPattern;
- });
-
- typePatterns = typePatterns.concat(subtypePatterns);
-
- var viewAllHTML = buildViewAllHTML(patternlab, subtypePatterns, patternPartial);
- writeFile(paths.public.patterns + p.flatPatternPath + '/index.html', mainPageHeadHtml + viewAllHTML + footerHTML);
- return true; //stop yelling at us eslint we know we know
- });
-
- //do not create a viewall page for flat patterns
- if (!writeViewAllFile || !p) {
- return false;
- }
-
- //render the footer needed for the viewall template
- var footerHTML = buildFooterHTML(patternlab, 'viewall-' + patternType + '-all');
-
- //add any flat patterns
- //todo this isn't quite working yet
- //typePatterns = typePatterns.concat(getPatternItems(patternlab, patternType));
-
- //get the appropriate patternType
- var anyPatternOfType = _.find(typePatterns, function (pat) {
- return pat.patternType && pat.patternType !== '';});
-
- //render the viewall template for the type
- var viewAllHTML = buildViewAllHTML(patternlab, typePatterns, patternType);
- writeFile(paths.public.patterns + anyPatternOfType.patternType + '/index.html', mainPageHeadHtml + viewAllHTML + footerHTML);
-
- //determine if we should omit this patterntype completely from the viewall page
- var omitPatternType = styleGuideExcludes && styleGuideExcludes.length
- && _.some(styleGuideExcludes, function (exclude) {
- return exclude === patternType;
- });
- if (omitPatternType) {
- if (patternlab.config.debug) {
- console.log('Omitting ' + patternType + ' from building a viewall page because its patternGroup is specified in styleguideExcludes.');
- }
- } else {
- patterns = patterns.concat(typePatterns);
- }
-
- return true; //stop yelling at us eslint we know we know
- });
- return patterns;
- }
-
- /**
- * Write out our pattern information for use by the front end
- * @param patternlab - global data store
- */
- function exportData(patternlab) {
- var annotation_exporter = new ae(patternlab);
- var paths = patternlab.config.paths;
-
- //write out the data
- var output = '';
-
- //config
- output += 'var config = ' + JSON.stringify(patternlab.config) + ';\n';
-
- //ishControls
- output += 'var ishControls = {"ishControlsHide":' + JSON.stringify(patternlab.config.ishControlsHide) + '};' + eol;
-
- //navItems
- output += 'var navItems = {"patternTypes": ' + JSON.stringify(patternlab.patternTypes) + '};' + eol;
-
- //patternPaths
- output += 'var patternPaths = ' + JSON.stringify(patternlab.patternPaths) + ';' + eol;
-
- //viewAllPaths
- output += 'var viewAllPaths = ' + JSON.stringify(patternlab.viewAllPaths) + ';' + eol;
-
- //plugins
- output += 'var plugins = ' + JSON.stringify(patternlab.plugins) + ';' + eol;
-
- //smaller config elements
- output += 'var defaultShowPatternInfo = ' + (patternlab.config.defaultShowPatternInfo ? patternlab.config.defaultShowPatternInfo : 'false') + ';' + eol;
- output += 'var defaultPattern = "' + (patternlab.config.defaultPattern ? patternlab.config.defaultPattern : 'all') + '";' + eol;
-
- //write all output to patternlab-data
- writeFile(path.resolve(paths.public.data, 'patternlab-data.js'), output);
-
- //annotations
- var annotationsJSON = annotation_exporter.gather();
- var annotations = 'var comments = { "comments" : ' + JSON.stringify(annotationsJSON) + '};';
- writeFile(path.resolve(paths.public.annotations, 'annotations.js'), annotations);
- }
-
- /**
- * Reset any global data we use between builds to guard against double adding things
- */
- function resetUIBuilderState(patternlab) {
- patternlab.patternPaths = {};
- patternlab.viewAllPaths = {};
- patternlab.patternTypes = [];
- }
-
- /**
- * The main entry point for ui_builder
- * @param patternlab - global data store
- */
- function buildFrontend(patternlab) {
-
- resetUIBuilderState(patternlab);
-
- var paths = patternlab.config.paths;
-
- //determine which patterns should be included in the front-end rendering
- var styleguidePatterns = groupPatterns(patternlab);
-
- //set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header
- var headerPartial = pattern_assembler.renderPattern(patternlab.header, {
- cacheBuster: patternlab.cacheBuster
- });
-
- var headFootData = patternlab.data;
- headFootData.patternLabHead = headerPartial;
- headFootData.cacheBuster = patternlab.cacheBuster;
- var headerHTML = pattern_assembler.renderPattern(patternlab.userHead, headFootData);
-
- //set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer
- var footerPartial = pattern_assembler.renderPattern(patternlab.footer, {
- patternData: '{}',
- cacheBuster: patternlab.cacheBuster
- });
- headFootData.patternLabFoot = footerPartial;
- var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, headFootData);
-
- //build the viewall pages
- var allPatterns = buildViewAllPages(headerHTML, patternlab, styleguidePatterns);
-
- //add the defaultPattern if we found one
- if (patternlab.defaultPattern) {
- allPatterns.push(patternlab.defaultPattern);
- addToPatternPaths(patternlab, patternlab.defaultPattern);
- }
-
- //build the main styleguide page
- var styleguideHtml = pattern_assembler.renderPattern(patternlab.viewAll,
- {
- partials: allPatterns
- }, {
- patternSection: patternlab.patternSection,
- patternSectionSubtype: patternlab.patternSectionSubType
- });
- writeFile(path.resolve(paths.public.styleguide, 'html/styleguide.html'), headerHTML + styleguideHtml + footerHTML);
-
- //move the index file from its asset location into public root
- var patternlabSiteHtml;
- try {
- patternlabSiteHtml = fs.readFileSync(path.resolve(paths.source.styleguide, 'index.html'), 'utf8');
- } catch (error) {
- console.log(error);
- console.log("\nERROR: Could not load one or more styleguidekit assets from", paths.source.styleguide, '\n');
- process.exit(1);
- }
- writeFile(path.resolve(paths.public.root, 'index.html'), patternlabSiteHtml);
-
- //write out patternlab.data object to be read by the client
- exportData(patternlab);
- }
-
- return {
- buildFrontend: function (patternlab) {
- buildFrontend(patternlab);
- },
- isPatternExcluded: function (pattern, patternlab) {
- return isPatternExcluded(pattern, patternlab);
- },
- groupPatterns: function (patternlab) {
- return groupPatterns(patternlab);
- },
- resetUIBuilderState: function (patternlab) {
- resetUIBuilderState(patternlab);
- }
- };
-
-};
-
-module.exports = ui_builder;
diff --git a/core/lib/utilities.js b/core/lib/utilities.js
deleted file mode 100644
index d05eec876..000000000
--- a/core/lib/utilities.js
+++ /dev/null
@@ -1,93 +0,0 @@
-"use strict";
-
-var fs = require('fs-extra'),
- path = require('path');
-
-var util = {
- // http://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array-in-javascript
- shuffle: function (o) {
- /*eslint-disable curly*/
- for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
- return o;
- },
-
- logGreen: function (message) {
- console.log('\x1b[32m', message, '\x1b[0m');
- },
-
- logOrange: function (message) {
- console.log('\x1b[33m', message, '\x1b[0m');
- },
-
- logRed: function (message) {
- console.log('\x1b[41m', message, '\x1b[0m');
- },
-
- /**
- * Recursively merge properties of two objects.
- *
- * @param {Object} obj1 If obj1 has properties obj2 doesn't, add to obj2.
- * @param {Object} obj2 This object's properties have priority over obj1.
- * @returns {Object} obj2
- */
- mergeData: function (obj1, obj2) {
- /*eslint-disable no-param-reassign, guard-for-in*/
- if (typeof obj2 === 'undefined') {
- obj2 = {};
- }
- for (var p in obj1) {
- try {
- // Only recurse if obj1[p] is an object.
- if (obj1[p].constructor === Object) {
- // Requires 2 objects as params; create obj2[p] if undefined.
- if (typeof obj2[p] === 'undefined') {
- obj2[p] = {};
- }
- obj2[p] = util.mergeData(obj1[p], obj2[p]);
-
- // Pop when recursion meets a non-object. If obj1[p] is a non-object,
- // only copy to undefined obj2[p]. This way, obj2 maintains priority.
- } else if (typeof obj2[p] === 'undefined') {
- obj2[p] = obj1[p];
- }
- } catch (e) {
- // Property in destination object not set; create it and set its value.
- if (typeof obj2[p] === 'undefined') {
- obj2[p] = obj1[p];
- }
- }
- }
- return obj2;
- },
-
- isObjectEmpty: function (obj) {
- for (var prop in obj) {
- if (obj.hasOwnProperty(prop)) { return false; }
- }
- return true;
- },
-
- // recursively delete the contents of directory
- // adapted from https://gist.github.com/tkihira/2367067
- emptyDirectory: function (dir, cleanDir) {
- var list = fs.readdirSync(dir);
- for (var i = 0; i < list.length; i++) {
- var filename = path.join(dir, list[i]);
- var stat = fs.statSync(filename);
-
- if (filename === "." || filename === "..") {
- // pass these files
- } else if (stat.isDirectory()) {
- this.emptyDirectory(filename);
- } else {
- // rm fiilename
- fs.unlinkSync(filename);
- }
- }
- if (cleanDir) {
- fs.rmdirSync(dir);
- }
- }
-};
-
-module.exports = util;
diff --git a/core/scripts/postinstall.js b/core/scripts/postinstall.js
deleted file mode 100644
index 53636e1d0..000000000
--- a/core/scripts/postinstall.js
+++ /dev/null
@@ -1,47 +0,0 @@
-"use strict";
-try {
- console.log('Beginning Pattern Lab postinstall...');
-
- var path = require('path');
- var fs = require('fs-extra');
- var smPath = path.resolve(__dirname, '..', 'lib/starterkit_manager.js');
- var pmPath = path.resolve(__dirname, '..', 'lib/plugin_manager.js');
- var uPath = path.resolve(__dirname, '..', 'lib/utilities.js');
- var sm = require(smPath);
- var pm = require(pmPath);
- var u = require(uPath);
-
- //get the config
- var configPath = path.resolve(process.cwd(), 'patternlab-config.json');
- var config = fs.readJSONSync(path.resolve(configPath), 'utf8');
-
- //determine if any starterkits are already installed
- var starterkit_manager = new sm(config);
- var foundStarterkits = starterkit_manager.detect_starterkits();
-
- //todo - enhance to support multiple kits with prompt for each or all
- if (foundStarterkits && foundStarterkits.length > 0) {
- starterkit_manager.load_starterkit(foundStarterkits[0], true);
- } else {
- console.log('No starterkits found to automatically load.');
- }
-
- //determine if any plugins are already installed
- var plugin_manager = new pm(config, configPath);
- var foundPlugins = plugin_manager.detect_plugins();
-
- if (foundPlugins && foundPlugins.length > 0) {
-
- for (var i = 0; i < foundPlugins.length; i++) {
- console.log('Found plugin', foundPlugins[i]);
- plugin_manager.install_plugin(foundPlugins[i]);
- }
- }
-
- u.logGreen('Pattern Lab postinstall complete.');
-
-} catch (ex) {
- console.log(ex);
- u.logOrange('An error occurred during Pattern Lab Node postinstall.');
- u.logOrange('Pattern Lab postinstall completed with errors.');
-}
diff --git a/lerna.json b/lerna.json
new file mode 100644
index 000000000..84951f7f0
--- /dev/null
+++ b/lerna.json
@@ -0,0 +1,38 @@
+{
+ "lerna": "3.11.0",
+ "version": "5.9.3",
+ "packages": [
+ "packages/*"
+ ],
+ "command": {
+ "init": {
+ "exact": true
+ },
+ "publish": {
+ "allowBranch": [
+ "master",
+ "dev"
+ ],
+ "conventionalCommits": true,
+ "gitReset": true,
+ "includeMergedTags": true,
+ "noCommitHooks": true,
+ "verifyAccess": true
+ },
+ "changed": {
+ "includeMergedTags": true
+ }
+ },
+ "ignoreChanges": [
+ "**/__fixtures__/**",
+ "**/__tests__/**",
+ "**/*.md",
+ "**/__snapshots__/**"
+ ],
+ "npmClient": "yarn",
+ "npmClientArgs": [
+ "--ignore-optional",
+ "--registry=https://registry.npmjs.org/"
+ ],
+ "useWorkspaces": true
+}
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 000000000..94337a4da
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,2 @@
+[context.deploy-preview]
+ command = "yarn setup && yarn preview:hbs && yarn preview:docs"
diff --git a/package.json b/package.json
index ab66fa4a4..d2c271c91 100644
--- a/package.json
+++ b/package.json
@@ -1,52 +1,53 @@
{
- "name": "patternlab-node",
- "description": "Pattern Lab is a collection of tools to help you create atomic design systems. This is the node command line interface (CLI).",
- "version": "2.6.0-alpha",
- "main": "./core/lib/patternlab.js",
- "dependencies": {
- "diveSync": "^0.3.0",
- "fs-extra": "^0.30.0",
- "glob": "^7.0.0",
- "js-beautify": "^1.6.3",
- "js-yaml": "^3.6.1",
- "json5": "^0.5.0",
- "lodash": "~4.13.1",
- "markdown-it": "^6.0.1",
- "node-fetch": "^1.6.0",
- "patternengine-node-mustache": "^1.0.0"
+ "workspaces": {
+ "packages": [
+ "packages/*"
+ ],
+ "nohoist": [
+ "@pattern-lab/engine-*",
+ "**/@pattern-lab/engine-*",
+ "**/@pattern-lab/uikit-workshop"
+ ]
},
- "devDependencies": {
- "grunt": "~1.0.1",
- "grunt-contrib-concat": "^1.0.1",
- "grunt-contrib-nodeunit": "^1.0.0",
- "grunt-eslint": "^18.0.0"
+ "dependencies": {
+ "@babel/plugin-proposal-decorators": "^7.4.4",
+ "@babel/plugin-syntax-jsx": "^7.2.0",
+ "babel-eslint": "^10.0.2",
+ "eslint": "^6.1.0",
+ "eslint-config-airbnb-base": "^14.0.0",
+ "eslint-config-prettier": "^6.0.0",
+ "eslint-plugin-import": "^2.18.2",
+ "eslint-plugin-prettier": "^3.1.0",
+ "prettier": "^1.14.3",
+ "lerna": "3.17.0",
+ "pretty-quick": "^1.11.1",
+ "auto": "^7.8.0"
},
- "keywords": [
- "Pattern Lab",
- "Atomic Web Design",
- "Node",
- "Grunt",
- "Gulp",
- "Javascript"
- ],
"repository": {
"type": "git",
- "url": "git://github.com/pattern-lab/patternlab-node.git"
- },
- "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
- "author": {
- "name": "Brian Muenzenmeyer"
+ "url": "git+https://github.com/pattern-lab/patternlab-node.git"
},
- "contributors": [
- {
- "name": "Geoff Pursell"
- }
- ],
- "license": "MIT",
+ "private": true,
"scripts": {
- "test": "grunt travis --verbose"
+ "postinstall": "lerna run postbootstrap",
+ "setup": "yarn",
+ "build:uikit": "cd packages/uikit-workshop && npm run build",
+ "precommit": "pretty-quick --staged",
+ "lint:fix": "npm run lint -- --fix",
+ "lint": "eslint --max-warnings 0 './packages/{core,cli,uikit-workshop}/**/*.js ' --ignore-path .eslintignore",
+ "test": "lerna run test",
+ "clean": "git clean -dfx",
+ "publish": "npx lerna publish -m \"[skip travis] chore(release): publish %s\"",
+ "postpublish": "auto release",
+ "preview:docs": "cd packages/docs && yarn production",
+ "preview:hbs": "cd packages/development-edition-engine-handlebars && npx patternlab add --starterkits @pattern-lab/starterkit-handlebars-vanilla && npm run pl:build"
},
- "engines": {
- "node": ">=4.0"
+ "nyc": {
+ "exclude": [
+ "**/*_tests.js",
+ "packages/cli",
+ "packages/core/test",
+ "packages/live-server"
+ ]
}
}
diff --git a/packages/cli/.gitattributes b/packages/cli/.gitattributes
new file mode 100644
index 000000000..141034634
--- /dev/null
+++ b/packages/cli/.gitattributes
@@ -0,0 +1,3 @@
+* text=auto
+bin/patternlab.js lf
+readme.md merge=union
diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore
new file mode 100644
index 000000000..55a89459b
--- /dev/null
+++ b/packages/cli/.gitignore
@@ -0,0 +1,81 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# macOS specific
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Pattern Lab CLI specific
+
+.DS_Store
+latest-change.txt
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+source/css/style.css.map
+.idea/
+public
+tmp
diff --git a/packages/cli/.npmrc b/packages/cli/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/cli/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/cli/.nvmrc b/packages/cli/.nvmrc
new file mode 100644
index 000000000..a13e7b9c8
--- /dev/null
+++ b/packages/cli/.nvmrc
@@ -0,0 +1 @@
+10.0.0
diff --git a/packages/cli/.travis.yml b/packages/cli/.travis.yml
new file mode 100644
index 000000000..1f0e15052
--- /dev/null
+++ b/packages/cli/.travis.yml
@@ -0,0 +1,11 @@
+language: node_js
+node_js:
+ - node
+ - 8
+before_script:
+ - npm install edition-node
+ - npm install starterkit-mustache-base
+ - npm install eslint
+branches:
+ only:
+ - master
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
new file mode 100644
index 000000000..3f84d02ec
--- /dev/null
+++ b/packages/cli/CHANGELOG.md
@@ -0,0 +1,293 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+
+### Bug Fixes
+
+* **cli:** fix test script glob ([ff18eb5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/ff18eb51ce24fc5423b009168e85ede366069139))
+
+
+
+
+
+
+## [5.9.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.9.0...v5.9.1) (2020-04-24)
+
+
+### Bug Fixes
+
+* **cli:** ensure specified directory exists prior to scaffold ([cc3b696](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/cc3b69624d486c94ee3b1f4b1bbb0334a514fa59))
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+
+### Bug Fixes
+
+* **cli:** set current working directory before scaffolded npm init ([6d2186d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/6d2186d8e8a74634198a4474ca8ae83221dd70a9))
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+
+### Features
+
+* **cli:** make options more user friendly ([ad845b3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/ad845b394ef81f90895ebb5bc6f12cc608e5e3d4))
+
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/cli
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/cli
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+**Note:** Version bump only for package @pattern-lab/cli
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Bug Fixes
+
+* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/5b7a057d46ccd16b5832af1441030c7b76f237a8))
+* **cli:** add custom install logic to edition-node ([f04fd26](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/f04fd266429cd806987dab747e6d69bff9b926a4))
+* **cli:** allow any package to be installed as a starterkit ([d2aa1be](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/d2aa1be810a0a7473dcc52391a2263dacfdda0b8)), closes [#1067](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/issues/1067)
+* **cli:** merge config arrays via overwrite instead of concatenate ([42e5f7b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/42e5f7b42a26b4fc1f262c68ee4b474b546f2eac))
+* **cli:** proper path resolution to helpers ([a18fe5e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/a18fe5ef4d1c074a5eba8bfa255ebbee2261bf74))
+* **cli:** re-order and clarify engines ([e39e301](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/e39e301a33306c6615fabf64262f1893ca682b97))
+* **plugin:** correct spelling error and function locations ([d4abd88](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/d4abd88cb017550002407241b5045a2ad1adb1dc))
+
+
+### Features
+
+* **starterkits:** add starterkit-handlebars-demo ([384d2cf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/384d2cfa3440c1e6f456d39f56ca6381f82f7689))
+
+
+### BREAKING CHANGES
+
+* **cli:** previously, we concatenated arrays, which is unlikely to be intended
+
+
+
+
+
+
+## [1.0.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@1.0.2...@pattern-lab/cli@1.0.3) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/cli
+
+
+
+
+
+
+# [1.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.1.0...@pattern-lab/cli@1.0.0) (2019-08-23)
+
+
+### Bug Fixes
+
+* **cli:** merge config arrays via overwrite instead of concatenate ([42e5f7b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/42e5f7b))
+* **cli:** proper path resolution to helpers ([a18fe5e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/a18fe5e))
+
+
+### BREAKING CHANGES
+
+* **cli:** previously, we concatenated arrays, which is unlikely to be intended
+
+
+
+
+
+# [0.1.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.3...@pattern-lab/cli@0.1.0) (2019-08-23)
+
+
+### Bug Fixes
+
+* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/5b7a057))
+* **cli:** add custom install logic to edition-node ([f04fd26](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/f04fd26))
+* **cli:** re-order and clarify engines ([e39e301](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/e39e301))
+
+
+### Features
+
+* **starterkits:** add starterkit-handlebars-demo ([384d2cf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/384d2cf))
+
+
+
+
+
+
+## [0.0.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.3-alpha.0...@pattern-lab/cli@0.0.3) (2019-05-16)
+
+**Note:** Version bump only for package @pattern-lab/cli
+
+
+
+
+
+## [0.0.1-beta.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-beta.0...@pattern-lab/cli@0.0.1-beta.2) (2019-02-09)
+
+
+### Bug Fixes
+
+* **cli:** do not call build before serve ([663d8e1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/663d8e1)), closes [#917](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/issues/917)
+* **cli:** pass watch options cleanly to core ([8bf186b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/8bf186b))
+* **cli:** remove copy-source-files ([64311a1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/64311a1)), closes [#833](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/issues/833)
+* **nvmrc:** bump Node version ([36a917f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/36a917f))
+* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/98dfadf))
+
+
+### Features
+
+* **README:** simplify README and add CLI configuration instructions ([ceec673](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/ceec673))
+
+
+
+
+
+
+## [0.0.1-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.23...@pattern-lab/cli@0.0.1-beta.0) (2018-09-07)
+
+
+### Bug Fixes
+
+* **cli:** set initialized to false during plugin installation ([88cce3f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/88cce3f))
+* **cli:** support scoped plugins ([4ae13ce](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/4ae13ce))
+* **package:** update tap dependency ([2b70ff4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/2b70ff4))
+
+
+
+
+
+
+
+## [0.0.1-alpha.23](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.22...@pattern-lab/cli@0.0.1-alpha.23) (2018-07-09)
+
+### Bug Fixes
+
+* **install:** copy dependencies ([1acef87](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/1acef87))
+
+
+
+## [0.0.1-alpha.22](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.21...@pattern-lab/cli@0.0.1-alpha.22) (2018-07-06)
+
+**Note:** Version bump only for package @pattern-lab/cli
+
+
+
+## [0.0.1-alpha.21](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.20...@pattern-lab/cli@0.0.1-alpha.21) (2018-07-06)
+
+### Bug Fixes
+
+* **install:** add break statements to install edition command ([3b1813c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/3b1813c))
+* **install:** use process to find package.json ([200c7cb](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/200c7cb))
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/5ab3995))
+
+
+
+## [0.0.1-alpha.20](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.19...@pattern-lab/cli@0.0.1-alpha.20) (2018-07-05)
+
+### Bug Fixes
+
+* **cli:** change whitespace to spaces per standard ([4556fc7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/4556fc7))
+* **tests:** change test command name similar to live-server until this passes CI ([5c39be1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/5c39be1))
+
+### Features
+
+* **serve:** change calling method ([3b86a0d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/3b86a0d))
+* **tests:** use lerna run test at the monorepo level ([38a01b1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/38a01b1))
+
+
+
+## [0.0.1-alpha.19](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.18...@pattern-lab/cli@0.0.1-alpha.19) (2018-05-19)
+
+### Bug Fixes
+
+* **cli:** change line-endings of cli entrypoint ([3fc86c2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/3fc86c2))
+* **wording:** reconcile Pattern Lab vs PatternLab ([f3d1e0d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/f3d1e0d))
+
+
+
+## [0.0.1-alpha.18](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.17...@pattern-lab/cli@0.0.1-alpha.18) (2018-05-04)
+
+### Bug Fixes
+
+* **version:** use static core method getVersion ([f9dcd4d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/f9dcd4d))
+
+
+
+## [0.0.1-alpha.17](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.16...@pattern-lab/cli@0.0.1-alpha.17) (2018-05-04)
+
+### Bug Fixes
+
+* **package:** update publish config and installation target ([27d2c8f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/27d2c8f))
+
+
+
+## [0.0.1-alpha.16](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.15...@pattern-lab/cli@0.0.1-alpha.16) (2018-05-04)
+
+### Features
+
+* **API:** standardize v() and version() into a single call ([6309e69](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/6309e69))
+
+### BREAKING CHANGES
+
+* **API:** change `version()` to return a string representation of the version, removing `v()`
+
+
+
+## [0.0.1-alpha.15](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.14...@pattern-lab/cli@0.0.1-alpha.15) (2018-03-21)
+
+### Features
+
+* **package:** standardize and hoist common devDependencies ([7f4ce6f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/7f4ce6f))
+
+
+
+## [0.0.1-alpha.14](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.13...@pattern-lab/cli@0.0.1-alpha.14) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/1473cd5))
+
+
+
+## 0.0.1-alpha.13 (2018-03-02)
+
+### Features
+
+* **cli:** Rename package ([9ea40d4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/9ea40d4))
diff --git a/packages/cli/bin/archive.js b/packages/cli/bin/archive.js
new file mode 100644
index 000000000..9d7a6989c
--- /dev/null
+++ b/packages/cli/bin/archive.js
@@ -0,0 +1,53 @@
+'use strict';
+const fs = require('fs');
+const path = require('path');
+const Archiver = require('archiver');
+const isValidConfig = require('./validate-config');
+const debug = require('./utils').debug;
+
+/**
+ * @func exportPatterns
+ * @desc Exports the patterns into the patternExportDirectory.
+ * @param {object} config - The passed Pattern Lab config.
+ */
+function exportPatterns(config) {
+ if (!isValidConfig) {
+ throw new TypeError(
+ 'export: Expects config not to be empty OR of type object if not empty.'
+ );
+ }
+
+ const archive = new Archiver('zip', {});
+ const exportsPath = path.resolve(
+ './',
+ config.patternExportDirectory,
+ 'patterns.zip'
+ );
+ const output = fs.createWriteStream(exportsPath);
+
+ output.on('close', () => {
+ debug(
+ `export: Exported patterns in ${exportsPath} - ${archive.pointer()} total bytes.`
+ );
+ });
+
+ archive.on('error', function(err) {
+ throw new TypeError(
+ `export: An error occured during zipping the patterns: ${err}`
+ );
+ });
+
+ archive.pipe(output);
+
+ archive
+ .glob(
+ '?(_patterns|_data|_meta|_annotations)/**',
+ {
+ cwd: config.paths.source.root,
+ },
+ {}
+ )
+ .finalize();
+}
+
+module.exports = exportPatterns;
diff --git a/packages/cli/bin/ask.js b/packages/cli/bin/ask.js
new file mode 100644
index 000000000..55a337230
--- /dev/null
+++ b/packages/cli/bin/ask.js
@@ -0,0 +1,49 @@
+'use strict';
+const inquirer = require('inquirer');
+const wrapsAsync = require('./utils').wrapAsync;
+const confirmSetup = require('./inquiries/confirm');
+const editionSetup = require('./inquiries/edition');
+const starterkitSetup = require('./inquiries/starterkit');
+const ask = inquirer.prompt;
+
+/**
+ * @func init
+ * @desc Initiates a Pattern Lab project by getting user input through inquiry. Scaffolds the project and download mandatory files
+ * @param {object} options - Options passed in from CLI
+ * @param {boolean} options.force - Flag whether to force install in existing project directory. May overwrite stuff.
+ */
+const init = options =>
+ wrapsAsync(function*() {
+ /**
+ * @property {string} project_root="./" - Path to the project root directory
+ * @property {string|Symbol} edition - The name of the edition npm package or a Symbol for no install
+ */
+ const editionAnswers = yield ask(editionSetup);
+
+ /**
+ * @property {object|Symbol} starterkit - The name of a starterkit npm package or a Symbol for no install
+ */
+ const starterkitAnswers = yield ask(starterkitSetup);
+
+ /**
+ * @property {boolean} confirm - A bool hold the confirmation status
+ */
+ const confirmation = yield ask(confirmSetup);
+
+ // IF we have no confirmation we start all over again.
+ if (!confirmation.confirm) {
+ return init(options);
+ }
+
+ return {
+ // Destructure the answers
+ projectDir: editionAnswers.project_root,
+ edition: editionAnswers.edition !== false ? editionAnswers.edition : '',
+ starterkit:
+ starterkitAnswers.starterkit !== false
+ ? starterkitAnswers.starterkit
+ : '',
+ };
+ });
+
+module.exports = init;
diff --git a/packages/cli/bin/build.js b/packages/cli/bin/build.js
new file mode 100644
index 000000000..51108a33d
--- /dev/null
+++ b/packages/cli/bin/build.js
@@ -0,0 +1,38 @@
+'use strict';
+const pl = require('@pattern-lab/core');
+const { debug } = require('./utils');
+const isValidConfig = require('./validate-config');
+
+/**
+ * @func build
+ * @desc Init patternLab core and build the Pattern Lab files.
+ * @param {object} config - The passed Pattern Lab config.
+ * @param {object} options - Additional opts to specify build mode.
+ */
+function build(config, options) {
+ if (!isValidConfig) {
+ throw new TypeError(
+ 'build: Expects config not to be empty and of type object.'
+ );
+ }
+
+ // Initiate Pattern Lab core with the config
+ const patternLab = pl(config);
+
+ /**
+ * Check whether a flag was passed for build
+ * 1. Build only patterns
+ * 2. Normal build
+ */
+ if (options && options.patternsOnly) {
+ // 1
+ debug(`build: Building only patterns now into ${config.paths.public.root}`);
+ return patternLab.patternsonly(config.cleanPublic);
+ } else {
+ // 2
+ debug(`build: Building your project now into ${config.paths.public.root}`);
+ return patternLab.build(config.cleanPublic);
+ }
+}
+
+module.exports = build;
diff --git a/packages/cli/bin/cli-actions/build.js b/packages/cli/bin/cli-actions/build.js
new file mode 100644
index 000000000..c6a34434a
--- /dev/null
+++ b/packages/cli/bin/cli-actions/build.js
@@ -0,0 +1,17 @@
+'use strict';
+const buildPatterns = require('../build');
+const resolveConfig = require('../resolve-config');
+const { error, info, wrapAsync } = require('../utils');
+
+const build = options =>
+ wrapAsync(function*() {
+ try {
+ const config = yield resolveConfig(options.parent.config);
+ yield buildPatterns(config, options);
+ info(`build: Yay, your Pattern Lab project was successfully built ☺`);
+ } catch (err) {
+ error(err);
+ }
+ });
+
+module.exports = build;
diff --git a/packages/cli/bin/cli-actions/disable.js b/packages/cli/bin/cli-actions/disable.js
new file mode 100644
index 000000000..6c53cb5a2
--- /dev/null
+++ b/packages/cli/bin/cli-actions/disable.js
@@ -0,0 +1,42 @@
+'use strict';
+const ora = require('ora');
+const _ = require('lodash');
+const resolveConfig = require('../resolve-config');
+const wrapAsync = require('../utils').wrapAsync;
+const writeJsonAsync = require('../utils').writeJsonAsync;
+
+/**
+ * disable
+ * @desc Handles deactivation of starterkits/plugins
+ * @param {object} options
+ */
+const enable = options =>
+ wrapAsync(function*() {
+ const {
+ parent: { config: configPath },
+ plugins,
+ } = options;
+ const config = yield resolveConfig(configPath);
+
+ const spinner = ora(`⊙ patternlab → Disable …`).start();
+
+ if (plugins && Array.isArray(plugins)) {
+ spinner.succeed(
+ `⊙ patternlab → Disable following plugins: ${plugins.join(', ')}`
+ );
+ plugins.map(plugin => {
+ if (_.has(config, `plugins[${plugin}]`)) {
+ _.set(config, `plugins[${plugin}]['enabled']`, false);
+ spinner.succeed(
+ `⊙ patternlab → Disabled following plugin: ${plugin}`
+ );
+ } else {
+ spinner.warn(`⊙ patternlab → Couldn't find plugin: ${plugin}`);
+ }
+ });
+ }
+ yield writeJsonAsync(options.parent.config, config);
+ spinner.succeed(`⊙ patternlab → Updated config`);
+ });
+
+module.exports = enable;
diff --git a/packages/cli/bin/cli-actions/enable.js b/packages/cli/bin/cli-actions/enable.js
new file mode 100644
index 000000000..0edb57401
--- /dev/null
+++ b/packages/cli/bin/cli-actions/enable.js
@@ -0,0 +1,40 @@
+'use strict';
+const ora = require('ora');
+const _ = require('lodash');
+const resolveConfig = require('../resolve-config');
+const wrapAsync = require('../utils').wrapAsync;
+const writeJsonAsync = require('../utils').writeJsonAsync;
+
+/**
+ * enable
+ * @desc Handles activation of starterkits/plugins
+ * @param {object} options
+ */
+const enable = options =>
+ wrapAsync(function*() {
+ const {
+ parent: { config: configPath },
+ plugins,
+ } = options;
+ const config = yield resolveConfig(configPath);
+
+ const spinner = ora(`⊙ patternlab → Enable …`).start();
+
+ if (plugins && Array.isArray(plugins)) {
+ spinner.succeed(
+ `⊙ patternlab → Enable following plugins: ${plugins.join(', ')}`
+ );
+ plugins.map(plugin => {
+ if (_.has(config, `plugins[${plugin}]`)) {
+ _.set(config, `plugins[${plugin}]['enabled']`, true);
+ spinner.succeed(`⊙ patternlab → Enabled following plugin: ${plugin}`);
+ } else {
+ spinner.warn(`⊙ patternlab → Couldn't find plugin: ${plugin}`);
+ }
+ });
+ }
+ yield writeJsonAsync(options.parent.config, config);
+ spinner.succeed(`⊙ patternlab → Updated config`);
+ });
+
+module.exports = enable;
diff --git a/packages/cli/bin/cli-actions/export.js b/packages/cli/bin/cli-actions/export.js
new file mode 100644
index 000000000..5f4f4ebc7
--- /dev/null
+++ b/packages/cli/bin/cli-actions/export.js
@@ -0,0 +1,12 @@
+'use strict';
+const archive = require('../archive');
+const resolveConfig = require('../resolve-config');
+const wrapAsync = require('../utils').wrapAsync;
+
+const _export = options =>
+ wrapAsync(function*() {
+ const config = yield resolveConfig(options.parent.config);
+ archive(config);
+ });
+
+module.exports = _export;
diff --git a/packages/cli/bin/cli-actions/help.js b/packages/cli/bin/cli-actions/help.js
new file mode 100644
index 000000000..6448e7749
--- /dev/null
+++ b/packages/cli/bin/cli-actions/help.js
@@ -0,0 +1,10 @@
+'use strict';
+module.exports = () => {
+ /* eslint-disable */
+ console.log(`
+ Examples:
+ $ patternlab init # Initialize a Pattern Lab project.');
+ $ patternlab # Builds Pattern Lab from the current dir');
+ $ patternlab --config # Pattern Lab from a config in a specified directory');`);
+ /* eslint-enable */
+};
diff --git a/packages/cli/bin/cli-actions/init.js b/packages/cli/bin/cli-actions/init.js
new file mode 100644
index 000000000..afc715a99
--- /dev/null
+++ b/packages/cli/bin/cli-actions/init.js
@@ -0,0 +1,89 @@
+'use strict';
+const patternlab = require('@pattern-lab/core');
+const merge = require('deepmerge');
+const ask = require('../ask');
+const scaffold = require('../scaffold');
+const installEdition = require('../install-edition');
+const installStarterkit = require('../install-starterkit');
+const replaceConfigPaths = require('../replace-config');
+const ora = require('ora');
+const path = require('path');
+const wrapAsync = require('../utils').wrapAsync;
+const writeJsonAsync = require('../utils').writeJsonAsync;
+
+const defaultPatternlabConfig = patternlab.getDefaultConfig();
+
+// https://github.com/TehShrike/deepmerge#overwrite-array
+const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray;
+
+const init = options =>
+ wrapAsync(function*() {
+ const sourceDir = 'source';
+ const publicDir = 'public';
+ const exportDir = 'pattern_exports';
+ const answers = options.projectDir ? options : yield ask(options);
+ const projectDir = answers.projectDir || './';
+ const edition = answers.edition;
+ const starterkit = answers.starterkit;
+
+ /**
+ * Process the init routines
+ * 1 Replace config paths
+ * 2. Scaffold the folder structure
+ * 3. If `edition` is present:
+ * 3.1 Install edition
+ * 3.2 Reassign adjustedconfig
+ * 4. If `starterkit` is present install it and copy over the mandatory starterkit files to sourceDir
+ * 5. Save patternlab-config.json in projectDir
+ */
+ const spinner = ora(`Setting up Pattern Lab in ${projectDir}`).start();
+ let patternlabConfig = replaceConfigPaths(
+ defaultPatternlabConfig,
+ projectDir,
+ sourceDir,
+ publicDir,
+ exportDir
+ ); // 1
+
+ yield scaffold(projectDir, sourceDir, publicDir, exportDir); // 2
+
+ if (edition) {
+ spinner.text = `⊙ patternlab → Installing edition: ${edition}`;
+ const newConf = yield installEdition(
+ edition,
+ patternlabConfig,
+ projectDir
+ ); // 3.1
+ if (newConf) {
+ patternlabConfig = merge(patternlabConfig, newConf, {
+ arrayMerge: overwriteMerge,
+ }); // 3.2
+ }
+ spinner.succeed(`⊙ patternlab → Installed edition: ${edition}`);
+ }
+ if (starterkit) {
+ spinner.text = `⊙ patternlab → Installing starterkit ${starterkit}`;
+ spinner.start();
+ const starterkitConfig = yield installStarterkit(
+ starterkit,
+ patternlabConfig
+ );
+ spinner.succeed(`⊙ patternlab → Installed starterkit: ${starterkit}`);
+ if (starterkitConfig) {
+ patternlabConfig = merge(patternlabConfig, starterkitConfig, {
+ arrayMerge: overwriteMerge,
+ });
+ }
+ } // 4
+ yield writeJsonAsync(
+ path.resolve(projectDir, 'patternlab-config.json'),
+ patternlabConfig
+ ); // 5
+
+ spinner.succeed(
+ `⊙ patternlab → Yay ☺. Pattern Lab Node was successfully initialized in ${projectDir}`
+ );
+ return true;
+ });
+
+module.exports = init;
diff --git a/packages/cli/bin/cli-actions/install.js b/packages/cli/bin/cli-actions/install.js
new file mode 100644
index 000000000..28aa8a445
--- /dev/null
+++ b/packages/cli/bin/cli-actions/install.js
@@ -0,0 +1,65 @@
+'use strict';
+const ora = require('ora');
+const installPlugin = require('../install-plugin');
+const installStarterkit = require('../install-starterkit');
+const resolveConfig = require('../resolve-config');
+const wrapAsync = require('../utils').wrapAsync;
+const writeJsonAsync = require('../utils').writeJsonAsync;
+
+/**
+ * install
+ * @desc Handles async install and activation of starterkits/plugins
+ * @param {object} options
+ */
+const install = options =>
+ wrapAsync(function*() {
+ const config = yield resolveConfig(options.parent.config);
+
+ const spinner = ora(
+ `⊙ patternlab → Installing additional resources …`
+ ).start();
+
+ if (options.starterkits && Array.isArray(options.starterkits)) {
+ const starterkits = yield Promise.all(
+ options.starterkits.map(starterkit =>
+ wrapAsync(function*() {
+ spinner.text = `⊙ patternlab → Installing starterkit: ${starterkit}`;
+ return yield installStarterkit(
+ {
+ name: starterkit,
+ value: starterkit,
+ },
+ config
+ );
+ })
+ )
+ );
+ spinner.succeed(
+ `⊙ patternlab → Installed following starterkits: ${starterkits.join(
+ ', '
+ )}`
+ );
+ }
+ if (options.plugins && Array.isArray(options.plugins)) {
+ const plugins = yield Promise.all(
+ options.plugins.map(plugin =>
+ wrapAsync(function*() {
+ return yield installPlugin(
+ {
+ name: plugin,
+ value: plugin,
+ },
+ config
+ );
+ })
+ )
+ );
+ spinner.succeed(
+ `⊙ patternlab → Installed following plugins: ${plugins.join(', ')}`
+ );
+ }
+ yield writeJsonAsync(options.parent.config, config);
+ spinner.succeed(`⊙ patternlab → Updated config`);
+ });
+
+module.exports = install;
diff --git a/packages/cli/bin/cli-actions/serve.js b/packages/cli/bin/cli-actions/serve.js
new file mode 100644
index 000000000..6e2cc8fd2
--- /dev/null
+++ b/packages/cli/bin/cli-actions/serve.js
@@ -0,0 +1,12 @@
+'use strict';
+const resolveConfig = require('../resolve-config');
+const servePatterns = require('../serve');
+const wrapAsync = require('../utils').wrapAsync;
+
+const serve = options =>
+ wrapAsync(function*() {
+ const config = yield resolveConfig(options.parent.config);
+ servePatterns(config, options);
+ });
+
+module.exports = serve;
diff --git a/packages/cli/bin/cli-actions/version.js b/packages/cli/bin/cli-actions/version.js
new file mode 100644
index 000000000..9332488b0
--- /dev/null
+++ b/packages/cli/bin/cli-actions/version.js
@@ -0,0 +1,5 @@
+'use strict';
+const patternlab = require('@pattern-lab/core');
+
+module.exports = ({ version }) =>
+ `${version} (Pattern Lab Node Core version: ${patternlab.getVersion()})`;
diff --git a/packages/cli/bin/inquiries/confirm.js b/packages/cli/bin/inquiries/confirm.js
new file mode 100644
index 000000000..9cabdf88d
--- /dev/null
+++ b/packages/cli/bin/inquiries/confirm.js
@@ -0,0 +1,12 @@
+'use strict';
+/** confirmSetup {Array} - Inquirer question to confirm selection */
+const confirmSetup = [
+ {
+ type: 'confirm',
+ name: 'confirm',
+ message: 'Are you happy with your choices? (Hit enter for YES)?',
+ default: true,
+ },
+];
+
+module.exports = confirmSetup;
diff --git a/packages/cli/bin/inquiries/edition.js b/packages/cli/bin/inquiries/edition.js
new file mode 100644
index 000000000..f62dba232
--- /dev/null
+++ b/packages/cli/bin/inquiries/edition.js
@@ -0,0 +1,40 @@
+'use strict';
+const inquirer = require('inquirer');
+
+/** editionSetup {Array} - Inquirer question logic for first question regarding editions */
+const editionSetup = [
+ {
+ type: 'input',
+ name: 'project_root',
+ message: 'Please specify a directory for your Pattern Lab project.',
+ default: () => './',
+ },
+ {
+ type: 'list',
+ name: 'edition',
+ message: 'What templating language do you want to use with Pattern Lab?',
+ choices: [
+ {
+ name: 'Handlebars',
+ value: '@pattern-lab/edition-node',
+ },
+ {
+ name: 'Twig (PHP)',
+ value: '@pattern-lab/edition-twig',
+ },
+ new inquirer.Separator(),
+ {
+ name: 'None',
+ value: false,
+ },
+ ],
+ default: function() {
+ return {
+ name: 'Handlebars',
+ value: '@pattern-lab/edition-node',
+ };
+ },
+ },
+];
+
+module.exports = editionSetup;
diff --git a/packages/cli/bin/inquiries/starterkit.js b/packages/cli/bin/inquiries/starterkit.js
new file mode 100644
index 000000000..523eb64bc
--- /dev/null
+++ b/packages/cli/bin/inquiries/starterkit.js
@@ -0,0 +1,50 @@
+'use strict';
+const inquirer = require('inquirer');
+const CUSTOM_STARTERKIT = Symbol('CUSTOM_STARTERKIT');
+
+/** starterkitSetup {Array} - Inquirer question logic for regarding starterkits */
+const starterkitSetup = [
+ {
+ type: 'list',
+ name: 'starterkit',
+ message: 'What initial patterns do you want included in your project?',
+ choices: [
+ {
+ name:
+ 'Handlebars base patterns (some basic patterns to get started with)',
+ value: '@pattern-lab/starterkit-handlebars-vanilla',
+ },
+ {
+ name: 'Handlebars demo patterns (full demo website and patterns)',
+ value: '@pattern-lab/starterkit-handlebars-demo',
+ },
+ {
+ name: 'Twig (PHP) demo patterns (full demo website and patterns)',
+ value: '@pattern-lab/starterkit-twig-demo',
+ },
+ new inquirer.Separator(),
+ {
+ name: 'Custom starterkit',
+ value: CUSTOM_STARTERKIT,
+ },
+ new inquirer.Separator(),
+ {
+ name: 'Blank project (no patterns)',
+ value: false,
+ },
+ ],
+ default: {
+ name: 'Handlebars demo patterns (full demo website and patterns)',
+ value: 'starterkit-handlebars-demo',
+ },
+ },
+ {
+ name: 'starterkit',
+ message: 'Type the name of the custom starterkit to use:',
+ type: 'input',
+ when(answers) {
+ return answers.starterkit === CUSTOM_STARTERKIT;
+ },
+ },
+];
+module.exports = starterkitSetup;
diff --git a/packages/cli/bin/install-edition.js b/packages/cli/bin/install-edition.js
new file mode 100644
index 000000000..a8cffe72b
--- /dev/null
+++ b/packages/cli/bin/install-edition.js
@@ -0,0 +1,109 @@
+/* eslint-disable no-param-reassign */
+'use strict';
+
+const path = require('path');
+const merge = require('deepmerge');
+const EOL = require('os').EOL;
+const {
+ checkAndInstallPackage,
+ copyAsync,
+ wrapAsync,
+ writeJsonAsync,
+ getJSONKey,
+} = require('./utils');
+
+// https://github.com/TehShrike/deepmerge#overwrite-array
+const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray;
+
+const installEdition = (edition, config, projectDir) => {
+ const pkg = require(path.resolve(projectDir, 'package.json'));
+
+ return wrapAsync(function*() {
+ /**
+ * 1. Trigger edition install
+ * 2. Copy over the mandatory edition files to sourceDir
+ * 3. Copy dependencies defined in edition
+ * 4. Do custom post-install procedures for different core editions:
+ * 4.1 Copy gulpfile.js for edition-node-gulp
+ * 4.2 Copy scripts for edition-node
+ * 4.3 Copy items for edition-twig
+ */
+ const sourceDir = config.paths.source.root;
+ yield checkAndInstallPackage(edition); // 1
+ yield copyAsync(
+ path.resolve('./node_modules', edition, 'source', '_meta'),
+ path.resolve(sourceDir, '_meta')
+ ); // 2
+ pkg.dependencies = Object.assign(
+ {},
+ pkg.dependencies || {},
+ yield getJSONKey(edition, 'dependencies')
+ ); // 3
+ switch (
+ edition // 4
+ ) {
+ // 4.1
+ case '@pattern-lab/edition-node-gulp': {
+ yield copyAsync(
+ path.resolve('./node_modules', edition, 'gulpfile.js'),
+ path.resolve(sourceDir, '../', 'gulpfile.js')
+ );
+ break;
+ }
+ // 4.2
+ case '@pattern-lab/edition-node': {
+ const editionPath = path.resolve('./node_modules', edition);
+ const editionConfigPath = path.resolve(
+ editionPath,
+ 'patternlab-config.json'
+ );
+
+ const editionConfig = require(editionConfigPath);
+
+ pkg.scripts = Object.assign(
+ {},
+ pkg.scripts || {},
+ yield getJSONKey(edition, 'scripts')
+ );
+
+ yield copyAsync(
+ path.join(editionPath, path.sep, 'helpers', path.sep, 'test.js'),
+ path.resolve(sourceDir, '../', 'helpers/test.js')
+ );
+
+ config = merge(config, editionConfig, { arrayMerge: overwriteMerge });
+ break;
+ }
+ // 4.3
+ case '@pattern-lab/edition-twig': {
+ const editionPath = path.resolve('./node_modules', edition);
+ const editionConfigPath = path.resolve(
+ editionPath,
+ 'patternlab-config.json'
+ );
+ const editionConfig = require(editionConfigPath);
+
+ pkg.scripts = Object.assign(
+ {},
+ pkg.scripts || {},
+ yield getJSONKey(edition, 'scripts')
+ );
+
+ yield copyAsync(
+ path.resolve(editionPath, 'alter-twig.php'),
+ path.resolve(sourceDir, '../', 'alter-twig.php')
+ );
+
+ config = merge(config, editionConfig, { arrayMerge: overwriteMerge });
+ break;
+ }
+ }
+ yield writeJsonAsync(path.resolve(projectDir, 'package.json'), pkg, {
+ spaces: 2,
+ EOL: EOL,
+ });
+ return config;
+ });
+};
+
+module.exports = installEdition;
diff --git a/packages/cli/bin/install-plugin.js b/packages/cli/bin/install-plugin.js
new file mode 100644
index 000000000..f297bd62a
--- /dev/null
+++ b/packages/cli/bin/install-plugin.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const path = require('path');
+
+const _ = require('lodash');
+
+const checkAndInstallPackage = require('./utils').checkAndInstallPackage;
+const wrapAsync = require('./utils').wrapAsync;
+
+const installPlugin = (plugin, config) =>
+ wrapAsync(function*() {
+ const name = plugin.name || plugin;
+ yield checkAndInstallPackage(name);
+ // Put the installed plugin in the patternlab-config.json
+ _.set(config, `plugins[${name}]['enabled']`, true);
+ _.set(config, `plugins[${name}]['initialized']`, false);
+
+ // Get the options from the plugin, if any
+ const pluginPathConfig = path.resolve(
+ path.join(process.cwd(), 'node_modules', name, 'config.json')
+ );
+ try {
+ const pluginConfigJSON = require(pluginPathConfig);
+ if (!_.has(config.plugins[name].options)) {
+ _.set(config, `plugins[${name}][options]`, pluginConfigJSON);
+ }
+ } catch (ex) {
+ //a config.json file is not required at this time
+ }
+ return name;
+ });
+
+module.exports = installPlugin;
diff --git a/packages/cli/bin/install-starterkit.js b/packages/cli/bin/install-starterkit.js
new file mode 100644
index 000000000..f7ce2b61c
--- /dev/null
+++ b/packages/cli/bin/install-starterkit.js
@@ -0,0 +1,26 @@
+'use strict';
+const path = require('path');
+const fs = require('fs-extra');
+const {
+ copyAsync,
+ wrapAsync,
+ checkAndInstallPackage,
+ readJsonAsync,
+} = require('./utils');
+
+const installStarterkit = (starterkit, config) =>
+ wrapAsync(function*() {
+ const sourceDir = config.paths.source.root;
+ const name = starterkit.value || starterkit;
+ yield checkAndInstallPackage(name);
+ const kitPath = path.resolve('./node_modules', name);
+ yield copyAsync(path.resolve(kitPath, 'dist'), path.resolve(sourceDir));
+ let kitConfig;
+ const kitConfigPath = path.resolve(kitPath, 'patternlab-config.json');
+ if (fs.existsSync(kitConfigPath)) {
+ kitConfig = yield readJsonAsync(kitConfigPath);
+ }
+ return kitConfig;
+ });
+
+module.exports = installStarterkit;
diff --git a/packages/cli/bin/patternlab.js b/packages/cli/bin/patternlab.js
new file mode 100755
index 000000000..a76468994
--- /dev/null
+++ b/packages/cli/bin/patternlab.js
@@ -0,0 +1,152 @@
+#!/usr/bin/env node
+/* eslint-disable no-unused-vars */
+'use strict';
+const cli = require('commander');
+const path = require('path');
+const build = require('./cli-actions/build');
+const disable = require('./cli-actions/disable');
+const enable = require('./cli-actions/enable');
+const help = require('./cli-actions/help');
+const version = require('./cli-actions/version');
+const init = require('./cli-actions/init');
+const install = require('./cli-actions/install');
+const exportPatterns = require('./cli-actions/export');
+const serve = require('./cli-actions/serve');
+const { error, log } = require('./utils');
+const pkg = require('../package.json');
+
+// Register info and error logging
+log.on('patternlab.error', err => console.log(err)); // eslint-disable-line
+log.on('patternlab.info', msg => console.log(msg)); // eslint-disable-line
+
+// Conditionally register verbose logging
+const verboseLogs = verbose =>
+ log.on('patternlab.debug', msg => console.log(msg)); // eslint-disable-line
+
+// Conditionally unregister all logging
+const silenceLogs = () => {
+ log.removeAllListeners('patternlab.debug');
+ log.removeAllListeners('patternlab.info');
+ log.removeAllListeners('patternlab.error');
+};
+
+// Split strings into an array
+const list = val => val.split(',');
+
+/**
+ * Hook up cli version, usage and options
+ */
+cli
+ .version(version(pkg), '-V, --version')
+ .usage(' [options]')
+ .arguments(' [options]')
+ .option(
+ '-c, --config ',
+ 'Specify config file. Default looks up the project dir',
+ val => val.trim(),
+ path.resolve(process.cwd(), 'patternlab-config.json')
+ )
+ .option('-v, --verbose', 'Show verbose console logs', verboseLogs)
+ .option('--silent', 'Turn off console logs', silenceLogs);
+
+/**
+ * build
+ * @desc Setup Pattern Lab's `build` cmd
+ */
+cli
+ .command('build')
+ .alias('compile')
+ .description('Build Pattern Lab. Optionally (re-)build only the patterns')
+ .option('-p, --patterns-only', 'Whether to only build patterns')
+ .option('--no-watch', 'Start watching for changes')
+ .action(build);
+
+/**
+ * export
+ * @desc Export a Pattern Lab patterns into a compressed format
+ */
+cli
+ .command('export')
+ .description('Export Pattern Lab patterns into a compressed format')
+ .action(exportPatterns);
+
+/**
+ * init
+ * @desc Initialize a Pattern Lab project from scratch or import an edition and/or starterkit
+ */
+cli
+ .command('init')
+ .description(
+ 'Initialize a Pattern Lab project from scratch or import an edition and/or starterkit'
+ )
+ .option('-p, --project-dir ', 'Specify a project directory')
+ .option('-e, --edition ', 'Specify an edition to install')
+ .option('-k, --starterkit ', 'Specify a starterkit to install')
+ .action(init);
+
+/**
+ * install
+ * @desc Installs Pattern Lab related modules like starterkits or plugins
+ */
+cli
+ .command('install')
+ .alias('add')
+ .description(
+ 'Installs Pattern Lab related modules like starterkits or plugins'
+ )
+ .option(
+ '--starterkits ',
+ 'Specify one or more starterkit to install',
+ list
+ )
+ .option('--plugins ', 'Specify one or more plugins to install', list)
+ .action(install);
+
+/**
+ * enable
+ * @desc Enable Pattern Lab plugins. Unavailable plugins are just skipped
+ */
+cli
+ .command('enable')
+ .alias('on')
+ .description('Enable Pattern Lab plugins')
+ .option('--plugins ', 'Specify one or more plugins to enable', list)
+ .action(enable);
+
+/**
+ * disable
+ * @desc Enable Pattern Lab plugins. Unavailable plugins are just skipped
+ */
+cli
+ .command('disable')
+ .alias('off')
+ .description('Disable Pattern Lab plugins')
+ .option('--plugins ', 'Specify one or more plugins to disable', list)
+ .action(disable);
+
+/**
+ * serve
+ * @desc Starts a server to inspect files in browser
+ */
+cli
+ .command('serve')
+ .alias('browse')
+ .description('Starts a server to inspect files in browser')
+ .option('--no-watch', 'Start watching for changes')
+ .action(serve);
+
+// Show additional help
+cli.on('--help', help);
+
+/**
+ * Catch all unsupported commands and delegate to the cli's help
+ * Parse at the end because Node emit is immediate
+ */
+cli
+ .on('*', () => {
+ error(
+ 'Invalid command provided. See the help for available commands/options.'
+ );
+ cli.help();
+ })
+ .parse(process.argv);
diff --git a/packages/cli/bin/replace-config.js b/packages/cli/bin/replace-config.js
new file mode 100644
index 000000000..19dd7f2ea
--- /dev/null
+++ b/packages/cli/bin/replace-config.js
@@ -0,0 +1,37 @@
+'use strict';
+const path = require('path');
+const _ = require('lodash');
+
+/**
+ * @func replaceConfigPaths
+ * @desc Immutable replace source and public paths in the passed config.
+ * @param {config} config - The passed Pattern Lab config.
+ * @param {string} projectDir - The project directory path, defaults to ./
+ * @param {string} sourceDir - The source root directory path.
+ * @param {string} publicDir - The public root directory path.
+ * @param {string} exportDir - The export root directory path.
+ * @return {config} - Returns a modified config. Original stays unaltered.
+ */
+function replaceConfigPaths(
+ config,
+ projectDir,
+ sourceDir,
+ publicDir,
+ exportDir
+) {
+ const conf = Object.assign({}, config);
+ _.map(conf.paths.source, (value, key) => {
+ conf.paths.source[key] = _.isString(value)
+ ? value.replace(/^\.\/source/g, path.join(projectDir, sourceDir))
+ : value;
+ });
+ _.map(conf.paths.public, (value, key) => {
+ conf.paths.public[key] = _.isString(value)
+ ? value.replace(/^\.\/public/g, path.join(projectDir, publicDir))
+ : value;
+ });
+ conf.patternExportDirectory = path.join(projectDir, exportDir);
+ return conf;
+}
+
+module.exports = replaceConfigPaths;
diff --git a/packages/cli/bin/resolve-config.js b/packages/cli/bin/resolve-config.js
new file mode 100644
index 000000000..f34c5e083
--- /dev/null
+++ b/packages/cli/bin/resolve-config.js
@@ -0,0 +1,45 @@
+'use strict';
+const exists = require('path-exists');
+const path = require('path');
+const error = require('./utils').error;
+const readJsonAsync = require('./utils').readJsonAsync;
+const wrapAsync = require('./utils').wrapAsync;
+
+/**
+ * @func resolveConfig
+ * @desc Resolves the given Pattern Lab config file.
+ * @param {string} [configPath=./patternlab-config.json] - Path to the patternlab-config.json. Defaults to project dir.
+ * @return {object|boolean} Returns the config object or false otherwise.
+ */
+function resolveConfig(configPath) {
+ return wrapAsync(function*() {
+ if (typeof configPath !== 'string') {
+ error(
+ 'resolveConfig: If configPath is set, it is expected to be of type string.'
+ );
+ return false;
+ }
+ if (!exists.sync(configPath)) {
+ error(`resolveConfig: configPath ${configPath} does not exists`);
+ return false;
+ }
+
+ /**
+ * Setup the config.
+ * 1. Check if user specified custom Pattern Lab config location
+ * 2. Read the config file
+ */
+ try {
+ const absoluteConfigPath = path.resolve(configPath); // 1
+ return yield readJsonAsync(absoluteConfigPath); // 2
+ } catch (err) {
+ error(
+ 'resolveConfig: Got an error during parsing your Pattern Lab config. Please make sure your config file exists.'
+ );
+ error(err);
+ return false;
+ }
+ });
+}
+
+module.exports = resolveConfig;
diff --git a/packages/cli/bin/scaffold.js b/packages/cli/bin/scaffold.js
new file mode 100644
index 000000000..de1e4df73
--- /dev/null
+++ b/packages/cli/bin/scaffold.js
@@ -0,0 +1,39 @@
+'use strict';
+const path = require('path');
+const execa = require('execa');
+const fs = require('fs-extra');
+const wrapAsync = require('./utils').wrapAsync;
+const mkdirsAsync = require('./utils').mkdirsAsync;
+
+/**
+ * @func scaffold
+ * @desc Generate file and folder structure for a Pattern Lab project
+ * @param {string} projectDir - The project root directory path.
+ * @param {string} sourceDir - The source root directory path.
+ * @param {string} publicDir - The public root directory path.
+ * @param {string} exportDir - The export root directory path.
+ * @return {void}
+ */
+const scaffold = (projectDir, sourceDir, publicDir, exportDir) =>
+ wrapAsync(function*() {
+ const projectPath = path.join(process.cwd(), projectDir);
+ if (!fs.existsSync(path.join(projectPath, 'package.json'))) {
+ fs.ensureDirSync(projectPath);
+ execa.sync('npm', ['init', '-y'], {
+ cwd: projectPath,
+ });
+ }
+ /**
+ * Create mandatory files structure
+ * 1. Create project source directory
+ * 2. Create project public directory
+ * 3. Create project export directory
+ */
+ yield Promise.all([
+ mkdirsAsync(path.resolve(projectDir, path.normalize(sourceDir))), // 1
+ mkdirsAsync(path.resolve(projectDir, path.normalize(publicDir))), // 2
+ mkdirsAsync(path.resolve(projectDir, path.normalize(exportDir))), // 3
+ ]);
+ });
+
+module.exports = scaffold;
diff --git a/packages/cli/bin/serve.js b/packages/cli/bin/serve.js
new file mode 100644
index 000000000..a7502b65f
--- /dev/null
+++ b/packages/cli/bin/serve.js
@@ -0,0 +1,47 @@
+'use strict';
+const patternlab = require('@pattern-lab/core');
+const _ = require('lodash');
+
+const isValidConfig = require('./validate-config');
+const { error, info } = require('./utils');
+
+/**
+ * @func serve
+ * @desc Start a browser-sync server in the Pattern Lab public dir
+ * @param {object} config - The passed Pattern Lab config
+ * @param {object} options - The passed options at invocation time
+ */
+function serve(config, options) {
+ if (!isValidConfig) {
+ throw new TypeError(
+ 'serve: Expects config not to be empty and of type object.'
+ );
+ }
+
+ if (
+ !_.has(config, 'paths.public.root') ||
+ _.isEmpty(config.paths.public.root)
+ ) {
+ throw new TypeError(
+ 'serve: config.paths.public.root is empty or does not exist. Please check your Pattern Lab config.'
+ );
+ }
+ if (
+ !_.has(config, 'paths.source.root') ||
+ _.isEmpty(config.paths.source.root)
+ ) {
+ throw new TypeError(
+ 'serve: config.paths.source.root is empty or does not exist. Please check your Pattern Lab config.'
+ );
+ }
+
+ try {
+ info(`serve: Serving your files …`);
+ const pl = patternlab(config);
+ pl.server.serve(options);
+ } catch (err) {
+ error(err);
+ }
+}
+
+module.exports = serve;
diff --git a/packages/cli/bin/utils.js b/packages/cli/bin/utils.js
new file mode 100644
index 000000000..771a6f1a9
--- /dev/null
+++ b/packages/cli/bin/utils.js
@@ -0,0 +1,216 @@
+'use strict';
+const fs = require('fs-extra');
+const spawn = require('execa');
+const glob = require('glob');
+const path = require('path');
+const chalk = require('chalk');
+const EventEmitter = require('events').EventEmitter;
+const hasYarn = require('has-yarn');
+
+/**
+ * @name log
+ * @desc tiny event-based logger
+ * @type {*}
+ */
+const log = Object.assign(
+ {
+ debug(msg) {
+ this.emit(
+ 'patternlab.debug',
+ `${chalk.green('⊙ patternlab →')} ${chalk.dim(msg)}`
+ );
+ },
+ info(msg) {
+ this.emit('patternlab.info', `⊙ patternlab → ${chalk.dim(msg)}`);
+ },
+ error(msg) {
+ this.emit(
+ 'patternlab.error',
+ `${chalk.red('⊙ patternlab →')} ${chalk.dim(msg)}`
+ );
+ },
+ },
+ EventEmitter.prototype
+);
+
+/**
+ * @func debug
+ * @desc Coloured debug log
+ * @param {*} msg - The variadic messages to log out.
+ * @return {void}
+ */
+const debug = log.debug.bind(log);
+
+/**
+ * @func info
+ * @desc Coloured debug log
+ * @param {*} msg - The variadic messages to log out.
+ * @return {void}
+ */
+const info = log.info.bind(log);
+
+/**
+ * @func error
+ * @desc Coloured error log
+ * @param {*} e - The variadic messages to log out.
+ * @return {void}
+ */
+const error = log.error.bind(log);
+
+/**
+ * @func wrapAsync
+ * @desc Wraps an generator function to yield out promisified stuff
+ * @param {function} fn - Takes a generator function
+ */
+const wrapAsync = fn =>
+ new Promise((resolve, reject) => {
+ const generator = fn();
+ /* eslint-disable */
+ (function spwn(val) {
+ let res;
+ try {
+ res =
+ {}.toString.call(val) !== '[object Error]'
+ ? generator.next(val)
+ : generator.throw(val);
+ } catch (err) {
+ return reject(err);
+ }
+ const v = res.value;
+ if (res.done) {
+ return resolve(v);
+ }
+ Promise.resolve(v)
+ .then(spwn)
+ .catch(spwn);
+ })();
+ /* eslint-enable */
+ });
+
+/**
+ * @func glob
+ * @desc Promisified glob function
+ * @param {string} pattern - A glob pattern to match against
+ * @param {object} opts - A configuration object. See glob package for details
+ * @return {Promise}
+ */
+const asyncGlob = (pattern, opts) =>
+ new Promise((resolve, reject) =>
+ glob(pattern, opts, (err, matches) =>
+ err !== null ? reject(err) : resolve(matches)
+ )
+ );
+
+/**
+ * @func copyWithPattern
+ * @desc Copies multiple files asynchronously from one dir to another according to a glob pattern specified
+ * @param {string} cwd - The path to search for file(s) at
+ * @param {string} pattern - A glob pattern to match the file(s)
+ * @param {string} dest - The destination dir path
+ * @return {Promise}
+ */
+const copyWithPattern = (cwd, pattern, dest) =>
+ wrapAsync(function*() {
+ const files = yield asyncGlob(pattern, { cwd: cwd });
+ if (files.length === 0) {
+ debug('copy: Nothing to copy');
+ }
+ // Copy concurrently
+ const promises = files.map(file =>
+ fs.copy(path.join(cwd, file), path.join(dest, file))
+ );
+ return yield Promise.all(promises);
+ });
+
+/**
+ * @func fetchPackage
+ * @desc Fetches and saves packages from npm into node_modules and adds a reference in the package.json under dependencies
+ * @param {string} packageName - The package name
+ */
+const fetchPackage = packageName =>
+ wrapAsync(function*() {
+ const useYarn = hasYarn();
+ const pm = useYarn ? 'yarn' : 'npm';
+ const installCmd = useYarn ? 'add' : 'install';
+ try {
+ if (packageName) {
+ const cmd = yield spawn(pm, [installCmd, packageName]);
+ error(cmd.stderr);
+ }
+ } catch (err) {
+ error(
+ `fetchPackage: Fetching required dependencies from ${pm} failed for ${packageName} with ${err}`
+ );
+ throw err; // Rethrow error
+ }
+ });
+
+/**
+ * @func checkAndInstallPackage
+ * Checks whether a package for a given packageName is installed locally. If package cannot be found, fetch and install it
+ * @param {string} packageName - The package name
+ * @return {boolean}
+ */
+const checkAndInstallPackage = packageName =>
+ wrapAsync(function*() {
+ try {
+ require.resolve(packageName);
+ return true;
+ } catch (err) {
+ debug(
+ `checkAndInstallPackage: ${packageName} not installed. Fetching it now …`
+ );
+ yield fetchPackage(packageName);
+ return false;
+ }
+ });
+
+/**
+ * @func noop
+ * @desc Plain arrow expression for noop
+ */
+const noop = () => {};
+
+/**
+ * @func writeJsonAsync
+ * Wrapper for fs.writeJsonAsync with consistent spacing
+ * @param {string} filePath
+ * @param {object} data
+ */
+const writeJsonAsync = (filePath, data) =>
+ wrapAsync(function*() {
+ yield fs.outputJSON(filePath, data, { spaces: 2 });
+ });
+
+/**
+ * @func getJSONKey
+ * Installs package, then returns the value for the given JSON file's key within
+ * @param {string} packageName - the node_module to install / load
+ * @param {object} key - the key to find
+ * @param {object} fileName - the filePath of the JSON
+ */
+const getJSONKey = (packageName, key, fileName = 'package.json') =>
+ wrapAsync(function*() {
+ yield checkAndInstallPackage(packageName);
+ const jsonData = yield fs.readJson(
+ path.resolve('node_modules', packageName, fileName)
+ );
+ return jsonData[key];
+ });
+
+module.exports = {
+ copyWithPattern,
+ copyAsync: fs.copy,
+ mkdirsAsync: fs.mkdirs,
+ moveAsync: fs.move,
+ writeJsonAsync: writeJsonAsync,
+ readJsonAsync: fs.readJson,
+ error,
+ info,
+ debug,
+ log,
+ wrapAsync,
+ checkAndInstallPackage,
+ noop,
+ getJSONKey,
+};
diff --git a/packages/cli/bin/validate-config.js b/packages/cli/bin/validate-config.js
new file mode 100644
index 000000000..eb4e521e8
--- /dev/null
+++ b/packages/cli/bin/validate-config.js
@@ -0,0 +1,12 @@
+'use strict';
+/**
+ * @func isValidConfig
+ * @desc Checks validity of a patternlab config
+ * @param {object} config - Name of the command to check against.
+ * @return {object} - Returns true is all is good, false otherwise.
+ */
+function isValidConfig(config) {
+ return !config || typeof config !== 'object';
+}
+
+module.exports = isValidConfig;
diff --git a/packages/cli/license b/packages/cli/license
new file mode 100644
index 000000000..ec350935b
--- /dev/null
+++ b/packages/cli/license
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Raphael Okon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/cli/package.json b/packages/cli/package.json
new file mode 100644
index 000000000..847f1d94d
--- /dev/null
+++ b/packages/cli/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "@pattern-lab/cli",
+ "description": "Command-line interface (CLI) for the @pattern-lab/core.",
+ "version": "5.9.3",
+ "bin": {
+ "patternlab": "bin/patternlab.js"
+ },
+ "author": {
+ "name": "Raphael Okon"
+ },
+ "dependencies": {
+ "@pattern-lab/core": "^5.9.3",
+ "@pattern-lab/live-server": "^5.9.3",
+ "@pattern-lab/starterkit-mustache-base": "3.0.3",
+ "archiver": "2.1.1",
+ "chalk": "2.4.1",
+ "commander": "2.15.1",
+ "deepmerge": "^2.1.1",
+ "execa": "0.10.0",
+ "fs-extra": "6.0.1",
+ "glob": "7.1.2",
+ "has-yarn": "1.0.0",
+ "inquirer": "5.1.0",
+ "lodash": "4.17.15",
+ "ora": "2.1.0",
+ "path-exists": "3.0.0",
+ "sanitize-filename": "1.6.1",
+ "starterkit-mustache-acidtest": "0.0.3",
+ "starterkit-mustache-bootstrap": "0.1.1",
+ "starterkit-mustache-foundation": "0.1.1",
+ "starterkit-mustache-materialdesign": "0.1.2"
+ },
+ "devDependencies": {
+ "eslint": "4.18.2",
+ "eslint-config-prettier": "2.9.0",
+ "eslint-plugin-prettier": "2.6.0",
+ "prettier": "1.11.1",
+ "proxyquire": "2.0.1",
+ "tap": "14.6.4"
+ },
+ "files": [
+ "bin"
+ ],
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "JavaScript"
+ ],
+ "scripts": {
+ "lint": "eslint ./{bin,test}",
+ "test:separate": "tap ./test/*.test.js --reporter spec --timeout=120"
+ },
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/cli/readme.md b/packages/cli/readme.md
new file mode 100644
index 000000000..e5a9ae95e
--- /dev/null
+++ b/packages/cli/readme.md
@@ -0,0 +1,142 @@
+# Pattern Lab Node CLI
+
+> Command-line interface (CLI) for the patternlab-node core.
+
+[](https://travis-ci.org/pattern-lab/patternlab-node)
+
+
+## Installation
+*Note: Global installs are currently not supported and will be fixed when the Pattern Lab core hits v3.0.0*
+
+#### Via NPM
+`npm install @pattern-lab/cli --save-dev`
+
+#### Via Yarn
+`yarn add @pattern-lab/cli --dev`
+
+## Configuring Your Project to Use the CLI
+
+If the CLI is installed globally, you may call commands directly, such as `patternlab --version`.
+
+If the CLI is not installed globally, you need to tell `npm` where to find the executable when invoking commands.
+
+Open `package.json` and add the following to your `scripts` object:
+
+```diff
+"scripts": {
++ "patternlab": "patternlab"
+},
+```
+This tells `npm` to look in the local `node_modules/.bin` directory for the `patternlab` CLI.
+
+Subcommands and options can then be forwarded to the CLI like this:
+
+```bash
+npm run patternlab -- serve
+```
+
+Installing [`edition-node`](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node) will add the following CLI commands for convenience:
+
+```diff
+ "scripts": {
++ "pl:build": "patternlab build --config ./patternlab-config.json",
++ "pl:help": "patternlab --help",
++ "pl:install": "patternlab install --config ./patternlab-config.json",
++ "pl:serve": "patternlab serve --config ./patternlab-config.json",
++ "pl:version": "patternlab --version"
+ },
+```
+
+Then you can invoke any of these like this:
+
+```
+npm run pl:serve
+```
+
+## API & Usage
+### General usage
+```
+Usage: patternlab [options]
+ Commands:
+ build|compile [options] Build Pattern Lab. Optionally (re-)build only the patterns
+ export Export a Pattern Lab patterns into a compressed format
+ init [options] Initialize a Pattern Lab project from scratch or import an edition and/or starterkit
+ install|add [options] Installs Pattern Lab related modules like starterkits or plugins
+ serve|browse [options] Starts a server to inspect files in browser
+
+ Options:
+ -h, --help output usage information
+ -V, --version output the version number
+ -c, --config Specify config file. Default looks up the project dir
+ -v, --verbose Show verbose logging
+ --silent Turn off console logs
+```
+
+### Build/Compile Pattern Lab
+```
+Usage: build|compile [options]
+
+Build Pattern Lab. Optionally (re-)build only the patterns
+
+ Options:
+ -h, --help output usage information
+ -p, --patterns-only Whether to only build patterns
+```
+
+### Initialize Pattern Lab
+```
+Usage: init [options]
+
+Initialize a Pattern Lab project from scratch or import an edition and/or starterkit
+Passing no options starts the init in interactive mode
+
+ Options:
+ -h, --help output usage information
+ -p, --project-dir Specify a project directory. Default: ./
+ -e, --edition Specify an edition to install. Default: edition-node
+ -k, --starterkit Specify a starterkit to install. Default: starterkit-mustache-base
+```
+
+### Serve Pattern Lab
+```
+Usage: serve|browse [options]
+
+Starts a server to inspect files in browser
+
+
+ Options:
+ -h, --help output usage information
+ -w, --watch Start watching for changes
+```
+
+### Export Pattern Lab
+```
+Usage: export [options]
+
+Export a Pattern Lab patterns into a compressed format
+
+ Options:
+ -h, --help output usage information
+```
+
+### Install Pattern Lab starterkits or plugins
+```
+Usage: install|add [options]
+
+Installs Pattern Lab related modules like starterkits or plugins
+
+ Options:
+ -h, --help output usage information
+ --starterkits Specify one or more starterkits to install
+ --plugins Specify one or more plugins to install
+
+```
+
+## Examples
+```
+ $ patternlab init # Initialize a Pattern Lab project.
+ $ patternlab build # Builds Pattern Lab from the current dir
+ $ patternlab build --config # Builds Pattern Lab from different project directory
+```
+## License
+MIT © [Raphael Okon](https://github.com/raphaelokon)
diff --git a/packages/cli/test/build.test.js b/packages/cli/test/build.test.js
new file mode 100644
index 000000000..6a56cb6fe
--- /dev/null
+++ b/packages/cli/test/build.test.js
@@ -0,0 +1,45 @@
+const patternlab = require('@pattern-lab/core');
+const proxyquire = require('proxyquire');
+const tap = require('tap');
+const patternLabMock = require('./mocks/patternlab.mock.js');
+const config = patternlab.getDefaultConfig();
+
+// Require build and mock patternlab.build() so that we only test the build module behavior
+const build = proxyquire('../bin/build', {
+ '@pattern-lab/core': patternLabMock,
+});
+const opts = { patternsOnly: true };
+
+tap.test('Build ->', t => {
+ t.throws(
+ () => {
+ build();
+ },
+ {},
+ 'throws when config is empty'
+ );
+ t.throws(
+ () => {
+ build(123);
+ },
+ {},
+ 'throws when config is not of type object'
+ );
+ t.throws(
+ () => {
+ build(undefined, opts);
+ },
+ {},
+ '--patterns-only throws when config is empty'
+ );
+ t.throws(
+ () => {
+ build(undefined, opts);
+ },
+ {},
+ '--patterns-only throws when config is not of type object'
+ );
+ t.type(build(config), 'boolean', 'returns a bool');
+ t.type(build(config, opts), 'boolean', '--patterns-only returns a bool');
+ t.end();
+});
diff --git a/packages/cli/test/cli-build.test.js b/packages/cli/test/cli-build.test.js
new file mode 100644
index 000000000..d6712deae
--- /dev/null
+++ b/packages/cli/test/cli-build.test.js
@@ -0,0 +1,49 @@
+const exists = require('path-exists');
+const getUniqueProjectPath = require('./utils/getUniqueProjectPath');
+const path = require('path');
+const spawnCmd = require('./utils/spawnCmd');
+const tap = require('tap');
+const wrapAsync = require('../bin/utils').wrapAsync;
+
+const projectRoot = getUniqueProjectPath();
+
+tap.test('Init and build ->', t =>
+ wrapAsync(function*() {
+ yield spawnCmd([
+ 'init',
+ '--verbose',
+ '--project-dir',
+ projectRoot,
+ '--edition',
+ '@pattern-lab/edition-node',
+ '--starterkit',
+ '@pattern-lab/starterkit-mustache-demo',
+ ]);
+ yield spawnCmd([
+ 'build',
+ '--config',
+ `${projectRoot}/patternlab-config.json`,
+ ]);
+ t.ok(
+ exists.sync(path.resolve(projectRoot, 'public')),
+ 'should build all files into public dir'
+ );
+ t.ok(
+ exists.sync(path.resolve(projectRoot, 'public', 'annotations')),
+ 'with an annotations dir'
+ );
+ t.ok(
+ exists.sync(path.resolve(projectRoot, 'public', 'css')),
+ 'with a css dir'
+ );
+ t.ok(
+ exists.sync(path.resolve(projectRoot, 'public', 'images')),
+ 'with a images dir'
+ );
+ t.ok(
+ exists.sync(path.resolve(projectRoot, 'public', 'styleguide')),
+ 'with a styleguide dir'
+ );
+ t.end();
+ })
+);
diff --git a/packages/cli/test/cli-disable.test.js b/packages/cli/test/cli-disable.test.js
new file mode 100644
index 000000000..64715b882
--- /dev/null
+++ b/packages/cli/test/cli-disable.test.js
@@ -0,0 +1,52 @@
+const getUniqueProjectPath = require('./utils/getUniqueProjectPath');
+const spawnCmd = require('./utils/spawnCmd');
+const tap = require('tap');
+const { readFileSync } = require('fs');
+const wrapAsync = require('../bin/utils').wrapAsync;
+
+const projectRoot = getUniqueProjectPath();
+
+tap.test('Disable ->', t =>
+ wrapAsync(function*() {
+ yield spawnCmd([
+ 'init',
+ '--verbose',
+ '--project-dir',
+ projectRoot,
+ '--edition',
+ '@pattern-lab/edition-node',
+ '--starterkit',
+ '@pattern-lab/starterkit-mustache-base',
+ ]);
+ yield spawnCmd([
+ 'install',
+ '--plugins',
+ '@pattern-lab/plugin-tab',
+ '--config',
+ `${projectRoot}/patternlab-config.json`,
+ ]);
+ yield spawnCmd([
+ 'enable',
+ '--plugins',
+ '@pattern-lab/plugin-tab',
+ '--config',
+ `${projectRoot}/patternlab-config.json`,
+ ]);
+ yield spawnCmd([
+ 'disable',
+ '--plugins',
+ '@pattern-lab/plugin-tab',
+ '--config',
+ `${projectRoot}/patternlab-config.json`,
+ ]);
+ const config = JSON.parse(
+ readFileSync(`${projectRoot}/patternlab-config.json`, 'utf8')
+ );
+ t.equal(
+ config.plugins['@pattern-lab/plugin-tab'].enabled,
+ false,
+ 'and set the enabled flag to false in patternlab-config.json'
+ );
+ t.end();
+ })
+);
diff --git a/packages/cli/test/cli-enable.test.js b/packages/cli/test/cli-enable.test.js
new file mode 100644
index 000000000..62b7d837a
--- /dev/null
+++ b/packages/cli/test/cli-enable.test.js
@@ -0,0 +1,45 @@
+const getUniqueProjectPath = require('./utils/getUniqueProjectPath');
+const spawnCmd = require('./utils/spawnCmd');
+const tap = require('tap');
+const { readFileSync } = require('fs');
+const wrapAsync = require('../bin/utils').wrapAsync;
+
+const projectRoot = getUniqueProjectPath();
+
+tap.test('Enable ->', t =>
+ wrapAsync(function*() {
+ yield spawnCmd([
+ 'init',
+ '--verbose',
+ '--project-dir',
+ projectRoot,
+ '--edition',
+ '@pattern-lab/edition-node',
+ '--starterkit',
+ '@pattern-lab/starterkit-mustache-base',
+ ]);
+ yield spawnCmd([
+ 'install',
+ '--plugins',
+ '@pattern-lab/plugin-tab',
+ '--config',
+ `${projectRoot}/patternlab-config.json`,
+ ]);
+ yield spawnCmd([
+ 'enable',
+ '--plugins',
+ '@pattern-lab/plugin-tab',
+ '--config',
+ `${projectRoot}/patternlab-config.json`,
+ ]);
+ const config = JSON.parse(
+ readFileSync(`${projectRoot}/patternlab-config.json`, 'utf8')
+ );
+ t.equal(
+ config.plugins['@pattern-lab/plugin-tab'].enabled,
+ true,
+ 'and set the enabled flag in patternlab-config.json'
+ );
+ t.end();
+ })
+);
diff --git a/packages/cli/test/cli-export.test.js b/packages/cli/test/cli-export.test.js
new file mode 100644
index 000000000..63e82ba06
--- /dev/null
+++ b/packages/cli/test/cli-export.test.js
@@ -0,0 +1,33 @@
+const exists = require('path-exists');
+const getUniqueProjectPath = require('./utils/getUniqueProjectPath');
+const path = require('path');
+const spawnCmd = require('./utils/spawnCmd');
+const tap = require('tap');
+const wrapAsync = require('../bin/utils').wrapAsync;
+
+const projectRoot = getUniqueProjectPath();
+
+tap.test('Init and export ->', t =>
+ wrapAsync(function*() {
+ yield spawnCmd([
+ 'init',
+ '--verbose',
+ '--project-dir',
+ projectRoot,
+ '--edition',
+ '@pattern-lab/edition-node',
+ '--starterkit',
+ '@pattern-lab/starterkit-mustache-base',
+ ]);
+ yield spawnCmd([
+ 'export',
+ '--config',
+ `${projectRoot}/patternlab-config.json`,
+ ]);
+ t.ok(
+ exists.sync(path.resolve(projectRoot, 'pattern_exports', 'patterns.zip')),
+ ' should create patterns.zip'
+ );
+ t.end();
+ })
+);
diff --git a/packages/cli/test/cli-init.test.js b/packages/cli/test/cli-init.test.js
new file mode 100644
index 000000000..a80c031ff
--- /dev/null
+++ b/packages/cli/test/cli-init.test.js
@@ -0,0 +1,38 @@
+const exists = require('path-exists');
+const getUniqueProjectPath = require('./utils/getUniqueProjectPath');
+const path = require('path');
+const spawnCmd = require('./utils/spawnCmd');
+const tap = require('tap');
+const wrapAsync = require('../bin/utils').wrapAsync;
+
+const projectRoot = getUniqueProjectPath();
+
+tap.test('Init ->', t =>
+ wrapAsync(function*() {
+ yield spawnCmd([
+ 'init',
+ '--verbose',
+ '--project-dir',
+ projectRoot,
+ '--edition',
+ '@pattern-lab/edition-node',
+ '--starterkit',
+ '@pattern-lab/starterkit-mustache-base',
+ ]);
+ t.ok(
+ exists.sync(path.resolve(projectRoot)),
+ 'should initialize a Pattern Lab project'
+ );
+ t.ok(exists.sync(path.resolve(projectRoot, 'source')), 'with a source dir');
+ t.ok(exists.sync(path.resolve(projectRoot, 'public')), 'with a public dir');
+ t.ok(
+ exists.sync(path.resolve(projectRoot, 'pattern_exports')),
+ 'with a pattern_exports dir'
+ );
+ t.ok(
+ exists.sync(path.resolve(projectRoot, 'patternlab-config.json')),
+ 'with a pattern_exports dir'
+ );
+ t.end();
+ })
+);
diff --git a/packages/cli/test/export.test.js b/packages/cli/test/export.test.js
new file mode 100644
index 000000000..169b60771
--- /dev/null
+++ b/packages/cli/test/export.test.js
@@ -0,0 +1,31 @@
+const exportPatterns = require('../bin/cli-actions/export');
+const tap = require('tap');
+const wrapAsync = require('../bin/utils').wrapAsync;
+
+tap.test('Export ->', t => {
+ t.plan(2);
+ t.test('with options empty', tt =>
+ wrapAsync(function*() {
+ try {
+ yield exportPatterns();
+ } catch (err) {
+ tt.type(err, TypeError, 'throws when options are empty');
+ tt.end();
+ }
+ })
+ );
+ t.test('with options not an object', tt =>
+ wrapAsync(function*() {
+ try {
+ yield exportPatterns(123);
+ } catch (err) {
+ tt.type(
+ err,
+ TypeError,
+ 'throws when passed options are not of type object'
+ );
+ tt.end();
+ }
+ })
+ );
+});
diff --git a/packages/cli/test/fixtures/patternlab-config.json b/packages/cli/test/fixtures/patternlab-config.json
new file mode 100644
index 000000000..6102f522f
--- /dev/null
+++ b/packages/cli/test/fixtures/patternlab-config.json
@@ -0,0 +1,85 @@
+{
+ "cacheBust": true,
+ "cleanPublic": true,
+ "defaultPattern": "all",
+ "defaultShowPatternInfo": false,
+ "ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "mqs": false,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+ },
+ "ishViewportRange": {
+ "s": [240, 500],
+ "m": [500, 800],
+ "l": [800, 2600]
+ },
+ "logLevel": "info",
+ "outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+ },
+ "paths": {
+ "source": {
+ "root": "./test/fixtures/source/",
+ "patterns": "./test/fixtures/source/_patterns/",
+ "data": "./test/fixtures/source/_data/",
+ "meta": "./test/fixtures/source/_meta/",
+ "annotations": "./test/fixtures/source/_annotations/",
+ "styleguide": "node_modules/@pattern-lab/uikit-workshop/dist/",
+ "patternlabFiles": {
+ "general-header":
+ "node_modules/@pattern-lab/uikit-workshop/views/partials/general-header.mustache",
+ "general-footer":
+ "node_modules/@pattern-lab/uikit-workshop/views/partials/general-footer.mustache",
+ "patternSection":
+ "node_modules/@pattern-lab/uikit-workshop/views/partials/patternSection.mustache",
+ "patternSectionSubtype":
+ "node_modules/@pattern-lab/uikit-workshop/views/partials/patternSectionSubtype.mustache",
+ "viewall":
+ "node_modules/@pattern-lab/uikit-workshop/views/viewall.mustache"
+ },
+ "js": "./test/fixtures/source/js",
+ "images": "./test/fixtures/source/images",
+ "fonts": "./test/fixtures/source/fonts",
+ "css": "./test/fixtures/source/css/"
+ },
+ "public": {
+ "root": "./test/fixtures/public/",
+ "patterns": "./test/fixtures/public/patterns/",
+ "data": "./test/fixtures/public/styleguide/data/",
+ "annotations": "./test/fixtures/public/annotations/",
+ "styleguide": "./test/fixtures/public/styleguide/",
+ "js": "./test/fixtures/public/js",
+ "images": "./test/fixtures/public/images",
+ "fonts": "./test/fixtures/public/fonts",
+ "css": "./test/fixtures/public/css"
+ }
+ },
+ "patternExtension": "mustache",
+ "patternStateCascade": ["inprogress", "inreview", "complete"],
+ "patternExportDirectory": "./pattern_exports/",
+ "patternExportPatternPartials": [],
+ "serverOptions": {
+ "wait": 1000
+ },
+ "starterkitSubDir": "dist",
+ "styleGuideExcludes": [],
+ "theme": {
+ "color": "dark",
+ "density": "compact",
+ "layout": "horizontal"
+ }
+}
diff --git a/packages/cli/test/install-plugin.test.js b/packages/cli/test/install-plugin.test.js
new file mode 100644
index 000000000..269953007
--- /dev/null
+++ b/packages/cli/test/install-plugin.test.js
@@ -0,0 +1,29 @@
+const tap = require('tap');
+const installPlugin = require('../bin/install-plugin');
+const wrapAsync = require('../bin/utils').wrapAsync;
+const getUniqueProjectPath = require('./utils/getUniqueProjectPath');
+const moduleExist = require.resolve;
+
+const projectRoot = getUniqueProjectPath();
+
+const minimalConfig = {
+ paths: {
+ source: {
+ root: projectRoot,
+ },
+ },
+};
+
+tap.test('Install plugin-tab ->', t =>
+ wrapAsync(function*() {
+ yield installPlugin('@pattern-lab/plugin-tab', minimalConfig);
+ const pkg = yield moduleExist('@pattern-lab/plugin-tab');
+ t.ok(pkg, 'module should exist after install');
+ t.equal(
+ minimalConfig.plugins['@pattern-lab/plugin-tab'].enabled,
+ false,
+ 'and persist it on the patternlab-config.json'
+ );
+ t.end();
+ })
+);
diff --git a/packages/cli/test/install-starterkit.test.js b/packages/cli/test/install-starterkit.test.js
new file mode 100644
index 000000000..0cda685c9
--- /dev/null
+++ b/packages/cli/test/install-starterkit.test.js
@@ -0,0 +1,78 @@
+const tap = require('tap');
+const installStarterkit = require('../bin/install-starterkit');
+const wrapAsync = require('../bin/utils').wrapAsync;
+const getUniqueProjectPath = require('./utils/getUniqueProjectPath');
+const moduleExist = require.resolve;
+
+const projectRoot = getUniqueProjectPath();
+
+const minimalConfig = {
+ paths: {
+ source: {
+ root: projectRoot,
+ },
+ },
+};
+
+tap.test('Install starterkit-mustache-demo ->', t =>
+ wrapAsync(function*() {
+ yield installStarterkit(
+ '@pattern-lab/starterkit-mustache-demo',
+ minimalConfig
+ );
+ const pkg = yield moduleExist('@pattern-lab/starterkit-mustache-demo');
+ t.ok(pkg, 'module should exist after install');
+ t.end();
+ })
+);
+
+tap.test('Install starterkit-mustache-base ->', t =>
+ wrapAsync(function*() {
+ yield installStarterkit(
+ '@pattern-lab/starterkit-mustache-base',
+ minimalConfig
+ );
+ const pkg = yield moduleExist('@pattern-lab/starterkit-mustache-base');
+ t.ok(pkg, 'module should exist after install');
+ t.end();
+ })
+);
+
+tap.test('Install starterkit-mustache-bootstrap ->', t =>
+ wrapAsync(function*() {
+ yield installStarterkit('starterkit-mustache-bootstrap', minimalConfig);
+ const pkg = yield moduleExist('starterkit-mustache-bootstrap');
+ t.ok(pkg, 'module should exist after install');
+ t.end();
+ })
+);
+
+tap.test('Install starterkit-mustache-foundation ->', t =>
+ wrapAsync(function*() {
+ yield installStarterkit('starterkit-mustache-foundation', minimalConfig);
+ const pkg = yield moduleExist('starterkit-mustache-foundation');
+ t.ok(pkg, 'module should exist after install');
+ t.end();
+ })
+);
+
+tap.test('Install starterkit-mustache-acidtest ->', t =>
+ wrapAsync(function*() {
+ yield installStarterkit('starterkit-mustache-acidtest', minimalConfig);
+ const pkg = yield moduleExist('starterkit-mustache-acidtest');
+ t.ok(pkg, 'module should exist after install');
+ t.end();
+ })
+);
+
+tap.test('Install starterkit-mustache-materialdesign ->', t =>
+ wrapAsync(function*() {
+ yield installStarterkit(
+ 'starterkit-mustache-materialdesign',
+ minimalConfig
+ );
+ const pkg = yield moduleExist('starterkit-mustache-materialdesign');
+ t.ok(pkg, 'module should exist after install');
+ t.end();
+ })
+);
diff --git a/packages/cli/test/mocks/liverserver.mock.js b/packages/cli/test/mocks/liverserver.mock.js
new file mode 100644
index 000000000..754fd5fc1
--- /dev/null
+++ b/packages/cli/test/mocks/liverserver.mock.js
@@ -0,0 +1,15 @@
+function liveServerMock() {
+ return {
+ reload: function() {
+ return true;
+ },
+ refreshCSS: function() {
+ return true;
+ },
+ start: function() {
+ return true;
+ },
+ };
+}
+
+module.exports = liveServerMock;
diff --git a/packages/cli/test/mocks/patternlab.mock.js b/packages/cli/test/mocks/patternlab.mock.js
new file mode 100644
index 000000000..7186ca734
--- /dev/null
+++ b/packages/cli/test/mocks/patternlab.mock.js
@@ -0,0 +1,21 @@
+function patternLabMock() {
+ return {
+ build: function() {
+ return true;
+ },
+ help: function() {
+ return true;
+ },
+ patternsonly: function() {
+ return true;
+ },
+ liststarterkits: function() {
+ return true;
+ },
+ loadstarterkit: function() {
+ return true;
+ },
+ };
+}
+
+module.exports = patternLabMock;
diff --git a/packages/cli/test/replace_config_paths.test.js b/packages/cli/test/replace_config_paths.test.js
new file mode 100644
index 000000000..6a98385ac
--- /dev/null
+++ b/packages/cli/test/replace_config_paths.test.js
@@ -0,0 +1,44 @@
+const patternlab = require('@pattern-lab/core');
+const tap = require('tap');
+const replaceConfigPaths = require('../bin/replace-config');
+const config = patternlab.getDefaultConfig();
+
+tap.test('replaceConfigPaths ->', t => {
+ const newConfig = replaceConfigPaths(
+ config,
+ 'projectDir',
+ 'sourceDir',
+ 'publicDir',
+ 'exportDir'
+ );
+ for (const k of Object.keys(newConfig.paths.source)) {
+ if (k === 'patternlabFiles') {
+ for (const l of Object.keys(newConfig.paths.source[k])) {
+ t.ok(
+ /^projectDir\/sourceDir\/|^\.\/node_modules/.test(
+ newConfig.paths.source[k][l]
+ ),
+ `should be ok for newConfig.paths.source.${k}.${l}`
+ );
+ }
+ } else {
+ t.ok(
+ /^projectDir\/sourceDir\/|^\.\/node_modules/.test(
+ newConfig.paths.source[k]
+ ),
+ `should be ok for newConfig.paths.source.${k}`
+ );
+ }
+ }
+ for (const l of Object.keys(newConfig.paths.public)) {
+ t.ok(
+ /^projectDir\/publicDir\//.test(newConfig.paths.public[l]),
+ `should be ok for newConfig.paths.public.${l}`
+ );
+ }
+ t.ok(
+ /^projectDir\/exportDir/.test(newConfig.patternExportDirectory),
+ `should be ok for newConfig.patternExportDirectory`
+ );
+ t.end();
+});
diff --git a/packages/cli/test/resolve_config.test.js b/packages/cli/test/resolve_config.test.js
new file mode 100644
index 000000000..36c250d9a
--- /dev/null
+++ b/packages/cli/test/resolve_config.test.js
@@ -0,0 +1,18 @@
+const tap = require('tap');
+const wrapAsync = require('../bin/utils').wrapAsync;
+const resolveConfig = require('../bin/resolve-config');
+
+tap.test('resolveConfig ->', t =>
+ wrapAsync(function*() {
+ const config = yield resolveConfig(
+ './test/fixtures/patternlab-config.json'
+ );
+ const badConfig = yield resolveConfig(123);
+ const configNotFound = yield resolveConfig('./test/fixtures/some-config');
+ t.type(config, 'object', 'should return a config of type object');
+ t.ok(config.paths, 'config should have a paths property');
+ t.notOk(badConfig, 'returns false when configPath is not of type string');
+ t.notOk(configNotFound, 'returns false when configPath is not found');
+ t.end();
+ })
+);
diff --git a/packages/cli/test/scaffold.test.js b/packages/cli/test/scaffold.test.js
new file mode 100644
index 000000000..c63d2365d
--- /dev/null
+++ b/packages/cli/test/scaffold.test.js
@@ -0,0 +1,31 @@
+const tap = require('tap');
+const path = require('path');
+const exists = require('path-exists');
+const scaffold = require('../bin/scaffold');
+const getUniqueProjectPath = require('./utils/getUniqueProjectPath');
+const wrapAsync = require('../bin/utils').wrapAsync;
+
+const projectDir = getUniqueProjectPath();
+const sourceDir = 'source';
+const publicDir = 'public';
+const exportDir = 'patterns_export';
+
+tap.test('Scaffold ->', t =>
+ wrapAsync(function*() {
+ yield scaffold(projectDir, sourceDir, publicDir, exportDir);
+ t.ok(exists.sync(path.resolve(projectDir)), 'should create project dir');
+ t.ok(
+ exists.sync(path.resolve(projectDir, sourceDir)),
+ 'should create source dir'
+ );
+ t.ok(
+ exists.sync(path.resolve(projectDir, publicDir)),
+ 'should create public dir'
+ );
+ t.ok(
+ exists.sync(path.resolve(projectDir, exportDir)),
+ 'should create export dir'
+ );
+ t.end();
+ })
+);
diff --git a/packages/cli/test/serve.test.js b/packages/cli/test/serve.test.js
new file mode 100644
index 000000000..c322cc41d
--- /dev/null
+++ b/packages/cli/test/serve.test.js
@@ -0,0 +1,51 @@
+const proxyquire = require('proxyquire');
+const tap = require('tap');
+const _ = require('lodash');
+const resolveConfig = require('../bin/resolve-config');
+const patternLabMock = require('./mocks/patternlab.mock.js');
+const wrapAsync = require('../bin/utils').wrapAsync;
+
+// Require preview but mock patternlab so that we only test the module behavior
+const preview = proxyquire('../bin/serve', {
+ '@pattern-lab/core': patternLabMock,
+});
+
+tap.test('Serve ->', t =>
+ wrapAsync(function*() {
+ const config = yield resolveConfig(
+ './test/fixtures/patternlab-config.json'
+ );
+ config.paths.source.root = undefined;
+ t.throws(
+ () => {
+ preview();
+ },
+ {},
+ 'throws when config is empty'
+ );
+ t.throws(
+ () => {
+ preview(123);
+ },
+ {},
+ 'throws when config is not of type object'
+ );
+ t.throws(
+ () => {
+ _.unset(config, 'paths.source.root');
+ preview(config);
+ },
+ {},
+ 'throws when no source root dir is set on config'
+ );
+ t.throws(
+ () => {
+ _.unset(config, 'paths.public.root');
+ preview(config);
+ },
+ {},
+ 'throws when no public root dir is set on config'
+ );
+ t.end();
+ })
+);
diff --git a/packages/cli/test/utils/getUniqueProjectPath.js b/packages/cli/test/utils/getUniqueProjectPath.js
new file mode 100644
index 000000000..0ac892b19
--- /dev/null
+++ b/packages/cli/test/utils/getUniqueProjectPath.js
@@ -0,0 +1,6 @@
+const crypto = require('crypto');
+
+module.exports = () => {
+ const UUID = crypto.randomBytes(16).toString('hex');
+ return `./tmp/${UUID}`;
+};
diff --git a/packages/cli/test/utils/spawnCmd.js b/packages/cli/test/utils/spawnCmd.js
new file mode 100644
index 000000000..4d0623515
--- /dev/null
+++ b/packages/cli/test/utils/spawnCmd.js
@@ -0,0 +1,13 @@
+const path = require('path');
+const spawn = require('execa');
+const wrapAsync = require('../../bin/utils').wrapAsync;
+const cli = path.resolve(__dirname, '../../bin/patternlab.js');
+
+const spawnCmd = (args, endFn) =>
+ wrapAsync(function*() {
+ const fn = endFn || function() {};
+ yield spawn('node', [cli].concat(args));
+ fn();
+ });
+
+module.exports = spawnCmd;
diff --git a/packages/core/.eslintrc.js b/packages/core/.eslintrc.js
new file mode 100644
index 000000000..5a2cc7f1e
--- /dev/null
+++ b/packages/core/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: ['../../.eslintrc.js'],
+};
diff --git a/packages/core/.npmrc b/packages/core/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/core/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/core/.nvmrc b/packages/core/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/core/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
new file mode 100644
index 000000000..dc3dbfe77
--- /dev/null
+++ b/packages/core/CHANGELOG.md
@@ -0,0 +1,331 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package @pattern-lab/core
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+
+### Bug Fixes
+
+* **core:** do not warn about uikit-polyfills ([6bb68e7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/6bb68e763769969546542bf7aaf6d1f4235c6622))
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+**Note:** Version bump only for package @pattern-lab/core
+
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+
+### Bug Fixes
+
+* add a new method to check if PL is currently compiling + add new method to get the config PL is using ([26e886c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/26e886c93db5d135c91de648724f7278c4d5b3e9))
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+
+### Bug Fixes
+
+* **core:** re-add cleanPublic fix ([c100bbc](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/c100bbca3f339e9132acb9c482e98c1c8a66b8b5))
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+
+### Features
+
+* **config:** add new default pattern export options ([a7487a0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a7487a0681cb11e6f3c5c8eaefd62e5648ad5ea3))
+* **core:** fix pattern export all conflicts ([b210d82](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/b210d820ba8ac0b64c82c7ff0f18c9f8a900fce2))
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Bug Fixes
+
+* add eslint fixes ([00d7bbe](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/00d7bbe319ea77a6ee8cc9cd0348856feaaf13ad))
+* correct typo in build logging ([96d989f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/96d989f8869630ba9f59705bfca66755f20e35ab))
+* updates to address eslint / prettier issues ([d945acc](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/d945acc13b8e4e36f3815b017fbc12266c323d1f))
+* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5b7a057d46ccd16b5832af1441030c7b76f237a8))
+* **1049:** Treat folders like patterns only if they're subfolders of pattern groupings ([4eb79ab](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/4eb79ab48b335a35b2e5ed3b7053974b8e8bb6b6))
+* **core:** allow plugin resolution to follow normal algorithm ([3f6b83b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/3f6b83be080c88aec1d8b73bececb76f0f57a79d))
+* **core:** find plugins from config only and with simpler args ([fe7351c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/fe7351cba346425512cbb2ef3a1b7728ab06ae60))
+* **plugin:** correct spelling error and function locations ([d4abd88](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/d4abd88cb017550002407241b5045a2ad1adb1dc))
+
+
+### Features
+
+* **core:** invoke registered plugin hooks ([a54d775](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a54d7753b6939fe6a58da543f4fb34f64dd8901a))
+* **plugin-tab, core:** initial plugin hook exploration ([2f3d39a](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/2f3d39ac6b125ad4c6b872e27ee224ce2ea33a12))
+
+
+### BREAKING CHANGES
+
+* **core:** plugins now use async functions instead of events
+
+
+
+
+
+
+## [3.0.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.1...@pattern-lab/core@3.0.2) (2019-08-23)
+
+
+### Bug Fixes
+
+* add eslint fixes ([00d7bbe](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/00d7bbe))
+* correct typo in build logging ([96d989f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/96d989f))
+* updates to address eslint / prettier issues ([d945acc](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/d945acc))
+* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5b7a057))
+
+
+
+
+
+
+## [3.0.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.1-alpha.0...@pattern-lab/core@3.0.1) (2019-05-16)
+
+**Note:** Version bump only for package @pattern-lab/core
+
+
+
+
+
+# [3.0.0-beta.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-beta.0...@pattern-lab/core@3.0.0-beta.2) (2019-02-09)
+
+
+### Bug Fixes
+
+* **cli:** pass watch options cleanly to core ([8bf186b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/8bf186b))
+* **docs:** regenerate API documentation ([830c568](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/830c568))
+* **patterns:** find all patterns inlcuding pseudo patterns ([d0672f6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/d0672f6)), closes [#975](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/975)
+* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/98dfadf))
+
+
+
+
+
+
+# [3.0.0-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.16...@pattern-lab/core@3.0.0-beta.0) (2018-09-07)
+
+
+### Bug Fixes
+
+* **docs:** update event info with tab example ([0f227a3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/0f227a3))
+* **package:** Allow .json extension on annotations file (issue [#836](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/836)) ([b92e62b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/b92e62b))
+* **package:** update tap dependency ([2b70ff4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/2b70ff4))
+* **plugins:** support scoped packages ([44f0f8e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/44f0f8e))
+
+
+### Features
+
+* **API:** remove reliance on patternlab object during plugin install ([0850fd6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/0850fd6))
+* **core:** remove plugin install / disable / enable logic ([5a58824](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5a58824)), closes [#872](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/872)
+
+
+
+
+
+
+
+# [3.0.0-alpha.16](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.15...@pattern-lab/core@3.0.0-alpha.16) (2018-07-06)
+
+**Note:** Version bump only for package @pattern-lab/core
+
+
+
+# [3.0.0-alpha.15](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.14...@pattern-lab/core@3.0.0-alpha.15) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5ab3995))
+
+
+
+# [3.0.0-alpha.14](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.13...@pattern-lab/core@3.0.0-alpha.14) (2018-07-05)
+
+### Bug Fixes
+
+* **core:** rename serverModule import to avoid conflict with CLI ([f3170e7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/f3170e7))
+* **server:** remove setInterval hack ([a76e4a2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a76e4a2))
+* **uikits:** fix generation of view all pages within uikits ([7d6bdce](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/7d6bdce))
+* **viewall:** fix viewall generation ([543558a](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/543558a))
+* **watch:** wire up serve and watch listeners correctly ([04cd18e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/04cd18e))
+
+### Features
+
+* **events:** add PATTERNLAB_BUILD_END event and rename BUILD_START ([5b7bfa3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5b7bfa3))
+* **help:** remove help. API is now documented ([2aef3a1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/2aef3a1))
+* **server:** beginning of refator ([a3d65c3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a3d65c3))
+* **server:** continue server refactor ([8f6cd91](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/8f6cd91))
+* **watches:** add additional assets to ignore ([18e74c2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/18e74c2))
+
+
+
+# [3.0.0-alpha.13](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.12...@pattern-lab/core@3.0.0-alpha.13) (2018-05-04)
+
+### Features
+
+* **api:** expose getVersion statically ([4683cd0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/4683cd0))
+
+
+
+# [3.0.0-alpha.12](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.11...@pattern-lab/core@3.0.0-alpha.12) (2018-05-04)
+
+### Bug Fixes
+
+* **build:** improve stability of changes causing a live-server reload ([06c6123](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/06c6123))
+* **buildPatterns:** move meta processing back into function for now ([cea2c45](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/cea2c45))
+* **pattern graph:** move support and coverage of graph file to root ([bb9ef3c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/bb9ef3c))
+* **pattern watch:** Defensively add change listeners ([cdbd11f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/cdbd11f))
+* **test:** fix the test please and thank you ([cdc6c38](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/cdc6c38))
+* **test:** sledgehammer a test ([8b34be0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/8b34be0))
+* **tests:** prevent dependency graph output file from being written ([0d9c57e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/0d9c57e))
+* **uikits:** fix ui_builder_tests ([e75f434](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/e75f434))
+
+### Features
+
+* **API:** standardize v() and version() into a single call ([6309e69](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/6309e69))
+* **uikits:** add uikits to test config ([43a2017](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/43a2017))
+* **uikits:** additional test coverage ([f5b60b2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/f5b60b2))
+* **uikits:** additional test coverage of the main API ([fbcacfb](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/fbcacfb))
+* **uikits:** clean each build directory if configured ([8e11342](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/8e11342))
+* **uikits:** copy pattern-specific javascript ([3ac93dc](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/3ac93dc))
+* **uikits:** create MVP output to disk ([e1598d3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/e1598d3))
+* **uikits:** filter out excluded pattern states from uikit output ([87c9d0d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/87c9d0d))
+* **uikits:** load uikits before build ([4565202](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/4565202))
+* **uikits:** output assets and annotations to each location ([b0a84ca](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/b0a84ca))
+* **uikits:** output pattern files to each location ([5df87b0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5df87b0))
+* **uikits:** promote dependencyGraph.json output to root ([dd3e708](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/dd3e708))
+* **uikits:** render header and footer data correctly ([f2a6f23](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/f2a6f23))
+* **uikits:** support incremental builds ([6670364](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/6670364))
+* **uikits:** support watched-asset copying ([4f05311](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/4f05311))
+* **uikits:** uikits config to default ([a393851](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a393851))
+
+### BREAKING CHANGES
+
+* **API:** change `version()` to return a string representation of the version, removing `v()`
+
+
+
+# [3.0.0-alpha.11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.10...@pattern-lab/core@3.0.0-alpha.11) (2018-03-21)
+
+### Bug Fixes
+
+* **changes_hunter:** guard for incrementalRebuild while watching ([c652b9c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/c652b9c)), closes [#794](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/794) [#802](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/802)
+* **data_loader:** look for exact name of the file passed in ([eb46be2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/eb46be2))
+* **get:** add internal ability to omit missing pattern warning ([e3dddc6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/e3dddc6)), closes [#786](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/786)
+* **lint:** run code through prettier ([ca52fde](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/ca52fde)), closes [#825](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/825)
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/337aa32))
+* **tests:** Revert annotations file back to expected legacy format ([3618f27](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/3618f27))
+
+### Features
+
+* **package:** standardize and hoist common devDependencies ([7f4ce6f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/7f4ce6f))
+
+
+
+# [3.0.0-alpha.10](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.9...@pattern-lab/core@3.0.0-alpha.10) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/1473cd5))
+* **config:** update patch to uikit files ([5ccd0d2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5ccd0d2))
+* **package:** clarify description of package ([c65611e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/c65611e))
+
+### Features
+
+* **README:** Update for brevity and consistency ([aec7c50](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/aec7c50))
+
+
+
+# 3.0.0-alpha.9 (2018-03-02)
+
+### Bug Fixes
+
+* **core:** Fix tests ([31d67a7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/31d67a7))
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/58beeb6))
+
+### Features
+
+* **core:** Add tests for help command ([62cd8fb](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/62cd8fb))
+* **package:** Hoist up tap and test command. ([6cacdb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/6cacdb6))
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5eb2c11))
+
+
+
+# [3.0.0-alpha.8](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-alpha.7...v3.0.0-alpha.8) (2018-02-22)
+
+### Bug Fixes
+
+* **asset copy:** Resolve paths correctly. Break apart files & asyncify ([379419c](https://github.com/pattern-lab/patternlab-node/commit/379419c))
+* **event emission:** Commit failing test ([12ccbd7](https://github.com/pattern-lab/patternlab-node/commit/12ccbd7))
+* **event emission:** Only listen once to changes ([ea6b7d3](https://github.com/pattern-lab/patternlab-node/commit/ea6b7d3))
+* **loadPattern:** Check proper data file paths for modification ([b7ba5b0](https://github.com/pattern-lab/patternlab-node/commit/b7ba5b0))
+* **package** Update dependencies to reduce vulnerabilities ([367d38f](https://github.com/pattern-lab/patternlab-node/commit/367d38f))
+* **package:** Update gitignore and npmignore with current files ([581b3c4](https://github.com/pattern-lab/patternlab-node/commit/581b3c4))
+* **serve:** Reference events by constants ([9f5c143](https://github.com/pattern-lab/patternlab-node/commit/9f5c143))
+* **test configuration:** Remove vestigial configuration entries ([481fce9](https://github.com/pattern-lab/patternlab-node/commit/481fce9))
+* **watchPatternLabFiles:** Register and manager watchers ([48f0190](https://github.com/pattern-lab/patternlab-node/commit/48f0190))
+
+### Features
+
+* **docs:** Add jsdoc output to public API and events ([d45e7b9](https://github.com/pattern-lab/patternlab-node/commit/d45e7b9))
+* **docs:** Experiment with doc generation ([8e1808b](https://github.com/pattern-lab/patternlab-node/commit/8e1808b))
+* **index:** Make the cleaning of public/ an asynchronous adventure ([bd485d2](https://github.com/pattern-lab/patternlab-node/commit/bd485d2))
+* **package:** Communicate official node support ([96ca87f](https://github.com/pattern-lab/patternlab-node/commit/96ca87f))
+* **pattern lab:** Copy pattern-specific js ([99bfc02](https://github.com/pattern-lab/patternlab-node/commit/99bfc02))
+* **pattern lab:** Pass `patternPartial` as data to render ([351ea5e](https://github.com/pattern-lab/patternlab-node/commit/351ea5e))
+
+
+
+# [3.0.0-alpha.7](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-alpha.6...v3.0.0-alpha.7) (2018-01-19)
+
+### Bug Fixes
+
+* **lint:** Manually resolve final lint issues ([7cad1f1](https://github.com/pattern-lab/patternlab-node/commit/7cad1f1))
+* **README:** Fix npm link instructions ([ce3a7f0](https://github.com/pattern-lab/patternlab-node/commit/ce3a7f0))
+* **README:** Update npm shield to point to scoped package ([1f62617](https://github.com/pattern-lab/patternlab-node/commit/1f62617)), closes [#760](https://github.com/pattern-lab/patternlab-node/issues/760)
+* **unit test:** Fix path to fixture ([b932f14](https://github.com/pattern-lab/patternlab-node/commit/b932f14))
+
+### Features
+
+* **Contributing:** Update contributing info with prettier ([2a0ce52](https://github.com/pattern-lab/patternlab-node/commit/2a0ce52))
+* **list_item_hunter:** Re-work algorithm ([1ac77a7](https://github.com/pattern-lab/patternlab-node/commit/1ac77a7))
+* **package:** Add prettier ([b8e3e11](https://github.com/pattern-lab/patternlab-node/commit/b8e3e11))
+* **package:** Add prettier precommit hook ([a0b85b5](https://github.com/pattern-lab/patternlab-node/commit/a0b85b5))
+* **package:** Add standard version ([b2ba31c](https://github.com/pattern-lab/patternlab-node/commit/b2ba31c))
+* **README:** Add prettier badge ([7c2787b](https://github.com/pattern-lab/patternlab-node/commit/7c2787b))
+
+---
+
+Older releases found at https://github.com/pattern-lab/patternlab-node/wiki/ChangeLog
diff --git a/packages/core/LICENSE b/packages/core/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/core/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/core/README.md b/packages/core/README.md
new file mode 100644
index 000000000..f6a81c7bd
--- /dev/null
+++ b/packages/core/README.md
@@ -0,0 +1,120 @@
+
+
+[](https://travis-ci.org/pattern-lab/patternlab-node)
+
+
+[](https://coveralls.io/github/pattern-lab/patternlab-node?branch=master)
+[](https://github.com/prettier/prettier)
+[]()
+[](https://gitter.im/pattern-lab/node)
+
+# Pattern Lab Node Core
+
+This is the core API and orchestrator of the [Pattern Lab ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html).
+
+## Installation
+
+Pattern Lab Node can be used different ways. Editions are **example** pairings of Pattern Lab code and do not always have an upgrade path or simple means to run as a dependency within a larger project. Users wishing to be most current and have the greatest flexibility are encouraged to consume `patternlab-node` directly. Users wanting to learn more about Pattern Lab and have a tailored default experience are encouraged to start with an Edition. Both methods still expect to interact with other elements of the [Pattern Lab Ecosystem](#ecosystem).
+
+### Direct Consumption
+
+As of Pattern Lab Node 3.X, `patternlab-node` can run standalone, without the need for task runners like gulp or grunt.
+
+`npm install @pattern-lab/core`
+
+See [Usage](#usage) for more information.
+
+### Editions
+
+For users wanting a more pre-packaged experience several editions are available.
+
+* [Pattern Lab/Node: Vanilla Edition](https://github.com/pattern-lab/patternlab-node/tree/dev/packages/edition-node) contains info how to get started within a pure node environment.
+
+* [Pattern Lab/Node: Gulp Edition](https://github.com/pattern-lab/patternlab-node/tree/dev/packages/edition-node-gulp) contains info how to get started within a Gulp task running environment.
+
+
+## Ecosystem
+
+
+
+Core, and Editions, are part of the [Pattern Lab Ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). With this architecture, we encourage people to write and maintain their own Editions, Starterkits, and even PatternEngines.
+
+## Usage
+
+`@pattern-lab/core` can be required within any Node environment, taking in a configuration file at instantiation.
+
+```javascript
+const config = require('./patternlab-config.json');
+const patternlab = require('@pattern-lab/core')(config);
+
+// build, optionally watching or choosing incremental builds
+patternlab.build({
+ cleanPublic: true,
+ watch: true,
+});
+
+// or build, watch, and then self-host
+patternlab.serve({
+ cleanPublic: true,
+});
+```
+
+* Read more about [configuration](http://patternlab.io/docs/advanced-config-options.html#node) via `patternlab-config.json`.
+
+* Read more about the rest of [Public API](./docs), and already implemented for you within [Editions](#editions).
+
+* A full-featured [command line interface](https://github.com/pattern-lab/patternlab-node/tree/dev/packages/cli) is also available.
+
+### Events
+
+Many [events](./docs/events.md) are emitted during Pattern Lab operations, originally built to support plugins. Below is a sample, allowing users to be informed of asset or pattern changes.
+
+```javascript
+patternlab.serve(...);
+
+patternlab.events.on('patternlab-asset-change', (data) => {
+ console.log(data); // {file: 'path/to/file.css', dest: 'path/to/destination'}
+});
+
+patternlab.events.on('patternlab-pattern-change', (data) => {
+ console.log(data); // {file: 'path/to/file.ext'}
+});
+
+patternlab.events.on('patternlab-global-change', (data) => {
+ console.log(data); // {file: 'path/to/file.ext'}
+});
+```
+
+## Development Installation / Workflow
+
+If you are interested in contributing to Pattern Lab, please do take some time to learn how we [develop locally](https://github.com/pattern-lab/patternlab-node/blob/master/.github/CONTRIBUTING.md#developing-locally) within the contribution guidelines.
+## Upgrading
+
+If you find yourself here and are looking to upgrade, check out how to upgrade from version to version of Pattern Lab Node here: [https://github.com/pattern-lab/patternlab-node/wiki/Upgrading](https://github.com/pattern-lab/patternlab-node/wiki/Upgrading)
+
+View the [latest releases](https://github.com/pattern-lab/patternlab-node/releases) for comprehensive changelogs.
+
+## Contributing
+
+If you'd like to contribute to Pattern Lab Node, please do so! There is always a lot of ground to cover and something for your wheelhouse.
+
+Please read the [contribution guidelines](https://github.com/pattern-lab/patternlab-node/blob/master/.github/CONTRIBUTING.md).
+
+## Core Team
+
+* [@bmuenzenmeyer](https://github.com/bmuenzenmeyer) - Lead Maintainer
+* [@geoffp](https://github.com/geoffp) - Core Contributor
+* [@raphaelokon](https://github.com/raphaelokon) - CLI Contributor
+* [@tburny](https://github.com/tburny) - Core Contributor
+
+## Community
+
+The Pattern Lab Node team uses [our gitter.im channel, pattern-lab/node](https://gitter.im/pattern-lab/node) to keep in sync, share updates, and talk shop. Please stop by to say hello or as a first place to turn if stuck. Other channels in the Pattern Lab organization can be found on gitter too.
+
+There is also a dedicated Pattern Lab channel on the [design system slack](http://designsystems.herokuapp.com) run by [@jina](https://twitter.com/jina).
+
+Ask or answer Pattern Lab questions on Stack Overflow: http://stackoverflow.com/questions/tagged/patternlab.io
+
+## License
+
+[MIT](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE)
diff --git a/packages/core/docs/README.md b/packages/core/docs/README.md
new file mode 100644
index 000000000..7317083f6
--- /dev/null
+++ b/packages/core/docs/README.md
@@ -0,0 +1,198 @@
+# Pattern Lab Node API
+
+[](https://gitter.im/pattern-lab/node)
+
+## [Installation](https://github.com/pattern-lab/patternlab-node#installation)
+
+## Usage
+
+`patternlab-node` can be required within any Node environment, taking in a configuration file at instantiation.
+
+``` javascript
+const config = require('./patternlab-config.json');
+const patternlab = require('@pattern-lab/core')(config);
+```
+
+
+
+## `patternlab` : object
+Build thoughtful, pattern-driven user interfaces using atomic design principles.
+Many of these functions are exposed to users within [Editions](https://github.com/pattern-lab/patternlab-node#editions), but [direct consumption](https://github.com/pattern-lab/patternlab-node#direct-consumption) is also encouraged.
+
+**Kind**: global namespace
+**See**
+
+- [patternlab.io](patternlab.io) for more documentation.
+- [https://github.com/pattern-lab/patternlab-node](https://github.com/pattern-lab/patternlab-node) for code, issues, and releases
+
+**License**: MIT
+
+* [`patternlab`](#patternlab) : object
+ * _instance_
+ * [`.version`](#patternlab+version) ⇒ string
+ * [`.build`](#patternlab+build) ⇒ Promise
+ * [`.getDefaultConfig`](#patternlab+getDefaultConfig) ⇒ object
+ * [`.getSupportedTemplateExtensions`](#patternlab+getSupportedTemplateExtensions) ⇒ Array.<string>
+ * [`.installplugin`](#patternlab+installplugin) ⇒ void
+ * [`.liststarterkits`](#patternlab+liststarterkits) ⇒ Promise
+ * [`.loadstarterkit`](#patternlab+loadstarterkit) ⇒ void
+ * [`.patternsonly`](#patternlab+patternsonly) ⇒ Promise
+ * _static_
+ * [`.getDefaultConfig`](#patternlab.getDefaultConfig) ⇒ object
+ * [`.getVersion`](#patternlab.getVersion) ⇒ string
+ * [`.server`](#patternlab.server) : object
+ * [`.serve(options)`](#patternlab.server.serve) ⇒ Promise
+ * [`.reload()`](#patternlab.server.reload) ⇒ Promise
+ * [`.refreshCSS()`](#patternlab.server.refreshCSS) ⇒ Promise
+ * [`.events`](#patternlab.events) : EventEmitter
+
+
+
+### `patternlab.version` ⇒ string
+Returns current version
+
+**Kind**: instance property of [patternlab](#patternlab)
+**Returns**: string - current patternlab-node version as defined in `package.json`, as string
+
+
+### `patternlab.build` ⇒ Promise
+Builds patterns, copies assets, and constructs user interface
+
+**Kind**: instance property of [patternlab](#patternlab)
+**Returns**: Promise - a promise fulfilled when build is complete
+**Emits**: event:PATTERNLAB_BUILD_START, event:PATTERNLAB_BUILD_END
+**See**: [all events](./events.md)
+
+| Param | Type | Default | Description |
+| --- | --- | --- | --- |
+| options | object | | an object used to control build behavior |
+| [options.cleanPublic] | bool | true | whether or not to delete the configured output location (usually `public/`) before build |
+| [options.data] | object | {} | additional data to be merged with global data prior to build |
+| [options.watch] | bool | true | whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild |
+
+
+
+### `patternlab.getDefaultConfig` ⇒ object
+Returns the standardized default config used to run Pattern Lab. This method can be called statically or after instantiation.
+
+**Kind**: instance property of [patternlab](#patternlab)
+**Returns**: object - Returns the object representation of the `patternlab-config.json`
+
+
+### `patternlab.getSupportedTemplateExtensions` ⇒ Array.<string>
+Returns all file extensions supported by installed PatternEngines
+
+**Kind**: instance property of [patternlab](#patternlab)
+**Returns**: Array.<string> - all supported file extensions
+
+
+### `patternlab.installplugin` ⇒ void
+Installs plugin already available via `node_modules/`
+
+**Kind**: instance property of [patternlab](#patternlab)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| pluginName | string | name of plugin |
+
+
+
+### `patternlab.liststarterkits` ⇒ Promise
+Fetches starterkit repositories from pattern-lab github org that contain 'starterkit' in their name
+
+**Kind**: instance property of [patternlab](#patternlab)
+**Returns**: Promise - Returns an Array<{name,url}> for the starterkit repos
+
+
+### `patternlab.loadstarterkit` ⇒ void
+Loads starterkit already available via `node_modules/`
+
+**Kind**: instance property of [patternlab](#patternlab)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| starterkitName | string | name of starterkit |
+| clean | boolean | whether or not to delete contents of source/ before load |
+
+
+
+### `patternlab.patternsonly` ⇒ Promise
+Builds patterns only, leaving existing user interface files intact
+
+**Kind**: instance property of [patternlab](#patternlab)
+**Returns**: Promise - a promise fulfilled when build is complete
+
+| Param | Type | Default | Description |
+| --- | --- | --- | --- |
+| [options.cleanPublic] | bool | true | whether or not to delete the configured output location (usually `public/`) before build |
+| [options.data] | object | {} | additional data to be merged with global data prior to build |
+| [options.watch] | bool | true | whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild |
+
+
+
+### `patternlab.getDefaultConfig` ⇒ object
+Static method that returns the standardized default config used to run Pattern Lab. This method can be called statically or after instantiation.
+
+**Kind**: static property of [patternlab](#patternlab)
+**Returns**: object - Returns the object representation of the `patternlab-config.json`
+
+
+### `patternlab.getVersion` ⇒ string
+Static method that returns current version
+
+**Kind**: static property of [patternlab](#patternlab)
+**Returns**: string - current @pattern-lab/core version as defined in `package.json`
+
+
+### `patternlab.server` : object
+Server module
+
+**Kind**: static property of [patternlab](#patternlab)
+
+* [`.server`](#patternlab.server) : object
+ * [`.serve(options)`](#patternlab.server.serve) ⇒ Promise
+ * [`.reload()`](#patternlab.server.reload) ⇒ Promise
+ * [`.refreshCSS()`](#patternlab.server.refreshCSS) ⇒ Promise
+
+
+
+#### `server.serve(options)` ⇒ Promise
+Build patterns, copies assets, and constructs user interface. Watches configured `source/` directories, and serves all output locally
+
+**Kind**: static method of [server](#patternlab.server)
+**Returns**: Promise - a promise fulfilled when build is complete
+
+| Param | Type | Default | Description |
+| --- | --- | --- | --- |
+| options | object | | an object used to control build behavior |
+| [options.cleanPublic] | bool | true | whether or not to delete the configured output location (usually `public/`) before build |
+| [options.data] | object | {} | additional data to be merged with global data prior to build |
+| [options.watch] | bool | true | whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild |
+
+
+
+#### `server.reload()` ⇒ Promise
+Reloads any active live-server instances
+
+**Kind**: static method of [server](#patternlab.server)
+**Returns**: Promise - a promise fulfilled when operation is complete
+
+
+#### `server.refreshCSS()` ⇒ Promise
+Reloads CSS on any active live-server instances
+
+**Kind**: static method of [server](#patternlab.server)
+**Returns**: Promise - a promise fulfilled when operation is complete
+
+
+### `patternlab.events` : EventEmitter
+**Kind**: static property of [patternlab](#patternlab)
+**See**
+
+- [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
+- [All Pattern Lab events](./events.md)
+
+
+* * *
+
+[Pattern Lab](http://patternlab.io) Node is [MIT Licensed](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE)
diff --git a/packages/core/docs/events.md b/packages/core/docs/events.md
new file mode 100644
index 000000000..2df8191d1
--- /dev/null
+++ b/packages/core/docs/events.md
@@ -0,0 +1,159 @@
+# Pattern Lab Node Events
+
+[](https://gitter.im/pattern-lab/node)
+
+Pattern Lab emits numerous events during the [build](../docs/) process. Some uses of events:
+
+* Core uses `patternlab-pattern-change` events when watching for changes in order to trigger another build
+* Plugins such as [plugin-tab](https://github.com/pattern-lab/patternlab-node/tree/master/packages/plugin-tab) can use an event like `patternlab-pattern-write-end` to define additional code tabs to the pattern viewer / modal
+
+Learn more about [Creating Plugins](https://github.com/pattern-lab/patternlab-node/wiki/Creating-Plugins).
+
+
+
+## Events
+
+
+
+### `EVENTS` ⏏
+
+**Kind**: Exported constant
+
+
+#### `EVENTS~PATTERNLAB_BUILD_START`
+
+Emitted before any logic run inside `build()`, which is the entry point for single builds, pattern-only builds, run singly or when watched.
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**Properties**
+
+| Name | Type | Description |
+| ---------- | ------------------- | ----------------- |
+| patternlab | object | global data store |
+
+
+
+#### `EVENTS~PATTERNLAB_BUILD_END`
+
+Emitted after all logic run inside `build()`, which is the entry point for single builds, pattern-only builds, run singly or when watched.
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**Properties**
+
+| Name | Type | Description |
+| ---------- | ------------------- | ----------------- |
+| patternlab | object | global data store |
+
+
+
+#### `EVENTS~PATTERNLAB_PATTERN_ITERATION_END`
+
+Emitted after patterns are iterated over to gather data about them. Right before Pattern Lab processes and renders patterns into HTML
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**Properties**
+
+| Name | Type | Description |
+| ---------- | ------------------- | ----------------- |
+| patternlab | object | global data store |
+
+
+
+#### `EVENTS~PATTERNLAB_BUILD_GLOBAL_DATA_END`
+
+Emitted after global `data.json` and `listitems.json` are read, and the supporting Pattern Lab templates are loaded into memory (header, footer, patternSection, patternSectionSubType, viewall). Right before patterns are iterated over to gather data about them.
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**Properties**
+
+| Name | Type | Description |
+| ---------- | ------------------- | ----------------- |
+| patternlab | object | global data store |
+
+
+
+#### `EVENTS~PATTERNLAB_PATTERN_BEFORE_DATA_MERGE`
+
+Emitted before all data is merged prior to a Pattern's render. Global `data.json` is merged with any pattern `.json`. Global `listitems.json` is merged with any pattern `.listitems.json`.
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**See**: [Pattern](https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16)
+**Properties**
+
+| Name | Type | Description |
+| ---------- | -------------------- | ----------------- |
+| patternlab | object | global data store |
+| pattern | Pattern | current pattern |
+
+
+
+#### `EVENTS~PATTERNLAB_PATTERN_WRITE_BEGIN`
+
+Emitted before a pattern's template, HTML, and encoded HTML files are written to their output location
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**See**: [Pattern](https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16)
+**Properties**
+
+| Name | Type | Description |
+| ---------- | -------------------- | ----------------- |
+| patternlab | object | global data store |
+| pattern | Pattern | current pattern |
+
+
+
+#### `EVENTS~PATTERNLAB_PATTERN_WRITE_END`
+
+Emitted after a pattern's template, HTML, and encoded HTML files are written to their output location
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**See**: [Pattern](https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16)
+**Properties**
+
+| Name | Type | Description |
+| ---------- | -------------------- | ----------------- |
+| patternlab | object | global data store |
+| pattern | Pattern | current pattern |
+
+
+
+#### `EVENTS~PATTERNLAB_PATTERN_ASSET_CHANGE`
+
+Invoked when a watched asset changes. Assets include anything in `source/` that is not under `['root', 'patterns', 'data', 'meta', 'annotations', 'patternlabFiles']` which are blacklisted for specific copying.
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**Properties**
+
+| Name | Type | Description |
+| -------- | ------------------- | --------------------------------------------------------- |
+| fileInfo | object | `{file: 'path/to/file.css', dest: 'path/to/destination'}` |
+
+
+
+#### `EVENTS~PATTERNLAB_GLOBAL_CHANGE`
+
+Invoked when a watched global file changes. These are files within the directories specified in `['data', 'meta']`paths.
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**Properties**
+
+| Name | Type | Description |
+| -------- | ------------------- | ---------------------------- |
+| fileInfo | object | `{file: 'path/to/file.ext'}` |
+
+
+
+#### `EVENTS~PATTERNLAB_PATTERN_CHANGE`
+
+Invoked when a pattern changes.
+
+**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS)
+**Properties**
+
+| Name | Type | Description |
+| -------- | ------------------- | ---------------------------- |
+| fileInfo | object | `{file: 'path/to/file.ext'}` |
+
+---
+
+[Pattern Lab](http://patternlab.io) Node is [MIT Licensed](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE)
diff --git a/packages/core/package.json b/packages/core/package.json
new file mode 100644
index 000000000..866ea172d
--- /dev/null
+++ b/packages/core/package.json
@@ -0,0 +1,79 @@
+{
+ "name": "@pattern-lab/core",
+ "description": "Create atomic design systems with Pattern Lab. This is the core API and orchestrator of the ecosystem.",
+ "version": "5.9.3",
+ "main": "./src/index.js",
+ "dependencies": {
+ "@pattern-lab/engine-mustache": "^5.0.0",
+ "@pattern-lab/live-server": "^5.9.3",
+ "chalk": "1.1.3",
+ "chokidar": "1.7.0",
+ "dive": "0.5.0",
+ "fs-extra": "5.0.0",
+ "glob": "7.0.0",
+ "graphlib": "2.1.1",
+ "js-beautify": "1.6.3",
+ "js-yaml": "3.13.1",
+ "lodash": "4.17.15",
+ "markdown-it": "6.0.1",
+ "node-fetch": "1.6.0",
+ "recursive-copy": "2.0.8",
+ "update-notifier": "2.2.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.5.5",
+ "@babel/plugin-proposal-decorators": "^7.4.4",
+ "@babel/plugin-syntax-jsx": "^7.2.0",
+ "babel-eslint": "^10.0.2",
+ "eslint": "^6.1.0",
+ "eslint-config-airbnb-base": "^14.0.0",
+ "eslint-config-prettier": "^6.0.0",
+ "eslint-plugin-import": "^2.18.2",
+ "eslint-plugin-prettier": "^3.1.0",
+ "husky": "0.14.3",
+ "jsdoc-to-markdown": "5.0.1",
+ "prettier": "^1.14.3",
+ "pretty-quick": "^1.11.1",
+ "rewire": "2.5.2",
+ "standard-version": "4.3.0",
+ "tap": "14.6.4"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Grunt",
+ "Gulp",
+ "Javascript"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/core",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": {
+ "name": "Brian Muenzenmeyer"
+ },
+ "contributors": [
+ {
+ "name": "Geoff Pursell"
+ },
+ {
+ "name": "Raphael Okon"
+ },
+ {
+ "name": "tburny"
+ }
+ ],
+ "license": "MIT",
+ "scripts": {
+ "docs": "node ./scripts/docs.js",
+ "lint": "eslint -c ../../.eslintrc.js src/**/*.js",
+ "pretest": "npm run lint",
+ "release": "standard-version",
+ "test": "tap test/*_tests.js --reporter spec --coverage"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/core/patternlab-config.json b/packages/core/patternlab-config.json
new file mode 100644
index 000000000..39eed54fd
--- /dev/null
+++ b/packages/core/patternlab-config.json
@@ -0,0 +1,98 @@
+{
+ "cacheBust": true,
+ "cleanPublic" : true,
+ "defaultPattern": "all",
+ "defaultShowPatternInfo": false,
+ "ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "mqs": false,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+ },
+ "ishViewportRange": {
+ "s": [240, 500],
+ "m": [500, 800],
+ "l": [800, 2600]
+ },
+ "logLevel": "info",
+ "outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+ },
+ "paths" : {
+ "source": {
+ "root": "./source/",
+ "patterns": "./source/_patterns/",
+ "data": "./source/_data/",
+ "meta": "./source/_meta/",
+ "annotations": "./source/_annotations/",
+ "styleguide": "dist/",
+ "patternlabFiles": {
+ "general-header":
+ "views/partials/general-header.mustache",
+ "general-footer":
+ "views/partials/general-footer.mustache",
+ "patternSection":
+ "views/partials/patternSection.mustache",
+ "patternSectionSubtype":
+ "views/partials/patternSectionSubtype.mustache",
+ "viewall":
+ "views/viewall.mustache"
+ },
+ "js": "./source/js",
+ "images": "./source/images",
+ "fonts": "./source/fonts",
+ "css": "./source/css"
+ },
+ "public": {
+ "root": "public/",
+ "patterns": "public/patterns/",
+ "data": "public/styleguide/data/",
+ "annotations": "public/annotations/",
+ "styleguide": "public/styleguide/",
+ "js": "public/js",
+ "images": "public/images",
+ "fonts": "public/fonts",
+ "css": "public/css"
+ }
+ },
+ "patternExtension": "mustache",
+ "patternStateCascade": ["inprogress", "inreview", "complete"],
+ "patternExportAll": false,
+ "patternExportDirectory": "pattern_exports",
+ "patternExportPatternPartials": [],
+ "patternExportPreserveDirectoryStructure": true,
+ "patternExportRaw": false,
+ "serverOptions": {
+ "wait": 1000
+ },
+ "starterkitSubDir": "dist",
+ "styleGuideExcludes": [
+ ],
+ "theme": {
+ "color": "dark",
+ "density": "compact",
+ "layout": "horizontal"
+ },
+ "uikits": [
+ {
+ "name": "uikit-workshop",
+ "outputDir": "",
+ "enabled": true,
+ "excludedPatternStates": [],
+ "excludedTags": []
+ }
+ ]
+}
diff --git a/packages/core/scripts/api.handlebars b/packages/core/scripts/api.handlebars
new file mode 100644
index 000000000..8959ef5cf
--- /dev/null
+++ b/packages/core/scripts/api.handlebars
@@ -0,0 +1,20 @@
+# Pattern Lab Node API
+
+[](https://gitter.im/pattern-lab/node)
+
+## [Installation](https://github.com/pattern-lab/patternlab-node#installation)
+
+## Usage
+
+`patternlab-node` can be required within any Node environment, taking in a configuration file at instantiation.
+
+``` javascript
+const config = require('./patternlab-config.json');
+const patternlab = require('@pattern-lab/core')(config);
+```
+
+{{>main}}
+
+* * *
+
+[Pattern Lab](http://patternlab.io) Node is [MIT Licensed](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE)
diff --git a/packages/core/scripts/docs.js b/packages/core/scripts/docs.js
new file mode 100644
index 000000000..8b0907aca
--- /dev/null
+++ b/packages/core/scripts/docs.js
@@ -0,0 +1,34 @@
+const doc = require('jsdoc-to-markdown');
+const path = require('path');
+const process = require('process');
+const fs = require('fs-extra');
+
+// doc
+// .getJsdocData({
+// files: './src/lib/events.js',
+// })
+// .then(x => {
+// console.log(x);
+// });
+
+doc
+ .render({
+ 'example-lang': 'javascript',
+ files: path.resolve(process.cwd(), './src/index.js'),
+ 'name-format': 'backticks',
+ template: fs.readFileSync('./scripts/api.handlebars', 'utf8'),
+ })
+ .then(x => {
+ fs.outputFile(path.resolve(process.cwd(), './docs/README.md'), x);
+ });
+
+doc
+ .render({
+ 'example-lang': 'javascript',
+ files: path.resolve(process.cwd(), './src/lib/events.js'),
+ 'name-format': 'backticks',
+ template: fs.readFileSync('./scripts/events.handlebars', 'utf8'),
+ })
+ .then(x => {
+ fs.outputFile(path.resolve(process.cwd(), './docs/events.md'), x);
+ });
diff --git a/packages/core/scripts/events.handlebars b/packages/core/scripts/events.handlebars
new file mode 100644
index 000000000..667e8c9bf
--- /dev/null
+++ b/packages/core/scripts/events.handlebars
@@ -0,0 +1,19 @@
+# Pattern Lab Node Events
+
+[](https://gitter.im/pattern-lab/node)
+
+Pattern Lab emits numerous events during the [build](../docs/) process. Some uses of events:
+
+* Core uses `patternlab-pattern-change` events when watching for changes in order to trigger another build
+* Plugins such as [plugin-tab](https://github.com/pattern-lab/patternlab-node/tree/master/packages/plugin-tab) can use an event like `patternlab-pattern-write-end` to define additional code tabs to the pattern viewer / modal
+
+Learn more about [Creating Plugins](https://github.com/pattern-lab/patternlab-node/wiki/Creating-Plugins).
+
+{{#module name="Events"}}
+{{>header~}}
+{{>members}}
+{{/module}}
+
+* * *
+
+[Pattern Lab](http://patternlab.io) Node is [MIT Licensed](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE)
diff --git a/packages/core/src/index.js b/packages/core/src/index.js
new file mode 100644
index 000000000..1c575c3ab
--- /dev/null
+++ b/packages/core/src/index.js
@@ -0,0 +1,337 @@
+/**
+ * Build thoughtful, pattern-driven user interfaces using atomic design principles.
+ * Many of these functions are exposed to users within {@link https://github.com/pattern-lab/patternlab-node#editions|Editions}, but {@link https://github.com/pattern-lab/patternlab-node#direct-consumption|direct consumption} is also encouraged.
+ *
+ * @namespace patternlab
+ * @see {@link patternlab.io} for more documentation.
+ * @see {@link https://github.com/pattern-lab/patternlab-node} for code, issues, and releases
+ * @license MIT
+ */
+
+'use strict';
+
+const path = require('path');
+
+const updateNotifier = require('update-notifier');
+
+const packageInfo = require('../package.json');
+const events = require('./lib/events');
+const pe = require('./lib/pattern_exporter');
+const pm = require('./lib/plugin_manager');
+
+const defaultConfig = require('../patternlab-config.json');
+
+let buildPatterns = require('./lib/buildPatterns'); // eslint-disable-line
+let logger = require('./lib/log'); // eslint-disable-line
+let fs = require('fs-extra'); // eslint-disable-line
+let ui_builder = require('./lib/ui_builder'); // eslint-disable-line
+let copier = require('./lib/copier'); // eslint-disable-line
+let pattern_exporter = new pe(); // eslint-disable-line
+let serverModule = require('./lib/server'); // eslint-disable-line
+
+//bootstrap update notifier
+updateNotifier({
+ pkg: packageInfo,
+ updateCheckInterval: 1000 * 60 * 60 * 24, // notify at most once a day
+}).notify();
+
+/**
+ * Static method that returns the standardized default config used to run Pattern Lab. This method can be called statically or after instantiation.
+ *
+ * @memberof patternlab
+ * @name getDefaultConfig
+ * @static
+ * @return {object} Returns the object representation of the `patternlab-config.json`
+ */
+const getDefaultConfig = function() {
+ return defaultConfig;
+};
+
+/**
+ * Static method that returns current version
+ *
+ * @memberof patternlab
+ * @name getVersion
+ * @static
+ * @returns {string} current @pattern-lab/core version as defined in `package.json`
+ */
+const getVersion = function() {
+ return packageInfo.version;
+};
+
+const patternlab_module = function(config) {
+ const PatternLabClass = require('./lib/patternlab');
+ const patternlab = new PatternLabClass(config);
+ const server = serverModule(patternlab);
+
+ const _api = {
+ /**
+ * Returns current version
+ *
+ * @memberof patternlab
+ * @name version
+ * @instance
+ * @returns {string} current patternlab-node version as defined in `package.json`, as string
+ */
+ version: function() {
+ return patternlab.getVersion();
+ },
+
+ /**
+ * Returns the current pattern lab configuration being used
+ *
+ * @memberof patternlab
+ * @name getConfig
+ * @instance
+ * @returns {object} the current patternlab-node config (defaults + customizations)
+ */
+ getConfig() {
+ return config;
+ },
+
+ /**
+ * Returns if Pattern Lab is busy compiling or not
+ *
+ * @memberof patternlab
+ * @name isBusy
+ * @instance
+ * @returns {boolean} if pattern lab is currently busy compiling
+ */
+ isBusy: function() {
+ return patternlab.isBusy;
+ },
+
+ /**
+ * Builds patterns, copies assets, and constructs user interface
+ *
+ * @memberof patternlab
+ * @name build
+ * @instance
+ * @param {object} options an object used to control build behavior
+ * @param {bool} [options.cleanPublic=true] whether or not to delete the configured output location (usually `public/`) before build
+ * @param {object} [options.data={}] additional data to be merged with global data prior to build
+ * @param {bool} [options.watch=true] whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild
+ * @emits PATTERNLAB_BUILD_START
+ * @emits PATTERNLAB_BUILD_END
+ * @see {@link ./events.md|all events}
+ * @returns {Promise} a promise fulfilled when build is complete
+ */
+ build: async function(options) {
+ // process.on('unhandledRejection', (reason, p) => {
+ // console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
+ // // application specific logging, throwing an error, or other logic here
+
+ // console.log(reason.stack);
+ // debugger;
+ // });
+
+ if (patternlab && patternlab.isBusy) {
+ logger.info(
+ 'Pattern Lab is busy building a previous run - returning early.'
+ );
+ return Promise.resolve();
+ }
+ patternlab.isBusy = true;
+
+ return await buildPatterns(
+ options.cleanPublic,
+ patternlab,
+ options.data
+ ).then(() => {
+ return new ui_builder().buildFrontend(patternlab).then(() => {
+ copier()
+ .copyAndWatch(patternlab.config.paths, patternlab, options)
+ .then(() => {
+ patternlab.isBusy = false;
+ // only wire up this listener and the one inside serve.js
+ // figure out how to detect if serve was called. we should not assume it was
+ if (
+ patternlab.serverReady //check for server presence
+ ? this.events.listenerCount(
+ events.PATTERNLAB_PATTERN_CHANGE
+ ) === 1 //if the server is started, it has already setup one listener
+ : !this.events.listenerCount(events.PATTERNLAB_PATTERN_CHANGE) // else, check for the presnce of none
+ ) {
+ this.events.on(events.PATTERNLAB_PATTERN_CHANGE, () => {
+ if (!patternlab.isBusy) {
+ return this.build(options).then(() => {
+ patternlab.isBusy = false;
+ });
+ }
+ return Promise.resolve();
+ });
+ }
+
+ if (!this.events.listenerCount(events.PATTERNLAB_GLOBAL_CHANGE)) {
+ this.events.on(events.PATTERNLAB_GLOBAL_CHANGE, () => {
+ if (!patternlab.isBusy) {
+ return this.build(
+ Object.assign({}, options, { cleanPublic: true }) // rebuild everything
+ );
+ }
+ return Promise.resolve();
+ });
+ }
+ })
+ .then(() => {
+ this.events.emit(events.PATTERNLAB_BUILD_END, patternlab);
+ });
+ });
+ });
+ },
+
+ /**
+ * Returns the standardized default config used to run Pattern Lab. This method can be called statically or after instantiation.
+ *
+ * @memberof patternlab
+ * @name getDefaultConfig
+ * @instance
+ * @return {object} Returns the object representation of the `patternlab-config.json`
+ */
+ getDefaultConfig: function() {
+ return getDefaultConfig();
+ },
+
+ /**
+ * Returns all file extensions supported by installed PatternEngines
+ *
+ * @memberof patternlab
+ * @name getSupportedTemplateExtensions
+ * @instance
+ * @returns {Array} all supported file extensions
+ */
+ getSupportedTemplateExtensions: function() {
+ return patternlab.getSupportedTemplateExtensions();
+ },
+
+ /**
+ * Installs plugin already available via `node_modules/`
+ *
+ * @memberof patternlab
+ * @name installplugin
+ * @instance
+ * @param {string} pluginName name of plugin
+ * @returns {void}
+ */
+ installplugin: function(pluginName) {
+ //get the config
+ const configPath = path.resolve(process.cwd(), 'patternlab-config.json');
+ const plugin_manager = new pm();
+
+ plugin_manager.install_plugin(pluginName);
+ },
+
+ /**
+ * Fetches starterkit repositories from pattern-lab github org that contain 'starterkit' in their name
+ *
+ * @memberof patternlab
+ * @name liststarterkits
+ * @instance
+ * @returns {Promise} Returns an Array<{name,url}> for the starterkit repos
+ */
+ liststarterkits: function() {
+ return patternlab.listStarterkits();
+ },
+
+ /**
+ * Loads starterkit already available via `node_modules/`
+ *
+ * @memberof patternlab
+ * @name loadstarterkit
+ * @instance
+ * @param {string} starterkitName name of starterkit
+ * @param {boolean} clean whether or not to delete contents of source/ before load
+ * @returns {void}
+ */
+ loadstarterkit: function(starterkitName, clean) {
+ patternlab.loadStarterKit(starterkitName, clean);
+ },
+
+ /**
+ * Builds patterns only, leaving existing user interface files intact
+ *
+ * @memberof patternlab
+ * @name patternsonly
+ * @instance
+ * @param {bool} [options.cleanPublic=true] whether or not to delete the configured output location (usually `public/`) before build
+ * @param {object} [options.data={}] additional data to be merged with global data prior to build
+ * @param {bool} [options.watch=true] whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild
+ * @returns {Promise} a promise fulfilled when build is complete
+ */
+ patternsonly: async function(options) {
+ if (patternlab && patternlab.isBusy) {
+ logger.info(
+ 'Pattern Lab is busy building a previous run - returning early.'
+ );
+ return Promise.resolve();
+ }
+ patternlab.isBusy = true;
+ return await buildPatterns(
+ options.cleanPublic,
+ patternlab,
+ options.data
+ ).then(() => {
+ patternlab.isBusy = false;
+ });
+ },
+
+ /**
+ * Server module
+ *
+ * @memberof patternlab
+ * @type {object}
+ */
+ server: {
+ /**
+ * Build patterns, copies assets, and constructs user interface. Watches configured `source/` directories, and serves all output locally
+ *
+ * @method serve
+ * @memberof patternlab.server
+ * @param {object} options an object used to control build behavior
+ * @param {bool} [options.cleanPublic=true] whether or not to delete the configured output location (usually `public/`) before build
+ * @param {object} [options.data={}] additional data to be merged with global data prior to build
+ * @param {bool} [options.watch=true] whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild
+ * @returns {Promise} a promise fulfilled when build is complete
+ */
+ serve: options => {
+ return _api
+ .build(options)
+ .then(() => server.serve())
+ .catch(e =>
+ logger.error(`error inside core index.js server serve: ${e}`)
+ );
+ },
+ /**
+ * Reloads any active live-server instances
+ *
+ * @method reload
+ * @memberof patternlab.server
+ * @returns {Promise} a promise fulfilled when operation is complete
+ */
+ reload: server.reload,
+ /**
+ * Reloads CSS on any active live-server instances
+ *
+ * @method refreshCSS
+ * @memberof patternlab.server
+ * @returns {Promise} a promise fulfilled when operation is complete
+ */
+ refreshCSS: server.refreshCSS,
+ },
+
+ /**
+ * @memberof patternlab
+ * @type {EventEmitter}
+ * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter|EventEmitter}
+ * @see {@link ./events.md|All Pattern Lab events}
+ */
+ events: patternlab.events,
+ };
+
+ return _api;
+};
+
+patternlab_module.getDefaultConfig = getDefaultConfig;
+patternlab_module.getVersion = getVersion;
+
+module.exports = patternlab_module;
diff --git a/packages/core/src/lib/addPattern.js b/packages/core/src/lib/addPattern.js
new file mode 100644
index 000000000..12c0c9429
--- /dev/null
+++ b/packages/core/src/lib/addPattern.js
@@ -0,0 +1,45 @@
+'use strict';
+
+const logger = require('./log');
+
+module.exports = function(pattern, patternlab) {
+ //add the link to the global object
+ if (!patternlab.data.link) {
+ patternlab.data.link = {};
+ }
+ patternlab.data.link[pattern.patternPartial] =
+ '/patterns/' + pattern.patternLink;
+
+ //only push to array if the array doesn't contain this pattern
+ let isNew = true;
+ for (let i = 0; i < patternlab.patterns.length; i++) {
+ //so we need the identifier to be unique, which patterns[i].relPath is
+ if (pattern.relPath === patternlab.patterns[i].relPath) {
+ //if relPath already exists, overwrite that element
+ patternlab.patterns[i] = pattern;
+ patternlab.partials[pattern.patternPartial] =
+ pattern.extendedTemplate || pattern.template;
+ isNew = false;
+ break;
+ }
+ }
+
+ // if the pattern is new, we must register it with various data structures!
+ if (isNew) {
+ logger.debug(`found new pattern ${pattern.patternPartial}`);
+
+ // do global registration
+ if (pattern.isPattern) {
+ patternlab.partials[pattern.patternPartial] =
+ pattern.extendedTemplate || pattern.template;
+
+ // do plugin-specific registration
+ pattern.registerPartial();
+ } else {
+ patternlab.partials[pattern.patternPartial] = pattern.patternDesc;
+ }
+
+ patternlab.patterns.push(pattern);
+ patternlab.graph.add(pattern);
+ }
+};
diff --git a/packages/core/src/lib/annotation_exporter.js b/packages/core/src/lib/annotation_exporter.js
new file mode 100644
index 000000000..7ed86da28
--- /dev/null
+++ b/packages/core/src/lib/annotation_exporter.js
@@ -0,0 +1,125 @@
+'use strict';
+const path = require('path');
+const glob = require('glob');
+const fs = require('fs-extra');
+const _ = require('lodash');
+const mp = require('./markdown_parser');
+const logger = require('./log');
+
+const annotations_exporter = function(pl) {
+ const paths = pl.config.paths;
+ let oldAnnotations;
+
+ /**
+ * Parses JS annotations.
+ * @returns array of comments that used to be wrapped in raw JS
+ */
+ function parseAnnotationsJS() {
+ //attempt to read the file
+ try {
+ oldAnnotations = fs.readFileSync(
+ path.resolve(paths.source.annotations, 'annotations.js'),
+ 'utf8'
+ );
+ } catch (ex) {
+ logger.debug(
+ `annotations.js file missing from ${paths.source.annotations}. This may be expected if you do not use annotations or are using markdown.`
+ );
+ return [];
+ }
+
+ //parse as JSON by removing the old wrapping js syntax. comments and the trailing semi-colon
+ oldAnnotations = oldAnnotations.replace('var comments = ', '');
+ oldAnnotations = oldAnnotations.replace('};', '}');
+
+ try {
+ const oldAnnotationsJSON = JSON.parse(oldAnnotations);
+ return oldAnnotationsJSON.comments;
+ } catch (ex) {
+ logger.error(
+ `There was an error parsing JSON for ${paths.source.annotations}annotations.js`
+ );
+ return [];
+ }
+ }
+
+ /**
+ * Build the annotation markdown.
+ * @param annotationsYAML
+ * @param markdown_parser
+ * @returns annotation
+ */
+ function buildAnnotationMD(annotationsYAML, markdown_parser) {
+ const annotation = {};
+ const markdownObj = markdown_parser.parse(annotationsYAML);
+
+ annotation.el = markdownObj.el || markdownObj.selector;
+ annotation.title = markdownObj.title;
+ annotation.comment = markdownObj.markdown;
+ return annotation;
+ }
+
+ /**
+ * Parse markdown file annotations.
+ * @param annotations
+ * @param parser
+ */
+ function parseMDFile(annotations, parser) {
+ //let annotations = annotations;
+ const markdown_parser = parser;
+
+ return function(filePath) {
+ const annotationsMD = fs.readFileSync(path.resolve(filePath), 'utf8');
+
+ //take the annotation snippets and split them on our custom delimiter
+ const annotationsYAML = annotationsMD.split('~*~');
+ for (let i = 0; i < annotationsYAML.length; i++) {
+ const annotation = buildAnnotationMD(
+ annotationsYAML[i],
+ markdown_parser
+ );
+ annotations.push(annotation);
+ }
+ return false;
+ };
+ }
+
+ /**
+ * Converts the *.md file yaml list into an array of annotations
+ *
+ * @returns annotations
+ */
+ function parseAnnotationsMD() {
+ const markdown_parser = new mp();
+ const annotations = [];
+ const mdFiles = glob.sync(paths.source.annotations + '/*.md');
+
+ mdFiles.forEach(parseMDFile(annotations, markdown_parser));
+ return annotations;
+ }
+
+ /**
+ * Gathers JS & MD annotations.
+ *
+ * @returns array of annotations
+ */
+ function gatherAnnotations() {
+ const annotationsJS = parseAnnotationsJS();
+ const annotationsMD = parseAnnotationsMD();
+ return _.unionBy(annotationsJS, annotationsMD, 'el');
+ }
+
+ return {
+ gather: function() {
+ return gatherAnnotations();
+ },
+ gatherJS: function() {
+ return parseAnnotationsJS();
+ },
+ gatherMD: function() {
+ return parseAnnotationsMD();
+ },
+ };
+};
+
+module.exports = annotations_exporter;
diff --git a/packages/core/src/lib/buildFooter.js b/packages/core/src/lib/buildFooter.js
new file mode 100644
index 000000000..5a3eb6217
--- /dev/null
+++ b/packages/core/src/lib/buildFooter.js
@@ -0,0 +1,43 @@
+'use strict';
+
+const jsonCopy = require('./json_copy');
+const logger = require('./log');
+const of = require('./object_factory');
+const Pattern = of.Pattern;
+
+let render = require('./render'); //eslint-disable-line prefer-const
+
+/**
+ * Builds footer HTML from the general footer and user-defined footer
+ * @param patternlab - global data store
+ * @param patternPartial - the partial key to build this for, either viewall-patternPartial or a viewall-patternType-all
+ * @returns A promise which resolves with the HTML
+ */
+module.exports = function(patternlab, patternPartial, uikit) {
+ //first render the general footer
+ return render(Pattern.createEmpty({ extendedTemplate: uikit.footer }), {
+ patternData: JSON.stringify({
+ patternPartial: patternPartial,
+ }),
+ cacheBuster: patternlab.cacheBuster,
+ })
+ .then(footerPartial => {
+ let allFooterData;
+ try {
+ allFooterData = jsonCopy(
+ patternlab.data,
+ 'config.paths.source.data plus patterns data'
+ );
+ } catch (err) {
+ logger.warning('There was an error parsing JSON for patternlab.data');
+ logger.warning(err);
+ }
+ allFooterData.patternLabFoot = footerPartial;
+
+ return render(patternlab.userFoot, allFooterData);
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error building buildFooterHTML');
+ });
+};
diff --git a/packages/core/src/lib/buildListItems.js b/packages/core/src/lib/buildListItems.js
new file mode 100644
index 000000000..af9ba783d
--- /dev/null
+++ b/packages/core/src/lib/buildListItems.js
@@ -0,0 +1,53 @@
+'use strict';
+
+let _ = require('lodash'); //eslint-disable-line prefer-const
+
+const items = [
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'ten',
+ 'eleven',
+ 'twelve',
+ 'thirteen',
+ 'fourteen',
+ 'fifteen',
+ 'sixteen',
+ 'seventeen',
+ 'eighteen',
+ 'nineteen',
+ 'twenty',
+];
+
+module.exports = function(container) {
+ //combine all list items into one structure
+ const list = [];
+ for (const item in container.listitems) {
+ if (container.listitems.hasOwnProperty(item)) {
+ list.push(container.listitems[item]);
+ }
+ }
+ const listItemArray = _.shuffle(list);
+
+ for (let i = 1; i <= listItemArray.length; i++) {
+ const tempItems = [];
+ if (i === 1) {
+ tempItems.push(listItemArray[0]);
+ container.listitems['listItems-' + items[i]] = tempItems;
+ delete container.listitems[i];
+ } else {
+ for (let c = 1; c <= i; c++) {
+ tempItems.push(listItemArray[c - 1]);
+ container.listitems['listItems-' + items[i]] = tempItems;
+ delete container.listitems[i];
+ }
+ }
+ }
+};
diff --git a/packages/core/src/lib/buildPatterns.js b/packages/core/src/lib/buildPatterns.js
new file mode 100644
index 000000000..c550a6880
--- /dev/null
+++ b/packages/core/src/lib/buildPatterns.js
@@ -0,0 +1,229 @@
+'use strict';
+
+const { concat, map } = require('lodash');
+const copy = require('recursive-copy');
+const path = require('path');
+
+const cleanBuildDirectory = require('./cleanBuildDirectory');
+const compose = require('./compose');
+const events = require('./events');
+const loadPatternGraph = require('./loadPatternGraph');
+const logger = require('./log');
+const PatternGraph = require('./pattern_graph').PatternGraph;
+const CompileState = require('./object_factory').CompileState;
+const processMetaPattern = require('./processMetaPattern');
+const pe = require('./pattern_exporter');
+const lh = require('./lineage_hunter');
+const pm = require('./plugin_manager');
+const pluginMananger = new pm();
+const markModifiedPatterns = require('./markModifiedPatterns');
+const parseAllLinks = require('./parseAllLinks');
+const render = require('./render');
+const Pattern = require('./object_factory').Pattern;
+
+let fs = require('fs-extra'); // eslint-disable-line
+let pattern_exporter = new pe(); // eslint-disable-line
+
+const lineage_hunter = new lh();
+
+module.exports = async (deletePatternDir, patternlab, additionalData) => {
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_BUILD_START,
+ patternlab
+ );
+
+ const paths = patternlab.config.paths;
+
+ //
+ // CHECK INCREMENTAL BUILD GRAPH
+ //
+ const graph = (patternlab.graph = loadPatternGraph(
+ patternlab,
+ patternlab.config.cleanPublic
+ ));
+ const graphNeedsUpgrade = !PatternGraph.checkVersion(graph);
+ if (graphNeedsUpgrade) {
+ logger.info(
+ 'Due to an upgrade, a complete rebuild is required and the public/patterns directory was deleted. ' +
+ 'Incremental build is available again on the next successful run.'
+ );
+
+ // Ensure that the freshly built graph has the latest version again.
+ patternlab.graph.upgradeVersion();
+ }
+
+ // Flags
+ patternlab.incrementalBuildsEnabled = !(
+ patternlab.config.cleanPublic || graphNeedsUpgrade
+ );
+
+ //
+ // CLEAN BUILD DIRECTORY, maybe
+ //
+ return cleanBuildDirectory(
+ patternlab.incrementalBuildsEnabled,
+ patternlab
+ ).then(() => {
+ patternlab.buildGlobalData(additionalData);
+
+ return patternlab
+ .processAllPatternsIterative(paths.source.patterns)
+ .then(async () => {
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_PATTERN_ITERATION_END,
+ patternlab
+ );
+
+ //now that all the main patterns are known, look for any links that might be within data and expand them
+ //we need to do this before expanding patterns & partials into extendedTemplates, otherwise we could lose the data -> partial reference
+ parseAllLinks(patternlab);
+
+ //dive again to recursively include partials, filling out the
+ //extendedTemplate property of the patternlab.patterns elements
+
+ return patternlab
+ .processAllPatternsRecursive(paths.source.patterns)
+ .then(() => {
+ //take the user defined head and foot and process any data and patterns that apply
+
+ //todo this need to be made aware of multiple ui kits
+ //perhaps we can check for a convention like [uikitname]_00-head.mustache, and if found, add them to patternlab.uikits[uikitname].userFoot
+ //then, if present, use those during compose()
+ const headPatternPromise = processMetaPattern(
+ `_00-head.${patternlab.config.patternExtension}`,
+ 'userHead',
+ patternlab
+ );
+ const footPatternPromise = processMetaPattern(
+ `_01-foot.${patternlab.config.patternExtension}`,
+ 'userFoot',
+ patternlab
+ );
+
+ return Promise.all([headPatternPromise, footPatternPromise])
+ .then(() => {
+ //cascade any patternStates
+ lineage_hunter.cascade_pattern_states(patternlab);
+
+ //set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header
+ return render(
+ Pattern.createEmpty({
+ // todo should this be uikit.header?
+ extendedTemplate: patternlab.header,
+ }),
+ {
+ cacheBuster: patternlab.cacheBuster,
+ }
+ )
+ .then(results => {
+ patternlab.data.patternLabHead = results;
+
+ // If deletePatternDir == true or graph needs to be updated
+ // rebuild all patterns
+ let patternsToBuild = null;
+
+ // If deletePatternDir == true or graph needs to be updated
+ // rebuild all patterns
+ patternsToBuild = null;
+
+ if (patternlab.incrementalBuildsEnabled) {
+ // When the graph was loaded from file, some patterns might have been moved/deleted between runs
+ // so the graph data become out of sync
+ patternlab.graph.sync().forEach(n => {
+ logger.info('[Deleted/Moved] ' + n);
+ });
+
+ // TODO Find created or deleted files
+ const now = new Date().getTime();
+ markModifiedPatterns(now, patternlab);
+ patternsToBuild = patternlab.graph.compileOrder();
+ } else {
+ // build all patterns, mark all to be rebuilt
+ patternsToBuild = patternlab.patterns;
+ for (const p of patternsToBuild) {
+ p.compileState = CompileState.NEEDS_REBUILD;
+ }
+ }
+ //render all patterns last, so lineageR works
+ const allPatternsPromise = patternsToBuild.map(
+ async pattern =>
+ await compose(
+ pattern,
+ patternlab
+ )
+ );
+ //copy non-pattern files like JavaScript
+ const allJS = patternsToBuild.map(pattern => {
+ const { name, patternPartial, subdir } = pattern;
+ const {
+ source: { patterns: sourceDir },
+ public: { patterns: publicDir },
+ } = patternlab.config.paths;
+ const src = path.join(sourceDir, subdir);
+ const dest = path.join(publicDir, name);
+ return map(patternlab.uikits, uikit => {
+ return copy(
+ src,
+ path.resolve(process.cwd(), uikit.outputDir, dest),
+ {
+ overwrite: true,
+ filter: ['*.js'],
+ rename: () => {
+ return `${patternPartial}.js`;
+ },
+ }
+ ).on(copy.events.COPY_FILE_COMPLETE, () => {
+ logger.debug(
+ `Copied JavaScript files from ${src} to ${dest}`
+ );
+ });
+ });
+ });
+ return Promise.all(concat(allPatternsPromise, allJS))
+ .then(() => {
+ // Saves the pattern graph when all files have been compiled
+ PatternGraph.storeToFile(patternlab);
+ if (patternlab.config.exportToGraphViz) {
+ PatternGraph.exportToDot(
+ patternlab,
+ 'dependencyGraph.dot'
+ );
+ logger.info(
+ `Exported pattern graph to ${path.join(
+ patternlab.config.paths.public.root,
+ 'dependencyGraph.dot'
+ )}`
+ );
+ }
+
+ //export patterns if necessary
+ pattern_exporter.export_patterns(patternlab);
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error rendering patterns');
+ });
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error rendering pattern lab header');
+ });
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error processing meta patterns');
+ });
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error processing patterns recursively');
+ });
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error in buildPatterns()');
+ });
+ });
+};
diff --git a/packages/core/src/lib/changes_hunter.js b/packages/core/src/lib/changes_hunter.js
new file mode 100644
index 000000000..038be8b41
--- /dev/null
+++ b/packages/core/src/lib/changes_hunter.js
@@ -0,0 +1,121 @@
+'use strict';
+
+const path = require('path');
+const _ = require('lodash');
+
+const CompileState = require('./object_factory').CompileState;
+
+//this is mocked in unit tests
+let fs = require('fs-extra'); //eslint-disable-line prefer-const
+
+/**
+ * For detecting changed patterns.
+ * @constructor
+ */
+const ChangesHunter = function() {};
+
+ChangesHunter.prototype = {
+ /**
+ * Checks the build state of a pattern by comparing the modification date of the rendered output
+ * file with the {@link Pattern.lastModified}. If the pattern was modified after the last
+ * time it has been rendered, it is flagged for rebuilding via {@link CompileState.NEEDS_REBUILD}.
+ *
+ * @param {Pattern} pattern
+ * @param patternlab
+ *
+ * @see {@link CompileState}
+ */
+ checkBuildState: function(pattern, patternlab) {
+ //write the compiled template to the public patterns directory
+ const renderedTemplatePath =
+ patternlab.config.paths.public.patterns +
+ pattern.getPatternLink(patternlab, 'rendered');
+
+ //write the compiled template to the public patterns directory
+ const markupOnlyPath =
+ patternlab.config.paths.public.patterns +
+ pattern.getPatternLink(patternlab, 'markupOnly');
+
+ if (!pattern.compileState) {
+ pattern.compileState = CompileState.NEEDS_REBUILD;
+ }
+
+ _.each(patternlab.uikits, uikit => {
+ try {
+ // renderedTemplatePath required to display a single element
+ // Markup only is required for "View All" pages. It will get loaded later on.
+ // If any of these is missing, mark pattern for recompile
+ [renderedTemplatePath, markupOnlyPath].forEach(renderedFile => {
+ // Prevent error message if file does not exist
+ fs.accessSync(
+ path.join(process.cwd(), uikit.outputDir, renderedFile),
+ fs.F_OK
+ );
+ });
+
+ const outputLastModified = fs
+ .statSync(
+ path.join(process.cwd(), uikit.outputDir, renderedTemplatePath)
+ )
+ .mtime.getTime();
+
+ if (pattern.lastModified && outputLastModified > pattern.lastModified) {
+ pattern.compileState = CompileState.CLEAN;
+ }
+ } catch (e) {
+ // Output does not exist yet, force recompile
+ pattern.compileState = CompileState.NEEDS_REBUILD;
+ }
+ });
+
+ const node = patternlab.graph.node(pattern);
+
+ // IF we are rebuilding due to watching and incrementally building, force add patterns to graph
+ if (
+ patternlab.incrementalBuildsEnabled &&
+ Object.keys(patternlab.watchers).length
+ ) {
+ patternlab.graph.add(pattern);
+ } else {
+ // Make the pattern known to the PatternGraph and remember its compileState
+ if (!node) {
+ patternlab.graph.add(pattern);
+ } else {
+ // Works via object reference, so we directly manipulate the node data here
+ node.compileState = pattern.compileState;
+ }
+ }
+ },
+
+ /**
+ * Updates {Pattern#lastModified} to the files modification date if the file was modified
+ * after {Pattern#lastModified}.
+ *
+ * @param {Pattern} currentPattern
+ * @param {string} file
+ */
+ checkLastModified: function(currentPattern, file) {
+ if (file && fs.pathExistsSync(file)) {
+ try {
+ const stat = fs.statSync(file);
+
+ // Needs recompile whenever one of the patterns files (template, json, pseudopatterns) changed
+ currentPattern.lastModified = Math.max(
+ stat.mtime.getTime(),
+ currentPattern.lastModified || 0
+ );
+ } catch (e) {
+ // Ignore, not a regular file
+ }
+ }
+ },
+
+ needsRebuild: function(lastModified, p) {
+ if (p.compileState !== CompileState.CLEAN || !p.lastModified) {
+ return true;
+ }
+ return p.lastModified >= lastModified;
+ },
+};
+
+module.exports = ChangesHunter;
diff --git a/packages/core/src/lib/cleanBuildDirectory.js b/packages/core/src/lib/cleanBuildDirectory.js
new file mode 100644
index 000000000..11d78c01e
--- /dev/null
+++ b/packages/core/src/lib/cleanBuildDirectory.js
@@ -0,0 +1,27 @@
+'use strict';
+
+const _ = require('lodash');
+const path = require('path');
+
+const logger = require('./log');
+
+let fs = require('fs-extra'); // eslint-disable-line
+
+module.exports = (incrementalBuildsEnabled, patternlab) => {
+ const paths = patternlab.config.paths;
+
+ if (incrementalBuildsEnabled) {
+ logger.info('Incremental builds enabled.');
+ return Promise.resolve();
+ } else {
+ return Promise.all(
+ _.map(patternlab.uikits, uikit => {
+ return fs.emptyDir(
+ path.join(process.cwd(), uikit.outputDir, paths.public.patterns)
+ );
+ })
+ ).catch(reason => {
+ logger.error(reason);
+ });
+ }
+};
diff --git a/packages/core/src/lib/compose.js b/packages/core/src/lib/compose.js
new file mode 100644
index 000000000..07c202ce7
--- /dev/null
+++ b/packages/core/src/lib/compose.js
@@ -0,0 +1,213 @@
+'use strict';
+
+const _ = require('lodash');
+
+const events = require('./events');
+const jsonCopy = require('./json_copy');
+const logger = require('./log');
+const parseLink = require('./parseLink');
+const render = require('./render');
+const uikitExcludePattern = require('./uikitExcludePattern');
+const pm = require('./plugin_manager');
+const pluginMananger = new pm();
+
+const Pattern = require('./object_factory').Pattern;
+const CompileState = require('./object_factory').CompileState;
+
+module.exports = async function(pattern, patternlab) {
+ // Pattern does not need to be built and recompiled more than once
+ if (!pattern.isPattern || pattern.compileState === CompileState.CLEAN) {
+ return Promise.resolve(false);
+ }
+
+ // Allows serializing the compile state
+ patternlab.graph.node(pattern).compileState = pattern.compileState =
+ CompileState.BUILDING;
+
+ //todo move this into lineage_hunter
+ pattern.patternLineages = pattern.lineage;
+ pattern.patternLineageExists = pattern.lineage.length > 0;
+ pattern.patternLineagesR = pattern.lineageR;
+ pattern.patternLineageRExists = pattern.lineageR.length > 0;
+ pattern.patternLineageEExists =
+ pattern.patternLineageExists || pattern.patternLineageRExists;
+
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_PATTERN_BEFORE_DATA_MERGE,
+ patternlab,
+ pattern
+ );
+
+ return Promise.all(
+ _.map(patternlab.uikits, uikit => {
+ // exclude pattern from uikit rendering
+ if (uikitExcludePattern(pattern, uikit)) {
+ return Promise.resolve();
+ }
+
+ //render the pattern, but first consolidate any data we may have
+ let allData;
+
+ let allListItems = _.merge({}, patternlab.listitems, pattern.listitems);
+ allListItems = parseLink(
+ patternlab,
+ allListItems,
+ 'listitems.json + any pattern listitems.json'
+ );
+
+ allData = _.merge({}, patternlab.data, pattern.jsonFileData);
+ allData = _.merge({}, allData, allListItems);
+ allData.cacheBuster = patternlab.cacheBuster;
+ allData.patternPartial = pattern.patternPartial;
+
+ ///////////////
+ // HEADER
+ ///////////////
+
+ //re-rendering the headHTML each time allows pattern-specific data to influence the head of the pattern
+ let headPromise;
+ if (patternlab.userHead) {
+ headPromise = render(patternlab.userHead, allData);
+ } else {
+ headPromise = render(
+ Pattern.createEmpty({ extendedTemplate: uikit.header }),
+ allData
+ );
+ }
+
+ ///////////////
+ // PATTERN
+ ///////////////
+
+ //render the extendedTemplate with all data
+ const patternPartialPromise = render(
+ pattern,
+ allData,
+ patternlab.partials
+ );
+
+ ///////////////
+ // FOOTER
+ ///////////////
+
+ // stringify this data for individual pattern rendering and use on the styleguide
+ // see if patternData really needs these other duped values
+
+ // construct our extraOutput dump
+ const extraOutput = Object.assign(
+ {},
+ pattern.extraOutput,
+ pattern.allMarkdown
+ );
+ delete extraOutput.title;
+ delete extraOutput.state;
+ delete extraOutput.markdown;
+
+ pattern.patternData = JSON.stringify({
+ cssEnabled: false,
+ patternLineageExists: pattern.patternLineageExists,
+ patternLineages: pattern.patternLineages,
+ lineage: pattern.patternLineages,
+ patternLineageRExists: pattern.patternLineageRExists,
+ patternLineagesR: pattern.patternLineagesR,
+ lineageR: pattern.patternLineagesR,
+ patternLineageEExists:
+ pattern.patternLineageExists || pattern.patternLineageRExists,
+ patternDesc: pattern.patternDescExists ? pattern.patternDesc : '',
+ patternBreadcrumb:
+ pattern.patternGroup === pattern.patternSubGroup
+ ? {
+ patternType: pattern.patternGroup,
+ }
+ : {
+ patternType: pattern.patternGroup,
+ patternSubtype: pattern.patternSubGroup,
+ },
+ patternExtension: pattern.fileExtension.substr(1), //remove the dot because styleguide asset default adds it for us
+ patternName: pattern.patternName,
+ patternPartial: pattern.patternPartial,
+ patternState: pattern.patternState,
+ patternEngineName: pattern.engine.engineName,
+ extraOutput: extraOutput,
+ });
+
+ //set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer
+ const footerPartialPromise = render(
+ Pattern.createEmpty({ extendedTemplate: uikit.footer }),
+ {
+ isPattern: pattern.isPattern,
+ patternData: pattern.patternData,
+ cacheBuster: patternlab.cacheBuster,
+ }
+ );
+
+ return Promise.all([
+ headPromise,
+ patternPartialPromise,
+ footerPartialPromise,
+ ])
+ .then(intermediateResults => {
+ // retrieve results of promises
+ const headHTML = intermediateResults[0]; //headPromise
+ pattern.patternPartialCode = intermediateResults[1]; //patternPartialPromise
+ const footerPartial = intermediateResults[2]; //footerPartialPromise
+
+ //finish up our footer data
+ let allFooterData;
+ try {
+ allFooterData = jsonCopy(
+ patternlab.data,
+ 'config.paths.source.data global data'
+ );
+ } catch (err) {
+ logger.info(
+ 'There was an error parsing JSON for ' + pattern.relPath
+ );
+ logger.info(err);
+ }
+ allFooterData = _.merge(allFooterData, pattern.jsonFileData);
+ allFooterData.cacheBuster = patternlab.cacheBuster;
+ allFooterData.patternLabFoot = footerPartial;
+
+ return render(patternlab.userFoot, allFooterData).then(
+ async footerHTML => {
+ ///////////////
+ // WRITE FILES
+ ///////////////
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_PATTERN_WRITE_BEGIN,
+ patternlab,
+ pattern
+ );
+
+ //write the compiled template to the public patterns directory
+ patternlab.writePatternFiles(
+ headHTML,
+ pattern,
+ footerHTML,
+ uikit.outputDir
+ );
+
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_PATTERN_WRITE_END,
+ patternlab,
+ pattern
+ );
+
+ // Allows serializing the compile state
+ patternlab.graph.node(
+ pattern
+ ).compileState = pattern.compileState = CompileState.CLEAN;
+ logger.info('Built pattern: ' + pattern.patternPartial);
+ }
+ );
+ })
+ .catch(reason => {
+ console.log(reason);
+ });
+ })
+ );
+};
diff --git a/packages/core/src/lib/copier.js b/packages/core/src/lib/copier.js
new file mode 100644
index 000000000..441ffdf5b
--- /dev/null
+++ b/packages/core/src/lib/copier.js
@@ -0,0 +1,125 @@
+'use strict';
+const _ = require('lodash');
+const path = require('path');
+const process = require('process');
+
+const copyFile = require('./copyFile');
+const watchAssets = require('./watchAssets');
+const watchPatternLabFiles = require('./watchPatternLabFiles');
+
+const copier = () => {
+ const transform_paths = directories => {
+ //create array with all source keys minus our blacklist
+ const dirs = {};
+ const blackList = [
+ 'root',
+ 'patterns',
+ 'data',
+ 'meta',
+ 'annotations',
+ 'patternlabFiles',
+ 'styleguide',
+ ];
+ _.each(directories.source, (dir, key) => {
+ if (blackList.includes(key)) {
+ return;
+ }
+
+ if (!dirs.key) {
+ dirs[key] = {};
+ }
+ });
+
+ // loop through all source keys
+ _.each(dirs, (dir, key) => {
+ // add source key path
+ dirs[key].source = directories.source[key];
+
+ // add public key path
+ dirs[key].public = directories.public[key];
+ });
+
+ return dirs;
+ };
+
+ const copyAndWatch = (assetDirectories, patternlab, options) => {
+ //take our configured paths and sanitize best we can to only the assets
+ const dirs = transform_paths(assetDirectories);
+
+ //find out where we are
+ const basePath = path.resolve(process.cwd());
+
+ const copyOptions = {
+ overwrite: true,
+ emitter: patternlab.events,
+ debug: patternlab.config.logLevel === 'debug',
+ };
+
+ //loop through each directory asset object (source / public pairing)
+
+ const copyPromises = [];
+
+ _.each(dirs, (dir, key) => {
+ //if we want to watch files, do so, otherwise just copy each file
+ if (options.watch) {
+ watchAssets(patternlab, basePath, dir, key, copyOptions);
+ } else {
+ //just copy
+ copyPromises.push(
+ _.map(patternlab.uikits, uikit => {
+ copyFile(
+ dir.source,
+ path.join(basePath, uikit.outputDir, dir.public),
+ copyOptions
+ );
+ })
+ );
+ }
+ });
+
+ // copy the styleguide
+ copyPromises.push(
+ _.map(patternlab.uikits, uikit => {
+ copyFile(
+ path.join(uikit.modulePath, assetDirectories.source.styleguide),
+ path.join(basePath, uikit.outputDir, assetDirectories.public.root),
+ copyOptions
+ );
+ })
+ );
+
+ // copy the favicon
+ copyPromises.push(
+ _.map(patternlab.uikits, uikit => {
+ copyFile(
+ `${assetDirectories.source.root}/favicon.ico`,
+ path.join(
+ basePath,
+ uikit.outputDir,
+ `${assetDirectories.public.root}/favicon.ico`
+ ),
+ copyOptions
+ );
+ })
+ );
+
+ return Promise.all(copyPromises).then(() => {
+ //we need to special case patterns/**/*.md|.json|.pattern-extensions as well as the global structures
+ if (options.watch) {
+ return watchPatternLabFiles(patternlab, assetDirectories, basePath);
+ }
+ return Promise.resolve();
+ });
+ };
+
+ return {
+ copyAndWatch: (assetDirectories, patternlab, options) => {
+ return copyAndWatch(assetDirectories, patternlab, options);
+ },
+ transformConfigPaths: paths => {
+ return transform_paths(paths);
+ },
+ };
+};
+
+module.exports = copier;
diff --git a/packages/core/src/lib/copyFile.js b/packages/core/src/lib/copyFile.js
new file mode 100644
index 000000000..ce1bc056a
--- /dev/null
+++ b/packages/core/src/lib/copyFile.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const logger = require('./log');
+const events = require('./events');
+
+let copy = require('recursive-copy'); // eslint-disable-line prefer-const
+
+const copyFile = (p, dest, options) => {
+ return copy(p, dest, options)
+ .on(copy.events.ERROR, function(error, copyOperation) {
+ logger.error('Unable to copy ' + copyOperation.dest);
+ })
+ .on(copy.events.COPY_FILE_ERROR, error => {
+ logger.error(error);
+ })
+ .on(copy.events.COPY_FILE_COMPLETE, () => {
+ logger.debug(`Moved ${p} to ${dest}`);
+ options.emitter.emit(events.PATTERNLAB_PATTERN_ASSET_CHANGE, {
+ file: p,
+ dest: dest,
+ });
+ });
+};
+
+module.exports = copyFile;
diff --git a/packages/core/src/lib/data_loader.js b/packages/core/src/lib/data_loader.js
new file mode 100644
index 000000000..f1ac874c2
--- /dev/null
+++ b/packages/core/src/lib/data_loader.js
@@ -0,0 +1,75 @@
+'use strict';
+
+const glob = require('glob'),
+ _ = require('lodash'),
+ path = require('path'),
+ yaml = require('js-yaml');
+
+/**
+ * Loads a single config file, in yaml/json format.
+ *
+ * @param dataFilePath - leave off the file extension.
+ * @param fsDep
+ * @returns {*}
+ */
+function loadFile(dataFilePath, fsDep) {
+ const dataFilesFullPath = `${dataFilePath}.{json,yml,yaml}`;
+
+ if (dataFilePath) {
+ const dataFiles = glob.sync(dataFilesFullPath),
+ dataFile = _.head(dataFiles);
+
+ if (dataFile && fsDep.existsSync(path.resolve(dataFile))) {
+ try {
+ return yaml.safeLoad(
+ fsDep.readFileSync(path.resolve(dataFile), 'utf8')
+ );
+ } catch (err) {
+ throw new Error(`Error loading file: ${dataFile} - ${err.message}`);
+ }
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Loads a set of config files from a folder, in yaml/json format.
+ *
+ * @param dataFilesPath - leave off the file extension
+ * @param excludeFileNames - leave off the file extension
+ * @param fsDep
+ * @returns Object, with merged data files, empty object if no files.
+ */
+function loadDataFromFolder(dataFilesPath, excludeFileNames, fsDep) {
+ const dataFilesFullPath = dataFilesPath + '*.{json,yml,yaml}',
+ excludeFullPath = dataFilesPath + excludeFileNames + '.{json,yml,yaml}';
+
+ const globOptions = {};
+ if (excludeFileNames) {
+ globOptions.ignore = [excludeFullPath];
+ }
+
+ const dataFiles = glob.sync(dataFilesFullPath, globOptions);
+ let mergeObject = {};
+
+ dataFiles.forEach(function(filePath) {
+ try {
+ const jsonData = yaml.safeLoad(
+ fsDep.readFileSync(path.resolve(filePath), 'utf8')
+ );
+ mergeObject = _.merge(mergeObject, jsonData);
+ } catch (err) {
+ throw new Error(`Error loading file: ${filePath} - ${err.message}`);
+ }
+ });
+
+ return mergeObject;
+}
+
+module.exports = function configFileLoader() {
+ return {
+ loadDataFromFile: loadFile,
+ loadDataFromFolder: loadDataFromFolder,
+ };
+};
diff --git a/packages/core/src/lib/decompose.js b/packages/core/src/lib/decompose.js
new file mode 100644
index 000000000..e541708b5
--- /dev/null
+++ b/packages/core/src/lib/decompose.js
@@ -0,0 +1,57 @@
+'use strict';
+
+const logger = require('./log');
+const lh = require('./lineage_hunter');
+const lih = require('./list_item_hunter');
+const addPattern = require('./addPattern');
+const expandPartials = require('./expandPartials');
+
+const lineage_hunter = new lh();
+const list_item_hunter = new lih();
+
+/**
+ * A helper that unravels a pattern looking for partials or listitems to unravel.
+ * The goal is really to convert pattern.template into pattern.extendedTemplate
+ * @param pattern - the pattern to decompose
+ * @param patternlab - global data store
+ * @param ignoreLineage - whether or not to hunt for lineage for this pattern
+ */
+module.exports = function(pattern, patternlab, ignoreLineage) {
+ //set the extendedTemplate to operate on later if we find partials to replace
+ if (!pattern.extendedTemplate) {
+ pattern.extendedTemplate = pattern.template;
+ }
+
+ //find any listItem blocks that within the pattern, even if there are no partials
+ const listItemPromise = list_item_hunter.process_list_item_partials(
+ pattern,
+ patternlab
+ );
+
+ const expandPartialPromise = expandPartials(pattern, patternlab);
+
+ let lineagePromise;
+
+ //find pattern lineage
+ if (!ignoreLineage) {
+ lineagePromise = Promise.resolve(
+ lineage_hunter.find_lineage(pattern, patternlab)
+ );
+ } else {
+ lineagePromise = Promise.resolve();
+ }
+
+ const addPromise = Promise.resolve(() => {
+ //add to patternlab object so we can look these up later.
+ addPattern(pattern, patternlab);
+ });
+
+ return Promise.all([
+ listItemPromise,
+ expandPartialPromise,
+ lineagePromise,
+ addPromise,
+ ]).catch(reason => {
+ logger.error(reason);
+ });
+};
diff --git a/packages/core/src/lib/events.js b/packages/core/src/lib/events.js
new file mode 100644
index 000000000..b1653b3c8
--- /dev/null
+++ b/packages/core/src/lib/events.js
@@ -0,0 +1,81 @@
+'use strict';
+
+/**
+ * All Pattern Lab Events
+ * @module Events
+ */
+
+/**
+ * @alias module:Events
+ */
+const EVENTS = Object.freeze({
+ /**
+ * @desc Emitted before any logic run inside `build()`, which is the entry point for single builds, pattern-only builds, run singly or when watched.
+ * @property {object} patternlab - global data store
+ *
+ */
+ PATTERNLAB_BUILD_START: 'patternlab-build-start',
+
+ /**
+ * @desc Emitted after all logic run inside `build()`, which is the entry point for single builds, pattern-only builds, run singly or when watched.
+ * @property {object} patternlab - global data store
+ *
+ */
+ PATTERNLAB_BUILD_END: 'patternlab-build-end',
+
+ /**
+ * @desc Emitted after patterns are iterated over to gather data about them. Right before Pattern Lab processes and renders patterns into HTML
+ * @property {object} patternlab - global data store
+ */
+ PATTERNLAB_PATTERN_ITERATION_END: 'patternlab-pattern-iteration-end',
+
+ /**
+ * @desc Emitted after global `data.json` and `listitems.json` are read, and the supporting Pattern Lab templates are loaded into memory (header, footer, patternSection, patternSectionSubType, viewall). Right before patterns are iterated over to gather data about them.
+ * @property {object} patternlab - global data store
+ */
+ PATTERNLAB_BUILD_GLOBAL_DATA_END: 'patternlab-build-global-data-end',
+
+ /**
+ * @desc Emitted before all data is merged prior to a Pattern's render. Global `data.json` is merged with any pattern `.json`. Global `listitems.json` is merged with any pattern `.listitems.json`.
+ * @property {object} patternlab - global data store
+ * @property {Pattern} pattern - current pattern
+ * @see {@link https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16|Pattern}
+ */
+ PATTERNLAB_PATTERN_BEFORE_DATA_MERGE: 'patternlab-pattern-before-data-merge',
+
+ /**
+ * @desc Emitted before a pattern's template, HTML, and encoded HTML files are written to their output location
+ * @property {object} patternlab - global data store
+ * @property {Pattern} pattern - current pattern
+ * @see {@link https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16|Pattern}
+ */
+ PATTERNLAB_PATTERN_WRITE_BEGIN: 'patternlab-pattern-write-begin',
+
+ /**
+ * @desc Emitted after a pattern's template, HTML, and encoded HTML files are written to their output location
+ * @property {object} patternlab - global data store
+ * @property {Pattern} pattern - current pattern
+ * @see {@link https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16|Pattern}
+ */
+ PATTERNLAB_PATTERN_WRITE_END: 'patternlab-pattern-write-end',
+
+ /**
+ * @desc Invoked when a watched asset changes. Assets include anything in `source/` that is not under `['root', 'patterns', 'data', 'meta', 'annotations', 'patternlabFiles']` which are blacklisted for specific copying.
+ * @property {object} fileInfo - `{file: 'path/to/file.css', dest: 'path/to/destination'}`
+ */
+ PATTERNLAB_PATTERN_ASSET_CHANGE: 'patternlab-pattern-asset-change',
+
+ /**
+ * @desc Invoked when a watched global file changes. These are files within the directories specified in `['data', 'meta']`paths.
+ * @property {object} fileInfo - `{file: 'path/to/file.ext'}`
+ */
+ PATTERNLAB_GLOBAL_CHANGE: 'patternlab-global-change',
+
+ /**
+ * @desc Invoked when a pattern changes.
+ * @property {object} fileInfo - `{file: 'path/to/file.ext'}`
+ */
+ PATTERNLAB_PATTERN_CHANGE: 'patternlab-pattern-change',
+});
+
+module.exports = EVENTS;
diff --git a/packages/core/src/lib/expandPartials.js b/packages/core/src/lib/expandPartials.js
new file mode 100644
index 000000000..0a9200052
--- /dev/null
+++ b/packages/core/src/lib/expandPartials.js
@@ -0,0 +1,90 @@
+'use strict';
+
+const logger = require('./log');
+const ph = require('./parameter_hunter');
+const smh = require('./style_modifier_hunter');
+const jsonCopy = require('./json_copy');
+const getPartial = require('./get');
+
+const parameter_hunter = new ph();
+const style_modifier_hunter = new smh();
+
+module.exports = function(currentPattern, patternlab) {
+ const processRecursive = require('./processRecursive');
+
+ //find how many partials there may be for the given pattern
+ const foundPatternPartials = currentPattern.findPartials();
+
+ // expand any partials present in this pattern; that is, drill down into
+ // the template and replace their calls in this template with rendered
+ // results
+ if (
+ currentPattern.engine.expandPartials &&
+ (foundPatternPartials !== null && foundPatternPartials.length > 0)
+ ) {
+ logger.debug(`found partials for ${currentPattern.patternPartial}`);
+
+ // determine if the template contains any pattern parameters. if so they
+ // must be immediately consumed
+ return parameter_hunter
+ .find_parameters(currentPattern, patternlab)
+ .then(() => {
+ //do something with the regular old partials
+ foundPatternPartials.forEach(foundPartial => {
+ const partial = currentPattern.findPartial(foundPartial);
+ const partialPattern = getPartial(partial, patternlab);
+
+ //recurse through nested partials to fill out this extended template.
+ return processRecursive(partialPattern.relPath, patternlab)
+ .then(() => {
+ //eslint-disable-line no-loop-func
+
+ //complete assembly of extended template
+ //create a copy of the partial so as to not pollute it after the getPartial call.
+ const cleanPartialPattern = jsonCopy(
+ partialPattern,
+ `partial pattern ${partial}`
+ );
+
+ //if partial has style modifier data, replace the styleModifier value
+ if (
+ currentPattern.stylePartials &&
+ currentPattern.stylePartials.length > 0
+ ) {
+ style_modifier_hunter.consume_style_modifier(
+ cleanPartialPattern,
+ foundPartial,
+ patternlab
+ );
+ }
+
+ //this is what we came here for
+ logger.debug(
+ `within ${currentPattern.patternPartial}, replacing extendedTemplate partial ${foundPartial} with ${cleanPartialPattern.patternPartial}'s extendedTemplate`
+ );
+
+ currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(
+ foundPartial,
+ cleanPartialPattern.extendedTemplate
+ );
+
+ // update the extendedTemplate in the partials object in case this
+ // pattern is consumed later
+ patternlab.partials[currentPattern.patternPartial] =
+ currentPattern.extendedTemplate;
+
+ return Promise.resolve();
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error(reason);
+ });
+ });
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error(reason);
+ });
+ }
+ return Promise.resolve();
+};
diff --git a/packages/core/src/lib/exportData.js b/packages/core/src/lib/exportData.js
new file mode 100644
index 000000000..9a8468ce5
--- /dev/null
+++ b/packages/core/src/lib/exportData.js
@@ -0,0 +1,112 @@
+'use strict';
+
+const eol = require('os').EOL;
+const path = require('path');
+const _ = require('lodash');
+
+const ae = require('./annotation_exporter');
+
+let fs = require('fs-extra'); //eslint-disable-line prefer-const
+
+/**
+ * Write out our pattern information for use by the front end
+ * @param patternlab - global data store
+ */
+module.exports = function(patternlab) {
+ const annotation_exporter = new ae(patternlab);
+
+ const paths = patternlab.config.paths;
+
+ //write out the data
+ let output = '';
+
+ //config
+ output += 'var config = ' + JSON.stringify(patternlab.config) + ';\n';
+
+ //ishControls
+ output +=
+ 'var ishControls = {"ishControlsHide":' +
+ JSON.stringify(patternlab.config.ishControlsHide) +
+ '};' +
+ eol;
+
+ //navItems
+ output +=
+ 'var navItems = {"patternTypes": ' +
+ JSON.stringify(patternlab.patternTypes) +
+ ', "ishControlsHide": ' +
+ JSON.stringify(patternlab.config.ishControlsHide) +
+ '};' +
+ eol;
+
+ //patternPaths
+ output +=
+ 'var patternPaths = ' + JSON.stringify(patternlab.patternPaths) + ';' + eol;
+
+ //viewAllPaths
+ output +=
+ 'var viewAllPaths = ' + JSON.stringify(patternlab.viewAllPaths) + ';' + eol;
+
+ //plugins
+ output +=
+ 'var plugins = ' + JSON.stringify(patternlab.plugins || []) + ';' + eol;
+
+ //smaller config elements
+ output +=
+ 'var defaultShowPatternInfo = ' +
+ (patternlab.config.defaultShowPatternInfo
+ ? patternlab.config.defaultShowPatternInfo
+ : 'false') +
+ ';' +
+ eol;
+ output +=
+ 'var defaultPattern = "' +
+ (patternlab.config.defaultPattern
+ ? patternlab.config.defaultPattern
+ : 'all') +
+ '";' +
+ eol;
+
+ //annotations
+ const annotationsJSON = annotation_exporter.gather();
+ const annotations =
+ 'var comments = { "comments" : ' + JSON.stringify(annotationsJSON) + '};';
+ _.each(patternlab.uikits, uikit => {
+ fs.outputFileSync(
+ path.resolve(
+ path.join(
+ process.cwd(),
+ uikit.outputDir,
+ paths.public.annotations,
+ 'annotations.js'
+ )
+ ),
+ annotations
+ );
+ });
+
+ // add module.export to the Nodejs-specific file generated.
+ const exportedOutput =
+ output +
+ 'module.exports = { config, ishControls, navItems, patternPaths, viewAllPaths, plugins, defaultShowPatternInfo, defaultPattern };';
+
+ //write all output to patternlab-data
+ _.each(patternlab.uikits, uikit => {
+ fs.outputFileSync(
+ path.resolve(
+ path.join(process.cwd(), uikit.outputDir, paths.public.data),
+ 'patternlab-data.js'
+ ),
+ output
+ );
+ fs.outputFileSync(
+ path.resolve(
+ path.join(process.cwd(), uikit.outputDir, paths.public.data),
+ 'patternlab-data.cjs.js'
+ ),
+ exportedOutput
+ );
+ });
+
+ return output;
+};
diff --git a/packages/core/src/lib/findModules.js b/packages/core/src/lib/findModules.js
new file mode 100644
index 000000000..246612ec8
--- /dev/null
+++ b/packages/core/src/lib/findModules.js
@@ -0,0 +1,60 @@
+'use strict';
+
+const path = require('path');
+
+const isScopedPackage = require('./isScopedPackage');
+
+let fs = require('fs-extra'); // eslint-disable-line
+
+const isDir = fPath => {
+ const stats = fs.lstatSync(fPath);
+ return stats.isDirectory() || stats.isSymbolicLink();
+};
+
+module.exports = (dir, filter) => {
+ /**
+ * @name findModules
+ * @desc Traverse the given path and gather possible engines
+ * @param {string} fPath - The file path to traverse
+ * @param {Array} foundModules - An array of modules
+ * @return {Array} - The final array of engines
+ */
+ const findModules = (fPath, foundModules) => {
+ /**
+ * @name dirList
+ * @desc A list of all directories in the given path
+ * @type {Array}
+ */
+ const dirList = fs
+ .readdirSync(fPath)
+ .filter(p => isDir(path.join(fPath, p)));
+
+ /**
+ * @name m
+ * @desc For the current dir get all modules
+ * @type {Array}
+ */
+ const m = foundModules.concat(
+ dirList.filter(filter).map(mod => {
+ return {
+ name: filter(mod),
+ modulePath: path.join(fPath, mod),
+ };
+ })
+ );
+
+ /**
+ * 1. Flatten all engines from inner recursions and current dir
+ * 2. Filter the dirList for scoped packages
+ * 3. Map over every scoped package and recurse into it to find scoped modules
+ */
+ return [].concat(
+ ...m,
+ ...dirList
+ .filter(isScopedPackage) // 2
+ .map(scope => findModules(path.join(fPath, scope), m)) // 3
+ );
+ };
+
+ return findModules(dir, []);
+};
diff --git a/packages/core/src/lib/get.js b/packages/core/src/lib/get.js
new file mode 100644
index 000000000..2feccc135
--- /dev/null
+++ b/packages/core/src/lib/get.js
@@ -0,0 +1,43 @@
+'use strict';
+
+const logger = require('./log');
+
+module.exports = function(partialName, patternlab, reportWarning = true) {
+ //look for exact partial matches
+ for (let i = 0; i < patternlab.patterns.length; i++) {
+ if (patternlab.patterns[i].patternPartial === partialName) {
+ return patternlab.patterns[i];
+ }
+ }
+
+ //else look by verbose syntax
+ for (let j = 0; j < patternlab.patterns.length; j++) {
+ switch (partialName) {
+ case patternlab.patterns[j].relPath:
+ return patternlab.patterns[j];
+ case patternlab.patterns[j].verbosePartial:
+ return patternlab.patterns[j];
+ }
+ }
+
+ //return the fuzzy match if all else fails
+ for (let k = 0; k < patternlab.patterns.length; k++) {
+ const partialParts = partialName.split('-');
+ const partialType = partialParts[0];
+ const partialNameEnd = partialParts.slice(1).join('-');
+
+ if (
+ patternlab.patterns[k].patternPartial.split('-')[0] === partialType &&
+ patternlab.patterns[k].patternPartial.indexOf(partialNameEnd) > -1
+ ) {
+ return patternlab.patterns[k];
+ }
+ }
+ if (reportWarning) {
+ logger.warning(
+ `Could not find pattern referenced with partial syntax ${partialName}.
+ This can occur when a pattern was renamed, moved, or no longer exists but it still referenced within a different template or within data as a link.`
+ );
+ }
+ return undefined;
+};
diff --git a/packages/core/src/lib/isScopedPackage.js b/packages/core/src/lib/isScopedPackage.js
new file mode 100644
index 000000000..cffd1d811
--- /dev/null
+++ b/packages/core/src/lib/isScopedPackage.js
@@ -0,0 +1,16 @@
+'use strict';
+
+const path = require('path');
+
+const scopeMatch = /^@(.*)$/;
+
+/**
+ * @name isScopedPackage
+ * @desc Checks whether a path in modules belongs to a scoped package
+ * @param {string} filePath - The pathname to check
+ * @return {Boolean} - Returns a bool when found, false othersie
+ */
+module.exports = filePath => {
+ const baseName = path.basename(filePath);
+ return scopeMatch.test(baseName);
+};
diff --git a/packages/core/src/lib/json_copy.js b/packages/core/src/lib/json_copy.js
new file mode 100644
index 000000000..a757452e3
--- /dev/null
+++ b/packages/core/src/lib/json_copy.js
@@ -0,0 +1,16 @@
+'use strict';
+
+const logger = require('./log');
+const json_copy = (data, callee) => {
+ try {
+ return JSON.parse(JSON.stringify(data));
+ } catch (e) {
+ //this is unlikely to be hit due to the passed in data already being loaded using JSON parsers
+ logger.warning(
+ `JSON provided by ${callee} is invalid and cannot be copied`
+ );
+ throw e;
+ }
+};
+
+module.exports = json_copy;
diff --git a/packages/core/src/lib/lineage_hunter.js b/packages/core/src/lib/lineage_hunter.js
new file mode 100644
index 000000000..657897d21
--- /dev/null
+++ b/packages/core/src/lib/lineage_hunter.js
@@ -0,0 +1,170 @@
+'use strict';
+const getPartial = require('./get');
+const logger = require('./log');
+
+const lineage_hunter = function() {
+ function findlineage(pattern, patternlab) {
+ // As we are adding edges from pattern to ancestor patterns, ensure it is known to the graph
+ patternlab.graph.add(pattern);
+
+ //find the {{> template-name }} within patterns
+ const matches = pattern.findPartials();
+ if (matches !== null) {
+ matches.forEach(function(match) {
+ //get the ancestorPattern
+ const ancestorPattern = getPartial(
+ pattern.findPartial(match),
+ patternlab
+ );
+
+ if (
+ ancestorPattern &&
+ pattern.lineageIndex.indexOf(ancestorPattern.patternPartial) === -1
+ ) {
+ //add it since it didnt exist
+ pattern.lineageIndex.push(ancestorPattern.patternPartial);
+
+ //create the more complex patternLineage object too
+ const l = {
+ lineagePattern: ancestorPattern.patternPartial,
+ lineagePath: '../../patterns/' + ancestorPattern.patternLink,
+ };
+ if (ancestorPattern.patternState) {
+ l.lineageState = ancestorPattern.patternState;
+ }
+
+ patternlab.graph.add(ancestorPattern);
+
+ // Confusing: pattern includes "ancestorPattern", not the other way round
+ patternlab.graph.link(pattern, ancestorPattern);
+
+ pattern.lineage.push(l);
+
+ //also, add the lineageR entry if it doesn't exist
+ if (
+ ancestorPattern.lineageRIndex.indexOf(pattern.patternPartial) === -1
+ ) {
+ ancestorPattern.lineageRIndex.push(pattern.patternPartial);
+
+ //create the more complex patternLineage object in reverse
+ const lr = {
+ lineagePattern: pattern.patternPartial,
+ lineagePath: '../../patterns/' + pattern.patternLink,
+ };
+ if (pattern.patternState) {
+ lr.lineageState = pattern.patternState;
+ }
+
+ ancestorPattern.lineageR.push(lr);
+ Object.assign(patternlab.graph.node(ancestorPattern), lr);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Apply the target pattern state either to any predecessors or successors of the given
+ * pattern in the pattern graph.
+ * @param direction Either 'fromPast' or 'fromFuture'
+ * @param pattern {Pattern}
+ * @param targetPattern {Pattern}
+ * @param graph {PatternGraph}
+ */
+ function setPatternState(direction, pattern, targetPattern, graph) {
+ let index = null;
+ if (direction === 'fromPast') {
+ index = graph.lineage(pattern);
+ } else {
+ index = graph.lineageR(pattern);
+ }
+
+ // if the request came from the past, apply target pattern state to current pattern lineage
+ for (let i = 0; i < index.length; i++) {
+ if (index[i].patternPartial === targetPattern.patternPartial) {
+ index[i].lineageState = targetPattern.patternState;
+ }
+ }
+ }
+
+ function cascadePatternStates(patternlab) {
+ for (let i = 0; i < patternlab.patterns.length; i++) {
+ const pattern = patternlab.patterns[i];
+
+ //for each pattern with a defined state
+ if (pattern.patternState) {
+ const lineage = patternlab.graph.lineage(pattern);
+
+ if (lineage && lineage.length > 0) {
+ //find all lineage - patterns being consumed by this one
+ for (let h = 0; h < lineage.length; h++) {
+ setPatternState(
+ 'fromFuture',
+ lineage[h],
+ pattern,
+ patternlab.graph
+ );
+ }
+ }
+ const lineageR = patternlab.graph.lineageR(pattern);
+ if (lineageR && lineageR.length > 0) {
+ //find all reverse lineage - that is, patterns consuming this one
+ for (let j = 0; j < lineageR.length; j++) {
+ const lineageRPattern = lineageR[j];
+
+ //only set patternState if pattern.patternState "is less than" the lineageRPattern.patternstate
+ //or if lineageRPattern.patternstate (the consuming pattern) does not have a state
+ //this makes patternlab apply the lowest common ancestor denominator
+ const patternStateCascade = patternlab.config.patternStateCascade;
+ const patternStateIndex = patternStateCascade.indexOf(
+ pattern.patternState
+ );
+ const patternReverseStateIndex = patternStateCascade.indexOf(
+ lineageRPattern.patternState
+ );
+ if (
+ lineageRPattern.patternState === '' ||
+ patternStateIndex < patternReverseStateIndex
+ ) {
+ const oldState =
+ lineageRPattern.patternState === ''
+ ? '<>'
+ : lineageRPattern.patternState;
+ logger.info(
+ `Found a lower common denominator pattern state: ${pattern.patternState} on ${pattern.patternPartial}. Setting reverse lineage pattern ${lineageRPattern.patternPartial} from ${oldState}`
+ );
+
+ lineageRPattern.patternState = pattern.patternState;
+
+ //take this opportunity to overwrite the lineageRPattern's lineage state too
+ setPatternState(
+ 'fromPast',
+ lineageRPattern,
+ pattern,
+ patternlab.graph
+ );
+ } else {
+ setPatternState(
+ 'fromPast',
+ pattern,
+ lineageRPattern,
+ patternlab.graph
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return {
+ find_lineage: function(pattern, patternlab) {
+ findlineage(pattern, patternlab);
+ },
+ cascade_pattern_states: function(patternlab) {
+ cascadePatternStates(patternlab);
+ },
+ };
+};
+
+module.exports = lineage_hunter;
diff --git a/packages/core/src/lib/list_item_hunter.js b/packages/core/src/lib/list_item_hunter.js
new file mode 100644
index 000000000..e6fd3818b
--- /dev/null
+++ b/packages/core/src/lib/list_item_hunter.js
@@ -0,0 +1,48 @@
+'use strict';
+
+const list_item_hunter = function() {
+ const logger = require('./log');
+
+ function processListItemPartials(pattern) {
+ //find any listitem blocks
+ const matches = pattern.findListItems();
+
+ if (matches !== null) {
+ return matches.reduce((previousMatchPromise, liMatchStart) => {
+ return previousMatchPromise.then(() => {
+ logger.debug(
+ `found listItem of size ${liMatchStart} inside ${pattern.patternPartial}`
+ );
+
+ //we found a listitem match
+ //replace it's beginning listitems.number with -number
+ const newStart = liMatchStart.replace('.', '-');
+ pattern.extendedTemplate = pattern.extendedTemplate.replace(
+ liMatchStart,
+ newStart
+ );
+
+ //replace it's ending listitems.number with -number
+ const liMatchEnd = liMatchStart.replace('#', '/');
+ const newEnd = liMatchEnd.replace('.', '-');
+ pattern.extendedTemplate = pattern.extendedTemplate.replace(
+ liMatchEnd,
+ newEnd
+ );
+
+ return Promise.resolve();
+ });
+ }, Promise.resolve());
+ } else {
+ return Promise.resolve();
+ }
+ }
+
+ return {
+ process_list_item_partials: function(pattern) {
+ return processListItemPartials(pattern);
+ },
+ };
+};
+
+module.exports = list_item_hunter;
diff --git a/packages/core/src/lib/loadPattern.js b/packages/core/src/lib/loadPattern.js
new file mode 100644
index 000000000..c0fb1d6a5
--- /dev/null
+++ b/packages/core/src/lib/loadPattern.js
@@ -0,0 +1,199 @@
+'use strict';
+
+const path = require('path');
+
+const Pattern = require('./object_factory').Pattern;
+const mp = require('./markdown_parser');
+const logger = require('./log');
+const patternEngines = require('./pattern_engines');
+const ch = require('./changes_hunter');
+const da = require('./data_loader');
+const addPattern = require('./addPattern');
+const buildListItems = require('./buildListItems');
+const readDocumentation = require('./readDocumentation');
+
+const markdown_parser = new mp();
+const changes_hunter = new ch();
+const dataLoader = new da();
+
+//this is mocked in unit tests
+let fs = require('fs-extra'); //eslint-disable-line prefer-const
+
+// loads a pattern from disk, creates a Pattern object from it and
+// all its associated files, and records it in patternlab.patterns[]
+module.exports = function(relPath, patternlab) {
+ const relativeDepth = (relPath.match(/\w(?=\\)|\w(?=\/)/g) || []).length;
+ if (relativeDepth > 3) {
+ logger.warning('');
+ logger.warning('Warning:');
+ logger.warning(
+ 'A pattern file: ' +
+ relPath +
+ ' was found greater than 3 levels deep from ' +
+ patternlab.config.paths.source.patterns +
+ '.'
+ );
+ logger.warning(
+ "It's strongly suggested to not deviate from the following structure under _patterns/"
+ );
+ logger.warning(
+ '[patternType]/[patternSubtype]/[patternName].[patternExtension]'
+ );
+ logger.warning('or');
+ logger.warning(
+ '[patternType]/[patternSubtype]/[patternName]/[patternName].[patternExtension]'
+ );
+ logger.warning('');
+ logger.warning(
+ 'While Pattern Lab may still function, assets may 404 and frontend links may break. Consider yourself warned. '
+ );
+ logger.warning(
+ 'Read More: http://patternlab.io/docs/pattern-organization.html'
+ );
+ logger.warning('');
+ }
+
+ //check if the found file is a top-level markdown file
+ const fileObject = path.parse(relPath);
+ if (fileObject.ext === '.md') {
+ try {
+ const proposedDirectory = path.resolve(
+ patternlab.config.paths.source.patterns,
+ fileObject.dir,
+ fileObject.name
+ );
+ const proposedDirectoryStats = fs.statSync(proposedDirectory);
+ if (proposedDirectoryStats.isDirectory()) {
+ const subTypeMarkdownFileContents = fs.readFileSync(
+ proposedDirectory + '.md',
+ 'utf8'
+ );
+ const subTypeMarkdown = markdown_parser.parse(
+ subTypeMarkdownFileContents
+ );
+ const subTypePattern = new Pattern(relPath, null, patternlab);
+ subTypePattern.patternSectionSubtype = true;
+ subTypePattern.patternDesc = subTypeMarkdown
+ ? subTypeMarkdown.markdown
+ : '';
+ subTypePattern.flatPatternPath =
+ subTypePattern.flatPatternPath + '-' + subTypePattern.fileName;
+ subTypePattern.isPattern = false;
+ subTypePattern.engine = null;
+ patternlab.subtypePatterns[
+ subTypePattern.patternPartial
+ ] = subTypePattern;
+
+ return subTypePattern;
+ }
+ } catch (err) {
+ // no file exists, meaning it's a pattern markdown file
+ if (err.code !== 'ENOENT') {
+ logger.warning(err);
+ }
+ }
+ }
+
+ //extract some information
+ const filename = fileObject.base;
+ const ext = fileObject.ext;
+ const patternsPath = patternlab.config.paths.source.patterns;
+
+ // skip non-pattern files
+ if (!patternEngines.isPatternFile(filename, patternlab)) {
+ return null;
+ }
+
+ //make a new Pattern Object
+ const currentPattern = new Pattern(relPath, null, patternlab);
+
+ //if file is named in the syntax for variants
+ if (patternEngines.isPseudoPatternJSON(filename)) {
+ return currentPattern;
+ }
+
+ //can ignore all non-supported files at this point
+ if (patternEngines.isFileExtensionSupported(ext) === false) {
+ return currentPattern;
+ }
+
+ //look for a json file for this template
+ let jsonFilename;
+ try {
+ jsonFilename = path.resolve(
+ patternsPath,
+ currentPattern.subdir,
+ currentPattern.fileName
+ );
+ const patternData = dataLoader.loadDataFromFile(jsonFilename, fs);
+
+ if (patternData) {
+ currentPattern.jsonFileData = patternData;
+ logger.debug(
+ `found pattern-specific data for ${currentPattern.patternPartial}`
+ );
+ }
+ } catch (err) {
+ logger.warning(
+ `There was an error parsing sibling JSON for ${currentPattern.relPath}`
+ );
+ logger.warning(err);
+ }
+
+ //look for a listitems.json file for this template
+ let listJsonFileName;
+ try {
+ listJsonFileName = path.resolve(
+ patternsPath,
+ currentPattern.subdir,
+ `${currentPattern.fileName}.listitems`
+ );
+ const listItemsData = dataLoader.loadDataFromFile(listJsonFileName, fs);
+
+ if (listItemsData) {
+ logger.debug(
+ `found pattern-specific listitems data for ${currentPattern.patternPartial}`
+ );
+ currentPattern.listitems = listItemsData;
+ buildListItems(currentPattern);
+ }
+ } catch (err) {
+ logger.warning(
+ `There was an error parsing sibling listitem JSON for ${currentPattern.relPath}`
+ );
+ logger.warning(err);
+ }
+
+ //look for a markdown file for this template
+ readDocumentation(currentPattern, patternlab);
+
+ //add the raw template to memory
+ const templatePath = path.resolve(patternsPath, currentPattern.relPath);
+
+ currentPattern.template = fs.readFileSync(templatePath, 'utf8');
+
+ //find any stylemodifiers that may be in the current pattern
+ currentPattern.stylePartials = currentPattern.findPartialsWithStyleModifiers();
+
+ //find any pattern parameters that may be in the current pattern
+ currentPattern.parameteredPartials = currentPattern.findPartialsWithPatternParameters();
+
+ [
+ templatePath,
+ `${jsonFilename}.json`,
+ `${jsonFilename}.yml`,
+ `${jsonFilename}.yaml`,
+ `${listJsonFileName}.json`,
+ `${listJsonFileName}.yml`,
+ `${listJsonFileName}.yaml`,
+ ].forEach(file => {
+ changes_hunter.checkLastModified(currentPattern, file);
+ });
+
+ changes_hunter.checkBuildState(currentPattern, patternlab);
+
+ //add currentPattern to patternlab.patterns array
+ addPattern(currentPattern, patternlab);
+
+ return currentPattern;
+};
diff --git a/packages/core/src/lib/loadPatternGraph.js b/packages/core/src/lib/loadPatternGraph.js
new file mode 100644
index 000000000..c1f4ab851
--- /dev/null
+++ b/packages/core/src/lib/loadPatternGraph.js
@@ -0,0 +1,21 @@
+'use strict';
+
+const PatternGraph = require('./pattern_graph').PatternGraph;
+
+/**
+ * If a graph was serialized and then {@code deletePatternDir == true}, there is a mismatch in the
+ * pattern metadata and not all patterns might be recompiled.
+ * For that reason an empty graph is returned in this case, so every pattern will be flagged as
+ * "needs recompile". Otherwise the pattern graph is loaded from the meta data.
+ *
+ * @param patternlab
+ * @param {boolean} deletePatternDir When {@code true}, an empty graph is returned
+ * @return {PatternGraph}
+ */
+module.exports = (patternlab, deletePatternDir) => {
+ // Sanity check to prevent problems when code is refactored
+ if (deletePatternDir) {
+ return PatternGraph.empty();
+ }
+ return PatternGraph.loadFromFile();
+};
diff --git a/packages/core/src/lib/loaduikits.js b/packages/core/src/lib/loaduikits.js
new file mode 100644
index 000000000..dc152ae3d
--- /dev/null
+++ b/packages/core/src/lib/loaduikits.js
@@ -0,0 +1,99 @@
+'use strict';
+
+const path = require('path');
+const _ = require('lodash');
+
+const logger = require('./log');
+
+let findModules = require('./findModules'); //eslint-disable-line prefer-const
+let fs = require('fs-extra'); // eslint-disable-line
+
+const uiKitMatcher = /^uikit-(.*)$/;
+const nodeModulesPath = path.join(process.cwd(), 'node_modules');
+
+/**
+ * Given a path: return the uikit name if the path points to a valid uikit
+ * module directory, or false if it doesn't.
+ * @param filePath
+ * @returns UIKit name if exists or FALSE
+ */
+const isUIKitModule = filePath => {
+ const baseName = path.basename(filePath);
+ const engineMatch = baseName.match(uiKitMatcher);
+
+ if (engineMatch) {
+ return engineMatch[1];
+ }
+ return false;
+};
+
+const readModuleFile = (kit, subPath) => {
+ return fs.readFileSync(
+ path.resolve(path.join(kit.modulePath, subPath)),
+ 'utf8'
+ );
+};
+
+/**
+ * Loads uikits, connecting configuration and installed modules
+ * [1] Looks in node_modules for uikits.
+ * [2] Filter out our uikit-polyfills package.
+ * [3] Only continue if uikit is enabled in patternlab-config.json
+ * [4] Reads files from uikit that apply to every template
+ * @param {object} patternlab
+ */
+module.exports = patternlab => {
+ const paths = patternlab.config.paths;
+
+ const uikits = findModules(nodeModulesPath, isUIKitModule) // [1]
+ .filter(kit => kit.name !== 'polyfills'); // [2]
+ uikits.forEach(kit => {
+ const configEntry = _.find(_.filter(patternlab.config.uikits, 'enabled'), {
+ name: `uikit-${kit.name}`,
+ }); // [3]
+
+ if (!configEntry) {
+ logger.warning(
+ `Could not find uikit with name uikit-${kit.name} defined within patternlab-config.json, or it is not enabled.`
+ );
+ return;
+ }
+
+ try {
+ patternlab.uikits[`uikit-${kit.name}`] = {
+ name: `uikit-${kit.name}`,
+ modulePath: kit.modulePath,
+ enabled: true,
+ outputDir: configEntry.outputDir,
+ excludedPatternStates: configEntry.excludedPatternStates,
+ excludedTags: configEntry.excludedTags,
+ header: readModuleFile(
+ kit,
+ paths.source.patternlabFiles['general-header']
+ ),
+ footer: readModuleFile(
+ kit,
+ paths.source.patternlabFiles['general-footer']
+ ),
+ patternSection: readModuleFile(
+ kit,
+ paths.source.patternlabFiles.patternSection
+ ),
+ patternSectionSubType: readModuleFile(
+ kit,
+ paths.source.patternlabFiles.patternSectionSubtype
+ ),
+ viewAll: readModuleFile(kit, paths.source.patternlabFiles.viewall),
+ }; // [4]
+ } catch (ex) {
+ logger.error(ex);
+ logger.error(
+ '\nERROR: missing an essential file from ' +
+ kit.modulePath +
+ paths.source.patternlabFiles +
+ ". Pattern Lab won't work without this file.\n"
+ );
+ }
+ });
+ return Promise.resolve();
+};
diff --git a/packages/core/src/lib/log.js b/packages/core/src/lib/log.js
new file mode 100644
index 000000000..87a7cd587
--- /dev/null
+++ b/packages/core/src/lib/log.js
@@ -0,0 +1,80 @@
+'use strict';
+
+const chalk = require('chalk');
+const EventEmitter = require('events').EventEmitter;
+
+/**
+ * @name log
+ * @desc tiny event-based logger
+ * @type {*}
+ */
+const log = Object.assign(
+ {
+ debug(msg) {
+ this.emit('debug', chalk.green(msg));
+ },
+ info(msg) {
+ this.emit('info', msg);
+ },
+ warning(msg) {
+ this.emit('warning', chalk.yellow(msg));
+ },
+ error(msg) {
+ this.emit('error', chalk.red(msg));
+ },
+ },
+ EventEmitter.prototype
+);
+
+/**
+ * @func debug
+ * @desc Coloured debug log
+ * @param {*} msg - The variadic messages to log out.
+ * @return {void}
+ */
+const debug = log.debug.bind(log);
+
+/**
+ * @func info
+ * @desc Coloured info log
+ * @param {*} msg - The variadic messages to log out.
+ * @return {void}
+ */
+const info = log.info.bind(log);
+
+/**
+ * @func warning
+ * @desc Coloured warning log
+ * @param {*} e - The variadic messages to log out.
+ * @return {void}
+ */
+const warning = log.warning.bind(log);
+
+/**
+ * @func error
+ * @desc Coloured error log
+ * @param {*} e - The variadic messages to log out.
+ * @return {void}
+ */
+const error = log.error.bind(log);
+
+/**
+ * Useful for reporting errors in .catch() on Promises
+ * @param {string} - a message to report
+ * @returns {function} - a callback to be passed to a Promise's .catch()
+ */
+const reportError = function(message) {
+ return function(err) {
+ console.log(message);
+ console.log(err);
+ };
+};
+
+module.exports = {
+ debug,
+ info,
+ warning,
+ error,
+ log,
+ reportError,
+};
diff --git a/packages/core/src/lib/markModifiedPatterns.js b/packages/core/src/lib/markModifiedPatterns.js
new file mode 100644
index 000000000..aa80b25bb
--- /dev/null
+++ b/packages/core/src/lib/markModifiedPatterns.js
@@ -0,0 +1,56 @@
+'use strict';
+
+const path = require('path');
+const _ = require('lodash');
+
+const CompileState = require('./object_factory').CompileState;
+const ch = require('./changes_hunter');
+const changes_hunter = new ch();
+
+//this is mocked in unit tests
+let fs = require('fs-extra'); //eslint-disable-line prefer-const
+
+/**
+ * Finds patterns that were modified and need to be rebuilt. For clean patterns load the already
+ * rendered markup.
+ *
+ * @param lastModified
+ * @param patternlab
+ */
+module.exports = function(lastModified, patternlab) {
+ /**
+ * If the given array exists, apply a function to each of its elements
+ * @param {Array} array
+ * @param {Function} func
+ */
+ const forEachExisting = (array, func) => {
+ if (array) {
+ array.forEach(func);
+ }
+ };
+ const modifiedOrNot = _.groupBy(patternlab.patterns, p =>
+ changes_hunter.needsRebuild(lastModified, p) ? 'modified' : 'notModified'
+ );
+
+ // For all unmodified patterns load their rendered template output
+ forEachExisting(modifiedOrNot.notModified, cleanPattern => {
+ _.each(patternlab.uikits, uikit => {
+ const xp = path.join(
+ process.cwd(),
+ uikit.outputDir,
+ patternlab.config.paths.public.patterns,
+ cleanPattern.getPatternLink(patternlab, 'markupOnly')
+ );
+
+ // Pattern with non-existing markupOnly files were already marked for rebuild and thus are not "CLEAN"
+ cleanPattern.patternPartialCode = fs.readFileSync(xp, 'utf8');
+ });
+ });
+
+ // For all patterns that were modified, schedule them for rebuild
+ forEachExisting(
+ modifiedOrNot.modified,
+ p => (p.compileState = CompileState.NEEDS_REBUILD)
+ );
+ return modifiedOrNot;
+};
diff --git a/core/lib/markdown_parser.js b/packages/core/src/lib/markdown_parser.js
similarity index 70%
rename from core/lib/markdown_parser.js
rename to packages/core/src/lib/markdown_parser.js
index 495e0d181..c940a5f67 100644
--- a/core/lib/markdown_parser.js
+++ b/packages/core/src/lib/markdown_parser.js
@@ -1,28 +1,27 @@
-"use strict";
-
-var md = require('markdown-it')();
-var yaml = require('js-yaml');
-
-var markdown_parser = function () {
+'use strict';
+const md = require('markdown-it')();
+const yaml = require('js-yaml');
+const logger = require('./log');
+const markdown_parser = function() {
/**
* Converts a markdown block with frontmatter (each is optional, technically) to a well-formed object.
* @param block - the ".md" file, which can contain frontmatter or not, or only frontmatter.
* @returns an object with any frontmatter keys, plus a .markdown key
- */
+ */
function parseMarkdownBlock(block) {
- var returnObject = {};
+ let returnObject = {};
try {
//for each block process the yaml frontmatter and markdown
- var frontmatterRE = /---\r?\n{1}([\s\S]*)---\r?\n{1}([\s\S]*)+/gm;
- var chunks = frontmatterRE.exec(block);
+ const frontmatterRE = /---\r?\n{1}([\s\S]*)---\r?\n{1}([\s\S]*)+/gm;
+ const chunks = frontmatterRE.exec(block);
if (chunks) {
//we got some frontmatter
if (chunks && chunks[1]) {
//parse the yaml if we got it
- var frontmatter = chunks[1];
+ const frontmatter = chunks[1];
returnObject = yaml.safeLoad(frontmatter);
}
@@ -37,8 +36,8 @@ var markdown_parser = function () {
returnObject.markdown = md.render(block);
}
} catch (ex) {
- console.log(ex);
- console.log('error parsing markdown block', block);
+ logger.warning(ex);
+ logger.warning(`error parsing markdown block ${block}`);
return undefined;
}
@@ -47,11 +46,10 @@ var markdown_parser = function () {
}
return {
- parse: function (block) {
+ parse: function(block) {
return parseMarkdownBlock(block);
- }
+ },
};
-
};
module.exports = markdown_parser;
diff --git a/packages/core/src/lib/object_factory.js b/packages/core/src/lib/object_factory.js
new file mode 100644
index 000000000..83524a9c7
--- /dev/null
+++ b/packages/core/src/lib/object_factory.js
@@ -0,0 +1,269 @@
+'use strict';
+const patternEngines = require('./pattern_engines');
+const path = require('path');
+
+// patternPrefixMatcher is intended to match the leading maybe-underscore,
+// zero or more digits, and maybe-dash at the beginning of a pattern file name we can hack them
+// off and get at the good part.
+const patternPrefixMatcher = /^_?(\d+-)?/;
+
+// Pattern properties
+/**
+ * Pattern constructor
+ * @constructor
+ */
+const Pattern = function(relPath, data, patternlab) {
+ /**
+ * We expect relPath to be the path of the pattern template, relative to the
+ * root of the pattern tree. Parse out the path parts and save the useful ones.
+ * @param {relPath} relative directory
+ * @param {data} The JSON used to render values in the pattern.
+ * @param {patternlab} rendered html files for the pattern
+ */
+ const pathObj = path.parse(path.normalize(relPath));
+ const info = {};
+ // 00-colors(.mustache) is subbed in 00-atoms-/00-global/00-colors
+ info.hasDir =
+ path.basename(pathObj.dir).replace(patternPrefixMatcher, '') ===
+ pathObj.name.replace(patternPrefixMatcher, '') ||
+ path.basename(pathObj.dir).replace(patternPrefixMatcher, '') ===
+ pathObj.name.split('~')[0].replace(patternPrefixMatcher, '');
+
+ info.dir = info.hasDir ? pathObj.dir.split(path.sep).pop() : '';
+ info.dirLevel = pathObj.dir.split(path.sep).length;
+
+ this.relPath = path.normalize(relPath); // '00-atoms/00-global/00-colors.mustache'
+ this.fileName = pathObj.name; // '00-colors'
+ this.subdir = pathObj.dir; // '00-atoms/00-global'
+ if ((this.subdir.match(/\w(?=\\)|\w(?=\/)/g) || []).length > 1) {
+ this.subdir = this.subdir.split(/\/|\\/, 2).join(path.sep); // '00-atoms/03-controls/00-button' -> '00-atoms/03-controls'
+ }
+ this.fileExtension = pathObj.ext; // '.mustache'
+
+ // this is the unique name, subDir + fileName (sans extension)
+ this.name = '';
+ if (info.hasDir && info.dirLevel > 2) {
+ let variant = '';
+
+ if (this.fileName.indexOf('~') !== -1) {
+ variant = '-' + this.fileName.split('~')[1];
+ }
+ this.name = this.subdir.replace(/[\/\\]/g, '-') + variant;
+ } else {
+ this.name =
+ this.subdir.replace(/[\/\\]/g, '-') +
+ '-' +
+ this.fileName.replace('~', '-'); // '00-atoms-00-global-00-colors'
+ }
+
+ // the JSON used to render values in the pattern
+ this.jsonFileData = data || {};
+
+ // strip leading "00-" from the file name and flip tildes to dashes
+ this.patternBaseName = this.fileName
+ .replace(patternPrefixMatcher, '')
+ .replace('~', '-'); // 'colors'
+
+ // Fancy name. No idea how this works. 'Colors'
+ this.patternName = this.patternBaseName
+ .split('-')
+ .reduce(function(val, working) {
+ return (
+ val.charAt(0).toUpperCase() +
+ val.slice(1) +
+ ' ' +
+ working.charAt(0).toUpperCase() +
+ working.slice(1)
+ );
+ }, '')
+ .trim(); //this is the display name for the ui. strip numeric + hyphen prefixes
+
+ //00-atoms if needed
+ this.patternType = this.getDirLevel(0);
+
+ // the top-level pattern group this pattern belongs to. 'atoms'
+ this.patternGroup = this.patternType.replace(patternPrefixMatcher, '');
+
+ //00-colors if needed
+ this.patternSubType = this.getDirLevel(1);
+
+ // the sub-group this pattern belongs to.
+ this.patternSubGroup = this.patternSubType.replace(patternPrefixMatcher, ''); // 'global'
+
+ // the joined pattern group and subgroup directory
+ this.flatPatternPath =
+ info.hasDir && info.dirLevel > 2
+ ? this.subdir
+ .replace(/[/\\]/g, '-')
+ .replace(new RegExp('-' + info.dir + '$'), '')
+ : this.subdir.replace(/[\/\\]/g, '-'); // '00-atoms-00-global'
+
+ // calculated path from the root of the public directory to the generated
+ // (rendered!) html file for this pattern, to be shown in the iframe
+ this.patternLink = this.patternSectionSubtype
+ ? `$${this.name}/index.html`
+ : patternlab
+ ? this.getPatternLink(patternlab, 'rendered')
+ : null;
+
+ // The canonical "key" by which this pattern is known. This is the callable
+ // name of the pattern. UPDATE: this.key is now known as this.patternPartial
+ this.patternPartial = this.patternGroup + '-' + this.patternBaseName;
+
+ // Let's calculate the verbose name ahead of time! We don't use path.sep here
+ // on purpose. This isn't a file name!
+ this.verbosePartial =
+ this.subdir.split(path.sep).join('/') + '/' + this.fileName;
+
+ this.isPattern = true;
+ this.isFlatPattern = this.patternGroup === this.patternSubGroup;
+ this.patternState = '';
+ this.template = '';
+ this.patternPartialCode = '';
+ this.lineage = [];
+ this.lineageIndex = [];
+ this.lineageR = [];
+ this.lineageRIndex = [];
+ this.isPseudoPattern = false;
+ this.order = Number.MAX_SAFE_INTEGER;
+ this.engine = patternEngines.getEngineForPattern(this);
+
+ /**
+ * Determines if this pattern needs to be recompiled.
+ *
+ * @ee {@link CompileState}*/
+ this.compileState = null;
+
+ /**
+ * Timestamp in milliseconds when the pattern template or auxilary file (e.g. json) were modified.
+ * If multiple files are affected, this is the timestamp of the most recent change.
+ *
+ * @see {@link pattern}
+ */
+ this.lastModified = null;
+};
+
+// Pattern methods
+
+Pattern.prototype = {
+ // render function - acts as a proxy for the PatternEngine's
+ render: function(data, partials) {
+ if (!this.extendedTemplate) {
+ this.extendedTemplate = this.template;
+ }
+
+ if (this.engine) {
+ const promise = this.engine.renderPattern(
+ this,
+ data || this.jsonFileData,
+ partials
+ );
+ return promise
+ .then(results => {
+ return results;
+ })
+ .catch(reason => {
+ return Promise.reject(reason);
+ });
+ }
+ return Promise.reject('where is the engine?');
+ },
+
+ registerPartial: function() {
+ if (this.engine && typeof this.engine.registerPartial === 'function') {
+ this.engine.registerPartial(this);
+ }
+ },
+
+ // calculated path from the root of the public directory to the generated html
+ // file for this pattern.
+ // Should look something like '00-atoms-00-global-00-colors/00-atoms-00-global-00-colors.html'
+ getPatternLink: function(patternlab, suffixType, customfileExtension) {
+ // if no suffixType is provided, we default to rendered
+ const suffixConfig = patternlab.config.outputFileSuffixes;
+ const suffix = suffixType
+ ? suffixConfig[suffixType]
+ : suffixConfig.rendered;
+
+ if (suffixType === 'rawTemplate') {
+ return this.name + path.sep + this.name + suffix + this.fileExtension;
+ }
+
+ if (suffixType === 'custom') {
+ return this.name + path.sep + this.name + customfileExtension;
+ }
+
+ return this.name + path.sep + this.name + suffix + '.html';
+ },
+
+ // the finders all delegate to the PatternEngine, which also encapsulates all
+ // appropriate regexes
+ findPartials: function() {
+ return this.engine.findPartials(this);
+ },
+
+ findPartialsWithStyleModifiers: function() {
+ return this.engine.findPartialsWithStyleModifiers(this);
+ },
+
+ findPartialsWithPatternParameters: function() {
+ return this.engine.findPartialsWithPatternParameters(this);
+ },
+
+ findListItems: function() {
+ return this.engine.findListItems(this);
+ },
+
+ findPartial: function(partialString) {
+ return this.engine.findPartial(partialString);
+ },
+
+ getDirLevel: function(level) {
+ const items = this.subdir.split(path.sep);
+
+ if (items[level]) {
+ return items[level];
+ } else if (items[level - 1]) {
+ return items[level - 1];
+ } else {
+ return '';
+ }
+ },
+};
+
+// Pattern static methods
+
+// factory: creates an empty Pattern for miscellaneous internal use, such as
+// by list_item_hunter
+Pattern.createEmpty = function(customProps, patternlab) {
+ let relPath = '';
+ if (customProps) {
+ if (customProps.relPath) {
+ relPath = customProps.relPath;
+ } else if (customProps.subdir && customProps.filename) {
+ relPath = customProps.subdir + path.sep + customProps.filename;
+ }
+ }
+
+ const pattern = new Pattern(relPath, null, patternlab);
+ return Object.assign(pattern, customProps);
+};
+
+// factory: creates an Pattern object on-demand from a hash; the hash accepts
+// parameters that replace the positional parameters that the Pattern
+// constructor takes.
+Pattern.create = function(relPath, data, customProps, patternlab) {
+ const newPattern = new Pattern(relPath || '', data || null, patternlab);
+ return Object.assign(newPattern, customProps);
+};
+
+const CompileState = {
+ NEEDS_REBUILD: 'needs rebuild',
+ BUILDING: 'building',
+ CLEAN: 'clean',
+};
+
+module.exports = {
+ Pattern: Pattern,
+ CompileState: CompileState,
+};
diff --git a/core/lib/parameter_hunter.js b/packages/core/src/lib/parameter_hunter.js
similarity index 58%
rename from core/lib/parameter_hunter.js
rename to packages/core/src/lib/parameter_hunter.js
index 6f2d50f1e..eeec121dc 100644
--- a/core/lib/parameter_hunter.js
+++ b/packages/core/src/lib/parameter_hunter.js
@@ -1,15 +1,15 @@
-"use strict";
+'use strict';
-var parameter_hunter = function () {
+const smh = require('./style_modifier_hunter');
+const style_modifier_hunter = new smh();
- var extend = require('util')._extend,
- JSON5 = require('json5'),
- pa = require('./pattern_assembler'),
- smh = require('./style_modifier_hunter'),
- plutils = require('./utilities'),
- style_modifier_hunter = new smh(),
- pattern_assembler = new pa();
+const getPartial = require('./get');
+const logger = require('./log');
+const parseLink = require('./parseLink');
+const jsonCopy = require('./json_copy');
+const replaceParameter = require('./replaceParameter');
+const parameter_hunter = function() {
/**
* This function is really to accommodate the lax JSON-like syntax allowed by
* Pattern Lab PHP for parameter submissions to partials. Unfortunately, no
@@ -19,7 +19,7 @@ var parameter_hunter = function () {
* The steps on a high-level are as follows:
* * Further escape all escaped quotes and colons. Use the string
* representation of their unicodes for this. This has the added bonus
- * of being interpreted correctly by JSON5.parse() without further
+ * of being interpreted correctly by JSON.parse() without further
* modification. This will be useful later in the function.
* * Once escaped quotes are out of the way, we know the remaining quotes
* are either key/value wrappers or wrapped within those wrappers. We know
@@ -53,14 +53,24 @@ var parameter_hunter = function () {
* @returns {string} paramStringWellFormed
*/
function paramToJson(pString) {
- var colonPos = -1;
- var keys = [];
- var paramString = pString; // to not reassign param
- var paramStringWellFormed;
- var quotePos = -1;
- var regex;
- var values = [];
- var wrapper;
+ let colonPos = -1;
+ const keys = [];
+ let paramString = pString; // to not reassign param
+ let paramStringWellFormed;
+ let quotePos = -1;
+ let regex;
+ const values = [];
+ let wrapper;
+
+ // attempt to parse the data in case it is already well formed JSON
+ try {
+ paramStringWellFormed = JSON.stringify(JSON.parse(pString));
+ return paramStringWellFormed;
+ } catch (err) {
+ logger.debug(
+ `Not valid JSON found for passed pattern parameter ${pString} will attempt to parse manually...`
+ );
+ }
//replace all escaped double-quotes with escaped unicode
paramString = paramString.replace(/\\"/g, '\\u0022');
@@ -74,7 +84,6 @@ var parameter_hunter = function () {
//with escaped chars out of the way, crawl through paramString looking for
//keys and values
do {
-
//check if searching for a key
if (paramString[0] === '{' || paramString[0] === ',') {
paramString = paramString.substring(1, paramString.length).trim();
@@ -82,10 +91,9 @@ var parameter_hunter = function () {
//search for end quote if wrapped in quotes. else search for colon.
//everything up to that position will be saved in the keys array.
switch (paramString[0]) {
-
//need to search for end quote pos in case the quotes wrap a colon
case '"':
- case '\'':
+ case "'":
wrapper = paramString[0];
quotePos = paramString.indexOf(wrapper, 1);
break;
@@ -98,11 +106,12 @@ var parameter_hunter = function () {
keys.push(paramString.substring(0, quotePos + 1).trim());
//truncate the beginning from paramString and look for a value
- paramString = paramString.substring(quotePos + 1, paramString.length).trim();
+ paramString = paramString
+ .substring(quotePos + 1, paramString.length)
+ .trim();
//unset quotePos
quotePos = -1;
-
} else if (colonPos > -1) {
keys.push(paramString.substring(0, colonPos).trim());
@@ -112,8 +121,8 @@ var parameter_hunter = function () {
//unset colonPos
colonPos = -1;
- //if there are no more colons, and we're looking for a key, there is
- //probably a problem. stop any further processing.
+ //if there are no more colons, and we're looking for a key, there is
+ //probably a problem. stop any further processing.
} else {
paramString = '';
break;
@@ -131,14 +140,13 @@ var parameter_hunter = function () {
//readable, we'll use a regex for match() and replace() instead of
//performing conditional logic with indexOf().
switch (paramString[0]) {
-
//since a quote of same type as its wrappers would be escaped, and we
//escaped those even further with their unicodes, it is safe to look
//for wrapper pairs and conclude that their contents are values
case '"':
regex = /^"(.|\s)*?"/;
break;
- case '\'':
+ case "'":
regex = /^'(.|\s)*?'/;
break;
@@ -159,8 +167,8 @@ var parameter_hunter = function () {
break;
}
- //if there are no more colons, and we're looking for a value, there is
- //probably a problem. stop any further processing.
+ //if there are no more colons, and we're looking for a value, there is
+ //probably a problem. stop any further processing.
} else {
paramString = '';
break;
@@ -169,20 +177,20 @@ var parameter_hunter = function () {
//build paramStringWellFormed string for JSON parsing
paramStringWellFormed = '{';
- for (var i = 0; i < keys.length; i++) {
-
+ for (let i = 0; i < keys.length; i++) {
//keys
//replace single-quote wrappers with double-quotes
- if (keys[i][0] === '\'' && keys[i][keys[i].length - 1] === '\'') {
+ if (keys[i][0] === "'" && keys[i][keys[i].length - 1] === "'") {
paramStringWellFormed += '"';
//any enclosed double-quotes must be escaped
- paramStringWellFormed += keys[i].substring(1, keys[i].length - 1).replace(/"/g, '\\"');
+ paramStringWellFormed += keys[i]
+ .substring(1, keys[i].length - 1)
+ .replace(/"/g, '\\"');
paramStringWellFormed += '"';
} else {
-
//open wrap with double-quotes if no wrapper
- if (keys[i][0] !== '"' && keys[i][0] !== '\'') {
+ if (keys[i][0] !== '"' && keys[i][0] !== "'") {
paramStringWellFormed += '"';
//this is to clean up vestiges from Pattern Lab PHP's escaping scheme.
@@ -196,7 +204,10 @@ var parameter_hunter = function () {
paramStringWellFormed += keys[i];
//close wrap with double-quotes if no wrapper
- if (keys[i][keys[i].length - 1] !== '"' && keys[i][keys[i].length - 1] !== '\'') {
+ if (
+ keys[i][keys[i].length - 1] !== '"' &&
+ keys[i][keys[i].length - 1] !== "'"
+ ) {
paramStringWellFormed += '"';
}
}
@@ -206,14 +217,16 @@ var parameter_hunter = function () {
//values
//replace single-quote wrappers with double-quotes
- if (values[i][0] === '\'' && values[i][values[i].length - 1] === '\'') {
+ if (values[i][0] === "'" && values[i][values[i].length - 1] === "'") {
paramStringWellFormed += '"';
//any enclosed double-quotes must be escaped
- paramStringWellFormed += values[i].substring(1, values[i].length - 1).replace(/"/g, '\\"');
+ paramStringWellFormed += values[i]
+ .substring(1, values[i].length - 1)
+ .replace(/"/g, '\\"');
paramStringWellFormed += '"';
- //for everything else, just add the value however it's wrapped
+ //for everything else, just add the value however it's wrapped
} else {
paramStringWellFormed += values[i];
}
@@ -226,76 +239,113 @@ var parameter_hunter = function () {
paramStringWellFormed += '}';
//unescape escaped unicode except for double-quotes
- paramStringWellFormed = paramStringWellFormed.replace(/\\u0027/g, '\'');
+ paramStringWellFormed = paramStringWellFormed.replace(/\\u0027/g, "'");
paramStringWellFormed = paramStringWellFormed.replace(/\\u0058/g, ':');
return paramStringWellFormed;
}
+ //compile this partial immeadiately, essentially consuming it.
function findparameters(pattern, patternlab) {
-
if (pattern.parameteredPartials && pattern.parameteredPartials.length > 0) {
-
- //compile this partial immeadiately, essentially consuming it.
- pattern.parameteredPartials.forEach(function (pMatch) {
- //find the partial's name and retrieve it
- var partialName = pMatch.match(/([\w\-\.\/~]+)/g)[0];
- var partialPattern = pattern_assembler.getPartial(partialName, patternlab);
-
- //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #190
- partialPattern.extendedTemplate = partialPattern.template;
-
- if (patternlab.config.debug) {
- console.log('found patternParameters for ' + partialName);
- }
-
- //strip out the additional data, convert string to JSON.
- var leftParen = pMatch.indexOf('(');
- var rightParen = pMatch.lastIndexOf(')');
- var paramString = '{' + pMatch.substring(leftParen + 1, rightParen) + '}';
- var paramStringWellFormed = paramToJson(paramString);
-
- var paramData = {};
- var globalData = {};
- var localData = {};
-
- try {
- paramData = JSON5.parse(paramStringWellFormed);
- globalData = JSON5.parse(JSON5.stringify(patternlab.data));
- localData = JSON5.parse(JSON5.stringify(pattern.jsonFileData || {}));
- } catch (err) {
- console.log('There was an error parsing JSON for ' + pattern.relPath);
- console.log(err);
- }
-
- var allData = plutils.mergeData(globalData, localData);
- allData = plutils.mergeData(allData, paramData);
-
- //if partial has style modifier data, replace the styleModifier value
- if (pattern.stylePartials && pattern.stylePartials.length > 0) {
- style_modifier_hunter.consume_style_modifier(partialPattern, pMatch, patternlab);
- }
-
- //extend pattern data links into link for pattern link shortcuts to work. we do this locally and globally
- allData.link = extend({}, patternlab.data.link);
-
- var renderedPartial = pattern_assembler.renderPattern(partialPattern.extendedTemplate, allData, patternlab.partials);
-
- //remove the parameter from the partial and replace it with the rendered partial + paramData
- pattern.extendedTemplate = pattern.extendedTemplate.replace(pMatch, renderedPartial);
-
- //update the extendedTemplate in the partials object in case this pattern is consumed later
- patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate;
- });
+ logger.debug(`processing patternParameters for ${pattern.partialName}`);
+
+ return pattern.parameteredPartials.reduce((previousPromise, pMatch) => {
+ return previousPromise
+ .then(() => {
+ logger.debug(`processing patternParameter ${pMatch}`);
+
+ //find the partial's name and retrieve it
+ const partialName = pMatch.match(/([\w\-\.\/~]+)/g)[0];
+ const partialPattern = jsonCopy(
+ getPartial(
+ partialName,
+ patternlab,
+ `partial pattern ${partialName}`
+ )
+ );
+
+ //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #190
+ if (!partialPattern.extendedTemplate) {
+ partialPattern.extendedTemplate = partialPattern.template;
+ }
+
+ if (!pattern.extendedTemplate) {
+ pattern.extendedTemplate = pattern.template;
+ }
+
+ logger.debug(`retrieved pattern ${partialName}`);
+
+ //strip out the additional data, convert string to JSON.
+ const leftParen = pMatch.indexOf('(');
+ const rightParen = pMatch.lastIndexOf(')');
+ const paramString =
+ '{' + pMatch.substring(leftParen + 1, rightParen) + '}';
+ const paramStringWellFormed = paramToJson(paramString);
+
+ let paramData = {};
+
+ try {
+ paramData = JSON.parse(paramStringWellFormed);
+ } catch (err) {
+ logger.warning(
+ `There was an error parsing JSON for ${pattern.relPath}`
+ );
+ logger.warning(err);
+ }
+
+ // resolve any pattern links that might be present
+ paramData = parseLink(
+ patternlab,
+ paramData,
+ pattern.patternPartial
+ );
+
+ // for each property in paramData
+ for (const prop in paramData) {
+ if (paramData.hasOwnProperty(prop)) {
+ // find it within partialPattern.extendedTemplate and replace its value
+ partialPattern.extendedTemplate = replaceParameter(
+ partialPattern.extendedTemplate,
+ prop,
+ paramData[prop]
+ );
+ }
+ }
+
+ //if partial has style modifier data, replace the styleModifier value
+ if (pattern.stylePartials && pattern.stylePartials.length > 0) {
+ style_modifier_hunter.consume_style_modifier(
+ partialPattern,
+ pMatch,
+ patternlab
+ );
+ }
+
+ // set pattern.extendedTemplate pMatch with replacedPartial
+ pattern.extendedTemplate = pattern.extendedTemplate.replace(
+ pMatch,
+ partialPattern.extendedTemplate
+ );
+
+ //todo: this no longer needs to be a promise
+ return Promise.resolve();
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error(reason);
+ });
+ }, Promise.resolve());
}
+ logger.debug(`pattern has no partials ${pattern.patternPartial}`);
+ return Promise.resolve();
}
return {
- find_parameters: function (pattern, patternlab) {
- findparameters(pattern, patternlab);
- }
+ find_parameters: function(pattern, patternlab) {
+ return findparameters(pattern, patternlab);
+ },
};
-
};
module.exports = parameter_hunter;
diff --git a/packages/core/src/lib/parseAllLinks.js b/packages/core/src/lib/parseAllLinks.js
new file mode 100644
index 000000000..894ad6f83
--- /dev/null
+++ b/packages/core/src/lib/parseAllLinks.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const parseLink = require('./parseLink');
+
+//look for pattern links included in data files.
+//these will be in the form of link.* WITHOUT {{}}, which would still be there from direct pattern inclusion
+module.exports = function(patternlab) {
+ //look for link.* such as link.pages-blog as a value
+ patternlab.data = parseLink(patternlab, patternlab.data, 'data.json');
+
+ //loop through all patterns
+ for (let i = 0; i < patternlab.patterns.length; i++) {
+ patternlab.patterns[i].jsonFileData = parseLink(
+ patternlab,
+ patternlab.patterns[i].jsonFileData,
+ patternlab.patterns[i].patternPartial
+ );
+ }
+};
diff --git a/packages/core/src/lib/parseLink.js b/packages/core/src/lib/parseLink.js
new file mode 100644
index 000000000..d3d0c455e
--- /dev/null
+++ b/packages/core/src/lib/parseLink.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const path = require('path');
+
+const logger = require('./log');
+const getPartial = require('./get');
+
+module.exports = function(patternlab, obj, key) {
+ //check for 'link.patternPartial'
+ const linkRE = /(?:'|")(link\.[A-z0-9-_]+)(?:'|")/g;
+
+ //stringify the passed in object
+ let dataObjAsString;
+ dataObjAsString = JSON.stringify(obj);
+ if (!dataObjAsString) {
+ return obj;
+ }
+
+ //find matches
+ const linkMatches = dataObjAsString.match(linkRE);
+
+ if (linkMatches) {
+ for (let i = 0; i < linkMatches.length; i++) {
+ const dataLink = linkMatches[i];
+ if (dataLink && dataLink.split('.').length >= 2) {
+ //get the partial the link refers to
+ const linkPatternPartial = dataLink
+ .split('.')[1]
+ .replace('"', '')
+ .replace("'", '');
+ const pattern = getPartial(linkPatternPartial, patternlab);
+ if (pattern !== undefined) {
+ //get the full built link and replace it
+ let fullLink = patternlab.data.link[linkPatternPartial];
+ if (fullLink) {
+ fullLink = path.normalize(fullLink).replace(/\\/g, '/');
+
+ logger.debug(
+ `expanded data link from ${dataLink} to ${fullLink} inside ${key}`
+ );
+
+ //also make sure our global replace didn't mess up a protocol
+ fullLink = fullLink.replace(/:\//g, '://');
+ dataObjAsString = dataObjAsString.replace(
+ 'link.' + linkPatternPartial,
+ fullLink
+ );
+ }
+ } else {
+ logger.warning(`pattern not found for ${dataLink} inside ${key}`);
+ }
+ }
+ }
+ }
+
+ let dataObj;
+ try {
+ dataObj = JSON.parse(dataObjAsString);
+ } catch (err) {
+ logger.warning(`There was an error parsing JSON for ${key}`);
+ logger.warning(err);
+ }
+
+ return dataObj;
+};
diff --git a/packages/core/src/lib/pattern_engines.js b/packages/core/src/lib/pattern_engines.js
new file mode 100644
index 000000000..cde010125
--- /dev/null
+++ b/packages/core/src/lib/pattern_engines.js
@@ -0,0 +1,251 @@
+// special shoutout to Geoffrey Pursell for single-handedly making Pattern Lab Node Pattern Engines possible! aww thanks :)
+'use strict';
+const { existsSync } = require('fs');
+const path = require('path');
+
+const findModules = require('./findModules');
+
+const engineMatcher = /^engine-(.*)$/;
+
+const logger = require('./log');
+
+const enginesDirectories = [
+ {
+ displayName: 'the core',
+ path: path.resolve(__dirname, '..', '..', 'node_modules'),
+ },
+ {
+ displayName: 'the edition or test directory',
+ path: path.join(process.cwd(), 'node_modules'),
+ },
+];
+
+/**
+ * Given a path: return the engine name if the path points to a valid engine
+ * module directory, or false if it doesn't.
+ * @param filePath
+ * @returns Engine name if exists or FALSE
+ */
+function isEngineModule(filePath) {
+ const baseName = path.basename(filePath);
+ const engineMatch = baseName.match(engineMatcher);
+
+ if (engineMatch) {
+ return engineMatch[1];
+ }
+ return false;
+}
+
+/**
+ * @name resolveEngines
+ * @desc Creates an array of all available patternlab engines
+ * @param {string} dir - The directory to search for engines and scoped engines)
+ * @return {Array} An array of engine objects
+ */
+function resolveEngines(dir) {
+ // Guard against non-existent directories.
+ if (!existsSync(dir)) {
+ return []; // Silence is golden …
+ }
+
+ return findModules(dir, isEngineModule);
+}
+
+function findEngineModulesInDirectory(dir) {
+ const foundEngines = resolveEngines(dir);
+ return foundEngines;
+}
+
+//
+// PatternEngines: the main export of this module
+//
+// It's an Object/hash of all loaded pattern engines, empty at first. My
+// intention here is to make this return an object that can be used to obtain
+// any loaded PatternEngine by addressing them like this:
+//
+// var PatternEngines = require('./pattern_engines/pattern_engines');
+// var Mustache = PatternEngines['mustache'];
+//
+// Object.create lets us create an object with a specified prototype. We want
+// this here because we would like the object's "own properties" to include
+// only the engine names so we can easily iterate over them; all the handy
+// methods and properites below should therefore be on its prototype.
+
+const PatternEngines = Object.create({
+ /**
+ * Load all pattern engines.
+ * @param patternLabConfig
+ * @memberof PatternEngines
+ */
+ loadAllEngines: function(patternLabConfig) {
+ const self = this;
+
+ // Try to load engines! We scan for engines at each path specified above. This
+ // function is kind of a big deal.
+ enginesDirectories.forEach(function(engineDirectory) {
+ const enginesInThisDir = findEngineModulesInDirectory(
+ engineDirectory.path
+ );
+
+ logger.debug(`Loading engines from ${engineDirectory.displayName}...`);
+
+ // find all engine-named things in this directory and try to load them,
+ // unless it's already been loaded.
+ enginesInThisDir.forEach(function(engineDiscovery) {
+ let errorMessage;
+ const successMessage = 'good to go';
+
+ try {
+ // Give it a try! load 'er up. But not if we already have,
+ // of course. Also pass the pattern lab config object into
+ // the engine's closure scope so it can know things about
+ // things.
+ if (self[engineDiscovery.name]) {
+ throw new Error('already loaded, skipping.');
+ }
+ self[engineDiscovery.name] = require(engineDiscovery.modulePath);
+ if (
+ typeof self[engineDiscovery.name].usePatternLabConfig === 'function'
+ ) {
+ self[engineDiscovery.name].usePatternLabConfig(patternLabConfig);
+ }
+ if (typeof self[engineDiscovery.name].spawnMeta === 'function') {
+ self[engineDiscovery.name].spawnMeta(patternLabConfig);
+ }
+ } catch (err) {
+ errorMessage = err.message;
+ } finally {
+ // report on the status of the engine, one way or another!
+ logger.info(
+ `Pattern Engine ${engineDiscovery.name}: ${
+ errorMessage ? errorMessage : successMessage
+ }`
+ );
+ }
+ });
+ });
+
+ // Complain if for some reason we haven't loaded any engines.
+ if (Object.keys(self).length === 0) {
+ logger.error('No engines loaded! Something is seriously wrong.');
+ }
+ logger.debug(`Done loading engines`);
+ },
+
+ /**
+ * Get engine name for pattern.
+ * @memberof PatternEngines
+ * @param pattern
+ * @returns engine name matching pattern
+ */
+ getEngineNameForPattern: function(pattern) {
+ // avoid circular dependency by putting this in here. TODO: is this slow?
+ const of = require('./object_factory');
+ if (
+ pattern instanceof of.Pattern &&
+ typeof pattern.fileExtension === 'string' &&
+ pattern.fileExtension
+ ) {
+ //loop through known engines and find the one that supports the pattern's fileExtension
+ const engineNames = Object.keys(this);
+ for (let i = 0; i < engineNames.length; i++) {
+ const engine = this[engineNames[i]];
+
+ if (Array.isArray(engine.engineFileExtension)) {
+ if (engine.engineFileExtension.includes(pattern.fileExtension)) {
+ return engine.engineName;
+ }
+ } else {
+ //this likely means the users engines are out of date. todo: tell them to upgrade
+ if (engine.engineFileExtension === pattern.fileExtension) {
+ return engine.engineName;
+ }
+ }
+ }
+ }
+
+ // otherwise, assume it's a plain mustache template string and act
+ // accordingly
+ return 'mustache';
+ },
+
+ /**
+ * Get engine for pattern.
+ * @memberof PatternEngines
+ * @param pattern
+ * @returns name of engine for pattern
+ */
+ getEngineForPattern: function(pattern) {
+ if (pattern.isPseudoPattern) {
+ return this.getEngineForPattern(pattern.basePattern);
+ } else {
+ const engineName = this.getEngineNameForPattern(pattern);
+ return this[engineName];
+ }
+ },
+
+ /**
+ * Combine all found engines into a single array of supported extensions.
+ * @memberof PatternEngines
+ * @returns Array all supported file extensions
+ */
+ getSupportedFileExtensions: function() {
+ const engineNames = Object.keys(PatternEngines);
+ const allEnginesExtensions = engineNames.map(engineName => {
+ return PatternEngines[engineName].engineFileExtension;
+ });
+ return [].concat.apply([], allEnginesExtensions);
+ },
+
+ /**
+ * Check if fileExtension is supported.
+ * @memberof PatternEngines
+ * @param fileExtension
+ * @returns Boolean
+ */
+ isFileExtensionSupported: function(fileExtension) {
+ const supportedExtensions = PatternEngines.getSupportedFileExtensions();
+ return supportedExtensions.lastIndexOf(fileExtension) !== -1;
+ },
+
+ /**
+ * Given a filename, return a boolean: whether or not the filename indicates
+ * that the file is pseudopattern JSON
+ * @param filename
+ * @return boolean
+ */
+ isPseudoPatternJSON: function(filename) {
+ const extension = path.extname(filename);
+ return extension === '.json' && filename.indexOf('~') > -1;
+ },
+
+ /**
+ * Takes a filename string, not a full path; a basename (plus extension)
+ * ignore _underscored patterns, dotfiles, and anything not recognized by a
+ * loaded pattern engine. Pseudo-pattern .json files ARE considered to be
+ * pattern files!
+ *
+ * @memberof PatternEngines
+ * @param filename
+ * @returns boolean
+ */
+ isPatternFile: function(filename) {
+ // skip hidden patterns/files without a second thought
+ const extension = path.extname(filename);
+ if (
+ filename.charAt(0) === '.' ||
+ (extension === '.json' && !PatternEngines.isPseudoPatternJSON(filename))
+ ) {
+ return false;
+ }
+
+ // not a hidden pattern, let's dig deeper
+ const supportedPatternFileExtensions = PatternEngines.getSupportedFileExtensions();
+ return (
+ supportedPatternFileExtensions.lastIndexOf(extension) !== -1 ||
+ PatternEngines.isPseudoPatternJSON(filename)
+ );
+ },
+});
+
+module.exports = PatternEngines;
diff --git a/packages/core/src/lib/pattern_exporter.js b/packages/core/src/lib/pattern_exporter.js
new file mode 100644
index 000000000..8eb4e1a88
--- /dev/null
+++ b/packages/core/src/lib/pattern_exporter.js
@@ -0,0 +1,75 @@
+'use strict';
+
+const fs = require('fs-extra');
+const path = require('path');
+
+function exportSinglePattern(patternlab, pattern) {
+ const preserveDirStructure =
+ patternlab.config.patternExportPreserveDirectoryStructure;
+ let patternName = pattern.patternPartial;
+ let patternDir = patternlab.config.patternExportDirectory;
+ let patternCode = pattern.patternPartialCode;
+ let patternFileExtension = '.html';
+ if (preserveDirStructure) {
+ // Extract the first part of the pattern partial as the directory in which
+ // it should go.
+ patternDir = path.join(patternDir, pattern.patternPartial.split('-')[0]);
+ patternName = pattern.patternPartial
+ .split('-')
+ .slice(1)
+ .join('-');
+ }
+
+ if (patternlab.config.patternExportRaw) {
+ patternCode = pattern.extendedTemplate;
+ patternFileExtension = `.${patternlab.config.patternExtension}`;
+ }
+
+ fs.outputFileSync(
+ path.join(patternDir, patternName) + patternFileExtension,
+ patternCode
+ );
+}
+
+const pattern_exporter = function() {
+ /**
+ * Exports all pattern's final HTML as defined in patternlab-config.json to desired location.
+ * Originally created to help facilitate easier consumption by jekyll.
+ * This method is off spec with PL PHP and will change or be augmented some day.
+ *
+ * @param patternlab {object} patternlab reference
+ */
+ function exportPatterns(patternlab) {
+ //read the config export options
+ const exportPartials = patternlab.config.patternExportPatternPartials;
+ const exportAll = patternlab.config.patternExportAll;
+
+ if (exportAll) {
+ for (let i = 0; i < patternlab.patterns.length; i++) {
+ if (!patternlab.patterns[i].patternPartial.startsWith('-')) {
+ exportSinglePattern(patternlab, patternlab.patterns[i]);
+ }
+ }
+
+ return;
+ }
+
+ //find the chosen patterns to export
+ for (let i = 0; i < exportPartials.length; i++) {
+ for (let j = 0; j < patternlab.patterns.length; j++) {
+ if (exportPartials[i] === patternlab.patterns[j].patternPartial) {
+ //write matches to the desired location
+ exportSinglePattern(patternlab, patternlab.patterns[j]);
+ }
+ }
+ }
+ }
+
+ return {
+ export_patterns: function(patternlab) {
+ exportPatterns(patternlab);
+ },
+ };
+};
+
+module.exports = pattern_exporter;
diff --git a/packages/core/src/lib/pattern_graph.js b/packages/core/src/lib/pattern_graph.js
new file mode 100644
index 000000000..6ad17d012
--- /dev/null
+++ b/packages/core/src/lib/pattern_graph.js
@@ -0,0 +1,425 @@
+'use strict';
+
+const graphlib = require('graphlib');
+const Graph = graphlib.Graph;
+const path = require('path');
+const fs = require('fs-extra');
+const Pattern = require('./object_factory').Pattern;
+const CompileState = require('./object_factory').CompileState;
+const PatternGraphDot = require('./pattern_graph_dot');
+const PatternRegistry = require('./pattern_registry');
+
+/**
+ * The most recent version of the pattern graph. This is used to rebuild the graph when
+ * the version of a serialized graph does not match the current version.
+ * @type {number}
+ */
+const PATTERN_GRAPH_VERSION = 1;
+
+/**
+ * Wrapper around a graph library to build a dependency graph of patterns.
+ * Each node in the graph will maintain a {@link CompileState}. This allows finding all
+ * changed patterns and their transitive dependencies.
+ *
+ * Internally the graph maintains a {@link PatternRegistry} to allow fast lookups of the patterns.
+ *
+ * @constructor Constructs a new PatternGraph from a JSON-style JavaScript object or an empty graph
+ * if no argument is given.
+ *
+ * @param {Graph} graph The graphlib graph object
+ * @param {int} timestamp The unix timestamp
+ * @param {int} version The graph version.
+ *
+ * @returns {{PatternGraph: PatternGraph}}
+
+ * @see PatternGraph#fromJson
+ * @see #540
+ */
+const PatternGraph = function(graph, timestamp, version) {
+ this.graph =
+ graph ||
+ new Graph({
+ directed: true,
+ });
+ this.graph.setDefaultEdgeLabel({});
+
+ // Allows faster lookups for patterns by name for each element in the graph
+ // The idea here is to make a pattern known to the graph as soon as it exists
+ this.patterns = new PatternRegistry();
+ this.timestamp = timestamp || new Date().getTime();
+ this.version = version || PATTERN_GRAPH_VERSION;
+};
+
+// shorthand. Use relPath as it is always unique, even with subPatternType
+const nodeName = p => (p instanceof Pattern ? p.relPath : p);
+
+PatternGraph.prototype = {
+ /**
+ * Synchronizes the graph nodes with the set of all known patterns.
+ * For instance when a pattern is deleted or moved, it might still have a node from the serialized
+ * JSON, but there is no source pattern.
+ *
+ * @see {@link https://github.com/pattern-lab/patternlab-node/issues/580|Issue #580}
+ */
+ sync: function() {
+ // Remove any patterns that are in the graph data, but that haven't been discovered when
+ // walking all patterns iteratively
+ const nodesToRemove = this.nodes().filter(n => !this.patterns.has(n));
+ nodesToRemove.forEach(n => this.remove(n));
+ return nodesToRemove;
+ },
+
+ /**
+ * Creates an independent copy of the graph where nodes and edges can be modified without
+ * affecting the source.
+ */
+ clone: function() {
+ const json = graphlib.json.write(this.graph);
+ const graph = graphlib.json.read(json);
+ return new PatternGraph(graph, this.timestamp, this.version);
+ },
+
+ /**
+ * Add a pattern to the graph and copy its {@link Pattern.compileState} to the node's data.
+ * If the pattern is already known, nothing is done.
+ *
+ * @param {Pattern} pattern
+ */
+ add: function(pattern) {
+ const n = nodeName(pattern);
+ if (!this.patterns.has(n)) {
+ this.graph.setNode(n, {
+ compileState: pattern.compileState,
+ });
+
+ this.patterns.put(pattern);
+ }
+ },
+
+ remove: function(pattern) {
+ const n = nodeName(pattern);
+ this.graph.removeNode(n);
+ this.patterns.remove(n);
+ },
+
+ /**
+ * Removes nodes from this graph for which the given predicate function returns false.
+ * @param {function} fn which takes a node name as argument
+ */
+ filter: function(fn) {
+ this.graph.nodes().forEach(n => {
+ if (!fn(n)) {
+ this.remove(n);
+ }
+ });
+ },
+
+ /**
+ * Creates a directed edge in the graph which indicates pattern inclusion.
+ * Patterns must be {@link PatternGraph.add added} before using this method.
+ *
+ * @param {Pattern} patternFrom The pattern (subject) which includes the other pattern
+ * @param {Pattern} patternTo The pattern (object) that is included by the subject.
+ *
+ * @throws {Error} If the pattern is unknown
+ */
+ link: function(patternFrom, patternTo) {
+ const nameFrom = nodeName(patternFrom);
+ const nameTo = nodeName(patternTo);
+ for (const name of [nameFrom, nameTo]) {
+ if (!this.patterns.has(name)) {
+ throw new Error('Pattern not known: ' + name);
+ }
+ }
+ this.graph.setEdge(nameFrom, nameTo);
+ },
+
+ /**
+ * Determines if there is one pattern is included by another.
+ * @param {Pattern} patternFrom
+ * @param {Pattern} patternTo
+ *
+ * @return {boolean}
+ */
+ hasLink: function(patternFrom, patternTo) {
+ const nameFrom = nodeName(patternFrom);
+ const nameTo = nodeName(patternTo);
+ return this.graph.hasEdge(nameFrom, nameTo);
+ },
+
+ /**
+ * Determines the order in which all changed patterns and there transitive predecessors must
+ * be rebuild.
+ *
+ * This first finds all patterns that must be rebuilt, second marks any patterns that transitively
+ * include these patterns for rebuilding and finally applies topological sorting to the graph.
+ *
+ * @return {Array} An Array of {@link Pattern}s in the order by which the changed patters must be
+ * compiled.
+ */
+ compileOrder: function() {
+ const compileStateFilter = function(patterns, n) {
+ const node = patterns.get(n);
+ return node.compileState !== CompileState.CLEAN;
+ };
+
+ /**
+ * This graph only contains those nodes that need recompilation
+ * Edges are added in reverse order for topological sorting(e.g. atom -> molecule -> organism,
+ * where "->" means "included by").
+ */
+ const compileGraph = new Graph({
+ directed: true,
+ });
+
+ const nodes = this.graph.nodes();
+ const changedNodes = nodes.filter(n =>
+ compileStateFilter(this.patterns, n)
+ );
+ this.nodes2patterns(changedNodes).forEach(pattern => {
+ const patternNode = nodeName(pattern);
+ if (!compileGraph.hasNode(patternNode)) {
+ compileGraph.setNode(patternNode);
+ }
+ this.applyReverse(pattern, (from, to) => {
+ from.compileState = CompileState.NEEDS_REBUILD;
+ const fromName = nodeName(from);
+ const toName = nodeName(to);
+ for (const name of [fromName, toName]) {
+ if (!compileGraph.hasNode(name)) {
+ compileGraph.setNode(name);
+ }
+ }
+ if (!compileGraph.hasNode(toName)) {
+ compileGraph.setNode(toName);
+ }
+
+ // reverse!
+ compileGraph.setEdge({ v: toName, w: fromName });
+ });
+ });
+
+ // Apply topological sorting, Start at the leafs of the graphs (e.g. atoms) and go further
+ // up in the hierarchy
+ const o = graphlib.alg.topsort(compileGraph);
+ return this.nodes2patterns(o);
+ },
+
+ /**
+ * Given a node and its predecessor, allows exchanging states between nodes.
+ * @param pattern
+ * @param fn A function that takes the currently viewed pattern and node data. Allows synching data
+ * between patterns and node metadata.
+ */
+ applyReverse: function(pattern, fn) {
+ for (const p of this.lineageR(pattern)) {
+ fn(p, pattern);
+ this.applyReverse(p, fn);
+ }
+ },
+
+ /**
+ * Find the node fro a pattern
+ *
+ * @param {Pattern} pattern
+ *
+ * @return [null|Pattern]
+ */
+ node: function(pattern) {
+ return this.graph.node(nodeName(pattern));
+ },
+
+ /**
+ *
+ * @param nodes {Array}
+ * @return {Array} An Array of Patterns
+ */
+ nodes2patterns: function(nodes) {
+ return nodes.map(n => this.patterns.get(n));
+ },
+
+ // TODO cache result in a Map[String, Array]?
+ // We trade the pattern.lineage array - O(pattern.lineage.length << |V|) - vs. O(|V|) of the graph.
+ // As long as no edges are added or removed, we can cache the result in a Map and just return it.
+ /**
+ * Finds all immediate successors of a pattern, i.e. all patterns which the given pattern includes.
+ * @param pattern
+ * @return {*|Array}
+ */
+ lineage: function(pattern) {
+ const nodes = this.graph.successors(nodeName(pattern));
+ return this.nodes2patterns(nodes);
+ },
+
+ /**
+ * Returns all patterns that include the given pattern
+ * @param {Pattern} pattern
+ * @return {*|Array}
+ */
+ lineageR: function(pattern) {
+ const nodes = this.graph.predecessors(nodeName(pattern));
+ return this.nodes2patterns(nodes);
+ },
+
+ /**
+ * Given a {Pattern}, return all partial names of {Pattern} objects included in this the given pattern
+ * @param {Pattern} pattern
+ *
+ * @see {@link PatternGraph.lineage(pattern)}
+ */
+ lineageIndex: function(pattern) {
+ const lineage = this.lineage(pattern);
+ return lineage.map(p => p.patternPartial);
+ },
+
+ /**
+ * Given a {Pattern}, return all partial names of {Pattern} objects which include the given pattern
+ * @param {Pattern} pattern
+ *
+ * @return {Array}
+ *
+ * @see {@link PatternGraph.lineageRIndex(pattern)}
+ */
+ lineageRIndex: function(pattern) {
+ const lineageR = this.lineageR(pattern);
+ return lineageR.map(p => p.patternPartial);
+ },
+
+ /**
+ * Creates an object representing the graph and meta data.
+ * @returns {{timestamp: number, graph}}
+ */
+ toJson: function() {
+ return {
+ version: this.version,
+ timestamp: this.timestamp,
+ graph: graphlib.json.write(this.graph),
+ };
+ },
+
+ /**
+ * @return {Array} An array of all node names.
+ */
+ nodes: function() {
+ return this.graph.nodes();
+ },
+
+ /**
+ * Updates the version to the most recent one
+ */
+ upgradeVersion: function() {
+ this.version = PATTERN_GRAPH_VERSION;
+ },
+};
+
+/**
+ * Creates an empty graph with a unix timestamp of 0 as last compilation date.
+ * @param {int} [version=PATTERN_GRAPH_VERSION]
+ * @return {PatternGraph}
+ */
+PatternGraph.empty = function(version) {
+ return new PatternGraph(null, 0, version || PATTERN_GRAPH_VERSION);
+};
+
+/**
+ * Checks if the version of
+ * @param {PatternGraph|Object} graphOrJson
+ * @return {boolean}
+ */
+PatternGraph.checkVersion = function(graphOrJson) {
+ return graphOrJson.version === PATTERN_GRAPH_VERSION;
+};
+
+/**
+ * Error that is thrown if the given version does not match the current graph version.
+ *
+ * @param oldVersion
+ * @constructor
+ */
+function VersionMismatch(oldVersion) {
+ this.message = `Version of graph on disk ${oldVersion} != current version ${PATTERN_GRAPH_VERSION}. Please clean your patterns output directory.`;
+ this.name = 'VersionMismatch';
+}
+
+/**
+ * Parse the graph from a JSON object.
+ * @param {object} o The JSON object to read from
+ * @return {PatternGraph}
+ */
+PatternGraph.fromJson = function(o) {
+ if (!PatternGraph.checkVersion(o)) {
+ throw new VersionMismatch(o.version);
+ }
+ const graph = graphlib.json.read(o.graph);
+ return new PatternGraph(graph, o.timestamp, o.version);
+};
+
+/**
+ * Resolve the path to the file containing the serialized graph
+ * @param {string} [filePath='process.cwd()'] Path to the graph file
+ * @param {string} [fileName='dependencyGraph.json'] Name of the graph file
+ * @return {string}
+ */
+PatternGraph.resolveJsonGraphFile = function(
+ filePath = process.cwd(),
+ fileName = 'dependencyGraph.json'
+) {
+ return path.resolve(filePath, fileName);
+};
+
+/**
+ * Loads a graph from the file. Does not add any patterns from the patternlab object,
+ * i.e. graph.patterns will be still empty until all patterns have been processed.
+ *
+ * @param {string} [filePath] path to the graph json file
+ * @param {string} [fileName] optional name of the graph json file
+ *
+ * @see {@link PatternGraph.fromJson}
+ * @see {@link PatternGraph.resolveJsonGraphFile}
+ */
+PatternGraph.loadFromFile = function(filePath, fileName) {
+ const jsonGraphFile = this.resolveJsonGraphFile(filePath, fileName);
+
+ // File is fresh, so simply construct an empty graph in memory
+ if (!fs.existsSync(jsonGraphFile)) {
+ return PatternGraph.empty();
+ }
+
+ const obj = fs.readJSONSync(jsonGraphFile);
+ if (!PatternGraph.checkVersion(obj)) {
+ return PatternGraph.empty(obj.version);
+ }
+ return this.fromJson(obj);
+};
+
+/**
+ * Serializes the graph to a file.
+ * @param patternlab
+ * @param {string} [file] For unit testing only.
+ *
+ * @see {@link PatternGraph.resolveJsonGraphFile}
+ */
+PatternGraph.storeToFile = function(patternlab) {
+ if (process.env.PATTERNLAB_ENV === 'CI') {
+ return;
+ }
+ const jsonGraphFile = this.resolveJsonGraphFile();
+ patternlab.graph.timestamp = new Date().getTime();
+ fs.writeJSONSync(jsonGraphFile, patternlab.graph.toJson());
+};
+
+/**
+ * Exports this graph to a GraphViz file.
+ * @param patternlab
+ @ @param {string} fileName Output filename
+ */
+PatternGraph.exportToDot = function(patternlab, fileName) {
+ const dotFile = this.resolveJsonGraphFile(undefined, fileName);
+ const g = PatternGraphDot.generate(patternlab.graph);
+ fs.outputFileSync(dotFile, g);
+};
+
+module.exports = {
+ PatternGraph: PatternGraph,
+ PATTERN_GRAPH_VERSION: PATTERN_GRAPH_VERSION,
+};
diff --git a/packages/core/src/lib/pattern_graph_dot.js b/packages/core/src/lib/pattern_graph_dot.js
new file mode 100644
index 000000000..c2e865203
--- /dev/null
+++ b/packages/core/src/lib/pattern_graph_dot.js
@@ -0,0 +1,147 @@
+'use strict';
+
+/**
+ * Overall settings
+ * @return {[string,string,string,string,string,string,string]}
+ */
+function header() {
+ return [
+ 'strict digraph {',
+ 'graph [fontname = "helvetica" size=20]',
+
+ /*compound=true;*/
+ 'concentrate=true;',
+ 'rankdir=LR;',
+ 'ranksep="4 equally·";',
+ 'node [style=filled,color=white];',
+ 'edge [style=dotted constraint=false]',
+ ];
+}
+
+/**
+ * Graph nodes cannot start with numbers in GrahViz and must not contain dashes.
+ * @param name
+ * @return {string}
+ */
+const niceKey = function(name) {
+ return 'O' + name.replace('-', '');
+};
+
+/**
+ * Adds the output for defining a node in GraphViz.
+ *
+ * @param {Pattern} pattern
+ * @return {string}
+ */
+function addNode(pattern) {
+ let more = '';
+ if (pattern.isPseudoPattern) {
+ more = ' [fillcolor=grey]';
+ }
+ return '"' + pattern.name + '"' + more + ';\n';
+}
+
+/**
+ *
+ * @param {Pattern} from
+ * @param {Pattern} to
+ * @param {string} color A valid color, e.g. HTMl or a color name
+ * @return {string}
+ */
+function addEdge(from, to, color) {
+ return `"${from.name}" -> "${to.name}" [color=${color}];\n`;
+}
+
+/**
+ * Creates a sub-graph which is used to group atoms, molecules, etc.
+ * @param group
+ * @param patterns
+ * @return {[*,*,string,string,*,*,string]}
+ */
+function subGraph(group, patterns) {
+ const s = niceKey(group);
+ return [
+ 'subgraph cluster_X' + s + ' {',
+ 'label=<' + group + ' >;',
+ 'style=filled;',
+ 'color=lightgrey;',
+ s + ' [shape=box];',
+ patterns.map(addNode).join(''),
+
+ //patterns.map(p => "\"" + p.name + "\"").join(" -> ") + "[style=invis]",
+ '}',
+ ];
+}
+
+function footer() {
+ return ['}'];
+}
+
+const PatternGraphDot = {};
+
+/**
+ * Create the GraphViz representation of the given graph
+ * @param patternGraph
+ * @return {string}
+ */
+PatternGraphDot.generate = function(patternGraph) {
+ const g = patternGraph.graph;
+ const patterns = patternGraph.patterns;
+ const buckets = new Map();
+ const colors = [
+ 'darkgreen',
+ 'firebrick',
+ 'slateblue',
+ 'darkgoldenrod',
+ 'black',
+ ];
+ const colorMap = new Map();
+ let colIdx = 0;
+ for (const p of patterns.partials.values()) {
+ if (p.isPseudoPattern || !p.patternType) {
+ continue;
+ }
+ let bucket = buckets.get(p.patternType);
+ if (bucket) {
+ bucket.push(p);
+ } else {
+ bucket = [p];
+ colorMap.set(p.patternType, colors[colIdx++]);
+
+ // Repeat if there are more categories
+ colIdx = colIdx % colors.length;
+ }
+ buckets.set(p.patternType, bucket);
+ }
+
+ let res = header();
+ const sortedKeys = Array.from(buckets.keys()).sort();
+
+ const niceKeys = sortedKeys.map(niceKey);
+
+ let subGraphLines = [];
+
+ for (const key of sortedKeys) {
+ const subPatterns = buckets.get(key);
+ subGraphLines = subGraphLines.concat(subGraph(key, subPatterns));
+ }
+ res = res.concat(subGraphLines);
+ res.push('edge[style=solid];');
+
+ foo: for (const edge of g.edges()) {
+ const fromTo = patternGraph.nodes2patterns([edge.v, edge.w]);
+ for (const pattern of fromTo) {
+ if (pattern.isPseudoPattern || !pattern.patternType) {
+ continue foo;
+ }
+ }
+ const thisColor = colorMap.get(fromTo[0].patternType);
+ res.push(addEdge(fromTo[0], fromTo[1], thisColor));
+ }
+
+ res.push(niceKeys.reverse().join(' -> ') + '[constraint=true];');
+ res = res.concat(footer());
+ return res.join('\n') + '\n';
+};
+
+module.exports = PatternGraphDot;
diff --git a/packages/core/src/lib/pattern_registry.js b/packages/core/src/lib/pattern_registry.js
new file mode 100644
index 000000000..95145234c
--- /dev/null
+++ b/packages/core/src/lib/pattern_registry.js
@@ -0,0 +1,100 @@
+'use strict';
+
+/**
+ * Allows lookups for patterns via a central registry.
+ * @constructor
+ */
+const PatternRegistry = function() {
+ this.key2pattern = new Map();
+
+ /** For lookups by {@link Pattern#partialKey} */
+ this.partials = new Map();
+};
+
+PatternRegistry.prototype = {
+ allPatterns: function() {
+ return Array.from(this.key2pattern.values());
+ },
+
+ has: function(name) {
+ return this.key2pattern.has(name);
+ },
+
+ get: function(name) {
+ return this.key2pattern.get(name);
+ },
+
+ /**
+ * Adds the given pattern to the registry. If a pattern with the same key exists, it is replaced.
+ * @param pattern {Pattern|*}
+ */
+ put: function(pattern) {
+ const name = PatternRegistry.partialName(pattern);
+ this.partials.set(name, pattern);
+ const key = PatternRegistry.patternKey(pattern);
+ this.key2pattern.set(key, pattern);
+ },
+
+ remove: function(name) {
+ this.key2pattern.delete(name);
+ },
+
+ getPartial: function(partialName) {
+ /*
+ Code in here has been moved from getPartial() to prepare for some refactoring.
+ There are a few advantages to this method:
+ - use a map lookup instead of interating through all patterns
+ - get rid of dependency to the patternlab object
+ - make code more readable
+ */
+
+ // This previously has been a for loop over an array in pattern_
+ const byPartialName = this.partials.get(partialName);
+ if (this.partials.has(partialName)) {
+ return byPartialName;
+ }
+
+ const patterns = this.allPatterns();
+
+ //else look by verbose syntax
+ for (const thisPattern of patterns) {
+ switch (partialName) {
+ case thisPattern.relPath:
+ case thisPattern.verbosePartial:
+ return thisPattern;
+ }
+ }
+
+ //return the fuzzy match if all else fails
+ for (const thisPattern of patterns) {
+ const partialParts = partialName.split('-'),
+ partialType = partialParts[0],
+ partialNameEnd = partialParts.slice(1).join('-');
+
+ const patternPartial = thisPattern.patternPartial;
+ if (
+ patternPartial.split('-')[0] === partialType &&
+ patternPartial.indexOf(partialNameEnd) > -1
+ ) {
+ return thisPattern;
+ }
+ }
+ return undefined;
+ },
+};
+
+PatternRegistry.patternKey = function(pattern) {
+ return pattern.relPath;
+};
+
+/**
+ * Defines how the partial key of a pattern is resolved.
+ *
+ * @param pattern {Pattern}
+ * @return {string}
+ */
+PatternRegistry.partialName = function(pattern) {
+ return pattern.patternPartial;
+};
+
+module.exports = PatternRegistry;
diff --git a/packages/core/src/lib/patternlab.js b/packages/core/src/lib/patternlab.js
new file mode 100644
index 000000000..cd89c64c4
--- /dev/null
+++ b/packages/core/src/lib/patternlab.js
@@ -0,0 +1,367 @@
+'use strict';
+
+const dive = require('dive');
+const _ = require('lodash');
+const path = require('path');
+const cleanHtml = require('js-beautify').html;
+
+const inherits = require('util').inherits;
+const pm = require('./plugin_manager');
+const plugin_manager = new pm();
+const packageInfo = require('../../package.json');
+const events = require('./events');
+const buildListItems = require('./buildListItems');
+const dataLoader = require('./data_loader')();
+const loaduikits = require('./loaduikits');
+const logger = require('./log');
+const processIterative = require('./processIterative');
+const processRecursive = require('./processRecursive');
+
+const loadPattern = require('./loadPattern');
+const sm = require('./starterkit_manager');
+
+const patternEngines = require('./pattern_engines');
+
+//these are mocked in unit tests, so let them be overridden
+let fs = require('fs-extra'); // eslint-disable-line
+
+const EventEmitter = require('events').EventEmitter;
+
+function PatternLabEventEmitter() {
+ EventEmitter.call(this);
+}
+inherits(PatternLabEventEmitter, EventEmitter);
+
+module.exports = class PatternLab {
+ constructor(config) {
+ // Either use the config we were passed, or load one up from the config file ourselves
+ this.config =
+ config ||
+ fs.readJSONSync(path.resolve(__dirname, '../../patternlab-config.json'));
+
+ //register our log events
+ this.registerLogger(config.logLevel);
+
+ logger.info(`Pattern Lab Node v${packageInfo.version}`);
+
+ // Load up engines please
+ this.engines = patternEngines;
+ this.engines.loadAllEngines(config);
+ this.isBusy = false;
+
+ //
+ // INITIALIZE EMPTY GLOBAL DATA STRUCTURES
+ //
+ this.data = {};
+ this.patterns = [];
+ this.subtypePatterns = {};
+ this.partials = {};
+
+ // Cache the package.json in RAM
+ this.package = fs.readJSONSync(
+ path.resolve(__dirname, '../../package.json')
+ );
+
+ // Make ye olde event emitter
+ this.events = new PatternLabEventEmitter();
+
+ this.hooks = {};
+ this.hooks[events.PATTERNLAB_PATTERN_WRITE_END] = [];
+
+ // Make a place for the pattern graph to sit
+ this.graph = null;
+
+ // Make a place to attach known watchers so we can manage them better during serve and watch
+ this.watchers = {};
+
+ // make a place to register any uikits
+ this.uikits = {};
+ loaduikits(this);
+
+ // Verify correctness of configuration (?)
+ this.checkConfiguration(this);
+
+ this.initializePlugins(this);
+ }
+
+ checkConfiguration(patternlab) {
+ //default the output suffixes if not present
+ const outputFileSuffixes = {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ };
+
+ if (!patternlab.config.outputFileSuffixes) {
+ logger.warning('');
+ logger.warning(
+ 'Configuration key [outputFileSuffixes] not found, and defaulted to the following:'
+ );
+ logger.info(outputFileSuffixes);
+ logger.warning(
+ 'Since Pattern Lab Node Core 2.3.0 this configuration option is required. Suggest you add it to your patternlab-config.json file.'
+ );
+ logger.warning('');
+ }
+ patternlab.config.outputFileSuffixes = _.extend(
+ outputFileSuffixes,
+ patternlab.config.outputFileSuffixes
+ );
+
+ if (typeof patternlab.config.paths.source.patternlabFiles === 'string') {
+ logger.warning('');
+ logger.warning(
+ `Configuration key [paths.source.patternlabFiles] inside patternlab-config.json was found as the string '${patternlab.config.paths.source.patternlabFiles}'`
+ );
+ logger.warning(
+ 'Since Pattern Lab Node Core 3.0.0 this key is an object. Suggest you update this key following this issue: https://github.com/pattern-lab/patternlab-node/issues/683.'
+ );
+ logger.warning('');
+ }
+
+ if (typeof patternlab.config.debug === 'boolean') {
+ logger.warning('');
+ logger.warning(
+ `Configuration key [debug] inside patternlab-config.json was found. As of Pattern Lab Node Core 3.0.0 this key is replaced with a new key, [logLevel]. This is a string with possible values ['debug', 'info', 'warning', 'error', 'quiet'].`
+ );
+ logger.warning(
+ `Turning on 'info', 'warning', and 'error' levels by default, unless [logLevel] is present. If that is the case, [debug] has no effect.`
+ );
+ logger.warning('');
+ }
+ }
+
+ /**
+ * Finds and calls the main method of any found plugins.
+ * @param patternlab - global data store
+ */
+ initializePlugins(patternlab) {
+ if (!patternlab.config.plugins) {
+ return;
+ }
+ plugin_manager.intialize_plugins(patternlab);
+ }
+
+ buildGlobalData(additionalData) {
+ const paths = this.config.paths;
+
+ //
+ // COLLECT GLOBAL LIBRARY DATA
+ //
+
+ // data.json
+ try {
+ this.data = this.buildPatternData(paths.source.data, fs); // eslint-disable-line no-use-before-define
+ this.data.link = {};
+ } catch (ex) {
+ logger.error(
+ 'missing or malformed' +
+ paths.source.data +
+ 'data.json Pattern Lab may not work without this file.'
+ );
+ this.data = {};
+ }
+
+ // listitems.json
+ try {
+ this.listitems = fs.readJSONSync(
+ path.resolve(paths.source.data, 'listitems.json')
+ );
+ } catch (ex) {
+ logger.warning(
+ 'WARNING: missing or malformed ' +
+ paths.source.data +
+ 'listitems.json file. Pattern Lab may not work without this file.'
+ );
+ this.listitems = {};
+ }
+
+ this.data = Object.assign({}, this.data, additionalData);
+
+ this.setCacheBust();
+
+ buildListItems(this);
+
+ this.events.emit(events.PATTERNLAB_BUILD_GLOBAL_DATA_END, this);
+ }
+
+ setCacheBust() {
+ if (this.config.cacheBust) {
+ logger.debug('setting cacheBuster value for frontend assets.');
+ this.cacheBuster = new Date().getTime();
+ } else {
+ this.cacheBuster = 0;
+ }
+ }
+
+ // Starter Kit loading methods
+
+ listStarterkits() {
+ const starterkit_manager = new sm(this.config);
+ return starterkit_manager.list_starterkits();
+ }
+
+ loadStarterKit(starterkitName, clean) {
+ const starterkit_manager = new sm(this.config);
+ starterkit_manager.load_starterkit(starterkitName, clean);
+ }
+
+ // info methods
+ getVersion() {
+ return this.package.version;
+ }
+ getSupportedTemplateExtensions() {
+ return this.engines.getSupportedFileExtensions();
+ }
+
+ writePatternFiles(headHTML, pattern, footerHTML, outputBasePath) {
+ const nullFormatter = str => str;
+ const defaultFormatter = codeString =>
+ cleanHtml(codeString, { indent_size: 2 });
+ const makePath = type =>
+ path.join(
+ this.config.paths.public.patterns,
+ pattern.getPatternLink(this, type)
+ );
+ const patternPage = headHTML + pattern.patternPartialCode + footerHTML;
+ const eng = pattern.engine;
+
+ //beautify the output if configured to do so
+ const formatters = this.config.cleanOutputHtml
+ ? {
+ rendered: eng.renderedCodeFormatter || defaultFormatter,
+ rawTemplate: eng.rawTemplateCodeFormatter || defaultFormatter,
+ markupOnly: eng.markupOnlyCodeFormatter || defaultFormatter,
+ }
+ : {
+ rendered: nullFormatter,
+ rawTemplate: nullFormatter,
+ markupOnly: nullFormatter,
+ };
+
+ //prepare the path and contents of each output file
+ const outputFiles = [
+ {
+ path: makePath('rendered'),
+ content: formatters.rendered(patternPage, pattern),
+ },
+ {
+ path: makePath('rawTemplate'),
+ content: formatters.rawTemplate(pattern.template, pattern),
+ },
+ {
+ path: makePath('markupOnly'),
+ content: formatters.markupOnly(pattern.patternPartialCode, pattern),
+ },
+ ].concat(
+ eng.addOutputFiles ? eng.addOutputFiles(this.config.paths, this) : []
+ );
+
+ //write the compiled template to the public patterns directory
+ outputFiles.forEach(outFile =>
+ fs.outputFileSync(
+ path.join(process.cwd(), outputBasePath, outFile.path),
+ outFile.content
+ )
+ );
+ }
+
+ /**
+ * Binds console logging to different levels
+ *
+ * @param {string} logLevel
+ * @memberof PatternLab
+ */
+ registerLogger(logLevel) {
+ if (logLevel === undefined) {
+ logger.log.on('info', msg => console.info(msg));
+ logger.log.on('warning', msg => console.info(msg));
+ logger.log.on('error', msg => console.info(msg));
+ } else {
+ if (logLevel === 'quiet') {
+ return;
+ }
+ switch (logLevel) {
+ case 'debug':
+ logger.log.on('debug', msg => console.info(msg));
+ case 'info':
+ logger.log.on('info', msg => console.info(msg));
+ case 'warning':
+ logger.log.on('warning', msg => console.info(msg));
+ case 'error':
+ logger.log.on('error', msg => console.info(msg));
+ }
+ }
+ }
+
+ /**
+ * Given a path, load info from the folder to compile into a single config object.
+ * @param dataFilesPath
+ * @param fsDep
+ * @returns {{}}
+ */
+ buildPatternData(dataFilesPath, fsDep) {
+ return dataLoader.loadDataFromFolder(dataFilesPath, 'listitems', fsDep);
+ }
+
+ // dive once to perform iterative populating of patternlab object
+ processAllPatternsIterative(patterns_dir) {
+ const self = this;
+ const promiseAllPatternFiles = new Promise(function(resolve) {
+ dive(
+ patterns_dir,
+ (err, file) => {
+ //log any errors
+ if (err) {
+ logger.info('error in processAllPatternsIterative():', err);
+ return;
+ }
+
+ // We now have the loading and process phases spearated; this
+ // loads all the patterns before beginning any analysis, so we
+ // can load them asynchronously and be sure we know about all
+ // of them before we start lineage hunting, for
+ // example. Incidentally, this should also allow people to do
+ // horrifying things like include a page in a atom. But
+ // please, if you're reading this: don't.
+
+ // NOTE: sync for now
+ loadPattern(path.relative(patterns_dir, file), self);
+ },
+ resolve
+ );
+ });
+ return promiseAllPatternFiles.then(() => {
+ return Promise.all(
+ this.patterns.map(pattern => {
+ return processIterative(pattern, self);
+ })
+ ).then(() => {
+ // patterns sorted by name so the patterntype and patternsubtype is adhered to for menu building
+ this.patterns.sort((pattern1, pattern2) =>
+ pattern1.name.localeCompare(pattern2.name)
+ );
+ });
+ });
+ }
+
+ processAllPatternsRecursive(patterns_dir) {
+ const self = this;
+
+ const promiseAllPatternFiles = new Promise(function(resolve) {
+ dive(
+ patterns_dir,
+ (err, file) => {
+ //log any errors
+ if (err) {
+ logger.info(err);
+ return;
+ }
+ processRecursive(path.relative(patterns_dir, file), self);
+ },
+ resolve
+ );
+ });
+ return promiseAllPatternFiles;
+ }
+};
diff --git a/packages/core/src/lib/plugin_manager.js b/packages/core/src/lib/plugin_manager.js
new file mode 100644
index 000000000..9816ff99d
--- /dev/null
+++ b/packages/core/src/lib/plugin_manager.js
@@ -0,0 +1,76 @@
+'use strict';
+
+const plugin_manager = function() {
+ const path = require('path');
+ const logger = require('./log');
+
+ const pluginMatcher = /^plugin-(.*)$/;
+
+ /**
+ * Loads a plugin
+ *
+ * @param modulePath {string} the path to the plugin
+ * @return {object} the loaded plugin
+ */
+ function loadPlugin(modulePath) {
+ return require(modulePath);
+ }
+
+ /**
+ * Given a path: return the plugin name if the path points to a valid plugin
+ * module directory, or false if it doesn't.
+ * @param filePath
+ * @returns Plugin name if exists or FALSE
+ */
+ function isPlugin(filePath) {
+ const baseName = path.basename(filePath);
+ const pluginMatch = baseName.match(pluginMatcher);
+
+ if (pluginMatch) {
+ return pluginMatch[1];
+ }
+ return false;
+ }
+
+ /**
+ * Looks for installed plugins, loads them, and invokes them
+ * @param {object} patternlab
+ */
+ function initializePlugins(patternlab) {
+ const foundPlugins = Object.keys(patternlab.config.plugins || {});
+ foundPlugins.forEach(plugin => {
+ logger.info(`Found plugin: ${plugin}`);
+ logger.info(`Attempting to load and initialize plugin.`);
+ const pluginModule = loadPlugin(plugin);
+ pluginModule(patternlab);
+ });
+ }
+
+ async function raiseEvent(patternlab, eventName, args) {
+ patternlab.events.emit(eventName, args);
+ await (async function() {
+ const hookHandlers = (patternlab.hooks[eventName] || []).map(h =>
+ h(args)
+ );
+
+ await Promise.all(hookHandlers);
+ })();
+ }
+
+ return {
+ intialize_plugins: patternlab => {
+ initializePlugins(patternlab);
+ },
+ load_plugin: modulePath => {
+ return loadPlugin(modulePath);
+ },
+ is_plugin: filePath => {
+ return isPlugin(filePath);
+ },
+ raiseEvent: async (patternlab, eventName, ...args) => {
+ await raiseEvent(patternlab, eventName, args);
+ },
+ };
+};
+
+module.exports = plugin_manager;
diff --git a/packages/core/src/lib/processIterative.js b/packages/core/src/lib/processIterative.js
new file mode 100644
index 000000000..1c65c85ca
--- /dev/null
+++ b/packages/core/src/lib/processIterative.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const logger = require('./log');
+const pph = require('./pseudopattern_hunter');
+
+// This is now solely for analysis; loading of the pattern file is
+// above, in loadPatternIterative()
+module.exports = function(pattern, patternlab) {
+ //look for a pseudo pattern by checking if there is a file
+ //containing same name, with ~ in it, ending in .json
+ return pph
+ .find_pseudopatterns(pattern, patternlab)
+ .then(() => {
+ //find any stylemodifiers that may be in the current pattern
+ pattern.stylePartials = pattern.findPartialsWithStyleModifiers();
+
+ //find any pattern parameters that may be in the current pattern
+ pattern.parameteredPartials = pattern.findPartialsWithPatternParameters();
+ return Promise.resolve(pattern);
+ })
+ .catch(
+ logger.reportError('There was an error in processPatternIterative():')
+ );
+};
diff --git a/packages/core/src/lib/processMetaPattern.js b/packages/core/src/lib/processMetaPattern.js
new file mode 100644
index 000000000..f793a103a
--- /dev/null
+++ b/packages/core/src/lib/processMetaPattern.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const path = require('path');
+
+const Pattern = require('./object_factory').Pattern;
+const decompose = require('./decompose');
+const logger = require('./log');
+
+//this may be mocked in unit tests, so let it be overridden
+let fs = require('fs-extra'); // eslint-disable-line
+
+module.exports = function(fileName, metaType, patternlab) {
+ const metaPath = path.resolve(patternlab.config.paths.source.meta, fileName);
+ const metaPattern = new Pattern(metaPath, null, patternlab);
+ metaPattern.template = fs.readFileSync(metaPath, 'utf8');
+ metaPattern.isPattern = false;
+ metaPattern.isMetaPattern = true;
+ return decompose(metaPattern, patternlab, true)
+ .then(() => {
+ patternlab[metaType] = metaPattern;
+ })
+ .catch(reason => {
+ logger.warning(
+ `Could not find the user-editable template ${fileName}, currently configured to be at ${patternlab.config.paths.source.meta}. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.`
+ );
+ logger.warning(reason);
+ });
+};
diff --git a/packages/core/src/lib/processRecursive.js b/packages/core/src/lib/processRecursive.js
new file mode 100644
index 000000000..97c4ac560
--- /dev/null
+++ b/packages/core/src/lib/processRecursive.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const logger = require('./log');
+const decompose = require('./decompose');
+const getPartial = require('./get');
+
+module.exports = function(file, patternlab) {
+ //find current pattern in patternlab object using file as a partial
+ const currentPattern = getPartial(file, patternlab, false);
+
+ //return if processing an ignored file
+ if (typeof currentPattern === 'undefined') {
+ return Promise.resolve();
+ }
+
+ //we are processing a markdown only pattern
+ if (currentPattern.engine === null) {
+ return Promise.resolve();
+ }
+
+ //call our helper method to actually unravel the pattern with any partials
+ return decompose(currentPattern, patternlab).catch(reason => {
+ console.log(reason);
+ logger.error(reason);
+ });
+};
diff --git a/packages/core/src/lib/pseudopattern_hunter.js b/packages/core/src/lib/pseudopattern_hunter.js
new file mode 100644
index 000000000..b95dd37f1
--- /dev/null
+++ b/packages/core/src/lib/pseudopattern_hunter.js
@@ -0,0 +1,125 @@
+'use strict';
+
+const ch = require('./changes_hunter');
+const glob = require('glob');
+const fs = require('fs-extra');
+const _ = require('lodash');
+const lh = require('./lineage_hunter');
+const Pattern = require('./object_factory').Pattern;
+const path = require('path');
+const addPattern = require('./addPattern');
+const logger = require('./log');
+const readDocumentation = require('./readDocumentation');
+const lineage_hunter = new lh();
+const changes_hunter = new ch();
+const yaml = require('js-yaml');
+
+const pseudopattern_hunter = function() {};
+
+pseudopattern_hunter.prototype.find_pseudopatterns = function(
+ currentPattern,
+ patternlab
+) {
+ const paths = patternlab.config.paths;
+
+ //look for a pseudo pattern by checking if there is a file containing same
+ //name, with ~ in it, ending in .json, .yml or .yaml
+ const needle =
+ currentPattern.subdir +
+ '/' +
+ currentPattern.fileName +
+ '~*.{json,yml,yaml}';
+ const pseudoPatterns = glob.sync(needle, {
+ cwd: paths.source.patterns,
+ debug: false,
+ nodir: true,
+ });
+
+ if (pseudoPatterns.length > 0) {
+ for (let i = 0; i < pseudoPatterns.length; i++) {
+ logger.debug(
+ `Found pseudoPattern variant of ${currentPattern.patternPartial}`
+ );
+
+ //we want to do everything we normally would here, except instead read the pseudoPattern data
+ let variantFileFullPath;
+ let variantFileData;
+ try {
+ variantFileFullPath = path.resolve(
+ paths.source.patterns,
+ pseudoPatterns[i]
+ );
+ variantFileData = yaml.safeLoad(
+ fs.readFileSync(variantFileFullPath, 'utf8')
+ );
+ } catch (err) {
+ logger.warning(
+ `There was an error parsing pseudopattern JSON for ${currentPattern.relPath}`
+ );
+ logger.warning(err);
+ }
+
+ //extend any existing data with variant data
+ variantFileData = _.merge(
+ {},
+ currentPattern.jsonFileData,
+ variantFileData
+ );
+
+ const variantName = pseudoPatterns[i]
+ .substring(pseudoPatterns[i].indexOf('~') + 1)
+ .split('.')[0];
+ const variantExtension = pseudoPatterns[i]
+ .split('.')
+ .slice(-1)
+ .pop();
+ const variantFilePath = path.join(
+ currentPattern.subdir,
+ currentPattern.fileName + '~' + variantName + '.' + variantExtension
+ );
+ const lm = fs.statSync(variantFileFullPath);
+ const patternVariant = Pattern.create(
+ variantFilePath,
+ variantFileData,
+ {
+ //use the same template as the non-variant
+ template: currentPattern.template,
+ fileExtension: currentPattern.fileExtension,
+ extendedTemplate: currentPattern.extendedTemplate,
+ isPseudoPattern: true,
+ basePattern: currentPattern,
+ stylePartials: currentPattern.stylePartials,
+ parameteredPartials: currentPattern.parameteredPartials,
+
+ // Only regular patterns are discovered during iterative walks
+ // Need to recompile on data change or template change
+ lastModified: Math.max(currentPattern.lastModified, lm.mtime),
+
+ // use the same template engine as the non-variant
+ engine: currentPattern.engine,
+ },
+ patternlab
+ );
+
+ changes_hunter.checkBuildState(patternVariant, patternlab);
+ patternlab.graph.add(patternVariant);
+ patternlab.graph.link(patternVariant, currentPattern);
+
+ //process the companion markdown file if it exists
+ readDocumentation(patternVariant, patternlab);
+
+ //find pattern lineage
+ lineage_hunter.find_lineage(patternVariant, patternlab);
+
+ //add to patternlab object so we can look these up later.
+ addPattern(patternVariant, patternlab);
+ }
+
+ // GTP: this is to emulate the behavior of the stale asynced
+ // version; when we have time, we can make all the FS calls in here
+ // async and see if it helps any, but it didn't when I tried it.
+ }
+ return Promise.resolve();
+};
+
+module.exports = new pseudopattern_hunter();
diff --git a/packages/core/src/lib/readDocumentation.js b/packages/core/src/lib/readDocumentation.js
new file mode 100644
index 000000000..2670bb307
--- /dev/null
+++ b/packages/core/src/lib/readDocumentation.js
@@ -0,0 +1,72 @@
+'use strict';
+
+const path = require('path');
+const _ = require('lodash');
+
+const ch = require('./changes_hunter');
+const logger = require('./log');
+const mp = require('./markdown_parser');
+
+const changes_hunter = new ch();
+const markdown_parser = new mp();
+
+let fs = require('fs-extra'); //eslint-disable-line prefer-const
+
+module.exports = function(pattern, patternlab) {
+ try {
+ const markdownFileName = path.resolve(
+ patternlab.config.paths.source.patterns,
+ pattern.subdir,
+ pattern.fileName + '.md'
+ );
+ changes_hunter.checkLastModified(pattern, markdownFileName);
+
+ const markdownFileContents = fs.readFileSync(markdownFileName, 'utf8');
+
+ const markdownObject = markdown_parser.parse(markdownFileContents);
+ if (!_.isEmpty(markdownObject)) {
+ //set keys and markdown itself
+ pattern.patternDescExists = true;
+ pattern.patternDesc = markdownObject.markdown;
+
+ //Add all markdown to the pattern, including frontmatter
+ pattern.allMarkdown = markdownObject;
+
+ //consider looping through all keys eventually. would need to blacklist some properties and whitelist others
+ if (markdownObject.state) {
+ pattern.patternState = markdownObject.state;
+ }
+ if (markdownObject.order) {
+ pattern.order = markdownObject.order;
+ }
+ if (markdownObject.hidden) {
+ pattern.hidden = markdownObject.hidden;
+ }
+ if (markdownObject.excludeFromStyleguide) {
+ pattern.excludeFromStyleguide = markdownObject.excludeFromStyleguide;
+ }
+ if (markdownObject.tags) {
+ pattern.tags = markdownObject.tags;
+ }
+ if (markdownObject.title) {
+ pattern.patternName = markdownObject.title;
+ }
+ if (markdownObject.links) {
+ pattern.links = markdownObject.links;
+ }
+ } else {
+ logger.warning(`error processing markdown for ${pattern.patternPartial}`);
+ }
+ logger.debug(
+ `found pattern-specific markdown for ${pattern.patternPartial}`
+ );
+ } catch (err) {
+ // do nothing when file not found
+ if (err.code !== 'ENOENT') {
+ logger.warning(
+ `'there was an error setting pattern keys after markdown parsing of the companion file for pattern ${pattern.patternPartial}`
+ );
+ logger.warning(err);
+ }
+ }
+};
diff --git a/packages/core/src/lib/render.js b/packages/core/src/lib/render.js
new file mode 100644
index 000000000..3ba1f2589
--- /dev/null
+++ b/packages/core/src/lib/render.js
@@ -0,0 +1,14 @@
+'use strict';
+
+const logger = require('./log');
+
+module.exports = function(pattern, data, partials) {
+ logger.debug(
+ `render: ${
+ pattern.patternPartial !== '-.'
+ ? pattern.patternPartial
+ : 'ad hoc partial with template' + pattern.extendedTemplate
+ }`
+ );
+ return pattern.render(data, partials);
+};
diff --git a/packages/core/src/lib/replaceParameter.js b/packages/core/src/lib/replaceParameter.js
new file mode 100644
index 000000000..7b0cd6734
--- /dev/null
+++ b/packages/core/src/lib/replaceParameter.js
@@ -0,0 +1,40 @@
+'use strict';
+
+const logger = require('./log');
+
+module.exports = function(template, prop, data) {
+ let t = template;
+
+ const valueRE = new RegExp(`{{{?\\s*[${prop}]+\\s*}?}}`);
+
+ if (typeof data === 'string') {
+ return t.replace(valueRE, data);
+ }
+
+ if (typeof data === 'boolean') {
+ const startRE = new RegExp(`{{\\s*#[${prop}]+\\s*}}`);
+ const endRE = new RegExp(`{{\\s*/[${prop}]+\\s*}}`);
+
+ const bIdx = t.search(startRE);
+ const eIdxStart = t.search(endRE);
+
+ // try to determine if this is a {{#section}}
+ // if it is, this looks like a boolean value meant to be a mere {{value}}
+ if (bIdx === -1) {
+ return t.replace(`{{${prop}}}`, data);
+ }
+
+ if (data) {
+ t = t.replace(startRE, '');
+ t = t.replace(endRE, '');
+ } else {
+ // data is falsey
+ const eIdxEnd = t.indexOf('}}', eIdxStart) + 2;
+ t = t.substring(0, bIdx) + t.substring(eIdxEnd, t.length);
+ }
+ return t;
+ }
+
+ logger.warning(`Could not replace ${prop} with ${data} inside ${template}`);
+ return t;
+};
diff --git a/packages/core/src/lib/server.js b/packages/core/src/lib/server.js
new file mode 100644
index 000000000..c55e2c4ab
--- /dev/null
+++ b/packages/core/src/lib/server.js
@@ -0,0 +1,140 @@
+'use strict';
+
+const path = require('path');
+const liveServer = require('@pattern-lab/live-server');
+
+const events = require('./events');
+const logger = require('./log');
+
+const server = patternlab => {
+ const _module = {
+ serve: () => {
+ let serverReady = false;
+
+ // our default liveserver config
+ const defaults = {
+ open: true,
+ file: 'index.html',
+ logLevel: 0, // errors only
+ wait: 1000,
+ port: 3000,
+ };
+
+ const servers = Object.keys(patternlab.uikits).map(kit => {
+ const uikit = patternlab.uikits[kit];
+ defaults.root = path.resolve(
+ path.join(
+ process.cwd(),
+ uikit.outputDir,
+ patternlab.config.paths.public.root
+ )
+ );
+ defaults.ignore = path.resolve(
+ path.join(
+ process.cwd(),
+ uikit.outputDir,
+ patternlab.config.paths.public.root
+ )
+ );
+ defaults.assets = [
+ path.resolve(
+ path.join(
+ process.cwd(),
+ patternlab.config.paths.source.js,
+ '**',
+ '*.js' // prevent preprocessors like typescript from reloading
+ )
+ ),
+ path.resolve(
+ path.join(process.cwd(), patternlab.config.paths.source.images)
+ ),
+ path.resolve(
+ path.join(process.cwd(), patternlab.config.paths.source.fonts)
+ ),
+ path.resolve(
+ path.join(
+ process.cwd(),
+ patternlab.config.paths.source.css,
+ '**',
+ '*.css' // prevent preprocessors from reloading
+ )
+ ),
+ ];
+
+ // allow for overrides should they exist inside patternlab-config.json
+ const liveServerConfig = Object.assign(
+ {},
+ defaults,
+ patternlab.config.serverOptions
+ );
+
+ const setupEventWatchers = () => {
+ //watch for builds to complete
+ patternlab.events.on(events.PATTERNLAB_BUILD_END, () => {
+ if (serverReady) {
+ _module.reload({
+ file: '',
+ action: 'reload',
+ });
+ }
+ });
+ };
+
+ //start!
+ //There is a new server instance for each uikit
+ const serveKit = new Promise((resolve, reject) => {
+ let resolveMsg = '';
+ setTimeout(() => {
+ try {
+ liveServer.start(liveServerConfig);
+ resolveMsg = `Pattern Lab is being served from http://127.0.0.1:${liveServerConfig.port}`;
+ logger.info(resolveMsg);
+ } catch (e) {
+ const err = `Pattern Lab serve failed to start: ${e}`;
+ logger.error(`Pattern Lab serve failed to start: ${e}`);
+ reject(err);
+ }
+ setupEventWatchers();
+ serverReady = true;
+ resolve(resolveMsg);
+ }, liveServerConfig.wait);
+ });
+ return serveKit;
+ });
+
+ return Promise.all(servers);
+ },
+ reload: data => {
+ const _data = data || {
+ file: '',
+ action: '',
+ };
+ return new Promise((resolve, reject) => {
+ let action;
+ try {
+ if (!patternlab.isBusy) {
+ if (_data.file.indexOf('css') > -1 || _data.action === 'refresh') {
+ action = 'refreshed CSS';
+ liveServer.refreshCSS();
+ } else {
+ action = 'reloaded';
+ liveServer.reload();
+ }
+ resolve(`Server ${action} successfully`);
+ }
+ } catch (e) {
+ reject(`Server reload or refresh failed: ${e}`);
+ }
+ });
+ },
+ refreshCSS: () => {
+ return _module.reload({
+ file: '',
+ action: 'refresh',
+ });
+ },
+ };
+ return _module;
+};
+
+module.exports = server;
diff --git a/packages/core/src/lib/starterkit_manager.js b/packages/core/src/lib/starterkit_manager.js
new file mode 100644
index 000000000..d11e5f2a4
--- /dev/null
+++ b/packages/core/src/lib/starterkit_manager.js
@@ -0,0 +1,140 @@
+'use strict';
+
+const starterkit_manager = function(config) {
+ const path = require('path');
+ const fetch = require('node-fetch');
+ const fs = require('fs-extra');
+ const logger = require('./log');
+ const paths = config.paths;
+
+ /**
+ * Loads npm module identified by the starterkitName parameter.
+ *
+ * @param starterkitName {string} Kit name
+ * @param clean {boolean} Indicates if the directory should be cleaned before loading
+ */
+ function loadStarterKit(starterkitName, clean) {
+ try {
+ const kitPath = path.resolve(
+ path.join(
+ process.cwd(),
+ 'node_modules',
+ starterkitName,
+ config.starterkitSubDir
+ )
+ );
+ logger.debug('Attempting to load starterkit from', kitPath);
+ let kitDirStats;
+ try {
+ kitDirStats = fs.statSync(kitPath);
+ } catch (ex) {
+ logger.warning(
+ `${starterkitName} not found, use npm to install it first.`
+ );
+ logger.warning(`${starterkitName} not loaded.`);
+ return;
+ }
+ const kitPathDirExists = kitDirStats.isDirectory();
+ if (kitPathDirExists) {
+ if (clean) {
+ logger.info(
+ `Deleting contents of ${paths.source.root} prior to starterkit load.`
+ );
+ fs.emptyDirSync(paths.source.root);
+ } else {
+ logger.info(
+ `Overwriting contents of ${paths.source.root} during starterkit load.`
+ );
+ }
+
+ try {
+ fs.copySync(kitPath, paths.source.root);
+ } catch (ex) {
+ logger.error(ex);
+ return;
+ }
+ logger.info('Starterkit ' + starterkitName + ' loaded into source/.');
+ }
+ } catch (ex) {
+ logger.warning(
+ `An error occurred during starterkit installation for starterkit ${starterkitName}`
+ );
+ logger.warning(ex);
+ }
+ }
+
+ /**
+ * Fetches starterkit repos from GH API that contain 'starterkit' in their name for the user 'pattern-lab'
+ *
+ * @return {Promise} Returns an Array<{name,url}> for the starterkit repos
+ */
+ function listStarterkits() {
+ return fetch(
+ 'https://api.github.com/search/repositories?q=starterkit+in:name+user:pattern-lab&sort=stars&order=desc',
+ {
+ method: 'GET',
+ headers: {
+ Accept: 'application/json',
+ },
+ }
+ )
+ .then(function(res) {
+ const contentType = res.headers.get('content-type');
+ if (contentType && contentType.indexOf('application/json') === -1) {
+ throw new TypeError(
+ 'StarterkitManager->listStarterkits: Not valid JSON'
+ );
+ }
+ return res.json();
+ })
+ .then(function(json) {
+ if (!json.items || !Array.isArray(json.items)) {
+ return false;
+ }
+ return json.items.map(function(repo) {
+ return { name: repo.name, url: repo.html_url };
+ });
+ })
+ .catch(function(err) {
+ logger.error(err);
+ return false;
+ });
+ }
+
+ function packStarterkit() {}
+
+ /**
+ * Detects installed starter kits
+ *
+ * @return {array} List of starter kits installed
+ */
+ //TODO review for deletion or convert callers to use findModules()
+ function detectStarterKits() {
+ const node_modules_path = path.join(process.cwd(), 'node_modules');
+ const npm_modules = fs.readdirSync(node_modules_path).filter(function(dir) {
+ const module_path = path.join(process.cwd(), 'node_modules', dir);
+ return (
+ fs.statSync(module_path).isDirectory() &&
+ dir.indexOf('starterkit-') === 0
+ );
+ });
+ return npm_modules;
+ }
+
+ return {
+ load_starterkit: function(starterkitName, clean) {
+ loadStarterKit(starterkitName, clean);
+ },
+ list_starterkits: function() {
+ return listStarterkits();
+ },
+ pack_starterkit: function() {
+ packStarterkit();
+ },
+ detect_starterkits: function() {
+ return detectStarterKits();
+ },
+ };
+};
+
+module.exports = starterkit_manager;
diff --git a/core/lib/style_modifier_hunter.js b/packages/core/src/lib/style_modifier_hunter.js
similarity index 50%
rename from core/lib/style_modifier_hunter.js
rename to packages/core/src/lib/style_modifier_hunter.js
index 8c0b7413f..13a7ff2d1 100644
--- a/core/lib/style_modifier_hunter.js
+++ b/packages/core/src/lib/style_modifier_hunter.js
@@ -1,21 +1,34 @@
-"use strict";
-
-var style_modifier_hunter = function () {
-
+'use strict';
+
+const logger = require('./log');
+
+const style_modifier_hunter = function() {
+ /**
+ * Modifies a patterns partial with any styleModifiers found on the supplied partial
+ *
+ * @param pattern {object} the pattern to extend
+ * @param partial {string} partial containing styleModifiers
+ * @param patternlab {object} the patternlab instance
+ */
function consumestylemodifier(pattern, partial, patternlab) {
//extract the classname from the stylemodifier which comes in the format of :className
- var styleModifier = partial.match(/:([\w\-_|])+/g) ? partial.match(/:([\w\-_|])+/g)[0].slice(1) : null;
+ let styleModifier = partial.match(/:([\w\-_|])+/g)
+ ? partial.match(/:([\w\-_|])+/g)[0].slice(1)
+ : null;
if (styleModifier) {
//replace the special character pipe | used to separate multiple classes with a space
styleModifier = styleModifier.replace(/\|/g, ' ');
- if (patternlab.config.debug) {
- console.log('found partial styleModifier within pattern ' + pattern.patternPartial);
- }
+ logger.debug(
+ `Found partial styleModifier within pattern ${pattern.patternPartial}`
+ );
//replace the stylemodifier placeholder with the class name
- pattern.extendedTemplate = pattern.extendedTemplate.replace(/{{[ ]?styleModifier[ ]?}}/i, styleModifier);
+ pattern.extendedTemplate = pattern.extendedTemplate.replace(
+ /{{[ ]?styleModifier[ ]?}}/i,
+ styleModifier
+ );
//update the extendedTemplate in the partials object in case this pattern is consumed later
patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate;
@@ -23,11 +36,10 @@ var style_modifier_hunter = function () {
}
return {
- consume_style_modifier: function (pattern, partial, patternlab) {
+ consume_style_modifier: function(pattern, partial, patternlab) {
consumestylemodifier(pattern, partial, patternlab);
- }
+ },
};
-
};
module.exports = style_modifier_hunter;
diff --git a/packages/core/src/lib/ui_builder.js b/packages/core/src/lib/ui_builder.js
new file mode 100644
index 000000000..5dcb36986
--- /dev/null
+++ b/packages/core/src/lib/ui_builder.js
@@ -0,0 +1,916 @@
+'use strict';
+
+const path = require('path');
+const _ = require('lodash');
+
+const of = require('./object_factory');
+const Pattern = of.Pattern;
+const logger = require('./log');
+const uikitExcludePattern = require('./uikitExcludePattern');
+
+//these are mocked in unit tests, so let them be overridden
+let render = require('./render'); //eslint-disable-line prefer-const
+let fs = require('fs-extra'); //eslint-disable-line prefer-const
+let buildFooter = require('./buildFooter'); //eslint-disable-line prefer-const
+let exportData = require('./exportData'); //eslint-disable-line prefer-const
+
+const ui_builder = function() {
+ /**
+ * Registers the pattern to the patternPaths object for the appropriate patternGroup and basename
+ * patternGroup + patternBaseName are what comprise the patternPartial (atoms-colors)
+ * @param patternlab - global data store
+ * @param pattern - the pattern to add
+ */
+ function addToPatternPaths(patternlab, pattern) {
+ if (!patternlab.patternPaths[pattern.patternGroup]) {
+ patternlab.patternPaths[pattern.patternGroup] = {};
+ }
+
+ //only add real patterns
+ if (pattern.isPattern && !pattern.isDocPattern) {
+ patternlab.patternPaths[pattern.patternGroup][pattern.patternBaseName] =
+ pattern.name;
+ }
+ }
+
+ /**
+ * Registers the pattern with the viewAllPaths object for the appropriate patternGroup and patternSubGroup
+ * @param patternlab - global data store
+ * @param pattern - the pattern to add
+ */
+ function addToViewAllPaths(patternlab, pattern) {
+ if (!patternlab.viewAllPaths[pattern.patternGroup]) {
+ patternlab.viewAllPaths[pattern.patternGroup] = {};
+ }
+
+ if (
+ !patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup]
+ ) {
+ patternlab.viewAllPaths[pattern.patternGroup][
+ pattern.patternSubGroup
+ ] = {};
+ }
+
+ //note these retain any number prefixes if present, because these paths match the filesystem
+ patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup] =
+ pattern.patternType + '-' + pattern.patternSubType;
+
+ //add all if it does not exist yet
+ if (!patternlab.viewAllPaths[pattern.patternGroup].all) {
+ patternlab.viewAllPaths[pattern.patternGroup].all = pattern.patternType;
+ }
+ }
+
+ /**
+ * Returns whether or not the pattern should be excluded from direct rendering or navigation on the front end
+ * @param pattern - the pattern to test for inclusion/exclusion
+ * @param patternlab - global data store
+ * @param uikit - the current uikit being built
+ * @returns boolean - whether or not the pattern is excluded
+ */
+ function isPatternExcluded(pattern, patternlab, uikit) {
+ let isOmitted;
+
+ // skip patterns that the uikit does not want to render
+ isOmitted = uikitExcludePattern(pattern, uikit);
+ if (isOmitted) {
+ logger.info(
+ `Omitting ${pattern.patternPartial} from styleguide patterns because its pattern state or tag is excluded within ${uikit.name}.`
+ );
+ return true;
+ }
+
+ // skip underscore-prefixed files
+ isOmitted = pattern.isPattern && pattern.fileName.charAt(0) === '_';
+ if (isOmitted) {
+ logger.info(
+ `Omitting ${pattern.patternPartial} from styleguide patterns because it has an underscore prefix.`
+ );
+ return true;
+ }
+
+ //this is meant to be a homepage that is not present anywhere else
+ isOmitted = pattern.patternPartial === patternlab.config.defaultPattern;
+ if (isOmitted) {
+ logger.info(
+ `Omitting ${pattern.patternPartial} from styleguide patterns because it is defined as a defaultPattern.`
+ );
+ patternlab.defaultPattern = pattern;
+ return true;
+ }
+
+ //this pattern is contained with a directory prefixed with an underscore (a handy way to hide whole directories from the nav
+ isOmitted =
+ pattern.relPath.charAt(0) === '_' ||
+ pattern.relPath.indexOf(path.sep + '_') > -1;
+ if (isOmitted) {
+ logger.info(
+ `Omitting ${pattern.patternPartial} from styleguide patterns because its contained within an underscored directory.`
+ );
+ return true;
+ }
+
+ //this pattern is a head or foot pattern
+ isOmitted = pattern.isMetaPattern;
+ if (isOmitted) {
+ logger.info(
+ `Omitting ${pattern.patternPartial} from styleguide patterns because its a meta pattern.`
+ );
+ return true;
+ }
+
+ //yay, let's include this on the front end
+ return isOmitted;
+ }
+
+ /**
+ * For the given pattern, find or construct the view-all pattern block for the group
+ * @param pattern - the pattern to derive our documentation pattern from
+ * @param patternlab - global data store
+ * @param isSubtypePattern - whether or not this is a subtypePattern or a typePattern (typePatterns not supported yet)
+ * @returns the found or created pattern object
+ */
+ function injectDocumentationBlock(pattern, patternlab, isSubtypePattern) {
+ //first see if loadPattern processed one already
+ let docPattern =
+ patternlab.subtypePatterns[
+ pattern.patternGroup +
+ (isSubtypePattern ? '-' + pattern.patternSubGroup : '')
+ ];
+ if (docPattern) {
+ docPattern.isDocPattern = true;
+ docPattern.order = -Number.MAX_SAFE_INTEGER;
+ return docPattern;
+ }
+
+ //if not, create one now
+ docPattern = new Pattern.createEmpty(
+ {
+ name: pattern.flatPatternPath,
+ patternName: isSubtypePattern
+ ? pattern.patternSubGroup
+ : pattern.patternGroup,
+ patternDesc: '',
+ patternPartial:
+ 'viewall-' +
+ pattern.patternGroup +
+ (isSubtypePattern ? '-' + pattern.patternSubGroup : ''),
+ patternSectionSubtype: isSubtypePattern,
+ patternLink: pattern.flatPatternPath + path.sep + 'index.html',
+ isPattern: false,
+ engine: null,
+ flatPatternPath: pattern.flatPatternPath,
+ isDocPattern: true,
+ order: -Number.MAX_SAFE_INTEGER,
+ },
+ patternlab
+ );
+ return docPattern;
+ }
+
+ /**
+ * Registers flat patterns with the patternTypes object
+ * This is a new menu group like atoms
+ * @param patternlab - global data store
+ * @param pattern - the pattern to register
+ */
+ function addPatternType(patternlab, pattern) {
+ patternlab.patternTypes.push({
+ patternTypeLC: pattern.patternGroup.toLowerCase(),
+ patternTypeUC:
+ pattern.patternGroup.charAt(0).toUpperCase() +
+ pattern.patternGroup.slice(1),
+ patternType: pattern.patternType,
+ patternTypeDash: pattern.patternGroup, //todo verify
+ patternTypeItems: [],
+ });
+ }
+
+ /**
+ * Return the patternType object for the given pattern. Exits application if not found.
+ * @param patternlab - global data store
+ * @param pattern - the pattern to derive the pattern Type from
+ * @returns the found pattern type object
+ */
+ function getPatternType(patternlab, pattern) {
+ const patternType = _.find(patternlab.patternTypes, [
+ 'patternType',
+ pattern.patternType,
+ ]);
+
+ if (!patternType) {
+ logger.error(
+ `Could not find patternType ${pattern.patternType}. This is a critical error.`
+ );
+ }
+
+ return patternType;
+ }
+
+ /**
+ * Return the patternSubType object for the given pattern. Exits application if not found.
+ * @param patternlab - global data store
+ * @param pattern - the pattern to derive the pattern subType from
+ * @returns the found patternSubType object
+ */
+ function getPatternSubType(patternlab, pattern) {
+ const patternType = getPatternType(patternlab, pattern);
+ const patternSubType = _.find(patternType.patternTypeItems, [
+ 'patternSubtype',
+ pattern.patternSubType,
+ ]);
+
+ if (!patternSubType) {
+ logger.error(
+ `Could not find patternType ${pattern.patternType}-${pattern.patternType}. This is a critical error.`
+ );
+ }
+
+ return patternSubType;
+ }
+
+ /**
+ * Registers the pattern with the appropriate patternType.patternTypeItems object
+ * This is a new menu group like atoms/global
+ * @param patternlab - global data store
+ * @param pattern - the pattern to register
+ */
+ function addPatternSubType(patternlab, pattern) {
+ const newSubType = {
+ patternSubtypeLC: pattern.patternSubGroup.toLowerCase(),
+ patternSubtypeUC:
+ pattern.patternSubGroup.charAt(0).toUpperCase() +
+ pattern.patternSubGroup.slice(1),
+ patternSubtype: pattern.patternSubType,
+ patternSubtypeDash: pattern.patternSubGroup, //todo verify
+ patternSubtypeItems: [],
+ };
+ const patternType = getPatternType(patternlab, pattern);
+ const insertIndex = _.sortedIndexBy(
+ patternType.patternTypeItems,
+ newSubType,
+ 'patternSubtype'
+ );
+ patternType.patternTypeItems.splice(insertIndex, 0, newSubType);
+ }
+
+ /**
+ * Creates a patternSubTypeItem object from a pattern
+ * This is a menu item you click on
+ * @param pattern - the pattern to derive the subtypeitem from
+ * @returns {{patternPartial: string, patternName: (*|string), patternState: string, patternSrcPath: string, patternPath: string}}
+ */
+ function createPatternSubTypeItem(pattern) {
+ let patternPath = '';
+ if (pattern.isFlatPattern) {
+ patternPath =
+ pattern.flatPatternPath +
+ '-' +
+ pattern.fileName +
+ '/' +
+ pattern.flatPatternPath +
+ '-' +
+ pattern.fileName +
+ '.html';
+ } else {
+ patternPath =
+ pattern.flatPatternPath + '/' + pattern.flatPatternPath + '.html';
+ }
+
+ return {
+ patternPartial: pattern.patternPartial,
+ patternName: pattern.patternName,
+ patternState: pattern.patternState,
+ patternSrcPath: encodeURI(pattern.subdir + '/' + pattern.fileName),
+ patternPath: patternPath,
+ order: pattern.order,
+ };
+ }
+
+ /**
+ * Registers the pattern with the appropriate patternType.patternSubType.patternSubtypeItems array
+ * These are the actual menu items you click on
+ * @param patternlab - global data store
+ * @param pattern - the pattern to derive the subtypeitem from
+ * @param createViewAllVariant - whether or not to create the special view all item
+ */
+ function addPatternSubTypeItem(
+ patternlab,
+ pattern,
+ createSubtypeViewAllVarient
+ ) {
+ let newSubTypeItem;
+
+ if (createSubtypeViewAllVarient) {
+ newSubTypeItem = {
+ patternPartial:
+ 'viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup,
+ patternName: 'View All',
+ patternPath: encodeURI(pattern.flatPatternPath + '/index.html'),
+ patternType: pattern.patternType,
+ patternSubtype: pattern.patternSubtype,
+ order: 0,
+ };
+ } else {
+ newSubTypeItem = createPatternSubTypeItem(pattern);
+ }
+
+ const patternSubType = getPatternSubType(patternlab, pattern);
+ patternSubType.patternSubtypeItems.push(newSubTypeItem);
+ patternSubType.patternSubtypeItems = _.sortBy(
+ patternSubType.patternSubtypeItems,
+ ['order', 'name']
+ );
+ }
+
+ /**
+ * Registers flat patterns to the appropriate type
+ * @param patternlab - global data store
+ * @param pattern - the pattern to add
+ */
+ function addPatternItem(patternlab, pattern, isViewAllVariant) {
+ const patternType = getPatternType(patternlab, pattern);
+ if (!patternType) {
+ logger.error(
+ `Could not find patternType ${pattern.patternType}. This is a critical error.`
+ );
+ }
+
+ if (!patternType.patternItems) {
+ patternType.patternItems = [];
+ }
+
+ if (isViewAllVariant) {
+ if (!pattern.isFlatPattern) {
+ //todo: it'd be nice if we could get this into createPatternSubTypeItem someday
+ patternType.patternItems.push({
+ patternPartial: 'viewall-' + pattern.patternGroup + '-all',
+ patternName: 'View All',
+ patternPath: encodeURI(pattern.patternType + '/index.html'),
+ order: -Number.MAX_SAFE_INTEGER,
+ });
+ }
+ } else {
+ patternType.patternItems.push(createPatternSubTypeItem(pattern));
+ }
+ patternType.patternItems = _.sortBy(patternType.patternItems, [
+ 'order',
+ 'name',
+ ]);
+ }
+
+ // function getPatternItems(patternlab, patternType) {
+ // var patternType = _.find(patternlab.patternTypes, ['patternTypeLC', patternType]);
+ // if (patternType) {
+ // return patternType.patternItems;
+ // }
+ // return [];
+ // }
+
+ /**
+ * Sorts patterns based on order property found within pattern markdown, falling back on name.
+ * @param patternsArray - patterns to sort
+ * @returns sorted patterns
+ */
+ function sortPatterns(patternsArray) {
+ return patternsArray.sort(function(a, b) {
+ let aOrder = parseInt(a.order, 10);
+ const bOrder = parseInt(b.order, 10);
+
+ if (aOrder === NaN) {
+ aOrder = Number.MAX_SAFE_INTEGER;
+ }
+
+ if (bOrder === NaN) {
+ aOrder = Number.MAX_SAFE_INTEGER;
+ }
+
+ //alwasy return a docPattern first
+ if (a.isDocPattern && !b.isDocPattern) {
+ return -1;
+ }
+
+ if (!a.isDocPattern && b.isDocPattern) {
+ return 1;
+ }
+
+ //use old alphabetical ordering if we have nothing else to use
+ //pattern.order will be Number.MAX_SAFE_INTEGER if never defined by markdown, or markdown parsing fails
+ if (
+ aOrder === Number.MAX_SAFE_INTEGER &&
+ bOrder === Number.MAX_SAFE_INTEGER
+ ) {
+ if (a.name > b.name) {
+ return 1;
+ }
+ if (a.name < b.name) {
+ return -1;
+ }
+ }
+
+ //if we get this far, we can sort safely
+ if (aOrder && bOrder) {
+ if (aOrder > bOrder) {
+ return 1;
+ }
+ if (aOrder < bOrder) {
+ return -1;
+ }
+ }
+ return 0;
+ });
+ }
+
+ /**
+ * Returns an object representing how the front end styleguide and navigation is structured
+ * @param patternlab - global data store
+ * @param uikit - the current uikit being built
+ * @returns ptterns grouped by type -> subtype like atoms -> global -> pattern, pattern, pattern
+ */
+ function groupPatterns(patternlab, uikit) {
+ const groupedPatterns = {
+ patternGroups: {},
+ };
+
+ _.forEach(patternlab.patterns, function(pattern) {
+ //ignore patterns we can omit from rendering directly
+ pattern.omitFromStyleguide = isPatternExcluded(
+ pattern,
+ patternlab,
+ uikit
+ );
+ if (pattern.omitFromStyleguide) {
+ return;
+ }
+
+ if (!groupedPatterns.patternGroups[pattern.patternGroup]) {
+ groupedPatterns.patternGroups[pattern.patternGroup] = {};
+ pattern.isSubtypePattern = false;
+ addPatternType(patternlab, pattern);
+
+ //todo: Pattern Type View All and Documentation
+ //groupedPatterns.patternGroups[pattern.patternGroup]['viewall-' + pattern.patternGroup] = injectDocumentationBlock(pattern, patternlab, false);
+ addPatternItem(patternlab, pattern, true);
+ }
+
+ //continue building navigation for nested patterns
+ if (pattern.patternGroup !== pattern.patternSubGroup) {
+ if (
+ !groupedPatterns.patternGroups[pattern.patternGroup][
+ pattern.patternSubGroup
+ ]
+ ) {
+ addPatternSubType(patternlab, pattern);
+
+ pattern.isSubtypePattern = !pattern.isPattern;
+ groupedPatterns.patternGroups[pattern.patternGroup][
+ pattern.patternSubGroup
+ ] = {};
+ groupedPatterns.patternGroups[pattern.patternGroup][
+ pattern.patternSubGroup
+ ][
+ 'viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup
+ ] = injectDocumentationBlock(pattern, patternlab, true);
+
+ addToViewAllPaths(patternlab, pattern);
+ addPatternSubTypeItem(patternlab, pattern, true);
+ }
+
+ groupedPatterns.patternGroups[pattern.patternGroup][
+ pattern.patternSubGroup
+ ][pattern.patternBaseName] = pattern;
+
+ addToPatternPaths(patternlab, pattern);
+ addPatternSubTypeItem(patternlab, pattern);
+ } else {
+ addPatternItem(patternlab, pattern);
+ addToPatternPaths(patternlab, pattern);
+ }
+ });
+
+ return groupedPatterns;
+ }
+
+ /**
+ * Takes a set of patterns and builds a viewall HTML page for them
+ * Used by the type and subtype viewall sets
+ * @param patternlab - global data store
+ * @param patterns - the set of patterns to build the viewall page for
+ * @param patternPartial - a key used to identify the viewall page
+ * @returns A promise which resolves with the HTML
+ */
+ function buildViewAllHTML(patternlab, patterns, patternPartial, uikit) {
+ return render(
+ Pattern.createEmpty({ extendedTemplate: uikit.viewAll }),
+ {
+ //data
+ partials: patterns,
+ patternPartial: 'viewall-' + patternPartial,
+ cacheBuster: patternlab.cacheBuster,
+ },
+ {
+ //templates
+ patternSection: uikit.patternSection,
+ patternSectionSubtype: uikit.patternSectionSubType,
+ }
+ ).catch(reason => {
+ console.log(reason);
+ logger.error('Error building buildViewAllHTML');
+ });
+ }
+
+ /**
+ * Constructs viewall pages for each set of grouped patterns
+ * @param mainPageHeadHtml - the already built main page HTML
+ * @param patternlab - global data store
+ * @param styleguidePatterns - the grouped set of patterns
+ * @returns every built pattern and set of viewall patterns, so the styleguide can use it
+ */
+ function buildViewAllPages(
+ mainPageHeadHtml,
+ patternlab,
+ styleguidePatterns,
+ uikit
+ ) {
+ const paths = patternlab.config.paths;
+ let patterns = [];
+ let writeViewAllFile = true;
+
+ //loop through the grouped styleguide patterns, building at each level
+ const allPatternTypePromises = _.map(
+ styleguidePatterns.patternGroups,
+ (patternGroup, patternType) => {
+ let typePatterns = [];
+ let styleguideTypePatterns = [];
+ const styleGuideExcludes =
+ patternlab.config.styleGuideExcludes ||
+ patternlab.config.styleguideExcludes;
+ const subTypePromises = _.map(
+ _.values(patternGroup),
+ (patternSubtypes, patternSubtype, originalPatternGroup) => {
+ let p;
+ const samplePattern = _.find(patternSubtypes, st => {
+ return !st.patternPartial.startsWith('viewall-');
+ });
+ const patternName = Object.keys(
+ _.values(originalPatternGroup)[patternSubtype]
+ )[1];
+ const patternPartial =
+ patternType + '-' + samplePattern.patternSubType;
+
+ //do not create a viewall page for flat patterns
+ if (patternType === patternName) {
+ writeViewAllFile = false;
+ logger.debug(
+ `skipping ${patternType} as flat patterns do not have view all pages`
+ );
+ return Promise.resolve();
+ }
+
+ //render the footer needed for the viewall template
+ return buildFooter(patternlab, `viewall-${patternPartial}`, uikit)
+ .then(footerHTML => {
+ //render the viewall template by finding these smallest subtype-grouped patterns
+ const subtypePatterns = sortPatterns(_.values(patternSubtypes));
+
+ //determine if we should write at this time by checking if these are flat patterns or grouped patterns
+ p = _.find(subtypePatterns, function(pat) {
+ return pat.isDocPattern;
+ });
+
+ //determine if we should omit this subpatterntype completely from the viewall page
+ const omitPatternType =
+ styleGuideExcludes &&
+ styleGuideExcludes.length &&
+ _.some(styleGuideExcludes, function(exclude) {
+ return exclude === patternType + '/' + patternName;
+ });
+ if (omitPatternType) {
+ logger.debug(
+ `Omitting ${patternType}/${patternName} from building a viewall page because its patternSubGroup is specified in styleguideExcludes.`
+ );
+ } else {
+ styleguideTypePatterns = styleguideTypePatterns.concat(
+ subtypePatterns
+ );
+ }
+
+ typePatterns = typePatterns.concat(subtypePatterns);
+
+ //render the viewall template for the subtype
+ return buildViewAllHTML(
+ patternlab,
+ subtypePatterns,
+ patternPartial,
+ uikit
+ )
+ .then(viewAllHTML => {
+ return fs.outputFile(
+ path.join(
+ process.cwd(),
+ uikit.outputDir,
+ paths.public.patterns +
+ p.flatPatternPath +
+ '/index.html'
+ ),
+ mainPageHeadHtml + viewAllHTML + footerHTML
+ );
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error building ViewAllHTML');
+ });
+ })
+ .then(() => {
+ //do not create a viewall page for flat patterns
+ if (!writeViewAllFile || !p) {
+ logger.debug(
+ `skipping ${patternType} as flat patterns do not have view all pages`
+ );
+ return Promise.resolve();
+ }
+
+ //render the footer needed for the viewall template
+ return buildFooter(
+ patternlab,
+ 'viewall-' + patternType + '-all',
+ uikit
+ )
+ .then(footerHTML => {
+ //add any flat patterns
+ //todo this isn't quite working yet
+ //typePatterns = typePatterns.concat(getPatternItems(patternlab, patternType));
+
+ //get the appropriate patternType
+ const anyPatternOfType = _.find(typePatterns, function(
+ pat
+ ) {
+ return pat.patternType && pat.patternType !== '';
+ });
+
+ if (!anyPatternOfType) {
+ logger.debug(
+ `skipping ${patternType} as flat patterns do not have view all pages`
+ );
+ return Promise.resolve();
+ }
+
+ //render the viewall template for the type
+ return buildViewAllHTML(
+ patternlab,
+ typePatterns,
+ patternType,
+ uikit
+ )
+ .then(viewAllHTML => {
+ fs.outputFileSync(
+ path.join(
+ process.cwd(),
+ uikit.outputDir,
+ paths.public.patterns +
+ anyPatternOfType.patternType +
+ '/index.html'
+ ),
+ mainPageHeadHtml + viewAllHTML + footerHTML
+ );
+
+ //determine if we should omit this patterntype completely from the viewall page
+ const omitPatternType =
+ styleGuideExcludes &&
+ styleGuideExcludes.length &&
+ _.some(styleGuideExcludes, function(exclude) {
+ return exclude === patternType;
+ });
+ if (omitPatternType) {
+ logger.debug(
+ `Omitting ${patternType} from building a viewall page because its patternGroup is specified in styleguideExcludes.`
+ );
+ } else {
+ patterns = patterns.concat(styleguideTypePatterns);
+ }
+ return Promise.resolve(patterns);
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error building ViewAllHTML');
+ });
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error building footerHTML');
+ });
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('Error building footer HTML');
+ });
+ }
+ );
+
+ return Promise.all(subTypePromises).catch(reason => {
+ console.log(reason);
+ logger.error('Error during buildViewAllPages');
+ });
+ }
+ );
+
+ return Promise.all(allPatternTypePromises).catch(reason => {
+ console.log(reason);
+ logger.error('Error during buildViewAllPages');
+ });
+ }
+
+ /**
+ * Reset any global data we use between builds to guard against double adding things
+ */
+ function resetUIBuilderState(patternlab) {
+ patternlab.patternPaths = {};
+ patternlab.viewAllPaths = {};
+ patternlab.patternTypes = [];
+ }
+
+ /**
+ * The main entry point for ui_builder
+ * @param patternlab - global data store
+ * @returns {Promise} a promise fulfilled when build is complete
+ */
+ function buildFrontend(patternlab) {
+ resetUIBuilderState(patternlab);
+
+ const paths = patternlab.config.paths;
+
+ const uikitPromises = _.map(patternlab.uikits, uikit => {
+ //determine which patterns should be included in the front-end rendering
+ const styleguidePatterns = groupPatterns(patternlab, uikit);
+
+ return new Promise(resolve => {
+ //set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header
+ const headerPromise = render(
+ Pattern.createEmpty({ extendedTemplate: uikit.header }),
+ {
+ cacheBuster: patternlab.cacheBuster,
+ }
+ )
+ .then(headerPartial => {
+ const headFootData = patternlab.data;
+ headFootData.patternLabHead = headerPartial;
+ headFootData.cacheBuster = patternlab.cacheBuster;
+ return render(patternlab.userHead, headFootData);
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('error during header render()');
+ });
+
+ //set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer
+ const footerPromise = render(
+ Pattern.createEmpty({ extendedTemplate: uikit.footer }),
+ {
+ patternData: '{}',
+ cacheBuster: patternlab.cacheBuster,
+ }
+ )
+ .then(footerPartial => {
+ const headFootData = patternlab.data;
+ headFootData.patternLabFoot = footerPartial;
+ return render(patternlab.userFoot, headFootData);
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('error during footer render()');
+ });
+
+ return Promise.all([headerPromise, footerPromise]).then(
+ headFootPromiseResults => {
+ //build the viewall pages
+
+ return buildViewAllPages(
+ headFootPromiseResults[0],
+ patternlab,
+ styleguidePatterns,
+ uikit
+ )
+ .then(allPatterns => {
+ //todo track down why we need to make this unique in the first place
+ const uniquePatterns = _.uniq(
+ _.flatMapDeep(allPatterns, pattern => {
+ return pattern;
+ })
+ );
+
+ //add the defaultPattern if we found one
+ if (patternlab.defaultPattern) {
+ uniquePatterns.push(patternlab.defaultPattern);
+ addToPatternPaths(patternlab, patternlab.defaultPattern);
+ }
+
+ //build the main styleguide page
+ return render(
+ Pattern.createEmpty({
+ extendedTemplate: uikit.viewAll,
+ }),
+ {
+ partials: uniquePatterns,
+ },
+ {
+ patternSection: uikit.patternSection,
+ patternSectionSubtype: uikit.patternSectionSubType,
+ }
+ )
+ .then(styleguideHtml => {
+ fs.outputFileSync(
+ path.resolve(
+ path.join(
+ process.cwd(),
+ uikit.outputDir,
+ paths.public.styleguide,
+ 'html/styleguide.html'
+ )
+ ),
+ headFootPromiseResults[0] +
+ styleguideHtml +
+ headFootPromiseResults[1]
+ );
+
+ logger.info('Built Pattern Lab front end');
+
+ //move the index file from its asset location into public root
+ let patternlabSiteHtml;
+ try {
+ patternlabSiteHtml = fs.readFileSync(
+ path.resolve(
+ path.join(
+ uikit.modulePath,
+ paths.source.styleguide,
+ 'index.html'
+ )
+ ),
+ 'utf8'
+ );
+ } catch (err) {
+ logger.error(
+ `Could not load one or more styleguidekit assets from ${paths.source.styleguide}`
+ );
+ }
+ fs.outputFileSync(
+ path.resolve(
+ path.join(
+ process.cwd(),
+ uikit.outputDir,
+ paths.public.root,
+ 'index.html'
+ )
+ ),
+ patternlabSiteHtml
+ );
+
+ //write out patternlab.data object to be read by the client
+ exportData(patternlab);
+ resolve();
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('error during buildFrontend()');
+ });
+ })
+ .catch(reason => {
+ console.log(reason);
+ logger.error('error during buildViewAllPages()');
+ });
+ }
+ );
+ });
+ });
+ return Promise.all(uikitPromises);
+ }
+
+ return {
+ buildFrontend: function(patternlab) {
+ return buildFrontend(patternlab);
+ },
+ isPatternExcluded: function(pattern, patternlab, uikit) {
+ return isPatternExcluded(pattern, patternlab, uikit);
+ },
+ groupPatterns: function(patternlab, uikit) {
+ return groupPatterns(patternlab, uikit);
+ },
+ resetUIBuilderState: function(patternlab) {
+ resetUIBuilderState(patternlab);
+ },
+ buildViewAllPages: function(
+ mainPageHeadHtml,
+ patternlab,
+ styleguidePatterns,
+ uikit
+ ) {
+ return buildViewAllPages(
+ mainPageHeadHtml,
+ patternlab,
+ styleguidePatterns,
+ uikit
+ );
+ },
+ };
+};
+
+module.exports = ui_builder;
diff --git a/packages/core/src/lib/uikitExcludePattern.js b/packages/core/src/lib/uikitExcludePattern.js
new file mode 100644
index 000000000..61cddcb57
--- /dev/null
+++ b/packages/core/src/lib/uikitExcludePattern.js
@@ -0,0 +1,7 @@
+'use strict';
+
+const uikitExcludePattern = (pattern, uikit) => {
+ const state = pattern.patternState;
+ return uikit.excludedPatternStates.includes(state);
+};
+module.exports = uikitExcludePattern;
diff --git a/packages/core/src/lib/watchAssets.js b/packages/core/src/lib/watchAssets.js
new file mode 100644
index 000000000..f087b8a28
--- /dev/null
+++ b/packages/core/src/lib/watchAssets.js
@@ -0,0 +1,70 @@
+'use strict';
+
+const path = require('path');
+const _ = require('lodash');
+const chokidar = require('chokidar');
+
+const logger = require('./log');
+
+let copyFile = require('./copyFile'); // eslint-disable-line prefer-const
+
+function onWatchTripped(patternlab, p, assetBase, basePath, dir, copyOptions) {
+ const subPath = p.replace(assetBase, '');
+ _.each(patternlab.uikits, uikit => {
+ const destination = path.resolve(
+ basePath,
+ uikit.outputDir,
+ dir.public + '/' + subPath
+ );
+ copyFile(p, destination, copyOptions);
+ });
+}
+
+const watchAssets = (
+ patternlab,
+ basePath,
+ dir,
+ key,
+ copyOptions,
+ watchOnce
+) => {
+ const assetBase = path.resolve(basePath, dir.source);
+ const assetsToIgnore = patternlab.config.transformedAssetTypes
+ ? patternlab.config.transformedAssetTypes.join('|')
+ : '';
+ logger.debug(`Pattern Lab is watching ${assetBase} for changes`);
+
+ if (patternlab.watchers[key]) {
+ patternlab.watchers[key].close();
+ }
+ const assetWatcher = chokidar.watch(assetBase, {
+ // *ignored* combines file types that the wrapper is watching, passed to pl config
+ // regex string escapes backslashes for JS
+ // second part of regex is holdover from existing ignore regex for '/index.html' and other
+ // files meant to be ignored, not based on file type
+ ignored: new RegExp(
+ `(?:(?:.*\\.(?:${assetsToIgnore})$)|(?:(^|[\\/\\\\])\\..))`,
+ 'i'
+ ),
+ // ignored: /(^|[\/\\])\../, //old version
+ ignoreInitial: false,
+ awaitWriteFinish: {
+ stabilityThreshold: 200,
+ pollInterval: 100,
+ },
+ persistent: !watchOnce,
+ });
+
+ //watch for changes and copy
+ assetWatcher
+ .on('add', p => {
+ onWatchTripped(patternlab, p, assetBase, basePath, dir, copyOptions);
+ })
+ .on('change', p => {
+ onWatchTripped(patternlab, p, assetBase, basePath, dir, copyOptions);
+ });
+
+ patternlab.watchers[key] = assetWatcher;
+};
+
+module.exports = watchAssets;
diff --git a/packages/core/src/lib/watchPatternLabFiles.js b/packages/core/src/lib/watchPatternLabFiles.js
new file mode 100644
index 000000000..f5ab0c1aa
--- /dev/null
+++ b/packages/core/src/lib/watchPatternLabFiles.js
@@ -0,0 +1,145 @@
+'use strict';
+const _ = require('lodash');
+const path = require('path');
+
+const logger = require('./log');
+const events = require('./events');
+const pm = require('./plugin_manager');
+const pluginMananger = new pm();
+
+let chokidar = require('chokidar'); // eslint-disable-line prefer-const
+
+const watchPatternLabFiles = (
+ patternlab,
+ assetDirectories,
+ basePath,
+ watchOnce
+) => {
+ // watch global structures, such as _data/* and _meta/
+ const globalSources = [
+ assetDirectories.source.data,
+ assetDirectories.source.meta,
+ ];
+ const globalPaths = globalSources.map(globalSource =>
+ path.join(path.resolve(basePath, globalSource), '*')
+ );
+
+ _.each(globalPaths, globalPath => {
+ logger.debug(`Pattern Lab is watching ${globalPath} for changes!`);
+
+ if (patternlab.watchers[globalPath]) {
+ patternlab.watchers[globalPath].close();
+ }
+
+ const globalWatcher = chokidar.watch(path.resolve(globalPath), {
+ ignored: /(^|[\/\\])\../,
+ ignoreInitial: true,
+ awaitWriteFinish: {
+ stabilityThreshold: 200,
+ pollInterval: 100,
+ },
+ persistent: !watchOnce,
+ });
+
+ //watch for changes and rebuild
+ globalWatcher
+ .on('addDir', async p => {
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_GLOBAL_CHANGE,
+ {
+ file: p,
+ }
+ );
+ })
+ .on('add', async p => {
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_GLOBAL_CHANGE,
+ {
+ file: p,
+ }
+ );
+ })
+ .on('change', async p => {
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_GLOBAL_CHANGE,
+ {
+ file: p,
+ }
+ );
+ });
+
+ patternlab.watchers[globalPath] = globalWatcher;
+ });
+
+ // watch patterns
+ const baseFileExtensions = ['.json', '.yml', '.yaml', '.md'];
+ const patternWatches = baseFileExtensions
+ .concat(patternlab.engines.getSupportedFileExtensions())
+ .map(dotExtension =>
+ path.join(
+ path.resolve(basePath, assetDirectories.source.patterns),
+ `/**/*${dotExtension}`
+ )
+ );
+ _.each(patternWatches, patternWatchPath => {
+ logger.debug(
+ `Pattern Lab is watching ${patternWatchPath} for changes - local!`
+ );
+
+ if (patternlab.watchers[patternWatchPath]) {
+ patternlab.watchers[patternWatchPath].close();
+ }
+
+ const patternWatcher = chokidar.watch(path.resolve(patternWatchPath), {
+ ignored: /(^|[\/\\])\../,
+ ignoreInitial: true,
+ awaitWriteFinish: {
+ stabilityThreshold: 200,
+ pollInterval: 100,
+ },
+ persistent: !watchOnce,
+ });
+
+ //watch for changes and rebuild
+ patternWatcher
+ .on('addDir', async p => {
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_PATTERN_CHANGE,
+ {
+ file: p,
+ }
+ );
+ })
+ .on('add', async p => {
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_PATTERN_CHANGE,
+ {
+ file: p,
+ }
+ );
+ })
+ .on('change', async p => {
+ await pluginMananger.raiseEvent(
+ patternlab,
+ events.PATTERNLAB_PATTERN_CHANGE,
+ {
+ file: p,
+ }
+ );
+ });
+
+ patternlab.watchers[patternWatchPath] = patternWatcher;
+ });
+
+ logger.info(
+ `Pattern Lab is watching for changes to files under ${assetDirectories.source.root}`
+ );
+ return Promise.resolve();
+};
+
+module.exports = watchPatternLabFiles;
diff --git a/packages/core/test/addPattern_tests.js b/packages/core/test/addPattern_tests.js
new file mode 100644
index 000000000..0957e43e0
--- /dev/null
+++ b/packages/core/test/addPattern_tests.js
@@ -0,0 +1,51 @@
+'use strict';
+
+const tap = require('tap');
+
+const addPattern = require('../src/lib/addPattern');
+var Pattern = require('../src/lib/object_factory').Pattern;
+const util = require('./util/test_utils.js');
+
+const patterns_dir = './test/files/_patterns';
+
+tap.test(
+ 'addPattern - adds pattern extended template to patternlab partial object',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var pattern = new Pattern('00-test/01-bar.mustache');
+ pattern.extendedTemplate = 'barExtended';
+ pattern.template = 'bar';
+
+ //act
+ addPattern(pattern, patternlab);
+
+ //assert
+ test.equals(patternlab.patterns.length, 1);
+ test.equals(patternlab.partials['test-bar'] !== undefined, true);
+ test.equals(patternlab.partials['test-bar'], 'barExtended');
+ test.end();
+ }
+);
+
+tap.test(
+ 'addPattern - adds pattern template to patternlab partial object if extendedtemplate does not exist yet',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var pattern = new Pattern('00-test/01-bar.mustache');
+ pattern.extendedTemplate = undefined;
+ pattern.template = 'bar';
+
+ //act
+ addPattern(pattern, patternlab);
+
+ //assert
+ test.equals(patternlab.patterns.length, 1);
+ test.equals(patternlab.partials['test-bar'] !== undefined, true);
+ test.equals(patternlab.partials['test-bar'], 'bar');
+ test.end();
+ }
+);
diff --git a/packages/core/test/annotation_exporter_tests.js b/packages/core/test/annotation_exporter_tests.js
new file mode 100644
index 000000000..68c7b2f48
--- /dev/null
+++ b/packages/core/test/annotation_exporter_tests.js
@@ -0,0 +1,85 @@
+'use strict';
+
+var tap = require('tap');
+
+var extend = require('util')._extend;
+var anPath = `${__dirname}/files/`;
+
+function createFakePatternLab(anPath, customProps) {
+ var pl = {
+ config: {
+ paths: {
+ source: {
+ annotations: anPath,
+ },
+ },
+ },
+ };
+
+ return extend(pl, customProps);
+}
+
+var patternlab = createFakePatternLab(anPath);
+var ae = require('../src/lib/annotation_exporter')(patternlab);
+
+tap.test('converts old JS annotations into new format', function(test) {
+ //arrange
+ //act
+ var annotations = ae.gatherJS();
+
+ //assert
+ test.equals(annotations.length, 2);
+ test.equals(annotations[1].el, '.logo');
+ test.equals(annotations[1].title, 'Logo');
+ test.equals(
+ annotations[1].comment,
+ 'The logo image is an SVG file, which ensures that the logo displays crisply even on high resolution displays. A PNG fallback is provided for browsers that don\'t support SVG images.
Further reading: Optimizing Web Experiences for High Resolution Screens
'
+ );
+
+ test.end();
+});
+
+tap.test('converts new markdown annotations into an array', function(test) {
+ //arrange
+ //act
+ var annotations = ae.gatherMD();
+
+ //assert
+ test.equals(annotations.length, 3);
+ test.equals(annotations[1].el, '.logo');
+ test.equals(annotations[1].title, 'Logo');
+ test.equals(
+ annotations[1].comment.replace(/\r?\n|\r/gm, ''),
+ 'The logo image is an SVG file.
'
+ );
+
+ test.end();
+});
+
+tap.test('merges both annotation methods into one array', function(test) {
+ //arrange
+
+ //act
+ var annotations = ae.gather();
+
+ //assert
+ test.equals(annotations.length, 3);
+ test.equals(annotations[2].el, '#nav');
+ test.equals(annotations[2].title, 'Navigation');
+ test.equals(
+ annotations[2].comment.replace(/\r?\n|\r/gm, ''),
+ 'Navigation for adaptive web experiences can be tricky. Refer to these repsonsive patterns when evaluating solutions.
'
+ );
+
+ test.end();
+});
+
+tap.test('when there are 0 annotation files', function(test) {
+ var emptyAnPath = './test/files/empty/';
+ var patternlab2 = createFakePatternLab(emptyAnPath);
+ var ae2 = require('../src/lib/annotation_exporter')(patternlab2);
+
+ var annotations = ae2.gather();
+ test.equals(annotations.length, 0);
+ test.end();
+});
diff --git a/packages/core/test/buildListItems_tests.js b/packages/core/test/buildListItems_tests.js
new file mode 100644
index 000000000..757c8f1aa
--- /dev/null
+++ b/packages/core/test/buildListItems_tests.js
@@ -0,0 +1,85 @@
+'use strict';
+
+const tap = require('tap');
+const rewire = require('rewire');
+
+const listItems = require('./files/_data/listitems.json');
+const buildlistItems = rewire('../src/lib/buildListItems');
+
+const _Mock = {
+ shuffle: function(list) {
+ return list;
+ },
+};
+
+//set our mocks in place of usual require()
+buildlistItems.__set__({
+ _: _Mock,
+});
+
+tap.test(
+ 'buildlistItems transforms container of listItems with one value',
+ test => {
+ // do this to avoid the shuffling for now
+ const container = Object.assign({}, { listitems: { '1': listItems['1'] } });
+ buildlistItems(container);
+ test.same(container.listitems, {
+ 'listItems-one': [
+ {
+ title: 'tA',
+ description: 'dA',
+ message: 'mA',
+ },
+ ],
+ });
+ test.end();
+ }
+);
+
+tap.test(
+ 'buildlistItems transforms container of listItems with three values',
+ test => {
+ // do this to avoid the shuffling for now
+ const container = { listitems: listItems };
+ buildlistItems(container);
+ test.same(container.listitems, {
+ 'listItems-one': [
+ {
+ title: 'tA',
+ description: 'dA',
+ message: 'mA',
+ },
+ ],
+ 'listItems-two': [
+ {
+ title: 'tA',
+ description: 'dA',
+ message: 'mA',
+ },
+ {
+ title: 'tB',
+ description: 'dB',
+ message: 'mB',
+ },
+ ],
+ 'listItems-three': [
+ {
+ title: 'tA',
+ description: 'dA',
+ message: 'mA',
+ },
+ {
+ title: 'tB',
+ description: 'dB',
+ message: 'mB',
+ },
+ {
+ title: 'tC',
+ description: 'dC',
+ message: 'mC',
+ },
+ ],
+ });
+ test.end();
+ }
+);
diff --git a/packages/core/test/changes_hunter_tests.js b/packages/core/test/changes_hunter_tests.js
new file mode 100644
index 000000000..68b6c054d
--- /dev/null
+++ b/packages/core/test/changes_hunter_tests.js
@@ -0,0 +1,67 @@
+'use strict';
+
+const tap = require('tap');
+const rewire = require('rewire');
+
+const ch = rewire('../src/lib/changes_hunter');
+
+const fsMock = {
+ statSync: function() {
+ return {
+ mtime: {
+ getTime: () => {
+ return 100;
+ },
+ },
+ };
+ },
+ pathExistsSync: () => {
+ return true;
+ },
+};
+
+//set our mocks in place of usual require()
+ch.__set__({
+ fs: fsMock,
+});
+
+const changes_hunter = new ch();
+
+tap.test('checkLastModified - sets lastModified to fileTime ', function(test) {
+ //arrange
+ const mockPattern = { lastModified: 0 };
+ //act
+ changes_hunter.checkLastModified(mockPattern, {});
+
+ //assert
+ test.equals(mockPattern.lastModified, 100);
+ test.end();
+});
+
+tap.test(
+ 'checkLastModified - does not alter pattern if file not found',
+ function(test) {
+ //arrange
+ const mockPattern = { lastModified: 1010 };
+ //act
+ changes_hunter.checkLastModified(mockPattern, null);
+
+ //assert
+ test.equals(mockPattern.lastModified, 1010);
+ test.end();
+ }
+);
+
+tap.test(
+ 'checkLastModified - uses pattern.lastModified if greater than file time',
+ function(test) {
+ //arrange
+ const mockPattern = { lastModified: 101 };
+ //act
+ changes_hunter.checkLastModified(mockPattern, {});
+
+ //assert
+ test.equals(mockPattern.lastModified, 101);
+ test.end();
+ }
+);
diff --git a/packages/core/test/copier_tests.js b/packages/core/test/copier_tests.js
new file mode 100644
index 000000000..388300a01
--- /dev/null
+++ b/packages/core/test/copier_tests.js
@@ -0,0 +1,69 @@
+'use strict';
+
+var tap = require('tap');
+var rewire = require('rewire');
+var _ = require('lodash');
+var eol = require('os').EOL;
+var Pattern = require('../src/lib/object_factory').Pattern;
+var extend = require('util')._extend;
+var c = rewire('../src/lib/copier');
+var path = require('path');
+var config = require('./util/patternlab-config.json');
+
+var engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+
+//set up a global mocks - we don't want to be writing/rendering any files right now
+// var chokidarMock = {
+// watch: function (path, data, cb) { }
+// };
+
+// c.__set__({
+// 'chokidar': chokidarMock,
+// });
+
+const copier = c();
+
+function createFakePatternLab(customProps) {
+ var pl = {
+ config: {
+ paths: {
+ source: {
+ img: './test/img',
+ css: './test/css',
+ },
+ public: {
+ img: './test/output/img',
+ css: './test/output/css',
+ },
+ },
+ styleGuideExcludes: [],
+ logLevel: 'quiet',
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ },
+ data: {},
+ };
+ return extend(pl, customProps);
+}
+
+tap.test(
+ 'transformConfigPaths takes configuration.paths() and maps to a better key store',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({});
+
+ //act
+ var result = copier.transformConfigPaths(patternlab.config.paths);
+
+ //assert
+ test.equals(result.img.source, './test/img');
+ test.equals(result.img.public, './test/output/img');
+ test.equals(result.css.source, './test/css');
+ test.equals(result.css.public, './test/output/css');
+ test.end();
+ }
+);
diff --git a/packages/core/test/data_loader_tests.js b/packages/core/test/data_loader_tests.js
new file mode 100644
index 000000000..9eaa8864b
--- /dev/null
+++ b/packages/core/test/data_loader_tests.js
@@ -0,0 +1,13 @@
+'use strict';
+
+const tap = require('tap');
+
+tap.test('loadDataFromFile - Load ', function(test) {
+ const fs = require('fs-extra'),
+ dataLoader = require('../src/lib/data_loader')(),
+ data_dir = `${__dirname}/files/_data/`;
+
+ let data = dataLoader.loadDataFromFile(data_dir + 'foo', fs);
+ test.equals(data.foo, 'bar');
+ test.end();
+});
diff --git a/packages/core/test/engine_handlebars_tests.js b/packages/core/test/engine_handlebars_tests.js
new file mode 100644
index 000000000..d268c1c63
--- /dev/null
+++ b/packages/core/test/engine_handlebars_tests.js
@@ -0,0 +1,405 @@
+'use strict';
+/*eslint-disable no-shadow*/
+
+const tap = require('tap');
+const path = require('path');
+const eol = require('os').EOL;
+
+const util = require('./util/test_utils.js');
+const loadPattern = require('../src/lib/loadPattern');
+const Pattern = require('../src/lib/object_factory').Pattern;
+const PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
+const processIterative = require('../src/lib/processIterative');
+const processRecursive = require('../src/lib/processRecursive');
+
+const testPatternsPath = path.resolve(
+ __dirname,
+ 'files',
+ '_handlebars-test-patterns'
+);
+
+const config = require('./util/patternlab-config.json');
+const engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+
+// don't run these tests unless handlebars is installed
+if (!engineLoader.handlebars) {
+ tap.test('Handlebars engine not installed, skipping tests.', function(test) {
+ test.end();
+ });
+ return;
+}
+
+// fake pattern lab constructor:
+// sets up a fake patternlab object, which is needed by the pattern processing
+// apparatus.
+function fakePatternLab() {
+ var fpl = {
+ graph: PatternGraph.empty(),
+ partials: {},
+ patterns: [],
+ footer: '',
+ header: '',
+ listitems: {},
+ data: {
+ link: {},
+ },
+ config: require('../patternlab-config.json'),
+ package: {},
+ };
+
+ // patch the pattern source so the pattern assembler can correctly determine
+ // the "subdir"
+ fpl.config.paths.source.patterns = testPatternsPath;
+
+ return fpl;
+}
+
+// function for testing sets of partials
+function testFindPartials(test, partialTests) {
+ test.plan(partialTests.length + 1);
+
+ // setup current pattern from what we would have during execution
+ // docs on partial syntax are here:
+ // http://patternlab.io/docs/pattern-including.html
+ var currentPattern = Pattern.create(
+ '01-molecules/00-testing/00-test-mol.hbs', // relative path now
+ null, // data
+ {
+ template: partialTests.join(),
+ }
+ );
+
+ // act
+ var results = currentPattern.findPartials();
+
+ // assert
+ test.equals(results.length, partialTests.length);
+ partialTests.forEach(function(testString, index) {
+ test.equals(results[index], testString);
+ });
+
+ test.end();
+}
+
+tap.test('hello world handlebars pattern renders', function(test) {
+ test.plan(1);
+
+ var patternPath = path.join('00-atoms', '00-global', '00-helloworld.hbs');
+
+ // do all the normal processing of the pattern
+ var patternlab = new fakePatternLab();
+ var helloWorldPattern = loadPattern(patternPath, patternlab);
+
+ processIterative(helloWorldPattern, patternlab).then(helloWorldPattern => {
+ processRecursive(patternPath, patternlab).then(() => {
+ helloWorldPattern.render().then(results => {
+ test.equals(results, 'Hello world!' + eol);
+ test.end();
+ });
+ });
+ });
+});
+
+tap.test(
+ 'hello worlds handlebars pattern can see the atoms-helloworld partial and renders it twice',
+ function(test) {
+ test.plan(1);
+
+ // pattern paths
+ var pattern1Path = path.join('00-atoms', '00-global', '00-helloworld.hbs');
+ var pattern2Path = path.join(
+ '00-molecules',
+ '00-global',
+ '00-helloworlds.hbs'
+ );
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal loading and processing of the pattern
+ const pattern1 = loadPattern(pattern1Path, patternlab);
+ const pattern2 = loadPattern(pattern2Path, patternlab);
+
+ Promise.all([
+ processIterative(pattern1, patternlab),
+ processIterative(pattern2, patternlab),
+ ]).then(() => {
+ processRecursive(pattern1Path, patternlab).then(() => {
+ processRecursive(pattern2Path, patternlab).then(() => {
+ // test
+ pattern2.render().then(results => {
+ test.equals(
+ results,
+ 'Hello world!' + eol + ' and Hello world!' + eol + eol
+ );
+ test.end();
+ });
+ });
+ });
+ });
+ }
+);
+
+tap.test('handlebars partials can render JSON values', function(test) {
+ test.plan(1);
+
+ // pattern paths
+ var pattern1Path = path.join(
+ '00-atoms',
+ '00-global',
+ '00-helloworld-withdata.hbs'
+ );
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ var helloWorldWithData = loadPattern(pattern1Path, patternlab);
+
+ processIterative(helloWorldWithData, patternlab).then(() => {
+ processRecursive(pattern1Path, patternlab).then(() => {
+ // test
+ helloWorldWithData.render().then(results => {
+ test.equals(
+ results,
+ 'Hello world!' +
+ eol +
+ 'Yeah, we got the subtitle from the JSON.' +
+ eol
+ );
+ test.end();
+ });
+ });
+ });
+});
+
+tap.test(
+ 'handlebars partials use the JSON environment from the calling pattern and can accept passed parameters',
+ function(test) {
+ test.plan(1);
+
+ // pattern paths
+ var atomPath = path.join(
+ '00-atoms',
+ '00-global',
+ '00-helloworld-withdata.hbs'
+ );
+ var molPath = path.join(
+ '00-molecules',
+ '00-global',
+ '00-call-atom-with-molecule-data.hbs'
+ );
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ const atom = loadPattern(atomPath, patternlab);
+ const mol = loadPattern(molPath, patternlab);
+
+ Promise.all([
+ processIterative(atom, patternlab),
+ processIterative(mol, patternlab),
+ processRecursive(atomPath, patternlab),
+ processRecursive(molPath, patternlab),
+ ]).then(() => {
+ mol.render().then(results => {
+ // test
+ test.equals(
+ results,
+ 'Call with default JSON environment: ' +
+ eol +
+ 'This is Hello world!' +
+ eol +
+ 'from the default JSON.' +
+ eol +
+ eol +
+ eol +
+ 'Call with passed parameter: ' +
+ eol +
+ 'However, this is Hello world!' +
+ eol +
+ 'from a totally different blob.' +
+ eol +
+ eol
+ );
+ });
+ });
+ }
+);
+
+tap.only('find_pattern_partials finds partials', function(test) {
+ testFindPartials(test, [
+ '{{> molecules-comment-header}}',
+ '{{> molecules-comment-header}}',
+ '{{> ' + eol + ' molecules-comment-header' + eol + '}}',
+ '{{> molecules-weird-spacing }}',
+ '{{> molecules-ba_d-cha*rs }}',
+ ]);
+});
+
+tap.test('find_pattern_partials finds verbose partials', function(test) {
+ testFindPartials(test, [
+ '{{> 01-molecules/06-components/03-comment-header.hbs }}',
+ "{{> 01-molecules/06-components/02-single-comment.hbs(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}",
+ '{{> molecules-single-comment:foo }}',
+ "{{>atoms-error(message: 'That's no moon...')}}",
+ "{{> atoms-error(message: 'That's no moon...') }}",
+ '{{> 00-atoms/00-global/06-test }}',
+ ]);
+});
+
+tap.test(
+ 'find_pattern_partials finds simple partials with parameters',
+ function(test) {
+ testFindPartials(test, [
+ "{{> molecules-single-comment(description: 'A life isn't like a garden. Perfect moments can be had, but not preserved, except in memory.') }}",
+ '{{> molecules-single-comment(description:"A life is like a "garden". Perfect moments can be had, but not preserved, except in memory.") }}',
+ ]);
+ }
+);
+
+tap.test(
+ 'find_pattern_partials finds simple partials with style modifiers',
+ function(test) {
+ testFindPartials(test, ['{{> molecules-single-comment:foo }}']);
+ }
+);
+
+tap.test(
+ 'find_pattern_partials finds partials with handlebars parameters',
+ function(test) {
+ testFindPartials(test, [
+ '{{> atoms-title title="bravo" headingLevel="2" headingSize="bravo" position="left"}}',
+ '{{> atoms-title title="bravo"' +
+ eol +
+ ' headingLevel="2"' +
+ eol +
+ ' headingSize="bravo"' +
+ eol +
+ ' position="left"}}',
+ '{{> atoms-title title="color midnight blue " headingSize="charlie"}}',
+ '{{> atoms-input label="city" required=true}}',
+ '{{> organisms-product-filter filterData}}',
+ '{{> atoms-input email required=true}}',
+ '{{> molecules-storycard variants.flex }}',
+ '{{> myPartial name=../name }}',
+ ]);
+ }
+);
+
+tap.test('find_pattern_partials finds handlebars block partials', function(
+ test
+) {
+ testFindPartials(test, ['{{#> myPartial }}']);
+});
+
+tap.only(
+ 'hidden handlebars patterns can be called by their nice names',
+ function(test) {
+ //arrange
+ const testPatternsPath = path.resolve(
+ __dirname,
+ 'files',
+ '_handlebars-test-patterns'
+ );
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var hiddenPatternPath = path.join(
+ '00-atoms',
+ '00-global',
+ '_00-hidden.hbs'
+ );
+ var testPatternPath = path.join(
+ '00-molecules',
+ '00-global',
+ '00-hidden-pattern-tester.hbs'
+ );
+
+ var hiddenPattern = loadPattern(hiddenPatternPath, pl);
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ Promise.all([
+ processIterative(hiddenPattern, pl),
+ processIterative(testPattern, pl),
+ processRecursive(hiddenPatternPath, pl),
+ processRecursive(testPatternPath, pl),
+ ]).then(() => {
+ testPattern.render().then(results => {
+ //act
+ test.equals(
+ util.sanitized(results),
+ util.sanitized("Here's the hidden atom: [I'm the hidden atom\n]\n")
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+tap.test(
+ '@partial-block template should render without throwing (@geoffp repo issue #3)',
+ function(test) {
+ test.plan(1);
+
+ var patternPath = path.join(
+ '00-atoms',
+ '00-global',
+ '10-at-partial-block.hbs'
+ );
+
+ // do all the normal processing of the pattern
+ var patternlab = new fakePatternLab();
+ var atPartialBlockPattern = loadPattern(patternPath, patternlab);
+
+ processIterative(atPartialBlockPattern, patternlab).then(() => {
+ processRecursive(patternPath, patternlab).then(() => {
+ atPartialBlockPattern.render().then(results => {
+ var expectedResults =
+ '{{> @partial-block }}' + eol + 'It worked!' + eol;
+ test.equal(results, expectedResults);
+ });
+ });
+ });
+ }
+);
+
+tap.test(
+ 'A template calling a @partial-block template should render correctly',
+ function(test) {
+ test.plan(1);
+
+ // pattern paths
+ var pattern1Path = path.join(
+ '00-atoms',
+ '00-global',
+ '10-at-partial-block.hbs'
+ );
+ var pattern2Path = path.join(
+ '00-molecules',
+ '00-global',
+ '10-call-at-partial-block.hbs'
+ );
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ const pattern1 = loadPattern(pattern1Path, patternlab);
+ const callAtPartialBlockPattern = loadPattern(pattern2Path, patternlab);
+
+ Promise.all([
+ processIterative(pattern1, patternlab),
+ processIterative(callAtPartialBlockPattern, patternlab),
+ processRecursive(pattern1Path, patternlab),
+ processRecursive(pattern2Path, patternlab),
+ ]).then(() => {
+ callAtPartialBlockPattern.render().then(results => {
+ // test
+ var expectedResults = 'Hello World!' + eol + 'It worked!' + eol;
+ test.equals(results, expectedResults);
+ });
+ });
+ }
+);
diff --git a/packages/core/test/engine_liquid_tests.js b/packages/core/test/engine_liquid_tests.js
new file mode 100644
index 000000000..9036dbca1
--- /dev/null
+++ b/packages/core/test/engine_liquid_tests.js
@@ -0,0 +1,261 @@
+'use strict';
+/*eslint-disable dot-notation*/
+/*eslint-disable no-shadow*/
+
+var tap = require('tap');
+var path = require('path');
+var loadPattern = require('../src/lib/loadPattern');
+var Pattern = require('../src/lib/object_factory').Pattern;
+var PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
+var eol = require('os').EOL;
+
+// don't run these tests unless liquid is installed
+var engineLoader = require('../src/lib/pattern_engines');
+if (!engineLoader.liquid) {
+ tap.test('Liquid engine not installed, skipping tests.', function(test) {
+ test.end();
+ });
+ return;
+}
+
+// fake pattern lab constructor:
+// sets up a fake patternlab object, which is needed by the pattern processing
+// apparatus.
+function fakePatternLab() {
+ var fpl = {
+ graph: PatternGraph.empty(),
+ partials: {},
+ patterns: [],
+ footer: '',
+ header: '',
+ listitems: {},
+ data: {
+ link: {},
+ },
+ config: require('../patternlab-config.json'),
+ package: {},
+ };
+
+ // patch the pattern source so the pattern assembler can correctly determine
+ // the "subdir"
+ fpl.config.paths.source.patterns = './test/files/_liquid-test-patterns';
+
+ return fpl;
+}
+
+// function for testing sets of partials
+function testFindPartials(test, partialTests) {
+ test.plan(partialTests.length + 1);
+
+ // setup current pattern from what we would have during execution
+ // docs on partial syntax are here:
+ // http://patternlab.io/docs/pattern-including.html
+ var currentPattern = Pattern.create(
+ '01-molecules/00-testing/00-test-mol.liquid', // relative path now
+ null, // data
+ {
+ template: partialTests.join(),
+ }
+ );
+
+ // act
+ var results = currentPattern.findPartials();
+
+ // assert
+ test.equals(results.length, partialTests.length);
+ partialTests.forEach(function(testString, index) {
+ test.equals(results[index], testString);
+ });
+
+ test.end();
+}
+
+tap.test('button liquid pattern renders', function(test) {
+ test.plan(1);
+
+ var patternPath = path.join('00-atoms', '00-general', '08-button.liquid');
+ var expectedValue =
+ '' +
+ eol +
+ eol +
+ 'Button ' +
+ eol;
+
+ // do all the normal processing of the pattern
+ var patternlab = new fakePatternLab();
+
+ var helloWorldPattern = loadPattern(patternPath, patternlab);
+
+ return assembler
+ .process_pattern_iterative(helloWorldPattern, patternlab)
+ .then(() => {
+ assembler.process_pattern_recursive(patternPath, patternlab);
+
+ test.equals(helloWorldPattern.render(), expectedValue);
+ });
+});
+
+tap.test(
+ 'media object liquid pattern can see the atoms-button and atoms-image partials and renders them',
+ function(test) {
+ test.plan(1);
+
+ // pattern paths
+ var buttonPatternPath = path.join(
+ '00-atoms',
+ '00-general',
+ '08-button.liquid'
+ );
+ var imagePatternPath = path.join(
+ '00-atoms',
+ '00-general',
+ '09-image.liquid'
+ );
+ var mediaObjectPatternPath = path.join(
+ '00-molecules',
+ '00-general',
+ '00-media-object.liquid'
+ );
+
+ var expectedValue =
+ '\n\n\n\n\n\n';
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ const buttonPattern = loadPattern(buttonPatternPath, patternlab);
+ const imagePattern = loadPattern(imagePatternPath, patternlab);
+ const mediaObjectPattern = loadPattern(mediaObjectPatternPath, patternlab);
+
+ return Promise.all([
+ assembler.process_pattern_iterative(buttonPattern, patternlab),
+ assembler.process_pattern_iterative(imagePattern, patternlab),
+ assembler.process_pattern_iterative(mediaObjectPattern, patternlab),
+ ]).then(() => {
+ assembler.process_pattern_recursive(buttonPatternPath, patternlab);
+ assembler.process_pattern_recursive(imagePatternPath, patternlab);
+ assembler.process_pattern_recursive(mediaObjectPatternPath, patternlab);
+
+ // test
+ // this pattern is too long - so just remove line endings on both sides and compare output
+ test.equals(
+ mediaObjectPattern.render().replace(/\r?\n|\r/gm, ''),
+ expectedValue.replace(/\r?\n|\r/gm, '')
+ );
+ });
+ }
+);
+
+tap.test('liquid partials can render JSON values', { skip: true }, function(
+ test
+) {
+ test.plan(1);
+
+ // pattern paths
+ var pattern1Path = path.resolve(
+ testPatternsPath,
+ '00-atoms',
+ '00-global',
+ '00-helloworld-withdata.hbs'
+ );
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ var helloWorldWithData = assembler.process_pattern_iterative(
+ pattern1Path,
+ patternlab
+ );
+ assembler.process_pattern_recursive(pattern1Path, patternlab);
+
+ // test
+ test.equals(
+ helloWorldWithData.render(),
+ 'Hello world!\nYeah, we got the subtitle from the JSON.\n'
+ );
+ test.end();
+});
+
+tap.test(
+ 'liquid partials use the JSON environment from the calling pattern and can accept passed parameters',
+ { skip: true },
+ function(test) {
+ test.plan(1);
+
+ // pattern paths
+ var atomPath = path.resolve(
+ testPatternsPath,
+ '00-atoms',
+ '00-global',
+ '00-helloworld-withdata.hbs'
+ );
+ var molPath = path.resolve(
+ testPatternsPath,
+ '00-molecules',
+ '00-global',
+ '00-call-atom-with-molecule-data.hbs'
+ );
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ var atom = assembler.process_pattern_iterative(atomPath, patternlab);
+ var mol = assembler.process_pattern_iterative(molPath, patternlab);
+ assembler.process_pattern_recursive(atomPath, patternlab);
+ assembler.process_pattern_recursive(molPath, patternlab);
+
+ // test
+ test.equals(
+ mol.render(),
+ 'Call with default JSON environment: \nThis is Hello world!\nfrom the default JSON.\n\n\nCall with passed parameter: \nHowever, this is Hello world!\nfrom a totally different blob.\n\n'
+ );
+ test.end();
+ }
+);
+
+tap.test('find_pattern_partials finds partials', function(test) {
+ testFindPartials(test, [
+ '{% include "atoms-image" %}',
+ "{% include 'atoms-image' %}",
+ "{%include 'atoms-image'%}",
+ "{% include 'molecules-template' only %}",
+ "{% include 'organisms-sidebar' ignore missing %}",
+ "{% include 'organisms-sidebar' ignore missing only %}",
+ ]);
+});
+
+tap.test('find_pattern_partials finds verbose partials', function(test) {
+ testFindPartials(test, [
+ "{% include '01-molecules/06-components/03-comment-header.liquid' %}",
+ "{% include '00-atoms/00-global/06-test' %}",
+ ]);
+});
+
+tap.test(
+ 'find_pattern_partials finds partials with liquid parameters',
+ function(test) {
+ testFindPartials(test, [
+ "{% include 'molecules-template' with {'foo': 'bar'} %}",
+ "{% include 'molecules-template' with vars %}",
+ "{% include 'molecules-template.liquid' with {'foo': 'bar'} only %}",
+ "{% include 'organisms-sidebar' ignore missing with {'foo': 'bar'} %}",
+ ]);
+ }
+);
diff --git a/test/engine_mustache_tests.js b/packages/core/test/engine_mustache_tests.js
similarity index 51%
rename from test/engine_mustache_tests.js
rename to packages/core/test/engine_mustache_tests.js
index da811be0e..8f0241d24 100644
--- a/test/engine_mustache_tests.js
+++ b/packages/core/test/engine_mustache_tests.js
@@ -1,27 +1,40 @@
-"use strict";
+'use strict';
+/*eslint-disable no-shadow*/
+var tap = require('tap');
var path = require('path');
-var pa = require('../core/lib/pattern_assembler');
-var Pattern = require('../core/lib/object_factory').Pattern;
+var Pattern = require('../src/lib/object_factory').Pattern;
+var PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
var testPatternsPath = path.resolve(__dirname, 'files', '_patterns');
var eol = require('os').EOL;
+var config = require('./util/patternlab-config.json');
+
+// don't run these tests unless mustache is installed
+var engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+if (!engineLoader.mustache) {
+ tap.test('Mustache engine not installed, skipping tests.', function(test) {
+ test.end();
+ });
+ return;
+}
// fake pattern lab constructor:
// sets up a fake patternlab object, which is needed by the pattern processing
// apparatus.
function fakePatternLab() {
var fpl = {
+ graph: PatternGraph.empty(),
partials: {},
patterns: [],
footer: '',
header: '',
listitems: {},
- listItemArray: [],
data: {
- link: {}
+ link: {},
},
- config: require('../patternlab-config.json'),
- package: {}
+ config: config,
+ package: {},
};
// patch the pattern source so the pattern assembler can correctly determine
@@ -33,7 +46,7 @@ function fakePatternLab() {
// function for testing sets of partials
function testFindPartials(test, partialTests) {
- test.expect(partialTests.length + 1);
+ test.plan(partialTests.length + 1);
// setup current pattern from what we would have during execution
// docs on partial syntax are here:
@@ -42,7 +55,7 @@ function testFindPartials(test, partialTests) {
'01-molecules/00-testing/00-test-mol.mustache', // relative path now
null, // data
{
- template: partialTests.join(eol)
+ template: partialTests.join(eol),
}
);
@@ -51,15 +64,15 @@ function testFindPartials(test, partialTests) {
// assert
test.equals(results.length, partialTests.length);
- partialTests.forEach(function (testString, index) {
+ partialTests.forEach(function(testString, index) {
test.equals(results[index], testString);
});
- test.done();
+ test.end();
}
function testFindPartialsWithStyleModifiers(test, partialTests) {
- test.expect(partialTests.length + 1);
+ test.plan(partialTests.length + 1);
// setup current pattern from what we would have during execution
// docs on partial syntax are here:
@@ -68,7 +81,7 @@ function testFindPartialsWithStyleModifiers(test, partialTests) {
'01-molecules/00-testing/00-test-mol.mustache', // relative path now
null, // data
{
- template: partialTests.join(eol)
+ template: partialTests.join(eol),
}
);
@@ -77,15 +90,15 @@ function testFindPartialsWithStyleModifiers(test, partialTests) {
// assert
test.equals(results.length, partialTests.length);
- partialTests.forEach(function (testString, index) {
+ partialTests.forEach(function(testString, index) {
test.equals(results[index], testString);
});
- test.done();
+ test.end();
}
function testFindPartialsWithPatternParameters(test, partialTests) {
- test.expect(partialTests.length + 1);
+ test.plan(partialTests.length + 1);
// setup current pattern from what we would have during execution
// docs on partial syntax are here:
@@ -94,7 +107,7 @@ function testFindPartialsWithPatternParameters(test, partialTests) {
'01-molecules/00-testing/00-test-mol.mustache', // relative path now
null, // data
{
- template: partialTests.join(eol)
+ template: partialTests.join(eol),
}
);
@@ -103,114 +116,136 @@ function testFindPartialsWithPatternParameters(test, partialTests) {
// assert
test.equals(results.length, partialTests.length);
- partialTests.forEach(function (testString, index) {
+ partialTests.forEach(function(testString, index) {
test.equals(results[index], testString);
});
- test.done();
+ test.end();
}
-exports['engine_mustache'] = {
- 'find_pattern_partials finds one simple partial': function (test) {
- testFindPartials(test, [
- "{{> molecules-comment-header}}"
- ]);
- },
-
- 'find_pattern_partials finds simple partials under stressed circumstances': function (test) {
- testFindPartials(test, [
- "{{>molecules-comment-header}}",
- "{{> " + eol + " molecules-comment-header" + eol + "}}",
- "{{> molecules-weird-spacing }}"
- ]);
- },
+tap.test('find_pattern_partials finds one simple partial', function(test) {
+ testFindPartials(test, ['{{> molecules-comment-header}}']);
+});
- 'find_pattern_partials finds one simple verbose partial': function (test) {
+tap.test(
+ 'find_pattern_partials finds simple partials under stressed circumstances',
+ function(test) {
testFindPartials(test, [
- '{{> 00-atoms/00-global/06-test }}'
+ '{{>molecules-comment-header}}',
+ '{{> ' + eol + ' molecules-comment-header' + eol + '}}',
+ '{{> molecules-weird-spacing }}',
]);
- },
-
- 'find_pattern_partials finds partials with parameters': function (test) {
- testFindPartials(test, [
- "{{> molecules-single-comment(description: true) }}",
- "{{> molecules-single-comment(description: 42) }}",
- "{{> molecules-single-comment(description: '42') }}",
- "{{> molecules-single-comment(description: \"42\") }}",
- "{{> molecules-single-comment(description: 'test', anotherThing: 'retest') }}",
- "{{> molecules-single-comment(description: false, anotherThing: \"retest\") }}",
- '{{> molecules-single-comment(description:"A life is like a \"garden\". Perfect moments can be had, but not preserved, except in memory.") }}'
- ]);
- },
-
- 'find_pattern_partials finds simple partials with style modifiers': function (test) {
+ }
+);
+
+tap.test('find_pattern_partials finds one simple verbose partial', function(
+ test
+) {
+ testFindPartials(test, ['{{> 00-atoms/00-global/06-test }}']);
+});
+
+tap.test('find_pattern_partials finds partials with parameters', function(
+ test
+) {
+ testFindPartials(test, [
+ '{{> molecules-single-comment(description: true) }}',
+ '{{> molecules-single-comment(description: 42) }}',
+ "{{> molecules-single-comment(description: '42') }}",
+ '{{> molecules-single-comment(description: "42") }}',
+ "{{> molecules-single-comment(description: 'test', anotherThing: 'retest') }}",
+ '{{> molecules-single-comment(description: false, anotherThing: "retest") }}',
+ '{{> molecules-single-comment(description:"A life is like a "garden". Perfect moments can be had, but not preserved, except in memory.") }}',
+ ]);
+});
+
+tap.test(
+ 'find_pattern_partials finds simple partials with style modifiers',
+ function(test) {
testFindPartials(test, [
'{{> molecules-single-comment:foo }}',
- '{{> molecules-single-comment:foo|bar }}'
- ]);
- },
- 'find_pattern_partials finds mixed partials': function (test) {
- testFindPartials(test, [
- '{{> molecules-single-comment:foo(description: "test", anotherThing: true) }}',
- '{{> molecules-single-comment:foo|bar(description: true) }}'
+ '{{> molecules-single-comment:foo|bar }}',
]);
- },
-
- 'find_pattern_partials finds one simple partial with styleModifier': function (test) {
+ }
+);
+
+tap.test('find_pattern_partials finds mixed partials', function(test) {
+ testFindPartials(test, [
+ '{{> molecules-single-comment:foo(description: "test", anotherThing: true) }}',
+ '{{> molecules-single-comment:foo|bar(description: true) }}',
+ ]);
+});
+
+tap.test(
+ 'find_pattern_partials finds one simple partial with styleModifier',
+ function(test) {
testFindPartialsWithStyleModifiers(test, [
- "{{> molecules-comment-header:test}}"
+ '{{> molecules-comment-header:test}}',
]);
- },
- 'find_pattern_partials finds partial with many styleModifiers': function (test) {
+ }
+);
+
+tap.test(
+ 'find_pattern_partials finds partial with many styleModifiers',
+ function(test) {
testFindPartialsWithStyleModifiers(test, [
- "{{> molecules-comment-header:test|test2|test3}}"
+ '{{> molecules-comment-header:test|test2|test3}}',
]);
- },
- 'find_pattern_partials finds partials with differing styleModifiers': function (test) {
+ }
+);
+
+tap.test(
+ 'find_pattern_partials finds partials with differing styleModifiers',
+ function(test) {
testFindPartialsWithStyleModifiers(test, [
- "{{> molecules-comment-header:test|test2|test3}}",
- "{{> molecules-comment-header:foo-1}}",
- "{{> molecules-comment-header:bar_1}}"
+ '{{> molecules-comment-header:test|test2|test3}}',
+ '{{> molecules-comment-header:foo-1}}',
+ '{{> molecules-comment-header:bar_1}}',
]);
- },
- 'find_pattern_partials finds partials with styleModifiers when parameters present': function (test) {
+ }
+);
+
+tap.test(
+ 'find_pattern_partials finds partials with styleModifiers when parameters present',
+ function(test) {
testFindPartialsWithStyleModifiers(test, [
- "{{> molecules-comment-header:test|test2|test3(description: true)}}",
+ '{{> molecules-comment-header:test|test2|test3(description: true)}}',
"{{> molecules-comment-header:foo-1(description: 'foo')}}",
- "{{> molecules-comment-header:bar_1(descrition: 'bar', anotherThing: 10102010) }}"
+ "{{> molecules-comment-header:bar_1(descrition: 'bar', anotherThing: 10102010) }}",
]);
- },
+ }
+);
- 'find_pattern_partials_with_parameters finds one simple partial with parameters': function (test) {
+tap.test(
+ 'find_pattern_partials_with_parameters finds one simple partial with parameters',
+ function(test) {
testFindPartialsWithPatternParameters(test, [
- "{{> molecules-comment-header(description: 'test')}}"
+ "{{> molecules-comment-header(description: 'test')}}",
]);
- },
- 'find_pattern_partials_with_parameters finds partials with parameters': function (test) {
+ }
+);
+
+tap.test(
+ 'find_pattern_partials_with_parameters finds partials with parameters',
+ function(test) {
testFindPartialsWithPatternParameters(test, [
- "{{> molecules-single-comment(description: true) }}",
- "{{> molecules-single-comment(description: 42) }}",
+ '{{> molecules-single-comment(description: true) }}',
+ '{{> molecules-single-comment(description: 42) }}',
"{{> molecules-single-comment(description: '42') }}",
- "{{> molecules-single-comment(description: \"42\") }}",
+ '{{> molecules-single-comment(description: "42") }}',
"{{> molecules-single-comment(description: 'test', anotherThing: 'retest') }}",
- "{{> molecules-single-comment(description: false, anotherThing: \"retest\") }}",
- '{{> molecules-single-comment(description:"A life is like a \"garden\". Perfect moments can be had, but not preserved, except in memory.") }}'
+ '{{> molecules-single-comment(description: false, anotherThing: "retest") }}',
+ '{{> molecules-single-comment(description:"A life is like a "garden". Perfect moments can be had, but not preserved, except in memory.") }}',
]);
- },
- 'find_pattern_partials finds partials with parameters when styleModifiers present': function (test) {
+ }
+);
+
+tap.test(
+ 'find_pattern_partials finds partials with parameters when styleModifiers present',
+ function(test) {
testFindPartialsWithPatternParameters(test, [
- "{{> molecules-comment-header:test|test2|test3(description: true)}}",
+ '{{> molecules-comment-header:test|test2|test3(description: true)}}',
"{{> molecules-comment-header:foo-1(description: 'foo')}}",
- "{{> molecules-comment-header:bar_1(descrition: 'bar', anotherThing: 10102010) }}"
+ "{{> molecules-comment-header:bar_1(descrition: 'bar', anotherThing: 10102010) }}",
]);
}
-
-};
-
-
-// don't run these tests unless mustache is installed
-var engineLoader = require('../core/lib/pattern_engines');
-if (!engineLoader.mustache) {
- console.log("Mustache engine not installed, skipping tests.");
- delete exports.engine_mustache;
-}
+);
diff --git a/packages/core/test/engine_react_tests.js b/packages/core/test/engine_react_tests.js
new file mode 100644
index 000000000..2d271bb9b
--- /dev/null
+++ b/packages/core/test/engine_react_tests.js
@@ -0,0 +1,47 @@
+const path = require('path');
+const fs = require('fs');
+const tap = require('tap');
+const loadPattern = require('../src/lib/loadPattern');
+const testUtils = require('./util/test_utils.js');
+const config = require('./util/patternlab-config.json');
+const engineLoader = require('../src/lib/pattern_engines');
+const testPatternsPath = path.resolve(
+ __dirname,
+ 'files',
+ '_react-test-patterns'
+);
+
+engineLoader.loadAllEngines(config);
+
+// don't run these tests unless the react engine is installed
+if (!engineLoader.react) {
+ tap.test('React engine not installed, skipping tests.', test => {
+ test.end();
+ });
+} else {
+ const fpl = testUtils.fakePatternLab(testPatternsPath);
+
+ tap.test('Load the hello world pattern and verify contents', test => {
+ const patternPath = path.join(
+ testPatternsPath,
+ '00-atoms/00-general/HelloWorld.jsx'
+ );
+ const patternContent = fs.readFileSync(patternPath, { encoding: 'utf8' });
+ const pattern = loadPattern(patternPath, fpl);
+
+ test.equals(pattern.template, patternContent);
+ test.end();
+ });
+
+ tap.test('Load the hello world pattern and verify output', test => {
+ const patternPath = path.join(
+ testPatternsPath,
+ '00-atoms/00-general/HelloWorld.jsx'
+ );
+ const pattern = loadPattern(patternPath, fpl);
+
+ return pattern.render().then(output => {
+ test.equals(output, 'Hello world!
\n');
+ });
+ });
+}
diff --git a/packages/core/test/engine_twig_tests.js b/packages/core/test/engine_twig_tests.js
new file mode 100644
index 000000000..33a49bbe9
--- /dev/null
+++ b/packages/core/test/engine_twig_tests.js
@@ -0,0 +1,256 @@
+'use strict';
+/*eslint-disable dot-notation*/
+/*eslint-disable no-shadow*/
+
+var tap = require('tap');
+var path = require('path');
+var loadPattern = require('../src/lib/loadPattern');
+var Pattern = require('../src/lib/object_factory').Pattern;
+var PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
+var eol = require('os').EOL;
+
+// don't run these tests unless twig is installed
+var engineLoader = require('../src/lib/pattern_engines');
+if (!engineLoader.twig) {
+ tap.test('Twig engine not installed, skipping tests.', function(test) {
+ test.end();
+ });
+ return;
+}
+
+// fake pattern lab constructor:
+// sets up a fake patternlab object, which is needed by the pattern processing
+// apparatus.
+function fakePatternLab() {
+ var fpl = {
+ graph: PatternGraph.empty(),
+ partials: {},
+ patterns: [],
+ footer: '',
+ header: '',
+ listitems: {},
+ data: {
+ link: {},
+ },
+ config: require('../patternlab-config.json'),
+ package: {},
+ };
+
+ // patch the pattern source so the pattern assembler can correctly determine
+ // the "subdir"
+ fpl.config.paths.source.patterns = './test/files/_twig-test-patterns';
+
+ return fpl;
+}
+
+// function for testing sets of partials
+function testFindPartials(test, partialTests) {
+ test.plan(partialTests.length + 1);
+
+ // setup current pattern from what we would have during execution
+ // docs on partial syntax are here:
+ // http://patternlab.io/docs/pattern-including.html
+ var currentPattern = Pattern.create(
+ '01-molecules/00-testing/00-test-mol.twig', // relative path now
+ null, // data
+ {
+ template: partialTests.join(),
+ }
+ );
+
+ // act
+ var results = currentPattern.findPartials();
+
+ // assert
+ test.equals(results.length, partialTests.length);
+ partialTests.forEach(function(testString, index) {
+ test.equals(results[index], testString);
+ });
+
+ test.end();
+}
+
+tap.test('button twig pattern renders', function(test) {
+ test.plan(1);
+
+ var patternPath = path.join('00-atoms', '00-general', '08-button.twig');
+ var expectedValue =
+ '' +
+ eol +
+ eol +
+ 'Button ' +
+ eol;
+
+ // do all the normal processing of the pattern
+ var patternlab = new fakePatternLab();
+
+ var helloWorldPattern = loadPattern(patternPath, patternlab);
+
+ return assembler
+ .process_pattern_iterative(helloWorldPattern, patternlab)
+ .then(() => {
+ assembler.process_pattern_recursive(patternPath, patternlab);
+
+ test.equals(helloWorldPattern.render(), expectedValue);
+ });
+});
+
+tap.test(
+ 'media object twig pattern can see the atoms-button and atoms-image partials and renders them',
+ function(test) {
+ test.plan(1);
+
+ // pattern paths
+ var buttonPatternPath = path.join(
+ '00-atoms',
+ '00-general',
+ '08-button.twig'
+ );
+ var imagePatternPath = path.join('00-atoms', '00-general', '09-image.twig');
+ var mediaObjectPatternPath = path.join(
+ '00-molecules',
+ '00-general',
+ '00-media-object.twig'
+ );
+
+ var expectedValue =
+ '\n\n\n\n\n\n';
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ const buttonPattern = loadPattern(buttonPatternPath, patternlab);
+ const imagePattern = loadPattern(imagePatternPath, patternlab);
+ const mediaObjectPattern = loadPattern(mediaObjectPatternPath, patternlab);
+
+ return Promise.all([
+ assembler.process_pattern_iterative(buttonPattern, patternlab),
+ assembler.process_pattern_iterative(imagePattern, patternlab),
+ assembler.process_pattern_iterative(mediaObjectPattern, patternlab),
+ ]).then(() => {
+ assembler.process_pattern_recursive(buttonPatternPath, patternlab);
+ assembler.process_pattern_recursive(imagePatternPath, patternlab);
+ assembler.process_pattern_recursive(mediaObjectPatternPath, patternlab);
+
+ // test
+ // this pattern is too long - so just remove line endings on both sides and compare output
+ test.equals(
+ mediaObjectPattern.render().replace(/\r?\n|\r/gm, ''),
+ expectedValue.replace(/\r?\n|\r/gm, '')
+ );
+ });
+ }
+);
+
+tap.test('twig partials can render JSON values', { skip: true }, function(
+ test
+) {
+ test.plan(1);
+
+ // pattern paths
+ var pattern1Path = path.resolve(
+ testPatternsPath,
+ '00-atoms',
+ '00-global',
+ '00-helloworld-withdata.hbs'
+ );
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ var helloWorldWithData = assembler.process_pattern_iterative(
+ pattern1Path,
+ patternlab
+ );
+ assembler.process_pattern_recursive(pattern1Path, patternlab);
+
+ // test
+ test.equals(
+ helloWorldWithData.render(),
+ 'Hello world!\nYeah, we got the subtitle from the JSON.\n'
+ );
+ test.end();
+});
+
+tap.test(
+ 'twig partials use the JSON environment from the calling pattern and can accept passed parameters',
+ { skip: true },
+ function(test) {
+ test.plan(1);
+
+ // pattern paths
+ var atomPath = path.resolve(
+ testPatternsPath,
+ '00-atoms',
+ '00-global',
+ '00-helloworld-withdata.hbs'
+ );
+ var molPath = path.resolve(
+ testPatternsPath,
+ '00-molecules',
+ '00-global',
+ '00-call-atom-with-molecule-data.hbs'
+ );
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ var atom = assembler.process_pattern_iterative(atomPath, patternlab);
+ var mol = assembler.process_pattern_iterative(molPath, patternlab);
+ assembler.process_pattern_recursive(atomPath, patternlab);
+ assembler.process_pattern_recursive(molPath, patternlab);
+
+ // test
+ test.equals(
+ mol.render(),
+ 'Call with default JSON environment: \nThis is Hello world!\nfrom the default JSON.\n\n\nCall with passed parameter: \nHowever, this is Hello world!\nfrom a totally different blob.\n\n'
+ );
+ test.end();
+ }
+);
+
+tap.test('find_pattern_partials finds partials', function(test) {
+ testFindPartials(test, [
+ '{% include "atoms-image" %}',
+ "{% include 'atoms-image' %}",
+ "{%include 'atoms-image'%}",
+ "{% include 'molecules-template' only %}",
+ "{% include 'organisms-sidebar' ignore missing %}",
+ "{% include 'organisms-sidebar' ignore missing only %}",
+ ]);
+});
+
+tap.test('find_pattern_partials finds verbose partials', function(test) {
+ testFindPartials(test, [
+ "{% include '01-molecules/06-components/03-comment-header.twig' %}",
+ "{% include '00-atoms/00-global/06-test' %}",
+ ]);
+});
+
+tap.test('find_pattern_partials finds partials with twig parameters', function(
+ test
+) {
+ testFindPartials(test, [
+ "{% include 'molecules-template' with {'foo': 'bar'} %}",
+ "{% include 'molecules-template' with vars %}",
+ "{% include 'molecules-template.twig' with {'foo': 'bar'} only %}",
+ "{% include 'organisms-sidebar' ignore missing with {'foo': 'bar'} %}",
+ ]);
+});
diff --git a/packages/core/test/engine_underscore_tests.js b/packages/core/test/engine_underscore_tests.js
new file mode 100644
index 000000000..fbdbb5617
--- /dev/null
+++ b/packages/core/test/engine_underscore_tests.js
@@ -0,0 +1,163 @@
+'use strict';
+
+var tap = require('tap');
+var path = require('path');
+var loadPattern = require('../src/lib/loadPattern');
+var PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
+var testPatternsPath = path.resolve(
+ __dirname,
+ 'files',
+ '_underscore-test-patterns'
+);
+var eol = require('os').EOL;
+
+// don't run tests unless underscore is installed
+var engineLoader = require('../src/lib/pattern_engines');
+if (!engineLoader.underscore) {
+ tap.test('Underscore engine not installed, skipping tests', function(test) {
+ test.end();
+ });
+ return;
+}
+
+// fake pattern lab constructor:
+// sets up a fake patternlab object, which is needed by the pattern processing
+// apparatus.
+function fakePatternLab() {
+ var fpl = {
+ graph: PatternGraph.empty(),
+ partials: {},
+ patterns: [],
+ footer: '',
+ header: '',
+ listitems: {},
+ data: {
+ link: {},
+ },
+ config: require('../patternlab-config.json'),
+ package: {},
+ };
+
+ // patch the pattern source so the pattern assembler can correctly determine
+ // the "subdir"
+ fpl.config.paths.source.patterns = testPatternsPath;
+
+ return fpl;
+}
+
+tap.test('hello world underscore pattern renders', function(test) {
+ test.plan(1);
+
+ var patternPath = path.resolve(
+ testPatternsPath,
+ '00-atoms',
+ '00-global',
+ '00-helloworld.html'
+ );
+
+ // do all the normal processing of the pattern
+ var patternlab = new fakePatternLab();
+
+ const helloWorldPattern = loadPattern(patternPath, patternlab);
+
+ return assembler
+ .process_pattern_iterative(helloWorldPattern, patternlab)
+ .then(() => {
+ assembler.process_pattern_recursive(patternPath, patternlab);
+
+ test.equals(helloWorldPattern.render(), 'Hello world!' + eol);
+ });
+});
+
+tap.test('underscore partials can render JSON values', function(test) {
+ test.plan(1);
+
+ // pattern paths
+ var pattern1Path = path.resolve(
+ testPatternsPath,
+ '00-atoms',
+ '00-global',
+ '00-helloworld-withdata.html'
+ );
+
+ // set up environment
+ var patternlab = new fakePatternLab(); // environment
+
+ // do all the normal processing of the pattern
+ const helloWorldWithData = loadPattern(pattern1Path, patternlab);
+ return assembler
+ .process_pattern_iterative(helloWorldWithData, patternlab)
+ .then(() => {
+ assembler.process_pattern_recursive(pattern1Path, patternlab);
+
+ // test
+ test.equals(
+ helloWorldWithData.render(),
+ 'Hello world!' + eol + 'Yeah, we got the subtitle from the JSON.' + eol
+ );
+ });
+});
+
+tap.test(
+ 'findPartial return the ID of the partial, given a whole partial call',
+ function(test) {
+ var engineLoader = require('../src/lib/pattern_engines');
+ var underscoreEngine = engineLoader.underscore;
+
+ test.plan(1);
+
+ // do all the normal processing of the pattern
+ // test
+ test.equals(
+ underscoreEngine.findPartial(
+ "<%= _.renderNamedPartial('molecules-details', obj) %>"
+ ),
+ 'molecules-details'
+ );
+ test.end();
+ }
+);
+
+tap.test(
+ 'hidden underscore patterns can be called by their nice names',
+ function(test) {
+ const util = require('./util/test_utils.js');
+
+ //arrange
+ const testPatternsPath = path.resolve(
+ __dirname,
+ 'files',
+ '_underscore-test-patterns'
+ );
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var hiddenPatternPath = path.join(
+ '00-atoms',
+ '00-global',
+ '_00-hidden.html'
+ );
+ var testPatternPath = path.join(
+ '00-molecules',
+ '00-global',
+ '00-hidden-pattern-tester.html'
+ );
+
+ var hiddenPattern = loadPattern(hiddenPatternPath, pl);
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ return Promise.all([
+ pattern_assembler.process_pattern_iterative(hiddenPattern, pl),
+ pattern_assembler.process_pattern_iterative(testPattern, pl),
+ ]).then(() => {
+ pattern_assembler.process_pattern_recursive(hiddenPatternPath, pl);
+ pattern_assembler.process_pattern_recursive(testPatternPath, pl);
+
+ //act
+ test.equals(
+ util.sanitized(testPattern.render()),
+ util.sanitized("Here's the hidden atom: [I'm the hidden atom\n]\n")
+ );
+ test.end();
+ });
+ }
+);
diff --git a/packages/core/test/exportData_tests.js b/packages/core/test/exportData_tests.js
new file mode 100644
index 000000000..93cd9573d
--- /dev/null
+++ b/packages/core/test/exportData_tests.js
@@ -0,0 +1,68 @@
+'use strict';
+
+const path = require('path');
+const tap = require('tap');
+const rewire = require('rewire');
+
+const exportData = rewire('../src/lib/exportData');
+const util = require('./util/test_utils.js');
+
+const testPatternsPath = path.resolve(__dirname, 'files', '_patterns');
+
+const fsMock = {
+ outputFileSync: function(path, content) {
+ /* INTENTIONAL NOOP */
+ },
+};
+
+//set our mocks in place of usual require()
+exportData.__set__({
+ fs: fsMock,
+});
+
+const patternlab = util.fakePatternLab(testPatternsPath);
+const result = exportData(patternlab);
+
+tap.test('exportData exports config', function(test) {
+ test.equals(result.indexOf('config') > -1, true);
+ test.equals(result.indexOf('paths') > -1, true);
+ test.equals(result.indexOf('theme') > -1, true);
+ test.end();
+});
+
+tap.test('exportData exports ishControls', function(test) {
+ test.equals(result.indexOf('ishControlsHide') > -1, true);
+ test.end();
+});
+
+tap.test('exportData exports navItems', function(test) {
+ test.equals(result.indexOf('patternTypes') > -1, true);
+ test.end();
+});
+
+tap.test('exportData exports patternPaths', function(test) {
+ test.equals(result.indexOf('patternPaths') > -1, true);
+ test.end();
+});
+
+tap.test('exportData exports viewAllPaths', function(test) {
+ test.equals(result.indexOf('viewAllPaths') > -1, true);
+ test.end();
+});
+
+tap.test('exportData exports plugins', function(test) {
+ test.equals(result.indexOf('plugins') > -1, true);
+ test.end();
+});
+
+tap.test('exportData exports defaultShowPatternInfo', function(test) {
+ test.equals(result.indexOf('defaultShowPatternInfo') > -1, true);
+ test.equals(result.indexOf('"defaultShowPatternInfo":false') > -1, true);
+ test.end();
+});
+
+tap.test('exportData exports defaultPattern', function(test) {
+ test.equals(result.indexOf('defaultPattern') > -1, true);
+ test.equals(result.indexOf('"defaultPattern":"all"') > -1, true);
+ test.end();
+});
diff --git a/packages/core/test/files/_data/data.json b/packages/core/test/files/_data/data.json
new file mode 100644
index 000000000..1e3cc53b3
--- /dev/null
+++ b/packages/core/test/files/_data/data.json
@@ -0,0 +1,2 @@
+{ "data" : "test", "from_json" : "from_json" }
+
diff --git a/packages/core/test/files/_data/data.yaml b/packages/core/test/files/_data/data.yaml
new file mode 100644
index 000000000..50c5fd0cd
--- /dev/null
+++ b/packages/core/test/files/_data/data.yaml
@@ -0,0 +1 @@
+from_yaml: "from_yaml"
diff --git a/packages/core/test/files/_data/data.yml b/packages/core/test/files/_data/data.yml
new file mode 100644
index 000000000..e64d69624
--- /dev/null
+++ b/packages/core/test/files/_data/data.yml
@@ -0,0 +1 @@
+from_yml: "from_yml"
diff --git a/packages/core/test/files/_data/foo-other.json b/packages/core/test/files/_data/foo-other.json
new file mode 100644
index 000000000..7b6c4a3c4
--- /dev/null
+++ b/packages/core/test/files/_data/foo-other.json
@@ -0,0 +1,3 @@
+{
+ "foo": "wrong"
+}
\ No newline at end of file
diff --git a/test/files/_data/foo.json b/packages/core/test/files/_data/foo.json
similarity index 100%
rename from test/files/_data/foo.json
rename to packages/core/test/files/_data/foo.json
diff --git a/packages/core/test/files/_data/listitems.json b/packages/core/test/files/_data/listitems.json
new file mode 100644
index 000000000..0cb4c328d
--- /dev/null
+++ b/packages/core/test/files/_data/listitems.json
@@ -0,0 +1,17 @@
+{
+ "1": {
+ "title": "tA",
+ "description": "dA",
+ "message": "mA"
+ },
+ "2": {
+ "title": "tB",
+ "description": "dB",
+ "message": "mB"
+ },
+ "3": {
+ "title": "tC",
+ "description": "dC",
+ "message": "mC"
+ }
+}
diff --git a/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.hbs b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.hbs
similarity index 100%
rename from test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.hbs
rename to packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.hbs
diff --git a/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.json b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.json
similarity index 100%
rename from test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.json
rename to packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.json
diff --git a/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld.hbs b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld.hbs
similarity index 100%
rename from test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld.hbs
rename to packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld.hbs
diff --git a/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/10-at-partial-block.hbs b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/10-at-partial-block.hbs
new file mode 100644
index 000000000..59e0522cd
--- /dev/null
+++ b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/10-at-partial-block.hbs
@@ -0,0 +1,2 @@
+{{> @partial-block }}
+It worked!
diff --git a/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/_00-hidden.hbs b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/_00-hidden.hbs
new file mode 100644
index 000000000..b2c93a13e
--- /dev/null
+++ b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/_00-hidden.hbs
@@ -0,0 +1 @@
+I'm the hidden atom
diff --git a/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.hbs b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.hbs
similarity index 100%
rename from test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.hbs
rename to packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.hbs
diff --git a/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json
similarity index 100%
rename from test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json
rename to packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json
diff --git a/test/files/_handlebars-test-patterns/00-molecules/00-global/00-helloworlds.hbs b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-helloworlds.hbs
similarity index 100%
rename from test/files/_handlebars-test-patterns/00-molecules/00-global/00-helloworlds.hbs
rename to packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-helloworlds.hbs
diff --git a/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.hbs b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.hbs
new file mode 100644
index 000000000..ce4a0c864
--- /dev/null
+++ b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.hbs
@@ -0,0 +1 @@
+Here's the hidden atom: [{{> atoms-hidden}}]
diff --git a/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/10-call-at-partial-block.hbs b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/10-call-at-partial-block.hbs
new file mode 100644
index 000000000..d653c1196
--- /dev/null
+++ b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/10-call-at-partial-block.hbs
@@ -0,0 +1,3 @@
+{{#> atoms-at-partial-block }}
+Hello World!
+{{/atoms-at-partial-block}}
diff --git a/test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig b/packages/core/test/files/_liquid_test-patterns/00-atoms/00-general/08-button.liquid
similarity index 100%
rename from test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig
rename to packages/core/test/files/_liquid_test-patterns/00-atoms/00-general/08-button.liquid
diff --git a/test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig b/packages/core/test/files/_liquid_test-patterns/00-atoms/00-general/09-image.liquid
similarity index 100%
rename from test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig
rename to packages/core/test/files/_liquid_test-patterns/00-atoms/00-general/09-image.liquid
diff --git a/test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig b/packages/core/test/files/_liquid_test-patterns/00-molecules/00-general/00-media-object.liquid
similarity index 100%
rename from test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig
rename to packages/core/test/files/_liquid_test-patterns/00-molecules/00-general/00-media-object.liquid
diff --git a/packages/core/test/files/_meta/_00-head.hbs b/packages/core/test/files/_meta/_00-head.hbs
new file mode 100644
index 000000000..b1f5c1ce0
--- /dev/null
+++ b/packages/core/test/files/_meta/_00-head.hbs
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
diff --git a/packages/core/test/files/_meta/_00-head.html b/packages/core/test/files/_meta/_00-head.html
new file mode 100644
index 000000000..b1f5c1ce0
--- /dev/null
+++ b/packages/core/test/files/_meta/_00-head.html
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
diff --git a/test/files/empty/.gitkeep b/packages/core/test/files/_meta/_00-head.mustache
similarity index 100%
rename from test/files/empty/.gitkeep
rename to packages/core/test/files/_meta/_00-head.mustache
diff --git a/packages/core/test/files/_meta/_01-foot.hbs b/packages/core/test/files/_meta/_01-foot.hbs
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/core/test/files/_meta/_01-foot.hbs
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/core/test/files/_meta/_01-foot.html b/packages/core/test/files/_meta/_01-foot.html
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/core/test/files/_meta/_01-foot.html
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/core/test/files/_meta/_01-foot.mustache b/packages/core/test/files/_meta/_01-foot.mustache
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/files/_patterns/00-test/00-foo.md b/packages/core/test/files/_patterns/00-test/00-foo.md
similarity index 84%
rename from test/files/_patterns/00-test/00-foo.md
rename to packages/core/test/files/_patterns/00-test/00-foo.md
index 4c8bbf296..b284e8103 100644
--- a/test/files/_patterns/00-test/00-foo.md
+++ b/packages/core/test/files/_patterns/00-test/00-foo.md
@@ -1,3 +1,6 @@
+---
+state: inreview
+---
## A Simple Include
This pattern contains an include of `test-bar`. It also has this markdown file, which does not have frontmatter.
diff --git a/test/files/_patterns/00-test/00-foo.mustache b/packages/core/test/files/_patterns/00-test/00-foo.mustache
similarity index 100%
rename from test/files/_patterns/00-test/00-foo.mustache
rename to packages/core/test/files/_patterns/00-test/00-foo.mustache
diff --git a/test/files/_patterns/00-test/01-bar.md b/packages/core/test/files/_patterns/00-test/01-bar.md
similarity index 57%
rename from test/files/_patterns/00-test/01-bar.md
rename to packages/core/test/files/_patterns/00-test/01-bar.md
index d4fd865a2..a811dacd7 100644
--- a/test/files/_patterns/00-test/01-bar.md
+++ b/packages/core/test/files/_patterns/00-test/01-bar.md
@@ -1,5 +1,7 @@
---
-status: complete
+state: complete
+title: An Atom Walks Into a Bar
+joke: bad
---
## A Simple Bit of Markup
diff --git a/test/files/_patterns/00-test/01-bar.mustache b/packages/core/test/files/_patterns/00-test/01-bar.mustache
similarity index 100%
rename from test/files/_patterns/00-test/01-bar.mustache
rename to packages/core/test/files/_patterns/00-test/01-bar.mustache
diff --git a/packages/core/test/files/_patterns/00-test/02-baz.md b/packages/core/test/files/_patterns/00-test/02-baz.md
new file mode 100644
index 000000000..f6f78b399
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/02-baz.md
@@ -0,0 +1 @@
+### Only baz
\ No newline at end of file
diff --git a/packages/core/test/files/_patterns/00-test/02-baz.mustache b/packages/core/test/files/_patterns/00-test/02-baz.mustache
new file mode 100644
index 000000000..3f9538666
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/02-baz.mustache
@@ -0,0 +1 @@
+baz
\ No newline at end of file
diff --git a/packages/core/test/files/_patterns/00-test/03-styled-atom.json b/packages/core/test/files/_patterns/00-test/03-styled-atom.json
new file mode 100644
index 000000000..475247e1a
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/03-styled-atom.json
@@ -0,0 +1,3 @@
+{
+ "message": "baseMessage"
+}
diff --git a/packages/core/test/files/_patterns/00-test/03-styled-atom.md b/packages/core/test/files/_patterns/00-test/03-styled-atom.md
new file mode 100644
index 000000000..58c494066
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/03-styled-atom.md
@@ -0,0 +1,3 @@
+---
+state: inprogress
+---
diff --git a/test/files/_patterns/00-test/03-styled-atom.mustache b/packages/core/test/files/_patterns/00-test/03-styled-atom.mustache
similarity index 100%
rename from test/files/_patterns/00-test/03-styled-atom.mustache
rename to packages/core/test/files/_patterns/00-test/03-styled-atom.mustache
diff --git a/test/files/_patterns/00-test/03-styled-atom~alt.json b/packages/core/test/files/_patterns/00-test/03-styled-atom~alt.json
similarity index 100%
rename from test/files/_patterns/00-test/03-styled-atom~alt.json
rename to packages/core/test/files/_patterns/00-test/03-styled-atom~alt.json
diff --git a/test/files/_patterns/00-test/04-group.mustache b/packages/core/test/files/_patterns/00-test/04-group.mustache
similarity index 100%
rename from test/files/_patterns/00-test/04-group.mustache
rename to packages/core/test/files/_patterns/00-test/04-group.mustache
diff --git a/test/files/_patterns/00-test/05-group2.mustache b/packages/core/test/files/_patterns/00-test/05-group2.mustache
similarity index 100%
rename from test/files/_patterns/00-test/05-group2.mustache
rename to packages/core/test/files/_patterns/00-test/05-group2.mustache
diff --git a/test/files/_patterns/00-test/06-mixed.mustache b/packages/core/test/files/_patterns/00-test/06-mixed.mustache
similarity index 100%
rename from test/files/_patterns/00-test/06-mixed.mustache
rename to packages/core/test/files/_patterns/00-test/06-mixed.mustache
diff --git a/test/files/_patterns/00-test/07-mixed-params.mustache b/packages/core/test/files/_patterns/00-test/07-mixed-params.mustache
similarity index 100%
rename from test/files/_patterns/00-test/07-mixed-params.mustache
rename to packages/core/test/files/_patterns/00-test/07-mixed-params.mustache
diff --git a/test/files/_patterns/00-test/08-bookend-params.mustache b/packages/core/test/files/_patterns/00-test/08-bookend-params.mustache
similarity index 100%
rename from test/files/_patterns/00-test/08-bookend-params.mustache
rename to packages/core/test/files/_patterns/00-test/08-bookend-params.mustache
diff --git a/test/files/_patterns/00-test/09-bookend.mustache b/packages/core/test/files/_patterns/00-test/09-bookend.mustache
similarity index 100%
rename from test/files/_patterns/00-test/09-bookend.mustache
rename to packages/core/test/files/_patterns/00-test/09-bookend.mustache
diff --git a/test/files/_patterns/00-test/10-multiple-classes-numeric.mustache b/packages/core/test/files/_patterns/00-test/10-multiple-classes-numeric.mustache
similarity index 100%
rename from test/files/_patterns/00-test/10-multiple-classes-numeric.mustache
rename to packages/core/test/files/_patterns/00-test/10-multiple-classes-numeric.mustache
diff --git a/test/files/_patterns/00-test/11-bookend-listitem.mustache b/packages/core/test/files/_patterns/00-test/11-bookend-listitem.mustache
similarity index 100%
rename from test/files/_patterns/00-test/11-bookend-listitem.mustache
rename to packages/core/test/files/_patterns/00-test/11-bookend-listitem.mustache
diff --git a/test/files/_patterns/00-test/12-another-styled-atom.mustache b/packages/core/test/files/_patterns/00-test/12-another-styled-atom.mustache
similarity index 100%
rename from test/files/_patterns/00-test/12-another-styled-atom.mustache
rename to packages/core/test/files/_patterns/00-test/12-another-styled-atom.mustache
diff --git a/test/files/_patterns/00-test/13-listitem.mustache b/packages/core/test/files/_patterns/00-test/13-listitem.mustache
similarity index 100%
rename from test/files/_patterns/00-test/13-listitem.mustache
rename to packages/core/test/files/_patterns/00-test/13-listitem.mustache
diff --git a/test/files/_patterns/00-test/14-inception.mustache b/packages/core/test/files/_patterns/00-test/14-inception.mustache
similarity index 100%
rename from test/files/_patterns/00-test/14-inception.mustache
rename to packages/core/test/files/_patterns/00-test/14-inception.mustache
diff --git a/packages/core/test/files/_patterns/00-test/15-hidden-pattern-tester.mustache b/packages/core/test/files/_patterns/00-test/15-hidden-pattern-tester.mustache
new file mode 100644
index 000000000..5598f783f
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/15-hidden-pattern-tester.mustache
@@ -0,0 +1,2 @@
+Hello there!
+Here's the hidden atom: [{{> test-hidden-pattern}}]
diff --git a/test/files/_patterns/00-test/474-pseudomodifier.mustache b/packages/core/test/files/_patterns/00-test/474-pseudomodifier.mustache
similarity index 100%
rename from test/files/_patterns/00-test/474-pseudomodifier.mustache
rename to packages/core/test/files/_patterns/00-test/474-pseudomodifier.mustache
diff --git a/test/files/_patterns/00-test/474-pseudomodifier~test.json b/packages/core/test/files/_patterns/00-test/474-pseudomodifier~test.json
similarity index 100%
rename from test/files/_patterns/00-test/474-pseudomodifier~test.json
rename to packages/core/test/files/_patterns/00-test/474-pseudomodifier~test.json
diff --git a/packages/core/test/files/_patterns/00-test/539-a.mustache b/packages/core/test/files/_patterns/00-test/539-a.mustache
new file mode 100644
index 000000000..ba154f442
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/539-a.mustache
@@ -0,0 +1,4 @@
+a
+{{ #a }}
+a!
+{{ /a }}
diff --git a/packages/core/test/files/_patterns/00-test/539-b.mustache b/packages/core/test/files/_patterns/00-test/539-b.mustache
new file mode 100644
index 000000000..70997abb3
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/539-b.mustache
@@ -0,0 +1,5 @@
+b
+{{ #b }}
+b!
+{{ /b }}
+{{> test-a(a: true) }}
diff --git a/packages/core/test/files/_patterns/00-test/539-c.mustache b/packages/core/test/files/_patterns/00-test/539-c.mustache
new file mode 100644
index 000000000..6098102c8
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/539-c.mustache
@@ -0,0 +1,2 @@
+c
+{{> test-b(b: true) }}
diff --git a/packages/core/test/files/_patterns/00-test/553-repeatedListItems.mustache b/packages/core/test/files/_patterns/00-test/553-repeatedListItems.mustache
new file mode 100644
index 000000000..8362b6d31
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/553-repeatedListItems.mustache
@@ -0,0 +1,2 @@
+{{# listItems.three }}A{{/ listItems.three }}
+{{# listItems.three }}B{{/ listItems.three }}
diff --git a/packages/core/test/files/_patterns/00-test/685-list.mustache b/packages/core/test/files/_patterns/00-test/685-list.mustache
new file mode 100644
index 000000000..1b3623c07
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/685-list.mustache
@@ -0,0 +1,3 @@
+{{#listItems.three}}
+{{title}}
+{{/listItems.three}}
diff --git a/packages/core/test/files/_patterns/00-test/_00-hidden-pattern.mustache b/packages/core/test/files/_patterns/00-test/_00-hidden-pattern.mustache
new file mode 100644
index 000000000..0f5831922
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/_00-hidden-pattern.mustache
@@ -0,0 +1 @@
+This is the hidden atom
\ No newline at end of file
diff --git a/test/files/_patterns/00-test/_ignored-pattern.mustache b/packages/core/test/files/_patterns/00-test/_ignored-pattern.mustache
similarity index 100%
rename from test/files/_patterns/00-test/_ignored-pattern.mustache
rename to packages/core/test/files/_patterns/00-test/_ignored-pattern.mustache
diff --git a/packages/core/test/files/_patterns/00-test/comment-tag.mustache b/packages/core/test/files/_patterns/00-test/comment-tag.mustache
new file mode 100644
index 000000000..c78688163
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/comment-tag.mustache
@@ -0,0 +1 @@
+{{{ tag1 }}}
{{{ tag2 }}}
{{{ tag3 }}}
diff --git a/packages/core/test/files/_patterns/00-test/comment.mustache b/packages/core/test/files/_patterns/00-test/comment.mustache
new file mode 100644
index 000000000..0dc3d3608
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/comment.mustache
@@ -0,0 +1 @@
+{{foo}} {{description}}
diff --git a/test/files/_patterns/00-test/link.mustache b/packages/core/test/files/_patterns/00-test/link.mustache
similarity index 100%
rename from test/files/_patterns/00-test/link.mustache
rename to packages/core/test/files/_patterns/00-test/link.mustache
diff --git a/packages/core/test/files/_patterns/00-test/linkInParameter.mustache b/packages/core/test/files/_patterns/00-test/linkInParameter.mustache
new file mode 100644
index 000000000..7cba618ce
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/linkInParameter.mustache
@@ -0,0 +1 @@
+{{> test-link(url: 'link.test-comment') }}
diff --git a/packages/core/test/files/_patterns/00-test/listWithListItems.listitems.json b/packages/core/test/files/_patterns/00-test/listWithListItems.listitems.json
new file mode 100644
index 000000000..01497948f
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/listWithListItems.listitems.json
@@ -0,0 +1,11 @@
+{
+ "1": {
+ "title": "tX"
+ },
+ "2": {
+ "title": "tY"
+ },
+ "3": {
+ "title": "tZ"
+ }
+}
diff --git a/packages/core/test/files/_patterns/00-test/listWithListItems.mustache b/packages/core/test/files/_patterns/00-test/listWithListItems.mustache
new file mode 100644
index 000000000..34699f54e
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/listWithListItems.mustache
@@ -0,0 +1,3 @@
+{{#listItems.three}}
+ {{> test-mirror }}
+{{/listItems.three}}
diff --git a/packages/core/test/files/_patterns/00-test/listWithPartial.mustache b/packages/core/test/files/_patterns/00-test/listWithPartial.mustache
new file mode 100644
index 000000000..5a734227a
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/listWithPartial.mustache
@@ -0,0 +1,3 @@
+{{#listItems.two}}
+{{> test-comment }}
+{{/listItems.two}}
diff --git a/packages/core/test/files/_patterns/00-test/mirror.mustache b/packages/core/test/files/_patterns/00-test/mirror.mustache
new file mode 100644
index 000000000..31ea13421
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/mirror.mustache
@@ -0,0 +1 @@
+{{title}}{{description}}
diff --git a/test/files/_patterns/00-test/nav.json b/packages/core/test/files/_patterns/00-test/nav.json
similarity index 100%
rename from test/files/_patterns/00-test/nav.json
rename to packages/core/test/files/_patterns/00-test/nav.json
diff --git a/test/files/_patterns/00-test/nav.mustache b/packages/core/test/files/_patterns/00-test/nav.mustache
similarity index 100%
rename from test/files/_patterns/00-test/nav.mustache
rename to packages/core/test/files/_patterns/00-test/nav.mustache
diff --git a/packages/core/test/files/_patterns/00-test/paramMiddle.mustache b/packages/core/test/files/_patterns/00-test/paramMiddle.mustache
new file mode 100644
index 000000000..31169fb7e
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/paramMiddle.mustache
@@ -0,0 +1,3 @@
+
+ {{> test-link }}
+
diff --git a/packages/core/test/files/_patterns/00-test/paramParent.json b/packages/core/test/files/_patterns/00-test/paramParent.json
new file mode 100644
index 000000000..d913cc535
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/paramParent.json
@@ -0,0 +1,3 @@
+{
+ "url" : "link.test-foo"
+}
diff --git a/packages/core/test/files/_patterns/00-test/paramParent.mustache b/packages/core/test/files/_patterns/00-test/paramParent.mustache
new file mode 100644
index 000000000..bf4e84562
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/paramParent.mustache
@@ -0,0 +1 @@
+{{> test-paramMiddle(styleModifier: "foo") }}
diff --git a/packages/core/test/files/_patterns/00-test/parameterTags.mustache b/packages/core/test/files/_patterns/00-test/parameterTags.mustache
new file mode 100644
index 000000000..d8d3955f4
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/parameterTags.mustache
@@ -0,0 +1 @@
+{{> test-comment-tag(tag1: 'Single-quoted ', tag2: \"Double-quoted \", tag3: 'With attributes ') }}
diff --git a/packages/core/test/files/_patterns/00-test/sticky-comment-verbose.mustache b/packages/core/test/files/_patterns/00-test/sticky-comment-verbose.mustache
new file mode 100644
index 000000000..55b21011d
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/sticky-comment-verbose.mustache
@@ -0,0 +1 @@
+{{> 00-test/comment(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}
diff --git a/packages/core/test/files/_patterns/00-test/sticky-comment.mustache b/packages/core/test/files/_patterns/00-test/sticky-comment.mustache
new file mode 100644
index 000000000..4bc1899e9
--- /dev/null
+++ b/packages/core/test/files/_patterns/00-test/sticky-comment.mustache
@@ -0,0 +1 @@
+{{> test-comment(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}
diff --git a/packages/core/test/files/_patterns/patternType1/patternSubType1.md b/packages/core/test/files/_patterns/patternType1/patternSubType1.md
new file mode 100644
index 000000000..c34a320f2
--- /dev/null
+++ b/packages/core/test/files/_patterns/patternType1/patternSubType1.md
@@ -0,0 +1,5 @@
+---
+title: Colors
+---
+
+Colors
diff --git a/test/files/_patterns/patternType1/patternSubType1/blue.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType1/blue.mustache
similarity index 100%
rename from test/files/_patterns/patternType1/patternSubType1/blue.mustache
rename to packages/core/test/files/_patterns/patternType1/patternSubType1/blue.mustache
diff --git a/test/files/_patterns/patternType1/patternSubType1/red.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType1/red.mustache
similarity index 100%
rename from test/files/_patterns/patternType1/patternSubType1/red.mustache
rename to packages/core/test/files/_patterns/patternType1/patternSubType1/red.mustache
diff --git a/test/files/_patterns/patternType1/patternSubType1/yellow.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType1/yellow.mustache
similarity index 100%
rename from test/files/_patterns/patternType1/patternSubType1/yellow.mustache
rename to packages/core/test/files/_patterns/patternType1/patternSubType1/yellow.mustache
diff --git a/test/files/_patterns/patternType1/patternType2/black.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType2/black.mustache
similarity index 100%
rename from test/files/_patterns/patternType1/patternType2/black.mustache
rename to packages/core/test/files/_patterns/patternType1/patternSubType2/black.mustache
diff --git a/test/files/_patterns/patternType1/patternType2/grey.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType2/grey.mustache
similarity index 100%
rename from test/files/_patterns/patternType1/patternType2/grey.mustache
rename to packages/core/test/files/_patterns/patternType1/patternSubType2/grey.mustache
diff --git a/test/files/_patterns/patternType1/patternType2/white.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType2/white.mustache
similarity index 100%
rename from test/files/_patterns/patternType1/patternType2/white.mustache
rename to packages/core/test/files/_patterns/patternType1/patternSubType2/white.mustache
diff --git a/packages/core/test/files/_react-test-patterns/00-atoms/00-general/HelloWorld.jsx b/packages/core/test/files/_react-test-patterns/00-atoms/00-general/HelloWorld.jsx
new file mode 100644
index 000000000..9790e5af0
--- /dev/null
+++ b/packages/core/test/files/_react-test-patterns/00-atoms/00-general/HelloWorld.jsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+const HelloWorld = () => Hello world!
;
+
+export default HelloWorld;
diff --git a/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig b/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig
new file mode 100644
index 000000000..cf0041685
--- /dev/null
+++ b/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig
@@ -0,0 +1,10 @@
+
+
+Button
diff --git a/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig b/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig
new file mode 100644
index 000000000..01c4af9f8
--- /dev/null
+++ b/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig
@@ -0,0 +1,5 @@
+
diff --git a/packages/core/test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig b/packages/core/test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig
new file mode 100644
index 000000000..09af05118
--- /dev/null
+++ b/packages/core/test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig
@@ -0,0 +1,32 @@
+
+
+
+{% set foo = "world!" %}
+
+
+
diff --git a/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.html b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.html
similarity index 100%
rename from test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.html
rename to packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.html
diff --git a/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.json b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.json
similarity index 100%
rename from test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.json
rename to packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.json
diff --git a/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld.html b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld.html
similarity index 100%
rename from test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld.html
rename to packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld.html
diff --git a/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/_00-hidden.html b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/_00-hidden.html
new file mode 100644
index 000000000..b2c93a13e
--- /dev/null
+++ b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/_00-hidden.html
@@ -0,0 +1 @@
+I'm the hidden atom
diff --git a/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.html b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.html
similarity index 100%
rename from test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.html
rename to packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.html
diff --git a/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json
similarity index 100%
rename from test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json
rename to packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json
diff --git a/test/files/_underscore-test-patterns/00-molecules/00-global/00-helloworlds.html b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-helloworlds.html
similarity index 100%
rename from test/files/_underscore-test-patterns/00-molecules/00-global/00-helloworlds.html
rename to packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-helloworlds.html
diff --git a/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.html b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.html
new file mode 100644
index 000000000..fe3beef68
--- /dev/null
+++ b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.html
@@ -0,0 +1 @@
+Here's the hidden atom: [<%=_.renderNamedPartial('atoms-hidden', obj)%>]
diff --git a/test/files/annotations.js b/packages/core/test/files/annotations.js
similarity index 91%
rename from test/files/annotations.js
rename to packages/core/test/files/annotations.js
index f1077ed02..6bc8c1432 100644
--- a/test/files/annotations.js
+++ b/packages/core/test/files/annotations.js
@@ -1,13 +1,13 @@
var comments = {
- "comments" : [
+ "comments": [
{
"el": "header[role=banner]",
- "title" : "Masthead",
+ "title": "Masthead",
"comment": "The main header of the site doesn't take up too much screen real estate in order to keep the focus on the core content. It's using a linear CSS gradient instead of a background image to give greater design flexibility and reduce HTTP requests."
},
{
"el": ".logo",
- "title" : "Logo",
+ "title": "Logo",
"comment": "The logo image is an SVG file, which ensures that the logo displays crisply even on high resolution displays. A PNG fallback is provided for browsers that don't support SVG images.Further reading: Optimizing Web Experiences for High Resolution Screens
"
}
]
diff --git a/test/files/annotations.md b/packages/core/test/files/annotations.md
similarity index 100%
rename from test/files/annotations.md
rename to packages/core/test/files/annotations.md
diff --git a/packages/core/test/files/empty/.gitkeep b/packages/core/test/files/empty/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/files/nav.md b/packages/core/test/files/nav.md
similarity index 100%
rename from test/files/nav.md
rename to packages/core/test/files/nav.md
diff --git a/packages/core/test/files/partials/general-footer.mustache b/packages/core/test/files/partials/general-footer.mustache
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/core/test/files/partials/general-header.mustache b/packages/core/test/files/partials/general-header.mustache
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/core/test/files/partials/patternSection.mustache b/packages/core/test/files/partials/patternSection.mustache
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/core/test/files/partials/patternSectionSubtype.mustache b/packages/core/test/files/partials/patternSectionSubtype.mustache
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/core/test/files/viewall.mustache b/packages/core/test/files/viewall.mustache
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/core/test/get_tests.js b/packages/core/test/get_tests.js
new file mode 100644
index 000000000..5e954182c
--- /dev/null
+++ b/packages/core/test/get_tests.js
@@ -0,0 +1,84 @@
+'use strict';
+
+const tap = require('tap');
+
+const util = require('./util/test_utils.js');
+const getPartial = require('../src/lib/get');
+
+const patterns_dir = './test/files/_patterns';
+
+tap.test('getPartial - returns the fuzzy result when no others found', function(
+ test
+) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ patternlab.patterns = [];
+
+ patternlab.patterns.push({
+ patternPartial: 'character-han-solo',
+ subdir: 'character',
+ fileName: 'han-solo',
+ verbosePartial: 'character/han-solo',
+ });
+
+ //act
+ var result = getPartial('character-han', patternlab);
+
+ //assert
+ test.equals(result, patternlab.patterns[0]);
+ test.end();
+});
+
+tap.test('getPartial - returns the verbose result if found', function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ patternlab.patterns = [];
+
+ patternlab.patterns.push(
+ {
+ patternPartial: 'molecules-primary-nav-jagged',
+ subdir: 'molecules',
+ fileName: 'primary-nav-jagged',
+ verbosePartial: 'molecules/primary-nav-jagged',
+ },
+ {
+ patternPartial: 'molecules-primary-nav',
+ subdir: 'molecules',
+ fileName: 'molecules-primary-nav',
+ verbosePartial: 'molecules/primary-nav',
+ }
+ );
+
+ //act
+ var result = getPartial('molecules/primary-nav', patternlab);
+
+ //assert
+ test.equals(result, patternlab.patterns[1]);
+ test.end();
+});
+
+tap.test('getPartial - returns the exact key if found', function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ patternlab.patterns = [];
+
+ patternlab.patterns.push(
+ {
+ patternPartial: 'molecules-primary-nav-jagged',
+ subdir: 'molecules',
+ fileName: 'primary-nav-jagged',
+ },
+ {
+ patternPartial: 'molecules-primary-nav',
+ subdir: 'molecules',
+ fileName: 'molecules-primary-nav',
+ }
+ );
+
+ //act
+ var result = getPartial('molecules-primary-nav', patternlab);
+
+ //assert
+ test.equals(result, patternlab.patterns[1]);
+ test.end();
+});
diff --git a/packages/core/test/index_tests.js b/packages/core/test/index_tests.js
new file mode 100644
index 000000000..d2e98895e
--- /dev/null
+++ b/packages/core/test/index_tests.js
@@ -0,0 +1,337 @@
+const tap = require('tap');
+const rewire = require('rewire');
+const _ = require('lodash');
+const fs = require('fs-extra');
+const get = require('../src/lib/get');
+const events = require('../src/lib/events');
+
+const util = require('./util/test_utils.js');
+const entry = rewire('../src/index');
+const defaultConfig = require('../patternlab-config.json');
+const testConfig = require('./util/patternlab-config.json');
+const packageInfo = require('./../package');
+
+process.env.PATTERNLAB_ENV = 'CI';
+
+//set up a global mocks - we don't want to be writing/rendering any files right now
+
+const copierMock = function() {
+ return {
+ copyAndWatch: function() {
+ return Promise.resolve();
+ },
+ };
+};
+
+const uiBuilderMock = function() {
+ return {
+ buildFrontend: function() {
+ return Promise.resolve();
+ },
+ };
+};
+
+const fsMock = {
+ outputFileSync: function(path, content) {
+ /* INTENTIONAL NOOP */
+ },
+ readJSONSync: function(path, encoding) {
+ return fs.readJSONSync(path, encoding);
+ },
+ emptyDir: function(path) {
+ return fs.emptyDir(path);
+ },
+ readFileSync: function(path, encoding) {
+ return fs.readFileSync(path, encoding);
+ },
+};
+
+const buildPatternsMock = () => {
+ return Promise.resolve();
+};
+
+//set our mocks in place of usual require()
+entry.__set__({
+ ui_builder: uiBuilderMock,
+ fs: fsMock,
+ copier: copierMock,
+});
+
+tap.test('version - should call patternlab.getVersion', test => {
+ //arrange
+ const pl = new entry(testConfig);
+
+ //act
+ //assert
+ test.equals(pl.version(), packageInfo.version);
+ test.end();
+});
+
+tap.test(
+ 'getDefaultConfig - static method should return the default config object',
+ test => {
+ const requestedConfig = entry.getDefaultConfig();
+ test.type(requestedConfig, 'object');
+ test.equals(requestedConfig, defaultConfig);
+ test.end();
+ }
+);
+
+tap.test(
+ 'getDefaultConfig - instance method should return the default config object',
+ test => {
+ //arrange
+ const pl = new entry(testConfig);
+
+ //act
+ //assert
+ const requestedConfig = pl.getDefaultConfig();
+ test.type(requestedConfig, 'object');
+ test.equals(requestedConfig, defaultConfig);
+ test.end();
+ }
+);
+
+tap.test('patternsonly a promise', test => {
+ //arrange
+ const revert = entry.__set__('buildPatterns', buildPatternsMock);
+ const pl = new entry(testConfig);
+
+ //act
+ test.resolves(pl.patternsonly({})).then(() => {
+ revert();
+ test.end();
+ });
+});
+
+tap.test('patternsonly calls buildPatterns', test => {
+ //arrange
+ const revert = entry.__set__(
+ 'buildPatterns',
+ (cleanPublic, patternlab, data) => {
+ test.type(cleanPublic, 'boolean');
+ test.ok(cleanPublic);
+ test.type(patternlab, 'object');
+ test.type(data, 'object');
+ test.equals(data.foo, 'bar');
+ return Promise.resolve();
+ }
+ );
+ const pl = new entry(testConfig);
+
+ //act
+ test
+ .resolves(pl.patternsonly({ cleanPublic: true, data: { foo: 'bar' } }))
+ .then(() => {
+ revert();
+ test.end();
+ });
+});
+
+tap.test('serve calls serve', test => {
+ //arrange
+ const revert = entry.__set__('serverModule', patternlab => {
+ return {
+ serve: () => {
+ test.ok(1);
+ test.type(patternlab, 'object');
+ },
+ reload: () => {},
+ };
+ });
+
+ const pl = new entry(testConfig);
+
+ //act
+ test.resolves(pl.server.serve({})).then(() => {
+ revert();
+ test.end();
+ });
+});
+
+tap.test('buildPatterns suite', test => {
+ //arrange
+
+ const patternExporterMock = {
+ /*
+ In this suite, we actually take advantage of the pattern export functionality post-build to inspect what
+ the contents of the patterns look like. This, coupled with a mocking of fs and the ui_builder, allow us to focus
+ only on the order of events within build.
+ */
+ export_patterns: function(patternlab) {
+ tap.test(
+ 'replace data link even when pattern parameter present',
+ function(test) {
+ var pattern = get('test-paramParent', patternlab);
+ test.equals(
+ util.sanitized(pattern.extendedTemplate),
+ '',
+ 'partial inclusion completes'
+ );
+ test.equals(
+ pattern.patternPartialCode.indexOf('00-test-00-foo.rendered.html') >
+ -1,
+ true,
+ 'data link should be replaced properly'
+ );
+ test.end();
+ }
+ );
+
+ tap.test(
+ 'finds partials with their own parameters and renders them too',
+ function(test) {
+ var pattern = get('test-c', patternlab);
+ test.equals(
+ util.sanitized(pattern.patternPartialCode),
+ util.sanitized(`c
+ b
+ b!
+ a
+ a! `)
+ );
+ test.end();
+ }
+ );
+
+ tap.test(
+ 'finds and extends templates with mixed parameter and global data',
+ function(test) {
+ var pattern = get('test-sticky-comment', patternlab);
+ test.equals(
+ util.sanitized(pattern.patternPartialCode),
+ util.sanitized(
+ `Bar A life is like a garden. Perfect moments can be had, but not preserved, except in memory.
`
+ )
+ );
+ test.end();
+ }
+ );
+
+ tap.test('expands links inside parameters', function(test) {
+ var pattern = get('test-linkInParameter', patternlab);
+ test.equals(
+ util.sanitized(pattern.patternPartialCode),
+ util.sanitized(
+ `Cool Dude `
+ )
+ );
+ test.end();
+ });
+
+ tap.test('uses global listItem property', test => {
+ var pattern = get('test-listWithPartial', patternlab);
+ let assertionCount = 0;
+ ['dA', 'dB', 'dC'].forEach(d => {
+ if (pattern.patternPartialCode.indexOf(d) > -1) {
+ assertionCount++;
+ }
+ });
+ test.ok(assertionCount === 2);
+ test.end();
+ });
+
+ tap.test(
+ 'overwrites listItem property if that property is in local .listitem.json',
+ test => {
+ var pattern = get('test-listWithListItems', patternlab);
+ test.ok(pattern.patternPartialCode.indexOf('tX') > -1);
+ test.ok(pattern.patternPartialCode.indexOf('tY') > -1);
+ test.ok(pattern.patternPartialCode.indexOf('tZ') > -1);
+
+ test.end();
+ }
+ );
+
+ tap.test(
+ 'uses global listItem property after merging local .listitem.json',
+ test => {
+ var pattern = get('test-listWithListItems', patternlab);
+ test.ok(pattern.patternPartialCode.indexOf('dA') > -1);
+ test.ok(pattern.patternPartialCode.indexOf('dB') > -1);
+ test.ok(pattern.patternPartialCode.indexOf('dC') > -1);
+ test.end();
+ }
+ );
+
+ tap.test(
+ 'correctly ignores bookended partials without a style modifier when the same partial has a style modifier between',
+ test => {
+ var pattern = get('test-bookend-listitem', patternlab);
+ test.equals(
+ util.sanitized(pattern.extendedTemplate),
+ util.sanitized(`
+ {{#listItems-two}}
+
+ {{message}}
+
+
+
+ {{message}}
+
+
+
+ {{message}}
+
+
+ {{/listItems-two}}
+
+ `)
+ );
+ test.end();
+ }
+ );
+
+ tap.test(
+ 'listItems keys (`one` through `twelve`) can be used more than once per pattern',
+ test => {
+ var pattern = get('test-repeatedListItems', patternlab);
+ test.equals(
+ util.sanitized(pattern.patternPartialCode),
+ util.sanitized(`AAA BBB`)
+ );
+ test.end();
+ }
+ );
+
+ /////////////// FAILING ///////////////////
+ // todo
+ // From issue #145 https://github.com/pattern-lab/patternlab-node/issues/145
+ // tap.test(' parses parameters containing html tags', function (test) {
+ // var pattern = get('test-parameterTags', patternlab);
+ // test.equals(util.sanitized(pattern.patternPartialCode), util.sanitized(`Single-quoted
Double-quoted
With attributes
`));
+ // test.end();
+ // });
+
+ process.env.PATTERNLAB_ENV = '';
+ },
+ };
+
+ entry.__set__({
+ pattern_exporter: patternExporterMock,
+ });
+
+ testConfig.patternExportPatternPartials = ['test-paramParent'];
+ const pl = new entry(testConfig);
+
+ test.equals(pl.events.eventNames().length, 0);
+
+ //act
+ return pl
+ .build({
+ cleanPublic: true,
+ data: {
+ foo: 'Bar',
+ description: 'Baz',
+ },
+ })
+ .then(() => {
+ test.equals(
+ pl.events.eventNames().length,
+ 2,
+ 'should register two events'
+ );
+ test.equals(pl.events.listenerCount(events.PATTERNLAB_PATTERN_CHANGE), 1);
+ test.equals(pl.events.listenerCount(events.PATTERNLAB_GLOBAL_CHANGE), 1);
+ });
+});
diff --git a/packages/core/test/lineage_hunter_tests.js b/packages/core/test/lineage_hunter_tests.js
new file mode 100644
index 000000000..b19157a28
--- /dev/null
+++ b/packages/core/test/lineage_hunter_tests.js
@@ -0,0 +1,694 @@
+'use strict';
+
+const tap = require('tap');
+const fs = require('fs-extra');
+const path = require('path');
+const extend = require('util')._extend;
+
+const lh = require('../src/lib/lineage_hunter');
+const loadPattern = require('../src/lib/loadPattern');
+const of = require('../src/lib/object_factory');
+const Pattern = require('../src/lib/object_factory').Pattern;
+const PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
+const config = require('./util/patternlab-config.json');
+const addPattern = require('../src/lib/addPattern');
+const getPartial = require('../src/lib/get');
+
+const engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+
+const lineage_hunter = new lh();
+
+// fake pattern creators
+function createFakeEmptyErrorPattern() {
+ return new Pattern(
+ '01-molecules/01-toast/00-error.mustache', // relative path now
+ null // data
+ );
+}
+
+function createBasePatternLabObject() {
+ var patterns_dir = `${__dirname}/files/_patterns/`;
+ var pl = {};
+ (pl.graph = PatternGraph.empty()),
+ (pl.config = {
+ paths: {
+ source: {
+ patterns: patterns_dir,
+ },
+ public: {
+ patterns: `${__dirname}/public/_patterns`,
+ },
+ },
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ patternStateCascade: ['inprogress', 'inreview', 'complete'],
+ });
+ pl.data = {};
+ pl.data.link = {};
+ pl.config.logLevel = 'quiet';
+ pl.patterns = [];
+ pl.partials = {};
+ pl.patternGroups = {};
+ pl.subtypePatterns = {};
+
+ return pl;
+}
+
+tap.test('find_lineage - finds lineage', function(test) {
+ //setup current pattern from what we would have during execution
+ var currentPattern = new Pattern(
+ '02-organisms/00-global/00-header.mustache', // relative path now
+ null // data
+ );
+ extend(currentPattern, {
+ template:
+ '\r\n\r\n\r\n',
+ patternPartialCode:
+ '\r\n\r\n\r\n',
+ });
+
+ var patternlab = {
+ graph: new PatternGraph(null, 0),
+ patterns: [
+ Pattern.createEmpty({
+ name: '00-atoms-03-images-00-logo',
+ subdir: '00-atoms\\03-images',
+ filename: '00-logo.mustache',
+ data: null,
+ template:
+ ' ',
+ patternPartialCode:
+ ' ',
+ patternBaseName: 'logo',
+ patternLink:
+ '00-atoms-03-images-00-logo/00-atoms-03-images-00-logo.html',
+ patternGroup: 'atoms',
+ patternSubGroup: 'atoms\\03-images',
+ flatPatternPath: '00-atoms\\03-images',
+ patternPartial: 'atoms-logo',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ }),
+ Pattern.createEmpty({
+ name: '01-molecules-05-navigation-00-primary-nav',
+ subdir: '01-molecules\\05-navigation',
+ filename: '00-primary-nav.mustache',
+ data: null,
+ template:
+ '\r\n\t\r\n \r\n',
+ patternPartialCode:
+ '\r\n\t\r\n \r\n',
+ patternBaseName: 'primary-nav',
+ patternLink:
+ '01-molecules-05-navigation-00-primary-nav/01-molecules-05-navigation-00-primary-nav.html',
+ patternGroup: 'molecules',
+ patternSubGroup: 'molecules\\05-navigation',
+ flatPatternPath: '01-molecules\\05-navigation',
+ patternPartial: 'molecules-primary-nav',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ }),
+ Pattern.createEmpty({
+ name: '01-molecules-04-forms-00-search',
+ subdir: '01-molecules\\04-forms',
+ filename: '00-search.mustache',
+ data: null,
+ template:
+ '',
+ patternPartialCode:
+ '',
+ patternBaseName: 'search',
+ patternLink:
+ '01-molecules-04-forms-00-search/01-molecules-04-forms-00-search.html',
+ patternGroup: 'molecules',
+ patternSubGroup: 'molecules\\04-forms',
+ flatPatternPath: '01-molecules\\04-forms',
+ patternPartial: 'molecules-search',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ }),
+ ],
+ config: {
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ },
+ };
+ // BAD: This "patches" the relative path which is unset when using "createEmpty"
+ patternlab.patterns.forEach(p => (p.relPath = p.patternLink));
+
+ lineage_hunter.find_lineage(currentPattern, patternlab);
+
+ var graphLineageIndex = patternlab.graph.lineageIndex(currentPattern);
+
+ // Ensure compatibility
+ for (let i of [currentPattern.lineageIndex, graphLineageIndex]) {
+ test.equals(i.length, 3);
+ test.equals(i[0], 'atoms-logo');
+ test.equals(i[1], 'molecules-primary-nav');
+ test.equals(i[2], 'molecules-search');
+ }
+
+ test.end();
+});
+
+tap.test(
+ 'find_lineage - finds lineage with spaced pattern parameters',
+ function(test) {
+ //setup current pattern from what we would have during execution
+ var currentPattern = createFakeEmptyErrorPattern();
+ extend(currentPattern, {
+ template: "{{> atoms-error(message: 'That\\'s no moon...') }}",
+ extendedTemplate: "{{> atoms-error(message: 'That\\'s no moon...') }}",
+ });
+
+ var patternlab = {
+ graph: new PatternGraph(null, 0),
+ patterns: [
+ Pattern.create('00-atoms/05-alerts/00-error.mustache', null, {
+ template: ' {{message}} ',
+ extendedTemplate: ' {{message}} ',
+ }),
+ ],
+ config: {
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ },
+ };
+
+ lineage_hunter.find_lineage(currentPattern, patternlab);
+
+ test.equals(currentPattern.lineageIndex.length, 1);
+ test.equals(currentPattern.lineageIndex[0], 'atoms-error');
+ test.equals(patternlab.patterns[0].lineageRIndex.length, 1);
+ test.equals(
+ patternlab.patterns[0].lineageR[0].lineagePattern,
+ 'molecules-error'
+ );
+
+ // Same as above, but as graph based variant
+ var graph = patternlab.graph;
+ // Test if there is an edge from molecule-toast-error to atoms-alerts-error
+ test.same(
+ graph.hasLink(currentPattern, patternlab.patterns[0]),
+ true,
+ 'There is an edge from the test-error molecule to the alerts-error atom'
+ );
+ var currentPatternLineageIndex = graph.lineageIndex(currentPattern);
+
+ test.equals(currentPatternLineageIndex.length, 1);
+ test.equals(currentPatternLineageIndex[0], 'atoms-error');
+
+ var patternlabPattern0_lineageRIndex = graph.lineageRIndex(
+ patternlab.patterns[0]
+ );
+ test.equals(patternlabPattern0_lineageRIndex.length, 1);
+ test.equals(patternlabPattern0_lineageRIndex[0], 'molecules-error');
+
+ test.end();
+ }
+);
+
+tap.test(
+ 'cascade_pattern_states promotes a lower pattern state up to the consumer',
+ function(test) {
+ //arrange
+ var pl = createBasePatternLabObject();
+
+ var atomPattern = new of.Pattern('00-test/01-bar.mustache');
+ atomPattern.template = fs.readFileSync(
+ pl.config.paths.source.patterns + '00-test/01-bar.mustache',
+ 'utf8'
+ );
+ atomPattern.extendedTemplate = atomPattern.template;
+ atomPattern.patternState = 'inreview';
+
+ addPattern(atomPattern, pl);
+
+ var consumerPattern = new of.Pattern('00-test/00-foo.mustache');
+ consumerPattern.template = fs.readFileSync(
+ pl.config.paths.source.patterns + '00-test/00-foo.mustache',
+ 'utf8'
+ );
+ consumerPattern.extendedTemplate = consumerPattern.template;
+ consumerPattern.patternState = 'complete';
+ addPattern(consumerPattern, pl);
+
+ lineage_hunter.find_lineage(consumerPattern, pl);
+
+ //act
+ lineage_hunter.cascade_pattern_states(pl);
+
+ //assert
+ var consumerPatternReturned = getPartial('test-foo', pl);
+ test.equals(consumerPatternReturned.patternState, 'inreview');
+ test.end();
+ }
+);
+
+tap.test(
+ 'cascade_pattern_states promotes a lower pattern state up to the consumers lineage',
+ function(test) {
+ //arrange
+ var pl = createBasePatternLabObject();
+
+ var atomPattern = new of.Pattern('00-test/01-bar.mustache');
+ atomPattern.template = fs.readFileSync(
+ pl.config.paths.source.patterns + '00-test/01-bar.mustache',
+ 'utf8'
+ );
+ atomPattern.extendedTemplate = atomPattern.template;
+ atomPattern.patternState = 'inreview';
+
+ addPattern(atomPattern, pl);
+
+ var consumerPattern = new of.Pattern('00-test/00-foo.mustache');
+ consumerPattern.template = fs.readFileSync(
+ pl.config.paths.source.patterns + '00-test/00-foo.mustache',
+ 'utf8'
+ );
+ consumerPattern.extendedTemplate = consumerPattern.template;
+ consumerPattern.patternState = 'complete';
+ addPattern(consumerPattern, pl);
+
+ lineage_hunter.find_lineage(consumerPattern, pl);
+
+ //act
+ lineage_hunter.cascade_pattern_states(pl);
+
+ //assert
+ var consumerPatternReturned = getPartial('test-foo', pl);
+ const lineage = pl.graph.lineage(consumerPatternReturned);
+ test.equals(lineage[0].lineageState, 'inreview');
+ test.end();
+ }
+);
+
+tap.test(
+ 'cascade_pattern_states sets the pattern state on any lineage patterns reverse lineage',
+ function(test) {
+ //arrange
+ var pl = createBasePatternLabObject();
+
+ var atomPattern = loadPattern('00-test/01-bar.mustache', pl);
+ var consumerPattern = loadPattern('00-test/00-foo.mustache', pl);
+
+ lineage_hunter.find_lineage(consumerPattern, pl);
+
+ //act
+ lineage_hunter.cascade_pattern_states(pl);
+
+ //assert
+ var consumedPatternReturned = getPartial('test-bar', pl);
+ let lineageR = pl.graph.lineageR(consumedPatternReturned);
+ test.equals(lineageR[0].lineageState, 'inreview');
+
+ test.end();
+ }
+);
+
+tap.test(
+ 'cascade_pattern_states promotes lower pattern state when consumer does not have its own state',
+ function(test) {
+ //arrange
+ var pl = createBasePatternLabObject();
+
+ var atomPattern = new of.Pattern('00-test/01-bar.mustache');
+ atomPattern.template = fs.readFileSync(
+ path.resolve(pl.config.paths.source.patterns, '00-test/01-bar.mustache'),
+ 'utf8'
+ );
+ atomPattern.extendedTemplate = atomPattern.template;
+ atomPattern.patternState = 'inreview';
+
+ addPattern(atomPattern, pl);
+
+ var consumerPattern = new of.Pattern('00-test/00-foo.mustache');
+ consumerPattern.template = fs.readFileSync(
+ path.resolve(pl.config.paths.source.patterns, '00-test/00-foo.mustache'),
+ 'utf8'
+ );
+ consumerPattern.extendedTemplate = consumerPattern.template;
+ addPattern(consumerPattern, pl);
+
+ lineage_hunter.find_lineage(consumerPattern, pl);
+
+ //act
+ lineage_hunter.cascade_pattern_states(pl);
+
+ //assert
+ var consumerPatternReturned = getPartial('test-foo', pl);
+ test.equals(consumerPatternReturned.lineage.length, 1);
+ test.equals(consumerPatternReturned.lineage[0].lineageState, 'inreview');
+ test.equals(consumerPatternReturned.patternState, 'inreview');
+ test.end();
+ }
+);
+
+tap.test(
+ 'find_lineage - finds lineage with unspaced pattern parameters',
+ function(test) {
+ //setup current pattern from what we would have during execution
+ var currentPattern = createFakeEmptyErrorPattern();
+ extend(currentPattern, {
+ template: "{{>atoms-error(message: 'That\\'s no moon...')}}",
+ extendedTemplate: "{{>atoms-error(message: 'That\\'s no moon...')}}",
+ });
+
+ var patternlab = {
+ graph: PatternGraph.empty(),
+ patterns: [
+ Pattern.createEmpty({
+ name: '01-atoms-05-alerts-00-error',
+ subdir: '01-atoms\\05-alerts',
+ filename: '00-error.mustache',
+ data: null,
+ template: ' {{message}} ',
+ extendedTemplate: ' {{message}} ',
+ patternBaseName: 'error',
+ patternLink:
+ '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html',
+ patternGroup: 'atoms',
+ patternSubGroup: 'atoms\\05-alerts',
+ flatPatternPath: '01-atoms\\05-alerts',
+ patternPartial: 'atoms-error',
+ patternState: '',
+ lineage: [],
+ currentPatternLineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ }),
+ ],
+ config: {
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ },
+ };
+
+ lineage_hunter.find_lineage(currentPattern, patternlab);
+
+ test.equals(currentPattern.lineageIndex.length, 1);
+ test.equals(currentPattern.lineageIndex[0], 'atoms-error');
+ test.equals(patternlab.patterns[0].lineageRIndex.length, 1);
+ test.equals(
+ patternlab.patterns[0].lineageR[0].lineagePattern,
+ 'molecules-error'
+ );
+
+ var currentPatternLineageIndex = patternlab.graph.lineageIndex(
+ currentPattern
+ );
+ test.equals(currentPatternLineageIndex.length, 1);
+ test.equals(currentPatternLineageIndex[0], 'atoms-error');
+
+ var pattern0LineageRIndex = patternlab.graph.lineageRIndex(
+ patternlab.patterns[0]
+ );
+ test.equals(pattern0LineageRIndex.length, 1);
+ test.equals(pattern0LineageRIndex[0], 'molecules-error');
+
+ test.end();
+ }
+);
+
+tap.test('find_lineage - finds lineage with spaced styleModifier', function(
+ test
+) {
+ //setup current pattern from what we would have during execution
+ var currentPattern = Pattern.createEmpty({
+ name: '01-molecules-01-toast-00-error',
+ subdir: '01-molecules\\01-toast',
+ filename: '00-error.mustache',
+ data: null,
+ template: '{{> atoms-error:foo }}',
+ extendedTemplate: '{{> atoms-error:foo }}',
+ patternBaseName: 'error',
+ patternLink:
+ '01-molecules-01-toast-00-error/01-molecules-01-toast-00-error.html',
+ patternGroup: 'molecules',
+ patternSubGroup: 'molecules\\01-toast',
+ flatPatternPath: '01-molecules\\01-toast',
+ patternPartial: 'molecules-error',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ });
+ var patternlab = {
+ graph: new PatternGraph(null, 0),
+ patterns: [
+ Pattern.createEmpty({
+ name: '01-atoms-05-alerts-00-error',
+ subdir: '01-atoms\\05-alerts',
+ filename: '00-error.mustache',
+ data: null,
+ template: ' {{message}} ',
+ extendedTemplate: ' {{message}} ',
+ patternBaseName: 'error',
+ patternLink:
+ '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html',
+ patternGroup: 'atoms',
+ patternSubGroup: 'atoms\\05-alerts',
+ flatPatternPath: '01-atoms\\05-alerts',
+ patternPartial: 'atoms-error',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ }),
+ ],
+ config: {
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ },
+ };
+
+ lineage_hunter.find_lineage(currentPattern, patternlab);
+
+ test.equals(currentPattern.lineageIndex.length, 1);
+ test.equals(currentPattern.lineageIndex[0], 'atoms-error');
+
+ test.end();
+});
+
+tap.test('find_lineage - finds lineage with unspaced styleModifier', function(
+ test
+) {
+ //setup current pattern from what we would have during execution
+ var currentPattern = Pattern.createEmpty({
+ name: '01-molecules-01-toast-00-error',
+ subdir: '01-molecules\\01-toast',
+ filename: '00-error.mustache',
+ data: null,
+ template: '{{> atoms-error:foo }}',
+ extendedTemplate: '{{>atoms-error:foo}}',
+ patternBaseName: 'error',
+ patternLink:
+ '01-molecules-01-toast-00-error/01-molecules-01-toast-00-error.html',
+ patternGroup: 'molecules',
+ patternSubGroup: 'molecules\\01-toast',
+ flatPatternPath: '01-molecules\\01-toast',
+ patternPartial: 'molecules-error',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ });
+ var patternlab = {
+ graph: PatternGraph.empty(),
+ patterns: [
+ Pattern.createEmpty({
+ name: '01-atoms-05-alerts-00-error',
+ subdir: '01-atoms\\05-alerts',
+ filename: '00-error.mustache',
+ data: null,
+ template: ' {{message}} ',
+ extendedTemlpate: ' {{message}} ',
+ patternBaseName: 'error',
+ patternLink:
+ '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html',
+ patternGroup: 'atoms',
+ patternSubGroup: 'atoms\\05-alerts',
+ flatPatternPath: '01-atoms\\05-alerts',
+ patternPartial: 'atoms-error',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ }),
+ ],
+ config: {
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ },
+ };
+
+ lineage_hunter.find_lineage(currentPattern, patternlab);
+
+ test.equals(currentPattern.lineageIndex.length, 1);
+ test.equals(currentPattern.lineageIndex[0], 'atoms-error');
+
+ test.end();
+});
+
+tap.test(
+ 'find_lineage - finds lineage with fuzzy partial with styleModifier',
+ function(test) {
+ //setup current pattern from what we would have during execution
+ var currentPattern = Pattern.createEmpty({
+ name: '01-molecules-01-toast-00-error',
+ subdir: '01-molecules\\01-toast',
+ filename: '00-error.mustache',
+ data: null,
+ template: '{{> atoms-e:foo }}',
+ extendedTemplate: '{{>atoms-e:foo}}',
+ patternBaseName: 'error',
+ patternLink:
+ '01-molecules-01-toast-00-error/01-molecules-01-toast-00-error.html',
+ patternGroup: 'molecules',
+ patternSubGroup: 'molecules\\01-toast',
+ flatPatternPath: '01-molecules\\01-toast',
+ patternPartial: 'molecules-error',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ });
+ var patternlab = {
+ graph: PatternGraph.empty(),
+ patterns: [
+ Pattern.createEmpty({
+ name: '01-atoms-05-alerts-00-error',
+ subdir: '01-atoms\\05-alerts',
+ filename: '00-error.mustache',
+ data: null,
+ template: ' {{message}} ',
+ extendedTemplate: ' {{message}} ',
+ patternBaseName: 'error',
+ patternLink:
+ '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html',
+ patternGroup: 'atoms',
+ patternSubGroup: 'atoms\\05-alerts',
+ flatPatternPath: '01-atoms\\05-alerts',
+ patternPartial: 'atoms-error',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ }),
+ ],
+ config: {
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ },
+ };
+
+ var lineage_hunter = new lh();
+ lineage_hunter.find_lineage(currentPattern, patternlab);
+
+ test.equals(currentPattern.lineageIndex.length, 1);
+ test.equals(currentPattern.lineageIndex[0], 'atoms-error');
+
+ test.end();
+ }
+);
+
+tap.test('find_lineage - does not apply lineage twice', function(test) {
+ //setup current pattern from what we would have during execution
+ var currentPattern = createFakeEmptyErrorPattern();
+ extend(currentPattern, {
+ template: "{{>atoms-error(message: 'That\\'s no moon...')}}",
+ extendedTemplate: "{{>atoms-error(message: 'That\\'s no moon...')}}",
+ });
+ var patternlab = {
+ graph: PatternGraph.empty(),
+ patterns: [
+ Pattern.createEmpty({
+ name: '01-atoms-05-alerts-00-error',
+ subdir: '01-atoms\\05-alerts',
+ filename: '00-error.mustache',
+ data: null,
+ template: ' {{message}} ',
+ extendedTemplate: ' {{message}} ',
+ patternBaseName: 'error',
+ patternLink:
+ '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html',
+ patternGroup: 'atoms',
+ patternSubGroup: 'atoms\\05-alerts',
+ flatPatternPath: '01-atoms\\05-alerts',
+ patternPartial: 'atoms-error',
+ patternState: '',
+ lineage: [],
+ lineageIndex: [],
+ lineageR: [],
+ lineageRIndex: [],
+ }),
+ ],
+ config: {
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ },
+ };
+
+ var lineage_hunter = new lh();
+ lineage_hunter.find_lineage(currentPattern, patternlab);
+ lineage_hunter.find_lineage(currentPattern, patternlab);
+
+ test.equals(currentPattern.lineageIndex.length, 1);
+ test.equals(currentPattern.lineageIndex[0], 'atoms-error');
+ test.equals(patternlab.patterns[0].lineageRIndex.length, 1);
+ test.equals(
+ patternlab.patterns[0].lineageR[0].lineagePattern,
+ 'molecules-error'
+ );
+
+ var graph = patternlab.graph;
+
+ var currentPatternLineageIndex = graph.lineageIndex(currentPattern);
+ test.equals(currentPatternLineageIndex.length, 1);
+ test.equals(currentPatternLineageIndex[0], 'atoms-error');
+ var patternZeroLineageR = graph.lineageR(patternlab.patterns[0]);
+ test.equals(patternZeroLineageR.length, 1);
+ test.equals(patternZeroLineageR[0].patternPartial, 'molecules-error');
+
+ test.end();
+});
diff --git a/packages/core/test/list_item_hunter_tests.js b/packages/core/test/list_item_hunter_tests.js
new file mode 100644
index 000000000..278de7dcc
--- /dev/null
+++ b/packages/core/test/list_item_hunter_tests.js
@@ -0,0 +1,69 @@
+'use strict';
+
+const tap = require('tap');
+const path = require('path');
+
+const lih = require('../src/lib/list_item_hunter');
+const list_item_hunter = new lih();
+const util = require('./util/test_utils.js');
+const loadPattern = require('../src/lib/loadPattern');
+
+const testPatternsPath = path.resolve(__dirname, 'files', '_patterns');
+
+const config = require('./util/patternlab-config.json');
+const engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+
+tap.test(
+ 'process_list_item_partials converts partial to simpler format',
+ test => {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+ const listPath = path.join('00-test', '685-list.mustache');
+ const testPattern = loadPattern(listPath, pl);
+
+ //usually decompose does this
+ testPattern.extendedTemplate = testPattern.template;
+
+ //act
+ list_item_hunter.process_list_item_partials(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized(`
+ {{#listItems-three}}
+ {{title}}
+ {{/listItems-three}}
+ `)
+ );
+ test.end();
+ });
+ }
+);
+
+tap.test(
+ 'process_list_item_partials converts partial with includes to simpler format',
+ test => {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+ const listPath = path.join('00-test', 'listWithPartial.mustache');
+ const testPattern = loadPattern(listPath, pl);
+
+ //usually decompose does this
+ testPattern.extendedTemplate = testPattern.template;
+
+ //act
+ list_item_hunter.process_list_item_partials(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized(`
+ {{#listItems-two}}
+ {{> test-comment }}
+ {{/listItems-two}}
+ `)
+ );
+ test.end();
+ });
+ }
+);
diff --git a/packages/core/test/loadPattern_tests.js b/packages/core/test/loadPattern_tests.js
new file mode 100644
index 000000000..2ebec760f
--- /dev/null
+++ b/packages/core/test/loadPattern_tests.js
@@ -0,0 +1,109 @@
+'use strict';
+
+const path = require('path');
+const tap = require('tap');
+
+const loadPattern = require('../src/lib/loadPattern');
+const util = require('./util/test_utils.js');
+const patternEngines = require('../src/lib/pattern_engines');
+var config = require('./util/patternlab-config.json');
+
+patternEngines.loadAllEngines(config);
+
+const patterns_dir = `${__dirname}/files/_patterns`;
+
+tap.test('loadPattern - returns null if file is not a pattern', function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ var patternPath = path.join('00-test', '03-styled-atom.json');
+
+ //act
+ var result = loadPattern(patternPath, patternlab);
+
+ //assert
+ test.equals(result, null);
+ test.end();
+});
+
+tap.test('loadPattern - loads pattern sibling json if found', function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ var patternPath = path.join('00-test', '03-styled-atom.mustache');
+
+ //act
+ var result = loadPattern(patternPath, patternlab);
+
+ //assert
+ test.equals(result.jsonFileData.message, 'baseMessage');
+ test.end();
+});
+
+tap.test(
+ 'loadPattern - adds the pattern to the patternlab.partials object',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ var fooPatternPath = path.join('00-test', '01-bar.mustache');
+
+ //act
+ var result = loadPattern(fooPatternPath, patternlab);
+
+ //assert
+ test.equals(util.sanitized(patternlab.partials['test-bar']), 'bar');
+ test.end();
+ }
+);
+
+tap.test('loadPattern - returns pattern with template populated', function(
+ test
+) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ var fooPatternPath = path.join('00-test', '01-bar.mustache');
+
+ //act
+ var result = loadPattern(fooPatternPath, patternlab);
+
+ //assert
+ test.equals(util.sanitized(result.template), util.sanitized('bar'));
+ test.end();
+});
+
+tap.test('loadPattern - adds a markdown pattern if encountered', function(
+ test
+) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ var colorsMarkDownPath = path.join('patternType1', 'patternSubType1.md');
+
+ //act
+ var result = loadPattern(colorsMarkDownPath, patternlab);
+
+ //assert
+ const subTypePattern =
+ patternlab.subtypePatterns['patternType1-patternSubType1'];
+ test.equals(subTypePattern.patternSectionSubtype, true);
+ test.equals(subTypePattern.isPattern, false);
+ test.equals(subTypePattern.patternDesc, 'Colors
\n');
+ test.equals(subTypePattern.engine, null);
+ test.equals(subTypePattern.flatPatternPath, 'patternType1-patternSubType1');
+ test.equals(result, subTypePattern);
+ test.end();
+});
+
+tap.test(
+ 'loadPattern - does not load pseudopattern data on the base pattern',
+ test => {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ const basePatternPath = path.join('00-test', '474-pseudomodifier.mustache');
+
+ //act
+ const result = loadPattern(basePatternPath, patternlab);
+
+ //assert
+ test.same(result.jsonFileData, {});
+
+ test.end();
+ }
+);
diff --git a/packages/core/test/loaduitkits_tests.js b/packages/core/test/loaduitkits_tests.js
new file mode 100644
index 000000000..23bdcd656
--- /dev/null
+++ b/packages/core/test/loaduitkits_tests.js
@@ -0,0 +1,150 @@
+'use strict';
+
+const tap = require('tap');
+const rewire = require('rewire');
+
+const logger = require('../src/lib/log');
+const loaduikits = rewire('../src/lib/loaduikits');
+
+const testConfig = require('./util/patternlab-config.json');
+
+const findModulesMock = function() {
+ return [
+ {
+ name: 'foo',
+ modulePath: 'node_modules/@pattern-lab/uikit-foo',
+ },
+ {
+ name: 'bar',
+ modulePath: 'node_modules/@pattern-lab/uikit-bar',
+ },
+ {
+ name: 'baz',
+ modulePath: 'node_modules/@pattern-lab/uikit-baz',
+ },
+ {
+ name: 'polyfills',
+ modulePath: 'node_modules/@pattern-lab/uikit-polyfills',
+ },
+ ];
+};
+
+const fsMock = {
+ readFileSync: function(path, encoding) {
+ return 'file';
+ },
+};
+
+loaduikits.__set__({
+ findModules: findModulesMock,
+ fs: fsMock,
+});
+
+logger;
+
+tap.test('loaduitkits - does not warn on uikit-polyfills', test => {
+ //arrange
+ const patternlab = {
+ config: testConfig,
+ uikits: {},
+ };
+
+ patternlab.config.logLevel = 'warning';
+ logger.log.on('warning', msg => test.notOk(msg.includes('uikit-polyfills')));
+
+ const uikitFoo = {
+ name: 'uikit-foo',
+ enabled: true,
+ outputDir: 'foo',
+ excludedPatternStates: ['legacy'],
+ excludedTags: ['baz'],
+ };
+
+ patternlab.config.uikits = [uikitFoo];
+
+ //act
+ loaduikits(patternlab).then(() => {
+ logger.warning = () => {};
+ test.done();
+ });
+});
+
+tap.test('loaduikits - maps fields correctly', function(test) {
+ //arrange
+ const patternlab = {
+ config: testConfig,
+ uikits: {},
+ };
+
+ const uikitFoo = {
+ name: 'uikit-foo',
+ enabled: true,
+ outputDir: 'foo',
+ excludedPatternStates: ['legacy'],
+ excludedTags: ['baz'],
+ };
+
+ patternlab.config.uikits = [uikitFoo];
+
+ //act
+ loaduikits(patternlab).then(() => {
+ //assert
+ test.equals(patternlab.uikits['uikit-foo'].name, uikitFoo.name);
+ test.equals(
+ patternlab.uikits['uikit-foo'].modulePath,
+ 'node_modules/@pattern-lab/uikit-foo'
+ );
+ test.ok(patternlab.uikits['uikit-foo'].enabled);
+ test.equals(patternlab.uikits['uikit-foo'].outputDir, uikitFoo.outputDir);
+ test.equals(
+ patternlab.uikits['uikit-foo'].excludedPatternStates,
+ uikitFoo.excludedPatternStates
+ );
+ test.equals(
+ patternlab.uikits['uikit-foo'].excludedTags,
+ uikitFoo.excludedTags
+ );
+ test.end();
+ });
+});
+
+tap.test('loaduikits - only adds files for enabled uikits', function(test) {
+ //arrange
+ const patternlab = {
+ config: testConfig,
+ uikits: {},
+ };
+
+ patternlab.config.uikits = [
+ {
+ name: 'uikit-foo',
+ enabled: true,
+ outputDir: 'foo',
+ excludedPatternStates: ['legacy'],
+ excludedTags: ['baz'],
+ },
+ {
+ name: 'uikit-bar',
+ enabled: true,
+ outputDir: 'bar',
+ excludedPatternStates: ['development'],
+ excludedTags: ['baz', 'foo'],
+ },
+ {
+ name: 'uikit-baz',
+ enabled: false,
+ outputDir: 'baz',
+ excludedPatternStates: [''],
+ excludedTags: [],
+ },
+ ];
+
+ //act
+ loaduikits(patternlab).then(() => {
+ //assert
+ test.ok(patternlab.uikits['uikit-foo']);
+ test.ok(patternlab.uikits['uikit-bar']);
+ test.notOk(patternlab.uikits['uikit-baz']);
+ test.end();
+ });
+});
diff --git a/packages/core/test/markModifiedPatterns_tests.js b/packages/core/test/markModifiedPatterns_tests.js
new file mode 100644
index 000000000..173e0283f
--- /dev/null
+++ b/packages/core/test/markModifiedPatterns_tests.js
@@ -0,0 +1,119 @@
+'use strict';
+
+var tap = require('tap');
+var rewire = require('rewire');
+
+var Pattern = require('../src/lib/object_factory').Pattern;
+var CompileState = require('../src/lib/object_factory').CompileState;
+var PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
+var engineLoader = require('../src/lib/pattern_engines');
+
+const markModifiedPatterns = rewire('../src/lib/markModifiedPatterns');
+
+const config = require('./util/patternlab-config.json');
+
+const fsMock = {
+ readFileSync: function(path, encoding, cb) {
+ return '';
+ },
+};
+
+function emptyPatternLab() {
+ return {
+ graph: PatternGraph.empty(),
+ };
+}
+
+const public_dir = './test/public';
+
+tap.only(
+ 'markModifiedPatterns - finds patterns modified since a given date',
+ function(test) {
+ //arrange
+ markModifiedPatterns.__set__('fs', fsMock);
+
+ var patternlab = emptyPatternLab();
+ patternlab.config = config;
+ patternlab.config.paths.public.patterns = public_dir + '/patterns';
+ patternlab.config.outputFileSuffixes = {
+ rendered: '',
+ markupOnly: '.markup-only',
+ };
+
+ var pattern = new Pattern('00-test/01-bar.mustache');
+
+ pattern.extendedTemplate = undefined;
+ pattern.template = 'bar';
+ pattern.lastModified = new Date('2016-01-31').getTime();
+
+ // Initially the compileState is clean,
+ // but we would change this after detecting that the file was modified
+ pattern.compileState = CompileState.CLEAN;
+ patternlab.patterns = [pattern];
+
+ var lastCompilationRun = new Date('2016-01-01').getTime();
+ var modifiedOrNot = markModifiedPatterns(lastCompilationRun, patternlab);
+
+ test.same(
+ modifiedOrNot.modified.length,
+ 1,
+ 'The pattern was modified after the last compilation'
+ );
+
+ // Reset the compile state as it was previously set by pattern_assembler.mark_modified_patterns
+ pattern.compileState = CompileState.CLEAN;
+ lastCompilationRun = new Date('2016-12-31').getTime();
+ modifiedOrNot = markModifiedPatterns(lastCompilationRun, patternlab);
+ test.same(
+ modifiedOrNot.notModified.length,
+ 1,
+ "Pattern was already compiled and hasn't been modified since last compile"
+ );
+ test.end();
+ }
+);
+
+tap.test(
+ 'markModifiedPatterns - finds patterns when modification date is missing',
+ function(test) {
+ //arrange
+ var patternlab = emptyPatternLab();
+ patternlab.partials = {};
+ patternlab.data = { link: {} };
+ patternlab.config = { logLevel: 'quiet' };
+ patternlab.config.outputFileSuffixes = { rendered: '' };
+
+ var pattern = new Pattern('00-test/01-bar.mustache');
+ pattern.extendedTemplate = undefined;
+ pattern.template = 'bar';
+ pattern.lastModified = undefined;
+ patternlab.patterns = [pattern];
+
+ let p = markModifiedPatterns(1000, patternlab);
+ test.same(p.modified.length, 1);
+ test.end();
+ }
+);
+
+// This is the case when we want to force recompilation
+tap.test('markModifiedPatterns - finds patterns via compile state', function(
+ test
+) {
+ //arrange
+ var patternlab = emptyPatternLab();
+ patternlab.partials = {};
+ patternlab.data = { link: {} };
+ patternlab.config = { logLevel: 'quiet' };
+ patternlab.config.outputFileSuffixes = { rendered: '' };
+
+ var pattern = new Pattern('00-test/01-bar.mustache');
+ pattern.extendedTemplate = undefined;
+ pattern.template = 'bar';
+ pattern.lastModified = 100000;
+ pattern.compileState = CompileState.NEEDS_REBUILD;
+ patternlab.patterns = [pattern];
+
+ let p = markModifiedPatterns(1000, patternlab);
+ test.same(p.modified.length, 1);
+ test.end();
+});
diff --git a/packages/core/test/markdown_parser_tests.js b/packages/core/test/markdown_parser_tests.js
new file mode 100644
index 000000000..3e25a5934
--- /dev/null
+++ b/packages/core/test/markdown_parser_tests.js
@@ -0,0 +1,64 @@
+'use strict';
+
+var tap = require('tap');
+
+var path = require('path');
+var fs = require('fs-extra');
+var mp = require('../src/lib/markdown_parser');
+var markdown_parser = new mp();
+
+tap.test(
+ 'parses pattern description block correctly when frontmatter not present',
+ function(test) {
+ //arrange
+ var markdownFileName = path.resolve(
+ `${__dirname}/files/_patterns/00-test/02-baz.md`
+ );
+ var markdownFileContents = fs.readFileSync(markdownFileName, 'utf8');
+
+ //act
+ var returnObject = markdown_parser.parse(markdownFileContents);
+
+ //assert
+ test.equals(returnObject.markdown, 'Only baz \n');
+ test.end();
+ }
+);
+
+tap.test(
+ 'parses pattern description block correctly when frontmatter present',
+ function(test) {
+ //arrange
+ var markdownFileName = path.resolve(
+ `${__dirname}/files/_patterns/00-test/01-bar.md`
+ );
+ var markdownFileContents = fs.readFileSync(markdownFileName, 'utf8');
+
+ //act
+ var returnObject = markdown_parser.parse(markdownFileContents);
+
+ //assert
+ test.equals(
+ returnObject.markdown,
+ 'A Simple Bit of Markup \nFoo cannot get simpler than bar, amiright?
\n'
+ );
+ test.equals(returnObject.state, 'complete');
+ test.end();
+ }
+);
+
+tap.test('parses frontmatter only when no markdown present', function(test) {
+ //arrange
+ var markdownFileName = path.resolve(
+ `${__dirname}/files/_patterns/00-test/03-styled-atom.md`
+ );
+ var markdownFileContents = fs.readFileSync(markdownFileName, 'utf8');
+
+ //act
+ var returnObject = markdown_parser.parse(markdownFileContents);
+
+ //assert
+ test.equals(returnObject.markdown, '');
+ test.equals(returnObject.state, 'inprogress');
+ test.end();
+});
diff --git a/packages/core/test/object_factory_tests.js b/packages/core/test/object_factory_tests.js
new file mode 100644
index 000000000..910c02d89
--- /dev/null
+++ b/packages/core/test/object_factory_tests.js
@@ -0,0 +1,262 @@
+'use strict';
+
+var tap = require('tap');
+var config = require('./util/patternlab-config.json');
+
+// fake pattern lab constructor:
+// sets up a fake patternlab object, which is needed by the pattern processing
+// apparatus.
+function fakePatternLab() {
+ var fpl = {
+ partials: {},
+ patterns: [],
+ footer: '',
+ header: '',
+ listitems: {},
+ data: {
+ link: {},
+ },
+ config: config,
+ package: {},
+ };
+
+ return fpl;
+}
+
+var of = require('../src/lib/object_factory');
+var Pattern = require('../src/lib/object_factory').Pattern;
+var path = require('path');
+var pl = fakePatternLab();
+var engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+
+tap.test('test Pattern initializes correctly', function(test) {
+ var p = new Pattern('00-atoms/00-global/00-colors.mustache', { d: 123 });
+ test.equals(
+ p.relPath,
+ '00-atoms' + path.sep + '00-global' + path.sep + '00-colors.mustache'
+ );
+ test.equals(p.name, '00-atoms-00-global-00-colors');
+ test.equals(p.subdir, '00-atoms' + path.sep + '00-global');
+ test.equals(p.fileName, '00-colors');
+ test.equals(p.fileExtension, '.mustache');
+ test.equals(p.jsonFileData.d, 123);
+ test.equals(p.patternBaseName, 'colors');
+ test.equals(p.patternName, 'Colors');
+ test.equals(
+ p.getPatternLink(pl),
+ '00-atoms-00-global-00-colors' +
+ path.sep +
+ '00-atoms-00-global-00-colors.rendered.html'
+ );
+ test.equals(p.patternGroup, 'atoms');
+ test.equals(p.patternSubGroup, 'global');
+ test.equals(p.flatPatternPath, '00-atoms-00-global');
+ test.equals(p.patternPartial, 'atoms-colors');
+ test.equals(p.template, '');
+ test.equals(p.patternPartialCode, '');
+ test.equals(p.lineage.length, 0);
+ test.equals(p.lineageIndex.length, 0);
+ test.equals(p.lineageR.length, 0);
+ test.equals(p.lineageRIndex.length, 0);
+ test.equals(p.patternState, '');
+ test.end();
+});
+
+tap.test(
+ 'test Pattern initializes correctly with pattern in sepatated directory',
+ function(test) {
+ var p = new Pattern('00-atoms/00-global/00-colors/colors.mustache', {
+ d: 123,
+ });
+ test.equals(
+ p.relPath,
+ '00-atoms' +
+ path.sep +
+ '00-global' +
+ path.sep +
+ '00-colors' +
+ path.sep +
+ 'colors.mustache'
+ );
+ test.equals(p.name, '00-atoms-00-global');
+ test.equals(p.subdir, '00-atoms' + path.sep + '00-global');
+ test.equals(p.fileName, 'colors');
+ test.equals(p.fileExtension, '.mustache');
+ test.equals(p.jsonFileData.d, 123);
+ test.equals(p.patternBaseName, 'colors');
+ test.equals(p.patternName, 'Colors');
+ test.equals(
+ p.getPatternLink(pl),
+ '00-atoms-00-global' + path.sep + '00-atoms-00-global.rendered.html'
+ );
+ test.equals(p.patternGroup, 'atoms');
+ test.equals(p.patternSubGroup, 'global'); //because of p.info.hasDir
+ test.equals(p.flatPatternPath, '00-atoms-00-global');
+ test.equals(p.patternPartial, 'atoms-colors');
+ test.equals(p.template, '');
+ test.equals(p.patternPartialCode, '');
+ test.equals(p.lineage.length, 0);
+ test.equals(p.lineageIndex.length, 0);
+ test.equals(p.lineageR.length, 0);
+ test.equals(p.lineageRIndex.length, 0);
+ test.equals(p.patternState, '');
+ test.end();
+ }
+);
+
+tap.test('test Pattern name for variants correctly initialzed', function(test) {
+ var p1 = new Pattern('00-atoms/00-global/colors~variant.mustache', {
+ d: 123,
+ });
+ var p2 = new Pattern('00-atoms/00-global/colors~variant-minus.json', {
+ d: 123,
+ });
+ test.equals(p1.name, '00-atoms-00-global-colors-variant');
+ test.equals(p2.name, '00-atoms-00-global-colors-variant-minus');
+ test.end();
+});
+
+tap.test('test Pattern with one-directory subdir works as expected', function(
+ test
+) {
+ var p = new Pattern('00-atoms/00-colors.mustache', { d: 123 });
+ test.equals(p.relPath, '00-atoms' + path.sep + '00-colors.mustache');
+ test.equals(p.name, '00-atoms-00-colors');
+ test.equals(p.subdir, '00-atoms');
+ test.equals(p.fileName, '00-colors');
+ test.equals(p.fileExtension, '.mustache');
+ test.equals(p.jsonFileData.d, 123);
+ test.equals(p.patternBaseName, 'colors');
+ test.equals(p.patternName, 'Colors');
+ test.equals(
+ p.getPatternLink(pl),
+ '00-atoms-00-colors' + path.sep + '00-atoms-00-colors.rendered.html'
+ );
+ test.equals(p.patternGroup, 'atoms');
+ test.equals(p.flatPatternPath, '00-atoms');
+ test.equals(p.patternPartial, 'atoms-colors');
+ test.equals(p.template, '');
+ test.equals(p.lineage.length, 0);
+ test.equals(p.lineageIndex.length, 0);
+ test.equals(p.lineageR.length, 0);
+ test.equals(p.lineageRIndex.length, 0);
+ test.end();
+});
+
+tap.test(
+ 'test Pattern with no numbers in pattern group works as expected',
+ function(test) {
+ var p = new Pattern('atoms/colors.mustache', { d: 123 });
+ test.equals(p.relPath, 'atoms' + path.sep + 'colors.mustache');
+ test.equals(p.name, 'atoms-colors');
+ test.equals(p.subdir, 'atoms');
+ test.equals(p.fileName, 'colors');
+ test.equals(
+ p.getPatternLink(pl),
+ 'atoms-colors' + path.sep + 'atoms-colors.rendered.html'
+ );
+ test.equals(p.patternGroup, 'atoms');
+ test.equals(p.flatPatternPath, 'atoms');
+ test.equals(p.patternPartial, 'atoms-colors');
+ test.end();
+ }
+);
+
+tap.test('test Pattern capitalizes patternDisplayName correctly', function(
+ test
+) {
+ var p = new Pattern('00-atoms/00-global/00-colors-alt.mustache', { d: 123 });
+ test.equals(p.patternBaseName, 'colors-alt');
+ test.equals(p.patternName, 'Colors Alt');
+ test.end();
+});
+
+tap.test('test Pattern get dir level no separated pattern directory', function(
+ test
+) {
+ var p = new Pattern('00-atoms/00-global/00-colors-alt.mustache', { d: 123 });
+ console.log(p);
+ test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 2 }), '00-atoms');
+ test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 2 }), '00-global');
+ test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 2 }), '');
+ var p = new Pattern('00-atoms/00-colors-alt.mustache', { d: 123 });
+ test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 1 }), '00-atoms');
+ test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 1 }), '00-atoms');
+ test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 1 }), '');
+ var p = new Pattern('00-colors-alt.mustache', { d: 123 });
+ test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 0 }), '');
+ test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 0 }), '');
+ test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 0 }), '');
+ test.end();
+});
+
+tap.test(
+ 'test Pattern get dir level with separated pattern directory',
+ function(test) {
+ var p = new Pattern(
+ '00-atoms/00-global/00-colors-alt/colors-alt.mustache',
+ { d: 123 }
+ );
+ test.equals(p.getDirLevel(0, { hasDir: true, dirLevel: 3 }), '00-atoms');
+ test.equals(p.getDirLevel(1, { hasDir: true, dirLevel: 3 }), '00-global');
+ test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 3 }), '');
+ var p = new Pattern('00-atoms/00-colors-alt/colors-alt.mustache', {
+ d: 123,
+ });
+ test.equals(p.getDirLevel(0, { hasDir: true, dirLevel: 2 }), '00-atoms');
+ test.equals(
+ p.getDirLevel(1, { hasDir: true, dirLevel: 2 }),
+ '00-colors-alt'
+ );
+ test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 2 }), '');
+ var p = new Pattern('00-colors-alt/colors-alt.mustache', { d: 123 });
+ test.equals(
+ p.getDirLevel(0, { hasDir: true, dirLevel: 1 }),
+ '00-colors-alt'
+ );
+ test.equals(
+ p.getDirLevel(1, { hasDir: true, dirLevel: 1 }),
+ '00-colors-alt'
+ );
+ test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 1 }), '');
+ test.end();
+ }
+);
+
+tap.test('The forms of Pattern.getPatternLink() work as expected', function(
+ test
+) {
+ var p = new Pattern('00-atoms/00-global/00-colors.hbs');
+ test.equals(
+ p.getPatternLink(pl),
+ '00-atoms-00-global-00-colors' +
+ path.sep +
+ '00-atoms-00-global-00-colors.rendered.html'
+ );
+ test.equals(
+ p.getPatternLink(pl, 'rendered'),
+ '00-atoms-00-global-00-colors' +
+ path.sep +
+ '00-atoms-00-global-00-colors.rendered.html'
+ );
+ test.equals(
+ p.getPatternLink(pl, 'rawTemplate'),
+ '00-atoms-00-global-00-colors' +
+ path.sep +
+ '00-atoms-00-global-00-colors.hbs'
+ );
+ test.equals(
+ p.getPatternLink(pl, 'markupOnly'),
+ '00-atoms-00-global-00-colors' +
+ path.sep +
+ '00-atoms-00-global-00-colors.markup-only.html'
+ );
+ test.equals(
+ p.getPatternLink(pl, 'custom', '.custom-extension'),
+ '00-atoms-00-global-00-colors' +
+ path.sep +
+ '00-atoms-00-global-00-colors.custom-extension'
+ );
+ test.end();
+});
diff --git a/packages/core/test/parameter_hunter_tests.js b/packages/core/test/parameter_hunter_tests.js
new file mode 100644
index 000000000..3c3328648
--- /dev/null
+++ b/packages/core/test/parameter_hunter_tests.js
@@ -0,0 +1,465 @@
+'use strict';
+
+const path = require('path');
+const util = require('./util/test_utils.js');
+const tap = require('tap');
+
+const loadPattern = require('../src/lib/loadPattern');
+const ph = require('../src/lib/parameter_hunter');
+const processIterative = require('../src/lib/processIterative');
+
+const parameter_hunter = new ph();
+
+const config = require('./util/patternlab-config.json');
+const engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+
+const testPatternsPath = path.resolve(__dirname, 'files', '_patterns');
+
+tap.test('parameter hunter finds and extends templates', function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ parameter_hunter
+ .find_parameters(testPattern, pl)
+ .then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized(
+ '{{foo}} A life is like a garden. Perfect moments can be had, but not preserved, except in memory.
'
+ )
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+});
+
+tap.test(
+ 'parameter hunter finds and extends templates with verbose partials',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join(
+ '00-test',
+ 'sticky-comment-verbose.mustache'
+ );
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ parameter_hunter
+ .find_parameters(testPattern, pl)
+ .then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized(
+ '{{foo}} A life is like a garden. Perfect moments can be had, but not preserved, except in memory.
'
+ )
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+ }
+);
+
+//previous tests were for unquoted parameter keys and single-quoted values.
+//test other quoting options.
+tap.test(
+ 'parameter hunter parses parameters with unquoted keys and unquoted values',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template = '{{> test-comment(description: true) }}';
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized('{{foo}} true
')
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+tap.test(
+ 'parameter hunter parses parameters with unquoted keys and double-quoted values',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template = '{{> test-comment(description: "true") }}';
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized('{{foo}} true
')
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+tap.test(
+ 'parameter hunter parses parameters with single-quoted keys and unquoted values',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template = "{{> test-comment('description': true) }}";
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized('{{foo}} true
')
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+tap.test(
+ 'parameter hunter parses parameters with single-quoted keys and single-quoted values wrapping internal escaped single-quotes',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template =
+ "{{> test-comment('description': 'true not,\\'true\\'') }}";
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized(`{{foo}} true not,'true'
`)
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+tap.test(
+ 'parameter hunter parses parameters with single-quoted keys and double-quoted values wrapping internal single-quotes',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template =
+ "{{> test-comment('description': \"true not:'true'\") }}";
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized(`{{foo}} true not:'true'
`)
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+tap.test(
+ 'parameter hunter parses parameters with double-unquoted keys and unquoted values',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template = '{{> test-comment("description": true) }}';
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized('{{foo}} true
')
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+tap.test(
+ 'parameter hunter parses parameters with double-quoted keys and single-quoted values wrapping internal double-quotes',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template =
+ '{{> test-comment("description": \'true not{"true"\') }}';
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized('{{foo}} true not{"true"
')
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+tap.test(
+ 'parameter hunter parses parameters with double-quoted keys and double-quoted values wrapping internal escaped double-quotes',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template =
+ '{{> test-comment("description": "true not}\\"true\\"") }}';
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized('{{foo}} true not}"true"
')
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+tap.test(
+ 'parameter hunter parses parameters with combination of quoting schemes for keys and values',
+ function(test) {
+ //arrange
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template =
+ '{{> test-comment(description: true, \'foo\': false, "bar": false, \'single\': true, \'singlesingle\': \'true\', \'singledouble\': "true", "double": true, "doublesingle": \'true\', "doubledouble": "true") }}';
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized('false true
')
+ );
+ test.end();
+ });
+ });
+ }
+);
+
+//todo https://github.com/pattern-lab/patternlab-node/issues/673
+// tap.test('parameter hunter parses parameters with values containing a closing parenthesis', function (test) {
+// //arrange
+// const pl = util.fakePatternLab(testPatternsPath);
+
+// var commentPath = path.join('00-test', 'comment.mustache');
+// var commentPattern = loadPattern(commentPath, pl);
+
+// var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+// var testPattern = loadPattern(testPatternPath, pl);
+
+// //override the file
+// testPattern.template = "{{> test-comment(description: 'Hello ) World') }}";
+// testPattern.extendedTemplate = testPattern.template;
+// testPattern.parameteredPartials[0] = testPattern.template;
+
+// var p1 = processIterative(commentPattern, pl);
+// var p2 = processIterative(testPattern, pl);
+
+// Promise.all([p1, p2]).then(() => {
+// //act
+// parameter_hunter.find_parameters(testPattern, pl).then(() => {
+// //assert
+// test.equals(util.sanitized(testPattern.extendedTemplate), util.sanitized('Hello ) World
'));
+// test.end();
+// });
+// });
+// });
+
+tap.test('parameter hunter skips malformed parameters', function(test) {
+ const pl = util.fakePatternLab(testPatternsPath);
+
+ var commentPath = path.join('00-test', 'comment.mustache');
+ var commentPattern = loadPattern(commentPath, pl);
+
+ var testPatternPath = path.join('00-test', 'sticky-comment.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //override the file
+ testPattern.template =
+ '{{> test-comment( missing-val: , : missing-key, : , , foo: "Hello World") }}';
+ testPattern.extendedTemplate = testPattern.template;
+ testPattern.parameteredPartials[0] = testPattern.template;
+
+ var p1 = processIterative(commentPattern, pl);
+ var p2 = processIterative(testPattern, pl);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ parameter_hunter.find_parameters(testPattern, pl).then(() => {
+ //assert
+ console.log(
+ '\nPattern Lab should catch JSON.parse() errors and output useful debugging information...'
+ );
+ test.equals(
+ util.sanitized(testPattern.extendedTemplate),
+ util.sanitized('{{foo}} {{description}}
')
+ );
+ test.end();
+ });
+ });
+});
diff --git a/packages/core/test/parseAllLinks_tests.js b/packages/core/test/parseAllLinks_tests.js
new file mode 100644
index 000000000..271b3ee5b
--- /dev/null
+++ b/packages/core/test/parseAllLinks_tests.js
@@ -0,0 +1,116 @@
+'use strict';
+
+const path = require('path');
+const tap = require('tap');
+const fs = require('fs-extra');
+
+const addPattern = require('../src/lib/addPattern');
+const parseAllLinks = require('../src/lib/parseAllLinks');
+
+const Pattern = require('../src/lib/object_factory').Pattern;
+const PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
+const da = require('../src/lib/data_loader');
+const dataLoader = new da();
+
+const util = require('./util/test_utils.js');
+const patterns_dir = './test/files/_patterns';
+
+tap.test(
+ 'parseDataLinks - replaces found link.* data for their expanded links',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+ patternlab.graph = PatternGraph.empty();
+
+ patternlab.patterns = [
+ Pattern.createEmpty({ patternPartial: 'twitter-brad' }, patternlab),
+ Pattern.createEmpty({ patternPartial: 'twitter-dave' }, patternlab),
+ Pattern.createEmpty({ patternPartial: 'twitter-brian' }, patternlab),
+ ];
+ patternlab.data.link = {};
+
+ // copies essential logic from loadPattern
+ const navPattern = new Pattern('00-test/nav.mustache');
+ const patternData = dataLoader.loadDataFromFile(
+ path.resolve(
+ __dirname,
+ 'files/_patterns',
+ navPattern.subdir,
+ navPattern.fileName
+ ),
+ fs
+ );
+ navPattern.jsonFileData = patternData;
+ addPattern(navPattern, patternlab);
+
+ //for the sake of the test, also imagining I have the following pages...
+ patternlab.data.link['twitter-brad'] = 'https://twitter.com/brad_frost';
+ patternlab.data.link['twitter-dave'] = 'https://twitter.com/dmolsen';
+ patternlab.data.link['twitter-brian'] = 'https://twitter.com/bmuenzenmeyer';
+
+ patternlab.data.brad = { url: 'link.twitter-brad' };
+ patternlab.data.dave = { url: 'link.twitter-dave' };
+ patternlab.data.brian = { url: 'link.twitter-brian' };
+
+ let pattern;
+ for (let i = 0; i < patternlab.patterns.length; i++) {
+ if (patternlab.patterns[i].patternPartial === 'test-nav') {
+ pattern = patternlab.patterns[i];
+ }
+ }
+
+ //assert before
+ test.equals(
+ pattern.jsonFileData.brad.url,
+ 'link.twitter-brad',
+ 'brad pattern data should be found'
+ );
+ test.equals(
+ pattern.jsonFileData.dave.url,
+ 'link.twitter-dave',
+ 'dave pattern data should be found'
+ );
+ test.equals(
+ pattern.jsonFileData.brian.url,
+ 'link.twitter-brian',
+ 'brian pattern data should be found'
+ );
+
+ //act
+ parseAllLinks(patternlab);
+
+ //assert after
+ test.equals(
+ pattern.jsonFileData.brad.url,
+ 'https://twitter.com/brad_frost',
+ 'brad pattern data should be replaced'
+ );
+ test.equals(
+ pattern.jsonFileData.dave.url,
+ 'https://twitter.com/dmolsen',
+ 'dave pattern data should be replaced'
+ );
+ test.equals(
+ pattern.jsonFileData.brian.url,
+ 'https://twitter.com/bmuenzenmeyer',
+ 'brian pattern data should be replaced'
+ );
+
+ test.equals(
+ patternlab.data.brad.url,
+ 'https://twitter.com/brad_frost',
+ 'global brad data should be replaced'
+ );
+ test.equals(
+ patternlab.data.dave.url,
+ 'https://twitter.com/dmolsen',
+ 'global dave data should be replaced'
+ );
+ test.equals(
+ patternlab.data.brian.url,
+ 'https://twitter.com/bmuenzenmeyer',
+ 'global brian data should be replaced'
+ );
+ test.end();
+ }
+);
diff --git a/packages/core/test/pattern_engines_tests.js b/packages/core/test/pattern_engines_tests.js
new file mode 100644
index 000000000..f79b129dd
--- /dev/null
+++ b/packages/core/test/pattern_engines_tests.js
@@ -0,0 +1,251 @@
+'use strict';
+
+var tap = require('tap');
+
+var patternEngines = require('../src/lib/pattern_engines');
+var Pattern = require('../src/lib/object_factory').Pattern;
+var config = require('./util/patternlab-config.json');
+
+patternEngines.loadAllEngines(config);
+
+// the mustache test pattern, stolen from object_factory unit tests
+var mustacheTestPattern = new Pattern(
+ 'source/_patterns/00-atoms/00-global/00-colors-alt.mustache',
+ { d: 123 }
+);
+var mustacheTestPseudoPatternBasePattern = new Pattern(
+ 'source/_patterns/04-pages/00-homepage.mustache',
+ { d: 123 }
+);
+var mustacheTestPseudoPattern = new Pattern(
+ 'source/_patterns/04-pages/00-homepage~emergency.json',
+ { d: 123 }
+);
+mustacheTestPseudoPattern.isPseudoPattern = true;
+mustacheTestPseudoPattern.basePattern = mustacheTestPseudoPatternBasePattern;
+var engineNames = Object.keys(patternEngines);
+
+tap.test(
+ 'getEngineNameForPattern returns "mustache" from test pattern',
+ function(test) {
+ var engineName = patternEngines.getEngineNameForPattern(
+ mustacheTestPattern
+ );
+ test.equals(engineName, 'mustache');
+ test.end();
+ }
+);
+
+tap.test(
+ 'getEngineNameForPattern returns "mustache" for a plain string template as a backwards compatibility measure',
+ function(test) {
+ test.plan(1);
+ test.equals(
+ patternEngines.getEngineNameForPattern('plain text string'),
+ 'mustache'
+ );
+ test.end();
+ }
+);
+
+tap.test(
+ 'getEngineNameForPattern returns "mustache" for an artificial empty template',
+ function(test) {
+ test.plan(1);
+ var emptyPattern = Pattern.createEmpty();
+ test.equals(
+ patternEngines.getEngineNameForPattern(emptyPattern),
+ 'mustache'
+ );
+ test.end();
+ }
+);
+
+tap.test(
+ 'getEngineForPattern returns a reference to the mustache engine from test pattern',
+ function(test) {
+ var engine = patternEngines.getEngineForPattern(mustacheTestPattern);
+ test.equals(engine, patternEngines.mustache);
+ test.end();
+ }
+);
+
+tap.test(
+ 'getEngineForPattern returns a reference to the mustache engine from test pseudo-pattern',
+ function(test) {
+ var engine = patternEngines.getEngineForPattern(mustacheTestPseudoPattern);
+ test.equals(engine, patternEngines.mustache);
+ test.end();
+ }
+);
+
+tap.test(
+ 'isPseudoPatternJSON correctly identifies pseudo-pattern JSON filenames',
+ function(test) {
+ // each test case
+ var filenames = {
+ '00-homepage~emergency.json': true,
+ '~emergency.json': true,
+ '00-homepage~emergency.js': false,
+ '00-homepage-emergency.js': false,
+ '00-homepage.hbs': false,
+ '00-homepage.json': false,
+ 'greatpic.jpg': false,
+ };
+ // expect one test per test case
+ test.plan(Object.keys(filenames).length);
+
+ // loop over each test case and test it
+ Object.keys(filenames).forEach(function(filename) {
+ var expectedResult = filenames[filename],
+ actualResult = patternEngines.isPseudoPatternJSON(filename),
+ testMessage =
+ 'isPseudoPatternJSON should return ' +
+ expectedResult +
+ ' for ' +
+ filename;
+ test.strictEqual(actualResult, expectedResult, testMessage);
+ });
+
+ // done
+ test.end();
+ }
+);
+
+tap.test(
+ 'isPatternFile correctly identifies pattern files and rejects non-pattern files',
+ function(test) {
+ // each test case
+ var filenames = {
+ '00-comment-thread.mustache': true,
+ '00-comment-thread.fakeextthatdoesntexist': false,
+ '00-comment-thread': false,
+ '_00-comment-thread.mustache': true,
+ '.00-comment-thread.mustache': false,
+ '00-comment-thread.json': false,
+ '00-homepage~emergency.json': true,
+ };
+ // expect one test per test case
+ test.plan(Object.keys(filenames).length);
+
+ // loop over each test case and test it
+ Object.keys(filenames).forEach(function(filename) {
+ var expectedResult = filenames[filename],
+ actualResult = patternEngines.isPatternFile(filename),
+ testMessage =
+ 'isPatternFile should return ' + expectedResult + ' for ' + filename;
+ test.strictEqual(actualResult, expectedResult, testMessage);
+ });
+
+ // done
+ test.end();
+ }
+);
+
+// testProps() utility function: given an object, and a hash of expected
+// 'property name':'property type' pairs, verify that the object contains each
+// expected property, and that each property is of the expected type.
+function testProps(object, propTests, test) {
+ // function to test each expected property is present and the correct type
+ function testProp(propName, types) {
+ var possibleTypes;
+
+ // handle "types" being a string or an array of strings
+ if (types instanceof Array) {
+ possibleTypes = types;
+ } else {
+ // "types" is just a single string, load it into an array; the rest of
+ // the code expects it!
+ possibleTypes = [types];
+ }
+
+ var isOneOfTheseTypes = possibleTypes
+ .map(function(type) {
+ return typeof object[propName] === type;
+ })
+ .reduce(function(isPrevType, isCurrentType) {
+ return isPrevType || isCurrentType;
+ });
+
+ test.ok(
+ object.hasOwnProperty(propName),
+ '"' + propName + '" prop should be present'
+ );
+ test.ok(
+ isOneOfTheseTypes,
+ '"' +
+ propName +
+ '" prop should be one of types ' +
+ possibleTypes +
+ ' but was instead ' +
+ typeof propName
+ );
+ }
+
+ // go over each property test and run it
+ Object.keys(propTests).forEach(function(propName) {
+ var propType = propTests[propName];
+ testProp(propName, propType);
+ });
+}
+
+tap.test(
+ 'patternEngines object contains at least the default mustache engine',
+ function(test) {
+ test.plan(1);
+ test.ok(patternEngines.hasOwnProperty('mustache'));
+ test.end();
+ }
+);
+
+tap.test(
+ 'patternEngines object reports that it supports the .mustache extension',
+ function(test) {
+ test.plan(1);
+ test.ok(patternEngines.isFileExtensionSupported('.mustache'));
+ test.end();
+ }
+);
+
+// make one big test group for each pattern engine
+engineNames.forEach(function(engineName) {
+ tap.test(
+ 'engine ' + engineName + ' contains expected properties and methods',
+ function(test) {
+ var propertyTests = {
+ engine: ['object', 'function'],
+ engineName: 'string',
+ engineFileExtension: ['string', 'object'],
+ renderPattern: 'function',
+ findPartials: 'function',
+ };
+
+ test.plan(Object.keys(propertyTests).length * 2);
+ testProps(patternEngines[engineName], propertyTests, test);
+ test.end();
+ }
+ );
+});
+
+tap.test(
+ 'patternEngines getSupportedFileExtensions flattens known engine extensions into a single array',
+ function(test) {
+ //arrange
+ patternEngines.fooEngine = {
+ engineFileExtension: ['.foo1', '.foo2'],
+ };
+ patternEngines.barEngine = {
+ engineFileExtension: '.bar',
+ };
+
+ const exts = patternEngines.getSupportedFileExtensions();
+ test.ok(exts.includes('.foo1'));
+ test.ok(exts.includes('.foo2'));
+ test.ok(exts.includes('.bar'));
+
+ delete patternEngines.fooEngine;
+ delete patternEngines.barEngine;
+
+ test.end();
+ }
+);
diff --git a/packages/core/test/pattern_graph_tests.js b/packages/core/test/pattern_graph_tests.js
new file mode 100644
index 000000000..a2618031c
--- /dev/null
+++ b/packages/core/test/pattern_graph_tests.js
@@ -0,0 +1,585 @@
+'use strict';
+
+var path = require('path');
+var tap = require('tap');
+
+var PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
+var VERSION = require('../src/lib/pattern_graph').PATTERN_GRAPH_VERSION;
+var Pattern = require('../src/lib/object_factory').Pattern;
+var CompileState = require('../src/lib/object_factory').CompileState;
+const posixPath = require('./util/test_utils.js').posixPath;
+var config = require('./util/patternlab-config.json');
+var engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+
+var patternlab = {
+ config: {
+ paths: {
+ public: {
+ root: `${__dirname}/public`,
+ },
+ },
+ },
+};
+
+var mockGraph = function() {
+ return PatternGraph.empty();
+};
+
+tap.test('checkVersion - Current version returns true', test => {
+ test.same(PatternGraph.checkVersion({ version: VERSION }), true);
+ test.end();
+});
+
+tap.test('checkVersion - Older version returns false', test => {
+ test.same(PatternGraph.checkVersion({ version: VERSION - 1 }), false);
+ test.end();
+});
+
+tap.test('Loading an empty graph works', test => {
+ var g = PatternGraph.loadFromFile(
+ path.resolve(__dirname, 'public'),
+ 'does not exist'
+ );
+ tap.equal(g.graph.nodes().length, 0, 'foo');
+ test.end();
+});
+
+tap.test('PatternGraph.fromJson() - Loading a graph from JSON', test => {
+ var graph = PatternGraph.loadFromFile(
+ path.resolve(__dirname, 'public'),
+ 'testDependencyGraph.json'
+ );
+ test.same(graph.timestamp, 1337);
+ test.same(graph.graph.nodes(), ['atom-foo', 'molecule-foo']);
+ test.same(graph.graph.edges(), [{ v: 'molecule-foo', w: 'atom-foo' }]);
+ test.end();
+});
+
+tap.test(
+ 'PatternGraph.fromJson() - Loading a graph from JSON using an older version throws error',
+ test => {
+ test.throws(
+ function() {
+ PatternGraph.fromJson({ version: 0 });
+ },
+ {},
+ /Version of graph on disk.*/g
+ );
+
+ test.end();
+ }
+);
+
+tap.test('toJson() - Storing a graph to JSON correctly', test => {
+ var graph = mockGraph();
+ graph.timestamp = 1337;
+ var atomFoo = Pattern.create('atom-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeFoo = Pattern.create('molecule-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ graph.add(atomFoo);
+ graph.add(moleculeFoo);
+ graph.link(moleculeFoo, atomFoo);
+ test.same(graph.toJson(), {
+ version: VERSION,
+ timestamp: 1337,
+ graph: {
+ options: { directed: true, multigraph: false, compound: false },
+ nodes: [
+ { v: 'atom-foo', value: { compileState: 'clean' } },
+ { v: 'molecule-foo', value: { compileState: 'clean' } },
+ ],
+ edges: [{ v: 'molecule-foo', w: 'atom-foo', value: {} }],
+ },
+ });
+ // For generating the above output:console.log(JSON.stringify(graph.toJson()));
+ test.end();
+});
+
+tap.test(
+ 'Storing and loading a graph from JSON return the identical graph',
+ test => {
+ var oldGraph = mockGraph();
+ oldGraph.timestamp = 1337;
+ var atomFoo = Pattern.create('atom-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeFoo = Pattern.create('molecule-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ oldGraph.add(atomFoo);
+ oldGraph.add(moleculeFoo);
+ oldGraph.link(moleculeFoo, atomFoo);
+
+ // act
+ var newGraph = PatternGraph.fromJson(oldGraph.toJson());
+
+ // assert
+ test.same(newGraph.version, VERSION);
+ test.same(newGraph.timestamp, 1337);
+ test.same(newGraph.graph.nodes(), ['atom-foo', 'molecule-foo']);
+ test.same(newGraph.graph.edges(), [{ w: 'atom-foo', v: 'molecule-foo' }]);
+ // The graph is a new object
+ test.notEqual(newGraph, oldGraph);
+ test.end();
+ }
+);
+
+tap.test('clone()', test => {
+ var oldGraph = mockGraph();
+ oldGraph.timestamp = 1337;
+ var atomFoo = Pattern.create('atom-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeFoo = Pattern.create('molecule-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ oldGraph.add(atomFoo);
+ oldGraph.add(moleculeFoo);
+ oldGraph.link(moleculeFoo, atomFoo);
+
+ // act
+ var newGraph = oldGraph.clone();
+
+ // assert
+ test.same(newGraph.version, VERSION);
+ test.same(newGraph.timestamp, 1337);
+ test.same(newGraph.graph.nodes(), ['atom-foo', 'molecule-foo']);
+ test.same(newGraph.graph.edges(), [{ w: 'atom-foo', v: 'molecule-foo' }]);
+ // The graph is a new object
+ test.notEqual(newGraph, oldGraph);
+ test.end();
+});
+
+tap.test('Adding a node', test => {
+ var g = mockGraph();
+ var pattern = Pattern.create('atom-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ g.add(pattern);
+ test.same(
+ { compileState: CompileState.CLEAN },
+ g.node('atom-foo'),
+ 'Data were set correctly'
+ );
+ var actual = g.nodes();
+ test.same(actual, ['atom-foo']);
+ test.end();
+});
+
+tap.test('Adding a node twice', test => {
+ var g = mockGraph();
+ var pattern = Pattern.create('atom-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ g.add(pattern);
+ g.add(pattern);
+ var actual = g.nodes();
+ test.same(actual, ['atom-foo']);
+ test.end();
+});
+
+tap.test('Adding two nodes', test => {
+ var g = mockGraph();
+ var atomFoo = Pattern.create('atom-foo', {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeFoo = Pattern.create('molecule-foo', {
+ compileState: CompileState.CLEAN,
+ });
+ g.add(atomFoo);
+ g.add(moleculeFoo);
+ var actual = g.nodes();
+ test.same(actual, ['atom-foo', 'molecule-foo']);
+ test.end();
+});
+
+tap.test('Adding two nodes with only different subpattern types', test => {
+ var g = mockGraph();
+ var atomFoo = Pattern.create('00-atoms/00-foo/baz.html', {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeFoo = Pattern.create('00-atoms/00-bar/baz.html', {
+ compileState: CompileState.CLEAN,
+ });
+ g.add(atomFoo);
+ g.add(moleculeFoo);
+ var actual = g.nodes();
+ test.same(posixPath(actual), [
+ '00-atoms/00-foo/baz.html',
+ '00-atoms/00-bar/baz.html',
+ ]);
+ test.end();
+});
+
+tap.test('Linking two nodes', test => {
+ var g = mockGraph();
+ var atomFoo = Pattern.create('atom-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeFoo = Pattern.create('molecule-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ g.add(atomFoo);
+ g.add(moleculeFoo);
+ g.link(moleculeFoo, atomFoo);
+ test.same(
+ g.graph.edges(),
+ [{ v: 'molecule-foo', w: 'atom-foo' }],
+ 'There is an edge from v to w'
+ );
+ test.end();
+});
+
+tap.test('remove() - Removing a node', test => {
+ var g = mockGraph();
+ var atomFoo = Pattern.create('atom-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeFoo = Pattern.create('molecule-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ g.add(atomFoo);
+ g.add(moleculeFoo);
+ test.same(g.nodes(), ['atom-foo', 'molecule-foo']);
+ g.remove(moleculeFoo);
+ test.same(
+ g.graph.nodes(),
+ ['atom-foo'],
+ 'The molecule was removed from the graph'
+ );
+ test.same(
+ g.patterns.allPatterns()[0].relPath,
+ 'atom-foo',
+ 'The molecule was removed from the known patterns'
+ );
+ test.end();
+});
+
+tap.test('filter() - Removing nodes via filter', test => {
+ var g = mockGraph();
+ var atomFoo = Pattern.create('atom-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeFoo = Pattern.create('molecule-foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ g.add(atomFoo);
+ g.add(moleculeFoo);
+ test.same(g.nodes(), ['atom-foo', 'molecule-foo']);
+ g.filter(n => n != 'molecule-foo');
+ test.same(
+ g.graph.nodes(),
+ ['atom-foo'],
+ 'The molecule was removed from the graph'
+ );
+ test.same(
+ g.patterns.allPatterns()[0].relPath,
+ 'atom-foo',
+ 'The molecule was removed from the known patterns'
+ );
+ test.end();
+});
+
+// Prevents nodes from escaping the scope, at the same time have some default graph for lineage to
+// test on
+(function() {
+ var atomFoo = Pattern.create('00-atom/xy/foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var atomIsolated = Pattern.create('00-atom/xy/isolated', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeFoo = Pattern.create('01-molecule/xy/foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var moleculeBar = Pattern.create('01-molecule/xy/bar', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var organismFoo = Pattern.create('02-organism/xy/foo', null, {
+ compileState: CompileState.CLEAN,
+ });
+ var organismBar = Pattern.create('02-organism/xy/bar', null, {
+ compileState: CompileState.CLEAN,
+ });
+
+ var g = mockGraph();
+
+ // Included nowhere
+ g.add(atomIsolated);
+
+ g.add(atomFoo);
+ g.add(moleculeFoo);
+ g.add(moleculeBar);
+ g.add(organismFoo);
+ g.add(organismBar);
+ // single molecule
+ g.link(organismFoo, moleculeFoo);
+
+ // include two molecules
+ g.link(organismBar, moleculeFoo);
+ g.link(organismBar, moleculeBar);
+
+ // both include atomFoo
+ g.link(moleculeFoo, atomFoo);
+ g.link(moleculeBar, atomFoo);
+
+ tap.test('lineage() - Calculate the lineage of a node', test => {
+ test.same(posixPath(g.lineage(organismFoo).map(p => p.relPath)), [
+ '01-molecule/xy/foo',
+ ]);
+ test.same(posixPath(g.lineage(organismBar).map(p => p.relPath)), [
+ '01-molecule/xy/foo',
+ '01-molecule/xy/bar',
+ ]);
+ test.same(posixPath(g.lineage(moleculeFoo).map(p => p.relPath)), [
+ '00-atom/xy/foo',
+ ]);
+ test.same(posixPath(g.lineage(moleculeBar).map(p => p.relPath)), [
+ '00-atom/xy/foo',
+ ]);
+ test.same(g.lineage(atomFoo).map(p => p.relPath), []);
+ test.same(g.lineage(atomIsolated).map(p => p.relPath), []);
+ test.end();
+ });
+
+ tap.test('lineageIndex() - Calculate the lineage of a node', test => {
+ test.same(g.lineageIndex(organismFoo), ['molecule-foo']);
+ test.same(g.lineageIndex(organismBar), ['molecule-foo', 'molecule-bar']);
+ test.same(g.lineageIndex(moleculeFoo), ['atom-foo']);
+ test.same(g.lineageIndex(moleculeBar), ['atom-foo']);
+ test.same(g.lineageIndex(atomFoo), []);
+ test.same(g.lineageIndex(atomIsolated), []);
+ test.end();
+ });
+
+ tap.test('lineageR() - Calculate the reverse lineage of a node', test => {
+ test.same(g.lineageR(organismFoo).map(p => p.relPath), []);
+ test.same(g.lineageR(organismBar).map(p => p.relPath), []);
+ test.same(posixPath(g.lineageR(moleculeFoo).map(p => p.relPath)), [
+ '02-organism/xy/foo',
+ '02-organism/xy/bar',
+ ]);
+ test.same(posixPath(g.lineageR(moleculeBar).map(p => p.relPath)), [
+ '02-organism/xy/bar',
+ ]);
+ test.same(posixPath(g.lineageR(atomFoo).map(p => p.relPath)), [
+ '01-molecule/xy/foo',
+ '01-molecule/xy/bar',
+ ]);
+ test.same(g.lineageR(atomIsolated).map(p => p.relPath), []);
+ test.end();
+ });
+
+ tap.test('lineageRIndex() - Calculate the lineage index of a node', test => {
+ test.same(g.lineageRIndex(organismFoo), []);
+ test.same(g.lineageRIndex(organismBar), []);
+ test.same(g.lineageRIndex(moleculeFoo), ['organism-foo', 'organism-bar']);
+ test.same(g.lineageRIndex(moleculeBar), ['organism-bar']);
+ test.same(g.lineageRIndex(atomFoo), ['molecule-foo', 'molecule-bar']);
+ test.same(g.lineageRIndex(atomIsolated), []);
+ test.end();
+ });
+})();
+
+(function() {
+ function TestGraph() {
+ function csAt(args, idx) {
+ return { compileState: args[idx] || CompileState.CLEAN };
+ }
+ var i = 0;
+ var atomFoo = (this.atomFoo = Pattern.create(
+ '00-atom/xy/foo',
+ null,
+ csAt(arguments, i++)
+ ));
+ var atomIsolated = (this.atomIsolated = Pattern.create(
+ '00-atom/xy/isolated',
+ null,
+ csAt(arguments, i++)
+ ));
+ var moleculeFoo = (this.moleculeFoo = Pattern.create(
+ '01-molecule/xy/foo',
+ null,
+ csAt(arguments, i++)
+ ));
+ var moleculeBar = (this.moleculeBar = Pattern.create(
+ '01-molecule/xy/bar',
+ null,
+ csAt(arguments, i++)
+ ));
+ var organismFoo = (this.organismFoo = Pattern.create(
+ '02-organism/xy/foo',
+ null,
+ csAt(arguments, i++)
+ ));
+ var organismBar = (this.organismBar = Pattern.create(
+ '02-organism/xy/bar',
+ null,
+ csAt(arguments, i++)
+ ));
+
+ var graph = (this.graph = mockGraph());
+
+ // Included nowhere
+ graph.add(atomIsolated);
+
+ graph.add(atomFoo);
+ graph.add(moleculeFoo);
+ graph.add(moleculeBar);
+ graph.add(organismFoo);
+ graph.add(organismBar);
+ // single molecule
+ graph.link(organismFoo, moleculeFoo);
+
+ // include two molecules
+ graph.link(organismBar, moleculeFoo);
+ graph.link(organismBar, moleculeBar);
+
+ // both include atomFoo
+ graph.link(moleculeFoo, atomFoo);
+ graph.link(moleculeBar, atomFoo);
+ }
+
+ tap.test(
+ 'compileOrder() - A clean graph results in no nodes to recompile',
+ test => {
+ var g = new TestGraph();
+ var co = g.graph.compileOrder();
+ test.equals(0, co.length);
+ test.end();
+ }
+ );
+
+ tap.test(
+ 'compileOrder() - Recompile isolated atoms does not do anything else',
+ test => {
+ var g = new TestGraph(
+ // atomFoo
+ CompileState.CLEAN,
+ // atomIsolated
+ CompileState.NEEDS_REBUILD
+ );
+
+ var co = g.graph.compileOrder();
+ test.same([g.atomIsolated], co, 'Only recompile atomIsolated');
+ co.forEach(p =>
+ test.same(
+ p.compileState,
+ CompileState.NEEDS_REBUILD,
+ 'All patterns are marked for rebuilding'
+ )
+ );
+
+ test.end();
+ }
+ );
+
+ tap.test(
+ 'compileOrder() - Changing a linked atom bubbles back to the organisms',
+ test => {
+ // Almost every pattern - except atomIsolated - has a transitive dependency on atomFoo
+ var g = new TestGraph(CompileState.NEEDS_REBUILD);
+ var co = g.graph.compileOrder();
+
+ test.equals(5, co.length);
+ test.same(
+ [g.atomFoo, g.moleculeFoo, g.organismFoo, g.moleculeBar, g.organismBar],
+ co,
+ 'Recompile everything except atomIsolated'
+ );
+ co.forEach(p =>
+ test.same(
+ p.compileState,
+ CompileState.NEEDS_REBUILD,
+ 'All patterns are marked for rebuilding'
+ )
+ );
+
+ test.end();
+ }
+ );
+
+ tap.test(
+ 'compileOrder() - Changing a molecule leaves atoms untouched',
+ test => {
+ // Bubble up from molecules to organisms, leaving atoms unchanged as they were not modified
+ var g = new TestGraph(null, null, CompileState.NEEDS_REBUILD);
+ var co = g.graph.compileOrder();
+
+ test.same(
+ [g.moleculeFoo, g.organismFoo, g.organismBar],
+ co,
+ 'Recompile moleculeFoo and transitive dependencies'
+ );
+ co.forEach(p =>
+ test.same(
+ p.compileState,
+ CompileState.NEEDS_REBUILD,
+ 'All patterns are marked for rebuilding'
+ )
+ );
+ test.end();
+ }
+ );
+
+ tap.test(
+ 'compileOrder() - Changing an organism leaves atoms and molecules untouched',
+ test => {
+ // Almost every pattern - except atomIsolated - has a transitive dependency on atomFoo
+ var g = new TestGraph(
+ // atoms
+ null,
+ null,
+ // molecules
+ null,
+ null,
+ // organismFoo
+ CompileState.NEEDS_REBUILD
+ );
+ var co = g.graph.compileOrder();
+
+ test.same([g.organismFoo], co);
+ test.same(
+ co[0].compileState,
+ CompileState.NEEDS_REBUILD,
+ 'All patterns are marked for rebuilding'
+ );
+ test.end();
+ }
+ );
+
+ tap.test('compileOrder() - Recompile everything', test => {
+ // Almost every pattern - except atomIsolated - has a transitive dependency on atomFoo
+ // Also recompile atomIsolated
+ var g = new TestGraph(
+ CompileState.NEEDS_REBUILD,
+ CompileState.NEEDS_REBUILD
+ );
+ var compileOrder = g.graph.compileOrder();
+
+ test.same(
+ [
+ g.atomIsolated,
+ g.atomFoo,
+ g.moleculeFoo,
+ g.organismFoo,
+ g.moleculeBar,
+ g.organismBar,
+ ],
+ compileOrder,
+ 'Recompile everything except atomIsolated'
+ );
+ compileOrder.forEach(p =>
+ test.same(
+ p.compileState,
+ CompileState.NEEDS_REBUILD,
+ 'All patterns are marked for rebuilding'
+ )
+ );
+ test.end();
+ });
+})();
diff --git a/packages/core/test/pattern_registry_tests.js b/packages/core/test/pattern_registry_tests.js
new file mode 100644
index 000000000..bf2e71914
--- /dev/null
+++ b/packages/core/test/pattern_registry_tests.js
@@ -0,0 +1,72 @@
+'use strict';
+
+var PatternRegistry = require('./../src/lib/pattern_registry');
+
+var tap = require('tap');
+
+// #540 Copied from pattern_assembler_tests
+tap.test(
+ 'get_pattern_by_key - returns the fuzzy result when no others found',
+ function(test) {
+ var pattern_registry = new PatternRegistry();
+
+ var pattern = {
+ key: 'character-han-solo',
+ patternPartial: 'character-han-solo',
+ subdir: 'character',
+ fileName: 'han-solo',
+ };
+ pattern_registry.put(pattern);
+
+ //act
+ var result = pattern_registry.getPartial('character-han');
+ //assert
+ test.equals(result, pattern);
+ test.end();
+ }
+);
+
+// #540 Copied from pattern_assembler_tests
+tap.test('remove - remove an existing pattern', function(test) {
+ var pattern_registry = new PatternRegistry();
+
+ var pattern = {
+ key: 'character-han-solo',
+ patternPartial: 'character-han-solo',
+ subdir: 'character',
+ fileName: 'han-solo',
+ };
+ pattern_registry.put(pattern);
+
+ //act
+ pattern_registry.remove('character-han-solo');
+ test.same(null, pattern_registry.get('character-han-solo'));
+ test.end();
+});
+
+// #540 Copied from pattern_assembler_tests
+tap.test('getPartial - returns the exact key if found', function(test) {
+ //arrange
+ var pattern_registry = new PatternRegistry();
+ let patterns = [
+ {
+ key: 'molecules-primary-nav-jagged',
+ patternPartial: 'molecules-primary-nav-jagged',
+ subdir: 'molecules',
+ fileName: 'primary-nav-jagged',
+ },
+ {
+ key: 'molecules-primary-nav',
+ patternPartial: 'molecules-primary-nav',
+ subdir: 'molecules',
+ fileName: 'molecules-primary-nav',
+ },
+ ];
+ patterns.forEach(p => pattern_registry.put(p));
+
+ //act
+ var result = pattern_registry.getPartial('molecules-primary-nav');
+ //assert
+ test.equals(result, patterns[1]);
+ test.end();
+});
diff --git a/packages/core/test/patternlab_tests.js b/packages/core/test/patternlab_tests.js
new file mode 100644
index 000000000..741fbca15
--- /dev/null
+++ b/packages/core/test/patternlab_tests.js
@@ -0,0 +1,56 @@
+'use strict';
+
+const tap = require('tap');
+const rewire = require('rewire');
+const fs = require('fs-extra');
+var config = require('./util/patternlab-config.json');
+
+var plEngineModule = rewire('../src/lib/patternlab');
+
+//set up a global mocks - we don't want to be writing/rendering any files right now
+const fsMock = {
+ outputFileSync: function(path, content) {
+ /* INTENTIONAL NOOP */
+ },
+ readJSONSync: function(path, encoding) {
+ return fs.readJSONSync(path, encoding);
+ },
+ emptyDir: function(path) {
+ return fs.emptyDir(path);
+ },
+ readFileSync: function(path, encoding) {
+ return fs.readFileSync(path, encoding);
+ },
+};
+
+//set our mocks in place of usual require()
+plEngineModule.__set__({
+ fs: fsMock,
+});
+
+tap.test(
+ 'buildPatternData - should merge all JSON files in the data folder except listitems',
+ function(test) {
+ const data_dir = `${__dirname}/files/_data/`;
+
+ var pl = new plEngineModule(config);
+ var dataResult = pl.buildPatternData(data_dir, fs);
+ test.equals(dataResult.data, 'test');
+ test.equals(dataResult.foo, 'bar');
+ test.equals(dataResult.test_list_item, undefined);
+ test.end();
+ }
+);
+
+tap.test('buildPatternData - can load json, yaml, and yml files', function(
+ test
+) {
+ const data_dir = `${__dirname}/files/_data/`;
+
+ var pl = new plEngineModule(config);
+ var dataResult = pl.buildPatternData(data_dir, fs);
+ test.equals(dataResult.from_yml, 'from_yml');
+ test.equals(dataResult.from_yaml, 'from_yaml');
+ test.equals(dataResult.from_json, 'from_json');
+ test.end();
+});
diff --git a/packages/core/test/processRecursive_tests.js b/packages/core/test/processRecursive_tests.js
new file mode 100644
index 000000000..8522c0403
--- /dev/null
+++ b/packages/core/test/processRecursive_tests.js
@@ -0,0 +1,465 @@
+'use strict';
+
+const tap = require('tap');
+const path = require('path');
+
+const util = require('./util/test_utils.js');
+const buildListItems = require('../src/lib/buildListItems');
+const loadPattern = require('../src/lib/loadPattern');
+const engineLoader = require('../src/lib/pattern_engines');
+const processRecursive = require('../src/lib/processRecursive');
+const processIterative = require('../src/lib/processIterative');
+
+var config = require('./util/patternlab-config.json');
+
+engineLoader.loadAllEngines(config);
+
+const patterns_dir = `${__dirname}/files/_patterns`;
+
+tap.test('processRecursive recursively includes partials', function(test) {
+ //assert
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var fooPatternPath = path.join('00-test', '00-foo.mustache');
+ var fooPattern = loadPattern(fooPatternPath, patternlab);
+
+ var barPatternPath = path.join('00-test', '01-bar.mustache');
+ var barPattern = loadPattern(barPatternPath, patternlab);
+
+ var p1 = processIterative(fooPattern, patternlab);
+ var p2 = processIterative(barPattern, patternlab);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ processRecursive(fooPatternPath, patternlab)
+ .then(() => {
+ //assert
+ const expectedValue = 'bar';
+ test.equals(
+ util.sanitized(fooPattern.extendedTemplate),
+ util.sanitized(expectedValue)
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+});
+
+tap.test(
+ 'processRecursive - correctly replaces all stylemodifiers when multiple duplicate patterns with different stylemodifiers found',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var atomPath = path.join('00-test', '03-styled-atom.mustache');
+ var atomPattern = loadPattern(atomPath, patternlab);
+
+ var groupPath = path.join('00-test', '04-group.mustache');
+ var groupPattern = loadPattern(groupPath, patternlab);
+
+ var p1 = processIterative(atomPattern, patternlab);
+ var p2 = processIterative(groupPattern, patternlab);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ processRecursive(groupPath, patternlab)
+ .then(() => {
+ //assert
+ const expectedValue =
+ ' {{message}} {{message}} {{message}} {{message}}
';
+ test.equals(
+ util.sanitized(groupPattern.extendedTemplate),
+ util.sanitized(expectedValue)
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+ }
+);
+
+tap.only(
+ 'processRecursive - correctly replaces multiple stylemodifier classes on same partial',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var atomPath = path.join('00-test', '03-styled-atom.mustache');
+ var atomPattern = loadPattern(atomPath, patternlab);
+
+ var groupPath = path.join(
+ '00-test',
+ '10-multiple-classes-numeric.mustache'
+ );
+ var groupPattern = loadPattern(groupPath, patternlab);
+
+ var p1 = processIterative(atomPattern, patternlab);
+ var p2 = processIterative(groupPattern, patternlab);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ processRecursive(groupPath, patternlab)
+ .then(() => {
+ //assert
+ const expectedValue =
+ ' {{message}} {{message}} bar
';
+ test.equals(
+ util.sanitized(groupPattern.extendedTemplate),
+ util.sanitized(expectedValue)
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+ }
+);
+
+tap.test(
+ 'processRecursive - correctly ignores a partial without a style modifier when the same partial later has a style modifier',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var atomPath = path.join('00-test', '03-styled-atom.mustache');
+ var atomPattern = loadPattern(atomPath, patternlab);
+
+ var mixedPath = path.join('00-test', '06-mixed.mustache');
+ var mixedPattern = loadPattern(mixedPath, patternlab);
+
+ var p1 = processIterative(atomPattern, patternlab);
+ var p2 = processIterative(mixedPattern, patternlab);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ processRecursive(mixedPath, patternlab)
+ .then(() => {
+ //assert. here we expect {{styleModifier}} to be in the first group, since it was not replaced by anything. rendering with data will then remove this (correctly)
+ const expectedValue =
+ ' {{message}} {{message}} {{message}} {{message}}
';
+ test.equals(
+ util.sanitized(mixedPattern.extendedTemplate),
+ util.sanitized(expectedValue)
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+ }
+);
+
+tap.test(
+ 'processRecursive - correctly ignores bookended partials without a style modifier when the same partial has a style modifier between',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var atomPath = path.join('00-test', '03-styled-atom.mustache');
+ var atomPattern = loadPattern(atomPath, patternlab);
+
+ var bookendPath = path.join('00-test', '09-bookend.mustache');
+ var bookendPattern = loadPattern(bookendPath, patternlab);
+
+ var p1 = processIterative(atomPattern, patternlab);
+ var p2 = processIterative(bookendPattern, patternlab);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ processRecursive(bookendPath, patternlab)
+ .then(() => {
+ //assert. here we expect {{styleModifier}} to be in the first and last group, since it was not replaced by anything. rendering with data will then remove this (correctly)
+ const expectedValue =
+ ' {{message}} {{message}} {{message}} {{message}}
';
+ test.equals(
+ util.sanitized(bookendPattern.extendedTemplate),
+ util.sanitized(expectedValue)
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+ }
+);
+
+tap.test(
+ 'processRecursive - correctly ignores a partial without a style modifier when the same partial later has a style modifier and pattern parameters',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var atomPath = path.join('00-test', '03-styled-atom.mustache');
+ var atomPattern = loadPattern(atomPath, patternlab);
+
+ var mixedPath = path.join('00-test', '07-mixed-params.mustache');
+ var mixedPattern = loadPattern(mixedPath, patternlab);
+
+ var p1 = processIterative(atomPattern, patternlab);
+ var p2 = processIterative(mixedPattern, patternlab);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ processRecursive(mixedPath, patternlab)
+ .then(() => {
+ //assert. here we expect {{styleModifier}} to be in the first span, since it was not replaced by anything. rendering with data will then remove this (correctly)
+ const expectedValue =
+ ' {{message}} 2 3 4
';
+ test.equals(
+ util.sanitized(mixedPattern.extendedTemplate),
+ util.sanitized(expectedValue)
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+ }
+);
+
+tap.test(
+ 'processRecursive - correctly ignores bookended partials without a style modifier when the same partial has a style modifier and pattern parameters between',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var atomPath = path.join('00-test', '03-styled-atom.mustache');
+ var atomPattern = loadPattern(atomPath, patternlab);
+
+ var bookendPath = path.join('00-test', '08-bookend-params.mustache');
+ var bookendPattern = loadPattern(bookendPath, patternlab);
+
+ var p1 = processIterative(atomPattern, patternlab);
+ var p2 = processIterative(bookendPattern, patternlab);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ processRecursive(bookendPath, patternlab)
+ .then(() => {
+ //assert. here we expect {{styleModifier}} to be in the first and last span, since it was not replaced by anything. rendering with data will then remove this (correctly)
+ const expectedValue =
+ ' {{message}} 2 3 {{message}}
';
+ test.equals(
+ util.sanitized(bookendPattern.extendedTemplate),
+ util.sanitized(expectedValue)
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+ }
+);
+
+tap.test(
+ 'processRecursive - does not pollute previous patterns when a later one is found with a styleModifier',
+ function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var atomPath = path.join('00-test', '03-styled-atom.mustache');
+ var atomPattern = loadPattern(atomPath, patternlab);
+
+ var anotherPath = path.join('00-test', '12-another-styled-atom.mustache');
+ var anotherPattern = loadPattern(anotherPath, patternlab);
+
+ var p1 = processIterative(atomPattern, patternlab);
+ var p2 = processIterative(anotherPattern, patternlab);
+
+ Promise.all([p1, p2])
+ .then(() => {
+ //act
+ processRecursive(anotherPath, patternlab)
+ .then(() => {
+ //assert
+ const expectedCleanValue =
+ ' {{message}} ';
+ const expectedSetValue =
+ ' {{message}} ';
+
+ //this is the "atom" - it should remain unchanged
+ test.equals(
+ util.sanitized(atomPattern.template),
+ util.sanitized(expectedCleanValue)
+ );
+ test.equals(
+ util.sanitized(atomPattern.extendedTemplate),
+ util.sanitized(expectedCleanValue)
+ );
+
+ // this is the style modifier pattern, which should resolve correctly
+ test.equals(
+ util.sanitized(anotherPattern.template),
+ '{{> test-styled-atom:test_1 }}'
+ );
+ test.equals(
+ util.sanitized(anotherPattern.extendedTemplate),
+ util.sanitized(expectedSetValue)
+ );
+ test.end();
+ })
+ .catch(test.threw);
+ })
+ .catch(test.threw);
+ }
+);
+
+tap
+ .test('processRecursive - ensure deep-nesting works', function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ var atomPath = path.join('00-test', '01-bar.mustache');
+ var atomPattern = loadPattern(atomPath, patternlab);
+
+ var templatePath = path.join('00-test', '00-foo.mustache');
+ var templatePattern = loadPattern(templatePath, patternlab);
+
+ var pagesPath = path.join('00-test', '14-inception.mustache');
+ var pagesPattern = loadPattern(pagesPath, patternlab);
+
+ var p1 = processIterative(atomPattern, patternlab);
+ var p2 = processIterative(templatePattern, patternlab);
+ var p3 = processIterative(pagesPattern, patternlab);
+
+ return Promise.all([
+ p1,
+ p2,
+ p3,
+ processRecursive(atomPath, patternlab),
+ processRecursive(templatePath, patternlab),
+ processRecursive(pagesPath, patternlab),
+ ]).then(() => {
+ //act
+ return test.test(
+ 'processRecursive - ensure deep-nesting works2',
+ function(tt) {
+ //assert
+ const expectedCleanValue = 'bar';
+ const expectedSetValue = 'bar';
+
+ //this is the "atom" - it should remain unchanged
+ tt.equals(util.sanitized(atomPattern.template), expectedCleanValue);
+ tt.equals(
+ util.sanitized(atomPattern.extendedTemplate),
+ expectedCleanValue
+ );
+
+ //this is the "template pattern" - it should have an updated extendedTemplate but an unchanged template
+ tt.equals(
+ util.sanitized(templatePattern.template),
+ '{{> test-bar }}'
+ );
+ tt.equals(
+ util.sanitized(templatePattern.extendedTemplate),
+ expectedSetValue
+ );
+
+ //this is the "pages pattern" - it should have an updated extendedTemplate equal to the template pattern but an unchanged template
+ tt.equals(util.sanitized(pagesPattern.template), '{{> test-foo }}');
+ tt.equals(
+ util.sanitized(pagesPattern.extendedTemplate),
+ expectedSetValue
+ );
+ tt.end();
+ test.end();
+ }
+ );
+ });
+ })
+ .catch(tap.threw);
+
+tap.test('hidden patterns can be called by their nice names', function(test) {
+ //arrange
+ const patternlab = util.fakePatternLab(patterns_dir);
+
+ //act
+ var hiddenPatternPath = path.join('00-test', '_00-hidden-pattern.mustache');
+ var hiddenPattern = loadPattern(hiddenPatternPath, patternlab);
+
+ var testPatternPath = path.join(
+ '00-test',
+ '15-hidden-pattern-tester.mustache'
+ );
+ var testPattern = loadPattern(testPatternPath, patternlab);
+
+ var p1 = processIterative(hiddenPattern, patternlab);
+ var p2 = processIterative(testPattern, patternlab);
+
+ Promise.all([p1, p2]).then(() => {
+ //act
+ processRecursive(hiddenPatternPath, patternlab).then(() => {
+ processRecursive(testPatternPath, patternlab).then(() => {
+ testPattern.render().then(results => {
+ //assert
+ test.equals(
+ util.sanitized(results),
+ util.sanitized(
+ "Hello there! Here's the hidden atom: [This is the hidden atom]"
+ ),
+ 'hidden pattern rendered output not as expected'
+ );
+ test.end();
+ });
+ });
+ });
+ });
+});
+
+tap.test('parses pattern title correctly when frontmatter present', function(
+ test
+) {
+ //arrange
+ var pl = util.fakePatternLab(patterns_dir);
+
+ var testPatternPath = path.join('00-test', '01-bar.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //act
+ Promise.all([
+ processIterative(testPattern, pl),
+ processRecursive(testPatternPath, pl),
+ ])
+ .then(results => {
+ //assert
+ test.equals(
+ results[0].patternName,
+ 'An Atom Walks Into a Bar',
+ 'patternName not overridden'
+ );
+ test.end();
+ })
+ .catch(test.threw);
+});
+
+tap.test(
+ 'parses pattern extra frontmatter correctly when frontmatter present',
+ function(test) {
+ //arrange
+ var pl = util.fakePatternLab(patterns_dir);
+
+ var testPatternPath = path.join('00-test', '01-bar.mustache');
+ var testPattern = loadPattern(testPatternPath, pl);
+
+ //act
+ Promise.all([
+ processIterative(testPattern, pl),
+ processRecursive(testPatternPath, pl),
+ ])
+ .then(results => {
+ //assert
+ test.equals(results[0].allMarkdown.joke, 'bad', 'extra key not added');
+ test.end();
+ })
+ .catch(test.threw);
+ }
+);
diff --git a/packages/core/test/pseudopattern_hunter_tests.js b/packages/core/test/pseudopattern_hunter_tests.js
new file mode 100644
index 000000000..1464f28b4
--- /dev/null
+++ b/packages/core/test/pseudopattern_hunter_tests.js
@@ -0,0 +1,135 @@
+'use strict';
+
+var tap = require('tap');
+
+var path = require('path');
+var pph = require('../src/lib/pseudopattern_hunter');
+
+var loadPattern = require('../src/lib/loadPattern');
+var Pattern = require('../src/lib/object_factory').Pattern;
+var PatternGraph = require('../src/lib/pattern_graph').PatternGraph;
+const addPattern = require('../src/lib/addPattern');
+
+var config = require('./util/patternlab-config.json');
+var engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+
+var fs = require('fs-extra');
+var patterns_dir = `${__dirname}/files/_patterns/`;
+var public_patterns_dir = `${__dirname}/test/public/patterns`;
+
+function stubPatternlab() {
+ var pl = {};
+ pl.graph = PatternGraph.empty();
+ pl.config = {
+ paths: {
+ source: {
+ patterns: patterns_dir,
+ },
+ public: {
+ patterns: public_patterns_dir,
+ },
+ },
+ };
+ pl.data = {};
+ pl.data.link = {};
+ pl.config.logLevel = 'quiet';
+ pl.patterns = [];
+ pl.partials = {};
+ pl.config.patternStates = {};
+ pl.config.outputFileSuffixes = { rendered: '' };
+
+ return pl;
+}
+
+tap.test('pseudpattern found and added as a pattern', function(test) {
+ //arrange
+ var pl = stubPatternlab();
+
+ var atomPattern = loadPattern('00-test/03-styled-atom.mustache', pl);
+ addPattern(atomPattern, pl);
+
+ //act
+ var patternCountBefore = pl.patterns.length;
+ return pph.find_pseudopatterns(atomPattern, pl).then(() => {
+ //assert
+ test.equals(patternCountBefore + 1, pl.patterns.length);
+ test.equals(pl.patterns[1].patternPartial, 'test-styled-atom-alt');
+ test.equals(
+ JSON.stringify(pl.patterns[1].jsonFileData),
+ JSON.stringify({ message: 'alternateMessage' })
+ );
+ test.equals(
+ pl.patterns[1].patternLink,
+ '00-test-03-styled-atom-alt' +
+ path.sep +
+ '00-test-03-styled-atom-alt.html'
+ );
+ });
+});
+
+tap.test('pseudpattern does not pollute base pattern data', function(test) {
+ //arrange
+ var pl = stubPatternlab();
+
+ var atomPattern = loadPattern('00-test/03-styled-atom.mustache', pl);
+
+ //act
+ var patternCountBefore = pl.patterns.length;
+ return pph.find_pseudopatterns(atomPattern, pl).then(() => {
+ //assert
+ test.equals(pl.patterns[0].patternPartial, 'test-styled-atom');
+ test.equals(
+ JSON.stringify(pl.patterns[0].jsonFileData),
+ JSON.stringify({ message: 'baseMessage' })
+ );
+ });
+});
+
+tap.test(
+ 'pseudpattern variant includes stylePartials and parameteredPartials',
+ function(test) {
+ //arrange
+ var pl = stubPatternlab();
+
+ var atomPattern = new Pattern('00-test/03-styled-atom.mustache');
+ atomPattern.template = fs.readFileSync(
+ patterns_dir + '00-test/03-styled-atom.mustache',
+ 'utf8'
+ );
+ atomPattern.extendedTemplate = atomPattern.template;
+ atomPattern.stylePartials = atomPattern.findPartialsWithStyleModifiers(
+ atomPattern
+ );
+ atomPattern.parameteredPartials = atomPattern.findPartialsWithPatternParameters(
+ atomPattern
+ );
+
+ var pseudoPattern = new Pattern('00-test/474-pseudomodifier.mustache');
+ pseudoPattern.template = fs.readFileSync(
+ patterns_dir + '00-test/474-pseudomodifier.mustache',
+ 'utf8'
+ );
+ pseudoPattern.extendedTemplate = atomPattern.template;
+ pseudoPattern.stylePartials = pseudoPattern.findPartialsWithStyleModifiers(
+ pseudoPattern
+ );
+ pseudoPattern.parameteredPartials = pseudoPattern.findPartialsWithPatternParameters(
+ pseudoPattern
+ );
+
+ addPattern(atomPattern, pl);
+ addPattern(pseudoPattern, pl);
+
+ //act
+ return pph.find_pseudopatterns(pseudoPattern, pl).then(() => {
+ //assert
+ test.equals(pl.patterns[2].patternPartial, 'test-pseudomodifier-test');
+ test.equals(pl.patterns[2].stylePartials, pseudoPattern.stylePartials);
+ test.equals(
+ pl.patterns[2].parameteredPartials,
+ pseudoPattern.parameteredPartials
+ );
+ });
+ }
+);
diff --git a/packages/core/test/public/testDependencyGraph.json b/packages/core/test/public/testDependencyGraph.json
new file mode 100644
index 000000000..47d86ede1
--- /dev/null
+++ b/packages/core/test/public/testDependencyGraph.json
@@ -0,0 +1 @@
+{"version":1,"timestamp":1337,"graph":{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"atom-foo","value":{"compileState":"clean"}},{"v":"molecule-foo","value":{"compileState":"clean"}}],"edges":[{"v":"molecule-foo","w":"atom-foo","value":{}}]}}
diff --git a/packages/core/test/replaceParameter_tests.js b/packages/core/test/replaceParameter_tests.js
new file mode 100644
index 000000000..41fde31c1
--- /dev/null
+++ b/packages/core/test/replaceParameter_tests.js
@@ -0,0 +1,49 @@
+'use strict';
+
+const path = require('path');
+const util = require('./util/test_utils.js');
+const tap = require('tap');
+
+const replaceParameter = require('../src/lib/replaceParameter');
+
+tap.test('replaces simple value', function(test) {
+ const result = replaceParameter('{{key}}', 'key', 'value');
+ test.equals(result, 'value');
+ test.end();
+});
+
+tap.test('replaces simple boolean true value', function(test) {
+ const result = replaceParameter('{{key}}', 'key', true);
+ test.equals(result, 'true');
+ test.end();
+});
+
+tap.test('replaces simple boolean false value', function(test) {
+ const result = replaceParameter('{{key}}', 'key', false);
+ test.equals(result, 'false');
+ test.end();
+});
+
+tap.test('replaces raw value', function(test) {
+ const result = replaceParameter('{{{key}}}', 'key', 'value');
+ test.equals(result, 'value');
+ test.end();
+});
+
+tap.test('replaces boolean true section', function(test) {
+ const result = replaceParameter('1{{#key}}value{{/key}}2', 'key', true);
+ test.equals(result, '1value2');
+ test.end();
+});
+
+tap.only('replaces boolean true section with spaces', function(test) {
+ const result = replaceParameter('1{{ #key }}value{{ /key }}2', 'key', true);
+ test.equals(result, '1value2');
+ test.end();
+});
+
+tap.test('replaces boolean section false', function(test) {
+ const result = replaceParameter('1{{#key}}value{{/key}}2', 'key', false);
+ test.equals(result, '12');
+ test.end();
+});
diff --git a/packages/core/test/style_modifier_hunter_tests.js b/packages/core/test/style_modifier_hunter_tests.js
new file mode 100644
index 000000000..bdbc1e994
--- /dev/null
+++ b/packages/core/test/style_modifier_hunter_tests.js
@@ -0,0 +1,110 @@
+'use strict';
+
+var tap = require('tap');
+
+var smh = require('../src/lib/style_modifier_hunter');
+
+tap.test(
+ 'uses the partial stylemodifer to modify the patterns extendedTemplate',
+ function(test) {
+ //arrange
+ var pl = {};
+ pl.partials = {};
+ pl.config = {};
+ pl.config.logLevel = 'quiet';
+
+ var pattern = {
+ extendedTemplate: '
',
+ };
+
+ var style_modifier_hunter = new smh();
+
+ //act
+ style_modifier_hunter.consume_style_modifier(
+ pattern,
+ '{{> partial:bar}}',
+ pl
+ );
+
+ //assert
+ test.equals(pattern.extendedTemplate, '
');
+ test.end();
+ }
+);
+
+tap.test('replaces style modifiers with spaces in the syntax', function(test) {
+ //arrange
+ var pl = {};
+ pl.partials = {};
+ pl.config = {};
+ pl.config.logLevel = 'quiet';
+
+ var pattern = {
+ extendedTemplate: '
',
+ };
+
+ var style_modifier_hunter = new smh();
+
+ //act
+ style_modifier_hunter.consume_style_modifier(
+ pattern,
+ '{{> partial:bar}}',
+ pl
+ );
+
+ //assert
+ test.equals(pattern.extendedTemplate, '
');
+ test.end();
+});
+
+tap.test('replaces multiple style modifiers', function(test) {
+ //arrange
+ var pl = {};
+ pl.partials = {};
+ pl.config = {};
+ pl.config.logLevel = 'quiet';
+
+ var pattern = {
+ extendedTemplate: '
',
+ };
+
+ var style_modifier_hunter = new smh();
+
+ //act
+ style_modifier_hunter.consume_style_modifier(
+ pattern,
+ '{{> partial:bar|baz|dum}}',
+ pl
+ );
+
+ //assert
+ test.equals(pattern.extendedTemplate, '
');
+ test.end();
+});
+
+tap.test(
+ 'does not alter pattern extendedTemplate if styleModifier not found in partial',
+ function(test) {
+ //arrange
+ var pl = {};
+ pl.partials = {};
+ pl.config = {};
+ pl.config.logLevel = 'quiet';
+
+ var pattern = {
+ extendedTemplate: '
',
+ };
+
+ var style_modifier_hunter = new smh();
+
+ //act
+ style_modifier_hunter.consume_style_modifier(pattern, '{{> partial}}', pl);
+
+ //assert
+ test.equals(
+ pattern.extendedTemplate,
+ '
'
+ );
+ test.end();
+ }
+);
diff --git a/packages/core/test/ui_builder_tests.js b/packages/core/test/ui_builder_tests.js
new file mode 100644
index 000000000..607d9fd95
--- /dev/null
+++ b/packages/core/test/ui_builder_tests.js
@@ -0,0 +1,585 @@
+'use strict';
+
+var tap = require('tap');
+var rewire = require('rewire');
+var _ = require('lodash');
+var eol = require('os').EOL;
+var Pattern = require('../src/lib/object_factory').Pattern;
+var extend = require('util')._extend;
+var uiModule = rewire('../src/lib/ui_builder');
+var path = require('path');
+var config = require('./util/patternlab-config.json');
+
+var engineLoader = require('../src/lib/pattern_engines');
+engineLoader.loadAllEngines(config);
+
+//set up a global mocks - we don't want to be writing/rendering any files right now
+var fsMock = {
+ outputFileSync: function(path, data, cb) {},
+ outputFile: function(path, data, cb) {},
+};
+
+var renderMock = function(template, data, partials) {
+ return Promise.resolve('');
+};
+var buildFooterMock = function(patternlab, patternPartial) {
+ return Promise.resolve('');
+};
+
+//set our mocks in place of usual require()
+uiModule.__set__({
+ fs: fsMock,
+ render: renderMock,
+ buildFooter: buildFooterMock,
+});
+
+const uikit = {
+ name: 'uikit-workshop',
+ modulePath: '',
+ outputDir: 'test/output',
+ excludedPatternStates: [],
+};
+
+var ui = uiModule();
+
+function createFakePatternLab(customProps) {
+ var pl = {
+ config: {
+ paths: {
+ source: {
+ patterns: './test/files/_patterns',
+ },
+ public: {
+ patterns: '',
+ },
+ },
+ styleGuideExcludes: ['templates'],
+ logLevel: 'quiet',
+ outputFileSuffixes: {
+ rendered: '.rendered',
+ rawTemplate: '',
+ markupOnly: '.markup-only',
+ },
+ },
+ data: {},
+ uikits: [uikit],
+ };
+ return extend(pl, customProps);
+}
+
+tap.test(
+ 'isPatternExcluded - returns true when pattern filename starts with underscore',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({});
+ var pattern = new Pattern('00-test/_ignored-pattern.mustache');
+
+ //act
+ var result = ui.isPatternExcluded(pattern, patternlab, uikit);
+
+ //assert
+ test.equals(result, true);
+ test.end();
+ }
+);
+
+tap.test(
+ 'isPatternExcluded - returns true when pattern is defaultPattern',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({});
+ var pattern = new Pattern('00-test/foo.mustache');
+ patternlab.config.defaultPattern = 'test-foo';
+
+ //act
+ var result = ui.isPatternExcluded(pattern, patternlab, uikit);
+
+ //assert
+ test.equals(result, true);
+ test.end();
+ }
+);
+
+tap.test(
+ 'isPatternExcluded - returns true when pattern within underscored directory - top level',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({});
+ var pattern = Pattern.createEmpty({
+ relPath:
+ path.sep +
+ '_hidden' +
+ path.sep +
+ 'patternsubtype' +
+ path.sep +
+ 'foo.mustache',
+ isPattern: true,
+ fileName: 'foo.mustache',
+ patternPartial: 'hidden-foo',
+ });
+
+ //act
+ var result = ui.isPatternExcluded(pattern, patternlab, uikit);
+
+ //assert
+ test.equals(result, true);
+ test.end();
+ }
+);
+
+tap.test(
+ 'isPatternExcluded - returns true when pattern within underscored directory - subtype level',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({});
+ var pattern = Pattern.createEmpty({
+ relPath:
+ 'shown' + path.sep + '_patternsubtype' + path.sep + 'foo.mustache',
+ isPattern: true,
+ fileName: 'foo.mustache',
+ patternPartial: 'shown-foo',
+ });
+
+ //act
+ var result = ui.isPatternExcluded(pattern, patternlab, uikit);
+
+ //assert
+ test.equals(result, true);
+ test.end();
+ }
+);
+
+tap.test(
+ 'isPatternExcluded - returns true when pattern state found withing uikit exclusions',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({});
+ var pattern = Pattern.createEmpty({
+ relPath:
+ 'shown' + path.sep + '_patternsubtype' + path.sep + 'foo.mustache',
+ isPattern: true,
+ fileName: 'foo.mustache',
+ patternPartial: 'shown-foo',
+ patternState: 'complete',
+ });
+
+ //act
+ var result = ui.isPatternExcluded(pattern, patternlab, {
+ excludedPatternStates: 'complete',
+ });
+
+ //assert
+ test.equals(result, true);
+ test.end();
+ }
+);
+
+tap.test('groupPatterns - creates pattern groups correctly', function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({
+ patterns: [],
+ patternGroups: {},
+ subtypePatterns: {},
+ });
+
+ patternlab.patterns.push(
+ new Pattern('00-test/bar.mustache'),
+ new Pattern('00-test/foo.mustache'),
+ new Pattern('patternType1/patternSubType1/blue.mustache'),
+ new Pattern('patternType1/patternSubType1/red.mustache'),
+ new Pattern('patternType1/patternSubType1/yellow.mustache'),
+ new Pattern('patternType1/patternSubType2/black.mustache'),
+ new Pattern('patternType1/patternSubType2/grey.mustache'),
+ new Pattern('patternType1/patternSubType2/white.mustache')
+ );
+ ui.resetUIBuilderState(patternlab);
+
+ //act
+ var result = ui.groupPatterns(patternlab, uikit);
+
+ test.equals(
+ result.patternGroups.patternType1.patternSubType1.blue.patternPartial,
+ 'patternType1-blue'
+ );
+ test.equals(
+ result.patternGroups.patternType1.patternSubType1.red.patternPartial,
+ 'patternType1-red'
+ );
+ test.equals(
+ result.patternGroups.patternType1.patternSubType1.yellow.patternPartial,
+ 'patternType1-yellow'
+ );
+ test.equals(
+ result.patternGroups.patternType1.patternSubType2.black.patternPartial,
+ 'patternType1-black'
+ );
+ test.equals(
+ result.patternGroups.patternType1.patternSubType2.grey.patternPartial,
+ 'patternType1-grey'
+ );
+ test.equals(
+ result.patternGroups.patternType1.patternSubType2.white.patternPartial,
+ 'patternType1-white'
+ );
+
+ test.equals(
+ patternlab.patternTypes[0].patternItems[0].patternPartial,
+ 'test-bar',
+ 'first pattern item should be test-bar'
+ );
+ test.equals(
+ patternlab.patternTypes[0].patternItems[1].patternPartial,
+ 'test-foo',
+ 'second pattern item should be test-foo'
+ );
+
+ //todo: patternlab.patternTypes[0].patternItems[1] looks malformed
+
+ test.end();
+});
+
+tap.test('groupPatterns - orders patterns when provided from md', function(
+ test
+) {
+ //arrange
+ var patternlab = createFakePatternLab({
+ patterns: [],
+ patternGroups: {},
+ subtypePatterns: {},
+ });
+
+ patternlab.patterns.push(
+ new Pattern('patternType1/patternSubType1/blue.mustache'),
+ new Pattern('patternType1/patternSubType1/red.mustache'),
+ new Pattern('patternType1/patternSubType1/yellow.mustache')
+ );
+ ui.resetUIBuilderState(patternlab);
+
+ patternlab.patterns[1].order = 1;
+
+ //act
+ ui.groupPatterns(patternlab, uikit);
+
+ let patternType = _.find(patternlab.patternTypes, [
+ 'patternType',
+ 'patternType1',
+ ]);
+ let patternSubType = _.find(patternType.patternTypeItems, [
+ 'patternSubtype',
+ 'patternSubType1',
+ ]);
+ var items = patternSubType.patternSubtypeItems;
+
+ //zero is viewall
+ test.equals(items[1].patternPartial, 'patternType1-red');
+ test.equals(items[2].patternPartial, 'patternType1-blue');
+ test.equals(items[3].patternPartial, 'patternType1-yellow');
+
+ test.end();
+});
+
+tap.test(
+ 'groupPatterns - retains pattern order from name when order provided from md is malformed',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({
+ patterns: [],
+ patternGroups: {},
+ subtypePatterns: {},
+ });
+
+ patternlab.patterns.push(
+ new Pattern('patternType1/patternSubType1/blue.mustache'),
+ new Pattern('patternType1/patternSubType1/red.mustache'),
+ new Pattern('patternType1/patternSubType1/yellow.mustache')
+ );
+ ui.resetUIBuilderState(patternlab);
+
+ patternlab.patterns[1].order = 'notanumber!';
+
+ //act
+ ui.groupPatterns(patternlab, uikit);
+
+ let patternType = _.find(patternlab.patternTypes, [
+ 'patternType',
+ 'patternType1',
+ ]);
+ let patternSubType = _.find(patternType.patternTypeItems, [
+ 'patternSubtype',
+ 'patternSubType1',
+ ]);
+ var items = patternSubType.patternSubtypeItems;
+
+ //zero is viewall
+ test.equals(items[1].patternPartial, 'patternType1-blue');
+ test.equals(items[2].patternPartial, 'patternType1-red');
+ test.equals(items[3].patternPartial, 'patternType1-yellow');
+
+ test.end();
+ }
+);
+
+tap.test(
+ 'groupPatterns - sorts viewall subtype pattern to the beginning',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({
+ patterns: [],
+ patternGroups: {},
+ subtypePatterns: {},
+ });
+
+ patternlab.patterns.push(
+ new Pattern('patternType1/patternSubType1/blue.mustache'),
+ new Pattern('patternType1/patternSubType1/red.mustache'),
+ new Pattern('patternType1/patternSubType1/yellow.mustache')
+ );
+ ui.resetUIBuilderState(patternlab);
+
+ patternlab.patterns[0].order = 1;
+ patternlab.patterns[1].order = 3;
+ patternlab.patterns[2].order = 2;
+
+ //act
+ ui.groupPatterns(patternlab, uikit);
+
+ let patternType = _.find(patternlab.patternTypes, [
+ 'patternType',
+ 'patternType1',
+ ]);
+ let patternSubType = _.find(patternType.patternTypeItems, [
+ 'patternSubtype',
+ 'patternSubType1',
+ ]);
+ var items = patternSubType.patternSubtypeItems;
+
+ //zero is viewall
+ test.equals(
+ items[0].patternPartial,
+ 'viewall-patternType1-patternSubType1'
+ );
+ test.equals(items[1].patternPartial, 'patternType1-blue');
+ test.equals(items[2].patternPartial, 'patternType1-yellow');
+ test.equals(items[3].patternPartial, 'patternType1-red');
+
+ test.end();
+ }
+);
+
+tap.test(
+ 'groupPatterns - creates documentation patterns for each type and subtype if not exists',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({
+ patterns: [],
+ patternGroups: {},
+ subtypePatterns: {},
+ });
+
+ patternlab.patterns.push(
+ new Pattern('00-test/foo.mustache'),
+ new Pattern('00-test/bar.mustache'),
+ new Pattern('patternType1/patternSubType1/blue.mustache'),
+ new Pattern('patternType1/patternSubType1/red.mustache'),
+ new Pattern('patternType1/patternSubType1/yellow.mustache'),
+ new Pattern('patternType1/patternSubType2/black.mustache'),
+ new Pattern('patternType1/patternSubType2/grey.mustache'),
+ new Pattern('patternType1/patternSubType2/white.mustache')
+ );
+ ui.resetUIBuilderState(patternlab);
+
+ //act
+ var result = ui.groupPatterns(patternlab, uikit);
+
+ //assert
+ test.equals(
+ result.patternGroups.patternType1.patternSubType1[
+ 'viewall-patternType1-patternSubType1'
+ ].patternPartial,
+ 'viewall-patternType1-patternSubType1'
+ );
+ test.equals(
+ result.patternGroups.patternType1.patternSubType2[
+ 'viewall-patternType1-patternSubType2'
+ ].patternPartial,
+ 'viewall-patternType1-patternSubType2'
+ );
+
+ test.end();
+ }
+);
+
+tap.test(
+ 'groupPatterns - adds each pattern to the patternPaths object',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({
+ patterns: [],
+ patternGroups: {},
+ subtypePatterns: {},
+ });
+
+ patternlab.patterns.push(
+ new Pattern('00-test/foo.mustache'),
+ new Pattern('00-test/bar.mustache'),
+ new Pattern('patternType1/patternSubType1/blue.mustache'),
+ new Pattern('patternType1/patternSubType1/red.mustache'),
+ new Pattern('patternType1/patternSubType1/yellow.mustache'),
+ new Pattern('patternType1/patternSubType2/black.mustache'),
+ new Pattern('patternType1/patternSubType2/grey.mustache'),
+ new Pattern('patternType1/patternSubType2/white.mustache')
+ );
+ ui.resetUIBuilderState(patternlab);
+
+ //act
+ var result = ui.groupPatterns(patternlab, uikit);
+
+ //assert
+ test.equals(patternlab.patternPaths['test']['foo'], '00-test-foo');
+ test.equals(patternlab.patternPaths['test']['bar'], '00-test-bar');
+ test.equals(
+ patternlab.patternPaths['patternType1']['blue'],
+ 'patternType1-patternSubType1-blue'
+ );
+ test.equals(
+ patternlab.patternPaths['patternType1']['red'],
+ 'patternType1-patternSubType1-red'
+ );
+ test.equals(
+ patternlab.patternPaths['patternType1']['yellow'],
+ 'patternType1-patternSubType1-yellow'
+ );
+ test.equals(
+ patternlab.patternPaths['patternType1']['black'],
+ 'patternType1-patternSubType2-black'
+ );
+ test.equals(
+ patternlab.patternPaths['patternType1']['grey'],
+ 'patternType1-patternSubType2-grey'
+ );
+ test.equals(
+ patternlab.patternPaths['patternType1']['white'],
+ 'patternType1-patternSubType2-white'
+ );
+
+ test.end();
+ }
+);
+
+tap.test(
+ 'groupPatterns - adds each pattern to the view all paths object',
+ function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({
+ patterns: [],
+ patternGroups: {},
+ subtypePatterns: {},
+ });
+
+ patternlab.patterns.push(
+ new Pattern('00-test/foo.mustache'),
+ new Pattern('00-test/bar.mustache'),
+ new Pattern('patternType1/patternSubType1/blue.mustache'),
+ new Pattern('patternType1/patternSubType1/red.mustache'),
+ new Pattern('patternType1/patternSubType1/yellow.mustache'),
+ new Pattern('patternType1/patternSubType2/black.mustache'),
+ new Pattern('patternType1/patternSubType2/grey.mustache'),
+ new Pattern('patternType1/patternSubType2/white.mustache')
+ );
+ ui.resetUIBuilderState(patternlab);
+
+ //act
+ var result = ui.groupPatterns(patternlab, uikit);
+
+ //assert
+ test.equals('todo', 'todo');
+
+ test.end();
+ }
+);
+
+tap.test('resetUIBuilderState - reset global objects', function(test) {
+ //arrange
+ var patternlab = createFakePatternLab({
+ patternPaths: { foo: 1 },
+ viewAllPaths: { bar: 2 },
+ patternTypes: ['baz'],
+ });
+
+ //act
+ ui.resetUIBuilderState(patternlab);
+
+ //assert
+ test.equals(patternlab.patternPaths.foo, undefined);
+ test.equals(patternlab.viewAllPaths.bar, undefined);
+ test.equals(patternlab.patternTypes.length, 0);
+
+ test.end();
+});
+
+tap.test(
+ 'buildViewAllPages - adds viewall page for each type and subtype',
+ function(test) {
+ //arrange
+ const mainPageHeadHtml = '';
+ const patternlab = createFakePatternLab({
+ patterns: [],
+ patternGroups: {},
+ subtypePatterns: {},
+ footer: {},
+ userFoot: {},
+ cacheBuster: 1234,
+ });
+
+ patternlab.patterns.push(
+ //this flat pattern is found and causes trouble for the rest of the crew
+ new Pattern('00-test/foo.mustache'),
+ new Pattern('patternType1/patternSubType1/blue.mustache'),
+ new Pattern('patternType1/patternSubType1/red.mustache'),
+ new Pattern('patternType1/patternSubType1/yellow.mustache'),
+ new Pattern('patternType1/patternSubType2/black.mustache'),
+ new Pattern('patternType1/patternSubType2/grey.mustache'),
+ new Pattern('patternType1/patternSubType2/white.mustache')
+ );
+ ui.resetUIBuilderState(patternlab);
+
+ const styleguidePatterns = ui.groupPatterns(patternlab, uikit);
+
+ //act
+ ui
+ .buildViewAllPages(
+ mainPageHeadHtml,
+ patternlab,
+ styleguidePatterns,
+ uikit
+ )
+ .then(allPatterns => {
+ //assert
+ //this was a nuanced one. buildViewAllPages() had return false; statements
+ //within _.forOwn(...) loops, causing premature termination of the entire loop
+ //when what was intended was a continue
+ //we expect 8 here because:
+ // - foo.mustache is flat and therefore does not have a viewall page
+ // - the colors.mustache files make 6
+ // - patternSubType1 and patternSubType2 make 8
+ //while most of that heavy lifting occurs inside groupPatterns and not buildViewAllPages,
+ //it's important to ensure that this method does not get prematurely terminated
+ //we choose to do that by checking it's return number of patterns
+
+ //todo: this workaround matches the code at the moment
+ const uniquePatterns = _.uniq(
+ _.flatMapDeep(allPatterns, pattern => {
+ return pattern;
+ })
+ );
+
+ test.equals(
+ uniquePatterns.length,
+ 8,
+ '2 viewall pages should be added'
+ );
+
+ test.end();
+ });
+ }
+);
diff --git a/packages/core/test/uikitExcludePattern_tests.js b/packages/core/test/uikitExcludePattern_tests.js
new file mode 100644
index 000000000..a31f4c3f3
--- /dev/null
+++ b/packages/core/test/uikitExcludePattern_tests.js
@@ -0,0 +1,53 @@
+'use strict';
+
+const tap = require('tap');
+
+const uikitExcludePattern = require('../src/lib/uikitExcludePattern');
+
+tap.test(
+ 'uikitExcludePattern - returns false when uikit has no excluded states',
+ test => {
+ //arrange
+ const uikit = { excludedPatternStates: [] };
+ const pattern = { patternState: 'complete' };
+
+ //act
+ const result = uikitExcludePattern(pattern, uikit);
+
+ //assert
+ test.false(result);
+ test.end();
+ }
+);
+
+tap.test(
+ 'uikitExcludePattern - returns false pattern does not have same state as uikit exclusions',
+ test => {
+ //arrange
+ const uikit = { excludedPatternStates: ['complete'] };
+ const pattern = { patternState: 'inprogress' };
+
+ //act
+ const result = uikitExcludePattern(pattern, uikit);
+
+ //assert
+ test.false(result);
+ test.end();
+ }
+);
+
+tap.test(
+ 'uikitExcludePattern - returns true when uikit has same state as pattern',
+ test => {
+ //arrange
+ const uikit = { excludedPatternStates: ['inreview', 'complete'] };
+ const pattern = { patternState: 'complete' };
+
+ //act
+ const result = uikitExcludePattern(pattern, uikit);
+
+ //assert
+ test.true(result);
+ test.end();
+ }
+);
diff --git a/packages/core/test/util/patternlab-config.json b/packages/core/test/util/patternlab-config.json
new file mode 100644
index 000000000..c2edf9561
--- /dev/null
+++ b/packages/core/test/util/patternlab-config.json
@@ -0,0 +1,83 @@
+{
+ "paths": {
+ "source": {
+ "root": "test/files/",
+ "patterns": "./test/files/_patterns/",
+ "data": "./test/files/_data/",
+ "meta": "./test/files/_meta/",
+ "styleguide": "./test/files/styleguide/",
+ "patternlabFiles": {
+ "general-header": "./test/files/partials/general-header.mustache",
+ "general-footer": "./test/files/partials/general-footer.mustache",
+ "patternSection": "./test/files/partials/patternSection.mustache",
+ "patternSectionSubtype":
+ "./test/files/partials/patternSectionSubtype.mustache",
+ "viewall": "./test/files/viewall.mustache"
+ },
+ "js": "./test/files/js",
+ "images": "./test/files/images",
+ "fonts": "./test/files/fonts",
+ "css": "./test/files/css/"
+ },
+ "public": {
+ "root": "public/",
+ "patterns": "public/patterns/",
+ "data": "public/data/",
+ "styleguide": "public/styleguide/",
+ "js": "public/js",
+ "images": "public/images",
+ "fonts": "public/fonts",
+ "css": "public/css"
+ }
+ },
+ "styleGuideExcludes": ["templates", "pages"],
+ "defaultPattern": "all",
+ "logLevel": "quiet",
+ "ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "mqs": false,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+ },
+ "ishMinimum": "240",
+ "ishMaximum": "2600",
+ "patternStateCascade": ["inprogress", "inreview", "complete"],
+ "patternStates": {},
+ "patternExportPatternPartials": [],
+ "patternExportDirectory": "./pattern_exports/",
+ "patternExtension": "mustache",
+ "cacheBust": true,
+ "outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+ },
+ "cleanOutputHtml": true,
+ "exportToGraphViz": false,
+ "cleanPublic": true,
+ "theme": {
+ "color": "dark",
+ "density": "compact",
+ "layout": "horizontal"
+ },
+ "uikits": [
+ {
+ "name": "uikit-workshop",
+ "outputDir": "packages/core/test/",
+ "enabled": true,
+ "excludedPatternStates": [],
+ "excludedTags": []
+ }
+ ]
+}
diff --git a/packages/core/test/util/test_utils.js b/packages/core/test/util/test_utils.js
new file mode 100644
index 000000000..36d3f6e1d
--- /dev/null
+++ b/packages/core/test/util/test_utils.js
@@ -0,0 +1,59 @@
+'use strict';
+
+var PatternGraph = require('./../../src/lib/pattern_graph').PatternGraph;
+
+module.exports = {
+ // fake pattern lab constructor:
+ // sets up a fake patternlab object, which is needed by the pattern processing
+ // apparatus.
+ fakePatternLab: (testPatternsPath, extraData) => {
+ var fpl = {
+ graph: PatternGraph.empty(),
+ partials: {},
+ patterns: [],
+ subtypePatterns: {},
+ footer: '',
+ header: '',
+ listitems: {},
+ data: {
+ link: {},
+ },
+ config: require('../../patternlab-config.json'),
+ package: {},
+ };
+
+ // patch the pattern source so the pattern assembler can correctly determine
+ // the "subdir"
+ fpl.config.paths.source.patterns = testPatternsPath;
+
+ return Object.assign({}, fpl, extraData);
+ },
+
+ /**
+ * Strip out control characters from output if needed so make comparisons easier
+ * @param output - the template to strip
+ */
+ sanitized: outputTemplate => {
+ return outputTemplate
+ .replace(/\n/g, ' ')
+ .replace(/\r/g, ' ')
+ .replace(/\s\s+/g, ' ')
+ .trim();
+ },
+
+ /**
+ * normalize a string (probably a path) to posix - style
+ * @param s - the string or array of strings to normalize path separators to posix - style
+ */
+ posixPath: s => {
+ if (Array.isArray(s)) {
+ var paths = [];
+ for (let i = 0; i < s.length; i++) {
+ paths.push(s[i].replace(/\\/g, '/'));
+ }
+ return paths;
+ } else {
+ return s.replace(/\\/g, '/');
+ }
+ },
+};
diff --git a/packages/core/test/watchAssets_tests.js b/packages/core/test/watchAssets_tests.js
new file mode 100644
index 000000000..d72cd0715
--- /dev/null
+++ b/packages/core/test/watchAssets_tests.js
@@ -0,0 +1,53 @@
+'use strict';
+
+const _ = require('lodash');
+const tap = require('tap');
+const rewire = require('rewire');
+const path = require('path');
+
+const util = require('./util/test_utils.js');
+const watchAssets = rewire('../src/lib/watchAssets');
+
+const patterns_dir = './test/files/_patterns';
+
+tap.test(
+ 'watchAssets - adds assetWatcher to patternlab.watchers for given key ',
+ test => {
+ const pl = util.fakePatternLab(patterns_dir, { watchers: [] });
+ const key = 'images';
+
+ watchAssets(
+ pl,
+ '/foo',
+ { source: '/images', public: '/images' },
+ key,
+ {},
+ true
+ );
+
+ test.equals(_.keys(pl.watchers)[0], 'images');
+
+ test.end();
+ }
+);
+
+tap.test('watchAssets - complete path copied', test => {
+ const copyFileMock = function(p, des) {
+ test.equals(des, path.resolve('/proj/public/images/sample/waterfall.jpg'));
+ };
+
+ //set our mocks in place of usual require()
+ watchAssets.__set__({
+ copyFile: copyFileMock,
+ });
+
+ const onWatchTripped = watchAssets.__get__('onWatchTripped');
+ onWatchTripped(
+ '/proj/source/images/sample/waterfall.jpg',
+ '/proj/source/images',
+ '/proj',
+ { public: '/proj/public/images' },
+ {}
+ );
+ test.end();
+});
diff --git a/packages/core/test/watchPatternLabFiles_tests.js b/packages/core/test/watchPatternLabFiles_tests.js
new file mode 100644
index 000000000..f61b873e6
--- /dev/null
+++ b/packages/core/test/watchPatternLabFiles_tests.js
@@ -0,0 +1,44 @@
+'use strict';
+
+const _ = require('lodash');
+const tap = require('tap');
+const rewire = require('rewire');
+const path = require('path');
+
+const util = require('./util/test_utils.js');
+const watchPatternLabFiles = rewire('../src/lib/watchPatternLabFiles');
+
+const patterns_dir = './test/files/_patterns';
+
+tap.test(
+ 'watchPatternLabFiles - adds watcher to patternlab.watchers for given patternWatchPath',
+ test => {
+ const pl = util.fakePatternLab(patterns_dir, {
+ watchers: [],
+ engines: {},
+ });
+
+ pl.engines.getSupportedFileExtensions = () => {
+ return ['.mustache'];
+ };
+
+ watchPatternLabFiles(
+ pl,
+ {
+ source: {
+ data: '_data',
+ meta: '_meta',
+ patterns: 'patterns',
+ },
+ },
+ '/foo',
+ true
+ );
+
+ // should have two for _data and _meta
+ // should have five for '.json', '.yml', '.yaml', '.md' and '.mustache'
+ test.equals(Object.keys(pl.watchers).length, 7);
+
+ test.end();
+ }
+);
diff --git a/packages/create/CHANGELOG.md b/packages/create/CHANGELOG.md
new file mode 100644
index 000000000..f964faba6
--- /dev/null
+++ b/packages/create/CHANGELOG.md
@@ -0,0 +1,100 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+## [5.9.1](https://github.com/pattern-lab/patternlab-node/compare/v5.9.0...v5.9.1) (2020-04-24)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/patternlab-node/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/patternlab-node/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/patternlab-node/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/patternlab-node/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+
+## [1.0.11](https://github.com/pattern-lab/patternlab-node/compare/create-pattern-lab@1.0.10...create-pattern-lab@1.0.11) (2019-10-14)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+
+## [1.0.8](https://github.com/pattern-lab/patternlab-node/compare/create-pattern-lab@1.0.7...create-pattern-lab@1.0.8) (2019-08-23)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+## [1.0.7](https://github.com/pattern-lab/patternlab-node/compare/create-pattern-lab@1.0.6...create-pattern-lab@1.0.7) (2019-08-23)
+
+**Note:** Version bump only for package create-pattern-lab
+
+
+
+
+
+
+## [1.0.6](https://github.com/sghoweri/patternlab-node/compare/create-pattern-lab@1.0.5...create-pattern-lab@1.0.6) (2019-05-16)
+
+**Note:** Version bump only for package create-pattern-lab
diff --git a/packages/create/README.md b/packages/create/README.md
new file mode 100644
index 000000000..53e6f5770
--- /dev/null
+++ b/packages/create/README.md
@@ -0,0 +1,16 @@
+# Create Pattern Lab
+
+To get started, simply run:
+
+```bash
+npm create pattern-lab
+```
+
+Then follow the prompts.
+
+This is the same as using the main Pattern Lab CLI's `init` command:
+
+```bash
+npm i -g @pattern-lab/cli
+pattern-lab init
+```
diff --git a/packages/create/index.js b/packages/create/index.js
new file mode 100755
index 000000000..ac811a7ba
--- /dev/null
+++ b/packages/create/index.js
@@ -0,0 +1,4 @@
+#! /usr/bin/env node
+const init = require('@pattern-lab/cli/bin/cli-actions/init');
+
+init({});
diff --git a/packages/create/package.json b/packages/create/package.json
new file mode 100644
index 000000000..e09874fee
--- /dev/null
+++ b/packages/create/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "create-pattern-lab",
+ "version": "5.9.3",
+ "description": "",
+ "bin": "index.js",
+ "main": "index.js",
+ "scripts": {},
+ "dependencies": {
+ "@pattern-lab/cli": "^5.9.3"
+ },
+ "author": "",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/development-edition-engine-handlebars/.gitignore b/packages/development-edition-engine-handlebars/.gitignore
new file mode 100644
index 000000000..0679bd2b5
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.DS_Store
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+.idea/
+public
+dependencyGraph.json
diff --git a/packages/development-edition-engine-handlebars/.npmrc b/packages/development-edition-engine-handlebars/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/development-edition-engine-handlebars/.nvmrc b/packages/development-edition-engine-handlebars/.nvmrc
new file mode 100644
index 000000000..7f976a5ae
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/.nvmrc
@@ -0,0 +1 @@
+12.12.0
diff --git a/packages/development-edition-engine-handlebars/CHANGELOG.md b/packages/development-edition-engine-handlebars/CHANGELOG.md
new file mode 100644
index 000000000..2aba60593
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/CHANGELOG.md
@@ -0,0 +1,256 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+## [5.9.2](https://github.com/pattern-lab/patternlab-node/compare/v5.9.1...v5.9.2) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+## [5.9.1](https://github.com/pattern-lab/patternlab-node/compare/v5.9.0...v5.9.1) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+## [5.7.2](https://github.com/pattern-lab/patternlab-node/compare/v5.7.1...v5.7.2) (2020-03-24)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+## [5.7.1](https://github.com/pattern-lab/patternlab-node/compare/v5.7.0...v5.7.1) (2020-02-24)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/patternlab-node/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+# [5.6.0](https://github.com/pattern-lab/patternlab-node/compare/v5.5.0...v5.6.0) (2020-01-18)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+
+# [5.5.0](https://github.com/pattern-lab/patternlab-node/compare/v5.4.2...v5.5.0) (2019-12-19)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+## [5.4.2](https://github.com/pattern-lab/patternlab-node/compare/v5.4.1...v5.4.2) (2019-11-27)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+## [5.4.1](https://github.com/pattern-lab/patternlab-node/compare/v5.4.0...v5.4.1) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/patternlab-node/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+
+### Features
+
+* major improvements to local UIKit workflow ([4dc9173](https://github.com/pattern-lab/patternlab-node/commit/4dc9173a5a44b422e9677824de3728048b7c4f05))
+
+
+
+
+
+## [5.3.3](https://github.com/pattern-lab/patternlab-node/compare/v5.3.2...v5.3.3) (2019-11-22)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+
+## [5.3.2](https://github.com/pattern-lab/patternlab-node/compare/v5.3.1...v5.3.2) (2019-11-14)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+## [5.3.1](https://github.com/pattern-lab/patternlab-node/compare/v5.3.0...v5.3.1) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/patternlab-node/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+# [5.2.0](https://github.com/pattern-lab/patternlab-node/compare/v5.1.0...v5.2.0) (2019-11-12)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/patternlab-node/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+
+### Features
+
+* **config:** add new default pattern export options ([a7487a0](https://github.com/pattern-lab/patternlab-node/commit/a7487a0681cb11e6f3c5c8eaefd62e5648ad5ea3))
+
+
+
+
+
+## [5.0.2](https://github.com/pattern-lab/patternlab-node/compare/v5.0.1...v5.0.2) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+## [5.0.1](https://github.com/pattern-lab/patternlab-node/compare/v5.0.0...v5.0.1) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Bug Fixes
+
+* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/commit/74e5af28c4e714fdfc1db535b94c52f3dc14a3a4))
+* update the default pattern that displays in the Handlebars demo ([ff1d85f](https://github.com/pattern-lab/patternlab-node/commit/ff1d85f2852fc4f210841e8e0aaf14b55165ce58))
+
+
+### Features
+
+* **engine-handlebars:** Demonstration of custom Handlebars helper ([f330b5b](https://github.com/pattern-lab/patternlab-node/commit/f330b5bca72f2f34bfafe5c2c64e6b0b8823eb1c))
+* **plugin-tab, core:** initial plugin hook exploration ([2f3d39a](https://github.com/pattern-lab/patternlab-node/commit/2f3d39ac6b125ad4c6b872e27ee224ce2ea33a12))
+* introduce netlify preview ([6c5d332](https://github.com/pattern-lab/patternlab-node/commit/6c5d332479fb6836bd8bd5530a074d13440f8ae4))
+
+
+
+
+
+
+## [0.1.6](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.1.5...@pattern-lab/development-edition-engine-handlebars@0.1.6) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+
+## [0.1.5](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.1.4...@pattern-lab/development-edition-engine-handlebars@0.1.5) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+
+## [0.1.1](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.1.0...@pattern-lab/development-edition-engine-handlebars@0.1.1) (2019-08-23)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+# [0.1.0](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.0.3...@pattern-lab/development-edition-engine-handlebars@0.1.0) (2019-08-23)
+
+
+### Bug Fixes
+
+* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/commit/74e5af2))
+
+
+### Features
+
+* **engine-handlebars:** Demonstration of custom Handlebars helper ([f330b5b](https://github.com/pattern-lab/patternlab-node/commit/f330b5b))
+
+
+
+
+
+
+## [0.0.3](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.0.3-alpha.0...@pattern-lab/development-edition-engine-handlebars@0.0.3) (2019-05-16)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars
+
+
+
+
+
+## 0.0.1-beta.0 (2019-02-09)
+
+
+### Bug Fixes
+
+* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/patternlab-node/commit/98dfadf))
+
+
+### Features
+
+* **package:** add handlebars development edition ([454095d](https://github.com/pattern-lab/patternlab-node/commit/454095d))
diff --git a/packages/development-edition-engine-handlebars/README.md b/packages/development-edition-engine-handlebars/README.md
new file mode 100644
index 000000000..5bb88ff96
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/README.md
@@ -0,0 +1,28 @@
+
+
+# Pattern Lab Node - Development Edition Engine Handlebars
+
+_here be dragons_
+
+This Development Edition is a variant of [Edition Node](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node) for convience purposes only, loaded with the Handlebars Engine. The goals of this Development Edition are two-fold:
+
+* Develop the [Handlebars Engine](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars)
+* Build and test against Handlebars pattern tree
+
+> Development Editions of Pattern Lab provide the ability to work on and commit changes to select packages within the overall Pattern Lab [ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). This Edition is NOT stable.
+
+
+## Working on Pattern Lab's UI Locally
+
+### Step 1: Install Dependencies
+Run the following in the root of the Pattern Lab repo:
+
+```
+yarn run setup
+```
+
+### Step 2 (Optional)
+If you want to build using a fuller set of examples than what comes with this default Handlebars demo, run `yarn run preview:hbs`. Otherwise skip to step 3.
+
+### Step 3
+Finally, go back into this folder, `cd packages/development-edition-engine-handlebars`, and start up the local dev server which watches UIKit and the local Pattern Lab instance for changes, live reloads, etc by running `yarn dev`
diff --git a/packages/development-edition-engine-handlebars/helpers/test.js b/packages/development-edition-engine-handlebars/helpers/test.js
new file mode 100644
index 000000000..8b32ed799
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/helpers/test.js
@@ -0,0 +1,5 @@
+module.exports = function(Handlebars) {
+ Handlebars.registerHelper('test', function() {
+ return 'This is a test helper';
+ });
+};
diff --git a/packages/development-edition-engine-handlebars/package.json b/packages/development-edition-engine-handlebars/package.json
new file mode 100644
index 000000000..6ede6103f
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "@pattern-lab/development-edition-engine-handlebars",
+ "private": true,
+ "version": "5.9.3",
+ "description": "The tree of components we use to test, develop and validate the Handlebars engine",
+ "scripts": {
+ "pl:build": "patternlab build --config ./patternlab-config.json",
+ "pl:help": "patternlab --help",
+ "pl:install": "patternlab install --config ./patternlab-config.json",
+ "pl:serve": "patternlab serve --config ./patternlab-config.json",
+ "pl:version": "patternlab --version",
+ "dev": "node ./node_modules/@pattern-lab/uikit-workshop/build-tools.js"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Handlebars",
+ "Edition"
+ ],
+ "author": "Brian Muenzenmeye",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/pattern-lab/patternlab-node.git"
+ },
+ "engines": {
+ "node": ">=12.12.0"
+ },
+ "dependencies": {
+ "@pattern-lab/cli": "^5.9.3",
+ "@pattern-lab/core": "^5.9.3",
+ "@pattern-lab/engine-handlebars": "^5.5.0",
+ "@pattern-lab/engine-mustache": "^5.0.0",
+ "@pattern-lab/plugin-tab": "^5.9.3",
+ "@pattern-lab/starterkit-mustache-demo": "^5.0.0",
+ "@pattern-lab/uikit-workshop": "^5.9.3"
+ }
+}
diff --git a/packages/development-edition-engine-handlebars/patternlab-config.json b/packages/development-edition-engine-handlebars/patternlab-config.json
new file mode 100644
index 000000000..5b9e9a0c2
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/patternlab-config.json
@@ -0,0 +1,107 @@
+{
+ "cacheBust": true,
+ "cleanPublic": true,
+ "defaultPattern": "all",
+ "defaultShowPatternInfo": false,
+ "ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": true,
+ "disco": true,
+ "hay": true,
+ "mqs": true,
+ "find": true,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+ },
+ "ishViewportRange": {
+ "s": [240, 500],
+ "m": [500, 800],
+ "l": [800, 2600]
+ },
+ "logLevel": "info",
+ "outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+ },
+ "paths": {
+ "source": {
+ "root": "source/",
+ "patterns": "source/_patterns/",
+ "data": "source/_data/",
+ "meta": "source/_meta/",
+ "annotations": "source/_annotations/",
+ "styleguide": "dist/",
+ "patternlabFiles": {
+ "general-header": "views/partials/general-header.mustache",
+ "general-footer": "views/partials/general-footer.mustache",
+ "patternSection": "views/partials/patternSection.mustache",
+ "patternSectionSubtype": "views/partials/patternSectionSubtype.mustache",
+ "viewall": "views/viewall.mustache"
+ },
+ "js": "source/js",
+ "images": "source/images",
+ "fonts": "source/fonts",
+ "css": "source/css"
+ },
+ "public": {
+ "root": "public/",
+ "patterns": "public/patterns/",
+ "data": "public/styleguide/data/",
+ "annotations": "public/annotations/",
+ "styleguide": "public/styleguide/",
+ "js": "public/js",
+ "images": "public/images",
+ "fonts": "public/fonts",
+ "css": "public/css"
+ }
+ },
+ "patternExtension": "hbs",
+ "patternStateCascade": ["inprogress", "inreview", "complete"],
+ "patternExportAll": false,
+ "patternExportDirectory": "pattern_exports",
+ "patternExportPatternPartials": [],
+ "patternExportPreserveDirectoryStructure": true,
+ "patternExportRaw": false,
+ "serverOptions": {
+ "wait": 1000
+ },
+ "starterkitSubDir": "dist",
+ "styleGuideExcludes": [],
+ "theme": {
+ "color": "dark",
+ "density": "compact",
+ "layout": "horizontal",
+ "noViewAll": false
+ },
+ "uikits": [
+ {
+ "name": "uikit-workshop",
+ "outputDir": "",
+ "enabled": true,
+ "excludedPatternStates": [],
+ "excludedTags": []
+ }
+ ],
+ "engines": {
+ "handlebars": {
+ "extend": "helpers/*.js"
+ }
+ },
+ "plugins": {
+ "@pattern-lab/plugin-tab": {
+ "enabled": true,
+ "initialized": false,
+ "options": {
+ "tabsToAdd": ["scss"]
+ }
+ }
+ }
+}
diff --git a/packages/development-edition-engine-handlebars/source/_annotations/annotations.json b/packages/development-edition-engine-handlebars/source/_annotations/annotations.json
new file mode 100644
index 000000000..a0d0268f8
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/_annotations/annotations.json
@@ -0,0 +1,3 @@
+{
+ "comments": []
+}
diff --git a/packages/development-edition-engine-handlebars/source/_data/data.json b/packages/development-edition-engine-handlebars/source/_data/data.json
new file mode 100644
index 000000000..250376db0
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/_data/data.json
@@ -0,0 +1,34 @@
+{
+ "version": "2",
+ "swatches": [
+ {
+ "color": {
+ "hex": "#031636",
+ "cmyk": "94 59 0 79"
+ },
+ "label": "dark blue"
+ },
+ {
+ "color": {
+ "hex": "#0D80F0",
+ "cmyk": "95 47 0 6"
+ },
+ "label": "light blue"
+ },
+ {
+ "color": {
+ "hex": "#4c4c4c",
+ "cmyk": "0 0 0 70"
+ },
+ "label": "dark grey"
+ },
+ {
+ "color": {
+ "hex": "#b2b2b2",
+ "cmyk": "0 0 0 30"
+ },
+ "label": "light grey",
+ "inverted": true
+ }
+ ]
+}
diff --git a/packages/development-edition-engine-handlebars/source/_data/listitems.json b/packages/development-edition-engine-handlebars/source/_data/listitems.json
new file mode 100644
index 000000000..c35d1076d
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/_data/listitems.json
@@ -0,0 +1,6 @@
+{
+ "1": {},
+ "2": {},
+ "3": {},
+ "4": {}
+}
diff --git a/packages/development-edition-engine-handlebars/source/_meta/README.md b/packages/development-edition-engine-handlebars/source/_meta/README.md
new file mode 100644
index 000000000..c6c8c3b8e
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/_meta/README.md
@@ -0,0 +1,5 @@
+This is the default location to place meta files, otherwise known a pattern's header and footer.
+
+Pattern Lab builds each pattern while prepending and appending the header and footer. Read more about [pattern headers and footers](http://patternlab.io/docs/pattern-header-footer.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.meta` property within `patternlab-config.json`.
diff --git a/packages/development-edition-engine-handlebars/source/_meta/_00-head.hbs b/packages/development-edition-engine-handlebars/source/_meta/_00-head.hbs
new file mode 100644
index 000000000..9058b6521
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/_meta/_00-head.hbs
@@ -0,0 +1,19 @@
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
+
diff --git a/packages/development-edition-engine-handlebars/source/_meta/_00-head.mustache b/packages/development-edition-engine-handlebars/source/_meta/_00-head.mustache
new file mode 100644
index 000000000..45ce3bb7d
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/_meta/_00-head.mustache
@@ -0,0 +1,17 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
+
diff --git a/packages/development-edition-engine-handlebars/source/_meta/_01-foot.hbs b/packages/development-edition-engine-handlebars/source/_meta/_01-foot.hbs
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/_meta/_01-foot.hbs
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/development-edition-engine-handlebars/source/_meta/_01-foot.mustache b/packages/development-edition-engine-handlebars/source/_meta/_01-foot.mustache
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/_meta/_01-foot.mustache
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/development-edition-engine-handlebars/source/css/pattern-scaffolding.css b/packages/development-edition-engine-handlebars/source/css/pattern-scaffolding.css
new file mode 100644
index 000000000..f6c2da29d
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/css/pattern-scaffolding.css
@@ -0,0 +1,52 @@
+/**
+ * This stylesheet is for styles you want to include only when displaying demo
+ * styles for grids, animations, color swatches, etc.
+ * These styles will not be your production CSS.
+ */
+
+#sg-patterns {
+ -webkit-box-sizing: border-box !important;
+ box-sizing: border-box !important;
+ max-width: 100%;
+ padding: 0 0.5em;
+}
+
+.sg-colors {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ list-style: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+}
+.sg-colors li {
+ -webkit-box-flex: 1;
+ -ms-flex: auto;
+ flex: auto;
+ margin: 0 0 0.5em 0.5em;
+ width: 14em;
+}
+
+.sg-swatch {
+ display: flex;
+ flex-direction: column;
+ height: 6em;
+ margin-bottom: 0.3em;
+ padding: 0.5rem;
+}
+
+.sg-label {
+ font-size: 90%;
+ line-height: 1;
+ color: white;
+}
+
+.sg-label__inverted {
+ color: black;
+}
+
+.sg-label__top {
+ margin-bottom: auto;
+}
diff --git a/packages/development-edition-engine-handlebars/source/css/style.css b/packages/development-edition-engine-handlebars/source/css/style.css
new file mode 100644
index 000000000..7f25a7fe6
--- /dev/null
+++ b/packages/development-edition-engine-handlebars/source/css/style.css
@@ -0,0 +1,3 @@
+.annotation {
+ color: #b2b2b2;
+}
diff --git a/packages/development-edition-engine-handlebars/source/favicon.ico b/packages/development-edition-engine-handlebars/source/favicon.ico
new file mode 100644
index 000000000..eee4aa78f
Binary files /dev/null and b/packages/development-edition-engine-handlebars/source/favicon.ico differ
diff --git a/packages/development-edition-engine-handlebars/source/fonts/.gitkeep b/packages/development-edition-engine-handlebars/source/fonts/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/development-edition-engine-react/.editorconfig b/packages/development-edition-engine-react/.editorconfig
new file mode 100644
index 000000000..8951c3929
--- /dev/null
+++ b/packages/development-edition-engine-react/.editorconfig
@@ -0,0 +1,11 @@
+# editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+tab_width = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/packages/development-edition-engine-react/.gitignore b/packages/development-edition-engine-react/.gitignore
new file mode 100644
index 000000000..f5ffa0899
--- /dev/null
+++ b/packages/development-edition-engine-react/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.DS_Store
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+.idea/
+public
diff --git a/packages/development-edition-engine-react/.npmrc b/packages/development-edition-engine-react/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/development-edition-engine-react/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/development-edition-engine-react/.nvmrc b/packages/development-edition-engine-react/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/development-edition-engine-react/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/development-edition-engine-react/CHANGELOG.md b/packages/development-edition-engine-react/CHANGELOG.md
new file mode 100644
index 000000000..611fa23a3
--- /dev/null
+++ b/packages/development-edition-engine-react/CHANGELOG.md
@@ -0,0 +1,266 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/edition-node-gulp/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+## [5.9.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.9.1...v5.9.2) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+## [5.7.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.7.1...v5.7.2) (2020-03-24)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+## [5.7.1](https://github.com/pattern-lab/edition-node-gulp/compare/v5.7.0...v5.7.1) (2020-02-24)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+# [5.6.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.5.0...v5.6.0) (2020-01-18)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+
+# [5.5.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.4.2...v5.5.0) (2019-12-19)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+## [5.4.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.4.1...v5.4.2) (2019-11-27)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+## [5.4.1](https://github.com/pattern-lab/edition-node-gulp/compare/v5.4.0...v5.4.1) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+## [5.3.3](https://github.com/pattern-lab/edition-node-gulp/compare/v5.3.2...v5.3.3) (2019-11-22)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+
+## [5.3.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.3.1...v5.3.2) (2019-11-14)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+## [5.3.1](https://github.com/pattern-lab/edition-node-gulp/compare/v5.3.0...v5.3.1) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+# [5.2.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.1.0...v5.2.0) (2019-11-12)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+
+### Features
+
+* **core:** fix pattern export all conflicts ([b210d82](https://github.com/pattern-lab/edition-node-gulp/commit/b210d820ba8ac0b64c82c7ff0f18c9f8a900fce2))
+
+
+
+
+
+## [5.0.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.0.1...v5.0.2) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+## [5.0.1](https://github.com/pattern-lab/edition-node-gulp/compare/v5.0.0...v5.0.1) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/edition-node-gulp/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+
+## [0.1.8](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.7...@pattern-lab/engine-react-testing-tree@0.1.8) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+
+## [0.1.7](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.6...@pattern-lab/engine-react-testing-tree@0.1.7) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+
+## [0.1.3](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.2...@pattern-lab/engine-react-testing-tree@0.1.3) (2019-08-23)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+
+## [0.1.2](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.2-alpha.0...@pattern-lab/engine-react-testing-tree@0.1.2) (2019-05-16)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+## [0.1.1-beta.1](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.4...@pattern-lab/engine-react-testing-tree@0.1.1-beta.1) (2019-02-09)
+
+
+### Bug Fixes
+
+* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/edition-node-gulp/commit/98dfadf))
+
+
+
+
+
+
+## [0.1.1-beta.0](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.4...@pattern-lab/engine-react-testing-tree@0.1.1-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+
+
+
+
+## [0.1.1-alpha.4](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.3...@pattern-lab/engine-react-testing-tree@0.1.1-alpha.4) (2018-07-06)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+## [0.1.1-alpha.3](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.2...@pattern-lab/engine-react-testing-tree@0.1.1-alpha.3) (2018-07-06)
+
+### Bug Fixes
+
+* **package:** fix fat-fingered dependency ([e439f4e](https://github.com/pattern-lab/edition-node-gulp/commit/e439f4e))
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/edition-node-gulp/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/edition-node-gulp/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/edition-node-gulp/commit/5ab3995))
+
+
+
+## [0.1.1-alpha.2](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.1...@pattern-lab/engine-react-testing-tree@0.1.1-alpha.2) (2018-07-05)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+## [0.1.1-alpha.1](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.0...@pattern-lab/engine-react-testing-tree@0.1.1-alpha.1) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree
+
+
+
+## 0.1.1-alpha.0 (2018-05-04)
+
+### Features
+
+* **README:** clarify purpose of development edition ([d90df0e](https://github.com/pattern-lab/edition-node-gulp/commit/d90df0e))
diff --git a/packages/development-edition-engine-react/LICENSE b/packages/development-edition-engine-react/LICENSE
new file mode 100644
index 000000000..0e6363fb1
--- /dev/null
+++ b/packages/development-edition-engine-react/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Pattern Lab
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/development-edition-engine-react/README.md b/packages/development-edition-engine-react/README.md
new file mode 100644
index 000000000..bdb531d95
--- /dev/null
+++ b/packages/development-edition-engine-react/README.md
@@ -0,0 +1,14 @@
+
+
+# Pattern Lab Node - Development Edition Engine React
+
+_here be dragons_
+
+This Development Edition is a variant of [Edition Node Gulp](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp) for convience purposes only, loaded with the React Engine. The goals of this Development Edition are two-fold:
+
+* Develop the [React Engine](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react)
+* Build and test against React pattern tree
+
+If you'd like to help with the React Engine, please reference the [contribution guidelines](https://github.com/pattern-lab/patternlab-node/blob/master/.github/CONTRIBUTING.md).
+
+> Development Editions of Pattern Lab provide the ability to work on and commit changes to select packages within the overall Pattern Lab [ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). This Edition is NOT stable.
diff --git a/packages/development-edition-engine-react/gulpfile.js b/packages/development-edition-engine-react/gulpfile.js
new file mode 100644
index 000000000..8d8653659
--- /dev/null
+++ b/packages/development-edition-engine-react/gulpfile.js
@@ -0,0 +1,72 @@
+/******************************************************
+ * PATTERN LAB NODE
+ * EDITION-NODE-GULP
+ * The gulp wrapper around patternlab-node core, providing tasks to interact with the core library.
+ ******************************************************/
+const gulp = require('gulp');
+const argv = require('minimist')(process.argv.slice(2));
+
+/******************************************************
+ * PATTERN LAB NODE WRAPPER TASKS with core library
+ ******************************************************/
+const config = require('./patternlab-config.json');
+const patternlab = require('@pattern-lab/core')(config);
+
+function build() {
+ return patternlab
+ .build({
+ watch: argv.watch,
+ cleanPublic: config.cleanPublic,
+ })
+ .then(() => {
+ // do something else when this promise resolves
+ });
+}
+
+function serve() {
+ return patternlab
+ .serve({
+ cleanPublic: config.cleanPublic,
+ })
+ .then(() => {
+ // do something else when this promise resolves
+ });
+}
+
+gulp.task('patternlab:version', function() {
+ patternlab.version();
+});
+
+gulp.task('patternlab:help', function() {
+ patternlab.help();
+});
+
+gulp.task('patternlab:patternsonly', function() {
+ patternlab.patternsonly(config.cleanPublic);
+});
+
+gulp.task('patternlab:liststarterkits', function() {
+ patternlab.liststarterkits();
+});
+
+gulp.task('patternlab:loadstarterkit', function() {
+ patternlab.loadstarterkit(argv.kit, argv.clean);
+});
+
+gulp.task('patternlab:build', function() {
+ build().then(() => {
+ // do something else when this promise resolves
+ });
+});
+
+gulp.task('patternlab:serve', function() {
+ serve().then(() => {
+ // do something else when this promise resolves
+ });
+});
+
+gulp.task('patternlab:installplugin', function() {
+ patternlab.installplugin(argv.plugin);
+});
+
+gulp.task('default', ['patternlab:help']);
diff --git a/packages/development-edition-engine-react/package.json b/packages/development-edition-engine-react/package.json
new file mode 100644
index 000000000..e17c489fd
--- /dev/null
+++ b/packages/development-edition-engine-react/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@pattern-lab/engine-react-testing-tree",
+ "description": "The tree of components we use to test, develop and validate the React engine",
+ "version": "5.9.3",
+ "private": true,
+ "main": "gulpfile.js",
+ "dependencies": {
+ "@pattern-lab/core": "^5.9.3",
+ "@pattern-lab/engine-mustache": "^5.0.0",
+ "@pattern-lab/engine-react": "^5.0.0",
+ "@pattern-lab/uikit-workshop": "^5.9.3",
+ "gulp": "3.9.1",
+ "minimist": "^1.2.0",
+ "react": "16.2.0"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Gulp",
+ "Javascript"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/pattern-lab/edition-node-gulp.git"
+ },
+ "bugs": "https://github.com/pattern-lab/edition-node-gulp/issues",
+ "author": "Brian Muenzenmeyer",
+ "scripts": {
+ "gulp": "gulp -- "
+ },
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0"
+ }
+}
diff --git a/packages/development-edition-engine-react/patternlab-config.json b/packages/development-edition-engine-react/patternlab-config.json
new file mode 100644
index 000000000..23b459f2d
--- /dev/null
+++ b/packages/development-edition-engine-react/patternlab-config.json
@@ -0,0 +1,95 @@
+{
+ "cacheBust": true,
+ "cleanPublic" : true,
+ "defaultPattern": "all",
+ "defaultShowPatternInfo": false,
+ "ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "mqs": false,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+ },
+ "ishViewportRange": {
+ "s": [240, 500],
+ "m": [500, 800],
+ "l": [800, 2600]
+ },
+<<<<<<< HEAD:packages/development-edition-engine-react/patternlab-config.json
+ "logLevel": "info",
+=======
+ "patternExportAll": false,
+ "patternExportPreserveDirectoryStructure": false,
+ "patternExportRaw": false,
+ "patternExportPatternPartials": [],
+ "patternExportDirectory": "./pattern_exports/",
+ "cacheBust": true,
+>>>>>>> 436dd99e50d808cd14691593b927209f1ecb663e:patternlab-config.json
+ "outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+ },
+ "paths" : {
+ "source" : {
+ "root": "./source/",
+ "patterns" : "./source/_patterns/",
+ "data" : "./source/_data/",
+ "meta": "./source/_meta/",
+ "annotations" : "./source/_annotations/",
+ "styleguide": "./node_modules/@pattern-lab/uikit-workshop/dist/",
+ "patternlabFiles": {
+ "general-header":
+ "./node_modules/@pattern-lab/uikit-workshop/views/partials/general-header.mustache",
+ "general-footer":
+ "./node_modules/@pattern-lab/uikit-workshop/views/partials/general-footer.mustache",
+ "patternSection":
+ "./node_modules/@pattern-lab/uikit-workshop/views/partials/patternSection.mustache",
+ "patternSectionSubtype":
+ "./node_modules/@pattern-lab/uikit-workshop/views/partials/patternSectionSubtype.mustache",
+ "viewall":
+ "./node_modules/@pattern-lab/uikit-workshop/views/viewall.mustache"
+ },
+ "js" : "./source/js",
+ "images" : "./source/images",
+ "fonts" : "./source/fonts",
+ "css" : "./source/css/"
+ },
+ "public" : {
+ "root" : "./public/",
+ "patterns" : "./public/patterns/",
+ "data" : "./public/styleguide/data/",
+ "annotations" : "./public/annotations/",
+ "styleguide" : "./public/styleguide/",
+ "js" : "./public/js",
+ "images" : "./public/images",
+ "fonts" : "./public/fonts",
+ "css" : "./public/css"
+ }
+ },
+ "patternExtension": "mustache",
+ "patternStateCascade": ["inprogress", "inreview", "complete"],
+ "patternExportDirectory": "./pattern_exports/",
+ "patternExportPatternPartials": [],
+ "serverOptions": {
+ "wait": 1000
+ },
+ "starterkitSubDir": "dist",
+ "styleGuideExcludes": [
+ ],
+ "theme": {
+ "color": "dark",
+ "density": "compact",
+ "layout": "horizontal"
+ }
+}
diff --git a/packages/development-edition-engine-react/source/_annotations/README.md b/packages/development-edition-engine-react/source/_annotations/README.md
new file mode 100644
index 000000000..42592a09b
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_annotations/README.md
@@ -0,0 +1,5 @@
+This is the default location to place annotations.
+
+Pattern Lab uses annotations defined here to markup the UI. Read more about [annotations](http://patternlab.io/docs/pattern-adding-annotations.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.annotations` property within `patternlab-config.json`.
diff --git a/packages/development-edition-engine-react/source/_data/README.md b/packages/development-edition-engine-react/source/_data/README.md
new file mode 100644
index 000000000..3b9ea1ea4
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_data/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global data files.
+
+Pattern Lab uses data defined here as the global fallback if a template does not provide its own data. Read more about [data](http://patternlab.io/docs/data-overview.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.data` property within `patternlab-config.json`.
diff --git a/packages/development-edition-engine-react/source/_meta/README.md b/packages/development-edition-engine-react/source/_meta/README.md
new file mode 100644
index 000000000..c6c8c3b8e
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_meta/README.md
@@ -0,0 +1,5 @@
+This is the default location to place meta files, otherwise known a pattern's header and footer.
+
+Pattern Lab builds each pattern while prepending and appending the header and footer. Read more about [pattern headers and footers](http://patternlab.io/docs/pattern-header-footer.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.meta` property within `patternlab-config.json`.
diff --git a/packages/development-edition-engine-react/source/_meta/_00-head.hbs b/packages/development-edition-engine-react/source/_meta/_00-head.hbs
new file mode 100644
index 000000000..cf826617a
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_meta/_00-head.hbs
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
diff --git a/packages/development-edition-engine-react/source/_meta/_00-head.html b/packages/development-edition-engine-react/source/_meta/_00-head.html
new file mode 100644
index 000000000..cf826617a
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_meta/_00-head.html
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
diff --git a/packages/development-edition-engine-react/source/_meta/_00-head.mustache b/packages/development-edition-engine-react/source/_meta/_00-head.mustache
new file mode 100644
index 000000000..069727248
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_meta/_00-head.mustache
@@ -0,0 +1,17 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
+
diff --git a/packages/development-edition-engine-react/source/_meta/_01-foot.hbs b/packages/development-edition-engine-react/source/_meta/_01-foot.hbs
new file mode 100644
index 000000000..2feb91336
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_meta/_01-foot.hbs
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/development-edition-engine-react/source/_meta/_01-foot.html b/packages/development-edition-engine-react/source/_meta/_01-foot.html
new file mode 100644
index 000000000..2feb91336
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_meta/_01-foot.html
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/development-edition-engine-react/source/_meta/_01-foot.mustache b/packages/development-edition-engine-react/source/_meta/_01-foot.mustache
new file mode 100644
index 000000000..7c15d6a74
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_meta/_01-foot.mustache
@@ -0,0 +1,6 @@
+
+
+ {{{ patternLabFoot }}}
+
+
+
diff --git a/packages/development-edition-engine-react/source/_patterns/00-atoms/00-general/HelloWorld.jsx b/packages/development-edition-engine-react/source/_patterns/00-atoms/00-general/HelloWorld.jsx
new file mode 100644
index 000000000..9790e5af0
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_patterns/00-atoms/00-general/HelloWorld.jsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+const HelloWorld = () => Hello world!
;
+
+export default HelloWorld;
diff --git a/packages/development-edition-engine-react/source/_patterns/01-molecules/00-general/HelloIncluder.jsx b/packages/development-edition-engine-react/source/_patterns/01-molecules/00-general/HelloIncluder.jsx
new file mode 100644
index 000000000..6b2f3c985
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_patterns/01-molecules/00-general/HelloIncluder.jsx
@@ -0,0 +1,30 @@
+import React, { Component } from 'react';
+
+import HelloWorld from '../../00-atoms/00-general/HelloWorld';
+
+// const HelloWorld = () => (
+//
+// Hello world!
+//
+// );
+
+class HelloIncluder extends Component {
+ constructor() {
+ super();
+ this.state = {
+ bgColor: 'transparent',
+ };
+ setTimeout(() => this.setState({ bgColor: 'red' }), 2000);
+ }
+
+ render() {
+ return (
+
+
Hey! Here's the Hello World component:
+
+
+ );
+ }
+}
+
+export default HelloIncluder;
diff --git a/packages/development-edition-engine-react/source/_patterns/README.md b/packages/development-edition-engine-react/source/_patterns/README.md
new file mode 100644
index 000000000..2f89266bf
--- /dev/null
+++ b/packages/development-edition-engine-react/source/_patterns/README.md
@@ -0,0 +1,5 @@
+This is the default location to place pattern files.
+
+Pattern Lab builds patterns and the ui from the structure defined within. Read more about [pattern organization](http://patternlab.io/docs/pattern-organization.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.patterns` property within `patternlab-config.json`.
diff --git a/packages/development-edition-engine-react/source/css/README.md b/packages/development-edition-engine-react/source/css/README.md
new file mode 100644
index 000000000..a6ce7bf27
--- /dev/null
+++ b/packages/development-edition-engine-react/source/css/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global css files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.css` property within `patternlab-config.json`.
diff --git a/packages/development-edition-engine-react/source/css/pattern-scaffolding.css b/packages/development-edition-engine-react/source/css/pattern-scaffolding.css
new file mode 100644
index 000000000..b09172fc8
--- /dev/null
+++ b/packages/development-edition-engine-react/source/css/pattern-scaffolding.css
@@ -0,0 +1,54 @@
+/**
+ * This stylesheet is for styles you want to include only when displaying demo
+ * styles for grids, animations, color swatches, etc.
+ * These styles will not be your production CSS.
+ */
+#sg-patterns {
+ -webkit-box-sizing: border-box !important;
+ box-sizing: border-box !important;
+ max-width: 100%;
+ padding: 0 0.5em;
+}
+
+.demo-animate {
+ background: #ddd;
+ padding: 1em;
+ margin-bottom: 1em;
+ text-align: center;
+ border-radius: 8px;
+ cursor: pointer;
+}
+
+.sg-colors {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ list-style: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+}
+.sg-colors li {
+ -webkit-box-flex: 1;
+ -ms-flex: auto;
+ flex: auto;
+ padding: 0.3em;
+ margin: 0 0.5em 0.5em 0;
+ min-width: 5em;
+ max-width: 14em;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+}
+
+.sg-swatch {
+ display: block;
+ height: 4em;
+ margin-bottom: 0.3em;
+ border-radius: 5px;
+}
+
+.sg-label {
+ font-size: 90%;
+ line-height: 1;
+}
diff --git a/packages/development-edition-engine-react/source/css/style.css b/packages/development-edition-engine-react/source/css/style.css
new file mode 100644
index 000000000..04f745349
--- /dev/null
+++ b/packages/development-edition-engine-react/source/css/style.css
@@ -0,0 +1,3 @@
+/*
+ * YOUR STYLES HERE
+ */
diff --git a/packages/development-edition-engine-react/source/favicon.ico b/packages/development-edition-engine-react/source/favicon.ico
new file mode 100644
index 000000000..eee4aa78f
Binary files /dev/null and b/packages/development-edition-engine-react/source/favicon.ico differ
diff --git a/packages/development-edition-engine-react/source/fonts/README.md b/packages/development-edition-engine-react/source/fonts/README.md
new file mode 100644
index 000000000..ff4d4dee0
--- /dev/null
+++ b/packages/development-edition-engine-react/source/fonts/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global font files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.fonts` property within `patternlab-config.json`.
diff --git a/packages/development-edition-engine-react/source/images/README.md b/packages/development-edition-engine-react/source/images/README.md
new file mode 100644
index 000000000..86c91d567
--- /dev/null
+++ b/packages/development-edition-engine-react/source/images/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global image files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.images` property within `patternlab-config.json`.
diff --git a/packages/development-edition-engine-react/source/js/README.md b/packages/development-edition-engine-react/source/js/README.md
new file mode 100644
index 000000000..857893854
--- /dev/null
+++ b/packages/development-edition-engine-react/source/js/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global javascript files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.js` property within `patternlab-config.json`.
diff --git a/packages/development-edition-engine-twig/.gitignore b/packages/development-edition-engine-twig/.gitignore
new file mode 100644
index 000000000..0679bd2b5
--- /dev/null
+++ b/packages/development-edition-engine-twig/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.DS_Store
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+.idea/
+public
+dependencyGraph.json
diff --git a/packages/development-edition-engine-twig/CHANGELOG.md b/packages/development-edition-engine-twig/CHANGELOG.md
new file mode 100644
index 000000000..f1bda1063
--- /dev/null
+++ b/packages/development-edition-engine-twig/CHANGELOG.md
@@ -0,0 +1,40 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig
+
+
+
+
+
+## [5.9.2](https://github.com/pattern-lab/patternlab-node/compare/v5.9.1...v5.9.2) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig
+
+
+
+
+
+## [5.9.1](https://github.com/pattern-lab/patternlab-node/compare/v5.9.0...v5.9.1) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig
+
+
+
+
+
+# [5.8.0](https://github.com/pattern-lab/patternlab-node/compare/v5.7.2...v5.8.0) (2020-04-03)
+
+**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig
diff --git a/packages/development-edition-engine-twig/README.md b/packages/development-edition-engine-twig/README.md
new file mode 100644
index 000000000..5ea6dce7b
--- /dev/null
+++ b/packages/development-edition-engine-twig/README.md
@@ -0,0 +1,25 @@
+
+
+# Pattern Lab Node - Development Edition Engine Twig
+
+_here be dragons_
+
+This Development Edition is a variant of [Edition Node](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node) for convience purposes only, loaded with the Twig Engine. The goals of this Development Edition are two-fold:
+
+* Develop the [Twig Engine](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig)
+* Build and test against Twig pattern tree
+
+> Development Editions of Pattern Lab provide the ability to work on and commit changes to select packages within the overall Pattern Lab [ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). This Edition is NOT stable.
+
+
+## Working on Pattern Lab's UI Locally
+
+### Step 1: Install Dependencies
+Run the following in the root of the Pattern Lab repo:
+
+```
+yarn run setup
+```
+
+### Step 2
+Finally, go back into this folder, `cd packages/development-edition-engine-twig`, and start up the local dev server which watches UIKit and the local Pattern Lab instance for changes, live reloads, etc by running `yarn dev`
diff --git a/packages/development-edition-engine-twig/package.json b/packages/development-edition-engine-twig/package.json
new file mode 100644
index 000000000..949ac8f24
--- /dev/null
+++ b/packages/development-edition-engine-twig/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@pattern-lab/development-edition-engine-twig",
+ "private": true,
+ "version": "5.9.3",
+ "description": "The tree of components we use to test, develop and validate the twig engine (not engine-twig-php)",
+ "scripts": {
+ "postbootstrap": "patternlab install --starterkits @pattern-lab/starterkit-twig-demo",
+ "pl:build": "patternlab build --config ./patternlab-config.json",
+ "pl:help": "patternlab --help",
+ "pl:install": "patternlab install --config ./patternlab-config.json",
+ "pl:serve": "patternlab serve --config ./patternlab-config.json",
+ "pl:version": "patternlab --version",
+ "dev": "node ./node_modules/@pattern-lab/uikit-workshop/build-tools.js"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Twig",
+ "Edition"
+ ],
+ "author": "Ringo De Smet",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/pattern-lab/patternlab-node.git"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "dependencies": {
+ "@pattern-lab/cli": "^5.9.3",
+ "@pattern-lab/core": "^5.9.3",
+ "@pattern-lab/engine-twig": "^5.9.3",
+ "@pattern-lab/starterkit-twig-demo": "^5.8.0",
+ "@pattern-lab/uikit-workshop": "^5.9.3"
+ },
+ "workspaces": {
+ "nohoist": [
+ "**/@pattern-lab/starterkit-twig-demo"
+ ]
+ }
+}
diff --git a/packages/development-edition-engine-twig/patternlab-config.json b/packages/development-edition-engine-twig/patternlab-config.json
new file mode 100644
index 000000000..3c18bd403
--- /dev/null
+++ b/packages/development-edition-engine-twig/patternlab-config.json
@@ -0,0 +1,114 @@
+{
+ "cacheBust": true,
+ "cleanPublic": true,
+ "defaultPattern": "pages-homepage",
+ "defaultShowPatternInfo": false,
+ "ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "mqs": false,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+ },
+ "ishViewportRange": {
+ "s": [
+ 240,
+ 500
+ ],
+ "m": [
+ 500,
+ 800
+ ],
+ "l": [
+ 800,
+ 2600
+ ]
+ },
+ "logLevel": "info",
+ "outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+ },
+ "paths": {
+ "source": {
+ "root": "source/",
+ "patterns": "source/_patterns/",
+ "data": "source/_data/",
+ "meta": "source/_meta/",
+ "annotations": "source/_annotations/",
+ "styleguide": "dist/",
+ "patternlabFiles": {
+ "general-header": "views/partials/general-header.mustache",
+ "general-footer": "views/partials/general-footer.mustache",
+ "patternSection": "views/partials/patternSection.mustache",
+ "patternSectionSubtype": "views/partials/patternSectionSubtype.mustache",
+ "viewall": "views/viewall.mustache"
+ },
+ "js": "source/js",
+ "images": "source/images",
+ "fonts": "source/fonts",
+ "css": "source/css"
+ },
+ "public": {
+ "root": "public/",
+ "patterns": "public/patterns/",
+ "data": "public/styleguide/data/",
+ "annotations": "public/annotations/",
+ "styleguide": "public/styleguide/",
+ "js": "public/js",
+ "images": "public/images",
+ "fonts": "public/fonts",
+ "css": "public/css"
+ }
+ },
+ "patternExtension": "twig",
+ "patternStateCascade": [
+ "inprogress",
+ "inreview",
+ "complete"
+ ],
+ "patternExportDirectory": "pattern_exports",
+ "patternExportPatternPartials": [],
+ "serverOptions": {
+ "wait": 1000
+ },
+ "starterkitSubDir": "dist",
+ "styleGuideExcludes": [],
+ "theme": {
+ "color": "dark",
+ "density": "compact",
+ "layout": "horizontal"
+ },
+ "uikits": [
+ {
+ "name": "uikit-workshop",
+ "outputDir": "",
+ "enabled": true,
+ "excludedPatternStates": [],
+ "excludedTags": []
+ }
+ ],
+ "engines": {
+ "twig": {
+ "namespaces": {
+ "atoms": "source/_patterns/00-atoms/",
+ "molecules": "source/_patterns/01-molecules/",
+ "organisms": "source/_patterns/02-organisms/",
+ "templates": "source/_patterns/03-templates/",
+ "pages": "source/_patterns/04-pages/",
+ "macros": "source/_macros/"
+ }
+ }
+ }
+}
diff --git a/packages/development-edition-engine-twig/source/.gitignore b/packages/development-edition-engine-twig/source/.gitignore
new file mode 100644
index 000000000..d6b7ef32c
--- /dev/null
+++ b/packages/development-edition-engine-twig/source/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/packages/docs/.eleventy.js b/packages/docs/.eleventy.js
new file mode 100644
index 000000000..9e8b8feaa
--- /dev/null
+++ b/packages/docs/.eleventy.js
@@ -0,0 +1,96 @@
+const rssPlugin = require('@11ty/eleventy-plugin-rss');
+const syntaxHighlight = require('@11ty/eleventy-plugin-syntaxhighlight');
+const eleventyNavigationPlugin = require('@11ty/eleventy-navigation');
+const fs = require('fs');
+
+// Import filters
+const dateFilter = require('./src/filters/date-filter.js');
+const markdownFilter = require('./src/filters/markdown-filter.js');
+const w3DateFilter = require('./src/filters/w3-date-filter.js');
+
+// Import transforms
+const htmlMinTransform = require('./src/transforms/html-min-transform.js');
+const parseTransform = require('./src/transforms/parse-transform.js');
+
+// Import data files
+const site = require('./src/_data/site.json');
+
+module.exports = function(config) {
+ // Filters
+ config.addFilter('dateFilter', dateFilter);
+ config.addFilter('markdownFilter', markdownFilter);
+ config.addFilter('w3DateFilter', w3DateFilter);
+
+ // Layout aliases
+ config.addLayoutAlias('home', 'layouts/home.njk');
+
+ // Transforms
+ config.addTransform('htmlmin', htmlMinTransform);
+ config.addTransform('parse', parseTransform);
+
+ // Passthrough copy
+ config.addPassthroughCopy('src/images');
+ config.addPassthroughCopy('src/js');
+ config.addPassthroughCopy('src/admin/config.yml');
+ config.addPassthroughCopy('src/admin/previews.js');
+ config.addPassthroughCopy('node_modules/nunjucks/browser/nunjucks-slim.js');
+
+ const now = new Date();
+
+ // Custom collections
+ const livePosts = post => post.date <= now && !post.data.draft;
+ config.addCollection('posts', collection => {
+ return [
+ ...collection.getFilteredByGlob('./src/posts/*.md').filter(livePosts)
+ ].reverse();
+ });
+
+ config.addCollection('demos', collection => {
+ return [...collection.getFilteredByGlob('./src/demos/*.md')].reverse();
+ });
+
+ config.addCollection('postFeed', collection => {
+ return [...collection.getFilteredByGlob('./src/posts/*.md').filter(livePosts)]
+ .reverse()
+ .slice(0, site.maxPostsPerPage);
+ });
+
+ config.addCollection('docs', collection => {
+ return [...collection.getFilteredByGlob('./src/docs/*.md')].reverse();
+ });
+
+ config.addCollection('docsOrdered', collection => {
+ const docs = collection.getFilteredByGlob('src/docs/*.md').sort((a, b) => {
+ return Number(a.data.order) - Number(b.data.order);
+ });
+ return docs;
+ });
+
+ // Plugins
+ config.addPlugin(rssPlugin);
+ config.addPlugin(syntaxHighlight);
+ config.addPlugin(eleventyNavigationPlugin);
+
+ // 404
+ config.setBrowserSyncConfig({
+ callbacks: {
+ ready: function(err, browserSync) {
+ const content_404 = fs.readFileSync('dist/404.html');
+
+ browserSync.addMiddleware('*', (req, res) => {
+ // Provides the 404 content without redirect.
+ res.write(content_404);
+ res.end();
+ });
+ }
+ }
+ });
+
+ return {
+ dir: {
+ input: 'src',
+ output: 'dist'
+ },
+ passthroughFileCopy: true
+ };
+};
diff --git a/packages/docs/.gitignore b/packages/docs/.gitignore
new file mode 100644
index 000000000..a54bbf4b4
--- /dev/null
+++ b/packages/docs/.gitignore
@@ -0,0 +1,17 @@
+*.log
+npm-debug.*
+*.scssc
+*.log
+*.swp
+.DS_Store
+.sass-cache
+node_modules
+dist
+
+# Specifics
+
+# Hide design tokens
+src/scss/_tokens.scss
+
+# Hide compiled CSS
+src/_includes/assets/*
diff --git a/packages/docs/.prettierrc b/packages/docs/.prettierrc
new file mode 100644
index 000000000..a20fac227
--- /dev/null
+++ b/packages/docs/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "printWidth": 90,
+ "useTabs": true,
+ "tabWidth": 2,
+ "singleQuote": true,
+ "bracketSpacing": false
+}
diff --git a/packages/docs/.vscode/launch.json b/packages/docs/.vscode/launch.json
new file mode 100644
index 000000000..2359da6e9
--- /dev/null
+++ b/packages/docs/.vscode/launch.json
@@ -0,0 +1,17 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Launch Program",
+ "skipFiles": [
+ "/**"
+ ],
+ "program": "${workspaceFolder}/index.js"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/docs/CHANGELOG.md b/packages/docs/CHANGELOG.md
new file mode 100644
index 000000000..3bf4e3f5c
--- /dev/null
+++ b/packages/docs/CHANGELOG.md
@@ -0,0 +1,19 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/bradfrost/pl-website-eleventy/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package patternlab-website
+
+
+
+
+
+# [5.9.0](https://github.com/bradfrost/pl-website-eleventy/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+
+### Features
+
+* **docs:** yarnify ([5a47dc7](https://github.com/bradfrost/pl-website-eleventy/commit/5a47dc7b90dc5c43c12a51143b41943dcbd8564c))
diff --git a/packages/docs/LICENSE.txt b/packages/docs/LICENSE.txt
new file mode 100644
index 000000000..312f7109c
--- /dev/null
+++ b/packages/docs/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 andy-bell.design and other contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/docs/README.md b/packages/docs/README.md
new file mode 100644
index 000000000..7b191158a
--- /dev/null
+++ b/packages/docs/README.md
@@ -0,0 +1,32 @@
+# Pattern Lab Website
+
+This is the website for patternlab.io. This site was build using the [Hylia starter kit](https://hylia.website/), which is a lightweight [Eleventy](https://11ty.io) starter kit.
+
+---
+
+## How to work with this project
+
+1. Clone this repository
+2. `cd` into the project directory and run `yarn`
+3. Once all the dependencies are installed run `yarn start`
+4. Open your browser at `http://localhost:8080`
+
+## Terminal commands
+
+### Serve the site locally
+
+```bash
+yarn start
+```
+
+### Build a production version of the site
+
+```bash
+yarn production
+```
+
+### Compile Sass
+
+```bash
+yarn sass:process
+```
diff --git a/packages/docs/package.json b/packages/docs/package.json
new file mode 100755
index 000000000..801697fa9
--- /dev/null
+++ b/packages/docs/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "patternlab-website",
+ "version": "5.9.3",
+ "description": "The website for patternlab.io",
+ "main": "index.js",
+ "dependencies": {
+ "@11ty/eleventy": "^0.8.3",
+ "@11ty/eleventy-plugin-rss": "^1.0.6",
+ "@11ty/eleventy-plugin-syntaxhighlight": "^2.0.3",
+ "@tbranyen/jsdom": "^13.0.0",
+ "concurrently": "^4.1.0",
+ "html-minifier": "^4.0.0",
+ "json-to-scss": "^1.3.1",
+ "sass": "^1.21.0",
+ "semver": "^6.3.0",
+ "slugify": "^1.3.4",
+ "stalfos": "github:hankchizljaw/stalfos#c8971d22726326cfc04089b2da4d51eeb1ebb0eb"
+ },
+ "devDependencies": {
+ "@11ty/eleventy-navigation": "^0.1.5",
+ "@erquhart/rollup-plugin-node-builtins": "^2.1.5",
+ "bl": "^3.0.0",
+ "chokidar-cli": "^2.0.0",
+ "cross-env": "^5.2.0",
+ "make-dir-cli": "^2.0.0",
+ "prettier": "^1.18.2",
+ "rollup": "^1.16.1",
+ "rollup-plugin-commonjs": "^10.0.0",
+ "rollup-plugin-json": "^4.0.0",
+ "rollup-plugin-node-resolve": "^5.0.3"
+ },
+ "scripts": {
+ "sass:tokens": "npx json-to-scss src/_data/tokens.json src/scss/_tokens.scss",
+ "sass:process": "yarn sass:tokens && sass src/scss/style.scss dist/css/style.css --style=compressed",
+ "cms:precompile": "make-dir dist/admin && nunjucks-precompile src/_includes > dist/admin/templates.js -i \"\\.(njk|css|svg)$\"",
+ "cms:bundle": "rollup --config",
+ "start": "concurrently \"yarn sass:process -- --watch\" \"yarn cms:bundle -- --watch\" \"chokidar \\\"src/_includes/**\\\" -c \\\"yarn cms:precompile\\\"\" \"yarn serve\"",
+ "serve": "cross-env ELEVENTY_ENV=development npx eleventy --serve",
+ "production": "yarn sass:process && yarn cms:precompile && yarn cms:bundle && npx eleventy"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/bradfrost/pl-website-eleventy.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/bradfrost/pl-website-eleventy/issues"
+ },
+ "homepage": "https://github.com/bradfrost/pl-website-eleventy/#readme"
+}
diff --git a/packages/docs/php-docs/advanced-auto-regenerate.md b/packages/docs/php-docs/advanced-auto-regenerate.md
new file mode 100644
index 000000000..fc6985825
--- /dev/null
+++ b/packages/docs/php-docs/advanced-auto-regenerate.md
@@ -0,0 +1,55 @@
+---
+title: Watching for Changes and Auto Regenerating Patterns
+tags:
+ - docs
+---
+
+Pattern Lab can watch for changes to files in `./source/` and automatically rebuild the entire Pattern Lab website for you. Make your changes, save the file, and Pattern Lab takes care of the rest.
+
+## How to Start Watching for Changes
+
+To start watching for changes do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `php core/console --watch`
+
+To stop watching files use `CTRL+C` in the same terminal window.
+
+### Only Watch for Changes to Pattern Lab Files
+
+If you use a task runner like Gulp or Grunt to compile Sass, JavaScript or images you may want Pattern Lab to only concern itself with its own files. To limit Pattern Lab to watch and move only its files do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `php core/console --watch --patternsonly`
+
+Or, better yet, use this command within your Gulp or Grunt script.
+
+### Start the Web Server & Watch for Changes at the Same Time
+
+If you're relying on Pattern Lab's server to view your content you'll want to run the watch and server with the same command. Do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `php core/console --server --with-watch`
+
+You can also start the server and watch only patterns:
+
+1. In a terminal window navigate to the root of your project
+2. Type `php core/console --server --with-watch --patternsonly`
+
+To stop the server and watching files use `CTRL+C` in the same terminal window.
+
+### Start the Web Server, Watch for Changes, and Reload the Browser at the Same Time
+
+The ultimate solution for working with Pattern Lab if you're not using a task runner is Pattern Lab's [Auto-Reload Plugin](https://github.com/pattern-lab/plugin-php-reload). Do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Install the [Auto-Reload Plugin](https://github.com/pattern-lab/plugin-php-reload) using `composer require pattern-lab/plugin-reload`
+3. Type `php core/console --server --with-watch`
+
+The Auto-Reload Plugin is automatically enabled when you install it. You can always [disable the plugin](https://github.com/pattern-lab/plugin-php-reload#disabling-the-plugin) if you need to.
+
+To stop the server, watching files, and auto-reload service use `CTRL+C` in the same terminal window.
+
+## What Pattern Lab Will Watch
+
+By default, the PHP version of Pattern Lab will watch all files in `./source` except those that match the "ignore" configuration options in `config/config.yml`. When using `--patternsonly` Pattern Lab will only watch those directories in `./source` that start with an underscore. For example, `_patterns`. To learn how to modify what is ignored check out "[Managing Assets for a Pattern](/docs/pattern-managing-assets.html)".
diff --git a/packages/docs/php-docs/advanced-clean-public.md b/packages/docs/php-docs/advanced-clean-public.md
new file mode 100644
index 000000000..408e3ed37
--- /dev/null
+++ b/packages/docs/php-docs/advanced-clean-public.md
@@ -0,0 +1,5 @@
+---
+title: Stopping public/ from Being "Cleaned"
+tags:
+ - docs
+---
diff --git a/packages/docs/php-docs/advanced-config-options.md b/packages/docs/php-docs/advanced-config-options.md
new file mode 100644
index 000000000..e21791b71
--- /dev/null
+++ b/packages/docs/php-docs/advanced-config-options.md
@@ -0,0 +1,7 @@
+---
+title: Editing the Configuration Options
+tags:
+ - docs
+---
+
+Pattern Lab comes with a simple configuration file that allows you to modify certain aspects of the system. The configuration file can be found in `./config/config.yml`.
diff --git a/packages/docs/php-docs/advanced-exporting-patterns.md b/packages/docs/php-docs/advanced-exporting-patterns.md
new file mode 100644
index 000000000..49f4857d1
--- /dev/null
+++ b/packages/docs/php-docs/advanced-exporting-patterns.md
@@ -0,0 +1,17 @@
+---
+title: Exporting Patterns
+tags:
+ - docs
+---
+
+Pattern Lab can export all of your patterns for you sans Pattern Lab's CSS and JavaScript. To export your patterns do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `php core/console --export`
+
+If you require your patterns to be exported without your global header and footer (_e.g. to export a clean molecule_) do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `php core/console --export --clean`
+
+In both cases the patterns will be exported to `./export/patterns`. The export directory is one of the many directories that can be [configured and changed](/docs/editing-source-files.html).
diff --git a/packages/docs/php-docs/advanced-page-follow.md b/packages/docs/php-docs/advanced-page-follow.md
new file mode 100644
index 000000000..3e38bebfc
--- /dev/null
+++ b/packages/docs/php-docs/advanced-page-follow.md
@@ -0,0 +1,7 @@
+---
+title: Multi browser & Multi device Testing with Page Follow
+tags:
+ - docs
+---
+
+An auto-reload service was built into Pattern Lab 1. With Pattern Lab 2 this feature has been removed. This feature may return as a plugin in the same way that the [Auto-Reload service](/docs/advanced-reload-browser.html) did.
diff --git a/packages/docs/php-docs/advanced-pattern-lab-nav.md b/packages/docs/php-docs/advanced-pattern-lab-nav.md
new file mode 100644
index 000000000..2bd2c28c5
--- /dev/null
+++ b/packages/docs/php-docs/advanced-pattern-lab-nav.md
@@ -0,0 +1,29 @@
+---
+title: Modifying Pattern Lab's Navigation
+tags:
+ - docs
+---
+
+When sharing Pattern Lab with a client it may be beneficial to turn-off certain elements in the default navigation. To turn-off navigation elements do the following:
+
+1. Open `./config/config.yml`
+2. Add the keys for the elements you'd like to hide to the `ishControlsHide` configuration option
+3. Re-generate your Pattern Lab site
+
+The following keys are supported and will hide their respective elements:
+
+```
+s
+m
+l
+full
+random
+disco
+hay
+find
+views-new
+tools-all
+tools-docs
+```
+
+`hay` is disabled by default.
diff --git a/packages/docs/php-docs/advanced-reload-browser.md b/packages/docs/php-docs/advanced-reload-browser.md
new file mode 100644
index 000000000..4da045d5f
--- /dev/null
+++ b/packages/docs/php-docs/advanced-reload-browser.md
@@ -0,0 +1,14 @@
+---
+title: Auto Reloading the Browser Window When Changes Are Made
+tags:
+ - docs
+---
+
+An auto-reload service was built into Pattern Lab 1. With Pattern Lab 2 this feature has been turned into the [Auto-Reload Plugin](https://github.com/pattern-lab/plugin-php-reload). To install this plugin do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `composer require pattern-lab/plugin-reload`
+
+The Auto-Reload Plugin is automatically enabled when you install it. You can always [disable the plugin](https://github.com/pattern-lab/plugin-php-reload#disabling-the-plugin) if you need to.
+
+This service is enabled when using the `--watch` or `--server --with-watch` commands. Learn more about [watching for changes](/docs/advanced-auto-regenerate.html).
diff --git a/packages/docs/php-docs/advanced-starterkits.md b/packages/docs/php-docs/advanced-starterkits.md
new file mode 100644
index 000000000..5a90e0bdf
--- /dev/null
+++ b/packages/docs/php-docs/advanced-starterkits.md
@@ -0,0 +1,21 @@
+---
+title: Starterkits
+tags:
+ - docs
+---
+
+StarterKits can be installed via the following commands:
+
+```
+php core/console --starterkit --install [starterkit-name]
+```
+
+where [starterkit-name] is the name of the Starterkit.
+
+so... a complete example:
+
+```
+php core/console --starterkit --install pattern-lab/starterkit-mustache-demo
+```
+
+It is recommended that you do not install this StarterKit as a dependency for your Pattern Lab project via Composer.
diff --git a/packages/docs/php-docs/changes-1-to-2.md b/packages/docs/php-docs/changes-1-to-2.md
new file mode 100644
index 000000000..38ec38bee
--- /dev/null
+++ b/packages/docs/php-docs/changes-1-to-2.md
@@ -0,0 +1,46 @@
+---
+title: Pattern Lab 1 to Pattern Lab 2 Changes
+tags:
+ - docs
+---
+
+With Pattern Lab 2 in development for almost two years many under-the-hood changes have been implemented. For the most part a Pattern Lab 1 project should work with minimal changes in Pattern Lab 2. Here is a non-exhaustive list of new features in Pattern Lab 2:
+
+* complete rebuilding of core
+* support for Composer
+* support for more template languages (_currently Mustache and Twig_)
+* support for StarterKits allowing separation of a team's unique needs from Pattern Lab proper
+* event notification system, getters, setters, and a clean install process to allow for plugins
+* redesigned and rebuilt modal view
+* redesigned and rebuilt styleguide view
+* multi-source directory support
+* support for YAML in global data, pattern-specific data, and pseudo-patterns
+* can have multiple JSON/YAML files in `./_data/`
+* support for JSON/YAML linting to find errors
+* can set multiple classes using the style modifier
+* can use link.[pattern-name] within data to link to other patterns
+* pattern parameters support simple lists
+* pattern parameters act more like mustache (_but not exactly!_)
+* patternParameters can over listItem loop numbers
+* global pattern header and footer is now in `./source/_meta`
+* upgraded console utility
+* patterns and pattern subtypes can be documented in the styleguide by using `[pattern-name].md` or `[pattern-subtype].md`
+* view all pages for pattern sub-types
+* annotations can be defined using Markdown
+* patterns can be exported minus Pattern Lab mark-up
+* can hide individual patterns from "view all" view still available via the nav
+* can set a pattern to be the default pattern when loading Pattern Lab
+* can turn on modal view by default
+* implemented server
+* sayings can now be defined in the config
+* install process that makes it easier to install various components
+
+These are the features of Pattern Lab 1 that have become plugins:
+
+* Automatic Browser Reload
+
+These are the features of Pattern Lab 1 that have been removed in Pattern Lab 2:
+
+* QR Code Generator
+* Page Follow
+* MQs
diff --git a/packages/docs/php-docs/command-line.md b/packages/docs/php-docs/command-line.md
new file mode 100644
index 000000000..264d235c3
--- /dev/null
+++ b/packages/docs/php-docs/command-line.md
@@ -0,0 +1,22 @@
+---
+title: Using The Command Line Options
+tags:
+ - docs
+---
+
+To use Pattern Lab you must use the command line interface. To view the available commands when using Pattern Lab do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `php core/console --help`
+
+To get the options for a particular command, for example the `--generate` command, you can type:
+
+ php core/console --help --generate
+
+## A Special Note About Windows
+
+To access the command prompt on Windows you can [follow the directions from Microsoft](http://windows.microsoft.com/en-us/windows-vista/open-a-command-prompt-window). After getting to the command prompt type the following to make sure you have PHP installed:
+
+ php -v
+
+If you get an error and know that you've installed PHP you may need to [update your path variable so Windows can find PHP](http://willj.co/2012/10/run-wamp-php-windows-7-command-line/).
diff --git a/packages/docs/php-docs/generating-pattern-lab.md b/packages/docs/php-docs/generating-pattern-lab.md
new file mode 100644
index 000000000..b9831d371
--- /dev/null
+++ b/packages/docs/php-docs/generating-pattern-lab.md
@@ -0,0 +1,14 @@
+---
+title: Generating Pattern Lab
+tags:
+ - docs
+---
+
+Pattern Lab consists of an empty shell when you first install it. To populate the public-facing side of Pattern Lab with your content and patterns do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `php core/console --generate`
+
+Your Pattern Lab install should now be populated and [available for viewing](/docs/viewing-patterns.html). As you [make changes to your patterns](/docs/editing-source-files.html) you'll need re-generate your site using step 2 above.
+
+Manually re-generating your site after each change or collection of changes can be cumbersome. Pattern Lab can [watch files in the `./source/` directory for changes and re-generate the site automatically](/docs/advanced-auto-regenerate.html). The Pattern Lab website can also be [automatically reloaded](/docs/advanced-reload-browser.html).
diff --git a/packages/docs/php-docs/installation.md b/packages/docs/php-docs/installation.md
new file mode 100644
index 000000000..23b3ec3c4
--- /dev/null
+++ b/packages/docs/php-docs/installation.md
@@ -0,0 +1,5 @@
+---
+title: Installing Pattern Lab
+tags:
+ - docs
+---
diff --git a/packages/docs/php-docs/pattern-managing-assets.md b/packages/docs/php-docs/pattern-managing-assets.md
new file mode 100644
index 000000000..1f6927cde
--- /dev/null
+++ b/packages/docs/php-docs/pattern-managing-assets.md
@@ -0,0 +1,41 @@
+---
+title: Managing Pattern Assets
+tags:
+ - docs
+---
+
+Assets for patterns - including JavaScript, CSS, and images - should be stored and edited in the `./source/` directory. Pattern Lab will move these assets to the `./public/` directory for you when you generate your site or when you watch the `./source/` directory for changes. **You can name and organize your assets however you like.** If you would like to use `./source/stylesheets/` to store your styles instead of `./source/css/` you can do that. There is nothing to configure. The structure will be maintained when they're moved to the `./public/` directory.
+
+## Ignoring and Not Moving Assets Based on File Extension
+
+By default, Pattern Lab will not move assets with the following file extensions:
+
+- `.less`
+- `.scss`
+- `.DS_Store`
+
+To ignore more file extensions edit the `ie` configuration option in `./config/config.yml`. For example, to ignore `*.png` files your `ie` configuration option would look like:
+
+ ie:
+ - DS_Store
+ - less
+ - scss
+ - png
+
+## Ignoring and Not Moving Assets Based on Directory
+
+By default, the PHP version of Pattern Lab will ignore **all** assets in directories that exactly match:
+
+- `scss`
+
+To ignore more directories just edit the `id` configuration option in `./config/config.yml`. For example, to ignore directories named `test/` your `id` configuration option would look like:
+
+ id:
+ - scss
+ - test
+
+**Important:** Pattern Lab will only ignore exact matches of ignored directories. For example, if you had a directory named `cool_scss/` it, and the assets underneath it, _would_ be moved to `./public/` even though `scss` was in the name of the directory.
+
+## Adding Assets to the Pattern Header & Footer
+
+Static assets like Javascript and CSS **are not** added automagically to your patterns. You need to add them manually to the [shared pattern header and footer](/docs/pattern-header-footer.html).
diff --git a/packages/docs/php-docs/pattern-states.md b/packages/docs/php-docs/pattern-states.md
new file mode 100644
index 000000000..cebea9de9
--- /dev/null
+++ b/packages/docs/php-docs/pattern-states.md
@@ -0,0 +1,45 @@
+---
+title: Using Pattern States
+tags:
+ - docs
+---
+
+Pattern states provide your team and client a simple visual of the current state of patterns in Pattern Lab. Pattern states can track progress of a pattern from development, through client review, to completion or they can be used to give certain patterns specific classes. It's important to note that the state of a pattern can be influenced by its pattern partials.
+
+## The Default Pattern States
+
+Pattern Lab comes with the following default pattern states:
+
+- **inprogress**: pattern is in development or being worked upon. a red dot.
+- **inreview**: pattern is ready for a client to look at and comment upon. a yellow dot.
+- **complete**: pattern is ready to be moved to production. a green dot.
+
+Any pattern that includes a pattern partial that has a lower pattern state will inherit that state. For example, a pattern with the state of `inreview` that includes a pattern partial with the state of `inprogress` will have its state overridden and set to `inprogress`. It will not change to `inreview` until the pattern partial has a state of `inreview` or `complete`.
+
+## Giving Patterns a State
+
+Giving patterns a state is simply a matter of modifying the file name. If we wanted to give our `molecules-media-block` pattern a state of `inprogress` we'd change the file name from:
+
+```
+./source/_patterns/01-molecules/02-blocks/00-media-block.mustache
+```
+
+to:
+
+```
+./source/_patterns/01-molecules/02-blocks/00-media-block@inprogress.mustache
+```
+
+## Adding Customized States
+
+The three default states included with Pattern Lab might not be enough for everyone. To add customized states you should modify your own CSS files. **DO NOT** modify `states.css` in `public/styleguide/css/`. This is because `states.css` will be overwritten in future upgrades.
+
+You can use the following as your CSS template for new pattern states:
+
+```css
+{% raw %}.newpatternstate:before {
+ color: #B10DC9 !important;
+}{% endraw %}
+```
+
+Then add `@newpatternstate` to your patterns to have the new look show up. If you want to add it to the cascade of the default patterns you can modify `./config/config.yml`. Simply add your new pattern state to the `patternStates` list.
diff --git a/packages/docs/php-docs/requirements.md b/packages/docs/php-docs/requirements.md
new file mode 100644
index 000000000..7a741bb60
--- /dev/null
+++ b/packages/docs/php-docs/requirements.md
@@ -0,0 +1,22 @@
+---
+title: Requirements
+tags:
+ - docs
+---
+
+
+
+The requirements for Pattern Lab 2 vary depending on what features you want to use.
+
+## Minimum Requirements
+
+To use the basic features of Pattern Lab to compile patterns, you must have **PHP 5.4+** installed. On Mac OS X Pattern Lab should work "out of the box." If you're on Windows you can [download PHP from PHP.net](http://windows.php.net/download/). Pattern Lab comes with its own built-in web server.
+
+Because Pattern Lab's output consists of HTML, CSS, and JavaScript there are **no requirements** for hosting your Pattern Lab site. Simply upload the `./public/` directory to your host and you should be good to go.
+
+## Highly Recommended: Composer
+
+Pattern Lab uses [Composer](https://getcomposer.org/) to manage project dependencies. While Pattern Lab can be downloaded as a Zip we highly recommend installing Composer so you can easily update your project in the future. Please follow the directions for [installing Composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx) on the Composer website. We recommend you [install it globally](https://getcomposer.org/doc/00-intro.md#globally).
+
+
+
diff --git a/packages/docs/php-docs/upgrading.md b/packages/docs/php-docs/upgrading.md
new file mode 100644
index 000000000..8daf7ab1d
--- /dev/null
+++ b/packages/docs/php-docs/upgrading.md
@@ -0,0 +1,52 @@
+---
+title: Upgrading Pattern Lab
+tags:
+ - docs
+---
+
+
+
+Pattern Lab 2 uses [Composer](https://getcomposer.org) to manage project dependencies. To upgrade an edition based on Pattern Lab 2 do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `composer update`
+
+During the upgrade process Pattern Lab 2 will move or add any files that are required for the new version to work. It will also update your configuration as appropriate. If you don't have Composer installed please [follow the directions for installing Composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx) that are available on the Composer website. We recommend you [install it globally](https://getcomposer.org/doc/00-intro.md#globally).
+
+## Upgrading Pattern Lab 1 to Pattern Lab 2
+
+Pattern Lab 2 was a complete rewrite and reorganization of Pattern Lab 1. [Learn about the changes](/docs/changes-1-to-2.html). To upgrade do the following:
+
+1. [Download](http://patternlab.io/download.html) the PHP edition that matches your needs
+
+If you chose a Mustache-based edition do the following:
+
+1. Copy `./source` from your old project to your new edition
+2. Copy `./source/_patterns/00-atoms/00-meta/_00-head.mustache` to `./source/_meta/_00-head.mustache`
+3. Copy `./source/_patterns/00-atoms/00-meta/_01-foot.mustache` to `./source/_meta/_00-foot.mustache` (you can then delete `source/_patterns/00-atoms/00-meta/` directory)
+4. In `./source/_meta/_00-head.mustache`, replace `{% raw %}{% pattern-lab-head %}{% endraw %}` with `{% raw %}{{{ patternLabHead }}}{% endraw %}`
+5. In `./source/_meta/_00-foot.mustache` replace `{% raw %}{% pattern-lab-foot %}{% endraw %}` with `{% raw %}{{{ patternLabFoot }}}{% endraw %}`
+6. Copy `./source/_data/annotations.js` to `./source/_annotations/annotations.js`
+7. Remove the underscore in front of the JSON files in `source/data` (i.e. `data.json` not `_data.json`).
+
+
+
+If you chose another version do the above and convert the templates as appropriate.
+
+## Learning About Upgrades
+
+New releases and upgrades are announced in Pattern Lab's [PHP room on Gitter](https://gitter.im/pattern-lab/php) and on Twitter at [@patternlabio](https://twitter.com/patternlabio).
+
+You can also determine if your version of Pattern Lab 2 can be upgraded yourself by doing the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `composer outdated`
+
+Two components of Pattern Lab 2 maintain CHANGELOGs as part of their "Releases" page on GitHub:
+
+* [pattern-lab/core](https://github.com/pattern-lab/patternlab-php-core/releases)
+* [pattern-lab/styleguidekit-assets-default](https://github.com/pattern-lab/styleguidekit-assets-default/releases)
+
+
+
+
diff --git a/packages/docs/php-docs/viewing-patterns.html b/packages/docs/php-docs/viewing-patterns.html
new file mode 100644
index 000000000..ebc7461d4
--- /dev/null
+++ b/packages/docs/php-docs/viewing-patterns.html
@@ -0,0 +1,17 @@
+---
+title: Viewing Patterns
+tags:
+ - docs
+---
+
+
+
+Pattern Lab utilizes PHP's [built-in web server](http://php.net/manual/en/features.commandline.webserver.php) to let you browse your generated patterns. To start the server do the following:
+
+1. In a terminal window navigate to the root of your project
+2. Type `php core/console --server`
+
+Your local Pattern Lab install should now be available for browsing at [http://localhost:8080](http://localhost:8080).
+
+
+
diff --git a/packages/docs/rollup.config.js b/packages/docs/rollup.config.js
new file mode 100644
index 000000000..7f62e07f4
--- /dev/null
+++ b/packages/docs/rollup.config.js
@@ -0,0 +1,19 @@
+const builtins = require('@erquhart/rollup-plugin-node-builtins');
+const commonjs = require('rollup-plugin-commonjs');
+const nodeResolve = require('rollup-plugin-node-resolve');
+const json = require('rollup-plugin-json');
+
+export default {
+ input: 'src/admin/util',
+ output: {
+ file: 'dist/admin/util.js',
+ format: 'iife',
+ name: 'previewUtil',
+ },
+ plugins: [
+ builtins(),
+ nodeResolve(),
+ commonjs(),
+ json(),
+ ]
+};
diff --git a/packages/docs/src/404.md b/packages/docs/src/404.md
new file mode 100644
index 000000000..eccd5a45c
--- /dev/null
+++ b/packages/docs/src/404.md
@@ -0,0 +1,17 @@
+---
+title: '404 - not found'
+layout: layouts/page.njk
+permalink: 404.html
+eleventyExcludeFromCollections: true
+---
+
+We’re sorry, but that content can’t be found. Please go [back to home](/).
+
+{% comment %}
+Read more: https://www.11ty.io/docs/quicktips/not-found/
+
+This will work for both GitHub pages and Netlify:
+
+- https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/
+- https://www.netlify.com/docs/redirects/#custom-404
+ {% endcomment %}
diff --git a/packages/docs/src/_data/global.js b/packages/docs/src/_data/global.js
new file mode 100644
index 000000000..683a0a332
--- /dev/null
+++ b/packages/docs/src/_data/global.js
@@ -0,0 +1,9 @@
+module.exports = {
+ random() {
+ const segment = () => {
+ return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
+ };
+ return `${segment()}-${segment()}-${segment()}`;
+ },
+ now: Date.now()
+};
diff --git a/packages/docs/src/_data/helpers.js b/packages/docs/src/_data/helpers.js
new file mode 100644
index 000000000..9ae9b7883
--- /dev/null
+++ b/packages/docs/src/_data/helpers.js
@@ -0,0 +1,10 @@
+module.exports = {
+ getNextHeadingLevel(currentLevel) {
+ return parseInt(currentLevel, 10) + 1;
+ },
+ getReadingTime(text) {
+ const wordsPerMinute = 200;
+ const numberOfWords = text.split(/\s/g).length;
+ return Math.ceil(numberOfWords / wordsPerMinute);
+ }
+};
diff --git a/packages/docs/src/_data/navigation.json b/packages/docs/src/_data/navigation.json
new file mode 100644
index 000000000..8838d1f0b
--- /dev/null
+++ b/packages/docs/src/_data/navigation.json
@@ -0,0 +1,66 @@
+{
+ "items": [
+ {
+ "label": "Getting Started",
+ "url": "/docs/installing-pattern-lab/"
+ },
+ {
+ "label": "Documentation",
+ "url": "/docs/",
+ "subnavIsClosed": true,
+ "subnav": [
+ {
+ "label": "Getting Started",
+ "category": "getting-started"
+ },
+ {
+ "label": "Working with Patterns",
+ "category": "patterns"
+ },
+ {
+ "label": "Working with Data",
+ "category": "data"
+ },
+ {
+ "label": "Advanced",
+ "category": "advanced"
+ }
+ ]
+ },
+ {
+ "label": "Demos",
+ "url": "/demos/"
+ },
+ {
+ "label": "Support",
+ "url": "/support/"
+ },
+ {
+ "label": "Resources",
+ "url": "/resources/"
+ },
+ {
+ "label": "On Github",
+ "url": "https://github.com/pattern-lab/patternlab-node",
+ "external": true
+ }
+ ],
+ "footerNav": [
+ {
+ "label": "Resources",
+ "url": "/resources"
+ },
+ {
+ "label": "Updates",
+ "url": "/updates"
+ },
+ {
+ "label": "Demos",
+ "url": "/demos"
+ },
+ {
+ "label": "On Github",
+ "url": "https://github.com/pattern-lab/patternlab-node"
+ }
+ ]
+}
diff --git a/packages/docs/src/_data/site.json b/packages/docs/src/_data/site.json
new file mode 100644
index 000000000..8a5e4ce23
--- /dev/null
+++ b/packages/docs/src/_data/site.json
@@ -0,0 +1,13 @@
+{
+ "showThemeCredit": true,
+ "name": "Pattern Lab",
+ "shortDesc": "Pattern Lab is a frontend workshop environment that helps you build, view, test, and showcase your design system's UI components.",
+ "url": "https://patternlab.io/",
+ "authorEmail": "brad@bradfrost.com",
+ "authorHandle": "@bradfrost",
+ "authorName": "Brad Frost",
+ "enableThirdPartyComments": false,
+ "maxPostsPerPage": 5,
+ "paymentPointer": "$coil.xrptipbot.com/c1f8f05d-8d8c-4f37-b1ad-25677ae129da",
+ "faviconPath": "/images/favicon.ico"
+}
diff --git a/packages/docs/src/_data/styleguide.js b/packages/docs/src/_data/styleguide.js
new file mode 100644
index 000000000..4969bec39
--- /dev/null
+++ b/packages/docs/src/_data/styleguide.js
@@ -0,0 +1,28 @@
+const tokens = require('./tokens.json');
+
+module.exports = {
+ colors() {
+ let response = [];
+
+ Object.keys(tokens.colors).forEach(key => {
+ response.push({
+ value: tokens.colors[key],
+ key
+ });
+ });
+
+ return response;
+ },
+ sizes() {
+ let response = [];
+
+ Object.keys(tokens['size-scale']).forEach(key => {
+ response.push({
+ value: tokens['size-scale'][key],
+ key
+ });
+ });
+
+ return response;
+ }
+};
diff --git a/packages/docs/src/_data/tokens.json b/packages/docs/src/_data/tokens.json
new file mode 100644
index 000000000..b5bf4b3ae
--- /dev/null
+++ b/packages/docs/src/_data/tokens.json
@@ -0,0 +1,26 @@
+{
+ "size-scale": {
+ "base": "1rem",
+ "300": "0.8rem",
+ "500": "1.25rem",
+ "600": "1.56rem",
+ "700": "1.95rem",
+ "800": "2.44rem",
+ "900": "3.05rem",
+ "max": "4rem"
+ },
+ "colors": {
+ "primary": "#173854",
+ "primary-shade": "#102538",
+ "primary-glare": "#22547c",
+ "highlight": "#fedb8b",
+ "light": "#ffffff",
+ "mid": "#cccccc",
+ "dark": "#333333",
+ "slate": "#404040"
+ },
+ "fonts": {
+ "base": "\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'\"",
+ "serif": "\"'Lora', serif\""
+ }
+}
diff --git a/packages/docs/src/_includes/components/footer-nav.njk b/packages/docs/src/_includes/components/footer-nav.njk
new file mode 100644
index 000000000..54a448266
--- /dev/null
+++ b/packages/docs/src/_includes/components/footer-nav.njk
@@ -0,0 +1,10 @@
+
diff --git a/packages/docs/src/_includes/components/footer.njk b/packages/docs/src/_includes/components/footer.njk
new file mode 100644
index 000000000..7764312d2
--- /dev/null
+++ b/packages/docs/src/_includes/components/footer.njk
@@ -0,0 +1,10 @@
+
diff --git a/packages/docs/src/_includes/components/header.njk b/packages/docs/src/_includes/components/header.njk
new file mode 100644
index 000000000..5e9ebd5de
--- /dev/null
+++ b/packages/docs/src/_includes/components/header.njk
@@ -0,0 +1,21 @@
+
+
+
diff --git a/packages/docs/src/_includes/components/hero.njk b/packages/docs/src/_includes/components/hero.njk
new file mode 100644
index 000000000..7053c247c
--- /dev/null
+++ b/packages/docs/src/_includes/components/hero.njk
@@ -0,0 +1,16 @@
+
+
+
+
Create atomic design systems with Pattern Lab
+
+
+ Pattern Lab is a frontend workshop environment that helps you build, view, test, and showcase your design system's UI components.
+
+
+
+ Run the following command in your terminal and read the installation guide to get started:
+
+
npm create pattern-lab
+
+
+
\ No newline at end of file
diff --git a/packages/docs/src/_includes/components/icon-chevron-down.njk b/packages/docs/src/_includes/components/icon-chevron-down.njk
new file mode 100644
index 000000000..37c8e757a
--- /dev/null
+++ b/packages/docs/src/_includes/components/icon-chevron-down.njk
@@ -0,0 +1,4 @@
+
+cheveron-down
+
+
\ No newline at end of file
diff --git a/packages/docs/src/_includes/components/logo.njk b/packages/docs/src/_includes/components/logo.njk
new file mode 100644
index 000000000..b8b528064
--- /dev/null
+++ b/packages/docs/src/_includes/components/logo.njk
@@ -0,0 +1,11 @@
+
+
+
diff --git a/packages/docs/src/_includes/components/meta-info.njk b/packages/docs/src/_includes/components/meta-info.njk
new file mode 100644
index 000000000..c7d6a3aa0
--- /dev/null
+++ b/packages/docs/src/_includes/components/meta-info.njk
@@ -0,0 +1,41 @@
+{% set pageTitle = title + ' - ' + site.name %}
+{% set pageDesc = '' %}
+{% set siteTitle = site.name %}
+{% set currentUrl = site.url + page.url %}
+
+{% if metaTitle %}
+ {% set pageTitle = metaTitle %}
+{% endif %}
+
+{% if metaDesc %}
+ {% set pageDesc = metaDesc %}
+{% endif %}
+
+{{ pageTitle }}
+
+
+
+
+
+
+
+{% if site.authorHandle %}
+
+{% endif %}
+
+{% if metaDesc %}
+
+
+
+{% endif %}
+
+{% if socialImage %}
+
+
+
+
+{% endif %}
+
+{% if site.paymentPointer %}
+
+{% endif %}
diff --git a/packages/docs/src/_includes/components/page-header.njk b/packages/docs/src/_includes/components/page-header.njk
new file mode 100644
index 000000000..8fbae6236
--- /dev/null
+++ b/packages/docs/src/_includes/components/page-header.njk
@@ -0,0 +1,13 @@
+
diff --git a/packages/docs/src/_includes/components/stacked-block.njk b/packages/docs/src/_includes/components/stacked-block.njk
new file mode 100644
index 000000000..24c79fb1f
--- /dev/null
+++ b/packages/docs/src/_includes/components/stacked-block.njk
@@ -0,0 +1,6 @@
+
+
+ {{ title }}
+
+
{{ description }}
+
\ No newline at end of file
diff --git a/packages/docs/src/_includes/components/tile.njk b/packages/docs/src/_includes/components/tile.njk
new file mode 100644
index 000000000..a2aeb04cf
--- /dev/null
+++ b/packages/docs/src/_includes/components/tile.njk
@@ -0,0 +1,9 @@
+
+
+
+
{{ description | safe }}
+
+
+
\ No newline at end of file
diff --git a/packages/docs/src/_includes/components/tree-nav.njk b/packages/docs/src/_includes/components/tree-nav.njk
new file mode 100644
index 000000000..83492e7f0
--- /dev/null
+++ b/packages/docs/src/_includes/components/tree-nav.njk
@@ -0,0 +1,33 @@
+
+
+
+
+ {% for item in navigation.items %}
+
+ {% if item.subnav %}
+
+ {{ item.label }}
+ {% set className = "c-tree-nav__icon" %}
+ {% include "components/icon-chevron-down.njk" %}
+
+
+
+ {% for subnav in item.subnav %}
+
+
+
+ {{ subnav.label }}
+
+ {% set subnavCategory = subnav.category %}
+ {% include "components/tree-subnav.njk" %}
+
+ {% endfor %}
+
+ {% else %}
+ {{ item.label }}
+ {% endif %}
+
+ {% endfor %}
+
+
+
diff --git a/packages/docs/src/_includes/components/tree-subnav.njk b/packages/docs/src/_includes/components/tree-subnav.njk
new file mode 100644
index 000000000..9265520ab
--- /dev/null
+++ b/packages/docs/src/_includes/components/tree-subnav.njk
@@ -0,0 +1,11 @@
+{% set navPages = collections.docs | eleventyNavigation %}
+
+{% for entry in navPages %}
+ {% if entry.key == subnavCategory %}
+
+ {{ entry.title }}
+
+ {% endif %}
+{% endfor %}
+
+
diff --git a/packages/docs/src/_includes/icons/arrow.svg b/packages/docs/src/_includes/icons/arrow.svg
new file mode 100644
index 000000000..153b9eed7
--- /dev/null
+++ b/packages/docs/src/_includes/icons/arrow.svg
@@ -0,0 +1,13 @@
+
+
+
diff --git a/packages/docs/src/_includes/layouts/archive.njk b/packages/docs/src/_includes/layouts/archive.njk
new file mode 100644
index 000000000..d1b328ed8
--- /dev/null
+++ b/packages/docs/src/_includes/layouts/archive.njk
@@ -0,0 +1,17 @@
+{% extends 'layouts/base.njk' %}
+{% set pageType = 'Post Archive' %}
+
+{# Intro content #}
+{% set introHeading = title %}
+{% set introSummary %}{{ content | safe }}{% endset %}
+
+{# Post list content #}
+{% set postListHeading = 'All posts' %}
+{% set postListItems = collections.posts %}
+
+{% block content %}
+
+ {% include "partials/components/intro.njk" %}
+ {% include "partials/components/post-list.njk" %}
+
+{% endblock %}
diff --git a/packages/docs/src/_includes/layouts/base.njk b/packages/docs/src/_includes/layouts/base.njk
new file mode 100644
index 000000000..6d21c8837
--- /dev/null
+++ b/packages/docs/src/_includes/layouts/base.njk
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+ {% include "components/meta-info.njk" %}
+
+
+ {% block head %}
+ {% endblock %}
+
+
+
+
+
+
+
+
+ {% block content %}
+ {% endblock content %}
+
+
+ {% include "components/footer.njk" %}
+ {% block foot %}
+ {% endblock %}
+
+
+
+
+
+
diff --git a/packages/docs/src/_includes/layouts/blog.njk b/packages/docs/src/_includes/layouts/blog.njk
new file mode 100644
index 000000000..bc7b10eef
--- /dev/null
+++ b/packages/docs/src/_includes/layouts/blog.njk
@@ -0,0 +1,17 @@
+{% extends 'layouts/base.njk' %}
+
+{# Intro content #}
+{% set introHeading = title %}
+{% set introDescription = description %}
+
+{# Post list content #}
+{% set postListItems = collections.posts %}
+
+{% block content %}
+
+
+ {% include "components/page-header.njk" %}
+ {% include "partials/components/post-list.njk" %}
+
+
+{% endblock %}
diff --git a/packages/docs/src/_includes/layouts/demos.njk b/packages/docs/src/_includes/layouts/demos.njk
new file mode 100644
index 000000000..f16878645
--- /dev/null
+++ b/packages/docs/src/_includes/layouts/demos.njk
@@ -0,0 +1,25 @@
+{% extends 'layouts/base.njk' %}
+{% set pageType = 'Post Archive' %}
+
+{# Intro content #}
+{% set introHeading = title %}
+{% set introSummary %}{{ content | safe }}{% endset %}
+
+{# Post list content #}
+{% set demoListItems = collections.demos %}
+
+{% block content %}
+
+
+ {% include "components/page-header.njk" %}
+
+
+ {% set demoListCategory = 'example' %}
+ {% include "partials/components/demo-list.njk" %}
+
+
+ {% set demoListCategory = 'starterkit' %}
+ {% include "partials/components/demo-list.njk" %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/packages/docs/src/_includes/layouts/docs.njk b/packages/docs/src/_includes/layouts/docs.njk
new file mode 100644
index 000000000..e29cc063e
--- /dev/null
+++ b/packages/docs/src/_includes/layouts/docs.njk
@@ -0,0 +1,28 @@
+---
+permalink: docs/{{ title | slug }}/
+---
+
+{% extends 'layouts/base.njk' %}
+{% set pageType = 'Docs' %}
+
+{# Intro content #}
+{% set introHeading = title %}
+{% set introSummary %}
+
+{% endset %}
+
+{% block content %}
+
+
+
+ {% include "components/page-header.njk" %}
+
+ {{ content | safe }}
+
+
+
+
+
+{% endblock %}
+
+{{ content | safe }}
diff --git a/packages/docs/src/_includes/layouts/home.njk b/packages/docs/src/_includes/layouts/home.njk
new file mode 100644
index 000000000..1172fcd55
--- /dev/null
+++ b/packages/docs/src/_includes/layouts/home.njk
@@ -0,0 +1,93 @@
+{% extends 'layouts/base.njk' %}
+{% set pageType = 'Homepage' %}
+
+{% block content %}
+
+ {% include "components/hero.njk" %}
+
+
+
+
+ {% set styleModifier = 'c-tile--green' %}
+ {% set title = 'Read the docs' %}
+ {% set link = '/docs/installing-pattern-lab' %}
+ {% set description = "Learn how to get up and running with Pattern Lab, work with patterns, design with dynamic data, and use Pattern Lab's advanced features." %}
+ {% include "components/tile.njk" %}
+
+
+ {% set styleModifier = 'c-tile--orange' %}
+ {% set title = 'Demos' %}
+ {% set link = '/demos' %}
+ {% set description = "Demos of pattern starterkits for your project as well as a gallery of Pattern Lab projects in the wild" %}
+ {% include "components/tile.njk" %}
+
+
+ {% set styleModifier = 'c-tile--purple' %}
+ {% set title = 'Resources' %}
+ {% set link = '/resources' %}
+ {% set description = "Links to articles and resources around Pattern Lab and design systems" %}
+ {% include "components/tile.njk" %}
+
+
+
+
+
+
Pattern Lab features
+
At its core, Pattern Lab is a Node-powered static site generator that stitches together UI components. But there's a whole lot more to it than that!
+
+
+ {% set title="Nested Patterns" %}
+ {% set description="Include UI patterns inside each other like Russian nesting dolls. Make a change to a pattern and immediately see those changes reflected anywhere it is included." %}
+ {% include "components/stacked-block.njk" %}
+
+
+ {% set title="Design With Dynamic Data" %}
+ {% set description="Create living UI prototypes using dynamic data to ensure your components can handle the dynamic nature of your content." %}
+ {% include "components/stacked-block.njk" %}
+
+
+ {% set title="Tool Agnostic" %}
+ {% set description="Pattern Lab doesn't impose any tools or libraries on you, which means you have full control over how author your project." %}
+ {% include "components/stacked-block.njk" %}
+
+
+ {% set title="Language Agnostic" %}
+ {% set description="Use atomic design language, or don't! it's totally up to you how you name, structure, and organize your Pattern Lab project." %}
+ {% include "components/stacked-block.njk" %}
+
+
+ {% set title="Pattern Documentation" %}
+ {% set description="Define and describe your UI patterns so your entire team can start speaking the same language to collaborate more effectively." %}
+ {% include "components/stacked-block.njk" %}
+
+
+ {% set title="Viewport Resizer Tools" %}
+ {% set description="Pattern Lab includes viewport resizing tools to ensure your design system's components and pages are fully responsive." %}
+ {% include "components/stacked-block.njk" %}
+
+
+ {% set title="Pattern Lineage" %}
+ {% set description="X-ray vision! Quickly view where patterns where components are used, speeding up design, development, and QA time." %}
+ {% include "components/stacked-block.njk" %}
+
+
+ {% set title="Pattern Starter Kits" %}
+ {% set description="Start your Pattern Lab project with a blank slate, a few sample components, or a full-on demo project." %}
+ {% include "components/stacked-block.njk" %}
+
+
+ {% set title="Flexible and Extensible" %}
+ {% set description="Pattern Lab supports Handlebars and Twig templating engines. Also you can or build a plugin to extend Pattern Lab's capabilities even further." %}
+ {% include "components/stacked-block.njk" %}
+
+
+
+ {% set styleModifier = 'c-tile--orange' %}
+ {% set title = 'Open source and community driven' %}
+ {% set link = '/support' %}
+ {% set description = "Pattern Lab is (and will always be) an open source project. Check out the project on
GitHub and join the
Pattern Lab Gitter community for conversation and support." %}
+ {% include "components/tile.njk" %}
+
+
+
+{% endblock %}
diff --git a/packages/docs/src/_includes/layouts/page-base.njk b/packages/docs/src/_includes/layouts/page-base.njk
new file mode 100644
index 000000000..4a921e27f
--- /dev/null
+++ b/packages/docs/src/_includes/layouts/page-base.njk
@@ -0,0 +1,24 @@
+{% extends 'layouts/base.njk' %}
+{% set pageType = 'Resources' %}
+
+{# Intro content #}
+{% set introHeading = title %}
+{% set introSummary %}
+
+{% endset %}
+
+{% block content %}
+
+
+
+ {% include "components/page-header.njk" %}
+
+ {{ content | safe }}
+
+
+
+
+
+{% endblock %}
+
+{{ content | safe }}
diff --git a/packages/docs/src/_includes/layouts/page.njk b/packages/docs/src/_includes/layouts/page.njk
new file mode 100644
index 000000000..0714fa10c
--- /dev/null
+++ b/packages/docs/src/_includes/layouts/page.njk
@@ -0,0 +1,19 @@
+{% extends 'layouts/base.njk' %}
+{% set pageType = 'Page' %}
+
+{# Intro content #}
+{% set introHeading = title %}
+
+{% block content %}
+
+
+
+ {% include "components/page-header.njk" %}
+ {{ content | safe }}
+
+
+
+
+{% endblock %}
+
+{{ content | safe }}
diff --git a/packages/docs/src/_includes/layouts/post.njk b/packages/docs/src/_includes/layouts/post.njk
new file mode 100644
index 000000000..6a4702769
--- /dev/null
+++ b/packages/docs/src/_includes/layouts/post.njk
@@ -0,0 +1,25 @@
+{% extends 'layouts/base.njk' %}
+{% set pageType = 'Post' %}
+
+{# Intro content #}
+{% if date %}
+{% set introKicker = date | dateFilter %}
+{% endif %}
+{% set introHeading = title %}
+
+
+{% block content %}
+
+
+
+ {% include "components/page-header.njk" %}
+
+
+ {{ content | safe }}
+
+
+
+
+{% endblock %}
+
+{{ content | safe }}
diff --git a/packages/docs/src/_includes/partials/components/demo-list.njk b/packages/docs/src/_includes/partials/components/demo-list.njk
new file mode 100644
index 000000000..d220b7a9a
--- /dev/null
+++ b/packages/docs/src/_includes/partials/components/demo-list.njk
@@ -0,0 +1,25 @@
+{% if demoListItems.length %}
+
+
+ {% for item in demoListItems %}
+
+ {% if item.data.category == demoListCategory %}
+
+
+
+
+
+ {{ item.data.title }}
+
+
+ {{ item.data.description}}
+
+
+
+
+ {% endif %}
+
+ {% endfor %}
+
+
+{% endif %}
diff --git a/packages/docs/src/_includes/partials/components/intro.njk b/packages/docs/src/_includes/partials/components/intro.njk
new file mode 100644
index 000000000..e5cc10c13
--- /dev/null
+++ b/packages/docs/src/_includes/partials/components/intro.njk
@@ -0,0 +1,8 @@
+
diff --git a/packages/docs/src/_includes/partials/components/nav.njk b/packages/docs/src/_includes/partials/components/nav.njk
new file mode 100644
index 000000000..0b9cbcf6f
--- /dev/null
+++ b/packages/docs/src/_includes/partials/components/nav.njk
@@ -0,0 +1,22 @@
+{% if navigation.items %}
+
+
+ {% for item in navigation.items %}
+ {% set relAttribute = '' %}
+ {% set currentAttribute = '' %}
+
+ {% if item.rel %}
+ {% set relAttribute = ' rel="' + item.rel + '"' %}
+ {% endif %}
+
+ {% if page.url == item.url %}
+ {% set currentAttribute = ' aria-current="page"' %}
+ {% endif %}
+
+
+ {{ item.text }}
+
+ {% endfor %}
+
+
+{% endif %}
diff --git a/packages/docs/src/_includes/partials/components/pagination.njk b/packages/docs/src/_includes/partials/components/pagination.njk
new file mode 100644
index 000000000..d78236a2a
--- /dev/null
+++ b/packages/docs/src/_includes/partials/components/pagination.njk
@@ -0,0 +1,23 @@
+{% set paginationLinkTokens = 'leading-tight text-500 weight-mid box-inline-flex align-center pad-bottom-300' %}
+
+{% if paginationNextUrl or paginationPrevUrl %}
+
+
+
+
+{% endif %}
diff --git a/packages/docs/src/_includes/partials/components/post-list.njk b/packages/docs/src/_includes/partials/components/post-list.njk
new file mode 100644
index 000000000..7a241e2e1
--- /dev/null
+++ b/packages/docs/src/_includes/partials/components/post-list.njk
@@ -0,0 +1,22 @@
+
+{% if postListItems.length %}
+
+
+ {% for item in postListItems %}
+
+
+ {{ item.data.date | dateFilter }}
+
+
+ {{ item.data.title }}
+
+
+ {{ item.data.description}}
+
+
+
+
+ {% endfor %}
+
+
+{% endif %}
diff --git a/packages/docs/src/admin.njk b/packages/docs/src/admin.njk
new file mode 100644
index 000000000..c1c5517d1
--- /dev/null
+++ b/packages/docs/src/admin.njk
@@ -0,0 +1,22 @@
+---
+permalink: '/admin/index.html'
+---
+
+
+
+
+
+ Content Manager
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docs/src/admin/config.yml b/packages/docs/src/admin/config.yml
new file mode 100644
index 000000000..15abea8bc
--- /dev/null
+++ b/packages/docs/src/admin/config.yml
@@ -0,0 +1,243 @@
+backend:
+ name: git-gateway
+ branch: master
+publish_mode: editorial_workflow
+site_url: 'https://example.com'
+media_folder: 'src/images'
+public_folder: 'images'
+collections:
+ - name: 'pages'
+ label: 'Pages'
+ files:
+ - name: 'home'
+ label: 'Homepage'
+ delete: false
+ file: 'src/index.md'
+ slug: '{{slug}}'
+ create: false
+ fields:
+ - {
+ label: 'Layout',
+ name: 'layout',
+ widget: 'hidden',
+ default: 'layouts/njk.njk',
+ }
+ - {label: 'Title', name: 'title', widget: 'string'}
+ - {
+ label: 'SEO Meta Title',
+ name: 'metaTitle',
+ widget: 'string',
+ required: false,
+ }
+ - {
+ label: 'SEO Meta Description',
+ name: 'metaDesc',
+ widget: 'string',
+ required: false,
+ }
+ - {
+ label: 'Post Feed Heading',
+ name: 'postsHeading',
+ widget: 'string',
+ default: 'Latest posts',
+ }
+ - {
+ label: 'Archive Link Text',
+ name: 'archiveButtonText',
+ widget: 'string',
+ default: 'See all posts',
+ }
+ - {label: 'Social Image', name: 'socialImage', widget: 'image', required: false}
+ - {label: 'Body', name: 'body', widget: 'markdown'}
+ - name: 'generic_pages'
+ label: 'Generic Pages'
+ folder: 'src/pages'
+ slug: '{{slug}}'
+ preview_path: 'pages/{{slug}}'
+ create: true
+ fields:
+ - {label: 'Layout', name: 'layout', widget: 'hidden', default: 'layouts/page.njk'}
+ - {label: 'Title', name: 'title', widget: 'string'}
+ - {
+ label: "Permalink Override (Pattern: '/your-slug/index.html')",
+ name: 'permalink',
+ widget: 'string',
+ required: false,
+ }
+ - {label: 'SEO Meta Title', name: 'metaTitle', widget: 'string', required: false}
+ - {
+ label: 'SEO Meta Description',
+ name: 'metaDesc',
+ widget: 'string',
+ required: false,
+ }
+ - {label: 'Social Image', name: 'socialImage', widget: 'image', required: false}
+ - {label: 'Body', name: 'body', widget: 'markdown'}
+ - name: 'posts'
+ label: 'Posts'
+ folder: 'src/posts'
+ slug: '{{slug}}'
+ preview_path: 'posts/{{slug}}'
+ create: true
+ fields:
+ - {label: 'Layout', name: 'layout', widget: 'hidden', default: 'layouts/post.njk'}
+ - {label: 'Title', name: 'title', widget: 'string'}
+ - {label: 'SEO Meta Title', name: 'metaTitle', widget: 'string', required: false}
+ - {
+ label: 'SEO Meta Description',
+ name: 'metaDesc',
+ widget: 'string',
+ required: false,
+ }
+ - {label: 'Social Image', name: 'socialImage', widget: 'image', required: false}
+ - {label: 'Publish Date', name: 'date', widget: 'datetime'}
+ - {label: 'Tags', name: 'tags', widget: 'list', allow_add: true}
+ - {label: 'Body', name: 'body', widget: 'markdown'}
+ - name: 'docs'
+ label: 'Docs'
+ folder: 'src/docs'
+ slug: '{{slug}}'
+ preview_path: 'docs/{{slug}}'
+ create: true
+ fields:
+ - {label: 'Layout', name: 'layout', widget: 'hidden', default: 'layouts/post.njk'}
+ - {label: 'Title', name: 'title', widget: 'string'}
+ - {label: 'Publish Date', name: 'date', widget: 'datetime'}
+ - {label: 'Tags', name: 'tags', widget: 'list', allow_add: true}
+ - {label: 'Body', name: 'body', widget: 'markdown'}
+ - label: 'Globals'
+ name: 'globals'
+ files:
+ - label: 'Site Data'
+ name: 'site_data'
+ delete: false
+ file: 'src/_data/site.json'
+ fields:
+ - {label: 'Site Name', name: 'name', widget: 'string'}
+ - {label: 'Site Url', name: 'url', widget: 'string'}
+ - {label: 'Author Name', name: 'authorName', widget: 'string'}
+ - {label: 'Author Email Address', name: 'authorEmail', widget: 'string'}
+ - {
+ label: 'Author Twitter Handle',
+ name: 'authorHandle',
+ widget: 'string',
+ required: false,
+ }
+ - {label: 'Footer Short Description', name: 'shortDesc', widget: 'string'}
+ - {
+ label: 'Maximum Posts Per Page',
+ name: 'maxPostsPerPage',
+ widget: 'number',
+ default: 5,
+ }
+ - {
+ label: 'Show Theme Credit',
+ name: 'showThemeCredit',
+ widget: 'boolean',
+ default: true,
+ }
+ - {
+ label: 'Enable Third Party Comments Area',
+ name: 'enableThirdPartyComments',
+ widget: 'boolean',
+ default: false,
+ }
+ - {
+ label: 'Payment Pointer (Web Monetization: https://bit.ly/2kTRI1b)',
+ name: 'paymentPointer',
+ widget: 'string',
+ }
+ - {
+ label: 'Favicon path (EG: /images/favicon.png)',
+ name: 'faviconPath',
+ widget: 'string',
+ }
+ - label: 'Navigation'
+ name: 'nav'
+ delete: false
+ file: 'src/_data/navigation.json'
+ fields:
+ - label: 'Items'
+ name: 'items'
+ widget: 'list'
+ fields:
+ - {label: 'Text', name: 'text', widget: 'string'}
+ - {label: 'Url', name: 'url', widget: 'string'}
+ - {
+ label: 'Is url to external site?',
+ name: 'external',
+ widget: 'boolean',
+ required: false,
+ }
+ - label: 'Theme Settings'
+ name: 'theme'
+ delete: false
+ file: 'src/_data/tokens.json'
+ fields:
+ - label: 'Size Scale'
+ name: 'size-scale'
+ widget: 'object'
+ fields:
+ - {label: 'Base Size', name: 'base', widget: 'string', default: '1rem'}
+ - {label: 'Ratio: 300', name: '300', widget: 'string', default: '0.8rem'}
+ - {label: 'Ratio: 500', name: '500', widget: 'string', default: '1.25rem'}
+ - {label: 'Ratio: 600', name: '600', widget: 'string', default: '1.56rem'}
+ - {label: 'Ratio: 700', name: '700', widget: 'string', default: '1.95rem'}
+ - {label: 'Ratio: 800', name: '800', widget: 'string', default: '2.44rem'}
+ - {label: 'Ratio: 900', name: '900', widget: 'string', default: '3.05rem'}
+ - {label: 'Max Size', name: 'max', widget: 'string', default: '4rem'}
+ - label: 'Colors'
+ name: 'colors'
+ widget: 'object'
+ fields:
+ - {
+ label: 'Primary',
+ name: 'primary',
+ widget: 'string',
+ default: 'hsl(208, 57%, 21%)',
+ }
+ - {
+ label: 'Primary Shade (darker)',
+ name: 'primary-shade',
+ widget: 'string',
+ default: 'hsl(208, 56%, 14%)',
+ }
+ - {
+ label: 'Primary Glare (lighter)',
+ name: 'primary-glare',
+ widget: 'string',
+ default: 'hsl(207, 57%, 31%)',
+ }
+ - {
+ label: 'Highlight',
+ name: 'highlight',
+ widget: 'string',
+ default: 'hsl(42, 98%, 77%)',
+ }
+ - {
+ label: 'Light',
+ name: 'light',
+ widget: 'string',
+ default: 'hsl(0, 0%, 100%)',
+ }
+ - {label: 'Mid', name: 'mid', widget: 'string', default: 'hsl(0, 0%, 80%)'}
+ - {
+ label: 'Dark',
+ name: 'dark',
+ widget: 'string',
+ default: 'hsl(0, 0%, 20%)',
+ }
+ - {
+ label: 'Slate',
+ name: 'slate',
+ widget: 'string',
+ default: 'hsl(0, 0%, 25%)',
+ }
+ - label: 'Fonts'
+ name: 'fonts'
+ widget: 'hidden'
+ default:
+ {
+ 'base': '"-apple-system, BlinkMacSystemFont, ''Segoe UI'', Roboto, Helvetica, Arial, sans-serif, ''Apple Color Emoji'', ''Segoe UI Emoji'', ''Segoe UI Symbol''"',
+ 'serif': '"''Lora'', serif"',
+ }
diff --git a/packages/docs/src/admin/previews.js b/packages/docs/src/admin/previews.js
new file mode 100644
index 000000000..82dcd1f2c
--- /dev/null
+++ b/packages/docs/src/admin/previews.js
@@ -0,0 +1,92 @@
+const {w3DateFilter, markdownFilter, dateFilter, helpers} = previewUtil;
+
+const env = nunjucks.configure();
+
+env.addFilter('w3DateFilter', w3DateFilter);
+env.addFilter('markdownFilter', markdownFilter);
+env.addFilter('dateFilter', dateFilter);
+
+const Preview = ({entry, path, context}) => {
+ const data = context(entry.get('data').toJS());
+ const html = env.render(path, {...data, helpers});
+ return
;
+};
+
+const Home = ({entry}) => (
+ ({
+ title,
+ content: markdownFilter(body),
+ postsHeading,
+ archiveButtonText,
+ collections: {
+ postFeed: [
+ {
+ url: 'javascript:void(0)',
+ date: new Date(),
+ data: {
+ title: 'Sample Post'
+ }
+ }
+ ]
+ }
+ })}
+ />
+);
+
+const Post = ({entry}) => (
+ ({
+ title,
+ date,
+ content: markdownFilter(body || '')
+ })}
+ />
+);
+
+const Page = ({entry}) => (
+ ({
+ title,
+ content: markdownFilter(body || '')
+ })}
+ />
+);
+
+const SiteData = ({entry}) => (
+ ({
+ site: {
+ name,
+ shortDesc,
+ showThemeCredit
+ }
+ })}
+ />
+);
+
+const Nav = ({entry}) => (
+ ({
+ navigation: {
+ items
+ }
+ })}
+ />
+);
+
+CMS.registerPreviewTemplate('home', Home);
+CMS.registerPreviewTemplate('posts', Post);
+CMS.registerPreviewTemplate('generic_pages', Page);
+CMS.registerPreviewTemplate('site_data', SiteData);
+CMS.registerPreviewTemplate('nav', Nav);
diff --git a/packages/docs/src/admin/util.js b/packages/docs/src/admin/util.js
new file mode 100644
index 000000000..302b159cf
--- /dev/null
+++ b/packages/docs/src/admin/util.js
@@ -0,0 +1,11 @@
+import helpers from '../_data/helpers';
+import dateFilter from '../filters/date-filter';
+import markdownFilter from '../filters/markdown-filter';
+import w3DateFilter from '../filters/w3-date-filter';
+
+export {
+ helpers,
+ dateFilter,
+ markdownFilter,
+ w3DateFilter,
+};
diff --git a/packages/docs/src/archive.md b/packages/docs/src/archive.md
new file mode 100644
index 000000000..76533c10f
--- /dev/null
+++ b/packages/docs/src/archive.md
@@ -0,0 +1,4 @@
+---
+title: 'Posts Archive'
+layout: 'layouts/archive.njk'
+---
diff --git a/packages/docs/src/demos.md b/packages/docs/src/demos.md
new file mode 100644
index 000000000..441563fc1
--- /dev/null
+++ b/packages/docs/src/demos.md
@@ -0,0 +1,13 @@
+---
+layout: layouts/demos.njk
+title: Pattern Lab Demos
+category: getting-started
+---
+
+
+
+Here’s a list of Pattern Lab’s UI starterkits that you can use to kickstart your UI design system project, as well as a list of Pattern Labs from around the web.
+
+
+
+
diff --git a/packages/docs/src/demos/bolt-design-systems.md b/packages/docs/src/demos/bolt-design-systems.md
new file mode 100644
index 000000000..54d091aa6
--- /dev/null
+++ b/packages/docs/src/demos/bolt-design-systems.md
@@ -0,0 +1,11 @@
+---
+title: Bolt Design System
+description: The Bolt Design System provides robust Twig and Web Component-powered UI components, reusable visual styles, and powerful tooling to help developers, designers, and content authors build, maintain, and scale best of class digital experiences.
+url: https://boltdesignsystem.com/
+category: example
+tags:
+ - demo-in-the-wild
+ - demo-content
+ - code
+image: /images/800x600.png
+---
diff --git a/packages/docs/src/demos/brad-frosts-website-pl.md b/packages/docs/src/demos/brad-frosts-website-pl.md
new file mode 100644
index 000000000..41bbe38c1
--- /dev/null
+++ b/packages/docs/src/demos/brad-frosts-website-pl.md
@@ -0,0 +1,11 @@
+---
+title: Brad Frost’s website PL
+description: Pattern Lab helps you and your team build thoughtful, pattern-driven user interfaces using atomic design principles.
+url: https://bradfrostdotcom-pl.netlify.com/
+category: example
+tags:
+ - demo-in-the-wild
+ - demo-content
+ - code
+image: /images/800x600.png
+---
diff --git a/packages/docs/src/demos/handlebars-base-starterkit.md b/packages/docs/src/demos/handlebars-base-starterkit.md
new file mode 100644
index 000000000..44ea634e4
--- /dev/null
+++ b/packages/docs/src/demos/handlebars-base-starterkit.md
@@ -0,0 +1,11 @@
+---
+title: Handlebars Base Starter Kit
+description: The StarterKit for Handlebars is meant to be used as a demonstration of a Handlebars-based project in Pattern Lab.
+url: https://patternlab-handlebars-preview.netlify.com/?p=all
+category: starterkit
+tags:
+ - demo-hbs-starter-kits
+ - demo-content
+ - code
+image: /images/800x600.png
+---
diff --git a/packages/docs/src/demos/handlebars-demo-starterkit.md b/packages/docs/src/demos/handlebars-demo-starterkit.md
new file mode 100644
index 000000000..28387c606
--- /dev/null
+++ b/packages/docs/src/demos/handlebars-demo-starterkit.md
@@ -0,0 +1,11 @@
+---
+title: Handlebars Demo Starter Kit
+description: The Demo StarterKit for Handlebars is meant to be used as a demonstration of a Handlebars-based project in Pattern Lab.
+url: https://www.npmjs.com/package/@pattern-lab/starterkit-handlebars-demo
+category: starterkit
+tags:
+ - demo-hbs-starter-kits
+ - demo-content
+ - code
+image: /images/800x600.png
+---
diff --git a/packages/docs/src/demos/handlebars-vanilla-starterkit.md b/packages/docs/src/demos/handlebars-vanilla-starterkit.md
new file mode 100644
index 000000000..4f7485a52
--- /dev/null
+++ b/packages/docs/src/demos/handlebars-vanilla-starterkit.md
@@ -0,0 +1,11 @@
+---
+title: Handlebars Vanilla Starter Kit
+description: The Vanilla StarterKit for Handlebars is meant to be used as a demonstration of a Handlebars-based project in Pattern Lab.
+url: https://www.npmjs.com/package/@pattern-lab/starterkit-handlebars-vanilla
+category: starterkit
+tags:
+ - demo-hbs-starter-kits
+ - demo-content
+ - code
+image: /images/800x600.png
+---
diff --git a/packages/docs/src/demos/pattern-lab-two-mustache-demo.md b/packages/docs/src/demos/pattern-lab-two-mustache-demo.md
new file mode 100644
index 000000000..56fda1e32
--- /dev/null
+++ b/packages/docs/src/demos/pattern-lab-two-mustache-demo.md
@@ -0,0 +1,11 @@
+---
+title: Pattern Lab 2 Mustache Demo
+description: Pattern Lab 1 projects should work with minimal changes in Pattern Lab 2.
+url: http://demo.patternlab.io/
+category: starterkit
+tags:
+ - demo-PL2
+ - demo-content
+ - code
+image: /images/800x600.png
+---
diff --git a/packages/docs/src/demos/twig-base.md b/packages/docs/src/demos/twig-base.md
new file mode 100644
index 000000000..0da6259b6
--- /dev/null
+++ b/packages/docs/src/demos/twig-base.md
@@ -0,0 +1,13 @@
+---
+title: Twig Base
+description: The Base StarterKit for Twig is meant to be used as a near-blank starting point for Twig-based projects in Pattern Lab.
+url: https://github.com/pattern-lab/starterkit-twig-base
+category: starterkit
+tags:
+ - demo-twig
+ - demo-content
+ - code
+image: /images/800x600.png
+---
+
+loremi
diff --git a/packages/docs/src/docs/a-post-with-code-samples.md b/packages/docs/src/docs/a-post-with-code-samples.md
new file mode 100644
index 000000000..d5c83f6c7
--- /dev/null
+++ b/packages/docs/src/docs/a-post-with-code-samples.md
@@ -0,0 +1,67 @@
+---
+title: DOCS DOCS DOCS
+tags:
+ - demo-content
+ - code
+ - blog
+eleventyNavigation:
+ key: DOCS DOCS DOCS
+ order: 300
+---
+
+The best way to demo a code post is to display a real life post, so check out this one from [andy-bell.design](https://andy-bell.design/wrote/creating-a-full-bleed-css-utility/) about a full bleed CSS utility.
+
+---
+
+Sometimes you want to break your components out of the constraints that they find themselves in. A common situation where this occurs is when you don’t have much control of the container that it exists in, such as a CMS main content area.
+
+This is even more the case with editing tools such as the [WordPress Gutenberg editor](https://wordpress.org/gutenberg/), where in theory, you could pull in a component from a design system and utilise it in the main content of your web page. In these situations, it can be pretty darn handy to have a little utility that makes the element 100% of the viewport’s width _and_ still maintain its flow within its parent container.
+
+This is when I normally pull the `.full-bleed` utility class out of my back pocket.
+
+## The `.full-bleed` utility
+
+It’s small, but hella mighty:
+
+```css
+.full-bleed {
+ width: 100vw;
+ margin-left: 50%;
+ transform: translateX(-50%);
+}
+```
+
+Here it is in a context where it makes a fancy `` and a `` element bleed out of their parent container.
+
+
+
+The `.full-bleed` utility gives those elements prominence and _importantly_ keeps their semantic place in the page. Just how I like it.
+
+---
+
+🔥 **Pro tip**: When working with a utility like `.full-bleed`, it’s a good idea to add an inner container that has a max-width and auto horizontal margin. For this, I normal create a shared `.wrapper` component like this:
+
+```css
+.wrapper {
+ max-width: 50rem;
+ margin-left: auto;
+ margin-right: auto;
+}
+```
+
+Having a container like `.wrapper` helps to create consistent, centred content.
+
+---
+
+### How the `.full-bleed` utility works
+
+We set the container to be `width: 100vw`, which equates to the full viewport width. We couldn’t set it to `width: 100%` because it would only fill the space of its parent element. The parent element’s width _is_ useful though, because by setting `margin-left: 50%`, we are telling the component to align its **left edge** to the center of its parent element, because `50%` is half of the **parent element’s** width.
+
+Finally, we use CSS transforms to `translateX(-50%)`. Because the transform works off the element’s dimensions and not the parent’s dimensions, it’ll pull the element back `50vw`, because it’s `100vw` wide, thus making it sit perfectly flush with the viewport’s edges.
+
+## Wrapping up
+
+Hopefully this short and sweet trick will help you out on your projects. If it does, [drop me a tweet](https://twitter.com/andybelldesign), because I’d love to see it!
diff --git a/packages/docs/src/docs/advanced-auto-regenerate.md b/packages/docs/src/docs/advanced-auto-regenerate.md
new file mode 100644
index 000000000..291b675d6
--- /dev/null
+++ b/packages/docs/src/docs/advanced-auto-regenerate.md
@@ -0,0 +1,44 @@
+---
+title: Watching for Changes and Auto Regenerating Patterns
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ key: Watching for Changes and Auto Regenerating Patterns
+ parent: advanced
+ order: 300
+---
+
+Pattern Lab has the ability to watch for changes to patterns and frontend assets. When these files change, it will automatically rebuild the entire Pattern Lab website. You simply make your changes, save the file, and Pattern Lab will take care of the rest.
+
+## How to Start the Watch
+
+Open your terminal and navigate to the root of your project. Type:
+
+```
+gulp patternlab:build --watch
+```
+
+> If using grunt, substitute `grunt` for `gulp` above.
+
+## How to Start the Watch and Self-Host the Pattern Lab Website
+
+Rather than manually refreshing your browser when your patterns or frontend assets change you can have Pattern Lab watch for changes and [auto-reload your browser window](/docs/viewing-patterns.html#node) for you when it’s in watch mode.
+
+## How to Stop the Watch
+
+To stop watching files on Mac OS X and Windows you can press`CTRL+C` in the command line window where the process is running.
+
+## The Default Files That Are Watched
+
+By default, Pattern Lab monitors the following files:
+
+- all of the JSON files under `source/_annotations/`
+- all of the JSON files under `source/_data/`
+- all of the files under `source/_meta/`
+- all of the pattern templates under `source/_patterns/`
+- all of the CSS files under `source/css/`
+- all of the files under `source/images/` and `source/fonts/`
+- all of the Javascript files under `source/js/`
+
+The watch configuration is found within the Gruntfile or Gulpfile at the root of the project.
diff --git a/packages/docs/src/docs/advanced-config-options.md b/packages/docs/src/docs/advanced-config-options.md
new file mode 100644
index 000000000..fcbe07f5b
--- /dev/null
+++ b/packages/docs/src/docs/advanced-config-options.md
@@ -0,0 +1,299 @@
+---
+title: Editing the Configuration Options
+tags:
+ - docs
+category: getting-started
+eleventyNavigation:
+ key: getting-started
+ title: Editing the Configuration Options
+ order: 30
+---
+
+Pattern Lab Node comes with a configuration file [(`patternlab-config.json`)](https://github.com/pattern-lab/patternlab-node/blob/master/packages/core/patternlab-config.json) that allows you to modify certain aspects of the system. The latest default values are included within. This file is shipped within [the editions](https://github.com/pattern-lab?utf8=%E2%9C%93&query=edition-node) or can be supplied from core and the command line interface. Below is a description of each configuration option and how it affects Pattern Lab Node.
+
+### cacheBust
+
+Instructs Pattern Lab to append a unique query string to Javascript and CSS assets throughout the frontend.
+
+```javascript
+"cacheBust": true
+```
+
+**default**: `true`
+
+### cleanPublic
+
+Sets whether or not to delete `public.patterns/` upon each build of Pattern Lab. When set to false, [incremental builds](https://github.com/pattern-lab/patternlab-node/wiki/Incremental-Builds) are also enabled.
+
+**default**: `true`
+
+### defaultPattern
+
+Sets a specific pattern upon launch of the styleguide. This pattern will not be available in the navigation, or in view all pages. The only way to get to it will be via a refresh. Set it using the [short-hand pattern-include syntax](/docs/including-patterns/):
+
+```javascript
+"defaultPattern": "pages-welcome",
+```
+
+A special value of `all` can also be supplied to display all patterns on load.
+
+**default**: `all`
+
+### defaultShowPatternInfo
+
+Sets whether or not you want the styleguide to load with the pattern info open or closed.
+
+**default**: `false`
+
+### ishControlsHide
+
+Sets whether or not to hide navigation options within the styleguide.
+
+**default**:
+
+```javascript
+"ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "mqs": false,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+}
+```
+
+### ishViewportRange
+
+Sets the boundaries of each of the viewport toggles, 'S'mall, 'M'edium, and 'L'arge. Clicking on one of these buttons will randomly set the ish Viewport to a value within the given range. Setting the range to the same number can effectively set an exact value. The first entry in `ishViewportRange.s` is the `ishViewportMinimum`, which is now obsolete. The second entry in `ishViewportRange.l` is the `ishViewportMaximum`, which is now also obsolete.
+
+**default**:
+
+```javascript
+"ishViewportRange": {
+ "s": [240, 500],
+ "m": [500, 800],
+ "l": [800, 2600]
+},
+```
+
+### logLevel
+
+Sets the level of verbosity for Pattern Lab Node logging.
+
+- `error` will output a message as a thrown error
+- `warning` will output all warnings plus above
+- `info` will output all info messages, plus above (intended default)
+- `debug` will output all debug messages, plus above
+- `quiet` will output ZERO logs
+
+This replaces the now obsolete `debug` flag.
+
+**default**: `info`
+
+### outputFileSuffixes
+
+Sets the naming of output pattern files. Suffixes are defined for 'rendered', 'rawTemplate', and 'markupOnly' files. This configuration is needed for some PatternEngines that use the same input and output file extensions. Most users will not have to change this.
+
+```javascript
+"outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+},
+```
+
+### paths
+
+Sets the configurable source and public directories for files Pattern Lab Node operates within. Build, copy, output, and server operations rely upon these paths. Some paths are relative to the current UIKit. See UIKit configuration for more info. Note the `patternlabFiles` which help create the front end styleguide. Note also the intentional repetition of the nested structure, made this way for maximum flexibility. These are unlikely to change unless you customize your environment or write custom UIKits.
+
+**default** :
+
+```javascript
+ "paths": {
+ "source": {
+ "root": "./source/",
+ "patterns": "./source/_patterns/",
+ "data": "./source/_data/",
+ "meta": "./source/_meta/",
+ "annotations": "./source/_annotations/",
+ "styleguide": "dist/",
+ "patternlabFiles": {
+ "general-header": "views/partials/general-header.mustache",
+ "general-footer": "views/partials/general-footer.mustache",
+ "patternSection": "views/partials/patternSection.mustache",
+ "patternSectionSubtype": "views/partials/patternSectionSubtype.mustache",
+ "viewall": "views/viewall.mustache"
+ },
+ "js": "./source/js",
+ "images": "./source/images",
+ "fonts": "./source/fonts",
+ "css": "./source/css"
+ },
+ "public": {
+ "root": "public/",
+ "patterns": "public/patterns/",
+ "data": "public/styleguide/data/",
+ "annotations": "public/annotations/",
+ "styleguide": "public/styleguide/",
+ "js": "public/js",
+ "images": "public/images",
+ "fonts": "public/fonts",
+ "css": "public/css"
+ }
+ },
+```
+
+### patternExtension
+
+Sets the panel name and language for the code tab on the styleguide. Since this only accepts one value, this is a place where mixed pattern trees (different PatternEngines in the same instance of Pattern Lab) does not quite work.
+
+**default**: `mustache`
+
+### patternStateCascade
+
+See the [Pattern State Documentation](/docs/using-pattern-states/)
+
+**default**:
+
+```javascript
+"patternStateCascade": ["inprogress", "inreview", "complete"],
+```
+
+### patternExportDirectory
+
+Sets the location that any export operations should output files to. This may be a relative or absolute path.
+
+**default**: `./pattern_exports/`
+
+### patternExportPatternPartials
+
+Sets an array of patterns (using the [short-hand pattern-include syntax](/docs/including-patterns/)) to be exported after a build.
+
+For example, to export the navigation, header, and footer, one might do:
+
+```javascript
+"patternExportPatternPartials": ["molecules-primary-nav", "organisms-footer", "organisms-header"],
+```
+
+**default**: `[]`
+
+### serverOptions
+
+Sets live-server options. See the [live-server documentation](https://github.com/pattern-lab/live-server#usage-from-node) for more details.
+
+**default**:
+
+```javascript
+"serverOptions": {
+ "wait": 1000
+},
+```
+
+### starterkitSubDir
+
+[Starterkits](/docs/starterkits/) by convention house their files within the `dist/` directory. Should someone ever wish to change this, this key is available.
+
+**default**:
+
+```javascript
+"starterkitSubDir": "dist",
+```
+
+### styleGuideExcludes
+
+Sets whole pattern types to be excluded from the "All" patterns page on the styleguide. This is useful to decrease initial load of the styleguide. For example, to exlude all patterns under `templates` and `pages`, add the following:
+
+```javascript
+"styleGuideExcludes": [
+ "templates",
+ "pages"
+]
+```
+
+These template and page patterns would still be accessible via navigation.
+
+**default**: `[]`
+
+### theme
+
+Sets the theme options for the styleguide. There are three options: 'color', 'density', and 'layout'.
+
+Available values are:
+
+```javascript
+"theme" : {
+ "color" : "dark" | "light",
+ "density" : "compact" | "cozy" | "comfortable",
+ "layout" : "horizontal" | "vertical"
+}
+```
+
+See the [initial release notes](https://github.com/pattern-lab/styleguidekit-assets-default/releases/tag/v4.0.0-alpha.2) for the theme feature for example output.
+
+**default**:
+
+```javascript
+"theme" : {
+ "color" : "dark",
+ "density" : "compact",
+ "layout" : "horizontal"
+}
+```
+
+### uikits
+
+Introduced in Pattern Lab Node v3, UIKits are a new term in the Pattern Lab [Ecosystem](/docs/overview-of-pattern-lab's-ecosystem/). They are an evolution of the original Styleguidekit pattern which separated front-end templates from front-end assets like stylesheets and code. The existing `styleguidekit-assets-default` and `styleguidekit-mustache-default` have merged into `uikit-workshop`.
+
+`uikits` accepts an array of UIKit objects, shipping with the one above.
+
+- `name`: the name of the UIKit
+- `outputDir` where to output this UIKit relative to the current root. By leaving this empty we retain the existing Pattern Lab 2.X behavior, outputting to `/public`. If you had multiple UIKits, however, you would provide different values, such as:
+
+```javascript
+ "uikits": [
+ {
+ "name": "uikit-workshop",
+ "outputDir": "workshop",
+ ...
+ },
+ {
+ "name": "uikit-storefront",
+ "outputDir": "storefront",
+ ...
+ }
+ ]
+```
+
+- `enabled`: quickly turn on or off the building of this UIKit
+- `excludedPatternStates`: tell Pattern Lab not to include patterns with these states in this UIKit's output
+- `excludedPatternTags`: tell Pattern Lab not to include patterns with these tags in this UIKit's output
+ - [currently not supported](https://github.com/pattern-lab/patternlab-node/issues/844)
+
+Important details:
+
+- the [default `paths.source` object paths](https://github.com/pattern-lab/patternlab-node/pull/840/commits/a4961bd5d696a05fb516cdd951163b0f918d5e19) within `patternlab-config.json` are now relative to the current UIKit. See the [structure of uikit-workshop](https://github.com/pattern-lab/patternlab-node/tree/master/packages/uikit-workshop) for more info
+- the [default `paths.public` object paths](https://github.com/pattern-lab/patternlab-node/pull/840/commits/812bab3659f504043e8b61b1dc1cdac71f248449) within `patternlab-config.json` are now relative to the current UIKit's `outputDir`. Absolute paths will no longer work. Someone could test putting an absolute path in a UIKit `outputDir` property and see what happens I suppose.
+- `dependencyGraph.json` has moved to the project root rather than `public/` as we should only retain one
+
+**default**:
+
+```javascript
+ "uikits": [
+ {
+ "name": "uikit-workshop",
+ "outputDir": "",
+ "enabled": true,
+ "excludedPatternStates": [],
+ "excludedTags": []
+ }
+ ]
+```
diff --git a/packages/docs/src/docs/advanced-ecosystem-overview.md b/packages/docs/src/docs/advanced-ecosystem-overview.md
new file mode 100644
index 000000000..f61744581
--- /dev/null
+++ b/packages/docs/src/docs/advanced-ecosystem-overview.md
@@ -0,0 +1,58 @@
+---
+title: Overview of Pattern Lab's Ecosystem
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ key: advanced
+ title: Overview of Pattern Lab's Ecosystem
+ order: 300
+---
+
+Pattern Lab 2 introduces the beginnings of an ecosystem that will allow teams to mix, match and extend Pattern Lab to meet their specific needs. It will also make it easier for the Pattern Lab team to push out new features. Documentation that explains how best to take advantage of the ecosystem will be released in the coming weeks.
+
+## Editions
+
+Editions let teams and agencies bundle all the things that support their unique workflows with Pattern Lab. An Edition can become the starting point for all of your projects while teams share and update functionality. The Node version of Pattern Lab uses [npm](https://www.npmjs.com/) to pull in separate components.
+
+## Components of an Edition
+
+The following is good overview of what components might make up an edition:
+
+
+
+This is by no means exhaustive and can be added to as needed. Here is a description of each component:
+
+### Pattern Lab Core
+
+Core is the guts of Pattern Lab and enables all of the other features. Because Core is standalone a team can update and stay current with the latest Pattern Lab features without disrupting the rest of their project.
+
+### StarterKits
+
+Have a trusty set of boilerplate code that you start every project with? Perhaps a common set of basic patterns, Sass mix-ins, and JavaScript libraries that are your go-to tools? A StarterKit is perfect for bundling these assets together into a boilerplate that makes sure each project starts off on the right foot.
+
+[Several starterkits](https://github.com/pattern-lab?utf8=%E2%9C%93&q=starterkit&type=&language=) already exist to kick your project off, whether you’re looking for a blank start, begin with a demo that showcases Pattern Lab’s features, or start with a popular framework like Bootstrap, Foundation, or Material Design. And you can roll your own, which can be fully version-controlled so your team’s StarterKit can evolve along with your tools.
+
+Importing a starterkit is only a few keystrokes away after installation.
+
+[Learn more about Starterkits](/docs/starterkits/)
+
+### StyleguideKits
+
+StyleguideKits are the front-end of Pattern Lab. We call this “The Viewer.” StyleguideKits allow agencies and organizations to develop custom, branded Pattern Lab UIs to show off their patterns.
+
+### PatternEngines
+
+PatternEngines are the templating engines that are responsible for parsing patterns and turning them into HTML. PatternEngines give Pattern Lab Core the flexibility to render many different types of template languages. Current PatternEngines include Mustache and Twig, with others like Handlebars and Underscore in development. And there’s no stopping you from adding another templating engine to Pattern Lab.
+
+### Plugins
+
+Plugins allow developers to extend Pattern Lab Core and other parts of the ecosystem. Pattern Lab’s architecture allows developers to modify data at different stages, add their own commands or pattern rules, or change the front-end to modify and extend Pattern Lab’s capabilities.
+
+### Other Types of Components
+
+The flexibility of the Pattern Lab ecosystem means that teams can develop tools on top of Pattern Lab that meet _their_ needs. Want to standardize and push entire data sets to teams? Want to develop with granular collections of components instead of entire StarterKits? Only want to customize the CSS for the default StyleguideKit and distribute it as part of your projects? All of this and more is possible. We feel we're just scratching the surface on what it means to develop projects and design systems with a tool like Pattern Lab
+
+## Guidance and Help
+
+If you have ideas or would like guidance before we have all of the documentation done please learn how you can [engage with the Pattern Lab community](/support/).
diff --git a/packages/docs/src/docs/advanced-exporting-patterns.md b/packages/docs/src/docs/advanced-exporting-patterns.md
new file mode 100644
index 000000000..0f5d4ff66
--- /dev/null
+++ b/packages/docs/src/docs/advanced-exporting-patterns.md
@@ -0,0 +1,21 @@
+---
+title: Exporting Patterns
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ title: Exporting Patterns
+ key: advanced
+ order: 300
+---
+
+While the Pattern Lab website is great for design, iteration, alignment, and discussion - you may find yourself wanting to export whole pattern markup snippets into a different environment.
+
+In Pattern Lab Node, `patternlab-config.json` has two properties that work together to export completed patterns for use elsewhere. To export, provide an array of patternPartials and an output directory. Pattern Lab Node doesn't ship with any patternPartials specified for export. The default directory,`'./pattern_exports/'`, is created inside the install directory. Here is an example with three patternPartials set.
+
+```javascript
+"patternExportPatternPartials": ["molecules-primary-nav", "organisms-header", "organisms-footer"],
+"patternExportDirectory": "./pattern_exports/"
+```
+
+Couple this technique with exported CSS via tools like [grunt-contrib-copy](https://github.com/gruntjs/grunt-contrib-copy) to really make patterns portable.
diff --git a/packages/docs/src/docs/advanced-generating-css.md b/packages/docs/src/docs/advanced-generating-css.md
new file mode 100644
index 000000000..74c2c539f
--- /dev/null
+++ b/packages/docs/src/docs/advanced-generating-css.md
@@ -0,0 +1,22 @@
+---
+title: Generating CSS
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ title: Generating CSS
+ key: advanced
+ order: 300
+---
+
+**Note:** _The [CSS Rule Saver](https://github.com/dmolsen/css-rule-saver) library and CSS generation feature was added in v0.6.0 of the PHP version of Pattern Lab._
+
+When using this feature, Pattern Lab can display only those CSS rules that affect a given pattern on the pattern detail view. This might be useful if you have a large Sass-generated CSS file or framework but only need a sub-set of styles that may affect a small piece of mark-up or pattern.
+
+## How to Generate the CSS
+
+To generate your Pattern Lab site with CSS support on Mac OS X you can do the following:
+
+1. Open `core/scripts/`
+2. Double-click `generateSiteWithCSS.command`
+3. Refresh the Pattern Lab site
diff --git a/packages/docs/src/docs/advanced-integration-with-compass.md b/packages/docs/src/docs/advanced-integration-with-compass.md
new file mode 100644
index 000000000..359a67273
--- /dev/null
+++ b/packages/docs/src/docs/advanced-integration-with-compass.md
@@ -0,0 +1,34 @@
+---
+title: Integration with Compass
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ title: Integration with Compass
+ key: advanced
+ order: 300
+---
+
+**Note:** _These directions incomplete. They are not meant to imply that Compass is officially supported with Pattern Lab. They should be modified to fit your instance of the PHP version of Pattern Lab._
+
+Setting up Compass to work with the PHP version of Pattern Lab should be really straightforward. To set-up a Compass config that uses SCSS and _doesn't_ install any starter stylesheets do the following:
+
+1. Open Terminal on a Mac
+2. `gem install compass` (if you don't have it)
+3. `cd /source`
+4. `compass create --bare --sass-dir "css" --css-dir "css" --javascripts-dir "js" --images-dir "images"`
+
+The directories provided in step #4 are based on the default install of the PHP version of Pattern Lab and should be updated to reflect your directory structure. Also, if you need Compass to watch other directories or implement features modify step #4 as appropriate.
+
+## Workflow with Pattern Lab
+
+Compass will only recompile your SCSS. To get Pattern Lab to rebuild your entire site as well as reload the browser when your SCSS files have been updated do the following:
+
+1. Open Terminal on a Mac
+2. `cd `
+3. `compass watch source`
+4. Open a new tab in Terminal
+5. `php core/builder.php -wr`
+6. Reload your browser
+
+As you make changes to the SCSS files Compass will recompile them and, seeing the changes to `styles.css`, the PHP version of Pattern Lab will rebuild the entire site. It should also reload the Pattern Lab website.
diff --git a/packages/docs/src/docs/advanced-integration-with-grunt.md b/packages/docs/src/docs/advanced-integration-with-grunt.md
new file mode 100644
index 000000000..4b4cc2418
--- /dev/null
+++ b/packages/docs/src/docs/advanced-integration-with-grunt.md
@@ -0,0 +1,41 @@
+---
+title: Integration with Grunt/Gulp
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ title: Integration with Grunt/Gulp
+ key: advanced
+ order: 300
+---
+
+**Note:** _These directions may be incomplete. They also require **v0.7.9** of the PHP version of Pattern Lab._
+
+Setting up Grunt to work with the PHP version of Pattern Lab should be straightforward. To do so please do the following:
+
+1. Open a terminal window
+2. Type `npm install --save-dev grunt-shell` to install [grunt-shell](https://github.com/sindresorhus/grunt-shell)
+3. Add the following to your `grunt.initConfig`. The `-p` flag ensures that Pattern Lab only generates patterns.
+
+shell: {
+ patternlab: {
+ command: "php core/builder.php -gp"
+ }
+}
+
+4. Add `grunt.loadNpmTasks('grunt-shell');` to your list of plugins.
+5. Add `'shell:patternlab'` to your list of tasks in `grunt.registerTask`.
+
+You should also be using `grunt-contrib-watch` to monitor changes to Pattern Lab's patterns and data. The Pattern Lab section for your `watch` might look like:
+
+ html: {
+ files: ['source/_patterns/**/*.mustache', 'source/_patterns/**/*.json', 'source/_data/*.json'],
+ tasks: ['shell:patternlab'],
+ options: {
+ spawn: false
+ }
+ }
+
+You might be able to use `livereload` as well but that hasn't been tested by us yet.
+
+For more information, check out [this post about using Pattern Lab with Grunt](http://bradfrost.com/blog/post/using-grunt-with-pattern-lab/).
diff --git a/packages/docs/src/docs/advanced-keyboard-shortcuts.md b/packages/docs/src/docs/advanced-keyboard-shortcuts.md
new file mode 100644
index 000000000..f338df46c
--- /dev/null
+++ b/packages/docs/src/docs/advanced-keyboard-shortcuts.md
@@ -0,0 +1,36 @@
+---
+title: Keyboard Shortcuts
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ title: Keyboard Shortcuts
+ key: advanced
+ order: 300
+---
+
+> **Note:** This feature is currently disabled. It will be back in a future release of `styleguidekit-assets-default`.
+
+Pattern Lab comes with support for a number of special keyboard shortcuts to make using Pattern Lab easier. These are broken up by where they work or are most useful.
+
+Modifying the viewport:
+
+- **ctrl+shift+0**: set the viewport to 320px
+- **ctrl+shift+s**: set the viewport to "small"
+- **ctrl+shift+m**: set the viewport to "medium"
+- **ctrl+shift+l**: set the viewport to "large"
+- **ctrl+shift+h**: toggle Hay mode
+- **ctrl+shift+d**: toggle disco mode
+
+Modifying the views:
+
+- **ctrl+shift+a**: open/close info panels
+- **ctrl+shift+c**: open/close info panels
+- **cmd+a/ctrl+a**: select the content of the current open tab in code view
+- **ctrl+shift+u**: make the Mustache tab active
+- **ctrl+shift+y**: make the HTML tab active
+- **esc**: close the open view
+
+Other:
+
+- **ctrl+shift+f**: open/close the pattern search
diff --git a/packages/docs/src/docs/advanced-page-follow.md b/packages/docs/src/docs/advanced-page-follow.md
new file mode 100644
index 000000000..f2015e2b1
--- /dev/null
+++ b/packages/docs/src/docs/advanced-page-follow.md
@@ -0,0 +1,44 @@
+---
+title: Multi browser & Multi device Testing with Page Follow
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ title: Multi browser & Multi device Testing with Page Follow
+ key: advanced
+ order: 300
+---
+
+The Pattern Lab's Page Follow feature gives developers the ability to have one browser control other browsers that connect to the Pattern Lab website. Pattern Lab Node utilizes [BrowserSync](http://www.browsersync.io/) to synchronize all connected browsers and devices.
+
+## How to Start and Connect to Pattern Lab with BrowserSync
+
+Running `gulp patternlab:serve` or `grunt patternlab:serve` from the command line of your working directory will start up Pattern Lab with BrowserSync. By default, BrowserSync will output four URLs of note:
+
+1. Local: [http://localhost:3000](http://localhost:3000)
+2. External: http://your.ip.address:3000
+3. UI: [http://localhost:3001](http://localhost:3001)
+4. UI External: http://your.ip.address:3001
+
+Any browsers on your machine will be able access these URLs. Browsers on other machines or devices on the same network should use the external URLs. Connecting to the Pattern Lab website will inform users they are also connected to BrowserSync.
+
+## How to Stop the Page Follow
+
+To stop watching files on Mac OS X and Windows you can press`CTRL+C` in the command line window where the process is running.
+
+## BrowserSync Capabilities
+
+It's strongly recommended to visit [BrowserSync](http://www.browsersync.io/) documentation or the BrowserSync UI at [http://localhost:3001](http://localhost:3001). From this administration interface one can perform the following:
+
+- See all connected devices and browsers
+- Open new tabbed instances of the Pattern Lab website on devices
+- Sync all connected devices
+- Reload all connected devices
+- Scroll all connected devices to the top
+- Toggle mouse click synchronization
+- Toggle scroll synchronization
+- Toggle form submission synchronization
+- Toggle form input synchronization
+- View browsing history of the connect session
+- Toggle remote debugging tools
+- Artificially throttle the network
diff --git a/packages/docs/src/docs/advanced-pattern-lab-nav.md b/packages/docs/src/docs/advanced-pattern-lab-nav.md
new file mode 100644
index 000000000..8a69cb693
--- /dev/null
+++ b/packages/docs/src/docs/advanced-pattern-lab-nav.md
@@ -0,0 +1,33 @@
+---
+title: Modifying Pattern Lab's Navigation
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ title: Modifying Pattern Lab's Navigation
+ key: advanced
+ order: 300
+---
+
+When sharing Pattern Lab with a client it may be beneficial to turn-off certain elements in the default navigation. To turn-off navigation elements, alter the flags inside the `ishControlsHide` object within `patternlab-config.json` and then re-generate the site. The following keys are supported and will hide their respective elements if toggled on:
+
+```javascript
+"ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+},
+```
+
+By default all navigation elements are visible except Hay Mode.
diff --git a/packages/docs/src/docs/advanced-starterkits.md b/packages/docs/src/docs/advanced-starterkits.md
new file mode 100644
index 000000000..48ead77e6
--- /dev/null
+++ b/packages/docs/src/docs/advanced-starterkits.md
@@ -0,0 +1,58 @@
+---
+title: Starterkits
+tags:
+ - docs
+category: advanced
+eleventyNavigation:
+ title: Starterkits
+ key: advanced
+ order: 300
+---
+
+Starterkits are a potent way create or augment a Pattern Lab instance with a baseline set of patterns and assets. They are an important part of the [Pattern Lab Ecosystem](/docs/overview-of-pattern-lab's-ecosystem/) An agency or team could use it for each new client or project. [Several starterkits](https://github.com/pattern-lab?utf8=%E2%9C%93&q=starterkit&type=&language=) already exist to kick your project off, whether you’re looking for a blank start, begin with a demo that showcases Pattern Lab’s features, or start with a popular framework like Bootstrap, Foundation, or Material Design.
+
+## Structure
+
+A Starterkit's structure mirrors that of the default file structure of Pattern Lab. Usually this is found under the `dist/` directory:
+
+```
+_annotations/
+_data/
+_meta/
+_patterns/
+css/
+fonts/
+images/
+js/
+favicon.ico
+```
+
+Teams constructing their own Starterkits should stick to this structure if they wish to publish it externally, else may alter the structure to their [configured `paths`](/docs/editing-the-configuration-options/).
+
+## Installing Starterkits
+
+Open your terminal and navigate to the root of your project. Type:
+
+```
+npm install [starterkit-name]
+gulp patternlab:loadstarterkit --kit=[starterkit-name]
+```
+
+where [starterkit-name] is the name of the Starterkit.
+
+so... a complete example:
+
+```
+npm install @pattern-lab/starterkit-mustache-demo
+gulp patternlab:loadstarterkit --kit=@pattern-lab/starterkit-mustache-demo
+```
+
+The [Pattern Lab Node CLI](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli) will also support installation of Starterkits should you not be using gulp.
+
+## PSA
+
+**LOADING A STARTERKIT WILL OVERWRITE ANY MATCHES INSIDE `./source`** Users can pass another flag `--clean=true` to attempt to delete the contents of `./source` prior to load.
+
+- Sometimes users will run into file permissions issues. It's recommended to run all command prompts as administrator if you can.
+
+`patternlab-config.json` also defines a `starterkitSubDir` key (with a default value of `dist`) which can be used to target a directory inside the starterkit module if need be.
diff --git a/packages/docs/src/docs/advanced-template-language-and-pattern-engines.md b/packages/docs/src/docs/advanced-template-language-and-pattern-engines.md
new file mode 100644
index 000000000..f59dff6b5
--- /dev/null
+++ b/packages/docs/src/docs/advanced-template-language-and-pattern-engines.md
@@ -0,0 +1,28 @@
+---
+title: Template Language and PatternEngines
+heading: Template Language and PatternEngines
+patternEnginesScript: true
+category: advanced
+eleventyNavigation:
+ title: Template Language and PatternEngines
+ key: advanced
+ order: 300
+---
+
+By default Pattern Lab uses the Mustache template language, extended with [pattern parameters](/docs/using-pattern-parameters/). PatternEngines let you add support for a template language of your personal choice. Each PatternEngine has it's own set of features and caveats.
+
+Right now the most mature PatternEngines are Handlebars, Mustache and Twig.
+
+## Official PatternEngines for Node
+
+
+
+## Install and Configure a PatternEngine
+
+1. Install a new PatternEngine that you wish to use. For example, to install the Handlebars engine, run `npm install --save @pattern-lab/engine-handlebars`.
+2. (Optional) Change the `"patternExtension"` property of your config. This sets the panel name and language for the code tab on the styleguide.
+
+You'll need to restart Pattern Lab for changes to take effect. Some PatternEngines may provide further configuration.
diff --git a/packages/docs/src/docs/changes-1-to-2.md b/packages/docs/src/docs/changes-1-to-2.md
new file mode 100644
index 000000000..b167ba607
--- /dev/null
+++ b/packages/docs/src/docs/changes-1-to-2.md
@@ -0,0 +1,10 @@
+---
+title: Pattern Lab 1 to Pattern Lab 2 Changes
+tags:
+ - docs
+eleventyNavigation:
+ title: Pattern Lab 1 to Pattern Lab 2 Changes
+ order: 300
+---
+
+The list of features is coming soon.
diff --git a/packages/docs/src/docs/data-json-mustache.md b/packages/docs/src/docs/data-json-mustache.md
new file mode 100644
index 000000000..cb8d37ec9
--- /dev/null
+++ b/packages/docs/src/docs/data-json-mustache.md
@@ -0,0 +1,108 @@
+---
+title: Introduction to JSON & Mustache Variables
+tags:
+ - docs
+category: data
+eleventyNavigation:
+ title: Introduction to JSON & Mustache Variables
+ key: data
+ order: 300
+---
+
+> This documentation is provided as a simple introduction to using one of the supported data types and one of the supported PatternEngines. The best reference for this topic is the [Mustache documentation](http://mustache.github.io/mustache.5.html) but this should provide a good beginner's primer.
+
+## Simple Variables
+
+At its core JSON is a simple key-value store. This means that any piece of data in JSON has a key and a value. The key is the name of an attribute and the value is what should be shown when that attribute is referenced. Here's a simple example:
+
+```javascript
+"src": "../../images/fpo-avatar.png"
+```
+
+In this case the key is `src` and the value is `../../images/fpo-avatar.png`. Let's look at how we might reference this data in a pattern template. Mustache variables are denoted by the double-curly braces (or mustaches).
+
+```html
+
+```
+
+The Mustache variable is `{% raw %}{{ src }}{% endraw %}`. Note that `src` matches the name of the key in our JSON example. When the Node version of Pattern Lab compile this template the end result will be:
+
+```html
+
+```
+
+Note that `{% raw %}{{ src }}{% endraw %}` was replaced by the value for `src` found in our JSON example.
+
+## Nested Variables
+
+We may want our JSON file to be a little more organized and our Mustache variable names to be a little more descriptive. For example, maybe we have multiple image sizes that we want to provide image sources for. We might organize our JSON key-values this way:
+
+```javascript
+"square": {
+ "src": "../../images/fpo-square.png",
+ "alt": "Square"
+},
+"avatar": {
+ "src": "../../images/fpo-avatar.png",
+ "alt": "Avatar"
+}
+```
+
+Note how their are attributes ( `src`, `alt` ) nested within a larger container ( `square` ). Also note how the attributes are separated by commas. If we wanted to use the attributes for the square image in our pattern we'd write:
+
+```html
+
+```
+
+This would compile to:
+
+```html
+
+```
+
+This nesting makes it easier to read how the attributes are organized in our patterns. The default `data.json` file has several examples of this type of nesting of attributes.
+
+## Rendering HTML in Variables
+
+You may want to include HTML in your variables. By default, Mustache will convert HTML mark-up to their HTML entity equivalents. For example, our JSON may look like:
+
+```javascript
+"lyrics": "Just good ol' boys , wouldn't change if they could, fightin' the system like a true modern day Robin Hood."
+```
+
+Based on our previous Mustache examples you would probably write out your template like so:
+
+```html
+TV Show Lyrics
+{% raw %}{{ lyrics }}{% endraw %}
+```
+
+Unfortunately, that would compile as:
+
+```html
+TV Show Lyrics
+
+ Just <em>good ol' boys</em>, wouldn't change if they could,
+ <strong>fightin'</strong> the system like a true modern day Robin Hood.
+
+```
+
+In order to make sure the mark-up doesn't get converted you must use _triple_ curly brackets like so:
+
+```html
+TV Show Lyrics
+{% raw %}{{{ lyrics }}}{% endraw %}
+```
+
+Now it would compile correctly:
+
+```html
+TV Show Lyrics
+
+ Just good ol' boys , wouldn't change if they could,
+ fightin' the system like a true modern day Robin Hood.
+
+```
diff --git a/packages/docs/src/docs/data-link-variable.md b/packages/docs/src/docs/data-link-variable.md
new file mode 100644
index 000000000..fa0fb18c3
--- /dev/null
+++ b/packages/docs/src/docs/data-link-variable.md
@@ -0,0 +1,42 @@
+---
+title: Linking to Patterns with Pattern Lab's Default `link` Variable
+category: data
+eleventyNavigation:
+ title: Linking to Patterns with Pattern Lab's Default `link` Variable
+ key: data
+ order: 100
+---
+
+You can build patterns that link to one another to help simulate using a real website. This is especially useful when working with the Pages and Templates pattern types. Rather than having to remember the actual path to a pattern you can use the same shorthand syntax you'd use to include one pattern within another. **Important:** Pattern links _do not_ support the same fuzzy matching of names as the shorthand partials syntax does. The basic format is:
+
+```html
+{% raw %}{{ link.pattern-name }}{% endraw %}
+```
+
+For example, if you wanted to add a link to the `article` page from your `blog` page you could write the following:
+
+```html
+Article Headline
+```
+
+This would compile to:
+
+```html
+Article Headline
+```
+
+Additionally, you can use pattern links within JSON to link to other pages, templates, or patterns within Pattern Lab. For instance, if you had a pattern containing the following:
+
+```html
+This is a link
+```
+
+You can set the URL to a pattern link via JSON like so:
+
+```javascript
+{
+ "url" : "link.pages-article"
+}
+```
+
+Using pattern links in JSON is especially helpful at keeping the pattern's structure and content entirely separate.
diff --git a/packages/docs/src/docs/data-overview.md b/packages/docs/src/docs/data-overview.md
new file mode 100644
index 000000000..0ce1caf10
--- /dev/null
+++ b/packages/docs/src/docs/data-overview.md
@@ -0,0 +1,38 @@
+---
+title: Overview of Data
+tags:
+ - docs
+category: data
+eleventyNavigation:
+ title: Overview of Data
+ key: data
+ order: 300
+---
+
+The primary default global source of data used when rendering Pattern Lab patterns can be found in `./source/_data/`.
+
+## Supported Data Formats
+
+The Node version of Pattern Lab only supports JSON.
+
+## Locations for Data
+
+There are three places to store data in Pattern Lab:
+
+- in `./source/_data`.
+- in [pattern-specific](/docs/creating-pattern-specific-values/) files in `./source/_patterns`.
+- in [pseudo-pattern](/docs/using-pseudo-patterns/) files in `./source/_patterns`.
+
+### A Special Note About Pattern Parameters
+
+[Pattern parameters](/docs/using-pattern-parameters/) are a simple find and replace of variables in the included pattern. As such they do not affect the context stack of Mustache and we don't consider them true data. They have no impact on overall data inheritance and they cannot be used any deeper than the included pattern. They are a hack.
+
+## Data Inheritance
+
+Data inheritance in Pattern Lab follows this flow:
+
+```
+Pattern-specific data for the pattern being rendered > Global data in _data
+```
+
+The only data that is loaded to render a pattern is its own data and global data. It will not include the data for any included patterns. For example, the pages template, `article`, might include the molecule, `block-hero`. `block-hero` may have its own pattern-specific data file, `block-hero.json`. The Node version of Pattern Lab **will not** use the `block-hero` data when rendering `article`. It will only use `article.json` (_if available_) and data found in `./source/_data`.
diff --git a/packages/docs/src/docs/data-pattern-specific.md b/packages/docs/src/docs/data-pattern-specific.md
new file mode 100644
index 000000000..495053a8f
--- /dev/null
+++ b/packages/docs/src/docs/data-pattern-specific.md
@@ -0,0 +1,50 @@
+---
+title: Creating Pattern-specific Values
+tags:
+ - docs
+category: data
+eleventyNavigation:
+ title: Creating Pattern-specific Values
+ key: data
+ order: 300
+---
+
+> **Note:** This article uses JSON because it is a standard between with the Node version of Pattern Lab.
+
+Storing data for your atoms, molecules, and organisms in `./source/_data` may work just fine. When fleshing out templates and pages, where data may need to be unique to each page even if they use the same molecules and organisms, data stored in `./source/_data` can become cumbersome. In order to work around this the Node version of Pattern Lab allows you to define pattern-specific data files that allow you to override the default values found in `./source/_data`.
+
+## Setting Up Pattern-specific Data
+
+In order to tell the Node version of Pattern Lab to use pattern-specific data to override the default global data create a JSON file with the same name as the pattern and put it in the same directory as the pattern. For example, if you wanted to provide pattern-specific data for the `article` pattern under the pattern type `pages` your `pages` directory would look like this:
+
+```
+pages/article.mustache
+pages/article.json
+```
+
+## Overriding the Default Variables
+
+To override the global data using pattern-specific data make sure the latter has the same variable names as the former. For example, the 4x3 landscape image source may look like this in `data.json`:
+
+```javascript
+"landscape-4x3": {
+ "src": "../../images/fpo-landscape-4x3.jpg",
+ "alt": "Landscape 4x3 Image"
+}
+```
+
+In our pattern-specific data file, `article.json`, we'd simply copy that structure and provide our own information:
+
+```javascript
+"landscape-4x3": {
+ "src": "../../images/a-team-hero.jpg"
+}
+```
+
+Now the article pattern will display an image of the A-Team when using `{% raw %}{{ landscape-4x3.src }}{% endraw %}`. All other patterns using `{% raw %}{{ landscape-4x3.src }}{% endraw %}` will display the default 4x3 image. Also, note that we **didn't** override the `landscape-4x3.alt` attribute. If we were to use that attribute in our pattern the default value, "Landscape 4x3 Image", would be displayed.
+
+**Important note:** You don't have to override every attribute. You can limit the data in your pattern-specific data file to just those variables you want. The Node version of Pattern Lab will fallback to using the default attributes from `data.json` if the attributes aren't defined in the pattern-specific data file.
+
+## Working With Includes
+
+The only data that is loaded to render a pattern is its own data and global data. It will not include the data for any included patterns. For example, the pages template, `article`, might include the molecule, `block-hero`. `block-hero` may have its own pattern-specific data file, `block-hero.json`. The Node version of Pattern Lab **will not** use the `block-hero` data when rendering `article`. It will only use `article.json` (_if available_) and data found in `./source/_data`.
diff --git a/packages/docs/src/docs/docs.json b/packages/docs/src/docs/docs.json
new file mode 100644
index 000000000..688f78f76
--- /dev/null
+++ b/packages/docs/src/docs/docs.json
@@ -0,0 +1,3 @@
+{
+ "layout": "layouts/docs.njk"
+}
diff --git a/packages/docs/src/docs/editing-source-files.md b/packages/docs/src/docs/editing-source-files.md
new file mode 100644
index 000000000..71af3301a
--- /dev/null
+++ b/packages/docs/src/docs/editing-source-files.md
@@ -0,0 +1,80 @@
+---
+title: Editing Pattern Lab Source Files
+tags:
+ - docs
+category: getting-started
+eleventyNavigation:
+ title: Editing Pattern Lab Source Files
+ key: getting-started
+ order: 20
+---
+
+When editing Pattern Lab you must put your files and edit them in the `./source/` directory. This includes your static assets like [JavaScript, CSS, and images](/docs/managing-pattern-assets/). Each time your site is generated your patterns will be compiled and your static assets will be moved to the `./public/` directory. Because of this you **should not edit** the files in the `./public/` directory.
+
+## Pattern Lab Directories
+
+For the most part you can organize `./source/` anyway you see fit. There are a few Pattern Lab-specific directories though. They are:
+
+- `_annotations/` - where your annotations reside. [learn more](/docs/adding-annotations/).
+- `_data/` - where the global data used to render your patterns resides. [learn more](/docs/overview-of-data/).
+- `_meta/` - where the header and footer that get applied to all of your patterns resides. [learn more](/docs/modifying-the-pattern-header-and-footer/).
+- `_patterns/` - where your patterns, pattern documentation, and pattern-specific data reside. [learn more](/docs/reorganizing-patterns/).
+
+## Configuring Pattern Lab Directories
+
+All Pattern Lab directories can be configured to suit your needs.
+
+```js
+// base directories
+exportDir: 'value'; // default is exports. where clean mark-up sans PL code is exported to.
+publicDir: 'value'; // default is public
+sourceDir: 'value'; // default is source
+
+// exportDir is the base directory for the following directories (e.g. ./exports/patterns)
+patternExportDir: 'value'; // default is patterns
+
+// publicDir is the base directory for the following directories (e.g. ./public/patterns)
+componentDir: 'value'; // default is patternlab-components. where plugin components are installed.
+patternPublicDir: 'value'; // default is patterns
+
+// sourceDir is the base directory for the following directories (e.g. ./source/_patterns)
+annotationsDir: 'value'; // default is _annotations
+dataDir: 'value'; // default is _data
+metaDir: 'value'; // default is _meta
+patternSourceDir: 'value'; // default is _patterns
+```
+
+In the Node version of Pattern Lab you can modify the following configuration options in `patternlab-config.json`:
+
+```javascript
+"paths" : {
+ "source" : {
+ "root": "./source/",
+ "patterns" : "./source/_patterns/",
+ "data" : "./source/_data/",
+ "meta": "./source/_meta/",
+ "annotations" : "./source/_annotations/",
+ "styleguide" : "./node_modules/styleguidekit-assets-default/dist/",
+ "patternlabFiles" : "./node_modules/styleguidekit-mustache-default/views/",
+ "js" : "./source/js",
+ "images" : "./source/images",
+ "fonts" : "./source/fonts",
+ "css" : "./source/css/"
+ },
+ "public" : {
+ "root" : "./public/",
+ "patterns" : "./public/patterns/",
+ "data" : "./public/styleguide/data/",
+ "annotations" : "./public/annotations/",
+ "styleguide" : "./public/styleguide/",
+ "js" : "./public/js",
+ "images" : "./public/images",
+ "fonts" : "./public/fonts",
+ "css" : "./public/css"
+ }
+}
+```
+
+## Watching for Source File Changes
+
+Manually generating the Pattern Lab website after each change can be cumbersome. The Node version of Pattern Lab comes with the ability to watch files in the `./source/` directory for changes and re-generate the site automatically. The Pattern Lab website can also be automatically reloaded..
diff --git a/packages/docs/src/docs/installation.md b/packages/docs/src/docs/installation.md
new file mode 100644
index 000000000..e205aa486
--- /dev/null
+++ b/packages/docs/src/docs/installation.md
@@ -0,0 +1,52 @@
+---
+title: Installing Pattern Lab
+tags:
+ - docs
+category: getting-started
+eleventyNavigation:
+ title: Installing Pattern Lab
+ key: getting-started
+ order: 0
+---
+
+## Step 1: Install requirements
+
+Make sure you have [Node.js](https://nodejs.org/en/download/) installed before setting up Pattern Lab.
+
+## Step 2: Run the create Pattern Lab command
+
+Open [the command line](https://tutorial.djangogirls.org/en/intro_to_command_line/) and run the following command:
+
+```
+npm create pattern-lab
+```
+
+This will bring up an installation menu that presents the following steps:
+
+## Step 3: Choose a directory
+
+**`Please specify a directory for your Pattern Lab project.`** Choose the directory where you want to install Pattern Lab. The default location is the current directory.
+
+## Step 4: Choose templating language
+
+**`What templating language do you want to use with Pattern Lab?`** This determines what templating engine you'll use to author components. The options are:
+
+- **`Handlebars`** - uses the [Handlebars](https://handlebarsjs.com/) templating engine
+- **`Twig (PHP)*`** - uses the [Twig](https://twig.symfony.com/) templating engine
+
+**\*A note on Twig:** while Pattern Lab is powered by Node, behind the scenes Twig files are compiled by Twig PHP, _not_ by twig.js (which isn't fully on par with Twig PHP).
+
+## Step 5: Choose initial patterns
+
+**`What initial patterns do you want included in your project?`** - Choose the Starterkit you want to begin your project with. The options are:
+
+- **`Handlebars base patterns`** `(some basic patterns to get started with)` - TODO: include demo link
+- **`Handlebars demo patterns`** `(full demo website and patterns)` - TODO: include demo link
+- **`Twig (PHP) demo patterns`** `(full demo website and patterns)` - TODO: include demo link
+- **`Custom starterkit`** - point to a custom Pattern Lab starterkit TODO: include instructions on including custom starterkits
+- **`Blank project (no patterns)`** - This won't include any initial patterns in your project so you can start completely from scratch
+
+## Step 6: Confirm your choices
+
+**`Are you happy with your choices? (Hit enter for YES)?`** - Confirm your choices, and when done the Pattern Lab installation will begin.
+
diff --git a/packages/docs/src/docs/pattern-add-new.md b/packages/docs/src/docs/pattern-add-new.md
new file mode 100644
index 000000000..4973b0547
--- /dev/null
+++ b/packages/docs/src/docs/pattern-add-new.md
@@ -0,0 +1,25 @@
+---
+title: Adding New Patterns
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Adding New Patterns
+ key: patterns
+ order: 70
+---
+
+To add new patterns to the Node version of Pattern Lab just add new Mustache templates under the appropriate pattern type or pattern subtype directories in `./source/_patterns`. For example, let's add a new pattern under the pattern type "molecules" and the pattern sub-type "blocks". The `./source/_patterns/molecules/blocks/` directory looks like:
+
+ block-hero.mustache
+ headline-byline.mustache
+ media-block.mustache
+
+If we want to add a new pattern we simply tack it onto the end:
+
+ block-hero.mustache
+ headline-byline.mustache
+ media-block.mustache
+ new-pattern.mustache
+
+If you want more control over their ordering please refer to "[Reorganizing Patterns](/docs/reorganizing-patterns/)."
diff --git a/packages/docs/src/docs/pattern-adding-annotations.md b/packages/docs/src/docs/pattern-adding-annotations.md
new file mode 100644
index 000000000..2c535188d
--- /dev/null
+++ b/packages/docs/src/docs/pattern-adding-annotations.md
@@ -0,0 +1,70 @@
+---
+title: Adding Annotations
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Adding Annotations
+ key: patterns
+ order: 180
+---
+
+Annotations provide an easy way to add notes to elements that may appear inside patterns. Annotations can be saved as a single JSON file at `./source/_annotations/annotations.js` or as multiple Markdown files in `./source/_annotations/`. They're _not_ tied to any specific patterns. When annotations are active they are compared against every pattern using a CSS selector syntax.
+
+## The Elements of an Annotation
+
+The elements of an annotation are:
+
+- **el** - the selector to be used to attach the annotation to a pattern
+- **title** - the title for a given annotation
+- **comment** - the description for a given annotation
+
+## JSON Example
+
+This is an example of an annotation saved as part of `annotations.js` that will be added to an element with the class `logo`:
+
+```javascript
+{
+ "el": ".logo",
+ "title" : "Logo",
+ "comment": "The logo image is an SVG file, which ensures that the logo displays crisply even on high resolution displays. A PNG fallback is provided for browsers that don't support SVG images.Further reading: Optimizing Web Experiences for High Resolution Screens
"
+}
+```
+
+## Markdown Example
+
+This is an example of an annotation saved as part of `annotations.md` that will be added to an element with the class `logo`:
+
+```
+---
+el: .logo
+title: Logo
+---
+The logo image is an SVG file, which ensures that the logo displays crisply even on high resolution displays. A PNG fallback is provided for browsers that don't support SVG images.
+
+Further reading: [Optimizing Web Experiences for High Resolution Screens](http://bradfrostweb.com/blog/mobile/hi-res-optimization/)
+```
+
+To separate multiple annotations within one file use `~*~` between annotations.
+
+```
+---
+el: .logo
+title: Logo
+---
+The logo image is an SVG file, which ensures that the logo displays crisply even on high resolution displays. A PNG fallback is provided for browsers that don't support SVG images.
+
+Further reading: [Optimizing Web Experiences for High Resolution Screens](http://bradfrostweb.com/blog/mobile/hi-res-optimization/)
+~*~
+---
+el: .hamburger
+title: Sandwiches Considered Harmful
+---
+According to everyone, hamburger menus are not obvious, and obvious always wins.
+
+Further reading: [Hamburger Menus and Hidden Navigation Hurt UX Metrics](https://www.nngroup.com/articles/hamburger-menus/)
+```
+
+## Viewing Annotations
+
+In order to view annotations click "Show Pattern Info" in the Pattern Lab toolbar.
diff --git a/packages/docs/src/docs/pattern-converting.md b/packages/docs/src/docs/pattern-converting.md
new file mode 100644
index 000000000..795eda217
--- /dev/null
+++ b/packages/docs/src/docs/pattern-converting.md
@@ -0,0 +1,20 @@
+---
+title: Converting Old Patterns
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Converting Old Patterns
+ key: patterns
+ order: 190
+---
+
+You may have invested time in building patterns for Brad's original edition of Pattern Lab but now want to convert them so they can be used with the new PHP version of Pattern Lab. To convert them all you need to do is swap out the old `inc()` calls for the Mustache-based [shorthand partials syntax](/docs/including-patterns/). For example, let's say this was a call to a pattern using the original syntax:
+
+
+
+The new Mustache-based shorthand partials syntax would be:
+
+ {% raw %}{{> atoms-logo }}{% endraw %}
+
+The only real difference between the two is that the pattern type, e.g. `atoms`, has to be exact when using the Mustache partials syntax. Otherwise, it should be very easy to convert between the two formats.
diff --git a/packages/docs/src/docs/pattern-documenting.md b/packages/docs/src/docs/pattern-documenting.md
new file mode 100644
index 000000000..d26d6d75e
--- /dev/null
+++ b/packages/docs/src/docs/pattern-documenting.md
@@ -0,0 +1,50 @@
+---
+title: Documenting Patterns
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Documenting Patterns
+ key: patterns
+ order: 110
+---
+
+Pattern documentation gives developers and designers the ability to provide context for their patterns. The documentation file consists of Markdown with YAML front matter. It should follow this format:
+
+```
+---
+title: Title for my pattern
+---
+This is a *Markdown* description of my pattern.
+```
+
+The `title` attribute is used in Pattern Lab's navigation as well as in the styleguide views. The `description` is used in the styleguide views.
+
+Pattern documentation needs to have a `.md` file extension and match the name of the pattern it's documenting. For example, to document the following pattern:
+
+ 00-atoms/images/landscape-16x9.mustache
+
+We'd name our documentation file:
+
+ 00-atoms/images/landscape-16x9.md
+
+## Documenting Pseudo-Patterns
+
+To add documentation to [pseudo-patterns](/docs/using-pseudo-patterns/), replace the tilde sign (`~`) with a dash (`-`) when naming your documentation file.
+
+For example, to document the following pseudo-pattern:
+
+```
+00-atoms/button/button~red.mustache
+```
+
+We'd name our documentation file:
+
+```
+00-atoms/button/button-red.md
+```
+
+## Adding More Attributes to the Front Matter
+
+A future update of Pattern Lab will support more front matter attributes including: state, order, hidden, links and tags.
+It will also support adding custom attributes that could be utilized by plugins. For example, GitHub issues related to patterns.
diff --git a/packages/docs/src/docs/pattern-header-footer.md b/packages/docs/src/docs/pattern-header-footer.md
new file mode 100644
index 000000000..ae61da38d
--- /dev/null
+++ b/packages/docs/src/docs/pattern-header-footer.md
@@ -0,0 +1,26 @@
+---
+title: Modifying the Pattern Header & Footer
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Modifying the Pattern Header & Footer
+ key: patterns
+ order: 130
+---
+
+To add your own assets like JavaScript and CSS to your patterns' header and footer you need to modify two files:
+
+- `./source/_meta/_00-head.mustache`
+- `./source/_meta/_01-foot.mustache`
+
+These files are added to every rendered pattern, "view all" page and style guide. To see your changes simply re-generate your site.
+
+## Important: Don't Remove Two Things...
+
+**Do not remove the following two lines in these patterns:**
+
+- a tag referencing `patternLabHead` in `_00-head.mustache`
+- a tag referencing `patternLabFoot` in `_00-foot.mustache`
+
+Pattern Lab will not so mysteriously stop working if you do.
diff --git a/packages/docs/src/docs/pattern-hiding.md b/packages/docs/src/docs/pattern-hiding.md
new file mode 100644
index 000000000..52ba3acae
--- /dev/null
+++ b/packages/docs/src/docs/pattern-hiding.md
@@ -0,0 +1,22 @@
+---
+title: Hiding Patterns in the Navigation
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Hiding Patterns in the Navigation
+ key: patterns
+ order: 170
+---
+
+To remove a pattern from Pattern Lab's drop-down navigation and style guide add an underscore (`_`) to the beginning of the pattern name. For example, we may have a Google Map-based pattern that we don't need for a particular project. The path might look like:
+
+ molecules/media/map.mustache
+
+To "hide" the pattern we add the underscore and re-generate our site:
+
+ molecules/media/_map.mustache
+
+A hidden pattern can still be included in other patterns.
+
+Not all PatternEngines support hiding patterns.
diff --git a/packages/docs/src/docs/pattern-including.md b/packages/docs/src/docs/pattern-including.md
new file mode 100644
index 000000000..8ca8f03fa
--- /dev/null
+++ b/packages/docs/src/docs/pattern-including.md
@@ -0,0 +1,83 @@
+---
+title: Including Patterns
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Including Patterns
+ key: patterns
+ order: 90
+---
+
+To include one pattern within another, for example to create a molecule from several atoms, you can either use:
+
+- a shorthand include syntax or
+- the default include syntax for the template language you're using (e.g. Mustache, Twig, Handlebars).
+
+## The Shorthand Include Syntax
+
+The shorthand include syntax is less verbose than the default include syntax for many template languages. The shorthand syntax uses the following format:
+
+ [patternType]-[patternName]
+
+For example, to include the following pattern in a molecule:
+
+ 00-atoms/images/landscape-16x9.mustache
+
+The shorthand include syntax would be:
+
+ atoms-landscape-16x9
+
+The pattern type matches the top-level folder and is `atoms`. The pattern name matches the template file and is `landscape-16x9`. Any digits used for ordering are _dropped_ from both the pattern type and pattern name. Pattern subtypes are _never_ a part of the shorthand include syntax. This way patterns can be re-organized within a pattern type and/or by using digits without needing to change your pattern includes.
+
+The following are examples of using the shorthand include syntax with our supported PatternEngines:
+
+```
+{% raw %}{{> atoms-landscape-16x9 }} // Mustache{% endraw %}
+{% raw %}{% include "atoms-landscape-16x9" %} // Twig{% endraw %}
+```
+
+The shorthand syntax also allows for fuzzy matching on pattern names. This means that if you feel your pattern name is going to be unique within a given pattern type you can supply just the unique part of the pattern name and the partial will be included correctly. For example, using the shorthand syntax the pattern `atoms-landscape-16x9.mustache` could be written as:
+
+ atoms-16x9
+
+_Warning:_ Because subtypes are not included in the shorthand include syntax a given pattern name needs to be unique within its _pattern type_ and not just its pattern subtype. If you run into this problem you can do one of two things:
+
+- use the default include syntax for your template language or
+- give your pattern a unique name and use [the pattern's documentation](/docs/documenting-patterns/) to provide the pattern name
+
+## The Default Include Syntax
+
+If you need more specificity when including patterns the Node version of Pattern Lab also support the include syntax for the template language that you're using. For example, the syntax for Mustache is the path to the pattern minus the `.mustache` extension. Let's say we wanted to include the following pattern in a molecule:
+
+ 00-atoms/images/landscape-16x9.mustache
+
+The default Mustache include syntax would be:
+
+```handlebars
+{% raw %}{{> 00-atoms/images/landscape-16x9 }}{% endraw %}
+```
+
+**Important:** Unlike the shorthand include syntax the template language specific include syntax **must** include any digits used for ordering and subtype directories. Pattern paths need to be updated when either is changed for a given pattern.
+
+## Examples and Gotchas
+
+Here are some examples of how to include patterns as well as some gotchas.
+
+```handlebars
+{% raw %}// partials to match
+00-atoms/global/05-test.mustache
+00-atoms/global/06-test.mustache
+00-atoms/global/test.mustache
+00-atoms/global/test-with-picture.mustache
+
+// using the shorthand partials syntax
+{{> atoms-test }} // will match 00-atoms/global/05-test.mustache
+ // using the shorthand syntax you'll never be able to match 06-test nor test in this scenario
+{{> atoms-test-with-picture }} // will match 00-atoms/global/test-with-picture.mustache
+{{> atoms-test-wit }} // will match 00-atoms/global/test-with-picture.mustache
+
+// using the default mustache partials syntax
+{{> atoms/global/05-test }} // won't match anything because atoms is missing its digits
+{{> 00-atoms/global/06-test }} // will match 00-atoms/global/06-test.mustache{% endraw %}
+```
diff --git a/packages/docs/src/docs/pattern-linking.md b/packages/docs/src/docs/pattern-linking.md
new file mode 100644
index 000000000..fe340d4c4
--- /dev/null
+++ b/packages/docs/src/docs/pattern-linking.md
@@ -0,0 +1,113 @@
+---
+title: Pattern Lab's Special Query String Variables
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Pattern Lab's Special Query String Variables
+ key: patterns
+ order: 100
+---
+
+Pattern Lab comes with support for a number of special query string variables to help you share patterns with clients. These query string variables include ways to link to patterns, set the Pattern Lab viewport to a specific width, open various views as well as start Hay and disco modes on page load. There are lots of options:
+
+- [Linking to Specific Patterns](#link-pattern)
+- [Setting the Default Width for the Viewport](#default-width)
+- [Opening Annotations View on Page Load](#annotations-view)
+- [Opening Code View on Page Load](#code-view)
+- [Starting Hay Mode on Page Load](#hay-mode)
+- [Starting Disco Mode on Page Load](#disco-mode)
+
+## Linking to Specific Patterns
+
+You can link directly to any pattern listed on the Pattern Lab website. This might be useful when asking clients for feedback on a particular template or page pattern. If you want to [link from one pattern to another use the `link` variable](/docs/linking-to-patterns-with-pattern-lab's-default-link-variable/).
+
+### Copy & Paste
+
+The simplest method is to copy the address found in the address bar.
+
+### Manually Creating the Link
+
+It's also very easy to create a link manually. Simply append `?p=pattern-name` to the end of the address for your Pattern Lab website. For example, if we wanted to link to the `templates-article` pattern we'd add the following to the address for our Pattern Lab website:
+
+```
+?p=templates-article
+```
+
+The direct link feature supports the [shorthand partials syntax](/docs/including-patterns/) found in the Node version of Pattern Lab. Just provide part of a pattern name and Pattern Lab will attempt to resolve it.
+
+## Setting the Default Width for the Viewport
+
+You can load a specific viewport size by using the `w` query string variable.
+
+```
+http://patternlab.localhost/?w=320 (sets the viewport to 320px)
+http://patternlab.localhost/?w=400px (sets the viewport to 400px)
+http://patternlab.localhost/?w=40em (sets the viewport to 40em or 640px)
+```
+
+And it works with the `p` query string variable so you can also do:
+
+```
+http://patternlab.localhost/?p=atoms-landscape-4x3&w=400px
+```
+
+## Opening Annotations View on Page Load
+
+When sending a particular pattern to a client for review you may not want to include directions for how to open annotations. You can force the annotations view to open on page load by using the `view` query string variables with the values `annotations` or `a`:
+
+```
+http://patternlab.localhost/?p=templates-homepage&view=annotations
+http://patternlab.localhost/?p=templates-homepage&view=a
+```
+
+You can also force the annotations panel to scroll to a particular item by including the query string variable `number`. To scroll to the fifth element in the list of annotations you'd do the following:
+
+```
+http://patternlab.localhost/?p=templates-homepage&view=annotations&number=5
+```
+
+## Opening Code View on Page Load
+
+When sending a particular pattern to a client for review you may not want to include directions for how to open the code view. You can force the code view to open on page load by using the `view` query string variables with the values `code` or `c`:
+
+```
+http://patternlab.localhost/?p=templates-homepage&view=code
+http://patternlab.localhost/?p=templates-homepage&view=c
+```
+
+You can also force the HTML tab mark-up to be highlighted for copying by including the query string variable `copy`.
+
+```
+http://patternlab.localhost/?p=templates-homepage&view=code©=true
+```
+
+## Starting Hay Mode on Page Load
+
+You can start Hay mode automatically when the page is loaded by using the `h` or `hay` query string variables.
+
+```
+http://patternlab.localhost/?h=true
+http://patternlab.localhost/?hay=true
+```
+
+And it works with the `p` query string variable so you can also do:
+
+```
+http://patternlab.localhost/?p=atoms-landscape-4x3&h=true
+```
+
+## Starting Disco Mode on Page Load
+
+You can start disco mode automatically when the page is loaded by using the `d` or `disco` query string variables.
+
+```
+http://patternlab.localhost/?d=true
+http://patternlab.localhost/?disco=true
+```
+
+And it works with the `p` query string variable so you can also do:
+
+```
+http://patternlab.localhost/?p=atoms-landscape-4x3&d=true
+```
diff --git a/packages/docs/src/docs/pattern-managing-assets.md b/packages/docs/src/docs/pattern-managing-assets.md
new file mode 100644
index 000000000..2f99b5879
--- /dev/null
+++ b/packages/docs/src/docs/pattern-managing-assets.md
@@ -0,0 +1,55 @@
+---
+title: Managing Pattern Assets
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Managing Pattern Assets
+ key: patterns
+ order: 120
+---
+
+Assets for patterns - including JavaScript, CSS, and images - should be stored and edited in the `./source/` directory. Pattern Lab will move these assets to the `./public/` directory for you when you generate your site or when you watch the `./source/` directory for changes. _You can name and organize your assets however you like._ If you would like to use `./source/stylesheets/` to store your styles instead of `./source/css/` you can do that. The structure will be maintained when they're moved to the `./public/` directory.
+
+Pattern Lab ships with copy tasks in the `Gruntfile.js` or `Gulpfile.js` of [the Editions](https://github.com/pattern-lab/?utf8=%E2%9C%93&query=edition-node) that copy your assets for you.
+
+This structure is meant to be extended to suit your purposes. Change targets, move files, or ignore certain filetypes altogether. **Note**: If you make changes to `Gruntfile.js` or `Gulpfile.js`, such as to copy a new directory, and have auto re-generation and browser reload enable, you will need to stop and start your tasks to pick up the changes.
+
+## Configuring Asset Locations
+
+Pattern Lab has a configuration object which allows users to separate source patterns and assets from what is generated. The paths are managed within `patternlab-config.json`, found at the root of the edition project. The contents are sampled here:
+
+```javascript
+ "paths" : {
+ "source" : {
+ "root": "./source/",
+ "patterns" : "./source/_patterns/",
+ "data" : "./source/_data/",
+ "meta": "./source/_meta/",
+ "annotations" : "./source/_annotations/",
+ "styleguide" : "./node_modules/styleguidekit-assets-default/dist/",
+ "patternlabFiles" : "./node_modules/styleguidekit-mustache-default/views/",
+ "js" : "./source/js",
+ "images" : "./source/images",
+ "fonts" : "./source/fonts",
+ "css" : "./source/css/"
+ },
+ "public" : {
+ "root" : "./public/",
+ "patterns" : "./public/patterns/",
+ "data" : "./public/styleguide/data/",
+ "annotations" : "./public/annotations/",
+ "styleguide" : "./public/styleguide/",
+ "js" : "./public/js",
+ "images" : "./public/images",
+ "fonts" : "./public/fonts",
+ "css" : "./public/css"
+ }
+ }
+```
+
+Note how some sets of files even extend into the "vendor" `./node_modules/` directory. Relative paths are the default but absolute paths are supported also. You may also use these paths within the Grunt or Gulp taskfiles by referring to the `paths()` function.
+
+## Adding Assets to the Pattern Header & Footer
+
+Static assets like Javascript and CSS **are not** added automagically to your patterns. You need to add them manually to the [shared pattern header and footer](/docs/modifying-the-pattern-header-and-footer/).
diff --git a/packages/docs/src/docs/pattern-organization.md b/packages/docs/src/docs/pattern-organization.md
new file mode 100644
index 000000000..ff3cab5ad
--- /dev/null
+++ b/packages/docs/src/docs/pattern-organization.md
@@ -0,0 +1,52 @@
+---
+title: Overview of Patterns
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Overview of Patterns
+ key: patterns
+ order: 10
+---
+
+Patterns can be found in `./source/_patterns/`. Patterns must be written in the template languages supported by Pattern Lab's PatternEngines. For Node there are [several more PatternEngines to choose from](/docs/template-language-and-patternengines/).
+
+## How Patterns Are Organized
+
+Patterns are organized in a nested folder structure under `./source/_patterns/`. This allows the Node version of Pattern Lab to automatically find and build assets like the "view all" pages and the drop down navigation. Pattern Lab uses the following organizational structure:
+
+ [patternType]/[patternSubtype]/[patternName].[patternExtension]
+
+Here are the parts:
+
+- `patternType` denotes the overall pattern type. If using Atomic Design this will be something like "atoms" or "molecules" but it can be anything you want. For example, "components" or "elements."
+- `patternSubtype` denotes the sub-type of pattern and is _optional_. This helps to organize patterns under an overall pattern type in the drop downs in Pattern Lab. For example, a "blocks" pattern subtype under the "molecules" pattern type.
+- `patternName` is the name of the pattern. This is used when the pattern is displayed in the drop downs in Pattern Lab.
+- `patternExtension` is the file extension that tells the PatternEngine to render the pattern. For example, `.mustache`.
+
+Dashes (`-`) in your pattern types, pattern subtypes or pattern names will be replaced with spaces. For example, if you want a pattern to be displayed in the drop-down as "Hamburger Navigation" and you're using the Mustache PatternEngine you should name it `hamburger-navigation.mustache`.
+
+## Pattern Type Naming Conventions
+
+You do **not** have to use the Atomic Design naming convention when organizing your patterns. You can name your pattern types whatever you like and use as many or as few as you like. For example, you could use the pattern types Nachos, Tacos, and Burritos instead of Atoms, Molecules, and Organisms.
+
+## Ordering
+
+By default, pattern types, pattern subtypes and patterns are ordered alphabetically. If you want more control over their ordering please refer to "[Reorganizing Patterns](/docs/reorganizing-patterns/)."
+
+## Deeper Nesting
+
+Node versions support nesting of folders under `patternSubtype`. For example, you may want to organize your [pattern documentation](/docs/documenting-patterns/), pattern, Sass files and [pseudo-patterns](/docs/using-pseudo-patterns/) in one directory like so:
+
+ - molecules/
+ - blocks/
+ - media-block/
+ - media-block.md
+ - media-block.mustache
+ - media-block.scss
+ - media-block~variant1.json
+ - media-block~variant2.json
+
+In this example the `media-block/` directory is ignored for the purposes of generating breadcrumbs and navigation in the Pattern Lab front-end but the documentation, pattern and pseudo-patterns are still rendered.
+
+Folders can be nested under `media-block/` if desired but this is discouraged because of possible collisions when using the [shorthand partial syntax](/docs/including-patterns/).
diff --git a/packages/docs/src/docs/pattern-parameters.md b/packages/docs/src/docs/pattern-parameters.md
new file mode 100644
index 000000000..8aea3eca8
--- /dev/null
+++ b/packages/docs/src/docs/pattern-parameters.md
@@ -0,0 +1,105 @@
+---
+title: Using Pattern Parameters
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Using Pattern Parameters
+ key: patterns
+ order: 150
+---
+
+**Important:** Pattern parameters are supported by the Node Mustache PatternEngines. Other template languages provide better solutions to this problem.
+
+Pattern parameters are a **simple** mechanism for replacing Mustache variables in an included pattern. They are limited to replacing variables in the included pattern and **only** the included pattern. They are especially useful when including a single pattern multiple times in a molecule, template, or page and you want to supply unique data to that pattern each time it's included. Pattern parameters **do not** currently support the following:
+
+- sub-lists (_e.g. iteration of a section_),
+- long strings of text (_can be unwieldy_)
+- modifying/replacing variables in patterns included _within_ the targeted pattern
+
+Pattern parameters are Pattern Lab-specific, have no relationship to Mustache, and are implemented outside of Mustache. Learn more about pattern parameters:
+
+- [The Pattern Parameter Syntax](#pattern-parameter-syntax)
+- [Adding Pattern Parameters to Your Pattern Partial](#adding-pattern-parameters)
+- [Toggling Sections with Pattern Parameters](#toggling-sections)
+
+## The Pattern Parameter Syntax
+
+The attributes listed in the pattern parameters need to match Mustache variable names in your pattern. The values listed for each attribute will replace the Mustache variables. For example:
+
+ {% raw %}{{> patternType-pattern(attribute1: value, attribute2: "value string") }}{% endraw %}
+
+Again, pattern parameters are a simple find and replace of Mustache variables with the supplied values.
+
+## Adding Pattern Parameters to Your Pattern Partial
+
+Let's look at a simple example for how we might use pattern parameters. First we'll set-up a pattern that might be included multiple times. We'll make it a simple "message" pattern with a single Mustache variable of `message`.
+
+```html
+{% raw %}{{ message }}{% endraw %}
+```
+
+We'll organize it under Atoms > Global and call it "message" so it'll have the pattern partial of `atoms-message`.
+
+Now let's create a pattern that includes our message pattern partial multiple times. It might look like this.
+
+```html
+
+ {% raw %}{{> atoms-message }}{% endraw %}
+
+ A bunch of extra information
+
+ {% raw %}{{> atoms-message }}{% endraw %}
+
+```
+
+Using `data.json` or a pattern-specific JSON file we wouldn't be able to supply separate messages to each pattern partial. For example, if we defined `message` in our `data.json` as "this is an alert" then "this is an alert" would show up twice when our parent pattern was rendered.
+
+Instead we can use pattern parameters to supply unique messages for each instance. So let's show what that would look like:
+
+```html
+{% raw %}
+
+ {{> atoms-message(message: "this is an alert message") }}
+
+ A bunch of extra information
+
+ {{> atoms-message(message: "this is an informational message") }}
+
+{% endraw %}
+```
+
+Now each pattern would have its very own message.
+
+## Toggling Sections with Pattern Parameters
+
+While pattern parameters are not a one-to-one match for Mustache they do offer the ability to toggle sections of content. For example we might have the following in a generic header pattern called `organisms-header`:
+
+```html
+{% raw %}
+
+ {{# emergency }}
+
Emergency!!!
+ {{/ emergency }}
+
+
+{% endraw %}
+```
+
+We call the header pattern in a template like so:
+
+```
+{% raw %}{{> organisms-header }}{% endraw %}
+... stuff ...
+```
+
+By default, if we don't have an `emergency` attribute in our `data.json` or the pattern-specific JSON file for the template the emergency alert will never be rendered. Instead of modifying either of those two files we can use a boolean pattern param to show it instead like so:
+
+```
+{% raw %}{{> organisms-header(emergency: true) }}{% endraw %}
+... stuff ...
+```
+
+Again, because pattern parameters aren't leveraging Mustache this may not fit the normal Mustache workflow. We still wanted to offer a way to quickly turn on and off sections of an included pattern.
diff --git a/packages/docs/src/docs/pattern-pseudo-patterns.md b/packages/docs/src/docs/pattern-pseudo-patterns.md
new file mode 100644
index 000000000..6018113a8
--- /dev/null
+++ b/packages/docs/src/docs/pattern-pseudo-patterns.md
@@ -0,0 +1,73 @@
+---
+title: Using Pseudo-Patterns
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Using Pseudo-Patterns
+ key: patterns
+ order: 140
+---
+
+Pseudo-patterns give developers and designers the ability to quickly build multiple unique variants of an existing pattern. This feature is especially useful when developing template- and page-style patterns or showing the states of other patterns.
+
+## The Pseudo-Pattern File Naming Convention
+
+Pseudo-patterns are similar to [pattern-specific JSON files](/docs/creating-pattern-specific-values/) but are hinted in such a way that a developer can build a variant of an existing pattern. The basic syntax:
+
+ patternName~pseudo-pattern-name.json
+
+The tilde (`~`) and `.json` file extension are the hints that Pattern Lab uses to determine that this is a pseudo-pattern. The `patternName` tells Pattern Lab which existing pattern it should use when rendering the pseudo-pattern. The JSON file itself works exactly like the [pattern-specific JSON file](/docs/creating-pattern-specific-values/). It has the added benefit that the pseudo-pattern will also inherit any values from the existing pattern's pattern-specific JSON file.
+
+From a navigation and naming perspective `patternName` and `pseudoPatternName` will be combined.
+
+## Adding Pseudo-Patterns to Your Project
+
+Adding a pseudo-pattern is as simple as naming it correctly and following the [pattern-specific JSON file](/docs/creating-pattern-specific-values/) instructions for organizing its content. Let's look at a simple example where we want to show an emergency notification on our homepage Mustache template. Our `03-templates/` directory might look like this:
+
+ article.mustache
+ blog.mustache
+ homepage.mustache
+
+Our `homepage.mustache` template might look like this:
+
+```html
+{% raw %}
+
+ {{# emergency }}
+
Oh Noes! Emergency!
+ {{/ emergency }} { ...a bunch of other content... }
+
+{% endraw %}
+```
+
+If our `_data.json` file doesn't give a value for `emergency` that section will never show up when `homepage.mustache` is rendered. Obviously we'd need to show _both_ the regular and emergency states of the homepage but we don't want to duplicate the entire `homepage.mustache` template. That would be a maintenance nightmare. So let's add our pseudo-pattern:
+
+```
+article.mustache
+blog.mustache
+homepage.mustache
+homepage~emergency.json
+```
+
+In our pseudo-pattern, `00-homepage~emergency.json`, we add our `emergency` attribute:
+
+```javascript
+{% raw %}{
+ "emergency": true
+}{% endraw %}
+```
+
+Now when we generate our site we'll have our homepage template rendered twice. Once as the regular template and once as a pseudo-pattern showing the emergency section. Note that the pseudo-pattern will show up in our navigation as `Homepage Emergency`.
+
+## Using Pseudo-Patterns as Pattern Includes
+
+By default, pseudo-patterns **cannot** be used as pattern includes. The data included in the pseudo-pattern, the bit that actually controls the magic, cannot be accessed when rendering the pattern include.
+
+## Re-ordering Pseudo-Patterns
+
+To learn how to re-order pseudo-patterns, check the documentation for [Reorganizing Patterns](/docs/reorganizing-patterns/).
+
+## Documenting Pseudo-Patterns
+
+To learn how to document pseudo-patterns, check the documentation for [Documenting Patterns](/docs/documenting-patterns/) to learn more.
diff --git a/packages/docs/src/docs/pattern-reorganizing.md b/packages/docs/src/docs/pattern-reorganizing.md
new file mode 100644
index 000000000..05978c80d
--- /dev/null
+++ b/packages/docs/src/docs/pattern-reorganizing.md
@@ -0,0 +1,50 @@
+---
+title: Reorganizing Patterns
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Reorganizing Patterns
+ key: patterns
+ order: 80
+---
+
+By default, the Node version of Pattern Lab organizes pattern types, pattern subtypes, and patterns alphabetically when displaying them in the drop-down navigation, pattern subtype "view all" pages, and the "all" style guide. This may not meet your needs. You can re-order pattern types, pattern subtypes and patterns by prefixing them with two digit numbers.
+
+For example, we'll look at how we can re-organize patterns. Using alphabetical ordering the `lists` pattern subtype in `atoms` looks like:
+
+```
+definition.mustache
+ordered.mustache
+unordered.mustache
+```
+
+This is also the order they'll show up in the drop-down navigation. Because you rarely need to see the definition list pattern maybe you want to have it show up last in the navigation. To re-order the patterns just add numbers to the beginning:
+
+```
+01-ordered.mustache
+02-unordered.mustache
+03-definition.mustache
+```
+
+You may want to put some space between the numbers just in case you want to further re-order and not touch the other patterns. For example, a better default ordering might be:
+
+```
+01-ordered.mustache
+05-unordered.mustache
+10-definition.mustache
+```
+
+The numbers will not show up when Pattern Lab displays the name of the pattern in the drop-down navigation. They're simply a re-ordering mechanism.
+
+##Re-ordering Pseudo-Patterns
+
+The rules for re-ordering [pseudo-patterns](/docs/pattern-pseudo-patterns.html) are slightly different than normal patterns. The numbers go **after** the tilde sign (`~`) rather than at the beginning of the file name. For instance:
+
+```
+- pattern.mustache
+- pattern.yml
+- pattern~01-variation2.yml
+- pattern~02-variation3.yml
+- pattern~03-variation1.yml
+```
diff --git a/packages/docs/src/docs/pattern-states.md b/packages/docs/src/docs/pattern-states.md
new file mode 100644
index 000000000..ec0aee7f6
--- /dev/null
+++ b/packages/docs/src/docs/pattern-states.md
@@ -0,0 +1,53 @@
+---
+title: Using Pattern States
+tags:
+ - docs
+category: patterns
+eleventyNavigation:
+ title: Using Pattern States
+ key: patterns
+ order: 160
+---
+
+Pattern states provide your team and client a simple visual of the current state of patterns in Pattern Lab. Pattern states can track progress of a pattern from development, through client review, to completion or they can be used to give certain patterns specific classes. It's important to note that the state of a pattern can be influenced by its pattern partials.
+
+## The Default Pattern States
+
+Pattern Lab comes with the following default pattern states:
+
+- **inprogress**: pattern is in development or being worked upon. a red dot.
+- **inreview**: pattern is ready for a client to look at and comment upon. a yellow dot.
+- **complete**: pattern is ready to be moved to production. a green dot.
+
+Any pattern that includes a pattern partial that has a lower pattern state will inherit that state. For example, a pattern with the state of `inreview` that includes a pattern partial with the state of `inprogress` will have its state overridden and set to `inprogress`. It will not change to `inreview` until the pattern partial has a state of `inreview` or `complete`.
+
+## Giving Patterns a State
+
+Giving patterns a state is accomplished by setting the `state` frontmatter key on any pattern's companion `.md` file. Consider this media block pattern:
+
+```
+./source/_patterns/molecules/blocks/media-block.mustache
+```
+
+We would create or edit a file in the same location, calling it `media-block.md`:
+
+```
+---
+state: inreview
+---
+The media block consists of...
+```
+
+## Adding Customized States
+
+The three default states included with Pattern Lab might not be enough for everyone. To add customized states you should modify your own CSS files. **DO NOT** modify pattern states in `public/`. You cannot be assured these files won't be overwritten.
+
+You can use the following as your CSS template for new pattern states:
+
+```css
+{% raw %}.newpatternstate:before {
+ color: #B10DC9 !important;
+}{% endraw %}
+```
+
+Place this class inside `./source/css/pattern-scaffolding.css` to separate it from your css. Then add `newpatternstate` to your patterns' markdown `state` to have the new look show up. If you want to add it to the cascade of the default patterns you can modify `./patternlab-config.json`. Simply add your new pattern state to the `patternStateCascade` array.
diff --git a/packages/docs/src/docs/php-compile.md b/packages/docs/src/docs/php-compile.md
new file mode 100644
index 000000000..338e96ddb
--- /dev/null
+++ b/packages/docs/src/docs/php-compile.md
@@ -0,0 +1,14 @@
+---
+title: php-compile
+tags:
+ - demo-content
+ - code
+ - blog
+eleventyNavigation:
+ key: php-compile
+ order: 300
+---
+
+The PHP version of Pattern Lab is being deprecated in favor of a new unified Pattern Lab core. The PHP docs for this topic can be viewed here.
+
+The current version of PL's website includes full docs for PHP. The new version of PL runs exclusively through Node, but for PHP projects will compile PHP with a PHP renderer.
diff --git a/packages/docs/src/feed.njk b/packages/docs/src/feed.njk
new file mode 100644
index 000000000..11c50e48f
--- /dev/null
+++ b/packages/docs/src/feed.njk
@@ -0,0 +1,28 @@
+---
+permalink: '/feed.xml'
+---
+
+
+ {{ site.name }}
+
+
+
+ {{ collections.posts | rssLastUpdatedDate }}
+ {{ site.url }}
+
+ {{ site.authorName }}
+ {{ site.authorEmail }}
+
+ {% for post in collections.posts %}
+ {% set absolutePostUrl %}{{ site.url }}{{ post.url | url }}{% endset %}
+
+ {{ post.data.title }}
+
+ {{ post.date | rssDate }}
+ {{ absolutePostUrl }}
+
+
+ {% endfor %}
+
diff --git a/packages/docs/src/filters/date-filter.js b/packages/docs/src/filters/date-filter.js
new file mode 100644
index 000000000..5206da52f
--- /dev/null
+++ b/packages/docs/src/filters/date-filter.js
@@ -0,0 +1,15 @@
+// Stolen from https://stackoverflow.com/a/31615643
+const appendSuffix = n => {
+ var s = ['th', 'st', 'nd', 'rd'],
+ v = n % 100;
+ return n + (s[(v - 20) % 10] || s[v] || s[0]);
+};
+
+module.exports = function dateFilter(value) {
+ const dateObject = new Date(value);
+
+ const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
+ const dayWithSuffix = appendSuffix(dateObject.getDate());
+
+ return `${dayWithSuffix} ${months[dateObject.getMonth()]} ${dateObject.getFullYear()}`;
+};
diff --git a/packages/docs/src/filters/markdown-filter.js b/packages/docs/src/filters/markdown-filter.js
new file mode 100644
index 000000000..530c2f571
--- /dev/null
+++ b/packages/docs/src/filters/markdown-filter.js
@@ -0,0 +1,9 @@
+const markdownIt = require('markdown-it')({
+ html: true,
+ breaks: true,
+ linkify: true
+});
+
+module.exports = function markdown(value) {
+ return markdownIt.render(value);
+};
diff --git a/packages/docs/src/filters/w3-date-filter.js b/packages/docs/src/filters/w3-date-filter.js
new file mode 100644
index 000000000..d31538da4
--- /dev/null
+++ b/packages/docs/src/filters/w3-date-filter.js
@@ -0,0 +1,5 @@
+module.exports = function w3cDate(value) {
+ const dateObject = new Date(value);
+
+ return dateObject.toISOString();
+};
diff --git a/packages/docs/src/images/1createpl.png b/packages/docs/src/images/1createpl.png
new file mode 100644
index 000000000..b92a451e0
Binary files /dev/null and b/packages/docs/src/images/1createpl.png differ
diff --git a/packages/docs/src/images/2choosedirectory.png b/packages/docs/src/images/2choosedirectory.png
new file mode 100644
index 000000000..11f331997
Binary files /dev/null and b/packages/docs/src/images/2choosedirectory.png differ
diff --git a/packages/docs/src/images/3chooseedition.png b/packages/docs/src/images/3chooseedition.png
new file mode 100644
index 000000000..7af97103a
Binary files /dev/null and b/packages/docs/src/images/3chooseedition.png differ
diff --git a/packages/docs/src/images/4choosestarterkit.png b/packages/docs/src/images/4choosestarterkit.png
new file mode 100644
index 000000000..4a7ef6a37
Binary files /dev/null and b/packages/docs/src/images/4choosestarterkit.png differ
diff --git a/packages/docs/src/images/5.png b/packages/docs/src/images/5.png
new file mode 100644
index 000000000..f7502ec4e
Binary files /dev/null and b/packages/docs/src/images/5.png differ
diff --git a/packages/docs/src/images/5areyouhappy.png b/packages/docs/src/images/5areyouhappy.png
new file mode 100644
index 000000000..43db88232
Binary files /dev/null and b/packages/docs/src/images/5areyouhappy.png differ
diff --git a/packages/docs/src/images/6.png b/packages/docs/src/images/6.png
new file mode 100644
index 000000000..f462b8db4
Binary files /dev/null and b/packages/docs/src/images/6.png differ
diff --git a/packages/docs/src/images/6settingup.png b/packages/docs/src/images/6settingup.png
new file mode 100644
index 000000000..6b4e3938e
Binary files /dev/null and b/packages/docs/src/images/6settingup.png differ
diff --git a/packages/docs/src/images/7.png b/packages/docs/src/images/7.png
new file mode 100644
index 000000000..4ee99b733
Binary files /dev/null and b/packages/docs/src/images/7.png differ
diff --git a/packages/docs/src/images/800x600.png b/packages/docs/src/images/800x600.png
new file mode 100644
index 000000000..d765b0574
Binary files /dev/null and b/packages/docs/src/images/800x600.png differ
diff --git a/packages/docs/src/images/createPL1.mp4 b/packages/docs/src/images/createPL1.mp4
new file mode 100644
index 000000000..d9686b649
Binary files /dev/null and b/packages/docs/src/images/createPL1.mp4 differ
diff --git a/packages/docs/src/images/favicon.ico b/packages/docs/src/images/favicon.ico
new file mode 100644
index 000000000..eee4aa78f
Binary files /dev/null and b/packages/docs/src/images/favicon.ico differ
diff --git a/packages/docs/src/images/icon-atom.svg b/packages/docs/src/images/icon-atom.svg
new file mode 100644
index 000000000..10bf0adf0
--- /dev/null
+++ b/packages/docs/src/images/icon-atom.svg
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docs/src/images/icon-molecule.svg b/packages/docs/src/images/icon-molecule.svg
new file mode 100644
index 000000000..2dc151733
--- /dev/null
+++ b/packages/docs/src/images/icon-molecule.svg
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docs/src/images/icon-organism.svg b/packages/docs/src/images/icon-organism.svg
new file mode 100644
index 000000000..c12cc435b
--- /dev/null
+++ b/packages/docs/src/images/icon-organism.svg
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docs/src/images/icon-page.svg b/packages/docs/src/images/icon-page.svg
new file mode 100644
index 000000000..fe5751723
--- /dev/null
+++ b/packages/docs/src/images/icon-page.svg
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docs/src/images/icon-template.svg b/packages/docs/src/images/icon-template.svg
new file mode 100644
index 000000000..3aa6df78e
--- /dev/null
+++ b/packages/docs/src/images/icon-template.svg
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docs/src/index.md b/packages/docs/src/index.md
new file mode 100644
index 000000000..aaa797e7d
--- /dev/null
+++ b/packages/docs/src/index.md
@@ -0,0 +1,4 @@
+---
+layout: home
+title: Create atomic design systems with Pattern Lab
+---
diff --git a/packages/docs/src/js/components/theme-toggle.js b/packages/docs/src/js/components/theme-toggle.js
new file mode 100644
index 000000000..7e93571a2
--- /dev/null
+++ b/packages/docs/src/js/components/theme-toggle.js
@@ -0,0 +1,98 @@
+// For syntax highlighting only
+const html = String.raw;
+
+class ThemeToggle extends HTMLElement {
+ constructor() {
+ super();
+
+ this.STORAGE_KEY = 'user-color-scheme';
+ this.COLOR_MODE_KEY = '--color-mode';
+ }
+
+ connectedCallback() {
+ this.render();
+ }
+
+ getCSSCustomProp(propKey) {
+ let response = getComputedStyle(document.documentElement).getPropertyValue(propKey);
+
+ // Tidy up the string if there’s something to work with
+ if (response.length) {
+ response = response.replace(/\'|"/g, '').trim();
+ }
+
+ // Return the string response by default
+ return response;
+ }
+
+ applySetting(passedSetting) {
+ let currentSetting = passedSetting || localStorage.getItem(this.STORAGE_KEY);
+
+ if (currentSetting) {
+ document.documentElement.setAttribute('data-user-color-scheme', currentSetting);
+ this.setButtonLabelAndStatus(currentSetting);
+ } else {
+ this.setButtonLabelAndStatus(this.getCSSCustomProp(this.COLOR_MODE_KEY));
+ }
+ }
+
+ toggleSetting() {
+ let currentSetting = localStorage.getItem(this.STORAGE_KEY);
+
+ switch (currentSetting) {
+ case null:
+ currentSetting =
+ this.getCSSCustomProp(this.COLOR_MODE_KEY) === 'dark' ? 'light' : 'dark';
+ break;
+ case 'light':
+ currentSetting = 'dark';
+ break;
+ case 'dark':
+ currentSetting = 'light';
+ break;
+ }
+
+ localStorage.setItem(this.STORAGE_KEY, currentSetting);
+
+ return currentSetting;
+ }
+
+ setButtonLabelAndStatus(currentSetting) {
+ this.modeToggleButton.innerText = `${
+ currentSetting === 'dark' ? 'Light' : 'Dark'
+ } theme`;
+ this.modeStatusElement.innerText = `Color mode is now "${currentSetting}"`;
+ }
+
+ render() {
+ this.innerHTML = html`
+
+ `;
+
+ this.afterRender();
+ }
+
+ afterRender() {
+ this.modeToggleButton = document.querySelector('.js-mode-toggle');
+ this.modeStatusElement = document.querySelector('.js-mode-status');
+
+ this.modeToggleButton.addEventListener('click', evt => {
+ evt.preventDefault();
+
+ this.applySetting(this.toggleSetting());
+ });
+
+ this.applySetting();
+ }
+}
+
+if ('customElements' in window) {
+ customElements.define('theme-toggle', ThemeToggle);
+}
+
+export default ThemeToggle;
diff --git a/packages/docs/src/js/primary-nav.js b/packages/docs/src/js/primary-nav.js
new file mode 100644
index 000000000..45b8a7d91
--- /dev/null
+++ b/packages/docs/src/js/primary-nav.js
@@ -0,0 +1,83 @@
+/*------------------------------------*\
+ #PRIMARY NAVIGATION
+\*------------------------------------*/
+/**
+ * Toggles active class on the primary nav item
+ * 1) Select all nav dropdown triggers and cycle through them
+ * 2) On click, find the nav dropdown trigger parent
+ * 3) If the nav dropdown trigger parent already has active class, remove it.
+ * 4) If the nav dropdown trigger parent does not have an active class, add it.
+ */
+(function() {
+ var navDropdownListItem = document.querySelector('.js-nav-dropdown');
+ var navLink = document.querySelectorAll('.js-nav-dropdown-trigger'); /* 1 */
+
+ for (i = 0; i < navLink.length; i++) {
+ /* 1 */
+
+ navLink[i].addEventListener('click', function(event) {
+ /* 2 */
+ event.preventDefault();
+ var navLinkParent = this.parentNode; /* 2 */
+
+ if (navLinkParent.classList.contains('is-active')) {
+ /* 3 */
+ navLinkParent.classList.remove('is-active');
+
+ this.setAttribute("aria-expanded", "false");
+ } else {
+ /* 4 */
+ navLinkParent.classList.add('is-active');
+
+ this.setAttribute("aria-expanded", "true");
+ }
+
+ });
+ }
+
+ /**
+ * Expose docs dropdown if on a docs page
+ */
+ if (window.location.href.indexOf('docs') > -1) {
+ navDropdownListItem.classList.add('is-active');
+ }
+
+ var pathName = location.pathname;
+
+ var navLinks = document.querySelectorAll('.c-tree-nav a');
+
+ for (i = 0; i < navLinks.length; i++) {
+ var subnavLink = navLinks[i].getAttribute('href');
+ if (subnavLink == pathName) {
+ navLinks[i].classList.add('is-active');
+ }
+ }
+
+ /**
+ * Toggles active class on the primary nav panel
+ * 1) Select all nav triggers and cycle through them
+ * 2) On click, find the nav panel within the header
+ * 3) If the navPanel already has active class, remove it on click.
+ * 4) If the navPanel does not have an active class, add it on click.
+ */
+ var navToggle = document.querySelectorAll('.js-nav-trigger'); /* 1 */
+
+ for (i = 0; i < navToggle.length; i++) {
+ /* 1 */
+
+ navToggle[i].addEventListener('click', function(event) {
+ /* 2 */
+ event.preventDefault();
+ var navToggleParent = this.parentNode; /* 2 */
+ var navPanel = navToggleParent.querySelector('.js-nav-panel'); /* 2 */
+
+ if (navPanel.classList.contains('is-active')) {
+ /* 3 */
+ navPanel.classList.remove('is-active');
+ } else {
+ /* 4 */
+ navPanel.classList.add('is-active');
+ }
+ });
+ }
+})();
diff --git a/packages/docs/src/pages/pages.json b/packages/docs/src/pages/pages.json
new file mode 100644
index 000000000..04d34aa3d
--- /dev/null
+++ b/packages/docs/src/pages/pages.json
@@ -0,0 +1,3 @@
+{
+ "layout": "layouts/page.njk"
+}
diff --git a/packages/docs/src/posts/pattern-lab-website-redesign.md b/packages/docs/src/posts/pattern-lab-website-redesign.md
new file mode 100644
index 000000000..0adfc23ac
--- /dev/null
+++ b/packages/docs/src/posts/pattern-lab-website-redesign.md
@@ -0,0 +1,14 @@
+---
+title: Pattern Lab website redesign
+description: The Pattern Lab website got a new facelift
+date: '2020-02-17'
+tags:
+ - blog
+url: '/posts/pattern-lab-website-redesign'
+---
+
+We're pleased to announce the Pattern Lab website is undergoing a much-needed facelift!
+
+There has been a lot of work on the Pattern Lab product that hasn't been reflected on the website. Over the coming months, we'll be updating the documentation to represent these improvements as well as bring home the new look and feel of the website.
+
+Stay tuned!
diff --git a/packages/docs/src/posts/posts.json b/packages/docs/src/posts/posts.json
new file mode 100644
index 000000000..d0428c43f
--- /dev/null
+++ b/packages/docs/src/posts/posts.json
@@ -0,0 +1,3 @@
+{
+ "layout": "layouts/post.njk"
+}
diff --git a/packages/docs/src/resources.md b/packages/docs/src/resources.md
new file mode 100644
index 000000000..15fca9770
--- /dev/null
+++ b/packages/docs/src/resources.md
@@ -0,0 +1,39 @@
+---
+layout: layouts/page-base.njk
+title: Resources
+---
+
+## Style guides and atomic design
+
+- [Styleguides.io](http://styleguides.io)
+- [Atomic Design by Brad Frost](http://atomicdesign.bradfrost.com)
+- [Atomic design article](http://bradfrost.com/blog/post/atomic-web-design/)
+- [A new link](http://google.com)
+
+## Pattern Lab Examples
+
+- [Pattern Lab Demo](http://demo.patternlab.io/)
+- [Frost Finery](http://patterns.frostfinery.com)
+- [Pittsburgh Food Bank](http://foodbank.bradfrostweb.com/patternlab/v10/)
+- [brianmuenzenmeyer.com](http://www.brianmuenzenmeyer.com/patternlab/public/)
+- [Altinn](http://altinn.github.io/DesignSystem/)
+
+## Articles
+
+- [Making And Maintaining Atomic Design Systems With Pattern Lab 2 by Brad Frost, Dave Olsen, Brian Muenzenmeyer](https://www.smashingmagazine.com/2016/07/building-maintaining-atomic-design-systems-pattern-lab/)
+- [Using Pattern Lab to Design Build and Maintain a (Jekyll) Website by Brian Muenzenmeyer](http://www.brianmuenzenmeyer.com/using-patternlab-to-design-build-and-maintain-a-website)
+- [Solving Problems with Pattern Lab - Small Team Workflow by Brian Muenzenmeyer](http://www.brianmuenzenmeyer.com/solving-problems-with-pattern-lab-small-team-workflow)
+- [Adding Common Gulp Tasks to Pattern Lab Node by Brian Muenzenmeyer](http://www.brianmuenzenmeyer.com/adding-common-gulp-tasks-to-pattern-lab-node)
+- [Why and How to Test Your Pattern Library: Testing Strategy by Jim Newbery](http://tinnedfruit.com/2016/09/12/why-and-how-to-test-your-pattern-library.html)
+- [Why and How to Test Your Pattern Library: Testing a Pattern Lab Project by Jim Newbery](http://tinnedfruit.com/2016/09/20/why-and-how-to-test-your-pattern-library-2.html)
+
+## Podcasts
+
+- [Dave Olsen talking Pattern Lab 2 on Non-Breaking Space Podcast](http://goodstuff.fm/nbsp/86)
+- [Brian Muenzenmeyer talking Pattern Lab 2 on the MS DEV SHOW Podcast](http://msdevshow.com/2015/12/pattern-lab-with-brian-muenzenmeyer/)
+
+## Presentations
+
+- [Screencast: A Quick Intro to Pattern Lab Node with Brian Muenzenmeyer](https://css-tricks.com/video-screencasts/149-quick-intro-pattern-lab-node-brian-muenzenmeyer/)
+- [Screencast: Migrating to Pattern Lab 3.0](https://www.youtube.com/watch?v=hIH-lI1QTns&t=1s)
+- [Code: Migrating to Pattern Lab 3.0](https://github.com/bmuenzenmeyer/pl-migrate-example)
diff --git a/packages/docs/src/scss/abstracts/_colors.scss b/packages/docs/src/scss/abstracts/_colors.scss
new file mode 100644
index 000000000..f1100a06d
--- /dev/null
+++ b/packages/docs/src/scss/abstracts/_colors.scss
@@ -0,0 +1,79 @@
+/*------------------------------------*\
+ #COLORS
+\*------------------------------------*/
+
+/**
+ * In this file, we take the literal colors from our palette (defined in variables.scss)
+ * and define them against variables that we can utilise anywhere throughout the project.
+ */
+
+
+
+
+
+/*------------------------------------*\
+ #GLOBAL TEXT COLOR
+\*------------------------------------*/
+
+/**
+ * Body text and background colors
+ */
+$color-text: $color-gray-93 !default;
+$color-text-bg: $color-white !default;
+
+/**
+ * Highlight colors
+ */
+$color-text-highlight: $color-gray-93;
+$color-text-highlight-bg: $color-gray-13;
+
+
+
+
+
+/*------------------------------------*\
+ #LINKS
+\*------------------------------------*/
+
+$color-links: $color-gray-73 !default;
+$color-links-hover: $color-gray-50!default;
+$color-links-active: $color-gray-93 !default;
+$color-links-visited: $color-gray-93 !default;
+
+
+
+
+
+/*------------------------------------*\
+ #BUTTONS
+\*------------------------------------*/
+
+$color-btn-primary: $color-white !default;
+$color-btn-primary-bg: $color-gray-93 !default;
+$color-btn-primary-bg-hover: $color-gray-50 !default;
+$color-btn-primary-border: $color-gray-93 !default;
+
+$color-btn-secondary: $color-gray-93 !default;
+$color-btn-secondary-bg: $color-white !default;
+$color-btn-secondary-bg-hover: $color-gray-07 !default;
+$color-btn-secondary-border: $color-gray-93 !default;
+
+$color-btn-disabled: $color-gray-50 !default;
+$color-btn-disabled-bg: $color-gray-07 !default;
+
+
+
+
+
+/*------------------------------------*\
+ #FORMS
+\*------------------------------------*/
+
+$color-form: $color-gray-93 !default;
+$color-form-bg: $color-white !default;
+$color-form-border: $color-gray-73 !default;
+$color-form-border-focus: $color-gray-93 !default;
+$color-form-border-error: $color-utility-error !default;
+$color-form-label: $color-gray-93 !default;
+$color-form-info: $color-gray-73;
+$color-form-placeholder: $color-gray-50;
diff --git a/packages/docs/src/scss/abstracts/_mixins.scss b/packages/docs/src/scss/abstracts/_mixins.scss
new file mode 100644
index 000000000..a2425f6df
--- /dev/null
+++ b/packages/docs/src/scss/abstracts/_mixins.scss
@@ -0,0 +1,175 @@
+/*------------------------------------*\
+ #MIXINS
+\*------------------------------------*/
+
+/**
+ * Body Styles
+ * 1) Prevent Mobile Safari from scaling up text: https://blog.55minutes.com/2012/04/iphone-text-resizing/
+ */
+@mixin typographyBody() {
+ font-family: $font-primary;
+ font-size: $font-size-med-2;
+ font-weight: 500;
+ line-height: 1.6;
+ -webkit-text-size-adjust: 100%; /* 1 */
+}
+
+/**
+ * XL Type Styles
+ */
+@mixin typographyBodyLarge() {
+ font-size: $font-size-med-2;
+ line-height: $line-height-large;
+}
+
+/**
+ * XL Heading Styles
+ */
+@mixin typographyHeadingXl() {
+ font-weight: $font-weight-bold;
+ font-size: $font-size-large-2;
+ line-height: $line-height-med-2;
+
+ @media all and (min-width: $bp-med) {
+ font-size: $font-size-xl;
+ }
+}
+
+/**
+ * Large Heading Styles
+ */
+@mixin typographyHeadingLarge() {
+ font-size: $font-size-large;
+ font-weight: 700;
+ line-height: 1.6;
+}
+
+/**
+ * Medium 2 Heading Styles
+ */
+@mixin typographyHeadingMed2() {
+ font-size: $font-size-med-2;
+ font-weight: 700;
+ line-height: 1.2;
+}
+
+/**
+ * Medium Heading Styles
+ */
+@mixin typographyHeadingMed() {
+ font-size: $font-size-med;
+ font-weight: 700;
+ line-height: 1.2;
+}
+
+/*------------------------------------*\
+ #FOCUS
+\*------------------------------------*/
+
+@mixin focus() {
+ outline: 2px dotted $color-black;
+ outline-offset: 4px;
+}
+
+@mixin focusInverted() {
+ outline: 1px dotted $color-white;
+ outline-offset: 4px;
+}
+
+/*------------------------------------*\
+ #DECORATIVE
+\*------------------------------------*/
+
+@mixin textShadowEffect() {
+ position: relative;
+ z-index: 1;
+
+ &:after {
+ position: absolute;
+ left: 4px;
+ top: 4px;
+ z-index: 0;
+ content: attr(data-text);
+
+ background-image: radial-gradient(
+ $color-brand-purple 0%,
+ $color-brand-purple 60%,
+ transparent 60%
+ );
+ background-size: 4px 4px;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ z-index: -5;
+ display: block;
+ text-shadow: none;
+
+ @media all and (max-width: $bp-large) {
+ left: 2px;
+ top: 2px;
+ background-size: 2px 2px;
+ }
+ }
+}
+
+@mixin boxShadowEffect($color: 'green') {
+ @if $color == 'green' {
+ box-shadow: 6px 6px 0 $color-brand-green, 12px 12px 0 $color-brand-green-light;
+ } @else if $color == 'orange' {
+ box-shadow: 6px 6px 0 $color-brand-orange, 12px 12px 0 $color-brand-orange-light;
+ } @else {
+ box-shadow: 6px 6px 0 $color-brand-purple, 12px 12px 0 $color-brand-purple-light;
+ }
+}
+
+@mixin stripedBoxShadow($color: 'green') {
+ @if $color == 'green' {
+ background-image: repeating-linear-gradient(
+ 45deg,
+ $color-brand-green,
+ $color-brand-green 1px,
+ transparent 1px,
+ transparent 4px
+ );
+ } @else if $color == 'orange' {
+ background-image: repeating-linear-gradient(
+ 45deg,
+ $color-brand-orange,
+ $color-brand-orange 1px,
+ transparent 1px,
+ transparent 4px
+ );
+ } @else {
+ background-image: repeating-linear-gradient(
+ 45deg,
+ $color-brand-purple,
+ $color-brand-purple 1px,
+ transparent 1px,
+ transparent 4px
+ );
+ }
+}
+
+@mixin hideScrollbar() {
+ -webkit-overflow-scrolling: touch;
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+ scrollbar-width: none;
+ scrollbar-color: transparent;
+
+ &::-webkit-scrollbar {
+ height: var(--scrollbar-size);
+ width: var(--scrollbar-size);
+ }
+ &::-webkit-scrollbar-track {
+ background-color: var(--scrollbar-track-color);
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: var(--scrollbar-color);
+ /* Add :hover, :active as needed */
+ }
+ &::-webkit-scrollbar-thumb:vertical {
+ min-height: var(--scrollbar-minlength);
+ }
+ &::-webkit-scrollbar-thumb:horizontal {
+ min-width: var(--scrollbar-minlength);
+ }
+}
diff --git a/packages/docs/src/scss/abstracts/_typography.scss b/packages/docs/src/scss/abstracts/_typography.scss
new file mode 100644
index 000000000..fd42dfc3c
--- /dev/null
+++ b/packages/docs/src/scss/abstracts/_typography.scss
@@ -0,0 +1,10 @@
+/*------------------------------------*\
+ #BREAKPOINTS
+\*------------------------------------*/
+
+/**
+ * In this file, we take the literal colors from our palette and define them
+ * against variables that we can utilise anywhere throughout the project.
+ */
+
+ $body-font-size: $font-size-med;
\ No newline at end of file
diff --git a/packages/docs/src/scss/abstracts/_variables.scss b/packages/docs/src/scss/abstracts/_variables.scss
new file mode 100644
index 000000000..f722ef498
--- /dev/null
+++ b/packages/docs/src/scss/abstracts/_variables.scss
@@ -0,0 +1,192 @@
+/*------------------------------------*\
+ #VARIABLES
+\*------------------------------------*/
+
+/**
+ * CONTENTS
+ *
+ * COLORS
+ * Brand Colors...............Globally-available variables and config
+ * Neutral Colors.............Grayscale colors, including white and black
+ * Utility Colors.............Info, Warning, Error, Success
+ *
+ * TYPOGRAPHY
+ * Font Families..............The fonts used in the design system
+ * Sizing.....................Font sizing
+ *
+ * LAYOUT
+ * Max-widths.................Maximum layout container width
+ *
+
+ * SPACING
+ * Spacing defaults...........Spacing between elements
+ *
+ * BORDERS
+ * Border Width...............Border thicknesses
+ * Border Radius..............Border radius definitions
+ *
+ * ANIMATION
+ * Animation Speed............Transition/animation speed variables
+ * Animation easing...........Easing variables
+ *
+ * BREAKPOINTS
+ * Breakpoints................Global breakpoint definitions
+ */
+
+/*------------------------------------*\
+ #COLORS
+ \*------------------------------------*/
+
+/**
+ * Brand Colors
+ * 1) Brand=specific colors
+ */
+$color-brand-purple: #aa85ba;
+$color-brand-purple-dark: #22062e;
+$color-brand-purple-light: #f9f5fc;
+$color-brand-green: #7ec699;
+$color-brand-green-dark: #376548;
+$color-brand-green-light: #f4fdf6;
+$color-brand-orange: #eeb31b;
+$color-brand-orange-dark: #966d04;
+$color-brand-orange-light: #fef8ea;
+
+/**
+ * Neutral Colors
+ * 1) Neutral colors are grayscale values used throughout the UI
+ */
+$color-white: #fff;
+$color-gray-02: #f2f2f2;
+$color-gray-07: #eee;
+$color-gray-13: #ddd;
+$color-gray-27: #bbb;
+$color-gray-50: #808080;
+$color-gray-60: #666;
+$color-gray-73: #444;
+$color-gray-88: #1f1f1f;
+$color-gray-93: #131313;
+$color-black: #000;
+
+/**
+ * Utility Colors
+ * 1) Utility colors are colors used to provide feedback, such as alert messages,
+ * form validation, etc.
+ */
+$color-utility-info: #0192d0;
+$color-utility-info-light: #d3f2ff;
+$color-utility-error: #b12a0b;
+$color-utility-error-light: #fdded8;
+$color-utility-success: #03804d;
+$color-utility-success-light: #d4f3e6;
+$color-utility-warning: #a59b15;
+$color-utility-warning-light: #fffecf;
+
+/*------------------------------------*\
+ #TYPOGRAPHY
+\*------------------------------------*/
+
+/**
+ * Font Family
+ */
+$font-primary: neue-haas-grotesk-display, sans-serif;
+$font-secondary: monospace;
+
+/**
+ * Font Sizing
+ */
+$font-size-sm: 0.75rem;
+$font-size-sm-2: 0.85rem;
+$font-size-med: 1rem;
+$font-size-med-2: 1.2rem;
+$font-size-large: 2rem;
+$font-size-large-2: 2.6rem;
+$font-size-xl: 5rem;
+
+$font-weight-bold: 700;
+
+/**
+ * Line Height
+ */
+$line-height-sm: 0.8;
+$line-height-sm-2: 0.9;
+$line-height-med: 1;
+$line-height-med-2: 1.2;
+$line-height-large: 1.6;
+$line-height-xl: 1.8;
+
+/*------------------------------------*\
+ #LAYOUT
+\*------------------------------------*/
+
+/**
+ * Max Width
+ */
+$l-max-width: 70rem !default;
+$l-linelength-width: 40rem !default;
+
+$l-sidebar-width: 18.75rem;
+
+/*------------------------------------*\
+ #SPACING
+\*------------------------------------*/
+
+/**
+ * Spacing and offsets
+ * 1) Used to space grids and body padding
+ */
+
+$spacing: 1rem;
+$spacing-small: round(0.5 * $spacing);
+$spacing-large: round(2 * $spacing);
+$spacing-xl: round(4 * $spacing);
+
+/*------------------------------------*\
+ #BORDERS
+\*------------------------------------*/
+
+/**
+ * Border
+ */
+$border-width: 1px;
+$border-width-thick: 0.5rem;
+
+/**
+ * Border radius
+ */
+$border-radius: 4px;
+
+/*------------------------------------*\
+ #ANIMATION
+\*------------------------------------*/
+
+/**
+ * Transition Speed
+ */
+$anim-fade-quick: 0.15s;
+$anim-fade-long: 0.4s;
+
+/**
+ * Transition Ease
+ */
+$anim-ease: ease-out;
+
+/*------------------------------------*\
+ #BREAKPOINTS
+\*------------------------------------*/
+
+/**
+ * Breakpoints used in media queries
+ * 1) These are not the only breakpoints used, but they provide a few defaults
+ */
+$bp-small: 28em;
+$bp-med: 47em;
+$bp-large: 60em;
+$bp-xl: 70em;
+
+:root {
+ --scrollbar-track-color: transparent;
+ --scrollbar-color: rgba(0, 0, 0, 0.2);
+
+ --scrollbar-size: 0.375rem;
+ --scrollbar-minlength: 1.5rem; /* Minimum length of scrollbar thumb (width of horizontal, height of vertical) */
+}
diff --git a/packages/docs/src/scss/base/_body.scss b/packages/docs/src/scss/base/_body.scss
new file mode 100644
index 000000000..897fa2846
--- /dev/null
+++ b/packages/docs/src/scss/base/_body.scss
@@ -0,0 +1,28 @@
+/*------------------------------------*\
+ #BODY
+\*------------------------------------*/
+
+/**
+ * HTML base styles
+ * 1) Set the html element's height to at least 100% of the viewport.
+ * This is used to achieve a sticky footer
+ */
+html {
+ min-height: 100vh; /* 1 */
+}
+
+/**
+ * Body base styles
+ * 1) Set the body element's height to at least 100% of the viewport.
+ * This is used to achieve a sticky footer
+ * 2) Prevent Mobile Safari from scaling up text: https://blog.55minutes.com/2012/04/iphone-text-resizing/
+ */
+body {
+ display: flex; /* 1 */
+ flex-direction: column; /* 1 */
+ min-height: 100vh; /* 1 */
+ @include typographyBody;
+ -webkit-text-size-adjust: 100%; /* 2 */
+ background-color: $color-white;
+ color: $color-gray-88;
+}
diff --git a/packages/docs/src/scss/base/_buttons.scss b/packages/docs/src/scss/base/_buttons.scss
new file mode 100644
index 000000000..31eca645a
--- /dev/null
+++ b/packages/docs/src/scss/base/_buttons.scss
@@ -0,0 +1,15 @@
+/*------------------------------------*\
+ #BUTTONS
+\*------------------------------------*/
+
+/**
+ * Button and submit inputs reset
+ * 1) These should be styled using c-btn
+ */
+button {
+ cursor: pointer;
+
+ &:focus {
+ @include focus();
+ }
+}
diff --git a/packages/docs/src/scss/base/_forms.scss b/packages/docs/src/scss/base/_forms.scss
new file mode 100644
index 000000000..fca47c803
--- /dev/null
+++ b/packages/docs/src/scss/base/_forms.scss
@@ -0,0 +1,130 @@
+/*------------------------------------*\
+ #FORMS
+\*------------------------------------*/
+
+/**
+ * 1) Form element base styles
+ */
+
+/**
+ * Input placeholder text base styles
+ */
+::-webkit-input-placeholder {
+ color: $color-form-placeholder;
+}
+
+::-moz-placeholder {
+ color: $color-form-placeholder;
+}
+
+:-ms-input-placeholder {
+ color: $color-form-placeholder;
+}
+
+/**
+ * Fieldset base styles
+ */
+fieldset {
+ border: 0;
+ padding: 0;
+ margin: 0;
+}
+
+/**
+ * Legend base styles
+ */
+legend {
+ margin-bottom: 0.25rem;
+}
+
+/**
+ * Label base styles
+ */
+label {
+ display: block;
+ padding-bottom: 0.25rem;
+ color: $color-form-label;
+}
+
+/**
+ * Add font size 100% of form element and margin 0 to these elements
+ */
+button,
+input,
+select,
+textarea {
+ font-family: inherit;
+ font-size: $font-size-med;
+ margin: 0;
+}
+
+/**
+ * Text area base styles
+ */
+textarea {
+ resize: none;
+}
+
+/**
+ * Input and text area base styles
+ */
+input,
+textarea {
+ width: 100%;
+ padding: 0.5rem;
+ border: $border-width solid $color-form-border;
+ background: $color-form-bg;
+
+ &:focus {
+ border-color: $color-form-border-focus;
+ }
+}
+
+/**
+ * Remove webkit appearance styles from these elements
+ */
+input[type='text'],
+input[type='search'],
+input[type='search']::-webkit-search-cancel-button,
+input[type='search']::-webkit-search-decoration,
+input[type='url'],
+input[type='number'],
+textarea {
+ -webkit-appearance: none;
+}
+
+/**
+ * Checkbox and radio button base styles
+ */
+input[type='checkbox'],
+input[type='radio'] {
+ width: auto;
+ margin-right: 0.3rem;
+ border-color: $color-form-border;
+}
+
+/**
+ * Search input base styles
+ */
+input[type='search'] {
+ -webkit-appearance: none;
+ border-radius: 0;
+}
+
+/**
+ * Select
+ * 1) Remove default styling
+ */
+select {
+ display: block;
+ font-size: $font-size-med;
+ width: 100%;
+ border: $border-width solid $color-form-border;
+ padding: 0.5rem;
+ background: $color-form-bg;
+ color: $color-form;
+
+ &:focus {
+ border-color: $color-form-border-focus;
+ }
+}
diff --git a/packages/docs/src/scss/base/_headings.scss b/packages/docs/src/scss/base/_headings.scss
new file mode 100644
index 000000000..918271967
--- /dev/null
+++ b/packages/docs/src/scss/base/_headings.scss
@@ -0,0 +1,35 @@
+/*------------------------------------*\
+ #HEADINGS
+\*------------------------------------*/
+
+/**
+ * Heading 1 base styles
+ */
+h1 {
+ @include typographyHeadingXl();
+ position: relative;
+}
+
+/**
+ * Heading 2 base styles
+ */
+h2 {
+ @include typographyHeadingLarge();
+ position: relative;
+}
+
+/**
+ * Heading 3 base styles
+ */
+h3 {
+ @include typographyHeadingMed2();
+ position: relative;
+}
+
+/**
+ * Heading 4 base styles
+ */
+h4 {
+ @include typographyHeadingMed();
+ position: relative;
+}
diff --git a/packages/docs/src/scss/base/_links.scss b/packages/docs/src/scss/base/_links.scss
new file mode 100644
index 000000000..fca78f65b
--- /dev/null
+++ b/packages/docs/src/scss/base/_links.scss
@@ -0,0 +1,25 @@
+/*------------------------------------*\
+ #LINKS
+\*------------------------------------*/
+
+/**
+ * Link base styles
+ */
+a {
+ color: $color-links;
+ text-decoration: none;
+ transition: color $anim-fade-quick $anim-ease;
+
+ &:hover,
+ &:focus {
+ color: $color-links-hover;
+ }
+
+ &:focus {
+ @include focus();
+ }
+
+ &:active {
+ color: $color-links-active;
+ }
+}
diff --git a/packages/docs/src/scss/base/_lists.scss b/packages/docs/src/scss/base/_lists.scss
new file mode 100644
index 000000000..c7d5cab82
--- /dev/null
+++ b/packages/docs/src/scss/base/_lists.scss
@@ -0,0 +1,15 @@
+/*------------------------------------*\
+ #LISTS
+\*------------------------------------*/
+
+/**
+ * 1) List base styles
+ */
+
+ /**
+ * Remove list styles from unordered and ordered lists
+ */
+ol, ul {
+ list-style: none;
+
+}
diff --git a/packages/docs/src/scss/base/_main.scss b/packages/docs/src/scss/base/_main.scss
new file mode 100644
index 000000000..4d4049703
--- /dev/null
+++ b/packages/docs/src/scss/base/_main.scss
@@ -0,0 +1,23 @@
+/*------------------------------------*\
+ #MAIN ELEMENT
+\*------------------------------------*/
+
+/**
+ * Main element
+ */
+main {
+ display: block;
+ margin-top: 2rem;
+
+ @media all and (min-width: $bp-med) {
+ margin-top: 5rem;
+ }
+}
+
+.c-main--flush {
+ margin-top: 0;
+
+ @media all and (min-width: $bp-med) {
+ margin-top: 0;
+ }
+}
diff --git a/packages/docs/src/scss/base/_media.scss b/packages/docs/src/scss/base/_media.scss
new file mode 100644
index 000000000..1d73ebfc4
--- /dev/null
+++ b/packages/docs/src/scss/base/_media.scss
@@ -0,0 +1,13 @@
+/*------------------------------------*\
+ #MEDIA
+\*------------------------------------*/
+
+/**
+ * Responsive image styling
+ * 1) Allows for images to flex with varying screen size
+ */
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
diff --git a/packages/docs/src/scss/base/_reset.scss b/packages/docs/src/scss/base/_reset.scss
new file mode 100644
index 000000000..82c641215
--- /dev/null
+++ b/packages/docs/src/scss/base/_reset.scss
@@ -0,0 +1,25 @@
+/*------------------------------------*\
+ #RESET
+\*------------------------------------*/
+
+/**
+ * Border-Box http:/paulirish.com/2012/box-sizing-border-box-ftw/
+ */
+* {
+ box-sizing: border-box;
+}
+
+/**
+ * 1) Zero out margins and padding for elements
+ */
+html, body, div, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, ol, ul, li, form, legend, label, table, header, footer, nav, section, figure {
+ margin: 0;
+ padding: 0;
+}
+
+/**
+ * 1) Set HTML5 elements to display: block
+ */
+header, footer, nav, section, article, figure {
+ display: block;
+}
diff --git a/packages/docs/src/scss/base/_table.scss b/packages/docs/src/scss/base/_table.scss
new file mode 100644
index 000000000..fa3215c54
--- /dev/null
+++ b/packages/docs/src/scss/base/_table.scss
@@ -0,0 +1,26 @@
+/*------------------------------------*\
+ #TABLES
+\*------------------------------------*/
+
+/**
+ * Table
+ */
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+ width: 100%;
+}
+
+/**
+ * Table header cell
+ */
+th {
+ text-align: left;
+}
+
+/**
+ * Table row
+ */
+tr {
+ vertical-align: top;
+}
diff --git a/packages/docs/src/scss/base/_text.scss b/packages/docs/src/scss/base/_text.scss
new file mode 100644
index 000000000..bbea0b569
--- /dev/null
+++ b/packages/docs/src/scss/base/_text.scss
@@ -0,0 +1,84 @@
+/*------------------------------------*\
+ #TEXT
+\*------------------------------------*/
+
+/**
+ * Paragraph base styles
+ */
+p {
+ margin-bottom: $spacing;
+}
+
+/**
+ * Blockquote base styles
+ */
+blockquote {
+ font-style: italic;
+ border-left: 1px solid $color-gray-50;
+ color: $color-gray-50;
+ padding-left: 1rem;
+ margin-bottom: $spacing;
+}
+
+/**
+ * Horizontal rule base styles
+ */
+hr {
+ border: 0;
+ height: $border-width-thick;
+ background: $color-gray-13;
+ margin: 2rem 0;
+}
+
+/**
+ * Selection styles
+ */
+::-moz-selection {
+ color: $color-text-highlight;
+ background: $color-text-highlight-bg; /* Gecko Browsers */
+}
+
+::selection {
+ color: $color-text-highlight;
+ background: $color-text-highlight-bg; /* WebKit/Blink Browsers */
+}
+
+/**
+ * Code base styles
+ */
+code {
+ display: inline;
+ background: $color-brand-purple-light;
+ padding: 0.2rem;
+ line-height: 1.2;
+}
+
+/**
+ * Preformatted text base styles
+ */
+pre {
+ background: $color-gray-88;
+ padding: 2rem;
+ @include boxShadowEffect('green');
+ color: $color-white;
+ position: relative;
+ @include hideScrollbar();
+
+ /**
+ * Remove border from code within preformatted text block
+ */
+ code {
+ border: 0;
+ background: transparent;
+ color: inherit;
+ }
+}
+
+// /**
+// * Code with languages associated with them
+// * 1) Override Prism sysles for code blocks with language
+// */
+// code[class*='language-'],
+// pre[class*='language-'] {
+// font-family: monospace !important;
+// }
diff --git a/packages/docs/src/scss/components/_block-grid.scss b/packages/docs/src/scss/components/_block-grid.scss
new file mode 100644
index 000000000..6b48edacd
--- /dev/null
+++ b/packages/docs/src/scss/components/_block-grid.scss
@@ -0,0 +1,14 @@
+.c-block-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, 250px);
+ grid-gap: $spacing-large;
+ margin-bottom: $spacing-large;
+}
+
+.c-stacked-block {
+ // background: orange;
+}
+
+.c-stacked-block__description {
+ font-size: $font-size-med;
+}
diff --git a/packages/docs/src/scss/components/_buttons.scss b/packages/docs/src/scss/components/_buttons.scss
new file mode 100644
index 000000000..1f8d8cb9d
--- /dev/null
+++ b/packages/docs/src/scss/components/_buttons.scss
@@ -0,0 +1,47 @@
+/*------------------------------------*\
+ #BUTTONS
+\*------------------------------------*/
+
+/**
+ *
+ * 1) Button or link that has functionality to it
+ */
+.c-btn {
+ font-family: $font-secondary;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: $border-width solid $color-btn-primary-border;
+ background: $color-brand-green;
+ color: $color-black;
+ line-height: 1;
+ padding: 1rem 2rem;
+ border: 0;
+ text-align: center;
+ transition: all $anim-fade-quick $anim-ease;
+
+ &:hover,
+ &:focus {
+ background: $color-btn-primary-bg-hover;
+ }
+}
+
+.c-btn--inverted {
+ background: none;
+ border: 1px solid $color-gray-50;
+ color: $color-gray-27;
+}
+
+.c-btn--small {
+ padding: 1rem;
+}
+
+/*
+ * Button icon
+ */
+.c-btn__icon {
+ width: 1rem;
+ height: 1rem;
+ fill: $color-btn-primary;
+ transition: fill $anim-fade-quick $anim-ease;
+}
diff --git a/packages/docs/src/scss/components/_demo-list.scss b/packages/docs/src/scss/components/_demo-list.scss
new file mode 100644
index 000000000..e6b4eaf29
--- /dev/null
+++ b/packages/docs/src/scss/components/_demo-list.scss
@@ -0,0 +1,10 @@
+.c-demo-list {
+ display: grid;
+ grid-column-gap: 1rem;
+ grid-row-gap: 2rem;
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+}
+
+.c-header-demo-page {
+ padding-bottom: 2rem;
+}
diff --git a/packages/docs/src/scss/components/_field.scss b/packages/docs/src/scss/components/_field.scss
new file mode 100644
index 000000000..eb2d65b76
--- /dev/null
+++ b/packages/docs/src/scss/components/_field.scss
@@ -0,0 +1,35 @@
+/*------------------------------------*\
+ #FIELDS
+\*------------------------------------*/
+
+/**
+ * 1) Consists of a label, form control, and an optional note about the field.
+ */
+ .c-field {
+ margin-bottom: $spacing-large;
+}
+
+/**
+ * Field label
+ */
+.c-field__label {
+ margin-bottom: 0.5rem;
+ font-size: $font-size-med;
+ font-weight: bold;
+}
+
+/**
+ * Field body
+ */
+.c-field__body {
+ position: relative;
+}
+
+/**
+ * Field note
+ */
+.c-field__note {
+ display: inline-block;
+ font-size: $font-size-sm;
+ color: $color-gray-50;
+}
\ No newline at end of file
diff --git a/packages/docs/src/scss/components/_footer-nav.scss b/packages/docs/src/scss/components/_footer-nav.scss
new file mode 100644
index 000000000..e851d4c10
--- /dev/null
+++ b/packages/docs/src/scss/components/_footer-nav.scss
@@ -0,0 +1,21 @@
+/*------------------------------------*\
+ #FOOTER NAV
+\*------------------------------------*/
+
+/**
+ * The nav inside the footer
+ */
+.c-footer-nav {
+ font-size: $font-size-sm-2;
+ margin-bottom: $spacing;
+
+ @media all and (min-width: $bp-med) {
+ display: flex;
+ }
+}
+
+.c-footer-nav__item {
+ @media all and (min-width: $bp-med) {
+ margin-right: $spacing-large;
+ }
+}
diff --git a/packages/docs/src/scss/components/_footer.scss b/packages/docs/src/scss/components/_footer.scss
new file mode 100644
index 000000000..e46bc7b03
--- /dev/null
+++ b/packages/docs/src/scss/components/_footer.scss
@@ -0,0 +1,22 @@
+/*------------------------------------*\
+ $FOOTER
+\*------------------------------------*/
+
+/**
+ * 1) Global block at the bottom of each page that contains a navigation and other information
+ */
+.c-footer {
+ padding: $spacing-large 0;
+}
+
+/**
+ * Footer note
+ * 1) Small paragraph of text in the footer
+ */
+ .c-footer__note {
+ font-size: $font-size-sm;
+
+ a {
+ text-decoration: underline;
+ }
+}
\ No newline at end of file
diff --git a/packages/docs/src/scss/components/_header.scss b/packages/docs/src/scss/components/_header.scss
new file mode 100644
index 000000000..bc5255b35
--- /dev/null
+++ b/packages/docs/src/scss/components/_header.scss
@@ -0,0 +1,87 @@
+/*------------------------------------*\
+ #HEADER
+\*------------------------------------*/
+
+/**
+ * Global block at the top of each page containing the navigation, logo, and other potential contents
+ */
+.c-header {
+ position: relative;
+ background: $color-gray-88;
+ color: $color-gray-27;
+ padding: $spacing;
+ @include hideScrollbar();
+
+ @media all and (min-width: $bp-large) {
+ padding: $spacing-large;
+ min-height: 100vh;
+ flex-direction: column;
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ overflow: auto;
+ width: $l-sidebar-width; //Because fixed position
+ }
+}
+
+/**
+ * Header inner
+ */
+.c-header__inner {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ @media all and (min-width: $bp-large) {
+ justify-content: flex-start;
+ align-items: flex-start;
+ flex-direction: column;
+ }
+}
+
+/**
+ * Header navigation button
+ * 1) Button used to toggle the navigation on/off on small screens
+ */
+.c-header__nav-btn {
+ margin-left: auto;
+
+ @media all and (min-width: $bp-large) {
+ display: none;
+ }
+}
+
+/**
+ * Header navigation conntainer
+ * 1) Contains the primary navigation and other possible patterns
+ */
+.c-header__nav-container {
+ display: none;
+
+ @media all and (min-width: $bp-large) {
+ display: block;
+ }
+}
+
+/**
+ * Active header nav container
+ */
+.c-header__nav-container.is-active {
+ display: block;
+ position: absolute;
+ background: $color-gray-88;
+ top: 100%;
+ left: 0;
+ width: 100%;
+ z-index: 5;
+ padding: $spacing;
+
+ @media all and (min-width: $bp-large) {
+ display: block;
+ position: static;
+ padding: 0;
+ margin-left: auto;
+ width: inherit;
+ }
+}
diff --git a/packages/docs/src/scss/components/_heading-permalink.scss b/packages/docs/src/scss/components/_heading-permalink.scss
new file mode 100644
index 000000000..d7ce2690d
--- /dev/null
+++ b/packages/docs/src/scss/components/_heading-permalink.scss
@@ -0,0 +1,38 @@
+/*------------------------------------*\
+ #HEADING PERMALINK
+\*------------------------------------*/
+
+/**
+ * Dynamically applied link icon
+ */
+
+.heading-permalink {
+ position: absolute;
+ top: -3px;
+ left: -20px;
+ display: block;
+ padding-right: 4px;
+
+ .c-text-passage & {
+ opacity: 0;
+ border-bottom: 0;
+ background: none;
+ transition: opacity 0.15s ease;
+
+ &:focus {
+ opacity: 1;
+ }
+ }
+
+ .c-text-passage h2:hover &,
+ .c-text-passage h2:focus &,
+ .c-text-passage h3:hover &,
+ .c-text-passage h3:focus & {
+ opacity: 1;
+ }
+}
+
+.heading-permalink__icon {
+ width: 16px;
+ height: 16px;
+}
diff --git a/packages/docs/src/scss/components/_hero.scss b/packages/docs/src/scss/components/_hero.scss
new file mode 100644
index 000000000..141aaa3a7
--- /dev/null
+++ b/packages/docs/src/scss/components/_hero.scss
@@ -0,0 +1,53 @@
+/*------------------------------------*\
+ #HERO
+\*------------------------------------*/
+
+.c-hero {
+ background-color: $color-brand-purple-light;
+ color: $color-black;
+ padding: 2rem 0;
+ margin-bottom: 2rem;
+}
+
+.c-hero__inner {
+ display: grid;
+ align-items: center;
+
+ @media all and (min-width: $bp-large) {
+ min-height: 60vh;
+ padding-top: 2rem;
+ padding-bottom: 2rem;
+ }
+}
+
+.c-hero__body {
+ max-width: 47rem;
+}
+
+.c-hero__title {
+ color: $color-brand-purple-dark;
+ font-size: $font-size-large-2;
+ line-height: 1;
+ @include textShadowEffect();
+ margin-bottom: 1rem;
+
+ @media all and (min-width: $bp-large) {
+ font-size: 4.5rem;
+ }
+
+ @media all and (min-width: $bp-xl) {
+ font-size: 6rem;
+ }
+}
+
+.c-hero__description {
+ font-weight: bold;
+}
+
+.c-hero__instructions a {
+ text-decoration: underline;
+}
+
+.c-hero__code {
+ display: inline-block;
+}
diff --git a/packages/docs/src/scss/components/_icon.scss b/packages/docs/src/scss/components/_icon.scss
new file mode 100644
index 000000000..b94380c52
--- /dev/null
+++ b/packages/docs/src/scss/components/_icon.scss
@@ -0,0 +1,11 @@
+/*------------------------------------*\
+ #ICON
+\*------------------------------------*/
+
+/**
+ * 1) Small image that represents functionality
+ */
+.c-icon {
+ height: 16px;
+ width: 16px;
+}
diff --git a/packages/docs/src/scss/components/_logo.scss b/packages/docs/src/scss/components/_logo.scss
new file mode 100644
index 000000000..38381483f
--- /dev/null
+++ b/packages/docs/src/scss/components/_logo.scss
@@ -0,0 +1,70 @@
+/*------------------------------------*\
+ #LOGO
+\*------------------------------------*/
+
+/**
+ * Branding image or text of the site
+ */
+.c-logo {
+ @media all and (min-width: $bp-med) {
+ margin-bottom: $spacing-large;
+ }
+}
+
+/**
+ * Logo link
+ */
+.c-logo__link {
+ display: flex;
+ align-items: center;
+
+ &:focus {
+ @include focusInverted();
+ }
+}
+
+/**
+ * Logo image
+ */
+.c-logo__img {
+ display: block;
+ max-width: 2.4rem;
+ margin-right: 0.5rem;
+ animation: rotate 10s linear infinite;
+}
+
+/**
+ * Logo body
+ * 1) contains the logo text and meta info
+ */
+.c-logo__body {
+ position: relative;
+}
+
+/**
+ * Logo text
+ */
+.c-logo__text {
+ text-transform: lowercase;
+ font-weight: bold;
+ color: $color-white;
+}
+
+.c-logo__meta {
+ display: block;
+ padding-top: 0.2rem;
+ font-size: $font-size-sm;
+ color: $color-gray-27;
+ font-weight: 500;
+ position: absolute;
+ top: 100%;
+}
+
+@keyframes rotate {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/packages/docs/src/scss/components/_page-header.scss b/packages/docs/src/scss/components/_page-header.scss
new file mode 100644
index 000000000..822368f90
--- /dev/null
+++ b/packages/docs/src/scss/components/_page-header.scss
@@ -0,0 +1,30 @@
+/*------------------------------------*\
+ #PAGE HEADER
+\*------------------------------------*/
+/**
+ * 1) Container that consists of of a page header title and description
+ */
+.c-page-header {
+ margin-bottom: $spacing-large;
+}
+
+.c-page-header__kicker {
+ color: $color-gray-50;
+ margin-bottom: 0.5rem;
+}
+
+/**
+ * Page header title
+ */
+.c-page-header__title {
+ margin-bottom: $spacing;
+ @include textShadowEffect();
+}
+
+/**
+ * Page description
+ */
+.c-page-header__desc {
+ margin-top: 1rem;
+ margin-bottom: 2rem;
+}
diff --git a/packages/docs/src/scss/components/_primary-nav.scss b/packages/docs/src/scss/components/_primary-nav.scss
new file mode 100644
index 000000000..a7d4e43de
--- /dev/null
+++ b/packages/docs/src/scss/components/_primary-nav.scss
@@ -0,0 +1,42 @@
+/*------------------------------------*\
+ #PRIMARY NAVIGATION
+\*------------------------------------*/
+
+/**
+ * Primary navigation existing in the header and maybe the footer
+ */
+.c-primary-nav {
+ margin-left: auto;
+}
+
+/**
+ * Primary navigation list
+ */
+.c-primary-nav__list {
+ display: flex;
+}
+
+/**
+ * Primary navigation item
+ */
+.c-primary-nav__item {
+ margin-right: 1rem;
+}
+
+/**
+ * Primary navigation link
+ */
+.c-primary-nav__link {
+ display: block;
+ padding: 0.5rem 0;
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: $color-white;
+ }
+
+ &:focus {
+ color: $color-white;
+ font-weight: bold;
+ }
+}
diff --git a/packages/docs/src/scss/components/_stacked-block-list.scss b/packages/docs/src/scss/components/_stacked-block-list.scss
new file mode 100644
index 000000000..885246efd
--- /dev/null
+++ b/packages/docs/src/scss/components/_stacked-block-list.scss
@@ -0,0 +1,12 @@
+/*------------------------------------*\
+ #STACKED BLOCK LIST
+\*------------------------------------*/
+
+/**
+ * Stacked block list item
+ */
+.c-stacked-block-list__item {
+ border-bottom: 1px solid $color-gray-27;
+ padding-bottom: 1rem;
+ margin-bottom: 2rem;
+}
diff --git a/packages/docs/src/scss/components/_stacked-block.scss b/packages/docs/src/scss/components/_stacked-block.scss
new file mode 100644
index 000000000..70d277eb4
--- /dev/null
+++ b/packages/docs/src/scss/components/_stacked-block.scss
@@ -0,0 +1,19 @@
+/*------------------------------------*\
+ #STACKED BLOCK
+\*------------------------------------*/
+
+/**
+ * Stacked block list item
+ */
+.c-stacked-block__title {
+ line-height: 1.2;
+ margin-bottom: 0.25rem;
+}
+
+/**
+ * Stacked block list item
+ */
+.c-stacked-block__kicker {
+ font-size: $font-size-sm-2;
+ color: $color-gray-50;
+}
diff --git a/packages/docs/src/scss/components/_table.scss b/packages/docs/src/scss/components/_table.scss
new file mode 100644
index 000000000..7e2b140d8
--- /dev/null
+++ b/packages/docs/src/scss/components/_table.scss
@@ -0,0 +1,53 @@
+/*------------------------------------*\
+ #TABLE
+\*------------------------------------*/
+
+/**
+ * 1) Data Table
+ */
+.c-table {
+ margin-bottom: 1rem;
+ min-width: 600px; /* 2 */
+}
+
+/**
+ * Table Header
+ */
+.c-table__header {
+ background: $color-gray-07;
+}
+
+/**
+ * Table Header Cell
+ */
+.c-table__header-cell {
+ padding: 0.8rem;
+}
+
+/**
+ * Table Row
+ */
+.c-table__row {
+ border-bottom: 1px solid $color-gray-07;
+}
+
+/**
+ * Table Cell
+ */
+.c-table__cell {
+ padding: 1.6rem 0.8rem;
+}
+
+/**
+ * Table Footer
+ */
+.c-table__footer {
+
+}
+
+/**
+ * Table Footer Cell
+ */
+.c-table__footer-cell {
+ padding: 0.8rem;
+}
diff --git a/packages/docs/src/scss/components/_text-passage.scss b/packages/docs/src/scss/components/_text-passage.scss
new file mode 100644
index 000000000..9f5fa2702
--- /dev/null
+++ b/packages/docs/src/scss/components/_text-passage.scss
@@ -0,0 +1,110 @@
+/*------------------------------------*\
+ #TEXT PASSAGE
+\*------------------------------------*/
+
+/**
+ * 1) A passage of text, including various components (i.e. article, blog post)
+ */
+.c-text-passage {
+ p {
+ margin-bottom: $spacing-large;
+ }
+
+ /**
+ * Link within the text passage
+ */
+ a {
+ border-bottom: 1px solid $color-black;
+ background: $color-brand-green-light;
+
+ &:hover,
+ &:focus {
+ color: $color-black;
+ border-bottom-color: $color-brand-green;
+ }
+ }
+
+ /**
+ * Blockquote within text passage
+ */
+ blockquote {
+ padding-left: 0.8rem;
+ border-left: 3px solid $color-gray-73;
+ color: $color-gray-50;
+ font-size: 1rem;
+ }
+
+ /**
+ * First-level heading within text passage
+ */
+ h1 {
+ margin-bottom: $spacing;
+ }
+
+ /**
+ * Second-level heading within text passage
+ */
+ h2 {
+ margin: 3rem 0 $spacing;
+ color: $color-gray-73;
+ }
+
+ /**
+ * Third-level heading within text passage
+ */
+ h3 {
+ margin: 3rem 0 $spacing;
+ }
+
+ /**
+ * Fourth-level heading within text passage
+ */
+ h4 {
+ margin: 3rem 0 $spacing;
+ }
+
+ /**
+ * Fifth-level heading within text passage
+ */
+ h5 {
+ margin: 3rem 0 $spacing;
+ }
+
+ /**
+ * Sixth-level heading within text passage
+ */
+ h6 {
+ margin: 3rem 0 $spacing;
+ }
+
+ /**
+ * Unordered list within text passage
+ */
+ ul {
+ list-style: disc;
+ margin-left: 1.25rem;
+ margin-bottom: $spacing-large;
+
+ li:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ /**
+ * Ordered list within text passage
+ */
+ ol {
+ list-style: decimal;
+ margin-left: $spacing;
+ margin-bottom: $spacing-large;
+
+ li:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ li {
+ margin-bottom: $spacing;
+ line-height: 1.6;
+ }
+}
diff --git a/packages/docs/src/scss/components/_tile-list.scss b/packages/docs/src/scss/components/_tile-list.scss
new file mode 100644
index 000000000..d5b90775d
--- /dev/null
+++ b/packages/docs/src/scss/components/_tile-list.scss
@@ -0,0 +1,31 @@
+/*------------------------------------*\
+ #TILE
+\*------------------------------------*/
+
+.c-tile-list {
+ display: grid;
+ grid-gap: $spacing-large;
+ margin-bottom: $spacing-xl;
+
+ @media all and (min-width: $bp-xl) {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+.c-tile-list__item:nth-child(1) {
+ @media all and (min-width: $bp-xl) {
+ grid-row: span 2;
+ }
+}
+
+.c-tile-list__item:nth-child(2) {
+ @media all and (min-width: $bp-xl) {
+ grid-column: 2/3;
+ }
+}
+.c-tile-list__item:nth-child(3) {
+ @media all and (min-width: $bp-xl) {
+ grid-row: 2;
+ grid-column: 2/3;
+ }
+}
diff --git a/packages/docs/src/scss/components/_tile.scss b/packages/docs/src/scss/components/_tile.scss
new file mode 100644
index 000000000..67e0f01ac
--- /dev/null
+++ b/packages/docs/src/scss/components/_tile.scss
@@ -0,0 +1,76 @@
+/*------------------------------------*\
+ #TILE
+\*------------------------------------*/
+
+.c-tile {
+ position: relative;
+ z-index: 100;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.c-tile__body {
+ padding: 2rem;
+ position: relative;
+ z-index: 1;
+ flex: 1;
+
+ .c-tile--green & {
+ color: $color-brand-green-dark;
+ background: $color-brand-green-light;
+ }
+
+ .c-tile--orange & {
+ color: $color-brand-orange-dark;
+ background: $color-brand-orange-light;
+ }
+
+ .c-tile--purple & {
+ color: $color-brand-purple-dark;
+ background: $color-brand-purple-light;
+ }
+}
+
+.c-tile__shadow {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ right: -10px;
+ bottom: -10px;
+ z-index: 0;
+ @include stripedBoxShadow('green');
+
+ .c-tile--orange & {
+ @include stripedBoxShadow('orange');
+ }
+
+ .c-tile--purple & {
+ @include stripedBoxShadow('purple');
+ }
+}
+
+.c-tile__title {
+ color: inherit;
+}
+
+.c-tile__link {
+ color: inherit;
+
+ &:hover,
+ &:focus {
+ color: inherit;
+ text-decoration: underline;
+ }
+}
+
+.c-tile__description a {
+ color: inherit;
+ text-decoration: underline;
+
+ &:hover,
+ &:focus {
+ color: inherit;
+ text-decoration: none;
+ }
+}
diff --git a/packages/docs/src/scss/components/_tree-nav.scss b/packages/docs/src/scss/components/_tree-nav.scss
new file mode 100644
index 000000000..15aea9570
--- /dev/null
+++ b/packages/docs/src/scss/components/_tree-nav.scss
@@ -0,0 +1,95 @@
+/*------------------------------------*\
+ #TREE NAV
+\*------------------------------------*/
+
+/**
+ * 1) A tree nav is a nested accordion-style navigation, similar
+ * to operating system file manager navigations
+ */
+.c-tree-nav {
+}
+
+.c-tree-nav__link {
+ display: flex;
+ align-items: center;
+ margin-bottom: 1.2rem;
+ color: inherit;
+ font-weight: $font-weight-bold;
+ transition: color 0.2s ease;
+
+ &:hover,
+ &.is-active {
+ color: $color-white;
+ }
+
+ &:focus {
+ color: $color-white;
+ @include focusInverted();
+ }
+}
+
+.c-tree-nav__link--is-active {
+ color: $color-white;
+ font-weight: bold;
+}
+
+/**
+ * Subnav
+ */
+.c-tree-nav__subnav {
+ padding-left: 1rem;
+ border-left: 1px solid $color-gray-73;
+ margin-bottom: 1rem;
+ font-size: $font-size-med;
+ display: none;
+
+ .c-tree-nav__item.is-active & {
+ display: block;
+ }
+}
+
+.c-tree-nav__icon {
+ transition: transform $anim-fade-quick $anim-ease;
+
+ .c-tree-nav__item.is-active & {
+ transform: rotate(180deg);
+ }
+}
+
+.c-tree-nav__subnav-title {
+ font-weight: bold;
+ display: block;
+ margin-bottom: 1rem;
+}
+
+.c-tree-nav__subnav-link {
+ color: inherit;
+ display: block;
+ margin-bottom: 1rem;
+
+ &:hover,
+ &:focus,
+ &.is-active {
+ color: $color-white;
+ }
+
+ &:focus {
+ @include focusInverted();
+ }
+}
+
+.c-tree-nav__link--btn {
+ background: transparent;
+ border: 0;
+ padding: 0;
+ font-size: inherit;
+
+ svg {
+ fill: currentColor;
+ }
+}
+
+.c-tree-nav__subnav-link--heading {
+ font-weight: bold;
+ color: $color-white;
+}
diff --git a/packages/docs/src/scss/layout/_layout.scss b/packages/docs/src/scss/layout/_layout.scss
new file mode 100644
index 000000000..a6954f67a
--- /dev/null
+++ b/packages/docs/src/scss/layout/_layout.scss
@@ -0,0 +1,42 @@
+/*------------------------------------*\
+ #LAYOUT
+\*------------------------------------*/
+
+/**
+ * Layout Container
+ * 1) Caps the width of the content to the maximum width
+ * and centers the container
+ */
+.l-container {
+ max-width: $l-max-width;
+ margin: 0 auto;
+ padding: 0 2rem;
+
+ @media all and (min-width: $bp-large) {
+ padding: 0 4rem;
+ }
+}
+
+/**
+ *
+ * 1) This narrow layout container is for lists, forms,
+ * and other singular objects that aren't dashboard-y
+ * kinds of displays
+ */
+.l-linelength-container {
+ max-width: $l-linelength-width;
+}
+
+/*------------------------------------*\
+ #GRID
+\*------------------------------------*/
+
+/**
+ * Grid Container
+ */
+.l-grid {
+ @media all and (min-width: $bp-large) {
+ display: grid;
+ grid-template-columns: $l-sidebar-width 1fr;
+ }
+}
diff --git a/packages/docs/src/scss/mixins/_dark-mode.scss b/packages/docs/src/scss/mixins/_dark-mode.scss
new file mode 100644
index 000000000..99e913d2d
--- /dev/null
+++ b/packages/docs/src/scss/mixins/_dark-mode.scss
@@ -0,0 +1,21 @@
+/**
+ * DARK MODE MIXIN
+ *
+ * A little wrapper that lets you define your dark mode custom
+ * properties in a way that supports the theme toggle web component
+ */
+@mixin dark-mode() {
+ @media (prefers-color-scheme: dark) {
+ :root {
+ --color-mode: 'dark';
+ }
+
+ :root:not([data-user-color-scheme]) {
+ @content;
+ }
+ }
+
+ [data-user-color-scheme='dark'] {
+ @content;
+ }
+}
diff --git a/packages/docs/src/scss/style.scss b/packages/docs/src/scss/style.scss
new file mode 100644
index 000000000..d6b95459d
--- /dev/null
+++ b/packages/docs/src/scss/style.scss
@@ -0,0 +1,71 @@
+/*------------------------------------*\
+ #TABLE OF CONTENTS
+\*------------------------------------*/
+/**
+ * ABSTRACTS..............................Declarations of Sass variables & mixins
+ * BASE...................................Default element styles
+ * LAYOUT.................................Layout-specific styles
+ * COMPONENTS.............................Component styles
+ * UTILITIES..............................Utility classes
+ */
+
+/*------------------------------------*\
+ #ABSTRACTS
+\*------------------------------------*/
+@import 'abstracts/variables';
+@import 'abstracts/mixins';
+@import 'abstracts/colors';
+@import 'abstracts/typography';
+
+/*------------------------------------*\
+ #BASE
+\*------------------------------------*/
+@import 'base/reset';
+@import 'base/body';
+@import 'base/links';
+@import 'base/lists';
+@import 'base/headings';
+@import 'base/forms';
+@import 'base/buttons';
+@import 'base/main';
+@import 'base/media';
+@import 'base/text';
+@import 'base/table';
+
+/*------------------------------------*\
+ #LAYOUT
+\*------------------------------------*/
+@import 'layout/layout';
+
+/*------------------------------------*\
+ #COMPONENTS
+\*------------------------------------*/
+@import 'components/buttons';
+@import 'components/footer';
+@import 'components/footer-nav';
+@import 'components/header';
+@import 'components/heading-permalink';
+@import 'components/hero';
+@import 'components/logo';
+@import 'components/icon';
+@import 'components/page-header';
+@import 'components/primary-nav';
+@import 'components/tree-nav';
+@import 'components/text-passage';
+@import 'components/tile';
+@import 'components/tile-list';
+@import 'components/field';
+@import 'components/stacked-block';
+@import 'components/stacked-block-list';
+@import 'components/demo-list';
+@import 'components/block-grid';
+
+/*------------------------------------*\
+ #UTILITIES
+\*------------------------------------*/
+@import 'utilities/visibility';
+
+/*------------------------------------*\
+ #VENDOR
+\*------------------------------------*/
+@import 'vendor/prism';
diff --git a/packages/docs/src/scss/utilities/_visibility.scss b/packages/docs/src/scss/utilities/_visibility.scss
new file mode 100644
index 000000000..ab9998f07
--- /dev/null
+++ b/packages/docs/src/scss/utilities/_visibility.scss
@@ -0,0 +1,26 @@
+/*------------------------------------*\
+ #VISIBILITY CLASSES
+\*------------------------------------*/
+
+/**
+ * Is Hidden
+ * 1) Completely remove from the flow and screen readers.
+ */
+.u-is-hidden {
+ display: none !important;
+ visibility: hidden !important;
+}
+
+/**
+ * Is Visibly Hidden
+ * 1) Completely remove from the flow but leave available to screen readers.
+ */
+.u-is-vishidden {
+ position: absolute !important;
+ overflow: hidden;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ border: 0;
+ clip: rect(1px, 1px, 1px, 1px);
+}
diff --git a/packages/docs/src/scss/vendor/_prism.scss b/packages/docs/src/scss/vendor/_prism.scss
new file mode 100644
index 000000000..1d63832a8
--- /dev/null
+++ b/packages/docs/src/scss/vendor/_prism.scss
@@ -0,0 +1,124 @@
+/* PrismJS 1.17.1
+https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+markup-templating+handlebars+php+json+scss+twig */
+/**
+ * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
+ * Based on https://github.com/chriskempson/tomorrow-theme
+ * @author Rose Pritchard
+ */
+
+code[class*='language-'],
+pre[class*='language-'] {
+ color: #ccc;
+ background: none;
+ font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+ font-size: $font-size-med;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ word-wrap: normal;
+ // white-space: pre-wrap;
+ line-height: 1.5;
+
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
+}
+
+/* Code blocks */
+pre[class*='language-'] {
+ padding: 1em;
+ margin: 0.5em 0;
+ overflow: auto;
+}
+
+:not(pre) > code[class*='language-'],
+pre[class*='language-'] {
+ background: #2d2d2d;
+}
+
+/* Inline code */
+:not(pre) > code[class*='language-'] {
+ padding: 0.1em;
+ border-radius: 0.3em;
+ white-space: normal;
+}
+
+.token.comment,
+.token.block-comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: #999;
+}
+
+.token.punctuation {
+ color: #ccc;
+}
+
+.token.tag,
+.token.attr-name,
+.token.namespace,
+.token.deleted {
+ color: #e2777a;
+}
+
+.token.function-name {
+ color: #6196cc;
+}
+
+.token.boolean,
+.token.number,
+.token.function {
+ color: #f08d49;
+}
+
+.token.property,
+.token.class-name,
+.token.constant,
+.token.symbol {
+ color: #f8c555;
+}
+
+.token.selector,
+.token.important,
+.token.atrule,
+.token.keyword,
+.token.builtin {
+ color: #cc99cd;
+}
+
+.token.string,
+.token.char,
+.token.attr-value,
+.token.regex,
+.token.variable {
+ color: #7ec699;
+}
+
+.token.operator,
+.token.entity,
+.token.url {
+ color: #67cdcc;
+}
+
+.token.important,
+.token.bold {
+ font-weight: bold;
+}
+.token.italic {
+ font-style: italic;
+}
+
+.token.entity {
+ cursor: help;
+}
+
+.token.inserted {
+ color: green;
+}
diff --git a/packages/docs/src/styleguide.njk b/packages/docs/src/styleguide.njk
new file mode 100644
index 000000000..66d64be12
--- /dev/null
+++ b/packages/docs/src/styleguide.njk
@@ -0,0 +1,112 @@
+---
+title: 'Styleguide'
+permalink: /styleguide/
+---
+
+{% extends 'layouts/base.njk' %}
+
+{# Intro content #}
+{% set introHeading = title %}
+
+{% block head %}
+
+{% endblock %}
+{% block content %}
+
+ {% include "partials/components/intro.njk" %}
+
+
+ Colours
+ Colour swatches with various values that you can copy.
+
+ {% for color in styleguide.colors() %}
+
+
+ {{ color.key }}
+
+ Value
+ {{ color.value }}
+ Sass function
+ get-color('{{ color.key }}')
+ Custom Property
+ var(--color-{{ color.key }})
+ Text util class
+ color-{{ color.key }}
+ Background util class
+ bg-{{ color.key }}
+
+
+ {% endfor %}
+
+ Fonts
+ Base — System stack
+
+ The quick brown fox jumps over the lazy fox
+ .font-base
+
+ Serif — Lora
+
+ The quick brown fox jumps over the lazy fox
+ .font-serif
+
+ Text sizes
+ Text sizes are available as standard classes or media query prefixed, such as lg:text-500.
+ {% for size in styleguide.sizes() %}
+ {{ size.value }} - text-{{ size.key }}
+ {% endfor %}
+ Spacing
+ There’s size ratio utilities that give you margin (gap-top, gap-bottom) and padding (pad-top, pad-left, pad-bottom).
+
Margin
+ Margin token classes that you can copy
+
+ {% for size in styleguide.sizes() %}
+
+ gap-top-{{ size.key }}
+
+ {% endfor %}
+
+ Padding
+ Padding token classes that you can copy
+ {% for size in styleguide.sizes() %}
+
+ pad-top-{{ size.key }}
+
+
+ pad-bottom-{{ size.key }}
+
+
+ pad-left-{{ size.key }}
+
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/packages/docs/src/support.md b/packages/docs/src/support.md
new file mode 100644
index 000000000..914404ecb
--- /dev/null
+++ b/packages/docs/src/support.md
@@ -0,0 +1,12 @@
+---
+layout: layouts/post.njk
+title: Pattern Lab Support
+---
+
+## GitHub
+
+[GitHub](https://github.com/pattern-lab/) is the home base for the Pattern Lab project. You can explore the repository and submit/track [issues](https://github.com/pattern-lab/patternlab-node/issues) per the [contribution guidelines](https://github.com/pattern-lab/patternlab-node/blob/master/.github/CONTRIBUTING.md).
+
+## Gitter
+
+You can head over to [Pattern Lab's Gitter](https://gitter.im/pattern-lab/home) to chat with the Pattern Lab community. This is a great place to talk shop, ask quesitons, and get (and give!) help.
diff --git a/packages/docs/src/tags.njk b/packages/docs/src/tags.njk
new file mode 100644
index 000000000..a7cc28d69
--- /dev/null
+++ b/packages/docs/src/tags.njk
@@ -0,0 +1,36 @@
+---
+title: Tag Archive
+pagination:
+ data: collections
+ size: 1
+ alias: tag
+ filter:
+ - all
+ - nav
+ - post
+ - posts
+ - tagList
+ - postFeed
+ addAllPagesToCollections: true
+permalink: /tags/{{ tag }}/
+---
+
+{% extends 'layouts/base.njk' %}
+{% set pageType = 'Tag Archive' %}
+
+{# Intro content #}
+{% set introHeading %}Posts filed under “{{ tag }}”{% endset %}
+{% set introHeadingLevel = '2' %}
+
+{# Post list content #}
+{% set postListHeadingLevel = '2' %}
+{% set postListHeading = 'Posts' %}
+{% set postListItems = collections[tag] %}
+
+{% block content %}
+
+ {% include "partials/components/intro.njk" %}
+ {% include "partials/components/post-list.njk" %}
+ {% include "partials/components/pagination.njk" %}
+
+{% endblock %}
diff --git a/packages/docs/src/transforms/html-min-transform.js b/packages/docs/src/transforms/html-min-transform.js
new file mode 100644
index 000000000..7d0c8f99b
--- /dev/null
+++ b/packages/docs/src/transforms/html-min-transform.js
@@ -0,0 +1,14 @@
+const htmlmin = require('html-minifier');
+
+module.exports = function htmlMinTransform(value, outputPath) {
+ if (outputPath.indexOf('.html') > -1) {
+ let minified = htmlmin.minify(value, {
+ useShortDoctype: true,
+ removeComments: true,
+ collapseWhitespace: true,
+ minifyCSS: true
+ });
+ return minified;
+ }
+ return value;
+};
diff --git a/packages/docs/src/transforms/parse-transform.js b/packages/docs/src/transforms/parse-transform.js
new file mode 100644
index 000000000..541f04cf7
--- /dev/null
+++ b/packages/docs/src/transforms/parse-transform.js
@@ -0,0 +1,79 @@
+const jsdom = require('@tbranyen/jsdom');
+const {JSDOM} = jsdom;
+const minify = require('../utils/minify.js');
+const slugify = require('slugify');
+
+module.exports = function(value, outputPath) {
+ if (outputPath.endsWith('.html')) {
+ const DOM = new JSDOM(value, {
+ resources: 'usable'
+ });
+
+ const document = DOM.window.document;
+ const articleImages = [...document.querySelectorAll('main article img')];
+ const articleHeadings = [
+ ...document.querySelectorAll('main article h2, main article h3')
+ ];
+ const articleEmbeds = [...document.querySelectorAll('main article iframe')];
+
+ if (articleImages.length) {
+ articleImages.forEach(image => {
+ image.setAttribute('loading', 'lazy');
+
+ // If an image has a title it means that the user added a caption
+ // so replace the image with a figure containing that image and a caption
+ if (image.hasAttribute('title')) {
+ const figure = document.createElement('figure');
+ const figCaption = document.createElement('figcaption');
+
+ figCaption.innerHTML = image.getAttribute('title');
+
+ image.removeAttribute('title');
+
+ figure.appendChild(image.cloneNode(true));
+ figure.appendChild(figCaption);
+
+ image.replaceWith(figure);
+ }
+
+ });
+ }
+
+ if (articleHeadings.length) {
+ // Loop each heading and add a little anchor and an ID to each one
+ articleHeadings.forEach(heading => {
+ const headingSlug = slugify(heading.textContent.toLowerCase());
+ const anchor = document.createElement('a');
+
+ anchor.setAttribute('href', `#heading-${headingSlug}`);
+ anchor.classList.add('heading-permalink');
+ anchor.innerHTML = minify(`
+ permalink
+
+
+ `);
+
+ heading.setAttribute('id', `heading-${headingSlug}`);
+ heading.appendChild(anchor);
+ });
+ }
+
+ // Look for videos are wrap them in a container element
+ if (articleEmbeds.length) {
+ articleEmbeds.forEach(embed => {
+ if (embed.hasAttribute('allowfullscreen')) {
+ const player = document.createElement('div');
+
+ player.classList.add('video-player');
+
+ player.appendChild(embed.cloneNode(true));
+
+ embed.replaceWith(player);
+ }
+ });
+ }
+
+ return '\r\n' + document.documentElement.outerHTML;
+ }
+ return value;
+};
diff --git a/packages/docs/src/updates.md b/packages/docs/src/updates.md
new file mode 100644
index 000000000..09a9c2d73
--- /dev/null
+++ b/packages/docs/src/updates.md
@@ -0,0 +1,5 @@
+---
+layout: layouts/blog.njk
+title: Pattern Lab Updates
+description: The latest news about the Pattern Lab project
+---
diff --git a/packages/docs/src/utils/minify.js b/packages/docs/src/utils/minify.js
new file mode 100644
index 000000000..dd80e240c
--- /dev/null
+++ b/packages/docs/src/utils/minify.js
@@ -0,0 +1,3 @@
+module.exports = function minify(input) {
+ return input.replace(/\s{2,}/g, '').replace(/\'/g, '"');
+};
diff --git a/packages/edition-node-gulp/.editorconfig b/packages/edition-node-gulp/.editorconfig
new file mode 100644
index 000000000..8951c3929
--- /dev/null
+++ b/packages/edition-node-gulp/.editorconfig
@@ -0,0 +1,11 @@
+# editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+tab_width = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/packages/edition-node-gulp/.gitignore b/packages/edition-node-gulp/.gitignore
new file mode 100644
index 000000000..0679bd2b5
--- /dev/null
+++ b/packages/edition-node-gulp/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.DS_Store
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+.idea/
+public
+dependencyGraph.json
diff --git a/packages/edition-node-gulp/.npmrc b/packages/edition-node-gulp/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/edition-node-gulp/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/edition-node-gulp/.nvmrc b/packages/edition-node-gulp/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/edition-node-gulp/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/edition-node-gulp/CHANGELOG.md b/packages/edition-node-gulp/CHANGELOG.md
new file mode 100644
index 000000000..5dd0a41e1
--- /dev/null
+++ b/packages/edition-node-gulp/CHANGELOG.md
@@ -0,0 +1,346 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [5.9.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.9.1...v5.9.2) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [5.9.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.9.0...v5.9.1) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [5.7.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.7.1...v5.7.2) (2020-03-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [5.7.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.7.0...v5.7.1) (2020-02-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+# [5.6.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.5.0...v5.6.0) (2020-01-18)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+
+# [5.5.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.4.2...v5.5.0) (2019-12-19)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [5.4.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.4.1...v5.4.2) (2019-11-27)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [5.4.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.4.0...v5.4.1) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [5.3.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.3.2...v5.3.3) (2019-11-22)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+
+## [5.3.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.3.1...v5.3.2) (2019-11-14)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [5.3.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.3.0...v5.3.1) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+# [5.2.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.1.0...v5.2.0) (2019-11-12)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+
+### Features
+
+* **config:** add new default pattern export options ([a7487a0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/a7487a0681cb11e6f3c5c8eaefd62e5648ad5ea3))
+
+
+
+
+
+## [5.0.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.0.1...v5.0.2) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [5.0.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v5.0.0...v5.0.1) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+
+## [2.0.9](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.8...@pattern-lab/edition-node-gulp@2.0.9) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+
+## [2.0.8](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.7...@pattern-lab/edition-node-gulp@2.0.8) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+
+## [2.0.4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.3...@pattern-lab/edition-node-gulp@2.0.4) (2019-08-23)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+## [2.0.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.2...@pattern-lab/edition-node-gulp@2.0.3) (2019-08-23)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+
+## [2.0.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.2-alpha.0...@pattern-lab/edition-node-gulp@2.0.2) (2019-05-16)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+# [2.0.0-beta.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-beta.0...@pattern-lab/edition-node-gulp@2.0.0-beta.2) (2019-02-09)
+
+
+### Bug Fixes
+
+* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/98dfadf))
+* version bump the PL gulp edition package that was also out of sync with the latest version published to NPM ([fb8b425](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/fb8b425))
+
+
+
+
+
+
+# [2.0.0-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.15...@pattern-lab/edition-node-gulp@2.0.0-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+
+
+
+
+# [2.0.0-alpha.15](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.14...@pattern-lab/edition-node-gulp@2.0.0-alpha.15) (2018-07-09)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+# [2.0.0-alpha.14](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.13...@pattern-lab/edition-node-gulp@2.0.0-alpha.14) (2018-07-06)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+# [2.0.0-alpha.13](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.12...@pattern-lab/edition-node-gulp@2.0.0-alpha.13) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/5ab3995))
+
+
+
+# [2.0.0-alpha.12](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.11...@pattern-lab/edition-node-gulp@2.0.0-alpha.12) (2018-07-05)
+
+### Bug Fixes
+
+* **gulp:** remove help command ([71575db](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/71575db))
+
+### Features
+
+* **serve:** change calling method ([f47217a](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/f47217a))
+
+
+
+# [2.0.0-alpha.11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.10...@pattern-lab/edition-node-gulp@2.0.0-alpha.11) (2018-05-19)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+# [2.0.0-alpha.10](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.9...@pattern-lab/edition-node-gulp@2.0.0-alpha.10) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+# [2.0.0-alpha.9](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.8...@pattern-lab/edition-node-gulp@2.0.0-alpha.9) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/edition-node-gulp
+
+
+
+# [2.0.0-alpha.8](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.7...@pattern-lab/edition-node-gulp@2.0.0-alpha.8) (2018-05-04)
+
+### Features
+
+* **API:** standardize v() and version() into a single call ([6309e69](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/6309e69))
+* **config:** add uikits config ([64c2e9f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/64c2e9f))
+* **config:** remove hard-coded base module path from pattern lab paths ([a4961bd](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/a4961bd))
+* **config:** simplify relative public paths ([812bab3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/812bab3))
+* **package:** add cli as a dependency ([a52b487](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/a52b487))
+* **uikits:** remove workshop for default config ([55570ff](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/55570ff))
+
+### BREAKING CHANGES
+
+* **API:** change `version()` to return a string representation of the version, removing `v()`
+
+
+
+# [2.0.0-alpha.7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.6...@pattern-lab/edition-node-gulp@2.0.0-alpha.7) (2018-03-21)
+
+### Bug Fixes
+
+* **package:** remove files obsoleted by monorepo ([9abb8ac](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/9abb8ac))
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/337aa32))
+
+
+
+# [2.0.0-alpha.6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/compare/@pattern-lab/edition-node-gulp@2.0.0-alpha.5...@pattern-lab/edition-node-gulp@2.0.0-alpha.6) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/1473cd5))
+
+### Features
+
+* **README:** Update for brevity and consistency ([65a2969](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/65a2969))
+
+
+
+# 2.0.0-alpha.5 (2018-03-02)
+
+### Bug Fixes
+
+* **package:** Regenerate package.lock and upgrade patternlab-node ([93ec49e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/93ec49e))
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/58beeb6))
+
+### Features
+
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp/commit/5eb2c11))
diff --git a/packages/edition-node-gulp/LICENSE b/packages/edition-node-gulp/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/edition-node-gulp/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/edition-node-gulp/README.md b/packages/edition-node-gulp/README.md
new file mode 100644
index 000000000..c735382a0
--- /dev/null
+++ b/packages/edition-node-gulp/README.md
@@ -0,0 +1,56 @@
+
+
+
+ [](https://gitter.im/pattern-lab/node)
+
+# Pattern Lab Node - Gulp Edition
+
+The Gulp wrapper around [Pattern Lab Node Core](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core)), the default PatternEngine, and supporting frontend assets.
+
+[Online Demo of Pattern Lab Output](http://demo.patternlab.io/)
+
+## Packaged Components
+
+This Edition comes with the following components:
+
+* `@pattern-lab/core`: [GitHub](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core) | [npm](https://www.npmjs.com/package/@pattern-lab/core)
+* `@pattern-lab/engine-mustache`: [GitHub](https://github.com/pattern-lab/tree/master/packages/engine-mustache) | [npm](https://www.npmjs.com/package/@pattern-lab/engine-mustache)
+* `@pattern-lab/uikit-workshop`: [GitHub](https://github.com/pattern-lab/tree/master/packages/uikit-workshop) | [npm](https://www.npmjs.com/package/@pattern-lab/uikit-workshop)
+
+## Prerequisites
+
+This Edition uses [Node](https://nodejs.org) for core processing, [npm](https://www.npmjs.com/) to manage project dependencies, and [gulp.js](http://gulpjs.com/) to run tasks and interface with the core library. You can follow the directions for [installing Node](https://nodejs.org/en/download/) on the Node website if you haven't done so already. Installation of Node will include npm.
+
+## Installing
+
+Pattern Lab Node can be used different ways. Editions like this one are **example** pairings of Pattern Lab code and do not always have an upgrade path or simple means to run as a dependency within a larger project. Users wishing to be most current and have the greatest flexibility are encouraged to consume `patternlab-node` directly. Users wanting to learn more about Pattern Lab and have a tailored default experience are encouraged to start with an Edition. Both methods still expect to interact with other elements of the [Pattern Lab Ecosystem](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core#ecosystem).
+
+Read the [installation instructions](https://github.com/pattern-lab/patternlab-node/tree/master#installation).
+
+## Getting Started
+
+This edition comes pre-packaged with a couple simple gulp tasks. Extend them as needed.
+
+**build** patterns, copy assets, and construct ui
+
+```bash
+gulp patternlab:build
+```
+
+build patterns, copy assets, and construct ui, watch source files, and **serve** locally
+
+```bash
+gulp patternlab:serve
+```
+
+logs Pattern Lab Node usage and **help** content
+
+```bash
+gulp patternlab:help
+```
+
+To interact further with Pattern Lab Node, such as to install plugins or starterkits, check out the rest of the `gulpfile.js`. You could also install the [Pattern Lab Node Command Line Interface](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli) or learn more about the [core API](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core#usage).
+
+## Updating Pattern Lab
+
+To update Pattern Lab please refer to each component's GitHub repository, and the [master instructions for core](https://github.com/pattern-lab/patternlab-node/wiki/Upgrading). The components are listed at the top of the README.
diff --git a/packages/edition-node-gulp/gulpfile.js b/packages/edition-node-gulp/gulpfile.js
new file mode 100644
index 000000000..62ddc007e
--- /dev/null
+++ b/packages/edition-node-gulp/gulpfile.js
@@ -0,0 +1,69 @@
+/******************************************************
+ * PATTERN LAB NODE
+ * EDITION-NODE-GULP
+ * The gulp wrapper around patternlab-node core, providing tasks to interact with the core library.
+ ******************************************************/
+const gulp = require('gulp');
+const argv = require('minimist')(process.argv.slice(2));
+
+/******************************************************
+ * PATTERN LAB NODE WRAPPER TASKS with core library
+ ******************************************************/
+const config = require('./patternlab-config.json');
+const patternlab = require('@pattern-lab/core')(config);
+
+function build() {
+ return patternlab
+ .build({
+ watch: argv.watch,
+ cleanPublic: config.cleanPublic,
+ })
+ .then(() => {
+ // do something else when this promise resolves
+ });
+}
+
+function serve() {
+ return patternlab.server
+ .serve({
+ cleanPublic: config.cleanPublic,
+ watch: true,
+ })
+ .then(() => {
+ // do something else when this promise resolves
+ });
+}
+
+gulp.task('patternlab:version', function() {
+ console.log(patternlab.version());
+});
+
+gulp.task('patternlab:patternsonly', function() {
+ patternlab.patternsonly(config.cleanPublic);
+});
+
+gulp.task('patternlab:liststarterkits', function() {
+ patternlab.liststarterkits();
+});
+
+gulp.task('patternlab:loadstarterkit', function() {
+ patternlab.loadstarterkit(argv.kit, argv.clean);
+});
+
+gulp.task('patternlab:build', function() {
+ build().then(() => {
+ // do something else when this promise resolves
+ });
+});
+
+gulp.task('patternlab:serve', function() {
+ serve().then(() => {
+ // do something else when this promise resolves
+ });
+});
+
+gulp.task('patternlab:installplugin', function() {
+ patternlab.installplugin(argv.plugin);
+});
+
+gulp.task('default', ['patternlab:help']);
diff --git a/packages/edition-node-gulp/package.json b/packages/edition-node-gulp/package.json
new file mode 100644
index 000000000..86df154c9
--- /dev/null
+++ b/packages/edition-node-gulp/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@pattern-lab/edition-node-gulp",
+ "description": "The gulp wrapper around patternlab-node core, providing tasks to interact with the core library and move supporting frontend assets.",
+ "version": "5.9.3",
+ "main": "gulpfile.js",
+ "dependencies": {
+ "@pattern-lab/cli": "^5.9.3",
+ "@pattern-lab/core": "^5.9.3",
+ "@pattern-lab/engine-mustache": "^5.0.0",
+ "@pattern-lab/uikit-workshop": "^5.9.3",
+ "gulp": "3.9.1",
+ "minimist": "1.2.0"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Gulp",
+ "Javascript"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": "Brian Muenzenmeyer",
+ "scripts": {
+ "gulp": "gulp -- ",
+ "patternlab": "patternlab"
+ },
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/edition-node-gulp/patternlab-config.json b/packages/edition-node-gulp/patternlab-config.json
new file mode 100644
index 000000000..20625a44f
--- /dev/null
+++ b/packages/edition-node-gulp/patternlab-config.json
@@ -0,0 +1,97 @@
+{
+ "cacheBust": true,
+ "cleanPublic": true,
+ "defaultPattern": "all",
+ "defaultShowPatternInfo": false,
+ "ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "mqs": false,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+ },
+ "ishViewportRange": {
+ "s": [240, 500],
+ "m": [500, 800],
+ "l": [800, 2600]
+ },
+ "logLevel": "info",
+ "outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+ },
+ "paths": {
+ "source": {
+ "root": "./source/",
+ "patterns": "./source/_patterns/",
+ "data": "./source/_data/",
+ "meta": "./source/_meta/",
+ "annotations": "./source/_annotations/",
+ "styleguide": "dist/",
+ "patternlabFiles": {
+ "general-header":
+ "views/partials/general-header.mustache",
+ "general-footer":
+ "views/partials/general-footer.mustache",
+ "patternSection":
+ "views/partials/patternSection.mustache",
+ "patternSectionSubtype":
+ "views/partials/patternSectionSubtype.mustache",
+ "viewall":
+ "views/viewall.mustache"
+ },
+ "js": "./source/js",
+ "images": "./source/images",
+ "fonts": "./source/fonts",
+ "css": "./source/css"
+ },
+ "public": {
+ "root": "public/",
+ "patterns": "public/patterns/",
+ "data": "public/styleguide/data/",
+ "annotations": "public/annotations/",
+ "styleguide": "public/styleguide/",
+ "js": "public/js",
+ "images": "public/images",
+ "fonts": "public/fonts",
+ "css": "public/css"
+ }
+ },
+ "patternExtension": "mustache",
+ "patternStateCascade": ["inprogress", "inreview", "complete"],
+ "patternExportAll": false,
+ "patternExportDirectory": "pattern_exports",
+ "patternExportPatternPartials": [],
+ "patternExportPreserveDirectoryStructure": true,
+ "patternExportRaw": false,
+ "serverOptions": {
+ "wait": 1000
+ },
+ "starterkitSubDir": "dist",
+ "styleGuideExcludes": [],
+ "theme": {
+ "color": "dark",
+ "density": "compact",
+ "layout": "horizontal"
+ },
+ "uikits": [
+ {
+ "name": "uikit-workshop",
+ "outputDir": "",
+ "enabled": true,
+ "excludedPatternStates": [],
+ "excludedTags": []
+ }
+ ]
+}
diff --git a/packages/edition-node-gulp/source/_annotations/README.md b/packages/edition-node-gulp/source/_annotations/README.md
new file mode 100644
index 000000000..42592a09b
--- /dev/null
+++ b/packages/edition-node-gulp/source/_annotations/README.md
@@ -0,0 +1,5 @@
+This is the default location to place annotations.
+
+Pattern Lab uses annotations defined here to markup the UI. Read more about [annotations](http://patternlab.io/docs/pattern-adding-annotations.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.annotations` property within `patternlab-config.json`.
diff --git a/packages/edition-node-gulp/source/_data/README.md b/packages/edition-node-gulp/source/_data/README.md
new file mode 100644
index 000000000..3b9ea1ea4
--- /dev/null
+++ b/packages/edition-node-gulp/source/_data/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global data files.
+
+Pattern Lab uses data defined here as the global fallback if a template does not provide its own data. Read more about [data](http://patternlab.io/docs/data-overview.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.data` property within `patternlab-config.json`.
diff --git a/packages/edition-node-gulp/source/_data/data.json b/packages/edition-node-gulp/source/_data/data.json
new file mode 100644
index 000000000..0670f0013
--- /dev/null
+++ b/packages/edition-node-gulp/source/_data/data.json
@@ -0,0 +1,3 @@
+{
+ "title": "Title"
+}
diff --git a/packages/edition-node-gulp/source/_meta/README.md b/packages/edition-node-gulp/source/_meta/README.md
new file mode 100644
index 000000000..c6c8c3b8e
--- /dev/null
+++ b/packages/edition-node-gulp/source/_meta/README.md
@@ -0,0 +1,5 @@
+This is the default location to place meta files, otherwise known a pattern's header and footer.
+
+Pattern Lab builds each pattern while prepending and appending the header and footer. Read more about [pattern headers and footers](http://patternlab.io/docs/pattern-header-footer.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.meta` property within `patternlab-config.json`.
diff --git a/packages/edition-node-gulp/source/_meta/_00-head.mustache b/packages/edition-node-gulp/source/_meta/_00-head.mustache
new file mode 100644
index 000000000..0001e7628
--- /dev/null
+++ b/packages/edition-node-gulp/source/_meta/_00-head.mustache
@@ -0,0 +1,17 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
+
diff --git a/packages/edition-node-gulp/source/_meta/_01-foot.mustache b/packages/edition-node-gulp/source/_meta/_01-foot.mustache
new file mode 100644
index 000000000..7c15d6a74
--- /dev/null
+++ b/packages/edition-node-gulp/source/_meta/_01-foot.mustache
@@ -0,0 +1,6 @@
+
+
+ {{{ patternLabFoot }}}
+
+
+
diff --git a/packages/edition-node-gulp/source/_patterns/README.md b/packages/edition-node-gulp/source/_patterns/README.md
new file mode 100644
index 000000000..2f89266bf
--- /dev/null
+++ b/packages/edition-node-gulp/source/_patterns/README.md
@@ -0,0 +1,5 @@
+This is the default location to place pattern files.
+
+Pattern Lab builds patterns and the ui from the structure defined within. Read more about [pattern organization](http://patternlab.io/docs/pattern-organization.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.patterns` property within `patternlab-config.json`.
diff --git a/packages/edition-node-gulp/source/css/README.md b/packages/edition-node-gulp/source/css/README.md
new file mode 100644
index 000000000..a6ce7bf27
--- /dev/null
+++ b/packages/edition-node-gulp/source/css/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global css files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.css` property within `patternlab-config.json`.
diff --git a/packages/edition-node-gulp/source/css/pattern-scaffolding.css b/packages/edition-node-gulp/source/css/pattern-scaffolding.css
new file mode 100644
index 000000000..2a69457ed
--- /dev/null
+++ b/packages/edition-node-gulp/source/css/pattern-scaffolding.css
@@ -0,0 +1,54 @@
+/**
+ * This stylesheet is for styles you want to include only when displaying demo
+ * styles for grids, animations, color swatches, etc.
+ * These styles will not be your production CSS.
+ */
+#sg-patterns {
+ -webkit-box-sizing: border-box !important;
+ box-sizing: border-box !important;
+ max-width: 100%;
+ padding: 0 0.5em;
+}
+
+.demo-animate {
+ background: #ddd;
+ padding: 1em;
+ margin-bottom: 1em;
+ text-align: center;
+ border-radius: 8px;
+ cursor: pointer;
+}
+
+.sg-colors {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ list-style: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+}
+.sg-colors li {
+ -webkit-box-flex: 1;
+ -ms-flex: auto;
+ flex: auto;
+ padding: 0.3em;
+ margin: 0 0.5em 0.5em 0;
+ min-width: 5em;
+ max-width: 14em;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+}
+
+.sg-swatch {
+ display: block;
+ height: 4em;
+ margin-bottom: 0.3em;
+ border-radius: 5px;
+}
+
+.sg-label {
+ font-size: 90%;
+ line-height: 1;
+}
diff --git a/packages/edition-node-gulp/source/css/style.css b/packages/edition-node-gulp/source/css/style.css
new file mode 100644
index 000000000..588a45915
--- /dev/null
+++ b/packages/edition-node-gulp/source/css/style.css
@@ -0,0 +1,3 @@
+/*
+ * YOUR STYLES HERE
+ */
\ No newline at end of file
diff --git a/packages/edition-node-gulp/source/favicon.ico b/packages/edition-node-gulp/source/favicon.ico
new file mode 100644
index 000000000..eee4aa78f
Binary files /dev/null and b/packages/edition-node-gulp/source/favicon.ico differ
diff --git a/packages/edition-node-gulp/source/fonts/README.md b/packages/edition-node-gulp/source/fonts/README.md
new file mode 100644
index 000000000..ff4d4dee0
--- /dev/null
+++ b/packages/edition-node-gulp/source/fonts/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global font files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.fonts` property within `patternlab-config.json`.
diff --git a/packages/edition-node-gulp/source/images/README.md b/packages/edition-node-gulp/source/images/README.md
new file mode 100644
index 000000000..86c91d567
--- /dev/null
+++ b/packages/edition-node-gulp/source/images/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global image files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.images` property within `patternlab-config.json`.
diff --git a/packages/edition-node-gulp/source/js/README.md b/packages/edition-node-gulp/source/js/README.md
new file mode 100644
index 000000000..857893854
--- /dev/null
+++ b/packages/edition-node-gulp/source/js/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global javascript files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.js` property within `patternlab-config.json`.
diff --git a/packages/edition-node/.editorconfig b/packages/edition-node/.editorconfig
new file mode 100644
index 000000000..8951c3929
--- /dev/null
+++ b/packages/edition-node/.editorconfig
@@ -0,0 +1,11 @@
+# editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+tab_width = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/packages/edition-node/.gitignore b/packages/edition-node/.gitignore
new file mode 100644
index 000000000..0679bd2b5
--- /dev/null
+++ b/packages/edition-node/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.DS_Store
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+.idea/
+public
+dependencyGraph.json
diff --git a/packages/edition-node/.npmrc b/packages/edition-node/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/edition-node/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/edition-node/.nvmrc b/packages/edition-node/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/edition-node/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/edition-node/CHANGELOG.md b/packages/edition-node/CHANGELOG.md
new file mode 100644
index 000000000..161487259
--- /dev/null
+++ b/packages/edition-node/CHANGELOG.md
@@ -0,0 +1,351 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+## [5.9.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.9.1...v5.9.2) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+## [5.9.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.9.0...v5.9.1) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+## [5.7.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.7.1...v5.7.2) (2020-03-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+## [5.7.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.7.0...v5.7.1) (2020-02-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+# [5.6.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.5.0...v5.6.0) (2020-01-18)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+
+# [5.5.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.4.2...v5.5.0) (2019-12-19)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+## [5.4.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.4.1...v5.4.2) (2019-11-27)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+## [5.4.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.4.0...v5.4.1) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+## [5.3.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.3.2...v5.3.3) (2019-11-22)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+
+## [5.3.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.3.1...v5.3.2) (2019-11-14)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+## [5.3.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.3.0...v5.3.1) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+# [5.2.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.1.0...v5.2.0) (2019-11-12)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+
+### Features
+
+* **config:** add new default pattern export options ([a7487a0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/a7487a0681cb11e6f3c5c8eaefd62e5648ad5ea3))
+
+
+
+
+
+## [5.0.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.0.1...v5.0.2) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+## [5.0.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v5.0.0...v5.0.1) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Features
+
+* **edition-node:** switch to engine-handlebars ([b481e22](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/b481e22dc1f41ddd4da709621640a15190fba257))
+
+
+### BREAKING CHANGES
+
+* **edition-node:** use handlebars over mustache
+
+
+
+
+
+
+## [2.0.6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@2.0.5...@pattern-lab/edition-node@2.0.6) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+
+## [2.0.5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@2.0.4...@pattern-lab/edition-node@2.0.5) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+
+## [2.0.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@2.0.0...@pattern-lab/edition-node@2.0.1) (2019-08-23)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+# [2.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.2...@pattern-lab/edition-node@2.0.0) (2019-08-23)
+
+
+### Features
+
+* **edition-node:** switch to engine-handlebars ([b481e22](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/b481e22))
+
+
+### BREAKING CHANGES
+
+* **edition-node:** use handlebars over mustache
+
+
+
+
+
+
+## [1.0.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.2-alpha.0...@pattern-lab/edition-node@1.0.2) (2019-05-16)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+# [1.0.0-beta.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-beta.0...@pattern-lab/edition-node@1.0.0-beta.2) (2019-02-09)
+
+
+### Bug Fixes
+
+* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/98dfadf))
+
+
+
+
+
+
+# [1.0.0-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.13...@pattern-lab/edition-node@1.0.0-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+
+
+
+
+# [1.0.0-alpha.13](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.12...@pattern-lab/edition-node@1.0.0-alpha.13) (2018-07-09)
+
+### Features
+
+* **scripts:** namespace scripts ([3ecbb3e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/3ecbb3e))
+
+
+
+# [1.0.0-alpha.12](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.11...@pattern-lab/edition-node@1.0.0-alpha.12) (2018-07-06)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+# [1.0.0-alpha.11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.10...@pattern-lab/edition-node@1.0.0-alpha.11) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/5ab3995))
+
+
+
+# [1.0.0-alpha.10](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.9...@pattern-lab/edition-node@1.0.0-alpha.10) (2018-07-05)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+# [1.0.0-alpha.9](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.8...@pattern-lab/edition-node@1.0.0-alpha.9) (2018-05-19)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+# [1.0.0-alpha.8](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.7...@pattern-lab/edition-node@1.0.0-alpha.8) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+# [1.0.0-alpha.7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.6...@pattern-lab/edition-node@1.0.0-alpha.7) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/edition-node
+
+
+
+# [1.0.0-alpha.6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.5...@pattern-lab/edition-node@1.0.0-alpha.6) (2018-05-04)
+
+### Features
+
+* **package:** add [@pattern-lab](https://github.com/pattern-lab)/cli as a dependency ([760d0e0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/760d0e0))
+* **scripts:** refactor to use cli commands ([e8d5c21](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/e8d5c21))
+* **uikits:** uikits config ([027e56c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/027e56c))
+
+
+
+# [1.0.0-alpha.5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.4...@pattern-lab/edition-node@1.0.0-alpha.5) (2018-03-21)
+
+### Bug Fixes
+
+* **package:** remove files obsoleted by monorepo ([9abb8ac](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/9abb8ac))
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/337aa32))
+
+
+
+# [1.0.0-alpha.4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/compare/@pattern-lab/edition-node@1.0.0-alpha.3...@pattern-lab/edition-node@1.0.0-alpha.4) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/1473cd5))
+
+### Features
+
+* **README:** Update for brevity and consistency ([65a2969](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/65a2969))
+* **README:** Update for brevity and consistency ([a7f6866](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/a7f6866))
+
+
+
+# 1.0.0-alpha.3 (2018-03-02)
+
+### Bug Fixes
+
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/58beeb6))
+* **README:** Fix typos ([b3d1846](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/b3d1846))
+
+### Features
+
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node/commit/5eb2c11))
diff --git a/packages/edition-node/LICENSE b/packages/edition-node/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/edition-node/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/edition-node/README.md b/packages/edition-node/README.md
new file mode 100644
index 000000000..bea6c5e16
--- /dev/null
+++ b/packages/edition-node/README.md
@@ -0,0 +1,57 @@
+
+
+
+[](https://gitter.im/pattern-lab/node)
+
+# Pattern Lab - Node Edition
+
+The pure wrapper around [Pattern Lab Node Core](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core), the default pattern engine, and supporting frontend assets.
+
+[Online Demo of Pattern Lab Output](http://demo.patternlab.io/)
+
+## Packaged Components
+
+This Edition comes with the following components:
+
+* `@pattern-lab/core`: [GitHub](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core) | [npm](https://www.npmjs.com/package/@pattern-lab/core)
+* `@pattern-lab/cli`: [GitHub](https://github.com/pattern-lab/tree/master/packages/cli) | [npm](https://www.npmjs.com/package/@pattern-lab/cli)
+* `@pattern-lab/engine-handlebars`: [GitHub](https://github.com/pattern-lab/tree/master/packages/engine-handlebars) | [npm](https://www.npmjs.com/package/@pattern-lab/engine-handlebars)
+* `@pattern-lab/uikit-workshop`: [GitHub](https://github.com/pattern-lab/tree/master/packages/uikit-workshop) | [npm](https://www.npmjs.com/package/@pattern-lab/uikit-workshop)
+
+## Prerequisites
+
+This Edition uses [Node](https://nodejs.org) for core processing and [npm](https://www.npmjs.com/) to manage project dependencies. You can follow the directions for [installing Node](https://nodejs.org/en/download/) on the Node website if you haven't done so already. Installation of Node will include npm.
+
+## Installing
+
+Pattern Lab Node can be used different ways. Editions lilke this one are **example** pairings of Pattern Lab code and do not always have an upgrade path or simple means to run as a dependency within a larger project. Users wishing to be most current and have the greatest flexibility are encouraged to consume `core` directly. Users wanting to learn more about Pattern Lab and have a tailored default experience are encouraged to start with an Edition. Both methods still expect to interact with other elements of the [Pattern Lab Ecosystem](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core#ecosystem).
+
+Read the [installation instructions](https://github.com/pattern-lab/patternlab-node/tree/master#installation).
+
+## Getting Started
+
+This edition comes pre-packaged with a couple simple scripts. Extend them as needed.
+
+**build** patterns, copy assets, and construct ui
+
+```bash
+npm run build
+```
+
+build patterns, copy assets, and construct ui, watch source files, and **serve** locally
+
+```bash
+npm run serve
+```
+
+logs Pattern Lab Node usage and **help** content
+
+```bash
+npm run help
+```
+
+To interact further with Pattern Lab Node, such as to install plugins or starterkits, it's suggested to incorporate the [Pattern Lab Node Command Line Interface](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli) or learn more about the [core API](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core#usage).
+
+## Updating Pattern Lab
+
+To update Pattern Lab please refer to each component's GitHub repository, and the [master instructions for core](https://github.com/pattern-lab/patternlab-node/wiki/Upgrading). The components are listed at the top of the README.
diff --git a/packages/edition-node/helpers/test.js b/packages/edition-node/helpers/test.js
new file mode 100644
index 000000000..8b32ed799
--- /dev/null
+++ b/packages/edition-node/helpers/test.js
@@ -0,0 +1,5 @@
+module.exports = function(Handlebars) {
+ Handlebars.registerHelper('test', function() {
+ return 'This is a test helper';
+ });
+};
diff --git a/packages/edition-node/package.json b/packages/edition-node/package.json
new file mode 100644
index 000000000..25c2821d6
--- /dev/null
+++ b/packages/edition-node/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@pattern-lab/edition-node",
+ "description": "A pure wrapper around patternlab-node core, the default pattern engine, and supporting frontend assets.",
+ "version": "5.9.3",
+ "main": "patternlab-config.json",
+ "dependencies": {
+ "@pattern-lab/cli": "^5.9.3",
+ "@pattern-lab/core": "^5.9.3",
+ "@pattern-lab/engine-handlebars": "^5.5.0",
+ "@pattern-lab/uikit-workshop": "^5.9.3"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Javascript"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": "Brian Muenzenmeyer",
+ "scripts": {
+ "pl:build": "patternlab build --config ./patternlab-config.json",
+ "pl:help": "patternlab --help",
+ "pl:install": "patternlab install --config ./patternlab-config.json",
+ "pl:serve": "patternlab serve --config ./patternlab-config.json",
+ "pl:version": "patternlab --version"
+ },
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/edition-node/patternlab-config.json b/packages/edition-node/patternlab-config.json
new file mode 100644
index 000000000..e5cef6811
--- /dev/null
+++ b/packages/edition-node/patternlab-config.json
@@ -0,0 +1,102 @@
+{
+ "cacheBust": true,
+ "cleanPublic": true,
+ "defaultPattern": "all",
+ "defaultShowPatternInfo": false,
+ "ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "mqs": false,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+ },
+ "ishViewportRange": {
+ "s": [240, 500],
+ "m": [500, 800],
+ "l": [800, 2600]
+ },
+ "logLevel": "info",
+ "outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+ },
+ "paths": {
+ "source": {
+ "root": "./source/",
+ "patterns": "./source/_patterns/",
+ "data": "./source/_data/",
+ "meta": "./source/_meta/",
+ "annotations": "./source/_annotations/",
+ "styleguide": "dist/",
+ "patternlabFiles": {
+ "general-header":
+ "views/partials/general-header.mustache",
+ "general-footer":
+ "views/partials/general-footer.mustache",
+ "patternSection":
+ "views/partials/patternSection.mustache",
+ "patternSectionSubtype":
+ "views/partials/patternSectionSubtype.mustache",
+ "viewall":
+ "views/viewall.mustache"
+ },
+ "js": "./source/js",
+ "images": "./source/images",
+ "fonts": "./source/fonts",
+ "css": "./source/css"
+ },
+ "public": {
+ "root": "public/",
+ "patterns": "public/patterns/",
+ "data": "public/styleguide/data/",
+ "annotations": "public/annotations/",
+ "styleguide": "public/styleguide/",
+ "js": "public/js",
+ "images": "public/images",
+ "fonts": "public/fonts",
+ "css": "public/css"
+ }
+ },
+ "patternExtension": "hbs",
+ "patternStateCascade": ["inprogress", "inreview", "complete"],
+ "patternExportAll": false,
+ "patternExportDirectory": "pattern_exports",
+ "patternExportPatternPartials": [],
+ "patternExportPreserveDirectoryStructure": true,
+ "patternExportRaw": false,
+ "serverOptions": {
+ "wait": 1000
+ },
+ "starterkitSubDir": "dist",
+ "styleGuideExcludes": [],
+ "theme": {
+ "color": "light",
+ "density": "compact",
+ "layout": "vertical"
+ },
+ "uikits": [
+ {
+ "name": "uikit-workshop",
+ "outputDir": "",
+ "enabled": true,
+ "excludedPatternStates": [],
+ "excludedTags": []
+ }
+ ],
+ "engines": {
+ "handlebars": {
+ "extend": "helpers/*.js"
+ }
+ }
+}
diff --git a/packages/edition-node/source/_annotations/README.md b/packages/edition-node/source/_annotations/README.md
new file mode 100644
index 000000000..42592a09b
--- /dev/null
+++ b/packages/edition-node/source/_annotations/README.md
@@ -0,0 +1,5 @@
+This is the default location to place annotations.
+
+Pattern Lab uses annotations defined here to markup the UI. Read more about [annotations](http://patternlab.io/docs/pattern-adding-annotations.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.annotations` property within `patternlab-config.json`.
diff --git a/packages/edition-node/source/_data/README.md b/packages/edition-node/source/_data/README.md
new file mode 100644
index 000000000..3b9ea1ea4
--- /dev/null
+++ b/packages/edition-node/source/_data/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global data files.
+
+Pattern Lab uses data defined here as the global fallback if a template does not provide its own data. Read more about [data](http://patternlab.io/docs/data-overview.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.data` property within `patternlab-config.json`.
diff --git a/packages/edition-node/source/_meta/README.md b/packages/edition-node/source/_meta/README.md
new file mode 100644
index 000000000..c6c8c3b8e
--- /dev/null
+++ b/packages/edition-node/source/_meta/README.md
@@ -0,0 +1,5 @@
+This is the default location to place meta files, otherwise known a pattern's header and footer.
+
+Pattern Lab builds each pattern while prepending and appending the header and footer. Read more about [pattern headers and footers](http://patternlab.io/docs/pattern-header-footer.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.meta` property within `patternlab-config.json`.
diff --git a/packages/edition-node/source/_meta/_00-head.mustache b/packages/edition-node/source/_meta/_00-head.mustache
new file mode 100644
index 000000000..45ce3bb7d
--- /dev/null
+++ b/packages/edition-node/source/_meta/_00-head.mustache
@@ -0,0 +1,17 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
+
diff --git a/packages/edition-node/source/_meta/_01-foot.mustache b/packages/edition-node/source/_meta/_01-foot.mustache
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/edition-node/source/_meta/_01-foot.mustache
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/edition-node/source/_patterns/README.md b/packages/edition-node/source/_patterns/README.md
new file mode 100644
index 000000000..2f89266bf
--- /dev/null
+++ b/packages/edition-node/source/_patterns/README.md
@@ -0,0 +1,5 @@
+This is the default location to place pattern files.
+
+Pattern Lab builds patterns and the ui from the structure defined within. Read more about [pattern organization](http://patternlab.io/docs/pattern-organization.html).
+
+If you wish to rename this directory, make sure you update the `paths.source.patterns` property within `patternlab-config.json`.
diff --git a/packages/edition-node/source/css/README.md b/packages/edition-node/source/css/README.md
new file mode 100644
index 000000000..a6ce7bf27
--- /dev/null
+++ b/packages/edition-node/source/css/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global css files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.css` property within `patternlab-config.json`.
diff --git a/packages/edition-node/source/favicon.ico b/packages/edition-node/source/favicon.ico
new file mode 100644
index 000000000..eee4aa78f
Binary files /dev/null and b/packages/edition-node/source/favicon.ico differ
diff --git a/packages/edition-node/source/fonts/README.md b/packages/edition-node/source/fonts/README.md
new file mode 100644
index 000000000..ff4d4dee0
--- /dev/null
+++ b/packages/edition-node/source/fonts/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global font files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.fonts` property within `patternlab-config.json`.
diff --git a/packages/edition-node/source/images/README.md b/packages/edition-node/source/images/README.md
new file mode 100644
index 000000000..86c91d567
--- /dev/null
+++ b/packages/edition-node/source/images/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global image files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.images` property within `patternlab-config.json`.
diff --git a/packages/edition-node/source/js/README.md b/packages/edition-node/source/js/README.md
new file mode 100644
index 000000000..857893854
--- /dev/null
+++ b/packages/edition-node/source/js/README.md
@@ -0,0 +1,5 @@
+This is the default location to place global javascript files.
+
+The entire contents of this directory will be recursively copied to your configured `public/` directory.
+
+If you wish to rename this directory, make sure you update the `paths.source.js` property within `patternlab-config.json`.
diff --git a/packages/edition-twig/.editorconfig b/packages/edition-twig/.editorconfig
new file mode 100644
index 000000000..8951c3929
--- /dev/null
+++ b/packages/edition-twig/.editorconfig
@@ -0,0 +1,11 @@
+# editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+tab_width = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/packages/edition-twig/.gitignore b/packages/edition-twig/.gitignore
new file mode 100644
index 000000000..0679bd2b5
--- /dev/null
+++ b/packages/edition-twig/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.DS_Store
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+.idea/
+public
+dependencyGraph.json
diff --git a/packages/edition-twig/.patternlabrc.js b/packages/edition-twig/.patternlabrc.js
new file mode 100644
index 000000000..698dedca8
--- /dev/null
+++ b/packages/edition-twig/.patternlabrc.js
@@ -0,0 +1,4 @@
+module.exports = {
+ // target the UIKit installed / symlinked under node_modules
+ buildDir: __dirname + '/node_modules/@pattern-lab/uikit-workshop/dist',
+};
diff --git a/packages/edition-twig/CHANGELOG.md b/packages/edition-twig/CHANGELOG.md
new file mode 100644
index 000000000..f83cfd3c6
--- /dev/null
+++ b/packages/edition-twig/CHANGELOG.md
@@ -0,0 +1,228 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+## [5.9.2](https://github.com/pattern-lab/patternlab-node/compare/v5.9.1...v5.9.2) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+## [5.9.1](https://github.com/pattern-lab/patternlab-node/compare/v5.9.0...v5.9.1) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+## [5.7.2](https://github.com/pattern-lab/patternlab-node/compare/v5.7.1...v5.7.2) (2020-03-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+## [5.7.1](https://github.com/pattern-lab/patternlab-node/compare/v5.7.0...v5.7.1) (2020-02-24)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/patternlab-node/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+# [5.6.0](https://github.com/pattern-lab/patternlab-node/compare/v5.5.0...v5.6.0) (2020-01-18)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+
+# [5.5.0](https://github.com/pattern-lab/patternlab-node/compare/v5.4.2...v5.5.0) (2019-12-19)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+## [5.4.2](https://github.com/pattern-lab/patternlab-node/compare/v5.4.1...v5.4.2) (2019-11-27)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+## [5.4.1](https://github.com/pattern-lab/patternlab-node/compare/v5.4.0...v5.4.1) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/patternlab-node/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+
+### Features
+
+* test adding cross-env to Twig Edition test ([3f8bb01](https://github.com/pattern-lab/patternlab-node/commit/3f8bb01bc4e96a0aba61c213ea1619c02593defc))
+
+
+
+
+
+## [5.3.3](https://github.com/pattern-lab/patternlab-node/compare/v5.3.2...v5.3.3) (2019-11-22)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+
+## [5.3.2](https://github.com/pattern-lab/patternlab-node/compare/v5.3.1...v5.3.2) (2019-11-14)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+## [5.3.1](https://github.com/pattern-lab/patternlab-node/compare/v5.3.0...v5.3.1) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/patternlab-node/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+# [5.2.0](https://github.com/pattern-lab/patternlab-node/compare/v5.1.0...v5.2.0) (2019-11-12)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/patternlab-node/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+
+### Features
+
+* **config:** add new default pattern export options ([a7487a0](https://github.com/pattern-lab/patternlab-node/commit/a7487a0681cb11e6f3c5c8eaefd62e5648ad5ea3))
+
+
+
+
+
+## [5.0.2](https://github.com/pattern-lab/patternlab-node/compare/v5.0.1...v5.0.2) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+## [5.0.1](https://github.com/pattern-lab/patternlab-node/compare/v5.0.0...v5.0.1) (2019-10-28)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Bug Fixes
+
+* add better pre-rendering support ([8ecd615](https://github.com/pattern-lab/patternlab-node/commit/8ecd6159a89232f42e0a9dc3c688b6e21de8fc30))
+* fix Twig Edition examples by adding missing Twig namespaces to config ([b4c20ef](https://github.com/pattern-lab/patternlab-node/commit/b4c20ef88ee0d3010760584c6f05ff7f92b711a6))
+
+
+
+
+
+
+## [3.1.8](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/edition-twig@3.1.7...@pattern-lab/edition-twig@3.1.8) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+
+## [3.1.7](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/edition-twig@3.1.6...@pattern-lab/edition-twig@3.1.7) (2019-10-14)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+
+## [3.1.3](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/edition-twig@3.1.2...@pattern-lab/edition-twig@3.1.3) (2019-08-23)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
+
+
+
+
+
+## [3.1.2](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/edition-twig@3.1.1...@pattern-lab/edition-twig@3.1.2) (2019-08-23)
+
+
+### Bug Fixes
+
+* add better pre-rendering support ([8ecd615](https://github.com/pattern-lab/patternlab-node/commit/8ecd615))
+
+
+
+
+
+
+## [3.1.1](https://github.com/sghoweri/patternlab-node/compare/@pattern-lab/edition-twig@3.1.0...@pattern-lab/edition-twig@3.1.1) (2019-05-16)
+
+**Note:** Version bump only for package @pattern-lab/edition-twig
diff --git a/packages/edition-twig/README.md b/packages/edition-twig/README.md
new file mode 100644
index 000000000..2d8d9f3d0
--- /dev/null
+++ b/packages/edition-twig/README.md
@@ -0,0 +1,11 @@
+## Working on Pattern Lab's UI Locally
+
+### Step 1: Install Dependencies
+Run the following in the root of the Pattern Lab repo:
+
+```
+yarn run setup
+```
+
+### Step 2
+Finally, go back into this folder, `cd packages/edition-twig`, and start up the local dev server which watches UIKit and the local Pattern Lab instance for changes, live reloads, etc by running `yarn dev`
diff --git a/packages/edition-twig/alter-twig.php b/packages/edition-twig/alter-twig.php
new file mode 100644
index 000000000..0c7c2918c
--- /dev/null
+++ b/packages/edition-twig/alter-twig.php
@@ -0,0 +1,34 @@
+Hello {{ customTwigFunctionThatSaysWorld() }}!` => `Hello Custom World `
+ */
+// $env->addFunction(new \Twig_SimpleFunction('customTwigFunctionThatSaysWorld', function () {
+// return 'Custom World';
+// }));
+
+ /*
+ * Reverse a string
+ * @param string $theString
+ * @example `{{ reverse('abc') }}
` => `cba
`
+ */
+// $env->addFunction(new \Twig_SimpleFunction('reverse', function ($theString) {
+// return strrev($theString);
+// }));
+
+
+// $env->addExtension(new \My\CustomExtension());
+
+// `{{ foo }}` => `bar`
+// $env->addGlobal('foo', 'bar');
+
+ // example of enabling the Twig debug mode extension (ex. {{ dump(my_variable) }} to check out the template's available data) -- comment out to disable
+ // $env->addExtension(new \Twig_Extension_Debug());
+
+}
diff --git a/packages/edition-twig/package-lock.json b/packages/edition-twig/package-lock.json
new file mode 100644
index 000000000..e68ff4f21
--- /dev/null
+++ b/packages/edition-twig/package-lock.json
@@ -0,0 +1,59 @@
+{
+ "name": "@pattern-lab/edition-twig",
+ "version": "3.1.5",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "cross-env": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz",
+ "integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==",
+ "requires": {
+ "cross-spawn": "7.0.1"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz",
+ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==",
+ "requires": {
+ "path-key": "3.1.0",
+ "shebang-command": "2.0.0",
+ "which": "2.0.1"
+ }
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ },
+ "path-key": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz",
+ "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg=="
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "requires": {
+ "shebang-regex": "3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
+ },
+ "which": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz",
+ "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==",
+ "requires": {
+ "isexe": "2.0.0"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/edition-twig/package.json b/packages/edition-twig/package.json
new file mode 100644
index 000000000..f3d13f65f
--- /dev/null
+++ b/packages/edition-twig/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@pattern-lab/edition-twig",
+ "version": "5.9.3",
+ "description": "Pattern Lab node with Twig PHP Engine",
+ "author": {
+ "name": "Evan Lovely",
+ "url": "http://evanlovely.com"
+ },
+ "maintainers": [
+ {
+ "name": "Salem Ghoweri"
+ }
+ ],
+ "main": "patternlab-config.json",
+ "scripts": {
+ "build:uikit": "cross-env-shell PL_CONFIG_PATH='${INIT_CWD}/.patternlabrc.js' npm run build --prefix node_modules/@pattern-lab/uikit-workshop -- --patternlabrc '$PL_CONFIG_PATH'",
+ "build": "patternlab build --config ./patternlab-config.json",
+ "help": "patternlab --help",
+ "install": "patternlab install --config ./patternlab-config.json",
+ "serve": "patternlab serve --config ./patternlab-config.json",
+ "start": "npm run serve",
+ "version": "patternlab --version",
+ "dev": "node ./node_modules/@pattern-lab/uikit-workshop/build-tools.js"
+ },
+ "dependencies": {
+ "@pattern-lab/cli": "^5.9.3",
+ "@pattern-lab/core": "^5.9.3",
+ "@pattern-lab/engine-twig-php": "^5.9.3",
+ "@pattern-lab/uikit-workshop": "^5.9.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "keywords": [
+ "Pattern",
+ "Lab",
+ "Atomic",
+ "Web",
+ "Design",
+ "Twig"
+ ],
+ "license": "MIT"
+}
diff --git a/packages/edition-twig/patternlab-config.json b/packages/edition-twig/patternlab-config.json
new file mode 100644
index 000000000..e9652bde5
--- /dev/null
+++ b/packages/edition-twig/patternlab-config.json
@@ -0,0 +1,162 @@
+{
+ "engines": {
+ "twig": {
+ "namespaces": [
+ {
+ "id": "uikit",
+ "recursive": true,
+ "paths": [
+ "./node_modules/@pattern-lab/uikit-workshop/views-twig"
+ ]
+ },
+ {
+ "id": "atoms",
+ "recursive": true,
+ "paths": [
+ "./source/_patterns/00-atoms"
+ ]
+ },
+ {
+ "id": "molecules",
+ "recursive": true,
+ "paths": [
+ "./source/_patterns/01-molecules"
+ ]
+ },
+ {
+ "id": "organisms",
+ "recursive": true,
+ "paths": [
+ "./source/_patterns/02-organisms"
+ ]
+ },
+ {
+ "id": "templates",
+ "recursive": true,
+ "paths": [
+ "./source/_patterns/03-templates"
+ ]
+ },
+ {
+ "id": "pages",
+ "recursive": true,
+ "paths": [
+ "./source/_patterns/04-pages"
+ ]
+ }
+ ],
+ "alterTwigEnv": [
+ {
+ "file": "alter-twig.php",
+ "functions": [
+ "addCustomExtension"
+ ]
+ }
+ ]
+ }
+ },
+ "cacheBust": true,
+ "cleanPublic": true,
+ "defaultPattern": "all",
+ "defaultShowPatternInfo": false,
+ "ishControlsHide": {
+ "s": false,
+ "m": false,
+ "l": false,
+ "full": false,
+ "random": false,
+ "disco": false,
+ "hay": true,
+ "mqs": false,
+ "find": false,
+ "views-all": false,
+ "views-annotations": false,
+ "views-code": false,
+ "views-new": false,
+ "tools-all": false,
+ "tools-docs": false
+ },
+ "ishViewportRange": {
+ "s": [
+ 240,
+ 500
+ ],
+ "m": [
+ 500,
+ 800
+ ],
+ "l": [
+ 800,
+ 2600
+ ]
+ },
+ "logLevel": "info",
+ "outputFileSuffixes": {
+ "rendered": ".rendered",
+ "rawTemplate": "",
+ "markupOnly": ".markup-only"
+ },
+ "paths": {
+ "source": {
+ "root": "./source/",
+ "patterns": "./source/_patterns/",
+ "data": "./source/_data/",
+ "meta": "./source/_meta/",
+ "annotations": "./source/_annotations/",
+ "styleguide": "dist/",
+ "patternlabFiles": {
+ "general-header": "views/partials/general-header.mustache",
+ "general-footer": "views/partials/general-footer.mustache",
+ "patternSection": "views/partials/patternSection.mustache",
+ "patternSectionSubtype": "views/partials/patternSectionSubtype.mustache",
+ "viewall": "views/viewall.mustache"
+ },
+ "js": "./source/js",
+ "images": "./source/images",
+ "fonts": "./source/fonts",
+ "css": "./source/css"
+ },
+ "public": {
+ "root": "public/",
+ "patterns": "public/patterns/",
+ "data": "public/styleguide/data/",
+ "annotations": "public/annotations/",
+ "styleguide": "public/styleguide/",
+ "js": "public/js",
+ "images": "public/images",
+ "fonts": "public/fonts",
+ "css": "public/css"
+ }
+ },
+ "patternExtension": "twig",
+ "patternStateCascade": [
+ "inprogress",
+ "inreview",
+ "complete"
+ ],
+ "patternExportAll": false,
+ "patternExportDirectory": "pattern_exports",
+ "patternExportPatternPartials": [],
+ "patternExportPreserveDirectoryStructure": true,
+ "patternExportRaw": false,
+ "serverOptions": {
+ "wait": 1000
+ },
+ "starterkitSubDir": "dist",
+ "styleGuideExcludes": [],
+ "theme": {
+ "color": "light",
+ "density": "compact",
+ "layout": "horizontal",
+ "noViewAll": false
+ },
+ "uikits": [
+ {
+ "name": "uikit-workshop",
+ "outputDir": "",
+ "enabled": true,
+ "excludedPatternStates": [],
+ "excludedTags": []
+ }
+ ]
+}
diff --git a/packages/edition-twig/source/_annotations/annotations.js b/packages/edition-twig/source/_annotations/annotations.js
new file mode 100755
index 000000000..6ae3d7dc3
--- /dev/null
+++ b/packages/edition-twig/source/_annotations/annotations.js
@@ -0,0 +1,9 @@
+{
+ "comments" : [
+ {
+ "el": "header[role=banner]",
+ "title" : "Masthead",
+ "comment": "The main header of the site doesn't take up too much screen real estate in order to keep the focus on the core content. It's using a linear CSS gradient instead of a background image to give greater design flexibility and reduce HTTP requests."
+ }
+ ]
+}
diff --git a/packages/edition-twig/source/_data/data.json b/packages/edition-twig/source/_data/data.json
new file mode 100755
index 000000000..2440c9d40
--- /dev/null
+++ b/packages/edition-twig/source/_data/data.json
@@ -0,0 +1,3 @@
+{
+ "title" : "Pattern Lab"
+}
diff --git a/packages/edition-twig/source/_data/listitems.json b/packages/edition-twig/source/_data/listitems.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/packages/edition-twig/source/_data/listitems.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/edition-twig/source/_layouts/.gitkeep b/packages/edition-twig/source/_layouts/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_layouts/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/_macros/.gitkeep b/packages/edition-twig/source/_macros/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_macros/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/_meta/_00-head.mustache b/packages/edition-twig/source/_meta/_00-head.mustache
new file mode 100644
index 000000000..45ce3bb7d
--- /dev/null
+++ b/packages/edition-twig/source/_meta/_00-head.mustache
@@ -0,0 +1,17 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
+
diff --git a/packages/edition-twig/source/_meta/_00-head.twig b/packages/edition-twig/source/_meta/_00-head.twig
new file mode 100755
index 000000000..3891e1793
--- /dev/null
+++ b/packages/edition-twig/source/_meta/_00-head.twig
@@ -0,0 +1,17 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{ patternLabHead | raw }}
+
+
+
+
+
diff --git a/packages/edition-twig/source/_meta/_01-foot.mustache b/packages/edition-twig/source/_meta/_01-foot.mustache
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/edition-twig/source/_meta/_01-foot.mustache
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/edition-twig/source/_meta/_01-foot.twig b/packages/edition-twig/source/_meta/_01-foot.twig
new file mode 100755
index 000000000..4d65e2a55
--- /dev/null
+++ b/packages/edition-twig/source/_meta/_01-foot.twig
@@ -0,0 +1,6 @@
+
+
+ {{ patternLabFoot | raw }}
+
+
+
diff --git a/packages/edition-twig/source/_patterns/00-atoms/00-text/00-headings.twig b/packages/edition-twig/source/_patterns/00-atoms/00-text/00-headings.twig
new file mode 100755
index 000000000..2ecbbf0c5
--- /dev/null
+++ b/packages/edition-twig/source/_patterns/00-atoms/00-text/00-headings.twig
@@ -0,0 +1,6 @@
+Heading Level 1
+Heading Level 2
+Heading Level 3
+Heading Level 4
+Heading Level 5
+Heading Level 6
diff --git a/packages/edition-twig/source/_patterns/00-atoms/05-buttons/_button.twig b/packages/edition-twig/source/_patterns/00-atoms/05-buttons/_button.twig
new file mode 100644
index 000000000..1d124b4ed
--- /dev/null
+++ b/packages/edition-twig/source/_patterns/00-atoms/05-buttons/_button.twig
@@ -0,0 +1 @@
+{{ text }}
diff --git a/packages/edition-twig/source/_patterns/00-atoms/05-buttons/button-dark-demo.twig b/packages/edition-twig/source/_patterns/00-atoms/05-buttons/button-dark-demo.twig
new file mode 100644
index 000000000..7899b77f8
--- /dev/null
+++ b/packages/edition-twig/source/_patterns/00-atoms/05-buttons/button-dark-demo.twig
@@ -0,0 +1,4 @@
+{% include '@atoms/05-buttons/_button.twig' with {
+ text: 'Click Me',
+ dark: true,
+} only %}
diff --git a/packages/edition-twig/source/_patterns/00-atoms/05-buttons/button-simple-demo.twig b/packages/edition-twig/source/_patterns/00-atoms/05-buttons/button-simple-demo.twig
new file mode 100644
index 000000000..b8afb5833
--- /dev/null
+++ b/packages/edition-twig/source/_patterns/00-atoms/05-buttons/button-simple-demo.twig
@@ -0,0 +1,3 @@
+{% include '@atoms/05-buttons/_button.twig' with {
+ text: 'Click Me',
+} only %}
diff --git a/packages/edition-twig/source/_patterns/01-molecules/.gitkeep b/packages/edition-twig/source/_patterns/01-molecules/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_patterns/01-molecules/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/_patterns/01-molecules/05-card/card.twig b/packages/edition-twig/source/_patterns/01-molecules/05-card/card.twig
new file mode 100644
index 000000000..0d8840f90
--- /dev/null
+++ b/packages/edition-twig/source/_patterns/01-molecules/05-card/card.twig
@@ -0,0 +1,6 @@
+
+
Card Title here
+ {% include '@atoms/05-buttons/_button.twig' with {
+ text: 'some text from card'
+ } only %}
+
diff --git a/packages/edition-twig/source/_patterns/02-organisms/.gitkeep b/packages/edition-twig/source/_patterns/02-organisms/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_patterns/02-organisms/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/_patterns/03-templates/.gitkeep b/packages/edition-twig/source/_patterns/03-templates/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_patterns/03-templates/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/_patterns/04-pages/.gitkeep b/packages/edition-twig/source/_patterns/04-pages/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_patterns/04-pages/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/_twig-components/filters/.gitkeep b/packages/edition-twig/source/_twig-components/filters/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_twig-components/filters/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/_twig-components/functions/.gitkeep b/packages/edition-twig/source/_twig-components/functions/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_twig-components/functions/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/_twig-components/tags/.gitkeep b/packages/edition-twig/source/_twig-components/tags/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_twig-components/tags/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/_twig-components/tests/.gitkeep b/packages/edition-twig/source/_twig-components/tests/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/_twig-components/tests/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/css/.gitkeep b/packages/edition-twig/source/css/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/css/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/favicon.ico b/packages/edition-twig/source/favicon.ico
new file mode 100644
index 000000000..eee4aa78f
Binary files /dev/null and b/packages/edition-twig/source/favicon.ico differ
diff --git a/packages/edition-twig/source/fonts/.gitkeep b/packages/edition-twig/source/fonts/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/fonts/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/images/.gitkeep b/packages/edition-twig/source/images/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/images/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/edition-twig/source/js/.gitkeep b/packages/edition-twig/source/js/.gitkeep
new file mode 100755
index 000000000..cdd065ddb
--- /dev/null
+++ b/packages/edition-twig/source/js/.gitkeep
@@ -0,0 +1 @@
+keeping this directory
\ No newline at end of file
diff --git a/packages/engine-handlebars/.gitignore b/packages/engine-handlebars/.gitignore
new file mode 100644
index 000000000..74ab03195
--- /dev/null
+++ b/packages/engine-handlebars/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+.DS_Store
+latest-change.txt
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+source/css/style.css.map
+.idea/
+public
diff --git a/packages/engine-handlebars/.npmrc b/packages/engine-handlebars/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/engine-handlebars/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/engine-handlebars/.nvmrc b/packages/engine-handlebars/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/engine-handlebars/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/engine-handlebars/CHANGELOG.md b/packages/engine-handlebars/CHANGELOG.md
new file mode 100644
index 000000000..4e702faa0
--- /dev/null
+++ b/packages/engine-handlebars/CHANGELOG.md
@@ -0,0 +1,124 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [5.5.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/v5.4.2...v5.5.0) (2019-12-19)
+
+**Note:** Version bump only for package @pattern-lab/engine-handlebars
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Bug Fixes
+
+* **lint:** Use const instead of var ([ad1e782](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/ad1e782ef71295eb610f56d019eaa35499fb3f85))
+* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/74e5af28c4e714fdfc1db535b94c52f3dc14a3a4))
+
+
+### Features
+
+* **engine-handlebars:** Default location for helpers, like engine-nunjucks ([11c4180](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/11c41805e0c3dbebb7109719c4f3c780d32feab5))
+* **engine-handlebars:** Document the Helpers feature ([a01e040](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/a01e040429a7f77dfeb28d67c690e835b97881de))
+* **engine-handlebars:** Load Handlebars helpers specified in the config ([a12df36](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/a12df36d2a644dfac8ded1dfd94b987e99c29d79))
+
+
+
+
+
+
+# [2.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/@pattern-lab/engine-handlebars@2.0.0-beta.1...@pattern-lab/engine-handlebars@2.0.0) (2019-08-23)
+
+
+### Bug Fixes
+
+* **lint:** Use const instead of var ([ad1e782](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/ad1e782))
+* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/74e5af2))
+
+
+### Features
+
+* **engine-handlebars:** Default location for helpers, like engine-nunjucks ([11c4180](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/11c4180))
+* **engine-handlebars:** Document the Helpers feature ([a01e040](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/a01e040))
+* **engine-handlebars:** Load Handlebars helpers specified in the config ([a12df36](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/a12df36))
+
+
+
+
+
+
+# [2.0.0-beta.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/@pattern-lab/engine-handlebars@2.0.0-beta.0...@pattern-lab/engine-handlebars@2.0.0-beta.1) (2019-02-09)
+
+**Note:** Version bump only for package @pattern-lab/engine-handlebars
+
+
+
+
+
+
+# [2.0.0-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/@pattern-lab/engine-handlebars@2.0.0-alpha.8...@pattern-lab/engine-handlebars@2.0.0-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/engine-handlebars
+
+
+
+
+
+
+
+# [2.0.0-alpha.8](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/@pattern-lab/engine-handlebars@2.0.0-alpha.7...@pattern-lab/engine-handlebars@2.0.0-alpha.8) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/5ab3995))
+
+
+
+# [2.0.0-alpha.7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/@pattern-lab/engine-handlebars@2.0.0-alpha.6...@pattern-lab/engine-handlebars@2.0.0-alpha.7) (2018-07-05)
+
+### Features
+
+* **tests:** use lerna run test at the monorepo level ([38a01b1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/38a01b1))
+
+
+
+# [2.0.0-alpha.6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/@pattern-lab/engine-handlebars@2.0.0-alpha.5...@pattern-lab/engine-handlebars@2.0.0-alpha.6) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/engine-handlebars
+
+
+
+# [2.0.0-alpha.5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/@pattern-lab/engine-handlebars@2.0.0-alpha.4...@pattern-lab/engine-handlebars@2.0.0-alpha.5) (2018-03-21)
+
+### Bug Fixes
+
+* **lint:** run code through prettier ([ca52fde](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/ca52fde)), closes [#825](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/issues/825)
+* **package:** remove files obsoleted by monorepo ([9abb8ac](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/9abb8ac))
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/337aa32))
+* **README:** update content for consistency ([4edf0d4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/4edf0d4)), closes [#815](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/issues/815)
+
+
+
+# [2.0.0-alpha.4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/compare/@pattern-lab/engine-handlebars@2.0.0-alpha.3...@pattern-lab/engine-handlebars@2.0.0-alpha.4) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/1473cd5))
+
+
+
+# 2.0.0-alpha.3 (2018-03-02)
+
+### Bug Fixes
+
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/58beeb6))
+
+### Features
+
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars/commit/5eb2c11))
diff --git a/packages/engine-handlebars/LICENSE b/packages/engine-handlebars/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/engine-handlebars/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/engine-handlebars/README.md b/packages/engine-handlebars/README.md
new file mode 100644
index 000000000..2dc828d60
--- /dev/null
+++ b/packages/engine-handlebars/README.md
@@ -0,0 +1,44 @@
+# The Handlebars PatternEngine for Pattern Lab / Node
+
+To install the Handlebars PatternEngine in your edition, `npm install --save @pattern-lab/engine-handlebars` should do the trick.
+
+## Supported features
+
+* [x] [Includes](http://patternlab.io/docs/pattern-including.html)
+* [x] Lineage
+* [x] [Hidden Patterns](http://patternlab.io/docs/pattern-hiding.html)
+* [x] [Pseudo-Patterns](http://patternlab.io/docs/pattern-pseudo-patterns.html)
+* [x] [Pattern States](http://patternlab.io/docs/pattern-states.html)
+* [ ] [Pattern Parameters](http://patternlab.io/docs/pattern-parameters.html) (Accomplished instead using [native Handlebars partial arguments](http://handlebarsjs.com/partials.html))
+* [ ] [Style Modifiers](http://patternlab.io/docs/pattern-stylemodifier.html) (Accomplished instead using [native Handlebars partial arguments](http://handlebarsjs.com/partials.html))
+
+## Helpers
+
+To add custom [helpers](http://handlebarsjs.com/#helpers) or otherwise interact with Handlebars directly, create a file named `patternlab-handlebars-config.js` in the root of your Pattern Lab project, or override the default location by specifying one or several glob patterns in the Pattern Lab config:
+
+```json
+ {
+ ...
+ "engines": {
+ "handlebars": {
+ "extend": [
+ "handlebars-helpers.js",
+ "helpers/**/*.js"
+ ]
+ }
+ }
+ }
+```
+
+Each file should export a function which takes Handlebars as an argument.
+
+```js
+module.exports = function(Handlebars) {
+ // Put helpers here
+
+ Handlebars.registerHelper('fullName', function(person) {
+ // Example: person = {firstName: "Alan", lastName: "Johnson"}
+ return person.firstName + " " + person.lastName;
+ });
+};
+```
diff --git a/packages/engine-handlebars/_meta/_00-head.hbs b/packages/engine-handlebars/_meta/_00-head.hbs
new file mode 100644
index 000000000..b1f5c1ce0
--- /dev/null
+++ b/packages/engine-handlebars/_meta/_00-head.hbs
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
diff --git a/packages/engine-handlebars/_meta/_01-foot.hbs b/packages/engine-handlebars/_meta/_01-foot.hbs
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/engine-handlebars/_meta/_01-foot.hbs
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/engine-handlebars/lib/engine_handlebars.js b/packages/engine-handlebars/lib/engine_handlebars.js
new file mode 100644
index 000000000..80e0b90da
--- /dev/null
+++ b/packages/engine-handlebars/lib/engine_handlebars.js
@@ -0,0 +1,166 @@
+'use strict';
+
+/*
+ * handlebars pattern engine for patternlab-node
+ *
+ * Geoffrey Pursell, Brian Muenzenmeyer, and the web community.
+ * Licensed under the MIT license.
+ *
+ * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice.
+ *
+ */
+
+/*
+ * ENGINE SUPPORT LEVEL:
+ *
+ * Full. Partial calls and lineage hunting are supported. Handlebars does not
+ * support the mustache-specific syntax extensions, style modifiers and pattern
+ * parameters, because their use cases are addressed by the core Handlebars
+ * feature set. It also does not support verbose partial syntax, because it
+ * seems like it can't tolerate slashes in partial names. But honestly, did you
+ * really want to use the verbose syntax anyway? I don't.
+ *
+ */
+
+const fs = require('fs-extra');
+const path = require('path');
+const Handlebars = require('handlebars');
+const glob = require('glob');
+
+// regexes, stored here so they're only compiled once
+const findPartialsRE = /{{#?>\s*([\w-\/.]+)(?:.|\s+)*?}}/g;
+const findListItemsRE = /({{#( )?)(list(I|i)tems.)(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)( )?}}/g;
+const findAtPartialBlockRE = /{{#?>\s*@partial-block\s*}}/g;
+
+function escapeAtPartialBlock(partialString) {
+ const partial = partialString.replace(
+ findAtPartialBlockRE,
+ '{{> @partial-block }}'
+ );
+ return partial;
+}
+
+function loadHelpers(helpers) {
+ helpers.forEach(globPattern => {
+ glob.sync(globPattern).forEach(filePath => {
+ require(path.join(process.cwd(), filePath))(Handlebars);
+ });
+ });
+}
+
+const engine_handlebars = {
+ engine: Handlebars,
+ engineName: 'handlebars',
+ engineFileExtension: ['.hbs', '.handlebars'],
+
+ // partial expansion is only necessary for Mustache templates that have
+ // style modifiers or pattern parameters (I think)
+ expandPartials: false,
+
+ // render it
+ renderPattern: function renderPattern(pattern, data, partials) {
+ if (partials) {
+ Handlebars.registerPartial(partials);
+ }
+
+ const compiled = Handlebars.compile(escapeAtPartialBlock(pattern.template));
+
+ return Promise.resolve(compiled(data));
+ },
+
+ registerPartial: function(pattern) {
+ // register exact partial name
+ Handlebars.registerPartial(pattern.patternPartial, pattern.template);
+
+ Handlebars.registerPartial(pattern.verbosePartial, pattern.template);
+ },
+
+ // find and return any {{> template-name }} within pattern
+ findPartials: function findPartials(pattern) {
+ const matches = pattern.template.match(findPartialsRE);
+ return matches;
+ },
+ findPartialsWithStyleModifiers: function() {
+ // TODO: make the call to this from oPattern objects conditional on their
+ // being implemented here.
+ return [];
+ },
+
+ // returns any patterns that match {{> value(foo:"bar") }} or {{>
+ // value:mod(foo:"bar") }} within the pattern
+ findPartialsWithPatternParameters: function() {
+ // TODO: make the call to this from oPattern objects conditional on their
+ // being implemented here.
+ return [];
+ },
+ findListItems: function(pattern) {
+ const matches = pattern.template.match(findListItemsRE);
+ return matches;
+ },
+
+ // given a pattern, and a partial string, tease out the "pattern key" and
+ // return it.
+ findPartial: function(partialString) {
+ const partial = partialString.replace(findPartialsRE, '$1');
+ return partial;
+ },
+
+ spawnFile: function(config, fileName) {
+ const paths = config.paths;
+ const metaFilePath = path.resolve(paths.source.meta, fileName);
+ try {
+ fs.statSync(metaFilePath);
+ } catch (err) {
+ //not a file, so spawn it from the included file
+ const metaFileContent = fs.readFileSync(
+ path.resolve(__dirname, '..', '_meta/', fileName),
+ 'utf8'
+ );
+ fs.outputFileSync(metaFilePath, metaFileContent);
+ }
+ },
+
+ /**
+ * Checks to see if the _meta directory has engine-specific head and foot files,
+ * spawning them if not found.
+ *
+ * @param {object} config - the global config object from core, since we won't
+ * assume it's already present
+ */
+ spawnMeta: function(config) {
+ this.spawnFile(config, '_00-head.hbs');
+ this.spawnFile(config, '_01-foot.hbs');
+ },
+
+ /**
+ * Accept a Pattern Lab config object from the core and use the settings to
+ * load helpers.
+ *
+ * @param {object} config - the global config object from core
+ */
+ usePatternLabConfig: function(config) {
+ let helpers;
+
+ try {
+ // Look for helpers in the config
+ helpers = config.engines.handlebars.extend;
+
+ if (typeof helpers === 'string') {
+ helpers = [helpers];
+ }
+ } catch (error) {
+ // Look for helpers in default location
+ const configPath = 'patternlab-handlebars-config.js';
+ if (fs.existsSync(path.join(process.cwd(), configPath))) {
+ helpers = [configPath];
+ }
+ }
+
+ // Load helpers if they were found
+ if (helpers) {
+ loadHelpers(helpers);
+ }
+ },
+};
+
+module.exports = engine_handlebars;
diff --git a/packages/engine-handlebars/package.json b/packages/engine-handlebars/package.json
new file mode 100644
index 000000000..5ab060d43
--- /dev/null
+++ b/packages/engine-handlebars/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "@pattern-lab/engine-handlebars",
+ "description": "The Handlebars engine for Pattern Lab / Node",
+ "version": "5.5.0",
+ "main": "lib/engine_handlebars.js",
+ "dependencies": {
+ "fs-extra": "^8.1.0",
+ "glob": "^7.1.6",
+ "handlebars": "^4.5.3"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Grunt",
+ "Gulp",
+ "Javascript",
+ "Handlebars"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": "Brian Muenzenmeyer & Geoffrey Pursell",
+ "license": "MIT",
+ "scripts": {},
+ "engines": {
+ "node": ">=12.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/engine-liquid/.gitignore b/packages/engine-liquid/.gitignore
new file mode 100644
index 000000000..74ab03195
--- /dev/null
+++ b/packages/engine-liquid/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+.DS_Store
+latest-change.txt
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+source/css/style.css.map
+.idea/
+public
diff --git a/packages/engine-liquid/.npmrc b/packages/engine-liquid/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/engine-liquid/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/engine-liquid/.nvmrc b/packages/engine-liquid/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/engine-liquid/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/engine-liquid/CHANGELOG.md b/packages/engine-liquid/CHANGELOG.md
new file mode 100644
index 000000000..ac7f4559b
--- /dev/null
+++ b/packages/engine-liquid/CHANGELOG.md
@@ -0,0 +1,77 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+**Note:** Version bump only for package @pattern-lab/engine-liquid
+
+
+
+
+
+
+# [1.0.0-beta.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/compare/@pattern-lab/engine-liquid@1.0.0-beta.0...@pattern-lab/engine-liquid@1.0.0-beta.1) (2019-02-09)
+
+**Note:** Version bump only for package @pattern-lab/engine-liquid
+
+
+
+
+
+
+# [1.0.0-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/compare/@pattern-lab/engine-liquid@1.0.0-alpha.10...@pattern-lab/engine-liquid@1.0.0-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/engine-liquid
+
+
+
+
+
+
+
+# [1.0.0-alpha.10](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/compare/@pattern-lab/engine-liquid@1.0.0-alpha.9...@pattern-lab/engine-liquid@1.0.0-alpha.10) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/5ab3995))
+
+
+
+# [1.0.0-alpha.9](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/compare/@pattern-lab/engine-liquid@1.0.0-alpha.8...@pattern-lab/engine-liquid@1.0.0-alpha.9) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/engine-liquid
+
+
+
+# [1.0.0-alpha.8](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/compare/@pattern-lab/engine-liquid@1.0.0-alpha.7...@pattern-lab/engine-liquid@1.0.0-alpha.8) (2018-03-21)
+
+### Bug Fixes
+
+* **lint:** run code through prettier ([ca52fde](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/ca52fde)), closes [#825](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/issues/825)
+* **package:** remove files obsoleted by monorepo ([9abb8ac](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/9abb8ac))
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/337aa32))
+* **README:** update content for consistency ([4edf0d4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/4edf0d4)), closes [#815](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/issues/815)
+
+
+
+# [1.0.0-alpha.7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/compare/@pattern-lab/engine-liquid@1.0.0-alpha.6...@pattern-lab/engine-liquid@1.0.0-alpha.7) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/1473cd5))
+
+
+
+# 1.0.0-alpha.6 (2018-03-02)
+
+### Bug Fixes
+
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/58beeb6))
+
+### Features
+
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid/commit/5eb2c11))
diff --git a/packages/engine-liquid/LICENSE b/packages/engine-liquid/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/engine-liquid/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/engine-liquid/README.md b/packages/engine-liquid/README.md
new file mode 100644
index 000000000..518aba27f
--- /dev/null
+++ b/packages/engine-liquid/README.md
@@ -0,0 +1,22 @@
+# The Liquid PatternEngine for Pattern Lab / Node
+
+Based on the the stellar initial work found in https://github.com/cameronroe/patternengine-node-liquid
+
+## Installing
+
+To install the Liquid PatternEngine in your edition, `npm install @pattern-lab/engine-liquid` should do the trick.
+
+## Supported features
+
+This PatternEngine is in alpha and considered a work in progress.
+
+* [x] [Includes](http://patternlab.io/docs/pattern-including.html)
+* [x] Lineage
+
+**TBD**
+
+* [ ] [Hidden Patterns](http://patternlab.io/docs/pattern-hiding.html)
+* [ ] [Pseudo-Patterns](http://patternlab.io/docs/pattern-pseudo-patterns.html)
+* [ ] [Pattern States](http://patternlab.io/docs/pattern-states.html)
+* [ ] [Pattern Parameters](http://patternlab.io/docs/pattern-parameters.html)
+* [ ] [Style Modifiers](http://patternlab.io/docs/pattern-stylemodifier.html)
diff --git a/packages/engine-liquid/_meta/_00-head.liquid b/packages/engine-liquid/_meta/_00-head.liquid
new file mode 100644
index 000000000..b1f5c1ce0
--- /dev/null
+++ b/packages/engine-liquid/_meta/_00-head.liquid
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
diff --git a/packages/engine-liquid/_meta/_01-foot.liquid b/packages/engine-liquid/_meta/_01-foot.liquid
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/engine-liquid/_meta/_01-foot.liquid
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/engine-liquid/lib/engine_liquid.js b/packages/engine-liquid/lib/engine_liquid.js
new file mode 100644
index 000000000..0f61aff59
--- /dev/null
+++ b/packages/engine-liquid/lib/engine_liquid.js
@@ -0,0 +1,194 @@
+/*
+ * Liquid pattern engine for patternlab-node - v2.X.X - 2017
+ *
+ * Cameron Roe
+ * Licensed under the MIT license.
+ *
+ *
+ */
+
+'use strict';
+
+const fs = require('fs-extra');
+const path = require('path');
+const isDirectory = source => fs.lstatSync(source).isDirectory();
+const getDirectories = source =>
+ fs
+ .readdirSync(source)
+ .map(name => path.join(source, name))
+ .filter(isDirectory);
+
+const { lstatSync, readdirSync } = require('fs');
+const { join } = require('path');
+
+var utils = require('./util_liquid');
+var Liquid = require('liquidjs');
+
+let engine = Liquid({
+ dynamicPartials: false,
+});
+
+// This holds the config from from core. The core has to call
+// usePatternLabConfig() at load time for this to be populated.
+let patternLabConfig = {};
+
+module.exports = {
+ engine: engine,
+ engineName: 'liquid',
+ engineFileExtension: ['.liquid', '.html'],
+ isAsync: true,
+
+ // // partial expansion is only necessary for Mustache templates that have
+ // // style modifiers or pattern parameters (I think)
+ // expandPartials: true,
+
+ // regexes, stored here so they're only compiled once
+ findPartialsRE: utils.partialsRE,
+ findPartialsWithStyleModifiersRE: utils.partialsWithStyleModifiersRE,
+ findPartialsWithPatternParametersRE: utils.partialsWithPatternParametersRE,
+ findListItemsRE: utils.listItemsRE,
+ findPartialRE: utils.partialRE,
+
+ // render it
+ renderPattern: function renderPattern(pattern, data, partials) {
+ return engine
+ .parseAndRender(pattern.template, data)
+ .then(function(html) {
+ return html;
+ })
+ .catch(function(ex) {
+ console.log(40, ex);
+ });
+ },
+
+ /**
+ * Find regex matches within both pattern strings and pattern objects.
+ *
+ * @param {string|object} pattern Either a string or a pattern object.
+ * @param {object} regex A JavaScript RegExp object.
+ * @returns {array|null} An array if a match is found, null if not.
+ */
+ patternMatcher: function patternMatcher(pattern, regex) {
+ var matches;
+ if (typeof pattern === 'string') {
+ matches = pattern.match(regex);
+ } else if (
+ typeof pattern === 'object' &&
+ typeof pattern.template === 'string'
+ ) {
+ matches = pattern.template.match(regex);
+ }
+ return matches;
+ },
+
+ // find and return any {{> template-name }} within pattern
+ findPartials: function findPartials(pattern) {
+ var matches = this.patternMatcher(pattern, this.findPartialsRE);
+ return matches;
+ },
+ findPartialsWithStyleModifiers: function(pattern) {
+ var matches = this.patternMatcher(
+ pattern,
+ this.findPartialsWithStyleModifiersRE
+ );
+ return matches;
+ },
+
+ // returns any patterns that match {{> value(foo:"bar") }} or {{>
+ // value:mod(foo:"bar") }} within the pattern
+ findPartialsWithPatternParameters: function(pattern) {
+ var matches = this.patternMatcher(
+ pattern,
+ this.findPartialsWithPatternParametersRE
+ );
+ return matches;
+ },
+ findListItems: function(pattern) {
+ var matches = this.patternMatcher(pattern, this.findListItemsRE);
+ return matches;
+ },
+
+ // given a pattern, and a partial string, tease out the "pattern key" and
+ // return it.
+ findPartial_new: function(partialString) {
+ var partial = partialString.replace(this.findPartialRE, '$1');
+ return partial;
+ },
+
+ // GTP: the old implementation works better. We might not need
+ // this.findPartialRE anymore if it works in all cases!
+ findPartial: function(partialString) {
+ //strip out the template cruft
+ var foundPatternPartial = partialString
+ .replace('{{> ', '')
+ .replace(' }}', '')
+ .replace('{{>', '')
+ .replace('}}', '');
+
+ // remove any potential pattern parameters. this and the above are rather brutish but I didn't want to do a regex at the time
+ if (foundPatternPartial.indexOf('(') > 0) {
+ foundPatternPartial = foundPatternPartial.substring(
+ 0,
+ foundPatternPartial.indexOf('(')
+ );
+ }
+
+ //remove any potential stylemodifiers.
+ foundPatternPartial = foundPatternPartial.split(':')[0];
+
+ return foundPatternPartial;
+ },
+
+ /**
+ * Accept a Pattern Lab config object from the core and put it in
+ * this module's closure scope so we can configure engine behavior.
+ *
+ * @param {object} config - the global config object from core
+ */
+ usePatternLabConfig: function(config) {
+ patternLabConfig = config;
+ let patternsPath = patternLabConfig.paths.source.patterns;
+
+ if (patternsPath.slice(-1) === '/') {
+ patternsPath = patternsPath.slice(0, -1);
+ }
+
+ const allPaths = getDirectories(patternsPath).reduce((allDirs, dir) => {
+ return allDirs.concat(getDirectories(dir));
+ }, []);
+
+ engine = Liquid({
+ dynamicPartials: false,
+ root: allPaths,
+ });
+ },
+
+ spawnFile: function(config, fileName) {
+ const paths = config.paths;
+ const metaFilePath = path.resolve(paths.source.meta, fileName);
+
+ try {
+ fs.statSync(metaFilePath);
+ } catch (err) {
+ //not a file, so spawn it from the included file
+ const localMetaFilePath = path.resolve(__dirname, '_meta/', fileName);
+ const metaFileContent = fs.readFileSync(
+ path.resolve(__dirname, '..', '_meta/', fileName),
+ 'utf8'
+ );
+ fs.outputFileSync(metaFilePath, metaFileContent);
+ }
+ },
+
+ /**
+ * Checks to see if the _meta directory has engine-specific head and foot files,
+ * spawning them if not found.
+ *
+ * @param {object} config - the global config object from core, since we won't
+ * assume it's already present
+ */
+ spawnMeta: function(config) {
+ this.spawnFile(config, '_00-head.liquid');
+ this.spawnFile(config, '_01-foot.liquid');
+ },
+};
diff --git a/packages/engine-liquid/lib/util_liquid.js b/packages/engine-liquid/lib/util_liquid.js
new file mode 100644
index 000000000..3bc200d78
--- /dev/null
+++ b/packages/engine-liquid/lib/util_liquid.js
@@ -0,0 +1,110 @@
+/*
+ * Liquid utilities for patternlab-node - v2.X.X - 2017
+ *
+ * Cameron Roe
+ * Licensed under the MIT license.
+ *
+ */
+
+'use strict';
+
+// the term "alphanumeric" includes underscores.
+
+// todo: document this exact regex long form.
+var partialsRE = new RegExp(
+ /{%\\include\\\s*?([\w\-\.\/~]+)(?:\:[A-Za-z0-9-_|]+)?(?:(?:| )\(.*)?(?:\s*)?%}/g
+);
+
+// look for an opening mustache include tag, followed by >=0 whitespaces
+var partialsWithStyleModifiersStr = '{{>\\s*';
+
+// one or more characters comprising any combination of alphanumerics,
+// hyphens, periods, slashses, and tildes
+partialsWithStyleModifiersStr += '([\\w\\-\\.\\/~]+)';
+
+// the previous group cannot be followed by an opening parenthesis
+partialsWithStyleModifiersStr += '(?!\\()';
+
+// a colon followed by one or more characters comprising any combination
+// of alphanumerics, hyphens, and pipes
+partialsWithStyleModifiersStr += '(\\:[\\w\\-\\|]+)';
+
+// an optional group of characters starting with >=0 whitespaces, followed by
+// an opening parenthesis, followed by any number of characters that are not
+// closing parentheses, followed by a closing parenthesis
+partialsWithStyleModifiersStr += '(\\s*\\([^\\)]*\\))?';
+
+// look for >=0 whitespaces, followed by closing mustache tag
+partialsWithStyleModifiersStr += '\\s*}}';
+var partialsWithStyleModifiersRE = new RegExp(
+ partialsWithStyleModifiersStr,
+ 'g'
+);
+
+// look for an opening mustache include tag, followed by >=0 whitespaces
+var partialsWithPatternParametersStr = '{{>\\s*';
+
+// one or more characters comprising any combination of alphanumerics,
+// hyphens, periods, slashses, and tildes
+partialsWithPatternParametersStr += '([\\w\\-\\.\\/~]+)';
+
+// an optional group comprising a colon followed by one or more characters
+// comprising any combination of alphanumerics,
+// hyphens, and pipes
+partialsWithPatternParametersStr += '(\\:[\\w\\-\\|]+)?';
+
+// a group of characters starting with >=0 whitespaces, followed by an opening
+// parenthesis, followed by any number of characters that are not closing
+// parentheses, followed by a closing parenthesis
+partialsWithPatternParametersStr += '(\\s*\\([^\\)]*\\))';
+
+// look for >=0 whitespaces, followed by closing mustache tag
+partialsWithPatternParametersStr += '\\s*}}';
+var partialsWithPatternParametersRE = new RegExp(
+ partialsWithPatternParametersStr,
+ 'g'
+);
+
+// look for an opening mustache loop tag, followed by >=0 whitespaces
+var listItemsStr = '{{#\\s*';
+
+// look for the string 'listItems.' or 'listitems.'
+listItemsStr += '(list(I|i)tems\\.)';
+
+// look for a number 1 - 20, spelled out
+listItemsStr +=
+ '(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)';
+
+// look for >=0 whitespaces, followed by closing mustache tag
+listItemsStr += '\\s*}}';
+var listItemsRE = new RegExp(listItemsStr, 'g');
+
+// look for an opening mustache loop tag, followed by >=0 whitespaces
+var partialKeyStr = '{{>\\s*';
+
+// one or more characters comprising any combination of alphanumerics,
+// hyphens, periods, slashses, and tildes
+partialKeyStr += '([\\w\\-\\.\\/~]+)';
+
+// an optional group of characters starting with a colon, followed by >0
+// alphanumerics, hyphens, or pipes
+partialKeyStr += '(\\:[\\w\\-|]+)?';
+
+// an optional group of characters starting with >=0 whitespaces, followed by
+// an opening parenthesis, followed by any number of characters that are not
+// closing parentheses, followed by a closing parenthesis
+partialKeyStr += '(\\s*\\([^\\)]*\\))?';
+
+// look for >=0 whitespaces, followed by closing mustache tag
+partialKeyStr += '\\s*}}';
+var partialKeyRE = new RegExp(partialKeyStr, 'g');
+
+var utilLiquid = {
+ partialsRE: partialsRE,
+ partialsWithStyleModifiersRE: partialsWithStyleModifiersRE,
+ partialsWithPatternParametersRE: partialsWithPatternParametersRE,
+ listItemsRE: listItemsRE,
+ partialKeyRE: partialKeyRE,
+};
+
+module.exports = utilLiquid;
diff --git a/packages/engine-liquid/package.json b/packages/engine-liquid/package.json
new file mode 100644
index 000000000..dfb15fed3
--- /dev/null
+++ b/packages/engine-liquid/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@pattern-lab/engine-liquid",
+ "description": "The Liquid engine for Pattern Lab / Node",
+ "version": "5.0.0",
+ "main": "lib/engine_liquid.js",
+ "dependencies": {
+ "fs-extra": "5.0.0",
+ "liquidjs": "2.2.0"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Grunt",
+ "Gulp",
+ "Javascript",
+ "Liquid"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-liquid",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": "Cameron Roe",
+ "license": "MIT",
+ "scripts": {},
+ "engines": {
+ "node": ">=10.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/engine-mustache/.gitignore b/packages/engine-mustache/.gitignore
new file mode 100644
index 000000000..74ab03195
--- /dev/null
+++ b/packages/engine-mustache/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+.DS_Store
+latest-change.txt
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+source/css/style.css.map
+.idea/
+public
diff --git a/packages/engine-mustache/.npmrc b/packages/engine-mustache/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/engine-mustache/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/engine-mustache/.nvmrc b/packages/engine-mustache/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/engine-mustache/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/engine-mustache/CHANGELOG.md b/packages/engine-mustache/CHANGELOG.md
new file mode 100644
index 000000000..76309e04f
--- /dev/null
+++ b/packages/engine-mustache/CHANGELOG.md
@@ -0,0 +1,91 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+**Note:** Version bump only for package @pattern-lab/engine-mustache
+
+
+
+
+
+
+# [2.0.0-beta.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/compare/@pattern-lab/engine-mustache@2.0.0-beta.0...@pattern-lab/engine-mustache@2.0.0-beta.1) (2019-02-09)
+
+**Note:** Version bump only for package @pattern-lab/engine-mustache
+
+
+
+
+
+
+# [2.0.0-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/compare/@pattern-lab/engine-mustache@2.0.0-alpha.8...@pattern-lab/engine-mustache@2.0.0-beta.0) (2018-09-07)
+
+
+### Bug Fixes
+
+* **package:** update mustache dependency ([27bd4cd](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/27bd4cd))
+
+
+
+
+
+
+
+# [2.0.0-alpha.8](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/compare/@pattern-lab/engine-mustache@2.0.0-alpha.7...@pattern-lab/engine-mustache@2.0.0-alpha.8) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/5ab3995))
+
+
+
+# [2.0.0-alpha.7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/compare/@pattern-lab/engine-mustache@2.0.0-alpha.6...@pattern-lab/engine-mustache@2.0.0-alpha.7) (2018-07-05)
+
+### Features
+
+* **tests:** use lerna run test at the monorepo level ([38a01b1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/38a01b1))
+
+
+
+# [2.0.0-alpha.6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/compare/@pattern-lab/engine-mustache@2.0.0-alpha.5...@pattern-lab/engine-mustache@2.0.0-alpha.6) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/engine-mustache
+
+
+
+# [2.0.0-alpha.5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/compare/@pattern-lab/engine-mustache@2.0.0-alpha.4...@pattern-lab/engine-mustache@2.0.0-alpha.5) (2018-03-21)
+
+### Bug Fixes
+
+* **package:** remove files obsoleted by monorepo ([9abb8ac](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/9abb8ac))
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/337aa32))
+* **README:** update content for consistency ([4edf0d4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/4edf0d4)), closes [#815](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/issues/815)
+
+### Features
+
+* **package:** add engine-nunjucks to monorepo ([bf527ed](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/bf527ed)), closes [#814](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/issues/814)
+
+
+
+# [2.0.0-alpha.4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/compare/@pattern-lab/engine-mustache@2.0.0-alpha.3...@pattern-lab/engine-mustache@2.0.0-alpha.4) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/1473cd5))
+
+
+
+# 2.0.0-alpha.3 (2018-03-02)
+
+### Bug Fixes
+
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/58beeb6))
+
+### Features
+
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache/commit/5eb2c11))
diff --git a/packages/engine-mustache/LICENSE b/packages/engine-mustache/LICENSE
new file mode 100644
index 000000000..f4b26b73e
--- /dev/null
+++ b/packages/engine-mustache/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Dan White, https://github.com/danwhite85 & Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/engine-mustache/README.md b/packages/engine-mustache/README.md
new file mode 100644
index 000000000..cb2c8838e
--- /dev/null
+++ b/packages/engine-mustache/README.md
@@ -0,0 +1,5 @@
+## The Mustache PatternEngine for Pattern Lab / Node
+
+This one should be included by default with [Pattern Lab Node Core](https://github.com/pattern-lab/patternlab-node/tree/dev/packages/core) and consumed by [Node Editions](https://github.com/pattern-lab?utf8=%E2%9C%93&query=edition-node).
+
+If it's missing from your project for any reason, `npm install @pattern-lab/engine-mustache` should do the trick.
diff --git a/packages/engine-mustache/_meta/_00-head.mustache b/packages/engine-mustache/_meta/_00-head.mustache
new file mode 100644
index 000000000..45ce3bb7d
--- /dev/null
+++ b/packages/engine-mustache/_meta/_00-head.mustache
@@ -0,0 +1,17 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
+
diff --git a/packages/engine-mustache/_meta/_01-foot.mustache b/packages/engine-mustache/_meta/_01-foot.mustache
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/engine-mustache/_meta/_01-foot.mustache
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/engine-mustache/lib/engine_mustache.js b/packages/engine-mustache/lib/engine_mustache.js
new file mode 100644
index 000000000..dc0838cfd
--- /dev/null
+++ b/packages/engine-mustache/lib/engine_mustache.js
@@ -0,0 +1,180 @@
+'use strict';
+
+/*
+ * mustache pattern engine for patternlab-node
+ *
+ * Geoffrey Pursell, Brian Muenzenmeyer, and the web community.
+ * Licensed under the MIT license.
+ *
+ * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice.
+ *
+ */
+
+/*
+ * ENGINE SUPPORT LEVEL:
+ *
+ * Full + extensions. Partial calls and lineage hunting are supported. Style
+ * modifiers and pattern parameters are used to extend the core feature set of
+ * Mustache templates.
+ *
+ */
+
+const fs = require('fs-extra');
+const path = require('path');
+const Mustache = require('mustache');
+const utilMustache = require('./util_mustache');
+
+// This holds the config from from core. The core has to call
+// usePatternLabConfig() at load time for this to be populated, which
+// it does, so we're cool, right?
+let patternLabConfig = {};
+
+var engine_mustache = {
+ engine: Mustache,
+ engineName: 'mustache',
+ engineFileExtension: '.mustache',
+
+ // partial expansion is only necessary for Mustache templates that have
+ // style modifiers or pattern parameters (I think)
+ expandPartials: true,
+
+ // regexes, stored here so they're only compiled once
+ findPartialsRE: utilMustache.partialsRE,
+ findPartialsWithStyleModifiersRE: utilMustache.partialsWithStyleModifiersRE,
+ findPartialsWithPatternParametersRE:
+ utilMustache.partialsWithPatternParametersRE,
+ findListItemsRE: utilMustache.listItemsRE,
+ findPartialRE: utilMustache.partialRE,
+
+ // render it
+ renderPattern: function renderPattern(pattern, data, partials) {
+ try {
+ if (partials) {
+ return Promise.resolve(
+ Mustache.render(pattern.extendedTemplate, data, partials)
+ );
+ }
+ return Promise.resolve(Mustache.render(pattern.extendedTemplate, data));
+ } catch (e) {
+ console.log('e = ', e);
+ return Promise.reject(e);
+ }
+ },
+
+ /**
+ * Find regex matches within both pattern strings and pattern objects.
+ *
+ * @param {string|object} pattern Either a string or a pattern object.
+ * @param {object} regex A JavaScript RegExp object.
+ * @returns {array|null} An array if a match is found, null if not.
+ */
+ patternMatcher: function patternMatcher(pattern, regex) {
+ var matches;
+ if (typeof pattern === 'string') {
+ matches = pattern.match(regex);
+ } else if (
+ typeof pattern === 'object' &&
+ typeof pattern.template === 'string'
+ ) {
+ matches = pattern.template.match(regex);
+ }
+ return matches;
+ },
+
+ spawnFile: function(config, fileName) {
+ const paths = config.paths;
+ const metaFilePath = path.resolve(paths.source.meta, fileName);
+ try {
+ fs.statSync(metaFilePath);
+ } catch (err) {
+ //not a file, so spawn it from the included file
+ const metaFileContent = fs.readFileSync(
+ path.resolve(__dirname, '..', '_meta/', fileName),
+ 'utf8'
+ );
+ fs.outputFileSync(metaFilePath, metaFileContent);
+ }
+ },
+
+ /**
+ * Checks to see if the _meta directory has engine-specific head and foot files,
+ * spawning them if not found.
+ *
+ * @param {object} config - the global config object from core, since we won't
+ * assume it's already present
+ */
+ spawnMeta: function(config) {
+ this.spawnFile(config, '_00-head.mustache');
+ this.spawnFile(config, '_01-foot.mustache');
+ },
+
+ // find and return any {{> template-name }} within pattern
+ findPartials: function findPartials(pattern) {
+ var matches = this.patternMatcher(pattern, this.findPartialsRE);
+ return matches;
+ },
+ findPartialsWithStyleModifiers: function(pattern) {
+ var matches = this.patternMatcher(
+ pattern,
+ this.findPartialsWithStyleModifiersRE
+ );
+ return matches;
+ },
+
+ // returns any patterns that match {{> value(foo:"bar") }} or {{>
+ // value:mod(foo:"bar") }} within the pattern
+ findPartialsWithPatternParameters: function(pattern) {
+ var matches = this.patternMatcher(
+ pattern,
+ this.findPartialsWithPatternParametersRE
+ );
+ return matches;
+ },
+ findListItems: function(pattern) {
+ var matches = this.patternMatcher(pattern, this.findListItemsRE);
+ return matches;
+ },
+
+ // given a pattern, and a partial string, tease out the "pattern key" and
+ // return it.
+ findPartial_new: function(partialString) {
+ var partial = partialString.replace(this.findPartialRE, '$1');
+ return partial;
+ },
+
+ // GTP: the old implementation works better. We might not need
+ // this.findPartialRE anymore if it works in all cases!
+ findPartial: function(partialString) {
+ //strip out the template cruft
+ var foundPatternPartial = partialString
+ .replace('{{> ', '')
+ .replace(' }}', '')
+ .replace('{{>', '')
+ .replace('}}', '');
+
+ // remove any potential pattern parameters. this and the above are rather brutish but I didn't want to do a regex at the time
+ if (foundPatternPartial.indexOf('(') > 0) {
+ foundPatternPartial = foundPatternPartial.substring(
+ 0,
+ foundPatternPartial.indexOf('(')
+ );
+ }
+
+ //remove any potential stylemodifiers.
+ foundPatternPartial = foundPatternPartial.split(':')[0];
+
+ return foundPatternPartial;
+ },
+
+ /**
+ * Accept a Pattern Lab config object from the core and put it in
+ * this module's closure scope so we can configure engine behavior.
+ *
+ * @param {object} config - the global config object from core
+ */
+ usePatternLabConfig: function(config) {
+ patternLabConfig = config;
+ },
+};
+
+module.exports = engine_mustache;
diff --git a/packages/engine-mustache/lib/util_mustache.js b/packages/engine-mustache/lib/util_mustache.js
new file mode 100644
index 000000000..bfc7c1804
--- /dev/null
+++ b/packages/engine-mustache/lib/util_mustache.js
@@ -0,0 +1,112 @@
+/*
+ * mustache utilities for patternlab-node - v2.X.X - 2016
+ *
+ * Geoffrey Pursell, Brian Muenzenmeyer, and the web community.
+ * Licensed under the MIT license.
+ *
+ * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice.
+ *
+ */
+
+'use strict';
+
+// the term "alphanumeric" includes underscores.
+
+// todo: document this exact regex long form.
+var partialsRE = new RegExp(
+ /{{>\s*?([\w\-\.\/~]+)(?:\:[A-Za-z0-9-_|]+)?(?:(?:| )\(.*)?(?:\s*)?}}/g
+);
+
+// look for an opening mustache include tag, followed by >=0 whitespaces
+var partialsWithStyleModifiersStr = '{{>\\s*';
+
+// one or more characters comprising any combination of alphanumerics,
+// hyphens, periods, slashses, and tildes
+partialsWithStyleModifiersStr += '([\\w\\-\\.\\/~]+)';
+
+// the previous group cannot be followed by an opening parenthesis
+partialsWithStyleModifiersStr += '(?!\\()';
+
+// a colon followed by one or more characters comprising any combination
+// of alphanumerics, hyphens, and pipes
+partialsWithStyleModifiersStr += '(\\:[\\w\\-\\|]+)';
+
+// an optional group of characters starting with >=0 whitespaces, followed by
+// an opening parenthesis, followed by any number of characters that are not
+// closing parentheses, followed by a closing parenthesis
+partialsWithStyleModifiersStr += '(\\s*\\([^\\)]*\\))?';
+
+// look for >=0 whitespaces, followed by closing mustache tag
+partialsWithStyleModifiersStr += '\\s*}}';
+var partialsWithStyleModifiersRE = new RegExp(
+ partialsWithStyleModifiersStr,
+ 'g'
+);
+
+// look for an opening mustache include tag, followed by >=0 whitespaces
+var partialsWithPatternParametersStr = '{{>\\s*';
+
+// one or more characters comprising any combination of alphanumerics,
+// hyphens, periods, slashses, and tildes
+partialsWithPatternParametersStr += '([\\w\\-\\.\\/~]+)';
+
+// an optional group comprising a colon followed by one or more characters
+// comprising any combination of alphanumerics,
+// hyphens, and pipes
+partialsWithPatternParametersStr += '(\\:[\\w\\-\\|]+)?';
+
+// a group of characters starting with >=0 whitespaces, followed by an opening
+// parenthesis, followed by any number of characters that are not closing
+// parentheses, followed by a closing parenthesis
+partialsWithPatternParametersStr += '(\\s*\\([^\\)]*\\))';
+
+// look for >=0 whitespaces, followed by closing mustache tag
+partialsWithPatternParametersStr += '\\s*}}';
+var partialsWithPatternParametersRE = new RegExp(
+ partialsWithPatternParametersStr,
+ 'g'
+);
+
+// look for an opening mustache loop tag, followed by >=0 whitespaces
+var listItemsStr = '{{#\\s*';
+
+// look for the string 'listItems.' or 'listitems.'
+listItemsStr += '(list(I|i)tems\\.)';
+
+// look for a number 1 - 20, spelled out
+listItemsStr +=
+ '(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)';
+
+// look for >=0 whitespaces, followed by closing mustache tag
+listItemsStr += '\\s*}}';
+var listItemsRE = new RegExp(listItemsStr, 'g');
+
+// look for an opening mustache loop tag, followed by >=0 whitespaces
+var partialKeyStr = '{{>\\s*';
+
+// one or more characters comprising any combination of alphanumerics,
+// hyphens, periods, slashses, and tildes
+partialKeyStr += '([\\w\\-\\.\\/~]+)';
+
+// an optional group of characters starting with a colon, followed by >0
+// alphanumerics, hyphens, or pipes
+partialKeyStr += '(\\:[\\w\\-|]+)?';
+
+// an optional group of characters starting with >=0 whitespaces, followed by
+// an opening parenthesis, followed by any number of characters that are not
+// closing parentheses, followed by a closing parenthesis
+partialKeyStr += '(\\s*\\([^\\)]*\\))?';
+
+// look for >=0 whitespaces, followed by closing mustache tag
+partialKeyStr += '\\s*}}';
+var partialKeyRE = new RegExp(partialKeyStr, 'g');
+
+var utilMustache = {
+ partialsRE: partialsRE,
+ partialsWithStyleModifiersRE: partialsWithStyleModifiersRE,
+ partialsWithPatternParametersRE: partialsWithPatternParametersRE,
+ listItemsRE: listItemsRE,
+ partialKeyRE: partialKeyRE,
+};
+
+module.exports = utilMustache;
diff --git a/packages/engine-mustache/package.json b/packages/engine-mustache/package.json
new file mode 100644
index 000000000..6142804b3
--- /dev/null
+++ b/packages/engine-mustache/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@pattern-lab/engine-mustache",
+ "description": "The Mustache engine for Pattern Lab / Node",
+ "version": "5.0.0",
+ "main": "lib/engine_mustache.js",
+ "dependencies": {
+ "fs-extra": "0.30.0",
+ "mustache": "3.1.0"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Grunt",
+ "Gulp",
+ "Javascript",
+ "Mustache"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-mustache",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": "Brian Muenzenmeyer & Geoffrey Pursell",
+ "license": "MIT",
+ "scripts": {},
+ "engines": {
+ "node": ">=10.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/engine-nunjucks/.gitignore b/packages/engine-nunjucks/.gitignore
new file mode 100644
index 000000000..74ab03195
--- /dev/null
+++ b/packages/engine-nunjucks/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+.DS_Store
+latest-change.txt
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+source/css/style.css.map
+.idea/
+public
diff --git a/packages/engine-nunjucks/.npmignore b/packages/engine-nunjucks/.npmignore
new file mode 100644
index 000000000..74ab03195
--- /dev/null
+++ b/packages/engine-nunjucks/.npmignore
@@ -0,0 +1,10 @@
+node_modules/
+.DS_Store
+latest-change.txt
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+source/css/style.css.map
+.idea/
+public
diff --git a/packages/engine-nunjucks/.npmrc b/packages/engine-nunjucks/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/engine-nunjucks/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/engine-nunjucks/.nvmrc b/packages/engine-nunjucks/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/engine-nunjucks/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/engine-nunjucks/CHANGELOG.md b/packages/engine-nunjucks/CHANGELOG.md
new file mode 100644
index 000000000..8a7b13cc9
--- /dev/null
+++ b/packages/engine-nunjucks/CHANGELOG.md
@@ -0,0 +1,97 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Bug Fixes
+
+* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/74e5af28c4e714fdfc1db535b94c52f3dc14a3a4))
+
+
+### Features
+
+* **engine-nunjucks:** Configurable extension locations; Use usePatternlabConfig() ([e54e3b3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/e54e3b3d48f934d3a4d44b9f4ff262f742a4aaf9))
+* update Node to v12 ([fcbb970](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/fcbb970648cdd775c9a88078f14c1f24c5b62d73))
+
+
+
+
+
+
+# [0.2.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/compare/@pattern-lab/engine-nunjucks@0.1.5-alpha.0...@pattern-lab/engine-nunjucks@0.2.0) (2019-08-23)
+
+
+### Bug Fixes
+
+* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/74e5af2))
+
+
+### Features
+
+* **engine-nunjucks:** Configurable extension locations; Use usePatternlabConfig() ([e54e3b3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/e54e3b3))
+
+
+
+
+
+
+## [0.1.4-beta.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/compare/@pattern-lab/engine-nunjucks@0.1.4-beta.0...@pattern-lab/engine-nunjucks@0.1.4-beta.2) (2019-02-09)
+
+
+### Bug Fixes
+
+* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/98dfadf))
+
+
+
+
+
+
+## [0.1.4-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/compare/@pattern-lab/engine-nunjucks@0.1.4-alpha.4...@pattern-lab/engine-nunjucks@0.1.4-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/engine-nunjucks
+
+
+
+
+
+
+
+## [0.1.4-alpha.4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/compare/@pattern-lab/engine-nunjucks@0.1.4-alpha.3...@pattern-lab/engine-nunjucks@0.1.4-alpha.4) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/5ab3995))
+
+
+
+## [0.1.4-alpha.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/compare/@pattern-lab/engine-nunjucks@0.1.4-alpha.2...@pattern-lab/engine-nunjucks@0.1.4-alpha.3) (2018-07-05)
+
+### Features
+
+* **tests:** use lerna run test at the monorepo level ([38a01b1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/38a01b1))
+
+
+
+## [0.1.4-alpha.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/compare/@pattern-lab/engine-nunjucks@0.1.4-alpha.1...@pattern-lab/engine-nunjucks@0.1.4-alpha.2) (2018-05-04)
+
+### Features
+
+* **package:** add [@pattern-lab](https://github.com/pattern-lab)/cli as a dependency ([760d0e0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/760d0e0))
+
+
+
+## 0.1.4-alpha.1 (2018-03-21)
+
+### Bug Fixes
+
+* **lint:** run code through prettier ([ca52fde](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/ca52fde)), closes [#825](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/issues/825)
+
+### Features
+
+* **package:** add engine-nunjucks to monorepo ([bf527ed](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/commit/bf527ed)), closes [#814](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks/issues/814)
diff --git a/packages/engine-nunjucks/LICENSE b/packages/engine-nunjucks/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/engine-nunjucks/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/engine-nunjucks/README.md b/packages/engine-nunjucks/README.md
new file mode 100644
index 000000000..9d6ab1c99
--- /dev/null
+++ b/packages/engine-nunjucks/README.md
@@ -0,0 +1,68 @@
+# The Nunjucks engine for Pattern Lab / Node
+
+## Installing
+
+To install the Nunjucks PatternEngine in your edition, run `npm install @pattern-lab/engine-nunjucks`.
+
+## Supported features
+- [x] [Includes](http://patternlab.io/docs/pattern-including.html)
+- [x] Lineage
+- [x] [Hidden Patterns](http://patternlab.io/docs/pattern-hiding.html)
+- [x] [Pseudo-Patterns](http://patternlab.io/docs/pattern-pseudo-patterns.html)
+- [x] [Pattern States](http://patternlab.io/docs/pattern-states.html)
+- [ ] [Pattern Parameters](http://patternlab.io/docs/pattern-parameters.html) (Accomplished instead using native Nunjucks variables)
+- [ ] [Style Modifiers](http://patternlab.io/docs/pattern-stylemodifier.html) (Accomplished instead using native Nunjucks variables)
+
+Level of Support is more or less full. Partial calls and lineage hunting are supported. Nunjucks does not support the mustache-specific syntax extensions, style modifiers and pattern parameters, because their use cases are addressed by the core Nunjucks feature set. Pattern Lab's listitems feature is still written in the mustache syntax.
+
+## Extending the Nunjucks instance
+
+To add custom filters or make customizations to the nunjucks instance, add the following to `patternlab-config.json`:
+
+```json
+ {
+ ...
+ "engines": {
+ "nunjucks": {
+ "extend": [
+ "nunjucks-extensions/*.js"
+ ]
+ }
+ }
+ }
+```
+
+...or use the default file name: `patternlab-nunjucks-config.js` (in the root of your Pattern Lab project).
+
+Each file providing extensions should export a function with the Nunjucks environment as parameter.
+
+```js
+module.exports = function (env) {
+ [YOUR CUSTOM CODE HERE]
+};
+```
+
+Example: `patternlab-nunjucks-config.js` file that uses lodash and adds three custom filters.
+
+```js
+var _shuffle = require('lodash/shuffle'),
+ _take = require('lodash/take');
+
+exports = module.exports = function (env) {
+ env.addFilter('shorten', function (str, count) {
+ return str.slice(0, count || 5);
+ });
+
+ env.addFilter('shuffle', (arr) => {
+ return _shuffle(arr);
+ });
+
+ env.addFilter('take', (arr, number) => {
+ return _take(arr, number);
+ });
+};
+```
+
+## What Nunjucks features are missing?
+
+I have not yet figured out a way to support variables in pattern includes. I'm thinking it might be possible to use Nunjucks precompile feature to get the compiled partial name before returning it to Pattern Lab, but just a thought at this point.
diff --git a/packages/engine-nunjucks/_meta/_00-head.njk b/packages/engine-nunjucks/_meta/_00-head.njk
new file mode 100644
index 000000000..b69898755
--- /dev/null
+++ b/packages/engine-nunjucks/_meta/_00-head.njk
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{ patternLabHead | safe }}
+
+
+
+
diff --git a/packages/engine-nunjucks/_meta/_01-foot.njk b/packages/engine-nunjucks/_meta/_01-foot.njk
new file mode 100644
index 000000000..d49a839e1
--- /dev/null
+++ b/packages/engine-nunjucks/_meta/_01-foot.njk
@@ -0,0 +1,6 @@
+
+
+{{ patternLabFoot | safe }}
+
+
+
diff --git a/packages/engine-nunjucks/lib/engine_nunjucks.js b/packages/engine-nunjucks/lib/engine_nunjucks.js
new file mode 100644
index 000000000..176f977aa
--- /dev/null
+++ b/packages/engine-nunjucks/lib/engine_nunjucks.js
@@ -0,0 +1,195 @@
+/*
+ * Nunjucks pattern engine for patternlab-node
+ *
+ * Dan White.
+ * Licensed under the MIT license.
+ *
+ */
+
+/*
+ * ENGINE SUPPORT LEVEL:
+ *
+ * Mostly full. Partial calls and lineage hunting are supported.
+ * Nunjucks does not support the mustache-specific syntax
+ * extensions, style modifiers and pattern parameters, because
+ * their use cases are addressed by the core Nunjucks feature set.
+ * Pattern Lab's listitems feature is still written in the
+ * mustache syntax.
+ *
+ */
+
+'use strict';
+
+const fs = require('fs-extra');
+const path = require('path');
+const nunjucks = require('nunjucks');
+const partialRegistry = [];
+
+let env;
+
+// Nunjucks Engine
+const engine_nunjucks = {
+ engine: nunjucks,
+ engineName: 'nunjucks',
+ engineFileExtension: '.njk',
+
+ //Important! Must be false for Nunjucks' block inheritance to work. Otherwise Nunjucks sees them as being defined more than once.
+ expandPartials: false,
+
+ // regexes, stored here so they're only compiled once
+ findPartialsRE: /{%\s*(?:extends|include|import|from)\s+(?:'[^']+'|"[^"]+").*%}/g,
+ findPartialKeyRE: /{%\s*(?:extends|include|import|from)\s+('[^']+'|"[^"]+")/,
+ findListItemsRE: /({{#( )?)(list(I|i)tems.)(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)( )?}}/g, // still requires mustache style syntax because of how PL implements lists
+
+ // render it
+ renderPattern: function renderPattern(pattern, data) {
+ try {
+ const result = env.renderString(pattern.extendedTemplate, data);
+ return Promise.resolve(result);
+ } catch (err) {
+ console.error('Failed to render pattern: ' + pattern.name);
+ console.error(err);
+ }
+ },
+
+ // find and return any Nunjucks style includes/imports/extends within pattern
+ findPartials: function findPartials(pattern) {
+ const matches = pattern.template.match(this.findPartialsRE);
+ return matches;
+ },
+
+ // given a pattern, and a partial string, tease out the "pattern key" and return it.
+ findPartial: function(partialString) {
+ try {
+ let partial = partialString.match(this.findPartialKeyRE)[1];
+ partial = partial.replace(/["']/g, '');
+ return partial;
+ } catch (err) {
+ console.error(
+ 'Error occured when trying to find partial name in: ' + partialString
+ );
+ }
+ },
+
+ // keep track of partials and their paths so we can replace the name with the path
+ registerPartial: function(pattern) {
+ // only register each partial once. Otherwise we'll eat up a ton of memory.
+ if (partialRegistry.indexOf(pattern.patternPartial) === -1) {
+ partialRegistry[pattern.patternPartial] = pattern.relPath.replace(
+ /\\/g,
+ '/'
+ );
+ }
+ },
+
+ // still requires the mustache syntax because of the way PL handles lists
+ findListItems: function(pattern) {
+ const matches = pattern.template.match(this.findListItemsRE);
+ return matches;
+ },
+
+ // handled by nunjucks. This is here to keep PL from erroring
+ findPartialsWithStyleModifiers: function() {
+ return null;
+ },
+
+ // handled by nunjucks. This is here to keep PL from erroring
+ findPartialsWithPatternParameters: function() {
+ return null;
+ },
+
+ spawnFile: function(config, fileName) {
+ const paths = config.paths;
+ const metaFilePath = path.resolve(paths.source.meta, fileName);
+ try {
+ fs.statSync(metaFilePath);
+ } catch (err) {
+ //not a file, so spawn it from the included file
+ const metaFileContent = fs.readFileSync(
+ path.resolve(__dirname, '..', '_meta/', fileName),
+ 'utf8'
+ );
+ fs.outputFileSync(metaFilePath, metaFileContent);
+ }
+ },
+
+ /**
+ * Checks to see if the _meta directory has engine-specific head and foot files,
+ * spawning them if not found.
+ *
+ * @param {object} config - the global config object from core, since we won't
+ * assume it's already present
+ */
+ spawnMeta: function(config) {
+ this.spawnFile(config, '_00-head.njk');
+ this.spawnFile(config, '_01-foot.njk');
+ },
+
+ /**
+ * Accept a Pattern Lab config object from the core and use the settings to
+ * load helpers.
+ *
+ * @param {object} config - the global config object from core
+ */
+ usePatternLabConfig: function(config) {
+ // Create Pattern Loader
+ // Since Pattern Lab includes are not path based we need a custom loader for Nunjucks.
+ function PatternLoader() {}
+
+ PatternLoader.prototype.getSource = function(name) {
+ const fullPath = path.resolve(
+ config.paths.source.patterns,
+ partialRegistry[name]
+ );
+ return {
+ src: fs.readFileSync(fullPath, 'utf-8'),
+ path: fullPath,
+ noCache: true,
+ };
+ };
+
+ env = new nunjucks.Environment(new PatternLoader());
+
+ let extensions;
+
+ try {
+ // Look for helpers in the config
+ extensions = config.engines.nunjucks.extend;
+
+ if (typeof extensions === 'string') {
+ extensions = [extensions];
+ }
+ } catch (error) {
+ // No defined path(s) found, look in default location
+
+ const configPath = 'patternlab-nunjucks-config.js';
+ if (fs.existsSync(path.join(process.cwd(), configPath))) {
+ extensions = [configPath];
+ }
+ }
+
+ if (extensions) {
+ extensions.forEach(extensionPath => {
+ // Load any user Defined configurations
+ const nunjucksConfigPath = path.join(process.cwd(), extensionPath);
+
+ try {
+ const nunjucksConfig = require(nunjucksConfigPath);
+ if (typeof nunjucksConfig === 'function') {
+ nunjucksConfig(env);
+ } else {
+ console.error(
+ `Failed to load Nunjucks extension: Expected ${extensionPath} to export a function.`
+ );
+ }
+ } catch (err) {
+ console.error(
+ `Failed to load Nunjucks extension ${nunjucksConfigPath}.`
+ );
+ }
+ });
+ }
+ },
+};
+
+module.exports = engine_nunjucks;
diff --git a/packages/engine-nunjucks/package.json b/packages/engine-nunjucks/package.json
new file mode 100644
index 000000000..575c4af28
--- /dev/null
+++ b/packages/engine-nunjucks/package.json
@@ -0,0 +1,33 @@
+{
+ "author": {
+ "name": "Dan White"
+ },
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-nunjucks",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "deprecated": false,
+ "description": "The nunjucks PatternEngine for Pattern Lab / Node",
+ "dependencies": {
+ "fs-extra": "7.0.0",
+ "nunjucks": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Grunt",
+ "Gulp",
+ "Javascript",
+ "Nunjucks"
+ ],
+ "license": "MIT",
+ "main": "lib/engine_nunjucks.js",
+ "name": "@pattern-lab/engine-nunjucks",
+ "scripts": {},
+ "version": "5.0.0",
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/engine-react/.gitignore b/packages/engine-react/.gitignore
new file mode 100644
index 000000000..5148e527a
--- /dev/null
+++ b/packages/engine-react/.gitignore
@@ -0,0 +1,37 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
diff --git a/packages/engine-react/.npmrc b/packages/engine-react/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/engine-react/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/engine-react/.nvmrc b/packages/engine-react/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/engine-react/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/engine-react/CHANGELOG.md b/packages/engine-react/CHANGELOG.md
new file mode 100644
index 000000000..467717ff5
--- /dev/null
+++ b/packages/engine-react/CHANGELOG.md
@@ -0,0 +1,87 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Features
+
+* **engine-react:** set package to private ([3aea881](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/3aea8815f19df5b527cdda0b75cf99a9a8c3bc1e))
+
+
+
+
+
+
+## [0.2.1-beta.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/compare/@pattern-lab/engine-react@0.2.1-beta.0...@pattern-lab/engine-react@0.2.1-beta.1) (2019-02-09)
+
+**Note:** Version bump only for package @pattern-lab/engine-react
+
+
+
+
+
+
+## [0.2.1-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/compare/@pattern-lab/engine-react@0.2.1-alpha.5...@pattern-lab/engine-react@0.2.1-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/engine-react
+
+
+
+
+
+
+
+## [0.2.1-alpha.5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/compare/@pattern-lab/engine-react@0.2.1-alpha.4...@pattern-lab/engine-react@0.2.1-alpha.5) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/5ab3995))
+
+
+
+## [0.2.1-alpha.4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/compare/@pattern-lab/engine-react@0.2.1-alpha.3...@pattern-lab/engine-react@0.2.1-alpha.4) (2018-07-05)
+
+### Features
+
+* **tests:** use lerna run test at the monorepo level ([38a01b1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/38a01b1))
+
+
+
+## [0.2.1-alpha.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/compare/@pattern-lab/engine-react@0.2.1-alpha.2...@pattern-lab/engine-react@0.2.1-alpha.3) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/engine-react
+
+
+
+## [0.2.1-alpha.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/compare/@pattern-lab/engine-react@0.2.1-alpha.1...@pattern-lab/engine-react@0.2.1-alpha.2) (2018-03-21)
+
+### Bug Fixes
+
+* **package:** remove files obsoleted by monorepo ([9abb8ac](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/9abb8ac))
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/337aa32))
+* **README:** update content for consistency ([4edf0d4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/4edf0d4)), closes [#815](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/issues/815)
+
+
+
+## [0.2.1-alpha.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/compare/@pattern-lab/engine-react@0.2.1-alpha.0...@pattern-lab/engine-react@0.2.1-alpha.1) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/1473cd5))
+
+
+
+## 0.2.1-alpha.0 (2018-03-02)
+
+### Bug Fixes
+
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/58beeb6))
+
+### Features
+
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react/commit/5eb2c11))
diff --git a/packages/engine-react/LICENSE b/packages/engine-react/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/engine-react/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/engine-react/README.md b/packages/engine-react/README.md
new file mode 100644
index 000000000..028006981
--- /dev/null
+++ b/packages/engine-react/README.md
@@ -0,0 +1,40 @@
+# The React PatternEngine for Pattern Lab / Node
+
+This is the **very preliminary** React PatternEngine for Patternlab / Node.
+
+## Status
+
+You can author standalone React components that include only the main React module, which I know isn't much yet. We're working on it.
+
+The current release works with the 2.X series of Patternlab / Node. Support for the 3.X series is underway on the `dev` branch.
+
+## Installing
+
+To install the React PatternEngine in your edition, `npm install @pattern-lab/engine-react` should do the trick.
+
+## Supported features
+
+* [x] [Includes](http://patternlab.io/docs/pattern-including.html)
+* [x] Data inheritance: This can be achieved by combining react `props` & `defaultProps`
+* [x] [Hidden Patterns](http://patternlab.io/docs/pattern-hiding.html)
+* [x] [Pseudo-Patterns](http://patternlab.io/docs/pattern-pseudo-patterns.html)
+* [x] [Pattern States](http://patternlab.io/docs/pattern-states.html#node)
+* [x] [Pattern Parameters](http://patternlab.io/docs/pattern-parameters.html): With react props
+* [x] [Style Modifiers](http://patternlab.io/docs/pattern-stylemodifier.html): With react props
+* [x] Lineage
+* [x] Incremental builds
+
+## Usage
+
+* `*.js` and `*.jsx` files are detected as patterns.
+* To include patterns, import components using the standard Pattern Lab naming convention, as in: ```javascript
+ import HelloWorld from 'atoms-hello-world';
+
+```
+* Standard pattern JSON is passed into React components as props.
+
+## Notes
+* Components are rendered statically to markup at build time using ReactDOMServer.renderToStaticMarkup(), but also transpiled and inlined as scripts in the pattern code to execute at runtime.
+* We currently assume the React include (and others, once we figure that out) are written using es2015 module syntax.
+* The Babel transforms are currently hard-coded into the engine, but we hope to make this configurable in the future.
+```
diff --git a/packages/engine-react/lib/engine_react.js b/packages/engine-react/lib/engine_react.js
new file mode 100644
index 000000000..b0f01cdd2
--- /dev/null
+++ b/packages/engine-react/lib/engine_react.js
@@ -0,0 +1,240 @@
+/*
+ * react pattern engine for patternlab-node - v0.1.0 - 2016
+ *
+ * Geoffrey Pursell, Brian Muenzenmeyer, and the web community.
+ * Licensed under the MIT license.
+ *
+ * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice.
+ *
+ */
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const React = require('react');
+const ReactDOMServer = require('react-dom/server');
+const Babel = require('babel-core');
+const Hogan = require('hogan');
+const beautify = require('js-beautify');
+const cheerio = require('cheerio');
+const _require = require;
+
+var errorStyling = `
+
+`;
+
+// This holds the config from from core. The core has to call
+// usePatternLabConfig() at load time for this to be populated.
+let patternLabConfig = {};
+
+let enableRuntimeCode = true;
+
+const outputTemplate = Hogan.compile(
+ fs.readFileSync(path.join(__dirname, './outputTemplate.mustache'), 'utf8')
+);
+
+let registeredComponents = {
+ byPatternPartial: {},
+};
+
+function moduleCodeString(pattern) {
+ return pattern.template || pattern.extendedTemplate;
+}
+
+function babelTransform(pattern) {
+ let transpiledModule = Babel.transform(moduleCodeString(pattern), {
+ presets: [require('babel-preset-react')],
+ plugins: [require('babel-plugin-transform-es2015-modules-commonjs')],
+ });
+
+ // eval() module code in this little scope that injects our
+ // custom wrap of require();
+ (require => {
+ /* eslint-disable no-eval */
+ transpiledModule = eval(transpiledModule.code);
+ })(customRequire);
+
+ return transpiledModule;
+}
+
+function customRequire(id) {
+ const registeredPattern = registeredComponents.byPatternPartial[id];
+
+ if (registeredPattern) {
+ return babelTransform(registeredPattern);
+ } else {
+ return _require(id);
+ }
+}
+
+var engine_react = {
+ engine: React,
+ engineName: 'react',
+ engineFileExtension: ['.jsx', '.js'],
+
+ // hell no
+ expandPartials: false,
+
+ // regexes, stored here so they're only compiled once
+ findPartialsRE: /import .* from '[^']+'/g,
+ findPartialsWithStyleModifiersRE: null,
+ findPartialsWithPatternParametersRE: null,
+ findListItemsRE: null,
+ findPartialRE: /from '([^']+)'/,
+
+ // render it
+ renderPattern(pattern, data, partials) {
+ let renderedHTML = '';
+ const transpiledModule = babelTransform(pattern);
+
+ const staticMarkup = ReactDOMServer.renderToStaticMarkup(
+ React.createFactory(transpiledModule)(data)
+ );
+
+ renderedHTML = outputTemplate.render({
+ htmlOutput: staticMarkup,
+ });
+
+ return Promise.resolve(renderedHTML).catch(e => {
+ var errorMessage = `Error rendering React pattern "${
+ pattern.patternName
+ }" (${pattern.relPath}): [${e.toString()}]`;
+ console.log(errorMessage);
+ renderedHTML = `${errorStyling}
+
Error rendering React pattern "${pattern.patternName}"
+
+ Message ${e.toString()}
+ Partial name ${pattern.patternName}
+ Template path ${pattern.relPath}
+
+
+ `;
+ });
+ },
+
+ registerPartial(pattern) {
+ // add to registry
+ registeredComponents.byPatternPartial[pattern.patternPartial] = pattern;
+ },
+
+ /**
+ * Find regex matches within both pattern strings and pattern objects.
+ *
+ * @param {string|object} pattern Either a string or a pattern object.
+ * @param {object} regex A JavaScript RegExp object.
+ * @returns {array|null} An array if a match is found, null if not.
+ */
+ patternMatcher(pattern, regex) {
+ var matches;
+ if (typeof pattern === 'string') {
+ matches = pattern.match(regex);
+ } else if (
+ typeof pattern === 'object' &&
+ typeof pattern.template === 'string'
+ ) {
+ matches = pattern.template.match(regex);
+ }
+ return matches;
+ },
+
+ // find and return any `import X from 'template-name'` within pattern
+ findPartials(pattern) {
+ const self = this;
+ const matches = pattern.template.match(this.findPartialsRE);
+ if (!matches) {
+ return [];
+ }
+
+ // Remove unregistered imports from the matches
+ matches.map(m => {
+ const key = self.findPartial(m);
+ if (!registeredComponents.byPatternPartial[key]) {
+ const i = matches.indexOf(m);
+ if (i > -1) {
+ matches.splice(i, 1);
+ }
+ }
+ });
+
+ return matches;
+ },
+
+ findPartialsWithStyleModifiers(pattern) {
+ return [];
+ },
+
+ // returns any patterns that match {{> value(foo:'bar') }} or {{>
+ // value:mod(foo:'bar') }} within the pattern
+ findPartialsWithPatternParameters(pattern) {
+ return [];
+ },
+ findListItems(pattern) {
+ return [];
+ },
+
+ // given a pattern, and a partial string, tease out the "pattern key" and
+ // return it.
+ findPartial(partialString) {
+ let partial = partialString.match(this.findPartialRE)[1];
+ return partial;
+ },
+
+ rawTemplateCodeFormatter(unformattedString) {
+ return beautify(unformattedString, { e4x: true, indent_size: 2 });
+ },
+
+ renderedCodeFormatter(unformattedString) {
+ return unformattedString;
+ },
+
+ markupOnlyCodeFormatter(unformattedString, pattern) {
+ // const $ = cheerio.load(unformattedString);
+ // return beautify.html($('.reactPatternContainer').html(), {indent_size: 2});
+ return unformattedString;
+ },
+
+ /**
+ * Add custom output files to the pattern output
+ * @param {object} patternlab - the global state object
+ * @returns {(object|object[])} - an object or array of objects,
+ * each with two properties: path, and content
+ */
+ addOutputFiles(paths, patternlab) {
+ return [];
+ },
+
+ /**
+ * Accept a Pattern Lab config object from the core and put it in
+ * this module's closure scope so we can configure engine behavior.
+ *
+ * @param {object} config - the global config object from core
+ */
+ usePatternLabConfig: function(config) {
+ patternLabConfig = config;
+
+ try {
+ enableRuntimeCode = patternLabConfig.engines.react.enableRuntimeCode;
+ } catch (error) {
+ console.log(
+ 'You’re missing the engines.react.enableRuntimeCode setting in your config file.'
+ );
+ }
+ },
+};
+
+module.exports = engine_react;
diff --git a/packages/engine-react/lib/outputTemplate.mustache b/packages/engine-react/lib/outputTemplate.mustache
new file mode 100644
index 000000000..0153d9810
--- /dev/null
+++ b/packages/engine-react/lib/outputTemplate.mustache
@@ -0,0 +1 @@
+{{{htmlOutput}}}
diff --git a/packages/engine-react/package.json b/packages/engine-react/package.json
new file mode 100644
index 000000000..bbde28cac
--- /dev/null
+++ b/packages/engine-react/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "@pattern-lab/engine-react",
+ "description": "The React engine for Pattern Lab / Node",
+ "version": "5.0.0",
+ "private": true,
+ "main": "lib/engine_react.js",
+ "dependencies": {
+ "babel-core": "6.17.0",
+ "babel-plugin-transform-es2015-modules-commonjs": "6.16.0",
+ "babel-preset-react": "6.16.0",
+ "cheerio": "0.22.0",
+ "hogan": "1.0.2",
+ "js-beautify": "1.6.4",
+ "react": "15.3.2",
+ "react-dom": "15.3.2"
+ },
+ "peerDependencies": {
+ "@pattern-lab/core": ">2"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Gulp",
+ "Javascript",
+ "React"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": "Brian Muenzenmeyer & Geoffrey Pursell",
+ "license": "MIT",
+ "scripts": {},
+ "engines": {
+ "node": ">=10.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/engine-twig-php/.gitignore b/packages/engine-twig-php/.gitignore
new file mode 100644
index 000000000..74ab03195
--- /dev/null
+++ b/packages/engine-twig-php/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+.DS_Store
+latest-change.txt
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+source/css/style.css.map
+.idea/
+public
diff --git a/packages/engine-twig-php/.npmrc b/packages/engine-twig-php/.npmrc
new file mode 100644
index 000000000..0ca8d2a0b
--- /dev/null
+++ b/packages/engine-twig-php/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/engine-twig-php/CHANGELOG.md b/packages/engine-twig-php/CHANGELOG.md
new file mode 100644
index 000000000..6c58d83e0
--- /dev/null
+++ b/packages/engine-twig-php/CHANGELOG.md
@@ -0,0 +1,149 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig-php
+
+
+
+
+
+# [5.9.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.8.0...v5.9.0) (2020-04-24)
+
+
+### Bug Fixes
+
+* actually exit build when Twig render fails ([5d28a24](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/5d28a24a53011396289c1e29e0a715cd82470185))
+* Update packages/engine-twig-php/lib/engine_twig_php.js ([c67d50e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/c67d50ebb5d69816b7514e85f129f8ecde984ad3))
+
+
+
+
+
+## [5.7.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.7.0...v5.7.1) (2020-02-24)
+
+
+### Bug Fixes
+
+* update twig-renderer ([46f53b7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/46f53b79f8bb0bb64a9c55fd32f29459cea6e28c))
+
+
+
+
+
+# [5.7.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.6.0...v5.7.0) (2020-02-17)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig-php
+
+
+
+
+
+# [5.6.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.5.0...v5.6.0) (2020-01-18)
+
+
+### Features
+
+* pass additional configuration into twig-php engine ([dff5a78](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/dff5a7830918fa46e2692d9f9daed4121f803461))
+
+
+
+
+
+
+# [5.5.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.4.2...v5.5.0) (2019-12-19)
+
+
+### Features
+
+* upgrade Twig to use new filter, map, reduce ([4218a5a](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/4218a5a04b06027548afd9f417486297dd25fef8))
+
+
+
+
+
+# [5.4.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.3.3...v5.4.0) (2019-11-26)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig-php
+
+
+
+
+
+# [5.3.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.2.0...v5.3.0) (2019-11-13)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig-php
+
+
+
+
+
+# [5.2.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.1.0...v5.2.0) (2019-11-12)
+
+
+### Bug Fixes
+
+* **engine_twig_php:** Allow additional flexibility with twig namespaces. ([07bfaa3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/07bfaa35a00ff62fd2016cc9f34e09cf5af36559))
+
+
+
+
+
+
+# [5.1.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v5.0.2...v5.1.0) (2019-10-29)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig-php
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+
+### Bug Fixes
+
+* **engine_twig_php:** Pseudo patterns Twig PHP ([226aa8b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/226aa8bbaaf5e418530ccf54a28f6c5657ee6dea)), closes [#1045](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/issues/1045)
+* **engine_twig_php:** Twig incremental rebuilds ([5d33f24](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/5d33f24f156ebe50900701513a855de7de608dcf)), closes [#1015](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/issues/1015)
+* **engine_twig_php:** Twig incremental rebuilds ([1ade945](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/1ade9451840b2645706a0b01129e2b697dc22d4b)), closes [#1015](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/issues/1015)
+
+
+
+
+
+
+## [3.0.5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/@pattern-lab/engine-twig-php@3.0.4...@pattern-lab/engine-twig-php@3.0.5) (2019-10-14)
+
+
+### Bug Fixes
+
+* **engine_twig_php:** Pseudo patterns Twig PHP ([226aa8b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/226aa8bbaaf5e418530ccf54a28f6c5657ee6dea)), closes [#1045](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/issues/1045)
+* **engine_twig_php:** Twig incremental rebuilds ([5d33f24](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/5d33f24f156ebe50900701513a855de7de608dcf)), closes [#1015](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/issues/1015)
+* **engine_twig_php:** Twig incremental rebuilds ([1ade945](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/commit/1ade9451840b2645706a0b01129e2b697dc22d4b)), closes [#1015](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/issues/1015)
+
+
+
+
+
+
+## [3.0.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/@pattern-lab/engine-twig-php@3.0.1...@pattern-lab/engine-twig-php@3.0.2) (2019-08-23)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig-php
+
+
+
+
+
+
+## [3.0.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php/compare/@pattern-lab/engine-twig-php@3.0.0...@pattern-lab/engine-twig-php@3.0.1) (2019-05-16)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig-php
+
+
+
+
+
+# Change Log
diff --git a/packages/engine-twig-php/LICENSE b/packages/engine-twig-php/LICENSE
new file mode 100644
index 000000000..8d83dd7de
--- /dev/null
+++ b/packages/engine-twig-php/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Evan Lovely, http://evanlovely.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/engine-twig-php/README.md b/packages/engine-twig-php/README.md
new file mode 100644
index 000000000..f6764d129
--- /dev/null
+++ b/packages/engine-twig-php/README.md
@@ -0,0 +1,3 @@
+## The Twig PHP PatternEngine for Pattern Lab / Node
+
+To install the Twig engine in your edition, `npm install --save @pattern-lab/engine-twig-php` should do the trick.
diff --git a/packages/engine-twig-php/_meta/_00-head.twig b/packages/engine-twig-php/_meta/_00-head.twig
new file mode 100644
index 000000000..4b49c63b7
--- /dev/null
+++ b/packages/engine-twig-php/_meta/_00-head.twig
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{ patternLabHead | raw }}
+
+
+
+
diff --git a/packages/engine-twig-php/_meta/_01-foot.twig b/packages/engine-twig-php/_meta/_01-foot.twig
new file mode 100644
index 000000000..159dae3eb
--- /dev/null
+++ b/packages/engine-twig-php/_meta/_01-foot.twig
@@ -0,0 +1,6 @@
+
+
+{{ patternLabFoot | raw }}
+
+
+
diff --git a/packages/engine-twig-php/lib/engine_twig_php.js b/packages/engine-twig-php/lib/engine_twig_php.js
new file mode 100644
index 000000000..f675e94d1
--- /dev/null
+++ b/packages/engine-twig-php/lib/engine_twig_php.js
@@ -0,0 +1,268 @@
+'use strict';
+
+/*
+ * Twig PHP pattern engine for patternlab-node
+ *
+ * Evan Lovely
+ * Licensed under the MIT license.
+ *
+ * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice.
+ *
+ */
+
+/*
+ * ENGINE SUPPORT LEVEL: Experimental
+ */
+
+const TwigRenderer = require('@basalt/twig-renderer');
+const fs = require('fs-extra');
+const path = require('path');
+const chalk = require('chalk');
+
+let twigRenderer;
+let patternLabConfig = {};
+
+const engine_twig_php = {
+ engine: TwigRenderer,
+ engineName: 'twig-php',
+ engineFileExtension: '.twig',
+ expandPartials: false,
+ findPartialsRE: /{%\s*(?:extends|include|embed)\s+('[^']+'|"[^"]+").*?(with|%}|\s*%})/g,
+ findPartialKeyRE: /"((?:\\.|[^"\\])*)"|'((?:\\.|[^"\\])*)'/,
+ namespaces: [],
+
+ /**
+ * Accept a Pattern Lab config object from the core and put it in
+ * this module's closure scope so we can configure engine behavior.
+ *
+ * @param {object} config - the global config object from core
+ */
+ usePatternLabConfig: function(config) {
+ patternLabConfig = config;
+
+ if (!config.engines.twig) {
+ console.error('Missing "twig" in Pattern Lab config file; exiting...');
+ process.exit(1);
+ }
+
+ const { namespaces, alterTwigEnv, relativeFrom, ...rest } = config.engines.twig;
+
+ // Schema on config object being passed in:
+ // https://github.com/basaltinc/twig-renderer/blob/master/config.schema.json
+ twigRenderer = new TwigRenderer({
+ src: {
+ roots: [config.paths.source.root, config.paths.source.patterns],
+ namespaces,
+ },
+ relativeFrom,
+ alterTwigEnv,
+ ...rest,
+ });
+
+ // Preserve the namespaces (after recursively adding nested folders) from the config so we can use them later to evaluate partials.
+ this.namespaces = twigRenderer.config.src.namespaces;
+ },
+
+ renderPattern(pattern, data) {
+ return new Promise((resolve, reject) => {
+ // If this is a pseudo pattern the relPath will be incorrect.
+ // i.e. /path/to/pattern.json
+ // Twig can't render that file so we need to use the base patterns
+ // relPath instead.
+ const relPath = pattern.isPseudoPattern
+ ? pattern.basePattern.relPath
+ : pattern.relPath;
+
+ const patternPath = path.isAbsolute(relPath)
+ ? path.relative(patternLabConfig.paths.source.root, relPath)
+ : relPath;
+ let details = '';
+ if (patternLabConfig.logLevel === 'debug') {
+ details = `${JSON.stringify(
+ { pattern, data },
+ null,
+ ' '
+ )} `;
+ }
+
+ twigRenderer
+ .render(patternPath, data)
+ .then(results => {
+ if (results.ok) {
+ resolve(results.html + details);
+ } else {
+ // make Twig rendering errors more noticeable + exit when not in dev mode (or running the `patternlab serve` command)
+ if (
+ process.argv.slice(1).includes('serve') ||
+ process.env.NODE_ENV === 'development'
+ ) {
+ reject(chalk.red(results.message));
+ } else {
+ console.log(chalk.red(results.message));
+ process.exit(1);
+ }
+ }
+ })
+ .catch(error => {
+ reject(error);
+ });
+ });
+ },
+
+ /**
+ * Checks to see if the _meta directory has engine-specific head and foot files,
+ * spawning them if not found.
+ *
+ * @param {object} config - the global config object from core, since we won't
+ * assume it's already present
+ */
+ spawnMeta(config) {
+ const { paths } = config;
+ ['_00-head.twig', '_01-foot.twig'].forEach(fileName => {
+ const metaFilePath = path.resolve(paths.source.meta, fileName);
+ try {
+ fs.statSync(metaFilePath);
+ } catch (err) {
+ //not a file, so spawn it from the included file
+ const metaFileContent = fs.readFileSync(
+ path.resolve(__dirname, '..', '_meta/', fileName),
+ 'utf8'
+ );
+ fs.outputFileSync(metaFilePath, metaFileContent);
+ }
+ });
+ },
+
+ // Below exists several functions that core uses to build lineage through RegEx
+ // @todo Add all functions that get called even if disabled to ease implementing engine further
+ // Currently all of them return `null` as I'm not totally sure there absence will be ok. Additionally, future improvements may be implemented in this functions.
+
+ // Find and return any {% extends|include|embed 'template-name' %} within pattern.
+ // The regex should match the following examples:
+ // {%
+ // include '@molecules/teaser-card/teaser-card.twig' with {
+ // teaser_card: card
+ // } only
+ // %}
+ // OR
+ // {% include '@molecules/teaser-card/teaser-card.twig' %}
+ // OR
+ // {%
+ // include '@molecules/teaser-card/teaser-card.twig'
+ // %}
+ findPartials: function(pattern) {
+ const matches = pattern.template.match(this.findPartialsRE);
+ const filteredMatches =
+ matches &&
+ matches.filter(match => {
+ // Filter out programmatically created includes.
+ // i.e. {% include '@namespace/icons/assets/' ~ name ~ '.svg' %}
+ return match.indexOf('~') === -1;
+ });
+ return filteredMatches;
+ },
+
+ findPartialsWithStyleModifiers(pattern) {
+ return null;
+ },
+
+ findPartialsWithPatternParameters(pattern) {
+ return null;
+ },
+
+ findListItems(pattern) {
+ return null;
+ },
+
+ findPartial_new(partialString) {
+ return null;
+ },
+
+ // Given a pattern, and a partial string, tease out the "pattern key" and
+ // return it.
+ findPartial: function(partialString) {
+ try {
+ let partial = partialString.match(this.findPartialKeyRE)[0];
+ partial = partial.replace(/"/g, '');
+ partial = partial.replace(/'/g, '');
+
+ // Check if namespaces is not empty.
+ const selectedNamespace = this.namespaces.filter(namespace => {
+ // Check to see if this partial contains within the namespace id.
+ return partial.indexOf(`@${namespace.id}`) !== -1;
+ });
+
+ let namespaceResolvedPartial = '';
+
+ if (selectedNamespace.length > 0) {
+ // Loop through all namespaces and try to resolve the namespace to a file path.
+ for (
+ let index = 0;
+ index < selectedNamespace[0].paths.length;
+ index++
+ ) {
+ const patternPath = path.isAbsolute(selectedNamespace[0].paths[index])
+ ? path.relative(
+ patternLabConfig.paths.source.root,
+ selectedNamespace[0].paths[index]
+ )
+ : selectedNamespace[0].paths[index];
+
+ // Replace the name space with the actual path.
+ // i.e. @atoms -> source/_patterns/00-atoms
+ const tempPartial = path.join(
+ process.cwd(),
+ partial.replace(`@${selectedNamespace[0].id}`, patternPath)
+ );
+
+ try {
+ // Check to see if the file actually exists.
+ if (fs.existsSync(tempPartial)) {
+ // get the path to the top-level folder of this pattern
+ // ex. /Users/bradfrost/sites/pattern-lab/packages/edition-twig/source/_patterns/00-atoms
+ const fullFolderPath = `${
+ tempPartial.split(selectedNamespace[0].paths[index])[0]
+ }${selectedNamespace[0].paths[index]}`;
+
+ // then tease out the folder name itself (including the # prefix)
+ // ex. 00-atoms
+ const folderName = fullFolderPath.substring(
+ fullFolderPath.lastIndexOf('/') + 1,
+ fullFolderPath.length
+ );
+
+ // finally, return the Twig path we created from the full file path
+ // ex. 00-atoms/05-buttons/button.twig
+ const fullIncludePath = tempPartial.replace(
+ tempPartial.split(
+ `${folderName}${tempPartial.split(folderName)[1]}`
+ )[0],
+ ''
+ );
+
+ namespaceResolvedPartial = fullIncludePath;
+
+ // After it matches one time, set the resolved partial and exit the loop.
+ break;
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+ // Return the path with the namespace resolved OR the regex'd partial.
+ return namespaceResolvedPartial || partial;
+ } catch (err) {
+ console.error(
+ 'Error occured when trying to find partial name in: ' + partialString
+ );
+ return null;
+ }
+ },
+
+ patternMatcher(pattern, regex) {
+ return null;
+ },
+};
+
+module.exports = engine_twig_php;
diff --git a/packages/engine-twig-php/package.json b/packages/engine-twig-php/package.json
new file mode 100644
index 000000000..d1a3bd703
--- /dev/null
+++ b/packages/engine-twig-php/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@pattern-lab/engine-twig-php",
+ "description": "The Twig PHP engine for Pattern Lab Node",
+ "version": "5.9.3",
+ "main": "lib/engine_twig_php.js",
+ "dependencies": {
+ "@basalt/twig-renderer": "0.13.1",
+ "@pattern-lab/core": "^5.9.3",
+ "chalk": "^4.0.0",
+ "fs-extra": "0.30.0"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Twig"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig-php",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": {
+ "name": "Evan Lovely",
+ "url": "http://evanlovely.com"
+ },
+ "maintainers": [
+ {
+ "name": "Salem Ghoweri"
+ }
+ ],
+ "license": "MIT",
+ "scripts": {},
+ "engines": {
+ "node": ">=8.9"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/engine-twig/.gitignore b/packages/engine-twig/.gitignore
new file mode 100644
index 000000000..74ab03195
--- /dev/null
+++ b/packages/engine-twig/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+.DS_Store
+latest-change.txt
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+source/css/style.css.map
+.idea/
+public
diff --git a/packages/engine-twig/.npmrc b/packages/engine-twig/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/engine-twig/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/engine-twig/.nvmrc b/packages/engine-twig/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/engine-twig/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/engine-twig/CHANGELOG.md b/packages/engine-twig/CHANGELOG.md
new file mode 100644
index 000000000..133363e5b
--- /dev/null
+++ b/packages/engine-twig/CHANGELOG.md
@@ -0,0 +1,113 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+
+### Bug Fixes
+
+* Update dependency on twing JS engine ([cfe88c6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/cfe88c6cdbf2219b9955eaa0ffcfc0e4a7683511))
+
+
+
+
+
+# [5.8.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/v5.7.2...v5.8.0) (2020-04-03)
+
+
+### Bug Fixes
+
+* Updated the README to reflect which issues are resolved. ([d90c3c4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/d90c3c4605f9a5bcd1153996e3f4d1a17d58bd92))
+
+
+### Features
+
+* switch engine-twig to use twing rather than node-twig ([daca95c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/daca95c4ffa48916fb6c67c5184bde9b624acd76))
+
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig
+
+
+
+
+
+
+## [0.2.1-beta.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/@pattern-lab/engine-twig@0.2.1-beta.0...@pattern-lab/engine-twig@0.2.1-beta.1) (2019-02-09)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig
+
+
+
+
+
+
+## [0.2.1-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/@pattern-lab/engine-twig@0.2.1-alpha.5...@pattern-lab/engine-twig@0.2.1-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig
+
+
+
+
+
+
+
+## [0.2.1-alpha.5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/@pattern-lab/engine-twig@0.2.1-alpha.4...@pattern-lab/engine-twig@0.2.1-alpha.5) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/5ab3995))
+
+
+
+## [0.2.1-alpha.4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/@pattern-lab/engine-twig@0.2.1-alpha.3...@pattern-lab/engine-twig@0.2.1-alpha.4) (2018-07-05)
+
+### Features
+
+* **tests:** use lerna run test at the monorepo level ([38a01b1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/38a01b1))
+
+
+
+## [0.2.1-alpha.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/@pattern-lab/engine-twig@0.2.1-alpha.2...@pattern-lab/engine-twig@0.2.1-alpha.3) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/engine-twig
+
+
+
+## [0.2.1-alpha.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/@pattern-lab/engine-twig@0.2.1-alpha.1...@pattern-lab/engine-twig@0.2.1-alpha.2) (2018-03-21)
+
+### Bug Fixes
+
+* **package:** remove files obsoleted by monorepo ([9abb8ac](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/9abb8ac))
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/337aa32))
+* **README:** update content for consistency ([4edf0d4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/4edf0d4)), closes [#815](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/issues/815)
+
+
+
+## [0.2.1-alpha.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/compare/@pattern-lab/engine-twig@0.2.1-alpha.0...@pattern-lab/engine-twig@0.2.1-alpha.1) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/1473cd5))
+
+
+
+## 0.2.1-alpha.0 (2018-03-02)
+
+### Bug Fixes
+
+* **engine-twig:** Fix package name ([58f7ec1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/58f7ec1))
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/58beeb6))
+
+### Features
+
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig/commit/5eb2c11))
diff --git a/packages/engine-twig/LICENSE b/packages/engine-twig/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/engine-twig/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/engine-twig/README.md b/packages/engine-twig/README.md
new file mode 100644
index 000000000..9d45fca95
--- /dev/null
+++ b/packages/engine-twig/README.md
@@ -0,0 +1,18 @@
+## The Twig PatternEngine for Pattern Lab / Node
+
+To install the Twig engine in your edition, `npm install @pattern-lab/engine-twig` should do the trick. This pattern engine uses the [`twing`](https://www.npmjs.com/package/twing) library.
+
+## Supported features
+
+Level of support for Twig constructs is on the level that the `twing` library supports. The following partial resolution schemes (`includes`, `extends`, `import`) are supported:
+
+* relative file paths: standard by `twing` libary
+* namespaces: standard by `twing` library, `engine-twig` only passes the configuration from `patternlab-config.json`
+* Patternlab pattern names: integration between Patternlab and `twing` implemented by a custom [`loader`](https://nightlycommit.github.io/twing/api.html#create-your-own-loader)
+
+Now that this engine uses a better Twig Javascript library, the following issues are resolved:
+
+* [Pattern Lab does not support twig extends](https://github.com/pattern-lab/patternlab-node/issues/554)
+* [Verify maturity of Twig engine](https://github.com/pattern-lab/patternlab-node/issues/285)
+
+See https://github.com/pattern-lab/the-spec/issues/37 for more info.
diff --git a/packages/engine-twig/_meta/_00-head.twig b/packages/engine-twig/_meta/_00-head.twig
new file mode 100644
index 000000000..4b49c63b7
--- /dev/null
+++ b/packages/engine-twig/_meta/_00-head.twig
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{ patternLabHead | raw }}
+
+
+
+
diff --git a/packages/engine-twig/_meta/_01-foot.twig b/packages/engine-twig/_meta/_01-foot.twig
new file mode 100644
index 000000000..159dae3eb
--- /dev/null
+++ b/packages/engine-twig/_meta/_01-foot.twig
@@ -0,0 +1,6 @@
+
+
+{{ patternLabFoot | raw }}
+
+
+
diff --git a/packages/engine-twig/lib/engine_twig.js b/packages/engine-twig/lib/engine_twig.js
new file mode 100644
index 000000000..0bfc99363
--- /dev/null
+++ b/packages/engine-twig/lib/engine_twig.js
@@ -0,0 +1,231 @@
+'use strict';
+
+/*
+ * twig pattern engine for patternlab-node - v0.15.1 - 2015
+ *
+ * Geoffrey Pursell, Brian Muenzenmeyer, and the web community.
+ * Licensed under the MIT license.
+ *
+ * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice.
+ *
+ */
+
+/*
+ * ENGINE SUPPORT LEVEL:
+ *
+ * Full. Partial calls and lineage hunting are supported. Twig does not support
+ * the mustache-specific syntax extensions, style modifiers and pattern
+ * parameters, because their use cases are addressed by the core Twig feature
+ * set.
+ *
+ */
+
+const fs = require('fs-extra');
+const path = require('path');
+const {
+ TwingEnvironment,
+ TwingLoaderFilesystem,
+ TwingLoaderChain,
+ TwingSource,
+} = require('twing');
+
+class TwingLoaderPatternLab {
+ patterns = new Map();
+
+ constuctor() {}
+
+ registerPartial(pattern) {
+ if (pattern.patternPartial) {
+ this.patterns.set(pattern.patternPartial, pattern);
+ }
+ }
+
+ /**
+ * Returns the source context for a given template logical name.
+ *
+ * @param {string} name The template logical name
+ * @param {TwingSource} from The source that initiated the template loading
+ *
+ * @returns {Promise}
+ *
+ * @throws TwingErrorLoader When name is not found
+ */
+ getSourceContext(name, from) {
+ var pattern = this.patterns.get(name);
+ return Promise.resolve(
+ new TwingSource(pattern.extendedTemplate, name, pattern.relPath)
+ );
+ }
+
+ /**
+ * Gets the cache key to use for the cache for a given template name.
+ *
+ * @param {string} name The name of the template to load
+ * @param {TwingSource} from The source that initiated the template loading
+ *
+ * @returns {Promise} The cache key
+ *
+ * @throws TwingErrorLoader When name is not found
+ */
+ getCacheKey(name, from) {
+ return Promise.resolve(name);
+ }
+
+ /**
+ * Returns true if the template is still fresh.
+ *l
+ * @param {string} name The template name
+ * @param {number} time Timestamp of the last modification time of the cached template
+ * @param {TwingSource} from The source that initiated the template loading
+ *
+ * @returns {Promise} true if the template is fresh, false otherwise
+ *
+ * @throws TwingErrorLoader When name is not found
+ */
+ isFresh(name, time, from) {
+ return Promise.resolve(this.patterns.has(name) ? true : false);
+ }
+
+ /**
+ * Check if we have the source code of a template, given its name.
+ *
+ * @param {string} name The name of the template to check if we can load
+ * @param {TwingSource} from The source that initiated the template loading
+ *
+ * @returns {Promise} If the template source code is handled by this loader or not
+ */
+ exists(name, from) {
+ return Promise.resolve(this.patterns.has(name) ? true : false);
+ }
+
+ /**
+ * Resolve the path of a template, given its name, whatever it means in the context of the loader.
+ *
+ * @param {string} name The name of the template to resolve
+ * @param {TwingSource} from The source that initiated the template loading
+ *
+ * @returns {Promise} The resolved path of the template
+ */
+ resolve(name, from) {
+ pattern = this.patterns.get(name);
+ return null;
+ }
+}
+
+const fileSystemLoader = new TwingLoaderFilesystem();
+const patternLabLoader = new TwingLoaderPatternLab();
+const chainLoader = new TwingLoaderChain([fileSystemLoader, patternLabLoader]);
+const twing = new TwingEnvironment(chainLoader);
+var metaPath;
+
+var engine_twig = {
+ engine: twing,
+ engineName: 'twig',
+ engineFileExtension: '.twig',
+
+ // regexes, stored here so they're only compiled once
+ findPartialsRE: /{%\s*(?:extends|include|embed)\s+('[^']+'|"[^"]+").*?%}/g,
+ findPartialKeyRE: /"((?:\\.|[^"\\])*)"/,
+ findListItemsRE: /({{#( )?)(list(I|i)tems.)(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)( )?}}/g, // TODO
+
+ // render it
+ renderPattern: function renderPattern(pattern, data, partials) {
+ var patternPath = pattern.relPath;
+ if (patternPath.lastIndexOf(metaPath) === 0) {
+ patternPath = patternPath.substring(metaPath.length + 1);
+ }
+ return Promise.resolve(twing.render(patternPath, data));
+ },
+
+ registerPartial: function registerPartial(pattern) {
+ console.log(
+ `registerPartial(${pattern.name} - ${pattern.patternPartial} - ${pattern.patternPath} - ${pattern.relPath})`
+ );
+ patternLabLoader.registerPartial(pattern);
+ },
+
+ // find and return any {% include 'template-name' %} within pattern
+ findPartials: function findPartials(pattern) {
+ var matches = pattern.template.match(this.findPartialsRE);
+ return matches;
+ },
+ findPartialsWithStyleModifiers: function() {
+ // TODO: make the call to this from oPattern objects conditional on their
+ // being implemented here.
+ return [];
+ },
+
+ // returns any patterns that match {{> value(foo:"bar") }} or {{>
+ // value:mod(foo:"bar") }} within the pattern
+ findPartialsWithPatternParameters: function() {
+ // TODO: make the call to this from oPattern objects conditional on their
+ // being implemented here.
+ return [];
+ },
+ findListItems: function(pattern) {
+ var matches = pattern.template.match(this.findListItemsRE);
+ return matches;
+ },
+
+ // given a pattern, and a partial string, tease out the "pattern key" and
+ // return it.
+ findPartial: function(partialString) {
+ var partial = partialString.match(this.findPartialKeyRE)[0];
+ partial = partial.replace(/"/g, '');
+
+ return partial;
+ },
+
+ spawnFile: function(config, fileName) {
+ const paths = config.paths;
+ const metaFilePath = path.resolve(paths.source.meta, fileName);
+ try {
+ fs.statSync(metaFilePath);
+ } catch (err) {
+ //not a file, so spawn it from the included file
+ const metaFileContent = fs.readFileSync(
+ path.resolve(__dirname, '..', '_meta/', fileName),
+ 'utf8'
+ );
+ fs.outputFileSync(metaFilePath, metaFileContent);
+ }
+ },
+
+ /**
+ * Checks to see if the _meta directory has engine-specific head and foot files,
+ * spawning them if not found.
+ *
+ * @param {object} config - the global config object from core, since we won't
+ * assume it's already present
+ */
+ spawnMeta: function(config) {
+ this.spawnFile(config, '_00-head.twig');
+ this.spawnFile(config, '_01-foot.twig');
+ },
+
+ /**
+ * Accept a Pattern Lab config object from the core and use the settings to
+ * load helpers.
+ *
+ * @param {object} config - the global config object from core
+ */
+ usePatternLabConfig: function(config) {
+ metaPath = path.resolve(config.paths.source.meta);
+ // Global paths
+ fileSystemLoader.addPath(config.paths.source.meta);
+ fileSystemLoader.addPath(config.paths.source.patterns);
+ // Namespaced paths
+ if (
+ config['engines'] &&
+ config['engines']['twig'] &&
+ config['engines']['twig']['namespaces']
+ ) {
+ var namespaces = config['engines']['twig']['namespaces'];
+ Object.keys(namespaces).forEach(function(key, index) {
+ fileSystemLoader.addPath(namespaces[key], key);
+ });
+ }
+ },
+};
+
+module.exports = engine_twig;
diff --git a/packages/engine-twig/package.json b/packages/engine-twig/package.json
new file mode 100644
index 000000000..2b3723c0a
--- /dev/null
+++ b/packages/engine-twig/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@pattern-lab/engine-twig",
+ "description": "The Twig engine for Pattern Lab / Node",
+ "version": "5.9.3",
+ "main": "lib/engine_twig.js",
+ "dependencies": {
+ "fs-extra": "0.30.0",
+ "twing": "4.0.6"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Grunt",
+ "Gulp",
+ "Javascript",
+ "Twig"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": "Brian Muenzenmeyer & Geoffrey Pursell",
+ "license": "MIT",
+ "scripts": {},
+ "engines": {
+ "node": ">=10.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/engine-underscore/.gitignore b/packages/engine-underscore/.gitignore
new file mode 100644
index 000000000..74ab03195
--- /dev/null
+++ b/packages/engine-underscore/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+.DS_Store
+latest-change.txt
+patternlab.json
+.sass-cache/*
+/sass-cache
+Thumbs.db
+source/css/style.css.map
+.idea/
+public
diff --git a/packages/engine-underscore/.npmrc b/packages/engine-underscore/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/engine-underscore/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/engine-underscore/.nvmrc b/packages/engine-underscore/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/engine-underscore/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/engine-underscore/CHANGELOG.md b/packages/engine-underscore/CHANGELOG.md
new file mode 100644
index 000000000..24bcead4c
--- /dev/null
+++ b/packages/engine-underscore/CHANGELOG.md
@@ -0,0 +1,84 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+**Note:** Version bump only for package @pattern-lab/engine-underscore
+
+
+
+
+
+
+# [2.0.0-beta.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/compare/@pattern-lab/engine-underscore@2.0.0-beta.0...@pattern-lab/engine-underscore@2.0.0-beta.1) (2019-02-09)
+
+**Note:** Version bump only for package @pattern-lab/engine-underscore
+
+
+
+
+
+
+# [2.0.0-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/compare/@pattern-lab/engine-underscore@2.0.0-alpha.7...@pattern-lab/engine-underscore@2.0.0-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/engine-underscore
+
+
+
+
+
+
+
+# [2.0.0-alpha.7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/compare/@pattern-lab/engine-underscore@2.0.0-alpha.6...@pattern-lab/engine-underscore@2.0.0-alpha.7) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/5ab3995))
+
+
+
+# [2.0.0-alpha.6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/compare/@pattern-lab/engine-underscore@2.0.0-alpha.5...@pattern-lab/engine-underscore@2.0.0-alpha.6) (2018-07-05)
+
+### Features
+
+* **tests:** use lerna run test at the monorepo level ([38a01b1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/38a01b1))
+
+
+
+# [2.0.0-alpha.5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/compare/@pattern-lab/engine-underscore@2.0.0-alpha.4...@pattern-lab/engine-underscore@2.0.0-alpha.5) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/engine-underscore
+
+
+
+# [2.0.0-alpha.4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/compare/@pattern-lab/engine-underscore@2.0.0-alpha.3...@pattern-lab/engine-underscore@2.0.0-alpha.4) (2018-03-21)
+
+### Bug Fixes
+
+* **package:** remove files obsoleted by monorepo ([9abb8ac](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/9abb8ac))
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/337aa32))
+* **README:** update content for consistency ([4edf0d4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/4edf0d4)), closes [#815](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/issues/815)
+
+
+
+# [2.0.0-alpha.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/compare/@pattern-lab/engine-underscore@2.0.0-alpha.2...@pattern-lab/engine-underscore@2.0.0-alpha.3) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/1473cd5))
+
+
+
+# 2.0.0-alpha.2 (2018-03-02)
+
+### Bug Fixes
+
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/58beeb6))
+
+### Features
+
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore/commit/5eb2c11))
diff --git a/packages/engine-underscore/LICENSE b/packages/engine-underscore/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/engine-underscore/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/engine-underscore/README.md b/packages/engine-underscore/README.md
new file mode 100644
index 000000000..59796d51f
--- /dev/null
+++ b/packages/engine-underscore/README.md
@@ -0,0 +1,64 @@
+# The Underscore PatternEngine for Pattern Lab / Node
+
+## Installing
+
+To install the Underscore PatternEngine in your edition, `npm install @pattern-lab/engine-underscore` should do the trick.
+
+## Supported features
+
+* [x] [Includes](http://patternlab.io/docs/pattern-including.html) (Accomplished using the included [`_.renderNamedPartial()`](https://github.com/pattern-lab/patternlab-node/blob/master/packages/engine-underscore/lib/engine_underscore.js#L54-L60))
+* [x] Lineage
+* [x] [Hidden Patterns](http://patternlab.io/docs/pattern-hiding.html)
+* [x] [Pseudo-Patterns](http://patternlab.io/docs/pattern-pseudo-patterns.html)
+* [x] [Pattern States](http://patternlab.io/docs/pattern-states.html)
+* [ ] [Pattern Parameters](http://patternlab.io/docs/pattern-parameters.html) (Accomplished instead using parameter object passed to the included [`_.renderNamedPartial()`](https://github.com/pattern-lab/patternlab-node/blob/master/packages/engine-underscore/lib/engine_underscore.js#L54-L60) mixin function)
+* [ ] [Style Modifiers](http://patternlab.io/docs/pattern-stylemodifier.html) (Accomplished instead using parameter object passed to the included [`_.renderNamedPartial()`](https://github.com/pattern-lab/patternlab-node/blob/master/packages/engine-underscore/lib/engine_underscore.js#L54-L60) mixin function)
+
+## Extensions to basic Underscore functionality
+
+### Pattern including
+
+Underscore templates include no native support for calling other templates, so support for pattern including is accomplished through an included Underscore mixin function, [`_.renderNamedPartial()`](https://github.com/pattern-lab/patternlab-node/blob/master/packages/engine-underscore/lib/engine_underscore.js#L54-L60), and is considered experimental, but seems to work just fine.
+
+#### Example
+
+```
+
+ Here's a large button, with parameters:
+ <%- _.renderNamedPartial('atoms-button', { variantClass: 'btn-large' }) %>
+
+```
+
+### Safely referring to deeply nested data in the pattern JSON
+
+When referring to deeply nested data, it's helpful to have a way of doing that (as Handlebars and Mustache do) that's tolerant of unexpected `null` values. For example, if you have the following pattern JSON:
+
+```json
+{
+ "foo": {
+ "bar": {
+ "value": "That is the question:"
+ }
+ }
+}
+```
+
+And the following in an underscore template that refers to it:
+
+```
+
+ To be, or not to be, <%= foo.bar.value %>
+
+```
+
+If you feed that template JSON that (for whatever reason) has `foo.bar` as `null`, the pattern will crash because `null.value` throws an exception. It's nice to be able to write this instead:
+
+```
+
+ To be, or not to be, <%= _.getPath('foo.bar.value', obj) %>
+
+```
+
+and know that the output will be more safely "To be, or, not to be, null" instead of just throwing an error and crashing the pattern. This is mainly useful for operationalized pattern templates that will be provided with JSON from services that you can't control in Pattern Lab.
+
+Note that `obj` is an Underscore pattern's current data context. See [Dr. Axel Rauschmeyer's article](http://www.2ality.com/2012/06/underscore-templates.html) for more.
diff --git a/packages/engine-underscore/_meta/_00-head.html b/packages/engine-underscore/_meta/_00-head.html
new file mode 100644
index 000000000..b1f5c1ce0
--- /dev/null
+++ b/packages/engine-underscore/_meta/_00-head.html
@@ -0,0 +1,16 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+ {{{ patternLabHead }}}
+
+
+
+
diff --git a/packages/engine-underscore/_meta/_01-foot.html b/packages/engine-underscore/_meta/_01-foot.html
new file mode 100644
index 000000000..797d9418d
--- /dev/null
+++ b/packages/engine-underscore/_meta/_01-foot.html
@@ -0,0 +1,6 @@
+
+
+{{{ patternLabFoot }}}
+
+
+
diff --git a/packages/engine-underscore/lib/engine_underscore.js b/packages/engine-underscore/lib/engine_underscore.js
new file mode 100644
index 000000000..79fd9ef81
--- /dev/null
+++ b/packages/engine-underscore/lib/engine_underscore.js
@@ -0,0 +1,222 @@
+'use strict';
+
+/*
+ * underscore pattern engine for patternlab-node - v0.15.1 - 2015
+ *
+ * Geoffrey Pursell, Brian Muenzenmeyer, and the web community.
+ * Licensed under the MIT license.
+ *
+ * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice.
+ *
+ */
+
+/*
+ * ENGINE SUPPORT LEVEL:
+ *
+ * Basic. We can't call partials from inside underscore templates yet, but we
+ * can render templates with backing JSON.
+ *
+ */
+
+const fs = require('fs-extra');
+const path = require('path');
+
+var _ = require('underscore');
+
+var partialRegistry = {};
+var errorStyling = `
+
+`;
+
+// extend underscore with partial-ing methods and other necessary tooling
+// HANDLESCORE! UNDERBARS!
+
+function addParentContext(data, currentContext) {
+ return Object.assign({}, currentContext, data);
+}
+
+_.mixin({
+ renderNamedPartial: function(partialKey, data, currentContext) {
+ var compiledPartial = partialRegistry[partialKey];
+ if (typeof compiledPartial !== 'function') {
+ throw `Pattern ${partialKey} not found.`;
+ }
+
+ return _.renderPartial(compiledPartial, data, currentContext);
+ },
+ renderPartial: function(compiledPartial, dataIn, currentContext) {
+ var data = dataIn || {};
+
+ if (
+ dataIn &&
+ currentContext &&
+ dataIn instanceof Object &&
+ currentContext instanceof Object
+ ) {
+ data = addParentContext(data, currentContext);
+ }
+
+ return compiledPartial(data);
+ },
+ /* eslint-disable no-eval, no-unused-vars */
+ getPath: function(pathString, currentContext, debug) {
+ try {
+ var result = eval('currentContext.' + pathString);
+ if (debug) {
+ console.log('getPath result = ', result);
+ }
+ return result;
+ } catch (e) {
+ return null;
+ }
+ },
+});
+
+var engine_underscore = {
+ engine: _,
+ engineName: 'underscore',
+ engineFileExtension: ['.html', '.underscore'],
+
+ // partial expansion is only necessary for Mustache templates that have
+ // style modifiers or pattern parameters (I think)
+ expandPartials: false,
+
+ // regexes, stored here so they're only compiled once
+ findPartialsRE: /<%=\s*_\.renderNamedPartial[ \t]*\(\s*("(?:[^"].*?)"|'(?:[^'].*?)').*?%>/g, // TODO
+ findListItemsRE: /({{#( )?)(list(I|i)tems.)(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)( )?}}/g,
+
+ // render it
+ renderPattern: function renderPattern(pattern, data, partials) {
+ var renderedHTML;
+ var compiled;
+
+ try {
+ compiled = partialRegistry[pattern.patternPartial];
+ } catch (e) {
+ console.log(
+ `Error looking up underscore template ${pattern.patternName}:`,
+ pattern.extendedTemplate,
+ e
+ );
+ }
+
+ // This try-catch is necessary because references to undefined variables
+ // in underscore templates are eval()ed directly as javascript, and as
+ // such will throw very real exceptions that will shatter the whole build
+ // process if we don't handle them.
+ try {
+ renderedHTML = compiled(
+ _.extend(data || {}, {
+ _allData: data,
+ _partials: partials,
+ })
+ );
+ } catch (e) {
+ var errorMessage = `Error rendering underscore pattern "${
+ pattern.patternName
+ }" (${pattern.relPath}): [${e.toString()}]`;
+ console.log(errorMessage);
+ renderedHTML = `${errorStyling}
+
Error rendering underscore pattern "${pattern.patternName}"
+
+ Message ${e.toString()}
+ Partial name ${pattern.patternName}
+ Template path ${pattern.relPath}
+
+
+`;
+ }
+
+ return renderedHTML;
+ },
+
+ registerPartial: function(pattern) {
+ var compiled;
+
+ try {
+ var templateString = pattern.extendedTemplate || pattern.template;
+ compiled = _.template(templateString);
+ } catch (e) {
+ console.log(
+ `Error compiling underscore template ${pattern.patternName}:`,
+ pattern.extendedTemplate,
+ e
+ );
+ }
+ partialRegistry[pattern.patternPartial] = compiled;
+ },
+
+ // find and return any {{> template-name }} within pattern
+ findPartials: function findPartials(pattern) {
+ var matches = pattern.template.match(this.findPartialsRE);
+ return matches;
+ },
+ findPartialsWithStyleModifiers: function() {
+ return [];
+ },
+
+ // returns any patterns that match {{> value(foo:"bar") }} or {{>
+ // value:mod(foo:"bar") }} within the pattern
+ findPartialsWithPatternParameters: function() {
+ return [];
+ },
+ findListItems: function(pattern) {
+ var matches = pattern.template.match(this.findListItemsRE);
+ return matches;
+ },
+
+ // given a pattern, and a partial string, tease out the "pattern key" and
+ // return it.
+ findPartial: function(partialString) {
+ var edgeQuotesMatcher = /^["']|["']$/g;
+ var partialIDWithQuotes = partialString.replace(this.findPartialsRE, '$1');
+ var partialID = partialIDWithQuotes.replace(edgeQuotesMatcher, '');
+
+ return partialID;
+ },
+
+ spawnFile: function(config, fileName) {
+ const paths = config.paths;
+ const metaFilePath = path.resolve(paths.source.meta, fileName);
+ try {
+ fs.statSync(metaFilePath);
+ } catch (err) {
+ //not a file, so spawn it from the included file
+ const localMetaFilePath = path.resolve(__dirname, '_meta/', fileName);
+ const metaFileContent = fs.readFileSync(
+ path.resolve(__dirname, '..', '_meta/', fileName),
+ 'utf8'
+ );
+ fs.outputFileSync(metaFilePath, metaFileContent);
+ }
+ },
+
+ /**
+ * Checks to see if the _meta directory has engine-specific head and foot files,
+ * spawning them if not found.
+ *
+ * @param {object} config - the global config object from core, since we won't
+ * assume it's already present
+ */
+ spawnMeta: function(config) {
+ this.spawnFile(config, '_00-head.html');
+ this.spawnFile(config, '_01-foot.html');
+ },
+};
+
+module.exports = engine_underscore;
diff --git a/packages/engine-underscore/package.json b/packages/engine-underscore/package.json
new file mode 100644
index 000000000..b103cbbcb
--- /dev/null
+++ b/packages/engine-underscore/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "@pattern-lab/engine-underscore",
+ "description": "The Underscore engine for Pattern Lab / Node",
+ "version": "5.0.0",
+ "main": "lib/engine_underscore.js",
+ "dependencies": {
+ "underscore": "1.8.3"
+ },
+ "keywords": [
+ "Pattern Lab",
+ "Atomic Web Design",
+ "Node",
+ "Grunt",
+ "Gulp",
+ "Javascript",
+ "Underscore"
+ ],
+ "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-underscore",
+ "bugs": "https://github.com/pattern-lab/patternlab-node/issues",
+ "author": "Brian Muenzenmeyer & Geoffrey Pursell",
+ "license": "MIT",
+ "scripts": {
+ "lint": "eslint **/*.js"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/live-server/.gitignore b/packages/live-server/.gitignore
new file mode 100644
index 000000000..3c3629e64
--- /dev/null
+++ b/packages/live-server/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/packages/live-server/.npmrc b/packages/live-server/.npmrc
new file mode 100644
index 000000000..ce73f58bf
--- /dev/null
+++ b/packages/live-server/.npmrc
@@ -0,0 +1,2 @@
+package-lock=false
+save-exact=true
diff --git a/packages/live-server/.nvmrc b/packages/live-server/.nvmrc
new file mode 100644
index 000000000..95c4e8d27
--- /dev/null
+++ b/packages/live-server/.nvmrc
@@ -0,0 +1 @@
+10.0.0
\ No newline at end of file
diff --git a/packages/live-server/CHANGELOG.md b/packages/live-server/CHANGELOG.md
new file mode 100644
index 000000000..8028bae59
--- /dev/null
+++ b/packages/live-server/CHANGELOG.md
@@ -0,0 +1,115 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [5.9.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/v5.9.2...v5.9.3) (2020-05-01)
+
+**Note:** Version bump only for package @pattern-lab/live-server
+
+
+
+
+
+
+# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25)
+
+**Note:** Version bump only for package @pattern-lab/live-server
+
+
+
+
+
+
+## [1.3.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/@pattern-lab/live-server@1.3.3-beta.1...@pattern-lab/live-server@1.3.3) (2019-05-16)
+
+**Note:** Version bump only for package @pattern-lab/live-server
+
+
+
+
+
+## [1.3.3-beta.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/@pattern-lab/live-server@1.3.3-beta.0...@pattern-lab/live-server@1.3.3-beta.1) (2019-02-09)
+
+**Note:** Version bump only for package @pattern-lab/live-server
+
+
+
+
+
+
+## [1.3.3-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/@pattern-lab/live-server@1.3.3-alpha.6...@pattern-lab/live-server@1.3.3-beta.0) (2018-09-07)
+
+**Note:** Version bump only for package @pattern-lab/live-server
+
+
+
+
+
+
+
+## [1.3.3-alpha.6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/@pattern-lab/live-server@1.3.3-alpha.5...@pattern-lab/live-server@1.3.3-alpha.6) (2018-07-06)
+
+### Bug Fixes
+
+* **dependencies:** pin all packages marked as latest ([87347d5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/87347d5))
+
+
+
+## [1.3.3-alpha.5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/@pattern-lab/live-server@1.3.3-alpha.4...@pattern-lab/live-server@1.3.3-alpha.5) (2018-07-06)
+
+### Features
+
+* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/55f5bc2))
+* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/415698e))
+* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/5ab3995))
+
+
+
+## [1.3.3-alpha.4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/@pattern-lab/live-server@1.3.3-alpha.3...@pattern-lab/live-server@1.3.3-alpha.4) (2018-07-05)
+
+### Features
+
+* **tests:** use lerna run test at the monorepo level ([38a01b1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/38a01b1))
+
+
+
+## [1.3.3-alpha.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/@pattern-lab/live-server@1.3.3-alpha.2...@pattern-lab/live-server@1.3.3-alpha.3) (2018-05-04)
+
+**Note:** Version bump only for package @pattern-lab/live-server
+
+
+
+## [1.3.3-alpha.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/@pattern-lab/live-server@1.3.3-alpha.1...@pattern-lab/live-server@1.3.3-alpha.2) (2018-03-21)
+
+### Bug Fixes
+
+* **lint:** run code through prettier ([ca52fde](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/ca52fde)), closes [#825](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/issues/825)
+* **package:** remove files obsoleted by monorepo ([9abb8ac](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/9abb8ac))
+* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/337aa32))
+* **README:** update content for consistency ([4edf0d4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/4edf0d4)), closes [#815](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/issues/815)
+
+### Features
+
+* **package:** standardize and hoist common devDependencies ([7f4ce6f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/7f4ce6f))
+
+
+
+## [1.3.3-alpha.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/compare/@pattern-lab/live-server@1.3.3-alpha.0...@pattern-lab/live-server@1.3.3-alpha.1) (2018-03-05)
+
+### Bug Fixes
+
+* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/1473cd5))
+
+
+
+## 1.3.3-alpha.0 (2018-03-02)
+
+### Bug Fixes
+
+* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/58beeb6))
+* **prettier:** Attempt to ignore package files ([e6c08bf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/e6c08bf))
+
+### Features
+
+* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/live-server/commit/5eb2c11))
diff --git a/packages/live-server/LICENSE b/packages/live-server/LICENSE
new file mode 100644
index 000000000..c9b8c1daa
--- /dev/null
+++ b/packages/live-server/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/live-server/README.md b/packages/live-server/README.md
new file mode 100644
index 000000000..a8be5a771
--- /dev/null
+++ b/packages/live-server/README.md
@@ -0,0 +1 @@
+Fork and extension of https://github.com/tapio/live-server
diff --git a/packages/live-server/index.js b/packages/live-server/index.js
new file mode 100644
index 000000000..e372fac33
--- /dev/null
+++ b/packages/live-server/index.js
@@ -0,0 +1,489 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const connect = require('connect');
+const serveIndex = require('serve-index');
+const logger = require('morgan');
+const WebSocket = require('faye-websocket');
+const path = require('path');
+const url = require('url');
+const http = require('http');
+const send = require('send');
+const open = require('opn');
+const es = require('event-stream');
+const os = require('os');
+const chokidar = require('chokidar');
+
+require('colors');
+
+const INJECTED_CODE = fs.readFileSync(
+ path.join(__dirname, 'injected.html'),
+ 'utf8'
+);
+
+const LiveServer = {
+ server: null,
+ watcher: null,
+ logLevel: 2,
+};
+
+function escape(html) {
+ return String(html)
+ .replace(/&(?!\w+;)/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"');
+}
+
+// Based on connect.static(), but streamlined and with added code injecter
+function staticServer(root) {
+ let isFile = false;
+ try {
+ // For supporting mounting files instead of just directories
+ isFile = fs.statSync(root).isFile();
+ } catch (e) {
+ if (e.code !== 'ENOENT') throw e;
+ }
+ return function(req, res, next) {
+ if (req.method !== 'GET' && req.method !== 'HEAD') return next();
+
+ const reqpath = isFile ? '' : url.parse(req.url).pathname;
+ const hasNoOrigin = !req.headers.origin;
+ const injectCandidates = [
+ new RegExp('