@shuvi/reporters 1.0.30
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/lib/helper/anonymous-meta.d.ts +25 -0
- package/lib/helper/anonymous-meta.js +99 -0
- package/lib/helper/post-payload.d.ts +1 -0
- package/lib/helper/post-payload.js +28 -0
- package/lib/helper/project-id.d.ts +1 -0
- package/lib/helper/project-id.js +20 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +20 -0
- package/lib/telemetry.d.ts +49 -0
- package/lib/telemetry.js +171 -0
- package/lib/to-json.d.ts +11 -0
- package/lib/to-json.js +150 -0
- package/package.json +32 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
declare type PackageManager = {
|
|
3
|
+
packageManager: 'npm' | 'pnpm' | 'yarn';
|
|
4
|
+
packageManagerVersion: string | undefined;
|
|
5
|
+
};
|
|
6
|
+
declare type AnonymousMeta = {
|
|
7
|
+
systemPlatform: NodeJS.Platform;
|
|
8
|
+
systemRelease: string;
|
|
9
|
+
systemArchitecture: string;
|
|
10
|
+
cpuCount: number;
|
|
11
|
+
cpuModel: string | null;
|
|
12
|
+
cpuSpeed: number | null;
|
|
13
|
+
memoryInMb: number;
|
|
14
|
+
isDocker: boolean;
|
|
15
|
+
isWsl: boolean;
|
|
16
|
+
isCI: boolean;
|
|
17
|
+
ciName: string | null;
|
|
18
|
+
packageManager: string;
|
|
19
|
+
packageManagerVersion: string | undefined;
|
|
20
|
+
nodeVersion: string;
|
|
21
|
+
shuviVersion?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function getAnonymousMeta(): AnonymousMeta;
|
|
24
|
+
export declare function getPkgManager(): PackageManager;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.getPkgManager = exports.getAnonymousMeta = void 0;
|
|
30
|
+
const is_docker_1 = __importDefault(require("is-docker"));
|
|
31
|
+
const is_wsl_1 = __importDefault(require("is-wsl"));
|
|
32
|
+
const os_1 = __importDefault(require("os"));
|
|
33
|
+
const child_process_1 = require("child_process");
|
|
34
|
+
const ciEnvironment = __importStar(require("ci-info"));
|
|
35
|
+
let traits;
|
|
36
|
+
function getAnonymousMeta() {
|
|
37
|
+
if (traits) {
|
|
38
|
+
return traits;
|
|
39
|
+
}
|
|
40
|
+
const cpus = os_1.default.cpus() || [];
|
|
41
|
+
const { packageManager, packageManagerVersion } = getPkgManager();
|
|
42
|
+
traits = {
|
|
43
|
+
// Software information
|
|
44
|
+
systemPlatform: os_1.default.platform(),
|
|
45
|
+
systemRelease: os_1.default.release(),
|
|
46
|
+
systemArchitecture: os_1.default.arch(),
|
|
47
|
+
// Machine information
|
|
48
|
+
cpuCount: cpus.length,
|
|
49
|
+
cpuModel: cpus.length ? cpus[0].model : null,
|
|
50
|
+
cpuSpeed: cpus.length ? cpus[0].speed : null,
|
|
51
|
+
memoryInMb: Math.trunc(os_1.default.totalmem() / Math.pow(1024, 2)),
|
|
52
|
+
// Environment information
|
|
53
|
+
isDocker: (0, is_docker_1.default)(),
|
|
54
|
+
isWsl: is_wsl_1.default,
|
|
55
|
+
isCI: ciEnvironment.isCI,
|
|
56
|
+
ciName: (ciEnvironment.isCI && ciEnvironment.name) || null,
|
|
57
|
+
packageManager,
|
|
58
|
+
packageManagerVersion,
|
|
59
|
+
nodeVersion: process.version
|
|
60
|
+
};
|
|
61
|
+
return traits;
|
|
62
|
+
}
|
|
63
|
+
exports.getAnonymousMeta = getAnonymousMeta;
|
|
64
|
+
function getPkgManager() {
|
|
65
|
+
try {
|
|
66
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
67
|
+
if (userAgent) {
|
|
68
|
+
const packageManagerVersion = userAgent.split(' ')[0].split('/')[1];
|
|
69
|
+
if (userAgent.startsWith('yarn')) {
|
|
70
|
+
return { packageManager: 'yarn', packageManagerVersion };
|
|
71
|
+
}
|
|
72
|
+
else if (userAgent.startsWith('pnpm')) {
|
|
73
|
+
return { packageManager: 'pnpm', packageManagerVersion };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const packageManagerVersion = (0, child_process_1.execSync)(`yarn --version`)
|
|
78
|
+
.toString()
|
|
79
|
+
.trim();
|
|
80
|
+
return { packageManager: 'yarn', packageManagerVersion };
|
|
81
|
+
}
|
|
82
|
+
catch (_a) {
|
|
83
|
+
const packageManagerVersion = (0, child_process_1.execSync)(`pnpm --version`)
|
|
84
|
+
.toString()
|
|
85
|
+
.trim();
|
|
86
|
+
return { packageManager: 'pnpm', packageManagerVersion };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (_b) {
|
|
90
|
+
try {
|
|
91
|
+
const packageManagerVersion = (0, child_process_1.execSync)(`npm --version`).toString().trim();
|
|
92
|
+
return { packageManager: 'npm', packageManagerVersion };
|
|
93
|
+
}
|
|
94
|
+
catch (_c) {
|
|
95
|
+
return { packageManager: 'npm', packageManagerVersion: undefined };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.getPkgManager = getPkgManager;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function _postPayload(endpoint: string, body: object): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports._postPayload = void 0;
|
|
7
|
+
const async_retry_1 = __importDefault(require("async-retry"));
|
|
8
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
9
|
+
function _postPayload(endpoint, body) {
|
|
10
|
+
return ((0, async_retry_1.default)(() => (0, node_fetch_1.default)(endpoint, {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
body: JSON.stringify(body),
|
|
13
|
+
headers: { 'content-type': 'application/json' },
|
|
14
|
+
timeout: 5000
|
|
15
|
+
}).then(res => {
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
const err = new Error(res.statusText);
|
|
18
|
+
err.response = res;
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
}), { minTimeout: 500, retries: 1, factor: 1 })
|
|
22
|
+
.catch(() => {
|
|
23
|
+
// We swallow errors when telemetry cannot be sent
|
|
24
|
+
})
|
|
25
|
+
// Ensure promise is voided
|
|
26
|
+
.then(() => { }, () => { }));
|
|
27
|
+
}
|
|
28
|
+
exports._postPayload = _postPayload;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getRawProjectId(): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRawProjectId = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
function _getProjectIdByGit() {
|
|
6
|
+
try {
|
|
7
|
+
const originBuffer = (0, child_process_1.execSync)(`git config --local --get remote.origin.url`, {
|
|
8
|
+
timeout: 1000,
|
|
9
|
+
stdio: `pipe`
|
|
10
|
+
});
|
|
11
|
+
return String(originBuffer).trim();
|
|
12
|
+
}
|
|
13
|
+
catch (_) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function getRawProjectId() {
|
|
18
|
+
return _getProjectIdByGit() || process.env.REPOSITORY_URL || process.cwd();
|
|
19
|
+
}
|
|
20
|
+
exports.getRawProjectId = getRawProjectId;
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This package 'telemetry' is a modified version of the Next.js that can be found here:
|
|
3
|
+
// https://github.com/vercel/next.js/tree/canary/packages/next/telemetry
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
16
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
__exportStar(require("./telemetry"), exports);
|
|
20
|
+
__exportStar(require("./to-json"), exports);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Reporter } from '@shuvi/shared/reporter';
|
|
2
|
+
export declare type TelemetryEvent = {
|
|
3
|
+
eventName: string;
|
|
4
|
+
payload: object;
|
|
5
|
+
};
|
|
6
|
+
export declare type EventContext = {
|
|
7
|
+
anonymousId?: string;
|
|
8
|
+
projectId?: string;
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
export declare type EventMeta = {
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
};
|
|
15
|
+
export declare type EventBatchShape = {
|
|
16
|
+
eventName: string;
|
|
17
|
+
fields: object;
|
|
18
|
+
};
|
|
19
|
+
export declare type RecordObject = {
|
|
20
|
+
isFulfilled: boolean;
|
|
21
|
+
isRejected: boolean;
|
|
22
|
+
value?: any;
|
|
23
|
+
reason?: any;
|
|
24
|
+
};
|
|
25
|
+
export declare class Telemetry {
|
|
26
|
+
private name;
|
|
27
|
+
private conf;
|
|
28
|
+
private sessionId;
|
|
29
|
+
private rawProjectId;
|
|
30
|
+
private projectId;
|
|
31
|
+
private meta;
|
|
32
|
+
private context;
|
|
33
|
+
private postEndpoint;
|
|
34
|
+
private queue;
|
|
35
|
+
constructor({ name, meta, context, postEndpoint }: {
|
|
36
|
+
name: string;
|
|
37
|
+
meta?: EventMeta;
|
|
38
|
+
context?: EventContext;
|
|
39
|
+
postEndpoint?: string;
|
|
40
|
+
});
|
|
41
|
+
get anonymousId(): string;
|
|
42
|
+
get salt(): string;
|
|
43
|
+
report: Reporter;
|
|
44
|
+
record(_events: TelemetryEvent | TelemetryEvent[]): Promise<RecordObject>;
|
|
45
|
+
flush(): Promise<void>;
|
|
46
|
+
private _notify;
|
|
47
|
+
private _oneWayHash;
|
|
48
|
+
private _submitRecord;
|
|
49
|
+
}
|
package/lib/telemetry.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.Telemetry = void 0;
|
|
16
|
+
const chalk_1 = __importDefault(require("@shuvi/utils/chalk"));
|
|
17
|
+
const conf_1 = __importDefault(require("conf"));
|
|
18
|
+
const crypto_1 = require("crypto");
|
|
19
|
+
const anonymous_meta_1 = require("./helper/anonymous-meta");
|
|
20
|
+
const post_payload_1 = require("./helper/post-payload");
|
|
21
|
+
const project_id_1 = require("./helper/project-id");
|
|
22
|
+
// This is the key that specifies when the user was informed about anonymous
|
|
23
|
+
// telemetry collection.
|
|
24
|
+
const TELEMETRY_KEY_NOTIFY_DATE = 'telemetry.notifiedAt';
|
|
25
|
+
// This is a quasi-persistent identifier used to dedupe recurring events. It's
|
|
26
|
+
// generated from random data and completely anonymous.
|
|
27
|
+
const TELEMETRY_KEY_ID = `telemetry.anonymousId`;
|
|
28
|
+
// This is the cryptographic salt that is included within every hashed value.
|
|
29
|
+
// This salt value is never sent to us, ensuring privacy and the one-way nature
|
|
30
|
+
// of the hash (prevents dictionary lookups of pre-computed hashes).
|
|
31
|
+
// See the `oneWayHash` function.
|
|
32
|
+
const TELEMETRY_KEY_SALT = `telemetry.salt`;
|
|
33
|
+
class Telemetry {
|
|
34
|
+
constructor({ name, meta, context, postEndpoint }) {
|
|
35
|
+
this.report = ({ timestamp, name, duration, startTime, id, parentId, attrs }) => {
|
|
36
|
+
this.record({
|
|
37
|
+
eventName: name,
|
|
38
|
+
payload: {
|
|
39
|
+
parentId,
|
|
40
|
+
name,
|
|
41
|
+
id,
|
|
42
|
+
startTime,
|
|
43
|
+
duration,
|
|
44
|
+
timestamp,
|
|
45
|
+
tags: attrs
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
// `conf` incorrectly throws a permission error during initialization
|
|
51
|
+
// instead of waiting for first use. We need to handle it, otherwise the
|
|
52
|
+
// process may crash.
|
|
53
|
+
this.conf = new conf_1.default({ projectName: 'shuvijs' });
|
|
54
|
+
}
|
|
55
|
+
catch (_) {
|
|
56
|
+
this.conf = null;
|
|
57
|
+
}
|
|
58
|
+
this.name = name;
|
|
59
|
+
this.sessionId = (0, crypto_1.randomBytes)(32).toString('hex');
|
|
60
|
+
this.rawProjectId = (0, project_id_1.getRawProjectId)();
|
|
61
|
+
this.projectId = this._oneWayHash(this.rawProjectId);
|
|
62
|
+
this.postEndpoint = postEndpoint;
|
|
63
|
+
this.queue = new Set();
|
|
64
|
+
this._notify();
|
|
65
|
+
this.meta = Object.assign(Object.assign({}, (0, anonymous_meta_1.getAnonymousMeta)()), meta);
|
|
66
|
+
this.context = Object.assign({ anonymousId: this.anonymousId, projectId: this.projectId, sessionId: this.sessionId }, context);
|
|
67
|
+
Object.freeze(this.meta);
|
|
68
|
+
Object.freeze(this.context);
|
|
69
|
+
}
|
|
70
|
+
get anonymousId() {
|
|
71
|
+
const val = this.conf && this.conf.get(TELEMETRY_KEY_ID);
|
|
72
|
+
if (val) {
|
|
73
|
+
return val;
|
|
74
|
+
}
|
|
75
|
+
const generated = (0, crypto_1.randomBytes)(32).toString('hex');
|
|
76
|
+
this.conf && this.conf.set(TELEMETRY_KEY_ID, generated);
|
|
77
|
+
return generated;
|
|
78
|
+
}
|
|
79
|
+
get salt() {
|
|
80
|
+
const val = this.conf && this.conf.get(TELEMETRY_KEY_SALT);
|
|
81
|
+
if (val) {
|
|
82
|
+
return val;
|
|
83
|
+
}
|
|
84
|
+
const generated = (0, crypto_1.randomBytes)(16).toString('hex');
|
|
85
|
+
this.conf && this.conf.set(TELEMETRY_KEY_SALT, generated);
|
|
86
|
+
return generated;
|
|
87
|
+
}
|
|
88
|
+
record(_events) {
|
|
89
|
+
const _this = this;
|
|
90
|
+
// pseudo try-catch
|
|
91
|
+
function wrapper() {
|
|
92
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
93
|
+
return yield _this._submitRecord(_events);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const prom = wrapper()
|
|
97
|
+
.then(value => ({
|
|
98
|
+
isFulfilled: true,
|
|
99
|
+
isRejected: false,
|
|
100
|
+
value
|
|
101
|
+
}))
|
|
102
|
+
.catch(reason => ({
|
|
103
|
+
isFulfilled: false,
|
|
104
|
+
isRejected: true,
|
|
105
|
+
reason
|
|
106
|
+
}))
|
|
107
|
+
// Acts as `Promise#finally` because `catch` transforms the error
|
|
108
|
+
.then(res => {
|
|
109
|
+
// Clean up the event to prevent unbounded `Set` growth
|
|
110
|
+
this.queue.delete(prom);
|
|
111
|
+
return res;
|
|
112
|
+
});
|
|
113
|
+
// Track this `Promise` so we can flush pending events
|
|
114
|
+
this.queue.add(prom);
|
|
115
|
+
return prom;
|
|
116
|
+
}
|
|
117
|
+
flush() {
|
|
118
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
119
|
+
yield Promise.all(this.queue).catch(() => null);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
_notify() {
|
|
123
|
+
if (!this.conf) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// The end-user has already been notified about our telemetry integration. We
|
|
127
|
+
// don't need to constantly annoy them about it.
|
|
128
|
+
// We will re-inform users about the telemetry if significant changes are
|
|
129
|
+
// ever made.
|
|
130
|
+
if (this.conf.get(TELEMETRY_KEY_NOTIFY_DATE, '')) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.conf.set(TELEMETRY_KEY_NOTIFY_DATE, Date.now().toString());
|
|
134
|
+
console.log(`${chalk_1.default.magenta.bold('Attention')}: ${this.name} now collects completely anonymous telemetry regarding usage.`);
|
|
135
|
+
console.log();
|
|
136
|
+
}
|
|
137
|
+
_oneWayHash(payload) {
|
|
138
|
+
const hash = (0, crypto_1.createHash)('sha256');
|
|
139
|
+
// Always prepend the payload value with salt. This ensures the hash is truly
|
|
140
|
+
// one-way.
|
|
141
|
+
hash.update(this.salt);
|
|
142
|
+
// Update is an append operation, not a replacement. The salt from the prior
|
|
143
|
+
// update is still present!
|
|
144
|
+
hash.update(payload);
|
|
145
|
+
return hash.digest('hex');
|
|
146
|
+
}
|
|
147
|
+
_submitRecord(_events) {
|
|
148
|
+
let events;
|
|
149
|
+
if (Array.isArray(_events)) {
|
|
150
|
+
events = _events;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
events = [_events];
|
|
154
|
+
}
|
|
155
|
+
if (events.length < 1) {
|
|
156
|
+
return Promise.resolve();
|
|
157
|
+
}
|
|
158
|
+
if (!this.postEndpoint) {
|
|
159
|
+
return Promise.resolve();
|
|
160
|
+
}
|
|
161
|
+
return (0, post_payload_1._postPayload)(this.postEndpoint, {
|
|
162
|
+
context: this.context,
|
|
163
|
+
meta: this.meta,
|
|
164
|
+
events: events.map(({ eventName, payload }) => ({
|
|
165
|
+
eventName,
|
|
166
|
+
fields: payload
|
|
167
|
+
}))
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.Telemetry = Telemetry;
|
package/lib/to-json.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Reporter } from '@shuvi/shared/reporter';
|
|
2
|
+
export declare class ToJson {
|
|
3
|
+
private phase;
|
|
4
|
+
private buildDir;
|
|
5
|
+
private traceId;
|
|
6
|
+
private writeStream;
|
|
7
|
+
private batch;
|
|
8
|
+
constructor(phase: string, buildDir: string, traceId: string);
|
|
9
|
+
flushAll: () => void;
|
|
10
|
+
report: Reporter;
|
|
11
|
+
}
|
package/lib/to-json.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ToJson = void 0;
|
|
16
|
+
const crypto_1 = require("crypto");
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
// Batch events as zipkin allows for multiple events to be sent in one go
|
|
20
|
+
function batcher(reportEvents) {
|
|
21
|
+
const events = [];
|
|
22
|
+
// Promise queue to ensure events are always sent on flushAll
|
|
23
|
+
const queue = new Set();
|
|
24
|
+
return {
|
|
25
|
+
flushAll: () => __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
yield Promise.all(queue);
|
|
27
|
+
if (events.length > 0) {
|
|
28
|
+
yield reportEvents(events);
|
|
29
|
+
events.length = 0;
|
|
30
|
+
}
|
|
31
|
+
}),
|
|
32
|
+
report: (event) => {
|
|
33
|
+
events.push(event);
|
|
34
|
+
if (events.length > 100) {
|
|
35
|
+
const evts = events.slice();
|
|
36
|
+
events.length = 0;
|
|
37
|
+
const report = reportEvents(evts);
|
|
38
|
+
queue.add(report);
|
|
39
|
+
report.then(() => queue.delete(report));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const writeStreamOptions = {
|
|
45
|
+
flags: 'a',
|
|
46
|
+
encoding: 'utf8'
|
|
47
|
+
};
|
|
48
|
+
class RotatingWriteStream {
|
|
49
|
+
constructor(file, sizeLimit) {
|
|
50
|
+
this.file = file;
|
|
51
|
+
this.size = 0;
|
|
52
|
+
this.sizeLimit = sizeLimit;
|
|
53
|
+
this.createWriteStream();
|
|
54
|
+
}
|
|
55
|
+
createWriteStream() {
|
|
56
|
+
this.writeStream = fs_1.default.createWriteStream(this.file, writeStreamOptions);
|
|
57
|
+
}
|
|
58
|
+
// Recreate the file
|
|
59
|
+
rotate() {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
yield this.end();
|
|
62
|
+
try {
|
|
63
|
+
fs_1.default.unlinkSync(this.file);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
// It's fine if the file does not exist yet
|
|
67
|
+
if (err.code !== 'ENOENT') {
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this.size = 0;
|
|
72
|
+
this.createWriteStream();
|
|
73
|
+
this.rotatePromise = undefined;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
write(data) {
|
|
77
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
if (this.rotatePromise)
|
|
79
|
+
yield this.rotatePromise;
|
|
80
|
+
this.size += data.length;
|
|
81
|
+
if (this.size > this.sizeLimit) {
|
|
82
|
+
yield (this.rotatePromise = this.rotate());
|
|
83
|
+
}
|
|
84
|
+
if (!this.writeStream.write(data, 'utf8')) {
|
|
85
|
+
if (this.drainPromise === undefined) {
|
|
86
|
+
this.drainPromise = new Promise((resolve, _reject) => {
|
|
87
|
+
this.writeStream.once('drain', () => {
|
|
88
|
+
this.drainPromise = undefined;
|
|
89
|
+
resolve();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
yield this.drainPromise;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
end() {
|
|
98
|
+
return new Promise(resolve => {
|
|
99
|
+
this.writeStream.end(resolve);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
class ToJson {
|
|
104
|
+
constructor(phase, buildDir, traceId) {
|
|
105
|
+
this.flushAll = () => {
|
|
106
|
+
this.batch.flushAll().then(() => {
|
|
107
|
+
var _a;
|
|
108
|
+
// Only end writeStream when manually flushing in production
|
|
109
|
+
if (this.phase !== 'PHASE_DEVELOPMENT_SERVER') {
|
|
110
|
+
(_a = this.writeStream) === null || _a === void 0 ? void 0 : _a.end();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
this.report = ({ timestamp, name, duration, startTime, id, parentId, attrs }) => {
|
|
115
|
+
if (!this.buildDir || !this.phase) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.batch.report({
|
|
119
|
+
traceId: this.traceId,
|
|
120
|
+
parentId,
|
|
121
|
+
name,
|
|
122
|
+
id,
|
|
123
|
+
startTime,
|
|
124
|
+
duration,
|
|
125
|
+
timestamp,
|
|
126
|
+
tags: attrs
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
this.phase = phase;
|
|
130
|
+
this.buildDir = buildDir;
|
|
131
|
+
this.traceId = traceId || (0, crypto_1.randomBytes)(8).toString('hex');
|
|
132
|
+
this.batch = batcher((events) => __awaiter(this, void 0, void 0, function* () {
|
|
133
|
+
if (!this.writeStream) {
|
|
134
|
+
yield fs_1.default.promises.mkdir(this.buildDir, { recursive: true });
|
|
135
|
+
const file = path_1.default.join(this.buildDir, 'trace');
|
|
136
|
+
this.writeStream = new RotatingWriteStream(file,
|
|
137
|
+
// Development is limited to 50MB, production is unlimited
|
|
138
|
+
this.phase === 'PHASE_DEVELOPMENT_SERVER' ? 52428800 : Infinity);
|
|
139
|
+
}
|
|
140
|
+
const eventsJson = JSON.stringify(events);
|
|
141
|
+
try {
|
|
142
|
+
yield this.writeStream.write(eventsJson + '\n');
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
console.log(err);
|
|
146
|
+
}
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.ToJson = ToJson;
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shuvi/reporters",
|
|
3
|
+
"version": "1.0.30",
|
|
4
|
+
"author": "liximomo",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "tsc -p tsconfig.build.json -m commonjs -w",
|
|
12
|
+
"prebuild": "rimraf lib",
|
|
13
|
+
"build": "tsc -p tsconfig.build.json -m commonjs"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@shuvi/shared": "1.0.30",
|
|
17
|
+
"@shuvi/utils": "1.0.30",
|
|
18
|
+
"conf": "5.0.0",
|
|
19
|
+
"is-docker": "2.0.0",
|
|
20
|
+
"is-wsl": "2.2.0",
|
|
21
|
+
"ci-info": "3.6.2",
|
|
22
|
+
"async-retry": "1.3.3",
|
|
23
|
+
"node-fetch": "2.6.7"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/async-retry": "1.3.0",
|
|
27
|
+
"@types/node-fetch": "2.6.1"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=12.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|