generated-signature 0.1.1 → 0.1.2
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/README.md +1 -1
- package/dist/eslint/rules/valid.js +1 -1
- package/dist/lib/manifest.js +3 -1
- package/dist/lib/merge.d.ts +3 -0
- package/dist/lib/merge.js +1 -1
- package/dist/lib/sections.js +2 -2
- package/dist/lib/signature.js +13 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Sign generated files so hand edits are detected, while keeping manual sections e
|
|
|
8
8
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
Guides and the full reference are on the [docs site](https://jacobley.github.io/leyman/generated-signature/).
|
|
11
|
+
Guides and the full reference are on the [docs site](https://jacobley.github.io/leyman/packages/generated-signature/).
|
|
12
12
|
|
|
13
13
|
- **Offline check**: `verify()` needs only the file, not the generator, so it is cheap enough for a lint rule or a pre-commit hook.
|
|
14
14
|
- **Manual sections**: mark regions a person may edit. Their content is left out of the signature and carried over when the file is regenerated.
|
|
@@ -4,7 +4,7 @@ export const valid = {
|
|
|
4
4
|
type: 'problem',
|
|
5
5
|
docs: {
|
|
6
6
|
description: 'Disallow editing generated files outside of their manual sections',
|
|
7
|
-
url: 'https://jacobley.github.io/leyman/generated-signature/guides/eslint#generated-signature-valid'
|
|
7
|
+
url: 'https://jacobley.github.io/leyman/packages/generated-signature/guides/eslint#generated-signature-valid'
|
|
8
8
|
},
|
|
9
9
|
messages: {
|
|
10
10
|
invalid: 'Generated file was edited outside of its manual sections. Regenerate it instead.',
|
package/dist/lib/manifest.js
CHANGED
|
@@ -59,14 +59,16 @@ export const parseManifest = (text)=>{
|
|
|
59
59
|
if (typeof files !== 'object' || files === null || Array.isArray(files)) {
|
|
60
60
|
throw new GeneratedSignatureManifestError('files is not an object');
|
|
61
61
|
}
|
|
62
|
+
const entries = {};
|
|
62
63
|
for (const [path, entry] of Object.entries(files)){
|
|
63
64
|
if (!isEntry(entry)) {
|
|
64
65
|
throw new GeneratedSignatureManifestError(`invalid entry for ${path}`);
|
|
65
66
|
}
|
|
67
|
+
entries[path] = entry;
|
|
66
68
|
}
|
|
67
69
|
return {
|
|
68
70
|
version,
|
|
69
|
-
files:
|
|
71
|
+
files: entries
|
|
70
72
|
};
|
|
71
73
|
};
|
|
72
74
|
const sortKeys = (record)=>Object.fromEntries(Object.entries(record).toSorted(([a], [b])=>a < b ? -1 : 1));
|
package/dist/lib/merge.d.ts
CHANGED
package/dist/lib/merge.js
CHANGED
|
@@ -24,7 +24,7 @@ export const manualSection = ({ id, body = '', indent = '', ...options })=>{
|
|
|
24
24
|
throw new GeneratedSignatureSectionIdError(id);
|
|
25
25
|
}
|
|
26
26
|
const style = resolveCommentStyle(options);
|
|
27
|
-
const lines = body === '' ? [] : body.replace(/\n$/
|
|
27
|
+
const lines = body === '' ? [] : body.replace(/\n$/v, '').split('\n');
|
|
28
28
|
return [
|
|
29
29
|
renderComment(style, `@manual-start ${id}`),
|
|
30
30
|
...lines,
|
package/dist/lib/sections.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GeneratedSignatureMalformedError } from './errors.js';
|
|
2
|
-
const markerPattern = /(?:^|\s)@manual-(?<kind>start
|
|
3
|
-
const idPattern = /^[\w
|
|
2
|
+
const markerPattern = /(?:^|\s)@manual-(?<kind>end|start)\b(?:\s+(?<id>\S+))?/v;
|
|
3
|
+
const idPattern = /^[\w\-.]+$/v;
|
|
4
4
|
export const isValidSectionId = (id)=>idPattern.test(id);
|
|
5
5
|
export const scanSections = (text)=>{
|
|
6
6
|
const sections = [];
|
package/dist/lib/signature.js
CHANGED
|
@@ -6,10 +6,11 @@ const VERSION = '1';
|
|
|
6
6
|
const HASH_LENGTH = 32;
|
|
7
7
|
const PLACEHOLDER = '0'.repeat(HASH_LENGTH);
|
|
8
8
|
const TOKEN_NAME = '@generated-signature';
|
|
9
|
-
const tokenPattern = new RegExp(String.raw`${TOKEN_NAME} v(?<version>\d+):(?<hash>[\da-f]{${HASH_LENGTH}})`, '
|
|
9
|
+
const tokenPattern = new RegExp(String.raw`${TOKEN_NAME} v(?<version>\d+):(?<hash>[\da-f]{${HASH_LENGTH}})`, 'gv');
|
|
10
10
|
const DEFAULT_MESSAGE = 'Generated file. Do not edit outside of manual sections.';
|
|
11
|
+
const renderToken = (hash)=>`${TOKEN_NAME} v${VERSION}:${hash}`;
|
|
11
12
|
const lineOf = (text, offset)=>text.slice(0, offset).split('\n').length;
|
|
12
|
-
const
|
|
13
|
+
const findHash = (text)=>{
|
|
13
14
|
const [first, second] = text.matchAll(tokenPattern);
|
|
14
15
|
if (!first) {
|
|
15
16
|
return null;
|
|
@@ -29,20 +30,14 @@ const findToken = (text)=>{
|
|
|
29
30
|
detail: `v${version} is not supported, expected v${VERSION}`
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
|
-
return
|
|
33
|
-
hash,
|
|
34
|
-
hashStart: first.index + first[0].length - HASH_LENGTH
|
|
35
|
-
};
|
|
33
|
+
return hash;
|
|
36
34
|
};
|
|
37
|
-
const replaceHash = (text, token, hash)=>text.slice(0, token.hashStart) + hash + text.slice(token.hashStart + HASH_LENGTH);
|
|
38
35
|
const computeHash = (text)=>{
|
|
39
|
-
const
|
|
40
|
-
const token = findToken(normalized);
|
|
41
|
-
const withPlaceholder = replaceHash(normalized, token, PLACEHOLDER);
|
|
36
|
+
const withPlaceholder = text.replaceAll('\r\n', '\n').replaceAll(tokenPattern, ()=>renderToken(PLACEHOLDER));
|
|
42
37
|
const hashable = replaceBodies(withPlaceholder, scanSections(withPlaceholder), ()=>'');
|
|
43
38
|
return createHash('sha256').update(hashable).digest('hex').slice(0, HASH_LENGTH);
|
|
44
39
|
};
|
|
45
|
-
export const signatureToken = ()
|
|
40
|
+
export const signatureToken = ()=>renderToken(PLACEHOLDER);
|
|
46
41
|
const insertHeader = (text, options)=>{
|
|
47
42
|
const style = resolveCommentStyle(options);
|
|
48
43
|
const lineBreak = text.includes('\r\n') ? '\r\n' : '\n';
|
|
@@ -51,7 +46,7 @@ const insertHeader = (text, options)=>{
|
|
|
51
46
|
const firstLineEnd = text.indexOf('\n');
|
|
52
47
|
return firstLineEnd === -1 ? text + lineBreak + header : text.slice(0, firstLineEnd + 1) + header + text.slice(firstLineEnd + 1);
|
|
53
48
|
}
|
|
54
|
-
const frontMatter = style.start === '<!--' ? /^---\r?\n(?:[^\n]*\n)*?---\r?(?:\n|$)/
|
|
49
|
+
const frontMatter = style.start === '<!--' ? /^---\r?\n(?:[^\n]*\n)*?---\r?(?:\n|$)/v.exec(text) : null;
|
|
55
50
|
if (frontMatter) {
|
|
56
51
|
const [fenced] = frontMatter;
|
|
57
52
|
return fenced.endsWith('\n') ? fenced + header + text.slice(fenced.length) : fenced + lineBreak + header;
|
|
@@ -60,24 +55,25 @@ const insertHeader = (text, options)=>{
|
|
|
60
55
|
};
|
|
61
56
|
export const sign = (text, options)=>{
|
|
62
57
|
let withToken = text;
|
|
63
|
-
if (
|
|
58
|
+
if (findHash(text) === null) {
|
|
64
59
|
if (!options) {
|
|
65
60
|
throw new TypeError('Text has no signature token, provide `comment` or `filePath`');
|
|
66
61
|
}
|
|
67
62
|
withToken = insertHeader(text, options);
|
|
68
63
|
}
|
|
69
|
-
|
|
64
|
+
const hash = computeHash(withToken);
|
|
65
|
+
return withToken.replaceAll(tokenPattern, ()=>renderToken(hash));
|
|
70
66
|
};
|
|
71
67
|
export const verify = (text)=>{
|
|
72
68
|
try {
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
69
|
+
const hash = findHash(text.replaceAll('\r\n', '\n'));
|
|
70
|
+
if (hash === null) {
|
|
75
71
|
return {
|
|
76
72
|
status: 'unsigned'
|
|
77
73
|
};
|
|
78
74
|
}
|
|
79
75
|
return {
|
|
80
|
-
status: computeHash(text) ===
|
|
76
|
+
status: computeHash(text) === hash ? 'valid' : 'invalid'
|
|
81
77
|
};
|
|
82
78
|
} catch (err) {
|
|
83
79
|
return {
|