heximon 0.10.0 → 0.11.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/README.md CHANGED
@@ -8,12 +8,14 @@ after your last `pnpm build`.
8
8
 
9
9
  ## Why you want it
10
10
 
11
- - **One binary for the whole surface.** `create`, `check`, `routes`, `graph`, `doctor`, `deploy`,
12
- `provision`, `migrations`, `task`, and `workflows` are one command each.
11
+ - **One binary for the whole surface.** `create`, `check`, `routes`, `graph`, `doctor`, `freshness`,
12
+ `deploy`, `provision`, `migrations`, `task`, and `workflows` are one command each.
13
13
  - **`heximon check` catches a dependency-injection error before `tsgo` does.** It compiles your app
14
14
  headlessly and reports every compile error with its position and a fix.
15
15
  - **`heximon doctor` finds a package installed twice.** Class identity is the only dependency-injection
16
16
  token, so two copies of one package bind two different sets of classes. `doctor` names both directories.
17
+ - **`heximon freshness` finds the packages that need a new `vp pack`.** It runs from any folder of a
18
+ workspace, lists every package that has a pack record, and names the file that made a package stale.
17
19
  - **`heximon deploy` ships with the platform's own tool.** It builds your app, then runs `wrangler`,
18
20
  `vercel`, `netlify`, or `deployctl` for you, with the right flags for your environment.
19
21
  - **Agents and CI read one JSON document.** Every command takes `--json`: stdout gets one document with
