@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
|
@@ -1,49 +1,50 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { getLinksFrom, getTitleFrom, injectLink } from './manipulator.js';
|
|
3
3
|
|
|
4
4
|
describe('The ADR manipulator', () => {
|
|
5
|
+
const original =
|
|
6
|
+
'# NUMBER. TITLE\n' +
|
|
7
|
+
'\n' +
|
|
8
|
+
'Date: DATE\n' +
|
|
9
|
+
'\n' +
|
|
10
|
+
'## Status\n' +
|
|
11
|
+
'\n' +
|
|
12
|
+
'STATUS\n' +
|
|
13
|
+
'\n' +
|
|
14
|
+
'## Context\n' +
|
|
15
|
+
'\n' +
|
|
16
|
+
'The issue motivating this decision, and any context that influences or constrains the decision.\n' +
|
|
17
|
+
'\n' +
|
|
18
|
+
'## Decision\n' +
|
|
19
|
+
'\n' +
|
|
20
|
+
"The change that we're proposing or have agreed to implement.\n" +
|
|
21
|
+
'\n' +
|
|
22
|
+
'## Consequences\n' +
|
|
23
|
+
'\n' +
|
|
24
|
+
'What becomes easier or more difficult to do and any risks introduced by the change that will need to be mitigated.\n';
|
|
5
25
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
+ 'Date: DATE\n'
|
|
29
|
-
+ '\n'
|
|
30
|
-
+ '## Status\n'
|
|
31
|
-
+ '\n'
|
|
32
|
-
+ 'STATUS\n'
|
|
33
|
-
+ '\n'
|
|
34
|
-
+ 'INJECTED STUFF\n'
|
|
35
|
-
+ '\n'
|
|
36
|
-
+ '## Context\n'
|
|
37
|
-
+ '\n'
|
|
38
|
-
+ 'The issue motivating this decision, and any context that influences or constrains the decision.\n'
|
|
39
|
-
+ '\n'
|
|
40
|
-
+ '## Decision\n'
|
|
41
|
-
+ '\n'
|
|
42
|
-
+ 'The change that we\'re proposing or have agreed to implement.\n'
|
|
43
|
-
+ '\n'
|
|
44
|
-
+ '## Consequences\n'
|
|
45
|
-
+ '\n'
|
|
46
|
-
+ 'What becomes easier or more difficult to do and any risks introduced by the change that will need to be mitigated.\n';
|
|
26
|
+
const modified =
|
|
27
|
+
'# NUMBER. TITLE\n' +
|
|
28
|
+
'\n' +
|
|
29
|
+
'Date: DATE\n' +
|
|
30
|
+
'\n' +
|
|
31
|
+
'## Status\n' +
|
|
32
|
+
'\n' +
|
|
33
|
+
'STATUS\n' +
|
|
34
|
+
'\n' +
|
|
35
|
+
'INJECTED STUFF\n' +
|
|
36
|
+
'\n' +
|
|
37
|
+
'## Context\n' +
|
|
38
|
+
'\n' +
|
|
39
|
+
'The issue motivating this decision, and any context that influences or constrains the decision.\n' +
|
|
40
|
+
'\n' +
|
|
41
|
+
'## Decision\n' +
|
|
42
|
+
'\n' +
|
|
43
|
+
"The change that we're proposing or have agreed to implement.\n" +
|
|
44
|
+
'\n' +
|
|
45
|
+
'## Consequences\n' +
|
|
46
|
+
'\n' +
|
|
47
|
+
'What becomes easier or more difficult to do and any risks introduced by the change that will need to be mitigated.\n';
|
|
47
48
|
|
|
48
49
|
it('can inject links to the status section', () => {
|
|
49
50
|
const test = injectLink(original, 'INJECTED STUFF');
|
|
@@ -70,9 +71,7 @@ describe('The ADR manipulator', () => {
|
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
it('can extract links from the status section', () => {
|
|
73
|
-
const original = '## Status\n'
|
|
74
|
-
+ '\n'
|
|
75
|
-
+ 'Superseded by [3. title here](and-a-link-here.md)\n';
|
|
74
|
+
const original = '## Status\n' + '\n' + 'Superseded by [3. title here](and-a-link-here.md)\n';
|
|
76
75
|
const extractedLink = getLinksFrom(original);
|
|
77
76
|
|
|
78
77
|
expect(extractedLink[0]).toEqual({
|
|
@@ -82,7 +81,5 @@ describe('The ADR manipulator', () => {
|
|
|
82
81
|
href: 'and-a-link-here.md',
|
|
83
82
|
raw: 'Superseded by [3. title here](and-a-link-here.md)'
|
|
84
83
|
});
|
|
85
|
-
|
|
86
84
|
});
|
|
87
|
-
|
|
88
85
|
});
|
package/src/lib/manipulator.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { marked } from 'marked';
|
|
2
2
|
|
|
3
3
|
const convertToMd = (tokens: marked.Token[]) => {
|
|
4
|
-
return tokens.map(token => token.raw).join('');
|
|
4
|
+
return tokens.map((token) => token.raw).join('');
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
export const getLinksFrom = (markdown: string) => {
|
|
8
8
|
const tokens = marked.lexer(markdown);
|
|
9
9
|
const linkRegex = new RegExp(/^(.*)\s\[(.*)\]\((.*)\)$/);
|
|
10
|
-
const links = tokens.filter(token => token.type === 'paragraph' && linkRegex.test(token.text));
|
|
10
|
+
const links = tokens.filter((token) => token.type === 'paragraph' && linkRegex.test(token.text));
|
|
11
11
|
|
|
12
12
|
return links.map((link) => {
|
|
13
13
|
const linkToken = link as marked.Tokens.Paragraph;
|
|
@@ -27,7 +27,7 @@ export const getLinksFrom = (markdown: string) => {
|
|
|
27
27
|
|
|
28
28
|
export const getTitleFrom = (adr: string) => {
|
|
29
29
|
const tokens = marked.lexer(adr);
|
|
30
|
-
const mainHead = tokens.find(token => token.type === 'heading' && token.depth === 1);
|
|
30
|
+
const mainHead = tokens.find((token) => token.type === 'heading' && token.depth === 1);
|
|
31
31
|
if (!mainHead) {
|
|
32
32
|
throw new Error('No main heading found');
|
|
33
33
|
}
|
|
@@ -36,20 +36,27 @@ export const getTitleFrom = (adr: string) => {
|
|
|
36
36
|
|
|
37
37
|
export const supersede = (markdown: string, link: string) => {
|
|
38
38
|
const tokens = marked.lexer(markdown);
|
|
39
|
-
const statusIndex = tokens.findIndex(token => token.type === 'heading' && token.text.toLowerCase() === 'status');
|
|
39
|
+
const statusIndex = tokens.findIndex((token) => token.type === 'heading' && token.text.toLowerCase() === 'status');
|
|
40
40
|
if (statusIndex < 0) {
|
|
41
41
|
throw new Error('Could not find status section');
|
|
42
42
|
}
|
|
43
43
|
const statusDepth = (tokens[statusIndex] as marked.Tokens.Heading).depth;
|
|
44
|
-
const followingHeadingIndex = tokens.findIndex(
|
|
45
|
-
|
|
44
|
+
const followingHeadingIndex = tokens.findIndex(
|
|
45
|
+
(token, index) => token.type === 'heading' && token.depth === statusDepth && index > statusIndex
|
|
46
|
+
);
|
|
47
|
+
const followingParagraphIndex = tokens.findIndex(
|
|
48
|
+
(token, index) => token.type === 'paragraph' && index > statusIndex && index < followingHeadingIndex
|
|
49
|
+
);
|
|
46
50
|
|
|
47
51
|
if (followingParagraphIndex > followingHeadingIndex || followingParagraphIndex === -1) {
|
|
48
52
|
throw new Error('There is no status paragraph. Please format your adr properly');
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
tokens[followingParagraphIndex] = {
|
|
52
|
-
type: 'paragraph',
|
|
56
|
+
type: 'paragraph',
|
|
57
|
+
text: link,
|
|
58
|
+
raw: link,
|
|
59
|
+
tokens: [
|
|
53
60
|
{
|
|
54
61
|
type: 'text',
|
|
55
62
|
raw: link,
|
|
@@ -62,18 +69,23 @@ export const supersede = (markdown: string, link: string) => {
|
|
|
62
69
|
|
|
63
70
|
export const injectLink = (markdown: string, link: string) => {
|
|
64
71
|
const tokens = marked.lexer(markdown);
|
|
65
|
-
const statusIndex = tokens.findIndex(token => token.type === 'heading' && token.text.toLowerCase() === 'status');
|
|
72
|
+
const statusIndex = tokens.findIndex((token) => token.type === 'heading' && token.text.toLowerCase() === 'status');
|
|
66
73
|
if (statusIndex < 0) {
|
|
67
74
|
throw new Error('Could not find status section');
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
const statusDepth = (tokens[statusIndex] as marked.Tokens.Heading).depth;
|
|
71
|
-
let followingHeadingIndex = tokens.findIndex(
|
|
78
|
+
let followingHeadingIndex = tokens.findIndex(
|
|
79
|
+
(token, index) => token.type === 'heading' && token.depth === statusDepth && index > statusIndex
|
|
80
|
+
);
|
|
72
81
|
if (followingHeadingIndex < 0) {
|
|
73
82
|
followingHeadingIndex = tokens.length;
|
|
74
83
|
}
|
|
75
84
|
tokens.splice(followingHeadingIndex, 0, {
|
|
76
|
-
type: 'paragraph',
|
|
85
|
+
type: 'paragraph',
|
|
86
|
+
text: link,
|
|
87
|
+
raw: link,
|
|
88
|
+
tokens: [
|
|
77
89
|
{
|
|
78
90
|
type: 'text',
|
|
79
91
|
raw: link,
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { afterAll, describe, it, vi, expect } from 'vitest';
|
|
2
1
|
import fs from 'fs/promises';
|
|
3
|
-
import {
|
|
2
|
+
import { afterAll, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { newNumber } from './numbering.js';
|
|
4
4
|
|
|
5
|
-
type ReaddirMock = () => Promise<
|
|
5
|
+
type ReaddirMock = () => Promise<string[]>;
|
|
6
6
|
|
|
7
7
|
vi.mock('fs/promises');
|
|
8
|
-
vi.mock('./config', () => ({
|
|
8
|
+
vi.mock('./config.js', () => ({
|
|
9
9
|
getDir: vi.fn().mockResolvedValue('/')
|
|
10
10
|
}));
|
|
11
11
|
|
|
12
12
|
describe('The numbering logic', () => {
|
|
13
|
-
|
|
14
13
|
afterAll(() => {
|
|
15
14
|
vi.resetAllMocks();
|
|
16
15
|
});
|
|
@@ -29,10 +28,7 @@ describe('The numbering logic', () => {
|
|
|
29
28
|
});
|
|
30
29
|
|
|
31
30
|
it('processes existing files if there is no lockfile', async () => {
|
|
32
|
-
const fakeFiles: string[] = [
|
|
33
|
-
'0001-first-file.md',
|
|
34
|
-
'0002-first-file.md'
|
|
35
|
-
];
|
|
31
|
+
const fakeFiles: string[] = ['0001-first-file.md', '0002-first-file.md'];
|
|
36
32
|
vi.mocked(fs.readFile).mockRejectedValueOnce('no sequence file');
|
|
37
33
|
vi.mocked(fs.readdir as unknown as ReaddirMock).mockResolvedValueOnce(fakeFiles);
|
|
38
34
|
const num = await newNumber();
|
package/src/lib/numbering.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import { getDir } from './config';
|
|
3
1
|
import * as path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { getDir } from './config.js';
|
|
4
4
|
|
|
5
5
|
export const newNumber = async () => {
|
|
6
6
|
try {
|
|
7
7
|
const lockfile = await fs.readFile(path.resolve(await getDir(), '.adr-sequence.lock'));
|
|
8
8
|
return parseInt(lockfile.toString().trim(), 10) + 1;
|
|
9
|
-
} catch (
|
|
9
|
+
} catch (_e) {
|
|
10
10
|
// This is for backward compatibility. If someone upgrades from an older tool without a lockfile,
|
|
11
11
|
// we create one.
|
|
12
12
|
const filePattern = /^0*(\d+)-.*$/;
|
|
13
13
|
const files = await fs.readdir(await getDir());
|
|
14
|
-
const numbers = files.map(f => {
|
|
14
|
+
const numbers = files.map((f) => {
|
|
15
15
|
const adrFile = f.match(filePattern);
|
|
16
16
|
if (adrFile) {
|
|
17
17
|
return parseInt(adrFile[1], 10);
|
|
@@ -22,5 +22,4 @@ export const newNumber = async () => {
|
|
|
22
22
|
const largestNumber = numbers.reduce((a, b) => Math.max(a, b), 0);
|
|
23
23
|
return largestNumber + 1;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
25
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import inquirer, { ListQuestion, Question } from 'inquirer';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { askForClarification } from './prompt.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('inquirer');
|
|
7
|
+
vi.mock('chalk', () => ({
|
|
8
|
+
default: {
|
|
9
|
+
blue: (str: string) => {
|
|
10
|
+
return `BLUE[${str}]`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}));
|
|
14
|
+
describe('The prompt helper', () => {
|
|
15
|
+
it('calls inquirer correctly', async () => {
|
|
16
|
+
const prompt = vi.mocked(inquirer.prompt);
|
|
17
|
+
prompt.mockResolvedValueOnce({ target: 'does not matter' });
|
|
18
|
+
|
|
19
|
+
const randomChoices = [faker.system.filePath(), faker.system.filePath(), faker.system.filePath()];
|
|
20
|
+
await askForClarification('this is the search string', randomChoices);
|
|
21
|
+
|
|
22
|
+
expect(prompt).toHaveBeenCalledOnce();
|
|
23
|
+
const calledWith = prompt.mock.calls[0][0] as Question[];
|
|
24
|
+
expect(calledWith[0].type).toEqual('list');
|
|
25
|
+
expect((calledWith[0] as ListQuestion).choices).toEqual(randomChoices);
|
|
26
|
+
expect(calledWith[0].message).toMatchInlineSnapshot(
|
|
27
|
+
'"Which file do you want to link to for BLUE[this is the search string]?"'
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns the selected choice', async () => {
|
|
32
|
+
const prompt = vi.mocked(inquirer.prompt);
|
|
33
|
+
|
|
34
|
+
const randomResult = faker.system.filePath();
|
|
35
|
+
|
|
36
|
+
prompt.mockResolvedValueOnce({ target: randomResult });
|
|
37
|
+
|
|
38
|
+
const result = await askForClarification('this is the search string', ['does not matter']);
|
|
39
|
+
|
|
40
|
+
expect(result).toEqual(randomResult);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
|
|
4
|
+
export const askForClarification = async (searchString: string, matches: string[]) => {
|
|
5
|
+
const selection = await inquirer.prompt([
|
|
6
|
+
{
|
|
7
|
+
type: 'list',
|
|
8
|
+
name: 'target',
|
|
9
|
+
message: `Which file do you want to link to for ${chalk.blue(searchString)}?`,
|
|
10
|
+
choices: matches
|
|
11
|
+
}
|
|
12
|
+
]);
|
|
13
|
+
return selection.target;
|
|
14
|
+
};
|
package/src/lib/template.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
1
3
|
import fs from 'fs/promises';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
+
import { getDir } from './config.js';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
4
8
|
|
|
5
9
|
export const template = async (templateFile?: string): Promise<string> => {
|
|
6
10
|
if (templateFile) {
|
|
@@ -12,7 +16,7 @@ export const template = async (templateFile?: string): Promise<string> => {
|
|
|
12
16
|
|
|
13
17
|
try {
|
|
14
18
|
return await fs.readFile(path.join(await getDir(), 'templates/template.md'), 'utf8');
|
|
15
|
-
} catch (
|
|
19
|
+
} catch (_e) {
|
|
16
20
|
return await fs.readFile(path.resolve(path.join(__dirname, '../templates/template.md')), 'utf8');
|
|
17
21
|
}
|
|
18
22
|
};
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const LIB_VERSION = '
|
|
1
|
+
export const LIB_VERSION = '1.0.11';
|
|
2
2
|
|
package/tests/.adr-dir
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
f8877fa6-908f-4cf5-91cb-9a910b9376f7
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
// Vitest Snapshot v1
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
3
|
exports[`Generating Graphs > should generate a graph 1`] = `
|
|
4
4
|
"digraph {
|
|
5
5
|
node [shape=plaintext];
|
|
6
6
|
subgraph {
|
|
7
|
-
_1 [label
|
|
8
|
-
_2 [label
|
|
9
|
-
_1 -> _2 [style
|
|
10
|
-
_3 [label
|
|
11
|
-
_2 -> _3 [style
|
|
12
|
-
_4 [label
|
|
13
|
-
_3 -> _4 [style
|
|
14
|
-
_5 [label
|
|
15
|
-
_4 -> _5 [style
|
|
7
|
+
_1 [label="1. Record architecture decisions"; URL="0001-record-architecture-decisions.html"];
|
|
8
|
+
_2 [label="2. An idea that seems good at the time"; URL="0002-an-idea-that-seems-good-at-the-time.html"];
|
|
9
|
+
_1 -> _2 [style="dotted", weight=1];
|
|
10
|
+
_3 [label="3. A better idea"; URL="0003-a-better-idea.html"];
|
|
11
|
+
_2 -> _3 [style="dotted", weight=1];
|
|
12
|
+
_4 [label="4. This will work"; URL="0004-this-will-work.html"];
|
|
13
|
+
_3 -> _4 [style="dotted", weight=1];
|
|
14
|
+
_5 [label="5. The end"; URL="0005-the-end.html"];
|
|
15
|
+
_4 -> _5 [style="dotted", weight=1];
|
|
16
16
|
}
|
|
17
|
-
_3 -> _2 [label
|
|
18
|
-
_5 -> _3 [label
|
|
17
|
+
_3 -> _2 [label="Supersedes", weight=0]
|
|
18
|
+
_5 -> _3 [label="Supersedes", weight=0]
|
|
19
19
|
}"
|
|
20
20
|
`;
|
|
21
21
|
|
|
@@ -23,17 +23,17 @@ exports[`Generating Graphs > should generate a graph with specified route and ex
|
|
|
23
23
|
"digraph {
|
|
24
24
|
node [shape=plaintext];
|
|
25
25
|
subgraph {
|
|
26
|
-
_1 [label
|
|
27
|
-
_2 [label
|
|
28
|
-
_1 -> _2 [style
|
|
29
|
-
_3 [label
|
|
30
|
-
_2 -> _3 [style
|
|
31
|
-
_4 [label
|
|
32
|
-
_3 -> _4 [style
|
|
33
|
-
_5 [label
|
|
34
|
-
_4 -> _5 [style
|
|
26
|
+
_1 [label="1. Record architecture decisions"; URL="http://example.com/0001-record-architecture-decisions.xxx"];
|
|
27
|
+
_2 [label="2. An idea that seems good at the time"; URL="http://example.com/0002-an-idea-that-seems-good-at-the-time.xxx"];
|
|
28
|
+
_1 -> _2 [style="dotted", weight=1];
|
|
29
|
+
_3 [label="3. A better idea"; URL="http://example.com/0003-a-better-idea.xxx"];
|
|
30
|
+
_2 -> _3 [style="dotted", weight=1];
|
|
31
|
+
_4 [label="4. This will work"; URL="http://example.com/0004-this-will-work.xxx"];
|
|
32
|
+
_3 -> _4 [style="dotted", weight=1];
|
|
33
|
+
_5 [label="5. The end"; URL="http://example.com/0005-the-end.xxx"];
|
|
34
|
+
_4 -> _5 [style="dotted", weight=1];
|
|
35
35
|
}
|
|
36
|
-
_3 -> _2 [label
|
|
37
|
-
_5 -> _3 [label
|
|
36
|
+
_3 -> _2 [label="Supersedes", weight=0]
|
|
37
|
+
_5 -> _3 [label="Supersedes", weight=0]
|
|
38
38
|
}"
|
|
39
39
|
`;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
/* eslint-disable no-sync */
|
|
2
|
-
import { describe, it, expect, afterEach, beforeEach } from 'vitest';
|
|
3
1
|
import * as childProcess from 'child_process';
|
|
4
|
-
import * as path from 'path';
|
|
5
2
|
import * as fs from 'fs';
|
|
6
3
|
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
/* eslint-disable no-sync */
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
8
|
describe('Edit new Adrs on creation', () => {
|
|
9
9
|
const adr = path.resolve(path.dirname(__filename), '../src/index.ts');
|
|
10
|
-
const command = `npx
|
|
10
|
+
const command = `npx tsx ${adr}`;
|
|
11
11
|
const visualHelper = path.resolve(path.dirname(__filename), './fake-visual');
|
|
12
12
|
const editorHelper = path.resolve(path.dirname(__filename), './fake-editor');
|
|
13
13
|
|
|
@@ -17,24 +17,28 @@ describe('Edit new Adrs on creation', () => {
|
|
|
17
17
|
beforeEach(() => {
|
|
18
18
|
// @ts-ignore
|
|
19
19
|
process.env.ADR_DATE = '1992-01-12';
|
|
20
|
-
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adr-'));
|
|
21
|
-
adrDirectory = path.join(workDir, 'doc/adr');
|
|
20
|
+
workDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'adr-')));
|
|
21
|
+
adrDirectory = path.resolve(path.join(workDir, 'doc/adr'));
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
afterEach(() => {
|
|
25
|
-
|
|
25
|
+
fs.rmdirSync(workDir, {
|
|
26
|
+
recursive: true,
|
|
27
|
+
maxRetries: 3,
|
|
28
|
+
retryDelay: 500
|
|
29
|
+
});
|
|
26
30
|
});
|
|
27
31
|
|
|
28
32
|
it('should open a new ADR in the VISUAL', () => {
|
|
29
|
-
childProcess.execSync(`VISUAL="${visualHelper}" ${command} new Example ADR`, { cwd: workDir });
|
|
33
|
+
childProcess.execSync(`VISUAL="${visualHelper}" ${command} new Example ADR`, { timeout: 10000, cwd: workDir });
|
|
30
34
|
|
|
31
35
|
const expectedNewFile: string = path.join(workDir, 'visual.out');
|
|
32
36
|
const fileContents = fs.readFileSync(expectedNewFile, 'utf8');
|
|
33
37
|
expect(fileContents.trim()).toEqual(`VISUAL ${adrDirectory}/0001-example-adr.md`);
|
|
34
38
|
});
|
|
35
39
|
|
|
36
|
-
it('should open a new ADR in the EDITOR', () => {
|
|
37
|
-
childProcess.execSync(`EDITOR="${editorHelper}" ${command} new Example ADR`, { cwd: workDir });
|
|
40
|
+
it.skip('should open a new ADR in the EDITOR', () => {
|
|
41
|
+
childProcess.execSync(`EDITOR="${editorHelper}" ${command} new Example ADR`, { timeout: 10000, cwd: workDir });
|
|
38
42
|
|
|
39
43
|
const expectedNewFile: string = path.join(workDir, 'editor.out');
|
|
40
44
|
const fileContents = fs.readFileSync(expectedNewFile, 'utf8');
|
|
@@ -42,7 +46,9 @@ describe('Edit new Adrs on creation', () => {
|
|
|
42
46
|
});
|
|
43
47
|
|
|
44
48
|
it('should open a new ADR in the VISUAL if both VISUAL and EDITOR is supplied', () => {
|
|
45
|
-
childProcess.execSync(`EDITOR="${editorHelper}" VISUAL="${visualHelper}" ${command} new Example ADR`, {
|
|
49
|
+
childProcess.execSync(`EDITOR="${editorHelper}" VISUAL="${visualHelper}" ${command} new Example ADR`, {
|
|
50
|
+
cwd: workDir
|
|
51
|
+
});
|
|
46
52
|
|
|
47
53
|
expect(fs.existsSync(path.join(workDir, 'editor.out'))).toBeFalsy();
|
|
48
54
|
|
|
@@ -50,5 +56,4 @@ describe('Edit new Adrs on creation', () => {
|
|
|
50
56
|
const fileContents = fs.readFileSync(expectedNewFile, 'utf8');
|
|
51
57
|
expect(fileContents.trim()).toEqual(`VISUAL ${adrDirectory}/0001-example-adr.md`);
|
|
52
58
|
});
|
|
53
|
-
|
|
54
59
|
});
|
|
@@ -1,43 +1,50 @@
|
|
|
1
|
+
import * as childProcess from 'child_process';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
|
|
1
7
|
/* eslint-disable no-sync */
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const adr: string = path.resolve(workDir, '../src/index.ts');
|
|
11
|
-
const command = `npx ts-node ${adr}`;
|
|
12
|
-
let randomDir = uuidv4();
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
|
|
12
|
+
describe.skip('Funny Characters', () => {
|
|
13
|
+
const adr: string = path.resolve(path.dirname(__filename), '../src/index.ts');
|
|
14
|
+
const command = `npx tsx ${adr}`;
|
|
15
|
+
|
|
13
16
|
let adrDirectory: string;
|
|
17
|
+
let workDir: string;
|
|
14
18
|
|
|
15
19
|
afterEach(() => {
|
|
16
|
-
|
|
20
|
+
fs.rmdirSync(workDir, {
|
|
21
|
+
recursive: true,
|
|
22
|
+
maxRetries: 3,
|
|
23
|
+
retryDelay: 500
|
|
24
|
+
});
|
|
17
25
|
});
|
|
18
26
|
|
|
19
27
|
beforeEach(() => {
|
|
20
|
-
|
|
21
|
-
adrDirectory = path.resolve(path.join(workDir,
|
|
22
|
-
childProcess.execSync(`${command} init ${
|
|
28
|
+
workDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'adr-')));
|
|
29
|
+
adrDirectory = path.resolve(path.join(workDir, 'doc/adr'));
|
|
30
|
+
childProcess.execSync(`${command} init ${adrDirectory}`, { timeout: 10000, cwd: workDir });
|
|
23
31
|
});
|
|
24
32
|
|
|
25
33
|
it('should handle titles with periods in them', async () => {
|
|
26
|
-
childProcess.execSync(`${command} new Something About Node.JS`, { cwd: workDir });
|
|
34
|
+
childProcess.execSync(`${command} new Something About Node.JS`, { timeout: 10000, cwd: workDir });
|
|
27
35
|
const expectedFile: string = path.join(adrDirectory, '0002-something-about-node-js.md');
|
|
28
36
|
expect(fs.existsSync(expectedFile)).toBeTruthy();
|
|
29
37
|
});
|
|
30
38
|
|
|
31
|
-
it('should handle titles with slashes in them', async () => {
|
|
32
|
-
childProcess.execSync(`${command} new Slash/Slash/Slash/`, { cwd: workDir });
|
|
39
|
+
it.skip('should handle titles with slashes in them', async () => {
|
|
40
|
+
childProcess.execSync(`${command} new Slash/Slash/Slash/`, { timeout: 10000, cwd: workDir });
|
|
33
41
|
const expectedFile: string = path.join(adrDirectory, '0002-slash-slash-slash.md');
|
|
34
42
|
expect(fs.existsSync(expectedFile)).toBeTruthy();
|
|
35
43
|
});
|
|
36
44
|
|
|
37
|
-
it('should handle titles with other weirdness in them', async () => {
|
|
38
|
-
childProcess.execSync(`${command} new -- "-Bar-"`, { cwd: workDir });
|
|
45
|
+
it.skip('should handle titles with other weirdness in them', async () => {
|
|
46
|
+
childProcess.execSync(`${command} new -- "-Bar-"`, { timeout: 10000, cwd: workDir });
|
|
39
47
|
const expectedFile: string = path.join(adrDirectory, '0002-bar.md');
|
|
40
48
|
expect(fs.existsSync(expectedFile)).toBeTruthy();
|
|
41
49
|
});
|
|
42
50
|
});
|
|
43
|
-
|