Documentation
Documentation/Integrations

SDK d'automatisation

Instrumentez les automatisations Node.js externes et les jobs Playwright natifs avec le SDK privé.

sdktypescriptexécutions externesbridge natiftélémétrie
Voir le Markdown
Sur cette page

Le SDK TypeScript privé ajoute une télémétrie BernOps structurée aux automatisations Node.js externes et aux jobs Playwright gérés par BernOps. Ce guide couvre l'artéfact pris en charge 0.1.0-internal.3. Le package est UNLICENSED et n'est pas publié dans un registre public. Les consommateurs externes installent le bundle de version conservé exact ; les jobs natifs déployés utilisent l'artéfact exact fourni par l'opérateur de leur worker BernOps.

Prérequis et installation

  • Utilisez Node.js 22 ou 24 pour les automatisations externes et la création locale de jobs natifs. Node.js 24 est recommandé.
  • Pour une automatisation externe ou une vérification locale des types, demandez au responsable des versions BernOps les fichiers .tgz, .tgz.sha256 et .manifest.json correspondants. Conservez les trois fichiers ensemble.
  • Importez uniquement depuis @bernops/automation. Les consommateurs ESM, CommonJS et TypeScript sont pris en charge ; les imports internes ne le sont pas.

Vérifiez la somme de contrôle avant d'installer l'artéfact dans une automatisation externe ou un projet de création local :

export SDK_ARTIFACT_DIR=/chemin/absolu/vers/la-version-conservee
export SDK_TARBALL="$SDK_ARTIFACT_DIR/bernops-automation-0.1.0-internal.3.tgz"
 
cd "$SDK_ARTIFACT_DIR"
sha256sum --check "$(basename "$SDK_TARBALL").sha256"
 
cd /chemin/absolu/vers/votre-automatisation
npm install --offline --ignore-scripts --no-audit --no-fund \
  --registry=https://registry.invalid/ "$SDK_TARBALL"
npm ls @bernops/automation

Le fichier SHA-256 doit provenir de la même version approuvée que le manifeste. Un responsable disposant de la révision source BernOps correspondante peut exécuter la vérification complète du manifeste, du contenu du package, des déclarations et des consommateurs propres :

cd /chemin/absolu/vers/bernops/sdk/typescript
npm run artifact:verify -- --manifest \
  "$SDK_ARTIFACT_DIR/bernops-automation-0.1.0-internal.3.manifest.json"
Utilisez un seul artéfact exact

Pour les projets externes et locaux, ne remplacez pas le tarball par un lien workspace, une installation du répertoire source, un alias mutable, un import interne ou une solution de repli vers un registre public. Un job natif déployé n'utilise pas votre node_modules local ; son worker fournit le package. Si l'une de ces versions d'artéfact diffère de ce guide, utilisez la documentation et la politique de compatibilité correspondantes plutôt que de supposer sa compatibilité.

Choisir un transport

Emplacement de l'automatisationTransportIdentifiantPropriétaire du cycle de vie
CI, ordonnanceur, service ou script hors d'un worker BernOpsexternalClé API ingest-runner liée au jobSDK
Job Playwright distribué par un worker BernOpsnativeJeton de bouclage injecté par le worker ; aucune clé APIWorker

Sélectionnez explicitement le transport avec BernOps.fromEnv({ transport: 'external' }) ou BernOps.fromEnv({ transport: 'native' }). Utilisez auto uniquement si le déploiement garantit une seule configuration complète. La sélection échoue de manière fermée et ne tente jamais une solution de repli après une erreur d'authentification, TLS, d'identité ou de capacité.

Démarrage rapide externe

Créez un job externe et une clé ingest-runner, puis stockez la clé en clair dans le gestionnaire de secrets du processus. Les scopes d'écriture minimaux sont runs:write, runs:finish, logs:write, steps:write, metrics:write, progress:write et attachments:write. Les scopes de lecture et d'administration ne sont pas requis par le SDK émetteur.

export BERNOPS_BASE_URL=https://bernops.example
export BERNOPS_API_KEY='secret-lie-au-job'
export BERNOPS_EXTERNAL_JOB_ID='uuid-job-externe'
export AUTOMATION_RUN_ID='tentative-ordonnanceur-123'

Définissez AUTOMATION_RUN_ID depuis un ID durable fourni par l'ordonnanceur, le système CI ou la tentative métier. Réutilisez cette valeur lorsque la même tentative est relancée ; ne générez pas un nouvel ID à chaque démarrage de processus.

import { BernOps, type DeliveryReport } from '@bernops/automation';
 
function requiredEnv(name: string): string {
  const value = process.env[name]?.trim();
  if (!value) throw new Error(`Variable d'environnement requise absente : ${name}`);
  return value;
}
 
function deliveryIsDegraded(report: DeliveryReport): boolean {
  return report.pending > 0 || report.failed > 0 ||
    report.dropped > 0 || report.timedOut;
}
 
const client = BernOps.fromEnv({
  transport: 'external',
  redaction: { secretEnvironmentVariables: ['CUSTOM_SERVICE_TOKEN'] },
  onDiagnostic(diagnostic) {
    // Le callback reçoit des diagnostics SDK bornés et déjà masqués.
    console.warn(`[bernops:${diagnostic.code}] ${diagnostic.message}`);
  },
});
 
try {
  await client.capabilities();
  await client.run(
    {
      externalRunId: requiredEnv('AUTOMATION_RUN_ID'),
      name: 'import nocturne',
      service: 'catalogue',
      environment: 'production',
    },
    async (run) => {
      const admission = run.log.info('import démarré');
      if (!admission.accepted) {
        console.warn(`Événement BernOps non mis en file : ${admission.reason}`);
      }
 
      await run.step('importer les enregistrements', async () => {
        run.metric.count('records.imported', 250, { unit: 'items' });
        run.progress({ current: 250, total: 500, stage: 'import' });
      });
 
      const report = await run.flush();
      if (deliveryIsDegraded(report)) console.warn('Livraison BernOps dégradée', report);
    }
  );
} finally {
  const report = await client.shutdown();
  if (deliveryIsDegraded(report)) console.warn('Arrêt BernOps dégradé', report);
}

BERNOPS_EXTERNAL_JOB_ID identifie le job BernOps enregistré. externalRunId identifie une tentative durable de ce job. Si externalRunId est omis, la valeur générée ne peut être rejouée de manière sûre que dans le processus courant.

Préférez client.run() : cette méthode ferme les étapes ouvertes, vide la télémétrie, termine en succès ou en échec une exécution externe et préserve la valeur retournée ou l'erreur exacte du callback. Utilisez startRun() uniquement lorsqu'un appelant externe doit gérer une exécution dans plusieurs fonctions ; attendez complete() ou fail() avant l'arrêt.

Démarrage rapide Playwright natif

N'ajoutez pas de tarball ni de commande d'installation de package à un job Playwright BernOps enregistré. Une fois le bootstrap privé approuvé installé, lancez bernops setup et choisissez 3 (worker d'automatisation) ou 4 (les deux) ; le menu propose par défaut l'authentification seule. La configuration unifiée du worker obtient une version immuable signée avec le SDK privé exact déjà intégré, vérifie la disponibilité réelle SDK/navigateur et exige une approbation distincte de l'espace pour native_bridge_v1 ainsi qu'une connexion worker confirmée. Ce parcours ne demande ni build depuis un checkout, ni installation publique du SDK, ni montage d'artéfact. Une installation locale du package sert seulement à la création et à la vérification des types ; elle ne configure pas le worker déployé. Les anciens workers ne sont pas actualisés silencieusement. Demandez à l'opérateur de vérifier les versions disponibles et les capacités approuvées si l'import du package ou la capacité native est indisponible.

Le worker BernOps injecte la configuration du bridge pour chaque exécution. Ne définissez et ne copiez pas AB_LOG_URL, BERNOPS_BRIDGE_TOKEN, BERNOPS_RUN_ID ou BERNOPS_PROJECT_ID, et n'ajoutez pas de BERNOPS_API_KEY au job.

import { test, expect } from '@playwright/test';
import { BernOps } from '@bernops/automation';
 
test('checkout', async ({ page }) => {
  const client = BernOps.fromEnv({
    transport: 'native',
    onDiagnostic(diagnostic) {
      console.warn(`[bernops:${diagnostic.code}] ${diagnostic.message}`);
    },
  });
 
  try {
    const capabilities = await client.capabilities();
    expect(capabilities.lifecycleOwner).toBe('worker');
 
    await client.run({ name: 'checkout' }, async (run) => {
      run.log.info('ouverture du checkout');
      await run.step('soumettre la commande', async () => {
        await page.goto('/checkout');
      });
    });
  } finally {
    await client.shutdown();
  }
});

Le worker et Playwright gèrent le démarrage, le heartbeat et l'état terminal. run.complete() et run.fail() existent sur le type partagé, mais rejettent l'appel en mode natif avec BernOpsCapabilityError et code === 'lifecycle_owned_by_worker'. Appelez capabilities() avant tout comportement facultatif : les pièces jointes persistantes natives ne sont disponibles que si persistentAttachments vaut true, et les limites de taille retournées font autorité.

Télémétrie, progression et pièces jointes

APIComportement
run.log.debug/info/warn/errorCapture un log structuré et retourne immédiatement son admission en file.
run.step / run.startStepCrée des étapes gérées par callback ou terminées manuellement. Transmettez un vrai handle d'étape parente pour le travail détaché.
run.metric.gauge/count/rate/durationCapture des métriques à faible cardinalité. Les durées sont exprimées en millisecondes.
run.progressSignale une progression par compteur, pourcentage ou sans valeur déterminée.
run.attachCalcule l'empreinte, téléverse et confirme de manière persistante une pièce jointe rejouable. Attendez toujours son résultat.
run.flushAttend le travail accepté jusqu'au marqueur courant et laisse l'exécution modifiable.

La progression peut être regroupée pendant son attente ; les trous de séquence sont donc valides. Elle ne détermine jamais le succès ou l'échec et BernOps ne fabrique pas une progression à 100 % pour une exécution active. Gardez les noms et tags de métriques à faible cardinalité : n'utilisez pas d'adresses e-mail, d'ID utilisateur, d'ID de requête, d'horodatages, d'URL complètes ou de valeurs libres comme noms ou tags.

Une pièce jointe doit utiliser exactement une source rejouable : path, des données Uint8Array ou Blob, ou une fabrique open qui retourne un nouveau flux non lu à chaque tentative de livraison.

// `run` est le handle fourni au callback de client.run().
const attachment = await run.attach({
  path: '/tmp/import-summary.json',
  fileName: 'import-summary.json',
  contentType: 'application/json',
});
 
console.log(attachment.attachmentId, attachment.sizeBytes, attachment.duplicate);

Les types de contenu pris en charge sont PNG, JPEG, WebP, texte brut, JSON, CSV et PDF. Ne modifiez pas les octets du chemin ou du flux avant la fin de attach(). La confirmation représente l'enregistrement persistant des métadonnées et du stockage, pas une simple admission en file.

Livraison, erreurs et sécurité

Les méthodes de log, de métrique et de progression retournent un EnqueueResult ; accepted: true signifie uniquement que la file en mémoire a accepté l'événement. La livraison réseau s'observe avec flush(), les méthodes terminales externes et shutdown() grâce à un DeliveryReport contenant delivered, duplicate, dropped, pending, failed, timedOut et droppedByReason.

La politique par défaut failurePolicy: 'diagnostic' préserve le résultat de l'automatisation si la télémétrie se dégrade et signale le problème dans onDiagnostic et les rapports de livraison. Choisissez failurePolicy: 'strict' uniquement si un travail métier réussi doit échouer parce que la télémétrie requise n'a pas pu être livrée. En mode strict, les limites de livraison explicites peuvent lever BernOpsDeliveryError ; le SDK ne remplace cependant jamais une erreur initialement levée par votre callback.

Testez les classes d'erreur publiques, code, retryable et les champs sûrs propres à chaque classe plutôt que de comparer le texte du message :

ErreurAction habituelle
BernOpsConfigurationErrorCorrigez une configuration de transport, d'URL, d'identifiant, de livraison ou de masquage incomplète ou ambiguë.
BernOpsAuthenticationErrorFaites pivoter ou corrigez la clé, ou examinez l'affectation courante du worker. Ne relancez pas avec les mêmes identifiants rejetés.
BernOpsValidationErrorCorrigez une télémétrie, progression, métrique ou pièce jointe invalide ou non prise en charge.
BernOpsCapabilityErrorAdaptez le comportement aux capacités négociées et au propriétaire du cycle de vie.
BernOpsStateErrorArrêtez les appels d'étape ou terminaux contradictoires et vérifiez les états obsolètes.
BernOpsTransportErrorExaminez status, requestId, retryAfterMs et retryable.
BernOpsDeliveryErrorExaminez le report immuable retourné par une limite de livraison stricte.
BernOpsFinalizedRunErrorConsidérez la tentative durable comme déjà terminée ou utilisez volontairement un nouvel ID de tentative métier.
  • Les endpoints externes exigent HTTPS. HTTP n'est accepté que pour les URL de test littérales 127.0.0.1 ou [::1], configurées explicitement avec allowInsecureLoopback: true ; les identifiants dans l'URL et les redirections sont rejetés.
  • Le masquage est une défense supplémentaire. Ne capturez jamais d'en-têtes d'autorisation, de cookies, de jetons, de données personnelles, de données de carte ou de corps de requête et de réponse non limités.
  • Les octets des pièces jointes ne sont pas masqués. Nettoyez les fichiers avant leur téléversement, évitez les captures d'écran inutiles et appliquez une rétention ainsi que des contrôles d'accès adaptés.
  • Les files et tentatives sont bornées et en mémoire ; elles ne survivent pas à SIGKILL, à un manque de mémoire, à la perte de l'hôte ou à une coupure de courant.
  • L'import du package n'installe aucun gestionnaire de signal de processus. L'hôte définit sa propre politique de signaux, mais chaque chemin normal doit attendre un client.shutdown() borné dans finally.

Dépannage

SymptômeVérification
Le transport est incomplet ou ambiguSélectionnez explicitement external ou native et retirez les variables appartenant à l'autre transport.
L'URL externe est rejetéeUtilisez HTTPS. Pour un test local, utilisez un constructeur explicite avec une adresse de bouclage littérale et allowInsecureLoopback: true.
L'authentification échoueVérifiez l'association job/projet et la rotation de la clé. Ne relancez pas avec des identifiants rejetés inchangés.
L'identité ou les capacités natives échouentCréez un nouveau client dans l'affectation courante du worker ; ne réutilisez jamais des valeurs de bridge copiées.
Une exécution externe durable est finaliséeRéutilisez-la uniquement pour observer son résultat terminal antérieur, ou choisissez un nouvel ID pour une véritable nouvelle tentative métier.
La livraison reste en attente, est abandonnée, échoue ou expireExaminez onDiagnostic, le rapport et droppedByReason ; corrigez le transport et les délais avant d'augmenter les limites de file.
La pièce jointe change ou ne peut pas être rouverteGardez les chemins et octets stables et retournez un nouveau flux non lu identique à chaque appel de la fabrique.
Le processus ne se termine pasAttendez les pièces jointes, les limites de vidage ou terminales et client.shutdown() ; nettoyez ensuite les minuteurs et listeners gérés par l'hôte.

Ne masquez pas les erreurs permanentes avec des tentatives illimitées, des scopes plus larges, la désactivation de TLS, le suivi des redirections, la journalisation brute des corps de réponse ou des files plus grandes.

L'artéfact installé contient ses propres README.md, CHANGELOG.md, COMPATIBILITY.md, UPGRADING.md et déclarations TypeScript. Utilisez ces fichiers versionnés et les types de la racine du package dans votre IDE pour obtenir les signatures exactes ; ne copiez pas une signature d'API provenant d'une autre révision du SDK.