@meza/adr-tools 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.adr-dir +1 -0
  2. package/.editorconfig +13 -0
  3. package/.eslintignore +2 -0
  4. package/.eslintrc.json +23 -0
  5. package/.github/dependabot.yml +14 -0
  6. package/.github/stale.yml +18 -0
  7. package/.github/workflows/auto-merge.yml +14 -0
  8. package/.github/workflows/ci.yml +89 -0
  9. package/.husky/commit-msg +4 -0
  10. package/.releaserc.json +41 -0
  11. package/README.md +30 -0
  12. package/doc/adr/0001-record-architecture-decisions.md +21 -0
  13. package/doc/adr/0002-using-heavy-e2e-tests.md +20 -0
  14. package/docs/CHANGELOG.md +6 -0
  15. package/package.json +91 -0
  16. package/src/environment.d.ts +14 -0
  17. package/src/index.ts +115 -0
  18. package/src/lib/adr.ts +242 -0
  19. package/src/lib/config.ts +34 -0
  20. package/src/lib/links.test.ts +86 -0
  21. package/src/lib/links.ts +34 -0
  22. package/src/lib/manipulator.test.ts +88 -0
  23. package/src/lib/manipulator.ts +86 -0
  24. package/src/lib/numbering.test.ts +41 -0
  25. package/src/lib/numbering.ts +26 -0
  26. package/src/lib/template.ts +18 -0
  27. package/src/templates/init.md +21 -0
  28. package/src/templates/template.md +19 -0
  29. package/src/version.ts +1 -0
  30. package/tests/__snapshots__/generate-graph.e2e.test.ts.snap +39 -0
  31. package/tests/__snapshots__/init-adr-repository.e2e.test.ts.snap +51 -0
  32. package/tests/__snapshots__/linking-records.e2e.test.ts.snap +155 -0
  33. package/tests/__snapshots__/new-adr.e2e.test.ts.snap +54 -0
  34. package/tests/__snapshots__/superseding-records.e2e.test.ts.snap +122 -0
  35. package/tests/__snapshots__/toc-prefixing.e2e.test.ts.snap +9 -0
  36. package/tests/__snapshots__/use-template-override.e2e.test.ts.snap +17 -0
  37. package/tests/edit-on-create.e2e.test.ts +54 -0
  38. package/tests/fake-editor +3 -0
  39. package/tests/fake-visual +3 -0
  40. package/tests/funny-characters.e2e.test.ts +43 -0
  41. package/tests/generate-graph.e2e.test.ts +45 -0
  42. package/tests/init-adr-repository.e2e.test.ts +53 -0
  43. package/tests/linking-records.e2e.test.ts +64 -0
  44. package/tests/list-adrs.e2e.test.ts +48 -0
  45. package/tests/new-adr.e2e.test.ts +58 -0
  46. package/tests/superseding-records.e2e.test.ts +58 -0
  47. package/tests/toc-prefixing.e2e.test.ts +38 -0
  48. package/tests/use-template-override.e2e.test.ts +43 -0
  49. package/tests/work-form-other-directories.e2e.test.ts +44 -0
  50. package/tsconfig.json +22 -0
  51. package/vitest.config.ts +12 -0
