as-test 0.5.3 → 1.0.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/bin/util.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync } from "fs";
2
2
  import { BuildOptions, Config, CoverageOptions, ModeConfig, ReporterConfig, RunOptions, Runtime, } from "./types.js";
3
3
  import chalk from "chalk";
4
+ import { createRequire } from "module";
4
5
  import { delimiter, dirname, join } from "path";
5
6
  import { fileURLToPath } from "url";
6
7
  export function formatTime(ms) {
@@ -33,8 +34,22 @@ export function loadConfig(CONFIG_PATH, warn = false) {
33
34
  return new Config();
34
35
  }
35
36
  else {
36
- const raw = JSON.parse(readFileSync(CONFIG_PATH).toString());
37
+ const rawText = readFileSync(CONFIG_PATH, "utf8");
38
+ let parsed;
39
+ try {
40
+ parsed = JSON.parse(rawText);
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ throw new Error(`invalid config JSON at ${CONFIG_PATH}\n${message}\nfix JSON syntax and rerun.`);
45
+ }
46
+ if (!parsed || typeof parsed != "object" || Array.isArray(parsed)) {
47
+ throw new Error(`invalid config at ${CONFIG_PATH}\nroot value must be an object. Example: { "input": ["./assembly/__tests__/*.spec.ts"] }`);
48
+ }
49
+ const raw = parsed;
50
+ validateConfig(raw, CONFIG_PATH);
37
51
  const config = Object.assign(new Config(), raw);
52
+ applyOutputConfig(raw.output, raw, config);
38
53
  config.env = parseEnvMap(raw.env);
39
54
  const runOptionsRaw = raw.runOptions ?? {};
40
55
  config.buildOptions = Object.assign(new BuildOptions(), raw.buildOptions ?? {});
@@ -44,7 +59,8 @@ export function loadConfig(CONFIG_PATH, warn = false) {
44
59
  ? config.buildOptions.args.filter((item) => typeof item == "string")
45
60
  : [];
46
61
  config.buildOptions.target =
47
- typeof config.buildOptions.target == "string" && config.buildOptions.target.length
62
+ typeof config.buildOptions.target == "string" &&
63
+ config.buildOptions.target.length
48
64
  ? config.buildOptions.target
49
65
  : "wasi";
50
66
  config.runOptions = Object.assign(new RunOptions(), runOptionsRaw);
@@ -88,6 +104,491 @@ export function loadConfig(CONFIG_PATH, warn = false) {
88
104
  return config;
89
105
  }
90
106
  }
107
+ const TOP_LEVEL_KEYS = new Set([
108
+ "$schema",
109
+ "input",
110
+ "output",
111
+ "outDir",
112
+ "logs",
113
+ "coverageDir",
114
+ "snapshotDir",
115
+ "config",
116
+ "coverage",
117
+ "env",
118
+ "buildOptions",
119
+ "modes",
120
+ "runOptions",
121
+ ]);
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"
125
+ const REPORTER_OPTION_KEYS = new Set(["name", "options", "outDir", "outFile"]);
126
+ const OUTPUT_OPTION_KEYS = new Set(["build", "logs", "coverage", "snapshots"]);
127
+ const MODE_KEYS = new Set([
128
+ "outDir",
129
+ "logs",
130
+ "coverageDir",
131
+ "snapshotDir",
132
+ "config",
133
+ "coverage",
134
+ "buildOptions",
135
+ "runOptions",
136
+ "env",
137
+ ]);
138
+ function validateConfig(raw, configPath) {
139
+ const issues = [];
140
+ validateUnknownKeys(raw, TOP_LEVEL_KEYS, "$", issues);
141
+ validateStringField(raw, "$schema", "$", issues);
142
+ validateInputField(raw, "input", "$", issues);
143
+ validateOutputField(raw, "output", "$", issues);
144
+ validateStringField(raw, "outDir", "$", issues);
145
+ validateStringField(raw, "logs", "$", issues);
146
+ validateStringField(raw, "coverageDir", "$", issues);
147
+ validateStringField(raw, "snapshotDir", "$", issues);
148
+ validateStringField(raw, "config", "$", issues);
149
+ validateCoverageField(raw, "coverage", "$", issues);
150
+ validateEnvField(raw, "env", "$", issues);
151
+ validateBuildOptionsField(raw, "buildOptions", "$", issues);
152
+ validateRunOptionsField(raw, "runOptions", "$", issues);
153
+ validateModesField(raw, "modes", "$", issues);
154
+ if (!issues.length)
155
+ return;
156
+ const lines = issues.map((issue, index) => {
157
+ const suffix = issue.fix ? `\n fix: ${issue.fix}` : "";
158
+ return `${index + 1}. ${issue.path}: ${issue.message}${suffix}`;
159
+ });
160
+ throw new Error(`invalid config at ${configPath}\n${lines.join("\n")}\nrun "ast doctor" to check your setup.`);
161
+ }
162
+ function validateUnknownKeys(raw, allowed, pathPrefix, issues) {
163
+ for (const key of Object.keys(raw)) {
164
+ if (allowed.has(key))
165
+ continue;
166
+ const suggestion = resolveClosestKey(key, [...allowed]);
167
+ issues.push({
168
+ path: `${pathPrefix}.${key}`,
169
+ message: "unknown property",
170
+ fix: suggestion
171
+ ? `use "${suggestion}" if that was intended, otherwise remove this property`
172
+ : `remove this property`,
173
+ });
174
+ }
175
+ }
176
+ function validateInputField(raw, key, pathPrefix, issues) {
177
+ if (!(key in raw) || raw[key] == undefined)
178
+ return;
179
+ const value = raw[key];
180
+ if (typeof value == "string") {
181
+ if (!value.length) {
182
+ issues.push({
183
+ path: `${pathPrefix}.${key}`,
184
+ message: "must not be an empty string",
185
+ fix: "set to a glob pattern or remove it to use the default input patterns",
186
+ });
187
+ }
188
+ return;
189
+ }
190
+ if (!Array.isArray(value)) {
191
+ issues.push({
192
+ path: `${pathPrefix}.${key}`,
193
+ message: "must be a string or array of strings",
194
+ fix: 'example: "input": ["./assembly/__tests__/*.spec.ts"]',
195
+ });
196
+ return;
197
+ }
198
+ for (let i = 0; i < value.length; i++) {
199
+ if (typeof value[i] == "string" && value[i].length)
200
+ continue;
201
+ issues.push({
202
+ path: `${pathPrefix}.${key}[${i}]`,
203
+ message: "must be a non-empty string",
204
+ fix: "remove invalid entries or replace them with valid glob strings",
205
+ });
206
+ }
207
+ }
208
+ function validateStringField(raw, key, pathPrefix, issues) {
209
+ if (!(key in raw) || raw[key] == undefined)
210
+ return;
211
+ if (typeof raw[key] != "string") {
212
+ issues.push({
213
+ path: `${pathPrefix}.${key}`,
214
+ message: "must be a string",
215
+ fix: `set "${key}" to a string value`,
216
+ });
217
+ }
218
+ }
219
+ function validateOutputField(raw, key, pathPrefix, issues) {
220
+ if (!(key in raw) || raw[key] == undefined)
221
+ return;
222
+ const value = raw[key];
223
+ if (typeof value == "string") {
224
+ if (!value.length) {
225
+ issues.push({
226
+ path: `${pathPrefix}.${key}`,
227
+ message: "must not be an empty string",
228
+ fix: 'example: "output": "./.as-test/"',
229
+ });
230
+ }
231
+ return;
232
+ }
233
+ if (!value || typeof value != "object" || Array.isArray(value)) {
234
+ issues.push({
235
+ path: `${pathPrefix}.${key}`,
236
+ message: "must be a string or object",
237
+ fix: 'example: "output": { "logs": "./.as-test/logs", "coverage": "./.as-test/coverage" }',
238
+ });
239
+ return;
240
+ }
241
+ const out = value;
242
+ validateUnknownKeys(out, OUTPUT_OPTION_KEYS, `${pathPrefix}.${key}`, issues);
243
+ if ("build" in out && (typeof out.build != "string" || !out.build.length)) {
244
+ issues.push({
245
+ path: `${pathPrefix}.${key}.build`,
246
+ message: "must be a non-empty string",
247
+ });
248
+ }
249
+ if ("snapshots" in out &&
250
+ (typeof out.snapshots != "string" || !out.snapshots.length)) {
251
+ issues.push({
252
+ path: `${pathPrefix}.${key}.snapshots`,
253
+ message: "must be a non-empty string",
254
+ });
255
+ }
256
+ if ("logs" in out && (typeof out.logs != "string" || !out.logs.length)) {
257
+ issues.push({
258
+ path: `${pathPrefix}.${key}.logs`,
259
+ message: 'must be a non-empty string or "none"',
260
+ });
261
+ }
262
+ if ("coverage" in out &&
263
+ (typeof out.coverage != "string" || !out.coverage.length)) {
264
+ issues.push({
265
+ path: `${pathPrefix}.${key}.coverage`,
266
+ message: 'must be a non-empty string or "none"',
267
+ });
268
+ }
269
+ }
270
+ function validateCoverageField(raw, key, pathPrefix, issues) {
271
+ if (!(key in raw) || raw[key] == undefined)
272
+ return;
273
+ validateCoverageValue(raw[key], `${pathPrefix}.${key}`, issues);
274
+ }
275
+ function validateCoverageValue(value, path, issues) {
276
+ if (typeof value == "boolean")
277
+ return;
278
+ if (!value || typeof value != "object" || Array.isArray(value)) {
279
+ issues.push({
280
+ path,
281
+ message: "must be a boolean or object",
282
+ fix: 'use true/false or { "enabled": true, "includeSpecs": false }',
283
+ });
284
+ return;
285
+ }
286
+ const obj = value;
287
+ if ("enabled" in obj && typeof obj.enabled != "boolean") {
288
+ issues.push({
289
+ path: `${path}.enabled`,
290
+ message: "must be a boolean",
291
+ fix: "set to true or false",
292
+ });
293
+ }
294
+ if ("includeSpecs" in obj && typeof obj.includeSpecs != "boolean") {
295
+ issues.push({
296
+ path: `${path}.includeSpecs`,
297
+ message: "must be a boolean",
298
+ fix: "set to true or false",
299
+ });
300
+ }
301
+ }
302
+ function validateEnvField(raw, key, pathPrefix, issues) {
303
+ if (!(key in raw) || raw[key] == undefined)
304
+ return;
305
+ const value = raw[key];
306
+ if (!value || typeof value != "object" || Array.isArray(value)) {
307
+ issues.push({
308
+ path: `${pathPrefix}.${key}`,
309
+ message: "must be an object of string values",
310
+ fix: 'example: "env": { "MY_FLAG": "1" }',
311
+ });
312
+ return;
313
+ }
314
+ for (const [name, item] of Object.entries(value)) {
315
+ if (typeof item == "string")
316
+ continue;
317
+ issues.push({
318
+ path: `${pathPrefix}.${key}.${name}`,
319
+ message: "must be a string",
320
+ fix: "set environment values as strings",
321
+ });
322
+ }
323
+ }
324
+ function validateBuildOptionsField(raw, key, pathPrefix, issues) {
325
+ if (!(key in raw) || raw[key] == undefined)
326
+ return;
327
+ const value = raw[key];
328
+ if (!value || typeof value != "object" || Array.isArray(value)) {
329
+ issues.push({
330
+ path: `${pathPrefix}.${key}`,
331
+ message: "must be an object",
332
+ fix: 'example: "buildOptions": { "cmd": "", "args": [], "target": "wasi" }',
333
+ });
334
+ return;
335
+ }
336
+ const obj = value;
337
+ validateUnknownKeys(obj, BUILD_OPTION_KEYS, `${pathPrefix}.${key}`, issues);
338
+ if ("cmd" in obj && typeof obj.cmd != "string") {
339
+ issues.push({
340
+ path: `${pathPrefix}.${key}.cmd`,
341
+ message: "must be a string",
342
+ fix: "set to an empty string or a command template",
343
+ });
344
+ }
345
+ if ("args" in obj && !isStringArray(obj.args)) {
346
+ issues.push({
347
+ path: `${pathPrefix}.${key}.args`,
348
+ message: "must be an array of strings",
349
+ fix: 'example: "args": ["--optimize"]',
350
+ });
351
+ }
352
+ if ("target" in obj) {
353
+ if (typeof obj.target != "string") {
354
+ issues.push({
355
+ path: `${pathPrefix}.${key}.target`,
356
+ message: "must be a string",
357
+ fix: 'set to "wasi" or "bindings"',
358
+ });
359
+ }
360
+ else if (obj.target != "wasi" && obj.target != "bindings") {
361
+ issues.push({
362
+ path: `${pathPrefix}.${key}.target`,
363
+ message: `must be "wasi" or "bindings"`,
364
+ fix: `received "${obj.target}"`,
365
+ });
366
+ }
367
+ }
368
+ }
369
+ function validateRunOptionsField(raw, key, pathPrefix, issues) {
370
+ if (!(key in raw) || raw[key] == undefined)
371
+ return;
372
+ const value = raw[key];
373
+ if (!value || typeof value != "object" || Array.isArray(value)) {
374
+ issues.push({
375
+ path: `${pathPrefix}.${key}`,
376
+ message: "must be an object",
377
+ fix: 'example: "runOptions": { "runtime": { "cmd": "node ... <file>" } }',
378
+ });
379
+ return;
380
+ }
381
+ const obj = value;
382
+ validateUnknownKeys(obj, RUN_OPTION_KEYS, `${pathPrefix}.${key}`, issues);
383
+ if ("run" in obj && typeof obj.run != "string") {
384
+ issues.push({
385
+ path: `${pathPrefix}.${key}.run`,
386
+ message: "must be a string",
387
+ fix: 'prefer "runtime.cmd"; legacy "run" must still be string',
388
+ });
389
+ }
390
+ if ("runtime" in obj && obj.runtime != undefined) {
391
+ const runtime = obj.runtime;
392
+ if (!runtime || typeof runtime != "object" || Array.isArray(runtime)) {
393
+ issues.push({
394
+ path: `${pathPrefix}.${key}.runtime`,
395
+ message: "must be an object",
396
+ fix: 'example: "runtime": { "cmd": "node ./.as-test/runners/default.wasi.js <file>" }',
397
+ });
398
+ }
399
+ else {
400
+ const runtimeObj = runtime;
401
+ validateUnknownKeys(runtimeObj, RUNTIME_OPTION_KEYS, `${pathPrefix}.${key}.runtime`, issues);
402
+ if ("cmd" in runtimeObj && typeof runtimeObj.cmd != "string") {
403
+ issues.push({
404
+ path: `${pathPrefix}.${key}.runtime.cmd`,
405
+ message: "must be a string",
406
+ fix: 'set to a runtime command including "<file>"',
407
+ });
408
+ }
409
+ if ("run" in runtimeObj && typeof runtimeObj.run != "string") {
410
+ issues.push({
411
+ path: `${pathPrefix}.${key}.runtime.run`,
412
+ message: "must be a string",
413
+ fix: 'legacy "run" should be a command string',
414
+ });
415
+ }
416
+ }
417
+ }
418
+ if ("reporter" in obj && obj.reporter != undefined) {
419
+ const reporter = obj.reporter;
420
+ if (typeof reporter == "string")
421
+ return;
422
+ if (!reporter || typeof reporter != "object" || Array.isArray(reporter)) {
423
+ issues.push({
424
+ path: `${pathPrefix}.${key}.reporter`,
425
+ message: "must be a string or object",
426
+ fix: 'use "default", "tap", or { "name": "...", ... }',
427
+ });
428
+ return;
429
+ }
430
+ const reporterObj = reporter;
431
+ validateUnknownKeys(reporterObj, REPORTER_OPTION_KEYS, `${pathPrefix}.${key}.reporter`, issues);
432
+ if ("name" in reporterObj && typeof reporterObj.name != "string") {
433
+ issues.push({
434
+ path: `${pathPrefix}.${key}.reporter.name`,
435
+ message: "must be a string",
436
+ fix: 'set to "default", "tap", or module path',
437
+ });
438
+ }
439
+ if (!("name" in reporterObj)) {
440
+ issues.push({
441
+ path: `${pathPrefix}.${key}.reporter`,
442
+ message: 'object reporter config requires "name"',
443
+ fix: 'example: { "name": "tap", "outDir": "./.as-test/reports" }',
444
+ });
445
+ }
446
+ if ("options" in reporterObj && !isStringArray(reporterObj.options)) {
447
+ issues.push({
448
+ path: `${pathPrefix}.${key}.reporter.options`,
449
+ message: "must be an array of strings",
450
+ fix: 'example: "options": ["single-file"]',
451
+ });
452
+ }
453
+ if ("outDir" in reporterObj && typeof reporterObj.outDir != "string") {
454
+ issues.push({
455
+ path: `${pathPrefix}.${key}.reporter.outDir`,
456
+ message: "must be a string",
457
+ });
458
+ }
459
+ if ("outFile" in reporterObj && typeof reporterObj.outFile != "string") {
460
+ issues.push({
461
+ path: `${pathPrefix}.${key}.reporter.outFile`,
462
+ message: "must be a string",
463
+ });
464
+ }
465
+ }
466
+ }
467
+ function validateModesField(raw, key, pathPrefix, issues) {
468
+ if (!(key in raw) || raw[key] == undefined)
469
+ return;
470
+ const value = raw[key];
471
+ if (!value || typeof value != "object" || Array.isArray(value)) {
472
+ issues.push({
473
+ path: `${pathPrefix}.${key}`,
474
+ message: "must be an object",
475
+ fix: 'example: "modes": { "wasi": { "buildOptions": { "target": "wasi" } } }',
476
+ });
477
+ return;
478
+ }
479
+ for (const [modeName, modeRaw] of Object.entries(value)) {
480
+ if (!modeRaw || typeof modeRaw != "object" || Array.isArray(modeRaw)) {
481
+ issues.push({
482
+ path: `${pathPrefix}.${key}.${modeName}`,
483
+ message: "must be an object",
484
+ });
485
+ continue;
486
+ }
487
+ const modeObj = modeRaw;
488
+ const modePath = `${pathPrefix}.${key}.${modeName}`;
489
+ validateUnknownKeys(modeObj, MODE_KEYS, modePath, issues);
490
+ validateStringField(modeObj, "outDir", modePath, issues);
491
+ validateStringField(modeObj, "logs", modePath, issues);
492
+ validateStringField(modeObj, "coverageDir", modePath, issues);
493
+ validateStringField(modeObj, "snapshotDir", modePath, issues);
494
+ validateStringField(modeObj, "config", modePath, issues);
495
+ validateCoverageField(modeObj, "coverage", modePath, issues);
496
+ validateEnvField(modeObj, "env", modePath, issues);
497
+ validateBuildOptionsField(modeObj, "buildOptions", modePath, issues);
498
+ validateRunOptionsField(modeObj, "runOptions", modePath, issues);
499
+ }
500
+ }
501
+ function isStringArray(value) {
502
+ return Array.isArray(value) && value.every((item) => typeof item == "string");
503
+ }
504
+ function resolveClosestKey(value, keys) {
505
+ let best = null;
506
+ let bestDistance = Number.POSITIVE_INFINITY;
507
+ for (const key of keys) {
508
+ const distance = levenshteinDistance(value, key);
509
+ if (distance < bestDistance) {
510
+ bestDistance = distance;
511
+ best = key;
512
+ }
513
+ }
514
+ if (best && bestDistance <= 3)
515
+ return best;
516
+ return null;
517
+ }
518
+ function levenshteinDistance(left, right) {
519
+ if (left == right)
520
+ return 0;
521
+ if (!left.length)
522
+ return right.length;
523
+ if (!right.length)
524
+ return left.length;
525
+ const matrix = [];
526
+ for (let i = 0; i <= left.length; i++) {
527
+ matrix[i] = [i];
528
+ }
529
+ for (let j = 0; j <= right.length; j++) {
530
+ matrix[0][j] = j;
531
+ }
532
+ for (let i = 1; i <= left.length; i++) {
533
+ for (let j = 1; j <= right.length; j++) {
534
+ const cost = left[i - 1] == right[j - 1] ? 0 : 1;
535
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
536
+ }
537
+ }
538
+ return matrix[left.length][right.length];
539
+ }
540
+ function applyOutputConfig(rawOutput, rawConfig, config) {
541
+ if (rawOutput == undefined)
542
+ return;
543
+ if (typeof rawOutput == "string") {
544
+ applyOutputRoot(rawOutput, rawConfig, config);
545
+ return;
546
+ }
547
+ if (!rawOutput || typeof rawOutput != "object" || Array.isArray(rawOutput)) {
548
+ return;
549
+ }
550
+ const output = rawOutput;
551
+ if ("build" in output &&
552
+ typeof output.build == "string" &&
553
+ output.build.length &&
554
+ !("outDir" in rawConfig)) {
555
+ config.outDir = output.build;
556
+ }
557
+ if ("logs" in output &&
558
+ typeof output.logs == "string" &&
559
+ output.logs.length &&
560
+ !("logs" in rawConfig)) {
561
+ config.logs = output.logs;
562
+ }
563
+ if ("coverage" in output &&
564
+ typeof output.coverage == "string" &&
565
+ output.coverage.length &&
566
+ !("coverageDir" in rawConfig)) {
567
+ config.coverageDir = output.coverage;
568
+ }
569
+ if ("snapshots" in output &&
570
+ typeof output.snapshots == "string" &&
571
+ output.snapshots.length &&
572
+ !("snapshotDir" in rawConfig)) {
573
+ config.snapshotDir = output.snapshots;
574
+ }
575
+ }
576
+ function applyOutputRoot(root, rawConfig, config) {
577
+ if (!root.length)
578
+ return;
579
+ if (!("outDir" in rawConfig)) {
580
+ config.outDir = join(root, "build");
581
+ }
582
+ if (!("logs" in rawConfig)) {
583
+ config.logs = join(root, "logs");
584
+ }
585
+ if (!("coverageDir" in rawConfig)) {
586
+ config.coverageDir = join(root, "coverage");
587
+ }
588
+ if (!("snapshotDir" in rawConfig)) {
589
+ config.snapshotDir = join(root, "snapshots");
590
+ }
591
+ }
91
592
  function parseModes(raw) {
92
593
  if (!raw || typeof raw != "object" || Array.isArray(raw))
93
594
  return {};
@@ -104,12 +605,10 @@ function parseModes(raw) {
104
605
  if (typeof modeRaw.logs == "string" && modeRaw.logs.length) {
105
606
  mode.logs = modeRaw.logs;
106
607
  }
107
- if (typeof modeRaw.coverageDir == "string" &&
108
- modeRaw.coverageDir.length) {
608
+ if (typeof modeRaw.coverageDir == "string" && modeRaw.coverageDir.length) {
109
609
  mode.coverageDir = modeRaw.coverageDir;
110
610
  }
111
- if (typeof modeRaw.snapshotDir == "string" &&
112
- modeRaw.snapshotDir.length) {
611
+ if (typeof modeRaw.snapshotDir == "string" && modeRaw.snapshotDir.length) {
113
612
  mode.snapshotDir = modeRaw.snapshotDir;
114
613
  }
115
614
  if (typeof modeRaw.config == "string" && modeRaw.config.length) {
@@ -144,8 +643,7 @@ function parseModes(raw) {
144
643
  if (typeof runtimeRaw.cmd == "string" && runtimeRaw.cmd.length) {
145
644
  runtime.cmd = runtimeRaw.cmd;
146
645
  }
147
- else if (typeof runtimeRaw.run == "string" &&
148
- runtimeRaw.run.length) {
646
+ else if (typeof runtimeRaw.run == "string" && runtimeRaw.run.length) {
149
647
  runtime.cmd = runtimeRaw.run;
150
648
  }
151
649
  else {
@@ -324,3 +822,70 @@ export function getExec(exec) {
324
822
  }
325
823
  return null;
326
824
  }
825
+ export function tokenizeCommand(command) {
826
+ const out = [];
827
+ let current = "";
828
+ let quote = null;
829
+ let escaping = false;
830
+ for (let i = 0; i < command.length; i++) {
831
+ const ch = command[i];
832
+ if (escaping) {
833
+ current += ch;
834
+ escaping = false;
835
+ continue;
836
+ }
837
+ if (ch == "\\") {
838
+ if (quote == "'") {
839
+ current += ch;
840
+ }
841
+ else {
842
+ escaping = true;
843
+ }
844
+ continue;
845
+ }
846
+ if (quote) {
847
+ if (ch == quote) {
848
+ quote = null;
849
+ }
850
+ else {
851
+ current += ch;
852
+ }
853
+ continue;
854
+ }
855
+ if (ch == '"' || ch == "'") {
856
+ quote = ch;
857
+ continue;
858
+ }
859
+ if (/\s/.test(ch)) {
860
+ if (current.length) {
861
+ out.push(current);
862
+ current = "";
863
+ }
864
+ continue;
865
+ }
866
+ current += ch;
867
+ }
868
+ if (escaping) {
869
+ current += "\\";
870
+ }
871
+ if (quote) {
872
+ throw new Error(`unterminated quote in command: ${command}`);
873
+ }
874
+ if (current.length) {
875
+ out.push(current);
876
+ }
877
+ return out;
878
+ }
879
+ export function resolveProjectModule(specifier) {
880
+ const cwdRequire = createRequire(join(process.cwd(), "package.json"));
881
+ const localRequire = createRequire(import.meta.url);
882
+ for (const req of [cwdRequire, localRequire]) {
883
+ try {
884
+ return req.resolve(specifier);
885
+ }
886
+ catch {
887
+ // try next resolver
888
+ }
889
+ }
890
+ return null;
891
+ }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "as-test",
3
- "version": "0.5.3",
3
+ "version": "1.0.0",
4
4
  "author": "Jairus Tanaka",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/JairusSW/as-test.git"
8
8
  },
9
9
  "dependencies": {
10
- "as-console": "^7.0.0",
11
10
  "as-rainbow": "^0.1.0",
11
+ "as-console": "^7.0.0",
12
12
  "chalk": "^5.6.2",
13
13
  "glob": "^13.0.3",
14
14
  "typer-diff": "^1.1.1"
@@ -59,15 +59,18 @@
59
59
  ],
60
60
  "license": "MIT",
61
61
  "publishConfig": {
62
- "@JairusSW:registry": "https://npm.pkg.github.com"
62
+ "access": "public"
63
63
  },
64
64
  "scripts": {
65
65
  "test": "node ./bin/index.js test",
66
+ "test:examples": "npm --prefix ./examples run test",
67
+ "typecheck": "tsc -p cli --noEmit && tsc -p transform --noEmit",
68
+ "lint": "eslint transform/src/**/*.ts tools/**/*.js eslint.config.js",
66
69
  "build:transform": "tsc -p ./transform",
67
70
  "build:cli": "tsc -p cli",
68
- "build:run": "tsc -p run",
71
+ "build:run": "npm run build:cli",
69
72
  "prettier": "prettier -w .",
70
- "release:check": "npm run build:cli && npm run build:transform && npm run test && npm pack --dry-run --cache /tmp/as-test-npm-cache",
73
+ "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",
71
74
  "prepublishOnly": "npm run build:cli && npm run build:transform && npm run test"
72
75
  },
73
76
  "type": "module",
@@ -26,7 +26,8 @@ export default class Transformer extends Transform {
26
26
  return 0;
27
27
  }
28
28
  });
29
- const entryFile = sources.find((v) => v.sourceKind == 1).simplePath;
29
+ const entrySource = sources.find((v) => v.sourceKind == 1);
30
+ const entryFile = entrySource ? entrySource.simplePath : "unknown";
30
31
  const mockedImportTargets = collectMockImportTargets(sources);
31
32
  for (const target of mockedImportTargets) {
32
33
  mock.importMocked.add(target);