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 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` pour orchestrer un deployment SeqPulse
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: `gha-${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT}`,
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
- ### CI behavior
84
+ ## CI/CD CLI (short YAML)
85
85
 
86
- - `nonBlocking: true` (default): returns a skipped/error result instead of throwing.
87
- - `nonBlocking: false`: throws on API/config errors.
88
- - idempotency key helper available: `seqpulse.buildCiIdempotencyKey()`.
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
- - le client CI est ajoute en couche supplementaire
110
+ - la CLI CI est une surcouche du client SDK existant
97
111
 
98
112
  ## Local smoke test
99
113
 
@@ -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.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"