as-test 1.0.1 → 1.0.3

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/bin/util.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { existsSync, readFileSync } from "fs";
2
- import { BuildOptions, Config, CoverageOptions, ModeConfig, ReporterConfig, RunOptions, Runtime, } from "./types.js";
2
+ import { BuildOptions, Config, CoverageOptions, FuzzConfig, ModeConfig, ReporterConfig, RunOptions, Runtime, } from "./types.js";
3
3
  import chalk from "chalk";
4
4
  import { createRequire } from "module";
5
- import { delimiter, dirname, join } from "path";
5
+ import { delimiter, dirname, join, resolve } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  export function formatTime(ms) {
8
8
  if (ms < 0) {
@@ -48,9 +48,10 @@ export function loadConfig(CONFIG_PATH, warn = false) {
48
48
  }
49
49
  const raw = parsed;
50
50
  validateConfig(raw, CONFIG_PATH);
51
+ const configDir = dirname(CONFIG_PATH);
51
52
  const config = Object.assign(new Config(), raw);
52
53
  applyOutputConfig(raw.output, raw, config);
53
- config.env = parseEnvMap(raw.env);
54
+ config.env = parseEnvValue(raw.env, configDir, "$.env");
54
55
  const runOptionsRaw = raw.runOptions ?? {};
55
56
  config.buildOptions = Object.assign(new BuildOptions(), raw.buildOptions ?? {});
56
57
  config.buildOptions.cmd =
@@ -58,6 +59,7 @@ export function loadConfig(CONFIG_PATH, warn = false) {
58
59
  config.buildOptions.args = Array.isArray(config.buildOptions.args)
59
60
  ? config.buildOptions.args.filter((item) => typeof item == "string")
60
61
  : [];
62
+ config.buildOptions.env = parseEnvValue(raw.buildOptions?.env, configDir, "$.buildOptions.env");
61
63
  config.buildOptions.target =
62
64
  typeof config.buildOptions.target == "string" &&
63
65
  config.buildOptions.target.length
@@ -99,8 +101,35 @@ export function loadConfig(CONFIG_PATH, warn = false) {
99
101
  ? legacyRun
100
102
  : runtime.cmd;
101
103
  runtime.cmd = cmd;
104
+ runtime.browser =
105
+ runtimeRaw && typeof runtimeRaw.browser == "string"
106
+ ? runtimeRaw.browser
107
+ : "";
102
108
  config.runOptions.runtime = runtime;
103
- config.modes = parseModes(raw.modes);
109
+ config.runOptions.env = parseEnvValue(runOptionsRaw.env, configDir, "$.runOptions.env");
110
+ const fuzzRaw = raw.fuzz ?? {};
111
+ config.fuzz = Object.assign(new FuzzConfig(), fuzzRaw);
112
+ config.fuzz.input = Array.isArray(config.fuzz.input)
113
+ ? config.fuzz.input.filter((item) => typeof item == "string")
114
+ : typeof fuzzRaw.input == "string"
115
+ ? [fuzzRaw.input]
116
+ : new FuzzConfig().input;
117
+ config.fuzz.runs = normalizePositiveNumber(config.fuzz.runs, 1000);
118
+ config.fuzz.seed = normalizeNonNegativeNumber(config.fuzz.seed, 1337);
119
+ config.fuzz.maxInputBytes = normalizePositiveNumber(config.fuzz.maxInputBytes, 4096);
120
+ config.fuzz.target =
121
+ typeof config.fuzz.target == "string" && config.fuzz.target.length
122
+ ? config.fuzz.target
123
+ : "bindings";
124
+ config.fuzz.corpusDir =
125
+ typeof config.fuzz.corpusDir == "string" && config.fuzz.corpusDir.length
126
+ ? config.fuzz.corpusDir
127
+ : "./.as-test/fuzz/corpus";
128
+ config.fuzz.crashDir =
129
+ typeof config.fuzz.crashDir == "string" && config.fuzz.crashDir.length
130
+ ? config.fuzz.crashDir
131
+ : "./.as-test/crashes";
132
+ config.modes = parseModes(raw.modes, configDir);
104
133
  return config;
105
134
  }
106
135
  }
@@ -116,14 +145,24 @@ const TOP_LEVEL_KEYS = new Set([
116
145
  "coverage",
117
146
  "env",
118
147
  "buildOptions",
148
+ "fuzz",
119
149
  "modes",
120
150
  "runOptions",
121
151
  ]);
122
- const BUILD_OPTION_KEYS = new Set(["cmd", "args", "target"]);
123
- const RUN_OPTION_KEYS = new Set(["runtime", "reporter", "run"]); // includes legacy "run"
124
- const RUNTIME_OPTION_KEYS = new Set(["cmd", "run"]); // includes legacy "run"
152
+ const BUILD_OPTION_KEYS = new Set(["cmd", "args", "target", "env"]);
153
+ const RUN_OPTION_KEYS = new Set(["runtime", "reporter", "run", "env"]); // includes legacy "run"
154
+ const RUNTIME_OPTION_KEYS = new Set(["cmd", "run", "browser"]); // includes legacy "run"
125
155
  const REPORTER_OPTION_KEYS = new Set(["name", "options", "outDir", "outFile"]);
126
156
  const OUTPUT_OPTION_KEYS = new Set(["build", "logs", "coverage", "snapshots"]);
157
+ const FUZZ_OPTION_KEYS = new Set([
158
+ "input",
159
+ "runs",
160
+ "seed",
161
+ "maxInputBytes",
162
+ "target",
163
+ "corpusDir",
164
+ "crashDir",
165
+ ]);
127
166
  const MODE_KEYS = new Set([
128
167
  "outDir",
129
168
  "logs",
@@ -149,6 +188,7 @@ function validateConfig(raw, configPath) {
149
188
  validateCoverageField(raw, "coverage", "$", issues);
150
189
  validateEnvField(raw, "env", "$", issues);
151
190
  validateBuildOptionsField(raw, "buildOptions", "$", issues);
191
+ validateFuzzField(raw, "fuzz", "$", issues);
152
192
  validateRunOptionsField(raw, "runOptions", "$", issues);
153
193
  validateModesField(raw, "modes", "$", issues);
154
194
  if (!issues.length)
@@ -279,7 +319,7 @@ function validateCoverageValue(value, path, issues) {
279
319
  issues.push({
280
320
  path,
281
321
  message: "must be a boolean or object",
282
- fix: 'use true/false or { "enabled": true, "includeSpecs": false }',
322
+ fix: 'use true/false or { "enabled": true, "includeSpecs": false, "include": ["assembly/**/*.ts"], "exclude": ["assembly/__tests__/**/*.spec.ts"] }',
283
323
  });
284
324
  return;
285
325
  }
@@ -298,16 +338,72 @@ function validateCoverageValue(value, path, issues) {
298
338
  fix: "set to true or false",
299
339
  });
300
340
  }
341
+ validateStringArrayField(obj, "include", path, issues);
342
+ validateStringArrayField(obj, "exclude", path, issues);
343
+ }
344
+ function validateStringArrayField(raw, key, pathPrefix, issues) {
345
+ if (!(key in raw) || raw[key] == undefined)
346
+ return;
347
+ const value = raw[key];
348
+ if (!Array.isArray(value)) {
349
+ issues.push({
350
+ path: `${pathPrefix}.${key}`,
351
+ message: "must be an array of strings",
352
+ fix: `set "${key}" to an array of glob patterns`,
353
+ });
354
+ return;
355
+ }
356
+ for (let i = 0; i < value.length; i++) {
357
+ if (typeof value[i] == "string" && value[i].length)
358
+ continue;
359
+ issues.push({
360
+ path: `${pathPrefix}.${key}[${i}]`,
361
+ message: "must be a non-empty string",
362
+ fix: "remove invalid entries or replace them with valid glob strings",
363
+ });
364
+ }
301
365
  }
302
366
  function validateEnvField(raw, key, pathPrefix, issues) {
303
367
  if (!(key in raw) || raw[key] == undefined)
304
368
  return;
305
369
  const value = raw[key];
306
- if (!value || typeof value != "object" || Array.isArray(value)) {
370
+ if (typeof value == "string") {
371
+ if (!value.length) {
372
+ issues.push({
373
+ path: `${pathPrefix}.${key}`,
374
+ message: "must not be an empty string",
375
+ fix: 'use a .env file path like "./secrets/.env"',
376
+ });
377
+ }
378
+ return;
379
+ }
380
+ if (Array.isArray(value)) {
381
+ for (let i = 0; i < value.length; i++) {
382
+ const item = value[i];
383
+ if (typeof item != "string" || !item.length) {
384
+ issues.push({
385
+ path: `${pathPrefix}.${key}[${i}]`,
386
+ message: "must be a non-empty string",
387
+ fix: 'use entries like "FOO=1"',
388
+ });
389
+ continue;
390
+ }
391
+ const separator = item.indexOf("=");
392
+ if (separator <= 0) {
393
+ issues.push({
394
+ path: `${pathPrefix}.${key}[${i}]`,
395
+ message: 'must use "KEY=value" format',
396
+ fix: 'example: "FOO=1"',
397
+ });
398
+ }
399
+ }
400
+ return;
401
+ }
402
+ if (!value || typeof value != "object") {
307
403
  issues.push({
308
404
  path: `${pathPrefix}.${key}`,
309
- message: "must be an object of string values",
310
- fix: 'example: "env": { "MY_FLAG": "1" }',
405
+ message: "must be a .env file path, array of KEY=value strings, or object of string values",
406
+ fix: 'example: "env": "./secrets/.env" or ["MY_FLAG=1"] or { "MY_FLAG": "1" }',
311
407
  });
312
408
  return;
313
409
  }
@@ -354,17 +450,20 @@ function validateBuildOptionsField(raw, key, pathPrefix, issues) {
354
450
  issues.push({
355
451
  path: `${pathPrefix}.${key}.target`,
356
452
  message: "must be a string",
357
- fix: 'set to "wasi" or "bindings"',
453
+ fix: 'set to "wasi", "bindings", or "web"',
358
454
  });
359
455
  }
360
- else if (obj.target != "wasi" && obj.target != "bindings") {
456
+ else if (obj.target != "wasi" &&
457
+ obj.target != "bindings" &&
458
+ obj.target != "web") {
361
459
  issues.push({
362
460
  path: `${pathPrefix}.${key}.target`,
363
- message: `must be "wasi" or "bindings"`,
461
+ message: `must be "wasi", "bindings", or "web"`,
364
462
  fix: `received "${obj.target}"`,
365
463
  });
366
464
  }
367
465
  }
466
+ validateEnvField(obj, "env", `${pathPrefix}.${key}`, issues);
368
467
  }
369
468
  function validateRunOptionsField(raw, key, pathPrefix, issues) {
370
469
  if (!(key in raw) || raw[key] == undefined)
@@ -413,6 +512,13 @@ function validateRunOptionsField(raw, key, pathPrefix, issues) {
413
512
  fix: 'legacy "run" should be a command string',
414
513
  });
415
514
  }
515
+ if ("browser" in runtimeObj && typeof runtimeObj.browser != "string") {
516
+ issues.push({
517
+ path: `${pathPrefix}.${key}.runtime.browser`,
518
+ message: "must be a string",
519
+ fix: 'set to "chrome", "chromium", "firefox", "webkit", or an executable path',
520
+ });
521
+ }
416
522
  }
417
523
  }
