@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
@@ -0,0 +1,64 @@
1
+ /* eslint-disable no-sync */
2
+ import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
3
+ import * as childProcess from 'child_process';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs/promises';
6
+ import * as os from 'os';
7
+
8
+ describe('Linking Adrs', () => {
9
+ const adr = path.resolve(path.dirname(__filename), '../src/index.ts');
10
+ const command = `npx ts-node ${adr}`;
11
+
12
+ let adrDirectory: string;
13
+ let workDir: string;
14
+
15
+ beforeEach(async () => {
16
+ // @ts-ignore
17
+ process.env.ADR_DATE = '1992-01-12';
18
+ workDir = await fs.mkdtemp(path.join(os.tmpdir(), 'adr-'));
19
+ adrDirectory = path.join(workDir, 'doc/adr');
20
+ });
21
+
22
+ afterEach(() => {
23
+ vi.clearAllMocks();
24
+ childProcess.execSync(`rm -rf ${workDir}`);
25
+ });
26
+
27
+ it('should link adrs as expected with adr new', async () => {
28
+ childProcess.execSync(`${command} new First Record`, { cwd: workDir });
29
+ childProcess.execSync(`${command} new Second Record`, { cwd: workDir });
30
+ childProcess.execSync(`${command} new -q -l "1:Amends:Amended by" -l "2:Clarifies:Clarified by" Third Record`, { cwd: workDir });
31
+
32
+ const first: string = path.join(adrDirectory, '0001-first-record.md');
33
+ const second: string = path.join(adrDirectory, '0002-second-record.md');
34
+ const third: string = path.join(adrDirectory, '0003-third-record.md');
35
+
36
+ const firstContent = await fs.readFile(first, 'utf8');
37
+ const secondContent = await fs.readFile(second, 'utf8');
38
+ const thirdContent = await fs.readFile(third, 'utf8');
39
+
40
+ expect(firstContent).toMatchSnapshot();
41
+ expect(secondContent).toMatchSnapshot();
42
+ expect(thirdContent).toMatchSnapshot();
43
+ });
44
+
45
+ it('should link adrs as expected with adr link', async () => {
46
+ childProcess.execSync(`${command} new First Record`, { cwd: workDir });
47
+ childProcess.execSync(`${command} new Second Record`, { cwd: workDir });
48
+ childProcess.execSync(`${command} new Third Record`, { cwd: workDir });
49
+ childProcess.execSync(`${command} link 3 Amends 1 "Amended by"`, { cwd: workDir });
50
+ childProcess.execSync(`${command} link 3 Clarifies 2 "Clarified by"`, { cwd: workDir });
51
+
52
+ const first: string = path.join(adrDirectory, '0001-first-record.md');
53
+ const second: string = path.join(adrDirectory, '0002-second-record.md');
54
+ const third: string = path.join(adrDirectory, '0003-third-record.md');
55
+
56
+ const firstContent = await fs.readFile(first, 'utf8');
57
+ const secondContent = await fs.readFile(second, 'utf8');
58
+ const thirdContent = await fs.readFile(third, 'utf8');
59
+
60
+ expect(firstContent).toMatchSnapshot();
61
+ expect(secondContent).toMatchSnapshot();
62
+ expect(thirdContent).toMatchSnapshot();
63
+ });
64
+ });
@@ -0,0 +1,48 @@
1
+ /* eslint-disable no-sync */
2
+ import { describe, it, expect, afterEach, beforeEach } from 'vitest';
3
+ import * as childProcess from 'child_process';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs/promises';
6
+ import * as os from 'os';
7
+
8
+ describe('Listing', () => {
9
+ const adr = path.resolve(path.dirname(__filename), '../src/index.ts');
10
+ const command = `npx ts-node ${adr}`;
11
+
12
+ let adrDirectory: string;
13
+ let workDir: string;
14
+
15
+ beforeEach(async () => {
16
+ workDir = await fs.mkdtemp(path.join(os.tmpdir(), 'adr-'));
17
+ adrDirectory = path.join(workDir, 'doc/adr');
18
+ childProcess.execSync(`${command} init ${adrDirectory}`, { cwd: workDir });
19
+ });
20
+
21
+ afterEach(() => {
22
+ childProcess.execSync(`rm -rf ${workDir}`);
23
+ });
24
+
25
+ it('should list an empty directory', async () => {
26
+ const child = childProcess.execSync(`${command} list`, { cwd: workDir });
27
+ const output = child.toString().trim();
28
+ expect(output).toEqual('doc/adr/0001-record-architecture-decisions.md');
29
+ });
30
+
31
+ it('should list when there is an additional one', async () => {
32
+ childProcess.execSync(`${command} new first`, { cwd: workDir });
33
+ const child = childProcess.execSync(`${command} list`, { cwd: workDir });
34
+ const output = child.toString().trim();
35
+ expect(output).toEqual('doc/adr/0001-record-architecture-decisions.md\ndoc/adr/0002-first.md');
36
+ });
37
+
38
+ it('should list when there are more', async () => {
39
+ childProcess.execSync(`${command} new first`, { cwd: workDir });
40
+ childProcess.execSync(`${command} new second`, { cwd: workDir });
41
+ childProcess.execSync(`${command} new third`, { cwd: workDir });
42
+ const child = childProcess.execSync(`${command} list`, { cwd: workDir });
43
+ const output = child.toString().trim();
44
+ expect(output).toEqual('doc/adr/0001-record-architecture-decisions.md\ndoc/adr/0002-first.md\ndoc/adr/0003-second.md\ndoc/adr/0004-third.md');
45
+ });
46
+
47
+ });
48
+
@@ -0,0 +1,58 @@
1
+ /* eslint-disable no-sync */
2
+ import { describe, it, expect, afterEach, beforeEach } from 'vitest';
3
+ import * as childProcess from 'child_process';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs';
6
+ import * as os from 'os';
7
+
8
+ describe('New Adrs', () => {
9
+ const adr = path.resolve(path.dirname(__filename), '../src/index.ts');
10
+ const command = `npx ts-node ${adr}`;
11
+
12
+ let adrDirectory: string;
13
+ let workDir: string;
14
+
15
+ beforeEach(() => {
16
+ // @ts-ignore
17
+ process.env.ADR_DATE = '1992-01-12';
18
+ workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adr-'));
19
+ adrDirectory = path.join(workDir, 'doc/adr');
20
+ });
21
+
22
+ afterEach(() => {
23
+ childProcess.execSync(`rm -rf ${workDir}`);
24
+ });
25
+
26
+ it('should create a new one normally', () => {
27
+ childProcess.execSync(`${command} init ${adrDirectory}`, { cwd: workDir });
28
+ childProcess.execSync(`${command} new Example ADR`, { cwd: workDir });
29
+
30
+ const expectedNewFile: string = path.join(adrDirectory, '0002-example-adr.md');
31
+ expect(fs.existsSync(expectedNewFile)).toBeTruthy();
32
+
33
+ const fileContents = fs.readFileSync(expectedNewFile, 'utf8');
34
+ expect(fileContents).toMatchSnapshot();
35
+ });
36
+
37
+ it('should create a new one even if no config exists', () => {
38
+ childProcess.execSync(`${command} new Example ADR`, { cwd: workDir });
39
+
40
+ const expectedNewFile: string = path.join(adrDirectory, '0001-example-adr.md');
41
+ expect(fs.existsSync(expectedNewFile)).toBeTruthy();
42
+
43
+ const fileContents = fs.readFileSync(expectedNewFile, 'utf8');
44
+ expect(fileContents).toMatchSnapshot();
45
+ });
46
+
47
+ it('should create a table of contents upon creation', () => {
48
+ childProcess.execSync(`${command} init ${adrDirectory}`, { cwd: workDir });
49
+ childProcess.execSync(`${command} new Example ADR`, { cwd: workDir });
50
+
51
+ const expectedNewFile: string = path.join(adrDirectory, 'decisions.md');
52
+ expect(fs.existsSync(expectedNewFile)).toBeTruthy();
53
+
54
+ const fileContents = fs.readFileSync(expectedNewFile, 'utf8');
55
+ expect(fileContents).toMatchSnapshot();
56
+ });
57
+
58
+ });
@@ -0,0 +1,58 @@
1
+ /* eslint-disable no-sync */
2
+ import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
3
+ import * as childProcess from 'child_process';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs/promises';
6
+ import * as os from 'os';
7
+
8
+ describe('Superseding Adrs', () => {
9
+ const adr = path.resolve(path.dirname(__filename), '../src/index.ts');
10
+ const command = `npx ts-node ${adr}`;
11
+
12
+ let adrDirectory: string;
13
+ let workDir: string;
14
+
15
+ beforeEach(async () => {
16
+ // @ts-ignore
17
+ process.env.ADR_DATE = '1992-01-12';
18
+ workDir = await fs.mkdtemp(path.join(os.tmpdir(), 'adr-'));
19
+ adrDirectory = path.join(workDir, 'doc/adr');
20
+ });
21
+
22
+ afterEach(() => {
23
+ vi.clearAllMocks();
24
+ childProcess.execSync(`rm -rf ${workDir}`);
25
+ });
26
+
27
+ it('should be able to supersede previous adrs', async () => {
28
+ childProcess.execSync(`${command} new First Record`, { cwd: workDir });
29
+ childProcess.execSync(`${command} new -s 1 Second Record`, { cwd: workDir });
30
+
31
+ const first: string = path.join(adrDirectory, '0001-first-record.md');
32
+ const second: string = path.join(adrDirectory, '0002-second-record.md');
33
+
34
+ const firstContent = await fs.readFile(first, 'utf8');
35
+ const secondContent = await fs.readFile(second, 'utf8');
36
+
37
+ expect(firstContent).toMatchSnapshot();
38
+ expect(secondContent).toMatchSnapshot();
39
+ });
40
+
41
+ it('should be able to supersede multiple records', async () => {
42
+ childProcess.execSync(`${command} new First Record`, { cwd: workDir });
43
+ childProcess.execSync(`${command} new Second Record`, { cwd: workDir });
44
+ childProcess.execSync(`${command} new -s 1 -s 2 Third Record`, { cwd: workDir });
45
+
46
+ const first: string = path.join(adrDirectory, '0001-first-record.md');
47
+ const second: string = path.join(adrDirectory, '0002-second-record.md');
48
+ const third: string = path.join(adrDirectory, '0003-third-record.md');
49
+
50
+ const firstContent = await fs.readFile(first, 'utf8');
51
+ const secondContent = await fs.readFile(second, 'utf8');
52
+ const thirdContent = await fs.readFile(third, 'utf8');
53
+
54
+ expect(firstContent).toMatchSnapshot();
55
+ expect(secondContent).toMatchSnapshot();
56
+ expect(thirdContent).toMatchSnapshot();
57
+ });
58
+ });
@@ -0,0 +1,38 @@
1
+ /* eslint-disable no-sync */
2
+ import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
3
+ import * as childProcess from 'child_process';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs/promises';
6
+ import * as os from 'os';
7
+
8
+ describe('Generating TOC', () => {
9
+ const adr = path.resolve(path.dirname(__filename), '../src/index.ts');
10
+ const command = `npx ts-node ${adr}`;
11
+
12
+ let adrDirectory: string;
13
+ let workDir: string;
14
+
15
+ beforeEach(async () => {
16
+ // @ts-ignore
17
+ process.env.ADR_DATE = '1992-01-12';
18
+ workDir = await fs.mkdtemp(path.join(os.tmpdir(), 'adr-'));
19
+ adrDirectory = path.join(workDir, 'doc/adr');
20
+ });
21
+
22
+ afterEach(() => {
23
+ vi.clearAllMocks();
24
+ childProcess.execSync(`rm -rf ${workDir}`);
25
+ });
26
+
27
+ it('should add a path prefix to the toc when there is one supplied', async () => {
28
+ childProcess.execSync(`${command} new First Record`, { cwd: workDir });
29
+ childProcess.execSync(`${command} new Second Record`, { cwd: workDir });
30
+ childProcess.execSync(`${command} new Third Record`, { cwd: workDir });
31
+ childProcess.execSync(`${command} generate toc -p foo/doc/adr/`, { cwd: workDir });
32
+
33
+ const tocFilePath: string = path.join(adrDirectory, 'decisions.md');
34
+ const tocContent = await fs.readFile(tocFilePath, 'utf8');
35
+ expect(tocContent).toMatchSnapshot();
36
+ });
37
+
38
+ });
@@ -0,0 +1,43 @@
1
+ /* eslint-disable no-sync */
2
+ import { describe, it, expect, afterEach, beforeEach } from 'vitest';
3
+ import * as childProcess from 'child_process';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs/promises';
6
+ import * as os from 'os';
7
+
8
+ describe('Overriding templates', () => {
9
+ const adr = path.resolve(path.dirname(__filename), '../src/index.ts');
10
+ const command = `npx ts-node ${adr}`;
11
+
12
+ let adrDirectory: string;
13
+ let workDir: string;
14
+
15
+ beforeEach(async () => {
16
+ // @ts-ignore
17
+ process.env.ADR_DATE = '1992-01-12';
18
+ workDir = await fs.mkdtemp(path.join(os.tmpdir(), 'adr-'));
19
+ adrDirectory = path.join(workDir, 'doc/adr');
20
+ childProcess.execSync(`${command} init ${adrDirectory}`, { cwd: workDir });
21
+ });
22
+
23
+ afterEach(() => {
24
+ childProcess.execSync(`rm -rf ${workDir}`);
25
+ });
26
+
27
+ it('should use an override template if one exists', async () => {
28
+ await fs.mkdir(path.join(adrDirectory, 'templates'), { recursive: true });
29
+ await fs.writeFile(path.join(adrDirectory, 'templates', 'template.md'), '# This is an override template\nTITLE\nDATE\nNUMBER\nSTATUS');
30
+
31
+ childProcess.execSync(`${command} new Example ADR`, { cwd: workDir });
32
+ childProcess.execSync(`${command} new Another Example ADR`, { cwd: workDir });
33
+
34
+ const expectedFile: string = path.join(adrDirectory, '0002-example-adr.md');
35
+ const expectedFile2: string = path.join(adrDirectory, '0003-another-example-adr.md');
36
+
37
+ const fileContents = await fs.readFile(expectedFile, 'utf8');
38
+ const fileContents2 = await fs.readFile(expectedFile2, 'utf8');
39
+
40
+ expect(fileContents).toMatchSnapshot();
41
+ expect(fileContents2).toMatchSnapshot();
42
+ });
43
+ });
@@ -0,0 +1,44 @@
1
+ /* eslint-disable no-sync */
2
+ import { describe, it, expect, afterEach, beforeEach } from 'vitest';
3
+ import * as childProcess from 'child_process';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs';
6
+ import * as os from 'os';
7
+
8
+ describe('deep directories', () => {
9
+
10
+ const adr = path.resolve(path.dirname(__filename), '../src/index.ts');
11
+ const command = `npx ts-node ${adr}`;
12
+
13
+ let adrDirectory: string;
14
+ let workDir: string;
15
+
16
+ beforeEach(() => {
17
+ workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adr-'));
18
+ adrDirectory = path.join(workDir, 'doc/adr');
19
+ childProcess.execSync(`${command} init ${adrDirectory}`, { cwd: workDir });
20
+ });
21
+
22
+ afterEach(() => {
23
+ childProcess.execSync(`rm -rf ${workDir}`);
24
+ });
25
+
26
+ it('can work', () => {
27
+ const innerPath = path.join(fs.mkdtempSync(path.resolve(workDir) + '/'), 'inner');
28
+ fs.mkdirSync(innerPath, { recursive: true });
29
+ childProcess.execSync(`${command} new this should exist`, { cwd: innerPath });
30
+ const expectedFile: string = path.join(adrDirectory, '0002-this-should-exist.md');
31
+ expect(fs.existsSync(expectedFile)).toBeTruthy();
32
+ });
33
+
34
+ it('can work when there has been no config initiated', () => {
35
+ childProcess.execSync(`rm -rf ${adrDirectory} ${workDir}/.adr-dir`);
36
+
37
+ const innerPath = path.join(fs.mkdtempSync(path.resolve(workDir) + '/'), 'inner');
38
+ fs.mkdirSync(innerPath, { recursive: true });
39
+ childProcess.execSync(`${command} new this should exist`, { cwd: innerPath });
40
+ const expectedFile: string = path.join(innerPath, 'doc', 'adr', '0001-this-should-exist.md');
41
+ expect(fs.existsSync(expectedFile)).toBeTruthy();
42
+ });
43
+ });
44
+
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": "@meza/tsconfig-base",
3
+ "compilerOptions": {
4
+ "target": "es6",
5
+ "module": "commonjs",
6
+ "emitDeclarationOnly": false,
7
+ "skipDefaultLibCheck": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "./dist",
10
+ "declarationDir": "./dist/types",
11
+ "isolatedModules": true,
12
+ "sourceRoot": "src"
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "tests/**/*"],
16
+ "ts-node": {
17
+ "compilerOptions": {
18
+ "module": "commonjs",
19
+ "noPropertyAccessFromIndexSignature": false
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ testTimeout: 10000,
6
+ watch: false,
7
+ coverage: {
8
+ reportsDirectory: './reports/coverage',
9
+ reporter: ['text', 'json', 'html']
10
+ }
11
+ }
12
+ });