as-test 0.5.2 → 0.5.4

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 CHANGED
@@ -1,5 +1,59 @@
1
1
  # Change Log
2
2
 
3
+ ## 2026-03-04
4
+
5
+ ### CLI & Config Validation
6
+
7
+ - feat: validate config shape and field types before applying defaults for all CLI entry points.
8
+ - feat: reject unknown config keys with nearest-key suggestions.
9
+ - feat: show structured validation diagnostics with JSON paths and fix hints.
10
+ - feat: fail fast on invalid config JSON with parser error details.
11
+
12
+ ### Docs
13
+
14
+ - docs: add README guidance for strict config validation behavior and example error output.
15
+
16
+ ## 2026-02-25 - v0.5.3
17
+
18
+ ### CLI, Modes & Matrix
19
+
20
+ - feat: support mode fan-out behavior consistently across `ast build`, `ast run`, and `ast test`.
21
+ - feat: when no `--mode` is provided and modes are configured, run using configured modes.
22
+ - feat: add matrix-style per-file output with mode-aware timing:
23
+ - non-verbose: average time
24
+ - verbose: per-mode times.
25
+ - feat: add real-time matrix line updates in the default reporter and normalize timing precision to one decimal.
26
+ - feat: support comma-separated bare selectors (for example `ast test box,custom,generics,string`) across build/run/test selectors.
27
+
28
+ ### Config Merge & Env Behavior
29
+
30
+ - fix: apply mode config as field-level merge over base config instead of replacing entire sections.
31
+ - fix: merge `buildOptions.args` between base config and mode config.
32
+ - fix: pass config env variables to both build and run processes for mode execution.
33
+
34
+ ### Build Pipeline & Feature Flags
35
+
36
+ - feat: include the exact build command in build failure output.
37
+ - feat: allow `buildOptions.cmd` to override default command generation while still appending user build args.
38
+ - feat: support CLI feature toggles:
39
+ - `--enable coverage` / `--disable coverage`
40
+ - `--enable try-as` / `--disable try-as`.
41
+
42
+ ### Reporter & Summaries
43
+
44
+ - fix: move mode summary rendering into the default reporter (via run-complete event payload).
45
+ - feat: include `modeSummary` for single-mode runs.
46
+ - feat: include mode and snapshot totals in final summary output.
47
+ - fix: `--clean` output now behaves as non-TTY in default reporter:
48
+ - no in-place line editing,
49
+ - no suite expand/collapse logs,
50
+ - final per-file verdict lines only.
51
+
52
+ ### Coverage & Transform
53
+
54
+ - fix: ignore AssemblyScript builtin/compiler helper calls during coverage instrumentation (including `isString`, `changetype<T>`, `idof<T>`, `sizeof<T>`).
55
+ - fix: mock transform now collects mocked import targets across sources so WASI mock imports are rewritten reliably (resolves import-shape runtime failures).
56
+
3
57
  ## 2026-02-24 - v0.5.2
4
58
 
5
59
  ### Runtime & Serialization