418
524
  if ("reporter" in obj && obj.reporter != undefined) {
@@ -463,6 +569,36 @@ function validateRunOptionsField(raw, key, pathPrefix, issues) {
463
569
  });
464
570
  }
465
571
  }
572
+ validateEnvField(obj, "env", `${pathPrefix}.${key}`, issues);
573
+ }
574
+ function validateFuzzField(raw, key, pathPrefix, issues) {
575
+ if (!(key in raw) || raw[key] == undefined)
576
+ return;
577
+ const value = raw[key];
578
+ if (!value || typeof value != "object" || Array.isArray(value)) {
579
+ issues.push({
580
+ path: `${pathPrefix}.${key}`,
581
+ message: "must be an object",
582
+ fix: 'example: "fuzz": { "input": ["./assembly/__fuzz__/*.fuzz.ts"], "runs": 1000 }',
583
+ });
584
+ return;
585
+ }
586
+ const obj = value;
587
+ validateUnknownKeys(obj, FUZZ_OPTION_KEYS, `${pathPrefix}.${key}`, issues);
588
+ validateInputField(obj, "input", `${pathPrefix}.${key}`, issues);
589
+ validateStringField(obj, "target", `${pathPrefix}.${key}`, issues);
590
+ validateStringField(obj, "corpusDir", `${pathPrefix}.${key}`, issues);
591
+ validateStringField(obj, "crashDir", `${pathPrefix}.${key}`, issues);
592
+ validateNumberField(obj, "runs", `${pathPrefix}.${key}`, issues, true);
593
+ validateNumberField(obj, "seed", `${pathPrefix}.${key}`, issues, false);
594
+ validateNumberField(obj, "maxInputBytes", `${pathPrefix}.${key}`, issues, true);
595
+ if ("target" in obj && obj.target != "bindings") {
596
+ issues.push({
597
+ path: `${pathPrefix}.${key}.target`,
598
+ message: 'must be "bindings"',
599
+ fix: 'set to "bindings"',
600
+ });
601
+ }
466
602
  }
