@opentermsarchive/engine 16.0.2 → 16.1.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/config/default.json +13 -0
- package/config/test.json +12 -0
- package/package.json +1 -1
- package/scripts/declarations/validate/index.mocha.js +7 -0
- package/scripts/import/index.js +1 -1
- package/scripts/import/loadCommits.js +1 -1
- package/scripts/rewrite/initializer/index.js +1 -1
- package/scripts/rewrite/rewrite-snapshots.js +1 -1
- package/scripts/rewrite/rewrite-versions.js +1 -1
- package/src/archivist/fetcher/htmlOnlyFetcher.js +8 -0
- package/src/archivist/fetcher/index.test.js +12 -0
- package/src/archivist/index.js +106 -23
- package/src/archivist/index.test.js +659 -9
- package/src/archivist/recorder/repositories/git/dataMapper.js +2 -1
- package/src/archivist/recorder/repositories/git/index.js +2 -13
- package/src/archivist/recorder/repositories/git/index.test.js +23 -1
- package/src/archivist/services/index.js +45 -1
- package/src/archivist/services/index.test.js +52 -1
- package/src/archivist/services/sourceDocument.js +8 -2
- package/src/archivist/services/sourceDocument.test.js +37 -0
- package/src/archivist/tracking-results/errors.js +5 -0
- package/src/archivist/tracking-results/index.js +233 -0
- package/src/archivist/tracking-results/index.test.js +406 -0
- package/src/archivist/tracking-results/recorder.js +156 -0
- package/src/archivist/tracking-results/recorder.test.js +559 -0
- package/src/archivist/tracking-results/repository.js +136 -0
- package/src/archivist/tracking-results/repository.test.js +763 -0
- package/src/archivist/tracking-results/run/dataMapper.js +51 -0
- package/src/archivist/tracking-results/run/dataMapper.test.js +168 -0
- package/src/archivist/tracking-results/run/index.js +115 -0
- package/src/archivist/tracking-results/run/index.test.js +221 -0
- package/src/archivist/tracking-results/terms-result/dataMapper.js +200 -0
- package/src/archivist/tracking-results/terms-result/dataMapper.test.js +575 -0
- package/src/archivist/tracking-results/terms-result/index.js +42 -0
- package/src/archivist/tracking-results/terms-result/index.test.js +228 -0
- package/src/git/errors.js +1 -0
- package/src/{archivist/recorder/repositories/git/git.js → git/index.js} +59 -9
- package/src/git/index.test.js +266 -0
- package/src/git/pathSegment.js +18 -0
- package/src/git/pathSegment.test.js +33 -0
- package/src/index.js +5 -4
- package/src/reporter/index.js +9 -4
- package/src/reporter/index.test.js +47 -9
- package/src/archivist/recorder/repositories/git/git.test.js +0 -114
- /package/src/{archivist/recorder/repositories/git → git}/trailers.js +0 -0
- /package/src/{archivist/recorder/repositories/git → git}/trailers.test.js +0 -0
package/src/index.js
CHANGED
|
@@ -12,10 +12,11 @@ import Reporter from './reporter/index.js';
|
|
|
12
12
|
const require = createRequire(import.meta.url);
|
|
13
13
|
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
14
14
|
|
|
15
|
-
async function initialize(services) {
|
|
15
|
+
async function initialize(services, { trackingResultsConfig } = {}) {
|
|
16
16
|
const archivist = new Archivist({
|
|
17
17
|
recorderConfig: config.get('@opentermsarchive/engine.recorder'),
|
|
18
18
|
fetcherConfig: config.get('@opentermsarchive/engine.fetcher'),
|
|
19
|
+
trackingResultsConfig,
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
archivist.attach(logger);
|
|
@@ -28,7 +29,7 @@ async function initialize(services) {
|
|
|
28
29
|
logger.info(`Start engine v${PACKAGE_VERSION}${collectionName}\n`);
|
|
29
30
|
|
|
30
31
|
if (services?.length) {
|
|
31
|
-
services = services.filter(serviceId => {
|
|
32
|
+
services = [...new Set(services)].filter(serviceId => { // Deduplicated so that a service ID given twice is not tracked twice in the same run
|
|
32
33
|
const isServiceDeclared = archivist.services[serviceId];
|
|
33
34
|
|
|
34
35
|
if (!isServiceDeclared) {
|
|
@@ -43,7 +44,7 @@ async function initialize(services) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export default async function track({ services, types, schedule }) {
|
|
46
|
-
const { archivist, services: filteredServices } = await initialize(services);
|
|
47
|
+
const { archivist, services: filteredServices } = await initialize(services, { trackingResultsConfig: config.get('@opentermsarchive/engine.tracking-results') });
|
|
47
48
|
|
|
48
49
|
// Technical upgrade pass: apply changes from engine, dependency, or declaration upgrades.
|
|
49
50
|
// This regenerates versions from existing snapshots with updated extraction logic.
|
|
@@ -84,7 +85,7 @@ export default async function track({ services, types, schedule }) {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
export async function applyTechnicalUpgrades({ services, types }) {
|
|
87
|
-
const { archivist, services: filteredServices } = await initialize(services);
|
|
88
|
+
const { archivist, services: filteredServices } = await initialize(services); // Without tracking-results: technical upgrades never record any, and initializing the module would finalize as crashed the run that a tracking process may have in progress
|
|
88
89
|
|
|
89
90
|
await archivist.applyTechnicalUpgrades({ services: filteredServices, types });
|
|
90
91
|
}
|
package/src/reporter/index.js
CHANGED
|
@@ -144,9 +144,14 @@ No changes were found in the last run, so no new version has been recorded.`,
|
|
|
144
144
|
const latestDeclarationLink = `[Latest declaration](${declarationFileUrl})`;
|
|
145
145
|
const latestVersionLink = `[Latest version](${this.reporter.generateVersionURL(terms.service.name, terms.type)})`;
|
|
146
146
|
const snapshotsBaseUrl = this.reporter.generateSnapshotsBaseUrl(terms.service.name, terms.type);
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
const recordedSourceDocuments = terms.sourceDocuments.filter(sourceDocument => sourceDocument.snapshotId); // A source document that has never been recorded has no snapshot file to link to, and its unknown MIME type would produce a link to a nonexistent ".null" file
|
|
148
|
+
let latestSnapshotsLink = '';
|
|
149
|
+
|
|
150
|
+
if (recordedSourceDocuments.length) {
|
|
151
|
+
latestSnapshotsLink = terms.hasMultipleSourceDocuments
|
|
152
|
+
? `Latest snapshots:\n - ${recordedSourceDocuments.map(sourceDocument => `[${sourceDocument.id}](${snapshotsBaseUrl}.%20#${sourceDocument.id}.${mime.getExtension(sourceDocument.mimeType)})`).join('\n - ')}`
|
|
153
|
+
: `[Latest snapshot](${snapshotsBaseUrl}.${mime.getExtension(recordedSourceDocuments[0].mimeType)})`;
|
|
154
|
+
}
|
|
150
155
|
|
|
151
156
|
/* eslint-disable no-irregular-whitespace */
|
|
152
157
|
return `
|
|
@@ -185,7 +190,7 @@ If the source documents are accessible in a browser but fetching them always fai
|
|
|
185
190
|
|
|
186
191
|
- ${latestDeclarationLink}
|
|
187
192
|
${this.repositories.versions ? `- ${latestVersionLink}` : ''}
|
|
188
|
-
${this.repositories.snapshots ? `- ${latestSnapshotsLink}` : ''}
|
|
193
|
+
${this.repositories.snapshots && latestSnapshotsLink ? `- ${latestSnapshotsLink}` : ''}
|
|
189
194
|
`;
|
|
190
195
|
/* eslint-enable no-irregular-whitespace */
|
|
191
196
|
}
|
|
@@ -64,17 +64,21 @@ describe('Reporter', () => {
|
|
|
64
64
|
describe('#generateDescription', () => {
|
|
65
65
|
const buildReporter = () => new Reporter({
|
|
66
66
|
type: 'github',
|
|
67
|
-
repositories: { declarations: 'OpenTermsArchive/test-declarations' },
|
|
67
|
+
repositories: { declarations: 'OpenTermsArchive/test-declarations', snapshots: 'OpenTermsArchive/test-snapshots' },
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
const buildTerms = ({ sourceCount = 1 } = {}) => {
|
|
71
|
-
const sourceDocuments = Array.from({ length: sourceCount }, (_, index) =>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
const buildTerms = ({ sourceCount = 1, hasSnapshots = true, withoutSnapshotIndexes = [] } = {}) => {
|
|
71
|
+
const sourceDocuments = Array.from({ length: sourceCount }, (_, index) => {
|
|
72
|
+
const hasSnapshot = hasSnapshots && !withoutSnapshotIndexes.includes(index); // A document that has never been recorded has neither a snapshot ID nor an observed MIME type
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
id: `source-${index}`,
|
|
76
|
+
location: `https://example.com/source-${index}`,
|
|
77
|
+
mimeType: hasSnapshot ? 'text/html' : null,
|
|
78
|
+
snapshotId: hasSnapshot ? `snapshot-${index}` : null,
|
|
79
|
+
toPersistence: () => ({ fetch: `https://example.com/source-${index}` }),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
78
82
|
|
|
79
83
|
return {
|
|
80
84
|
service: { id: 'TestService', name: 'TestService' },
|
|
@@ -94,6 +98,40 @@ describe('Reporter', () => {
|
|
|
94
98
|
|
|
95
99
|
const error = { reasons: ['HTTP code 404'] };
|
|
96
100
|
|
|
101
|
+
context('when the source documents have been recorded as snapshots', () => {
|
|
102
|
+
it('mentions that the missed versions might be recovered', () => {
|
|
103
|
+
const description = buildReporter().generateDescription({ error, terms: buildTerms() });
|
|
104
|
+
|
|
105
|
+
expect(description).to.include('it might still be possible to recover the missed versions');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
context('when the source documents could not be recorded as snapshots', () => {
|
|
110
|
+
it('does not suggest that the missed versions might be recovered', () => {
|
|
111
|
+
const description = buildReporter().generateDescription({ error, terms: buildTerms({ hasSnapshots: false }) });
|
|
112
|
+
|
|
113
|
+
expect(description).to.include('has not been recorded as a snapshot');
|
|
114
|
+
expect(description).to.not.include('it might still be possible to recover the missed versions');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('omits the snapshot link, which would point to a nonexistent file', () => {
|
|
118
|
+
const description = buildReporter().generateDescription({ error, terms: buildTerms({ hasSnapshots: false }) });
|
|
119
|
+
|
|
120
|
+
expect(description).to.not.include('Latest snapshot');
|
|
121
|
+
expect(description).to.not.include('.null');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
context('when a source document of combined terms has never been recorded as a snapshot', () => {
|
|
126
|
+
it('omits its snapshot link and keeps the ones of the recorded documents', () => {
|
|
127
|
+
const description = buildReporter().generateDescription({ error, terms: buildTerms({ sourceCount: 2, withoutSnapshotIndexes: [1] }) });
|
|
128
|
+
|
|
129
|
+
expect(description).to.include('#source-0.html'); // The `#<id>` fragment only appears in snapshot links, unlike the document locations listed in the accessibility checklist
|
|
130
|
+
expect(description).to.not.include('#source-1');
|
|
131
|
+
expect(description).to.not.include('.null');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
97
135
|
context('when the terms has a single source document', () => {
|
|
98
136
|
it('deep-links to the contribution tool with the serialized declaration as the edit target', () => {
|
|
99
137
|
const description = buildReporter().generateDescription({ error, terms: buildTerms({ sourceCount: 1 }) });
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
|
|
5
|
-
import { expect } from 'chai';
|
|
6
|
-
import config from 'config';
|
|
7
|
-
|
|
8
|
-
import Git from './git.js';
|
|
9
|
-
|
|
10
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const RECORDER_PATH = path.resolve(__dirname, '../../../../../', config.get('@opentermsarchive/engine.recorder.versions.storage.git.path'));
|
|
12
|
-
|
|
13
|
-
describe('Git', () => {
|
|
14
|
-
const DEFAULT_CONTENT = 'default content';
|
|
15
|
-
const DEFAULT_COMMIT_MESSAGE = 'default commit message';
|
|
16
|
-
let subject;
|
|
17
|
-
|
|
18
|
-
before(() => {
|
|
19
|
-
subject = new Git({
|
|
20
|
-
path: RECORDER_PATH,
|
|
21
|
-
author: {
|
|
22
|
-
name: config.get('@opentermsarchive/engine.recorder.versions.storage.git.author.name'),
|
|
23
|
-
email: config.get('@opentermsarchive/engine.recorder.versions.storage.git.author.email'),
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
return subject.initialize();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('#commit', () => {
|
|
31
|
-
const expectedFilePath = `${RECORDER_PATH}/test.md`;
|
|
32
|
-
|
|
33
|
-
let commitId;
|
|
34
|
-
|
|
35
|
-
before(async () => {
|
|
36
|
-
await fs.writeFile(expectedFilePath, DEFAULT_CONTENT);
|
|
37
|
-
|
|
38
|
-
await subject.add(expectedFilePath);
|
|
39
|
-
commitId = await subject.commit({ filePath: expectedFilePath, message: DEFAULT_COMMIT_MESSAGE });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
after(() => subject.destroyHistory());
|
|
43
|
-
|
|
44
|
-
it('returns a full length SHA1 commit ID', () => {
|
|
45
|
-
expect(commitId).to.match(/\b[0-9a-f]{40}\b/);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
context('when stage area is dirty', () => {
|
|
49
|
-
const expectedFileName = 'file-to-commit.md';
|
|
50
|
-
const expectedFilePath = `${RECORDER_PATH}/${expectedFileName}`;
|
|
51
|
-
const unwantedFilePath = `${RECORDER_PATH}/unwanted-file.md`;
|
|
52
|
-
let commit;
|
|
53
|
-
let committedFiles;
|
|
54
|
-
let committedFileName;
|
|
55
|
-
|
|
56
|
-
before(async () => {
|
|
57
|
-
await fs.writeFile(expectedFilePath, DEFAULT_CONTENT);
|
|
58
|
-
await fs.writeFile(unwantedFilePath, DEFAULT_CONTENT);
|
|
59
|
-
|
|
60
|
-
await subject.add(expectedFilePath);
|
|
61
|
-
await subject.add(unwantedFilePath);
|
|
62
|
-
|
|
63
|
-
const commitId = await subject.commit({ filePath: expectedFilePath, message: DEFAULT_COMMIT_MESSAGE });
|
|
64
|
-
|
|
65
|
-
commit = await subject.getCommit([commitId]);
|
|
66
|
-
|
|
67
|
-
if (!commit) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
({ files: committedFiles } = commit.diff);
|
|
72
|
-
([{ file: committedFileName }] = committedFiles);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
after(() => subject.destroyHistory());
|
|
76
|
-
|
|
77
|
-
it('commits the specified file', () => {
|
|
78
|
-
expect(committedFileName).to.equal(expectedFileName);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('commits only one file', () => {
|
|
82
|
-
expect(committedFiles).to.have.lengthOf(1);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('#cleanUp', () => {
|
|
88
|
-
context('when a commit-graph lock has been left behind by an interrupted process', () => {
|
|
89
|
-
const infoDirectoryPath = path.join(RECORDER_PATH, '.git', 'objects', 'info');
|
|
90
|
-
const lockFilePath = path.join(infoDirectoryPath, 'commit-graph.lock');
|
|
91
|
-
|
|
92
|
-
before(async () => {
|
|
93
|
-
const filePath = `${RECORDER_PATH}/file-to-clean.md`;
|
|
94
|
-
|
|
95
|
-
await fs.writeFile(filePath, DEFAULT_CONTENT);
|
|
96
|
-
await subject.add(filePath);
|
|
97
|
-
await subject.commit({ filePath, message: DEFAULT_COMMIT_MESSAGE });
|
|
98
|
-
|
|
99
|
-
await fs.mkdir(infoDirectoryPath, { recursive: true });
|
|
100
|
-
await fs.writeFile(lockFilePath, '');
|
|
101
|
-
|
|
102
|
-
await subject.cleanUp();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
after(() => subject.destroyHistory());
|
|
106
|
-
|
|
107
|
-
it('removes the stale commit-graph lock', async () => {
|
|
108
|
-
const infoDirectoryContent = await fs.readdir(infoDirectoryPath);
|
|
109
|
-
|
|
110
|
-
expect(infoDirectoryContent).to.not.include('commit-graph.lock');
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
});
|
|
File without changes
|
|
File without changes
|