@tomkapa/tayto 0.5.4 → 0.6.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/dist/index.js +238 -14
- package/dist/index.js.map +1 -1
- package/dist/{tui-24ZW56Q6.js → tui-WMESKCRD.js} +143 -41
- package/dist/tui-WMESKCRD.js.map +1 -0
- package/package.json +1 -1
- package/dist/tui-24ZW56Q6.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -33,7 +33,9 @@ function loadConfig() {
|
|
|
33
33
|
dbPath: process.env["TASK_DB_PATH"] ?? join(dataDir, "data.db"),
|
|
34
34
|
logDir,
|
|
35
35
|
logLevel: process.env["TASK_LOG_LEVEL"] ?? "info",
|
|
36
|
-
otelEndpoint: process.env["OTEL_EXPORTER_OTLP_ENDPOINT"]
|
|
36
|
+
otelEndpoint: process.env["OTEL_EXPORTER_OTLP_ENDPOINT"],
|
|
37
|
+
updateCachePath: join(dataDir, "update-check.json"),
|
|
38
|
+
noUpdateCheck: process.env["TAYTO_NO_UPDATE_CHECK"] === "1"
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -109,6 +111,10 @@ async function shutdownTelemetry() {
|
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
// src/cli/container.ts
|
|
115
|
+
import { join as join3 } from "path";
|
|
116
|
+
import { tmpdir } from "os";
|
|
117
|
+
|
|
112
118
|
// src/errors/app-error.ts
|
|
113
119
|
var AppError = class extends Error {
|
|
114
120
|
constructor(code, message, cause) {
|
|
@@ -120,6 +126,9 @@ var AppError = class extends Error {
|
|
|
120
126
|
code;
|
|
121
127
|
cause;
|
|
122
128
|
};
|
|
129
|
+
function toMessage(e) {
|
|
130
|
+
return e instanceof Error ? e.message : String(e);
|
|
131
|
+
}
|
|
123
132
|
|
|
124
133
|
// src/repository/project.repository.ts
|
|
125
134
|
import { ulid } from "ulid";
|
|
@@ -1997,8 +2006,141 @@ var PortabilityServiceImpl = class {
|
|
|
1997
2006
|
}
|
|
1998
2007
|
};
|
|
1999
2008
|
|
|
2009
|
+
// src/service/update.service.ts
|
|
2010
|
+
import { readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
2011
|
+
import { execFileSync } from "child_process";
|
|
2012
|
+
import { trace } from "@opentelemetry/api";
|
|
2013
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@tomkapa/tayto/latest";
|
|
2014
|
+
var PACKAGE_NAME = "@tomkapa/tayto";
|
|
2015
|
+
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2016
|
+
function isValidCache(value) {
|
|
2017
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2018
|
+
const obj = value;
|
|
2019
|
+
return typeof obj["checkedAt"] === "number" && typeof obj["latestVersion"] === "string";
|
|
2020
|
+
}
|
|
2021
|
+
function isNewerVersion(a, b) {
|
|
2022
|
+
const parse = (v) => {
|
|
2023
|
+
const parts = v.replace(/^v/, "").split(".").map(Number);
|
|
2024
|
+
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
2025
|
+
};
|
|
2026
|
+
const [aMaj, aMin, aPatch] = parse(a);
|
|
2027
|
+
const [bMaj, bMin, bPatch] = parse(b);
|
|
2028
|
+
if (aMaj !== bMaj) return aMaj > bMaj;
|
|
2029
|
+
if (aMin !== bMin) return aMin > bMin;
|
|
2030
|
+
return aPatch > bPatch;
|
|
2031
|
+
}
|
|
2032
|
+
function defaultExec(cmd, args) {
|
|
2033
|
+
execFileSync(cmd, args, { stdio: "inherit", timeout: 6e4 });
|
|
2034
|
+
}
|
|
2035
|
+
var tracer = trace.getTracer("task");
|
|
2036
|
+
var UpdateServiceImpl = class {
|
|
2037
|
+
constructor(cachePath, fetchImpl = globalThis.fetch, execImpl = defaultExec) {
|
|
2038
|
+
this.cachePath = cachePath;
|
|
2039
|
+
this.fetchImpl = fetchImpl;
|
|
2040
|
+
this.execImpl = execImpl;
|
|
2041
|
+
}
|
|
2042
|
+
cachePath;
|
|
2043
|
+
fetchImpl;
|
|
2044
|
+
execImpl;
|
|
2045
|
+
async checkForUpdate(currentVersion) {
|
|
2046
|
+
return tracer.startActiveSpan("UpdateService.checkForUpdate", async (span) => {
|
|
2047
|
+
try {
|
|
2048
|
+
span.setAttribute("update.current_version", currentVersion);
|
|
2049
|
+
const cached = this.readCache();
|
|
2050
|
+
if (cached && Date.now() - cached.checkedAt < CHECK_TTL_MS) {
|
|
2051
|
+
span.setAttribute("update.cache_hit", true);
|
|
2052
|
+
const updateAvailable2 = isNewerVersion(cached.latestVersion, currentVersion);
|
|
2053
|
+
span.setAttribute("update.latest_version", cached.latestVersion);
|
|
2054
|
+
span.setAttribute("update.available", updateAvailable2);
|
|
2055
|
+
return ok({ currentVersion, latestVersion: cached.latestVersion, updateAvailable: updateAvailable2 });
|
|
2056
|
+
}
|
|
2057
|
+
span.setAttribute("update.cache_hit", false);
|
|
2058
|
+
let latestVersion;
|
|
2059
|
+
const controller = new AbortController();
|
|
2060
|
+
const timeout = setTimeout(() => {
|
|
2061
|
+
controller.abort();
|
|
2062
|
+
}, 5e3);
|
|
2063
|
+
try {
|
|
2064
|
+
const response = await this.fetchImpl(NPM_REGISTRY_URL, { signal: controller.signal });
|
|
2065
|
+
if (!response.ok) {
|
|
2066
|
+
logger.warn("Update check: registry returned non-OK status", {
|
|
2067
|
+
status: response.status
|
|
2068
|
+
});
|
|
2069
|
+
return err(
|
|
2070
|
+
new AppError(
|
|
2071
|
+
"UPGRADE_CHECK",
|
|
2072
|
+
`npm registry returned status ${String(response.status)}`
|
|
2073
|
+
)
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
const body = await response.json();
|
|
2077
|
+
if (typeof body["version"] !== "string") {
|
|
2078
|
+
return err(
|
|
2079
|
+
new AppError("UPGRADE_CHECK", "Unexpected response format from npm registry")
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
latestVersion = body["version"];
|
|
2083
|
+
} catch (e) {
|
|
2084
|
+
logger.warn("Update check: fetch failed", { error: toMessage(e) });
|
|
2085
|
+
return err(
|
|
2086
|
+
new AppError("UPGRADE_CHECK", `Failed to check for updates: ${toMessage(e)}`, e)
|
|
2087
|
+
);
|
|
2088
|
+
} finally {
|
|
2089
|
+
clearTimeout(timeout);
|
|
2090
|
+
}
|
|
2091
|
+
this.writeCache({ checkedAt: Date.now(), latestVersion });
|
|
2092
|
+
const updateAvailable = isNewerVersion(latestVersion, currentVersion);
|
|
2093
|
+
span.setAttribute("update.latest_version", latestVersion);
|
|
2094
|
+
span.setAttribute("update.available", updateAvailable);
|
|
2095
|
+
return ok({ currentVersion, latestVersion, updateAvailable });
|
|
2096
|
+
} finally {
|
|
2097
|
+
span.end();
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
performUpgrade(currentVersion) {
|
|
2102
|
+
return logger.startSpan("UpdateService.performUpgrade", (span) => {
|
|
2103
|
+
span.setAttribute("update.previous_version", currentVersion);
|
|
2104
|
+
try {
|
|
2105
|
+
this.execImpl("npm", ["install", "-g", `${PACKAGE_NAME}@latest`]);
|
|
2106
|
+
} catch (e) {
|
|
2107
|
+
logger.error("Upgrade failed", e, { command: `npm install -g ${PACKAGE_NAME}@latest` });
|
|
2108
|
+
span.setAttribute("update.success", false);
|
|
2109
|
+
return err(new AppError("UPGRADE_CHECK", `Upgrade failed: ${toMessage(e)}`, e));
|
|
2110
|
+
}
|
|
2111
|
+
const cached = this.readCache();
|
|
2112
|
+
const installedVersion = cached?.latestVersion ?? "unknown";
|
|
2113
|
+
span.setAttribute("update.success", true);
|
|
2114
|
+
span.setAttribute("update.installed_version", installedVersion);
|
|
2115
|
+
this.writeCache({ checkedAt: 0, latestVersion: installedVersion });
|
|
2116
|
+
return ok({ installedVersion });
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
readCache() {
|
|
2120
|
+
try {
|
|
2121
|
+
const raw = readFileSync2(this.cachePath, "utf-8");
|
|
2122
|
+
const parsed = JSON.parse(raw);
|
|
2123
|
+
return isValidCache(parsed) ? parsed : null;
|
|
2124
|
+
} catch (e) {
|
|
2125
|
+
logger.info("Update cache not available", {
|
|
2126
|
+
error: toMessage(e)
|
|
2127
|
+
});
|
|
2128
|
+
return null;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
writeCache(entry) {
|
|
2132
|
+
try {
|
|
2133
|
+
writeFileSync(this.cachePath, JSON.stringify(entry), "utf-8");
|
|
2134
|
+
} catch (e) {
|
|
2135
|
+
logger.warn("Failed to write update cache", {
|
|
2136
|
+
error: toMessage(e)
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
};
|
|
2141
|
+
|
|
2000
2142
|
// src/cli/container.ts
|
|
2001
|
-
function createContainer(db, dbPath, detectGitRemote2) {
|
|
2143
|
+
function createContainer(db, dbPath, detectGitRemote2, updateCachePath) {
|
|
2002
2144
|
const projectRepo = new SqliteProjectRepository(db);
|
|
2003
2145
|
const taskRepo = new SqliteTaskRepository(db);
|
|
2004
2146
|
const depRepo = new SqliteDependencyRepository(db);
|
|
@@ -2006,12 +2148,25 @@ function createContainer(db, dbPath, detectGitRemote2) {
|
|
|
2006
2148
|
const dependencyService = new DependencyServiceImpl(depRepo, taskRepo);
|
|
2007
2149
|
const taskService = new TaskServiceImpl(taskRepo, projectService, () => dependencyService);
|
|
2008
2150
|
const portabilityService = new PortabilityServiceImpl(taskService, dependencyService);
|
|
2009
|
-
|
|
2151
|
+
const updateService = new UpdateServiceImpl(
|
|
2152
|
+
updateCachePath ?? join3(tmpdir(), "tayto-update-check.json")
|
|
2153
|
+
);
|
|
2154
|
+
return {
|
|
2155
|
+
dbPath,
|
|
2156
|
+
projectService,
|
|
2157
|
+
taskService,
|
|
2158
|
+
dependencyService,
|
|
2159
|
+
portabilityService,
|
|
2160
|
+
updateService
|
|
2161
|
+
};
|
|
2010
2162
|
}
|
|
2011
2163
|
|
|
2012
2164
|
// src/cli/index.ts
|
|
2013
2165
|
import { Command } from "commander";
|
|
2014
2166
|
|
|
2167
|
+
// src/version.ts
|
|
2168
|
+
var APP_VERSION = true ? "0.6.0" : "0.0.0-dev";
|
|
2169
|
+
|
|
2015
2170
|
// src/cli/output.ts
|
|
2016
2171
|
function printSuccess(data) {
|
|
2017
2172
|
process.stdout.write(JSON.stringify({ ok: true, data }, null, 2) + "\n");
|
|
@@ -2204,12 +2359,12 @@ function registerTaskDelete(parent, container) {
|
|
|
2204
2359
|
}
|
|
2205
2360
|
|
|
2206
2361
|
// src/cli/commands/task/breakdown.ts
|
|
2207
|
-
import { readFileSync as
|
|
2362
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2208
2363
|
function registerTaskBreakdown(parent, container) {
|
|
2209
2364
|
parent.command("breakdown <parentId>").description("Create subtasks from a JSON file").requiredOption("-f, --file <path>", "JSON file with array of subtask definitions").action((parentId, opts) => {
|
|
2210
2365
|
let content;
|
|
2211
2366
|
try {
|
|
2212
|
-
content =
|
|
2367
|
+
content = readFileSync3(opts.file, "utf-8");
|
|
2213
2368
|
} catch (e) {
|
|
2214
2369
|
return printError(
|
|
2215
2370
|
new AppError("VALIDATION", `Failed to read subtasks file: ${opts.file}`, e)
|
|
@@ -2263,7 +2418,7 @@ function registerTaskSearch(parent, container) {
|
|
|
2263
2418
|
}
|
|
2264
2419
|
|
|
2265
2420
|
// src/cli/commands/task/export.ts
|
|
2266
|
-
import { writeFileSync } from "fs";
|
|
2421
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
2267
2422
|
function registerTaskExport(parent, container) {
|
|
2268
2423
|
parent.command("export").description("Export tasks to JSON file").option("-p, --project <project>", "Project id or name").option("-o, --output <file>", "Output file path (defaults to stdout)").action((opts) => {
|
|
2269
2424
|
const projectResult = withProject(container, opts.project);
|
|
@@ -2274,7 +2429,7 @@ function registerTaskExport(parent, container) {
|
|
|
2274
2429
|
}
|
|
2275
2430
|
if (opts.output) {
|
|
2276
2431
|
try {
|
|
2277
|
-
|
|
2432
|
+
writeFileSync2(opts.output, JSON.stringify(result.value, null, 2) + "\n", "utf-8");
|
|
2278
2433
|
} catch (e) {
|
|
2279
2434
|
return printError(new AppError("UNKNOWN", `Failed to write file: ${opts.output}`, e));
|
|
2280
2435
|
}
|
|
@@ -2290,7 +2445,7 @@ function registerTaskExport(parent, container) {
|
|
|
2290
2445
|
}
|
|
2291
2446
|
|
|
2292
2447
|
// src/cli/commands/task/import.ts
|
|
2293
|
-
import { readFileSync as
|
|
2448
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
2294
2449
|
function registerTaskImport(parent, container) {
|
|
2295
2450
|
parent.command("import").description("Import tasks from JSON file").requiredOption("-f, --file <file>", "Input JSON file path").option("-p, --project <project>", "Target project id or name").option(
|
|
2296
2451
|
"--map <mapping>",
|
|
@@ -2298,7 +2453,7 @@ function registerTaskImport(parent, container) {
|
|
|
2298
2453
|
).action((opts) => {
|
|
2299
2454
|
let fileData;
|
|
2300
2455
|
try {
|
|
2301
|
-
const raw =
|
|
2456
|
+
const raw = readFileSync4(opts.file, "utf-8");
|
|
2302
2457
|
fileData = JSON.parse(raw);
|
|
2303
2458
|
} catch (e) {
|
|
2304
2459
|
return printError(
|
|
@@ -2372,10 +2527,42 @@ function registerDepGraph(parent, container) {
|
|
|
2372
2527
|
});
|
|
2373
2528
|
}
|
|
2374
2529
|
|
|
2530
|
+
// src/cli/commands/upgrade.ts
|
|
2531
|
+
function registerUpgrade(program, container) {
|
|
2532
|
+
program.command("upgrade").description("Check for updates and upgrade to the latest version").action(async () => {
|
|
2533
|
+
const checkResult = await container.updateService.checkForUpdate(APP_VERSION);
|
|
2534
|
+
if (!checkResult.ok) {
|
|
2535
|
+
printError(checkResult.error);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
if (!checkResult.value.updateAvailable) {
|
|
2539
|
+
printSuccess({
|
|
2540
|
+
message: "Already up to date",
|
|
2541
|
+
currentVersion: checkResult.value.currentVersion
|
|
2542
|
+
});
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
process.stderr.write(
|
|
2546
|
+
`Upgrading from ${checkResult.value.currentVersion} to ${checkResult.value.latestVersion}...
|
|
2547
|
+
`
|
|
2548
|
+
);
|
|
2549
|
+
const upgradeResult = container.updateService.performUpgrade(APP_VERSION);
|
|
2550
|
+
if (!upgradeResult.ok) {
|
|
2551
|
+
printError(upgradeResult.error);
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2554
|
+
printSuccess({
|
|
2555
|
+
message: "Upgrade complete",
|
|
2556
|
+
previousVersion: checkResult.value.currentVersion,
|
|
2557
|
+
installedVersion: upgradeResult.value.installedVersion
|
|
2558
|
+
});
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2375
2562
|
// src/cli/index.ts
|
|
2376
2563
|
function buildCLI(container) {
|
|
2377
2564
|
const program = new Command();
|
|
2378
|
-
program.name("tayto").description("CLI task management for solo devs and AI agents").version(
|
|
2565
|
+
program.name("tayto").description("CLI task management for solo devs and AI agents").version(APP_VERSION);
|
|
2379
2566
|
const project = program.command("project").description("Manage projects");
|
|
2380
2567
|
registerProjectCreate(project, container);
|
|
2381
2568
|
registerProjectList(project, container);
|
|
@@ -2400,26 +2587,63 @@ function buildCLI(container) {
|
|
|
2400
2587
|
registerDepRemove(dep, container);
|
|
2401
2588
|
registerDepList(dep, container);
|
|
2402
2589
|
registerDepGraph(dep, container);
|
|
2590
|
+
registerUpgrade(program, container);
|
|
2403
2591
|
program.command("tui").description("Launch interactive terminal UI").option("-p, --project <project>", "Start with specific project").action(async (opts) => {
|
|
2404
|
-
const { launchTUI } = await import("./tui-
|
|
2592
|
+
const { launchTUI } = await import("./tui-WMESKCRD.js");
|
|
2405
2593
|
await launchTUI(container, opts.project);
|
|
2406
2594
|
});
|
|
2407
2595
|
return program;
|
|
2408
2596
|
}
|
|
2409
2597
|
|
|
2410
2598
|
// src/index.ts
|
|
2599
|
+
async function checkForUpdateQuietly(container, currentVersion) {
|
|
2600
|
+
let timerId;
|
|
2601
|
+
try {
|
|
2602
|
+
const result = await Promise.race([
|
|
2603
|
+
container.updateService.checkForUpdate(currentVersion),
|
|
2604
|
+
new Promise((resolve) => {
|
|
2605
|
+
timerId = setTimeout(() => {
|
|
2606
|
+
resolve(null);
|
|
2607
|
+
}, 2e3);
|
|
2608
|
+
})
|
|
2609
|
+
]);
|
|
2610
|
+
clearTimeout(timerId);
|
|
2611
|
+
if (result !== null && result.ok) {
|
|
2612
|
+
return result.value;
|
|
2613
|
+
}
|
|
2614
|
+
return null;
|
|
2615
|
+
} catch {
|
|
2616
|
+
clearTimeout(timerId);
|
|
2617
|
+
return null;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2411
2620
|
async function main() {
|
|
2412
2621
|
const config = loadConfig();
|
|
2413
2622
|
logger.init(config.logDir);
|
|
2414
2623
|
initTelemetry(config);
|
|
2415
2624
|
const db = createDatabase(config.dbPath);
|
|
2416
2625
|
runMigrations(db);
|
|
2417
|
-
const container = createContainer(db, config.dbPath);
|
|
2626
|
+
const container = createContainer(db, config.dbPath, void 0, config.updateCachePath);
|
|
2418
2627
|
const args = process.argv.slice(2);
|
|
2628
|
+
const isUpgradeCommand = args[0] === "upgrade";
|
|
2629
|
+
let updateCheck = null;
|
|
2630
|
+
if (!config.noUpdateCheck && !isUpgradeCommand) {
|
|
2631
|
+
updateCheck = await checkForUpdateQuietly(container, APP_VERSION);
|
|
2632
|
+
}
|
|
2419
2633
|
if (args.length === 0) {
|
|
2420
|
-
const { launchTUI } = await import("./tui-
|
|
2421
|
-
await launchTUI(
|
|
2634
|
+
const { launchTUI } = await import("./tui-WMESKCRD.js");
|
|
2635
|
+
await launchTUI(
|
|
2636
|
+
container,
|
|
2637
|
+
void 0,
|
|
2638
|
+
updateCheck?.updateAvailable ? updateCheck.latestVersion : void 0
|
|
2639
|
+
);
|
|
2422
2640
|
} else {
|
|
2641
|
+
if (updateCheck?.updateAvailable) {
|
|
2642
|
+
process.stderr.write(
|
|
2643
|
+
`\x1B[33m[tayto]\x1B[0m Update available: ${updateCheck.currentVersion} \u2192 ${updateCheck.latestVersion}. Run: tayto upgrade
|
|
2644
|
+
`
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2423
2647
|
const program = buildCLI(container);
|
|
2424
2648
|
await program.parseAsync(process.argv);
|
|
2425
2649
|
}
|