{"id":19658124,"url":"https://github.com/cdaringe/counsel","last_synced_at":"2025-04-28T20:31:16.828Z","repository":{"id":11356458,"uuid":"69410814","full_name":"cdaringe/counsel","owner":"cdaringe","description":"the end of boilerplate. automatically bake structure, opinions, and biz rules into projects.","archived":false,"fork":false,"pushed_at":"2023-12-15T02:44:45.000Z","size":1858,"stargazers_count":1,"open_issues_count":5,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-08T00:10:07.115Z","etag":null,"topics":["automation","management","nodejs","opinions","project","rule"],"latest_commit_sha":null,"homepage":"https://cdaringe.github.io/counsel/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cdaringe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/contributing.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-09-28T00:46:31.000Z","updated_at":"2019-02-13T06:51:03.000Z","dependencies_parsed_at":"2024-06-21T17:45:25.732Z","dependency_job_id":null,"html_url":"https://github.com/cdaringe/counsel","commit_stats":null,"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdaringe%2Fcounsel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdaringe%2Fcounsel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdaringe%2Fcounsel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cdaringe%2Fcounsel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cdaringe","download_url":"https://codeload.github.com/cdaringe/counsel/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224130032,"owners_count":17260741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","management","nodejs","opinions","project","rule"],"created_at":"2024-11-11T15:36:11.022Z","updated_at":"2024-11-11T15:36:12.091Z","avatar_url":"https://github.com/cdaringe.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- AUTO GENERATED - DO NOT EDIT --\u003e\n\u003cp align=\"center\"\u003e\u003cimg height=\"80px\" src=\"https://github.com/cdaringe/counsel/raw/master/img/counsel.png\" /\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://cdaringe.github.io/counsel/static/demo-apply-bef8a2b7283196e26d7cc45340443922.svg\" /\u003e\n\u003c/p\u003e\n\n# counsel\n\n[![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)\n\nthe end of boilerplate. bake structure, opinions, and rules into projects. see the [documentation site](https://cdaringe.github.io/counsel/).\n\nit's similar to the popular [yeoman/yo](http://yeoman.io/) package, but manages\nprojects programmatically versus using boilerplate.\n\n`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.\n\n\u003c!--  npx svg-term --out ./demo-apply.svg --height=7 --width=50 --padding 10 --window --\u003e\n\n\u003ca name='install'\u003e\u003c/a\u003e\n\n## install\n\n`yarn add --dev counsel`\n\nalternatively, `npm install --save-dev counsel`\n\n\u003ca name='usage'\u003e\u003c/a\u003e\n\n## usage\n\nconventional usage is to add a `.counsel.ts` file to your project root dirname.\n\nyou can have counsel insert a generic `.counsel.ts` file for you using `--init`:\n\n```sh\n$ counsel --init\ninfo: ⚙️ config file .counsel.ts created successfully\n```\n\nalternatively, as shown next, we can bootstrap our own `counsel.ts` file.\n\nonce a project has a counsel file, run various counsel commands:\n\n- `npx counsel apply`\n\n![](./img/demo-apply.svg)\n\n\n- `npx counsel check`\n\n![](./img/demo-check-fail.svg)\n\n\n`npx counsel --help` is also there to help!\n\n\u003ca name='concepts'\u003e\u003c/a\u003e\n\n## concepts\n\ncounsel has only one major concept to understand--the `Rule`.  counsel can apply rules\nand 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.\n\n\u003ca name='counselfile'\u003e\u003c/a\u003e\n\n### counsel file\n\nthe counsel file declares and exports `Rule`s.  the only expectation is that\nit exports a function named `create` with following signature:\n\n`ContextWithRules =\u003e ContextWithRules`\n\nlet's create a basic rule that enforces that the project has a readme file:\n\n```typescript\n// .counsel.ts\nexport const assertReadmeExists: Rule = {\n  name: 'assert-readme-exists',\n  check: async ({ fs, path, ctx: { projectDirname } }) =\u003e {\n    const filename = path.resolve(projectDirname, 'readme.md')\n    const isReadable = await fs.lstat(filename).catch(() =\u003e false)\n    if (!isReadable) throw new Error('readme.md file missing')\n  }\n}\n\n// export your rules via a `create` function\nexport function create (opts: ContextWithRules) =\u003e\n  ({ ...opts, rules: [assertReadmeExists] })\n```\n\ncreate, import, and use as many rules as desired.  rules can be used for all sorts\nof reasons.  sky is the limit.\n\n\u003ca name='rule'\u003e\u003c/a\u003e\n\n### rule\n\n`Rule`s are basic interfaces with:\n\n1. a [`name`](#rulename)\n1. an optional [`plan`](#ruleplan) function\n1. an optional [`check`](#rulecheck) function\n1. an optional list of [`dependencies`](#ruledependencies)\n1. an optional list of [`devDependencies`](#ruledevdependencies)\n\nin a nut-shell, _that's it_.  counsel is a small set of functions that run these\n`Rule`s against your project.\n\nhere's a simple rule that exercises some of the rule api:\n\n```ts\nexport const exampleRule: Rule = {\n  name: 'example-rule',\n  plan: ({ ctx }) =\u003e {\n    console.log(\n      `planning to add keyword 'example' to pkg: ${ctx.packageJson.name}`\n    )\n    return () =\u003e {\n      ctx.packageJson.keywords = ctx.packageJson.keywords || []\n      ctx.packageJson.keywords.push('example')\n    }\n  },\n  check: async ({ ctx: { packageJson } }) =\u003e {\n    const keywords = packageJson.keywords || []\n    console.log(`existing keywords: ${keywords.join(' ')}`)\n    const keywordExists = keywords.find(val =\u003e val === 'example')\n    if (!keywordExists) throw new Error(\"'example' keyword missing\")\n  },\n  devDependencies: [{ name: 'debug', range: '*' }]\n}\n```\n\n\u003ca name='rulename'\u003e\u003c/a\u003e\n\n### rule.name\n\nevery rule requires a `name`.  it must always be a `string`.\n\n\u003ca name='ruleplan'\u003e\u003c/a\u003e\n\n### rule.plan\n\na `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.\n\nfor example, here's a simplified version of counsel's baked in `copy` rule:\n\n```ts\nexport interface CopyRule {\n  src: string\n  dest: string\n}\nconst plan = (opts: TaskPayload\u003cCopyRule\u003e) =\u003e\n  () =\u003e fs.copy(opts.rule.src, opts.rule.dest)\n```\n\nthe `() =\u003e fs.copy(...)` matches the `Migration` type, so it should be set!\nplan receives a [TaskPayload](#taskpayload) as input, covered later.\n\n```ts\nexport type Migration =\n  null // return null when there is nothing to migrate\n  | (() =\u003e void | Promise\u003cvoid\u003e) // otherwise, migrate in a returned function\n```\n\n\u003ca name='rulecheck'\u003e\u003c/a\u003e\n\n### rule.check\n\ncheck recieves a [TaskPayload](#taskpayload) as is responsible for ensuring\nthat a rule is enforced.  we've already seen a few examples of check functions:\n\n- [asserting that a keyword was added](#rule)\n- [asserting that a readme file exists](##counselfile)\n\ncheck functions should:\n\n- be synchronous, or return a promise\n- `throw` (or reject) `Error`s when a violation is detected\n- tend to be lenient\n\non the topic of leniency, consider counsel's baked in `ScriptRule`.\nif you wanted a rule to provide a default npm script named `test`,\nwhere the test command was `node test/index.js`, consider if the project added a\ntimeout flag, such as `\"test\": \"node test/index.js --timeout 10s\"`.\n\nit would be a bad user experience to `throw` if the script did not strictly equal `node test/index.js`.\nadding a simple flag is likely something that rule implementer would be OK with.\nmore imporantly, the core intent of the rule is likely to assert that the user\nhas written tests.  a better `check` implementation would be to ensure that a `test`\nscript is present, and is truthy (i.e. runs _some test script_).  enforcing rules\nat any given granularity is something that needs to be worked through with rule makers and\ntheir teams.  **be weary of agitating consumers by implementing\noverly strict checks**.\n\n\n\u003ca name='ruledependencies'\u003e\u003c/a\u003e\n\u003ca name='ruledevdependencies'\u003e\u003c/a\u003e\n\n### rule.dependencies\n\nrules can request dependencies \u0026 devDependencies to be installed.  dependencies\nare always requested in a range format:\n\n```ts\nconst installRule: Rule = {\n  name: 'install-koa',\n  dependencies: [\n    { name: 'koa', range: '^2' }\n  ],\n  devDependencies: [\n    { name: 'node-fetch': range: '*' }\n  ]\n}\n```\n\nby using [semver](https://www.npmjs.com/package/semver) ranges, you can pin dependencies\nwith moderate precision or flexibility.\n\n\u003ca name='typings'\u003e\u003c/a\u003e\n\n## typings\n\nit is worth brief mention that the majority of counsel's interfaces/typings are packed nicely\ninto a \u003c 100 LOC file [here, for your viewing](https://github.com/cdaringe/counsel/blob/master/src/interfaces.ts).\n\n\n\u003ca name='taskpayload'\u003e\u003c/a\u003e\n\n### TaskPayload\n\n`plan` and `check` receive a task payload as input. the payload is rich with\ndata and async functions to help plan and check. check out the typings in the\n[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)).\n\n\u003ca name='batteries'\u003e\u003c/a\u003e\n\n## batteries\n\ncounsel exports a handful of common and helpful rules. **batteries included!**\n\nsee `counsel.rules`, or [src/rules](./src/rules) to see a handful.  at the time of\nwriting, these default rules include:\n\n\u003ca name='copy'\u003e\u003c/a\u003e\n\n#### copy\n\n- [copy](https://github.com/cdaringe/counsel/blob/master/src/rules/copy.ts) - copies files or folders into a project\n\n```ts\nimport { rules } from 'counsel'\nconst { plan } = rules.copy\nconst rule: CopyRule = {\n  name: 'copy-markdown-file-test',\n  src: path.resolve(__dirname, 'readme-template.md'),\n  dest: path.resolve(ctx.projectDirname, 'readme.md'),\n  plan\n}\n```\n\n\u003ca name='filenameformat'\u003e\u003c/a\u003e\n\n#### filename-format\n\n- [filename-format](https://github.com/cdaringe/counsel/blob/master/src/rules/filename-format.ts) - enforces a filename-format convention\n\n```ts\nimport { kebabCase } from 'lodash'\nimport { rules } from 'counsel'\nconst { check } = rules.filenameFormat\n\nconst rule: FilenameFormatRule = {\n  name: 'test-filename-rule',\n  filenameFormatExtensions: ['js'],\n  filenameFormatExclude: ['coffee'],\n  filenameFormatFunction: kebabCase,\n  check\n}\n// test-file.js // ok\n// functional-module.js // ok\n// SomeFile // not ok\n```\n\n\u003ca name='githook'\u003e\u003c/a\u003e\n\n#### githook\n\n- [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\n\n```ts\nimport { rules } from 'counsel'\nconst { create } = rules.githook\n\nconst rule: GitHooksRule = create({\n  name: 'lint-on-commit',\n  hooks: {\n    'pre-commit': 'yarn lint'\n  }\n})\n```\n\n\u003ca name='readme'\u003e\u003c/a\u003e\n\n\n#### readme\n\n- [readme](https://github.com/cdaringe/counsel/blob/master/src/rules/readme.ts) - enforces that a project has a readme file\n\n```ts\nimport { rules } from 'counsel'\nconst { rule } = rules.readme\n```\n\n\u003ca name='script'\u003e\u003c/a\u003e\n\n#### script\n\n- [script](https://github.com/cdaringe/counsel/blob/master/src/rules/script.ts) - installs a npm script to a project\n\n```ts\nimport { rules } from 'counsel'\nconst { create } = rules.script\nconst rule: criptRule = create({\n  name: 'add-test-script-rule',\n  scriptName: 'test',\n  scriptCommand: 'tape test/blah.js'\n})\n```\n\n\u003ca name='examples'\u003e\u003c/a\u003e\n\n## examples\n\n- \u003ca href='https://github.com/cdaringe/counsel/blob/master/src/rulesets/nodelib.ts' target='_blank'\u003enode library example ruleset\u003c/a\u003e\n    - see it used [in this project, here](https://github.com/cdaringe/counsel/blob/master/.counsel.ts)\n\n\u003ca name='similarworks'\u003e\u003c/a\u003e\n\n## similar works\n\n- [FormidableLabs/builder](https://github.com/FormidableLabs/builder)\n    - 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.\n    - builder also claims flexibility and an anti-\"buy the farm\" attitude.  in practice, we've observed the opposite.  feel free to try both! :)\n\n\u003cdiv style='margin-bottom: 100px;'\u003e\u003c/div\u003e\n\n# logo credit\n\n[margdking](https://github.com/margdking)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcdaringe%2Fcounsel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcdaringe%2Fcounsel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcdaringe%2Fcounsel/lists"}