@visulima/vis 1.0.0-alpha.1 → 1.0.0-alpha.11

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +403 -12
  2. package/LICENSE.md +283 -0
  3. package/README.md +254 -9
  4. package/dist/bin.js +9 -146
  5. package/dist/config/index.d.ts +1818 -0
  6. package/dist/config/index.js +2 -0
  7. package/dist/generate/index.d.ts +157 -0
  8. package/dist/generate/index.js +3 -0
  9. package/dist/packem_chunks/applyDefaults.js +336 -0
  10. package/dist/packem_chunks/bin.js +9577 -0
  11. package/dist/packem_chunks/doctor-probe.js +112 -0
  12. package/dist/packem_chunks/fix.js +234 -0
  13. package/dist/packem_chunks/handler.js +99 -0
  14. package/dist/packem_chunks/handler10.js +53 -0
  15. package/dist/packem_chunks/handler11.js +32 -0
  16. package/dist/packem_chunks/handler12.js +100 -0
  17. package/dist/packem_chunks/handler13.js +25 -0
  18. package/dist/packem_chunks/handler14.js +916 -0
  19. package/dist/packem_chunks/handler15.js +206 -0
  20. package/dist/packem_chunks/handler16.js +124 -0
  21. package/dist/packem_chunks/handler17.js +13 -0
  22. package/dist/packem_chunks/handler18.js +106 -0
  23. package/dist/packem_chunks/handler19.js +19 -0
  24. package/dist/packem_chunks/handler2.js +75 -0
  25. package/dist/packem_chunks/handler20.js +29 -0
  26. package/dist/packem_chunks/handler21.js +222 -0
  27. package/dist/packem_chunks/handler22.js +237 -0
  28. package/dist/packem_chunks/handler23.js +101 -0
  29. package/dist/packem_chunks/handler24.js +110 -0
  30. package/dist/packem_chunks/handler25.js +402 -0
  31. package/dist/packem_chunks/handler26.js +13 -0
  32. package/dist/packem_chunks/handler27.js +63 -0
  33. package/dist/packem_chunks/handler28.js +34 -0
  34. package/dist/packem_chunks/handler29.js +458 -0
  35. package/dist/packem_chunks/handler3.js +95 -0
  36. package/dist/packem_chunks/handler30.js +170 -0
  37. package/dist/packem_chunks/handler31.js +530 -0
  38. package/dist/packem_chunks/handler32.js +214 -0
  39. package/dist/packem_chunks/handler33.js +119 -0
  40. package/dist/packem_chunks/handler34.js +630 -0
  41. package/dist/packem_chunks/handler35.js +283 -0
  42. package/dist/packem_chunks/handler36.js +542 -0
  43. package/dist/packem_chunks/handler37.js +762 -0
  44. package/dist/packem_chunks/handler38.js +989 -0
  45. package/dist/packem_chunks/handler39.js +574 -0
  46. package/dist/packem_chunks/handler4.js +90 -0
  47. package/dist/packem_chunks/handler40.js +1685 -0
  48. package/dist/packem_chunks/handler41.js +1088 -0
  49. package/dist/packem_chunks/handler42.js +797 -0
  50. package/dist/packem_chunks/handler43.js +2658 -0
  51. package/dist/packem_chunks/handler44.js +3886 -0
  52. package/dist/packem_chunks/handler45.js +2574 -0
  53. package/dist/packem_chunks/handler46.js +3769 -0
  54. package/dist/packem_chunks/handler47.js +1491 -0
  55. package/dist/packem_chunks/handler5.js +174 -0
  56. package/dist/packem_chunks/handler6.js +95 -0
  57. package/dist/packem_chunks/handler7.js +115 -0
  58. package/dist/packem_chunks/handler8.js +12 -0
  59. package/dist/packem_chunks/handler9.js +29 -0
  60. package/dist/packem_chunks/heal-accept.js +522 -0
  61. package/dist/packem_chunks/heal.js +673 -0
  62. package/dist/packem_chunks/index.js +873 -0
  63. package/dist/packem_chunks/loader.js +23 -0
  64. package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +1316 -0
  65. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +5 -0
  66. package/dist/packem_shared/ai-analysis-CHeB1joD.js +367 -0
  67. package/dist/packem_shared/ai-cache-Be_jexe4.js +142 -0
  68. package/dist/packem_shared/ai-fix-B9iQVcD2.js +379 -0
  69. package/dist/packem_shared/cache-directory-2qvs4goY.js +98 -0
  70. package/dist/packem_shared/catalog-BJTtyi-O.js +1371 -0
  71. package/dist/packem_shared/dependency-scan-A0KSklpG.js +188 -0
  72. package/dist/packem_shared/docker-2iZzc280.js +181 -0
  73. package/dist/packem_shared/failure-log-Cz3Z4SKL.js +100 -0
  74. package/dist/packem_shared/flakiness-goTxXuCX.js +180 -0
  75. package/dist/packem_shared/otel-DCvqCTz_.js +158 -0
  76. package/dist/packem_shared/otelPlugin-DFaLDvJf.js +3 -0
  77. package/dist/packem_shared/registry-CbqXI0rc.js +272 -0
  78. package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +130 -0
  79. package/dist/packem_shared/runtime-check-Cobi3p6l.js +127 -0
  80. package/dist/packem_shared/selectors-SM69TfqC.js +194 -0
  81. package/dist/packem_shared/symbols-Ta7g2nU-.js +14 -0
  82. package/dist/packem_shared/toolchain-BdZd9eBi.js +975 -0
  83. package/dist/packem_shared/typosquats-C-bCh3PX.js +1210 -0
  84. package/dist/packem_shared/use-measured-height-CNP0vT4M.js +20 -0
  85. package/dist/packem_shared/utils-CthVdBPS.js +40 -0
  86. package/dist/packem_shared/xxh3-Ck8mXNg1.js +239 -0
  87. package/index.js +773 -0
  88. package/package.json +82 -21
  89. package/schemas/project.schema.json +420 -0
  90. package/schemas/vis-config.schema.json +501 -0
  91. package/skills/vis/SKILL.md +96 -0
  92. package/templates/buildkite-ci/.buildkite/pipeline.yml.tera +85 -0
  93. package/templates/buildkite-ci/template.yml +20 -0
  94. package/dist/ai-analysis.d.ts +0 -40
  95. package/dist/ai-cache.d.ts +0 -21
  96. package/dist/bin.d.ts +0 -1
  97. package/dist/catalog.d.ts +0 -110
  98. package/dist/commands/affected.d.ts +0 -3
  99. package/dist/commands/ai.d.ts +0 -3
  100. package/dist/commands/analyze.d.ts +0 -3
  101. package/dist/commands/check.d.ts +0 -3
  102. package/dist/commands/graph.d.ts +0 -3
  103. package/dist/commands/hook/constants.d.ts +0 -8
  104. package/dist/commands/hook/index.d.ts +0 -3
  105. package/dist/commands/hook/install.d.ts +0 -7
  106. package/dist/commands/hook/migrate.d.ts +0 -27
  107. package/dist/commands/hook/uninstall.d.ts +0 -3
  108. package/dist/commands/migrate/constants.d.ts +0 -12
  109. package/dist/commands/migrate/deps.d.ts +0 -32
  110. package/dist/commands/migrate/index.d.ts +0 -3
  111. package/dist/commands/migrate/json.d.ts +0 -20
  112. package/dist/commands/migrate/lint-staged.d.ts +0 -62
  113. package/dist/commands/migrate/types.d.ts +0 -20
  114. package/dist/commands/run.d.ts +0 -3
  115. package/dist/commands/staged.d.ts +0 -3
  116. package/dist/commands/update.d.ts +0 -3
  117. package/dist/config.d.ts +0 -40
  118. package/dist/config.js +0 -1
  119. package/dist/package-manager.d.ts +0 -23
  120. package/dist/workspace.d.ts +0 -58
