@birdcc/parser 0.0.1-alpha.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.
Files changed (76) hide show
  1. package/.oxfmtrc.json +16 -0
  2. package/LICENSE +674 -0
  3. package/README.md +312 -0
  4. package/dist/declarations/basic.d.ts +9 -0
  5. package/dist/declarations/basic.d.ts.map +1 -0
  6. package/dist/declarations/basic.js +180 -0
  7. package/dist/declarations/basic.js.map +1 -0
  8. package/dist/declarations/filter.d.ts +6 -0
  9. package/dist/declarations/filter.d.ts.map +1 -0
  10. package/dist/declarations/filter.js +330 -0
  11. package/dist/declarations/filter.js.map +1 -0
  12. package/dist/declarations/parse-declarations.d.ts +4 -0
  13. package/dist/declarations/parse-declarations.d.ts.map +1 -0
  14. package/dist/declarations/parse-declarations.js +54 -0
  15. package/dist/declarations/parse-declarations.js.map +1 -0
  16. package/dist/declarations/protocol.d.ts +6 -0
  17. package/dist/declarations/protocol.d.ts.map +1 -0
  18. package/dist/declarations/protocol.js +444 -0
  19. package/dist/declarations/protocol.js.map +1 -0
  20. package/dist/declarations/shared.d.ts +56 -0
  21. package/dist/declarations/shared.d.ts.map +1 -0
  22. package/dist/declarations/shared.js +169 -0
  23. package/dist/declarations/shared.js.map +1 -0
  24. package/dist/declarations/top-level.d.ts +6 -0
  25. package/dist/declarations/top-level.d.ts.map +1 -0
  26. package/dist/declarations/top-level.js +141 -0
  27. package/dist/declarations/top-level.js.map +1 -0
  28. package/dist/declarations.d.ts +2 -0
  29. package/dist/declarations.d.ts.map +1 -0
  30. package/dist/declarations.js +2 -0
  31. package/dist/declarations.js.map +1 -0
  32. package/dist/index.d.ts +8 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +49 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/issues.d.ts +9 -0
  37. package/dist/issues.d.ts.map +1 -0
  38. package/dist/issues.js +119 -0
  39. package/dist/issues.js.map +1 -0
  40. package/dist/runtime.d.ts +5 -0
  41. package/dist/runtime.d.ts.map +1 -0
  42. package/dist/runtime.js +51 -0
  43. package/dist/runtime.js.map +1 -0
  44. package/dist/tree-sitter-birdcc.wasm +0 -0
  45. package/dist/tree.d.ts +16 -0
  46. package/dist/tree.d.ts.map +1 -0
  47. package/dist/tree.js +150 -0
  48. package/dist/tree.js.map +1 -0
  49. package/dist/types.d.ts +222 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/dist/types.js +2 -0
  52. package/dist/types.js.map +1 -0
  53. package/grammar.js +601 -0
  54. package/package.json +46 -0
  55. package/scripts/sync-wasm-paths.mjs +21 -0
  56. package/src/declarations/basic.ts +272 -0
  57. package/src/declarations/filter.ts +437 -0
  58. package/src/declarations/parse-declarations.ts +84 -0
  59. package/src/declarations/protocol.ts +597 -0
  60. package/src/declarations/shared.ts +275 -0
  61. package/src/declarations/top-level.ts +185 -0
  62. package/src/declarations.ts +1 -0
  63. package/src/index.ts +102 -0
  64. package/src/issues.ts +154 -0
  65. package/src/runtime.ts +64 -0
  66. package/src/tree-sitter-birdcc.wasm +0 -0
  67. package/src/tree.ts +210 -0
  68. package/src/types.ts +329 -0
  69. package/test/fixtures.test.ts +48 -0
  70. package/test/ip-literal-candidate.test.ts +39 -0
  71. package/test/parser.test.ts +475 -0
  72. package/test/realworld-smoke.test.ts +46 -0
  73. package/test/runtime.test.ts +51 -0
  74. package/test/tree.test.ts +83 -0
  75. package/tree-sitter.json +37 -0
  76. package/tsconfig.json +8 -0
