@neocompose/cli 0.26.2 → 0.26.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,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.26.4] - 2026-08-10
4
+
5
+ ### Fixed
6
+
7
+ - Read an omitted nullable member on a newly constructed class as `null`
8
+ instead of throwing a missing-object-key error. `neo test` can now assert
9
+ explicit nullable defaults without requiring an otherwise-empty value row.
10
+ - Let local candidate validation resolve an image or audio file created in the
11
+ same source change. `neo doctor`, `neo test`, and push dry runs now preserve
12
+ the pending file identity while rehearsing the uploaded project-file row.
13
+
14
+ ## [0.26.3] - 2026-08-10
15
+
16
+ ### Changed
17
+
18
+ - Show a phase-aware loading spinner during interactive `neo test` runs, then
19
+ render files, tests, failures, counts, and durations with Vitest-style
20
+ grouping, alignment, color, and emphasis. JSON reports now expose prepare,
21
+ selection, compilation, registration, execution, and finalization timings.
22
+ - Reuse matcher dispatch metadata, build assertion diagnostics only on failure,
23
+ and compare structured values directly instead of serializing them to JSON.
24
+ This keeps successful `expect`, `toBe`, `toEqual`, `toHaveLength`,
25
+ `toContain`, `toMatchObject`, and mock assertions on a smaller hot path.
26
+
3
27
  ## [0.26.2] - 2026-08-10
4
28
 
5
29
  ### Changed
