Skip to content

Commit 88ce14c

Browse files
committed
[release] Make --push honor --auto and centralize the CLI log file
Two fixes that ship together. --auto for --push: --autoSync (alias --auto) previously only short-circuited the --sync flow, so a CI step running --push --auto would still drop into inquirer's "Select databases to sync" checkbox and hang on stdin. The push flow now reads the same flag: when set, every available database is selected, every local table for each database is selected, and the summary confirmation is skipped. Centralized logging: the winston file transport now defaults to ~/.appwrite-utils-cli/logs/ instead of process.cwd()/zlogs, and is enabled by default for every CLI run via a new "file" preset. The active log path is printed once at startup and again on any failure (graceful or uncaught), so when something breaks the user has one filename to share. --logDir overrides the directory. --debug still upgrades to debug-level with the console transport.
1 parent d31bb06 commit 88ce14c

2 files changed

Lines changed: 83 additions & 9 deletions

File tree

packages/appwrite-utils-cli/src/main.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
logger,
2020
AuthenticationError,
2121
configureLoggingPreset,
22+
getActiveLogPaths,
2223
} from "appwrite-utils-helpers";
2324
import { ConfirmationDialogs } from "./shared/confirmationDialogs.js";
2425
import { SelectionDialogs } from "./shared/selectionDialogs.js";
@@ -99,6 +100,7 @@ interface CliOptions {
99100
useSession?: boolean;
100101
sessionCookie?: string;
101102
debug?: boolean;
103+
logDir?: string;
102104
listBackups?: boolean;
103105
autoSync?: boolean;
104106
selectBuckets?: boolean;
@@ -497,7 +499,7 @@ const argv = yargs(hideBin(process.argv))
497499
.option("autoSync", {
498500
alias: ["auto"],
499501
type: "boolean",
500-
description: "Skip prompts and sync all databases, tables, and buckets (current behavior)"
502+
description: "Skip every interactive prompt for --push and --sync: select all databases, all local tables, no confirmation. The flag CI workflows want."
501503
})
502504
.option("selectBuckets", {
503505
type: "boolean",
@@ -839,6 +841,11 @@ const argv = yargs(hideBin(process.argv))
839841
default: false,
840842
description: "Enable verbose helpers logging (debug level + console transport). Use when auth/discovery is failing silently.",
841843
})
844+
.option("logDir", {
845+
alias: ["log-dir"],
846+
type: "string",
847+
description: "Override the directory where the CLI writes combined.log and error.log. Defaults to ~/.appwrite-utils-cli/logs/.",
848+
})
842849
.parse() as ParsedArgv;
843850

844851
// Idempotent process-wide exit. Multiple SIGINTs (or SIGINT-then-SIGTERM)
@@ -854,6 +861,15 @@ function __awuExit(code: number): void {
854861
process.on("SIGINT", () => __awuExit(130));
855862
process.on("SIGTERM", () => __awuExit(143));
856863

864+
// Tell the user where the centralized log file lives, both at startup and on
865+
// failure. Without this they hunt for it; with it they can paste one path.
866+
function __awuPrintLogPath(): void {
867+
const paths = getActiveLogPaths();
868+
if (!paths) return;
869+
// eslint-disable-next-line no-console
870+
console.error(`📝 Full log: ${paths.combined}`);
871+
}
872+
857873
// Global error capture. Without these handlers, an inquirer-internal crash
858874
// (or any other top-level throw inside an async path) just dumps a raw stack
859875
// trace and dies — making bug reports impossible to triage because the
@@ -866,6 +882,7 @@ process.on("uncaughtException", (error) => {
866882
error,
867883
context: { cwd: process.cwd() },
868884
});
885+
__awuPrintLogPath();
869886
__awuExit(1);
870887
});
871888
process.on("unhandledRejection", (reason) => {
@@ -875,20 +892,27 @@ process.on("unhandledRejection", (reason) => {
875892
error: reason,
876893
context: { cwd: process.cwd() },
877894
});
895+
__awuPrintLogPath();
878896
__awuExit(1);
879897
});
880898

