nomen-lang 0.0.8 → 0.0.9
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/NOMEN_AGENTS.md +5 -5
- package/dist/index.mjs +150 -18
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/init.ts +7 -7
- package/src/test.ts +14 -6
package/NOMEN_AGENTS.md
CHANGED
|
@@ -68,11 +68,11 @@ func safe = (string[] s, int i: i >= 0 && i < s.length, out string) {
|
|
|
68
68
|
|
|
69
69
|
### Memory modifiers
|
|
70
70
|
|
|
71
|
-
| Modifier | Meaning
|
|
72
|
-
|
|
73
|
-
| `ref T` | mutable borrow; caller must also write `ref`
|
|
74
|
-
| `view T` | read-only borrow
|
|
75
|
-
| `mov T` | transfer ownership; caller writes `mov`
|
|
71
|
+
| Modifier | Meaning |
|
|
72
|
+
| -------- | ---------------------------------------------- |
|
|
73
|
+
| `ref T` | mutable borrow; caller must also write `ref` |
|
|
74
|
+
| `view T` | read-only borrow |
|
|
75
|
+
| `mov T` | transfer ownership; caller writes `mov` |
|
|
76
76
|
| `out T` | output parameter, assigned inside the function |
|
|
77
77
|
|
|
78
78
|
`const` values cannot be passed to `ref` — rebind the caller as `var` first.
|
package/dist/index.mjs
CHANGED
|
@@ -16310,14 +16310,23 @@ function describe(token) {
|
|
|
16310
16310
|
//#region ../src/join.ts
|
|
16311
16311
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16312
16312
|
const DEFAULT_LIB_PATH = path.resolve(__dirname, "../../core/src");
|
|
16313
|
-
|
|
16313
|
+
let test_src_dir;
|
|
16314
|
+
function join$1(entry_file_path, lib_path, options) {
|
|
16314
16315
|
const folder_path = path.dirname(entry_file_path);
|
|
16315
16316
|
const file_path = path.basename(entry_file_path);
|
|
16316
16317
|
const inputs = /* @__PURE__ */ new Map();
|
|
16317
16318
|
const resolved_lib_path = lib_path ? path.resolve(lib_path, "src") : DEFAULT_LIB_PATH;
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16319
|
+
const for_test = options?.for_test ?? file_path.endsWith(".test.nm");
|
|
16320
|
+
const prev_test_src_dir = test_src_dir;
|
|
16321
|
+
test_src_dir = for_test ? resolve_src_module(folder_path) : void 0;
|
|
16322
|
+
try {
|
|
16323
|
+
add_source(folder_path, `./${file_path}`, inputs, resolved_lib_path);
|
|
16324
|
+
gather_module_siblings(folder_path, file_path, inputs, resolved_lib_path);
|
|
16325
|
+
gather_module_parent(folder_path, file_path, inputs, resolved_lib_path);
|
|
16326
|
+
if (test_src_dir) gather_src_module(test_src_dir, inputs, resolved_lib_path);
|
|
16327
|
+
} finally {
|
|
16328
|
+
test_src_dir = prev_test_src_dir;
|
|
16329
|
+
}
|
|
16321
16330
|
return Array.from(inputs.values()).join("\n\n") + "\n";
|
|
16322
16331
|
}
|
|
16323
16332
|
function gather_module_siblings(folder_path, entry_file, inputs, lib_path) {
|
|
@@ -16361,6 +16370,26 @@ function gather_module_parent(folder_path, entry_file, inputs, lib_path) {
|
|
|
16361
16370
|
add_source(parent_path, `./${name}`, inputs, lib_path);
|
|
16362
16371
|
}
|
|
16363
16372
|
}
|
|
16373
|
+
/** The program's `src/` module dir for a file in `entry_dir`, if one exists. */
|
|
16374
|
+
function resolve_src_module(entry_dir) {
|
|
16375
|
+
for (const dir of [path.join(entry_dir, "src"), path.join(path.dirname(entry_dir), "src")]) try {
|
|
16376
|
+
if (fs.statSync(dir).isDirectory()) return dir;
|
|
16377
|
+
} catch {}
|
|
16378
|
+
}
|
|
16379
|
+
function gather_src_module(src_dir, inputs, lib_path) {
|
|
16380
|
+
let names;
|
|
16381
|
+
try {
|
|
16382
|
+
names = fs.readdirSync(src_dir);
|
|
16383
|
+
} catch {
|
|
16384
|
+
return;
|
|
16385
|
+
}
|
|
16386
|
+
for (const name of names.sort()) {
|
|
16387
|
+
if (!name.endsWith(".nm")) continue;
|
|
16388
|
+
const import_file_path = `./${name}`;
|
|
16389
|
+
if (inputs.has(import_file_path)) continue;
|
|
16390
|
+
add_source(src_dir, import_file_path, inputs, lib_path);
|
|
16391
|
+
}
|
|
16392
|
+
}
|
|
16364
16393
|
function has_main(source) {
|
|
16365
16394
|
return /\bfunc\s+main\b/.test(source);
|
|
16366
16395
|
}
|
|
@@ -16370,8 +16399,9 @@ function add_source(folder_path, file_path, inputs, lib_path) {
|
|
|
16370
16399
|
const lib_source_path = path.resolve(lib_path, file_path);
|
|
16371
16400
|
if (fs.existsSync(lib_source_path)) source_path = lib_source_path;
|
|
16372
16401
|
}
|
|
16373
|
-
let source =
|
|
16374
|
-
|
|
16402
|
+
let source = fs.readFileSync(source_path, "utf8");
|
|
16403
|
+
if (test_src_dir && is_within$1(source_path, test_src_dir)) source = strip_main_functions(source);
|
|
16404
|
+
source = `// file://${source_path}\n` + source;
|
|
16375
16405
|
source = source.replaceAll(/^import(.*)$/gm, (match, name) => {
|
|
16376
16406
|
const trimmed = name.trim();
|
|
16377
16407
|
if (trimmed === "System" || trimmed.startsWith("System/")) return match;
|
|
@@ -16381,6 +16411,105 @@ function add_source(folder_path, file_path, inputs, lib_path) {
|
|
|
16381
16411
|
});
|
|
16382
16412
|
inputs.set(file_path, source);
|
|
16383
16413
|
}
|
|
16414
|
+
function is_within$1(child, parent) {
|
|
16415
|
+
const rel = path.relative(parent, child);
|
|
16416
|
+
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
16417
|
+
}
|
|
16418
|
+
/**
|
|
16419
|
+
* Remove every top-level `func main` (optionally `pub`) declaration, body and
|
|
16420
|
+
* all. Used when compiling a test: the generated harness provides `main`, so
|
|
16421
|
+
* the program's own `main` would collide as a duplicate declaration.
|
|
16422
|
+
*/
|
|
16423
|
+
function strip_main_functions(source) {
|
|
16424
|
+
let result = "";
|
|
16425
|
+
let i = 0;
|
|
16426
|
+
const n = source.length;
|
|
16427
|
+
let depth = 0;
|
|
16428
|
+
while (i < n) {
|
|
16429
|
+
if (depth === 0 && is_main_declaration(source, i)) {
|
|
16430
|
+
i = skip_main_function(source, i);
|
|
16431
|
+
continue;
|
|
16432
|
+
}
|
|
16433
|
+
const ch = source[i];
|
|
16434
|
+
result += ch;
|
|
16435
|
+
if (ch === "\"" || ch === "'") {
|
|
16436
|
+
i++;
|
|
16437
|
+
let escaped = false;
|
|
16438
|
+
while (i < n) {
|
|
16439
|
+
const c = source[i];
|
|
16440
|
+
result += c;
|
|
16441
|
+
i++;
|
|
16442
|
+
if (escaped) escaped = false;
|
|
16443
|
+
else if (c === "\\") escaped = true;
|
|
16444
|
+
else if (c === ch) break;
|
|
16445
|
+
}
|
|
16446
|
+
continue;
|
|
16447
|
+
}
|
|
16448
|
+
if (ch === "{") depth++;
|
|
16449
|
+
if (ch === "}") depth--;
|
|
16450
|
+
i++;
|
|
16451
|
+
}
|
|
16452
|
+
return result;
|
|
16453
|
+
}
|
|
16454
|
+
function is_main_declaration(source, i) {
|
|
16455
|
+
if (i > 0 && source[i - 1] !== "\n") return false;
|
|
16456
|
+
const line_end = source.indexOf("\n", i);
|
|
16457
|
+
const line = line_end === -1 ? source.slice(i) : source.slice(i, line_end);
|
|
16458
|
+
return /^\s*(pub\s+)?func\s+main\b/.test(line);
|
|
16459
|
+
}
|
|
16460
|
+
function skip_main_function(source, i) {
|
|
16461
|
+
const n = source.length;
|
|
16462
|
+
let line_end = i;
|
|
16463
|
+
while (line_end < n && source[line_end] !== "\n") line_end++;
|
|
16464
|
+
const decl_line = source.slice(i, line_end);
|
|
16465
|
+
const brace = decl_line.indexOf("{");
|
|
16466
|
+
if (brace === -1) {
|
|
16467
|
+
if (decl_line.includes("=>")) return line_end < n ? line_end + 1 : n;
|
|
16468
|
+
let j = line_end;
|
|
16469
|
+
while (j < n && source[j] !== "{") {
|
|
16470
|
+
if (source[j] === "\"" || source[j] === "'") {
|
|
16471
|
+
const quote = source[j];
|
|
16472
|
+
j++;
|
|
16473
|
+
while (j < n && source[j] !== quote) j++;
|
|
16474
|
+
}
|
|
16475
|
+
j++;
|
|
16476
|
+
}
|
|
16477
|
+
if (j >= n) return n;
|
|
16478
|
+
return skip_balanced_block(source, j);
|
|
16479
|
+
}
|
|
16480
|
+
return skip_balanced_block(source, i + brace);
|
|
16481
|
+
}
|
|
16482
|
+
function skip_balanced_block(source, brace) {
|
|
16483
|
+
const n = source.length;
|
|
16484
|
+
let depth = 0;
|
|
16485
|
+
let j = brace;
|
|
16486
|
+
while (j < n) {
|
|
16487
|
+
const ch = source[j];
|
|
16488
|
+
if (ch === "\"" || ch === "'") {
|
|
16489
|
+
const quote = ch;
|
|
16490
|
+
j++;
|
|
16491
|
+
let escaped = false;
|
|
16492
|
+
while (j < n) {
|
|
16493
|
+
const c = source[j++];
|
|
16494
|
+
if (escaped) escaped = false;
|
|
16495
|
+
else if (c === "\\") escaped = true;
|
|
16496
|
+
else if (c === quote) break;
|
|
16497
|
+
}
|
|
16498
|
+
continue;
|
|
16499
|
+
}
|
|
16500
|
+
if (ch === "{") depth++;
|
|
16501
|
+
if (ch === "}") {
|
|
16502
|
+
depth--;
|
|
16503
|
+
if (depth === 0) {
|
|
16504
|
+
j++;
|
|
16505
|
+
while (j < n && (source[j] === "\r" || source[j] === "\n")) j++;
|
|
16506
|
+
return j;
|
|
16507
|
+
}
|
|
16508
|
+
}
|
|
16509
|
+
j++;
|
|
16510
|
+
}
|
|
16511
|
+
return n;
|
|
16512
|
+
}
|
|
16384
16513
|
//#endregion
|
|
16385
16514
|
//#region ../src/lib.ts
|
|
16386
16515
|
function read_library_config(lib_dir) {
|
|
@@ -22513,6 +22642,7 @@ function dedupe_warnings(warnings) {
|
|
|
22513
22642
|
function warn_unused_function(func, referenced, status, struct) {
|
|
22514
22643
|
if (func.is_library || struct && struct.is_library) return;
|
|
22515
22644
|
if (func.name === "main" || is_discard(func.name) || func.name.startsWith("#")) return;
|
|
22645
|
+
if (func.visibility === "pub") return;
|
|
22516
22646
|
if (func.is_generic) return;
|
|
22517
22647
|
if (struct && struct.traits.length > 0) return;
|
|
22518
22648
|
if (referenced.has(func.name)) return;
|
|
@@ -25084,22 +25214,22 @@ function package_jsonc(name) {
|
|
|
25084
25214
|
"entry": "src/main.nm"
|
|
25085
25215
|
// The System library is resolved automatically from your nomen-lang
|
|
25086
25216
|
// install. To pin a local checkout instead, uncomment:
|
|
25087
|
-
// "imports": { "System": "
|
|
25217
|
+
// "imports": { "System": "~/Source/nomen/core" }
|
|
25088
25218
|
}
|
|
25089
25219
|
`;
|
|
25090
25220
|
}
|
|
25091
25221
|
const MAIN_NM = `import System
|
|
25092
25222
|
|
|
25223
|
+
pub func add = (int a, int b) => a + b
|
|
25224
|
+
|
|
25093
25225
|
pub func main = (Init init) {
|
|
25094
|
-
Console.write("Hello world!\\n")
|
|
25226
|
+
Console.write("Hello world, 2 + 2 is \\{add(a + b)}!\\n")
|
|
25095
25227
|
}
|
|
25096
25228
|
`;
|
|
25097
25229
|
const TEST_NM = `import System
|
|
25098
25230
|
import System/Test
|
|
25099
25231
|
|
|
25100
|
-
func
|
|
25101
|
-
|
|
25102
|
-
pub func test_smoke = (ref Tester t) {
|
|
25232
|
+
pub func test_add = (ref Tester t) {
|
|
25103
25233
|
t.expect(add(2, 2) == 4, "2 + 2 should equal 4")
|
|
25104
25234
|
}
|
|
25105
25235
|
`;
|
|
@@ -25164,14 +25294,14 @@ function run_init(name) {
|
|
|
25164
25294
|
fs.writeFileSync(path.join(target, ".gitignore"), GITIGNORE);
|
|
25165
25295
|
fs.writeFileSync(path.join(target, "package.jsonc"), package_jsonc(name));
|
|
25166
25296
|
fs.writeFileSync(path.join(src, "main.nm"), MAIN_NM);
|
|
25167
|
-
fs.writeFileSync(path.join(test, "
|
|
25297
|
+
fs.writeFileSync(path.join(test, "main.test.nm"), TEST_NM);
|
|
25168
25298
|
fs.writeFileSync(path.join(target, "README.md"), readme(name));
|
|
25169
25299
|
fs.writeFileSync(path.join(target, "AGENTS.md"), fs.readFileSync(agents_template));
|
|
25170
25300
|
console.log(`Created ${name}/`);
|
|
25171
25301
|
console.log(` ${name}/.gitignore`);
|
|
25172
25302
|
console.log(` ${name}/package.jsonc`);
|
|
25173
25303
|
console.log(` ${name}/src/main.nm`);
|
|
25174
|
-
console.log(` ${name}/test/
|
|
25304
|
+
console.log(` ${name}/test/main.test.nm`);
|
|
25175
25305
|
console.log(` ${name}/README.md`);
|
|
25176
25306
|
console.log(` ${name}/AGENTS.md`);
|
|
25177
25307
|
console.log(`\nNext: cd ${name} && nomen run`);
|
|
@@ -25455,7 +25585,7 @@ function runTests(root, options = {}) {
|
|
|
25455
25585
|
const arch = options.arch ?? "aarch64";
|
|
25456
25586
|
const lib = resolve_lib_for(root);
|
|
25457
25587
|
const files = collect_test_files(root).filter((f) => !options.filter || options.filter.test(f));
|
|
25458
|
-
console.log(
|
|
25588
|
+
console.log(`Found ${files.length} test file(s)\n`);
|
|
25459
25589
|
const startTime = performance.now();
|
|
25460
25590
|
const results = [];
|
|
25461
25591
|
for (const file of files) {
|
|
@@ -25465,14 +25595,16 @@ function runTests(root, options = {}) {
|
|
|
25465
25595
|
}
|
|
25466
25596
|
const elapsed = performance.now() - startTime;
|
|
25467
25597
|
const totalFiles = results.length;
|
|
25598
|
+
const totalFilesFailed = results.reduce((a, r) => a + (r.fails ? 1 : 0), 0);
|
|
25599
|
+
const totalFilesPassed = totalFiles - totalFilesFailed;
|
|
25468
25600
|
const totalTests = results.reduce((a, r) => a + r.tests.length, 0);
|
|
25469
25601
|
const totalFailed = results.reduce((a, r) => a + r.tests.reduce((x, t) => x + t.failed, 0), 0);
|
|
25470
25602
|
const totalPassed = totalTests - totalFailed;
|
|
25471
25603
|
const anyFailed = results.some((r) => !r.ok);
|
|
25472
25604
|
console.log("");
|
|
25473
|
-
console.log(` ${C.
|
|
25474
|
-
console.log(` ${C.
|
|
25475
|
-
console.log(` ${C.
|
|
25605
|
+
console.log(` ${C.dim("Files ")} ${C.green(`${totalFilesPassed} passed`)}` + (totalFilesFailed ? ` ${C.dim("|")} ${C.red(`${totalFilesFailed} failed`)}` : "") + C.dim(` (${totalFiles})`));
|
|
25606
|
+
console.log(` ${C.dim("Tests ")} ${C.green(`${totalPassed} passed`)}` + (totalFailed ? ` ${C.dim("|")} ${C.red(`${totalFailed} failed`)}` : "") + C.dim(` (${totalTests})`));
|
|
25607
|
+
console.log(` ${C.dim(" Time ")} ${format_duration(elapsed)}`);
|
|
25476
25608
|
console.log("");
|
|
25477
25609
|
return !anyFailed;
|
|
25478
25610
|
}
|
|
@@ -25536,7 +25668,7 @@ function collect_nm_files(folder) {
|
|
|
25536
25668
|
return out;
|
|
25537
25669
|
}
|
|
25538
25670
|
let build_root;
|
|
25539
|
-
console.
|
|
25671
|
+
console.error("\n~ NOMEN ~\n");
|
|
25540
25672
|
const args = parse_args();
|
|
25541
25673
|
const command = args.command;
|
|
25542
25674
|
try {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -71,7 +71,7 @@ function collect_nm_files(folder: string): string[] {
|
|
|
71
71
|
// package.jsonc discovery — the package folder (cwd), not the entry's folder.
|
|
72
72
|
let build_root: string | undefined;
|
|
73
73
|
|
|
74
|
-
console.
|
|
74
|
+
console.error("\n~ NOMEN ~\n");
|
|
75
75
|
|
|
76
76
|
const args: Args = parse_args();
|
|
77
77
|
const command = args.command;
|
package/src/init.ts
CHANGED
|
@@ -16,24 +16,24 @@ function package_jsonc(name: string): string {
|
|
|
16
16
|
"entry": "src/main.nm"
|
|
17
17
|
// The System library is resolved automatically from your nomen-lang
|
|
18
18
|
// install. To pin a local checkout instead, uncomment:
|
|
19
|
-
// "imports": { "System": "
|
|
19
|
+
// "imports": { "System": "~/Source/nomen/core" }
|
|
20
20
|
}
|
|
21
21
|
`;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const MAIN_NM = `import System
|
|
25
25
|
|
|
26
|
+
pub func add = (int a, int b) => a + b
|
|
27
|
+
|
|
26
28
|
pub func main = (Init init) {
|
|
27
|
-
Console.write("Hello world!\\n")
|
|
29
|
+
Console.write("Hello world, 2 + 2 is \\{add(a + b)}!\\n")
|
|
28
30
|
}
|
|
29
31
|
`;
|
|
30
32
|
|
|
31
33
|
const TEST_NM = `import System
|
|
32
34
|
import System/Test
|
|
33
35
|
|
|
34
|
-
func
|
|
35
|
-
|
|
36
|
-
pub func test_smoke = (ref Tester t) {
|
|
36
|
+
pub func test_add = (ref Tester t) {
|
|
37
37
|
t.expect(add(2, 2) == 4, "2 + 2 should equal 4")
|
|
38
38
|
}
|
|
39
39
|
`;
|
|
@@ -113,7 +113,7 @@ export function run_init(name: string | undefined): void {
|
|
|
113
113
|
fs.writeFileSync(path.join(target, ".gitignore"), GITIGNORE);
|
|
114
114
|
fs.writeFileSync(path.join(target, "package.jsonc"), package_jsonc(name));
|
|
115
115
|
fs.writeFileSync(path.join(src, "main.nm"), MAIN_NM);
|
|
116
|
-
fs.writeFileSync(path.join(test, "
|
|
116
|
+
fs.writeFileSync(path.join(test, "main.test.nm"), TEST_NM);
|
|
117
117
|
fs.writeFileSync(path.join(target, "README.md"), readme(name));
|
|
118
118
|
fs.writeFileSync(path.join(target, "AGENTS.md"), fs.readFileSync(agents_template));
|
|
119
119
|
|
|
@@ -121,7 +121,7 @@ export function run_init(name: string | undefined): void {
|
|
|
121
121
|
console.log(` ${name}/.gitignore`);
|
|
122
122
|
console.log(` ${name}/package.jsonc`);
|
|
123
123
|
console.log(` ${name}/src/main.nm`);
|
|
124
|
-
console.log(` ${name}/test/
|
|
124
|
+
console.log(` ${name}/test/main.test.nm`);
|
|
125
125
|
console.log(` ${name}/README.md`);
|
|
126
126
|
console.log(` ${name}/AGENTS.md`);
|
|
127
127
|
console.log(`\nNext: cd ${name} && nomen run`);
|
package/src/test.ts
CHANGED
|
@@ -434,7 +434,8 @@ export function runTests(root: string, options: RunTestsOptions = {}): boolean {
|
|
|
434
434
|
const lib = resolve_lib_for(root);
|
|
435
435
|
const files = collect_test_files(root).filter((f) => !options.filter || options.filter.test(f));
|
|
436
436
|
|
|
437
|
-
console.log(
|
|
437
|
+
console.log(`Found ${files.length} test file(s)\n`);
|
|
438
|
+
|
|
438
439
|
const startTime = performance.now();
|
|
439
440
|
|
|
440
441
|
const results: TestFileResult[] = [];
|
|
@@ -446,6 +447,8 @@ export function runTests(root: string, options: RunTestsOptions = {}): boolean {
|
|
|
446
447
|
|
|
447
448
|
const elapsed = performance.now() - startTime;
|
|
448
449
|
const totalFiles = results.length;
|
|
450
|
+
const totalFilesFailed = results.reduce((a, r) => a + (r.fails ? 1 : 0), 0);
|
|
451
|
+
const totalFilesPassed = totalFiles - totalFilesFailed;
|
|
449
452
|
// `tests[].failed` (from each test's `done` record) is the authoritative
|
|
450
453
|
// failure count; `result.fails` holds the same failures' messages for
|
|
451
454
|
// display, so don't sum both or failures are double-counted.
|
|
@@ -455,15 +458,20 @@ export function runTests(root: string, options: RunTestsOptions = {}): boolean {
|
|
|
455
458
|
const anyFailed = results.some((r) => !r.ok);
|
|
456
459
|
|
|
457
460
|
console.log("");
|
|
461
|
+
//console.log(
|
|
462
|
+
// ` ${C.dim("Files ")} ${totalFiles} ${anyFailed ? C.red("failed") : C.green("passed")} (${totalFiles})`,
|
|
463
|
+
//);
|
|
458
464
|
console.log(
|
|
459
|
-
` ${C.
|
|
465
|
+
` ${C.dim("Files ")} ${C.green(`${totalFilesPassed} passed`)}` +
|
|
466
|
+
(totalFilesFailed ? ` ${C.dim("|")} ${C.red(`${totalFilesFailed} failed`)}` : "") +
|
|
467
|
+
C.dim(` (${totalFiles})`),
|
|
460
468
|
);
|
|
461
469
|
console.log(
|
|
462
|
-
` ${C.
|
|
463
|
-
(totalFailed ? ` | ${C.red(`${totalFailed} failed`)}` : "") +
|
|
464
|
-
` (${totalTests})
|
|
470
|
+
` ${C.dim("Tests ")} ${C.green(`${totalPassed} passed`)}` +
|
|
471
|
+
(totalFailed ? ` ${C.dim("|")} ${C.red(`${totalFailed} failed`)}` : "") +
|
|
472
|
+
C.dim(` (${totalTests})`),
|
|
465
473
|
);
|
|
466
|
-
console.log(` ${C.
|
|
474
|
+
console.log(` ${C.dim(" Time ")} ${format_duration(elapsed)}`);
|
|
467
475
|
console.log("");
|
|
468
476
|
|
|
469
477
|
return !anyFailed;
|