@@ -58,6 +60,7 @@ const main = defineCommand({
58
60
  routes: routesCommand,
59
61
  graph: graphCommand,
60
62
  doctor: doctorCommand,
63
+ freshness: freshnessCommand,
61
64
  provision: provisionCommand,
62
65
  migrations: migrationsCommand,
63
66
  workflows: workflowsCommand,
@@ -76,6 +79,7 @@ const main = defineCommand({
76
79
  | `heximon routes` | Prints one table for every path the app answers — routes, SSE streams, and WebSocket upgrades — read from the compiler, with no boot required. |
77
80
  | `heximon graph` | Prints the resolved module dependency graph: modules, providers, and dependencies. |
78
81
  | `heximon doctor` | Reports a package installed twice, an untraced deploy target, and a stale published module. |
82
+ | `heximon freshness` | Lists every workspace package that has a pack record (`dist/heximon.manifest.json` or `dist/heximon.inputs.json`), with its status and the input that made it stale. It finds the workspace root from any folder (`pnpm-workspace.yaml`, or a `workspaces` field in `package.json`), compiles nothing, and exits `1` when a package is stale or its check failed. A record file that exists but cannot be read is a failed check, not a missing record. `--stale` lists only those packages. |
79
83
  | `heximon deploy` | Builds the app, then ships it with the target platform's own command-line tool. |
80
84
  | `heximon provision` | Creates a resource your build needs an identifier for, before the build runs. |
81
85
  | `heximon migrations` | Runs `generate`, `migrate`, `status`, or `push` against your database, with no hand-written `drizzle-kit` config. |
@@ -114,6 +118,7 @@ The document types are exported as types from the package root:
114
118
  | `routes` | `RoutesJsonDocument` | `RoutesDiagnosticCode` |
115
119
  | `graph` | `GraphJsonDocument` | `GraphDiagnosticCode` |
116
120
  | `doctor` | `DoctorJsonDocument` | `DoctorDiagnosticCode` |
121
+ | `freshness` | `FreshnessJsonDocument` | `FreshnessDiagnosticCode` |
117
122
  | `skills add`, `skills sync` | `SkillsAddJsonDocument`, `SkillsSyncJsonDocument` | `SkillsDiagnosticCode` |
118
123
  | `task run` | `TaskRunJsonDocument` | `TaskDiagnosticCode` |
119
124
  | `migrations generate`, `migrate`, `push`, `status` | `MigrationsGenerateJsonDocument`, `MigrationsMigrateJsonDocument`, `MigrationsPushJsonDocument`, `MigrationsStatusJsonDocument` | `MigrationsDiagnosticCode` |
package/dist/bin.mjs CHANGED
@@ -4,15 +4,16 @@ import { createRequire, register } from "node:module";
4
4
  import { cancel, confirm, intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
5
5
  import process from "node:process";
6
6
  import { defineCommand } from "citty";
7
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
7
+ import { existsSync, globSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
8
8
  import { AstNodeType, CompileEnvironment, CompileMode, DeployPlatform, DiagnosticCode, DiagnosticRenderMode, DiagnosticRenderer, DiagnosticSeverity, ImportKind, ReexportIndex, RuntimeConfigFile, RuntimeSingletons, TerminalStyle, UpgradeBaselineFile } from "@heximon/compiler";
9
9
  import { spawn } from "node:child_process";
10
10
  import { basename, dirname, isAbsolute, join, relative, resolve } from "pathe";
11
11
  import tab from "@bomb.sh/tab/citty";
12
- import { ConfigLoader, LibraryEmitter, LibraryFactoryPreflight, LibraryFreshness, ProjectCompiler, RuntimeConfigLoader, SourceModuleResolution, SourceParser, WorkspaceTopologyReader } from "@heximon/compiler/project";
12
+ import { ConfigLoader, LibraryEmitter, LibraryFactoryPreflight, LibraryFreshness, LibraryFreshnessStatus, PnpmWorkspaceManifest, ProjectCompiler, RuntimeConfigLoader, SourceModuleResolution, SourceParser, WorkspaceTopologyReader } from "@heximon/compiler/project";
13
13
  import { NodeCompileSeam } from "@heximon/node/compiler";
14
14
  import { TransactionCensus, TransactionCensusPolicy } from "@heximon/cqrs/compiler";
15
15
  import { BoundaryProvenanceAuditor, BoundaryProvenanceStatus, CredentialSelection, DatabaseConfigDetector, DeployDiagnosticSeverity, DeployLedger, DeployManifestStore, DeployRunner, DeployStrategyUnavailableError, DeployTargetResolver, DeployableServiceReader, DotenvCascade, DrizzleKitRunner, EnvFileReader, EnvFileUnreadableError, HostManagedDeployUnsupportedError, ImportScanner, MigrationInspector, MigrationTargetComposer, PackageCopyAuditor, PeerClosureAuditor, PlatformTracingAuditor, PlatformTracingStatus, SecretMask, WorkspaceConsumerLocator } from "@heximon/deploy";
16
+ import { matchesGlob } from "node:path";
16
17
  import { RouteCensus, RouteScopeGap } from "@heximon/http/compiler";
17
18
  import { pathToFileURL } from "node:url";
18
19
  import { homedir } from "node:os";
@@ -1677,7 +1678,7 @@ var DoctorAudit = class DoctorAudit {
1677
1678
  const hasConsumers = (result.workspaceConsumers?.consumers.length ?? 0) > 0;
1678
1679
  parts.push(hasConsumers ? "Forked against a workspace consumer — equalize the two packages' installed peer contexts (a one-sided cover can flip the fork, not remove it) and reinstall." : "Forked within this package's own resolution (no workspace consumers found to compare against) — align the versions its own dependency tree resolves and reinstall.");
1679
1680
  }
1680
- if (result.selfReport === null) parts.push("This directory is not a packed Heximon library (no dist/heximon.manifest.json) — the peer-closure and copy census above still ran; run `vp pack` to enable the staleness check too.");
1681
+ if (result.selfReport === null) parts.push("This directory is not a packed Heximon library (no dist/heximon.manifest.json and no dist/heximon.inputs.json) — the peer-closure and copy census above still ran; run `vp pack` to enable the staleness check too.");
1681
1682
  else if (result.selfReport.status === BoundaryProvenanceStatus.Stale || !forked) parts.push(DoctorAudit.selfProvenanceSummary(result.selfReport));
1682
1683
  return parts.join(" ");
1683
1684
  }
@@ -1707,7 +1708,7 @@ var DoctorAudit = class DoctorAudit {
1707
1708
  */
1708
1709
  static selfProvenanceSummary(report) {
1709
1710
  switch (report.status) {
1710
- case BoundaryProvenanceStatus.Fresh: return "This package's dist is current with its src/.";
1711
+ case BoundaryProvenanceStatus.Fresh: return report.manifestSourceHash === void 0 ? "This package's dist is current with its build inputs." : "This package's dist is current with its src/.";
1711
1712
  case BoundaryProvenanceStatus.NoProvenance: return "Its packed manifest predates the provenance stamp, so nothing was compared — repack (`vp pack`) to enable the staleness check.";
1712
1713
  case BoundaryProvenanceStatus.NoSource: return "It ships no local src/, so its packed manifest was not compared with anything.";
1713
1714
  case BoundaryProvenanceStatus.Stale: return "Your dist is STALE — repack (`vp pack`) before publishing or consuming this package.";
@@ -1890,61 +1891,113 @@ var DoctorJsonReport = class DoctorJsonReport {
1890
1891
  /** One staleness entry. `self` marks the package that `--self` audits. */
1891
1892
  static staleness(report, self) {
1892
1893
  const freshness = report;
1893
- const repack = `cd ${DoctorJsonReport.shellWord(report.packageDirectory)} && vp pack`;
1894
- const details = {
1894
+ const repack = DoctorJsonReport.repackCommand(report.packageDirectory);
1895
+ const verdict = DoctorJsonReport.verdictOf(report.status, self ? "Repack (`vp pack`) before publishing or consuming it." : `Rebuild it (\`vp pack\` in ${report.packageDirectory}) before trusting this app's build.`, repack);
1896
+ return {
1897
+ name: report.specifier,
1898
+ status: verdict.status,
1899
+ reason: DoctorJsonReport.reasonOf(freshness),
1900
+ ...verdict.hint === void 0 ? {} : { hint: verdict.hint },
1901
+ ...verdict.fix === void 0 ? {} : { fix: verdict.fix },
1902
+ failing: verdict.failing,
1895
1903
  self,
1896
1904
  ...report.packageVersion === void 0 ? {} : { version: report.packageVersion },
1897
1905
  directory: report.packageDirectory,
1898
- ...report.manifestSourceHash === void 0 ? {} : { manifestSourceHash: report.manifestSourceHash },
1899
- ...report.recomputedSourceHash === void 0 ? {} : { recomputedSourceHash: report.recomputedSourceHash },
1900
- ...report.missingSource === void 0 ? {} : { missingSource: report.missingSource },
1901
- ...freshness.currentSource === void 0 ? {} : { currentSource: freshness.currentSource },
1902
- ...report.manifestBundleInputHash === void 0 ? {} : { manifestBundleInputHash: report.manifestBundleInputHash },
1903
- ...report.recomputedBundleInputHash === void 0 ? {} : { recomputedBundleInputHash: report.recomputedBundleInputHash },
1904
- ...report.missingBundleInputs === void 0 ? {} : { missingBundleInputs: report.missingBundleInputs }
1906
+ ...DoctorJsonReport.freshnessDetails(freshness)
1905
1907
  };
1906
- switch (report.status) {
1907
- case BoundaryProvenanceStatus.Fresh: return {
1908
- name: report.specifier,
1908
+ }
1909
+ /**
1910
+ * The status, the hint, the fix and whether it fails the run, for a verdict: a stale package fails and is
1911
+ * repacked, a package with no provenance is repacked, and the others need nothing.
1912
+ *
1913
+ * @param status - The status of a {@link LibraryFreshness} verdict.
1914
+ * @param staleHint - The hint of a stale package.
1915
+ * @param repack - The command that repacks the package.
1916
+ * @returns The fields.
1917
+ */
1918
+ static verdictOf(status, staleHint, repack) {
1919
+ switch (status) {
1920
+ case LibraryFreshnessStatus.Fresh: return {
1909
1921
  status: DoctorPackageStatus.Fresh,
1910
- reason: `The packed manifest matches the local source (${String(report.manifestSourceHash)}).`,
1911
- failing: false,
1912
- ...details
1922
+ failing: false
1913
1923
  };
1914
- case BoundaryProvenanceStatus.Stale: return {
1915
- name: report.specifier,
1924
+ case LibraryFreshnessStatus.Stale: return {
1916
1925
  status: DoctorPackageStatus.Stale,
1917
- reason: DoctorJsonReport.staleReason(freshness),
1918
- hint: self ? "Repack (`vp pack`) before publishing or consuming it." : `Rebuild it (\`vp pack\` in ${report.packageDirectory}) before trusting this app's build.`,
1926
+ hint: staleHint,
1919
1927
  fix: repack,
1920
- failing: true,
1921
- ...details
1928
+ failing: true
1922
1929
  };
1923
- case BoundaryProvenanceStatus.NoProvenance: return {
1924
- name: report.specifier,
1930
+ case LibraryFreshnessStatus.NoProvenance: return {
1925
1931
  status: DoctorPackageStatus.NoProvenance,
1926
- reason: "The packed manifest predates the provenance stamp, so nothing was compared.",
1927
1932
  hint: "Repack (`vp pack`) to enable the staleness check.",
1928
1933
  fix: repack,
1929
- failing: false,
1930
- ...details
1934
+ failing: false
1931
1935
  };
1932
- case BoundaryProvenanceStatus.NoSource: return {
1933
- name: report.specifier,
1936
+ case LibraryFreshnessStatus.NoSource: return {
1934
1937
  status: DoctorPackageStatus.NoSource,
1935
- reason: "A published package with no local src/, so its provenance is display-only.",
1936
- failing: false,
1937
- ...details
1938
+ failing: false
1938
1939
  };
1939
1940
  }
1940
1941
  }
1942
+ /**
1943
+ * The fields of a verdict that the JSON documents print beside its status: the hashes, the missing and the
1944
+ * current source, and the stale build entries. A field the verdict does not have is left out.
1945
+ *
1946
+ * @param report - A {@link LibraryFreshness} verdict.
1947
+ * @returns The fields.
1948
+ */
1949
+ static freshnessDetails(report) {
1950
+ return {
1951
+ ...report.manifestSourceHash === void 0 ? {} : { manifestSourceHash: report.manifestSourceHash },
1952
+ ...report.recomputedSourceHash === void 0 ? {} : { recomputedSourceHash: report.recomputedSourceHash },
1953
+ ...report.missingSource === void 0 ? {} : { missingSource: report.missingSource },
1954
+ ...report.currentSource === void 0 ? {} : { currentSource: report.currentSource },
1955
+ ...report.manifestBundleInputHash === void 0 ? {} : { manifestBundleInputHash: report.manifestBundleInputHash },
1956
+ ...report.recomputedBundleInputHash === void 0 ? {} : { recomputedBundleInputHash: report.recomputedBundleInputHash },
1957
+ ...report.missingBundleInputs === void 0 ? {} : { missingBundleInputs: report.missingBundleInputs },
1958
+ ...report.staleBuildEntries === void 0 ? {} : { staleBuildEntries: report.staleBuildEntries }
1959
+ };
1960
+ }
1961
+ /**
1962
+ * Why a package has the status of its verdict, as one sentence.
1963
+ *
1964
+ * @param report - A {@link LibraryFreshness} verdict.
1965
+ * @returns The reason.
1966
+ */
1967
+ static reasonOf(report) {
1968
+ switch (report.status) {
1969
+ case LibraryFreshnessStatus.Fresh: return report.manifestSourceHash === void 0 ? "The pack inputs file matches the local build inputs." : `The packed manifest matches the local source (${report.manifestSourceHash}).`;
1970
+ case LibraryFreshnessStatus.Stale: return DoctorJsonReport.staleReason(report);
1971
+ case LibraryFreshnessStatus.NoProvenance: return "The packed manifest predates the provenance stamp, so nothing was compared.";
1972
+ case LibraryFreshnessStatus.NoSource: return "A published package with no local src/, so its provenance is display-only.";
1973
+ }
1974
+ }
1975
+ /**
1976
+ * The shell command that repacks a package.
1977
+ *
1978
+ * @param packageDirectory - The package root.
1979
+ * @returns `cd <directory> && vp pack`, with the directory quoted when it needs quotes.
1980
+ */
1981
+ static repackCommand(packageDirectory) {
1982
+ return `cd ${DoctorJsonReport.shellWord(packageDirectory)} && vp pack`;
1983
+ }
1941
1984
  /** Why a stale report is stale, in the words and the order of the text report. */
1942
1985
  static staleReason(report) {
1943
- if (report.currentSource !== void 0) return `The source folders it was packed from (${(report.missingSource ?? []).join(", ")}) no longer exist, and its source is now in ${report.currentSource}.`;
1944
- if (report.missingBundleInputs !== void 0) return `Files its bundle read outside its source (${report.missingBundleInputs.join(", ")}) are no longer on disk.`;
1945
- if (report.recomputedBundleInputHash !== void 0) return `A file its bundle read outside its source changed: the bundle inputs (${report.recomputedBundleInputHash}) no longer match the packed manifest (${String(report.manifestBundleInputHash)}).`;
1946
- if (report.missingSource !== void 0 && report.missingSource.length > 0) return `The source it was packed from (${report.missingSource.join(", ")}) is no longer on disk.`;
1947
- return `Its src/ (${String(report.recomputedSourceHash)}) no longer matches the packed manifest (${String(report.manifestSourceHash)}).`;
1986
+ const entries = LibraryFreshness.staleBuildEntriesClause(report);
1987
+ if (report.currentSource !== void 0) return `The source folders it was packed from (${(report.missingSource ?? []).join(", ")}) no longer exist, and its source is now in ${report.currentSource}${entries}.`;
1988
+ if (report.missingSource !== void 0 && report.missingSource.length > 0) return `The source it was packed from (${report.missingSource.join(", ")}) is no longer on disk${entries}.`;
1989
+ if (report.recomputedSourceHash !== void 0 && report.manifestSourceHash !== void 0 && report.recomputedSourceHash !== report.manifestSourceHash) return DoctorJsonReport.sourceChangedReason(report);
1990
+ if (report.staleBuildEntries !== void 0 && report.staleBuildEntries.length > 0) {
1991
+ const reason = LibraryFreshness.staleBuildEntriesReason(report.staleBuildEntries);
1992
+ return `${reason.charAt(0).toUpperCase()}${reason.slice(1)}.`;
1993
+ }
1994
+ if (report.missingBundleInputs !== void 0) return `Files its build read outside its source (${report.missingBundleInputs.join(", ")}) are no longer on disk.`;
1995
+ if (report.recomputedBundleInputHash !== void 0) return `A file its build read outside its source changed: the build inputs (${report.recomputedBundleInputHash}) no longer match the packed manifest (${String(report.manifestBundleInputHash)}).`;
1996
+ return DoctorJsonReport.sourceChangedReason(report);
1997
+ }
1998
+ /** The reason for a source fingerprint that no longer matches the manifest. */
1999
+ static sourceChangedReason(report) {
2000
+ return `Its src/ (${String(report.recomputedSourceHash)}) no longer matches the packed manifest (${String(report.manifestSourceHash)})${LibraryFreshness.staleBuildEntriesClause(report)}.`;
1948
2001
  }
1949
2002
  /** The registry diagnostic, when the probe failed. */
1950
2003
  static registryDiagnostics(result) {
@@ -2032,8 +2085,10 @@ var DoctorReport = class DoctorReport {
2032
2085
  const name = self ? `${report.specifier} (this package)` : report.specifier;
2033
2086
  const version = report.packageVersion === void 0 ? "" : ` v${report.packageVersion}`;
2034
2087
  switch (report.status) {
2035
- case BoundaryProvenanceStatus.Fresh: return self ? `✔ ${name}${version} — dist is current with src/ (${report.manifestSourceHash})` : `✔ ${name}${version} — up to date (${report.manifestSourceHash})`;
2036
- case BoundaryProvenanceStatus.Stale: return self && report.missingSource === void 0 && !LibraryFreshness.isBundleStale(report) ? `✖ ${name}${version} — STALE: your src/ (${report.recomputedSourceHash}) no longer matches your own dist manifest (${report.manifestSourceHash}). Repack (\`vp pack\`) before publishing or consuming it.` : LibraryFreshness.staleLine(self ? name : report.specifier, report);
2088
+ case BoundaryProvenanceStatus.Fresh:
2089
+ if (report.manifestSourceHash === void 0) return `✔ ${name}${version} — dist is current with its build inputs`;
2090
+ return self ? `✔ ${name}${version} — dist is current with src/ (${report.manifestSourceHash})` : `✔ ${name}${version} — up to date (${report.manifestSourceHash})`;
2091
+ case BoundaryProvenanceStatus.Stale: return self && report.missingSource === void 0 && !LibraryFreshness.isBundleStale(report) ? `✖ ${name}${version} — STALE: your src/ (${report.recomputedSourceHash}) no longer matches your own dist manifest (${report.manifestSourceHash})${LibraryFreshness.staleBuildEntriesClause(report)}. Repack (\`vp pack\`) before publishing or consuming it.` : LibraryFreshness.staleLine(self ? name : report.specifier, report);
2037
2092
  case BoundaryProvenanceStatus.NoProvenance: return `● ${name}${version} — manifest predates the provenance stamp; repack to enable the check.`;
2038
2093
  case BoundaryProvenanceStatus.NoSource: return `● ${name}${version} — published package (no local src/); provenance is display-only.`;
2039
2094
  }
@@ -2192,6 +2247,411 @@ const doctorCommand = defineCommand({
2192
2247
  }
2193
2248
  });
2194
2249
  //#endregion
2250
+ //#region src/freshness-json-document.ts
2251
+ /**
2252
+ * The verdict on one workspace package in a {@link FreshnessJsonDocument}. The first four are the staleness
2253
+ * values of `DoctorPackageStatus`.
2254
+ */
2255
+ const FreshnessPackageStatus = {
2256
+ /** Every recorded input of the package matches the files on disk. */
2257
+ Fresh: "fresh",
2258
+ /** The source or another recorded input changed since the last pack, or is gone. */
2259
+ Stale: "stale",
2260
+ /** The packed manifest has no provenance stamp, so nothing was compared. */
2261
+ NoProvenance: "no-provenance",
2262
+ /** None of the recorded source is on disk, so nothing was compared. */
2263
+ NoSource: "no-source",
2264
+ /** The check of the package failed, for example because its `heximon.config.ts` threw. */
2265
+ Error: "error"
2266
+ };
2267
+ /** The stable codes of the `heximon freshness` diagnostics. */
2268
+ const FreshnessDiagnosticCode = {
2269
+ /** A workspace package changed since its last pack. Error. */
2270
+ StalePackage: "freshness.stale-package",
2271
+ /** The check of one package failed; the message has the error. Error. */
2272
+ CheckFailed: "freshness.check-failed",
2273
+ /** The `pnpm-workspace.yaml` or the root `package.json` cannot be read or parsed. Error. */
2274
+ WorkspaceUnreadable: "freshness.workspace-unreadable"
2275
+ };
2276
+ //#endregion
2277
+ //#region src/workspace-packages.ts
2278
+ /** Where the member list of a {@link Workspace} comes from. */
2279
+ const WorkspaceKind = {
2280
+ /** The `packages:` list of a `pnpm-workspace.yaml`. */
2281
+ Pnpm: "pnpm",
2282
+ /** The `workspaces` field of a root `package.json` (npm, Yarn, Bun). */
2283
+ PackageJson: "package-json",
2284
+ /** No workspace file: the nearest package is the only member. */
2285
+ SinglePackage: "single-package",
2286
+ /** No workspace file and no `package.json` from the start directory up. */
2287
+ None: "none"
2288
+ };
2289
+ /**
2290
+ * Finds the workspace that holds a directory and lists its packages. It walks up to the nearest directory with a
2291
+ * `pnpm-workspace.yaml`, or with a `package.json` that has a `workspaces` field (an array, or Yarn's
2292
+ * `{ packages }` form). With neither, the nearest package is a one-package workspace. The root package is a
2293
+ * member when it has a `package.json`. A `!` pattern removes the members it matches, and `node_modules` is never
2294
+ * searched. A folder that two paths reach (a symlink) is one member, under the path that sorts first.
2295
+ */
2296
+ var WorkspacePackages = class WorkspacePackages {
2297
+ /** The package manifest file name. */
2298
+ static packageJsonFileName = "package.json";
2299
+ /** The folder name the member search never enters. */
2300
+ static installedDirectoryName = "node_modules";
2301
+ /**
2302
+ * Find the workspace of a directory.
2303
+ *
2304
+ * @param startDirectory - The directory to start the walk from.
2305
+ * @returns The workspace and its member packages.
2306
+ * @throws When a `pnpm-workspace.yaml` or a root `package.json` on the walk cannot be read or parsed.
2307
+ */
2308
+ static locate(startDirectory) {
2309
+ const start = resolve(startDirectory);
2310
+ let nearestPackage;
2311
+ let current = start;
2312
+ while (true) {
2313
+ if (existsSync(join(current, PnpmWorkspaceManifest.fileName))) {
2314
+ const manifestPath = join(current, PnpmWorkspaceManifest.fileName);
2315
+ const patterns = PnpmWorkspaceManifest.parsePackagePatterns(readFileSync(manifestPath, "utf8"), manifestPath);
2316
+ return WorkspacePackages.workspaceOf(current, WorkspaceKind.Pnpm, patterns);
2317
+ }
2318
+ const packageJsonPath = join(current, WorkspacePackages.packageJsonFileName);
2319
+ if (existsSync(packageJsonPath)) {
2320
+ nearestPackage ??= current;
2321
+ const patterns = WorkspacePackages.workspacePatternsOf(packageJsonPath);
2322
+ if (patterns !== void 0) return WorkspacePackages.workspaceOf(current, WorkspaceKind.PackageJson, patterns);
2323
+ }
2324
+ const parent = dirname(current);
2325
+ if (parent === current) break;
2326
+ current = parent;
2327
+ }
2328
+ return nearestPackage === void 0 ? {
2329
+ root: start,
2330
+ kind: WorkspaceKind.None,
2331
+ members: []
2332
+ } : {
2333
+ root: nearestPackage,
2334
+ kind: WorkspaceKind.SinglePackage,
2335
+ members: [{
2336
+ directory: WorkspacePackages.realPathOf(nearestPackage),
2337
+ path: "."
2338
+ }]
2339
+ };
2340
+ }
2341
+ /**
2342
+ * The `workspaces` patterns of a `package.json`.
2343
+ *
2344
+ * @returns The patterns, or `undefined` when the file has no `workspaces` field in a known form.
2345
+ * @throws When the file is not valid JSON.
2346
+ */
2347
+ static workspacePatternsOf(packageJsonPath) {
2348
+ let parsed;
2349
+ try {
2350
+ parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
2351
+ } catch (cause) {
2352
+ const reason = cause instanceof Error ? cause.message : String(cause);
2353
+ throw new Error(`${packageJsonPath} could not be parsed as JSON: ${reason}`, { cause });
2354
+ }
2355
+ if (typeof parsed !== "object" || parsed === null || !("workspaces" in parsed)) return;
2356
+ const workspaces = parsed.workspaces;
2357
+ const patterns = typeof workspaces === "object" && workspaces !== null && "packages" in workspaces ? workspaces.packages : workspaces;
2358
+ return Array.isArray(patterns) ? patterns.filter((pattern) => typeof pattern === "string") : void 0;
2359
+ }
2360
+ /** The workspace at a root: the root package, then every directory the patterns name that has a package. */
2361
+ static workspaceOf(root, kind, patterns) {
2362
+ const includes = patterns.filter((pattern) => !pattern.trim().startsWith("!")).map((pattern) => WorkspacePackages.normalizedPattern(pattern)).filter((pattern) => pattern !== "");
2363
+ const excludes = patterns.filter((pattern) => pattern.trim().startsWith("!")).map((pattern) => WorkspacePackages.normalizedPattern(pattern.trim().slice(1))).filter((pattern) => pattern !== "");
2364
+ const members = (includes.length === 0 ? [] : globSync(includes, {
2365
+ cwd: root,
2366
+ exclude: (entry) => basename(String(entry)) === WorkspacePackages.installedDirectoryName
2367
+ })).map((match) => relative(root, resolve(root, match))).filter((path) => !WorkspacePackages.isExcluded(path, excludes)).sort();
2368
+ const workspaceMembers = [];
2369
+ const seen = /* @__PURE__ */ new Set();
2370
+ for (const memberPath of ["", ...members]) {
2371
+ const directory = resolve(root, memberPath);
2372
+ if (!WorkspacePackages.isPackageDirectory(directory)) continue;
2373
+ const realDirectory = WorkspacePackages.realPathOf(directory);
2374
+ if (!seen.has(realDirectory)) {
2375
+ seen.add(realDirectory);
2376
+ workspaceMembers.push({
2377
+ directory: realDirectory,
2378
+ path: memberPath || "."
2379
+ });
2380
+ }
2381
+ }
2382
+ return {
2383
+ root,
2384
+ kind,
2385
+ members: workspaceMembers
2386
+ };
2387
+ }
2388
+ /** A pattern without a leading `./` and a trailing `/`, so it matches the paths the glob returns. */
2389
+ static normalizedPattern(pattern) {
2390
+ return pattern.trim().replace(/^(?:\.\/)+/, "").replace(/\/+$/, "");
2391
+ }
2392
+ /** Whether a `!` pattern removes a member path. A pattern that ends in a globstar also removes its folder. */
2393
+ static isExcluded(memberPath, excludes) {
2394
+ return excludes.some((pattern) => matchesGlob(memberPath, pattern) || matchesGlob(`${memberPath}/`, pattern));
2395
+ }
2396
+ /** Whether a path is a directory that holds a `package.json`. */
2397
+ static isPackageDirectory(directory) {
2398
+ try {
2399
+ return statSync(directory).isDirectory() && existsSync(join(directory, WorkspacePackages.packageJsonFileName));
2400
+ } catch {
2401
+ return false;
2402
+ }
2403
+ }
2404
+ /** The real path of a directory, or the path itself when it cannot be resolved. */
2405
+ static realPathOf(directory) {
2406
+ try {
2407
+ return realpathSync(directory);
2408
+ } catch {
2409
+ return directory;
2410
+ }
2411
+ }
2412
+ };
2413
+ //#endregion
2414
+ //#region src/workspace-freshness.ts
2415
+ /**
2416
+ * Judges every package of a workspace with {@link LibraryFreshness}: each package that has a Heximon manifest or
2417
+ * a `dist/heximon.inputs.json`. It compiles nothing and runs no other audit, so it only stats and hashes files.
2418
+ * A package whose check throws (a `heximon.config.ts` that fails to load) gets an `error` verdict.
2419
+ */
2420
+ var WorkspaceFreshness = class WorkspaceFreshness {
2421
+ /** The fix hint of a package whose check failed. */
2422
+ static checkFailedHint = "Fix the error, for example in the package's heximon.config.ts, then run heximon freshness again.";
2423
+ /**
2424
+ * Find the workspace of a directory and judge each of its packages.
2425
+ *
2426
+ * @param startDirectory - Any directory inside the workspace.
2427
+ * @returns The verdicts.
2428
+ * @throws CommandFailure with {@link FreshnessDiagnosticCode.WorkspaceUnreadable} when the workspace file
2429
+ * cannot be read.
2430
+ */
2431
+ static async run(startDirectory) {
2432
+ const workspace = WorkspaceFreshness.locate(startDirectory);
2433
+ const freshness = new LibraryFreshness();
2434
+ const packages = [];
2435
+ for (const member of workspace.members) {
2436
+ const identity = WorkspaceFreshness.identityOf(member);
2437
+ const recordProblem = LibraryFreshness.recordProblemOf(member.directory);
2438
+ if (recordProblem !== void 0) {
2439
+ packages.push({
2440
+ ...identity,
2441
+ error: recordProblem,
2442
+ recordUnreadable: true
2443
+ });
2444
+ continue;
2445
+ }
2446
+ try {
2447
+ const report = await freshness.inspect(member.directory);
2448
+ if (report !== null) packages.push({
2449
+ ...identity,
2450
+ report
2451
+ });
2452
+ } catch (error) {
2453
+ packages.push({
2454
+ ...identity,
2455
+ error: error instanceof Error ? error.message : String(error)
2456
+ });
2457
+ }
2458
+ }
2459
+ return {
2460
+ workspace,
2461
+ packages,
2462
+ skippedPackages: workspace.members.length - packages.length
2463
+ };
2464
+ }
2465
+ /** Whether a verdict fails the run: a stale package, or one whose check failed. */
2466
+ static isFailing(verdict) {
2467
+ return verdict.error !== void 0 || verdict.report?.status === LibraryFreshnessStatus.Stale;
2468
+ }
2469
+ /** Whether the run exits 1. */
2470
+ static isFailure(result) {
2471
+ return result.packages.some((verdict) => WorkspaceFreshness.isFailing(verdict));
2472
+ }
2473
+ /** The status of a verdict. */
2474
+ static statusOf(verdict) {
2475
+ return verdict.report === void 0 ? FreshnessPackageStatus.Error : verdict.report.status;
2476
+ }
2477
+ /**
2478
+ * The text line of one package: its name, version and path, its status, and for a stale package the
2479
+ * {@link LibraryFreshness.staleLine} that names the changed and missing inputs.
2480
+ *
2481
+ * @param verdict - The verdict.
2482
+ * @returns The line.
2483
+ */
2484
+ static lineOf(verdict) {
2485
+ const version = verdict.version === void 0 ? "" : ` v${verdict.version}`;
2486
+ const label = `${verdict.name}${version} (${verdict.path})`;
2487
+ const report = verdict.report;
2488
+ if (report === void 0) return `✖ ${label} — ERROR: ${String(verdict.error)}`;
2489
+ switch (report.status) {
2490
+ case LibraryFreshnessStatus.Fresh: return `✔ ${label} — fresh`;
2491
+ case LibraryFreshnessStatus.Stale: return LibraryFreshness.staleLine(label, {
2492
+ ...report,
2493
+ packageVersion: void 0
2494
+ }, { forApp: false });
2495
+ case LibraryFreshnessStatus.NoProvenance: return `● ${label} — no provenance: the manifest predates the provenance stamp. Repack to enable the check.`;
2496
+ case LibraryFreshnessStatus.NoSource: return `● ${label} — no source: none of its recorded source is on disk, so nothing was compared.`;
2497
+ }
2498
+ }
2499
+ /** The closing sentence of the run. */
2500
+ static summary(result) {
2501
+ const skipped = result.skippedPackages === 0 ? "" : ` ${String(result.skippedPackages)} package(s) without a record were skipped.`;
2502
+ if (result.packages.length === 0) return result.workspace.members.length === 0 ? `No package found from ${result.workspace.root}.` : `No workspace package has a freshness record.${skipped}`;
2503
+ const count = (status) => result.packages.filter((verdict) => WorkspaceFreshness.statusOf(verdict) === status).length;
2504
+ const parts = [`${String(count(FreshnessPackageStatus.Stale))} stale`, `${String(count(FreshnessPackageStatus.Fresh))} fresh`];
2505
+ const notCompared = count(FreshnessPackageStatus.NoProvenance) + count(FreshnessPackageStatus.NoSource);
2506
+ if (notCompared > 0) parts.push(`${String(notCompared)} not compared`);
2507
+ if (count(FreshnessPackageStatus.Error) > 0) parts.push(`${String(count(FreshnessPackageStatus.Error))} failed`);
2508
+ return `${String(result.packages.length)} package(s) with a record: ${parts.join(", ")}.${skipped}`;
2509
+ }
2510
+ /**
2511
+ * Build the `--json` document of one run.
2512
+ *
2513
+ * @param result - The verdicts.
2514
+ * @param staleOnly - List only the entries that fail the run.
2515
+ * @returns The document. Its `ok` is `false` exactly when {@link isFailure} is `true`.
2516
+ */
2517
+ static toJson(result, staleOnly) {
2518
+ const packages = result.packages.map((verdict) => WorkspaceFreshness.entryOf(verdict));
2519
+ return {
2520
+ ok: !WorkspaceFreshness.isFailure(result),
2521
+ diagnostics: packages.flatMap((entry) => WorkspaceFreshness.diagnosticsOf(entry)),
2522
+ workspaceRoot: result.workspace.root,
2523
+ workspaceKind: result.workspace.kind,
2524
+ packages: staleOnly ? packages.filter((entry) => entry.failing) : packages,
2525
+ skippedPackages: result.skippedPackages,
2526
+ summary: WorkspaceFreshness.summary(result)
2527
+ };
2528
+ }
2529
+ /** The workspace of a directory, with a file that cannot be read as the command's failure. */
2530
+ static locate(startDirectory) {
2531
+ try {
2532
+ return WorkspacePackages.locate(startDirectory);
2533
+ } catch (error) {
2534
+ throw new CommandFailure([CliDiagnostics.error(FreshnessDiagnosticCode.WorkspaceUnreadable, error instanceof Error ? error.message : String(error), { hint: "Fix the workspace file, then run heximon freshness again." })]);
2535
+ }
2536
+ }
2537
+ /** One JSON package entry. */
2538
+ static entryOf(verdict) {
2539
+ const location = {
2540
+ ...verdict.version === void 0 ? {} : { version: verdict.version },
2541
+ path: verdict.path,
2542
+ directory: verdict.directory
2543
+ };
2544
+ if (verdict.report === void 0) return {
2545
+ name: verdict.name,
2546
+ status: FreshnessPackageStatus.Error,
2547
+ reason: String(verdict.error),
2548
+ ...verdict.recordUnreadable === true ? {
2549
+ hint: `Repack it (\`vp pack\` in ${verdict.directory}) to write the record again.`,
2550
+ fix: DoctorJsonReport.repackCommand(verdict.directory)
2551
+ } : { hint: WorkspaceFreshness.checkFailedHint },
2552
+ failing: true,
2553
+ ...location
2554
+ };
2555
+ const fields = DoctorJsonReport.verdictOf(verdict.report.status, `Rebuild it (\`vp pack\` in ${verdict.directory}).`, DoctorJsonReport.repackCommand(verdict.directory));
2556
+ return {
2557
+ name: verdict.name,
2558
+ status: verdict.report.status,
2559
+ reason: DoctorJsonReport.reasonOf(verdict.report),
2560
+ ...fields.hint === void 0 ? {} : { hint: fields.hint },
2561
+ ...fields.fix === void 0 ? {} : { fix: fields.fix },
2562
+ failing: fields.failing,
2563
+ ...location,
2564
+ ...DoctorJsonReport.freshnessDetails(verdict.report)
2565
+ };
2566
+ }
2567
+ /** The error diagnostic of an entry that fails the run. */
2568
+ static diagnosticsOf(entry) {
2569
+ if (!entry.failing) return [];
2570
+ return [CliDiagnostics.error(entry.status === FreshnessPackageStatus.Error ? FreshnessDiagnosticCode.CheckFailed : FreshnessDiagnosticCode.StalePackage, `${entry.name} (${entry.path}): ${entry.reason}`, entry.hint === void 0 ? {} : { hint: entry.hint })];
2571
+ }
2572
+ /** The name, version and paths of a package, from its `package.json`. */
2573
+ static identityOf(member) {
2574
+ const directory = member.directory;
2575
+ let name;
2576
+ let version;
2577
+ try {
2578
+ const parsed = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
2579
+ if (typeof parsed === "object" && parsed !== null) {
2580
+ const fields = parsed;
2581
+ name = typeof fields.name === "string" ? fields.name : void 0;
2582
+ version = typeof fields.version === "string" ? fields.version : void 0;
2583
+ }
2584
+ } catch {}
2585
+ return {
2586
+ name: name ?? basename(directory),
2587
+ ...version === void 0 ? {} : { version },
2588
+ path: member.path,
2589
+ directory
2590
+ };
2591
+ }
2592
+ };
2593
+ //#endregion
2594
+ //#region src/freshness-command.ts
2595
+ /** The text renderer behind {@link freshnessCommand}: one clack line per package, then the summary. */
2596
+ var FreshnessReport = class {
2597
+ /**
2598
+ * Print the report of one run.
2599
+ *
2600
+ * @param result - The verdicts.
2601
+ * @param staleOnly - Print only the packages that fail the run.
2602
+ */
2603
+ static render(result, staleOnly) {
2604
+ for (const verdict of result.packages) {
2605
+ const failing = WorkspaceFreshness.isFailing(verdict);
2606
+ if (staleOnly && !failing) continue;
2607
+ const line = WorkspaceFreshness.lineOf(verdict);
2608
+ if (failing) log.error(line);
2609
+ else if (verdict.report?.status === LibraryFreshnessStatus.Fresh) log.success(line);
2610
+ else log.info(line);
2611
+ }
2612
+ outro(WorkspaceFreshness.summary(result));
2613
+ }
2614
+ };
2615
+ /**
2616
+ * `heximon freshness` — lists every package of the workspace that has a freshness record, with its status. It
2617
+ * finds the workspace root from any directory inside it, judges each package with the rule of `heximon doctor`
2618
+ * ({@link LibraryFreshness}), and exits 1 when a package is stale or its check failed. It compiles nothing and
2619
+ * runs no other audit.
2620
+ */
2621
+ const freshnessCommand = defineCommand({
2622
+ meta: {
2623
+ name: "freshness",
2624
+ description: "List every workspace package with a freshness record (a Heximon manifest or dist/heximon.inputs.json), its status, and the input that made it stale. Works from any directory of the workspace. Exits 1 when a package is stale."
2625
+ },
2626
+ args: {
2627
+ cwd: {
2628
+ type: "string",
2629
+ description: "A directory inside the workspace; the command walks up to the workspace root.",
2630
+ default: "."
2631
+ },
2632
+ json: CommandOutput.jsonFlag,
2633
+ stale: {
2634
+ type: "boolean",
2635
+ description: "List only the packages that fail the run: the stale ones, and the ones whose check failed.",
2636
+ default: false
2637
+ }
2638
+ },
2639
+ async run({ args }) {
2640
+ const commandOutput = CommandOutput.resolve("freshness", args);
2641
+ await commandOutput.run(async () => {
2642
+ const startDirectory = ProjectRoot.resolve(args.cwd);
2643
+ const result = await WorkspaceFreshness.run(startDirectory);
2644
+ const staleOnly = args.stale === true;
2645
+ if (WorkspaceFreshness.isFailure(result)) process.exitCode = 1;
2646
+ if (commandOutput.isJson()) commandOutput.emit(WorkspaceFreshness.toJson(result, staleOnly));
2647
+ else {
2648
+ intro(`heximon freshness — ${basename(result.workspace.root)}`);
2649
+ FreshnessReport.render(result, staleOnly);
2650
+ }
2651
+ });
2652
+ }
2653
+ });
2654
+ //#endregion
2195
2655
  //#region src/graph-json-document.ts
2196
2656
  /** The stable codes of the failures `heximon graph` raises itself. */
2197
2657
  const GraphDiagnosticCode = {
@@ -5927,6 +6387,7 @@ const main = defineCommand({
5927
6387
  routes: routesCommand,
5928
6388
  graph: graphCommand,
5929
6389
  doctor: doctorCommand,
6390
+ freshness: freshnessCommand,
5930
6391
  provision: provisionCommand,
5931
6392
  migrations: migrationsCommand,
5932
6393
  workflows: workflowsCommand,
package/dist/index.d.mts CHANGED
@@ -827,11 +827,45 @@ interface DoctorPackageCopy {
827
827
  /** Who resolves to this copy (`the app`, `this package`, or a package name). */
828
828
  readonly origins: readonly string[];
829
829
  }
830
+ /** One build entry of a package whose recorded inputs no longer match the files on disk. */
831
+ interface DoctorStaleBuildEntry {
832
+ /** The key of the build entry: its output folder, its entry file-name patterns and its format. */
833
+ readonly entry: string;
834
+ /** The package-relative inputs whose digest changed. */
835
+ readonly changedInputs: readonly string[];
836
+ /** The package-relative inputs that are no longer on disk. */
837
+ readonly missingInputs: readonly string[];
838
+ }
839
+ /**
840
+ * The fields of a staleness verdict, as `heximon doctor --json` and `heximon freshness --json` both print them.
841
+ * Each field is present only when the verdict has it.
842
+ */
843
+ interface DoctorFreshnessDetails {
844
+ /** The source fingerprint the packed manifest recorded. */
845
+ readonly manifestSourceHash?: string;
846
+ /** The source fingerprint computed from the local source now. */
847
+ readonly recomputedSourceHash?: string;
848
+ /** The recorded source folders and files that are no longer on disk. */
849
+ readonly missingSource?: readonly string[];
850
+ /** The current source directory, when the source moved since the pack. */
851
+ readonly currentSource?: string;
852
+ /** The hash of the files the bundle read outside the source, as the packed manifest recorded it. */
853
+ readonly manifestBundleInputHash?: string;
854
+ /** That hash recomputed from the files now, when the source matched and every file is on disk. */
855
+ readonly recomputedBundleInputHash?: string;
856
+ /** The recorded bundle inputs (package-relative) that are no longer on disk. */
857
+ readonly missingBundleInputs?: readonly string[];
858
+ /**
859
+ * The build entries of the package's `dist/heximon.inputs.json` whose inputs changed or are gone: each entry's
860
+ * key and its changed and missing inputs (package-relative).
861
+ */
862
+ readonly staleBuildEntries?: readonly DoctorStaleBuildEntry[];
863
+ }
830
864
  /**
831
865
  * One audited package. A staleness verdict and a fork are separate entries, so a stale library that also forks
832
866
  * appears twice: once with its staleness status and once with `fork`.
833
867
  */
834
- interface DoctorPackageResult {
868
+ interface DoctorPackageResult extends DoctorFreshnessDetails {
835
869
  /** The package name, or the bare specifier the app imports a consumed library under. */
836
870
  readonly name: string;
837
871
  /** The verdict. */
@@ -850,20 +884,6 @@ interface DoctorPackageResult {
850
884
  readonly version?: string;
851
885
  /** The real package directory, for a staleness entry. */
852
886
  readonly directory?: string;
853
- /** The source fingerprint the packed manifest recorded. */
854
- readonly manifestSourceHash?: string;
855
- /** The source fingerprint computed from the local source now. */
856
- readonly recomputedSourceHash?: string;
857
- /** The recorded source folders and files that are no longer on disk. */
858
- readonly missingSource?: readonly string[];
859
- /** The current source directory, when the source moved since the pack. */
860
- readonly currentSource?: string;
861
- /** The hash of the files the bundle read outside the source, as the packed manifest recorded it. */
862
- readonly manifestBundleInputHash?: string;
863
- /** That hash recomputed from the files now, when the source matched and every file is on disk. */
864
- readonly recomputedBundleInputHash?: string;
865
- /** The recorded bundle inputs (package-relative) that are no longer on disk. */
866
- readonly missingBundleInputs?: readonly string[];
867
887
  /** Every physical copy, for a `fork` entry. */
868
888
  readonly copies?: readonly DoctorPackageCopy[];
869
889
  }
@@ -935,6 +955,86 @@ interface DoctorJsonDocument extends CliJsonDocument {
935
955
  readonly summary: string;
936
956
  }
937
957
  //#endregion
958
+ //#region src/workspace-packages.d.ts
959
+ /** Where the member list of a {@link Workspace} comes from. */
960
+ declare const WorkspaceKind: {
961
+ /** The `packages:` list of a `pnpm-workspace.yaml`. */
962
+ readonly Pnpm: "pnpm";
963
+ /** The `workspaces` field of a root `package.json` (npm, Yarn, Bun). */
964
+ readonly PackageJson: "package-json";
965
+ /** No workspace file: the nearest package is the only member. */
966
+ readonly SinglePackage: "single-package";
967
+ /** No workspace file and no `package.json` from the start directory up. */
968
+ readonly None: "none";
969
+ };
970
+ /** One of the {@link WorkspaceKind} values. */
971
+ type WorkspaceKind = (typeof WorkspaceKind)[keyof typeof WorkspaceKind];
972
+ //#endregion
973
+ //#region src/freshness-json-document.d.ts
974
+ /**
975
+ * The verdict on one workspace package in a {@link FreshnessJsonDocument}. The first four are the staleness
976
+ * values of `DoctorPackageStatus`.
977
+ */
978
+ declare const FreshnessPackageStatus: {
979
+ /** Every recorded input of the package matches the files on disk. */
980
+ readonly Fresh: "fresh";
981
+ /** The source or another recorded input changed since the last pack, or is gone. */
982
+ readonly Stale: "stale";
983
+ /** The packed manifest has no provenance stamp, so nothing was compared. */
984
+ readonly NoProvenance: "no-provenance";
985
+ /** None of the recorded source is on disk, so nothing was compared. */
986
+ readonly NoSource: "no-source";
987
+ /** The check of the package failed, for example because its `heximon.config.ts` threw. */
988
+ readonly Error: "error";
989
+ };
990
+ /** One of the {@link FreshnessPackageStatus} values. */
991
+ type FreshnessPackageStatus = (typeof FreshnessPackageStatus)[keyof typeof FreshnessPackageStatus];
992
+ /** The stable codes of the `heximon freshness` diagnostics. */
993
+ declare const FreshnessDiagnosticCode: {
994
+ /** A workspace package changed since its last pack. Error. */
995
+ readonly StalePackage: "freshness.stale-package";
996
+ /** The check of one package failed; the message has the error. Error. */
997
+ readonly CheckFailed: "freshness.check-failed";
998
+ /** The `pnpm-workspace.yaml` or the root `package.json` cannot be read or parsed. Error. */
999
+ readonly WorkspaceUnreadable: "freshness.workspace-unreadable";
1000
+ };
1001
+ /** One of the {@link FreshnessDiagnosticCode} values. */
1002
+ type FreshnessDiagnosticCode = (typeof FreshnessDiagnosticCode)[keyof typeof FreshnessDiagnosticCode];
1003
+ /** One workspace package that has a record: a Heximon manifest, or a `dist/heximon.inputs.json`. */
1004
+ interface FreshnessPackageResult extends DoctorFreshnessDetails {
1005
+ /** The `name` of the package's `package.json`, else its folder name. */
1006
+ readonly name: string;
1007
+ /** The verdict. */
1008
+ readonly status: FreshnessPackageStatus;
1009
+ /** Why the package has this status. For `error`, the error message. */
1010
+ readonly reason: string;
1011
+ /** How to fix the package, as text. Absent when there is nothing to fix. */
1012
+ readonly hint?: string;
1013
+ /** The shell command that fixes the package. Absent when no single command fixes it. */
1014
+ readonly fix?: string;
1015
+ /** Whether this entry fails the run: `stale` and `error` do. */
1016
+ readonly failing: boolean;
1017
+ /** The `version` of the package's `package.json`, when it has one. */
1018
+ readonly version?: string;
1019
+ /** The package folder, relative to the workspace root (`.` for the root package). */
1020
+ readonly path: string;
1021
+ /** The absolute package folder. */
1022
+ readonly directory: string;
1023
+ }
1024
+ /** The `heximon freshness --json` document. `ok` is `false` exactly when the run exits 1. */
1025
+ interface FreshnessJsonDocument extends CliJsonDocument {
1026
+ /** The absolute workspace root. */
1027
+ readonly workspaceRoot: string;
1028
+ /** Where the member list came from. */
1029
+ readonly workspaceKind: WorkspaceKind;
1030
+ /** Each workspace package that has a record. With `--stale`, only the entries that fail the run. */
1031
+ readonly packages: readonly FreshnessPackageResult[];
1032
+ /** How many workspace packages have no record, so they were not checked. */
1033
+ readonly skippedPackages: number;
1034
+ /** The closing sentence of the text report. */
1035
+ readonly summary: string;
1036
+ }
1037
+ //#endregion
938
1038
  //#region src/module-graph-report.d.ts
939
1039
  /** A cross-package boundary node summarized for the graph report — its factory plus its `requires`/`exports` tokens. */
940
1040
  interface BoundarySummary {
@@ -1395,4 +1495,4 @@ interface WorkflowsJsonDocument extends CliJsonDocument {
1395
1495
  };
1396
1496
  }
1397
1497
  //#endregion
1398
- export type { BoundarySummary, CheckDiagnosticCode, CheckJsonDocument, CheckUpgradeBaselineResult, CliDiagnostic, CliDiagnosticCode, CliFailureDocument, CliJsonDocument, CreateDiagnosticCode, CreateJsonDocument, DeployAction, DeployDiagnosticCode, DeployJsonCredential, DeployJsonDestroy, DeployJsonDocument, DeployJsonMigration, DeployJsonResources, DeployJsonShip, DeployJsonShipResult, DeployJsonTarget, DoctorCopyCensusResult, DoctorCopyCensusStatus, DoctorDiagnosticCode, DoctorJsonDocument, DoctorMode, DoctorPackageCopy, DoctorPackageResult, DoctorPackageStatus, DoctorPeerClosureResult, DoctorPeerPivot, DoctorPlatformTracingResult, DoctorRegistryResult, GraphDiagnosticCode, GraphJsonDocument, InstalledSkill, MigrationsDatabaseView, MigrationsDiagnosticCode, MigrationsDrizzleKitResult, MigrationsGenerateEntry, MigrationsGenerateJsonDocument, MigrationsMigrateJsonDocument, MigrationsPushJsonDocument, MigrationsStatusEntry, MigrationsStatusJsonDocument, MigrationsStepEntry, MigrationsStepStatus, PreparedTarget, ProcessRunResult, ProcessRunner, Prompter, ProvisionDiagnosticCode, ProvisionJsonDocument, ProvisionJsonResource, ProvisionJsonResult, RegistryAccessReport, RoutesDiagnosticCode, RoutesJsonDocument, ScaffoldRequest, ScaffoldResult, SkillJsonEntry, SkillSourceJsonEntry, SkillSourceRecording, SkillsAddJsonDocument, SkillsDiagnosticCode, SkillsSyncJsonDocument, StartersScaffoldRequest, TaskDiagnosticCode, TaskRunError, TaskRunJsonDocument, TemplateSelection, TemplateSource, WorkflowBaselineState, WorkflowInstanceInventory, WorkflowInstanceView, WorkflowStatusEntry, WorkflowStatusJsonEntry, WorkflowsDiagnosticCode, WorkflowsJsonDocument };
1498
+ export type { BoundarySummary, CheckDiagnosticCode, CheckJsonDocument, CheckUpgradeBaselineResult, CliDiagnostic, CliDiagnosticCode, CliFailureDocument, CliJsonDocument, CreateDiagnosticCode, CreateJsonDocument, DeployAction, DeployDiagnosticCode, DeployJsonCredential, DeployJsonDestroy, DeployJsonDocument, DeployJsonMigration, DeployJsonResources, DeployJsonShip, DeployJsonShipResult, DeployJsonTarget, DoctorCopyCensusResult, DoctorCopyCensusStatus, DoctorDiagnosticCode, DoctorFreshnessDetails, DoctorJsonDocument, DoctorMode, DoctorPackageCopy, DoctorPackageResult, DoctorPackageStatus, DoctorPeerClosureResult, DoctorPeerPivot, DoctorPlatformTracingResult, DoctorRegistryResult, DoctorStaleBuildEntry, FreshnessDiagnosticCode, FreshnessJsonDocument, FreshnessPackageResult, FreshnessPackageStatus, GraphDiagnosticCode, GraphJsonDocument, InstalledSkill, MigrationsDatabaseView, MigrationsDiagnosticCode, MigrationsDrizzleKitResult, MigrationsGenerateEntry, MigrationsGenerateJsonDocument, MigrationsMigrateJsonDocument, MigrationsPushJsonDocument, MigrationsStatusEntry, MigrationsStatusJsonDocument, MigrationsStepEntry, MigrationsStepStatus, PreparedTarget, ProcessRunResult, ProcessRunner, Prompter, ProvisionDiagnosticCode, ProvisionJsonDocument, ProvisionJsonResource, ProvisionJsonResult, RegistryAccessReport, RoutesDiagnosticCode, RoutesJsonDocument, ScaffoldRequest, ScaffoldResult, SkillJsonEntry, SkillSourceJsonEntry, SkillSourceRecording, SkillsAddJsonDocument, SkillsDiagnosticCode, SkillsSyncJsonDocument, StartersScaffoldRequest, TaskDiagnosticCode, TaskRunError, TaskRunJsonDocument, TemplateSelection, TemplateSource, WorkflowBaselineState, WorkflowInstanceInventory, WorkflowInstanceView, WorkflowStatusEntry, WorkflowStatusJsonEntry, WorkflowsDiagnosticCode, WorkflowsJsonDocument, WorkspaceKind };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heximon",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "The Heximon CLI: create, develop, build, migrate, and deploy Heximon apps.",
5
5
  "keywords": [
6
6
  "cli",
@@ -38,26 +38,26 @@
38
38
  "dependencies": {
39
39
  "@bomb.sh/tab": "^0.0.22",
40
40
  "@clack/prompts": "^1.8.1",
41
- "@heximon/compiler": "0.10.0",
42
- "@heximon/cqrs": "0.10.0",
43
- "@heximon/deploy": "0.10.0",
44
- "@heximon/http": "0.10.0",
45
- "@heximon/node": "0.10.0",
46
- "@heximon/runtime": "0.10.0",
47
- "@heximon/testing": "0.10.0",
41
+ "@heximon/compiler": "0.11.0",
42
+ "@heximon/cqrs": "0.11.0",
43
+ "@heximon/deploy": "0.11.0",
44
+ "@heximon/http": "0.11.0",
45
+ "@heximon/node": "0.11.0",
46
+ "@heximon/runtime": "0.11.0",
47
+ "@heximon/testing": "0.11.0",
48
48
  "citty": "^0.2.2",
49
49
  "giget": "^3.3.1",
50
50
  "pathe": "^2.0.3"
51
51
  },
52
52
  "devDependencies": {
53
- "@heximon/cloudflare": "0.10.0",
54
- "@heximon/durable": "0.10.0",
55
- "@heximon/integration": "0.10.0",
56
- "@heximon/library": "0.10.0",
57
- "@heximon/nitro": "0.10.0",
58
- "@heximon/queue": "0.10.0",
59
- "@heximon/schedule": "0.10.0",
60
- "@heximon/schema": "0.10.0",
53
+ "@heximon/cloudflare": "0.11.0",
54
+ "@heximon/durable": "0.11.0",
55
+ "@heximon/integration": "0.11.0",
56
+ "@heximon/library": "0.11.0",
57
+ "@heximon/nitro": "0.11.0",
58
+ "@heximon/queue": "0.11.0",
59
+ "@heximon/schedule": "0.11.0",
60
+ "@heximon/schema": "0.11.0",
61
61
  "@heximon/test-support": "0.0.1",
62
62
  "@types/node": "^24.19.0",
63
63
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",