@pulse-si/next 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +113 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/start.d.ts +9 -0
- package/dist/start.js +51 -0
- package/dist/start.js.map +1 -0
- package/package.json +44 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Charles Désiré Bally
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,114 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @pulse-si/next
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Ton app Next.js surveillée par [Pulse](https://pulse-app.si), sur ton iPhone et ton Mac : signal de vie, base de données, Redis, files, routes de cron et journaux du serveur, depuis `instrumentation.ts`.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
ton serveur Next.js ton iPhone, ton Mac
|
|
7
|
+
┌────────────────────────────┐ HTTPS ┌──────────┐ ┌──────────────┐
|
|
8
|
+
│ instrumentation.ts │ ────────────────▶ │ Pulse │ ────▶ │ app Pulse │
|
|
9
|
+
│ └ startPulse({ watch }) │ état, crons, │ (API) │ push │ alertes, │
|
|
10
|
+
│ ├ Prisma SELECT 1 │ journaux │ │ ◀──── │ journaux, │
|
|
11
|
+
│ ├ Redis PING │ (chiffrés si tu │ │ SSE │ en direct │
|
|
12
|
+
│ └ crons trackCron │ le veux) └──────────┘ └──────────────┘
|
|
13
|
+
└────────────────────────────┘
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## En deux minutes
|
|
17
|
+
|
|
18
|
+
À la racine du projet, avec la clé que l'app Pulse t'affiche :
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @pulse-si/init pk_live_…
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
La commande installe `@pulse-si/next`, écrit la clé dans `.env.local` (lisible par toi seul, couvert par le `.gitignore`), crée `instrumentation.ts` au bon endroit (`src/` si ton projet l'utilise), puis te demande ce que tu veux envoyer. Relance ton serveur.
|
|
25
|
+
|
|
26
|
+
### Ou à la main
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// instrumentation.ts (ou src/instrumentation.ts)
|
|
30
|
+
export async function register() {
|
|
31
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
32
|
+
const { startPulse } = await import('@pulse-si/next');
|
|
33
|
+
const { prisma } = await import('./lib/prisma');
|
|
34
|
+
startPulse({ watch: [prisma] });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`watch` reçoit tes clients : Prisma, TypeORM, Mongoose, ioredis, node-redis, files BullMQ. Pulse les reconnaît à leur forme et les vérifie en lecture seule (`SELECT 1`, `PING`), 5 secondes au plus. Next.js 14 demande en plus `experimental.instrumentationHook: true` dans `next.config`.
|
|
40
|
+
|
|
41
|
+
## Les routes de cron
|
|
42
|
+
|
|
43
|
+
Une route appelée par Vercel Cron, un planificateur externe ou un `curl` sur le serveur remonte ses échecs :
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { trackCron } from '@pulse-si/next';
|
|
47
|
+
|
|
48
|
+
export async function GET() {
|
|
49
|
+
await trackCron('relance-abonnements', () => relancer());
|
|
50
|
+
return Response.json({ ok: true });
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
L'erreur est nettoyée, notée dans Pulse, puis relancée : ta route répond comme avant.
|
|
55
|
+
|
|
56
|
+
## Où ça marche
|
|
57
|
+
|
|
58
|
+
`@pulse-si/next` vit dans le processus Node de ton serveur. Il est fait pour `next start` sur un VPS, dans Docker ou chez un hébergeur qui garde le processus en vie. Sur une plateforme sans serveur (Vercel, Netlify), une instance peut dormir entre deux requêtes : surveille plutôt l'app par son URL dans Pulse, et garde `trackCron` pour tes routes de cron. Le runtime Edge est ignoré sans erreur.
|
|
59
|
+
|
|
60
|
+
## Choisir ce qui part
|
|
61
|
+
|
|
62
|
+
Le signal de vie part toujours. Le reste se règle par variables ou dans `startPulse` :
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
PULSE_SEND=checks,crons # pas de journaux ni de versions
|
|
66
|
+
PULSE_EXCLUDE=File emails # ignorer une instance (* permis)
|
|
67
|
+
PULSE_LOG_LEVEL=WARN # journaux : avertissements et erreurs seulement
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
startPulse({ watch: [prisma, redis], send: ['checks', 'logs'], logLevel: 'WARN' });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Journaux et mode direct (Pro et Team)
|
|
75
|
+
|
|
76
|
+
Pulse recopie ce que ton serveur écrit (`console`, pino, messages de Next.js) sans rien changer à ton terminal. Les niveaux sont reconnus (`⚠` et `⨯` de Next.js compris), les couleurs retirées, les secrets masqués avant de partir. Quand tu ouvres l'app dans Pulse, l'état part toutes les 5 secondes et les journaux chaque seconde. Gardés 3 jours en Pro, 7 jours en Team.
|
|
77
|
+
|
|
78
|
+
## Mode sécurisé : Pulse ne peut rien lire
|
|
79
|
+
|
|
80
|
+
Ton iPhone ou ton Mac fabrique une paire de clés ; la clé publique va dans `.env.local`, la clé privée reste dans ton trousseau.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
PULSE_PUBLIC_KEY=pulse_pub_…
|
|
84
|
+
PULSE_CLEAR=status # par défaut : « ok » ou « N soucis », pour garder les alertes
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Rapports et journaux partent chiffrés (HPKE, X25519 et AES-256-GCM) et Pulse ne garde qu'un paquet qu'il ne sait pas ouvrir. Avec `PULSE_CLEAR=none`, Pulse ne sait plus que si ton app est en vie.
|
|
88
|
+
|
|
89
|
+
## Sécurité, en clair
|
|
90
|
+
|
|
91
|
+
- Le package ne lève jamais d'erreur et ne bloque jamais une requête : tout se passe à côté, sur des minuteurs qui ne retiennent pas le processus.
|
|
92
|
+
- Rien n'est écrit chez toi, rien de métier ne part : état, temps de réponse, messages d'erreur nettoyés.
|
|
93
|
+
- La clé ne voyage qu'en HTTPS, dans un en-tête. Elle n'ouvre pas ton compte et se remplace depuis l'app.
|
|
94
|
+
- En développement, Next.js recharge `instrumentation.ts` à chaque modification : un seul rapporteur tourne par processus.
|
|
95
|
+
|
|
96
|
+
## Variables
|
|
97
|
+
|
|
98
|
+
| Variable | Défaut | Rôle |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `PULSE_KEY` | (obligatoire) | clé de l'app, `pk_live_…` ou `pk_test_…` |
|
|
101
|
+
| `PULSE_SEND` | `checks,crons,logs,versions` | ce qui part en plus du signal de vie |
|
|
102
|
+
| `PULSE_INCLUDE`, `PULSE_EXCLUDE` | (tout), (rien) | instances et tâches suivies, `*` permis |
|
|
103
|
+
| `PULSE_LOG_LEVEL` | `DEBUG` | niveau minimal des journaux |
|
|
104
|
+
| `PULSE_LOGS` | `true` | `false` coupe les journaux |
|
|
105
|
+
| `PULSE_PUBLIC_KEY` | (aucune) | active le mode sécurisé |
|
|
106
|
+
| `PULSE_CLEAR` | `status` | en mode sécurisé, ce qui reste lisible par Pulse |
|
|
107
|
+
| `PULSE_ENDPOINT` | `https://api.pulse-app.si` | ton Pulse Relay en mode VPS perso (Team), par exemple `https://relais.kalao.ci` |
|
|
108
|
+
| `PULSE_RELAY_FINGERPRINT` | (aucune) | empreinte du certificat d'un relais sans domaine : rien ne part si elle ne correspond pas |
|
|
109
|
+
| `PULSE_APP_VERSION` | version du `package.json` | version affichée dans Pulse |
|
|
110
|
+
| `PULSE_DISABLED` | `false` | `true` coupe tout |
|
|
111
|
+
|
|
112
|
+
## Licence
|
|
113
|
+
|
|
114
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackCron = exports.stopPulse = exports.startPulse = exports.pulse = exports.httpProbe = void 0;
|
|
4
|
+
var core_1 = require("@pulse-si/core");
|
|
5
|
+
Object.defineProperty(exports, "httpProbe", { enumerable: true, get: function () { return core_1.httpProbe; } });
|
|
6
|
+
var start_1 = require("./start");
|
|
7
|
+
Object.defineProperty(exports, "pulse", { enumerable: true, get: function () { return start_1.pulse; } });
|
|
8
|
+
Object.defineProperty(exports, "startPulse", { enumerable: true, get: function () { return start_1.startPulse; } });
|
|
9
|
+
Object.defineProperty(exports, "stopPulse", { enumerable: true, get: function () { return start_1.stopPulse; } });
|
|
10
|
+
Object.defineProperty(exports, "trackCron", { enumerable: true, get: function () { return start_1.trackCron; } });
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uCAAuD;AAA9C,iGAAA,SAAS,OAAA;AAClB,iCAAyF;AAAhF,8FAAA,KAAK,OAAA;AAAE,mGAAA,UAAU,OAAA;AAAE,kGAAA,SAAS,OAAA;AAAE,kGAAA,SAAS,OAAA"}
|
package/dist/start.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PulseReporter, type Probe, type PulseOptions } from '@pulse-si/core';
|
|
2
|
+
export type PulseNextOptions = PulseOptions & {
|
|
3
|
+
watch?: unknown[];
|
|
4
|
+
probes?: Probe[];
|
|
5
|
+
};
|
|
6
|
+
export declare function startPulse(options?: PulseNextOptions): PulseReporter | null;
|
|
7
|
+
export declare function pulse(): PulseReporter | null;
|
|
8
|
+
export declare function trackCron<T>(name: string, task: () => Promise<T>): Promise<T>;
|
|
9
|
+
export declare function stopPulse(): void;
|
package/dist/start.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startPulse = startPulse;
|
|
4
|
+
exports.pulse = pulse;
|
|
5
|
+
exports.trackCron = trackCron;
|
|
6
|
+
exports.stopPulse = stopPulse;
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const core_1 = require("@pulse-si/core");
|
|
9
|
+
const GLOBAL_KEY = Symbol.for('pulse-next.reporter');
|
|
10
|
+
function startPulse(options = {}) {
|
|
11
|
+
if (process.env.NEXT_RUNTIME && process.env.NEXT_RUNTIME !== 'nodejs')
|
|
12
|
+
return null;
|
|
13
|
+
const store = globalThis;
|
|
14
|
+
const existing = store[GLOBAL_KEY];
|
|
15
|
+
if (existing)
|
|
16
|
+
return existing;
|
|
17
|
+
const reporter = new core_1.PulseReporter({
|
|
18
|
+
packageName: 'pulse-next',
|
|
19
|
+
packageVersion: (0, core_1.ownVersion)((0, node_path_1.join)(__dirname, '..')),
|
|
20
|
+
framework: 'nextjs',
|
|
21
|
+
frameworkVersion: (0, core_1.installedVersion)('next'),
|
|
22
|
+
}, options);
|
|
23
|
+
if (!reporter.enabled)
|
|
24
|
+
return null;
|
|
25
|
+
for (const probe of [...(options.probes ?? []), ...(0, core_1.detectProbes)(options.watch ?? [])]) {
|
|
26
|
+
reporter.addProbe(probe);
|
|
27
|
+
}
|
|
28
|
+
reporter.start();
|
|
29
|
+
store[GLOBAL_KEY] = reporter;
|
|
30
|
+
return reporter;
|
|
31
|
+
}
|
|
32
|
+
function pulse() {
|
|
33
|
+
return globalThis[GLOBAL_KEY] ?? null;
|
|
34
|
+
}
|
|
35
|
+
async function trackCron(name, task) {
|
|
36
|
+
try {
|
|
37
|
+
const result = await task();
|
|
38
|
+
pulse()?.recordCronRun(name, true);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
pulse()?.recordCronRun(name, false, error);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function stopPulse() {
|
|
47
|
+
const store = globalThis;
|
|
48
|
+
store[GLOBAL_KEY]?.stop();
|
|
49
|
+
delete store[GLOBAL_KEY];
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=start.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.js","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":";;AAoBA,gCAqBC;AAED,sBAEC;AAGD,8BASC;AAED,8BAIC;AA/DD,yCAAiC;AACjC,yCAOwB;AAOxB,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;AAKrD,SAAgB,UAAU,CAAC,UAA4B,EAAE;IACvD,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnF,MAAM,KAAK,GAAG,UAAyB,CAAC;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;IACnC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,QAAQ,GAAG,IAAI,oBAAa,CAChC;QACE,WAAW,EAAE,YAAY;QACzB,cAAc,EAAE,IAAA,iBAAU,EAAC,IAAA,gBAAI,EAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACjD,SAAS,EAAE,QAAQ;QACnB,gBAAgB,EAAE,IAAA,uBAAgB,EAAC,MAAM,CAAC;KAC3C,EACD,OAAO,CACR,CAAC;IACF,IAAI,CAAC,QAAQ,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,GAAG,IAAA,mBAAY,EAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QACtF,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IACD,QAAQ,CAAC,KAAK,EAAE,CAAC;IACjB,KAAK,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC;IAC7B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAgB,KAAK;IACnB,OAAQ,UAA0B,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;AACzD,CAAC;AAGM,KAAK,UAAU,SAAS,CAAI,IAAY,EAAE,IAAsB;IACrE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;QAC5B,KAAK,EAAE,EAAE,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,KAAK,EAAE,EAAE,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAC3C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAgB,SAAS;IACvB,MAAM,KAAK,GAAG,UAAyB,CAAC;IACxC,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC;IAC1B,OAAO,KAAK,CAAC,UAAU,CAAC,CAAC;AAC3B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulse-si/next",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Surveillance Pulse pour Next.js : signal de vie et dépendances depuis instrumentation.ts.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18.17"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://pulse-app.si",
|
|
15
|
+
"jest": {
|
|
16
|
+
"rootDir": "src",
|
|
17
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
18
|
+
"transform": {
|
|
19
|
+
"^.+\\.ts$": "ts-jest"
|
|
20
|
+
},
|
|
21
|
+
"testEnvironment": "node"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@eslint/js": "^9.39.5",
|
|
25
|
+
"@types/jest": "^30.0.0",
|
|
26
|
+
"@types/node": "^22.20.5",
|
|
27
|
+
"eslint": "^9.39.5",
|
|
28
|
+
"eslint-config-prettier": "^10.1.8",
|
|
29
|
+
"globals": "^17.13.0",
|
|
30
|
+
"jest": "^30.5.2",
|
|
31
|
+
"ts-jest": "^29.4.15",
|
|
32
|
+
"typescript": "~5.9.3",
|
|
33
|
+
"typescript-eslint": "^8.71.2"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@pulse-si/core": "^0.0.1"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"next": ">=14"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.build.json",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
45
|
+
"test": "jest"
|
|
46
|
+
}
|
|
6
47
|
}
|