Skip to content

feat: add backup command - #6

Merged
nazozokc merged 1 commit into
mainfrom
AI-agent
Jun 16, 2026
Merged

feat: add backup command#6
nazozokc merged 1 commit into
mainfrom
AI-agent

Conversation

@nazozokc

@nazozokc nazozokc commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Summary

Add subtrack backup <destination> command to create timestamped copies of the SQLite database file.

Changes

  • src/db.ts: Export saveDb() and add getDbPath() to expose DB file path
  • src/commands.ts: Implement handleBackup() — validates destination dir, flushes DB to disk, copies with COPYFILE_EXCL to prevent overwrite
  • src/index.ts: Register backup subcommand with <destination> argument

Notes

Backup filename format: subtrack_YYYYMMDD_HHMMSS.db

Summary by CodeRabbit

  • New Features
    • Added a backup command (subtrack backup <destination>) that creates timestamped database backups to a specified destination with validation to prevent overwriting existing backups.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

A new backup subcommand is added to the subtrack CLI. The db.ts module exposes saveDb and a new getDbPath() helper. A handleBackup(destination) function in commands.ts flushes the DB, validates the destination as an existing directory, copies the DB file with a timestamped name using exclusive creation, and reports errors for invalid destinations or existing backup files.

Changes

Backup Command

Layer / File(s) Summary
DB path and save helpers exposed
subtrack/src/db.ts
saveDb is promoted to a public export and a new getDbPath() function is added, initializing the DB before returning the resolved on-disk path.
handleBackup implementation and CLI wiring
subtrack/src/commands.ts, subtrack/src/index.ts
Imports for copyFileSync, statSync, constants, join, getDbPath, and saveDb are added to commands.ts. handleBackup(destination) flushes the DB, validates the destination is an existing directory, builds a timestamped .db filename, and copies the file with COPYFILE_EXCL. EEXIST and other errors produce user-facing messages; fatal errors call process.exit. The backup <destination> Commander subcommand is registered in index.ts and routed to handleBackup.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A backup command, fresh as morning dew,
I copy the database—timestamped, brand new!
The path must exist, or I'll hop away fast,
With EXCL semantics, no file is overcast.
My SQLite safe, tucked in a folder at last! 🗄️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add backup command' directly and concisely summarizes the main change—adding a new backup command to the CLI tool.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch AI-agent

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@subtrack/src/commands.ts`:
- Around line 170-195: The `saveDb()` call in the `handleBackup` function is
ineffective until the DB path is initialized via `_dbPath`. In a fresh process,
this leaves the source database file absent, causing `copyFileSync(getDbPath(),
destPath, constants.COPYFILE_EXCL)` to fail with ENOENT. Initialize the DB path
(typically by calling the function that sets `_dbPath`) before calling
`saveDb()` to ensure the in-memory state is actually flushed to disk and the
source file exists for the backup copy operation.
- Around line 175-177: The variable declaration for `destStat` lacks an explicit
type annotation, which violates strict TypeScript settings and creates an
implicit `any` hazard. Add an explicit type annotation to the `destStat`
variable at its declaration point that matches the return type of `statSync()`.
The return type from `statSync()` in the fs module is Stats, so annotate
`destStat` with the appropriate Stats type from the fs module to satisfy strict
type checking while maintaining compatibility with the subsequent
`.isDirectory()` method call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a87fbcf-faec-41b8-8b56-d725120bf1ba

📥 Commits

Reviewing files that changed from the base of the PR and between fb37665 and 71ed96f.

📒 Files selected for processing (3)
  • subtrack/src/commands.ts
  • subtrack/src/db.ts
  • subtrack/src/index.ts

Comment thread subtrack/src/commands.ts
Comment on lines +170 to +195
export async function handleBackup(destination: string) {
// flush in-memory state to disk
saveDb()

// validate destination
let destStat
try {
destStat = statSync(destination)
} catch {
consola.error(`Backup destination does not exist: ${destination}`)
process.exit(1)
}
if (!destStat.isDirectory()) {
consola.error(`Backup destination must be a directory: ${destination}`)
process.exit(1)
}

// generate timestamped filename
const now = new Date()
const ts = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`
const destPath = join(destination, `subtrack_${ts}.db`)

