@meza/adr-tools 1.0.8 → 1.0.11
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.
- package/.gitattributes +40 -0
- package/.github/renovate.json +5 -0
- package/.github/workflows/ci-pr.yml +44 -0
- package/.github/workflows/ci.yml +21 -22
- package/.github/workflows/sync-deps-to-main.yml +25 -0
- package/.github/workflows/sync-to-deps.yml +26 -0
- package/.releaserc.json +2 -7
- package/CHANGELOG.md +84 -0
- package/LICENSE +674 -0
- package/README.md +64 -5
- package/biome.json +148 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/adr.js +177 -0
- package/dist/lib/adr.js.map +1 -0
- package/dist/lib/config.js +33 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/links.js +25 -0
- package/dist/lib/links.js.map +1 -0
- package/dist/lib/links.test.js +65 -0
- package/dist/lib/links.test.js.map +1 -0
- package/dist/lib/manipulator.js +84 -0
- package/dist/lib/manipulator.js.map +1 -0
- package/dist/lib/manipulator.test.js +76 -0
- package/dist/lib/manipulator.test.js.map +1 -0
- package/dist/lib/numbering.js +25 -0
- package/dist/lib/numbering.js.map +1 -0
- package/dist/lib/numbering.test.js +32 -0
- package/dist/lib/numbering.test.js.map +1 -0
- package/dist/lib/prompt.js +14 -0
- package/dist/lib/prompt.js.map +1 -0
- package/dist/lib/prompt.test.js +33 -0
- package/dist/lib/prompt.test.js.map +1 -0
- package/dist/lib/template.js +21 -0
- package/dist/lib/template.js.map +1 -0
- package/{doc/adr/0001-record-architecture-decisions.md → dist/templates/init.md} +2 -2
- package/dist/templates/template.md +19 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/lib/adr.d.ts +18 -0
- package/dist/types/lib/adr.d.ts.map +1 -0
- package/dist/types/lib/config.d.ts +3 -0
- package/dist/types/lib/config.d.ts.map +1 -0
- package/dist/types/lib/links.d.ts +10 -0
- package/dist/types/lib/links.d.ts.map +1 -0
- package/dist/types/lib/links.test.d.ts +2 -0
- package/dist/types/lib/links.test.d.ts.map +1 -0
- package/dist/types/lib/manipulator.d.ts +11 -0
- package/dist/types/lib/manipulator.d.ts.map +1 -0
- package/dist/types/lib/manipulator.test.d.ts +2 -0
- package/dist/types/lib/manipulator.test.d.ts.map +1 -0
- package/dist/types/lib/numbering.d.ts +2 -0
- package/dist/types/lib/numbering.d.ts.map +1 -0
- package/dist/types/lib/numbering.test.d.ts +2 -0
- package/dist/types/lib/numbering.test.d.ts.map +1 -0
- package/dist/types/lib/prompt.d.ts +2 -0
- package/dist/types/lib/prompt.d.ts.map +1 -0
- package/dist/types/lib/prompt.test.d.ts +2 -0
- package/dist/types/lib/prompt.test.d.ts.map +1 -0
- package/dist/types/lib/template.d.ts +2 -0
- package/dist/types/lib/template.d.ts.map +1 -0
- package/dist/types/version.d.ts +2 -0
- package/dist/types/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/doc/adr/.adr-sequence.lock +1 -0
- package/doc/adr/decisions.md +3 -0
- package/package.json +78 -48
- package/src/index.ts +52 -27
- package/src/lib/adr.ts +67 -72
- package/src/lib/config.ts +3 -3
- package/src/lib/links.test.ts +8 -24
- package/src/lib/links.ts +2 -2
- package/src/lib/manipulator.test.ts +44 -47
- package/src/lib/manipulator.ts +22 -10
- package/src/lib/numbering.test.ts +5 -9
- package/src/lib/numbering.ts +4 -5
- package/src/lib/prompt.test.ts +42 -0
- package/src/lib/prompt.ts +14 -0
- package/src/lib/template.ts +7 -3
- package/src/version.ts +1 -1
- package/tests/.adr-dir +1 -0
- package/tests/__snapshots__/generate-graph.e2e.test.ts.snap +23 -23
- package/tests/__snapshots__/init-adr-repository.e2e.test.ts.snap +1 -1
- package/tests/__snapshots__/linking-records.e2e.test.ts.snap +1 -1
- package/tests/__snapshots__/new-adr.e2e.test.ts.snap +1 -1
- package/tests/__snapshots__/superseding-records.e2e.test.ts.snap +1 -1
- package/tests/__snapshots__/toc-prefixing.e2e.test.ts.snap +1 -1
- package/tests/__snapshots__/use-template-override.e2e.test.ts.snap +1 -1
- package/tests/edit-on-create.e2e.test.ts +17 -12
- package/tests/funny-characters.e2e.test.ts +28 -21
- package/tests/generate-graph.e2e.test.ts +21 -13
- package/tests/init-adr-repository.e2e.test.ts +12 -8
- package/tests/linking-records.e2e.test.ts +21 -14
- package/tests/list-adrs.e2e.test.ts +23 -18
- package/tests/new-adr.e2e.test.ts +15 -12
- package/tests/superseding-records.e2e.test.ts +16 -11
- package/tests/toc-prefixing.e2e.test.ts +15 -11
- package/tests/use-template-override.e2e.test.ts +18 -10
- package/tests/work-form-other-directories.e2e.test.ts +14 -12
- package/tsconfig.json +9 -8
- package/vitest.config.e2e.ts +13 -0
- package/vitest.config.ts +8 -1
- package/.eslintignore +0 -2
- package/.eslintrc.json +0 -23
- package/.github/dependabot.yml +0 -14
- package/.github/workflows/auto-merge.yml +0 -14
- package/doc/adr/0002-using-heavy-e2e-tests.md +0 -20
- /package/src/{environment.d.ts → types/environment.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,40 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meza/adr-tools",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"
|
|
3
|
+
"version": "1.0.11",
|
|
4
|
+
"exports": {
|
|
5
|
+
".": {
|
|
6
|
+
"import": {
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
|
+
"default": "dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
},
|
|
5
12
|
"bin": {
|
|
6
13
|
"adr": "dist/index.js"
|
|
7
14
|
},
|
|
15
|
+
"license": "GPL-3.0",
|
|
16
|
+
"type": "module",
|
|
8
17
|
"types": "dist/index.d.ts",
|
|
9
18
|
"private": false,
|
|
10
19
|
"scripts": {
|
|
11
|
-
"prebuild": "
|
|
12
|
-
"
|
|
20
|
+
"prebuild": "./scripts/inject-version.sh ./src/version.ts",
|
|
21
|
+
"prepack": "./scripts/inject-version.sh ./src/version.ts && tsc && yarn copy ",
|
|
22
|
+
"copy": "copyfiles -u 1 ./src/templates/** ./dist",
|
|
23
|
+
"build": "tsc && yarn copy",
|
|
13
24
|
"start": "ts-node src/index.ts",
|
|
14
|
-
"commit": "
|
|
15
|
-
"ci": "npm set editor '' &&
|
|
25
|
+
"commit": "cz",
|
|
26
|
+
"ci": "npm set editor 'true' && npm-run-all --parallel lint test",
|
|
16
27
|
"ci:dev": "exit 0",
|
|
17
|
-
"clean": "
|
|
28
|
+
"clean": "rimraf dist .cache/tsbuildinfo",
|
|
18
29
|
"clean:all": "yarn clean && rm -rf node_modules .cache",
|
|
19
|
-
"lint:
|
|
30
|
+
"lint:ci": "biome ci --reporter=github",
|
|
20
31
|
"lint:tsc": "tsc --noEmit",
|
|
21
|
-
"lint
|
|
22
|
-
"
|
|
23
|
-
"test": "vitest",
|
|
32
|
+
"lint": "npm-run-all --parallel lint:*",
|
|
33
|
+
"test": "npm-run-all --parallel test:*",
|
|
34
|
+
"test:unit": "vitest",
|
|
35
|
+
"test:e2e": "vitest --config vitest.config.e2e.ts",
|
|
24
36
|
"prepare": "is-ci || husky install",
|
|
25
37
|
"report": "exit 0",
|
|
26
38
|
"semantic-release": "semantic-release",
|
|
27
39
|
"release": "semantic-release"
|
|
28
40
|
},
|
|
29
41
|
"dependencies": {
|
|
30
|
-
"@types/inquirer": "
|
|
31
|
-
"@types/
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"marked": "^4.0.17"
|
|
42
|
+
"@types/inquirer": "9.0.8",
|
|
43
|
+
"@types/node": "22.15.21",
|
|
44
|
+
"chalk": "5.4.1",
|
|
45
|
+
"commander": "12.1.0",
|
|
46
|
+
"core-js": "3.42.0",
|
|
47
|
+
"inquirer": "9.3.7",
|
|
48
|
+
"marked": "4.3.0"
|
|
38
49
|
},
|
|
39
50
|
"commitlint": {
|
|
40
51
|
"extends": [
|
|
@@ -50,35 +61,39 @@
|
|
|
50
61
|
}
|
|
51
62
|
},
|
|
52
63
|
"devDependencies": {
|
|
53
|
-
"@
|
|
54
|
-
"@commitlint/
|
|
55
|
-
"@commitlint/
|
|
56
|
-
"@
|
|
57
|
-
"@
|
|
58
|
-
"@
|
|
59
|
-
"@
|
|
60
|
-
"@
|
|
61
|
-
"@
|
|
62
|
-
"@semantic-release/
|
|
63
|
-
"@
|
|
64
|
-
"@
|
|
65
|
-
"@
|
|
66
|
-
"@
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"husky": "
|
|
73
|
-
"install-deps-postmerge": "
|
|
74
|
-
"is-ci": "
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
64
|
+
"@biomejs/biome": "1.9.4",
|
|
65
|
+
"@commitlint/cli": "19.8.1",
|
|
66
|
+
"@commitlint/config-conventional": "19.8.1",
|
|
67
|
+
"@commitlint/prompt-cli": "19.8.1",
|
|
68
|
+
"@faker-js/faker": "9.8.0",
|
|
69
|
+
"@meza/adr-tools": "1.0.10",
|
|
70
|
+
"@meza/tsconfig-base": "1.1.0",
|
|
71
|
+
"@ryansonshine/commitizen": "4.2.8",
|
|
72
|
+
"@ryansonshine/cz-conventional-changelog": "3.3.4",
|
|
73
|
+
"@semantic-release/changelog": "6.0.3",
|
|
74
|
+
"@semantic-release/commit-analyzer": "13.0.1",
|
|
75
|
+
"@semantic-release/git": "10.0.1",
|
|
76
|
+
"@semantic-release/github": "11.0.2",
|
|
77
|
+
"@semantic-release/npm": "12.0.1",
|
|
78
|
+
"@semantic-release/release-notes-generator": "14.0.3",
|
|
79
|
+
"@types/uuid": "10.0.0",
|
|
80
|
+
"@vitest/ui": "3.1.4",
|
|
81
|
+
"copyfiles": "2.4.1",
|
|
82
|
+
"cross-env": "7.0.3",
|
|
83
|
+
"husky": "9.1.7",
|
|
84
|
+
"install-deps-postmerge": "2.0.1",
|
|
85
|
+
"is-ci": "4.1.0",
|
|
86
|
+
"lint-staged": "16.0.0",
|
|
87
|
+
"mock-cwd": "1.0.0",
|
|
88
|
+
"npm-run-all2": "8.0.3",
|
|
89
|
+
"rimraf": "6.0.1",
|
|
90
|
+
"semantic-release": "24.2.4",
|
|
91
|
+
"ts-node": "10.9.2",
|
|
92
|
+
"tsx": "4.19.4",
|
|
93
|
+
"typescript": "5.8.3",
|
|
94
|
+
"uuid": "11.1.0",
|
|
95
|
+
"vitest": "3.1.4",
|
|
96
|
+
"yarn": "1.22.22"
|
|
82
97
|
},
|
|
83
98
|
"repository": {
|
|
84
99
|
"type": "git",
|
|
@@ -86,5 +101,20 @@
|
|
|
86
101
|
},
|
|
87
102
|
"publishConfig": {
|
|
88
103
|
"access": "public"
|
|
104
|
+
},
|
|
105
|
+
"config": {
|
|
106
|
+
"commitizen": {
|
|
107
|
+
"path": "./node_modules/@ryansonshine/cz-conventional-changelog"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"lint-staged": {
|
|
111
|
+
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
|
|
112
|
+
"biome check --files-ignore-unknown=true",
|
|
113
|
+
"biome check --write --no-errors-on-unmatched",
|
|
114
|
+
"biome check --write --organize-imports-enabled=false --no-errors-on-unmatched",
|
|
115
|
+
"biome check --write --unsafe --no-errors-on-unmatched",
|
|
116
|
+
"biome format --write --no-errors-on-unmatched",
|
|
117
|
+
"biome lint --write --no-errors-on-unmatched"
|
|
118
|
+
]
|
|
89
119
|
}
|
|
90
120
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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
3
|
import * as path from 'path';
|
|
9
|
-
import
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { Command } from 'commander';
|
|
10
6
|
import fs from 'fs/promises';
|
|
7
|
+
import { generateToc, init, link, listAdrs, newAdr } from './lib/adr.js';
|
|
8
|
+
import { workingDir } from './lib/config.js';
|
|
9
|
+
import { getLinksFrom, getTitleFrom } from './lib/manipulator.js';
|
|
10
|
+
import { LIB_VERSION } from './version.js';
|
|
11
11
|
|
|
12
12
|
const program = new Command();
|
|
13
13
|
|
|
@@ -21,7 +21,7 @@ const collectSupersedes = (val: string, memo: string[]) => {
|
|
|
21
21
|
return memo;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
const generateGraph = async (options?: {prefix: string
|
|
24
|
+
const generateGraph = async (options?: { prefix: string; extension: string }) => {
|
|
25
25
|
let text = 'digraph {\n';
|
|
26
26
|
text += ' node [shape=plaintext];\n';
|
|
27
27
|
text += ' subgraph {\n';
|
|
@@ -49,7 +49,6 @@ const generateGraph = async (options?: {prefix: string, extension :string}) => {
|
|
|
49
49
|
text += ` _${n} -> _${linksInADR[j].targetNumber} [label="${linksInADR[j].label}", weight=0]\n`;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
text += '}\n';
|
|
@@ -58,16 +57,30 @@ const generateGraph = async (options?: {prefix: string, extension :string}) => {
|
|
|
58
57
|
|
|
59
58
|
program.name('adr').version(LIB_VERSION).description('Manage Architecture Decision Logs');
|
|
60
59
|
|
|
61
|
-
program
|
|
60
|
+
program
|
|
61
|
+
.command('new')
|
|
62
62
|
.argument('<title...>', 'The title of the decision')
|
|
63
|
-
.option(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.option(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
.option(
|
|
64
|
+
'-q, --quiet',
|
|
65
|
+
'Do not ask for clarification. If multiple files match the search pattern, an error will be thrown.'
|
|
66
|
+
)
|
|
67
|
+
.option(
|
|
68
|
+
'-s, --supersede <SUPERSEDE>',
|
|
69
|
+
'A reference (number or partial filename) of a previous decision that the new decision supercedes.\n' +
|
|
70
|
+
'A Markdown link to the superceded ADR is inserted into the Status section.\n' +
|
|
71
|
+
'The status of the superceded ADR is changed to record that it has been superceded by the new ADR.',
|
|
72
|
+
collectSupersedes,
|
|
73
|
+
[]
|
|
74
|
+
)
|
|
75
|
+
.option(
|
|
76
|
+
'-l, --link "<TARGET:LINK:REVERSE-LINK>"',
|
|
77
|
+
'Links the new ADR to a previous ADR.\n' +
|
|
78
|
+
`${chalk.bold('TARGET')} is a reference (number or partial filename) of a previous decision.\n` +
|
|
79
|
+
`${chalk.bold('LINK')} is the description of the link created in the new ADR.\n` +
|
|
80
|
+
`${chalk.bold('REVERSE-LINK')} is the description of the link created in the existing ADR that will refer to the new ADR`,
|
|
81
|
+
collectLinks,
|
|
82
|
+
[]
|
|
83
|
+
)
|
|
71
84
|
.action(async (title: string[], options) => {
|
|
72
85
|
try {
|
|
73
86
|
await newAdr(title.join(' '), {
|
|
@@ -83,33 +96,45 @@ program.command('new')
|
|
|
83
96
|
|
|
84
97
|
const generate = program.command('generate');
|
|
85
98
|
|
|
86
|
-
generate
|
|
99
|
+
generate
|
|
100
|
+
.command('toc')
|
|
87
101
|
.option('-p, --prefix <PREFIX>', 'The prefix to use for each file link in the generated TOC.')
|
|
88
102
|
.action((options) => generateToc(options));
|
|
89
103
|
|
|
90
|
-
generate
|
|
104
|
+
generate
|
|
105
|
+
.command('graph')
|
|
91
106
|
.option('-p, --prefix <PREFIX>', 'Prefix each decision file link with PREFIX.')
|
|
92
|
-
.option(
|
|
107
|
+
.option(
|
|
108
|
+
'-e, --extension <EXTENSION>',
|
|
109
|
+
'the file extension of the documents to which generated links refer. Defaults to .html',
|
|
110
|
+
'.html'
|
|
111
|
+
)
|
|
93
112
|
.action(async (options) => {
|
|
94
113
|
await generateGraph(options);
|
|
95
114
|
});
|
|
96
115
|
|
|
97
|
-
program
|
|
116
|
+
program
|
|
117
|
+
.command('link')
|
|
98
118
|
.argument('<SOURCE>', 'Full or Partial reference number to an ADR')
|
|
99
119
|
.argument('<LINK>', 'The description of the link created in the SOURCE')
|
|
100
120
|
.argument('<TARGET>', 'Full or Partial reference number to an ADR')
|
|
101
121
|
.argument('<REVERSE-LINK>', 'The description of the link created in the TARGET')
|
|
102
|
-
.option(
|
|
122
|
+
.option(
|
|
123
|
+
'-q, --quiet',
|
|
124
|
+
'Do not ask for clarification. If multiple files match the search pattern, an error will be thrown.'
|
|
125
|
+
)
|
|
103
126
|
.action(link);
|
|
104
127
|
|
|
105
|
-
program
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
program
|
|
129
|
+
.command('init')
|
|
130
|
+
.argument('[directory]', 'Initialize a new ADR directory')
|
|
131
|
+
.action(async (directory?: string) => {
|
|
132
|
+
await init(directory);
|
|
133
|
+
});
|
|
108
134
|
|
|
109
135
|
program.command('list').action(async () => {
|
|
110
136
|
const adrs = await listAdrs();
|
|
111
|
-
console.log(adrs.map(adr => path.relative(workingDir(), adr)).join('\n'));
|
|
137
|
+
console.log(adrs.map((adr) => path.relative(workingDir(), adr)).join('\n'));
|
|
112
138
|
});
|
|
113
139
|
|
|
114
140
|
program.parse();
|
|
115
|
-
|
package/src/lib/adr.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { newNumber } from './numbering';
|
|
2
|
-
import { template } from './template';
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { getDir, workingDir } from './config';
|
|
6
|
-
import { prompt } from 'inquirer';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { getTitleFrom, injectLink, supersede } from './manipulator';
|
|
9
|
-
import { findMatchingFilesFor, getLinkDetails } from './links';
|
|
10
1
|
import childProcess from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import { getDir, workingDir } from './config.js';
|
|
6
|
+
import { findMatchingFilesFor, getLinkDetails } from './links.js';
|
|
7
|
+
import { getTitleFrom, injectLink, supersede } from './manipulator.js';
|
|
8
|
+
import { newNumber } from './numbering.js';
|
|
9
|
+
import { askForClarification } from './prompt.js';
|
|
10
|
+
import { template } from './template.js';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
13
|
|
|
12
14
|
interface NewOptions {
|
|
13
15
|
supersedes?: string[];
|
|
@@ -17,18 +19,6 @@ interface NewOptions {
|
|
|
17
19
|
links?: string[];
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
const askForClarification = async (searchString: string, matches: string[]) => {
|
|
21
|
-
const selection = await prompt([
|
|
22
|
-
{
|
|
23
|
-
type: 'list',
|
|
24
|
-
name: 'target',
|
|
25
|
-
message: `Which file do you want to link to for ${chalk.blue(searchString)}?`,
|
|
26
|
-
choices: matches
|
|
27
|
-
}
|
|
28
|
-
]);
|
|
29
|
-
return selection.target;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
22
|
// eslint-disable-next-line no-unused-vars
|
|
33
23
|
enum LinkType {
|
|
34
24
|
// eslint-disable-next-line no-unused-vars
|
|
@@ -51,14 +41,21 @@ const actuallyLink = async (task: LinkTask) => {
|
|
|
51
41
|
const oldAdrContent = await fs.readFile(linkedFile, 'utf8');
|
|
52
42
|
const oldTitle = getTitleFrom(oldAdrContent);
|
|
53
43
|
const newTitle = getTitleFrom(newAdrContent);
|
|
54
|
-
let dirtyOld = '',
|
|
44
|
+
let dirtyOld = '',
|
|
45
|
+
dirtyNew = '';
|
|
55
46
|
switch (task.type) {
|
|
56
47
|
case LinkType.LINK:
|
|
57
|
-
dirtyOld = injectLink(
|
|
48
|
+
dirtyOld = injectLink(
|
|
49
|
+
oldAdrContent,
|
|
50
|
+
`${task.reverseLink} [${newTitle}](${path.relative(await getDir(), task.sourcePath)})`
|
|
51
|
+
);
|
|
58
52
|
dirtyNew = injectLink(newAdrContent, `${task.link} [${oldTitle}](${task.targetPath})`);
|
|
59
53
|
break;
|
|
60
54
|
case LinkType.SUPERSEDE:
|
|
61
|
-
dirtyOld = supersede(
|
|
55
|
+
dirtyOld = supersede(
|
|
56
|
+
oldAdrContent,
|
|
57
|
+
`${task.reverseLink} [${newTitle}](${path.relative(await getDir(), task.sourcePath)})`
|
|
58
|
+
);
|
|
62
59
|
dirtyNew = injectLink(newAdrContent, `${task.link} [${oldTitle}](${task.targetPath})`);
|
|
63
60
|
break;
|
|
64
61
|
default:
|
|
@@ -69,11 +66,7 @@ const actuallyLink = async (task: LinkTask) => {
|
|
|
69
66
|
await fs.writeFile(task.sourcePath, dirtyNew);
|
|
70
67
|
};
|
|
71
68
|
|
|
72
|
-
const processSupersedes = async (
|
|
73
|
-
sourcePath: string,
|
|
74
|
-
supersedes: string[] = [],
|
|
75
|
-
suppressPrompts: boolean = false
|
|
76
|
-
) => {
|
|
69
|
+
const processSupersedes = async (sourcePath: string, supersedes: string[] = [], suppressPrompts: boolean = false) => {
|
|
77
70
|
if (supersedes.length === 0) {
|
|
78
71
|
return;
|
|
79
72
|
}
|
|
@@ -91,22 +84,19 @@ const processSupersedes = async (
|
|
|
91
84
|
|
|
92
85
|
if (targetDetails.matches.length > 1) {
|
|
93
86
|
if (suppressPrompts) {
|
|
94
|
-
throw new Error(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Multiple files match the search pattern for "${targetDetails.original}".\n` +
|
|
89
|
+
'Please specify which file you want to targetDetails to more or remove the -q or --quiet options from the command line.'
|
|
90
|
+
);
|
|
98
91
|
}
|
|
92
|
+
task.targetPath = await askForClarification(targetDetails.original, targetDetails.matches);
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
await actuallyLink(task);
|
|
102
96
|
}
|
|
103
97
|
};
|
|
104
98
|
|
|
105
|
-
const injectLinksTo = async (
|
|
106
|
-
sourcePath: string,
|
|
107
|
-
links: string[] = [],
|
|
108
|
-
suppressPrompts: boolean = false
|
|
109
|
-
) => {
|
|
99
|
+
const injectLinksTo = async (sourcePath: string, links: string[] = [], suppressPrompts: boolean = false) => {
|
|
110
100
|
if (links.length === 0) {
|
|
111
101
|
return;
|
|
112
102
|
}
|
|
@@ -124,20 +114,19 @@ const injectLinksTo = async (
|
|
|
124
114
|
|
|
125
115
|
if (targetDetails.matches.length > 1) {
|
|
126
116
|
if (suppressPrompts) {
|
|
127
|
-
throw new Error(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Multiple files match the search pattern for "${targetDetails.original}".\n` +
|
|
119
|
+
'Please specify which file you want to targetDetails to more or remove the -q or --quiet options from the command line.'
|
|
120
|
+
);
|
|
131
121
|
}
|
|
122
|
+
task.targetPath = await askForClarification(targetDetails.original, targetDetails.matches);
|
|
132
123
|
}
|
|
133
124
|
|
|
134
125
|
await actuallyLink(task);
|
|
135
126
|
}
|
|
136
|
-
|
|
137
127
|
};
|
|
138
128
|
//Generate a table of contents for the adr directory
|
|
139
|
-
export const generateToc = async (options?: {prefix?: string}) => {
|
|
140
|
-
|
|
129
|
+
export const generateToc = async (options?: { prefix?: string }) => {
|
|
141
130
|
const adrDir = await getDir();
|
|
142
131
|
const files = await fs.readdir(adrDir);
|
|
143
132
|
const toc = files.filter((file) => file.match(/^\d{4}-.*\.md$/));
|
|
@@ -157,27 +146,26 @@ export const newAdr = async (title: string, config?: NewOptions) => {
|
|
|
157
146
|
const newNum = await newNumber();
|
|
158
147
|
const formattedDate = config?.date || new Date().toISOString().split('T')[0] || 'ERROR';
|
|
159
148
|
const tpl = await template(config?.template);
|
|
160
|
-
const adr = tpl
|
|
149
|
+
const adr = tpl
|
|
150
|
+
.replace('DATE', formattedDate)
|
|
151
|
+
.replace('TITLE', title)
|
|
152
|
+
.replace('NUMBER', newNum.toString())
|
|
153
|
+
.replace('STATUS', 'Accepted');
|
|
161
154
|
const paddedNumber = newNum.toString().padStart(4, '0');
|
|
162
|
-
const cleansedTitle = title
|
|
155
|
+
const cleansedTitle = title
|
|
156
|
+
.toLowerCase()
|
|
157
|
+
.replace(/\W/g, '-')
|
|
158
|
+
.replace(/^(.*)\W$/, '$1')
|
|
159
|
+
.replace(/^\W(.*)$/, '$1');
|
|
163
160
|
const fileName = `${paddedNumber}-${cleansedTitle}.md`;
|
|
164
161
|
const adrDirectory = await getDir();
|
|
165
162
|
const adrPath = path.resolve(path.join(adrDirectory, fileName));
|
|
166
163
|
await fs.writeFile(adrPath, adr);
|
|
167
164
|
await fs.writeFile(path.resolve(adrDirectory, '.adr-sequence.lock'), newNum.toString());
|
|
168
165
|
|
|
169
|
-
await processSupersedes(
|
|
170
|
-
|
|
171
|
-
config?.supersedes,
|
|
172
|
-
config?.suppressPrompts
|
|
173
|
-
);
|
|
174
|
-
await injectLinksTo(
|
|
175
|
-
adrPath,
|
|
176
|
-
config?.links,
|
|
177
|
-
config?.suppressPrompts
|
|
178
|
-
);
|
|
166
|
+
await processSupersedes(adrPath, config?.supersedes, config?.suppressPrompts);
|
|
167
|
+
await injectLinksTo(adrPath, config?.links, config?.suppressPrompts);
|
|
179
168
|
await generateToc();
|
|
180
|
-
const newAdrPath = path.relative(workingDir(), adrPath);
|
|
181
169
|
|
|
182
170
|
if (process.env.VISUAL) {
|
|
183
171
|
await childProcess.spawn(process.env.VISUAL, [adrPath], {
|
|
@@ -193,12 +181,10 @@ export const newAdr = async (title: string, config?: NewOptions) => {
|
|
|
193
181
|
});
|
|
194
182
|
return;
|
|
195
183
|
}
|
|
196
|
-
console.log(newAdrPath);
|
|
197
|
-
|
|
198
184
|
};
|
|
199
185
|
|
|
200
186
|
export const init = async (directory?: string) => {
|
|
201
|
-
const dir = directory || await getDir();
|
|
187
|
+
const dir = directory || (await getDir());
|
|
202
188
|
await fs.mkdir(dir, { recursive: true });
|
|
203
189
|
await fs.writeFile(path.join(workingDir(), '.adr-dir'), path.relative(workingDir(), dir));
|
|
204
190
|
await newAdr('Record Architecture Decisions', {
|
|
@@ -207,7 +193,13 @@ export const init = async (directory?: string) => {
|
|
|
207
193
|
});
|
|
208
194
|
};
|
|
209
195
|
|
|
210
|
-
export const link = async (
|
|
196
|
+
export const link = async (
|
|
197
|
+
source: string,
|
|
198
|
+
link: string,
|
|
199
|
+
target: string,
|
|
200
|
+
reverseLink: string,
|
|
201
|
+
options?: { quiet?: boolean }
|
|
202
|
+
) => {
|
|
211
203
|
const getFor = async (pattern: string) => {
|
|
212
204
|
const found = await findMatchingFilesFor(pattern);
|
|
213
205
|
if (found.length === 1) {
|
|
@@ -215,8 +207,10 @@ export const link = async (source: string, link: string, target: string, reverse
|
|
|
215
207
|
}
|
|
216
208
|
|
|
217
209
|
if (options?.quiet) {
|
|
218
|
-
throw new Error(
|
|
219
|
-
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Multiple files match the search pattern for "${pattern}".\n` +
|
|
212
|
+
'Please specify which file you want to targetDetails to more or remove the -q or --quiet options from the command line.'
|
|
213
|
+
);
|
|
220
214
|
}
|
|
221
215
|
|
|
222
216
|
return askForClarification(pattern, found);
|
|
@@ -225,18 +219,19 @@ export const link = async (source: string, link: string, target: string, reverse
|
|
|
225
219
|
const sourceFile = await getFor(source);
|
|
226
220
|
|
|
227
221
|
await injectLinksTo(path.resolve(await getDir(), sourceFile), [`${target}:${link}:${reverseLink}`]);
|
|
228
|
-
|
|
229
222
|
};
|
|
230
223
|
|
|
231
224
|
export const listAdrs = async () => {
|
|
232
225
|
const dir = await getDir();
|
|
233
226
|
const files = await fs.readdir(dir);
|
|
234
|
-
const toc = files
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
227
|
+
const toc = files
|
|
228
|
+
.map((f) => {
|
|
229
|
+
const adrFile = f.match(/^0*(\d+)-.*$/);
|
|
230
|
+
if (adrFile) {
|
|
231
|
+
return path.resolve(dir, adrFile[0]);
|
|
232
|
+
}
|
|
233
|
+
return '';
|
|
234
|
+
})
|
|
235
|
+
.filter((f) => f !== '');
|
|
241
236
|
return toc;
|
|
242
237
|
};
|
package/src/lib/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
1
|
import { constants } from 'fs';
|
|
3
2
|
import path from 'path';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
4
|
|
|
5
5
|
export const workingDir = () => process.cwd();
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ const findTopLevelDir = async (dir: string): Promise<string> => {
|
|
|
8
8
|
try {
|
|
9
9
|
await fs.access(path.join(dir, '.adr-dir'), constants.F_OK);
|
|
10
10
|
return dir;
|
|
11
|
-
} catch (
|
|
11
|
+
} catch (_e) {
|
|
12
12
|
if (dir === '/') {
|
|
13
13
|
throw new Error('No ADR directory config found');
|
|
14
14
|
}
|
|
@@ -22,7 +22,7 @@ const getDirPath = async (): Promise<string> => {
|
|
|
22
22
|
const configDir = await findTopLevelDir(workingDir());
|
|
23
23
|
const configFile = await fs.readFile(path.join(configDir, '.adr-dir'), 'utf8');
|
|
24
24
|
return path.relative(workingDir(), path.join(configDir, configFile.trim()));
|
|
25
|
-
} catch (
|
|
25
|
+
} catch (_e) {
|
|
26
26
|
return path.resolve(path.join(workingDir(), 'doc/adr'));
|
|
27
27
|
}
|
|
28
28
|
};
|
package/src/lib/links.test.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { vi, describe, it, expect, afterEach } from 'vitest';
|
|
2
1
|
import fs from 'fs/promises';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { getDir } from './config.js';
|
|
4
|
+
import { getLinkDetails } from './links.js';
|
|
5
5
|
|
|
6
|
-
vi.mock('./config');
|
|
6
|
+
vi.mock('./config.js');
|
|
7
7
|
vi.mock('fs/promises');
|
|
8
8
|
|
|
9
9
|
describe('The link lib', () => {
|
|
@@ -27,11 +27,7 @@ describe('The link lib', () => {
|
|
|
27
27
|
|
|
28
28
|
it('does handles multiple matches', async () => {
|
|
29
29
|
vi.mocked(getDir).mockResolvedValueOnce('/');
|
|
30
|
-
vi.mocked(fs.readdir).mockResolvedValueOnce([
|
|
31
|
-
'1-one',
|
|
32
|
-
'1-two',
|
|
33
|
-
'1-three'
|
|
34
|
-
] as any);
|
|
30
|
+
vi.mocked(fs.readdir).mockResolvedValueOnce(['1-one', '1-two', '1-three'] as any);
|
|
35
31
|
const linkString = '1:overrides:overriden by';
|
|
36
32
|
const response = await getLinkDetails(linkString);
|
|
37
33
|
expect(response).toEqual({
|
|
@@ -39,21 +35,13 @@ describe('The link lib', () => {
|
|
|
39
35
|
original: '1:overrides:overriden by',
|
|
40
36
|
link: 'overrides',
|
|
41
37
|
reverseLink: 'overriden by',
|
|
42
|
-
matches: [
|
|
43
|
-
'1-one',
|
|
44
|
-
'1-two',
|
|
45
|
-
'1-three'
|
|
46
|
-
]
|
|
38
|
+
matches: ['1-one', '1-two', '1-three']
|
|
47
39
|
});
|
|
48
40
|
});
|
|
49
41
|
|
|
50
42
|
it('returns only files that match the pattern', async () => {
|
|
51
43
|
vi.mocked(getDir).mockResolvedValueOnce('/');
|
|
52
|
-
vi.mocked(fs.readdir).mockResolvedValueOnce([
|
|
53
|
-
'on1e',
|
|
54
|
-
'1-two',
|
|
55
|
-
'three'
|
|
56
|
-
] as any);
|
|
44
|
+
vi.mocked(fs.readdir).mockResolvedValueOnce(['on1e', '1-two', 'three'] as any);
|
|
57
45
|
const linkString = '1:overrides:overriden by';
|
|
58
46
|
const response = await getLinkDetails(linkString);
|
|
59
47
|
expect(response).toEqual({
|
|
@@ -61,10 +49,7 @@ describe('The link lib', () => {
|
|
|
61
49
|
original: '1:overrides:overriden by',
|
|
62
50
|
link: 'overrides',
|
|
63
51
|
reverseLink: 'overriden by',
|
|
64
|
-
matches: [
|
|
65
|
-
'on1e',
|
|
66
|
-
'1-two'
|
|
67
|
-
]
|
|
52
|
+
matches: ['on1e', '1-two']
|
|
68
53
|
});
|
|
69
54
|
});
|
|
70
55
|
|
|
@@ -82,5 +67,4 @@ describe('The link lib', () => {
|
|
|
82
67
|
matches: []
|
|
83
68
|
});
|
|
84
69
|
});
|
|
85
|
-
|
|
86
70
|
});
|
package/src/lib/links.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
|
-
import { getDir } from './config';
|
|
2
|
+
import { getDir } from './config.js';
|
|
3
3
|
|
|
4
4
|
export interface LinkDetails {
|
|
5
5
|
pattern: string;
|
|
@@ -11,7 +11,7 @@ export interface LinkDetails {
|
|
|
11
11
|
|
|
12
12
|
export const findMatchingFilesFor = async (pattern: string) => {
|
|
13
13
|
const files = await fs.readdir(await getDir());
|
|
14
|
-
return files.filter(file => file.includes(pattern));
|
|
14
|
+
return files.filter((file) => file.includes(pattern));
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export const getLinkDetails = async (linkString: string, isSupersede: boolean = false): Promise<LinkDetails> => {
|