@lynx-js/autolink-codegen 0.0.0 → 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @lynx-js/autolink-codegen
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add the Native Autolink codegen package. ([#2601](https://github.com/lynx-family/lynx-stack/pull/2601))
8
+
9
+ ## 0.0.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Initial Native Autolink codegen package.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @lynx-js/autolink-codegen
2
+
3
+ Native Autolink code generator for Lynx extensions.
4
+
5
+ It scans `types/**/*.d.ts` for native module declarations annotated with
6
+ `/** @lynxmodule */`, reads `lynx.ext.json`, and generates:
7
+
8
+ - `generated/<ModuleName>.ts`
9
+ - Android `<ModuleName>Spec.java`
10
+ - iOS `<ModuleName>Spec.h` and `<ModuleName>Spec.m`
11
+
12
+ Run it from an extension package:
13
+
14
+ ```bash
15
+ npx @lynx-js/autolink-codegen
16
+ ```
17
+
18
+ The installed binary name is `lynx-autolink-codegen`, so generated extensions
19
+ can use:
20
+
21
+ ```json
22
+ {
23
+ "scripts": {
24
+ "codegen": "lynx-autolink-codegen"
25
+ }
26
+ }
27
+ ```
28
+
29
+ The first version intentionally supports only Native Autolink. Web Autolink and
30
+ Web spec generation are outside this package.
package/dist/131.js ADDED
@@ -0,0 +1,480 @@
1
+ import node_fs from "node:fs";
2
+ import node_path from "node:path";
3
+ const MODULE_HEADER_PATTERN = /\/\*\*[\s\S]*?@lynxmodule[\s\S]*?\*\/\s*export\s+declare\s+class\s+([A-Za-z_$][\w$]*)\s*\{/g;
4
+ const IDENTIFIER_PATTERN = /^[A-Z_$][\w$]*$/i;
5
+ const JAVA_PACKAGE_NAME_PATTERN = /^[A-Z_]\w*(?:\.[A-Z_]\w*)*$/i;
6
+ function parseNativeModules(source, filename = '<inline>') {
7
+ const modules = [];
8
+ const seen = new Set();
9
+ for (const { body, name: moduleName } of findNativeModuleDeclarations(source, filename)){
10
+ if (seen.has(moduleName)) throw new Error(`Duplicate native module "${moduleName}" in ${filename}`);
11
+ seen.add(moduleName);
12
+ modules.push({
13
+ name: moduleName,
14
+ methods: parseMethods(body, filename, moduleName)
15
+ });
16
+ }
17
+ return modules;
18
+ }
19
+ function findNativeModuleDeclarations(source, filename) {
20
+ const declarations = [];
21
+ const pattern = new RegExp(MODULE_HEADER_PATTERN);
22
+ let match = pattern.exec(source);
23
+ while(null !== match){
24
+ const moduleName = match[1];
25
+ const matchedHeader = match[0];
26
+ if (void 0 === moduleName) {
27
+ match = pattern.exec(source);
28
+ continue;
29
+ }
30
+ const openBraceIndex = match.index + matchedHeader.length - 1;
31
+ const closeBraceIndex = findMatchingBrace(source, openBraceIndex);
32
+ if (-1 === closeBraceIndex) throw new Error(`Invalid native module declaration in ${filename}: ${moduleName} is missing a closing brace`);
33
+ declarations.push({
34
+ name: moduleName,
35
+ body: source.slice(openBraceIndex + 1, closeBraceIndex)
36
+ });
37
+ pattern.lastIndex = closeBraceIndex + 1;
38
+ match = pattern.exec(source);
39
+ }
40
+ return declarations;
41
+ }
42
+ function findMatchingBrace(source, openBraceIndex) {
43
+ let braceDepth = 1;
44
+ let inBlockComment = false;
45
+ let inLineComment = false;
46
+ let quote;
47
+ let escaped = false;
48
+ for(let index = openBraceIndex + 1; index < source.length; index += 1){
49
+ const character = source.charAt(index);
50
+ const next = source.charAt(index + 1);
51
+ if (inBlockComment) {
52
+ if ('*' === character && '/' === next) {
53
+ index += 1;
54
+ inBlockComment = false;
55
+ }
56
+ continue;
57
+ }
58
+ if (inLineComment) {
59
+ if ('\n' === character || '\r' === character) inLineComment = false;
60
+ continue;
61
+ }
62
+ if (void 0 !== quote) {
63
+ if (escaped) {
64
+ escaped = false;
65
+ continue;
66
+ }
67
+ if ('\\' === character) {
68
+ escaped = true;
69
+ continue;
70
+ }
71
+ if (character === quote) quote = void 0;
72
+ continue;
73
+ }
74
+ if ('/' === character && '*' === next) {
75
+ index += 1;
76
+ inBlockComment = true;
77
+ continue;
78
+ }
79
+ if ('/' === character && '/' === next) {
80
+ index += 1;
81
+ inLineComment = true;
82
+ continue;
83
+ }
84
+ if ('\'' === character || '"' === character || '`' === character) {
85
+ quote = character;
86
+ continue;
87
+ }
88
+ if ('{' === character) {
89
+ braceDepth += 1;
90
+ continue;
91
+ }
92
+ if ('}' === character) {
93
+ braceDepth -= 1;
94
+ if (0 === braceDepth) return index;
95
+ }
96
+ }
97
+ return -1;
98
+ }
99
+ function generate(options = {}) {
100
+ const root = node_path.resolve(options.root ?? process.cwd());
101
+ const manifest = readManifest(root);
102
+ const modules = readNativeModuleSpecs(root);
103
+ const seenModules = new Set();
104
+ const files = [];
105
+ for (const module of modules){
106
+ if (seenModules.has(module.name)) throw new Error(`Duplicate native module "${module.name}" across types`);
107
+ seenModules.add(module.name);
108
+ files.push({
109
+ path: node_path.posix.join('generated', `${module.name}.ts`),
110
+ content: generateJsFacade(module)
111
+ });
112
+ files.push({
113
+ path: node_path.posix.join(manifest.platforms.android.sourceDir, 'src', 'main', 'java', ...manifest.platforms.android.packageName.split('.'), 'generated', `${module.name}Spec.java`),
114
+ content: generateAndroidSpec(module, manifest.platforms.android.packageName)
115
+ });
116
+ files.push({
117
+ path: node_path.posix.join(manifest.platforms.ios.sourceDir, 'src', 'generated', `${module.name}Spec.h`),
118
+ content: generateIosHeader(module)
119
+ });
120
+ files.push({
121
+ path: node_path.posix.join(manifest.platforms.ios.sourceDir, 'src', 'generated', `${module.name}Spec.m`),
122
+ content: generateIosImplementation(module)
123
+ });
124
+ }
125
+ return files;
126
+ }
127
+ function runCodegen(options = {}) {
128
+ const root = node_path.resolve(options.root ?? process.cwd());
129
+ const files = generate({
130
+ root
131
+ });
132
+ const targets = files.map((file)=>({
133
+ file,
134
+ target: resolveInside(root, file.path, 'package root')
135
+ }));
136
+ for (const { target } of targets)assertNoSymlinkTraversal(root, target);
137
+ for (const { file, target } of targets){
138
+ node_fs.mkdirSync(node_path.dirname(target), {
139
+ recursive: true
140
+ });
141
+ node_fs.writeFileSync(target, file.content);
142
+ }
143
+ return files;
144
+ }
145
+ function parseMethods(body, filename, moduleName) {
146
+ const methods = [];
147
+ const seen = new Set();
148
+ for (const trimmed of splitMethodDeclarations(body, filename, moduleName)){
149
+ const openParen = trimmed.indexOf('(');
150
+ const closeParen = trimmed.lastIndexOf(')');
151
+ const returnColon = -1 === closeParen ? -1 : trimmed.indexOf(':', closeParen);
152
+ if (openParen <= 0 || closeParen <= openParen || returnColon <= closeParen) throw new Error(`Invalid method declaration in ${filename}: ${moduleName}.${trimmed}`);
153
+ const methodName = trimmed.slice(0, openParen).trim();
154
+ const paramsSource = trimmed.slice(openParen + 1, closeParen);
155
+ const returnSource = trimmed.slice(returnColon + 1).trim();
156
+ if (!IDENTIFIER_PATTERN.test(methodName)) throw new Error(`Invalid method name "${methodName}" in ${filename}: ${moduleName}`);
157
+ if (seen.has(methodName)) throw new Error(`Duplicate method "${moduleName}.${methodName}" in ${filename}`);
158
+ seen.add(methodName);
159
+ methods.push({
160
+ name: methodName,
161
+ params: parseParams(paramsSource, filename, moduleName, methodName),
162
+ returnType: parseType(returnSource.trim(), filename, `${moduleName}.${methodName} return`)
163
+ });
164
+ }
165
+ return methods;
166
+ }
167
+ function stripTypeScriptComments(source) {
168
+ let result = '';
169
+ let inBlockComment = false;
170
+ let inLineComment = false;
171
+ let quote;
172
+ let escaped = false;
173
+ for(let index = 0; index < source.length; index += 1){
174
+ const character = source.charAt(index);
175
+ const next = source.charAt(index + 1);
176
+ if (inBlockComment) {
177
+ if ('\n' === character || '\r' === character) result += character;
178
+ else result += ' ';
179
+ if ('*' === character && '/' === next) {
180
+ result += ' ';
181
+ index += 1;
182
+ inBlockComment = false;
183
+ }
184
+ continue;
185
+ }
186
+ if (inLineComment) {
187
+ if ('\n' === character || '\r' === character) {
188
+ result += character;
189
+ inLineComment = false;
190
+ } else result += ' ';
191
+ continue;
192
+ }
193
+ if (void 0 !== quote) {
194
+ result += character;
195
+ if (escaped) {
196
+ escaped = false;
197
+ continue;
198
+ }
199
+ if ('\\' === character) {
200
+ escaped = true;
201
+ continue;
202
+ }
203
+ if (character === quote) quote = void 0;
204
+ continue;
205
+ }
206
+ if ('/' === character && '*' === next) {
207
+ result += ' ';
208
+ index += 1;
209
+ inBlockComment = true;
210
+ continue;
211
+ }
212
+ if ('/' === character && '/' === next) {
213
+ result += ' ';
214
+ index += 1;
215
+ inLineComment = true;
216
+ continue;
217
+ }
218
+ if ('\'' === character || '"' === character || '`' === character) quote = character;
219
+ result += character;
220
+ }
221
+ return result;
222
+ }
223
+ function splitMethodDeclarations(body, filename, moduleName) {
224
+ const declarations = [];
225
+ const source = stripTypeScriptComments(body);
226
+ let buffer = '';
227
+ for (const line of source.split(/\r?\n/)){
228
+ const parts = line.split(';');
229
+ for(let index = 0; index < parts.length; index += 1){
230
+ const part = parts[index]?.trim();
231
+ if (void 0 !== part && part.length > 0) buffer = `${buffer} ${part}`.trim();
232
+ if (buffer.length > 0 && (index < parts.length - 1 || isCompleteMethodDeclaration(buffer))) {
233
+ declarations.push(buffer);
234
+ buffer = '';
235
+ }
236
+ }
237
+ }
238
+ if (buffer.length > 0) throw new Error(`Invalid method declaration in ${filename}: ${moduleName}.${buffer}`);
239
+ return declarations;
240
+ }
241
+ function isCompleteMethodDeclaration(source) {
242
+ if (0 === source.length) return false;
243
+ let parenDepth = 0;
244
+ for (const character of source){
245
+ if ('(' === character) {
246
+ parenDepth += 1;
247
+ continue;
248
+ }
249
+ if (')' === character) {
250
+ parenDepth -= 1;
251
+ if (parenDepth < 0) return false;
252
+ }
253
+ }
254
+ if (0 !== parenDepth) return false;
255
+ const openParen = source.indexOf('(');
256
+ const closeParen = source.lastIndexOf(')');
257
+ const returnColon = -1 === closeParen ? -1 : source.indexOf(':', closeParen);
258
+ return openParen > 0 && closeParen > openParen && returnColon > closeParen;
259
+ }
260
+ function parseParams(source, filename, moduleName, methodName) {
261
+ const trimmed = source.trim();
262
+ if (0 === trimmed.length) return [];
263
+ const params = trimmed.split(',').filter((paramSource)=>paramSource.trim().length > 0);
264
+ return params.map((paramSource)=>{
265
+ const normalizedParam = paramSource.trim();
266
+ const colon = normalizedParam.indexOf(':');
267
+ if (colon <= 0 || colon === normalizedParam.length - 1) throw new Error(`Invalid parameter declaration in ${filename}: ${moduleName}.${methodName}(${paramSource})`);
268
+ const rawName = normalizedParam.slice(0, colon).trim();
269
+ const optional = rawName.endsWith('?');
270
+ const name = optional ? rawName.slice(0, -1).trim() : rawName;
271
+ const typeSource = normalizedParam.slice(colon + 1).trim();
272
+ if (!IDENTIFIER_PATTERN.test(name)) throw new Error(`Invalid parameter name "${rawName}" in ${filename}: ${moduleName}.${methodName}`);
273
+ if (optional) throw new Error(`Optional parameter "${moduleName}.${methodName}.${name}" is not supported by Native Autolink codegen v1`);
274
+ const type = parseType(typeSource, filename, `${moduleName}.${methodName}.${name}`);
275
+ if ('void' === type.name) throw new Error(`Unsupported parameter type "void" for ${moduleName}.${methodName}.${name} in ${filename}. Native Autolink codegen v1 only supports void as a return type.`);
276
+ return {
277
+ name,
278
+ type
279
+ };
280
+ });
281
+ }
282
+ function resolveInside(root, filePath, label) {
283
+ const resolvedRoot = node_path.resolve(root);
284
+ const target = node_path.resolve(resolvedRoot, filePath);
285
+ const relativePath = node_path.relative(resolvedRoot, target);
286
+ if ('..' === relativePath || relativePath.startsWith(`..${node_path.sep}`) || node_path.isAbsolute(relativePath)) throw new Error(`Generated path escapes ${label}: ${filePath}`);
287
+ return target;
288
+ }
289
+ function assertNoSymlinkTraversal(root, target) {
290
+ const resolvedRoot = node_path.resolve(root);
291
+ const relativePath = node_path.relative(resolvedRoot, target);
292
+ if (0 === relativePath.length) return;
293
+ let current = resolvedRoot;
294
+ for (const segment of relativePath.split(node_path.sep)){
295
+ current = node_path.join(current, segment);
296
+ if (node_fs.existsSync(current) && node_fs.lstatSync(current).isSymbolicLink()) throw new Error(`Generated path escapes package root via symlink: ${node_path.relative(resolvedRoot, current)}`);
297
+ }
298
+ }
299
+ function parseType(source, filename, context) {
300
+ const parts = source.split('|').map((part)=>part.trim()).filter(Boolean);
301
+ const nullable = parts.includes('null');
302
+ const nonNullParts = parts.filter((part)=>'null' !== part);
303
+ if (1 !== nonNullParts.length) throw unsupportedType(source, filename, context);
304
+ const name = nonNullParts[0];
305
+ if (void 0 === name || 'void' !== name && 'string' !== name && 'number' !== name && 'boolean' !== name) throw unsupportedType(source, filename, context);
306
+ if (nullable && 'void' === name) throw unsupportedType(source, filename, context);
307
+ return {
308
+ name,
309
+ nullable
310
+ };
311
+ }
312
+ function unsupportedType(source, filename, context) {
313
+ return new Error(`Unsupported type "${source}" for ${context} in ${filename}. Native Autolink codegen v1 supports void, string, number, boolean, and unions with null.`);
314
+ }
315
+ function readNativeModuleSpecs(root) {
316
+ const typesDir = node_path.join(root, 'types');
317
+ if (!node_fs.existsSync(typesDir)) return [];
318
+ const modules = [];
319
+ for (const file of walkFiles(typesDir)){
320
+ if (!file.endsWith('.d.ts')) continue;
321
+ const source = node_fs.readFileSync(file, 'utf8');
322
+ modules.push(...parseNativeModules(source, node_path.relative(root, file)));
323
+ }
324
+ return modules;
325
+ }
326
+ function walkFiles(dir) {
327
+ const entries = node_fs.readdirSync(dir, {
328
+ withFileTypes: true
329
+ });
330
+ const files = [];
331
+ for (const entry of entries){
332
+ const entryPath = node_path.join(dir, entry.name);
333
+ if (entry.isDirectory()) files.push(...walkFiles(entryPath));
334
+ else if (entry.isFile()) files.push(entryPath);
335
+ }
336
+ return files.sort();
337
+ }
338
+ function readManifest(root) {
339
+ const manifestPath = node_path.join(root, 'lynx.ext.json');
340
+ if (!node_fs.existsSync(manifestPath)) throw new Error(`Missing lynx.ext.json in ${root}. Native Autolink codegen must run from an extension package root.`);
341
+ const json = JSON.parse(node_fs.readFileSync(manifestPath, 'utf8'));
342
+ const platforms = readObject(json, 'platforms', manifestPath);
343
+ const android = readObject(platforms, 'android', manifestPath);
344
+ const ios = readObject(platforms, 'ios', manifestPath);
345
+ const packageName = readRequiredString(android, 'packageName', manifestPath, 'platforms.android.packageName');
346
+ if (!JAVA_PACKAGE_NAME_PATTERN.test(packageName)) throw new Error(`${manifestPath} must define "platforms.android.packageName" as a valid Java package identifier (got "${packageName}")`);
347
+ return {
348
+ platforms: {
349
+ android: {
350
+ packageName,
351
+ sourceDir: readOptionalString(android, 'sourceDir', manifestPath, 'platforms.android.sourceDir') ?? 'android'
352
+ },
353
+ ios: {
354
+ sourceDir: readOptionalString(ios, 'sourceDir', manifestPath, 'platforms.ios.sourceDir') ?? 'ios'
355
+ }
356
+ }
357
+ };
358
+ }
359
+ function readObject(value, key, manifestPath) {
360
+ if (!isRecord(value)) throw new Error(`${manifestPath} must be a JSON object`);
361
+ const child = value[key];
362
+ if (!isRecord(child)) throw new Error(`${manifestPath} must define object "${key}"`);
363
+ return child;
364
+ }
365
+ function readRequiredString(value, key, manifestPath, displayPath) {
366
+ const child = value[key];
367
+ if ('string' != typeof child || 0 === child.trim().length) throw new Error(`${manifestPath} must define string "${displayPath}"`);
368
+ return child;
369
+ }
370
+ function readOptionalString(value, key, manifestPath, displayPath) {
371
+ const child = value[key];
372
+ if (void 0 === child) return;
373
+ if ('string' == typeof child && child.trim().length > 0) return child;
374
+ throw new Error(`${manifestPath} must define non-empty string "${displayPath}"`);
375
+ }
376
+ function isRecord(value) {
377
+ return 'object' == typeof value && null !== value && !Array.isArray(value);
378
+ }
379
+ function generateJsFacade(module) {
380
+ const methods = module.methods.map((method)=>` ${method.name}(${method.params.map((param)=>`${param.name}: ${toTsType(param.type)}`).join(', ')}): ${toTsType(method.returnType)};`).join('\n');
381
+ return `// Generated by @lynx-js/autolink-codegen. Do not edit.
382
+
383
+ declare const NativeModules: {
384
+ ${module.name}: {
385
+ ${methods}
386
+ };
387
+ };
388
+
389
+ export const ${module.name}: typeof NativeModules.${module.name} = NativeModules.${module.name};
390
+ export default ${module.name};
391
+ `;
392
+ }
393
+ function generateAndroidSpec(module, packageName) {
394
+ const methods = module.methods.map((method)=>` @LynxMethod\n public abstract ${toJavaType(method.returnType)} ${method.name}(${method.params.map((param)=>`${toJavaType(param.type)} ${param.name}`).join(', ')});`).join('\n\n');
395
+ return `// Generated by @lynx-js/autolink-codegen. Do not edit.
396
+ package ${packageName}.generated;
397
+
398
+ import androidx.annotation.Nullable;
399
+ import com.lynx.jsbridge.LynxContextModule;
400
+ import com.lynx.jsbridge.LynxMethod;
401
+ import com.lynx.tasm.behavior.LynxContext;
402
+
403
+ public abstract class ${module.name}Spec extends LynxContextModule {
404
+ public ${module.name}Spec(LynxContext context) {
405
+ super(context);
406
+ }
407
+
408
+ ${methods}
409
+ }
410
+ `;
411
+ }
412
+ function generateIosHeader(module) {
413
+ const methods = module.methods.map((method)=>`- (${toObjCReturnType(method.returnType)})${method.name}${0 === method.params.length ? '' : toObjCParams(method.params)};`).join('\n');
414
+ return `// Generated by @lynx-js/autolink-codegen. Do not edit.
415
+ #import <Foundation/Foundation.h>
416
+ #import <Lynx/LynxModule.h>
417
+
418
+ NS_ASSUME_NONNULL_BEGIN
419
+
420
+ @protocol ${module.name}Spec <LynxModule>
421
+
422
+ ${methods}
423
+
424
+ @end
425
+
426
+ NS_ASSUME_NONNULL_END
427
+ `;
428
+ }
429
+ function generateIosImplementation(module) {
430
+ return `// Generated by @lynx-js/autolink-codegen. Do not edit.
431
+ #import "${module.name}Spec.h"
432
+ `;
433
+ }
434
+ function toTsType(type) {
435
+ const base = 'void' === type.name ? 'void' : type.name;
436
+ return type.nullable ? `${base} | null` : base;
437
+ }
438
+ function toJavaType(type) {
439
+ switch(type.name){
440
+ case 'void':
441
+ return 'void';
442
+ case 'string':
443
+ return type.nullable ? '@Nullable String' : 'String';
444
+ case 'number':
445
+ return type.nullable ? '@Nullable Double' : 'double';
446
+ case 'boolean':
447
+ return type.nullable ? '@Nullable Boolean' : 'boolean';
448
+ }
449
+ }
450
+ function toObjCReturnType(type) {
451
+ switch(type.name){
452
+ case 'void':
453
+ return 'void';
454
+ case 'string':
455
+ return type.nullable ? 'nullable NSString *' : 'NSString *';
456
+ case 'number':
457
+ return type.nullable ? 'nullable NSNumber *' : 'double';
458
+ case 'boolean':
459
+ return type.nullable ? 'nullable NSNumber *' : 'BOOL';
460
+ }
461
+ }
462
+ function toObjCParamType(type) {
463
+ switch(type.name){
464
+ case 'void':
465
+ throw new Error('void parameters are not supported');
466
+ case 'string':
467
+ return type.nullable ? 'nullable NSString *' : 'NSString *';
468
+ case 'number':
469
+ return type.nullable ? 'nullable NSNumber *' : 'double';
470
+ case 'boolean':
471
+ return type.nullable ? 'nullable NSNumber *' : 'BOOL';
472
+ }
473
+ }
474
+ function toObjCParams(params) {
475
+ return params.map((param, index)=>{
476
+ const prefix = 0 === index ? ':' : ` ${param.name}:`;
477
+ return `${prefix}(${toObjCParamType(param.type)})${param.name}`;
478
+ }).join('');
479
+ }
480
+ export { generate, node_path, parseNativeModules, runCodegen };
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import { node_path, runCodegen } from "./131.js";
3
+ function parseArgs(argv) {
4
+ const options = {
5
+ help: false
6
+ };
7
+ for(let index = 0; index < argv.length; index += 1){
8
+ const arg = argv[index];
9
+ if ('--help' === arg || '-h' === arg) {
10
+ options.help = true;
11
+ continue;
12
+ }
13
+ if ('--root' === arg || '-r' === arg) {
14
+ const value = argv[index + 1];
15
+ if (void 0 === value || value.startsWith('-')) throw new Error(`${arg} requires a value`);
16
+ options.root = value;
17
+ index += 1;
18
+ continue;
19
+ }
20
+ throw new Error(`Unknown option "${arg ?? ''}"`);
21
+ }
22
+ return options;
23
+ }
24
+ function printHelp() {
25
+ console.info(`Usage: lynx-autolink-codegen [--root <dir>]
26
+
27
+ Generate Native Autolink JS, Android, and iOS specs from types/**/*.d.ts.
28
+
29
+ Options:
30
+ --root, -r <dir> Extension package root. Defaults to the current directory.
31
+ --help, -h Show this help message.
32
+ `);
33
+ }
34
+ function main() {
35
+ const cliOptions = parseArgs(process.argv.slice(2));
36
+ if (cliOptions.help) return void printHelp();
37
+ const options = {};
38
+ if (void 0 !== cliOptions.root) options.root = node_path.resolve(cliOptions.root);
39
+ const files = runCodegen(options);
40
+ for (const file of files)console.info(`generated ${file.path}`);
41
+ }
42
+ try {
43
+ main();
44
+ } catch (error) {
45
+ console.error(error instanceof Error ? error.message : String(error));
46
+ process.exitCode = 1;
47
+ }
@@ -0,0 +1,48 @@
1
+ export declare interface CodegenOptions {
2
+ root?: string;
3
+ }
4
+
5
+ /**
6
+ * Builds the generated JS facade, Android spec, and iOS spec file contents for an extension package.
7
+ */
8
+ export declare function generate(options?: CodegenOptions): GeneratedFile[];
9
+
10
+ export declare interface GeneratedFile {
11
+ path: string;
12
+ content: string;
13
+ }
14
+
15
+ export declare interface NativeModuleMethod {
16
+ name: string;
17
+ params: NativeModuleParam[];
18
+ returnType: NativeModuleType;
19
+ }
20
+
21
+ export declare interface NativeModuleParam {
22
+ name: string;
23
+ type: NativeModuleType;
24
+ }
25
+
26
+ export declare interface NativeModuleSpec {
27
+ name: string;
28
+ methods: NativeModuleMethod[];
29
+ }
30
+
31
+ export declare interface NativeModuleType {
32
+ name: NativeModuleTypeName;
33
+ nullable: boolean;
34
+ }
35
+
36
+ export declare type NativeModuleTypeName = 'void' | 'string' | 'number' | 'boolean';
37
+
38
+ /**
39
+ * Parses native module declarations marked with `@lynxmodule` from a TypeScript declaration source.
40
+ */
41
+ export declare function parseNativeModules(source: string, filename?: string): NativeModuleSpec[];
42
+
43
+ /**
44
+ * Writes generated files to disk and returns the generated file descriptors.
45
+ */
46
+ export declare function runCodegen(options?: CodegenOptions): GeneratedFile[];
47
+
48
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { generate, parseNativeModules, runCodegen } from "./131.js";
package/package.json CHANGED
@@ -1,9 +1,40 @@
1
1
  {
2
2
  "name": "@lynx-js/autolink-codegen",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "Native Autolink code generator for Lynx extensions",
5
- "keywords": [],
5
+ "keywords": [
6
+ "Lynx",
7
+ "Autolink",
8
+ "codegen"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/lynx-family/lynx-stack.git",
13
+ "directory": "packages/lynx/autolink-codegen"
14
+ },
6
15
  "license": "Apache-2.0",
16
+ "sideEffects": false,
7
17
  "type": "module",
8
- "main": "index.js"
9
- }
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "bin": {
25
+ "lynx-autolink-codegen": "./dist/cli.js"
26
+ },
27
+ "files": [
28
+ "CHANGELOG.md",
29
+ "README.md",
30
+ "dist"
31
+ ],
32
+ "engines": {
33
+ "node": "^20 || ^22 || ^24"
34
+ },
35
+ "scripts": {
36
+ "build": "rslib build",
37
+ "dev": "rslib build --watch",
38
+ "test": "vitest run"
39
+ }
40
+ }
package/index.js DELETED
@@ -1 +0,0 @@
1
- export {}