// copy with exclusive create to prevent overwrite
try {
copyFileSync(getDbPath(), destPath, constants.COPYFILE_EXCL)
consola.success(`Backup created: ${destPath}`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Initialize DB path before flushing, or first-run backup can fail.

saveDb() is a no-op until _dbPath is initialized. In a fresh process, this can leave the source DB file absent and copyFileSync(...) fails with ENOENT even for a valid destination.

Proposed fix
 export async function handleBackup(destination: string) {
-  // flush in-memory state to disk
-  saveDb()
+  // ensure DB path exists, then flush in-memory state to disk
+  const dbPath = getDbPath()
+  saveDb()
@@
-    copyFileSync(getDbPath(), destPath, constants.COPYFILE_EXCL)
+    copyFileSync(dbPath, destPath, constants.COPYFILE_EXCL)
     consola.success(`Backup created: ${destPath}`)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function handleBackup(destination: string) {
// flush in-memory state to disk
saveDb()
// validate destination
let destStat
try {
destStat = statSync(destination)
} catch {
consola.error(`Backup destination does not exist: ${destination}`)
process.exit(1)
}
if (!destStat.isDirectory()) {
consola.error(`Backup destination must be a directory: ${destination}`)
process.exit(1)
}
// generate timestamped filename
const now = new Date()
const ts = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`
const destPath = join(destination, `subtrack_${ts}.db`)
// copy with exclusive create to prevent overwrite
try {
copyFileSync(getDbPath(), destPath, constants.COPYFILE_EXCL)
consola.success(`Backup created: ${destPath}`)
export async function handleBackup(destination: string) {
// ensure DB path exists, then flush in-memory state to disk
const dbPath = getDbPath()
saveDb()
// validate destination
let destStat
try {
destStat = statSync(destination)
} catch {
consola.error(`Backup destination does not exist: ${destination}`)
process.exit(1)
}
if (!destStat.isDirectory()) {
consola.error(`Backup destination must be a directory: ${destination}`)
process.exit(1)
}
// generate timestamped filename
const now = new Date()
const ts = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`
const destPath = join(destination, `subtrack_${ts}.db`)
// copy with exclusive create to prevent overwrite
try {
copyFileSync(dbPath, destPath, constants.COPYFILE_EXCL)
consola.success(`Backup created: ${destPath}`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/commands.ts` around lines 170 - 195, The `saveDb()` call in the
`handleBackup` function is ineffective until the DB path is initialized via
`_dbPath`. In a fresh process, this leaves the source database file absent,
causing `copyFileSync(getDbPath(), destPath, constants.COPYFILE_EXCL)` to fail
with ENOENT. Initialize the DB path (typically by calling the function that sets
`_dbPath`) before calling `saveDb()` to ensure the in-memory state is actually
flushed to disk and the source file exists for the backup copy operation.

Comment thread subtrack/src/commands.ts
Comment on lines +175 to +177
let destStat
try {
destStat = statSync(destination)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify strict mode intent + confirm the untyped declaration exists.

fd -i tsconfig.json --exec sh -c '
  echo "== $1 =="
  rg -n "\"strict\"|\"noImplicitAny\"" "$1"
' sh {}

rg -n -C2 'let destStat' subtrack/src/commands.ts

Repository: nazozokc/subtrack

Length of output: 222


🏁 Script executed:

#!/bin/bash
# Check imports in commands.ts and full context of destStat usage
head -50 subtrack/src/commands.ts | cat -n

# Find statSync usage context
rg -n -B5 -A10 'let destStat' subtrack/src/commands.ts

# Check where statSync is imported from
rg -n 'statSync|from.*fs' subtrack/src/commands.ts | head -20

Repository: nazozokc/subtrack

Length of output: 2097


Add explicit type to destStat under strict TypeScript.

let destStat at line 175 has no type annotation. With strict mode enabled, this creates an implicit any hazard. The variable is assigned the return value of statSync(destination) and used with .isDirectory(), so it requires an explicit type.

Proposed fix
-  let destStat
+  let destStat: ReturnType<typeof statSync>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let destStat
try {
destStat = statSync(destination)
let destStat: ReturnType<typeof statSync>
try {
destStat = statSync(destination)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/commands.ts` around lines 175 - 177, The variable declaration
for `destStat` lacks an explicit type annotation, which violates strict
TypeScript settings and creates an implicit `any` hazard. Add an explicit type
annotation to the `destStat` variable at its declaration point that matches the
return type of `statSync()`. The return type from `statSync()` in the fs module
is Stats, so annotate `destStat` with the appropriate Stats type from the fs
module to satisfy strict type checking while maintaining compatibility with the
subsequent `.isDirectory()` method call.

Source: Coding guidelines

@nazozokc
nazozokc merged commit ac96c6c into main Jun 16, 2026
7 checks passed
@nazozokc
nazozokc deleted the AI-agent branch June 16, 2026 11:24
@coderabbitai coderabbitai Bot mentioned this pull request Jul 1, 2026
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant