@opentermsarchive/engine 0.26.1 → 0.27.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 (60) hide show
  1. package/README.md +1 -3
  2. package/bin/ota-track.js +3 -3
  3. package/bin/ota-validate.js +2 -2
  4. package/bin/ota.js +1 -1
  5. package/config/default.json +1 -1
  6. package/package.json +3 -4
  7. package/scripts/dataset/export/index.js +4 -4
  8. package/scripts/dataset/export/index.test.js +11 -17
  9. package/scripts/declarations/lint/index.mocha.js +1 -1
  10. package/scripts/declarations/utils/index.js +12 -12
  11. package/scripts/declarations/validate/definitions.js +1 -1
  12. package/scripts/declarations/validate/index.mocha.js +30 -34
  13. package/scripts/declarations/validate/service.history.schema.js +11 -11
  14. package/scripts/declarations/validate/service.schema.js +13 -13
  15. package/scripts/history/migrate-services.js +4 -4
  16. package/scripts/history/update-to-full-hash.js +2 -2
  17. package/scripts/import/index.js +14 -14
  18. package/scripts/rewrite/rewrite-snapshots.js +3 -3
  19. package/scripts/rewrite/rewrite-versions.js +14 -14
  20. package/scripts/utils/renamer/README.md +3 -3
  21. package/scripts/utils/renamer/index.js +13 -13
  22. package/src/archivist/errors.js +1 -1
  23. package/src/archivist/extract/exports.js +3 -0
  24. package/src/archivist/{filter → extract}/index.js +23 -27
  25. package/src/archivist/extract/index.test.js +516 -0
  26. package/src/archivist/index.js +101 -140
  27. package/src/archivist/index.test.js +178 -166
  28. package/src/archivist/recorder/index.js +11 -55
  29. package/src/archivist/recorder/index.test.js +310 -356
  30. package/src/archivist/recorder/record.js +18 -7
  31. package/src/archivist/recorder/repositories/git/dataMapper.js +41 -31
  32. package/src/archivist/recorder/repositories/git/index.js +11 -15
  33. package/src/archivist/recorder/repositories/git/index.test.js +1058 -463
  34. package/src/archivist/recorder/repositories/interface.js +8 -6
  35. package/src/archivist/recorder/repositories/mongo/dataMapper.js +21 -14
  36. package/src/archivist/recorder/repositories/mongo/index.js +8 -8
  37. package/src/archivist/recorder/repositories/mongo/index.test.js +898 -479
  38. package/src/archivist/recorder/snapshot.js +5 -0
  39. package/src/archivist/recorder/snapshot.test.js +65 -0
  40. package/src/archivist/recorder/version.js +14 -0
  41. package/src/archivist/recorder/version.test.js +65 -0
  42. package/src/archivist/services/index.js +60 -51
  43. package/src/archivist/services/index.test.js +63 -83
  44. package/src/archivist/services/service.js +26 -22
  45. package/src/archivist/services/service.test.js +46 -68
  46. package/src/archivist/services/{pageDeclaration.js → sourceDocument.js} +11 -9
  47. package/src/archivist/services/{pageDeclaration.test.js → sourceDocument.test.js} +21 -21
  48. package/src/archivist/services/terms.js +26 -0
  49. package/src/archivist/services/{documentDeclaration.test.js → terms.test.js} +15 -15
  50. package/src/exports.js +2 -2
  51. package/src/index.js +16 -13
  52. package/src/logger/index.js +35 -36
  53. package/src/notifier/index.js +8 -8
  54. package/src/tracker/index.js +6 -6
  55. package/src/archivist/filter/exports.js +0 -3
  56. package/src/archivist/filter/index.test.js +0 -564
  57. package/src/archivist/recorder/record.test.js +0 -91
  58. package/src/archivist/services/documentDeclaration.js +0 -26
  59. /package/scripts/utils/renamer/rules/{documentTypes.json → termsTypes.json} +0 -0
  60. /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', 'documentType', 'mimeType', 'fetchDate' ]);
9
+ static REQUIRED_PARAMS = Object.freeze([ 'serviceId', 'termsType', 'fetchDate', 'content' ]);
5
10
 