467
603
  function validateModesField(raw, key, pathPrefix, issues) {
468
604
  if (!(key in raw) || raw[key] == undefined)
@@ -501,6 +637,33 @@ function validateModesField(raw, key, pathPrefix, issues) {
501
637
  function isStringArray(value) {
502
638
  return Array.isArray(value) && value.every((item) => typeof item == "string");
503
639
  }
640
+ function validateNumberField(raw, key, pathPrefix, issues, positiveOnly) {
641
+ if (!(key in raw) || raw[key] == undefined)
642
+ return;
643
+ if (typeof raw[key] != "number" || !Number.isFinite(raw[key])) {
644
+ issues.push({
645
+ path: `${pathPrefix}.${key}`,
646
+ message: "must be a finite number",
647
+ fix: `set "${key}" to a numeric value`,
648
+ });
649
+ return;
650
+ }
651
+ if (positiveOnly && Number(raw[key]) <= 0) {
652
+ issues.push({
653
+ path: `${pathPrefix}.${key}`,
654
+ message: "must be greater than zero",
655
+ fix: `set "${key}" to a positive integer`,
656
+ });
657
+ return;
658
+ }
659
+ if (!positiveOnly && Number(raw[key]) < 0) {
660
+ issues.push({
661
+ path: `${pathPrefix}.${key}`,
662
+ message: "must be zero or greater",
663
+ fix: `set "${key}" to a non-negative integer`,
664
+ });
665
+ }
666
+ }
504
667
  function resolveClosestKey(value, keys) {
505
668
  let best = null;
506
669
  let bestDistance = Number.POSITIVE_INFINITY;
@@ -589,7 +752,7 @@ function applyOutputRoot(root, rawConfig, config) {
589
752
  config.snapshotDir = join(root, "snapshots");
590
753
  }
591
754
  }
592
- function parseModes(raw) {
755
+ function parseModes(raw, configDir) {
593
756
  if (!raw || typeof raw != "object" || Array.isArray(raw))
594
757
  return {};
595
758
  const out = {};
@@ -629,6 +792,7 @@ function parseModes(raw) {
629
792
  if (Array.isArray(buildRaw.args)) {
630
793
  build.args = buildRaw.args.filter((item) => typeof item == "string");
631
794
  }
795
+ build.env = parseEnvValue(buildRaw.env, configDir, `$.modes.${name}.buildOptions.env`);
632
796
  if (typeof buildRaw.target == "string" && buildRaw.target.length) {
633
797
  build.target = buildRaw.target;
634
798
  }
@@ -649,6 +813,8 @@ function parseModes(raw) {
649
813
  else {
650
814
  runtime.cmd = "";
651
815
  }
816
+ runtime.browser =
817
+ typeof runtimeRaw.browser == "string" ? runtimeRaw.browser : "";
652
818
  run.runtime = runtime;
653
819
  }
654
820
  if (typeof runRaw.reporter == "string") {
@@ -666,16 +832,10 @@ function parseModes(raw) {
666
832
  typeof reporter.outFile == "string" ? reporter.outFile : "";
667
833
  run.reporter = reporter;
668
834
  }
835
+ run.env = parseEnvValue(runRaw.env, configDir, `$.modes.${name}.runOptions.env`);
669
836
  mode.runOptions = run;
670
837
  }
671
- if (modeRaw.env && typeof modeRaw.env == "object") {
672
- const env = {};
673
- for (const [key, val] of Object.entries(modeRaw.env)) {
674
- if (typeof val == "string")
675
- env[key] = val;
676
- }
677
- mode.env = env;
678
- }
838
+ mode.env = parseEnvValue(modeRaw.env, configDir, `$.modes.${name}.env`);
679
839
  out[name] = mode;
680
840
  }
681
841
  return out;
@@ -690,6 +850,87 @@ function parseEnvMap(raw) {
690
850
  }
691
851
  return env;
692
852
  }
853
+ function parseEnvValue(raw, configDir, pathLabel) {
854
+ if (raw == undefined)
855
+ return {};
856
+ if (typeof raw == "string") {
857
+ return parseEnvFile(resolve(configDir, raw), pathLabel);
858
+ }
859
+ if (Array.isArray(raw)) {
860
+ return parseInlineEnvEntries(raw, pathLabel);
861
+ }
862
+ return parseEnvMap(raw);
863
+ }
864
+ function parseInlineEnvEntries(values, pathLabel) {
865
+ const env = {};
866
+ for (let i = 0; i < values.length; i++) {
867
+ const item = values[i];
868
+ if (typeof item != "string")
869
+ continue;
870
+ const separator = item.indexOf("=");
871
+ if (separator <= 0) {
872
+ throw new Error(`invalid config at ${pathLabel}\nenv entry at index ${i} must use KEY=value format`);
873
+ }
874
+ const key = item.slice(0, separator).trim();
875
+ const value = item.slice(separator + 1);
876
+ if (!key.length) {
877
+ throw new Error(`invalid config at ${pathLabel}\nenv entry at index ${i} must use a non-empty key`);
878
+ }
879
+ env[key] = value;
880
+ }
881
+ return env;
882
+ }
883
+ function parseEnvFile(envPath, pathLabel) {
884
+ if (!existsSync(envPath)) {
885
+ throw new Error(`invalid config at ${pathLabel}\nenv file not found: ${envPath}`);
886
+ }
887
+ const env = {};
888
+ const lines = readFileSync(envPath, "utf8").split(/\r?\n/);
889
+ for (let i = 0; i < lines.length; i++) {
890
+ const rawLine = lines[i];
891
+ const line = rawLine.trim();
892
+ if (!line.length || line.startsWith("#"))
893
+ continue;
894
+ const normalized = line.startsWith("export ") ? line.slice(7).trim() : line;
895
+ const separator = normalized.indexOf("=");
896
+ if (separator <= 0) {
897
+ throw new Error(`invalid config at ${pathLabel}\ninvalid env line ${i + 1} in ${envPath}: expected KEY=value`);
898
+ }
899
+ const key = normalized.slice(0, separator).trim();
900
+ const value = normalized.slice(separator + 1).trim();
901
+ env[key] = unquoteEnvValue(value);
902
+ }
903
+ return env;
904
+ }
905
+ function unquoteEnvValue(value) {
906
+ if (value.length < 2)
907
+ return value;
908
+ const quote = value[0];
909
+ if ((quote != '"' && quote != "'") || value[value.length - 1] != quote) {
910
+ return value;
911
+ }
912
+ const inner = value.slice(1, -1);
913
+ if (quote == "'")
914
+ return inner;
915
+ return inner
916
+ .replace(/\\n/g, "\n")
917
+ .replace(/\\r/g, "\r")
918
+ .replace(/\\t/g, "\t")
919
+ .replace(/\\"/g, '"')
920
+ .replace(/\\\\/g, "\\");
921
+ }
922
+ function normalizePositiveNumber(value, fallback) {
923
+ if (typeof value != "number" || !Number.isFinite(value) || value <= 0) {
924
+ return fallback;
925
+ }
926
+ return Math.floor(value);
927
+ }
928
+ function normalizeNonNegativeNumber(value, fallback) {
929
+ if (typeof value != "number" || !Number.isFinite(value) || value < 0) {
930
+ return fallback;
931
+ }
932
+ return Math.floor(value);
933
+ }
693
934
  export function resolveModeNames(rawArgs) {
694
935
  const names = [];
695
936
  for (let i = 0; i < rawArgs.length; i++) {
@@ -718,12 +959,32 @@ function appendModeTokens(out, value) {
718
959
  }
719
960
  export function applyMode(config, modeName) {
720
961
  if (!modeName) {
962
+ const merged = Object.assign(new Config(), config);
963
+ merged.buildOptions = Object.assign(new BuildOptions(), config.buildOptions);
964
+ merged.runOptions = Object.assign(new RunOptions(), config.runOptions);
965
+ merged.runOptions.runtime = Object.assign(new Runtime(), config.runOptions.runtime);
966
+ merged.buildOptions.env = { ...config.buildOptions.env };
967
+ merged.runOptions.env = { ...config.runOptions.env };
968
+ merged.outDir = appendPathSegment(config.outDir, "default");
969
+ if (config.logs != "none") {
970
+ merged.logs = appendPathSegment(config.logs, "default");
971
+ }
972
+ if (config.coverageDir != "none") {
973
+ merged.coverageDir = appendPathSegment(config.coverageDir, "default");
974
+ }
975
+ merged.fuzz = Object.assign(new FuzzConfig(), config.fuzz);
976
+ merged.fuzz.crashDir = appendPathSegment(config.fuzz.crashDir, "default");
977
+ merged.fuzz.corpusDir = appendPathSegment(config.fuzz.corpusDir, "default");
978
+ const env = {
979
+ ...process.env,
980
+ ...config.env,
981
+ };
982
+ if (merged.runOptions.runtime.browser.length) {
983
+ env.BROWSER = merged.runOptions.runtime.browser;
984
+ }
721
985
  return {
722
- config,
723
- env: {
724
- ...process.env,
725
- ...config.env,
726
- },
986
+ config: merged,
987
+ env,
727
988
  };
728
989
  }
729
990
  const mode = config.modes[modeName];
@@ -736,6 +997,8 @@ export function applyMode(config, modeName) {
736
997
  merged.buildOptions = Object.assign(new BuildOptions(), config.buildOptions);
737
998
  merged.runOptions = Object.assign(new RunOptions(), config.runOptions);
738
999
  merged.runOptions.runtime = Object.assign(new Runtime(), config.runOptions.runtime);
1000
+ merged.buildOptions.env = { ...config.buildOptions.env };
1001
+ merged.runOptions.env = { ...config.runOptions.env };
739
1002
  if (mode.outDir)
740
1003
  merged.outDir = mode.outDir;
741
1004
  else
@@ -764,19 +1027,38 @@ export function applyMode(config, modeName) {
764
1027
  ...mode.buildOptions.args,
765
1028
  ];
766
1029
  }
1030
+ if (mode.buildOptions.env) {
1031
+ merged.buildOptions.env = {
1032
+ ...merged.buildOptions.env,
1033
+ ...mode.buildOptions.env,
1034
+ };
1035
+ }
767
1036
  if (mode.runOptions.runtime?.cmd) {
768
1037
  merged.runOptions.runtime.cmd = mode.runOptions.runtime.cmd;
769
1038
  }
1039
+ if (mode.runOptions.runtime?.browser != undefined) {
1040
+ merged.runOptions.runtime.browser = mode.runOptions.runtime.browser;
1041
+ }
770
1042
  if (mode.runOptions.reporter != undefined) {
771
1043
  merged.runOptions.reporter = mode.runOptions.reporter;
772
1044
  }
1045
+ if (mode.runOptions.env) {
1046
+ merged.runOptions.env = {
1047
+ ...merged.runOptions.env,
1048
+ ...mode.runOptions.env,
1049
+ };
1050
+ }
1051
+ const env = {
1052
+ ...process.env,
1053
+ ...config.env,
1054
+ ...mode.env,
1055
+ };
1056
+ if (merged.runOptions.runtime.browser.length) {
1057
+ env.BROWSER = merged.runOptions.runtime.browser;
1058
+ }
773
1059
  return {
774
1060
  config: merged,
775
- env: {
776
- ...process.env,
777
- ...config.env,
778
- ...mode.env,
779
- },
1061
+ env,
780
1062
  modeName,
781
1063
  };
782
1064
  }
package/bin/wipc.js ADDED
@@ -0,0 +1,79 @@
1
+ export var MessageType;
2
+ (function (MessageType) {
3
+ MessageType[MessageType["OPEN"] = 0] = "OPEN";
4
+ MessageType[MessageType["CLOSE"] = 1] = "CLOSE";
5
+ MessageType[MessageType["CALL"] = 2] = "CALL";
6
+ MessageType[MessageType["DATA"] = 3] = "DATA";
7
+ })(MessageType || (MessageType = {}));
8
+ export class Channel {
9
+ constructor(input = process.stdin, output = process.stdout) {
10
+ this.input = input;
11
+ this.output = output;
12
+ this.buffer = Buffer.alloc(0);
13
+ this.input.on("data", (chunk) => this.onData(chunk));
14
+ }
15
+ send(type, payload) {
16
+ const body = payload ?? Buffer.alloc(0);
17
+ const header = Buffer.alloc(Channel.HEADER_SIZE);
18
+ Channel.MAGIC.copy(header, 0);
19
+ header.writeUInt8(type, 4);
20
+ header.writeUInt32LE(body.length, 5);
21
+ this.output.write(Buffer.concat([header, body]));
22
+ }
23
+ sendJSON(type, msg) {
24
+ const json = Buffer.from(JSON.stringify(msg), "utf8");
25
+ this.send(type, json);
26
+ }
27
+ onData(chunk) {
28
+ this.buffer = Buffer.concat([this.buffer, chunk]);
29
+ while (true) {
30
+ if (this.buffer.length === 0)
31
+ return;
32
+ const idx = this.buffer.indexOf(Channel.MAGIC);
33
+ if (idx === -1) {
34
+ this.onPassthrough(this.buffer);
35
+ this.buffer = Buffer.alloc(0);
36
+ return;
37
+ }
38
+ if (idx > 0) {
39
+ this.onPassthrough(this.buffer.subarray(0, idx));
40
+ this.buffer = this.buffer.subarray(idx);
41
+ }
42
+ if (this.buffer.length < Channel.HEADER_SIZE)
43
+ return;
44
+ const type = this.buffer.readUInt8(4);
45
+ const length = this.buffer.readUInt32LE(5);
46
+ const frameSize = Channel.HEADER_SIZE + length;
47
+ if (this.buffer.length < frameSize)
48
+ return;
49
+ const payload = this.buffer.subarray(Channel.HEADER_SIZE, frameSize);
50
+ this.buffer = this.buffer.subarray(frameSize);
51
+ this.handleFrame(type, payload);
52
+ }
53
+ }
54
+ handleFrame(type, payload) {
55
+ switch (type) {
56
+ case MessageType.OPEN:
57
+ this.onOpen();
58
+ break;
59
+ case MessageType.CLOSE:
60
+ this.onClose();
61
+ break;
62
+ case MessageType.CALL:
63
+ this.onCall(JSON.parse(payload.toString("utf8")));
64
+ break;
65
+ case MessageType.DATA:
66
+ this.onDataMessage(payload);
67
+ break;
68
+ default:
69
+ throw new Error(`Unknown frame type: ${type}`);
70
+ }
71
+ }
72
+ onPassthrough(_data) { }
73
+ onOpen() { }
74
+ onClose() { }
75
+ onCall(_msg) { }
76
+ onDataMessage(_data) { }
77
+ }
78
+ Channel.MAGIC = Buffer.from("WIPC");
79
+ Channel.HEADER_SIZE = 9;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "as-test",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "author": "Jairus Tanaka",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,7 +11,8 @@
11
11
  "as-console": "^7.0.0",
12
12
  "chalk": "^5.6.2",
13
13
  "glob": "^13.0.3",
14
- "typer-diff": "^1.1.1"
14
+ "typer-diff": "^1.1.1",
15
+ "wipc-js": "^0.1.1"
15
16
  },
16
17
  "devDependencies": {
17
18
  "@assemblyscript/wasi-shim": "^0.1.0",
@@ -20,11 +21,12 @@
20
21
  "as-sleep": "^0.0.2",
21
22
  "as-test": "./",
22
23
  "assemblyscript": "^0.28.9",
23
- "assemblyscript-prettier": "^3.0.1",
24
- "prettier": "3.6.2",
24
+ "assemblyscript-prettier": "^3.0.4",
25
+ "prettier": "3.8.1",
25
26
  "try-as": "^0.2.5",
26
27
  "typescript": "^5.9.3",
27
- "typescript-eslint": "^8.55.0"
28
+ "typescript-eslint": "^8.55.0",
29
+ "vitepress": "^1.6.4"
28
30
  },
29
31
  "bin": {
30
32
  "as-test": "./bin/index.js",
@@ -49,7 +51,7 @@
49
51
  "as-test.config.schema.json",
50
52
  "index.ts"
51
53
  ],
52
- "homepage": "https://github.com/JairusSW/as-test#readme",
54
+ "homepage": "https://docs.jairus.dev/as-test",
53
55
  "keywords": [
54
56
  "assemblyscript",
55
57
  "testing",
@@ -63,16 +65,24 @@
63
65
  },
64
66
  "scripts": {
65
67
  "test": "node ./bin/index.js test",
68
+ "fuzz": "node ./bin/index.js fuzz",
69
+ "test:ci": "node ./bin/index.js test --tap --config ./as-test.ci.config.json",
66
70
  "test:examples": "npm --prefix ./examples run test",
71
+ "ci:act": "bash ./tools/act.sh push",
72
+ "ci:act:pr": "bash ./tools/act.sh pull_request",
73
+ "ci:act:tests": "act push -W .github/workflows/as-test.yml",
74
+ "ci:act:examples": "act push -W .github/workflows/examples.yml",
67
75
  "typecheck": "tsc -p cli --noEmit && tsc -p transform --noEmit",
68
76
  "lint": "eslint transform/src/**/*.ts tools/**/*.js eslint.config.js",
69
77
  "build:transform": "tsc -p ./transform",
70
78
  "build:cli": "tsc -p cli",
71
79
  "build:run": "npm run build:cli",
72
- "prettier": "prettier -w .",
80
+ "docs:dev": "vitepress dev docs",
81
+ "docs:build": "vitepress build docs",
82
+ "docs:preview": "vitepress preview docs",
83
+ "format": "prettier -w .",
73
84
  "release:check": "npm run build:cli && npm run build:transform && npm run test && npm run test:examples && npm pack --dry-run --cache /tmp/as-test-npm-cache",
74
85
  "prepublishOnly": "npm run build:cli && npm run build:transform && npm run test"
75
86
  },
76
- "type": "module",
77
- "types": "assembly/index.ts"
87
+ "type": "module"
78
88
  }
@@ -15,8 +15,7 @@ const COVERAGE_IGNORED_CALLS = new Set([
15
15
  "unmockFn",
16
16
  "mockImport",
17
17
  "unmockImport",
18
- "snapshotImport",
19
- "restoreImport",
18
+ "snapshotFn",
20
19
  ]);
21
20
  const COVERAGE_IGNORED_BUILTINS = new Set([
22
21
  "alignof",