@@ -0,0 +1,188 @@
1
+ import { dim, yellow, green, red } from '@visulima/colorize';
2
+ import { Box, Text, Spinner, render } from '@visulima/tui';
3
+ import { i as isInCi, S as SYMBOLS } from '../packem_chunks/bin.js';
4
+ import React from 'react';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+ import { D as DASH, W as WARNING, T as TICK, C as CROSS } from './symbols-Ta7g2nU-.js';
7
+ import { readFileSync } from '@visulima/fs';
8
+ import { parseLockFileContent } from '@visulima/package';
9
+ import { join } from '@visulima/path';
10
+
11
+ const ScanProgressApp = ({ rows }) => jsx(Box, { flexDirection: "column", children: rows.map((row) => {
12
+ let icon;
13
+ switch (row.status) {
14
+ case "error": {
15
+ icon = jsx(Text, { color: "red", children: CROSS });
16
+ break;
17
+ }
18
+ case "ok": {
19
+ icon = jsx(Text, { color: "green", children: TICK });
20
+ break;
21
+ }
22
+ case "running": {
23
+ icon = jsx(Text, { color: "white", children: jsx(Spinner, { type: "dots" }) });
24
+ break;
25
+ }
26
+ case "warn": {
27
+ icon = jsx(Text, { color: "yellow", children: WARNING });
28
+ break;
29
+ }
30
+ default: {
31
+ icon = jsx(Text, { dimColor: true, children: DASH });
32
+ break;
33
+ }
34
+ }
35
+ return jsxs(Box, { children: [
36
+ jsx(Box, { width: 3, children: icon }),
37
+ jsx(Box, { flexGrow: 1, children: jsx(Text, { children: row.label }) }),
38
+ row.summary ? jsx(Box, { children: jsxs(Text, { dimColor: true, children: [
39
+ DASH,
40
+ " ",
41
+ row.summary
42
+ ] }) }) : null
43
+ ] }, row.id);
44
+ }) });
45
+
46
+ const STATIC_GLYPH = {
47
+ error: red(SYMBOLS.failure),
48
+ ok: green(SYMBOLS.success),
49
+ pending: dim(SYMBOLS.dash),
50
+ skip: dim(SYMBOLS.dash),
51
+ warn: yellow(SYMBOLS.warning)
52
+ };
53
+ const formatStaticRow = (label, status, summary) => {
54
+ const text = summary ? `${label} ${dim(`— ${summary}`)}` : label;
55
+ return ` ${STATIC_GLYPH[status]} ${text}
56
+ `;
57
+ };
58
+ const startScanProgress = (tasks, options = {}) => {
59
+ const stream = options.stream ?? process.stderr;
60
+ const isTty = typeof stream.isTTY === "boolean" && stream.isTTY;
61
+ const liveDefault = isTty && !isInCi;
62
+ const live = options.live ?? liveDefault;
63
+ const states = /* @__PURE__ */ new Map();
64
+ for (const task of tasks) {
65
+ states.set(task.id, { id: task.id, label: task.label, status: "pending" });
66
+ }
67
+ if (!live || tasks.length === 0) {
68
+ return {
69
+ finish: (id, status, summary) => {
70
+ const state = states.get(id);
71
+ if (!state) {
72
+ return;
73
+ }
74
+ states.set(id, { ...state, status, summary });
75
+ stream.write(formatStaticRow(state.label, status, summary));
76
+ },
77
+ start: (id) => {
78
+ const state = states.get(id);
79
+ if (state) {
80
+ states.set(id, { ...state, status: "running" });
81
+ }
82
+ },
83
+ stop: () => {
84
+ }
85
+ };
86
+ }
87
+ const buildRows = () => tasks.map((task) => states.get(task.id));
88
+ let instance = render(React.createElement(ScanProgressApp, { rows: buildRows() }), {
89
+ interactive: true,
90
+ patchConsole: false
91
+ });
92
+ const rerender = () => {
93
+ instance?.rerender(React.createElement(ScanProgressApp, { rows: buildRows() }));
94
+ };
95
+ return {
96
+ finish: (id, status, summary) => {
97
+ const state = states.get(id);
98
+ if (!state) {
99
+ return;
100
+ }
101
+ states.set(id, { ...state, status, summary });
102
+ rerender();
103
+ },
104
+ start: (id) => {
105
+ const state = states.get(id);
106
+ if (!state) {
107
+ return;
108
+ }
109
+ states.set(id, { ...state, status: "running" });
110
+ rerender();
111
+ },
112
+ stop: () => {
113
+ if (!instance) {
114
+ return;
115
+ }
116
+ rerender();
117
+ instance.unmount();
118
+ instance = void 0;
119
+ }
120
+ };
121
+ };
122
+
123
+ const LOCKFILE_NAMES = {
124
+ bun: { file: "bun.lock", type: "bun" },
125
+ npm: { file: "package-lock.json", type: "npm" },
126
+ pnpm: { file: "pnpm-lock.yaml", type: "pnpm" },
127
+ yarn: { file: "yarn.lock", type: "yarn" }
128
+ };
129
+ const lockedPackages = (workspaceRoot, pmName) => {
130
+ const lockInfo = LOCKFILE_NAMES[pmName];
131
+ if (!lockInfo) {
132
+ return [];
133
+ }
134
+ let lockContent;
135
+ try {
136
+ lockContent = readFileSync(join(workspaceRoot, lockInfo.file));
137
+ } catch {
138
+ return [];
139
+ }
140
+ const entries = parseLockFileContent(lockContent, lockInfo.type);
141
+ if (entries.length === 0) {
142
+ return [];
143
+ }
144
+ const seen = /* @__PURE__ */ new Set();
145
+ const packages = [];
146
+ for (const entry of entries) {
147
+ const key = `${entry.name}@${entry.version}`;
148
+ if (seen.has(key)) {
149
+ continue;
150
+ }
151
+ seen.add(key);
152
+ packages.push({ isDev: false, name: entry.name, version: entry.version });
153
+ }
154
+ return packages;
155
+ };
156
+ const findDuplicateDependencies = (workspaceRoot, pmName) => {
157
+ const lockInfo = LOCKFILE_NAMES[pmName];
158
+ if (!lockInfo) {
159
+ return [];
160
+ }
161
+ let lockContent;
162
+ try {
163
+ lockContent = readFileSync(join(workspaceRoot, lockInfo.file));
164
+ } catch {
165
+ return [];
166
+ }
167
+ const entries = parseLockFileContent(lockContent, lockInfo.type);
168
+ if (entries.length === 0) {
169
+ return [];
170
+ }
171
+ const versionMap = /* @__PURE__ */ new Map();
172
+ for (const entry of entries) {
173
+ if (!versionMap.has(entry.name)) {
174
+ versionMap.set(entry.name, /* @__PURE__ */ new Set());
175
+ }
176
+ versionMap.get(entry.name).add(entry.version);
177
+ }
178
+ const duplicates = [];
179
+ for (const [name, versions] of versionMap) {
180
+ if (versions.size <= 1) {
181
+ continue;
182
+ }
183
+ duplicates.push({ name, versions: [...versions] });
184
+ }
185
+ return duplicates.sort((a, b) => a.name.localeCompare(b.name));
186
+ };
187
+
188
+ export { findDuplicateDependencies as f, lockedPackages as l, startScanProgress as s };
@@ -0,0 +1,181 @@
1
+ import { createRequire as __cjs_createRequire } from "node:module";
2
+
3
+ const __cjs_require = __cjs_createRequire(import.meta.url);
4
+
5
+ const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
+
7
+ const __cjs_getBuiltinModule = (module) => {
8
+ // Check if we're in Node.js and version supports getBuiltinModule
9
+ if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
+ const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
+ // Node.js 20.16.0+ and 22.3.0+
12
+ if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
+ return __cjs_getProcess.getBuiltinModule(module);
14
+ }
15
+ }
16
+ // Fallback to createRequire
17
+ return __cjs_require(module);
18
+ };
19
+
20
+ const {
21
+ rmSync,
22
+ cpSync,
23
+ lstatSync,
24
+ readdirSync
25
+ } = __cjs_getBuiltinModule("node:fs");
26
+ import { writeFileSync, isAccessibleSync, readJsonSync, ensureDirSync } from '@visulima/fs';
27
+ import { join, relative, dirname } from '@visulima/path';
28
+
29
+ const collectTransitiveProjectDeps = (start, projectGraph) => {
30
+ const result = /* @__PURE__ */ new Set();
31
+ const queue = [start];
32
+ const visited = /* @__PURE__ */ new Set([start]);
33
+ while (queue.length > 0) {
34
+ const current = queue.shift();
35
+ const edges = projectGraph.dependencies[current] ?? [];
36
+ for (const edge of edges) {
37
+ if (visited.has(edge.target)) {
38
+ continue;
39
+ }
40
+ visited.add(edge.target);
41
+ result.add(edge.target);
42
+ queue.push(edge.target);
43
+ }
44
+ }
45
+ return result;
46
+ };
47
+ const DOCKER_MANIFEST_FILENAME = "vis-docker-manifest.json";
48
+ const MANIFEST_FILES = ["package.json", "project.json"];
49
+ const ROOT_MANIFEST_FILES = [
50
+ "package.json",
51
+ "pnpm-workspace.yaml",
52
+ "pnpm-lock.yaml",
53
+ "package-lock.json",
54
+ "yarn.lock",
55
+ "bun.lock",
56
+ "bun.lockb",
57
+ ".npmrc",
58
+ "vis.config.ts",
59
+ "vis.config.mts",
60
+ "vis.config.cts",
61
+ "vis.config.js",
62
+ "vis.config.mjs",
63
+ "vis.config.cjs"
64
+ ];
65
+ const resolveFocusProjects = (focus, projectGraph) => {
66
+ const result = new Set(focus);
67
+ for (const name of focus) {
68
+ const transitive = collectTransitiveProjectDeps(name, projectGraph);
69
+ for (const dep of transitive) {
70
+ result.add(dep);
71
+ }
72
+ }
73
+ return result;
74
+ };
75
+ const ensureDir = (path) => {
76
+ ensureDirSync(path);
77
+ };
78
+ const copyFileIfExists = (src, dest) => {
79
+ try {
80
+ ensureDir(dirname(dest));
81
+ cpSync(src, dest);
82
+ return true;
83
+ } catch (error) {
84
+ if (error.code === "ENOENT") {
85
+ return false;
86
+ }
87
+ throw error;
88
+ }
89
+ };
90
+ const copyTreeExcludingNodeModules = (src, dest) => {
91
+ let stats;
92
+ try {
93
+ stats = lstatSync(src);
94
+ } catch {
95
+ return;
96
+ }
97
+ if (stats.isSymbolicLink()) {
98
+ return;
99
+ }
100
+ if (stats.isFile()) {
101
+ ensureDir(dirname(dest));
102
+ cpSync(src, dest);
103
+ return;
104
+ }
105
+ ensureDir(dest);
106
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
107
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.isSymbolicLink()) {
108
+ continue;
109
+ }
110
+ copyTreeExcludingNodeModules(join(src, entry.name), join(dest, entry.name));
111
+ }
112
+ };
113
+ const scaffoldDockerContext = (options) => {
114
+ const { focus, includeSources = false, outDir, projectGraph, workspace, workspaceRoot } = options;
115
+ const unknown = focus.filter((name) => workspace.projects[name] === void 0);
116
+ if (unknown.length > 0) {
117
+ throw new Error(`Unknown focus project(s): ${unknown.join(", ")}. Check project names in your workspace.`);
118
+ }
119
+ const projects = resolveFocusProjects(focus, projectGraph);
120
+ const workspaceDir = join(outDir, "workspace");
121
+ const sourcesDir = join(outDir, "sources");
122
+ rmSync(workspaceDir, { force: true, recursive: true });
123
+ rmSync(sourcesDir, { force: true, recursive: true });
124
+ ensureDir(workspaceDir);
125
+ for (const manifest of ROOT_MANIFEST_FILES) {
126
+ copyFileIfExists(join(workspaceRoot, manifest), join(workspaceDir, manifest));
127
+ }
128
+ for (const name of projects) {
129
+ const project = workspace.projects[name];
130
+ if (!project?.root) {
131
+ continue;
132
+ }
133
+ for (const manifest of MANIFEST_FILES) {
134
+ copyFileIfExists(join(workspaceRoot, project.root, manifest), join(workspaceDir, project.root, manifest));
135
+ }
136
+ }
137
+ if (includeSources) {
138
+ ensureDir(sourcesDir);
139
+ for (const name of focus) {
140
+ const project = workspace.projects[name];
141
+ if (!project?.root) {
142
+ continue;
143
+ }
144
+ copyTreeExcludingNodeModules(join(workspaceRoot, project.root), join(sourcesDir, project.root));
145
+ }
146
+ }
147
+ writeFileSync(join(outDir, DOCKER_MANIFEST_FILENAME), `${JSON.stringify({ focus, projects: [...projects].sort() }, null, 2)}
148
+ `);
149
+ return { projects: [...projects] };
150
+ };
151
+ const pruneDockerContext = (options) => {
152
+ const { contextRoot, workspace, workspaceRoot } = options;
153
+ const manifestPath = join(contextRoot, DOCKER_MANIFEST_FILENAME);
154
+ if (!isAccessibleSync(manifestPath)) {
155
+ throw new Error(`No ${DOCKER_MANIFEST_FILENAME} at ${contextRoot}. Run \`vis docker scaffold\` first.`);
156
+ }
157
+ const manifest = readJsonSync(manifestPath);
158
+ if (!Array.isArray(manifest.projects)) {
159
+ throw new TypeError(`Invalid ${DOCKER_MANIFEST_FILENAME}: "projects" must be an array.`);
160
+ }
161
+ const keep = new Set(manifest.projects);
162
+ const removed = [];
163
+ for (const [name, project] of Object.entries(workspace.projects)) {
164
+ if (keep.has(name)) {
165
+ continue;
166
+ }
167
+ if (!project.root) {
168
+ continue;
169
+ }
170
+ const absolute = join(workspaceRoot, project.root);
171
+ const rel = relative(workspaceRoot, absolute);
172
+ if (rel === "" || rel === "." || rel.startsWith("..")) {
173
+ continue;
174
+ }
175
+ rmSync(absolute, { force: true, recursive: true });
176
+ removed.push(rel);
177
+ }
178
+ return { removed };
179
+ };
180
+
181
+ export { pruneDockerContext as p, resolveFocusProjects as r, scaffoldDockerContext as s };
@@ -0,0 +1,100 @@
1
+ import { createRequire as __cjs_createRequire } from "node:module";
2
+
3
+ const __cjs_require = __cjs_createRequire(import.meta.url);
4
+
5
+ const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
+
7
+ const __cjs_getBuiltinModule = (module) => {
8
+ // Check if we're in Node.js and version supports getBuiltinModule
9
+ if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
+ const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
+ // Node.js 20.16.0+ and 22.3.0+
12
+ if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
+ return __cjs_getProcess.getBuiltinModule(module);
14
+ }
15
+ }
16
+ // Fallback to createRequire
17
+ return __cjs_require(module);
18
+ };
19
+
20
+ const {
21
+ mkdirSync,
22
+ writeFileSync,
23
+ readdirSync
24
+ } = __cjs_getBuiltinModule("node:fs");
25
+ const {
26
+ readFile
27
+ } = __cjs_getBuiltinModule("node:fs/promises");
28
+ import { join } from '@visulima/path';
29
+
30
+ const FAILURE_LOG_DIRNAME = "last-failures";
31
+ const getFailureLogDirectory = (workspaceRoot) => join(workspaceRoot, ".task-runner", FAILURE_LOG_DIRNAME);
32
+ const getFailureLogPath = (workspaceRoot, taskId) => join(getFailureLogDirectory(workspaceRoot), `${encodeURIComponent(taskId)}.json`);
33
+ class FailureLogLifeCycle {
34
+ #workspaceRoot;
35
+ #runId;
36
+ constructor(workspaceRoot, runId) {
37
+ this.#workspaceRoot = workspaceRoot;
38
+ this.#runId = runId;
39
+ }
40
+ printTaskTerminalOutput(task, status, terminalOutput) {
41
+ if (status !== "failure") {
42
+ return;
43
+ }
44
+ const directory = getFailureLogDirectory(this.#workspaceRoot);
45
+ try {
46
+ mkdirSync(directory, { recursive: true });
47
+ } catch {
48
+ return;
49
+ }
50
+ const command = task.overrides["command"];
51
+ const cwd = task.projectRoot;
52
+ const entry = {
53
+ command,
54
+ cwd,
55
+ exitCode: void 0,
56
+ hash: task.hash,
57
+ runId: this.#runId,
58
+ status,
59
+ taskId: task.id,
60
+ terminalOutput,
61
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
62
+ };
63
+ try {
64
+ writeFileSync(getFailureLogPath(this.#workspaceRoot, task.id), `${JSON.stringify(entry, void 0, 2)}
65
+ `, "utf8");
66
+ } catch {
67
+ }
68
+ }
69
+ }
70
+ const loadFailureLog = async (workspaceRoot, taskId) => {
71
+ const path = getFailureLogPath(workspaceRoot, taskId);
72
+ try {
73
+ const content = await readFile(path, "utf8");
74
+ return JSON.parse(content);
75
+ } catch {
76
+ return void 0;
77
+ }
78
+ };
79
+ const listFailureLogs = (workspaceRoot) => {
80
+ const directory = getFailureLogDirectory(workspaceRoot);
81
+ let entries;
82
+ try {
83
+ entries = readdirSync(directory);
84
+ } catch {
85
+ return [];
86
+ }
87
+ const taskIds = [];
88
+ for (const name of entries) {
89
+ if (!name.endsWith(".json")) {
90
+ continue;
91
+ }
92
+ try {
93
+ taskIds.push(decodeURIComponent(name.slice(0, -".json".length)));
94
+ } catch {
95
+ }
96
+ }
97
+ return taskIds;
98
+ };
99
+
100
+ export { FailureLogLifeCycle as F, loadFailureLog as a, listFailureLogs as l };
@@ -0,0 +1,180 @@
1
+ import { createRequire as __cjs_createRequire } from "node:module";
2
+
3
+ const __cjs_require = __cjs_createRequire(import.meta.url);
4
+
5
+ const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
+
7
+ const __cjs_getBuiltinModule = (module) => {
8
+ // Check if we're in Node.js and version supports getBuiltinModule
9
+ if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
+ const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
+ // Node.js 20.16.0+ and 22.3.0+
12
+ if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
+ return __cjs_getProcess.getBuiltinModule(module);
14
+ }
15
+ }
16
+ // Fallback to createRequire
17
+ return __cjs_require(module);
18
+ };
19
+
20
+ const {
21
+ readdirSync
22
+ } = __cjs_getBuiltinModule("node:fs");
23
+ import { isAccessibleSync, readJsonSync } from '@visulima/fs';
24
+ import { join } from '@visulima/path';
25
+
26
+ const formatTimingSummary = (results, durationMs) => {
27
+ let succeeded = 0;
28
+ let cached = 0;
29
+ let failed = 0;
30
+ for (const [, result] of results) {
31
+ switch (result.status) {
32
+ case "failure": {
33
+ failed += 1;
34
+ break;
35
+ }
36
+ case "local-cache":
37
+ case "local-cache-kept-existing":
38
+ case "remote-cache": {
39
+ cached += 1;
40
+ break;
41
+ }
42
+ case "success": {
43
+ succeeded += 1;
44
+ break;
45
+ }
46
+ }
47
+ }
48
+ const parts = [];
49
+ if (succeeded > 0) {
50
+ parts.push(`${String(succeeded)} succeeded`);
51
+ }
52
+ if (cached > 0) {
53
+ parts.push(`${String(cached)} cached`);
54
+ }
55
+ if (failed > 0) {
56
+ parts.push(`${String(failed)} failed`);
57
+ }
58
+ const seconds = (durationMs / 1e3).toFixed(1);
59
+ parts.push(`${seconds}s`);
60
+ return parts.join(" · ");
61
+ };
62
+ const loadRunSummaries = (workspaceRoot) => {
63
+ const runsDir = join(workspaceRoot, ".task-runner", "runs");
64
+ if (!isAccessibleSync(runsDir)) {
65
+ return [];
66
+ }
67
+ const files = readdirSync(runsDir).filter((f) => f.endsWith(".json"));
68
+ const summaries = [];
69
+ for (const file of files) {
70
+ try {
71
+ summaries.push(readJsonSync(join(runsDir, file)));
72
+ } catch {
73
+ }
74
+ }
75
+ return summaries;
76
+ };
77
+ const compareDuration = (workspaceRoot, currentDurationMs, summaries) => {
78
+ const history = summaries ?? loadRunSummaries(workspaceRoot);
79
+ if (history.length < 2) {
80
+ return void 0;
81
+ }
82
+ let totalDuration = 0;
83
+ let count = 0;
84
+ for (const data of history) {
85
+ if (typeof data.duration === "number" && data.duration > 0) {
86
+ totalDuration += data.duration;
87
+ count += 1;
88
+ }
89
+ }
90
+ if (count < 2) {
91
+ return void 0;
92
+ }
93
+ const avgMs = totalDuration / count;
94
+ const diffMs = avgMs - currentDurationMs;
95
+ const absDiffSeconds = Math.abs(diffMs / 1e3).toFixed(1);
96
+ if (Math.abs(diffMs) < 500) {
97
+ return "(about average)";
98
+ }
99
+ return diffMs > 0 ? `(${absDiffSeconds}s faster than avg)` : `(${absDiffSeconds}s slower than avg)`;
100
+ };
101
+
102
+ const analyzeFlakiness = (workspaceRoot, options = {}, summaries) => {
103
+ const history = summaries ?? loadRunSummaries(workspaceRoot);
104
+ if (history.length === 0) {
105
+ return [];
106
+ }
107
+ const stats = /* @__PURE__ */ new Map();
108
+ for (const summary of history) {
109
+ if (options.since && (summary.startTime === void 0 || summary.startTime < options.since)) {
110
+ continue;
111
+ }
112
+ if (!Array.isArray(summary.tasks)) {
113
+ continue;
114
+ }
115
+ for (const task of summary.tasks) {
116
+ if (task.cacheStatus === "HIT" || task.cacheStatus === "REMOTE_HIT" || task.cacheStatus === "SKIPPED") {
117
+ continue;
118
+ }
119
+ const existing = stats.get(task.taskId) ?? {
120
+ failures: 0,
121
+ project: task.target.project,
122
+ successes: 0,
123
+ target: task.target.target,
124
+ totalRuns: 0
125
+ };
126
+ existing.totalRuns += 1;
127
+ if (task.exitCode !== void 0 && task.exitCode !== 0) {
128
+ existing.failures += 1;
129
+ existing.lastFailure = task.startTime ?? summary.startTime;
130
+ } else {
131
+ existing.successes += 1;
132
+ }
133
+ stats.set(task.taskId, existing);
134
+ }
135
+ }
136
+ const minRuns = options.minRuns ?? 2;
137
+ const results = [];
138
+ for (const [taskId, data] of stats) {
139
+ if (data.totalRuns < minRuns) {
140
+ continue;
141
+ }
142
+ if (data.failures === 0) {
143
+ continue;
144
+ }
145
+ results.push({
146
+ failures: data.failures,
147
+ flakinessRate: data.failures / data.totalRuns,
148
+ lastFailure: data.lastFailure,
149
+ project: data.project,
150
+ successes: data.successes,
151
+ target: data.target,
152
+ taskId,
153
+ totalRuns: data.totalRuns
154
+ });
155
+ }
156
+ results.sort((a, b) => b.flakinessRate - a.flakinessRate);
157
+ return results;
158
+ };
159
+ const formatFlakinessTable = (stats) => {
160
+ if (stats.length === 0) {
161
+ return ["No flaky tasks detected."];
162
+ }
163
+ const header = ["Task", "Runs", "Failures", "Rate", "Last Failure"];
164
+ const rows = stats.map((s) => [s.taskId, String(s.totalRuns), String(s.failures), `${(s.flakinessRate * 100).toFixed(1)}%`, s.lastFailure ?? "—"]);
165
+ const widths = header.map((h, i) => {
166
+ const maxDataWidth = rows.reduce((max, row) => Math.max(max, (row[i] ?? "").length), 0);
167
+ return Math.max(h.length, maxDataWidth);
168
+ });
169
+ const pad = (str, width) => str.padEnd(width);
170
+ const separator = widths.map((w) => "─".repeat(w)).join("──");
171
+ const lines = [];
172
+ lines.push(header.map((h, i) => pad(h, widths[i])).join(" "));
173
+ lines.push(separator);
174
+ for (const row of rows) {
175
+ lines.push(row.map((cell, i) => pad(cell, widths[i])).join(" "));
176
+ }
177
+ return lines;
178
+ };
179
+
180
+ export { analyzeFlakiness as a, formatTimingSummary as b, compareDuration as c, formatFlakinessTable as f, loadRunSummaries as l };