ruby on rails

This commit is contained in:
Chris Wanstrath 2026-02-12 08:07:23 -08:00
parent 14281a1bf5
commit a96aa1d2dc

View File

@ -9,6 +9,8 @@ import { del, download, get, getManifest, handleError, makeUrl, post, put } from
import { confirm, prompt } from '../prompts' import { confirm, prompt } from '../prompts'
import { getAppName, getAppPackage, isApp, resolveAppName } from '../name' import { getAppName, getAppPackage, isApp, resolveAppName } from '../name'
const s = (n: number) => n === 1 ? '' : 's'
function notAppError(): string { function notAppError(): string {
const pkg = getAppPackage() const pkg = getAppPackage()
if (!pkg) return 'No package.json found. Use `toes get <app>` to grab one.' if (!pkg) return 'No package.json found. Use `toes get <app>` to grab one.'
@ -76,7 +78,7 @@ export async function historyApp(name?: string) {
console.log(` ${color.green('+')} ${file}`) console.log(` ${color.green('+')} ${file}`)
} }
for (const file of entry.modified) { for (const file of entry.modified) {
console.log(` ${color.yellow('~')} ${file}`) console.log(` ${color.magenta('*')} ${file}`)
} }
for (const file of entry.deleted) { for (const file of entry.deleted) {
console.log(` ${color.red('-')} ${file}`) console.log(` ${color.red('-')} ${file}`)
@ -104,7 +106,7 @@ export async function getApp(name: string) {
mkdirSync(appPath, { recursive: true }) mkdirSync(appPath, { recursive: true })
const files = Object.keys(result.manifest.files) const files = Object.keys(result.manifest.files)
console.log(`Downloading ${files.length} files...`) console.log(`Downloading ${files.length} file${s(files.length)}...`)
for (const file of files) { for (const file of files) {
const content = await download(`/api/sync/apps/${name}/files/${file}`) const content = await download(`/api/sync/apps/${name}/files/${file}`)
@ -208,7 +210,7 @@ export async function pushApp(options: { quiet?: boolean, force?: boolean } = {}
const actualDeletes = toDelete.filter(f => !renamedDeletes.has(f)) const actualDeletes = toDelete.filter(f => !renamedDeletes.has(f))
if (renames.length > 0) { if (renames.length > 0) {
console.log(`Renaming ${renames.length} files...`) console.log(`Renaming ${renames.length} file${s(renames.length)}...`)
for (const { from, to } of renames) { for (const { from, to } of renames) {
const content = readFileSync(join(process.cwd(), to)) const content = readFileSync(join(process.cwd(), to))
const uploadOk = await put(`/api/sync/apps/${appName}/files/${to}?version=${version}`, content) const uploadOk = await put(`/api/sync/apps/${appName}/files/${to}?version=${version}`, content)
@ -222,7 +224,7 @@ export async function pushApp(options: { quiet?: boolean, force?: boolean } = {}
} }
if (actualUploads.length > 0) { if (actualUploads.length > 0) {
console.log(`Uploading ${actualUploads.length} files...`) console.log(`Uploading ${actualUploads.length} file${s(actualUploads.length)}...`)
let failedUploads = 0 let failedUploads = 0
for (const file of actualUploads) { for (const file of actualUploads) {
@ -237,7 +239,7 @@ export async function pushApp(options: { quiet?: boolean, force?: boolean } = {}
} }
if (failedUploads > 0) { if (failedUploads > 0) {
console.error(`Failed to upload ${failedUploads} file(s). Deployment aborted.`) console.error(`Failed to upload ${failedUploads} file${s(failedUploads)}. Deployment aborted.`)
console.error(`Incomplete version ${version} left on server (not activated).`) console.error(`Incomplete version ${version} left on server (not activated).`)
return return
} }
@ -245,11 +247,11 @@ export async function pushApp(options: { quiet?: boolean, force?: boolean } = {}
// 3. Delete files that no longer exist locally // 3. Delete files that no longer exist locally
if (actualDeletes.length > 0) { if (actualDeletes.length > 0) {
console.log(`Deleting ${actualDeletes.length} files...`) console.log(`Deleting ${actualDeletes.length} file${s(actualDeletes.length)}...`)
for (const file of actualDeletes) { for (const file of actualDeletes) {
const success = await del(`/api/sync/apps/${appName}/files/${file}?version=${version}`) const success = await del(`/api/sync/apps/${appName}/files/${file}?version=${version}`)
if (success) { if (success) {
console.log(` ${color.red('')} ${file}`) console.log(` ${color.red('-')} ${file}`)
} else { } else {
console.log(` ${color.red('✗')} ${file} (failed)`) console.log(` ${color.red('✗')} ${file} (failed)`)
} }
@ -307,7 +309,7 @@ export async function pullApp(options: { force?: boolean, quiet?: boolean } = {}
if (hasDiffs && !options.force) { if (hasDiffs && !options.force) {
console.error('Cannot pull: you have local changes that would be overwritten') console.error('Cannot pull: you have local changes that would be overwritten')
for (const file of changed) { for (const file of changed) {
console.error(` ${color.yellow('~')} ${file}`) console.error(` ${color.magenta('*')} ${file}`)
} }
for (const file of localOnly) { for (const file of localOnly) {
console.error(` ${color.green('+')} ${file} (local only)`) console.error(` ${color.green('+')} ${file} (local only)`)
@ -333,7 +335,7 @@ export async function pullApp(options: { force?: boolean, quiet?: boolean } = {}
console.log(`Pulling ${color.bold(appName)} from server...`) console.log(`Pulling ${color.bold(appName)} from server...`)
if (toDownload.length > 0) { if (toDownload.length > 0) {
console.log(`Downloading ${toDownload.length} files...`) console.log(`Downloading ${toDownload.length} file${s(toDownload.length)}...`)
for (const file of toDownload) { for (const file of toDownload) {
const content = await download(`/api/sync/apps/${appName}/files/${file}`) const content = await download(`/api/sync/apps/${appName}/files/${file}`)
if (!content) { if (!content) {
@ -354,12 +356,12 @@ export async function pullApp(options: { force?: boolean, quiet?: boolean } = {}
} }
if (toDelete.length > 0) { if (toDelete.length > 0) {
console.log(`Deleting ${toDelete.length} local files...`) console.log(`Deleting ${toDelete.length} local file${s(toDelete.length)}...`)
for (const file of toDelete) { for (const file of toDelete) {
const fullPath = join(process.cwd(), file) const fullPath = join(process.cwd(), file)
if (existsSync(fullPath)) { if (existsSync(fullPath)) {
unlinkSync(fullPath) unlinkSync(fullPath)
console.log(` ${color.red('')} ${file}`) console.log(` ${color.red('-')} ${file}`)
} }
} }
} }
@ -500,7 +502,7 @@ export async function statusApp() {
if (!remoteManifest) { if (!remoteManifest) {
console.log(color.yellow('App does not exist on server')) console.log(color.yellow('App does not exist on server'))
const localFileCount = Object.keys(localManifest.files).length const localFileCount = Object.keys(localManifest.files).length
console.log(`\nWould create new app with ${localFileCount} files on push\n`) console.log(`\nWould create new app with ${localFileCount} file${s(localFileCount)} on push\n`)
return return
} }
@ -525,7 +527,7 @@ export async function statusApp() {
console.log(` ${color.cyan('→')} ${from}${to}`) console.log(` ${color.cyan('→')} ${from}${to}`)
} }
for (const file of changed) { for (const file of changed) {
console.log(` ${color.green('↑')} ${file}`) console.log(` ${color.magenta('*')} ${file}`)
} }
for (const file of localOnly) { for (const file of localOnly) {
console.log(` ${color.green('+')} ${file}`) console.log(` ${color.green('+')} ${file}`)
@ -542,7 +544,7 @@ export async function statusApp() {
console.log(` ${color.cyan('→')} ${from}${to}`) console.log(` ${color.cyan('→')} ${from}${to}`)
} }
for (const file of changed) { for (const file of changed) {
console.log(` ${color.yellow('~')} ${file}`) console.log(` ${color.magenta('*')} ${file}`)
} }
for (const file of localOnly) { for (const file of localOnly) {
console.log(` ${color.green('+')} ${file} (local only)`) console.log(` ${color.green('+')} ${file} (local only)`)
@ -798,12 +800,12 @@ export async function stashApp() {
const content = readFileSync(srcPath) const content = readFileSync(srcPath)
writeFileSync(destPath, content) writeFileSync(destPath, content)
console.log(` ${color.yellow('→')} ${file}`) console.log(` ${color.magenta('*')} ${file}`)
} }
// Restore changed files from server // Restore changed files from server
if (changed.length > 0) { if (changed.length > 0) {
console.log(`\nRestoring ${changed.length} changed files from server...`) console.log(`\nRestoring ${changed.length} changed file${s(changed.length)} from server...`)
for (const file of changed) { for (const file of changed) {
const content = await download(`/api/sync/apps/${appName}/files/${file}`) const content = await download(`/api/sync/apps/${appName}/files/${file}`)
if (content) { if (content) {
@ -814,13 +816,13 @@ export async function stashApp() {
// Delete local-only files // Delete local-only files
if (localOnly.length > 0) { if (localOnly.length > 0) {
console.log(`Removing ${localOnly.length} local-only files...`) console.log(`Removing ${localOnly.length} local-only file${s(localOnly.length)}...`)
for (const file of localOnly) { for (const file of localOnly) {
unlinkSync(join(process.cwd(), file)) unlinkSync(join(process.cwd(), file))
} }
} }
console.log(color.green(`\n✓ Stashed ${toStash.length} file(s)`)) console.log(color.green(`\n✓ Stashed ${toStash.length} file${s(toStash.length)}`))
} }
export async function stashListApp() { export async function stashListApp() {
@ -846,7 +848,7 @@ export async function stashListApp() {
files: string[] files: string[]
} }
const date = new Date(metadata.timestamp).toLocaleString() const date = new Date(metadata.timestamp).toLocaleString()
console.log(` ${color.bold(stash.name)} ${color.gray(date)} ${color.gray(`(${metadata.files.length} files)`)}`) console.log(` ${color.bold(stash.name)} ${color.gray(date)} ${color.gray(`(${metadata.files.length} file${s(metadata.files.length)})`)}`)
} else { } else {
console.log(` ${color.bold(stash.name)} ${color.gray('(invalid)')}`) console.log(` ${color.bold(stash.name)} ${color.gray('(invalid)')}`)
} }
@ -908,7 +910,7 @@ export async function stashPopApp() {
// Remove stash directory // Remove stash directory
rmSync(stashDir, { recursive: true }) rmSync(stashDir, { recursive: true })
console.log(color.green(`\n✓ Restored ${metadata.files.length} file(s)`)) console.log(color.green(`\n✓ Restored ${metadata.files.length} file${s(metadata.files.length)}`))
} }
export async function cleanApp(options: { force?: boolean, dryRun?: boolean } = {}) { export async function cleanApp(options: { force?: boolean, dryRun?: boolean } = {}) {
@ -934,7 +936,7 @@ export async function cleanApp(options: { force?: boolean, dryRun?: boolean } =
if (options.dryRun) { if (options.dryRun) {
console.log('Would remove:') console.log('Would remove:')
for (const file of localOnly) { for (const file of localOnly) {
console.log(` ${color.red('')} ${file}`) console.log(` ${color.red('-')} ${file}`)
} }
return return
} }
@ -942,20 +944,20 @@ export async function cleanApp(options: { force?: boolean, dryRun?: boolean } =
if (!options.force) { if (!options.force) {
console.log('Files not on server:') console.log('Files not on server:')
for (const file of localOnly) { for (const file of localOnly) {
console.log(` ${color.red('')} ${file}`) console.log(` ${color.red('-')} ${file}`)
} }
console.log() console.log()
const ok = await confirm(`Remove ${localOnly.length} file(s)?`) const ok = await confirm(`Remove ${localOnly.length} file${s(localOnly.length)}?`)
if (!ok) return if (!ok) return
} }
for (const file of localOnly) { for (const file of localOnly) {
const fullPath = join(process.cwd(), file) const fullPath = join(process.cwd(), file)
unlinkSync(fullPath) unlinkSync(fullPath)
console.log(` ${color.red('')} ${file}`) console.log(` ${color.red('-')} ${file}`)
} }
console.log(color.green(`✓ Removed ${localOnly.length} file(s)`)) console.log(color.green(`✓ Removed ${localOnly.length} file${s(localOnly.length)}`))
} }
interface VersionsResponse { interface VersionsResponse {