Skip to content

Commit f9c1321

Browse files
committed
feat(generator): add node.config.json
1 parent 0c04871 commit f9c1321

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

src/generators/index.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import addonVerify from './addon-verify/index.mjs';
1010
import apiLinks from './api-links/index.mjs';
1111
import oramaDb from './orama-db/index.mjs';
1212
import astJs from './ast-js/index.mjs';
13+
import nodeConfigSchema from './node-config-schema/index.mjs';
1314

1415
export const publicGenerators = {
1516
'json-simple': jsonSimple,
@@ -21,6 +22,7 @@ export const publicGenerators = {
2122
'addon-verify': addonVerify,
2223
'api-links': apiLinks,
2324
'orama-db': oramaDb,
25+
'node-config-schema': nodeConfigSchema,
2426
};
2527

2628
export const allGenerators = {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Error Messages
2+
export const ERRORS = {
3+
missingCCandHFiles:
4+
'Both node_options.cc and node_options.h must be provided.',
5+
headerTypeNotFound:
6+
'Header type for "{{headerKey}}" not found in the header file.',
7+
missingTypeDefinition:
8+
'No type definition found for header type "{{headerType}}" in TYPE_DEFINITION_MAP.',
9+
};
10+
11+
// Regex pattern to match calls to the AddOption function.
12+
export const ADD_OPTION_REGEX =
13+
/AddOption[\s\n\r]*\([\s\n\r]*"([^"]+)"(.*?)\);/gs;
14+
15+
// Regex pattern to match header keys in the Options class.
16+
export const OPTION_HEADER_KEY_REGEX = /Options::(\w+)/;
17+
18+
// Basic JSON schema for node.config.json
19+
export const BASIC_SCHEMA = {
20+
$schema: 'https://json-schema.org/draft/2020-12/schema',
21+
additionalProperties: false,
22+
properties: {
23+
$schema: {
24+
type: 'string',
25+
},
26+
nodeOptions: {
27+
additionalProperties: false,
28+
properties: {},
29+
type: 'object',
30+
},
31+
},
32+
type: 'object',
33+
};
34+
35+
// Schema Definition Map for Data Types
36+
export const TYPE_DEFINITION_MAP = {
37+
'std::vector<std::string>': {
38+
oneOf: [
39+
{ type: 'string' }, // Single string case
40+
{
41+
items: { type: 'string', minItems: 1 }, // Array of strings, ensuring at least one item
42+
type: 'array',
43+
},
44+
],
45+
},
46+
uint64_t: { type: 'number' }, // 64-bit unsigned integer maps to a number
47+
int64_t: { type: 'number' }, // 64-bit signed integer maps to a number
48+
HostPort: { type: 'number' }, // HostPort is a number, like 4000
49+
'std::string': { type: 'string' }, // String type
50+
bool: { type: 'boolean' }, // Boolean type
51+
};
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { readFile, writeFile } from 'node:fs/promises';
2+
import {
3+
ERRORS,
4+
ADD_OPTION_REGEX,
5+
BASIC_SCHEMA,
6+
OPTION_HEADER_KEY_REGEX,
7+
TYPE_DEFINITION_MAP,
8+
} from './constants.mjs';
9+
import { join } from 'node:path';
10+
11+
/**
12+
* This generator generates the `node.config.json` schema.
13+
*
14+
* @typedef {Array<ApiDocMetadataEntry>} Input
15+
*
16+
* @type {GeneratorMetadata<Input, string>}
17+
*/
18+
export default {
19+
name: 'node-config-schema',
20+
21+
version: '1.0.0',
22+
23+
description: 'Generates the node.config.json schema.',
24+
25+
/**
26+
* Generates the `node.config.json` schema.
27+
* @param {unknown} _ - Unused parameter
28+
* @param {Partial<GeneratorOptions>} options - Options containing the input file paths
29+
* @throws {Error} If the required files node_options.cc or node_options.h are missing or invalid.
30+
*/
31+
async generate(_, options) {
32+
let ccFile, hFile;
33+
34+
// Ensure input files are provided and capture the paths
35+
for (const filePath of options.input) {
36+
if (filePath.endsWith('node_options.cc')) {
37+
ccFile = filePath;
38+
} else if (filePath.endsWith('node_options.h')) {
39+
hFile = filePath;
40+
}
41+
}
42+
43+
// Error handling if either cc or h file is missing
44+
if (!ccFile || !hFile) {
45+
throw new Error(ERRORS.missingCCandHFiles);
46+
}
47+
48+
// Read the contents of the cc and h files
49+
const ccContent = await readFile(ccFile, 'utf-8');
50+
const hContent = await readFile(hFile, 'utf-8');
51+
52+
// Clone the BASIC_SCHEMA to avoid mutating the original schema object
53+
/** @type {typeof BASIC_SCHEMA} */
54+
const schema = Object.assign({}, BASIC_SCHEMA);
55+
const { nodeOptions } = schema.properties;
56+
57+
// Process the cc content and match AddOption calls
58+
for (const [, option, config] of ccContent.matchAll(ADD_OPTION_REGEX)) {
59+
// If config doesn't include 'kAllowedInEnvvar', skip this option
60+
if (!config.includes('kAllowedInEnvvar')) {
61+
continue;
62+
}
63+
64+
const headerKey = config.match(OPTION_HEADER_KEY_REGEX)?.[1];
65+
// If there's no header key, it's either a V8 option or a no-op
66+
if (!headerKey) {
67+
continue;
68+
}
69+
70+
// Try to find the corresponding header type in the hContent
71+
const headerTypeMatch = hContent.match(
72+
new RegExp(`\\s*(.+)\\s${headerKey}[^\\B_]`)
73+
);
74+
75+
if (!headerTypeMatch) {
76+
throw new Error(
77+
formatErrorMessage(ERRORS.headerTypeNotFound, { headerKey })
78+
);
79+
}
80+
81+
const headerType = headerTypeMatch[1].trim();
82+
83+
// Ensure the headerType exists in the TYPE_DEFINITION_MAP
84+
const typeDefinition = TYPE_DEFINITION_MAP[headerType];
85+
if (!typeDefinition) {
86+
throw new Error(
87+
formatErrorMessage(ERRORS.missingTypeDefinition, { headerType })
88+
);
89+
}
90+
91+
// Add the option to the schema after removing the '--' prefix
92+
nodeOptions.properties[option.replace('--', '')] = typeDefinition;
93+
}
94+
95+
nodeOptions.properties = Object.fromEntries(
96+
Object.keys(nodeOptions.properties)
97+
.sort()
98+
.map(key => [key, nodeOptions.properties[key]])
99+
);
100+
101+
await writeFile(
102+
join(options.output, 'node-config-schema.json'),
103+
JSON.stringify(schema, null, 2) + '\n'
104+
);
105+
106+
return schema;
107+
},
108+
};
109+
110+
/**
111+
* Helper function to replace placeholders in error messages with dynamic values.
112+
* @param {string} message - The error message with placeholders.
113+
* @param {Object} params - The values to replace the placeholders.
114+
* @returns {string} - The formatted error message.
115+
*/
116+
function formatErrorMessage(message, params) {
117+
return message.replace(/{{(\w+)}}/g, (_, key) => params[key] || `{{${key}}}`);
118+
}

0 commit comments

Comments
 (0)