Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/cdaringe/counsel
the end of boilerplate. automatically bake structure, opinions, and biz rules into projects.
https://github.com/cdaringe/counsel
automation management nodejs opinions project rule
Last synced: 2 days ago
JSON representation
the end of boilerplate. automatically bake structure, opinions, and biz rules into projects.
- Host: GitHub
- URL: https://github.com/cdaringe/counsel
- Owner: cdaringe
- Created: 2016-09-28T00:46:31.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2023-12-15T02:44:45.000Z (11 months ago)
- Last Synced: 2024-11-08T00:10:07.115Z (6 days ago)
- Topics: automation, management, nodejs, opinions, project, rule
- Language: TypeScript
- Homepage: https://cdaringe.github.io/counsel/
- Size: 1.77 MB
- Stars: 1
- Watchers: 3
- Forks: 1
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Contributing: .github/contributing.md
Awesome Lists containing this project
README
# counsel
[![CircleCI](https://circleci.com/gh/cdaringe/counsel.svg?style=svg)](https://circleci.com/gh/cdaringe/counsel) ![](https://img.shields.io/badge/standardjs-%E2%9C%93-brightgreen.svg) [![TypeScript package](https://img.shields.io/badge/language-typescript-blue.svg)](https://www.typescriptlang.org)
the end of boilerplate. bake structure, opinions, and rules into projects. see the [documentation site](https://cdaringe.github.io/counsel/).
it's similar to the popular [yeoman/yo](http://yeoman.io/) package, but manages
projects programmatically versus using boilerplate.`counsel` is for **project maintainers**. counsel makes sense for people who are developing _many_ projects. counsel doesn't always make sense for teams or maintainers working on just a single project or two.
## install
`yarn add --dev counsel`
alternatively, `npm install --save-dev counsel`
## usage
conventional usage is to add a `.counsel.ts` file to your project root dirname.
you can have counsel insert a generic `.counsel.ts` file for you using `--init`:
```sh
$ counsel --init
info: ⚙️ config file .counsel.ts created successfully
```alternatively, as shown next, we can bootstrap our own `counsel.ts` file.
once a project has a counsel file, run various counsel commands:
- `npx counsel apply`
![](./img/demo-apply.svg)
- `npx counsel check`
![](./img/demo-check-fail.svg)
`npx counsel --help` is also there to help!
## concepts
counsel has only one major concept to understand--the `Rule`. counsel can apply rules
and check that rules are enforced. counsel rules are specified using a `.counsel.ts` file, hereby "counsel file." let's look at counsel files and rules next.### counsel file
the counsel file declares and exports `Rule`s. the only expectation is that
it exports a function named `create` with following signature:`ContextWithRules => ContextWithRules`
let's create a basic rule that enforces that the project has a readme file:
```typescript
// .counsel.ts
export const assertReadmeExists: Rule = {
name: 'assert-readme-exists',
check: async ({ fs, path, ctx: { projectDirname } }) => {
const filename = path.resolve(projectDirname, 'readme.md')
const isReadable = await fs.lstat(filename).catch(() => false)
if (!isReadable) throw new Error('readme.md file missing')
}
}// export your rules via a `create` function
export function create (opts: ContextWithRules) =>
({ ...opts, rules: [assertReadmeExists] })
```create, import, and use as many rules as desired. rules can be used for all sorts
of reasons. sky is the limit.### rule
`Rule`s are basic interfaces with:
1. a [`name`](#rulename)
1. an optional [`plan`](#ruleplan) function
1. an optional [`check`](#rulecheck) function
1. an optional list of [`dependencies`](#ruledependencies)
1. an optional list of [`devDependencies`](#ruledevdependencies)in a nut-shell, _that's it_. counsel is a small set of functions that run these
`Rule`s against your project.here's a simple rule that exercises some of the rule api:
```ts
export const exampleRule: Rule = {
name: 'example-rule',
plan: ({ ctx }) => {
console.log(
`planning to add keyword 'example' to pkg: ${ctx.packageJson.name}`
)
return () => {
ctx.packageJson.keywords = ctx.packageJson.keywords || []
ctx.packageJson.keywords.push('example')
}
},
check: async ({ ctx: { packageJson } }) => {
const keywords = packageJson.keywords || []
console.log(`existing keywords: ${keywords.join(' ')}`)
const keywordExists = keywords.find(val => val === 'example')
if (!keywordExists) throw new Error("'example' keyword missing")
},
devDependencies: [{ name: 'debug', range: '*' }]
}
```### rule.name
every rule requires a `name`. it must always be a `string`.
### rule.plan
a `plan` returns a function or `null`, which we call a `Migration`. a `Migration` is responsible for changing the project in some way. rather than mutating the project upfront, all changes to a project are encouraged to happen in the `Migration`. this gives the user an opporitunity to _opt-out_ of rules in counsel's interactive mode.
for example, here's a simplified version of counsel's baked in `copy` rule:
```ts
export interface CopyRule {
src: string
dest: string
}
const plan = (opts: TaskPayload) =>
() => fs.copy(opts.rule.src, opts.rule.dest)
```the `() => fs.copy(...)` matches the `Migration` type, so it should be set!
plan receives a [TaskPayload](#taskpayload) as input, covered later.```ts
export type Migration =
null // return null when there is nothing to migrate
| (() => void | Promise) // otherwise, migrate in a returned function
```### rule.check
check recieves a [TaskPayload](#taskpayload) as is responsible for ensuring
that a rule is enforced. we've already seen a few examples of check functions:- [asserting that a keyword was added](#rule)
- [asserting that a readme file exists](##counselfile)check functions should:
- be synchronous, or return a promise
- `throw` (or reject) `Error`s when a violation is detected
- tend to be lenienton the topic of leniency, consider counsel's baked in `ScriptRule`.
if you wanted a rule to provide a default npm script named `test`,
where the test command was `node test/index.js`, consider if the project added a
timeout flag, such as `"test": "node test/index.js --timeout 10s"`.it would be a bad user experience to `throw` if the script did not strictly equal `node test/index.js`.
adding a simple flag is likely something that rule implementer would be OK with.
more imporantly, the core intent of the rule is likely to assert that the user
has written tests. a better `check` implementation would be to ensure that a `test`
script is present, and is truthy (i.e. runs _some test script_). enforcing rules
at any given granularity is something that needs to be worked through with rule makers and
their teams. **be weary of agitating consumers by implementing
overly strict checks**.### rule.dependencies
rules can request dependencies & devDependencies to be installed. dependencies
are always requested in a range format:```ts
const installRule: Rule = {
name: 'install-koa',
dependencies: [
{ name: 'koa', range: '^2' }
],
devDependencies: [
{ name: 'node-fetch': range: '*' }
]
}
```by using [semver](https://www.npmjs.com/package/semver) ranges, you can pin dependencies
with moderate precision or flexibility.## typings
it is worth brief mention that the majority of counsel's interfaces/typings are packed nicely
into a < 100 LOC file [here, for your viewing](https://github.com/cdaringe/counsel/blob/master/src/interfaces.ts).### TaskPayload
`plan` and `check` receive a task payload as input. the payload is rich with
data and async functions to help plan and check. check out the typings in the
[source code](https://github.com/cdaringe/counsel/blob/7537c31c3cce4bdaaaae18718b53cf9719bb29fb/src/interfaces.ts#L67) ([1](https://github.com/cdaringe/counsel/blob/7537c31c3cce4bdaaaae18718b53cf9719bb29fb/src/interfaces.ts#L28), [2](https://github.com/cdaringe/counsel/blob/7537c31c3cce4bdaaaae18718b53cf9719bb29fb/src/interfaces.ts#L46)).## batteries
counsel exports a handful of common and helpful rules. **batteries included!**
see `counsel.rules`, or [src/rules](./src/rules) to see a handful. at the time of
writing, these default rules include:#### copy
- [copy](https://github.com/cdaringe/counsel/blob/master/src/rules/copy.ts) - copies files or folders into a project
```ts
import { rules } from 'counsel'
const { plan } = rules.copy
const rule: CopyRule = {
name: 'copy-markdown-file-test',
src: path.resolve(__dirname, 'readme-template.md'),
dest: path.resolve(ctx.projectDirname, 'readme.md'),
plan
}
```#### filename-format
- [filename-format](https://github.com/cdaringe/counsel/blob/master/src/rules/filename-format.ts) - enforces a filename-format convention
```ts
import { kebabCase } from 'lodash'
import { rules } from 'counsel'
const { check } = rules.filenameFormatconst rule: FilenameFormatRule = {
name: 'test-filename-rule',
filenameFormatExtensions: ['js'],
filenameFormatExclude: ['coffee'],
filenameFormatFunction: kebabCase,
check
}
// test-file.js // ok
// functional-module.js // ok
// SomeFile // not ok
```#### githook
- [githook](https://github.com/cdaringe/counsel/blob/master/src/rules/githook.ts) - installs githook support via [husky](https://www.npmjs.com/package/husky) into a project
```ts
import { rules } from 'counsel'
const { create } = rules.githookconst rule: GitHooksRule = create({
name: 'lint-on-commit',
hooks: {
'pre-commit': 'yarn lint'
}
})
```#### readme
- [readme](https://github.com/cdaringe/counsel/blob/master/src/rules/readme.ts) - enforces that a project has a readme file
```ts
import { rules } from 'counsel'
const { rule } = rules.readme
```#### script
- [script](https://github.com/cdaringe/counsel/blob/master/src/rules/script.ts) - installs a npm script to a project
```ts
import { rules } from 'counsel'
const { create } = rules.script
const rule: criptRule = create({
name: 'add-test-script-rule',
scriptName: 'test',
scriptCommand: 'tape test/blah.js'
})
```## examples
- node library example ruleset
- see it used [in this project, here](https://github.com/cdaringe/counsel/blob/master/.counsel.ts)## similar works
- [FormidableLabs/builder](https://github.com/FormidableLabs/builder)
- counsel is very similar to builder, but counsel doesn't _need_ to be yet-another-task-runner. you can `npx counsel apply`, never fully install it, and reap many of it's benefits.
- builder also claims flexibility and an anti-"buy the farm" attitude. in practice, we've observed the opposite. feel free to try both! :)# logo credit
[margdking](https://github.com/margdking)