@opentermsarchive/engine 0.26.1 → 0.27.0
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/bin/ota-track.js +3 -3
- package/bin/ota-validate.js +2 -2
- package/bin/ota.js +1 -1
- package/config/default.json +1 -1
- package/package.json +3 -4
- package/scripts/dataset/export/index.js +4 -4
- package/scripts/dataset/export/index.test.js +11 -17
- package/scripts/declarations/lint/index.mocha.js +1 -1
- package/scripts/declarations/utils/index.js +12 -12
- package/scripts/declarations/validate/definitions.js +1 -1
- package/scripts/declarations/validate/index.mocha.js +30 -34
- package/scripts/declarations/validate/service.history.schema.js +11 -11
- package/scripts/declarations/validate/service.schema.js +13 -13
- package/scripts/history/migrate-services.js +4 -4
- package/scripts/history/update-to-full-hash.js +2 -2
- package/scripts/import/index.js +14 -14
- package/scripts/rewrite/rewrite-snapshots.js +3 -3
- package/scripts/rewrite/rewrite-versions.js +14 -14
- package/scripts/utils/renamer/README.md +3 -3
- package/scripts/utils/renamer/index.js +13 -13
- package/src/archivist/errors.js +1 -1
- package/src/archivist/extract/exports.js +3 -0
- package/src/archivist/{filter → extract}/index.js +23 -27
- package/src/archivist/extract/index.test.js +516 -0
- package/src/archivist/index.js +101 -140
- package/src/archivist/index.test.js +178 -166
- package/src/archivist/recorder/index.js +11 -55
- package/src/archivist/recorder/index.test.js +310 -356
- package/src/archivist/recorder/record.js +18 -7
- package/src/archivist/recorder/repositories/git/dataMapper.js +41 -31
- package/src/archivist/recorder/repositories/git/index.js +11 -15
- package/src/archivist/recorder/repositories/git/index.test.js +1058 -463
- package/src/archivist/recorder/repositories/interface.js +8 -6
- package/src/archivist/recorder/repositories/mongo/dataMapper.js +21 -14
- package/src/archivist/recorder/repositories/mongo/index.js +8 -8
- package/src/archivist/recorder/repositories/mongo/index.test.js +898 -479
- package/src/archivist/recorder/snapshot.js +5 -0
- package/src/archivist/recorder/snapshot.test.js +65 -0
- package/src/archivist/recorder/version.js +14 -0
- package/src/archivist/recorder/version.test.js +65 -0
- package/src/archivist/services/index.js +60 -51
- package/src/archivist/services/index.test.js +63 -83
- package/src/archivist/services/service.js +26 -22
- package/src/archivist/services/service.test.js +46 -68
- package/src/archivist/services/{pageDeclaration.js → sourceDocument.js} +11 -9
- package/src/archivist/services/{pageDeclaration.test.js → sourceDocument.test.js} +21 -21
- package/src/archivist/services/terms.js +26 -0
- package/src/archivist/services/{documentDeclaration.test.js → terms.test.js} +15 -15
- package/src/exports.js +2 -2
- package/src/index.js +16 -13
- package/src/logger/index.js +35 -36
- package/src/notifier/index.js +8 -8
- package/src/tracker/index.js +6 -6
- package/src/archivist/filter/exports.js +0 -3
- package/src/archivist/filter/index.test.js +0 -564
- package/src/archivist/recorder/record.test.js +0 -91
- package/src/archivist/services/documentDeclaration.js +0 -26
- /package/scripts/utils/renamer/rules/{documentTypes.json → termsTypes.json} +0 -0
- /package/scripts/utils/renamer/rules/{documentTypesByService.json → termsTypesByService.json} +0 -0
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract Class Record.
|
|
3
|
+
*
|
|
4
|
+
* @class Record
|
|
5
|
+
*/
|
|
1
6
|
export default class Record {
|
|
2
7
|
#content;
|
|
3
8
|
|
|
4
|
-
static REQUIRED_PARAMS = Object.freeze([ 'serviceId', '
|
|
9
|
+
static REQUIRED_PARAMS = Object.freeze([ 'serviceId', 'termsType', 'fetchDate', 'content' ]);
|
|
5
10
|
|
|
6
11
|
constructor(params) {
|
|
7
|
-
Record
|
|
12
|
+
if (this.constructor == Record) {
|
|
13
|
+
throw new Error("Abstract Record class can't be instantiated.");
|
|
14
|
+
}
|
|
8
15
|
|
|
9
16
|
Object.assign(this, Object.fromEntries(Object.entries(params)));
|
|
10
17
|
|
|
@@ -15,7 +22,7 @@ export default class Record {
|
|
|
15
22
|
|
|
16
23
|
get content() {
|
|
17
24
|
if (this.#content === undefined) {
|
|
18
|
-
throw new Error('
|
|
25
|
+
throw new Error('Content not defined, set the content or use Repository#loadRecordContent');
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
return this.#content;
|
|
@@ -25,10 +32,14 @@ export default class Record {
|
|
|
25
32
|
this.#content = content;
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
for (const
|
|
30
|
-
if (
|
|
31
|
-
|
|
35
|
+
validate() {
|
|
36
|
+
for (const requiredParam of this.constructor.REQUIRED_PARAMS) {
|
|
37
|
+
if (requiredParam == 'content') {
|
|
38
|
+
if (this[requiredParam] == '' || this[requiredParam] == null) {
|
|
39
|
+
throw new Error(`${this.constructor.name} is not valid; "${requiredParam}" is empty or null`);
|
|
40
|
+
}
|
|
41
|
+
} else if (!Object.prototype.hasOwnProperty.call(this, requiredParam) || this[requiredParam] == null) {
|
|
42
|
+
throw new Error(`${this.constructor.name} is not valid; "${requiredParam}" is missing`);
|
|
32
43
|
}
|
|
33
44
|
}
|
|
34
45
|
}
|
|
@@ -2,44 +2,48 @@ import path from 'path';
|
|
|
2
2
|
|
|
3
3
|
import mime from 'mime';
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import Snapshot from '../../snapshot.js';
|
|
6
|
+
import Version from '../../version.js';
|
|
6
7
|
|
|
7
8
|
mime.define({ 'text/markdown': ['md'] }, true); // ensure extension for markdown files is `.md` and not `.markdown`
|
|
8
9
|
|
|
9
|
-
export const
|
|
10
|
-
startTracking: '
|
|
11
|
-
|
|
12
|
-
update: '
|
|
10
|
+
export const COMMIT_MESSAGE_PREFIXES = {
|
|
11
|
+
startTracking: 'First record of',
|
|
12
|
+
extractOnly: 'Apply technical or declaration upgrade on',
|
|
13
|
+
update: 'Record new changes of',
|
|
14
|
+
deprecated_startTracking: 'Start tracking',
|
|
15
|
+
deprecated_refilter: 'Refilter',
|
|
16
|
+
deprecated_update: 'Update',
|
|
13
17
|
};
|
|
14
18
|
|
|
15
|
-
export const
|
|
19
|
+
export const TERMS_TYPE_AND_DOCUMENT_ID_SEPARATOR = ' #';
|
|
16
20
|
export const SNAPSHOT_ID_MARKER = '%SNAPSHOT_ID';
|
|
17
|
-
const
|
|
18
|
-
const
|
|
21
|
+
const SINGLE_SOURCE_DOCUMENT_PREFIX = 'This version was recorded after extracting from snapshot';
|
|
22
|
+
const MULTIPLE_SOURCE_DOCUMENTS_PREFIX = 'This version was recorded after extracting from and assembling the following snapshots from %NUMBER source documents:';
|
|
19
23
|
|
|
20
|
-
export const COMMIT_MESSAGE_PREFIXES_REGEXP = new RegExp(`^(${
|
|
24
|
+
export const COMMIT_MESSAGE_PREFIXES_REGEXP = new RegExp(`^(${Object.values(COMMIT_MESSAGE_PREFIXES).join('|')})`);
|
|
21
25
|
|
|
22
26
|
export function toPersistence(record, snapshotIdentiferTemplate) {
|
|
23
|
-
const { serviceId,
|
|
27
|
+
const { serviceId, termsType, documentId, isExtractOnly, snapshotIds = [], mimeType, isFirstRecord } = record;
|
|
24
28
|
|
|
25
|
-
let prefix =
|
|
29
|
+
let prefix = isExtractOnly ? COMMIT_MESSAGE_PREFIXES.extractOnly : COMMIT_MESSAGE_PREFIXES.update;
|
|
26
30
|
|
|
27
|
-
prefix = isFirstRecord ?
|
|
31
|
+
prefix = isFirstRecord ? COMMIT_MESSAGE_PREFIXES.startTracking : prefix;
|
|
28
32
|
|
|
29
|
-
const subject = `${prefix} ${serviceId} ${
|
|
30
|
-
const
|
|
33
|
+
const subject = `${prefix} ${serviceId} ${termsType}`;
|
|
34
|
+
const documentIdMessage = `${documentId ? `Document ID ${documentId}\n\n` : ''}`;
|
|
31
35
|
let snapshotIdsMessage;
|
|
32
36
|
|
|
33
37
|
if (snapshotIds.length == 1) {
|
|
34
|
-
snapshotIdsMessage = `${
|
|
38
|
+
snapshotIdsMessage = `${SINGLE_SOURCE_DOCUMENT_PREFIX} ${snapshotIdentiferTemplate.replace(SNAPSHOT_ID_MARKER, snapshotIds[0])}`;
|
|
35
39
|
} else if (snapshotIds.length > 1) {
|
|
36
|
-
snapshotIdsMessage = `${
|
|
40
|
+
snapshotIdsMessage = `${MULTIPLE_SOURCE_DOCUMENTS_PREFIX.replace('%NUMBER', snapshotIds.length)}\n${snapshotIds.map(snapshotId => `- ${snapshotIdentiferTemplate.replace(SNAPSHOT_ID_MARKER, snapshotId)}`).join('\n')}`;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
const filePath = generateFilePath(serviceId,
|
|
43
|
+
const filePath = generateFilePath(serviceId, termsType, documentId, mimeType);
|
|
40
44
|
|
|
41
45
|
return {
|
|
42
|
-
message: `${subject}\n\n${
|
|
46
|
+
message: `${subject}\n\n${documentIdMessage || ''}\n\n${snapshotIdsMessage || ''}`,
|
|
43
47
|
content: record.content,
|
|
44
48
|
filePath,
|
|
45
49
|
};
|
|
@@ -51,33 +55,39 @@ export function toDomain(commit) {
|
|
|
51
55
|
const modifiedFilesInCommit = diff.files.map(({ file }) => file);
|
|
52
56
|
|
|
53
57
|
if (modifiedFilesInCommit.length > 1) {
|
|
54
|
-
throw new Error(`Only one
|
|
58
|
+
throw new Error(`Only one file should have been recorded in ${hash}, but all these files were recorded: ${modifiedFilesInCommit.join(', ')}`);
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
const [relativeFilePath] = modifiedFilesInCommit;
|
|
58
62
|
const snapshotIdsMatch = body.match(/\b[0-9a-f]{5,40}\b/g);
|
|
59
63
|
|
|
60
|
-
const [
|
|
64
|
+
const [ termsType, documentId ] = path.basename(relativeFilePath, path.extname(relativeFilePath)).split(TERMS_TYPE_AND_DOCUMENT_ID_SEPARATOR);
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
const attributes = {
|
|
63
67
|
id: hash,
|
|
64
68
|
serviceId: path.dirname(relativeFilePath),
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
termsType,
|
|
70
|
+
documentId,
|
|
67
71
|
mimeType: mime.getType(relativeFilePath),
|
|
68
72
|
fetchDate: new Date(date),
|
|
69
|
-
isFirstRecord: message.startsWith(
|
|
70
|
-
|
|
73
|
+
isFirstRecord: message.startsWith(COMMIT_MESSAGE_PREFIXES.startTracking) || message.startsWith(COMMIT_MESSAGE_PREFIXES.deprecated_startTracking),
|
|
74
|
+
isExtractOnly: message.startsWith(COMMIT_MESSAGE_PREFIXES.extractOnly) || message.startsWith(COMMIT_MESSAGE_PREFIXES.deprecated_refilter),
|
|
71
75
|
snapshotIds: snapshotIdsMatch || [],
|
|
72
|
-
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (attributes.mimeType == mime.getType('markdown')) {
|
|
79
|
+
return new Version(attributes);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return new Snapshot(attributes);
|
|
73
83
|
}
|
|
74
84
|
|
|
75
|
-
function generateFileName(
|
|
76
|
-
return `${
|
|
85
|
+
function generateFileName(termsType, documentId, extension) {
|
|
86
|
+
return `${termsType}${documentId ? `${TERMS_TYPE_AND_DOCUMENT_ID_SEPARATOR}${documentId}` : ''}.${extension}`;
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
export function generateFilePath(serviceId,
|
|
80
|
-
const extension = mime.getExtension(mimeType) || '*'; // If mime type is undefined, an asterisk is set as an extension. Used to match all files for the given service ID, terms type and
|
|
89
|
+
export function generateFilePath(serviceId, termsType, documentId, mimeType) {
|
|
90
|
+
const extension = mime.getExtension(mimeType) || '*'; // If mime type is undefined, an asterisk is set as an extension. Used to match all files for the given service ID, terms type and document ID when mime type is unknown
|
|
81
91
|
|
|
82
|
-
return `${serviceId}/${generateFileName(
|
|
92
|
+
return `${serviceId}/${generateFileName(termsType, documentId, extension)}`; // Do not use `path.join` as even for Windows, the path should be with `/` and not `\`
|
|
83
93
|
}
|
|
@@ -34,17 +34,17 @@ export default class GitRepository extends RepositoryInterface {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
async save(record) {
|
|
37
|
-
const { serviceId,
|
|
37
|
+
const { serviceId, termsType, documentId, fetchDate } = record;
|
|
38
38
|
|
|
39
39
|
if (record.isFirstRecord === undefined || record.isFirstRecord === null) {
|
|
40
|
-
record.isFirstRecord = !await this.#isTracked(serviceId,
|
|
40
|
+
record.isFirstRecord = !await this.#isTracked(serviceId, termsType, documentId);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const { message, content, filePath: relativeFilePath } = await this.#toPersistence(record);
|
|
44
44
|
|
|
45
45
|
const filePath = path.join(this.path, relativeFilePath);
|
|
46
46
|
|
|
47
|
-
await GitRepository
|
|
47
|
+
await GitRepository.writeFile({ filePath, content });
|
|
48
48
|
const sha = await this.#commit({ filePath, message, date: fetchDate });
|
|
49
49
|
|
|
50
50
|
if (!sha) {
|
|
@@ -64,8 +64,8 @@ export default class GitRepository extends RepositoryInterface {
|
|
|
64
64
|
return this.git.pushChanges();
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
async findLatest(serviceId,
|
|
68
|
-
const filePath = DataMapper.generateFilePath(serviceId,
|
|
67
|
+
async findLatest(serviceId, termsType, documentId) {
|
|
68
|
+
const filePath = DataMapper.generateFilePath(serviceId, termsType, documentId);
|
|
69
69
|
const commit = await this.git.getCommit([filePath]);
|
|
70
70
|
|
|
71
71
|
return this.#toDomain(commit);
|
|
@@ -82,11 +82,7 @@ export default class GitRepository extends RepositoryInterface {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async count() {
|
|
85
|
-
return (await this.git.log(
|
|
86
|
-
`--grep=${DataMapper.COMMIT_MESSAGE_PREFIX.startTracking}`,
|
|
87
|
-
`--grep=${DataMapper.COMMIT_MESSAGE_PREFIX.refilter}`,
|
|
88
|
-
`--grep=${DataMapper.COMMIT_MESSAGE_PREFIX.update}`,
|
|
89
|
-
])).length;
|
|
85
|
+
return (await this.git.log(Object.values(DataMapper.COMMIT_MESSAGE_PREFIXES).map(prefix => `--grep=${prefix}`))).length;
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
async* iterate() {
|
|
@@ -102,7 +98,7 @@ export default class GitRepository extends RepositoryInterface {
|
|
|
102
98
|
}
|
|
103
99
|
|
|
104
100
|
async loadRecordContent(record) {
|
|
105
|
-
const relativeFilePath = DataMapper.generateFilePath(record.serviceId, record.
|
|
101
|
+
const relativeFilePath = DataMapper.generateFilePath(record.serviceId, record.termsType, record.documentId, record.mimeType);
|
|
106
102
|
|
|
107
103
|
if (record.mimeType != mime.getType('pdf')) {
|
|
108
104
|
record.content = await this.git.show(`${record.id}:${relativeFilePath}`);
|
|
@@ -126,11 +122,11 @@ export default class GitRepository extends RepositoryInterface {
|
|
|
126
122
|
|
|
127
123
|
async #getCommits() {
|
|
128
124
|
return (await this.git.listCommits())
|
|
129
|
-
.filter(({ message }) => message.match(DataMapper.COMMIT_MESSAGE_PREFIXES_REGEXP)) // Skip commits which are not a
|
|
125
|
+
.filter(({ message }) => message.match(DataMapper.COMMIT_MESSAGE_PREFIXES_REGEXP)) // Skip commits which are not a record (README, LICENSE…)
|
|
130
126
|
.sort((commitA, commitB) => new Date(commitA.date) - new Date(commitB.date)); // Make sure that the commits are sorted in ascending chronological order
|
|
131
127
|
}
|
|
132
128
|
|
|
133
|
-
static async
|
|
129
|
+
static async writeFile({ filePath, content }) {
|
|
134
130
|
const directory = path.dirname(filePath);
|
|
135
131
|
|
|
136
132
|
if (!fsApi.existsSync(directory)) {
|
|
@@ -152,8 +148,8 @@ export default class GitRepository extends RepositoryInterface {
|
|
|
152
148
|
}
|
|
153
149
|
}
|
|
154
150
|
|
|
155
|
-
#isTracked(serviceId,
|
|
156
|
-
return this.git.isTracked(`${this.path}/${DataMapper.generateFilePath(serviceId,
|
|
151
|
+
#isTracked(serviceId, termsType, documentId) {
|
|
152
|
+
return this.git.isTracked(`${this.path}/${DataMapper.generateFilePath(serviceId, termsType, documentId)}`);
|
|
157
153
|
}
|
|
158
154
|
|
|
159
155
|
async #toDomain(commit, { deferContentLoading } = {}) {
|