@unotest/core 0.26.1 → 0.27.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/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.27.0] - 2026-08-31
4
+
5
+ ### Minor Changes
6
+
7
+ - New `computeSchedulesFingerprint(entries)` — sha256 of
8
+ `canonicalScheduleSet` from `@unotest/protocol` (the canon lives
9
+ there, the crypto here). `BundleProvenance` gains optional
10
+ `schedulesFingerprint`, carried into the packed manifest: what lets a
11
+ box compare the schedule set it reads with the set the pusher saw.
12
+
13
+ - c843d21: Schedules from the viewer — for collections and single tests.
14
+
15
+ Right-click a test or a collection in the viewer → **Schedule…**: a
16
+ human schedule builder (frequency select — every 5/15 min, hourly,
17
+ every 2/4/6/12 h, daily, weekly — plus toggle badges for minutes,
18
+ hours and weekdays; multi-select, so `04` + `16` × `00` + `30` runs
19
+ four times a day). Saved to `unotest/schedules.yaml`, versioned next
20
+ to the tests; a box applies it after commit & sync. The Home screen
21
+ gains a `last run | schedule` toggle: in schedule mode tiles are
22
+ tinted by how often a test runs (grey = not scheduled), with an
23
+ upcoming list and per-tile next-run times in your browser's clock.
24
+
25
+ Under the hood:
26
+
27
+ - `unotest/schedules.yaml` is a new schedule registry; `unotest-web
28
+ schedules [--json]` prints it merged with the config's `schedules`
29
+ (the file wins on a duplicate target), so boxes pick both up
30
+ unchanged.
31
+ - A schedule entry may now target a single test: `scenario:
32
+ <ref>` instead of `collection: <name>`.
33
+ - `unotest-web e2e <name> --scheduled[=index]` runs a test the way its
34
+ entry says (env + prepare, exit 94 when prepare fails) — the
35
+ per-test mirror of `collection --scheduled`.
36
+ - Viewer server: `GET/PUT /api/schedules` (validated by the same
37
+ parser the CLI reads the file with).
38
+
39
+ ### Patch Changes
40
+
41
+ - Updated dependencies [c843d21]
42
+ - @unotest/protocol@0.27.0
43
+ - @unotest/dsl@0.27.0
44
+
3
45
  ## [0.26.1] - 2026-08-30
4
46
 
5
47
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { RunArtifact, RunManifest, RunSources, RuntimeStateFile, RuntimeState, DbgCommand, PickerMode, DbgCommandRecord, DbgCommandProbe, IRunQueue, EnqueueRunInput, RunQueueTicketHandle, QueueSnapshot, QueueTicket, RunQueueLease, BundleGitInfo, BundleManifest } from '@unotest/protocol';
1
+ import { RunArtifact, RunManifest, RunSources, RuntimeStateFile, RuntimeState, DbgCommand, PickerMode, DbgCommandRecord, DbgCommandProbe, IRunQueue, EnqueueRunInput, RunQueueTicketHandle, QueueSnapshot, QueueTicket, RunQueueLease, BundleGitInfo, BundleManifest, ScheduleEntry } from '@unotest/protocol';
2
2
  export { isRecord } from '@unotest/protocol';
3
3
  import { ExecutionEvent } from '@unotest/dsl/executor';
4
4
 
@@ -222,6 +222,13 @@ interface E2EFlags {
222
222
  * Empty when --break wasn't passed — caller falls back to the
223
223
  * `.debugger[-suffix].json` breakpoint store. */
224
224
  breakFlag: string[];
225
+ /** Scheduled run (`--scheduled[=index]`): honour the scenario's
226
+ * schedules entry — its env and prepare. Mirrors the collection
227
+ * command's flag. */
228
+ scheduled: boolean;
229
+ /** WHICH schedules entry (zero-based position in the merged list),
230
+ * when the executor pins it. */
231
+ scheduleIndex?: number;
225
232
  }
226
233
  declare function parseE2EFlags(args: string[]): E2EFlags;
227
234
 
@@ -426,6 +433,9 @@ interface BundleProvenance {
426
433
  * ride in the manifest — that is what lets a box say at upload which
427
434
  * of them it cannot supply. */
428
435
  declaredVariables?: readonly string[];
436
+ /** `computeSchedulesFingerprint` of the effective schedules at pack
437
+ * time — see the manifest field's doc in `@unotest/protocol`. */
438
+ schedulesFingerprint?: string;
429
439
  }
