toes diff and toes status
This commit is contained in:
parent
ebf3ffc3af
commit
28948b13b2
8
bun.lock
8
bun.lock
|
|
@ -5,13 +5,15 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "toes",
|
"name": "toes",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@because/forge": "^0.0.1.",
|
"@because/forge": "^0.0.1",
|
||||||
"@because/hype": "^0.0.1",
|
"@because/hype": "^0.0.1",
|
||||||
"commander": "^14.0.2",
|
"commander": "^14.0.2",
|
||||||
|
"diff": "^8.0.3",
|
||||||
"kleur": "^4.1.5",
|
"kleur": "^4.1.5",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
"@types/diff": "^8.0.0",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
|
|
@ -25,12 +27,16 @@
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.8", "https://npm.nose.space/@types/bun/-/bun-1.3.8.tgz", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
"@types/bun": ["@types/bun@1.3.8", "https://npm.nose.space/@types/bun/-/bun-1.3.8.tgz", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
||||||
|
|
||||||
|
"@types/diff": ["@types/diff@8.0.0", "https://npm.nose.space/@types/diff/-/diff-8.0.0.tgz", { "dependencies": { "diff": "*" } }, "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.1.0", "https://npm.nose.space/@types/node/-/node-25.1.0.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="],
|
"@types/node": ["@types/node@25.1.0", "https://npm.nose.space/@types/node/-/node-25.1.0.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.8", "https://npm.nose.space/bun-types/-/bun-types-1.3.8.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
"bun-types": ["bun-types@1.3.8", "https://npm.nose.space/bun-types/-/bun-types-1.3.8.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
||||||
|
|
||||||
"commander": ["commander@14.0.2", "https://npm.nose.space/commander/-/commander-14.0.2.tgz", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="],
|
"commander": ["commander@14.0.2", "https://npm.nose.space/commander/-/commander-14.0.2.tgz", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="],
|
||||||
|
|
||||||
|
"diff": ["diff@8.0.3", "https://npm.nose.space/diff/-/diff-8.0.3.tgz", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||||
|
|
||||||
"hono": ["hono@4.11.7", "https://npm.nose.space/hono/-/hono-4.11.7.tgz", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="],
|
"hono": ["hono@4.11.7", "https://npm.nose.space/hono/-/hono-4.11.7.tgz", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="],
|
||||||
|
|
||||||
"kleur": ["kleur@4.1.5", "https://npm.nose.space/kleur/-/kleur-4.1.5.tgz", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
"kleur": ["kleur@4.1.5", "https://npm.nose.space/kleur/-/kleur-4.1.5.tgz", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,17 @@
|
||||||
"test": "bun test"
|
"test": "bun test"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest"
|
"@types/bun": "latest",
|
||||||
|
"@types/diff": "^8.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^14.0.2",
|
|
||||||
"@because/forge": "^0.0.1",
|
"@because/forge": "^0.0.1",
|
||||||
"@because/hype": "^0.0.1",
|
"@because/hype": "^0.0.1",
|
||||||
|
"commander": "^14.0.2",
|
||||||
|
"diff": "^8.0.3",
|
||||||
"kleur": "^4.1.5"
|
"kleur": "^4.1.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,4 +10,4 @@ export {
|
||||||
startApp,
|
startApp,
|
||||||
stopApp,
|
stopApp,
|
||||||
} from './manage'
|
} from './manage'
|
||||||
export { getApp, pullApp, pushApp, syncApp } from './sync'
|
export { diffApp, getApp, pullApp, pushApp, statusApp, syncApp } from './sync'
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,21 @@ import type { Manifest } from '@types'
|
||||||
import { loadGitignore } from '@gitignore'
|
import { loadGitignore } from '@gitignore'
|
||||||
import { computeHash, generateManifest } from '%sync'
|
import { computeHash, generateManifest } from '%sync'
|
||||||
import color from 'kleur'
|
import color from 'kleur'
|
||||||
|
import { diffLines } from 'diff'
|
||||||
import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, watch, writeFileSync } from 'fs'
|
import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, watch, writeFileSync } from 'fs'
|
||||||
import { dirname, join } from 'path'
|
import { dirname, join } from 'path'
|
||||||
import { del, download, get, getManifest, handleError, makeUrl, post, put } from '../http'
|
import { del, download, get, getManifest, handleError, makeUrl, post, put } from '../http'
|
||||||
import { confirm } from '../prompts'
|
import { confirm } from '../prompts'
|
||||||
import { getAppName, isApp } from '../name'
|
import { getAppName, isApp } from '../name'
|
||||||
|
|
||||||
|
interface ManifestDiff {
|
||||||
|
changed: string[]
|
||||||
|
localOnly: string[]
|
||||||
|
remoteOnly: string[]
|
||||||
|
localManifest: Manifest
|
||||||
|
remoteManifest: Manifest | null
|
||||||
|
}
|
||||||
|
|
||||||
export async function getApp(name: string) {
|
export async function getApp(name: string) {
|
||||||
console.log(`Fetching ${color.bold(name)} from server...`)
|
console.log(`Fetching ${color.bold(name)} from server...`)
|
||||||
|
|
||||||
|
|
@ -136,42 +145,40 @@ export async function pushApp() {
|
||||||
console.log(color.green(`✓ Deployed and activated version ${version}`))
|
console.log(color.green(`✓ Deployed and activated version ${version}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pullApp() {
|
export async function pullApp(options: { force?: boolean } = {}) {
|
||||||
if (!isApp()) {
|
if (!isApp()) {
|
||||||
console.error('Not a toes app. Use `toes get <app>` to grab one.')
|
console.error('Not a toes app. Use `toes get <app>` to grab one.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const appName = getAppName()
|
const appName = getAppName()
|
||||||
|
const diff = await getManifestDiff(appName)
|
||||||
|
|
||||||
|
if (diff === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { changed, localOnly, remoteOnly, remoteManifest } = diff
|
||||||
|
|
||||||
const remoteManifest: Manifest | undefined = await get(`/api/sync/apps/${appName}/manifest`)
|
|
||||||
if (!remoteManifest) {
|
if (!remoteManifest) {
|
||||||
console.error('App not found on server')
|
console.error('App not found on server')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const localManifest = generateManifest(process.cwd(), appName)
|
// Check for local changes that would be overwritten
|
||||||
|
const wouldOverwrite = changed.length > 0 || localOnly.length > 0
|
||||||
const localFiles = new Set(Object.keys(localManifest.files))
|
if (wouldOverwrite && !options.force) {
|
||||||
const remoteFiles = new Set(Object.keys(remoteManifest.files))
|
console.error('Cannot pull: you have local changes that would be overwritten')
|
||||||
|
console.error(' Use `toes status` and `toes diff` to see differences')
|
||||||
// Files to download (new or changed)
|
console.error(' Use `toes pull --force` to overwrite local changes')
|
||||||
const toDownload: string[] = []
|
return
|
||||||
for (const file of remoteFiles) {
|
|
||||||
const remote = remoteManifest.files[file]!
|
|
||||||
const local = localManifest.files[file]
|
|
||||||
if (!local || remote.hash !== local.hash) {
|
|
||||||
toDownload.push(file)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Files to delete (in local but not remote)
|
// Files to download: changed + remoteOnly
|
||||||
const toDelete: string[] = []
|
const toDownload = [...changed, ...remoteOnly]
|
||||||
for (const file of localFiles) {
|
|
||||||
if (!remoteFiles.has(file)) {
|
// Files to delete: localOnly
|
||||||
toDelete.push(file)
|
const toDelete = localOnly
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toDownload.length === 0 && toDelete.length === 0) {
|
if (toDownload.length === 0 && toDelete.length === 0) {
|
||||||
console.log('Already up to date')
|
console.log('Already up to date')
|
||||||
|
|
@ -213,6 +220,151 @@ export async function pullApp() {
|
||||||
console.log(color.green('✓ Pull complete'))
|
console.log(color.green('✓ Pull complete'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function diffApp() {
|
||||||
|
if (!isApp()) {
|
||||||
|
console.error('Not a toes app. Use `toes get <app>` to grab one.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const appName = getAppName()
|
||||||
|
const diff = await getManifestDiff(appName)
|
||||||
|
|
||||||
|
if (diff === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { changed, localOnly, remoteOnly } = diff
|
||||||
|
|
||||||
|
if (changed.length === 0 && localOnly.length === 0 && remoteOnly.length === 0) {
|
||||||
|
console.log(color.green('✓ No differences'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all changed files in parallel
|
||||||
|
const remoteContents = await Promise.all(
|
||||||
|
changed.map(file => download(`/api/sync/apps/${appName}/files/${file}`))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Show diffs for changed files
|
||||||
|
for (let i = 0; i < changed.length; i++) {
|
||||||
|
const file = changed[i]!
|
||||||
|
const remoteContent = remoteContents[i]
|
||||||
|
const localContent = readFileSync(join(process.cwd(), file), 'utf-8')
|
||||||
|
|
||||||
|
if (!remoteContent) {
|
||||||
|
console.log(color.red(`Failed to fetch remote version of ${file}`))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteText = new TextDecoder().decode(remoteContent)
|
||||||
|
|
||||||
|
console.log(color.bold(`\n${file}`))
|
||||||
|
console.log(color.gray('─'.repeat(60)))
|
||||||
|
showDiff(remoteText, localContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show local-only files
|
||||||
|
for (const file of localOnly) {
|
||||||
|
console.log(color.green('\nNew file (local only)'))
|
||||||
|
console.log(color.bold(`${file}`))
|
||||||
|
console.log(color.gray('─'.repeat(60)))
|
||||||
|
const content = readFileSync(join(process.cwd(), file), 'utf-8')
|
||||||
|
const lines = content.split('\n')
|
||||||
|
for (let i = 0; i < Math.min(lines.length, 10); i++) {
|
||||||
|
console.log(color.green(`+ ${lines[i]}`))
|
||||||
|
}
|
||||||
|
if (lines.length > 10) {
|
||||||
|
console.log(color.gray(`... ${lines.length - 10} more lines`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all remote-only files in parallel
|
||||||
|
const remoteOnlyContents = await Promise.all(
|
||||||
|
remoteOnly.map(file => download(`/api/sync/apps/${appName}/files/${file}`))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Show remote-only files
|
||||||
|
for (let i = 0; i < remoteOnly.length; i++) {
|
||||||
|
const file = remoteOnly[i]!
|
||||||
|
const content = remoteOnlyContents[i]
|
||||||
|
|
||||||
|
console.log(color.bold(`\n${file}`))
|
||||||
|
console.log(color.gray('─'.repeat(60)))
|
||||||
|
console.log(color.red('Remote only (would be deleted on push)'))
|
||||||
|
if (content) {
|
||||||
|
const text = new TextDecoder().decode(content)
|
||||||
|
const lines = text.split('\n')
|
||||||
|
for (let i = 0; i < Math.min(lines.length, 10); i++) {
|
||||||
|
console.log(color.red(`- ${lines[i]}`))
|
||||||
|
}
|
||||||
|
if (lines.length > 10) {
|
||||||
|
console.log(color.gray(`... ${lines.length - 10} more lines`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function statusApp() {
|
||||||
|
if (!isApp()) {
|
||||||
|
console.error('Not a toes app. Use `toes get <app>` to grab one.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const appName = getAppName()
|
||||||
|
const diff = await getManifestDiff(appName)
|
||||||
|
|
||||||
|
if (diff === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { changed, localOnly, remoteOnly, localManifest, remoteManifest } = diff
|
||||||
|
|
||||||
|
// toPush = changed + localOnly (new or modified locally)
|
||||||
|
const toPush = [...changed, ...localOnly]
|
||||||
|
|
||||||
|
// Local changes block pull
|
||||||
|
const hasLocalChanges = toPush.length > 0
|
||||||
|
|
||||||
|
// Display status
|
||||||
|
console.log(`Status for ${color.bold(appName)}:\n`)
|
||||||
|
|
||||||
|
if (!remoteManifest) {
|
||||||
|
console.log(color.yellow('App does not exist on server'))
|
||||||
|
const localFileCount = Object.keys(localManifest.files).length
|
||||||
|
console.log(`\nWould create new app with ${localFileCount} files on push\n`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push status
|
||||||
|
if (toPush.length > 0 || remoteOnly.length > 0) {
|
||||||
|
console.log(color.bold('Changes to push:'))
|
||||||
|
for (const file of toPush) {
|
||||||
|
console.log(` ${color.green('↑')} ${file}`)
|
||||||
|
}
|
||||||
|
for (const file of remoteOnly) {
|
||||||
|
console.log(` ${color.red('✗')} ${file}`)
|
||||||
|
}
|
||||||
|
console.log()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull status (only show if no local changes blocking)
|
||||||
|
if (!hasLocalChanges && remoteOnly.length > 0) {
|
||||||
|
console.log(color.bold('Changes to pull:'))
|
||||||
|
for (const file of remoteOnly) {
|
||||||
|
console.log(` ${color.green('↓')} ${file}`)
|
||||||
|
}
|
||||||
|
console.log()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
if (toPush.length === 0 && remoteOnly.length === 0) {
|
||||||
|
console.log(color.green('✓ In sync with server'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function syncApp() {
|
export async function syncApp() {
|
||||||
if (!isApp()) {
|
if (!isApp()) {
|
||||||
console.error('Not a toes app. Use `toes get <app>` to grab one.')
|
console.error('Not a toes app. Use `toes get <app>` to grab one.')
|
||||||
|
|
@ -318,3 +470,132 @@ export async function syncApp() {
|
||||||
watcher.close()
|
watcher.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getManifestDiff(appName: string): Promise<ManifestDiff | null> {
|
||||||
|
const localManifest = generateManifest(process.cwd(), appName)
|
||||||
|
const result = await getManifest(appName)
|
||||||
|
|
||||||
|
if (result === null) {
|
||||||
|
// Connection error - already printed
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const localFiles = new Set(Object.keys(localManifest.files))
|
||||||
|
const remoteFiles = new Set(Object.keys(result.manifest?.files ?? {}))
|
||||||
|
|
||||||
|
// Files that differ
|
||||||
|
const changed: string[] = []
|
||||||
|
for (const file of localFiles) {
|
||||||
|
if (remoteFiles.has(file)) {
|
||||||
|
const local = localManifest.files[file]!
|
||||||
|
const remote = result.manifest!.files[file]!
|
||||||
|
if (local.hash !== remote.hash) {
|
||||||
|
changed.push(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files only in local
|
||||||
|
const localOnly: string[] = []
|
||||||
|
for (const file of localFiles) {
|
||||||
|
if (!remoteFiles.has(file)) {
|
||||||
|
localOnly.push(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files only in remote
|
||||||
|
const remoteOnly: string[] = []
|
||||||
|
for (const file of remoteFiles) {
|
||||||
|
if (!localFiles.has(file)) {
|
||||||
|
remoteOnly.push(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
changed,
|
||||||
|
localOnly,
|
||||||
|
remoteOnly,
|
||||||
|
localManifest,
|
||||||
|
remoteManifest: result.manifest ?? null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDiff(remote: string, local: string) {
|
||||||
|
const changes = diffLines(remote, local)
|
||||||
|
let lineCount = 0
|
||||||
|
const maxLines = 50
|
||||||
|
const contextLines = 3
|
||||||
|
|
||||||
|
for (let i = 0; i < changes.length; i++) {
|
||||||
|
const part = changes[i]!
|
||||||
|
const lines = part.value.replace(/\n$/, '').split('\n')
|
||||||
|
|
||||||
|
if (part.added) {
|
||||||
|
for (const line of lines) {
|
||||||
|
if (lineCount >= maxLines) {
|
||||||
|
console.log(color.gray('... diff truncated'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(color.green(`+ ${line}`))
|
||||||
|
lineCount++
|
||||||
|
}
|
||||||
|
} else if (part.removed) {
|
||||||
|
for (const line of lines) {
|
||||||
|
if (lineCount >= maxLines) {
|
||||||
|
console.log(color.gray('... diff truncated'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(color.red(`- ${line}`))
|
||||||
|
lineCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Context: show lines near changes
|
||||||
|
const prevHasChange = i > 0 && (changes[i - 1]!.added || changes[i - 1]!.removed)
|
||||||
|
const nextHasChange = i < changes.length - 1 && (changes[i + 1]!.added || changes[i + 1]!.removed)
|
||||||
|
|
||||||
|
if (prevHasChange && nextHasChange && lines.length <= contextLines * 2) {
|
||||||
|
// Small gap between changes - show all
|
||||||
|
for (const line of lines) {
|
||||||
|
if (lineCount >= maxLines) {
|
||||||
|
console.log(color.gray('... diff truncated'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(color.gray(` ${line}`))
|
||||||
|
lineCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show context before next change
|
||||||
|
if (nextHasChange) {
|
||||||
|
const start = Math.max(0, lines.length - contextLines)
|
||||||
|
if (start > 0) {
|
||||||
|
console.log(color.gray(' ...'))
|
||||||
|
lineCount++
|
||||||
|
}
|
||||||
|
for (let j = start; j < lines.length; j++) {
|
||||||
|
if (lineCount >= maxLines) {
|
||||||
|
console.log(color.gray('... diff truncated'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(color.gray(` ${lines[j]}`))
|
||||||
|
lineCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Show context after previous change
|
||||||
|
if (prevHasChange) {
|
||||||
|
const end = Math.min(lines.length, contextLines)
|
||||||
|
for (let j = 0; j < end; j++) {
|
||||||
|
if (lineCount >= maxLines) {
|
||||||
|
console.log(color.gray('... diff truncated'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(color.gray(` ${lines[j]}`))
|
||||||
|
lineCount++
|
||||||
|
}
|
||||||
|
if (end < lines.length && !nextHasChange) {
|
||||||
|
console.log(color.gray(' ...'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { readFileSync } from 'fs'
|
||||||
|
|
||||||
import color from 'kleur'
|
import color from 'kleur'
|
||||||
import {
|
import {
|
||||||
|
diffApp,
|
||||||
getApp,
|
getApp,
|
||||||
infoApp,
|
infoApp,
|
||||||
listApps,
|
listApps,
|
||||||
|
|
@ -15,6 +16,7 @@ import {
|
||||||
restartApp,
|
restartApp,
|
||||||
rmApp,
|
rmApp,
|
||||||
startApp,
|
startApp,
|
||||||
|
statusApp,
|
||||||
stopApp,
|
stopApp,
|
||||||
syncApp,
|
syncApp,
|
||||||
} from './commands'
|
} from './commands'
|
||||||
|
|
@ -114,8 +116,19 @@ program
|
||||||
program
|
program
|
||||||
.command('pull')
|
.command('pull')
|
||||||
.description('Pull changes from server')
|
.description('Pull changes from server')
|
||||||
|
.option('-f, --force', 'overwrite local changes')
|
||||||
.action(pullApp)
|
.action(pullApp)
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('status')
|
||||||
|
.description('Show what would be pushed/pulled')
|
||||||
|
.action(statusApp)
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('diff')
|
||||||
|
.description('Show diff of changed files')
|
||||||
|
.action(diffApp)
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('sync')
|
.command('sync')
|
||||||
.description('Watch and sync changes bidirectionally')
|
.description('Watch and sync changes bidirectionally')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user