@egintegrations/telemetry 0.1.0 → 0.2.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/README.md +123 -10
- package/dist/cli.js +503 -0
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @egintegrations/telemetry
|
|
2
2
|
|
|
3
3
|
Official JavaScript/TypeScript telemetry SDK for EGIntegrations client engines.
|
|
4
4
|
|
|
@@ -6,19 +6,132 @@ Official JavaScript/TypeScript telemetry SDK for EGIntegrations client engines.
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
# npm
|
|
9
|
-
npm install @
|
|
9
|
+
npm install @egintegrations/telemetry
|
|
10
10
|
|
|
11
11
|
# pnpm
|
|
12
|
-
pnpm add @
|
|
12
|
+
pnpm add @egintegrations/telemetry
|
|
13
13
|
|
|
14
14
|
# yarn
|
|
15
|
-
yarn add @
|
|
15
|
+
yarn add @egintegrations/telemetry
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
## Quick
|
|
18
|
+
## Quick Setup with CLI
|
|
19
|
+
|
|
20
|
+
The fastest way to integrate telemetry into your project:
|
|
21
|
+
|
|
22
|
+
### Interactive Setup
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @egintegrations/telemetry init
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The CLI will:
|
|
29
|
+
1. Detect your framework (Next.js, Express, Fastify, or generic)
|
|
30
|
+
2. Prompt for engine name, SKU version, and Control Center URL
|
|
31
|
+
3. Generate configuration file (`.egi/telemetry.json`)
|
|
32
|
+
4. Create integration code (`lib/telemetry.ts` for Next.js, `src/telemetry.ts` for Express)
|
|
33
|
+
5. Add environment variables (`.env.local` or `.env`)
|
|
34
|
+
6. Optionally generate status endpoint boilerplate
|
|
35
|
+
|
|
36
|
+
### Non-Interactive Setup
|
|
37
|
+
|
|
38
|
+
For CI/CD or automated setups:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @egintegrations/telemetry init \
|
|
42
|
+
--engine my-engine \
|
|
43
|
+
--sku 1.0.0 \
|
|
44
|
+
--url https://control-center.egintegrations.com \
|
|
45
|
+
--token your-auth-token
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### CLI Options
|
|
49
|
+
|
|
50
|
+
| Option | Description | Default |
|
|
51
|
+
|--------|-------------|---------|
|
|
52
|
+
| `--engine <name>` | Engine name (required) | - |
|
|
53
|
+
| `--sku <version>` | SKU version (required) | - |
|
|
54
|
+
| `--url <url>` | Control Center URL (required) | - |
|
|
55
|
+
| `--token <token>` | Auth token (optional) | - |
|
|
56
|
+
| `--modules <modules>` | Comma-separated module list | - |
|
|
57
|
+
| `--output <path>` | Config file location | `.egi/telemetry.json` |
|
|
58
|
+
| `--framework <type>` | Force framework type (nextjs, express, fastify, generic) | Auto-detected |
|
|
59
|
+
| `--no-interactive` | Skip prompts, fail on missing args | Interactive mode |
|
|
60
|
+
| `--no-codegen` | Only generate config file | Code generation enabled |
|
|
61
|
+
| `--status-endpoint` | Generate status endpoint boilerplate | false |
|
|
62
|
+
|
|
63
|
+
### Framework-Specific Examples
|
|
64
|
+
|
|
65
|
+
#### Next.js Project
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd my-nextjs-app
|
|
69
|
+
npx @egintegrations/telemetry init
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Generated Files:**
|
|
73
|
+
- `.egi/telemetry.json` - Configuration
|
|
74
|
+
- `lib/telemetry.ts` - Telemetry client singleton
|
|
75
|
+
- `.env.local` - Environment variables
|
|
76
|
+
- `app/api/engine-status/route.ts` - Status endpoint (if requested)
|
|
77
|
+
|
|
78
|
+
**Usage in Next.js:**
|
|
79
|
+
```typescript
|
|
80
|
+
// app/api/data/route.ts
|
|
81
|
+
import telemetry from '@/lib/telemetry';
|
|
82
|
+
|
|
83
|
+
export async function GET() {
|
|
84
|
+
await telemetry.sendMetrics({
|
|
85
|
+
metrics: { api_calls: 1 }
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return Response.json({ ok: true });
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Express Project
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
cd my-express-app
|
|
96
|
+
npx @egintegrations/telemetry init
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Generated Files:**
|
|
100
|
+
- `.egi/telemetry.json` - Configuration
|
|
101
|
+
- `src/telemetry.ts` - Telemetry client
|
|
102
|
+
- `.env` - Environment variables
|
|
103
|
+
- `src/routes/status.ts` - Status endpoint (if requested)
|
|
104
|
+
|
|
105
|
+
**Usage in Express:**
|
|
106
|
+
```typescript
|
|
107
|
+
// src/index.ts
|
|
108
|
+
import express from 'express';
|
|
109
|
+
import telemetry from './telemetry';
|
|
110
|
+
|
|
111
|
+
const app = express();
|
|
112
|
+
|
|
113
|
+
// Auto-track requests
|
|
114
|
+
app.use(telemetry.middleware());
|
|
115
|
+
|
|
116
|
+
// Use in routes
|
|
117
|
+
app.get('/data', async (req, res) => {
|
|
118
|
+
await telemetry.sendMetrics({ metrics: { requests: 1 } });
|
|
119
|
+
res.json({ ok: true });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
app.listen(3000, async () => {
|
|
123
|
+
await telemetry.register();
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Manual Setup (Advanced)
|
|
128
|
+
|
|
129
|
+
If you prefer manual setup instead of using the CLI:
|
|
130
|
+
|
|
131
|
+
### Quick Start
|
|
19
132
|
|
|
20
133
|
```typescript
|
|
21
|
-
import { TelemetryClient } from '@
|
|
134
|
+
import { TelemetryClient } from '@egintegrations/telemetry';
|
|
22
135
|
|
|
23
136
|
// Initialize the client
|
|
24
137
|
const telemetry = new TelemetryClient({
|
|
@@ -57,11 +170,11 @@ await telemetry.sendMetrics({
|
|
|
57
170
|
});
|
|
58
171
|
```
|
|
59
172
|
|
|
60
|
-
|
|
173
|
+
### Express/Fastify Integration
|
|
61
174
|
|
|
62
175
|
```typescript
|
|
63
176
|
import express from 'express';
|
|
64
|
-
import { TelemetryClient } from '@
|
|
177
|
+
import { TelemetryClient } from '@egintegrations/telemetry';
|
|
65
178
|
|
|
66
179
|
const app = express();
|
|
67
180
|
const telemetry = new TelemetryClient({
|
|
@@ -76,7 +189,7 @@ app.use(telemetry.middleware());
|
|
|
76
189
|
app.listen(3000);
|
|
77
190
|
```
|
|
78
191
|
|
|
79
|
-
|
|
192
|
+
### Automatic Health Checks
|
|
80
193
|
|
|
81
194
|
```typescript
|
|
82
195
|
// Report health every 60 seconds
|
|
@@ -86,7 +199,7 @@ const healthCheckTimer = telemetry.startHealthCheck(60000);
|
|
|
86
199
|
clearInterval(healthCheckTimer);
|
|
87
200
|
```
|
|
88
201
|
|
|
89
|
-
|
|
202
|
+
### Graceful Shutdown
|
|
90
203
|
|
|
91
204
|
```typescript
|
|
92
205
|
process.on('SIGTERM', async () => {
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
|
|
29
|
+
// src/cli/detector.ts
|
|
30
|
+
var fs = __toESM(require("fs"));
|
|
31
|
+
var path = __toESM(require("path"));
|
|
32
|
+
async function detectFramework(cwd) {
|
|
33
|
+
const pkgJsonPath = path.join(cwd, "package.json");
|
|
34
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
35
|
+
return { type: "generic", rootDir: cwd };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const pkgContent = fs.readFileSync(pkgJsonPath, "utf-8");
|
|
39
|
+
const pkg = JSON.parse(pkgContent);
|
|
40
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
41
|
+
if (deps.next) {
|
|
42
|
+
const hasAppDir = fs.existsSync(path.join(cwd, "app"));
|
|
43
|
+
return {
|
|
44
|
+
type: "nextjs",
|
|
45
|
+
version: deps.next,
|
|
46
|
+
appDir: hasAppDir,
|
|
47
|
+
rootDir: cwd
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (deps.express) {
|
|
51
|
+
return {
|
|
52
|
+
type: "express",
|
|
53
|
+
version: deps.express,
|
|
54
|
+
rootDir: cwd
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (deps.fastify) {
|
|
58
|
+
return {
|
|
59
|
+
type: "fastify",
|
|
60
|
+
version: deps.fastify,
|
|
61
|
+
rootDir: cwd
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { type: "generic", rootDir: cwd };
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.warn("Failed to parse package.json, assuming generic project");
|
|
67
|
+
return { type: "generic", rootDir: cwd };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/cli/prompts.ts
|
|
72
|
+
var import_prompts2 = __toESM(require("prompts"));
|
|
73
|
+
|
|
74
|
+
// src/cli/utils.ts
|
|
75
|
+
var fs2 = __toESM(require("fs"));
|
|
76
|
+
var path2 = __toESM(require("path"));
|
|
77
|
+
var import_prompts = __toESM(require("prompts"));
|
|
78
|
+
async function writeFile(file) {
|
|
79
|
+
const dir = path2.dirname(file.path);
|
|
80
|
+
await fs2.promises.mkdir(dir, { recursive: true });
|
|
81
|
+
if (fs2.existsSync(file.path) && file.overwrite !== true) {
|
|
82
|
+
const { confirm } = await (0, import_prompts.default)({
|
|
83
|
+
type: "confirm",
|
|
84
|
+
name: "confirm",
|
|
85
|
+
message: `File ${file.path} already exists. Overwrite?`,
|
|
86
|
+
initial: false
|
|
87
|
+
});
|
|
88
|
+
if (!confirm) {
|
|
89
|
+
console.log(`\u23ED\uFE0F Skipped: ${file.path}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (file.path.endsWith(".env") || file.path.endsWith(".env.local")) {
|
|
94
|
+
await appendEnvFile(file.path, file.content);
|
|
95
|
+
} else {
|
|
96
|
+
await fs2.promises.writeFile(file.path, file.content, "utf-8");
|
|
97
|
+
console.log(`\u2713 Created: ${file.path}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function appendEnvFile(envPath, content) {
|
|
101
|
+
let existing = "";
|
|
102
|
+
if (fs2.existsSync(envPath)) {
|
|
103
|
+
existing = await fs2.promises.readFile(envPath, "utf-8");
|
|
104
|
+
}
|
|
105
|
+
if (existing.includes("EGI_ENGINE_NAME")) {
|
|
106
|
+
console.log(`\u26A0\uFE0F ${envPath} already contains EGI variables, skipping`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const separator = existing.trim() ? "\n\n" : "";
|
|
110
|
+
const updated = existing + separator + content;
|
|
111
|
+
await fs2.promises.writeFile(envPath, updated, "utf-8");
|
|
112
|
+
console.log(`\u2713 Updated: ${envPath}`);
|
|
113
|
+
}
|
|
114
|
+
function validateSku(sku) {
|
|
115
|
+
return /^\d+\.\d+\.\d+/.test(sku);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/cli/prompts.ts
|
|
119
|
+
async function gatherOptions(cliArgs, detected) {
|
|
120
|
+
const questions = [];
|
|
121
|
+
if (!cliArgs.engine) {
|
|
122
|
+
questions.push({
|
|
123
|
+
type: "text",
|
|
124
|
+
name: "engine",
|
|
125
|
+
message: "Engine name (e.g., hs-and-c):",
|
|
126
|
+
validate: (v) => v.length > 0 || "Engine name is required"
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (!cliArgs.sku) {
|
|
130
|
+
questions.push({
|
|
131
|
+
type: "text",
|
|
132
|
+
name: "sku",
|
|
133
|
+
message: "SKU version (e.g., 1.0.0):",
|
|
134
|
+
validate: (v) => validateSku(v) || "Invalid SKU format (expected: X.Y.Z...)"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (!cliArgs.url) {
|
|
138
|
+
questions.push({
|
|
139
|
+
type: "text",
|
|
140
|
+
name: "url",
|
|
141
|
+
message: "Control Center URL:",
|
|
142
|
+
initial: "https://control-center.egintegrations.com"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (cliArgs.token === void 0) {
|
|
146
|
+
questions.push({
|
|
147
|
+
type: "password",
|
|
148
|
+
name: "token",
|
|
149
|
+
message: "Auth token (optional, press Enter to skip):"
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (!cliArgs.framework) {
|
|
153
|
+
const frameworkChoices = [
|
|
154
|
+
{ title: `${detected.type} (detected)`, value: detected.type },
|
|
155
|
+
{ title: "Next.js", value: "nextjs", disabled: detected.type === "nextjs" },
|
|
156
|
+
{ title: "Express", value: "express", disabled: detected.type === "express" },
|
|
157
|
+
{ title: "Fastify", value: "fastify", disabled: detected.type === "fastify" },
|
|
158
|
+
{ title: "Generic/Other", value: "generic", disabled: detected.type === "generic" }
|
|
159
|
+
].filter((choice) => !choice.disabled);
|
|
160
|
+
if (frameworkChoices.length > 1) {
|
|
161
|
+
questions.push({
|
|
162
|
+
type: "select",
|
|
163
|
+
name: "framework",
|
|
164
|
+
message: "Select framework:",
|
|
165
|
+
choices: frameworkChoices,
|
|
166
|
+
initial: 0
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (cliArgs.codegen !== false && detected.type !== "generic") {
|
|
171
|
+
questions.push({
|
|
172
|
+
type: "confirm",
|
|
173
|
+
name: "statusEndpoint",
|
|
174
|
+
message: "Generate status endpoint boilerplate?",
|
|
175
|
+
initial: true
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const answers = await (0, import_prompts2.default)(questions, {
|
|
179
|
+
onCancel: () => {
|
|
180
|
+
console.log("\n\u274C Setup cancelled");
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
engine: cliArgs.engine || answers.engine,
|
|
186
|
+
sku: cliArgs.sku || answers.sku,
|
|
187
|
+
url: cliArgs.url || answers.url,
|
|
188
|
+
token: cliArgs.token || answers.token || void 0,
|
|
189
|
+
modules: cliArgs.modules,
|
|
190
|
+
output: cliArgs.output || ".egi/telemetry.json",
|
|
191
|
+
framework: cliArgs.framework || answers.framework || detected.type,
|
|
192
|
+
codegen: cliArgs.codegen !== false,
|
|
193
|
+
statusEndpoint: cliArgs.statusEndpoint || answers.statusEndpoint || false
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/cli/generators/nextjs.ts
|
|
198
|
+
var path3 = __toESM(require("path"));
|
|
199
|
+
|
|
200
|
+
// src/cli/generators/templates.ts
|
|
201
|
+
function generateConfigFile(ctx) {
|
|
202
|
+
return JSON.stringify({
|
|
203
|
+
engine: ctx.engine,
|
|
204
|
+
sku: ctx.sku,
|
|
205
|
+
url: ctx.url,
|
|
206
|
+
...ctx.token && { token: ctx.token },
|
|
207
|
+
...ctx.modules && { modules: ctx.modules }
|
|
208
|
+
}, null, 2);
|
|
209
|
+
}
|
|
210
|
+
function generateNextjsTelemetryClient(ctx) {
|
|
211
|
+
return `import { TelemetryClient } from '@egintegrations/telemetry';
|
|
212
|
+
|
|
213
|
+
const telemetry = new TelemetryClient({
|
|
214
|
+
engineName: process.env.EGI_ENGINE_NAME || '${ctx.engine}',
|
|
215
|
+
skuVersion: process.env.EGI_SKU_VERSION || '${ctx.sku}',
|
|
216
|
+
controlCenterUrl: process.env.EGI_CONTROL_CENTER_URL || '${ctx.url}',
|
|
217
|
+
${ctx.token ? `authToken: process.env.EGI_AUTH_TOKEN,` : "// authToken: process.env.EGI_AUTH_TOKEN, // Optional"}
|
|
218
|
+
enabled: process.env.NODE_ENV === 'production'
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Auto-register on first import in production
|
|
222
|
+
if (process.env.NODE_ENV === 'production') {
|
|
223
|
+
telemetry.register().catch(err => {
|
|
224
|
+
console.error('[EGI Telemetry] Failed to register:', err);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export default telemetry;
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
function generateNextjsStatusEndpoint() {
|
|
232
|
+
return `import { NextResponse } from 'next/server';
|
|
233
|
+
import telemetry from '@/lib/telemetry';
|
|
234
|
+
|
|
235
|
+
export async function GET() {
|
|
236
|
+
try {
|
|
237
|
+
await telemetry.reportStatus({
|
|
238
|
+
status: 'healthy',
|
|
239
|
+
metadata: {
|
|
240
|
+
timestamp: new Date().toISOString(),
|
|
241
|
+
uptime: process.uptime()
|
|
242
|
+
},
|
|
243
|
+
metrics: {},
|
|
244
|
+
message: 'Engine is healthy'
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return NextResponse.json({
|
|
248
|
+
status: 'ok',
|
|
249
|
+
timestamp: new Date().toISOString()
|
|
250
|
+
});
|
|
251
|
+
} catch (error) {
|
|
252
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
253
|
+
return NextResponse.json(
|
|
254
|
+
{ status: 'error', message: errorMessage },
|
|
255
|
+
{ status: 500 }
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
`;
|
|
260
|
+
}
|
|
261
|
+
function generateExpressTelemetryClient(ctx) {
|
|
262
|
+
return `import { TelemetryClient } from '@egintegrations/telemetry';
|
|
263
|
+
|
|
264
|
+
const telemetry = new TelemetryClient({
|
|
265
|
+
engineName: process.env.EGI_ENGINE_NAME || '${ctx.engine}',
|
|
266
|
+
skuVersion: process.env.EGI_SKU_VERSION || '${ctx.sku}',
|
|
267
|
+
controlCenterUrl: process.env.EGI_CONTROL_CENTER_URL || '${ctx.url}',
|
|
268
|
+
${ctx.token ? `authToken: process.env.EGI_AUTH_TOKEN,` : "// authToken: process.env.EGI_AUTH_TOKEN, // Optional"}
|
|
269
|
+
enabled: process.env.NODE_ENV === 'production'
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
export default telemetry;
|
|
273
|
+
`;
|
|
274
|
+
}
|
|
275
|
+
function generateExpressStatusRoute() {
|
|
276
|
+
return `import express from 'express';
|
|
277
|
+
import telemetry from '../telemetry';
|
|
278
|
+
|
|
279
|
+
const router = express.Router();
|
|
280
|
+
|
|
281
|
+
router.get('/engine-status', async (req, res) => {
|
|
282
|
+
try {
|
|
283
|
+
await telemetry.reportStatus({
|
|
284
|
+
status: 'healthy',
|
|
285
|
+
metadata: {
|
|
286
|
+
uptime: process.uptime(),
|
|
287
|
+
memory: process.memoryUsage()
|
|
288
|
+
},
|
|
289
|
+
metrics: {},
|
|
290
|
+
message: 'Engine is healthy'
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
res.json({
|
|
294
|
+
status: 'ok',
|
|
295
|
+
timestamp: new Date().toISOString()
|
|
296
|
+
});
|
|
297
|
+
} catch (error) {
|
|
298
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
299
|
+
res.status(500).json({
|
|
300
|
+
status: 'error',
|
|
301
|
+
message: errorMessage
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
export default router;
|
|
307
|
+
`;
|
|
308
|
+
}
|
|
309
|
+
function generateEnvVars(ctx) {
|
|
310
|
+
const lines = [
|
|
311
|
+
"# EGI Telemetry Configuration",
|
|
312
|
+
`EGI_ENGINE_NAME=${ctx.engine}`,
|
|
313
|
+
`EGI_SKU_VERSION=${ctx.sku}`,
|
|
314
|
+
`EGI_CONTROL_CENTER_URL=${ctx.url}`
|
|
315
|
+
];
|
|
316
|
+
if (ctx.token) {
|
|
317
|
+
lines.push(`EGI_AUTH_TOKEN=${ctx.token}`);
|
|
318
|
+
}
|
|
319
|
+
return lines.join("\n");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/cli/generators/nextjs.ts
|
|
323
|
+
var nextjsGenerator = {
|
|
324
|
+
name: "Next.js",
|
|
325
|
+
supports: (ctx) => ctx.framework.type === "nextjs",
|
|
326
|
+
async generate(ctx) {
|
|
327
|
+
const files = [];
|
|
328
|
+
files.push({
|
|
329
|
+
path: path3.join(ctx.rootDir, ctx.options.output),
|
|
330
|
+
content: generateConfigFile(ctx.options)
|
|
331
|
+
});
|
|
332
|
+
if (ctx.options.codegen) {
|
|
333
|
+
const libDir = path3.join(ctx.rootDir, "lib");
|
|
334
|
+
files.push({
|
|
335
|
+
path: path3.join(libDir, "telemetry.ts"),
|
|
336
|
+
content: generateNextjsTelemetryClient(ctx.options),
|
|
337
|
+
overwrite: false
|
|
338
|
+
// Ask if exists
|
|
339
|
+
});
|
|
340
|
+
if (ctx.framework.appDir && ctx.options.statusEndpoint) {
|
|
341
|
+
files.push({
|
|
342
|
+
path: path3.join(ctx.rootDir, "app/api/engine-status/route.ts"),
|
|
343
|
+
content: generateNextjsStatusEndpoint(),
|
|
344
|
+
overwrite: false
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
files.push({
|
|
348
|
+
path: path3.join(ctx.rootDir, ".env.local"),
|
|
349
|
+
content: generateEnvVars(ctx.options),
|
|
350
|
+
overwrite: false
|
|
351
|
+
// Append, don't replace
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return files;
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// src/cli/generators/express.ts
|
|
359
|
+
var path4 = __toESM(require("path"));
|
|
360
|
+
var fs3 = __toESM(require("fs"));
|
|
361
|
+
var expressGenerator = {
|
|
362
|
+
name: "Express",
|
|
363
|
+
supports: (ctx) => ctx.framework.type === "express" || ctx.framework.type === "fastify",
|
|
364
|
+
async generate(ctx) {
|
|
365
|
+
const files = [];
|
|
366
|
+
files.push({
|
|
367
|
+
path: path4.join(ctx.rootDir, ctx.options.output),
|
|
368
|
+
content: generateConfigFile(ctx.options)
|
|
369
|
+
});
|
|
370
|
+
if (ctx.options.codegen) {
|
|
371
|
+
const hasSrcDir = fs3.existsSync(path4.join(ctx.rootDir, "src"));
|
|
372
|
+
const baseDir = hasSrcDir ? "src" : ".";
|
|
373
|
+
files.push({
|
|
374
|
+
path: path4.join(ctx.rootDir, baseDir, "telemetry.ts"),
|
|
375
|
+
content: generateExpressTelemetryClient(ctx.options),
|
|
376
|
+
overwrite: false
|
|
377
|
+
});
|
|
378
|
+
if (ctx.options.statusEndpoint) {
|
|
379
|
+
const routesDir = path4.join(ctx.rootDir, baseDir, "routes");
|
|
380
|
+
files.push({
|
|
381
|
+
path: path4.join(routesDir, "status.ts"),
|
|
382
|
+
content: generateExpressStatusRoute(),
|
|
383
|
+
overwrite: false
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
files.push({
|
|
387
|
+
path: path4.join(ctx.rootDir, ".env"),
|
|
388
|
+
content: generateEnvVars(ctx.options),
|
|
389
|
+
overwrite: false
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return files;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/cli/generators/generic.ts
|
|
397
|
+
var path5 = __toESM(require("path"));
|
|
398
|
+
var genericGenerator = {
|
|
399
|
+
name: "Generic Node.js",
|
|
400
|
+
supports: () => true,
|
|
401
|
+
// Fallback generator, always supports
|
|
402
|
+
async generate(ctx) {
|
|
403
|
+
const files = [];
|
|
404
|
+
files.push({
|
|
405
|
+
path: path5.join(ctx.rootDir, ctx.options.output),
|
|
406
|
+
content: generateConfigFile(ctx.options)
|
|
407
|
+
});
|
|
408
|
+
console.log(`
|
|
409
|
+
\u26A0\uFE0F Generic project detected - only config file generated.
|
|
410
|
+
|
|
411
|
+
To use the telemetry SDK, manually:
|
|
412
|
+
1. Import the SDK: import { TelemetryClient } from '@egintegrations/telemetry';
|
|
413
|
+
2. Create a client with your config
|
|
414
|
+
3. Call telemetry.register() on startup
|
|
415
|
+
|
|
416
|
+
Example:
|
|
417
|
+
import { TelemetryClient } from '@egintegrations/telemetry';
|
|
418
|
+
|
|
419
|
+
const telemetry = new TelemetryClient({
|
|
420
|
+
engineName: '${ctx.options.engine}',
|
|
421
|
+
skuVersion: '${ctx.options.sku}',
|
|
422
|
+
controlCenterUrl: '${ctx.options.url}',
|
|
423
|
+
${ctx.options.token ? `authToken: '${ctx.options.token}',` : ""}
|
|
424
|
+
enabled: true
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
await telemetry.register();
|
|
428
|
+
|
|
429
|
+
See README for full documentation.
|
|
430
|
+
`);
|
|
431
|
+
return files;
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// src/cli/generators/index.ts
|
|
436
|
+
var generators = [
|
|
437
|
+
nextjsGenerator,
|
|
438
|
+
expressGenerator,
|
|
439
|
+
genericGenerator
|
|
440
|
+
// Always last as fallback
|
|
441
|
+
];
|
|
442
|
+
async function runGenerator(ctx) {
|
|
443
|
+
const generator = generators.find((g) => g.supports(ctx));
|
|
444
|
+
if (!generator) {
|
|
445
|
+
throw new Error(`No generator found for framework: ${ctx.framework.type}`);
|
|
446
|
+
}
|
|
447
|
+
console.log(`
|
|
448
|
+
\u{1F3A8} Using ${generator.name} generator
|
|
449
|
+
`);
|
|
450
|
+
return generator.generate(ctx);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/cli/commands.ts
|
|
454
|
+
async function initCommand(options) {
|
|
455
|
+
console.log("\u{1F680} EGI Telemetry SDK Initialization\n");
|
|
456
|
+
try {
|
|
457
|
+
const cwd = process.cwd();
|
|
458
|
+
console.log("\u{1F50D} Detecting framework...");
|
|
459
|
+
const detected = await detectFramework(cwd);
|
|
460
|
+
console.log(` Found: ${detected.type}${detected.version ? ` v${detected.version}` : ""}`);
|
|
461
|
+
if (detected.type === "nextjs" && detected.appDir) {
|
|
462
|
+
console.log(" Next.js App Router detected");
|
|
463
|
+
}
|
|
464
|
+
console.log("");
|
|
465
|
+
const telemetryOptions = await gatherOptions(options, detected);
|
|
466
|
+
console.log("");
|
|
467
|
+
const ctx = {
|
|
468
|
+
options: telemetryOptions,
|
|
469
|
+
framework: detected,
|
|
470
|
+
rootDir: cwd
|
|
471
|
+
};
|
|
472
|
+
const files = await runGenerator(ctx);
|
|
473
|
+
for (const file of files) {
|
|
474
|
+
await writeFile(file);
|
|
475
|
+
}
|
|
476
|
+
console.log("\n\u2705 Telemetry SDK initialized successfully!\n");
|
|
477
|
+
if (telemetryOptions.codegen && detected.type !== "generic") {
|
|
478
|
+
console.log("\u{1F4DD} Next steps:");
|
|
479
|
+
console.log(" 1. Review the generated files");
|
|
480
|
+
console.log(" 2. Import telemetry in your code:");
|
|
481
|
+
if (detected.type === "nextjs") {
|
|
482
|
+
console.log(" import telemetry from '@/lib/telemetry';");
|
|
483
|
+
} else {
|
|
484
|
+
console.log(" import telemetry from './telemetry';");
|
|
485
|
+
}
|
|
486
|
+
console.log(" 3. Start using telemetry.sendMetrics() in your app");
|
|
487
|
+
console.log("");
|
|
488
|
+
}
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (error instanceof Error) {
|
|
491
|
+
console.error("\n\u274C Error:", error.message);
|
|
492
|
+
} else {
|
|
493
|
+
console.error("\n\u274C Unknown error occurred");
|
|
494
|
+
}
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/cli.ts
|
|
500
|
+
var program = new import_commander.Command();
|
|
501
|
+
program.name("egi-telemetry").description("EGIntegrations Telemetry SDK CLI").version("0.2.0");
|
|
502
|
+
program.command("init").description("Initialize telemetry in your project").option("--engine <name>", "Engine name").option("--sku <version>", "SKU version").option("--url <url>", "Control Center URL").option("--token <token>", "Auth token (optional)").option("--modules <modules>", "Comma-separated module list").option("--output <path>", "Config output path", ".egi/telemetry.json").option("--framework <type>", "Framework (nextjs, express, fastify, generic)").option("--no-interactive", "Skip prompts, fail if args missing").option("--no-codegen", "Only generate config, skip integration code").option("--status-endpoint", "Generate status endpoint boilerplate").action(initCommand);
|
|
503
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@egintegrations/telemetry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Telemetry SDK for EGIntegrations client engines",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"egi-telemetry": "./dist/cli.js"
|
|
10
|
+
},
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
13
|
"require": "./dist/index.js",
|
|
@@ -13,9 +16,12 @@
|
|
|
13
16
|
}
|
|
14
17
|
},
|
|
15
18
|
"scripts": {
|
|
16
|
-
"build": "
|
|
19
|
+
"build": "npm run build:lib && npm run build:cli",
|
|
20
|
+
"build:lib": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
21
|
+
"build:cli": "tsup src/cli.ts --format cjs --no-dts --shims",
|
|
17
22
|
"prepublishOnly": "npm run build",
|
|
18
|
-
"test": "echo \"No tests yet\""
|
|
23
|
+
"test": "echo \"No tests yet\"",
|
|
24
|
+
"dev:cli": "tsx src/cli.ts"
|
|
19
25
|
},
|
|
20
26
|
"keywords": [
|
|
21
27
|
"telemetry",
|
|
@@ -26,11 +32,15 @@
|
|
|
26
32
|
"author": "EGIntegrations",
|
|
27
33
|
"license": "PROPRIETARY",
|
|
28
34
|
"dependencies": {
|
|
29
|
-
"axios": "^1.6.0"
|
|
35
|
+
"axios": "^1.6.0",
|
|
36
|
+
"commander": "^12.0.0",
|
|
37
|
+
"prompts": "^2.4.2"
|
|
30
38
|
},
|
|
31
39
|
"devDependencies": {
|
|
32
40
|
"@types/node": "^20.0.0",
|
|
41
|
+
"@types/prompts": "^2.4.9",
|
|
33
42
|
"tsup": "^8.0.0",
|
|
43
|
+
"tsx": "^4.0.0",
|
|
34
44
|
"typescript": "^5.3.0"
|
|
35
45
|
},
|
|
36
46
|
"files": [
|