@tomkapa/tayto 0.7.0 → 0.8.1
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/dist/{chunk-5V4TBQ5S.js → chunk-6ZNCVCAS.js} +24 -2
- package/dist/chunk-6ZNCVCAS.js.map +1 -0
- package/dist/index.js +160 -22
- package/dist/index.js.map +1 -1
- package/dist/{tui-NCL4RFFD.js → tui-APMSKU4E.js} +563 -362
- package/dist/tui-APMSKU4E.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-5V4TBQ5S.js.map +0 -1
- package/dist/tui-NCL4RFFD.js.map +0 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// <define:__CHANGELOG_ENTRIES__>
|
|
4
|
+
var define_CHANGELOG_ENTRIES_default = [{ version: "0.8.1", date: "2026-04-14", sections: [{ heading: "Changed", items: ["Lower minimum Node.js requirement from >=25 to >=22"] }] }, { version: "0.8.0", date: "2026-04-13", sections: [{ heading: "Added", items: ["What's new ticker and changelog dialog for post-upgrade visibility"] }] }, { version: "0.7.0", date: "2026-04-12", sections: [{ heading: "Added", items: ["Prompt to create project when unlinked git remote is detected on TUI startup", "Project edit support in TUI and `--git-remote` flag for CLI", "Git remote field to create project form", "Arrow key navigation and inline cursor movement in form fields"] }, { heading: "Changed", items: ["Introduce `GitRemote` value object to enforce URL normalization"] }, { heading: "Fixed", items: ["Restore terminal raw mode around external editor to prevent readonly mode"] }] }, { version: "0.6.0", date: "2026-04-12", sections: [{ heading: "Added", items: ["Auto-upgrade with npm registry version check and `tayto upgrade` command", "Contextual hints in TUI for discoverability", "Shift+Tab panel navigation in TUI"] }, { heading: "Changed", items: ["Improve TUI intuitiveness with better keyboard shortcut guidance"] }, { heading: "Chore", items: ["Add open-source project scaffolding and fix CI node version", "Add demo video to README"] }] }, { version: "0.5.0", date: "2026-04-10", sections: [{ heading: "Changed", items: ["Redesign TUI color theme around #9B9BA5 (logo helmet grey) for consistent palette", "Redesign header with 3-column layout: logo, product info, and shortcut hints", "Integrate Tayto pixel logo into TUI header bar"] }, { heading: "Improved", items: ["Consolidate logo assets into `src/tui/assets/` and `src/tui/components/Logo.tsx`", "Theme colors now import from logo PALETTE for single source of truth", "Memoize Logo component to avoid unnecessary re-renders"] }, { heading: "Removed", items: ["Standalone `tayto-logo/` directory (AGENT.md, preview PNGs)"] }] }, { version: "0.4.1", date: "2026-04-10", sections: [{ heading: "Fixed", items: ["Prevent rank collisions from floating-point precision collapse", "Scope `listTasks` and `searchTasks` to default project when none specified"] }, { heading: "Changed", items: ["Resolve project at CLI/TUI boundary; services now accept `Project` directly"] }] }, { version: "0.4.0", date: "2026-04-09", sections: [{ heading: "Added", items: ["Link projects to git remotes for automatic project selection", "Running `tayto` inside a git repo auto-selects the linked project"] }] }, { version: "0.3.0", date: null, sections: [{ heading: "Added", items: ["Dependency management (`tayto dep add/remove/list/graph`)", "Dependency types: `blocks`, `blocked-by`, `relates-to`, `duplicates`", "Task breakdown command to split tasks into subtasks"] }] }, { version: "0.2.0", date: null, sections: [{ heading: "Added", items: ["TUI (terminal UI) built with Ink/React", "Full-text search via SQLite FTS5", "Task export and import (JSON)", "OpenTelemetry tracing on every operation"] }] }, { version: "0.1.0", date: null, sections: [{ heading: "Added", items: ["Initial release", "CLI with Commander.js", "SQLite database backend with ULID identifiers", "Fractional ranking for O(1) reorders", "Task types: `story`, `bug`, `tech-debt`", "Task statuses: `backlog` \u2192 `todo` \u2192 `in-progress` \u2192 `review` \u2192 `done` / `cancelled`", "Project management with default project support", "Zod validation across all service layer inputs"] }] }];
|
|
5
|
+
|
|
3
6
|
// src/logging/logger.ts
|
|
4
7
|
import { appendFileSync, readdirSync, unlinkSync } from "fs";
|
|
5
8
|
import { join } from "path";
|
|
@@ -88,6 +91,9 @@ var Logger = class {
|
|
|
88
91
|
};
|
|
89
92
|
var logger = new Logger();
|
|
90
93
|
|
|
94
|
+
// src/version.ts
|
|
95
|
+
var APP_VERSION = true ? "0.8.1" : "0.0.0-dev";
|
|
96
|
+
|
|
91
97
|
// src/types/enums.ts
|
|
92
98
|
var TaskStatus = {
|
|
93
99
|
Backlog: "backlog",
|
|
@@ -212,7 +218,21 @@ function detectGitRemote(cwd) {
|
|
|
212
218
|
}
|
|
213
219
|
}
|
|
214
220
|
|
|
221
|
+
// src/utils/version.ts
|
|
222
|
+
function isNewerVersion(a, b) {
|
|
223
|
+
const parse = (v) => {
|
|
224
|
+
const parts = v.replace(/^v/, "").split(".").map(Number);
|
|
225
|
+
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
226
|
+
};
|
|
227
|
+
const [aMaj, aMin, aPatch] = parse(a);
|
|
228
|
+
const [bMaj, bMin, bPatch] = parse(b);
|
|
229
|
+
if (aMaj !== bMaj) return aMaj > bMaj;
|
|
230
|
+
if (aMin !== bMin) return aMin > bMin;
|
|
231
|
+
return aPatch > bPatch;
|
|
232
|
+
}
|
|
233
|
+
|
|
215
234
|
export {
|
|
235
|
+
define_CHANGELOG_ENTRIES_default,
|
|
216
236
|
logger,
|
|
217
237
|
ok,
|
|
218
238
|
err,
|
|
@@ -228,6 +248,8 @@ export {
|
|
|
228
248
|
midpoint,
|
|
229
249
|
TERMINAL_STATUSES,
|
|
230
250
|
isTerminalStatus,
|
|
231
|
-
detectGitRemote
|
|
251
|
+
detectGitRemote,
|
|
252
|
+
isNewerVersion,
|
|
253
|
+
APP_VERSION
|
|
232
254
|
};
|
|
233
|
-
//# sourceMappingURL=chunk-
|
|
255
|
+
//# sourceMappingURL=chunk-6ZNCVCAS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["<define:__CHANGELOG_ENTRIES__>","../src/logging/logger.ts","../src/version.ts","../src/types/enums.ts","../src/utils/git.ts","../src/types/common.ts","../src/types/git-remote.ts","../src/utils/version.ts"],"sourcesContent":["[{\"version\":\"0.8.1\",\"date\":\"2026-04-14\",\"sections\":[{\"heading\":\"Changed\",\"items\":[\"Lower minimum Node.js requirement from >=25 to >=22\"]}]},{\"version\":\"0.8.0\",\"date\":\"2026-04-13\",\"sections\":[{\"heading\":\"Added\",\"items\":[\"What's new ticker and changelog dialog for post-upgrade visibility\"]}]},{\"version\":\"0.7.0\",\"date\":\"2026-04-12\",\"sections\":[{\"heading\":\"Added\",\"items\":[\"Prompt to create project when unlinked git remote is detected on TUI startup\",\"Project edit support in TUI and `--git-remote` flag for CLI\",\"Git remote field to create project form\",\"Arrow key navigation and inline cursor movement in form fields\"]},{\"heading\":\"Changed\",\"items\":[\"Introduce `GitRemote` value object to enforce URL normalization\"]},{\"heading\":\"Fixed\",\"items\":[\"Restore terminal raw mode around external editor to prevent readonly mode\"]}]},{\"version\":\"0.6.0\",\"date\":\"2026-04-12\",\"sections\":[{\"heading\":\"Added\",\"items\":[\"Auto-upgrade with npm registry version check and `tayto upgrade` command\",\"Contextual hints in TUI for discoverability\",\"Shift+Tab panel navigation in TUI\"]},{\"heading\":\"Changed\",\"items\":[\"Improve TUI intuitiveness with better keyboard shortcut guidance\"]},{\"heading\":\"Chore\",\"items\":[\"Add open-source project scaffolding and fix CI node version\",\"Add demo video to README\"]}]},{\"version\":\"0.5.0\",\"date\":\"2026-04-10\",\"sections\":[{\"heading\":\"Changed\",\"items\":[\"Redesign TUI color theme around #9B9BA5 (logo helmet grey) for consistent palette\",\"Redesign header with 3-column layout: logo, product info, and shortcut hints\",\"Integrate Tayto pixel logo into TUI header bar\"]},{\"heading\":\"Improved\",\"items\":[\"Consolidate logo assets into `src/tui/assets/` and `src/tui/components/Logo.tsx`\",\"Theme colors now import from logo PALETTE for single source of truth\",\"Memoize Logo component to avoid unnecessary re-renders\"]},{\"heading\":\"Removed\",\"items\":[\"Standalone `tayto-logo/` directory (AGENT.md, preview PNGs)\"]}]},{\"version\":\"0.4.1\",\"date\":\"2026-04-10\",\"sections\":[{\"heading\":\"Fixed\",\"items\":[\"Prevent rank collisions from floating-point precision collapse\",\"Scope `listTasks` and `searchTasks` to default project when none specified\"]},{\"heading\":\"Changed\",\"items\":[\"Resolve project at CLI/TUI boundary; services now accept `Project` directly\"]}]},{\"version\":\"0.4.0\",\"date\":\"2026-04-09\",\"sections\":[{\"heading\":\"Added\",\"items\":[\"Link projects to git remotes for automatic project selection\",\"Running `tayto` inside a git repo auto-selects the linked project\"]}]},{\"version\":\"0.3.0\",\"date\":null,\"sections\":[{\"heading\":\"Added\",\"items\":[\"Dependency management (`tayto dep add/remove/list/graph`)\",\"Dependency types: `blocks`, `blocked-by`, `relates-to`, `duplicates`\",\"Task breakdown command to split tasks into subtasks\"]}]},{\"version\":\"0.2.0\",\"date\":null,\"sections\":[{\"heading\":\"Added\",\"items\":[\"TUI (terminal UI) built with Ink/React\",\"Full-text search via SQLite FTS5\",\"Task export and import (JSON)\",\"OpenTelemetry tracing on every operation\"]}]},{\"version\":\"0.1.0\",\"date\":null,\"sections\":[{\"heading\":\"Added\",\"items\":[\"Initial release\",\"CLI with Commander.js\",\"SQLite database backend with ULID identifiers\",\"Fractional ranking for O(1) reorders\",\"Task types: `story`, `bug`, `tech-debt`\",\"Task statuses: `backlog` → `todo` → `in-progress` → `review` → `done` / `cancelled`\",\"Project management with default project support\",\"Zod validation across all service layer inputs\"]}]}]","import { appendFileSync, readdirSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { trace, type Span, SpanStatusCode } from '@opentelemetry/api';\n\nconst tracer = trace.getTracer('task');\nconst LOG_RETENTION_DAYS = 7;\n\nexport interface LogAttributes {\n [key: string]: string | number | boolean;\n}\n\ntype LogLevel = 'INFO' | 'WARN' | 'ERROR';\n\nfunction formatTimestamp(): string {\n return new Date().toISOString();\n}\n\nfunction formatAttrs(attrs?: LogAttributes): string {\n if (!attrs || Object.keys(attrs).length === 0) return '';\n return ' ' + JSON.stringify(attrs);\n}\n\nclass Logger {\n private logFilePath: string | null = null;\n\n init(logDir: string): void {\n const date = new Date().toISOString().slice(0, 10); // YYYY-MM-DD\n this.logFilePath = join(logDir, `task-${date}.log`);\n this.pruneOldLogs(logDir);\n }\n\n info(message: string, attrs?: LogAttributes): void {\n this.write('INFO', message, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(message, attrs);\n }\n }\n\n warn(message: string, attrs?: LogAttributes): void {\n this.write('WARN', message, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(`WARN: ${message}`, attrs);\n }\n }\n\n error(message: string, error?: unknown, attrs?: LogAttributes): void {\n const errorDetail = error instanceof Error ? ` | ${error.stack ?? error.message}` : '';\n this.write('ERROR', `${message}${errorDetail}`, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(`ERROR: ${message}`, attrs);\n if (error instanceof Error) {\n span.recordException(error);\n }\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n }\n }\n\n startSpan<T>(name: string, fn: (span: Span) => T): T {\n return tracer.startActiveSpan(name, (span) => {\n try {\n const result = fn(span);\n span.end();\n return result;\n } catch (e) {\n if (e instanceof Error) {\n span.recordException(e);\n }\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.end();\n throw e;\n }\n });\n }\n\n private write(level: LogLevel, message: string, attrs?: LogAttributes): void {\n if (!this.logFilePath) return;\n const line = `${formatTimestamp()} [${level}] ${message}${formatAttrs(attrs)}\\n`;\n try {\n appendFileSync(this.logFilePath, line);\n } catch {\n // Swallowing here is intentional: logging must never crash the app.\n // If the log file is unwritable, the OTel span still captures the event.\n }\n }\n\n private pruneOldLogs(logDir: string): void {\n try {\n const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000;\n const files = readdirSync(logDir).filter((f) => f.startsWith('task-') && f.endsWith('.log'));\n for (const file of files) {\n const dateStr = file.slice('task-'.length, -'.log'.length);\n const fileDate = new Date(dateStr).getTime();\n if (!isNaN(fileDate) && fileDate < cutoff) {\n unlinkSync(join(logDir, file));\n }\n }\n } catch {\n // Best-effort cleanup — don't crash if pruning fails\n }\n }\n}\n\nexport const logger = new Logger();\n","declare const __APP_VERSION__: string;\n\nexport const APP_VERSION: string =\n typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0-dev';\n","export const TaskStatus = {\n Backlog: 'backlog',\n Todo: 'todo',\n InProgress: 'in-progress',\n Review: 'review',\n Done: 'done',\n Cancelled: 'cancelled',\n} as const;\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport const TaskType = {\n Epic: 'epic',\n Story: 'story',\n TechDebt: 'tech-debt',\n Bug: 'bug',\n} as const;\nexport type TaskType = (typeof TaskType)[keyof typeof TaskType];\n\n/**\n * Task level derived from type.\n * Level 1: epics (grouping/planning layer)\n * Level 2: stories, tech-debt, bugs (execution layer)\n */\nexport const TaskLevel = {\n Epic: 1,\n Work: 2,\n} as const;\nexport type TaskLevel = (typeof TaskLevel)[keyof typeof TaskLevel];\n\nconst TYPE_TO_LEVEL: Record<string, TaskLevel> = {\n [TaskType.Epic]: TaskLevel.Epic,\n [TaskType.Story]: TaskLevel.Work,\n [TaskType.TechDebt]: TaskLevel.Work,\n [TaskType.Bug]: TaskLevel.Work,\n};\n\nexport function getTaskLevel(type: string): TaskLevel {\n return TYPE_TO_LEVEL[type] ?? TaskLevel.Work;\n}\n\n/** Types that belong to the work (level 2) execution layer. */\nexport const WORK_TYPES: ReadonlySet<string> = new Set([\n TaskType.Story,\n TaskType.TechDebt,\n TaskType.Bug,\n]);\n\n/** Types stored in the database. */\nexport const DependencyType = {\n Blocks: 'blocks',\n RelatesTo: 'relates-to',\n Duplicates: 'duplicates',\n} as const;\nexport type DependencyType = (typeof DependencyType)[keyof typeof DependencyType];\n\n/**\n * UI-level dependency types — includes BlockedBy which is a reverse-Blocks\n * relationship resolved before persisting to the database.\n */\nexport const UIDependencyType = {\n ...DependencyType,\n BlockedBy: 'blocked-by',\n} as const;\nexport type UIDependencyType = (typeof UIDependencyType)[keyof typeof UIDependencyType];\n\n/** Gap between consecutive rank values, used for insertion between neighbors. */\nexport const RANK_GAP = 1000.0;\n\n/**\n * Collapse-safe midpoint between two rank values. Returns `null` when\n * IEEE 754 double precision cannot represent a strictly-between value —\n * callers use this as a signal to rebalance the rank grid and retry.\n * Returning a number unconditionally would silently collide with an\n * endpoint and corrupt the task ordering.\n */\nexport function midpoint(a: number, b: number): number | null {\n const m = (a + b) / 2;\n return m > a && m < b ? m : null;\n}\n\n/** Statuses that represent terminal/completed task states. */\nexport const TERMINAL_STATUSES: ReadonlySet<string> = new Set([\n TaskStatus.Done,\n TaskStatus.Cancelled,\n]);\n\nexport function isTerminalStatus(status: string): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n","import { spawnSync } from 'node:child_process';\nimport type { Result } from '../types/common.js';\nimport { ok } from '../types/common.js';\nimport { GitRemote } from '../types/git-remote.js';\nimport { logger } from '../logging/logger.js';\n\n/**\n * Detect the git origin remote URL from the given directory.\n * Returns ok(GitRemote) if found, ok(null) if no git repo or no origin remote.\n * Never returns err() — all git failures are silent fallbacks.\n */\nexport function detectGitRemote(cwd?: string): Result<GitRemote | null> {\n try {\n const result = spawnSync('git', ['remote', 'get-url', 'origin'], {\n cwd: cwd ?? process.cwd(),\n encoding: 'utf-8',\n timeout: 5000,\n });\n\n if (result.status !== 0 || result.error) {\n logger.info('detectGitRemote: no git remote found (non-zero exit or error)');\n return ok(null);\n }\n\n const raw = result.stdout.trim();\n if (!raw) {\n logger.info('detectGitRemote: empty stdout from git remote get-url');\n return ok(null);\n }\n\n const remote = GitRemote.parse(raw);\n logger.info(`detectGitRemote: found remote=${remote.value}`);\n return ok(remote);\n } catch (e: unknown) {\n logger.info(`detectGitRemote: exception during git detection: ${String(e)}`);\n return ok(null);\n }\n}\n","import type { AppError } from '../errors/app-error.js';\n\nexport type Result<T, E = AppError> = { ok: true; value: T } | { ok: false; error: E };\n\nexport function ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\nexport interface CLIOutput<T> {\n ok: boolean;\n data?: T;\n error?: { code: string; message: string };\n}\n\nexport type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;\n","/**\n * Value object representing a normalized git remote URL.\n * Accepts any common format (SSH or HTTPS, with or without .git suffix)\n * and stores the canonical `host/owner/repo` form.\n */\nexport class GitRemote {\n readonly value: string;\n\n private constructor(normalized: string) {\n this.value = normalized;\n }\n\n static parse(raw: string): GitRemote {\n return new GitRemote(GitRemote.normalize(raw));\n }\n\n private static normalize(url: string): string {\n const trimmed = url.trim();\n\n // SSH format: git@github.com:owner/repo.git or git@github.com:owner/repo\n const sshMatch = trimmed.match(/^git@([^:]+):(.+?)(?:\\.git)?$/);\n if (sshMatch) {\n const [, host, path] = sshMatch;\n return `${host}/${path}`.toLowerCase();\n }\n\n // HTTPS/HTTP format: https://github.com/owner/repo.git or without .git\n try {\n const parsed = new URL(trimmed);\n const path = parsed.pathname.replace(/\\.git$/, '').replace(/^\\//, '');\n return `${parsed.host}/${path}`.toLowerCase();\n } catch {\n return trimmed.toLowerCase();\n }\n }\n\n equals(other: GitRemote): boolean {\n return this.value === other.value;\n }\n\n toString(): string {\n return this.value;\n }\n}\n","/**\n * Compare two semver strings (major.minor.patch).\n * Returns true if `a` is strictly newer than `b`.\n */\nexport function isNewerVersion(a: string, b: string): boolean {\n const parse = (v: string): [number, number, number] => {\n const parts = v.replace(/^v/, '').split('.').map(Number);\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];\n };\n const [aMaj, aMin, aPatch] = parse(a);\n const [bMaj, bMin, bPatch] = parse(b);\n if (aMaj !== bMaj) return aMaj > bMaj;\n if (aMin !== bMin) return aMin > bMin;\n return aPatch > bPatch;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
APP_VERSION,
|
|
3
4
|
DependencyType,
|
|
4
5
|
GitRemote,
|
|
5
6
|
RANK_GAP,
|
|
@@ -12,11 +13,12 @@ import {
|
|
|
12
13
|
detectGitRemote,
|
|
13
14
|
err,
|
|
14
15
|
getTaskLevel,
|
|
16
|
+
isNewerVersion,
|
|
15
17
|
isTerminalStatus,
|
|
16
18
|
logger,
|
|
17
19
|
midpoint,
|
|
18
20
|
ok
|
|
19
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-6ZNCVCAS.js";
|
|
20
22
|
|
|
21
23
|
// src/config/index.ts
|
|
22
24
|
import { mkdirSync } from "fs";
|
|
@@ -37,6 +39,7 @@ function loadConfig() {
|
|
|
37
39
|
otelEndpoint: process.env["OTEL_EXPORTER_OTLP_ENDPOINT"],
|
|
38
40
|
updateCachePath: join(dataDir, "update-check.json"),
|
|
39
41
|
dismissedGitRemotesPath: join(dataDir, "dismissed-git-remotes.json"),
|
|
42
|
+
telemetryStatePath: join(dataDir, "telemetry.json"),
|
|
40
43
|
noUpdateCheck: process.env["TAYTO_NO_UPDATE_CHECK"] === "1"
|
|
41
44
|
};
|
|
42
45
|
}
|
|
@@ -2023,17 +2026,6 @@ function isValidCache(value) {
|
|
|
2023
2026
|
const obj = value;
|
|
2024
2027
|
return typeof obj["checkedAt"] === "number" && typeof obj["latestVersion"] === "string";
|
|
2025
2028
|
}
|
|
2026
|
-
function isNewerVersion(a, b) {
|
|
2027
|
-
const parse = (v) => {
|
|
2028
|
-
const parts = v.replace(/^v/, "").split(".").map(Number);
|
|
2029
|
-
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
2030
|
-
};
|
|
2031
|
-
const [aMaj, aMin, aPatch] = parse(a);
|
|
2032
|
-
const [bMaj, bMin, bPatch] = parse(b);
|
|
2033
|
-
if (aMaj !== bMaj) return aMaj > bMaj;
|
|
2034
|
-
if (aMin !== bMin) return aMin > bMin;
|
|
2035
|
-
return aPatch > bPatch;
|
|
2036
|
-
}
|
|
2037
2029
|
function defaultExec(cmd, args) {
|
|
2038
2030
|
execFileSync(cmd, args, { stdio: "inherit", timeout: 6e4 });
|
|
2039
2031
|
}
|
|
@@ -2135,7 +2127,16 @@ var UpdateServiceImpl = class {
|
|
|
2135
2127
|
}
|
|
2136
2128
|
writeCache(entry) {
|
|
2137
2129
|
try {
|
|
2138
|
-
|
|
2130
|
+
let existing = {};
|
|
2131
|
+
try {
|
|
2132
|
+
const raw = readFileSync2(this.cachePath, "utf-8");
|
|
2133
|
+
const parsed = JSON.parse(raw);
|
|
2134
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
2135
|
+
existing = parsed;
|
|
2136
|
+
}
|
|
2137
|
+
} catch {
|
|
2138
|
+
}
|
|
2139
|
+
writeFileSync(this.cachePath, JSON.stringify({ ...existing, ...entry }), "utf-8");
|
|
2139
2140
|
} catch (e) {
|
|
2140
2141
|
logger.warn("Failed to write update cache", {
|
|
2141
2142
|
error: toMessage(e)
|
|
@@ -2153,11 +2154,11 @@ function createContainer(db, dbPath, detectGitRemote2, updateCachePath, dismisse
|
|
|
2153
2154
|
const dependencyService = new DependencyServiceImpl(depRepo, taskRepo);
|
|
2154
2155
|
const taskService = new TaskServiceImpl(taskRepo, projectService, () => dependencyService);
|
|
2155
2156
|
const portabilityService = new PortabilityServiceImpl(taskService, dependencyService);
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
);
|
|
2157
|
+
const resolvedUpdateCachePath = updateCachePath ?? join3(tmpdir(), "tayto-update-check.json");
|
|
2158
|
+
const updateService = new UpdateServiceImpl(resolvedUpdateCachePath);
|
|
2159
2159
|
return {
|
|
2160
2160
|
dbPath,
|
|
2161
|
+
updateCachePath: resolvedUpdateCachePath,
|
|
2161
2162
|
dismissedGitRemotesPath: dismissedGitRemotesPath ?? join3(tmpdir(), "tayto-dismissed-git-remotes.json"),
|
|
2162
2163
|
projectService,
|
|
2163
2164
|
taskService,
|
|
@@ -2170,9 +2171,6 @@ function createContainer(db, dbPath, detectGitRemote2, updateCachePath, dismisse
|
|
|
2170
2171
|
// src/cli/index.ts
|
|
2171
2172
|
import { Command } from "commander";
|
|
2172
2173
|
|
|
2173
|
-
// src/version.ts
|
|
2174
|
-
var APP_VERSION = true ? "0.7.0" : "0.0.0-dev";
|
|
2175
|
-
|
|
2176
2174
|
// src/cli/output.ts
|
|
2177
2175
|
function printSuccess(data) {
|
|
2178
2176
|
process.stdout.write(JSON.stringify({ ok: true, data }, null, 2) + "\n");
|
|
@@ -2597,12 +2595,145 @@ function buildCLI(container) {
|
|
|
2597
2595
|
registerDepGraph(dep, container);
|
|
2598
2596
|
registerUpgrade(program, container);
|
|
2599
2597
|
program.command("tui").description("Launch interactive terminal UI").option("-p, --project <project>", "Start with specific project").action(async (opts) => {
|
|
2600
|
-
const { launchTUI } = await import("./tui-
|
|
2598
|
+
const { launchTUI } = await import("./tui-APMSKU4E.js");
|
|
2601
2599
|
await launchTUI(container, opts.project);
|
|
2602
2600
|
});
|
|
2603
2601
|
return program;
|
|
2604
2602
|
}
|
|
2605
2603
|
|
|
2604
|
+
// src/telemetry/heartbeat.ts
|
|
2605
|
+
import { readFile, writeFile } from "fs/promises";
|
|
2606
|
+
|
|
2607
|
+
// src/types/install-id.ts
|
|
2608
|
+
import { ulid as ulid2 } from "ulid";
|
|
2609
|
+
var ULID_REGEX = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
2610
|
+
var InstallId = class _InstallId {
|
|
2611
|
+
value;
|
|
2612
|
+
constructor(id) {
|
|
2613
|
+
this.value = id;
|
|
2614
|
+
}
|
|
2615
|
+
static generate() {
|
|
2616
|
+
return new _InstallId(ulid2());
|
|
2617
|
+
}
|
|
2618
|
+
static parse(raw) {
|
|
2619
|
+
const trimmed = raw.trim();
|
|
2620
|
+
if (!ULID_REGEX.test(trimmed)) {
|
|
2621
|
+
throw new Error(`Invalid InstallId: expected ULID format, got "${trimmed}"`);
|
|
2622
|
+
}
|
|
2623
|
+
return new _InstallId(trimmed.toUpperCase());
|
|
2624
|
+
}
|
|
2625
|
+
toString() {
|
|
2626
|
+
return this.value;
|
|
2627
|
+
}
|
|
2628
|
+
};
|
|
2629
|
+
|
|
2630
|
+
// src/utils/ci.ts
|
|
2631
|
+
var CI_ENV_VARS = [
|
|
2632
|
+
"CI",
|
|
2633
|
+
"GITHUB_ACTIONS",
|
|
2634
|
+
"JENKINS_URL",
|
|
2635
|
+
"GITLAB_CI",
|
|
2636
|
+
"CIRCLECI",
|
|
2637
|
+
"BUILDKITE",
|
|
2638
|
+
"TF_BUILD",
|
|
2639
|
+
"CODEBUILD_BUILD_ID"
|
|
2640
|
+
];
|
|
2641
|
+
function isCI(env = process.env) {
|
|
2642
|
+
return CI_ENV_VARS.some((key) => {
|
|
2643
|
+
const val = env[key];
|
|
2644
|
+
return val !== void 0 && val !== "";
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
// src/telemetry/heartbeat.ts
|
|
2649
|
+
var HEARTBEAT_URL = "https://tayto-telemetry.tomkapa.workers.dev/api/heartbeat";
|
|
2650
|
+
var TIMEOUT_MS = 3e3;
|
|
2651
|
+
function isValidRawState(value) {
|
|
2652
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2653
|
+
const obj = value;
|
|
2654
|
+
return typeof obj["installId"] === "string" && typeof obj["lastPingDate"] === "string";
|
|
2655
|
+
}
|
|
2656
|
+
function todayUTC() {
|
|
2657
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2658
|
+
}
|
|
2659
|
+
async function readState(statePath) {
|
|
2660
|
+
let raw;
|
|
2661
|
+
try {
|
|
2662
|
+
raw = await readFile(statePath, "utf-8");
|
|
2663
|
+
} catch (e) {
|
|
2664
|
+
if (e.code !== "ENOENT") {
|
|
2665
|
+
logger.warn("telemetry: failed to read state", { error: toMessage(e), path: statePath });
|
|
2666
|
+
}
|
|
2667
|
+
return null;
|
|
2668
|
+
}
|
|
2669
|
+
try {
|
|
2670
|
+
const parsed = JSON.parse(raw);
|
|
2671
|
+
if (!isValidRawState(parsed)) return null;
|
|
2672
|
+
return { installId: InstallId.parse(parsed.installId), lastPingDate: parsed.lastPingDate };
|
|
2673
|
+
} catch (e) {
|
|
2674
|
+
logger.warn("telemetry: corrupt state file, will regenerate", {
|
|
2675
|
+
error: toMessage(e),
|
|
2676
|
+
path: statePath
|
|
2677
|
+
});
|
|
2678
|
+
return null;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
async function writeState(statePath, state) {
|
|
2682
|
+
try {
|
|
2683
|
+
await writeFile(statePath, JSON.stringify(state), "utf-8");
|
|
2684
|
+
} catch (e) {
|
|
2685
|
+
logger.warn("telemetry: failed to write state", { error: toMessage(e), path: statePath });
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
function maybeSendHeartbeat(deps) {
|
|
2689
|
+
const { statePath, version, fetchImpl = globalThis.fetch, env = process.env } = deps;
|
|
2690
|
+
void (async () => {
|
|
2691
|
+
try {
|
|
2692
|
+
if (env["TASKCLI_TELEMETRY_DISABLED"] === "1") {
|
|
2693
|
+
logger.info("telemetry: disabled via TASKCLI_TELEMETRY_DISABLED");
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
if (isCI(env)) {
|
|
2697
|
+
logger.info("telemetry: skipped in CI environment");
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
const today = todayUTC();
|
|
2701
|
+
const state = await readState(statePath);
|
|
2702
|
+
if (state && state.lastPingDate === today) {
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
const installId = state ? state.installId : InstallId.generate();
|
|
2706
|
+
await writeState(statePath, { installId: installId.toString(), lastPingDate: today });
|
|
2707
|
+
const controller = new AbortController();
|
|
2708
|
+
const timeout = setTimeout(() => {
|
|
2709
|
+
controller.abort();
|
|
2710
|
+
}, TIMEOUT_MS);
|
|
2711
|
+
timeout.unref();
|
|
2712
|
+
const payload = {
|
|
2713
|
+
installId: installId.toString(),
|
|
2714
|
+
version,
|
|
2715
|
+
os: process.platform,
|
|
2716
|
+
nodeVersion: process.version,
|
|
2717
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2718
|
+
};
|
|
2719
|
+
fetchImpl(HEARTBEAT_URL, {
|
|
2720
|
+
method: "POST",
|
|
2721
|
+
headers: { "Content-Type": "application/json" },
|
|
2722
|
+
body: JSON.stringify(payload),
|
|
2723
|
+
signal: controller.signal
|
|
2724
|
+
}).then(() => {
|
|
2725
|
+
clearTimeout(timeout);
|
|
2726
|
+
logger.info("telemetry: heartbeat sent", { installId: installId.toString() });
|
|
2727
|
+
}).catch((e) => {
|
|
2728
|
+
clearTimeout(timeout);
|
|
2729
|
+
logger.info("telemetry: heartbeat failed (non-blocking)", { error: toMessage(e) });
|
|
2730
|
+
});
|
|
2731
|
+
} catch (e) {
|
|
2732
|
+
logger.warn("telemetry: unexpected error in maybeSendHeartbeat", { error: toMessage(e) });
|
|
2733
|
+
}
|
|
2734
|
+
})();
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2606
2737
|
// src/index.ts
|
|
2607
2738
|
async function checkForUpdateQuietly(container, currentVersion) {
|
|
2608
2739
|
let timerId;
|
|
@@ -2629,9 +2760,16 @@ async function main() {
|
|
|
2629
2760
|
const config = loadConfig();
|
|
2630
2761
|
logger.init(config.logDir);
|
|
2631
2762
|
initTelemetry(config);
|
|
2763
|
+
maybeSendHeartbeat({ statePath: config.telemetryStatePath, version: APP_VERSION });
|
|
2632
2764
|
const db = createDatabase(config.dbPath);
|
|
2633
2765
|
runMigrations(db);
|
|
2634
|
-
const container = createContainer(
|
|
2766
|
+
const container = createContainer(
|
|
2767
|
+
db,
|
|
2768
|
+
config.dbPath,
|
|
2769
|
+
void 0,
|
|
2770
|
+
config.updateCachePath,
|
|
2771
|
+
config.dismissedGitRemotesPath
|
|
2772
|
+
);
|
|
2635
2773
|
const args = process.argv.slice(2);
|
|
2636
2774
|
const isUpgradeCommand = args[0] === "upgrade";
|
|
2637
2775
|
let updateCheck = null;
|
|
@@ -2639,7 +2777,7 @@ async function main() {
|
|
|
2639
2777
|
updateCheck = await checkForUpdateQuietly(container, APP_VERSION);
|
|
2640
2778
|
}
|
|
2641
2779
|
if (args.length === 0) {
|
|
2642
|
-
const { launchTUI } = await import("./tui-
|
|
2780
|
+
const { launchTUI } = await import("./tui-APMSKU4E.js");
|
|
2643
2781
|
await launchTUI(
|
|
2644
2782
|
container,
|
|
2645
2783
|
void 0,
|