6
11
  constructor(params) {
7
- Record.validate(params);
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('Record content not defined, set the content or use Repository#loadRecordContent');
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
- static validate(givenParams) {
29
- for (const param of Record.REQUIRED_PARAMS) {
30
- if (!Object.prototype.hasOwnProperty.call(givenParams, param) || givenParams[param] == null) {
31
- throw new Error(`"${param}" is required`);
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 Record from '../../record.js';
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 COMMIT_MESSAGE_PREFIX = {
10
- startTracking: 'Start tracking',
11
- refilter: 'Refilter',
12
- update: '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 DOCUMENT_TYPE_AND_PAGE_ID_SEPARATOR = ' #';
19
+ export const TERMS_TYPE_AND_DOCUMENT_ID_SEPARATOR = ' #';
16
20
  export const SNAPSHOT_ID_MARKER = '%SNAPSHOT_ID';
17
- const SINGLE_SNAPSHOT_PREFIX = 'This version was recorded after filtering snapshot';
18
- const MULTIPLE_SNAPSHOT_PREFIX = 'This version was recorded after filtering and assembling the following snapshots from %NUMBER pages:';
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(`^(${COMMIT_MESSAGE_PREFIX.startTracking}|${COMMIT_MESSAGE_PREFIX.refilter}|${COMMIT_MESSAGE_PREFIX.update})`);
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, documentType, pageId, isRefilter, snapshotIds = [], mimeType, isFirstRecord } = record;
27
+ const { serviceId, termsType, documentId, isExtractOnly, snapshotIds = [], mimeType, isFirstRecord } = record;
24
28
 
25
- let prefix = isRefilter ? COMMIT_MESSAGE_PREFIX.refilter : COMMIT_MESSAGE_PREFIX.update;
29
+ let prefix = isExtractOnly ? COMMIT_MESSAGE_PREFIXES.extractOnly : COMMIT_MESSAGE_PREFIXES.update;
26
30
 
27
- prefix = isFirstRecord ? COMMIT_MESSAGE_PREFIX.startTracking : prefix;
31
+ prefix = isFirstRecord ? COMMIT_MESSAGE_PREFIXES.startTracking : prefix;
28
32
 
29
- const subject = `${prefix} ${serviceId} ${documentType}`;
30
- const pageIdMessage = `${pageId ? `Page ID ${pageId}\n\n` : ''}`;
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 = `${SINGLE_SNAPSHOT_PREFIX} ${snapshotIdentiferTemplate.replace(SNAPSHOT_ID_MARKER, snapshotIds[0])}`;
38
+ snapshotIdsMessage = `${SINGLE_SOURCE_DOCUMENT_PREFIX} ${snapshotIdentiferTemplate.replace(SNAPSHOT_ID_MARKER, snapshotIds[0])}`;
35
39
  } else if (snapshotIds.length > 1) {
36
- snapshotIdsMessage = `${MULTIPLE_SNAPSHOT_PREFIX.replace('%NUMBER', snapshotIds.length)}\n${snapshotIds.map(snapshotId => `- ${snapshotIdentiferTemplate.replace(SNAPSHOT_ID_MARKER, snapshotId)}`).join('\n')}`;
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, documentType, pageId, mimeType);
43
+ const filePath = generateFilePath(serviceId, termsType, documentId, mimeType);
40
44
 
41
45
  return {
42
- message: `${subject}\n\n${pageIdMessage || ''}\n\n${snapshotIdsMessage || ''}`,
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 document should have been recorded in ${hash}, but all these documents were recorded: ${modifiedFilesInCommit.join(', ')}`);
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 [ documentType, pageId ] = path.basename(relativeFilePath, path.extname(relativeFilePath)).split(DOCUMENT_TYPE_AND_PAGE_ID_SEPARATOR);
64
+ const [ termsType, documentId ] = path.basename(relativeFilePath, path.extname(relativeFilePath)).split(TERMS_TYPE_AND_DOCUMENT_ID_SEPARATOR);
61
65
 
62
- return new Record({
66
+ const attributes = {
63
67
  id: hash,
64
68
  serviceId: path.dirname(relativeFilePath),
65
- documentType,
66
- pageId,
69
+ termsType,
70
+ documentId,
67
71
  mimeType: mime.getType(relativeFilePath),
68
72
  fetchDate: new Date(date),
69
- isFirstRecord: message.startsWith(COMMIT_MESSAGE_PREFIX.startTracking),
70
- isRefilter: message.startsWith(COMMIT_MESSAGE_PREFIX.refilter),
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(documentType, pageId, extension) {
76
- return `${documentType}${pageId ? `${DOCUMENT_TYPE_AND_PAGE_ID_SEPARATOR}${pageId}` : ''}.${extension}`;
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, documentType, pageId, mimeType) {
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 page ID when mime type is unknown.
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(documentType, pageId, extension)}`; // Do not use `path.join` as even for Windows, the path should be with `/` and not `\`
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, documentType, pageId, fetchDate } = record;
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, documentType, pageId);
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.#writeFile({ filePath, content });
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, documentType, pageId) {
68
- const filePath = DataMapper.generateFilePath(serviceId, documentType, pageId);
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.documentType, record.pageId, record.mimeType);
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 document record (README, LICENSE…)
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 #writeFile({ filePath, content }) {
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, documentType, pageId) {
156
- return this.git.isTracked(`${this.path}/${DataMapper.generateFilePath(serviceId, documentType, pageId)}`);
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 } = {}) {