package/grammar.js ADDED
@@ -0,0 +1,601 @@
1
+ const commaSep1 = (rule) => seq(rule, repeat(seq(",", rule)));
2
+
3
+ export default grammar({
4
+ name: "bird",
5
+
6
+ extras: ($) => [/\s/, $.comment],
7
+
8
+ word: ($) => $.identifier,
9
+
10
+ rules: {
11
+ // Entry: keep top-level declarations explicit so downstream AST extraction stays stable.
12
+ source_file: ($) => repeat($._top_level_item),
13
+
14
+ _top_level_item: ($) =>
15
+ choice(
16
+ $.include_declaration,
17
+ $.define_declaration,
18
+ $.router_id_declaration,
19
+ $.table_declaration,
20
+ $.protocol_declaration,
21
+ $.template_declaration,
22
+ $.filter_declaration,
23
+ $.function_declaration,
24
+ $.top_level_statement,
25
+ ),
26
+
27
+ comment: () => token(seq("#", /.*/)),
28
+
29
+ string: () =>
30
+ token(
31
+ choice(
32
+ seq('"', repeat(choice(/[^"\\\n]+/, /\\./)), '"'),
33
+ seq("'", repeat(choice(/[^'\\\n]+/, /\\./)), "'"),
34
+ ),
35
+ ),
36
+
37
+ identifier: () => /[A-Za-z_][A-Za-z0-9_-]*/,
38
+ number: () => /[0-9][0-9A-Za-z_]*/,
39
+ ipv4_literal: () => token(/[0-9]{1,3}(\.[0-9]{1,3}){3}/),
40
+ ipv6_literal: () => token(/[0-9A-Fa-f]*:[0-9A-Fa-f:]+/),
41
+ ip_literal: ($) => choice($.ipv4_literal, $.ipv6_literal),
42
+ prefix_literal: () =>
43
+ token(/[0-9A-Fa-f:.]+\/[0-9]{1,3}([+-]|\{[0-9]{1,3},[0-9]{1,3}\})?/),
44
+
45
+ // Generic fallback token used to keep recovery resilient in partial grammar coverage.
46
+ raw_token: () => token(prec(-1, /[^{}\s"';#]+/)),
47
+
48
+ // Top-level declarations
49
+ include_declaration: ($) =>
50
+ seq("include", optional(field("path", $.string)), ";"),
51
+
52
+ define_declaration: ($) =>
53
+ choice(
54
+ seq(
55
+ "define",
56
+ field("name", $.identifier),
57
+ optional(
58
+ seq(
59
+ "=",
60
+ repeat1(
61
+ choice(
62
+ $.string,
63
+ $.number,
64
+ $.ip_literal,
65
+ $.prefix_literal,
66
+ $.identifier,
67
+ $.raw_token,
68
+ $.block,
69
+ ),
70
+ ),
71
+ ),
72
+ ),
73
+ ";",
74
+ ),
75
+ seq("define", ";"),
76
+ ),
77
+
78
+ router_id_declaration: ($) =>
79
+ seq("router", "id", optional(field("value", $.router_id_value)), ";"),
80
+
81
+ router_id_value: ($) =>
82
+ choice(
83
+ $.router_id_from_clause,
84
+ $.ipv4_literal,
85
+ $.number,
86
+ $.identifier,
87
+ $.raw_token,
88
+ ),
89
+
90
+ router_id_from_clause: ($) =>
91
+ seq("from", field("from_source", choice("routing", "dynamic"))),
92
+
93
+ table_declaration: ($) =>
94
+ choice(
95
+ seq(
96
+ field("table_type", "routing"),
97
+ "table",
98
+ optional(field("name", $.identifier)),
99
+ optional(field("attrs", $.table_attrs)),
100
+ ";",
101
+ ),
102
+ seq(
103
+ field("table_type", $.table_type),
104
+ "table",
105
+ optional(field("name", $.identifier)),
106
+ optional(field("attrs", $.table_attrs)),
107
+ ";",
108
+ ),
109
+ seq("table", ";"),
110
+ ),
111
+
112
+ table_type: () =>
113
+ choice("ipv4", "ipv6", "vpn4", "vpn6", "roa4", "roa6", "flow4", "flow6"),
114
+
115
+ table_attrs: ($) =>
116
+ seq(
117
+ "attrs",
118
+ choice(
119
+ $.identifier,
120
+ seq("(", commaSep1(choice($.identifier, $.raw_token)), ")"),
121
+ seq(
122
+ "{",
123
+ repeat(
124
+ choice($.identifier, $.raw_token, $.number, $.string, ",", ";"),
125
+ ),
126
+ "}",
127
+ ),
128
+ ),
129
+ ),
130
+
131
+ protocol_declaration: ($) =>
132
+ choice(
133
+ prec(
134
+ 1,
135
+ seq(
136
+ "protocol",
137
+ field("protocol_type", $.identifier),
138
+ field("body", $.block),
139
+ optional(";"),
140
+ ),
141
+ ),
142
+ prec(
143
+ 2,
144
+ seq(
145
+ "protocol",
146
+ field("protocol_type", $.identifier),
147
+ field("protocol_variant", $.protocol_variant),
148
+ field("name", $.identifier),
149
+ optional(seq("from", field("from_template", $.identifier))),
150
+ field("body", $.block),
151
+ optional(";"),
152
+ ),
153
+ ),
154
+ prec(
155
+ 2,
156
+ seq(
157
+ "protocol",
158
+ field("protocol_type", $.identifier),
159
+ field("name", $.identifier),
160
+ optional(seq("from", field("from_template", $.identifier))),
161
+ field("body", $.block),
162
+ optional(";"),
163
+ ),
164
+ ),
165
+ ),
166
+
167
+ protocol_variant: () => token(prec(1, /v[0-9]+/)),
168
+
169
+ template_declaration: ($) =>
170
+ choice(
171
+ prec(
172
+ 1,
173
+ seq(
174
+ "template",
175
+ field("template_type", $.identifier),
176
+ field("body", $.block),
177
+ optional(";"),
178
+ ),
179
+ ),
180
+ prec(
181
+ 2,
182
+ seq(
183
+ "template",
184
+ field("template_type", $.identifier),
185
+ field("name", $.identifier),
186
+ field("body", $.block),
187
+ optional(";"),
188
+ ),
189
+ ),
190
+ ),
191
+
192
+ filter_declaration: ($) =>
193
+ choice(
194
+ prec(1, seq("filter", field("body", $.block), optional(";"))),
195
+ prec(
196
+ 2,
197
+ seq(
198
+ "filter",
199
+ field("name", $.identifier),
200
+ field("body", $.block),
201
+ optional(";"),
202
+ ),
203
+ ),
204
+ ),
205
+
206
+ function_declaration: ($) =>
207
+ choice(
208
+ prec(
209
+ 1,
210
+ seq(
211
+ "function",
212
+ optional($.parameter_list),
213
+ optional($.return_annotation),
214
+ field("body", $.block),
215
+ optional(";"),
216
+ ),
217
+ ),
218
+ prec(
219
+ 2,
220
+ seq(
221
+ "function",
222
+ field("name", $.identifier),
223
+ optional($.parameter_list),
224
+ optional($.return_annotation),
225
+ field("body", $.block),
226
+ optional(";"),
227
+ ),
228
+ ),
229
+ ),
230
+
231
+ parameter_list: ($) =>
232
+ seq(
233
+ "(",
234
+ optional(
235
+ commaSep1(
236
+ choice(
237
+ $.identifier,
238
+ $.string,
239
+ $.number,
240
+ $.ip_literal,
241
+ $.prefix_literal,
242
+ $.raw_token,
243
+ ),
244
+ ),
245
+ ),
246
+ ")",
247
+ ),
248
+
249
+ return_annotation: ($) =>
250
+ seq("->", repeat1(choice($.identifier, $.raw_token))),
251
+
252
+ top_level_statement: ($) =>
253
+ prec(
254
+ -1,
255
+ seq(
256
+ repeat1(
257
+ choice(
258
+ $.string,
259
+ $.number,
260
+ $.ip_literal,
261
+ $.prefix_literal,
262
+ $.identifier,
263
+ $.raw_token,
264
+ $.block,
265
+ ),
266
+ ),
267
+ ";",
268
+ ),
269
+ ),
270
+
271
+ // Block is intentionally permissive for error recovery (missing brace / incomplete header).
272
+ block: ($) => seq("{", repeat($._block_item), "}"),
273
+
274
+ _block_item: ($) =>
275
+ choice(
276
+ $.local_as_statement,
277
+ $.neighbor_statement,
278
+ $.channel_keep_filtered_statement,
279
+ $.channel_limit_statement,
280
+ $.import_statement,
281
+ $.export_statement,
282
+ $.channel_statement,
283
+ $.if_statement,
284
+ $.accept_statement,
285
+ $.reject_statement,
286
+ $.return_statement,
287
+ $.case_statement,
288
+ $.expression_statement,
289
+ $.block,
290
+ ";",
291
+ ),
292
+
293
+ // Protocol common statements used by linter rules.
294
+ local_as_statement: ($) =>
295
+ seq(
296
+ "local",
297
+ "as",
298
+ field("asn", choice($.number, $.identifier, $.raw_token)),
299
+ ";",
300
+ ),
301
+
302
+ neighbor_statement: ($) =>
303
+ seq(
304
+ "neighbor",
305
+ field(
306
+ "address",
307
+ choice($.ip_literal, $.number, $.identifier, $.string, $.raw_token),
308
+ ),
309
+ optional(
310
+ seq(
311
+ "%",
312
+ field("interface", choice($.identifier, $.string, $.raw_token)),
313
+ ),
314
+ ),
315
+ optional(
316
+ seq("as", field("asn", choice($.number, $.identifier, $.raw_token))),
317
+ ),
318
+ ";",
319
+ ),
320
+
321
+ channel_statement: ($) =>
322
+ choice(
323
+ prec(
324
+ 2,
325
+ seq(
326
+ field("channel_type", $.channel_type),
327
+ field("body", $.channel_block),
328
+ ";",
329
+ ),
330
+ ),
331
+ prec(
332
+ 1,
333
+ seq(
334
+ field("channel_type", $.channel_type),
335
+ field("body", $.channel_block),
336
+ ),
337
+ ),
338
+ seq(field("channel_type", $.channel_type), ";"),
339
+ ),
340
+
341
+ channel_type: () =>
342
+ choice(
343
+ "ipv4",
344
+ "ipv6",
345
+ "vpn4",
346
+ "vpn6",
347
+ "roa4",
348
+ "roa6",
349
+ "flow4",
350
+ "flow6",
351
+ "mpls",
352
+ ),
353
+
354
+ channel_block: ($) => seq("{", repeat($._channel_item), "}"),
355
+
356
+ _channel_item: ($) =>
357
+ choice(
358
+ $.channel_table_statement,
359
+ $.channel_keep_filtered_statement,
360
+ $.channel_limit_statement,
361
+ $.import_statement,
362
+ $.export_statement,
363
+ $.channel_debug_statement,
364
+ $.if_statement,
365
+ $.accept_statement,
366
+ $.reject_statement,
367
+ $.return_statement,
368
+ $.case_statement,
369
+ $.expression_statement,
370
+ $.block,
371
+ ";",
372
+ ),
373
+
374
+ channel_table_statement: ($) =>
375
+ seq("table", field("table_name", $.identifier), ";"),
376
+
377
+ channel_keep_filtered_statement: ($) =>
378
+ seq(
379
+ "import",
380
+ "keep",
381
+ "filtered",
382
+ field("switch_value", choice($.identifier, $.number, $.raw_token)),
383
+ ";",
384
+ ),
385
+
386
+ channel_limit_statement: ($) =>
387
+ seq(
388
+ field("direction", choice("import", "receive", "export")),
389
+ "limit",
390
+ field(
391
+ "limit_value",
392
+ choice("off", $.number, $.identifier, $.raw_token),
393
+ ),
394
+ optional(
395
+ seq(
396
+ "action",
397
+ field("limit_action", choice($.identifier, $.raw_token)),
398
+ ),
399
+ ),
400
+ ";",
401
+ ),
402
+
403
+ channel_debug_statement: ($) =>
404
+ seq(
405
+ "debug",
406
+ field(
407
+ "debug_clause",
408
+ repeat1(
409
+ choice(
410
+ $.identifier,
411
+ $.number,
412
+ $.raw_token,
413
+ $.string,
414
+ "all",
415
+ "off",
416
+ ",",
417
+ "{",
418
+ "}",
419
+ ),
420
+ ),
421
+ ),
422
+ ";",
423
+ ),
424
+
425
+ import_statement: ($) =>
426
+ choice(
427
+ seq(
428
+ "import",
429
+ field(
430
+ "clause",
431
+ choice(
432
+ $.all_clause,
433
+ $.none_clause,
434
+ $.where_clause,
435
+ $.filter_name_clause,
436
+ $.generic_clause,
437
+ ),
438
+ ),
439
+ ";",
440
+ ),
441
+ seq("import", field("clause", $.filter_block_clause)),
442
+ ),
443
+
444
+ export_statement: ($) =>
445
+ choice(
446
+ seq(
447
+ "export",
448
+ field(
449
+ "clause",
450
+ choice(
451
+ $.all_clause,
452
+ $.none_clause,
453
+ $.where_clause,
454
+ $.filter_name_clause,
455
+ $.generic_clause,
456
+ ),
457
+ ),
458
+ ";",
459
+ ),
460
+ seq("export", field("clause", $.filter_block_clause)),
461
+ ),
462
+
463
+ all_clause: () => "all",
464
+
465
+ none_clause: () => "none",
466
+
467
+ where_clause: ($) =>
468
+ seq("where", field("where_expression", $.simple_expression)),
469
+
470
+ filter_name_clause: ($) =>
471
+ seq("filter", field("filter_name", $.identifier)),
472
+
473
+ filter_block_clause: ($) => seq("filter", field("filter_block", $.block)),
474
+
475
+ generic_clause: ($) =>
476
+ repeat1(
477
+ choice(
478
+ $.string,
479
+ $.number,
480
+ $.ip_literal,
481
+ $.prefix_literal,
482
+ $.identifier,
483
+ $.raw_token,
484
+ $.block,
485
+ ),
486
+ ),
487
+
488
+ if_statement: ($) =>
489
+ seq(
490
+ "if",
491
+ optional(field("condition", $.simple_expression)),
492
+ "then",
493
+ field("consequence", $.inline_statement),
494
+ optional(seq("else", field("alternative", $.inline_statement))),
495
+ ),
496
+
497
+ inline_statement: ($) =>
498
+ choice(
499
+ $.block,
500
+ $.accept_statement,
501
+ $.reject_statement,
502
+ $.return_statement,
503
+ $.expression_statement,
504
+ ),
505
+
506
+ accept_statement: () => seq("accept", ";"),
507
+
508
+ reject_statement: () => seq("reject", ";"),
509
+
510
+ return_statement: ($) =>
511
+ seq("return", optional(field("value", $.simple_expression)), ";"),
512
+
513
+ case_statement: ($) =>
514
+ seq(
515
+ "case",
516
+ optional(field("subject", $.simple_expression)),
517
+ field("body", $.block),
518
+ ),
519
+
520
+ expression_statement: ($) =>
521
+ seq(field("expression", $.simple_expression), ";"),
522
+
523
+ simple_expression: ($) =>
524
+ choice($.binary_expression, $.unary_expression, $.expression_atom),
525
+
526
+ binary_expression: ($) =>
527
+ prec.left(
528
+ seq(
529
+ field("left", $.expression_atom),
530
+ field(
531
+ "operator",
532
+ choice("~", "=", "!=", "<", ">", "<=", ">=", "&&", "||"),
533
+ ),
534
+ field("right", $.expression_atom),
535
+ ),
536
+ ),
537
+
538
+ unary_expression: ($) =>
539
+ prec(2, seq(choice("!", "-"), field("value", $.expression_atom))),
540
+
541
+ expression_atom: ($) =>
542
+ choice(
543
+ $.function_call,
544
+ $.member_expression,
545
+ $.set_literal,
546
+ $.literal_expression,
547
+ $.identifier,
548
+ $.raw_token,
549
+ seq("(", $.simple_expression, ")"),
550
+ ),
551
+
552
+ function_call: ($) =>
553
+ seq(
554
+ field("name", $.identifier),
555
+ "(",
556
+ optional(commaSep1($.simple_expression)),
557
+ ")",
558
+ ),
559
+
560
+ member_expression: ($) =>
561
+ seq(
562
+ field("object", $.identifier),
563
+ repeat1(seq(".", field("member", $.identifier))),
564
+ ),
565
+
566
+ set_literal: ($) =>
567
+ seq(
568
+ "[",
569
+ repeat(
570
+ choice(
571
+ $.string,
572
+ $.number,
573
+ $.ip_literal,
574
+ $.prefix_literal,
575
+ $.identifier,
576
+ $.raw_token,
577
+ ",",
578
+ "..",
579
+ "+",
580
+ "-",
581
+ "{",
582
+ "}",
583
+ "*",
584
+ "=",
585
+ ),
586
+ ),
587
+ "]",
588
+ ),
589
+
590
+ literal_expression: ($) =>
591
+ choice(
592
+ $.string,
593
+ $.number,
594
+ $.ip_literal,
595
+ $.prefix_literal,
596
+ $.bool_literal,
597
+ ),
598
+
599
+ bool_literal: () => choice("true", "false"),
600
+ },
601
+ });
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@birdcc/parser",
3
+ "version": "0.0.1-alpha.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "license": "GPL-3.0-only",
7
+ "author": {
8
+ "name": "BIRD Chinese Community",
9
+ "email": "npm-dev@birdcc.link",
10
+ "url": "https://github.com/bird-chinese-community/"
11
+ },
12
+ "main": "./dist/index.js",
13
+ "types": "./src/index.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./src/index.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "devDependencies": {
21
+ "tree-sitter-cli": "0.25.10"
22
+ },
23
+ "dependencies": {
24
+ "web-tree-sitter": "^0.26.6"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/bird-chinese-community/BIRD-LSP",
29
+ "directory": "packages/@birdcc/parser"
30
+ },
31
+ "homepage": "https://github.com/bird-chinese-community/BIRD-LSP/blob/main/packages/%40birdcc/parser/README.md",
32
+ "bugs": {
33
+ "url": "https://github.com/bird-chinese-community/BIRD-LSP/issues"
34
+ },
35
+ "scripts": {
36
+ "build:grammar": "tree-sitter generate && oxfmt . && node scripts/sync-wasm-paths.mjs",
37
+ "build:wasm": "tree-sitter build --wasm && node scripts/sync-wasm-paths.mjs",
38
+ "build": "tsc -p tsconfig.json && node scripts/sync-wasm-paths.mjs",
39
+ "ci:parser": "pnpm run build:grammar && pnpm run lint && pnpm run test && pnpm run build && pnpm run typecheck && pnpm run format",
40
+ "typecheck": "tsc -p tsconfig.json --noEmit",
41
+ "lint": "oxlint --deny-warnings src test",
42
+ "test": "vitest run",
43
+ "format": "oxfmt .",
44
+ "coverage": "vitest run --coverage --coverage.provider=v8 --coverage.reporter=text-summary --coverage.reporter=json-summary"
45
+ }
46
+ }
@@ -0,0 +1,21 @@
1
+ import { copyFileSync, existsSync, mkdirSync, renameSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
6
+ const pkgDir = dirname(scriptDir);
7
+
8
+ const rootWasm = join(pkgDir, "tree-sitter-birdcc.wasm");
9
+ const srcWasm = join(pkgDir, "src", "tree-sitter-birdcc.wasm");
10
+ const distWasm = join(pkgDir, "dist", "tree-sitter-birdcc.wasm");
11
+
12
+ if (existsSync(rootWasm)) {
13
+ renameSync(rootWasm, srcWasm);
14
+ }
15
+
16
+ if (!existsSync(srcWasm)) {
17
+ throw new Error(`Missing WASM grammar file: ${srcWasm}`);
18
+ }
19
+
20
+ mkdirSync(dirname(distWasm), { recursive: true });
21
+ copyFileSync(srcWasm, distWasm);