430
440
  interface PackedBundle {
431
441
  bundleId: string;
@@ -446,6 +456,13 @@ interface UnpackOptions {
446
456
  }
447
457
  declare function unpackBundle(archive: Uint8Array, options?: UnpackOptions): UnpackedBundle;
448
458
 
459
+ /** sha256 of the canonical effective schedule set — what `bundle push`
460
+ * records in the manifest (`schedulesFingerprint`) and a box re-computes
461
+ * after reading `schedules --json` in its own environment. Equal hashes
462
+ * mean the box arms exactly the set the pusher saw; different hashes
463
+ * mean the project's config computed schedules from its surroundings. */
464
+ declare function computeSchedulesFingerprint(entries: readonly ScheduleEntry[]): string;
465
+
449
466
  /** The archive is not a readable bundle: broken gzip, a truncated tar, a
450
467
  * header whose checksum does not add up, a member that is not a plain
451
468
  * file. Everything a box refuses BEFORE writing anything to disk. */
@@ -481,4 +498,4 @@ declare class BundleIdMismatchError extends UnotestError {
481
498
  constructor(claimed: string, computed: string);
482
499
  }
483
500
 
484
- export { type ArtifactRedactor, type BundleFile, BundleFormatError, BundleIdMismatchError, BundlePathError, type BundleProvenance, BundleTooLargeError, type DebugCommandsWatcherDeps, type DebugControlTarget, type DebugWatcherLogger, type E2EFlags, FsRunQueue, type FsRunQueueOptions, type GitignoreUpdate, type GlobalPoolOptions, type IRunArtifactWriter, JsonlRunArtifactWriter, type JsonlRunArtifactWriterOptions, type PackedBundle, QueueTicketLostError, QueueUnavailableError, QueueWaitAbortedError, type RunHeartbeat, type RuntimeExecState, type RuntimeInspection, type RuntimeInspectionInput, type RuntimeStateExtra, type RuntimeStateWriter, type RuntimeStateWriterDeps, type TarEntry, UnotestError, type UnpackOptions, type UnpackedBundle, type WriteManifestInput, type WriteRunSourcesInput, appendUniqueLines, applyEnvLayers, assertSafeBundlePath, buildRuntimeInspection, computeBundleId, computeTreeSha256, createRunArtifactWriter, createRuntimeStateWriter, currentLocation, extractCallStack, globalPoolFromEnv, leaseEnv, levenshteinDistance, packBundle, parseE2EFlags, readDebuggerBreakpoints, readEnvFile, readEnvLayers, readTar, startDebugCommandsWatcher, startRunHeartbeat, suggestClosest, toProtocolRuntimeState, unpackBundle, writeRunManifest, writeRunSources, writeTar };
501
+ export { type ArtifactRedactor, type BundleFile, BundleFormatError, BundleIdMismatchError, BundlePathError, type BundleProvenance, BundleTooLargeError, type DebugCommandsWatcherDeps, type DebugControlTarget, type DebugWatcherLogger, type E2EFlags, FsRunQueue, type FsRunQueueOptions, type GitignoreUpdate, type GlobalPoolOptions, type IRunArtifactWriter, JsonlRunArtifactWriter, type JsonlRunArtifactWriterOptions, type PackedBundle, QueueTicketLostError, QueueUnavailableError, QueueWaitAbortedError, type RunHeartbeat, type RuntimeExecState, type RuntimeInspection, type RuntimeInspectionInput, type RuntimeStateExtra, type RuntimeStateWriter, type RuntimeStateWriterDeps, type TarEntry, UnotestError, type UnpackOptions, type UnpackedBundle, type WriteManifestInput, type WriteRunSourcesInput, appendUniqueLines, applyEnvLayers, assertSafeBundlePath, buildRuntimeInspection, computeBundleId, computeSchedulesFingerprint, computeTreeSha256, createRunArtifactWriter, createRuntimeStateWriter, currentLocation, extractCallStack, globalPoolFromEnv, leaseEnv, levenshteinDistance, packBundle, parseE2EFlags, readDebuggerBreakpoints, readEnvFile, readEnvLayers, readTar, startDebugCommandsWatcher, startRunHeartbeat, suggestClosest, toProtocolRuntimeState, unpackBundle, writeRunManifest, writeRunSources, writeTar };
package/dist/index.js CHANGED
@@ -423,11 +423,19 @@ __name(runCapability, "runCapability");
423
423
  function parseE2EFlags(args) {
424
424
  const positional = [];
425
425
  let debug = false;
426
+ let scheduled = false;
427
+ let scheduleIndex;
426
428
  const breakFlag = [];
427
429
  for (let i = 0; i < args.length; i++) {
428
430
  const a = args[i];
429
431
  if (a === "--debug") {
430
432
  debug = true;
433
+ } else if (a === "--scheduled") {
434
+ scheduled = true;
435
+ } else if (a.startsWith("--scheduled=")) {
436
+ scheduled = true;
437
+ const n = Number(a.slice("--scheduled=".length));
438
+ if (Number.isInteger(n) && n >= 0) scheduleIndex = n;
431
439
  } else if (a === "--break") {
432
440
  const next = args[i + 1];
433
441
  if (next === void 0 || next.startsWith("--")) {
@@ -441,7 +449,13 @@ function parseE2EFlags(args) {
441
449
  positional.push(a);
442
450
  }
443
451
  }
444
- return { positional, debug, breakFlag };
452
+ return {
453
+ positional,
454
+ debug,
455
+ breakFlag,
456
+ scheduled,
457
+ ...scheduleIndex !== void 0 ? { scheduleIndex } : {}
458
+ };
445
459
  }
446
460
  __name(parseE2EFlags, "parseE2EFlags");
447
461
  function pushBreaks(value, out) {
@@ -1484,7 +1498,8 @@ function packBundle(files, provenance) {
1484
1498
  byteSize: files.reduce((total, file) => total + file.content.byteLength, 0),
1485
1499
  ...provenance.author !== void 0 ? { author: provenance.author } : {},
1486
1500
  ...provenance.git !== void 0 ? { git: provenance.git } : {},
1487
- ...provenance.declaredVariables && provenance.declaredVariables.length > 0 ? { declaredVariables: [...provenance.declaredVariables] } : {}
1501
+ ...provenance.declaredVariables && provenance.declaredVariables.length > 0 ? { declaredVariables: [...provenance.declaredVariables] } : {},
1502
+ ...provenance.schedulesFingerprint !== void 0 ? { schedulesFingerprint: provenance.schedulesFingerprint } : {}
1488
1503
  };
1489
1504
  const archive = writeTar([
1490
1505
  ...files,
@@ -1549,6 +1564,14 @@ function gunzip(archive, maxBytes) {
1549
1564
  }
1550
1565
  }
1551
1566
  __name(gunzip, "gunzip");
1567
+
1568
+ // src/bundle/schedules-fingerprint.ts
1569
+ import { canonicalScheduleSet } from "@unotest/protocol";
1570
+ import { createHash as createHash2 } from "crypto";
1571
+ function computeSchedulesFingerprint(entries) {
1572
+ return createHash2("sha256").update(canonicalScheduleSet(entries)).digest("hex");
1573
+ }
1574
+ __name(computeSchedulesFingerprint, "computeSchedulesFingerprint");
1552
1575
  export {
1553
1576
  BundleFormatError,
1554
1577
  BundleIdMismatchError,
@@ -1565,6 +1588,7 @@ export {
1565
1588
  assertSafeBundlePath,
1566
1589
  buildRuntimeInspection,
1567
1590
  computeBundleId,
1591
+ computeSchedulesFingerprint,
1568
1592
  computeTreeSha256,
1569
1593
  createRunArtifactWriter,
1570
1594
  createRuntimeStateWriter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unotest/core",
3
- "version": "0.26.1",
3
+ "version": "0.27.0",
4
4
  "description": "Shared runner-side utilities for the @unotest ecosystem: JSONL run-artifact writer (steps.jsonl + heartbeat), atomic run-manifest + run-sources writers, atomic runtime-state writer with a protocol-normalizing runtime-inspection helper, layered .env reader/applier (target + environment axes), idempotent .gitignore updater. Used by @unotest/web and @unotest/mobile; depends on @unotest/protocol plus a type-only import of @unotest/dsl/executor event types (M-20). Unlike protocol (pure data), this package owns the thin filesystem layer both runners need.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -29,8 +29,8 @@
29
29
  ],
30
30
  "dependencies": {
31
31
  "chokidar": "^4.0.3",
32
- "@unotest/dsl": "^0.26.1",
33
- "@unotest/protocol": "^0.26.1"
32
+ "@unotest/dsl": "^0.27.0",
33
+ "@unotest/protocol": "^0.27.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^22.10.0",