current commit
260613.9 · src: officially deprecate `node::ObjectWrap`
#!/usr/bin/env node

// Extracts C++ addon examples from doc/api/addons.md into numbered test
// directories under test/addons/.
//
// Each code block to extract is preceded by a marker in the markdown:
//
//   <!-- addon-verify-file worker_support/addon.cc [...] -->
//   ```cpp
//   #include <node.h>
//   ...
//   ```
//
// This produces test/addons/01_worker_support/addon.cc.
// Sections are numbered in order of first appearance.

import { mkdirSync, writeFileSync } from 'node:fs';
import { open } from 'node:fs/promises';
import { join } from 'node:path';
import { parseArgs } from 'node:util';

const { values } = parseArgs({
  options: {
    input: { type: 'string' },
    output: { type: 'string' },
  },
});

if (!values.input || !values.output) {
  console.error('Usage: addon-verify.mjs --input <file> --output <dir>');
  process.exit(1);
}

const src = await open(values.input, 'r');

const MARKER_RE = /^<!--\s*addon-verify-file\s+(?<filenames>((\S+?)\/(\S+)\s?)+)\s*-->$/;

const entries = [];
let nextBlockIsAddonVerifyFile = false;
let expectedClosingFenceMarker;
for await (const line of src.readLines()) {
  if (expectedClosingFenceMarker) {
    // We're inside a Addon snippet
    if (line === expectedClosingFenceMarker) {
      // End of the snippet
      expectedClosingFenceMarker = null;
      continue;
    }

    entries.at(-1).contentRef.content += `${line}\n`;
  }
  if (nextBlockIsAddonVerifyFile) {
    if (line) {
      expectedClosingFenceMarker = line.replace(/\w/g, '');
      nextBlockIsAddonVerifyFile = false;
    }
    continue;
  }
  const match = MARKER_RE.exec(line);
  if (match) {
    nextBlockIsAddonVerifyFile = true;
    const { filenames } = match.groups;
    // Use a single contentRef for files with identical contents
    const contentRef = { content: '' };
    for (const filename of filenames.split(/\s+/).filter(Boolean)) {
      const [dir, name] = filename.split('/');
      entries.push({ dir, name, contentRef });
    }
  }
}

// Collect files grouped by section directory name.
const sections = Map.groupBy(entries, (e) => e.dir);

let idx = 0;
for (const [name, files] of sections) {
  const dirName = `${String(++idx).padStart(2, '0')}_${name}`;
  const dir = join(values.output, dirName);
  mkdirSync(dir, { recursive: true });

  for (const file of files) {
    let { content } = file.contentRef;
    if (file.name === 'test.js') {
      content =
        "'use strict';\n" +
        "const common = require('../../common');\n" +
        content.replace(
          "'./build/Release/addon'",
          // eslint-disable-next-line no-template-curly-in-string
          '`./build/${common.buildType}/addon`',
        );
    }
    writeFileSync(join(dir, file.name), content);
  }

  // Generate binding.gyp
  const names = files.map((f) => f.name);
  writeFileSync(join(dir, 'binding.gyp'), JSON.stringify({
    targets: [{
      target_name: 'addon',
      sources: names,
      includes: ['../common.gypi'],
    }],
  }));

  console.log(`Generated ${dirName} with files: ${names.join(', ')}`);
}