package/dist/neo.mjs CHANGED
@@ -169,6 +169,9 @@ function warn(text) {
169
169
  function note(text) {
170
170
  console.log(color.dim(text));
171
171
  }
172
+ function pad(text, width) {
173
+ return text.length >= width ? text : text + " ".repeat(width - text.length);
174
+ }
172
175
  function paintChangeKind(kind) {
173
176
  const painter = CHANGE_KIND_COLOR[kind];
174
177
  return painter === void 0 ? kind : painter(kind);
@@ -46689,9 +46692,9 @@ ${namedArguments(
46689
46692
  `;
46690
46693
  }
46691
46694
  function namedArguments(entries, spaces, raw = /* @__PURE__ */ new Set()) {
46692
- const pad = " ".repeat(spaces);
46695
+ const pad2 = " ".repeat(spaces);
46693
46696
  return entries.map(
46694
- ([name, value]) => `${pad}${name}: ${raw.has(name) ? rawValue(value) : jsonValue2(value)},`
46697
+ ([name, value]) => `${pad2}${name}: ${raw.has(name) ? rawValue(value) : jsonValue2(value)},`
46695
46698
  ).join("\n");
46696
46699
  }
46697
46700
  function rawValue(value) {
@@ -57376,6 +57379,13 @@ var init_NeoScriptScope = __esm({
57376
57379
  setLocal(bindingId, value) {
57377
57380
  this.#bindings.set(bindingId, value);
57378
57381
  }
57382
+ bindInvocationEntry(bindingId, value) {
57383
+ this.#bindings.set(bindingId, value);
57384
+ }
57385
+ bindInvocationKeyAndEntry(keyBindingId, key, entryBindingId, entry) {
57386
+ this.#bindings.set(keyBindingId, key);
57387
+ this.#bindings.set(entryBindingId, entry);
57388
+ }
57379
57389
  resetInvocationLocals(parameterCount) {
57380
57390
  if (this.#bindings.size > parameterCount) this.#bindings.clear();
57381
57391
  if (this.#readonlyBindingErrors.size > 0) {
@@ -61898,6 +61908,7 @@ function dispatchSchemaMember(receiver, schemaKey, ctx) {
61898
61908
  };
61899
61909
  }
61900
61910
  if (!Object.prototype.hasOwnProperty.call(receiver, schemaKey)) {
61911
+ if (!member.required) return { kind: "ok", value: null };
61901
61912
  return { kind: "no-info", matchedMember: true };
61902
61913
  }
61903
61914
  const at = receiver[schemaKey];
@@ -62423,6 +62434,211 @@ function evalDeclaredListIndex(info, scope, ctx) {
62423
62434
  }
62424
62435
  return Array.isArray(hit) ? [...hit] : [];
62425
62436
  }
62437
+ function prepareCollectionCallback(callback, parentScope, ctx, isList, returnContract, onPredicateMatch) {
62438
+ const metrics = ctx.__collectionCallbackPreparationMetrics;
62439
+ if (metrics !== void 0) metrics.bodyValidations += 1;
62440
+ const compilerRevision = callback.compilerRevision ?? 1;
62441
+ if (!Number.isSafeInteger(compilerRevision)) {
62442
+ throw new NSGetterRuntimeError(
62443
+ "Collection callback compiler revision must be a safe integer."
62444
+ );
62445
+ }
62446
+ if (compilerRevision < 1) {
62447
+ throw new NSGetterRuntimeError(
62448
+ "Collection callback compiler revision must be at least 1."
62449
+ );
62450
+ }
62451
+ if (compilerRevision > NEOSCRIPT_COMPILER_REVISION) {
62452
+ throw new NSGetterRuntimeError(
62453
+ `Collection callback compiler revision ${String(compilerRevision)} is newer than supported revision ${String(NEOSCRIPT_COMPILER_REVISION)}.`
62454
+ );
62455
+ }
62456
+ if (!isNSFunctionWithReturnType(callback)) {
62457
+ throw new NSGetterRuntimeError(
62458
+ `Collection callback body metadata is invalid for compiler revision ${String(compilerRevision)}.`
62459
+ );
62460
+ }
62461
+ const parameters = callback.parameters;
62462
+ if (parameters.length < 1) {
62463
+ throw new NSGetterRuntimeError(
62464
+ "Collection callback requires at least one parameter."
62465
+ );
62466
+ }
62467
+ if (parameters.length > 2) {
62468
+ throw new NSGetterRuntimeError(
62469
+ `Collection callback supports at most two parameters, but received ${String(parameters.length)}.`
62470
+ );
62471
+ }
62472
+ if (returnContract === "predicate") {
62473
+ if (callback.typeInfo.type !== 1 /* Bool */) {
62474
+ throw new NSGetterRuntimeError(
62475
+ "Collection predicate callback must declare a Bool return type."
62476
+ );
62477
+ }
62478
+ if (!callback.typeInfo.required) {
62479
+ throw new NSGetterRuntimeError(
62480
+ "Collection predicate callback must declare a required return type."
62481
+ );
62482
+ }
62483
+ }
62484
+ const parameterCount = parameters.length;
62485
+ const callbackScope = ctx.__collectionCallbackStrategy === "fresh" ? null : createChildScope(parentScope);
62486
+ const callbackOptions = evaluationOptions(ctx, false);
62487
+ const instructions = callback.instructions;
62488
+ const returnTypeInfo = callback.typeInfo;
62489
+ const requiresInvocationReset = callbackBodyMayAddInvocationLocals(
62490
+ instructions,
62491
+ new Set(parameters.map((parameter4) => parameter4.id))
62492
+ );
62493
+ let finishBody;
62494
+ if (returnContract === "predicate") {
62495
+ finishBody = (innerScope2) => {
62496
+ const result = evalInstructions(
62497
+ instructions,
62498
+ innerScope2,
62499
+ ctx,
62500
+ callbackOptions
62501
+ );
62502
+ rejectEscapedLoopTransfer(result, "collection callback");
62503
+ if (result.kind !== "return") {
62504
+ throw new NSGetterRuntimeError(
62505
+ "Collection callback ended without returning a value."
62506
+ );
62507
+ }
62508
+ const value = result.value;
62509
+ if (typeof value !== "boolean") {
62510
+ throw new NSGetterRuntimeError(
62511
+ "Collection predicate callback returned a value that does not match its required Bool contract."
62512
+ );
62513
+ }
62514
+ return value;
62515
+ };
62516
+ } else {
62517
+ finishBody = (innerScope2) => {
62518
+ const result = evalInstructions(
62519
+ instructions,
62520
+ innerScope2,
62521
+ ctx,
62522
+ callbackOptions
62523
+ );
62524
+ rejectEscapedLoopTransfer(result, "collection callback");
62525
+ if (result.kind !== "return") {
62526
+ throw new NSGetterRuntimeError(
62527
+ "Collection callback ended without returning a value."
62528
+ );
62529
+ }
62530
+ let value = result.value;
62531
+ if (returnTypeInfo.type === 20 /* Decimal */ && typeof value === "number") {
62532
+ value = coerceDecimalOperand(value, "collection callback return");
62533
+ }
62534
+ if (!runtimeValueMatchesType(value, returnTypeInfo, ctx)) {
62535
+ throw new NSGetterRuntimeError(
62536
+ "Collection projection callback returned a value that does not match its compiled return type."
62537
+ );
62538
+ }
62539
+ return value;
62540
+ };
62541
+ }
62542
+ if (metrics !== void 0) metrics.bindingPlanCreations += 1;
62543
+ if (ctx.__collectionCallbackStrategy === "fresh" || ctx.__collectionCallbackStrategy === "prepared") {
62544
+ return (entry, key, valueId) => {
62545
+ if (onPredicateMatch !== void 0) {
62546
+ consumeBudget(ctx, "workUnits", 1, "work unit");
62547
+ }
62548
+ const innerScope2 = callbackScope ?? createChildScope(parentScope);
62549
+ if (callbackScope !== null) {
62550
+ callbackScope.resetInvocationLocals(parameterCount);
62551
+ }
62552
+ if (parameters.length === 1) {
62553
+ innerScope2.setLocal(parameters[0].id, entry);
62554
+ } else if (parameters.length === 2) {
62555
+ innerScope2.setLocal(
62556
+ parameters[0].id,
62557
+ isList ? Number(key) : String(key)
62558
+ );
62559
+ innerScope2.setLocal(parameters[1].id, entry);
62560
+ }
62561
+ const value = finishBody(innerScope2);
62562
+ if (onPredicateMatch === void 0) return value;
62563
+ return value === true ? onPredicateMatch(entry, key, valueId) : 0 /* Continue */;
62564
+ };
62565
+ }
62566
+ const innerScope = callbackScope ?? createChildScope(parentScope);
62567
+ let bindValues;
62568
+ if (parameterCount === 1) {
62569
+ const entryParameterId = parameters[0].id;
62570
+ bindValues = (entry) => {
62571
+ innerScope.bindInvocationEntry(entryParameterId, entry);
62572
+ };
62573
+ } else {
62574
+ const keyParameterId = parameters[0].id;
62575
+ const entryParameterId = parameters[1].id;
62576
+ bindValues = (entry, key) => {
62577
+ innerScope.bindInvocationKeyAndEntry(
62578
+ keyParameterId,
62579
+ key,
62580
+ entryParameterId,
62581
+ entry
62582
+ );
62583
+ };
62584
+ }
62585
+ if (returnContract === "predicate") {
62586
+ if (onPredicateMatch === void 0) {
62587
+ throw new NSGetterRuntimeError(
62588
+ "Collection predicate preparation requires a match handler."
62589
+ );
62590
+ }
62591
+ if (requiresInvocationReset) {
62592
+ return (entry, key, valueId) => {
62593
+ consumeBudget(ctx, "workUnits", 1, "work unit");
62594
+ innerScope.resetInvocationLocals(parameterCount);
62595
+ bindValues(entry, key);
62596
+ return finishBody(innerScope) === true ? onPredicateMatch(entry, key, valueId) : 0 /* Continue */;
62597
+ };
62598
+ }
62599
+ return (entry, key, valueId) => {
62600
+ consumeBudget(ctx, "workUnits", 1, "work unit");
62601
+ bindValues(entry, key);
62602
+ return finishBody(innerScope) === true ? onPredicateMatch(entry, key, valueId) : 0 /* Continue */;
62603
+ };
62604
+ }
62605
+ if (requiresInvocationReset) {
62606
+ return (entry, key) => {
62607
+ innerScope.resetInvocationLocals(parameterCount);
62608
+ bindValues(entry, key);
62609
+ return finishBody(innerScope);
62610
+ };
62611
+ }
62612
+ return (entry, key) => {
62613
+ bindValues(entry, key);
62614
+ return finishBody(innerScope);
62615
+ };
62616
+ }
62617
+ function callbackBodyMayAddInvocationLocals(instructions, parameterIds) {
62618
+ return instructions.some((instruction) => {
62619
+ switch (instruction.type) {
62620
+ case "variable" /* variable */:
62621
+ return !parameterIds.has(instruction.variable.id);
62622
+ case "assign" /* assign */:
62623
+ return instruction.target.pointer.type === "variable" /* variable */ && !parameterIds.has(instruction.target.pointer.variableId);
62624
+ case "if" /* if */:
62625
+ case "for" /* for */:
62626
+ case "forEach" /* forEach */:
62627
+ case "switch" /* switch */:
62628
+ case "try" /* try */:
62629
+ return true;
62630
+ default:
62631
+ return false;
62632
+ }
62633
+ });
62634
+ }
62635
+ function isListCollection(collection) {
62636
+ if (Array.isArray(collection)) return true;
62637
+ if (typeof collection === "object" && collection !== null) return false;
62638
+ throw new NSGetterRuntimeError(
62639
+ "Collection callback receiver must be a present List or Dictionary value."
62640
+ );
62641
+ }
62426
62642
  function evalFunction(fn, scope, ctx) {
62427
62643
  switch (fn.type) {
62428
62644
  case "classConstructor" /* classConstructor */:
@@ -62609,34 +62825,15 @@ function evalFunction(fn, scope, ctx) {
62609
62825
  case "where" /* where */: {
62610
62826
  const c = evalPointer(fn.info.collectionPointer, scope, ctx);
62611
62827
  const innerFn = fn.info.function;
62612
- const isList = Array.isArray(c);
62828
+ const isList = isListCollection(c);
62613
62829
  const out = isList ? [] : {};
62614
- const useFreshCallbackScope = ctx.__collectionCallbackStrategy === "fresh";
62615
- const callbackScope = useFreshCallbackScope ? null : createChildScope(scope);
62616
- const callbackOptions = useFreshCallbackScope ? null : evaluationOptions(ctx, false);
62617
- const callbackParameterCount = innerFn.parameters.length === 1 || innerFn.parameters.length === 2 ? innerFn.parameters.length : 0;
62618
- iterateCollection(c, ctx, (entry, key, valueId) => {
62619
- consumeBudget(ctx, "workUnits", 1, "work unit");
62620
- const innerScope = callbackScope ?? pushParams(scope, innerFn.parameters, [key, entry], isList);
62621
- if (callbackScope !== null) {
62622
- callbackScope.resetInvocationLocals(callbackParameterCount);
62623
- if (callbackParameterCount === 1) {
62624
- callbackScope.setLocal(innerFn.parameters[0].id, entry);
62625
- } else if (callbackParameterCount === 2) {
62626
- callbackScope.setLocal(
62627
- innerFn.parameters[0].id,
62628
- isList ? Number(key) : String(key)
62629
- );
62630
- callbackScope.setLocal(innerFn.parameters[1].id, entry);
62631
- }
62632
- }
62633
- const result = evalInstructions(
62634
- innerFn.instructions,
62635
- innerScope,
62636
- ctx,
62637
- callbackOptions ?? evaluationOptions(ctx, false)
62638
- );
62639
- if (result.kind === "return" && result.value === true) {
62830
+ const callback = prepareCollectionCallback(
62831
+ innerFn,
62832
+ scope,
62833
+ ctx,
62834
+ isList,
62835
+ "predicate",
62836
+ (entry, key, valueId) => {
62640
62837
  consumeBudget(
62641
62838
  ctx,
62642
62839
  "producedCollectionEntries",
@@ -62648,53 +62845,38 @@ function evalFunction(fn, scope, ctx) {
62648
62845
  } else {
62649
62846
  out[String(key)] = valueId ?? entry;
62650
62847
  }
62848
+ return 0 /* Continue */;
62651
62849
  }
62652
- return 0 /* Continue */;
62653
- });
62850
+ );
62851
+ iterateCollection(c, ctx, callback);
62654
62852
  return out;
62655
62853
  }
62656
62854
  case "first" /* first */:
62657
62855
  case "firstOrDefault" /* firstOrDefault */: {
62658
62856
  const c = evalPointer(fn.info.collectionPointer, scope, ctx);
62659
62857
  const innerFn = fn.info.function ?? null;
62660
- const isList = Array.isArray(c);
62858
+ const isList = isListCollection(c);
62661
62859
  const sentinel = /* @__PURE__ */ Symbol("not-found");
62662
- const useFreshCallbackScope = ctx.__collectionCallbackStrategy === "fresh";
62663
- const callbackScope = innerFn === null || useFreshCallbackScope ? null : createChildScope(scope);
62664
- const callbackOptions = innerFn === null || useFreshCallbackScope ? null : evaluationOptions(ctx, false);
62665
- const callbackParameterCount = innerFn !== null && (innerFn.parameters.length === 1 || innerFn.parameters.length === 2) ? innerFn.parameters.length : 0;
62666
62860
  let found = sentinel;
62667
- iterateCollection(c, ctx, (entry, key) => {
62668
- if (innerFn === null) {
62861
+ if (innerFn === null) {
62862
+ iterateCollection(c, ctx, (entry) => {
62669
62863
  found = entry;
62670
62864
  return 1 /* Break */;
62671
- }
62672
- consumeBudget(ctx, "workUnits", 1, "work unit");
62673
- const innerScope = callbackScope ?? pushParams(scope, innerFn.parameters, [key, entry], isList);
62674
- if (callbackScope !== null) {
62675
- callbackScope.resetInvocationLocals(callbackParameterCount);
62676
- if (callbackParameterCount === 1) {
62677
- callbackScope.setLocal(innerFn.parameters[0].id, entry);
62678
- } else if (callbackParameterCount === 2) {
62679
- callbackScope.setLocal(
62680
- innerFn.parameters[0].id,
62681
- isList ? Number(key) : String(key)
62682
- );
62683
- callbackScope.setLocal(innerFn.parameters[1].id, entry);
62684
- }
62685
- }
62686
- const result = evalInstructions(
62687
- innerFn.instructions,
62688
- innerScope,
62865
+ });
62866
+ } else {
62867
+ const callback = prepareCollectionCallback(
62868
+ innerFn,
62869
+ scope,
62689
62870
  ctx,
62690
- callbackOptions ?? evaluationOptions(ctx, false)
62871
+ isList,
62872
+ "predicate",
62873
+ (entry) => {
62874
+ found = entry;
62875
+ return 1 /* Break */;
62876
+ }
62691
62877
  );
62692
- if (result.kind === "return" && result.value === true) {
62693
- found = entry;
62694
- return 1 /* Break */;
62695
- }
62696
- return 0 /* Continue */;
62697
- });
62878
+ iterateCollection(c, ctx, callback);
62879
+ }
62698
62880
  if (found !== sentinel) return found;
62699
62881
  if (fn.type === "first" /* first */) {
62700
62882
  throw new NSGetterRuntimeError(
@@ -62705,43 +62887,26 @@ function evalFunction(fn, scope, ctx) {
62705
62887
  }
62706
62888
  case "select" /* select */: {
62707
62889
  const c = evalPointer(fn.info.collectionPointer, scope, ctx);
62708
- const useFreshCallbackScope = ctx.__collectionCallbackStrategy === "fresh";
62709
- const callbackScope = useFreshCallbackScope ? null : createChildScope(scope);
62710
- const callbackOptions = useFreshCallbackScope ? null : evaluationOptions(ctx, false);
62711
62890
  const innerFn = fn.info.function;
62712
- const callbackParameterCount = innerFn.parameters.length === 1 || innerFn.parameters.length === 2 ? innerFn.parameters.length : 0;
62713
- const isList = Array.isArray(c);
62891
+ const isList = isListCollection(c);
62892
+ const callback = prepareCollectionCallback(
62893
+ innerFn,
62894
+ scope,
62895
+ ctx,
62896
+ isList,
62897
+ "projection"
62898
+ );
62714
62899
  const out = [];
62715
- iterateCollection(c, ctx, (entry, key) => {
62900
+ iterateCollection(c, ctx, (entry, key, valueId) => {
62716
62901
  consumeBudget(ctx, "workUnits", 1, "work unit");
62717
- const innerScope = callbackScope ?? pushParams(scope, innerFn.parameters, [key, entry], isList);
62718
- if (callbackScope !== null) {
62719
- callbackScope.resetInvocationLocals(callbackParameterCount);
62720
- if (callbackParameterCount === 1) {
62721
- callbackScope.setLocal(innerFn.parameters[0].id, entry);
62722
- } else if (callbackParameterCount === 2) {
62723
- callbackScope.setLocal(
62724
- innerFn.parameters[0].id,
62725
- isList ? Number(key) : String(key)
62726
- );
62727
- callbackScope.setLocal(innerFn.parameters[1].id, entry);
62728
- }
62729
- }
62730
- const result = evalInstructions(
62731
- innerFn.instructions,
62732
- innerScope,
62902
+ const value = callback(entry, key, valueId);
62903
+ consumeBudget(
62733
62904
  ctx,
62734
- callbackOptions ?? evaluationOptions(ctx, false)
62905
+ "producedCollectionEntries",
62906
+ 1,
62907
+ "produced collection entry"
62735
62908
  );
62736
- if (result.kind === "return") {
62737
- consumeBudget(
62738
- ctx,
62739
- "producedCollectionEntries",
62740
- 1,
62741
- "produced collection entry"
62742
- );
62743
- out.push(result.value);
62744
- }
62909
+ out.push(value);
62745
62910
  return 0 /* Continue */;
62746
62911
  });
62747
62912
  return out;
@@ -65294,17 +65459,6 @@ function iterateCollection(c, ctx, callback) {
65294
65459
  return callback(entry, key, valueId);
65295
65460
  });
65296
65461
  }
65297
- function pushParams(parent, parameters, positional, isList) {
65298
- const child = createChildScope(parent);
65299
- if (parameters.length === 1) {
65300
- child.setLocal(parameters[0].id, positional[1]);
65301
- } else if (parameters.length === 2) {
65302
- const first = isList ? Number(positional[0]) : String(positional[0]);
65303
- child.setLocal(parameters[0].id, first);
65304
- child.setLocal(parameters[1].id, positional[1]);
65305
- }
65306
- return child;
65307
- }
65308
65462
  var DELEGATE_LEXICAL_THIS, DELEGATE_LEXICAL_ROOT, NonCatchableNSGetterRuntimeError, NativeFunctionDelegateUnavailableError, CorruptNeoScriptIRError, NeoScriptResourceLimitError, NeoScriptWallClockTimeoutError, DEFAULT_NEO_SCRIPT_EXECUTION_BUDGET_LIMITS, liveListIndexesByProject, evaluatorOwnershipCachesByBase, MAX_CONSTRUCTION_DEPTH, MAX_LOOP_ITERATIONS, resolutionCacheByMembers, NO_SCHEMA_REVISION, LazyValueOverlay, READONLY_FOREACH_BINDING_ERROR, READONLY_CATCH_BINDING_ERROR;
65309
65463
  var init_evaluateNSGetter = __esm({
65310
65464
  "../src/view-models/neoscript-evaluator/evaluateNSGetter.ts"() {
@@ -65318,6 +65472,7 @@ var init_evaluateNSGetter = __esm({
65318
65472
  init_neoscript();
65319
65473
  init_NSGetterRuntimeError();
65320
65474
  init_value_row_owner_members();
65475
+ init_src();
65321
65476
  init_decimal();
65322
65477
  init_members();
65323
65478
  init_core();
@@ -83124,6 +83279,15 @@ function applyProjectVersionWriteChanges(document, changes) {
83124
83279
  "constructor"
83125
83280
  ),
83126
83281
  values: applyArrayChanges(document.values ?? [], changes, "value"),
83282
+ // Project-file staging finalizes pending uploads before schema
83283
+ // preparation. Defaults and authored instance graphs may reference a file
83284
+ // created by the same source transaction, so every construction pass must
83285
+ // see that finalized row in its post-write document.
83286
+ projectFiles: applyArrayChanges(
83287
+ document.projectFiles ?? [],
83288
+ changes,
83289
+ "project-file"
83290
+ ),
83127
83291
  internalRecordRelations: applyArrayChanges(
83128
83292
  document.internalRecordRelations ?? [],
83129
83293
  changes,
@@ -100901,7 +101065,11 @@ async function prepareLocalPushArtifactsV4(workspace, status, onPhase = () => vo
100901
101065
  );
100902
101066
  preparedChanges = assertServerPreparationSucceeds({
100903
101067
  workspace,
100904
- changes: transportChanges,
101068
+ changes: completeLocallyStagedProjectFileChanges({
101069
+ projectId: workspace.config.projectId,
101070
+ changes: transportChanges,
101071
+ stagedRecordIds: new Set(preparedFiles.map((file) => file.recordId))
101072
+ }),
100905
101073
  authoredValueSeeds: transportSeeds,
100906
101074
  sourceByRecord: status.reconstructed,
100907
101075
  localInitializerMaterialization: pendingAssignment.localInitializerMaterialization,
@@ -101024,6 +101192,33 @@ async function prepareLocalCandidateV4(workspace, options = {}) {
101024
101192
  preparedLocal
101025
101193
  };
101026
101194
  }
101195
+ function completeLocallyStagedProjectFileChanges(args) {
101196
+ return args.changes.map((change) => {
101197
+ if (!args.stagedRecordIds.has(change.recordId)) return change;
101198
+ if (change.recordKind !== "project-file") {
101199
+ throw new Error(
101200
+ `Staged project file ${change.recordId} matched a ${change.recordKind} change.`
101201
+ );
101202
+ }
101203
+ if (!isObjectRecord2(change.nextData)) {
101204
+ throw new Error(
101205
+ `Staged project file ${change.recordId} has no local source record.`
101206
+ );
101207
+ }
101208
+ const completed = {
101209
+ ...change,
101210
+ nextData: {
101211
+ ...change.nextData,
101212
+ id: change.recordId,
101213
+ projectId: args.projectId,
101214
+ status: "uploaded",
101215
+ storageKey: `local-candidate/${change.recordId}`,
101216
+ storageETag: "local-candidate"
101217
+ }
101218
+ };
101219
+ return completed;
101220
+ });
101221
+ }
101027
101222
  function transportChangesForStatus(status) {
101028
101223
  return status.changes.map((change) => ({
101029
101224
  recordKind: change.recordKind,
@@ -102478,7 +102673,7 @@ var init_registry2 = __esm({
102478
102673
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
102479
102674
  formatVersion: 3,
102480
102675
  contractVersion: "3.9",
102481
- cliVersion: "0.26.2",
102676
+ cliVersion: "0.26.4",
102482
102677
  projectFileUploadBatchSize: 32,
102483
102678
  documentRecords: {
102484
102679
  member: {
@@ -103792,6 +103987,7 @@ import {
103792
103987
  resolve as resolve3,
103793
103988
  sep as sep5
103794
103989
  } from "node:path";
103990
+ import { isDeepStrictEqual } from "node:util";
103795
103991
  function isRecord10(value) {
103796
103992
  return typeof value === "object" && value !== null && !Array.isArray(value);
103797
103993
  }
@@ -103875,9 +104071,46 @@ function stableTestValue(value) {
103875
104071
  function display(value) {
103876
104072
  return stableTestValue(value);
103877
104073
  }
103878
- function assertMatcher(condition, expectationValue, message, expected, received = expectationValue.actual) {
103879
- if (condition === expectationValue.negated)
103880
- throw new NeoTestAssertionError(message, expected, received);
104074
+ function matcherFailureMessage(kind, expectationValue, expected, received, details) {
104075
+ const actual = expectationValue.actual;
104076
+ const negation = expectationValue.negated ? "not " : "";
104077
+ switch (kind) {
104078
+ case "toBe":
104079
+ return `Expected ${display(actual)} ${negation}to be ${display(expected)}.`;
104080
+ case "toEqual":
104081
+ return `Expected ${display(actual)} ${negation}to equal ${display(expected)}.`;
104082
+ case "toBeNull":
104083
+ return `Expected ${display(actual)} ${negation}to be null.`;
104084
+ case "boolean":
104085
+ return `Expected ${display(actual)} ${negation}to be ${String(expected)}.`;
104086
+ case "numeric":
104087
+ return `Expected ${display(actual)} ${negation}to satisfy numeric comparison with ${display(expected)}.`;
104088
+ case "toContain":
104089
+ return `Expected ${display(actual)} ${negation}to contain ${display(expected)}.`;
104090
+ case "toHaveLength":
104091
+ return `Expected length ${String(expected)}, received ${String(received)}.`;
104092
+ case "toMatchObject":
104093
+ return `Expected ${display(actual)} ${negation}to match ${display(expected)}.`;
104094
+ case "toThrow":
104095
+ return `Expected callback ${negation}to throw${details === void 0 ? "" : ` a message containing ${display(details)}`}.`;
104096
+ case "mock":
104097
+ return `Expected mock ${negation}to match recorded calls ${display(details)}.`;
104098
+ }
104099
+ }
104100
+ function assertMatcher(condition, expectationValue, messageKind, expected, received = expectationValue.actual, details) {
104101
+ if (condition === expectationValue.negated) {
104102
+ throw new NeoTestAssertionError(
104103
+ matcherFailureMessage(
104104
+ messageKind,
104105
+ expectationValue,
104106
+ expected,
104107
+ received,
104108
+ details
104109
+ ),
104110
+ expected,
104111
+ received
104112
+ );
104113
+ }
103881
104114
  }
103882
104115
  function errorMessage2(error) {
103883
104116
  return error instanceof Error ? error.message : String(error);
@@ -104426,8 +104659,7 @@ function mockResponse(environment, mock, selected2, call) {
104426
104659
  };
104427
104660
  }
104428
104661
  function partialObjectMatch(actual, expected) {
104429
- if (!isRecord10(expected))
104430
- return stableTestValue(actual) === stableTestValue(expected);
104662
+ if (!isRecord10(expected)) return isDeepStrictEqual(actual, expected);
104431
104663
  if (!isRecord10(actual)) return false;
104432
104664
  return Object.entries(expected).every(
104433
104665
  ([key, value]) => partialObjectMatch(actual[key], value)
@@ -104584,56 +104816,28 @@ function interceptTestCallCore(environment, call) {
104584
104816
  }
104585
104817
  return { handled: true, value: null };
104586
104818
  }
104587
- const matcherIds = /* @__PURE__ */ new Set([
104588
- NEO_TEST_IDS.toBe,
104589
- NEO_TEST_IDS.toEqual,
104590
- NEO_TEST_IDS.toBeNull,
104591
- NEO_TEST_IDS.toBeTrue,
104592
- NEO_TEST_IDS.toBeFalse,
104593
- NEO_TEST_IDS.toBeGreaterThan,
104594
- NEO_TEST_IDS.toBeGreaterThanOrEqual,
104595
- NEO_TEST_IDS.toBeLessThan,
104596
- NEO_TEST_IDS.toBeLessThanOrEqual,
104597
- NEO_TEST_IDS.toContain,
104598
- NEO_TEST_IDS.toHaveLength,
104599
- NEO_TEST_IDS.toMatchObject,
104600
- NEO_TEST_IDS.toThrow,
104601
- NEO_TEST_IDS.toHaveBeenCalled,
104602
- NEO_TEST_IDS.toHaveBeenCalledTimes,
104603
- NEO_TEST_IDS.toHaveBeenCalledWith
104604
- ]);
104605
- if (!matcherIds.has(call.memberId)) return { handled: false };
104819
+ if (!MATCHER_IDS.has(call.memberId)) return { handled: false };
104606
104820
  const expectationValue = readExpectation(call.receiver);
104607
104821
  const actual = expectationValue.actual;
104608
104822
  if (call.memberId === NEO_TEST_IDS.toBe) {
104609
104823
  assertMatcher(
104610
104824
  Object.is(actual, call.args[0]),
104611
104825
  expectationValue,
104612
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to be ${display(call.args[0])}.`,
104826
+ "toBe",
104613
104827
  call.args[0]
104614
104828
  );
104615
104829
  } else if (call.memberId === NEO_TEST_IDS.toEqual) {
104616
104830
  assertMatcher(
104617
- stableTestValue(actual) === stableTestValue(call.args[0]),
104831
+ isDeepStrictEqual(actual, call.args[0]),
104618
104832
  expectationValue,
104619
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to equal ${display(call.args[0])}.`,
104833
+ "toEqual",
104620
104834
  call.args[0]
104621
104835
  );
104622
104836
  } else if (call.memberId === NEO_TEST_IDS.toBeNull) {
104623
- assertMatcher(
104624
- actual === null,
104625
- expectationValue,
104626
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to be null.`,
104627
- null
104628
- );
104837
+ assertMatcher(actual === null, expectationValue, "toBeNull", null);
104629
104838
  } else if (call.memberId === NEO_TEST_IDS.toBeTrue || call.memberId === NEO_TEST_IDS.toBeFalse) {
104630
104839
  const expected = call.memberId === NEO_TEST_IDS.toBeTrue;
104631
- assertMatcher(
104632
- actual === expected,
104633
- expectationValue,
104634
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to be ${String(expected)}.`,
104635
- expected
104636
- );
104840
+ assertMatcher(actual === expected, expectationValue, "boolean", expected);
104637
104841
  } else if (call.memberId === NEO_TEST_IDS.toBeGreaterThan || call.memberId === NEO_TEST_IDS.toBeGreaterThanOrEqual || call.memberId === NEO_TEST_IDS.toBeLessThan || call.memberId === NEO_TEST_IDS.toBeLessThanOrEqual) {
104638
104842
  const left = Number(actual);
104639
104843
  const right = Number(call.args[0]);
@@ -104641,25 +104845,18 @@ function interceptTestCallCore(environment, call) {
104641
104845
  assertMatcher(
104642
104846
  Number.isFinite(left) && Number.isFinite(right) && matches,
104643
104847
  expectationValue,
104644
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to satisfy numeric comparison with ${display(call.args[0])}.`,
104848
+ "numeric",
104645
104849
  call.args[0]
104646
104850
  );
104647
104851
  } else if (call.memberId === NEO_TEST_IDS.toContain) {
104648
- const contains2 = typeof actual === "string" ? actual.includes(String(call.args[0])) : Array.isArray(actual) && actual.some(
104649
- (entry) => stableTestValue(entry) === stableTestValue(call.args[0])
104650
- );
104651
- assertMatcher(
104652
- contains2,
104653
- expectationValue,
104654
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to contain ${display(call.args[0])}.`,
104655
- call.args[0]
104656
- );
104852
+ const contains2 = typeof actual === "string" ? actual.includes(String(call.args[0])) : Array.isArray(actual) && actual.some((entry) => isDeepStrictEqual(entry, call.args[0]));
104853
+ assertMatcher(contains2, expectationValue, "toContain", call.args[0]);
104657
104854
  } else if (call.memberId === NEO_TEST_IDS.toHaveLength) {
104658
104855
  const length = typeof actual === "string" || Array.isArray(actual) ? actual.length : isRecord10(actual) ? Object.keys(actual).length : -1;
104659
104856
  assertMatcher(
104660
104857
  length === call.args[0],
104661
104858
  expectationValue,
104662
- `Expected length ${String(call.args[0])}, received ${String(length)}.`,
104859
+ "toHaveLength",
104663
104860
  call.args[0],
104664
104861
  length
104665
104862
  );
@@ -104667,7 +104864,7 @@ function interceptTestCallCore(environment, call) {
104667
104864
  assertMatcher(
104668
104865
  partialObjectMatch(actual, call.args[0]),
104669
104866
  expectationValue,
104670
- `Expected ${display(actual)} ${expectationValue.negated ? "not " : ""}to match ${display(call.args[0])}.`,
104867
+ "toMatchObject",
104671
104868
  call.args[0]
104672
104869
  );
104673
104870
  } else if (call.memberId === NEO_TEST_IDS.toThrow) {
@@ -104683,9 +104880,10 @@ function interceptTestCallCore(environment, call) {
104683
104880
  assertMatcher(
104684
104881
  matches,
104685
104882
  expectationValue,
104686
- `Expected callback ${expectationValue.negated ? "not " : ""}to throw${pattern === void 0 ? "" : ` a message containing ${display(pattern)}`}.`,
104883
+ "toThrow",
104687
104884
  pattern ?? "a catchable NeoScript error",
104688
- thrown === void 0 ? "no throw" : errorMessage2(thrown)
104885
+ thrown === void 0 ? "no throw" : errorMessage2(thrown),
104886
+ pattern
104689
104887
  );
104690
104888
  } else {
104691
104889
  const handle = readMockHandle(actual);
@@ -104695,15 +104893,14 @@ function interceptTestCallCore(environment, call) {
104695
104893
  "Expected value is not an active mock handle."
104696
104894
  );
104697
104895
  }
104698
- const matches = call.memberId === NEO_TEST_IDS.toHaveBeenCalled ? mock.calls.length > 0 : call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? mock.calls.length === call.args[0] : mock.calls.some(
104699
- (args) => stableTestValue(args) === stableTestValue(call.args)
104700
- );
104896
+ const matches = call.memberId === NEO_TEST_IDS.toHaveBeenCalled ? mock.calls.length > 0 : call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? mock.calls.length === call.args[0] : mock.calls.some((args) => isDeepStrictEqual(args, call.args));
104701
104897
  assertMatcher(
104702
104898
  matches,
104703
104899
  expectationValue,
104704
- `Expected mock ${expectationValue.negated ? "not " : ""}to match recorded calls ${display(mock.calls)}.`,
104900
+ "mock",
104705
104901
  call.memberId === NEO_TEST_IDS.toHaveBeenCalled ? "at least one call" : call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? call.args[0] : call.args,
104706
- call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? mock.calls.length : mock.calls
104902
+ call.memberId === NEO_TEST_IDS.toHaveBeenCalledTimes ? mock.calls.length : mock.calls,
104903
+ mock.calls
104707
104904
  );
104708
104905
  }
104709
104906
  return { handled: true, value: null };
@@ -104994,8 +105191,24 @@ function maintainNeoTestBuildCache(root, maxBytes = TEST_BUILD_CACHE_LIMIT_BYTES
104994
105191
  }
104995
105192
  }
104996
105193
  function formatTestDuration(durationMs) {
105194
+ if (durationMs >= 1e3) {
105195
+ const seconds = durationMs / 1e3;
105196
+ return `${seconds < 10 ? seconds.toFixed(2) : seconds.toFixed(1)}s`;
105197
+ }
104997
105198
  return `${durationMs < 10 ? durationMs.toFixed(2) : durationMs.toFixed(1)}ms`;
104998
105199
  }
105200
+ function paintTestDuration(durationMs) {
105201
+ const formatted = formatTestDuration(durationMs);
105202
+ return durationMs >= 300 ? color.yellow(formatted) : color.dim(formatted);
105203
+ }
105204
+ function formatStatusCounts(passed, failed, total, skipped = 0) {
105205
+ const parts = [
105206
+ failed > 0 ? color.red(`${failed} failed`) : null,
105207
+ passed > 0 ? color.green(`${passed} passed`) : null,
105208
+ skipped > 0 ? color.yellow(`${skipped} skipped`) : null
105209
+ ].filter((part) => part !== null);
105210
+ return `${parts.join(color.dim(" | "))} ${color.dim(`(${total})`)}`;
105211
+ }
104999
105212
  async function runTest(workspace, options, dependencies = {}) {
105000
105213
  const started = Date.now();
105001
105214
  const performanceStarted = performance.now();
@@ -105025,7 +105238,29 @@ async function runTest(workspace, options, dependencies = {}) {
105025
105238
  }
105026
105239
  };
105027
105240
  process.on("SIGINT", handleSigint);
105241
+ const phaseDurations = {
105242
+ prepare: 0,
105243
+ select: 0,
105244
+ compile: 0,
105245
+ register: 0,
105246
+ execute: 0,
105247
+ finalize: 0
105248
+ };
105028
105249
  let phase = "prepare";
105250
+ let phaseStarted = performanceStarted;
105251
+ const enterPhase = (next, label) => {
105252
+ const finished = performance.now();
105253
+ phaseDurations[phase] += finished - phaseStarted;
105254
+ phase = next;
105255
+ phaseStarted = finished;
105256
+ dependencies.progress?.update(label);
105257
+ };
105258
+ const finishPhase = () => {
105259
+ const finished = performance.now();
105260
+ phaseDurations[phase] += finished - phaseStarted;
105261
+ phaseStarted = finished;
105262
+ };
105263
+ dependencies.progress?.update("Preparing test project\u2026");
105029
105264
  try {
105030
105265
  let candidate = dependencies.prepareLocalCandidate === void 0 ? preparedHookCandidate(workspace) : null;
105031
105266
  let candidateInputFingerprint = null;
@@ -105037,7 +105272,11 @@ async function runTest(workspace, options, dependencies = {}) {
105037
105272
  candidate = cachedTestCandidate(workspace, candidateInputFingerprint);
105038
105273
  }
105039
105274
  if (candidate === null) {
105040
- const prepared = dependencies.prepareLocalCandidate === void 0 ? await prepareLocalCandidateV4(workspace) : await dependencies.prepareLocalCandidate(workspace);
105275
+ const prepared = dependencies.prepareLocalCandidate === void 0 ? await prepareLocalCandidateV4(workspace, {
105276
+ onPhase: (label) => {
105277
+ dependencies.progress?.update(label);
105278
+ }
105279
+ }) : await dependencies.prepareLocalCandidate(workspace);
105041
105280
  if (prepared.document === null) {
105042
105281
  throw new NeoTestReportWriteError(
105043
105282
  "Local test preparation did not produce a project document."
@@ -105069,7 +105308,7 @@ async function runTest(workspace, options, dependencies = {}) {
105069
105308
  rawDocument,
105070
105309
  candidate.documentSha256
105071
105310
  );
105072
- phase = "select";
105311
+ enterPhase("select", "Selecting test files\u2026");
105073
105312
  let selectedPaths;
105074
105313
  try {
105075
105314
  selectedPaths = selectedSpecPaths(workspace, options.selectors);
@@ -105084,7 +105323,7 @@ async function runTest(workspace, options, dependencies = {}) {
105084
105323
  "No .spec.neo files matched. Pass --passWithNoTests to treat this as success."
105085
105324
  );
105086
105325
  }
105087
- phase = "compile";
105326
+ enterPhase("compile", "Compiling test files\u2026");
105088
105327
  specs = selectedPaths.map(
105089
105328
  (path) => compileSpec(workspace, rawDocument, path, compilationHash)
105090
105329
  );
@@ -105098,7 +105337,7 @@ async function runTest(workspace, options, dependencies = {}) {
105098
105337
  );
105099
105338
  }
105100
105339
  }
105101
- phase = "register";
105340
+ enterPhase("register", "Registering tests\u2026");
105102
105341
  const registered = specs.map((spec) => registerSpec(spec, rawDocument));
105103
105342
  const selectedTests = registered.flatMap((entry) => entry.tests).filter((test) => pattern === null || pattern.test(test.fullName));
105104
105343
  for (const entry of registered) {
@@ -105115,7 +105354,7 @@ async function runTest(workspace, options, dependencies = {}) {
105115
105354
  );
105116
105355
  }
105117
105356
  const selectedIds = new Set(selectedTests.map((test) => test.id));
105118
- phase = "execute";
105357
+ enterPhase("execute", "Running tests\u2026");
105119
105358
  const timeoutMs = options.timeoutMs ?? workspace.config.test?.timeoutMs ?? 5e3;
105120
105359
  for (const entry of registered) {
105121
105360
  const executed = await executeRegisteredSpec(
@@ -105133,6 +105372,7 @@ async function runTest(workspace, options, dependencies = {}) {
105133
105372
  if (results.some((result) => result.status === "failed") || [...fileFailuresByPath.values()].some((failures) => failures.length > 0)) {
105134
105373
  exitCode = 1;
105135
105374
  }
105375
+ enterPhase("finalize", "Finalizing test report\u2026");
105136
105376
  const buildManifest = {
105137
105377
  version: 1,
105138
105378
  cliVersion: PROJECT_SCHEMA_CONTRACT.cliVersion,
@@ -105168,6 +105408,8 @@ async function runTest(workspace, options, dependencies = {}) {
105168
105408
  related: []
105169
105409
  });
105170
105410
  }
105411
+ finishPhase();
105412
+ dependencies.progress?.stop();
105171
105413
  process.off("SIGINT", handleSigint);
105172
105414
  const failed = results.filter((result) => result.status === "failed").length;
105173
105415
  const selectedTestTotal = [...selectedTestCountByFile.values()].reduce(
@@ -105221,6 +105463,14 @@ async function runTest(workspace, options, dependencies = {}) {
105221
105463
  sharedStartupDurationMs,
105222
105464
  testDurationMs
105223
105465
  },
105466
+ timings: {
105467
+ prepareMs: phaseDurations.prepare,
105468
+ selectMs: phaseDurations.select,
105469
+ compileMs: phaseDurations.compile,
105470
+ registerMs: phaseDurations.register,
105471
+ executeMs: phaseDurations.execute,
105472
+ finalizeMs: phaseDurations.finalize
105473
+ },
105224
105474
  testFiles,
105225
105475
  diagnostics,
105226
105476
  startedAt
@@ -105244,33 +105494,63 @@ async function runTest(workspace, options, dependencies = {}) {
105244
105494
  if (options.reporter === "json") {
105245
105495
  if (options.outputFile === null) process.stdout.write(serialized);
105246
105496
  } else {
105497
+ console.log("");
105247
105498
  console.log(
105248
- `\u21BB shared startup (${formatTestDuration(report.summary.sharedStartupDurationMs)})`
105499
+ ` ${color.bold("RUN")} ${color.dim(`neo ${PROJECT_SCHEMA_CONTRACT.cliVersion} ${workspace.root}`)}`
105249
105500
  );
105250
- for (const result of results) {
105501
+ console.log("");
105502
+ console.log(
105503
+ ` ${color.cyan("\u21BB")} ${color.dim("shared startup")} ${paintTestDuration(report.summary.sharedStartupDurationMs)}`
105504
+ );
105505
+ for (const file of testFiles) {
105506
+ const fileDurationMs = file.tests.reduce(
105507
+ (sum, test) => sum + test.durationMs,
105508
+ 0
105509
+ );
105510
+ const fileSymbol = file.status === "passed" ? color.green("\u2713") : file.status === "failed" ? color.red("\xD7") : color.yellow("!");
105511
+ const testLabel = `${file.tests.length} ${file.tests.length === 1 ? "test" : "tests"}`;
105251
105512
  console.log(
105252
- `${result.status === "passed" ? "\u2713" : "\u2717"} ${result.file} > ${result.name} (total ${formatTestDuration(result.durationMs)}; test ${formatTestDuration(result.testDurationMs)}; startup ${formatTestDuration(result.startupDurationMs)})`
105513
+ ` ${fileSymbol} ${color.bold(file.path)} ${color.dim(`(${testLabel})`)} ${paintTestDuration(fileDurationMs)}`
105253
105514
  );
105254
- for (const failure of result.failures)
105255
- console.log(` ${failure.message}`);
105256
- }
105257
- for (const file of testFiles) {
105515
+ for (const result of file.tests) {
105516
+ const resultSymbol = result.status === "passed" ? color.green("\u2713") : color.red("\xD7");
105517
+ console.log(
105518
+ ` ${resultSymbol} ${result.name} ${color.dim("total")} ${paintTestDuration(result.durationMs)} ${color.dim(`(test ${formatTestDuration(result.testDurationMs)}, startup ${formatTestDuration(result.startupDurationMs)})`)}`
105519
+ );
105520
+ for (const failure of result.failures) {
105521
+ console.log(` ${color.red(failure.message)}`);
105522
+ }
105523
+ }
105258
105524
  if (file.status === "interrupted") {
105259
105525
  console.log(
105260
- `! ${file.path} > interrupted before all selected tests ran`
105526
+ ` ${color.yellow("!")} interrupted before all selected tests ran`
105261
105527
  );
105262
105528
  }
105263
105529
  for (const failure of file.failures) {
105264
105530
  console.log(
105265
- `\u2717 ${file.path} > ${failure.frames[0]?.member ?? "suite hook"}`
105531
+ ` ${color.red("\xD7")} ${failure.frames[0]?.member ?? "suite hook"}`
105266
105532
  );
105267
- console.log(` ${failure.message}`);
105533
+ console.log(` ${color.red(failure.message)}`);
105268
105534
  }
105269
105535
  }
105270
- for (const diagnostic of diagnostics)
105271
- console.log(`\u2717 ${diagnostic.message}`);
105536
+ for (const diagnostic of diagnostics) {
105537
+ console.log(` ${color.red("\xD7")} ${diagnostic.message}`);
105538
+ }
105539
+ console.log("");
105540
+ const passedFiles = testFiles.filter(
105541
+ (file) => file.status === "passed"
105542
+ ).length;
105543
+ const failedFiles = testFiles.filter(
105544
+ (file) => file.status === "failed"
105545
+ ).length;
105546
+ console.log(
105547
+ ` ${color.bold(pad("Test Files", 11))} ${formatStatusCounts(passedFiles, failedFiles, report.summary.files)}`
105548
+ );
105272
105549
  console.log(
105273
- `${report.summary.passed} passed, ${report.summary.failed} failed (total ${formatTestDuration(report.summary.durationMs)}; test ${formatTestDuration(report.summary.testDurationMs)}; startup ${formatTestDuration(report.summary.startupDurationMs)})`
105550
+ ` ${color.bold(pad("Tests", 11))} ${formatStatusCounts(report.summary.passed, report.summary.failed, report.summary.tests, report.summary.skipped)}`
105551
+ );
105552
+ console.log(
105553
+ ` ${color.bold(pad("Duration", 11))} ${color.bold(`total ${formatTestDuration(report.summary.durationMs)}`)} ${color.dim(`(test ${formatTestDuration(report.summary.testDurationMs)}, startup ${formatTestDuration(report.summary.startupDurationMs)})`)}`
105274
105554
  );
105275
105555
  }
105276
105556
  if (!report.success) process.exitCode = exitCode;
@@ -105317,7 +105597,7 @@ async function inspectNeoTestCompilation(workspace, dependencies = {}) {
105317
105597
  errors
105318
105598
  };
105319
105599
  }
105320
- var TEST_BUILD_CACHE_LIMIT_BYTES, ABANDONED_TEMP_MAX_AGE_MS, TEST_CANDIDATE_CACHE_REVISION, NeoTestUsageError, NeoTestNoTestsError, NeoTestRegistrationError, NeoTestReportWriteError, NeoTestPreparedCandidateError, NeoTestInterruptedError, NeoTestAssertionError, ERROR_SOURCE_POSITIONS, SHARED_EVALUATOR_BASES, REGISTRATION_IDS;
105600
+ var TEST_BUILD_CACHE_LIMIT_BYTES, ABANDONED_TEMP_MAX_AGE_MS, TEST_CANDIDATE_CACHE_REVISION, NeoTestUsageError, NeoTestNoTestsError, NeoTestRegistrationError, NeoTestReportWriteError, NeoTestPreparedCandidateError, NeoTestInterruptedError, NeoTestAssertionError, ERROR_SOURCE_POSITIONS, SHARED_EVALUATOR_BASES, REGISTRATION_IDS, MATCHER_IDS;
105321
105601
  var init_test = __esm({
105322
105602
  "src/commands/test.ts"() {
105323
105603
  "use strict";
@@ -105330,6 +105610,7 @@ var init_test = __esm({
105330
105610
  init_push();
105331
105611
  init_registry2();
105332
105612
  init_push_hook();
105613
+ init_ui();
105333
105614
  TEST_BUILD_CACHE_LIMIT_BYTES = 512 * 1024 * 1024;
105334
105615
  ABANDONED_TEMP_MAX_AGE_MS = 60 * 60 * 1e3;
105335
105616
  TEST_CANDIDATE_CACHE_REVISION = 1;
@@ -105374,6 +105655,24 @@ var init_test = __esm({
105374
105655
  NEO_TEST_IDS.beforeEach,
105375
105656
  NEO_TEST_IDS.afterEach
105376
105657
  ]);
105658
+ MATCHER_IDS = /* @__PURE__ */ new Set([
105659
+ NEO_TEST_IDS.toBe,
105660
+ NEO_TEST_IDS.toEqual,
105661
+ NEO_TEST_IDS.toBeNull,
105662
+ NEO_TEST_IDS.toBeTrue,
105663
+ NEO_TEST_IDS.toBeFalse,
105664
+ NEO_TEST_IDS.toBeGreaterThan,
105665
+ NEO_TEST_IDS.toBeGreaterThanOrEqual,
105666
+ NEO_TEST_IDS.toBeLessThan,
105667
+ NEO_TEST_IDS.toBeLessThanOrEqual,
105668
+ NEO_TEST_IDS.toContain,
105669
+ NEO_TEST_IDS.toHaveLength,
105670
+ NEO_TEST_IDS.toMatchObject,
105671
+ NEO_TEST_IDS.toThrow,
105672
+ NEO_TEST_IDS.toHaveBeenCalled,
105673
+ NEO_TEST_IDS.toHaveBeenCalledTimes,
105674
+ NEO_TEST_IDS.toHaveBeenCalledWith
105675
+ ]);
105377
105676
  }
105378
105677
  });
105379
105678
 
@@ -108939,7 +109238,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
108939
109238
  async function main() {
108940
109239
  const args = parseArgs(process.argv.slice(2));
108941
109240
  if (args.command === "--version") {
108942
- console.log("0.26.2");
109241
+ console.log("0.26.4");
108943
109242
  return;
108944
109243
  }
108945
109244
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -109223,15 +109522,25 @@ async function main() {
109223
109522
  "--testTimeout must be a positive number of milliseconds."
109224
109523
  );
109225
109524
  }
109226
- const { runTest: runTest2 } = await Promise.resolve().then(() => (init_test(), test_exports));
109227
- await runTest2(workspace, {
109228
- selectors: args.positional,
109229
- testNamePattern: stringFlag(args, "testNamePattern"),
109230
- reporter: reporterValue,
109231
- outputFile: stringFlag(args, "outputFile") ?? stringFlag(args, "output-file"),
109232
- passWithNoTests: boolFlag(args, "passWithNoTests"),
109233
- ...timeoutMs === void 0 ? {} : { timeoutMs }
109234
- });
109525
+ const progress = reporterValue === "default" && process.stdout.isTTY === true ? spinner("Preparing test project\u2026") : null;
109526
+ try {
109527
+ const { runTest: runTest2 } = await Promise.resolve().then(() => (init_test(), test_exports));
109528
+ await runTest2(
109529
+ workspace,
109530
+ {
109531
+ selectors: args.positional,
109532
+ testNamePattern: stringFlag(args, "testNamePattern"),
109533
+ reporter: reporterValue,
109534
+ outputFile: stringFlag(args, "outputFile") ?? stringFlag(args, "output-file"),
109535
+ passWithNoTests: boolFlag(args, "passWithNoTests"),
109536
+ ...timeoutMs === void 0 ? {} : { timeoutMs }
109537
+ },
109538
+ progress === null ? {} : { progress }
109539
+ );
109540
+ } catch (error) {
109541
+ progress?.fail("Test run failed.");
109542
+ throw error;
109543
+ }
109235
109544
  return;
109236
109545
  }
109237
109546
  case "dev": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.26.2",
3
+ "version": "0.26.4",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.26.2 -->
12
+ <!-- reviewed-through-cli: 0.26.4 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.26.2 -->
86
+ <!-- reviewed-through-cli: 0.26.4 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale