< Summary - Envilder CLI

Information
Class: src/envilder/core/infrastructure/github/GitHubActionsSecretMasker.ts
Assembly: Default
File(s): src/envilder/core/infrastructure/github/GitHubActionsSecretMasker.ts
Tag: 561_41292037308
Line coverage
100%
Covered lines: 13
Uncovered lines: 0
Coverable lines: 13
Total lines: 51
Line coverage: 100%
Branch coverage
100%
Covered branches: 5
Total branches: 5
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

File(s)

src/envilder/core/infrastructure/github/GitHubActionsSecretMasker.ts

#LineLine coverage
 1import type { ISecretMasker } from '../../domain/ports/ISecretMasker.js';
 2
 3/**
 4 * Registers values with GitHub Actions log masking through `::add-mask::`
 5 * workflow commands, on stdout unless another stream is given.
 6 *
 7 * Writes to a piped stdout are asynchronous on POSIX, so each write is
 8 * tracked through its callback: `flush()` resolves once every registration
 9 * so far has been written, letting a caller start a process that shares
 10 * stdout only after its values are masked. It rejects as soon as a failed
 11 * write is reached, without waiting for later registrations: the caller must
 12 * not start that process anyway.
 13 */
 14export class GitHubActionsSecretMasker implements ISecretMasker {
 2315  private pending: Promise<void> = Promise.resolve();
 16  private readonly stream: NodeJS.WritableStream;
 17
 18  /**
 19   * @param options.stream - Where workflow commands are written (default:
 20   * stdout). The runner reads them from stdout and stderr; exec mode uses
 21   * stderr so stdout carries only the child's output.
 22   */
 23  constructor(options: { stream?: NodeJS.WritableStream } = {}) {
 2324    this.stream = options.stream ?? process.stdout;
 25  }
 26
 27  mask(value: string): void {
 2028    const escapedValue = value
 29      .replace(/%/g, '%25')
 30      .replace(/\r/g, '%0D')
 31      .replace(/\n/g, '%0A');
 2032    const written = new Promise<void>((resolve, reject) => {
 2033      this.stream.write(`::add-mask::${escapedValue}\n`, (error) => {
 1834        if (error) {
 735          reject(error);
 736          return;
 37        }
 1138        resolve();
 39      });
 40    });
 2041    this.pending = this.pending.then(() => written);
 42    // Modes that never flush (pull, the GitHub Action) must not crash on an
 43    // unhandled rejection; flush() still reports the failure.
 2044    written.catch(() => undefined);
 2045    this.pending.catch(() => undefined);
 46  }
 47
 48  flush(): Promise<void> {
 1049    return this.pending;
 50  }
 51}