seqpulse 0.3.0 → 0.4.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 +21 -7
- package/bin/seqpulse.js +200 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
SeqPulse Node SDK couvre deux usages:
|
|
4
4
|
|
|
5
5
|
- Runtime application: instrumentation HTTP + endpoint metrics + validation HMAC v2
|
|
6
|
-
- CI/CD integration: client `trigger/finish`
|
|
6
|
+
- CI/CD integration: client `trigger/finish` + CLI `seqpulse ci ...`
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -66,7 +66,7 @@ const client = seqpulse.createCIClient({
|
|
|
66
66
|
async function runDeployment() {
|
|
67
67
|
const trigger = await client.trigger({
|
|
68
68
|
branch: process.env.GITHUB_REF_NAME,
|
|
69
|
-
idempotencyKey:
|
|
69
|
+
idempotencyKey: seqpulse.buildCiIdempotencyKey(),
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
// Deploy your app here...
|
|
@@ -81,11 +81,25 @@ async function runDeployment() {
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
## CI/CD CLI (short YAML)
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
86
|
+
```bash
|
|
87
|
+
# Trigger
|
|
88
|
+
npx -y seqpulse@0.4.0 ci trigger \
|
|
89
|
+
--env prod \
|
|
90
|
+
--github-output "$GITHUB_OUTPUT"
|
|
91
|
+
|
|
92
|
+
# Finish
|
|
93
|
+
npx -y seqpulse@0.4.0 ci finish \
|
|
94
|
+
--deployment-id "$SEQPULSE_DEPLOYMENT_ID" \
|
|
95
|
+
--job-status "$JOB_STATUS"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Notes:
|
|
99
|
+
|
|
100
|
+
- `--github-output` ecrit `deployment_id`, `status`, `http_status`.
|
|
101
|
+
- mode par defaut: non-bloquant (`--non-blocking true`).
|
|
102
|
+
- pour mode strict: `--blocking`.
|
|
89
103
|
|
|
90
104
|
## Compatibility note
|
|
91
105
|
|
|
@@ -93,7 +107,7 @@ Cette evolution **n'ecrase pas** le SDK runtime actuel:
|
|
|
93
107
|
|
|
94
108
|
- l'endpoint `/seqpulse-metrics` reste le meme
|
|
95
109
|
- la logique HMAC runtime reste la meme
|
|
96
|
-
-
|
|
110
|
+
- la CLI CI est une surcouche du client SDK existant
|
|
97
111
|
|
|
98
112
|
## Local smoke test
|
|
99
113
|
|
package/bin/seqpulse.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const seqpulse = require("../index.js");
|
|
6
|
+
|
|
7
|
+
function printUsage() {
|
|
8
|
+
console.error(`Usage:
|
|
9
|
+
seqpulse ci trigger [options]
|
|
10
|
+
seqpulse ci finish [options]
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
ci trigger Trigger SeqPulse deployment in CI
|
|
14
|
+
ci finish Finish SeqPulse deployment in CI
|
|
15
|
+
|
|
16
|
+
Common options:
|
|
17
|
+
--base-url <url>
|
|
18
|
+
--api-key <key>
|
|
19
|
+
--metrics-endpoint <url>
|
|
20
|
+
--timeout-ms <number>
|
|
21
|
+
--env <name> (default: prod)
|
|
22
|
+
--non-blocking <true|false> (default: true)
|
|
23
|
+
--blocking (equivalent to --non-blocking false)
|
|
24
|
+
--github-output <path> (writes outputs for GitHub Actions)
|
|
25
|
+
|
|
26
|
+
Trigger options:
|
|
27
|
+
--branch <name>
|
|
28
|
+
--idempotency-key <key>
|
|
29
|
+
|
|
30
|
+
Finish options:
|
|
31
|
+
--deployment-id <id>
|
|
32
|
+
--result <success|failed>
|
|
33
|
+
--job-status <status>
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseArgs(argv) {
|
|
38
|
+
const positional = [];
|
|
39
|
+
const options = {};
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
42
|
+
const token = argv[i];
|
|
43
|
+
if (!token.startsWith("--")) {
|
|
44
|
+
positional.push(token);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const eq = token.indexOf("=");
|
|
49
|
+
if (eq > -1) {
|
|
50
|
+
const key = token.slice(2, eq);
|
|
51
|
+
const value = token.slice(eq + 1);
|
|
52
|
+
options[key] = value;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const key = token.slice(2);
|
|
57
|
+
const next = argv[i + 1];
|
|
58
|
+
if (!next || next.startsWith("--")) {
|
|
59
|
+
options[key] = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
options[key] = next;
|
|
64
|
+
i += 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { positional, options };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseBoolean(value, defaultValue) {
|
|
71
|
+
if (value === undefined) return defaultValue;
|
|
72
|
+
if (typeof value === "boolean") return value;
|
|
73
|
+
const normalized = String(value).trim().toLowerCase();
|
|
74
|
+
if (["1", "true", "yes", "y", "on"].includes(normalized)) return true;
|
|
75
|
+
if (["0", "false", "no", "n", "off"].includes(normalized)) return false;
|
|
76
|
+
throw new Error(`Invalid boolean value: ${value}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseNumber(value, defaultValue) {
|
|
80
|
+
if (value === undefined) return defaultValue;
|
|
81
|
+
const num = Number(value);
|
|
82
|
+
if (!Number.isFinite(num) || num <= 0) {
|
|
83
|
+
throw new Error(`Invalid numeric value: ${value}`);
|
|
84
|
+
}
|
|
85
|
+
return num;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function sanitizeOutput(value) {
|
|
89
|
+
return String(value === undefined || value === null ? "" : value).replace(/[\r\n]/g, "");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function appendGithubOutput(filePath, pairs) {
|
|
93
|
+
if (!filePath) return;
|
|
94
|
+
const lines = Object.entries(pairs)
|
|
95
|
+
.map(([k, v]) => `${k}=${sanitizeOutput(v)}`)
|
|
96
|
+
.join("\n");
|
|
97
|
+
fs.appendFileSync(filePath, `${lines}\n`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function resolveNonBlocking(options) {
|
|
101
|
+
if (options.blocking !== undefined) return false;
|
|
102
|
+
if (options["non-blocking"] !== undefined) return parseBoolean(options["non-blocking"], true);
|
|
103
|
+
if (options.nonblocking !== undefined) return parseBoolean(options.nonblocking, true);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildClient(options) {
|
|
108
|
+
return {
|
|
109
|
+
baseUrl: options["base-url"],
|
|
110
|
+
apiKey: options["api-key"],
|
|
111
|
+
metricsEndpoint: options["metrics-endpoint"],
|
|
112
|
+
env: options.env || "prod",
|
|
113
|
+
timeoutMs: parseNumber(options["timeout-ms"], undefined),
|
|
114
|
+
nonBlocking: resolveNonBlocking(options),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function runTrigger(options) {
|
|
119
|
+
const clientConfig = buildClient(options);
|
|
120
|
+
const client = seqpulse.createCIClient(clientConfig);
|
|
121
|
+
const result = await client.trigger({
|
|
122
|
+
env: options.env || clientConfig.env || "prod",
|
|
123
|
+
branch: options.branch,
|
|
124
|
+
idempotencyKey: options["idempotency-key"] || seqpulse.buildCiIdempotencyKey(),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
appendGithubOutput(options["github-output"], {
|
|
128
|
+
deployment_id: result.ok ? result.deploymentId || "" : "",
|
|
129
|
+
status: result.status || result.error || "",
|
|
130
|
+
http_status: result.httpStatus || "",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
console.log(JSON.stringify({ action: "trigger", ...result }));
|
|
134
|
+
|
|
135
|
+
if (!result.ok && !clientConfig.nonBlocking) {
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function runFinish(options) {
|
|
141
|
+
const clientConfig = buildClient(options);
|
|
142
|
+
const deploymentId = options["deployment-id"] || process.env.SEQPULSE_DEPLOYMENT_ID || "";
|
|
143
|
+
const jobStatus = options["job-status"] || process.env.JOB_STATUS || process.env.CI_JOB_STATUS || "success";
|
|
144
|
+
const resultValue = options.result || seqpulse.inferResultFromPipelineStatus(jobStatus);
|
|
145
|
+
|
|
146
|
+
const client = seqpulse.createCIClient(clientConfig);
|
|
147
|
+
const result = await client.finish({
|
|
148
|
+
deploymentId,
|
|
149
|
+
result: resultValue,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
appendGithubOutput(options["github-output"], {
|
|
153
|
+
status: result.status || result.error || "",
|
|
154
|
+
http_status: result.httpStatus || "",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log(JSON.stringify({ action: "finish", ...result }));
|
|
158
|
+
|
|
159
|
+
if (!result.ok && !clientConfig.nonBlocking) {
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function main() {
|
|
165
|
+
const { positional, options } = parseArgs(process.argv.slice(2));
|
|
166
|
+
const [scope, command] = positional;
|
|
167
|
+
|
|
168
|
+
if (options.help || options.h) {
|
|
169
|
+
printUsage();
|
|
170
|
+
process.exit(0);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!scope || !command) {
|
|
175
|
+
printUsage();
|
|
176
|
+
process.exit(1);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (scope !== "ci") {
|
|
181
|
+
throw new Error(`Unsupported scope: ${scope}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (command === "trigger") {
|
|
185
|
+
await runTrigger(options);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (command === "finish") {
|
|
190
|
+
await runFinish(options);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
throw new Error(`Unsupported command: ${command}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
main().catch((error) => {
|
|
198
|
+
console.error(`[seqpulse] ${error && error.message ? error.message : String(error)}`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "seqpulse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "SeqPulse SDK for metrics endpoint instrumentation and HMAC validation",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"seqpulse": "bin/seqpulse.js"
|
|
9
|
+
},
|
|
7
10
|
"exports": {
|
|
8
11
|
".": {
|
|
9
12
|
"require": "./index.js",
|
|
@@ -13,6 +16,7 @@
|
|
|
13
16
|
"files": [
|
|
14
17
|
"index.js",
|
|
15
18
|
"index.d.ts",
|
|
19
|
+
"bin/seqpulse.js",
|
|
16
20
|
"README.md",
|
|
17
21
|
"LICENSE",
|
|
18
22
|
"scripts/smoke.js"
|