├── .editorconfig
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── package.json
├── rollup.config.js
├── src
├── index.js
└── lib
│ ├── args.js
│ ├── color.js
│ ├── copy-template-dir.js
│ ├── exec.js
│ ├── fs.js
│ ├── git-config.js
│ ├── git-info.js
│ ├── github-user.js
│ └── question.js
└── template
├── .editorconfig
├── .gitignore
├── .rollup.js
├── .tape.js
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── INSTALL.md
├── LICENSE.md
├── README.md
├── package.json
├── src
└── index.js
└── test
├── basic.css
└── basic.expect.css
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = tab
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
13 | [*.{json,md,yml}]
14 | indent_size = 2
15 | indent_style = space
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | .*
4 | !.editorconfig
5 | !.gitignore
6 | !.tape.js
7 | !.travis.yml
8 | *.log*
9 | *.result.css
10 | /index.*
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # https://docs.travis-ci.com/user/travis-lint
2 |
3 | language: node_js
4 |
5 | os:
6 | - windows
7 | - linux
8 | - osx
9 |
10 | node_js:
11 | - 12
12 | - 10
13 | - 8
14 |
15 | install:
16 | - npm install --ignore-scripts
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changes to Create PostCSS Plugin
2 |
3 | ### 3.1.1 (November 9, 2019)
4 |
5 | - Updated: `postcss` to 7.0.21 (patch)
6 |
7 | ### 3.1.0 (July 29, 2019)
8 |
9 | - Added: Separated `build` task, automatically run before `test:tape`
10 | - Updated: All dependencies
11 |
12 | ### 3.0.0 (June 4, 2019)
13 |
14 | - Updated: `postcss` to 7.0.16 (patch)
15 | - Updated: Various development dependencies
16 | - Changed: Using standard instead of `eslint-config-dev` development dependency
17 | - Changed: Source moved into `src` directory more fully checked by eslint
18 | - Updated: Node 8+ compatibility (major)
19 | - Updated: Markdown templates to link back to the project less
20 | - Updated: README template to reference the master branch within Travis
21 | - Fixed: Issue with `.gitignore` being copied as `.npmignore`
22 | - Fixed: Potential issue in internally promisified fs.
23 |
24 | ### 2.0.0 (Jan 31, 2019)
25 |
26 | - Added: Support for package description (`pkgdesc`)
27 | - Updated: `postcss` to 7.0.14 (patch)
28 |
29 | ### 1.0.0 (Dec 22, 2018)
30 |
31 | - Initial version
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Create PostCSS Plugin [
][PostCSS]
2 |
3 | [![NPM Version][npm-img]][npm-url]
4 | [![Build Status][cli-img]][cli-url]
5 | [![Gitter Chat][git-img]][git-url]
6 |
7 | [Create PostCSS Plugin] lets you quickly create new [PostCSS] plugins with
8 | documentation, tests, and built-in compiling for supported Node environments.
9 |
10 | ```sh
11 | npm init postcss-plugin YOUR_DESTINATION
12 | ```
13 |
14 | Alternatively, you can use the `npx` command:
15 |
16 | ```sh
17 | npx create-postcss-plugin YOUR_DESTINATION
18 | ```
19 |
20 | After completing the instructions, write your plugin to `src/index.js` and
21 | update `README.md` with further details outlining your plugin functionality.
22 |
23 | ## Usage
24 |
25 | By default, **Create PostCSS Plugin** provides you the following prompts:
26 |
27 | ```
28 | Plugin Name: [TITLE]
29 | Keywords: [KEYWORDS]
30 | ```
31 |
32 | Once completed, you will see the following message:
33 |
34 | ```
35 | Success! Created PostCSS [TITLE] at [DIRECTORY]
36 |
37 | We suggest that you begin by typing:
38 | cd [DIRECTORY]
39 | npm test
40 |
41 | Happy PostCSS-ing!
42 | ```
43 |
44 | To skip all prompts, you must at least provide a title and keywords.
45 |
46 | ```sh
47 | create-postcss-plugin --title Stuff --keywords comma,separated,keywords
48 | ```
49 |
50 | If your system cannot access git user information from `.gitconfig`, you must
51 | also provide an author, email, and user.
52 |
53 | ```sh
54 | create-postcss-plugin --title Stuff --author "Cee S Esse" --email "postcss@postcss.org" --user ceesesse --keywords comma,separated,keywords
55 | ```
56 |
57 | ## Options
58 |
59 | You can pass options into postcss-plugin to automate plugin creation.
60 |
61 | ### to
62 |
63 | The `to` argument defines the destination of the new project. The first
64 | undefined argument will also determine this value.
65 |
66 | ```sh
67 | npm init postcss-plugin --to path/to/plugin
68 | ```
69 |
70 | ### title
71 |
72 | The `title` argument defines the formal name of the project.
73 |
74 | ```sh
75 | npm init postcss-plugin --title Stuff
76 | ```
77 |
78 | ```sh
79 | npm init postcss-plugin --title "Awesome Blossom"
80 | ```
81 |
82 | ### id
83 |
84 | The `id` argument defines the id used by the project package.json and
85 | repository.
86 |
87 | ```sh
88 | npm init postcss-plugin --id awes-blos
89 | ```
90 |
91 | ### desc
92 |
93 | The `desc` or `description` argument defines the description used by the
94 | project README.md and package.json.
95 |
96 | ```sh
97 | # becomes "Use exciting new functions" and "Awesome Blossom lets you create new functions in CSS."
98 | npm init postcss-plugin --desc "use exciting new functions"
99 | ```
100 |
101 | ```sh
102 | # becomes "Use exciting new functions" and "Awesome Blossom lets you create new functions in CSS."
103 | npm init postcss-plugin --description "use exciting new functions"
104 | ```
105 |
106 | ### author
107 |
108 | The `author` argument defines the author used by the project package.json.
109 |
110 | ```sh
111 | npm init postcss-plugin --author "Cee S Esse"
112 | ```
113 |
114 | ### email
115 |
116 | The `email` argument defines the email used by the project package.json.
117 |
118 | ```sh
119 | npm init postcss-plugin --email "postcss@postcss.org"
120 | ```
121 |
122 | ### user
123 |
124 | The `user` argument defines the user or organization hosting the project.
125 |
126 | ```sh
127 | npm init postcss-plugin --user "postcss"
128 | ```
129 |
130 | ### keywords
131 |
132 | The `keywords` argument defines the keywords used by the project package.json.
133 |
134 | ```sh
135 | npm init postcss-plugin --keywords "awesome,blossom"
136 | ```
137 |
138 | ### no-install
139 |
140 | The `no-install` argument instructs the project to not automatically install
141 | dependencies.
142 |
143 | ```sh
144 | npm init postcss-plugin --no-install
145 | ```
146 |
147 | [Create PostCSS Plugin]: https://github.com/csstools/create-postcss-plugin
148 | [PostCSS]: https://github.com/postcss/postcss
149 |
150 | [cli-img]: https://img.shields.io/travis/csstools/create-postcss-plugin/master.svg
151 | [cli-url]: https://travis-ci.org/csstools/create-postcss-plugin
152 | [git-img]: https://img.shields.io/badge/support-chat-blue.svg
153 | [git-url]: https://gitter.im/postcss/postcss
154 | [npm-img]: https://img.shields.io/npm/v/create-postcss-plugin.svg
155 | [npm-url]: https://www.npmjs.com/package/create-postcss-plugin
156 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-postcss-plugin",
3 | "version": "3.1.1",
4 | "description": "Quickly create new PostCSS plugins",
5 | "author": "Jonathan Neal ",
6 | "license": "CC0-1.0",
7 | "repository": "csstools/create-postcss-plugin",
8 | "homepage": "https://github.com/csstools/create-postcss-plugin#readme",
9 | "bugs": "https://github.com/csstools/create-postcss-plugin/issues",
10 | "main": "index.js",
11 | "bin": {
12 | "create-postcss-plugin": "./index.js"
13 | },
14 | "files": [
15 | "index.js",
16 | "template"
17 | ],
18 | "scripts": {
19 | "build": "rollup --config --silent",
20 | "posttest:cli": "npm unlink && rm -rf .test",
21 | "prepublishOnly": "npm test && npm run build",
22 | "pretest:cli": "npm run build && npm link",
23 | "test": "npm run test:js && npm run test:cli",
24 | "test:cli": "create-postcss-plugin .test --title test --author test --email test@test.test --user test --keywords comma,separated,keywords",
25 | "test:js": "eslint src/{*,**/*}.js --cache --ignore-path .gitignore --quiet"
26 | },
27 | "engines": {
28 | "node": ">=8.0.0"
29 | },
30 | "devDependencies": {
31 | "@babel/core": "^7.7.2",
32 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
33 | "@babel/preset-env": "^7.7.1",
34 | "babel-eslint": "^10.0.3",
35 | "eslint": "^6.6.0",
36 | "pre-commit": "^1.2.2",
37 | "rollup": "^1.26.3",
38 | "rollup-plugin-babel": "^4.3.3",
39 | "rollup-plugin-terser": "^5.1.2"
40 | },
41 | "eslintConfig": {
42 | "env": {
43 | "browser": true,
44 | "es6": true,
45 | "node": true
46 | },
47 | "extends": "eslint:recommended",
48 | "parser": "babel-eslint",
49 | "parserOptions": {
50 | "ecmaVersion": 2018,
51 | "impliedStrict": true,
52 | "sourceType": "module"
53 | },
54 | "root": true,
55 | "rules": {
56 | "no-console": [
57 | 0,
58 | {
59 | "allow": [
60 | "warn",
61 | "error"
62 | ]
63 | }
64 | ],
65 | "require-atomic-updates": [
66 | 0
67 | ]
68 | }
69 | },
70 | "keywords": [
71 | "postcss",
72 | "css",
73 | "postcss-plugin",
74 | "boilerplate"
75 | ]
76 | }
77 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import { terser } from 'rollup-plugin-terser';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | output: { file: 'index.js', format: 'cjs', strict: false },
7 | plugins: [
8 | babel({
9 | plugins: [
10 | '@babel/syntax-dynamic-import'
11 | ],
12 | presets: [
13 | ['@babel/env', { modules: false, targets: { node: 8 } }]
14 | ]
15 | }),
16 | terser(),
17 | addHashBang()
18 | ]
19 | };
20 |
21 | function addHashBang () {
22 | return {
23 | name: 'add-hash-bang',
24 | renderChunk (code) {
25 | return `#!/usr/bin/env node\n${code}`;
26 | }
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import args from './lib/args';
3 | import color from './lib/color';
4 | import copyTemplateDir from './lib/copy-template-dir';
5 | import { exec, pipeExec } from './lib/exec';
6 | import gitInfo from './lib/git-info';
7 | import question from './lib/question';
8 |
9 | async function main () {
10 | await updateArgs();
11 | await createTemplate();
12 | }
13 |
14 | async function updateArgs () {
15 | if (!args.to) {
16 | args.to = './';
17 | }
18 |
19 | // --date, or formatted date
20 | if (!args.date) {
21 | args.date = new Date(Date.now()).toLocaleDateString('en-US', {
22 | weekday: 'narrow',
23 | year: 'numeric',
24 | month: 'long',
25 | day: 'numeric'
26 | }).slice(3);
27 | }
28 |
29 | // --title, prompt, or fallback
30 | if (!args.title) {
31 | try {
32 | args.title = await question(titlePrompt);
33 | } catch (error) {
34 | args.title = titleFallback
35 | }
36 | }
37 |
38 | args.title = formatTitle(args.title);
39 |
40 | // --id, prompt, or fallback
41 | args.id = formatId(args.id || args.title);
42 | args.idCamelCase = formatCamelCase(args['camel-case-id'] || args.id);
43 |
44 | // --desc or fallback
45 | if (!args.desc) {
46 | args.desc = formatDesc(args.description || descFallback);
47 | }
48 |
49 | args.pkgdesc = formatPkgDesc(args['package-description'] || args.desc);
50 |
51 | // --author, git name, prompt, or fallback
52 | if (!args.author) {
53 | try {
54 | args.author = await gitInfo('name');
55 | } catch (error) {
56 | try {
57 | args.author = await question(authorPrompt);
58 | } catch (error2) {
59 | args.author = authorFallback;
60 | }
61 | }
62 | }
63 |
64 | // --email, git email, prompt, or fallback
65 | if (!args.email) {
66 | try {
67 | args.email = await gitInfo('email');
68 | } catch (error) {
69 | try {
70 | args.email = await question(emailPrompt);
71 | } catch (error2) {
72 | args.email = emailFallback;
73 | }
74 | }
75 | }
76 |
77 | // --user, git user, prompt, or fallback
78 | if (!args.user) {
79 | try {
80 | args.user = await gitInfo('user');
81 | } catch (error) {
82 | try {
83 | args.user = await question(userPrompt);
84 | } catch (error2) {
85 | args.user = userFallback;
86 | }
87 | }
88 | }
89 |
90 | // --keywords, or prompt
91 | if (!args.keywords) {
92 | try {
93 | args.keywords = await question(keywordPrompt);
94 | } catch (error) {
95 | args.keywords = keywordFallback;
96 | }
97 | }
98 |
99 | args.keywords = formatKeywords(args.keywords);
100 |
101 | console.log('');
102 | }
103 |
104 | async function createTemplate () {
105 | const __tpl = path.join(__dirname, 'template');
106 | const __out = path.resolve(process.cwd(), args.to);
107 | const execOpts = { cwd: __out };
108 |
109 | try {
110 | await copyTemplateDir(__tpl, __out, args);
111 |
112 | try {
113 | await exec('git init', execOpts);
114 | await exec(`git remote add origin git@github.com:${args.user}/${args.id}.git`, execOpts);
115 | } catch (error2) {
116 | // do nothing and continue
117 | }
118 |
119 | if (!args['no-install']) {
120 | await pipeExec('npm install', execOpts);
121 | }
122 |
123 | console.log(getSuccessMessage(args));
124 |
125 | process.exit(0);
126 | } catch (error) {
127 | console.error(error);
128 |
129 | process.exit(1);
130 | }
131 | }
132 |
133 | function getSuccessMessage () {
134 | const cd = path.relative(process.cwd(), args.to);
135 | const isCwd = cd === './';
136 |
137 | return `Success! Created ${color.bold(args.title)} at ${color.bold(args.to)}
138 |
139 | We suggest that you begin by typing:${
140 | isCwd
141 | ? ''
142 | : ` cd ${cd}`
143 | }
144 | npm test
145 |
146 | Happy PostCSS-ing!\n`;
147 | }
148 |
149 | const authorFallback = 'PostCSS Community';
150 | const authorPrompt = 'Author Name';
151 | const descFallback = '...';
152 | const emailFallback = 'postcss@postcss.org';
153 | const emailPrompt = 'GitHub Email';
154 | const keywordFallback = '';
155 | const keywordPrompt = 'Keywords';
156 | const titleFallback = 'Example';
157 | const titlePrompt = 'Plugin Name';
158 | const userFallback = 'postcss';
159 | const userPrompt = 'GitHub User';
160 |
161 | const formatTitle = string => string
162 | .trim()
163 | .replace(/^(postcss\s+)?/i, 'PostCSS '); // force normalized "PostCSS" prefix
164 | const formatDesc = string => string.trim();
165 | const formatPkgDesc = string => string.replace(/^[a-z]/, match => match.toUpperCase());
166 | const formatId = string => string
167 | .trim()
168 | .replace(/[^\w]+/g, '-') // replace non-words (special characters, spaces) with a dash
169 | .replace(/^-+|-+$/g, '') // remove trailing dashes
170 | .toLowerCase();
171 | const formatCamelCase = string => string
172 | .replace(/-[a-z]/g, match => match.slice(1).toUpperCase()); // replace "-\w" with uppercase
173 | const formatKeywords = string => ['postcss', 'css', 'postcss-plugin']
174 | .concat(
175 | string
176 | .trim()
177 | .split(/\s*,\s*/)
178 | )
179 | .join('",\n "');
180 |
181 | main();
182 |
--------------------------------------------------------------------------------
/src/lib/args.js:
--------------------------------------------------------------------------------
1 | // match double-dash arguments
2 | const dash = /^--([^\s]+)$/;
3 |
4 | // export object from double-dash arguments
5 | export default process.argv.slice(2).reduce(
6 | (object, arg, i, args) => {
7 | if (dash.test(arg)) {
8 | object[arg.replace(dash, '$1')] = (i + 1) in args ? args[i + 1] : true;
9 | } else if (!dash.test(args[i - 1]) && !object.to) {
10 | object.to = arg;
11 | }
12 |
13 | return object;
14 | },
15 | {}
16 | );
17 |
--------------------------------------------------------------------------------
/src/lib/color.js:
--------------------------------------------------------------------------------
1 | // color strings
2 | const colors = {
3 | reset: '\x1b[0m',
4 | bold: '\x1b[1m',
5 | dim: '\x1b[2m',
6 | underline: '\x1b[4m',
7 | blink: '\x1b[5m',
8 | reverse: '\x1b[7m',
9 | hidden: '\x1b[8m',
10 | black: '\x1b[30m',
11 | red: '\x1b[31m',
12 | green: '\x1b[32m',
13 | yellow: '\x1b[33m',
14 | blue: '\x1b[34m',
15 | magenta: '\x1b[35m',
16 | cyan: '\x1b[36m',
17 | white: '\x1b[37m',
18 | bgBlack: '\x1b[40m',
19 | bgRed: '\x1b[41m',
20 | bgGreen: '\x1b[42m',
21 | bgYellow: '\x1b[43m',
22 | bgBlue: '\x1b[44m',
23 | bgMagenta: '\x1b[45m',
24 | bgCyan: '\x1b[46m',
25 | bgWhite: '\x1b[47m'
26 | };
27 |
28 | const reset = colors.reset;
29 |
30 | // method-ified color wrappers
31 | export default Object.keys(colors).reduce(
32 | (fns, name) => {
33 | const color = colors[name];
34 |
35 | fns[name] = string => color + string.replace(reset, reset + color) + reset;
36 |
37 | return fns;
38 | },
39 | {}
40 | );
41 |
--------------------------------------------------------------------------------
/src/lib/copy-template-dir.js:
--------------------------------------------------------------------------------
1 | import fs from './fs';
2 | import path from 'path';
3 |
4 | export default function copyTemplateDir (__tpl, __out, answers) {
5 | return fs.readdir(__tpl).then(
6 | pathnames => Promise.all(pathnames.map(
7 | pathname => fs.lstat(
8 | path.resolve(__tpl, pathname)
9 | ).then(
10 | stat => stat.isFile()
11 | ? fs.readFile(path.join(__tpl, pathname), 'utf8').then(
12 | content => content.replace(
13 | keys,
14 | (match, key) => key in answers ? answers[key] : match
15 | ).replace(patchPkg, '')
16 | ).then(
17 | content => fs.writeFile(path.join(__out, patchNpmIgnore(pathname)), content)
18 | )
19 | : fs.mkdir(path.resolve(__out, pathname)).then(
20 | () => copyTemplateDir(
21 | path.resolve(__tpl, pathname),
22 | path.resolve(__out, pathname),
23 | answers
24 | )
25 | )
26 | )
27 | ))
28 | );
29 | }
30 |
31 | function patchNpmIgnore (pathname) {
32 | return pathname === '.npmignore' ? '.gitignore' : pathname;
33 | }
34 |
35 | const keys = /\$\{([^}]+)\}/g;
36 | const patchPkg = ',\n ".*",\n "*"';
37 |
--------------------------------------------------------------------------------
/src/lib/exec.js:
--------------------------------------------------------------------------------
1 | import { exec as callbackExec, execSync } from 'child_process';
2 |
3 | export function exec (cmd, opts) {
4 | return new Promise((resolve, reject) => {
5 | callbackExec(cmd, Object(opts), (error, stdout, stderr) => {
6 | if (error) {
7 | reject(stderr)
8 | } else {
9 | resolve(stdout)
10 | }
11 | })
12 | });
13 | }
14 |
15 | export function pipeExec (cmd, opts) {
16 | return new Promise((resolve, reject) => {
17 | try {
18 | const execSyncOpts = Object.assign({ stdio: ['pipe', 'pipe', process.stderr] }, opts);
19 |
20 | const result = execSync(cmd, execSyncOpts);
21 |
22 | resolve(result);
23 | } catch (error) {
24 | reject(error);
25 | }
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/src/lib/fs.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 |
4 | const fse = {};
5 | [
6 | 'access',
7 | 'appendFile',
8 | 'chmod',
9 | 'chown',
10 | 'close',
11 | 'exists',
12 | 'fchmod',
13 | 'fchown',
14 | 'fdatasync',
15 | 'fstat',
16 | 'fsync',
17 | 'ftruncate',
18 | 'futimes',
19 | 'lchmod',
20 | 'lchown',
21 | 'link',
22 | 'lstat',
23 | 'mkdtemp',
24 | 'open',
25 | 'read',
26 | 'readdir',
27 | 'readFile',
28 | 'readlink',
29 | 'realpath',
30 | 'rename',
31 | 'rmdir',
32 | 'stat',
33 | 'symlink',
34 | 'truncate',
35 | 'unlink',
36 | 'utimes',
37 | 'write',
38 | 'write'
39 | ].forEach(name => {
40 | fse[name] = (pathname, ...args) => new Promise(
41 | (resolve, reject) => fs[name](pathname, ...args,
42 | (err, ...res) => {
43 | if (err) {
44 | reject(err);
45 | } else {
46 | resolve(...res);
47 | }
48 | }
49 | )
50 | )
51 |
52 | return fse;
53 | });
54 |
55 | // mkdir + writeFile
56 | ['mkdir', 'writeFile'].forEach(name => {
57 | fse[name] = (pathname, ...args) => new Promise(
58 | (resolve, reject) => {
59 | // execute the native method
60 | fs[name](
61 | pathname,
62 | ...args,
63 | (err) => {
64 | // if there is no parent directory
65 | if (err && err.code === 'ENOENT') {
66 | // promise to make the parent directory
67 | fse.mkdir(path.dirname(pathname)).then(
68 | // and then try again
69 | () => fse[name](pathname, ...args)
70 | ).then(resolve);
71 | } else if (err && err.code !== 'EEXIST') {
72 | // otherwise, reject any error not about an existing directory
73 | reject(err);
74 | } else {
75 | // otherwise, resolve
76 | resolve();
77 | }
78 | }
79 | );
80 | }
81 | );
82 | });
83 |
84 | // mkdirSync, writeFileSync
85 | ['mkdirSync', 'writeFileSync'].forEach(name => {
86 | fse[name] = (pathname, ...args) => {
87 | try {
88 | // try to execute the native method
89 | fs[name](pathname, ...args);
90 | } catch (err) {
91 | // if there is no parent directory
92 | if (err && err.code === 'ENOENT') {
93 | // make the parent directory
94 | fse.mkdirSync(path.dirname(pathname));
95 |
96 | // and then try again
97 | fse[name](pathname, ...args)
98 | } else if (err && err.code !== 'EEXIST') {
99 | // otherwise, throw any error not about an existing directory
100 | throw err;
101 | }
102 | }
103 | };
104 | });
105 |
106 | // copydir
107 | fse.copydir = (source, target) => fse.mkdir(target).then(
108 | // make the target directory, then promise to read the source directory
109 | () => fse.readdir(source)
110 | ).then(
111 | // promise to copy the contents of the source directory
112 | children => Promise.all(
113 | children.map(
114 | child => {
115 | const sourceChild = path.resolve(source, child);
116 | const targetChild = path.resolve(target, child);
117 |
118 | // promise the appropriate copy of the child
119 | return fse.lstat(sourceChild).then(
120 | lstat => lstat.isDirectory()
121 | ? fse.copydir(sourceChild, targetChild)
122 | : fse.copyFile(sourceChild, targetChild)
123 | );
124 | }
125 | )
126 | ).then(
127 | () => Promise.resolve()
128 | )
129 | );
130 |
131 | // copydirSync
132 | fse.copydirSync = (source, target) => {
133 | // make the target directory
134 | fse.mkdirSync(target);
135 |
136 | // read the source directory
137 | const children = fse.readdirSync(source)
138 |
139 | // copy the contents of the source directory
140 | children.forEach(
141 | child => {
142 | const sourceChild = path.resolve(source, child);
143 | const targetChild = path.resolve(target, child);
144 |
145 | const lstat = fse.lstatSync(sourceChild);
146 |
147 | // execute the appropriate copy of the child
148 | if (lstat.isDirectory()) {
149 | fse.copydirSync(sourceChild, targetChild);
150 | } else {
151 | fse.copyFileSync(sourceChild, targetChild);
152 | }
153 | }
154 | );
155 | };
156 |
157 | // copyFile
158 | fse.copyFile = (source, target) => fse.touchFile(target).then(
159 | // make the target directory, then promise to copy the file
160 | () => new Promise(
161 | (resolve, reject) => {
162 | // create streams
163 | const readStream = fse.createReadStream(path.resolve(source));
164 | const writeStream = fse.createWriteStream(path.resolve(target));
165 |
166 | // reject on read error
167 | readStream.on('error', prereject);
168 |
169 | // reject on write error
170 | writeStream.on('error', prereject);
171 |
172 | // resolve on finish
173 | writeStream.on('finish', resolve);
174 |
175 | // copy stream
176 | readStream.pipe(writeStream);
177 |
178 | function prereject (err) {
179 | // destroy streams
180 | readStream.destroy();
181 | writeStream.end();
182 |
183 | // reject with error
184 | reject(err);
185 | }
186 | }
187 | )
188 | );
189 |
190 | // copyFileSync
191 | fse.copyFileSync = (source, target) => {
192 | // make the target directory
193 | fse.mkdirSync(path.dirname(target));
194 |
195 | // buffer
196 | const bufferLength = 64 * 1024;
197 | const buffer = Buffer.alloc(bufferLength);
198 |
199 | // position
200 | let bytesRead = 1;
201 | let position = 0;
202 |
203 | // open the reader and writer
204 | const reader = fs.openSync(source, 'r');
205 | const writer = fs.openSync(target, 'w');
206 |
207 | // copy the file
208 | while (bytesRead > 0) {
209 | bytesRead = fs.readSync(reader, buffer, 0, bufferLength, position);
210 |
211 | fs.writeSync(writer, buffer, 0, bytesRead);
212 |
213 | position += bytesRead;
214 | }
215 |
216 | // close the reader and writer
217 | fs.closeSync(reader);
218 | fs.closeSync(writer);
219 | };
220 |
221 | // readJson
222 | fse.readJson = filename => fse.readFile(filename, 'utf8').then(JSON.parse);
223 |
224 | // readJsonSync
225 | fse.readJsonSync = filename => JSON.parse(fse.readFileSync(filename, 'utf8'));
226 |
227 | // rmdir
228 | fse.rmdir = dirname => new Promise(
229 | (resolve, reject) => {
230 | // remove the directory
231 | fs.rmdir(dirname,
232 | err => {
233 | // if there is an error about the directory not being empty
234 | if (err && err.code === 'ENOTEMPTY') {
235 | // resolve to read the directory
236 | resolve(fse.readdir(dirname).then(
237 | // promise to remove each child of the directory
238 | children => Promise.all(
239 | children.map(
240 | child => {
241 | const resolvedChild = path.resolve(dirname, child);
242 |
243 | // promise to remove of the child
244 | return fse.lstat(resolvedChild).then(
245 | lstat => lstat.isDirectory()
246 | ? fse.rmdir(resolvedChild)
247 | : fse.unlink(resolvedChild)
248 | );
249 | }
250 | )
251 | )
252 | ).then(
253 | // and then try again
254 | () => fse.rmdir(dirname)
255 | ));
256 | } else if (err) {
257 | // otherwise, reject any error
258 | reject(err);
259 | } else {
260 | // otherwise, resolve
261 | resolve();
262 | }
263 | }
264 | );
265 | }
266 | );
267 |
268 | // rmdirSync
269 | fse.rmdirSync = dirname => {
270 | try {
271 | // try to remove the directory
272 | fs.rmdirSync(dirname);
273 | } catch (err) {
274 | // if there is an error about the directory not being empty
275 | if (err && err.code === 'ENOTEMPTY') {
276 | const children = fse.readdirSync(dirname);
277 |
278 | // remove each child of the directory
279 | children.forEach(
280 | child => {
281 | const resolvedChild = path.resolve(dirname, child);
282 |
283 | const lstat = fse.lstatSync(resolvedChild);
284 |
285 | // remove the child
286 | if (lstat.isDirectory()) {
287 | fse.rmdirSync(resolvedChild);
288 | } else {
289 | fse.unlinkSync(resolvedChild);
290 | }
291 | }
292 | );
293 |
294 | // and then try again
295 | fse.rmdirSync(dirname);
296 | } else {
297 | // otherwise, throw any error
298 | throw err;
299 | }
300 | }
301 | };
302 |
303 | // touchFile
304 | fse.touchFile = filename => new Promise(
305 | (resolve, reject) => {
306 | // touch the file
307 | fs.open(filename, 'wx',
308 | (err) => {
309 | // if there is no parent directory
310 | if (err && err.code === 'ENOENT') {
311 | // promise to make the parent directory
312 | fse.mkdir(path.dirname(filename)).then(
313 | // and then try again
314 | () => fse.touchFile(filename)
315 | ).then(resolve);
316 | } else if (err && err.code !== 'EEXIST') {
317 | // otherwise, reject any error not about the directory already existing
318 | reject(err);
319 | } else {
320 | // otherwise, resolve
321 | resolve();
322 | }
323 | }
324 | );
325 | }
326 | );
327 |
328 | // touchFileSync
329 | fse.touchFileSync = filename => {
330 | try {
331 | // try to touch the file
332 | fs.openSync(filename, 'wx');
333 | } catch (err) {
334 | // if there is no parent directory
335 | if (err && err.code === 'ENOENT') {
336 | // make the parent directory
337 | fse.mkdirSync(path.dirname(filename));
338 |
339 | // and then try again
340 | fse.touchFileSync(filename);
341 | } else if (err && err.code !== 'EEXIST') {
342 | // otherwise, throw any error not about the directory already existing
343 | throw err;
344 | }
345 | }
346 | };
347 |
348 | export default fse;
349 |
--------------------------------------------------------------------------------
/src/lib/git-config.js:
--------------------------------------------------------------------------------
1 | // tooling
2 | import fs from './fs';
3 | import os from 'os';
4 | import path from 'path';
5 |
6 | // parsing
7 | const iniLine = /\s*\n+\s*/;
8 | const iniHeading = /^\[\s*(.*)\s*\]$/;
9 | const iniValue = /^\s*([^\s]*)\s*=\s*(.*)\s*$/;
10 |
11 | // caching
12 | const iniObject = {};
13 |
14 | let iniCurrent = iniObject;
15 |
16 | // read ~/.gitconfig
17 | export default fs.readFile(path.resolve(os.homedir(), '.gitconfig'), 'utf8').then(
18 | // parse gitconfig as object
19 | content => content.split(iniLine).map(
20 | item => {
21 | if (iniHeading.test(item)) {
22 | // create subgroup
23 | iniCurrent = iniObject[item.replace(iniHeading, '$1')] = {};
24 | } else {
25 | // assign to subgroup
26 | iniCurrent[item.replace(iniValue, '$1')] = item.replace(iniValue, '$2');
27 | }
28 | }
29 | )
30 | ).then(
31 | () => {
32 | if ('user' in iniObject) {
33 | // return user group
34 | return iniObject.user;
35 | } else {
36 | throw 'No user object in .gitconfig';
37 | }
38 | }
39 | );
40 |
--------------------------------------------------------------------------------
/src/lib/git-info.js:
--------------------------------------------------------------------------------
1 | import githubUser from './github-user';
2 | import gitConfig from './git-config';
3 |
4 | // caching
5 | let gituser;
6 |
7 | // return parsed git info
8 | export default key => gitConfig.then(
9 | // ~/.gitconfig user
10 | async user => {
11 | if (key in user) {
12 | // user key
13 | return user[key];
14 | } else if (key === 'user') {
15 | // github api email
16 | gituser = await (gituser || githubUser(user.email));
17 |
18 | return gituser;
19 | } else {
20 | throw `No ${key} in .gitconfig`
21 | }
22 | }
23 | );
24 |
--------------------------------------------------------------------------------
/src/lib/github-user.js:
--------------------------------------------------------------------------------
1 | // tooling
2 | import https from 'https';
3 |
4 | // return first matching user login from github api
5 | export default email => new Promise(
6 | (resolve, reject) => {
7 | // search for users with the specified email address
8 | https.get({
9 | hostname: 'api.github.com',
10 | path: `/search/users?q=${email}+in:email`,
11 | headers: {
12 | 'Accept': 'application/vnd.github.v3+json',
13 | 'user-agent': 'https://github.com/sindresorhus/gh-got'
14 | }
15 | }, response => {
16 | // initialized response
17 | let body = '';
18 |
19 | if (response.statusCode !== 200) {
20 | reject(response.statusCode);
21 | }
22 |
23 | // during response
24 | response.on('data', chunk => body += chunk);
25 |
26 | // after response
27 | response.on('end', () => {
28 | // parse response
29 | const results = JSON.parse(body);
30 |
31 | if (results.items && results.items.length) {
32 | // resolve login if available
33 | resolve(results.items[0].login);
34 | } else {
35 | // reject with notice
36 | reject('No User Found');
37 | }
38 | });
39 | });
40 | }
41 | );
42 |
--------------------------------------------------------------------------------
/src/lib/question.js:
--------------------------------------------------------------------------------
1 | // tooling
2 | import color from './color';
3 | import readline from 'readline';
4 |
5 | // then-ified readline prompt
6 | export default prompt => new Promise(
7 | (resolve, reject) => {
8 | const readlineInterface = readline.createInterface({
9 | input: process.stdin,
10 | output: process.stdout
11 | });
12 |
13 | readlineInterface.question(
14 | color.dim(`${prompt}: `),
15 | answer => {
16 | readlineInterface.close();
17 |
18 | if (answer) {
19 | resolve(answer);
20 | } else {
21 | reject();
22 | }
23 | }
24 | );
25 | }
26 | );
27 |
--------------------------------------------------------------------------------
/template/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = tab
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
13 | [*.{json,md,yml}]
14 | indent_size = 2
15 | indent_style = space
16 |
--------------------------------------------------------------------------------
/template/.gitignore:
--------------------------------------------------------------------------------
1 | .*
2 | !.editorconfig
3 | !.gitignore
4 | !.rollup.js
5 | !.tape.js
6 | !.travis.yml
7 | *.log*
8 | *.result.css
9 | /index.*
10 | node_modules
11 | package-lock.json
12 |
--------------------------------------------------------------------------------
/template/.rollup.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 |
3 | export default {
4 | input: 'src/index.js',
5 | output: [
6 | { file: 'index.js', format: 'cjs', sourcemap: true, strict: false },
7 | { file: 'index.mjs', format: 'esm', sourcemap: true, strict: false }
8 | ],
9 | plugins: [
10 | babel({
11 | presets: [
12 | ['@babel/env', { modules: false, targets: { node: 8 } }]
13 | ]
14 | })
15 | ]
16 | };
17 |
--------------------------------------------------------------------------------
/template/.tape.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'basic': {
3 | message: 'supports basic usage'
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/template/.travis.yml:
--------------------------------------------------------------------------------
1 | # https://docs.travis-ci.com/user/travis-lint
2 |
3 | language: node_js
4 |
5 | node_js:
6 | - 8
7 |
8 | install:
9 | - npm install --ignore-scripts
10 |
--------------------------------------------------------------------------------
/template/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changes to ${title}
2 |
3 | ### 1.0.0 (${date})
4 |
5 | - Initial version
6 |
--------------------------------------------------------------------------------
/template/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to ${title}
2 |
3 | You want to help? You rock! Now, take a moment to be sure your contributions
4 | make sense to everyone else.
5 |
6 | ## Reporting Issues
7 |
8 | Found a problem? Want a new feature?
9 |
10 | - See if your issue or idea has [already been reported].
11 | - Provide a [reduced test case] or a [live example].
12 |
13 | Remember, a bug is a _demonstrable problem_ caused by _our_ code.
14 |
15 | ## Submitting Pull Requests
16 |
17 | Pull requests are the greatest contributions, so be sure they are focused in
18 | scope and avoid unrelated commits.
19 |
20 | 1. To begin; [fork this project], clone your fork, and add our upstream.
21 | ```bash
22 | # Clone your fork of the repo into the current directory
23 | git clone git@github.com:YOUR_USER/${id}.git
24 |
25 | # Navigate to the newly cloned directory
26 | cd ${id}
27 |
28 | # Assign the original repo to a remote called "upstream"
29 | git remote add upstream git@github.com:csstools/${id}.git
30 |
31 | # Install the tools necessary for testing
32 | npm install
33 | ```
34 |
35 | 2. Create a branch for your feature or fix:
36 | ```bash
37 | # Move into a new branch for your feature
38 | git checkout -b feature/thing
39 | ```
40 | ```bash
41 | # Move into a new branch for your fix
42 | git checkout -b fix/something
43 | ```
44 |
45 | 3. If your code follows our practices, then push your feature branch:
46 | ```bash
47 | # Test current code
48 | npm test
49 | ```
50 | ```bash
51 | # Push the branch for your new feature
52 | git push origin feature/thing
53 | ```
54 | ```bash
55 | # Or, push the branch for your update
56 | git push origin update/something
57 | ```
58 |
59 | That’s it! Now [open a pull request] with a clear title and description.
60 |
61 | [already been reported]: issues
62 | [fork this project]: fork
63 | [live example]: https://codepen.io/pen
64 | [open a pull request]: https://help.github.com/articles/using-pull-requests/
65 | [reduced test case]: https://css-tricks.com/reduced-test-cases/
66 |
--------------------------------------------------------------------------------
/template/INSTALL.md:
--------------------------------------------------------------------------------
1 | # Installing ${title}
2 |
3 | [${title}] runs in all Node environments, with special instructions for:
4 |
5 | | [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) |
6 | | --- | --- | --- | --- | --- | --- |
7 |
8 | ## Node
9 |
10 | Add [${title}] to your project:
11 |
12 | ```bash
13 | npm install ${id} --save-dev
14 | ```
15 |
16 | Use **${title}** to process your CSS:
17 |
18 | ```js
19 | const ${idCamelCase} = require('${id}');
20 |
21 | ${idCamelCase}.process(YOUR_CSS /*, processOptions, pluginOptions */);
22 | ```
23 |
24 | Or use it as a [PostCSS] plugin:
25 |
26 | ```js
27 | const postcss = require('postcss');
28 | const ${idCamelCase} = require('${id}');
29 |
30 | postcss([
31 | ${idCamelCase}(/* pluginOptions */)
32 | ]).process(YOUR_CSS /*, processOptions */);
33 | ```
34 |
35 | ## PostCSS CLI
36 |
37 | Add [PostCSS CLI] to your project:
38 |
39 | ```bash
40 | npm install postcss-cli --save-dev
41 | ```
42 |
43 | Use **${title}** in your `postcss.config.js` configuration file:
44 |
45 | ```js
46 | const ${idCamelCase} = require('${id}');
47 |
48 | module.exports = {
49 | plugins: [
50 | ${idCamelCase}(/* pluginOptions */)
51 | ]
52 | }
53 | ```
54 |
55 | ## Webpack
56 |
57 | Add [PostCSS Loader] to your project:
58 |
59 | ```bash
60 | npm install postcss-loader --save-dev
61 | ```
62 |
63 | Use **${title}** in your Webpack configuration:
64 |
65 | ```js
66 | const ${idCamelCase} = require('${id}');
67 |
68 | module.exports = {
69 | module: {
70 | rules: [
71 | {
72 | test: /\.css$/,
73 | use: [
74 | 'style-loader',
75 | { loader: 'css-loader', options: { importLoaders: 1 } },
76 | { loader: 'postcss-loader', options: {
77 | ident: 'postcss',
78 | plugins: () => [
79 | ${idCamelCase}(/* pluginOptions */)
80 | ]
81 | } }
82 | ]
83 | }
84 | ]
85 | }
86 | }
87 | ```
88 |
89 | ## Create React App
90 |
91 | Add [React App Rewired] and [React App Rewire PostCSS] to your project:
92 |
93 | ```bash
94 | npm install react-app-rewired react-app-rewire-postcss --save-dev
95 | ```
96 |
97 | Use **React App Rewire PostCSS** and **${title}** in your
98 | `config-overrides.js` file:
99 |
100 | ```js
101 | const reactAppRewirePostcss = require('react-app-rewire-postcss');
102 | const ${idCamelCase} = require('${id}');
103 |
104 | module.exports = config => reactAppRewirePostcss(config, {
105 | plugins: () => [
106 | ${idCamelCase}(/* pluginOptions */)
107 | ]
108 | });
109 | ```
110 |
111 | ## Gulp
112 |
113 | Add [Gulp PostCSS] to your project:
114 |
115 | ```bash
116 | npm install gulp-postcss --save-dev
117 | ```
118 |
119 | Use **${title}** in your Gulpfile:
120 |
121 | ```js
122 | const postcss = require('gulp-postcss');
123 | const ${idCamelCase} = require('${id}');
124 |
125 | gulp.task('css', () => gulp.src('./src/*.css').pipe(
126 | postcss([
127 | ${idCamelCase}(/* pluginOptions */)
128 | ])
129 | ).pipe(
130 | gulp.dest('.')
131 | ));
132 | ```
133 |
134 | ## Grunt
135 |
136 | Add [Grunt PostCSS] to your project:
137 |
138 | ```bash
139 | npm install grunt-postcss --save-dev
140 | ```
141 |
142 | Use **${title}** in your Gruntfile:
143 |
144 | ```js
145 | const ${idCamelCase} = require('${id}');
146 |
147 | grunt.loadNpmTasks('grunt-postcss');
148 |
149 | grunt.initConfig({
150 | postcss: {
151 | options: {
152 | use: [
153 | ${idCamelCase}(/* pluginOptions */)
154 | ]
155 | },
156 | dist: {
157 | src: '*.css'
158 | }
159 | }
160 | });
161 | ```
162 |
163 | [Gulp PostCSS]: https://github.com/postcss/gulp-postcss
164 | [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
165 | [PostCSS]: https://github.com/postcss/postcss
166 | [PostCSS CLI]: https://github.com/postcss/postcss-cli
167 | [PostCSS Loader]: https://github.com/postcss/postcss-loader
168 | [${title}]: https://github.com/${user}/${id}
169 | [React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss
170 | [React App Rewired]: https://github.com/timarney/react-app-rewired
171 |
--------------------------------------------------------------------------------
/template/LICENSE.md:
--------------------------------------------------------------------------------
1 | # CC0 1.0 Universal
2 |
3 | ## Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an “owner”) of an original work of
8 | authorship and/or a database (each, a “Work”).
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific works
12 | (“Commons”) that the public can reliably and without fear of later claims of
13 | infringement build upon, modify, incorporate in other works, reuse and
14 | redistribute as freely as possible in any form whatsoever and for any purposes,
15 | including without limitation commercial purposes. These owners may contribute
16 | to the Commons to promote the ideal of a free culture and the further
17 | production of creative, cultural and scientific works, or to gain reputation or
18 | greater distribution for their Work in part through the use and efforts of
19 | others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation of
22 | additional consideration or compensation, the person associating CC0 with a
23 | Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
25 | publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights (“Copyright and
31 | Related Rights”). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 | 1. the right to reproduce, adapt, distribute, perform, display, communicate,
34 | and translate a Work;
35 | 2. moral rights retained by the original author(s) and/or performer(s);
36 | 3. publicity and privacy rights pertaining to a person’s image or likeness
37 | depicted in a Work;
38 | 4. rights protecting against unfair competition in regards to a Work,
39 | subject to the limitations in paragraph 4(i), below;
40 | 5. rights protecting the extraction, dissemination, use and reuse of data in
41 | a Work;
42 | 6. database rights (such as those arising under Directive 96/9/EC of the
43 | European Parliament and of the Council of 11 March 1996 on the legal
44 | protection of databases, and under any national implementation thereof,
45 | including any amended or successor version of such directive); and
46 | 7. other similar, equivalent or corresponding rights throughout the world
47 | based on applicable law or treaty, and any national implementations
48 | thereof.
49 |
50 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
51 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
52 | unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
53 | and Related Rights and associated claims and causes of action, whether now
54 | known or unknown (including existing as well as future claims and causes of
55 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
56 | duration provided by applicable law or treaty (including future time
57 | extensions), (iii) in any current or future medium and for any number of
58 | copies, and (iv) for any purpose whatsoever, including without limitation
59 | commercial, advertising or promotional purposes (the “Waiver”). Affirmer
60 | makes the Waiver for the benefit of each member of the public at large and
61 | to the detriment of Affirmer’s heirs and successors, fully intending that
62 | such Waiver shall not be subject to revocation, rescission, cancellation,
63 | termination, or any other legal or equitable action to disrupt the quiet
64 | enjoyment of the Work by the public as contemplated by Affirmer’s express
65 | Statement of Purpose.
66 |
67 | 3. Public License Fallback. Should any part of the Waiver for any reason be
68 | judged legally invalid or ineffective under applicable law, then the Waiver
69 | shall be preserved to the maximum extent permitted taking into account
70 | Affirmer’s express Statement of Purpose. In addition, to the extent the
71 | Waiver is so judged Affirmer hereby grants to each affected person a
72 | royalty-free, non transferable, non sublicensable, non exclusive,
73 | irrevocable and unconditional license to exercise Affirmer’s Copyright and
74 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
75 | maximum duration provided by applicable law or treaty (including future time
76 | extensions), (iii) in any current or future medium and for any number of
77 | copies, and (iv) for any purpose whatsoever, including without limitation
78 | commercial, advertising or promotional purposes (the “License”). The License
79 | shall be deemed effective as of the date CC0 was applied by Affirmer to the
80 | Work. Should any part of the License for any reason be judged legally
81 | invalid or ineffective under applicable law, such partial invalidity or
82 | ineffectiveness shall not invalidate the remainder of the License, and in
83 | such case Affirmer hereby affirms that he or she will not (i) exercise any
84 | of his or her remaining Copyright and Related Rights in the Work or (ii)
85 | assert any associated claims and causes of action with respect to the Work,
86 | in either case contrary to Affirmer’s express Statement of Purpose.
87 |
88 | 4. Limitations and Disclaimers.
89 | 1. No trademark or patent rights held by Affirmer are waived, abandoned,
90 | surrendered, licensed or otherwise affected by this document.
91 | 2. Affirmer offers the Work as-is and makes no representations or warranties
92 | of any kind concerning the Work, express, implied, statutory or
93 | otherwise, including without limitation warranties of title,
94 | merchantability, fitness for a particular purpose, non infringement, or
95 | the absence of latent or other defects, accuracy, or the present or
96 | absence of errors, whether or not discoverable, all to the greatest
97 | extent permissible under applicable law.
98 | 3. Affirmer disclaims responsibility for clearing rights of other persons
99 | that may apply to the Work or any use thereof, including without
100 | limitation any person’s Copyright and Related Rights in the Work.
101 | Further, Affirmer disclaims responsibility for obtaining any necessary
102 | consents, permissions or other rights required for any use of the Work.
103 | 4. Affirmer understands and acknowledges that Creative Commons is not a
104 | party to this document and has no duty or obligation with respect to this
105 | CC0 or use of the Work.
106 |
107 | For more information, please see
108 | http://creativecommons.org/publicdomain/zero/1.0/.
109 |
--------------------------------------------------------------------------------
/template/README.md:
--------------------------------------------------------------------------------
1 | # ${title} [
][postcss]
2 |
3 | [![NPM Version][npm-img]][npm-url]
4 | [![Build Status][cli-img]][cli-url]
5 | [![Support Chat][git-img]][git-url]
6 |
7 | [${title}] lets you ${desc} in CSS.
8 |
9 | ```pcss
10 | .example { ... }
11 |
12 | /* becomes */
13 |
14 | .example { ... }
15 | ```
16 |
17 | ## Usage
18 |
19 | Add [${title}] to your project:
20 |
21 | ```bash
22 | npm install ${id} --save-dev
23 | ```
24 |
25 | Use **${title}** to process your CSS:
26 |
27 | ```js
28 | const ${idCamelCase} = require('${id}');
29 |
30 | ${idCamelCase}.process(YOUR_CSS /*, processOptions, pluginOptions */);
31 | ```
32 |
33 | Or use it as a [PostCSS] plugin:
34 |
35 | ```js
36 | const postcss = require('postcss');
37 | const ${idCamelCase} = require('${id}');
38 |
39 | postcss([
40 | ${idCamelCase}(/* pluginOptions */)
41 | ]).process(YOUR_CSS /*, processOptions */);
42 | ```
43 |
44 | **${title}** runs in all Node environments, with special instructions for:
45 |
46 | | [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) |
47 | | --- | --- | --- | --- | --- | --- |
48 |
49 | ## Options
50 |
51 | ...
52 |
53 | [cli-img]: https://img.shields.io/travis/${user}/${id}/master.svg
54 | [cli-url]: https://travis-ci.org/${user}/${id}
55 | [git-img]: https://img.shields.io/badge/support-chat-blue.svg
56 | [git-url]: https://gitter.im/postcss/postcss
57 | [npm-img]: https://img.shields.io/npm/v/${id}.svg
58 | [npm-url]: https://www.npmjs.com/package/${id}
59 |
60 | [PostCSS]: https://github.com/postcss/postcss
61 | [${title}]: https://github.com/${user}/${id}
62 |
--------------------------------------------------------------------------------
/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "${id}",
3 | "version": "1.0.0",
4 | "description": "${pkgdesc} in CSS",
5 | "author": "${author} <${email}>",
6 | "license": "CC0-1.0",
7 | "repository": "${user}/${id}",
8 | "homepage": "https://github.com/${user}/${id}#readme",
9 | "bugs": "https://github.com/${user}/${id}/issues",
10 | "main": "index.js",
11 | "module": "index.mjs",
12 | "files": [
13 | "index.js",
14 | "index.js.map",
15 | "index.mjs",
16 | "index.mjs.map",
17 | ".*",
18 | "*"
19 | ],
20 | "scripts": {
21 | "build": "rollup --config .rollup.js --silent",
22 | "prepublishOnly": "npm test",
23 | "pretest:tape": "npm run build",
24 | "test": "npm run test:js && npm run test:tape",
25 | "test:js": "eslint src/{*,**/*}.js --cache --ignore-path .gitignore --quiet",
26 | "test:tape": "postcss-tape"
27 | },
28 | "engines": {
29 | "node": ">=8.0.0"
30 | },
31 | "dependencies": {
32 | "postcss": "^7.0.21"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "^7.7.2",
36 | "@babel/preset-env": "^7.7.1",
37 | "babel-eslint": "^10.0.3",
38 | "eslint": "^6.6.0",
39 | "postcss-tape": "^5.0.2",
40 | "pre-commit": "^1.2.2",
41 | "rollup": "^1.26.3",
42 | "rollup-plugin-babel": "^4.3.3"
43 | },
44 | "eslintConfig": {
45 | "env": {
46 | "browser": true,
47 | "es6": true,
48 | "node": true
49 | },
50 | "extends": "eslint:recommended",
51 | "parser": "babel-eslint",
52 | "parserOptions": {
53 | "ecmaVersion": 2018,
54 | "impliedStrict": true,
55 | "sourceType": "module"
56 | },
57 | "root": true
58 | },
59 | "keywords": [
60 | "${keywords}"
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/template/src/index.js:
--------------------------------------------------------------------------------
1 | import postcss from 'postcss';
2 |
3 | export default postcss.plugin('${id}', opts => {
4 | console.log({ opts }); // eslint-disable-line no-console
5 |
6 | return (root, result) => {
7 | console.log({ root, result }); // eslint-disable-line no-console
8 | };
9 | });
10 |
--------------------------------------------------------------------------------
/template/test/basic.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csstools/create-postcss-plugin/3a6dfa388e14ddfb5a36277bcfab83b23d4dbac6/template/test/basic.css
--------------------------------------------------------------------------------
/template/test/basic.expect.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/csstools/create-postcss-plugin/3a6dfa388e14ddfb5a36277bcfab83b23d4dbac6/template/test/basic.expect.css
--------------------------------------------------------------------------------