package/README.md CHANGED
@@ -9,11 +9,13 @@
9
9
  - [Installation](#installation)
10
10
  - [Examples](#examples)
11
11
  - [Writing Tests](#writing-tests)
12
+ - [Setup Diagnostics](#setup-diagnostics)
12
13
  - [Mocking](#mocking)
13
14
  - [Snapshots](#snapshots)
14
15
  - [Coverage](#coverage)
15
16
  - [Custom Reporters](#custom-reporters)
16
17
  - [Assertions](#assertions)
18
+ - [CLI Style Guide](#cli-style-guide)
17
19
  - [License](#license)
18
20
  - [Contact](#contact)
19
21
 
@@ -40,6 +42,11 @@ The installation script will set everything up for you:
40
42
  npx as-test init --dir ./path-to-install
41
43
  ```
42
44
 
45
+ To scaffold and install dependencies in one step:
46
+ ```bash
47
+ npx as-test init --dir ./path-to-install --install
48
+ ```
49
+
43
50
  Alternatively, you can install it manually:
44
51
  ```bash
45
52
  npm install as-test --save-dev
@@ -74,7 +81,7 @@ describe("math", () => {
74
81
  });
75
82
  ```
76
83
 
77
- ### Test file selection (`ast test`)
84
+ ### File selection (`ast run`, `ast build`, `ast test`)
78
85
 
79
86
  No selectors:
80
87
 
@@ -105,6 +112,14 @@ Multiple selectors:
105
112
  ast test sleep array ./assembly/__tests__/snapshot.spec.ts
106
113
  ```
107
114
 
115
+ Comma-separated bare suite names:
116
+
117
+ ```bash
118
+ ast test box,custom,generics,string
119
+ ast run box,custom,generics,string
120
+ ast build box,custom,generics,string
121
+ ```
122
+
108
123
  If nothing matches, `ast test` exits non-zero with:
109
124
 
110
125
  ```text
@@ -114,11 +129,57 @@ No test files matched: ...
114
129
  ### Useful flags
115
130
 
116
131
  - `--config <path>`: use another config file
117
- - `--mode <name[,name...]>`: run one or multiple named config modes
132
+ - `--mode <name[,name...]>`: run one or multiple named config modes (if omitted and `modes` is configured, as-test runs all configured modes)
118
133
  - `--update-snapshots`: write snapshot updates
119
134
  - `--no-snapshot`: disable snapshot assertions for the run
120
135
  - `--show-coverage`: print uncovered coverage points
136
+ - `--enable <feature>`: enable as-test feature (`coverage`, `try-as`)
137
+ - `--disable <feature>`: disable as-test feature (`coverage`, `try-as`)
121
138
  - `--verbose`: keep expanded suite/test lines and update running `....` statuses in place
139
+ - `--clean`: disable in-place TTY updates and print only final per-file verdict lines. Useful for CI/CD.
140
+ - `--list`: show resolved files, per-mode artifacts, and runtime command without executing
141
+ - `--list-modes`: show configured and selected modes without executing
142
+ - `--help` / `-h`: show command-specific help (`ast test --help`, `ast init --help`, etc.)
143
+
144
+ Example:
145
+
146
+ ```bash
147
+ ast build --enable try-as
148
+ ast test --disable coverage
149
+ ```
150
+
151
+ Preview execution plan:
152
+
153
+ ```bash
154
+ ast test --list
155
+ ast test --list-modes
156
+ ast run sleep --list --mode wasi
157
+ ast build --list --mode wasi,bindings
158
+ ```
159
+
160
+ ## Setup Diagnostics
161
+
162
+ Use `ast doctor` to validate local setup before running tests.
163
+
164
+ ```bash
165
+ ast doctor
166
+ ```
167
+
168
+ You can also target specific modes and config files:
169
+
170
+ ```bash
171
+ ast doctor --config ./as-test.config.json --mode wasi,bindings
172
+ ```
173
+
174
+ `doctor` checks:
175
+
176
+ - config file loading and mode resolution
177
+ - required dependencies (for example `assemblyscript`, `@assemblyscript/wasi-shim` for WASI targets)
178
+ - runtime command parsing and executable availability
179
+ - runtime script path existence (for script-host runtimes)
180
+ - test spec file discovery from configured input patterns
181
+
182
+ If any `ERROR` checks are found, `ast doctor` exits non-zero.
122
183
 
123
184
  ## Mocking
124
185
 
@@ -230,13 +291,12 @@ Example:
230
291
  {
231
292
  "$schema": "./as-test.config.schema.json",
232
293
  "input": ["./assembly/__tests__/*.spec.ts"],
233
- "outDir": "./.as-test/build",
234
- "logs": "./.as-test/logs",
235
- "coverageDir": "./.as-test/coverage",
236
- "snapshotDir": "./.as-test/snapshots",
294
+ "output": "./.as-test/",
237
295
  "config": "none",
238
296
  "coverage": true,
297
+ "env": {},
239
298
  "buildOptions": {
299
+ "cmd": "",
240
300
  "args": [],
241
301
  "target": "wasi"
242
302
  },
@@ -253,15 +313,38 @@ Example:
253
313
  Key fields:
254
314
 
255
315
  - `input`: glob list of spec files
316
+ - `output`: output alias. Use a root string (`"./.as-test/"`) or object (`{ "build": "...", "logs": "...", "coverage": "...", "snapshots": "..." }`)
256
317
  - `outDir`: compiled wasm output dir
257
318
  - `logs`: log output dir or `"none"`
258
319
  - `coverageDir`: coverage output dir or `"none"`
259
320
  - `snapshotDir`: snapshot storage dir
321
+ - `outDir`, `logs`, `coverageDir`, and `snapshotDir` still work; when both are set, these explicit fields override `output`
322
+ - `env`: environment variables injected into build and runtime processes
323
+ - `buildOptions.cmd`: optional custom build command template; when set it replaces default build command and flags. Supports `<file>`, `<name>`, `<outFile>`, `<target>`, `<mode>`
260
324
  - `buildOptions.target`: `wasi` or `bindings`
261
- - `modes`: named overrides for target/args/runtime/env/artifact directories (selected via `--mode`)
325
+ - `modes`: named overrides for command/target/args/runtime/env/artifact directories (selected via `--mode`); `mode.env` overrides top-level `env`
262
326
  - `runOptions.runtime.cmd`: runtime command, supports `<file>` and `<name>`; if its script path is missing, as-test falls back to the default runner for the selected target
263
327
  - `runOptions.reporter`: reporter selection as a string or object
264
328
 
329
+ Validation behavior:
330
+
331
+ - Config parsing is strict for `ast build`, `ast run`, `ast test`, and `ast doctor`.
332
+ - Invalid JSON fails early with parser details (`line`/`column` when provided by Node).
333
+ - Unknown properties are rejected and include a nearest-key suggestion when possible.
334
+ - Invalid property types are reported with their JSON path and a short fix hint.
335
+ - On validation failure, the command exits non-zero and prints `run "ast doctor" to check your setup.`
336
+
337
+ Example validation error:
338
+
339
+ ```text
340
+ invalid config at ./as-test.config.json
341
+ 1. $.inpoot: unknown property
342
+ fix: use "input" if that was intended, otherwise remove this property
343
+ 2. $.runOptions.runtime.cmd: must be a string
344
+ fix: set to a runtime command including "<file>"
345
+ run "ast doctor" to check your setup.
346
+ ```
347
+
265
348
  Example multi-runtime matrix:
266
349
 
267
350
  ```json
@@ -309,6 +392,13 @@ Run all modes:
309
392
  ast test --mode wasi-simd,wasi-nosimd,bindings-node-simd
310
393
  ```
311
394
 
395
+ Summary totals:
396
+
397
+ - `Modes` in the default reporter is config-scoped (`total` is all configured modes)
398
+ - when selecting fewer modes with `--mode`, unselected modes are counted as `skipped`
399
+ - `Files` in the default reporter is also config-scoped (`total` is all files from configured input patterns)
400
+ - when selecting fewer files, unselected files are counted as `skipped`
401
+
312
402
  When using `--mode`, compiled artifacts are emitted as:
313
403
 
314
404
  ```text
@@ -472,7 +562,7 @@ Available matchers:
472
562
  - `toStartWith(prefix)`
473
563
  - `toEndWith(suffix)`
474
564
  - `toHaveLength(length)`
475
- - `toContain(item)`
565
+ - `toContain(itemOrSubstring)` (`toContains` alias supported)
476
566
  - `toThrow()` (with `try-as`)
477
567
  - `toMatchSnapshot(name?)`
478
568
 
@@ -17,6 +17,37 @@
17
17
  },
18
18
  "default": ["./assembly/__tests__/*.spec.ts"]
19
19
  },
20
+ "output": {
21
+ "description": "Output alias. Use a root string or a per-artifact object. Legacy fields (outDir/logs/coverageDir/snapshotDir) still work and override this alias when both are set.",
22
+ "oneOf": [
23
+ {
24
+ "type": "string",
25
+ "description": "Root output directory. Expands to build/logs/coverage/snapshots subdirectories."
26
+ },
27
+ {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "properties": {
31
+ "build": {
32
+ "type": "string",
33
+ "description": "Build artifact output directory (maps to outDir)."
34
+ },
35
+ "logs": {
36
+ "type": "string",
37
+ "description": "Log artifact output directory (maps to logs). Use \"none\" to disable."
38
+ },
39
+ "coverage": {
40
+ "type": "string",
41
+ "description": "Coverage artifact output directory (maps to coverageDir). Use \"none\" to disable."
42
+ },
43
+ "snapshots": {
44
+ "type": "string",
45
+ "description": "Snapshot output directory (maps to snapshotDir)."
46
+ }
47
+ }
48
+ }
49
+ ]
50
+ },
20
51
  "outDir": {
21
52
  "type": "string",
22
53
  "description": "Directory where compiled artifacts are written.",
@@ -80,10 +111,23 @@
80
111
  ],
81
112
  "default": true
82
113
  },
114
+ "env": {
115
+ "type": "object",
116
+ "description": "Environment variables injected when building/running.",
117
+ "additionalProperties": {
118
+ "type": "string"
119
+ },
120
+ "default": {}
121
+ },
83
122
  "buildOptions": {
84
123
  "type": "object",
85
124
  "additionalProperties": false,
86
125
  "properties": {
126
+ "cmd": {
127
+ "type": "string",
128
+ "description": "Custom build command template. When set, this replaces the default asc command/flags/output handling. Supports placeholders: <file>, <name>, <outFile>, <target>, <mode>.",
129
+ "default": ""
130
+ },
87
131
  "args": {
88
132
  "type": "array",
89
133
  "items": {
@@ -98,6 +142,7 @@
98
142
  }
99
143
  },
100
144
  "default": {
145
+ "cmd": "",
101
146
  "args": [],
102
147
  "target": "wasi"
103
148
  }
@@ -153,6 +198,10 @@
153
198
  "type": "object",
154
199
  "additionalProperties": false,
155
200
  "properties": {
201
+ "cmd": {
202
+ "type": "string",
203
+ "description": "Mode-specific custom build command template."
204
+ },
156
205
  "args": {
157
206
  "type": "array",
158
207
  "items": {
@@ -23,6 +23,26 @@ export function __REGISTER(point: CoverPoint): void {
23
23
  Coverage.SN.hashes.set(point.hash, point);
24
24
  }
25
25
 
26
+ export function __REGISTER_RAW(
27
+ file: string,
28
+ hash: string,
29
+ line: i32,
30
+ column: i32,
31
+ type: string,
32
+ ): void {
33
+ if (Coverage.SN.allIndex.has(hash)) return;
34
+ const point = new CoverPoint();
35
+ point.file = file;
36
+ point.hash = hash;
37
+ point.line = line;
38
+ point.column = column;
39
+ point.type = type;
40
+ Coverage.SN.points++;
41
+ Coverage.SN.allIndex.set(hash, Coverage.SN.all.length);
42
+ Coverage.SN.all.push(point);
43
+ Coverage.SN.hashes.set(hash, point);
44
+ }
45
+
26
46
  export function __COVER(hash: string): void {
27
47
  if (Coverage.SN.allIndex.has(hash)) {
28
48
  const index = Coverage.SN.allIndex.get(hash);
@@ -334,18 +334,41 @@ export class Expectation<T> extends Tests {
334
334
  }
335
335
 
336
336
  /**
337
- * Tests if an array contains an element
337
+ * Tests if an array or string contains a value
338
338
  */
339
339
  // @ts-ignore
340
340
  toContain(value: valueof<T>): void {
341
- // @ts-ignore
342
- const passed = isArray<T>() && this._left.includes(value);
343
- this._resolve(
344
- passed,
345
- "toContain",
346
- q("includes value"),
347
- q("does not include value"),
348
- );
341
+ if (isString<T>()) {
342
+ // @ts-ignore
343
+ const left = this._left as string;
344
+ // @ts-ignore
345
+ const needle = value as string;
346
+ const passed = left.indexOf(needle) >= 0;
347
+ this._resolve(passed, "toContain", q(left), q(needle));
348
+ return;
349
+ }
350
+
351
+ if (isArray<T>()) {
352
+ // @ts-ignore
353
+ const passed = this._left.includes(value);
354
+ this._resolve(
355
+ passed,
356
+ "toContain",
357
+ stringifyValue<T>(this._left),
358
+ stringifyValue<valueof<T>>(value),
359
+ );
360
+ return;
361
+ }
362
+
363
+ ERROR("toContain() can only be used on string and array types!");
364
+ }
365
+
366
+ /**
367
+ * Alias for toContain().
368
+ */
369
+ // @ts-ignore
370
+ toContains(value: valueof<T>): void {
371
+ this.toContain(value);
349
372
  }
350
373
 
351
374
  /**
@@ -18,17 +18,16 @@ export function stringifyValue<T>(value: T): string {
18
18
  }
19
19
 
20
20
  if (isInteger<T>() || isFloat<T>()) {
21
- // @ts-ignore
21
+ // @ts-expect-error: type
22
22
  return value.toString();
23
23
  }
24
24
 
25
25
  if (isString<T>()) {
26
- // @ts-ignore
27
26
  return quote(value as string);
28
27
  }
29
28
 
30
29
  if (isArray<T>()) {
31
- // @ts-ignore
30
+ // @ts-expect-error: type
32
31
  return stringifyArray<valueof<T>>(value as valueof<T>[]);
33
32
  }
34
33
 
@@ -0,0 +1,268 @@
1
+ import { existsSync } from "fs";
2
+ import { glob } from "glob";
3
+ import chalk from "chalk";
4
+ import { spawnSync } from "child_process";
5
+ import * as path from "path";
6
+ import { applyMode, getPkgRunner, loadConfig, tokenizeCommand, } from "../util.js";
7
+ const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
8
+ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, featureToggles = {}) {
9
+ const loadedConfig = loadConfig(configPath, false);
10
+ const mode = applyMode(loadedConfig, modeName);
11
+ const config = mode.config;
12
+ if (!hasCustomBuildCommand(config)) {
13
+ ensureDeps(config);
14
+ }
15
+ const pkgRunner = getPkgRunner();
16
+ const inputPatterns = resolveInputPatterns(config.input, selectors);
17
+ const inputFiles = (await glob(inputPatterns)).sort((a, b) => a.localeCompare(b));
18
+ const duplicateSpecBasenames = await resolveDuplicateSpecBasenames(config.input);
19
+ const coverageEnabled = resolveCoverageEnabled(config.coverage, featureToggles.coverage);
20
+ const buildEnv = {
21
+ ...mode.env,
22
+ AS_TEST_COVERAGE_ENABLED: coverageEnabled ? "1" : "0",
23
+ };
24
+ for (const file of inputFiles) {
25
+ const outFile = `${config.outDir}/${resolveArtifactFileName(file, config.buildOptions.target, modeName, duplicateSpecBasenames)}`;
26
+ const invocation = getBuildCommand(config, pkgRunner, file, outFile, modeName, featureToggles);
27
+ try {
28
+ buildFile(invocation, buildEnv);
29
+ }
30
+ catch (error) {
31
+ const modeLabel = modeName ?? "default";
32
+ throw new Error(`Failed to build ${path.basename(file)} in mode ${modeLabel} with ${getBuildStderr(error)}\nBuild command: ${formatInvocation(invocation)}`);
33
+ }
34
+ }
35
+ }
36
+ function hasCustomBuildCommand(config) {
37
+ return !!config.buildOptions.cmd.trim().length;
38
+ }
39
+ function getBuildCommand(config, pkgRunner, file, outFile, modeName, featureToggles = {}) {
40
+ const userArgs = getUserBuildArgs(config);
41
+ if (hasCustomBuildCommand(config)) {
42
+ const tokens = tokenizeCommand(expandBuildCommand(config.buildOptions.cmd, file, outFile, config.buildOptions.target, modeName));
43
+ if (!tokens.length) {
44
+ throw new Error("custom build command is empty");
45
+ }
46
+ return {
47
+ command: tokens[0],
48
+ args: [...tokens.slice(1), ...userArgs],
49
+ };
50
+ }
51
+ const defaultArgs = getDefaultBuildArgs(config, featureToggles);
52
+ const args = ["asc", file, ...userArgs, ...defaultArgs];
53
+ if (config.outDir.length) {
54
+ args.push("-o", outFile);
55
+ }
56
+ return {
57
+ command: pkgRunner,
58
+ args,
59
+ };
60
+ }
61
+ function getUserBuildArgs(config) {
62
+ return config.buildOptions.args.filter((value) => value.length > 0);
63
+ }
64
+ function expandBuildCommand(template, file, outFile, target, modeName) {
65
+ const name = path
66
+ .basename(file)
67
+ .replace(/\.spec\.ts$/, "")
68
+ .replace(/\.ts$/, "");
69
+ return template
70
+ .replace(/<file>/g, file)
71
+ .replace(/<name>/g, name)
72
+ .replace(/<outFile>/g, outFile)
73
+ .replace(/<target>/g, target)
74
+ .replace(/<mode>/g, modeName ?? "");
75
+ }
76
+ function resolveArtifactFileName(file, target, modeName, duplicateSpecBasenames = new Set()) {
77
+ const base = path
78
+ .basename(file)
79
+ .replace(/\.spec\.ts$/, "")
80
+ .replace(/\.ts$/, "");
81
+ const legacy = !modeName
82
+ ? `${path.basename(file).replace(".ts", ".wasm")}`
83
+ : `${base}.${modeName}.${target}.wasm`;
84
+ if (!duplicateSpecBasenames.has(path.basename(file))) {
85
+ return legacy;
86
+ }
87
+ const disambiguator = resolveDisambiguator(file);
88
+ if (!disambiguator.length) {
89
+ return legacy;
90
+ }
91
+ const ext = path.extname(legacy);
92
+ const stem = ext.length ? legacy.slice(0, -ext.length) : legacy;
93
+ return `${stem}.${disambiguator}${ext}`;
94
+ }
95
+ async function resolveDuplicateSpecBasenames(configured) {
96
+ const patterns = Array.isArray(configured) ? configured : [configured];
97
+ const files = await glob(patterns);
98
+ const counts = new Map();
99
+ for (const file of files) {
100
+ const base = path.basename(file);
101
+ counts.set(base, (counts.get(base) ?? 0) + 1);
102
+ }
103
+ const duplicates = new Set();
104
+ for (const [base, count] of counts) {
105
+ if (count > 1)
106
+ duplicates.add(base);
107
+ }
108
+ return duplicates;
109
+ }
110
+ function resolveDisambiguator(file) {
111
+ const relDir = path.dirname(path.relative(process.cwd(), file));
112
+ if (!relDir.length || relDir == ".")
113
+ return "";
114
+ return relDir
115
+ .replace(/[\\/]+/g, "__")
116
+ .replace(/[^A-Za-z0-9._-]/g, "_")
117
+ .replace(/^_+|_+$/g, "");
118
+ }
119
+ function resolveInputPatterns(configured, selectors) {
120
+ const configuredInputs = Array.isArray(configured)
121
+ ? configured
122
+ : [configured];
123
+ if (!selectors.length)
124
+ return configuredInputs;
125
+ const patterns = new Set();
126
+ for (const selector of expandSelectors(selectors)) {
127
+ if (!selector)
128
+ continue;
129
+ if (isBareSuiteSelector(selector)) {
130
+ const base = stripSuiteSuffix(selector);
131
+ for (const configuredInput of configuredInputs) {
132
+ patterns.add(path.join(path.dirname(configuredInput), `${base}.spec.ts`));
133
+ }
134
+ continue;
135
+ }
136
+ patterns.add(selector);
137
+ }
138
+ return [...patterns];
139
+ }
140
+ function expandSelectors(selectors) {
141
+ const expanded = [];
142
+ for (const selector of selectors) {
143
+ if (!selector)
144
+ continue;
145
+ if (!shouldSplitSelector(selector)) {
146
+ expanded.push(selector);
147
+ continue;
148
+ }
149
+ for (const token of selector.split(",")) {
150
+ const trimmed = token.trim();
151
+ if (!trimmed.length)
152
+ continue;
153
+ expanded.push(trimmed);
154
+ }
155
+ }
156
+ return expanded;
157
+ }
158
+ function shouldSplitSelector(selector) {
159
+ return (selector.includes(",") &&
160
+ !selector.includes("/") &&
161
+ !selector.includes("\\") &&
162
+ !/[*?[\]{}]/.test(selector));
163
+ }
164
+ function isBareSuiteSelector(selector) {
165
+ return (!selector.includes("/") &&
166
+ !selector.includes("\\") &&
167
+ !/[*?[\]{}]/.test(selector));
168
+ }
169
+ function stripSuiteSuffix(selector) {
170
+ return selector.replace(/\.spec\.ts$/, "").replace(/\.ts$/, "");
171
+ }
172
+ function ensureDeps(config) {
173
+ if (config.buildOptions.target == "wasi") {
174
+ if (!existsSync("./node_modules/@assemblyscript/wasi-shim/asconfig.json")) {
175
+ console.log(`${chalk.bgRed(" ERROR ")}${chalk.dim(":")} could not find @assemblyscript/wasi-shim! Add it to your dependencies to run with WASI!`);
176
+ process.exit(1);
177
+ }
178
+ }
179
+ }
180
+ function buildFile(invocation, env) {
181
+ const result = spawnSync(invocation.command, invocation.args, {
182
+ stdio: ["ignore", "pipe", "pipe"],
183
+ encoding: "utf8",
184
+ env,
185
+ shell: false,
186
+ });
187
+ if (result.error)
188
+ throw result.error;
189
+ if (result.status !== 0) {
190
+ const error = new Error(result.stderr?.trim() ||
191
+ result.stdout?.trim() ||
192
+ `command exited with code ${result.status}`);
193
+ error.stderr = result.stderr ?? "";
194
+ throw error;
195
+ }
196
+ }
197
+ function formatInvocation(invocation) {
198
+ return [invocation.command, ...invocation.args]
199
+ .map((token) => (/\s/.test(token) ? JSON.stringify(token) : token))
200
+ .join(" ");
201
+ }
202
+ function getBuildStderr(error) {
203
+ const err = error;
204
+ const stderr = err?.stderr;
205
+ if (typeof stderr == "string") {
206
+ const trimmed = stderr.trim();
207
+ if (trimmed.length)
208
+ return trimmed;
209
+ }
210
+ else if (stderr instanceof Buffer) {
211
+ const trimmed = stderr.toString("utf8").trim();
212
+ if (trimmed.length)
213
+ return trimmed;
214
+ }
215
+ const message = typeof err?.message == "string" ? err.message.trim() : "";
216
+ return message || "unknown error";
217
+ }
218
+ function getDefaultBuildArgs(config, featureToggles) {
219
+ const buildArgs = [];
220
+ const tryAsEnabled = resolveTryAsEnabled(featureToggles.tryAs);
221
+ buildArgs.push("--transform", "as-test/transform");
222
+ if (tryAsEnabled) {
223
+ buildArgs.push("--transform", "try-as/transform");
224
+ }
225
+ if (config.config && config.config !== "none") {
226
+ buildArgs.push("--config", config.config);
227
+ }
228
+ if (tryAsEnabled) {
229
+ buildArgs.push("--use", "AS_TEST_TRY_AS=1");
230
+ }
231
+ // Should also strip any bindings-enabling from asconfig
232
+ if (config.buildOptions.target == "bindings") {
233
+ buildArgs.push("--use", "AS_TEST_BINDINGS=1", "--bindings", "raw", "--exportRuntime", "--exportStart", "_start");
234
+ }
235
+ else if (config.buildOptions.target == "wasi") {
236
+ buildArgs.push("--use", "AS_TEST_WASI=1", "--config", "./node_modules/@assemblyscript/wasi-shim/asconfig.json");
237
+ }
238
+ else {
239
+ console.log(`${chalk.bgRed(" ERROR ")}${chalk.dim(":")} could not determine target in config! Set target to 'bindings' or 'wasi'`);
240
+ process.exit(1);
241
+ }
242
+ return buildArgs;
243
+ }
244
+ function resolveTryAsEnabled(override) {
245
+ const installed = hasTryAsRuntime();
246
+ if (override === false)
247
+ return false;
248
+ if (override === true && !installed) {
249
+ throw new Error('try-as feature was enabled, but package "try-as" is not installed');
250
+ }
251
+ return installed;
252
+ }
253
+ function resolveCoverageEnabled(rawCoverage, override) {
254
+ if (override != undefined)
255
+ return override;
256
+ if (typeof rawCoverage == "boolean")
257
+ return rawCoverage;
258
+ if (rawCoverage && typeof rawCoverage == "object") {
259
+ const enabled = rawCoverage.enabled;
260
+ if (typeof enabled == "boolean")
261
+ return enabled;
262
+ }
263
+ return true;
264
+ }
265
+ function hasTryAsRuntime() {
266
+ return (existsSync(path.join(process.cwd(), "node_modules/try-as")) ||
267
+ existsSync(path.join(process.cwd(), "node_modules/try-as/package.json")));
268
+ }