881899
async function main() {
882900
const startTime = Date.now();
883901
const operationStats: Record<string, number> = {};
884902

885-
// --debug: flip the helpers winston logger from silent default to
886-
// debug-level with console transport. Without this, all the diagnostic
887-
// logs in SessionAuthService/ConfigManager are invisible — the silent
888-
// default is great for production but useless when something's broken.
903+
// Centralized logging: write everything to a stable, predictable file path
904+
// by default so users can always paste a single log. --debug additionally
905+
// turns on the console transport at debug level. --logDir overrides the
906+
// directory both presets write to.
889907
if (argv.debug) {
890-
configureLoggingPreset("debug");
908+
configureLoggingPreset("debug", argv.logDir);
891909
MessageFormatter.info("Debug logging enabled (helpers logs → console at debug level)", { prefix: "CLI" });
910+
} else {
911+
configureLoggingPreset("file", argv.logDir);
912+
}
913+
const __logPaths = getActiveLogPaths();
914+
if (__logPaths) {
915+
MessageFormatter.info(`Logs → ${__logPaths.combined}`, { prefix: "CLI" });
892916
}
893917

894918
if ((argv.schemaFormat || argv.schemaOutDir) && !argv.generate) {
@@ -1645,6 +1669,13 @@ async function main() {
16451669
let selectedDbIds: string[] = [];
16461670
if (parsedArgv.dbIds) {
16471671
selectedDbIds = parsedArgv.dbIds.split(/[,\s]+/).filter(Boolean);
1672+
} else if (parsedArgv.autoSync) {
1673+
// --auto: skip the interactive picker and push every available DB.
1674+
selectedDbIds = availableDatabases.map(db => db.$id);
1675+
MessageFormatter.info(
1676+
`--auto: selected all ${selectedDbIds.length} database(s): ${selectedDbIds.join(", ")}`,
1677+
{ prefix: "Push" }
1678+
);
16481679
} else {
16491680
selectedDbIds = await SelectionDialogs.selectDatabases(
16501681
availableDatabases,
@@ -1704,6 +1735,9 @@ async function main() {
17041735
if (parsedArgv.tableIds) {
17051736
// Non-interactive: respect provided table IDs as-is (apply to each selected DB)
17061737
selectedTableIds = parsedArgv.tableIds.split(/[\,\s]+/).filter(Boolean);
1738+
} else if (parsedArgv.autoSync) {
1739+
// --auto: take every local item for this DB, no prompt.
1740+
selectedTableIds = [...localItemIds];
17071741
} else {
17081742
const inquirer = (await import("inquirer")).default;
17091743
const choices: Array<{ name: string; value: string }> = [];
@@ -1788,8 +1822,9 @@ async function main() {
17881822
collections: databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0),
17891823
details: databaseSelections.map(s => `${s.databaseId}: ${s.tableIds.length} items`),
17901824
};
1791-
// Skip confirmation if both dbIds and tableIds are provided (non-interactive)
1792-
if (!(parsedArgv.dbIds && parsedArgv.tableIds)) {
1825+
// Skip confirmation when running non-interactively: either --auto, or
1826+
// both --dbIds and --tableIds explicitly provided.
1827+
if (!parsedArgv.autoSync && !(parsedArgv.dbIds && parsedArgv.tableIds)) {
17931828
const confirmed = await ConfirmationDialogs.showOperationSummary('Push', pushSummary, { confirmationRequired: true });
17941829
if (!confirmed) {
17951830
MessageFormatter.info("Push operation cancelled", { prefix: "Push" });
@@ -1963,5 +1998,6 @@ main().catch((error) => {
19631998
return;
19641999
}
19652000
MessageFormatter.error("CLI execution failed", error, { prefix: "CLI" });
2001+
__awuPrintLogPath();
19662002
process.exit(1);
19672003
});

packages/appwrite-utils-helpers/src/shared/logging.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import winston from "winston";
22
import fs from "fs";
3+
import os from "os";
34
import path from "path";
45

56
export interface LoggingConfig {
@@ -9,6 +10,18 @@ export interface LoggingConfig {
910
console: boolean;
1011
}
1112

13+
/**
14+
* The CLI's stable, predictable log directory. Lives under the user's home so
15+
* a single path is always valid regardless of cwd — that's what users paste
16+
* when something breaks. Override with config.logging.logDirectory or
17+
* --logDir.
18+
*/
19+
export const DEFAULT_LOG_DIRECTORY = path.join(
20+
os.homedir(),
21+
".appwrite-utils-cli",
22+
"logs"
23+
);
24+
1225
/**
1326
* Predefined logging configurations for common debugging scenarios
1427
*/
@@ -31,6 +44,12 @@ export const LOGGING_PRESETS = {
3144
level: 'debug',
3245
console: true
3346
},
47+
/** File-only: capture everything to disk without spamming the user's terminal. The default for any CLI run. */
48+
file: {
49+
enabled: true,
50+
level: 'info',
51+
console: false
52+
},
3453
/** Silent - no logging output */
3554
silent: {
3655
enabled: false,
@@ -78,7 +97,7 @@ const createLogger = () => {
7897

7998
// Add file transports if logging is enabled
8099
if (loggingConfig.enabled) {
81-
const logDir = loggingConfig.logDirectory || path.join(process.cwd(), "zlogs");
100+
const logDir = loggingConfig.logDirectory || DEFAULT_LOG_DIRECTORY;
82101

83102
if (!fs.existsSync(logDir)) {
84103
fs.mkdirSync(logDir, { recursive: true });
@@ -147,3 +166,22 @@ export const disableLogging = () => {
147166
* Get current logging configuration
148167
*/
149168
export const getLoggingConfig = () => ({ ...loggingConfig });
169+
170+
/**
171+
* Return the absolute paths the file transports are writing to right now, or
172+
* null when file logging is disabled. main.ts uses this to print the paths up
173+
* front and on failure so the user has one thing to paste.
174+
*/
175+
export const getActiveLogPaths = (): {
176+
directory: string;
177+
combined: string;
178+
error: string;
179+
} | null => {
180+
if (!loggingConfig.enabled) return null;
181+
const directory = loggingConfig.logDirectory || DEFAULT_LOG_DIRECTORY;
182+
return {
183+
directory,
184+
combined: path.join(directory, "combined.log"),
185+
error: path.join(directory, "error.log"),
186+
};
187+
};

0 commit comments

Comments
 (0)