@opentermsarchive/engine 2.5.0 → 2.7.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.
@@ -48,7 +48,8 @@
48
48
  "host": "smtp-relay.sendinblue.com",
49
49
  "username": "admin@opentermsarchive.org"
50
50
  },
51
- "sendMailOnError": false
51
+ "sendMailOnError": false,
52
+ "timestampPrefix": true
52
53
  },
53
54
  "notifier": {
54
55
  "sendInBlue": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentermsarchive/engine",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Tracks and makes visible changes to the terms of online services",
5
5
  "homepage": "https://opentermsarchive.org",
6
6
  "bugs": {
@@ -1,3 +1,4 @@
1
+ import config from 'config';
1
2
  import winston from 'winston';
2
3
 
3
4
  import logger from '../../../src/logger/index.js';
@@ -6,11 +7,13 @@ const { combine, timestamp, printf, colorize } = winston.format;
6
7
 
7
8
  logger.format = combine(
8
9
  colorize(),
9
- timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
10
+ timestamp({ format: 'YYYY-MM-DDTHH:MM:SSZ' }),
10
11
  printf(({ level, message, counter, hash, timestamp }) => {
11
12
  const prefix = counter && hash ? `${counter.toString().padEnd(6)} ${hash.padEnd(40)}` : '';
12
13
 
13
- return `${timestamp} ${level.padEnd(15)} ${prefix.padEnd(50)} ${message}`;
14
+ const timestampPrefix = config.get('@opentermsarchive/engine.logger.timestampPrefix') ? `${timestamp} ` : '';
15
+
16
+ return `${timestampPrefix}${level.padEnd(15)} ${prefix.padEnd(50)} ${message}`;
14
17
  }),
15
18
  );
16
19
 
@@ -0,0 +1,36 @@
1
+ import fsApi from 'fs';
2
+ import path from 'path';
3
+ import url from 'url';
4
+
5
+ import config from 'config';
6
+ import { Octokit } from 'octokit';
7
+
8
+ import * as readme from '../../assets/README.template.js';
9
+
10
+ export default async function publish({ archivePath, releaseDate, stats }) {
11
+ const octokit = new Octokit({ auth: process.env.OTA_ENGINE_GITHUB_TOKEN });
12
+
13
+ const [ owner, repo ] = url.parse(config.get('@opentermsarchive/engine.dataset.versionsRepositoryURL')).pathname.split('/').filter(component => component);
14
+
15
+ const tagName = `${path.basename(archivePath, path.extname(archivePath))}`; // use archive filename as Git tag
16
+
17
+ const { data: { upload_url: uploadUrl, html_url: releaseUrl } } = await octokit.rest.repos.createRelease({
18
+ owner,
19
+ repo,
20
+ tag_name: tagName,
21
+ name: readme.title({ releaseDate }),
22
+ body: readme.body(stats),
23
+ });
24
+
25
+ await octokit.rest.repos.uploadReleaseAsset({
26
+ data: fsApi.readFileSync(archivePath),
27
+ headers: {
28
+ 'content-type': 'application/zip',
29
+ 'content-length': fsApi.statSync(archivePath).size,
30
+ },
31
+ name: path.basename(archivePath),
32
+ url: uploadUrl,
33
+ });
34
+
35
+ return releaseUrl;
36
+ }
@@ -0,0 +1,133 @@
1
+ import fsApi from 'fs';
2
+ import path from 'path';
3
+
4
+ import config from 'config';
5
+ import dotenv from 'dotenv';
6
+ import FormData from 'form-data';
7
+ import nodeFetch from 'node-fetch';
8
+
9
+ import GitLab from '../../../../src/reporter/gitlab/index.js';
10
+ import * as readme from '../../assets/README.template.js';
11
+ import logger from '../../logger/index.js';
12
+
13
+ dotenv.config();
14
+
15
+ export default async function publish({
16
+ archivePath,
17
+ releaseDate,
18
+ stats,
19
+ }) {
20
+ let projectId = null;
21
+ const gitlabAPIUrl = config.get('@opentermsarchive/engine.dataset.apiBaseURL');
22
+
23
+ const [ owner, repo ] = new URL(config.get('@opentermsarchive/engine.dataset.versionsRepositoryURL'))
24
+ .pathname
25
+ .split('/')
26
+ .filter(Boolean);
27
+ const commonParams = { owner, repo };
28
+
29
+ try {
30
+ const repositoryPath = `${commonParams.owner}/${commonParams.repo}`;
31
+
32
+ const options = GitLab.baseOptionsHttpReq(process.env.OTA_ENGINE_GITLAB_RELEASES_TOKEN);
33
+
34
+ options.method = 'GET';
35
+ options.headers = {
36
+ 'Content-Type': 'application/json',
37
+ ...options.headers,
38
+ };
39
+
40
+ const response = await nodeFetch(
41
+ `${gitlabAPIUrl}/projects/${encodeURIComponent(repositoryPath)}`,
42
+ options,
43
+ );
44
+ const res = await response.json();
45
+
46
+ projectId = res.id;
47
+ } catch (error) {
48
+ logger.error(`Error while obtaining projectId: ${error}`);
49
+ projectId = null;
50
+ }
51
+
52
+ const tagName = `${path.basename(archivePath, path.extname(archivePath))}`; // use archive filename as Git tag
53
+
54
+ try {
55
+ let options = GitLab.baseOptionsHttpReq(process.env.OTA_ENGINE_GITLAB_RELEASES_TOKEN);
56
+
57
+ options.method = 'POST';
58
+ options.body = {
59
+ ref: 'main',
60
+ tag_name: tagName,
61
+ name: readme.title({ releaseDate }),
62
+ description: readme.body(stats),
63
+ };
64
+ options.headers = {
65
+ 'Content-Type': 'application/json',
66
+ ...options.headers,
67
+ };
68
+
69
+ options.body = JSON.stringify(options.body);
70
+
71
+ const releaseResponse = await nodeFetch(
72
+ `${gitlabAPIUrl}/projects/${projectId}/releases`,
73
+ options,
74
+ );
75
+ const releaseRes = await releaseResponse.json();
76
+
77
+ const releaseId = releaseRes.commit.id;
78
+
79
+ logger.info(`Created release with releaseId: ${releaseId}`);
80
+
81
+ // Upload the package
82
+ options = GitLab.baseOptionsHttpReq(process.env.OTA_ENGINE_GITLAB_RELEASES_TOKEN);
83
+ options.method = 'PUT';
84
+ options.body = fsApi.createReadStream(archivePath);
85
+
86
+ // restrict characters to the ones allowed by GitLab APIs
87
+ const packageName = config.get('@opentermsarchive/engine.dataset.title').replace(/[^a-zA-Z0-9.\-_]/g, '-');
88
+ const packageVersion = tagName.replace(/[^a-zA-Z0-9.\-_]/g, '-');
89
+ const packageFileName = archivePath.replace(/[^a-zA-Z0-9.\-_/]/g, '-');
90
+
91
+ logger.debug(`packageName: ${packageName}, packageVersion: ${packageVersion} packageFileName: ${packageFileName}`);
92
+
93
+ const packageResponse = await nodeFetch(
94
+ `${gitlabAPIUrl}/projects/${projectId}/packages/generic/${packageName}/${packageVersion}/${packageFileName}?status=default&select=package_file`,
95
+ options,
96
+ );
97
+ const packageRes = await packageResponse.json();
98
+
99
+ const packageFilesId = packageRes.id;
100
+
101
+ logger.debug(`package file id: ${packageFilesId}`);
102
+
103
+ // use the package id to build the download url for the release
104
+ const publishedPackageUrl = `${config.get('@opentermsarchive/engine.dataset.versionsRepositoryURL')}/-/package_files/${packageFilesId}/download`;
105
+
106
+ // Create the release and link the package
107
+ const formData = new FormData();
108
+
109
+ formData.append('name', archivePath);
110
+ formData.append('url', publishedPackageUrl);
111
+ formData.append('file', fsApi.createReadStream(archivePath), { filename: path.basename(archivePath) });
112
+
113
+ options = GitLab.baseOptionsHttpReq(process.env.OTA_ENGINE_GITLAB_RELEASES_TOKEN);
114
+ options.method = 'POST';
115
+ options.headers = {
116
+ ...formData.getHeaders(),
117
+ ...options.headers,
118
+ };
119
+ options.body = formData;
120
+
121
+ const uploadResponse = await nodeFetch(
122
+ `${gitlabAPIUrl}/projects/${projectId}/releases/${tagName}/assets/links`,
123
+ options,
124
+ );
125
+ const uploadRes = await uploadResponse.json();
126
+ const releaseUrl = uploadRes.direct_asset_url;
127
+
128
+ return releaseUrl;
129
+ } catch (error) {
130
+ logger.error('Failed to create release or upload ZIP file:', error);
131
+ throw error;
132
+ }
133
+ }
@@ -1,36 +1,15 @@
1
- import fsApi from 'fs';
2
- import path from 'path';
3
- import url from 'url';
1
+ import publishGitHub from './github/index.js';
2
+ import publishGitLab from './gitlab/index.js';
4
3
 
5
- import config from 'config';
6
- import { Octokit } from 'octokit';
4
+ export default function publishRelease({ archivePath, releaseDate, stats }) {
5
+ // If both GitHub and GitLab tokens are defined, GitHub takes precedence
6
+ if (process.env.OTA_ENGINE_GITHUB_TOKEN) {
7
+ return publishGitHub({ archivePath, releaseDate, stats });
8
+ }
7
9
 
8
- import * as readme from '../assets/README.template.js';
10
+ if (process.env.OTA_ENGINE_GITLAB_TOKEN) {
11
+ return publishGitLab({ archivePath, releaseDate, stats });
12
+ }
9
13
 
10
- export default async function publish({ archivePath, releaseDate, stats }) {
11
- const octokit = new Octokit({ auth: process.env.OTA_ENGINE_GITHUB_TOKEN });
12
-
13
- const [ owner, repo ] = url.parse(config.get('@opentermsarchive/engine.dataset.versionsRepositoryURL')).pathname.split('/').filter(component => component);
14
-
15
- const tagName = `${path.basename(archivePath, path.extname(archivePath))}`; // use archive filename as Git tag
16
-
17
- const { data: { upload_url: uploadUrl, html_url: releaseUrl } } = await octokit.rest.repos.createRelease({
18
- owner,
19
- repo,
20
- tag_name: tagName,
21
- name: readme.title({ releaseDate }),
22
- body: readme.body(stats),
23
- });
24
-
25
- await octokit.rest.repos.uploadReleaseAsset({
26
- data: fsApi.readFileSync(archivePath),
27
- headers: {
28
- 'content-type': 'application/zip',
29
- 'content-length': fsApi.statSync(archivePath).size,
30
- },
31
- name: path.basename(archivePath),
32
- url: uploadUrl,
33
- });
34
-
35
- return releaseUrl;
14
+ throw new Error('No GitHub nor GitLab token found in environment variables (OTA_ENGINE_GITHUB_TOKEN or OTA_ENGINE_GITLAB_TOKEN). Cannot publish the dataset without authentication.');
36
15
  }
@@ -30,8 +30,12 @@ if (config.get('@opentermsarchive/engine.logger.sendMailOnError')) {
30
30
  const logger = winston.createLogger({
31
31
  format: combine(
32
32
  colorize(),
33
- timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
34
- printf(({ level, message, timestamp }) => `${timestamp} ${level.padEnd(15)} ${message}`),
33
+ timestamp({ format: 'YYYY-MM-DDTHH:MM:SSZ' }),
34
+ printf(({ level, message, timestamp }) => {
35
+ const timestampPrefix = config.get('@opentermsarchive/engine.logger.timestampPrefix') ? `${timestamp} ` : '';
36
+
37
+ return `${timestampPrefix}${level.padEnd(15)} ${message}`;
38
+ }),
35
39
  ),
36
40
  transports,
37
41
  rejectionHandlers: transports,
package/src/index.js CHANGED
@@ -55,21 +55,17 @@ export default async function track({ services, types, extractOnly, schedule })
55
55
  logger.warn('Environment variable "OTA_ENGINE_SENDINBLUE_API_KEY" was not found; the Notifier module will be ignored');
56
56
  }
57
57
 
58
- if (process.env.OTA_ENGINE_GITHUB_TOKEN) {
59
- if (config.has('@opentermsarchive/engine.reporter.githubIssues.repositories.declarations')) {
60
- try {
61
- const reporter = new Reporter(config.get('@opentermsarchive/engine.reporter'));
62
-
63
- await reporter.initialize();
64
- archivist.attach(reporter);
65
- } catch (error) {
66
- logger.error('Cannot instantiate the Reporter module; it will be ignored:', error);
67
- }
68
- } else {
69
- logger.warn('Configuration key "reporter.githubIssues.repositories.declarations" was not found; issues on the declarations repository cannot be created');
58
+ if (process.env.OTA_ENGINE_GITHUB_TOKEN || process.env.OTA_ENGINE_GITLAB_TOKEN) {
59
+ try {
60
+ const reporter = new Reporter(config.get('@opentermsarchive/engine.reporter'));
61
+
62
+ await reporter.initialize();
63
+ archivist.attach(reporter);
64
+ } catch (error) {
65
+ logger.error('Cannot instantiate the Reporter module; it will be ignored:', error);
70
66
  }
71
67
  } else {
72
- logger.warn('Environment variable "OTA_ENGINE_GITHUB_TOKEN" was not found; the Reporter module will be ignored');
68
+ logger.warn('Environment variable with token for GitHub or GitLab was not found; the Reporter module will be ignored');
73
69
  }
74
70
 
75
71
  if (!schedule) {
@@ -8,23 +8,17 @@ const { combine, timestamp, printf, colorize } = winston.format;
8
8
 
9
9
  const alignedWithColorsAndTime = combine(
10
10
  colorize(),
11
- timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
11
+ timestamp({ format: 'YYYY-MM-DDTHH:MM:SSZ' }),
12
12
  printf(({ level, message, timestamp, serviceId, termsType, documentId }) => {
13
- let prefix = '';
13
+ const servicePrefix = serviceId && termsType
14
+ ? `${serviceId} — ${termsType}${documentId ? `:${documentId}` : ''}`
15
+ : '';
14
16
 
15
- if (serviceId && termsType) {
16
- prefix = `${serviceId} — ${termsType}`;
17
- }
18
-
19
- if (documentId) {
20
- prefix = `${prefix}:${documentId}`;
21
- }
17
+ const truncatedPrefix = servicePrefix.length > 75 ? `${servicePrefix.slice(0, 74)}…` : servicePrefix;
22
18
 
23
- if (prefix.length > 75) {
24
- prefix = `${prefix.substring(0, 74)}…`;
25
- }
19
+ const timestampPrefix = config.get('@opentermsarchive/engine.logger.timestampPrefix') ? `${timestamp} ` : '';
26
20
 
27
- return `${timestamp} ${level.padEnd(15)} ${prefix.padEnd(75)} ${message}`;
21
+ return `${timestampPrefix}${level.padEnd(15)} ${truncatedPrefix.padEnd(75)} ${message}`;
28
22
  }),
29
23
  );
30
24
 
@@ -0,0 +1,13 @@
1
+ import GitHub from './github/index.js';
2
+ import GitLab from './gitlab/index.js';
3
+
4
+ export function createReporter(config) {
5
+ switch (config.type) {
6
+ case 'github':
7
+ return new GitHub(config.repositories.declarations);
8
+ case 'gitlab':
9
+ return new GitLab(config.repositories.declarations, config.baseURL, config.apiBaseURL);
10
+ default:
11
+ throw new Error(`Unsupported reporter type: ${config.type}`);
12
+ }
13
+ }
@@ -2,7 +2,7 @@ import { createRequire } from 'module';
2
2
 
3
3
  import { Octokit } from 'octokit';
4
4
 
5
- import logger from '../logger/index.js';
5
+ import logger from '../../logger/index.js';
6
6
 
7
7
  const require = createRequire(import.meta.url);
8
8
 
@@ -14,7 +14,7 @@ export default class GitHub {
14
14
  static ISSUE_STATE_ALL = 'all';
15
15
 
16
16
  constructor(repository) {
17
- const { version } = require('../../package.json');
17
+ const { version } = require('../../../package.json');
18
18
 
19
19
  this.octokit = new Octokit({
20
20
  auth: process.env.OTA_ENGINE_GITHUB_TOKEN,
@@ -198,4 +198,16 @@ export default class GitHub {
198
198
  logger.error(`Failed to update issue "${title}": ${error.stack}`);
199
199
  }
200
200
  }
201
+
202
+ generateDeclarationURL(serviceName) {
203
+ return `https://github.com/${this.commonParams.owner}/${this.commonParams.repo}/blob/main/declarations/${encodeURIComponent(serviceName)}.json`;
204
+ }
205
+
206
+ generateVersionURL(serviceName, termsType) {
207
+ return `https://github.com/${this.commonParams.owner}/${this.commonParams.repo}/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}.md`;
208
+ }
209
+
210
+ generateSnapshotsBaseUrl(serviceName, termsType) {
211
+ return `https://github.com/${this.commonParams.owner}/${this.commonParams.repo}/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}`;
212
+ }
201
213
  }
@@ -3,7 +3,7 @@ import { createRequire } from 'module';
3
3
  import { expect } from 'chai';
4
4
  import nock from 'nock';
5
5
 
6
- import GitHub from './github.js';
6
+ import GitHub from './index.js';
7
7
 
8
8
  const require = createRequire(import.meta.url);
9
9
 
@@ -2,7 +2,7 @@ import { createRequire } from 'module';
2
2
 
3
3
  import chai from 'chai';
4
4
 
5
- import { MANAGED_BY_OTA_MARKER } from './github.js';
5
+ import { MANAGED_BY_OTA_MARKER } from './index.js';
6
6
 
7
7
  const require = createRequire(import.meta.url);
8
8