package/.adr-dir ADDED
@@ -0,0 +1 @@
1
+ doc/adr
package/.editorconfig ADDED
@@ -0,0 +1,13 @@
1
+ # EditorConfig is awesome: http://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ # Unix-style newlines with a newline ending every file
7
+ [*]
8
+ charset = utf-8
9
+ insert_final_newline = true
10
+ end_of_line = lf
11
+ indent_size = 2
12
+ indent_style = space
13
+ trim_trailing_whitespace = true
package/.eslintignore ADDED
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ dist
package/.eslintrc.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "extends": [
3
+ "tailored-tunes",
4
+ "plugin:json/recommended",
5
+ "plugin:security/recommended"
6
+ ],
7
+ "root": true,
8
+ "parser": "@typescript-eslint/parser",
9
+ "plugins": [
10
+ "json",
11
+ "@typescript-eslint"
12
+ ],
13
+ "rules": {
14
+ "no-console": "off",
15
+ "security/detect-object-injection": 0,
16
+ "security/detect-non-literal-fs-filename": 0
17
+ },
18
+ "env": {
19
+ "commonjs": false,
20
+ "es6": true,
21
+ "node": true
22
+ }
23
+ }
@@ -0,0 +1,14 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "npm" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ versioning-strategy: increase-if-necessary
11
+ commit-message:
12
+ prefix: "chore: "
13
+ schedule:
14
+ interval: "daily"
@@ -0,0 +1,18 @@
1
+ # Number of days of inactivity before an issue becomes stale
2
+ daysUntilStale: 90
3
+ # Number of days of inactivity before a stale issue is closed
4
+ daysUntilClose: 275
5
+ # Issues with these labels will never be considered stale
6
+ exemptLabels:
7
+ - pinned
8
+ - security
9
+ # Label to use when marking an issue as stale
10
+ staleLabel: stale
11
+ # Comment to post when marking an issue as stale. Set to `false` to disable
12
+ markComment: >
13
+ This issue has been automatically marked as stale because it has not had
14
+ recent activity. It will be closed if no further activity occurs. Thank you
15
+ for your contributions.
16
+ # Comment to post when closing a stale issue. Set to `false` to disable
17
+ closeComment: >
18
+ This issue has been automatically closed because it has not had recent activity.
@@ -0,0 +1,14 @@
1
+ name: auto-merge dependabot
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ jobs:
7
+ auto-merge:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v2
11
+ - uses: ahmadnassri/action-dependabot-auto-merge@v2
12
+ with:
13
+ target: minor # includes patch updates!
14
+ github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,89 @@
1
+ name: Verify and Release
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+
8
+ runs-on: ubuntu-latest
9
+
10
+ strategy:
11
+ matrix:
12
+ node-version: [ 18.x ]
13
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
14
+
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+
18
+ - name: Cache multiple paths
19
+ uses: actions/cache@v2
20
+ with:
21
+ path: |
22
+ ~/.cache
23
+ ~/node_modules
24
+ key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
25
+
26
+ - name: Get yarn cache directory path
27
+ id: yarn-cache-dir-path
28
+ run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
29
+
30
+ - uses: actions/cache@v2
31
+ id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
32
+ with:
33
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
34
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
35
+ restore-keys: |
36
+ ${{ runner.os }}-yarn-
37
+
38
+ - name: Use Node.js ${{ matrix.node-version }}
39
+ uses: actions/setup-node@v2
40
+ with:
41
+ node-version: ${{ matrix.node-version }}
42
+ cache: 'yarn'
43
+
44
+ - run: yarn --pure-lockfile
45
+ - run: yarn lint
46
+
47
+ release:
48
+ needs: [build]
49
+ name: Release
50
+ runs-on: ubuntu-latest
51
+ if: ${{github.ref == 'ref/head/main'}} || ${{github.ref == 'ref/head/next'}}
52
+ steps:
53
+ - name: Checkout
54
+ uses: actions/checkout@v2
55
+ with:
56
+ fetch-depth: 0
57
+ - name: Cache multiple paths
58
+ uses: actions/cache@v2
59
+ with:
60
+ path: |
61
+ ~/.cache
62
+ ~/node_modules
63
+ key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
64
+
65
+ - name: Get yarn cache directory path
66
+ id: yarn-cache-dir-path
67
+ run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
68
+
69
+ - uses: actions/cache@v2
70
+ id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
71
+ with:
72
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
73
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
74
+ restore-keys: |
75
+ ${{ runner.os }}-yarn-
76
+
77
+ - name: Use Node.js ${{ matrix.node-version }}
78
+ uses: actions/setup-node@v2
79
+ with:
80
+ node-version: ${{ matrix.node-version }}
81
+ cache: 'yarn'
82
+
83
+ - run: yarn --pure-lockfile --offline
84
+
85
+ - name: Release
86
+ env:
87
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
88
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
89
+ run: yarn release
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ npx --no -- commitlint --edit "${1}"
@@ -0,0 +1,41 @@
1
+ {
2
+ "plugins": [
3
+ "@semantic-release/npm",
4
+ "@semantic-release/commit-analyzer",
5
+ "@semantic-release/release-notes-generator",
6
+ [
7
+ "@semantic-release/changelog",
8
+ {
9
+ "changelogFile": "docs/CHANGELOG.md"
10
+ }
11
+ ],
12
+ [
13
+ "@semantic-release/git",
14
+ {
15
+ "assets": [
16
+ "CHANGELOG.md"
17
+ ]
18
+ }
19
+ ],
20
+ [
21
+ "@semantic-release/git",
22
+ {
23
+ "assets": ["CHANGELOG.md"]
24
+ }
25
+ ],
26
+ [
27
+ "@semantic-release/github",
28
+ {
29
+ "assets": [
30
+ {
31
+ "path": [
32
+ "dist/**",
33
+ "CHANGELOG.md"
34
+ ],
35
+ "name": "adr-tools-${nextRelease.gitTag}.zip",
36
+ "label": "ADR-Tools ${nextRelease.gitTag}"
37
+ }
38
+ ]
39
+ }]
40
+ ]
41
+ }
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # ADR-TOOLS
2
+
3
+ > THIS REPO IS UNDER HEAVY DEVELOPMENT. CODE YOU SEE HERE IS... NOT QUITE READY OR PRETTY (yet).
4
+
5
+ This is a Typescript fork of Nat Pryce's [ADR-TOOLS](https://github.com/npryce/adr-tools).
6
+
7
+ There are a few other forks out there which do some parts of the original tool but none actually do it fully.
8
+
9
+ This does.
10
+
11
+ More documentation to follow very soon!
12
+
13
+ ## Conventions
14
+
15
+ An ADR file MUST have a `# title` at the top and a `## Status` header.
16
+
17
+ ## Local Development
18
+
19
+ `yarn install`
20
+
21
+ ### Commits
22
+
23
+ This project uses conventional commits. See [conventional-commits](https://www.conventionalcommits.org/en/v1.0.0/).
24
+
25
+ There are git hooks that check the commit messages and enforce the commit rules.
26
+ There is a helper tool to make it easier to create commits when unfamiliar with the rules:
27
+
28
+ `yarn commit`
29
+
30
+ This will use the [commitlint prompt tool](https://commitlint.js.org/#/guides-use-prompt) to help you create commits.
@@ -0,0 +1,21 @@
1
+ # 1. Record architecture decisions
2
+
3
+ Date: 2022-06-22
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ We need to record the architectural decisions made on this project.
12
+
13
+ ## Decision
14
+
15
+ We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
16
+
17
+ ## Consequences
18
+
19
+ See Michael Nygard's article, linked above.
20
+ For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).
21
+ > For a node version of the same tooling, see Meza's [adr-tools](https://github.com/meza/adr-tools).
@@ -0,0 +1,20 @@
1
+ # 2. Using Heavy E2E Tests
2
+
3
+ Date: 2022-06-22
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ The original tool has an [exhaustive test suite](https://github.com/npryce/adr-tools/tree/master/tests) that allows us to make sure that we're backwards compatible.
12
+
13
+ ## Decision
14
+
15
+ We'll be re-implementing those tests for ourselves too. This means that we will be using the original examples,
16
+ expectations and the ethos of invoking the tool with the given examples.
17
+
18
+ ## Consequences
19
+
20
+ The E2E test suite will be very slow to run and it can only be executed sequentially.
@@ -0,0 +1,6 @@
1
+ ## [1.0.1](https://github.com/meza/adr-tools/compare/v1.0.0...v1.0.1) (2022-06-27)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * fixed deployment restrictions [#27](https://github.com/meza/adr-tools/issues/27) ([af4f4cc](https://github.com/meza/adr-tools/commit/af4f4cc2b208ff696baddad1bda0563170bfe1ce))
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@meza/adr-tools",
3
+ "version": "1.0.1",
4
+ "main": "dist/index.js",
5
+ "bin": {
6
+ "adr": "dist/index.js"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "types": "dist/index.d.ts",
12
+ "private": false,
13
+ "scripts": {
14
+ "prebuild": "node -p \"'export const LIB_VERSION = ' + require('./package.json').version + ';'\" > src/version.ts",
15
+ "build": "tsc",
16
+ "start": "ts-node src/index.ts",
17
+ "commit": "commit",
18
+ "ci": "npm set editor '' && yarn lint && yarn test",
19
+ "ci:dev": "exit 0",
20
+ "clean": "rm -rf dist .cache/tsbuildinfo",
21
+ "clean:all": "yarn clean && rm -rf node_modules .cache",
22
+ "lint:eslint": "eslint . --ext .ts --ext .json --cache --cache-location .cache/",
23
+ "lint:tsc": "tsc --noEmit",
24
+ "lint:fix": "yarn lint:eslint --fix && yarn lint:tsc",
25
+ "lint": "yarn lint:eslint && yarn lint:tsc",
26
+ "test": "vitest",
27
+ "prepare": "husky install",
28
+ "report": "exit 0",
29
+ "semantic-release": "semantic-release",
30
+ "release": "semantic-release"
31
+ },
32
+ "dependencies": {
33
+ "@types/inquirer": "^8.2.1",
34
+ "@types/marked": "^4.0.3",
35
+ "@types/node": "^18.0.0",
36
+ "chalk": "^4.1.2",
37
+ "commander": "^9.3.0",
38
+ "core-js": "^3.19.1",
39
+ "inquirer": "^8.2.4",
40
+ "marked": "^4.0.17"
41
+ },
42
+ "commitlint": {
43
+ "extends": [
44
+ "@commitlint/config-conventional"
45
+ ]
46
+ },
47
+ "husky": {
48
+ "hooks": {
49
+ "post-merge": "install-deps-postmerge",
50
+ "pre-push": "yarn ci",
51
+ "pre-commit": "yarn lint",
52
+ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
53
+ }
54
+ },
55
+ "devDependencies": {
56
+ "@commitlint/cli": "^17.0.2",
57
+ "@commitlint/config-conventional": "^17.0.2",
58
+ "@commitlint/prompt-cli": "^17.0.0",
59
+ "@meza/tsconfig-base": "^1.1.0",
60
+ "@release-it/conventional-changelog": "^5.0.0",
61
+ "@semantic-release/changelog": "^6.0.1",
62
+ "@semantic-release/commit-analyzer": "^9.0.2",
63
+ "@semantic-release/git": "^10.0.1",
64
+ "@semantic-release/github": "^8.0.4",
65
+ "@semantic-release/npm": "^9.0.1",
66
+ "@semantic-release/release-notes-generator": "^10.0.3",
67
+ "@types/uuid": "^8.3.4",
68
+ "@typescript-eslint/eslint-plugin": "^5.29.0",
69
+ "@typescript-eslint/parser": "^5.3.0",
70
+ "@vitest/ui": "^0.15.2",
71
+ "c8": "^7.11.3",
72
+ "eslint": "^8.1.0",
73
+ "eslint-config-tailored-tunes": "^5.0.2",
74
+ "eslint-plugin-json": "^3.1.0",
75
+ "eslint-plugin-security": "^1.5.0",
76
+ "husky": "^8.0.1",
77
+ "install-deps-postmerge": "^2.0.1",
78
+ "mock-cwd": "^1.0.0",
79
+ "semantic-release": "^19.0.3",
80
+ "standard-version": "^9.3.2",
81
+ "ts-node": "^10.8.1",
82
+ "typescript": "^4.4.4",
83
+ "uuid": "^8.3.2",
84
+ "vitest": "^0.15.2",
85
+ "yarn": "^1.22.17"
86
+ },
87
+ "repository": {
88
+ "type": "git",
89
+ "url": "https://github.com/meza/adr-tools.git"
90
+ }
91
+ }
@@ -0,0 +1,14 @@
1
+ declare global {
2
+ // eslint-disable-next-line no-unused-vars
3
+ namespace NodeJS {
4
+ // eslint-disable-next-line no-unused-vars
5
+ interface ProcessEnv {
6
+ ADR_TEMPLATE: string;
7
+ ADR_DATE: string;
8
+ VISUAL: string;
9
+ EDITOR: string;
10
+ }
11
+ }
12
+ }
13
+
14
+ export {};
package/src/index.ts ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { LIB_VERSION } from './version';
5
+ import { generateToc, init, link, listAdrs, newAdr } from './lib/adr';
6
+ import chalk from 'chalk';
7
+ import { workingDir } from './lib/config';
8
+ import * as path from 'path';
9
+ import { getLinksFrom, getTitleFrom } from './lib/manipulator';
10
+ import fs from 'fs/promises';
11
+
12
+ const program = new Command();
13
+
14
+ const collectLinks = (val: string, memo: string[]) => {
15
+ memo.push(val);
16
+ return memo;
17
+ };
18
+
19
+ const collectSupersedes = (val: string, memo: string[]) => {
20
+ memo.push(val);
21
+ return memo;
22
+ };
23
+
24
+ const generateGraph = async (options?: {prefix: string, extension :string}) => {
25
+ let text = 'digraph {\n';
26
+ text += ' node [shape=plaintext];\n';
27
+ text += ' subgraph {\n';
28
+
29
+ const adrs = await listAdrs();
30
+ for (let i = 0; i < adrs.length; i++) {
31
+ const n = i + 1;
32
+ const adrPath = adrs[i];
33
+ const contents = await fs.readFile(adrPath, 'utf8');
34
+ const title = getTitleFrom(contents);
35
+ text += ` _${n} [label="${title}"; URL="${options?.prefix || ''}${path.basename(adrPath, '.md')}${options?.extension}"];\n`;
36
+ if (n > 1) {
37
+ text += ` _${n - 1} -> _${n} [style="dotted", weight=1];\n`;
38
+ }
39
+ }
40
+ text += ' }\n';
41
+ for (let i = 0; i < adrs.length; i++) {
42
+ const n = i + 1;
43
+ const adrPath = adrs[i];
44
+ const contents = await fs.readFile(adrPath, 'utf8');
45
+ const linksInADR = getLinksFrom(contents);
46
+
47
+ for (let j = 0; j < linksInADR.length; j++) {
48
+ if (!linksInADR[j].label.endsWith('by')) {
49
+ text += ` _${n} -> _${linksInADR[j].targetNumber} [label="${linksInADR[j].label}", weight=0]\n`;
50
+ }
51
+ }
52
+
53
+ }
54
+
55
+ text += '}\n';
56
+ console.log(text);
57
+ };
58
+
59
+ program.name('adr').version(LIB_VERSION).description('Manage Architecture Decision Logs');
60
+
61
+ program.command('new')
62
+ .argument('<title...>', 'The title of the decision')
63
+ .option('-q, --quiet', 'Do not ask for clarification. If multiple files match the search pattern, an error will be thrown.')
64
+ .option('-s, --supersede <SUPERSEDE>', 'A reference (number or partial filename) of a previous decision that the new decision supercedes.\n'
65
+ + 'A Markdown link to the superceded ADR is inserted into the Status section.\n'
66
+ + 'The status of the superceded ADR is changed to record that it has been superceded by the new ADR.', collectSupersedes, [])
67
+ .option('-l, --link "<TARGET:LINK:REVERSE-LINK>"', 'Links the new ADR to a previous ADR.\n'
68
+ + `${chalk.bold('TARGET')} is a reference (number or partial filename) of a previous decision.\n`
69
+ + `${chalk.bold('LINK')} is the description of the link created in the new ADR.\n`
70
+ + `${chalk.bold('REVERSE-LINK')} is the description of the link created in the existing ADR that will refer to the new ADR`, collectLinks, [])
71
+ .action(async (title: string[], options) => {
72
+ try {
73
+ await newAdr(title.join(' '), {
74
+ supersedes: options.supersede,
75
+ date: process.env.ADR_DATE,
76
+ suppressPrompts: options.quiet || false,
77
+ links: options.link
78
+ });
79
+ } catch (e) {
80
+ program.error(chalk.red((e as Error).message), { exitCode: 1 });
81
+ }
82
+ });
83
+
84
+ const generate = program.command('generate');
85
+
86
+ generate.command('toc')
87
+ .option('-p, --prefix <PREFIX>', 'The prefix to use for each file link in the generated TOC.')
88
+ .action((options) => generateToc(options));
89
+
90
+ generate.command('graph')
91
+ .option('-p, --prefix <PREFIX>', 'Prefix each decision file link with PREFIX.')
92
+ .option('-e, --extension <EXTENSION>', 'the file extension of the documents to which generated links refer. Defaults to .html', '.html')
93
+ .action(async (options) => {
94
+ await generateGraph(options);
95
+ });
96
+
97
+ program.command('link')
98
+ .argument('<SOURCE>', 'Full or Partial reference number to an ADR')
99
+ .argument('<LINK>', 'The description of the link created in the SOURCE')
100
+ .argument('<TARGET>', 'Full or Partial reference number to an ADR')
101
+ .argument('<REVERSE-LINK>', 'The description of the link created in the TARGET')
102
+ .option('-q, --quiet', 'Do not ask for clarification. If multiple files match the search pattern, an error will be thrown.')
103
+ .action(link);
104
+
105
+ program.command('init').argument('[directory]', 'Initialize a new ADR directory').action(async (directory?: string) => {
106
+ await init(directory);
107
+ });
108
+
109
+ program.command('list').action(async () => {
110
+ const adrs = await listAdrs();
111
+ console.log(adrs.map(adr => path.relative(workingDir(), adr)).join('\n'));
112
+ });
113
+
114
+ program.parse();
115
+