diff --git a/packages/cli/package.json b/packages/cli/package.json index 6a8b9f8fe..7ece2491f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -55,7 +55,8 @@ "@pgpmjs/server-utils": "workspace:^", "@pgpmjs/types": "workspace:^", "find-and-require-package-json": "^0.8.2", - "inquirerer": "^4.1.0", + "@inquirerer/utils": "^3.1.1", + "inquirerer": "^4.1.1", "js-yaml": "^4.1.0", "minimist": "^1.2.8", "pg-cache": "workspace:^", diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index a46a1b3a2..3d28adb56 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -1,4 +1,5 @@ import { findAndRequirePackageJson } from 'find-and-require-package-json'; +import { cliExitWithError, checkForUpdates, extractFirst } from '@inquirerer/utils'; import { CLIOptions, Inquirerer } from 'inquirerer'; import { ParsedArgs } from 'minimist'; @@ -6,8 +7,7 @@ import codegen from './commands/codegen'; import explorer from './commands/explorer'; import getGraphqlSchema from './commands/get-graphql-schema'; import server from './commands/server'; -import { cliExitWithError, extractFirst, usageText } from './utils'; -import { checkForUpdates } from './utils/update-check'; +import { usageText } from './utils'; const createCommandMap = (): Record => { return { @@ -22,16 +22,18 @@ export const commands = async (argv: Partial, prompter: Inquirerer, let { first: command, newArgv } = extractFirst(argv); // Run update check early so it shows on help/version paths too + // (checkForUpdates auto-skips in CI or when INQUIRERER_SKIP_UPDATE_CHECK / CONSTRUCTIVE_SKIP_UPDATE_CHECK is set) try { const pkg = findAndRequirePackageJson(__dirname); - await checkForUpdates({ - command: command || 'help', + const updateResult = await checkForUpdates({ pkgName: pkg.name, pkgVersion: pkg.version, toolName: 'constructive', - key: pkg.name, - updateCommand: `Run npm i -g ${pkg.name}@latest to upgrade.` }); + if (updateResult.hasUpdate && updateResult.message) { + console.warn(updateResult.message); + console.warn(`Run npm i -g ${pkg.name}@latest to upgrade.`); + } } catch { // ignore update check failures } diff --git a/packages/cli/src/utils/argv.ts b/packages/cli/src/utils/argv.ts deleted file mode 100644 index 2a2fb40a2..000000000 --- a/packages/cli/src/utils/argv.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ParsedArgs } from 'minimist'; - -export const extractFirst = (argv: Partial) => { - const first = argv._?.[0]; - const newArgv = { - ...argv, - _: argv._?.slice(1) ?? [] - }; - return { first, newArgv }; -}; diff --git a/packages/cli/src/utils/cli-error.ts b/packages/cli/src/utils/cli-error.ts deleted file mode 100644 index be0b158f1..000000000 --- a/packages/cli/src/utils/cli-error.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Logger } from '@pgpmjs/logger'; -import { PgpmError } from '@pgpmjs/types'; - -const log = new Logger('cli'); - -/** - * CLI error utility that logs error information and exits with code 1. - * Provides consistent error handling and user experience across all CLI commands. - */ -export const cliExitWithError = async ( - error: PgpmError | Error | string, - context?: Record -): Promise => { - if (error instanceof PgpmError) { - log.error(`Error: ${error.message}`); - - if (error.context && Object.keys(error.context).length > 0) { - log.debug('Error context:', error.context); - } - - if (context) { - log.debug('Additional context:', context); - } - } else if (error instanceof Error) { - log.error(`Error: ${error.message}`); - if (context) { - log.debug('Context:', context); - } - } else if (typeof error === 'string') { - log.error(`Error: ${error}`); - if (context) { - log.debug('Context:', context); - } - } - - process.exit(1); -}; diff --git a/packages/cli/src/utils/index.ts b/packages/cli/src/utils/index.ts index 259fbb11a..834e2bf93 100644 --- a/packages/cli/src/utils/index.ts +++ b/packages/cli/src/utils/index.ts @@ -1,3 +1 @@ -export { extractFirst } from './argv'; -export { cliExitWithError } from './cli-error'; export { usageText } from './display'; diff --git a/packages/cli/src/utils/update-check.ts b/packages/cli/src/utils/update-check.ts deleted file mode 100644 index 6453f7bc2..000000000 --- a/packages/cli/src/utils/update-check.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { findAndRequirePackageJson } from 'find-and-require-package-json'; -import { Logger } from '@pgpmjs/logger'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; - -const log = new Logger('update-check'); - -const UPDATE_CHECK_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 1 week - -export interface CheckForUpdatesOptions { - pkgName?: string; - pkgVersion?: string; - command?: string; - now?: number; - updateCommand?: string; - toolName?: string; - key?: string; -} - -interface UpdateCheckConfig { - lastCheckedAt: number; - latestKnownVersion: string; -} - -const shouldSkip = (command?: string): boolean => { - if (process.env.PGPM_SKIP_UPDATE_CHECK) return true; - if (process.env.CI === 'true') return true; - return false; -}; - -function getConfigPath(toolName: string, key: string): string { - const configDir = path.join(os.homedir(), `.${toolName}`); - return path.join(configDir, `${key}.json`); -} - -function readConfig(toolName: string, key: string): UpdateCheckConfig | null { - try { - const configPath = getConfigPath(toolName, key); - if (!fs.existsSync(configPath)) return null; - const content = fs.readFileSync(configPath, 'utf8'); - return JSON.parse(content); - } catch { - return null; - } -} - -function writeConfig(toolName: string, key: string, config: UpdateCheckConfig): void { - try { - const configPath = getConfigPath(toolName, key); - const configDir = path.dirname(configPath); - if (!fs.existsSync(configDir)) { - fs.mkdirSync(configDir, { recursive: true }); - } - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - } catch { - // Ignore write errors - } -} - -async function fetchLatestVersion(pkgName: string): Promise { - try { - const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`); - if (!response.ok) return null; - const data = await response.json(); - return data.version || null; - } catch { - return null; - } -} - -function compareVersions(current: string, latest: string): number { - const currentParts = current.replace(/^v/, '').split('.').map(Number); - const latestParts = latest.replace(/^v/, '').split('.').map(Number); - - for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) { - const c = currentParts[i] || 0; - const l = latestParts[i] || 0; - if (c < l) return -1; - if (c > l) return 1; - } - return 0; -} - -export async function checkForUpdates(options: CheckForUpdatesOptions = {}): Promise { - const { - pkgName = '@constructive-io/cli', - pkgVersion = findAndRequirePackageJson(__dirname).version, - command, - now = Date.now(), - key = 'update-check', - toolName = 'constructive' - } = options; - - if (shouldSkip(command)) { - return null; - } - - try { - const existing = readConfig(toolName, key); - let latestKnownVersion = existing?.latestKnownVersion ?? pkgVersion; - - const needsCheck = !existing?.lastCheckedAt || (now - existing.lastCheckedAt) > UPDATE_CHECK_TTL_MS; - - if (needsCheck) { - const fetched = await fetchLatestVersion(pkgName); - if (fetched) { - latestKnownVersion = fetched; - } - - writeConfig(toolName, key, { - lastCheckedAt: now, - latestKnownVersion - }); - } - - const comparison = compareVersions(pkgVersion, latestKnownVersion); - const isOutdated = comparison < 0; - - if (isOutdated) { - const updateInstruction = options.updateCommand ?? `Run npm i -g ${pkgName}@latest to upgrade.`; - - log.warn( - `A new version of ${pkgName} is available (current ${pkgVersion}, latest ${latestKnownVersion}). ${updateInstruction}` - ); - - writeConfig(toolName, key, { - lastCheckedAt: now, - latestKnownVersion - }); - } - - return { - lastCheckedAt: now, - latestKnownVersion - }; - } catch (error) { - log.debug('Update check skipped due to error:', error); - return null; - } -} diff --git a/pgpm/cli/package.json b/pgpm/cli/package.json index 4aee7da43..c6653bfe2 100644 --- a/pgpm/cli/package.json +++ b/pgpm/cli/package.json @@ -46,14 +46,15 @@ "ts-node": "^10.9.2" }, "dependencies": { + "@inquirerer/utils": "^3.1.1", "@pgpmjs/core": "workspace:^", "@pgpmjs/env": "workspace:^", "@pgpmjs/logger": "workspace:^", "@pgpmjs/types": "workspace:^", "appstash": "^0.2.6", - "genomic": "^5.0.1", "find-and-require-package-json": "^0.8.2", - "inquirerer": "^4.1.0", + "genomic": "^5.0.2", + "inquirerer": "^4.1.1", "js-yaml": "^4.1.0", "minimist": "^1.2.8", "pg-cache": "workspace:^", diff --git a/pgpm/cli/src/commands.ts b/pgpm/cli/src/commands.ts index 9be4235a9..3922be102 100644 --- a/pgpm/cli/src/commands.ts +++ b/pgpm/cli/src/commands.ts @@ -1,4 +1,5 @@ import { findAndRequirePackageJson } from 'find-and-require-package-json'; +import { cliExitWithError, checkForUpdates, extractFirst } from '@inquirerer/utils'; import { CLIOptions, Inquirerer } from 'inquirerer'; import { ParsedArgs } from 'minimist'; import { teardownPgPools } from 'pg-cache'; @@ -27,9 +28,7 @@ import revert from './commands/revert'; import tag from './commands/tag'; import testPackages from './commands/test-packages'; import verify from './commands/verify'; -import { extractFirst, usageText } from './utils'; -import { cliExitWithError } from './utils/cli-error'; -import { checkForUpdates } from './utils/update-check'; +import { usageText } from './utils'; const withPgTeardown = (fn: Function, skipTeardown: boolean = false) => async (...args: any[]) => { try { @@ -104,13 +103,23 @@ export const commands = async (argv: Partial, prompter: Inquirerer, command = answer.command; } - try { - await checkForUpdates({ - command, - pkgVersion: findAndRequirePackageJson(__dirname).version - }); - } catch { - // ignore update check failures + // Run update check (skip on 'update' command to avoid redundant check) + // (checkForUpdates auto-skips in CI or when INQUIRERER_SKIP_UPDATE_CHECK / PGPM_SKIP_UPDATE_CHECK is set) + if (command !== 'update') { + try { + const pkg = findAndRequirePackageJson(__dirname); + const updateResult = await checkForUpdates({ + pkgName: pkg.name, + pkgVersion: pkg.version, + toolName: 'pgpm', + }); + if (updateResult.hasUpdate && updateResult.message) { + console.warn(updateResult.message); + console.warn('Run pgpm update to upgrade.'); + } + } catch { + // ignore update check failures + } } newArgv = await prompter.prompt(newArgv, [ @@ -128,7 +137,7 @@ export const commands = async (argv: Partial, prompter: Inquirerer, if (!commandFn) { console.log(usageText); - await cliExitWithError(`Unknown command: ${command}`); + await cliExitWithError(`Unknown command: ${command}`, { beforeExit: teardownPgPools }); } await commandFn(newArgv, prompter, options); diff --git a/pgpm/cli/src/commands/add.ts b/pgpm/cli/src/commands/add.ts index d8eb086b9..5fc178e90 100644 --- a/pgpm/cli/src/commands/add.ts +++ b/pgpm/cli/src/commands/add.ts @@ -1,10 +1,9 @@ +import { extractFirst } from '@inquirerer/utils'; import { PgpmPackage } from '@pgpmjs/core'; import { CLIOptions, Inquirerer } from 'inquirerer'; import { ParsedArgs } from 'minimist'; import * as path from 'path'; -import { extractFirst } from '../utils/argv'; - const addUsageText = ` Add Command: diff --git a/pgpm/cli/src/commands/admin-users.ts b/pgpm/cli/src/commands/admin-users.ts index a1894aabe..de1c82e69 100644 --- a/pgpm/cli/src/commands/admin-users.ts +++ b/pgpm/cli/src/commands/admin-users.ts @@ -1,7 +1,6 @@ +import { extractFirst } from '@inquirerer/utils'; import { CLIOptions, Inquirerer } from 'inquirerer'; import { ParsedArgs } from 'minimist'; - -import { extractFirst } from '../utils'; import add from './admin-users/add'; import bootstrap from './admin-users/bootstrap'; import remove from './admin-users/remove'; diff --git a/pgpm/cli/src/commands/cache.ts b/pgpm/cli/src/commands/cache.ts index 3c3293b7c..8e0182a89 100644 --- a/pgpm/cli/src/commands/cache.ts +++ b/pgpm/cli/src/commands/cache.ts @@ -1,6 +1,6 @@ +import { cliExitWithError } from '@inquirerer/utils'; import { CLIOptions, Inquirerer } from 'inquirerer'; import { CacheManager } from 'genomic'; -import { cliExitWithError } from '../utils/cli-error'; const cacheUsageText = ` Cache Command: diff --git a/pgpm/cli/src/commands/docker.ts b/pgpm/cli/src/commands/docker.ts index 5a9e9ef6a..2f5ed42d9 100644 --- a/pgpm/cli/src/commands/docker.ts +++ b/pgpm/cli/src/commands/docker.ts @@ -1,8 +1,7 @@ import { spawn } from 'child_process'; +import { cliExitWithError, extractFirst } from '@inquirerer/utils'; import { CLIOptions, Inquirerer } from 'inquirerer'; -import { cliExitWithError,extractFirst } from '../utils'; - const dockerUsageText = ` Docker Command: diff --git a/pgpm/cli/src/commands/migrate.ts b/pgpm/cli/src/commands/migrate.ts index e57790316..f813583f2 100644 --- a/pgpm/cli/src/commands/migrate.ts +++ b/pgpm/cli/src/commands/migrate.ts @@ -1,7 +1,6 @@ +import { extractFirst } from '@inquirerer/utils'; import { CLIOptions, Inquirerer } from 'inquirerer'; import { ParsedArgs } from 'minimist'; - -import { extractFirst } from '../utils'; import deps from './migrate/deps'; // Migrate subcommands import init from './migrate/init'; diff --git a/pgpm/cli/src/commands/remove.ts b/pgpm/cli/src/commands/remove.ts index ce60fc61b..19792ca4d 100644 --- a/pgpm/cli/src/commands/remove.ts +++ b/pgpm/cli/src/commands/remove.ts @@ -1,3 +1,4 @@ +import { cliExitWithError } from '@inquirerer/utils'; import { PgpmPackage } from '@pgpmjs/core'; import { getEnvOptions } from '@pgpmjs/env'; import { Logger } from '@pgpmjs/logger'; @@ -5,7 +6,6 @@ import { CLIOptions, Inquirerer, Question } from 'inquirerer'; import { getPgEnvOptions } from 'pg-env'; import { getTargetDatabase } from '../utils'; -import { cliExitWithError } from '../utils/cli-error'; const log = new Logger('remove'); diff --git a/pgpm/cli/src/commands/rename.ts b/pgpm/cli/src/commands/rename.ts index 45bb2b1e6..28ef22108 100644 --- a/pgpm/cli/src/commands/rename.ts +++ b/pgpm/cli/src/commands/rename.ts @@ -1,10 +1,9 @@ +import { cliExitWithError } from '@inquirerer/utils'; import { PgpmPackage } from '@pgpmjs/core'; import { Inquirerer } from 'inquirerer'; import { ParsedArgs } from 'minimist'; import path from 'path'; -import { cliExitWithError } from '../utils/cli-error'; - export default async (argv: Partial, _prompter: Inquirerer) => { const cwd = (argv.cwd as string) || process.cwd(); const to = (argv.to as string) || (argv._ && argv._[0] as string); diff --git a/pgpm/cli/src/commands/revert.ts b/pgpm/cli/src/commands/revert.ts index 9c7d3e302..f5824b8dc 100644 --- a/pgpm/cli/src/commands/revert.ts +++ b/pgpm/cli/src/commands/revert.ts @@ -1,3 +1,4 @@ +import { cliExitWithError } from '@inquirerer/utils'; import { PgpmPackage } from '@pgpmjs/core'; import { getEnvOptions } from '@pgpmjs/env'; import { Logger } from '@pgpmjs/logger'; @@ -5,7 +6,6 @@ import { CLIOptions, Inquirerer, Question } from 'inquirerer'; import { getPgEnvOptions } from 'pg-env'; import { getTargetDatabase, resolvePackageAlias } from '../utils'; -import { cliExitWithError } from '../utils/cli-error'; import { selectDeployedChange, selectDeployedPackage } from '../utils/deployed-changes'; const log = new Logger('revert'); diff --git a/pgpm/cli/src/commands/tag.ts b/pgpm/cli/src/commands/tag.ts index 61cfa1f8c..b568fe74d 100644 --- a/pgpm/cli/src/commands/tag.ts +++ b/pgpm/cli/src/commands/tag.ts @@ -1,10 +1,9 @@ +import { extractFirst } from '@inquirerer/utils'; import { PgpmPackage } from '@pgpmjs/core'; import { Logger } from '@pgpmjs/logger'; import { errors } from '@pgpmjs/types'; import { CLIOptions, Inquirerer, Question } from 'inquirerer'; import * as path from 'path'; - -import { extractFirst } from '../utils/argv'; import { selectPackage } from '../utils/module-utils'; import { resolvePackageAlias } from '../utils/package-alias'; diff --git a/pgpm/cli/src/commands/update.ts b/pgpm/cli/src/commands/update.ts index 258aafde7..a94a931e2 100644 --- a/pgpm/cli/src/commands/update.ts +++ b/pgpm/cli/src/commands/update.ts @@ -1,9 +1,9 @@ +import { cliExitWithError } from '@inquirerer/utils'; import { findAndRequirePackageJson } from 'find-and-require-package-json'; import { Logger } from '@pgpmjs/logger'; import { CLIOptions, Inquirerer } from 'inquirerer'; import { spawn } from 'child_process'; import { fetchLatestVersion } from '../utils/npm-version'; -import { cliExitWithError } from '../utils/cli-error'; const log = new Logger('update'); @@ -76,7 +76,7 @@ export default async ( } catch (error: any) { await cliExitWithError( error instanceof Error ? error.message : String(error), - { package: pkgName, registry } + { context: { package: pkgName, registry } } ); } diff --git a/pgpm/cli/src/commands/verify.ts b/pgpm/cli/src/commands/verify.ts index 767a83351..bec917eb4 100644 --- a/pgpm/cli/src/commands/verify.ts +++ b/pgpm/cli/src/commands/verify.ts @@ -1,3 +1,4 @@ +import { cliExitWithError } from '@inquirerer/utils'; import { PgpmPackage } from '@pgpmjs/core'; import { getEnvOptions } from '@pgpmjs/env'; import { Logger } from '@pgpmjs/logger'; @@ -5,7 +6,6 @@ import { CLIOptions, Inquirerer, Question } from 'inquirerer'; import { getPgEnvOptions } from 'pg-env'; import { getTargetDatabase, resolvePackageAlias } from '../utils'; -import { cliExitWithError } from '../utils/cli-error'; import { selectDeployedChange, selectDeployedPackage } from '../utils/deployed-changes'; const log = new Logger('verify'); diff --git a/pgpm/cli/src/utils/argv.ts b/pgpm/cli/src/utils/argv.ts deleted file mode 100644 index 2a2fb40a2..000000000 --- a/pgpm/cli/src/utils/argv.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ParsedArgs } from 'minimist'; - -export const extractFirst = (argv: Partial) => { - const first = argv._?.[0]; - const newArgv = { - ...argv, - _: argv._?.slice(1) ?? [] - }; - return { first, newArgv }; -}; diff --git a/pgpm/cli/src/utils/cli-error.ts b/pgpm/cli/src/utils/cli-error.ts deleted file mode 100644 index f38e8e9fb..000000000 --- a/pgpm/cli/src/utils/cli-error.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Logger } from '@pgpmjs/logger'; -import { PgpmError } from '@pgpmjs/types'; -import { teardownPgPools } from 'pg-cache'; - -const log = new Logger('cli'); - -/** - * CLI error utility that logs error information and exits with code 1. - * Provides consistent error handling and user experience across all CLI commands. - * - * IMPORTANT: This function properly cleans up PostgreSQL connections before exiting. - */ -export const cliExitWithError = async ( - error: PgpmError | Error | string, - context?: Record -): Promise => { - if (error instanceof PgpmError) { - // For PgpmError instances, use structured logging - log.error(`Error: ${error.message}`); - - // Log additional context if available - if (error.context && Object.keys(error.context).length > 0) { - log.debug('Error context:', error.context); - } - - // Log any additional context provided - if (context) { - log.debug('Additional context:', context); - } - } else if (error instanceof Error) { - // For generic Error instances - log.error(`Error: ${error.message}`); - if (context) { - log.debug('Context:', context); - } - } else if (typeof error === 'string') { - // For simple string messages - log.error(`Error: ${error}`); - if (context) { - log.debug('Context:', context); - } - } - - // Perform cleanup before exiting - try { - await teardownPgPools(); - log.debug('Database connections cleaned up'); - } catch (cleanupError) { - log.warn('Failed to cleanup database connections:', cleanupError); - // Don't let cleanup errors prevent the exit - } - - process.exit(1); -}; \ No newline at end of file diff --git a/pgpm/cli/src/utils/index.ts b/pgpm/cli/src/utils/index.ts index fb956c353..2f4699fd3 100644 --- a/pgpm/cli/src/utils/index.ts +++ b/pgpm/cli/src/utils/index.ts @@ -1,10 +1,6 @@ -export * from './argv'; export * from './database'; export * from './display'; -export * from './cli-error'; export * from './deployed-changes'; export * from './module-utils'; export * from './npm-version'; export * from './package-alias'; -export * from './update-check'; -export * from './update-config'; diff --git a/pgpm/cli/src/utils/update-check.ts b/pgpm/cli/src/utils/update-check.ts deleted file mode 100644 index a9f05e751..000000000 --- a/pgpm/cli/src/utils/update-check.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { findAndRequirePackageJson } from 'find-and-require-package-json'; -import { Logger } from '@pgpmjs/logger'; -import { - UpdateCheckConfig, - UPDATE_CHECK_APPSTASH_KEY, - UPDATE_CHECK_TTL_MS, - UPDATE_PACKAGE_NAME -} from '@pgpmjs/types'; -import { compareVersions, fetchLatestVersion } from './npm-version'; -import { readUpdateConfig, shouldCheck, writeUpdateConfig, UpdateConfigOptions } from './update-config'; - -export interface CheckForUpdatesOptions extends UpdateConfigOptions { - pkgName?: string; - pkgVersion?: string; - command?: string; - now?: number; - updateCommand?: string; -} - -const log = new Logger('update-check'); - -const shouldSkip = (command?: string): boolean => { - if (process.env.PGPM_SKIP_UPDATE_CHECK) return true; - if (process.env.CI === 'true') return true; - if (command === 'update') return true; - return false; -}; - -export async function checkForUpdates(options: CheckForUpdatesOptions = {}): Promise { - const { - pkgName = UPDATE_PACKAGE_NAME, - pkgVersion = findAndRequirePackageJson(__dirname).version, - command, - now = Date.now(), - key = UPDATE_CHECK_APPSTASH_KEY, - toolName, - baseDir - } = options; - - if (shouldSkip(command)) { - return null; - } - - try { - const existing = await readUpdateConfig({ toolName, baseDir, key }); - let latestKnownVersion = existing?.latestKnownVersion ?? pkgVersion; - - const needsCheck = shouldCheck(now, existing?.lastCheckedAt, UPDATE_CHECK_TTL_MS); - - if (needsCheck) { - const fetched = await fetchLatestVersion(pkgName); - if (fetched) { - latestKnownVersion = fetched; - } - - await writeUpdateConfig( - { - lastCheckedAt: now, - latestKnownVersion - }, - { toolName, baseDir, key } - ); - } - - const comparison = compareVersions(pkgVersion, latestKnownVersion); - const isOutdated = comparison < 0; - - if (isOutdated) { - const defaultUpdateCommand = - pkgName === UPDATE_PACKAGE_NAME - ? 'Run pgpm update to upgrade.' - : `Run npm i -g ${pkgName}@latest to upgrade.`; - const updateInstruction = options.updateCommand ?? defaultUpdateCommand; - - log.warn( - `A new version of ${pkgName} is available (current ${pkgVersion}, latest ${latestKnownVersion}). ${updateInstruction}` - ); - - await writeUpdateConfig( - { - lastCheckedAt: now, - latestKnownVersion - }, - { toolName, baseDir, key } - ); - } - - return { - lastCheckedAt: now, - latestKnownVersion - }; - } catch (error) { - log.debug('Update check skipped due to error:', error); - return null; - } -} diff --git a/pgpm/cli/src/utils/update-config.ts b/pgpm/cli/src/utils/update-config.ts deleted file mode 100644 index affa22779..000000000 --- a/pgpm/cli/src/utils/update-config.ts +++ /dev/null @@ -1,63 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { appstash, resolve as resolveAppstash } from 'appstash'; -import { UpdateCheckConfig, UPDATE_CHECK_APPSTASH_KEY } from '@pgpmjs/types'; - -export interface UpdateConfigOptions { - toolName?: string; - baseDir?: string; - key?: string; -} - -const defaultToolName = 'pgpm'; - -const getConfigPath = (options: UpdateConfigOptions = {}) => { - const toolName = options.toolName ?? defaultToolName; - const dirs = appstash(toolName, { - ensure: true, - baseDir: options.baseDir - }); - - const configDir = resolveAppstash(dirs, 'config'); - if (!fs.existsSync(configDir)) { - fs.mkdirSync(configDir, { recursive: true }); - } - - const fileName = `${(options.key ?? UPDATE_CHECK_APPSTASH_KEY).replace(/[^a-z0-9-_]/gi, '_')}.json`; - return path.join(configDir, fileName); -}; - -export const shouldCheck = (now: number, lastCheckedAt: number | undefined, ttlMs: number): boolean => { - if (!lastCheckedAt) return true; - return now - lastCheckedAt > ttlMs; -}; - -export async function readUpdateConfig(options: UpdateConfigOptions = {}): Promise { - const configPath = getConfigPath(options); - - if (!fs.existsSync(configPath)) { - return null; - } - - try { - const contents = await fs.promises.readFile(configPath, 'utf8'); - const parsed = JSON.parse(contents) as UpdateCheckConfig; - return parsed; - } catch { - // Corrupted config – clear it - try { - await fs.promises.rm(configPath, { force: true }); - } catch { - // ignore - } - return null; - } -} - -export async function writeUpdateConfig(config: UpdateCheckConfig, options: UpdateConfigOptions = {}): Promise { - const configPath = getConfigPath(options); - await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8'); -} - -// Exposed for testing to locate the config path for a given namespace/baseDir -export const resolveUpdateConfigPath = (options: UpdateConfigOptions = {}) => getConfigPath(options); diff --git a/pgpm/core/package.json b/pgpm/core/package.json index b9a60946a..3e1eae841 100644 --- a/pgpm/core/package.json +++ b/pgpm/core/package.json @@ -44,7 +44,7 @@ "@pgsql/types": "^17.6.2", "@types/pg": "^8.16.0", "copyfiles": "^2.4.1", - "inquirerer": "^4.1.0", + "inquirerer": "^4.1.1", "makage": "^0.1.9" }, "dependencies": { @@ -52,8 +52,8 @@ "@pgpmjs/logger": "workspace:^", "@pgpmjs/server-utils": "workspace:^", "@pgpmjs/types": "workspace:^", - "genomic": "^5.0.1", "csv-to-pg": "workspace:^", + "genomic": "^5.0.2", "glob": "^13.0.0", "komoji": "^0.7.11", "parse-package-name": "^1.0.0", diff --git a/pgpm/core/src/core/template-scaffold.ts b/pgpm/core/src/core/template-scaffold.ts index 665ca540b..f9ce38e4b 100644 --- a/pgpm/core/src/core/template-scaffold.ts +++ b/pgpm/core/src/core/template-scaffold.ts @@ -74,7 +74,7 @@ export interface ScaffoldTemplateResult { export const DEFAULT_TEMPLATE_REPO = 'https://github.com/constructive-io/pgpm-boilerplates.git'; -export const DEFAULT_TEMPLATE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 1 week +export const DEFAULT_TEMPLATE_TTL_MS = 1 * 24 * 60 * 60 * 1000; // 1 day export const DEFAULT_TEMPLATE_TOOL_NAME = 'pgpm'; function resolveCacheBaseDir(cacheBaseDir?: string): string | undefined { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b63cd594..88b6ad85b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1224,6 +1224,9 @@ importers: '@constructive-io/graphql-server': specifier: workspace:^ version: link:../../graphql/server/dist + '@inquirerer/utils': + specifier: ^3.1.1 + version: 3.1.1 '@pgpmjs/core': specifier: workspace:^ version: link:../../pgpm/core/dist @@ -1240,8 +1243,8 @@ importers: specifier: ^0.8.2 version: 0.8.3 inquirerer: - specifier: ^4.1.0 - version: 4.1.0 + specifier: ^4.1.1 + version: 4.1.1 js-yaml: specifier: ^4.1.0 version: 4.1.1 @@ -1411,6 +1414,9 @@ importers: pgpm/cli: dependencies: + '@inquirerer/utils': + specifier: ^3.1.1 + version: 3.1.1 '@pgpmjs/core': specifier: workspace:^ version: link:../core/dist @@ -1430,11 +1436,11 @@ importers: specifier: ^0.8.2 version: 0.8.3 genomic: - specifier: ^5.0.1 - version: 5.0.1 + specifier: ^5.0.2 + version: 5.0.2 inquirerer: - specifier: ^4.1.0 - version: 4.1.0 + specifier: ^4.1.1 + version: 4.1.1 js-yaml: specifier: ^4.1.0 version: 4.1.1 @@ -1510,8 +1516,8 @@ importers: specifier: workspace:^ version: link:../../packages/csv-to-pg/dist genomic: - specifier: ^5.0.1 - version: 5.0.1 + specifier: ^5.0.2 + version: 5.0.2 glob: specifier: ^13.0.0 version: 13.0.0 @@ -1550,8 +1556,8 @@ importers: specifier: ^2.4.1 version: 2.4.1 inquirerer: - specifier: ^4.1.0 - version: 4.1.0 + specifier: ^4.1.1 + version: 4.1.1 makage: specifier: ^0.1.9 version: 0.1.9 @@ -2757,6 +2763,9 @@ packages: '@types/node': optional: true + '@inquirerer/utils@3.1.1': + resolution: {integrity: sha512-8slhzP+uuBv1Wr67bxUm6zG5IwFRpeeR+i34vvIMVeyW2pvBzXjYMjMYjcfBCS6HHqb2x9Dhq5Px0bwhcO44MQ==} + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -3914,6 +3923,9 @@ packages: appstash@0.2.6: resolution: {integrity: sha512-93YKWpKwIaGDZeLB1dVLX63V92EINorNgGIUUm6+U3zib+QYwis7NqwqNcqt4948Nz4aLmFkme820kxMO3grtw==} + appstash@0.2.7: + resolution: {integrity: sha512-EdJDs164q4OuKOBo/mdN6srwJdCy5e2NxmPKUyTBI8Z6aEAkX3ViSRFoAA78A4P0azysSMk8wqafsuM0R6weww==} + aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} @@ -5156,6 +5168,9 @@ packages: find-and-require-package-json@0.8.3: resolution: {integrity: sha512-8FVl3RyEnn8FEdfrm24abOjSQRSRqVEgDsIuveRG+YHFka3jUaKzRbKDw7M4Ox/z7ImcxaFmRxDeeXnqNheeLg==} + find-and-require-package-json@0.8.4: + resolution: {integrity: sha512-08+BvFbBNU1pvxSMzZnZ01bRGxRyOUAbQG/ttfFrt6G5cFyitmZbtPVKPPz75L/3etyccRq/xxQ1GXUdYB+BkQ==} + find-up@2.1.0: resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} engines: {node: '>=4'} @@ -5256,8 +5271,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - genomic@5.0.1: - resolution: {integrity: sha512-4XwbYz5NRsdm79MsAcp7ikw2r22jq2pp8Gp1RmnHj61pcRl6D4QI6WxoLfn56kzbxp349/2+DmR0Bf7q9Wxy3A==} + genomic@5.0.2: + resolution: {integrity: sha512-/cW9JC/akIXGB7Y1qTZWpiFfFINaRZMApZh3eMXhjuLu4VLVkdUO5+TIZWLx+slvHzFXRkwB+dPkIp8QgOnGfA==} gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -5639,6 +5654,9 @@ packages: inquirerer@4.1.0: resolution: {integrity: sha512-XCrUg5A/nagVyBpkpMb5OowbBDmq7rKTwPV0xEw5Zsauu9rsFUOHFLAST+vpOss8ST1uH3gZK2uqAcX5wm6QcA==} + inquirerer@4.1.1: + resolution: {integrity: sha512-OC8UJ7GRxVEFvrT7G5IDIBttjMrqREuax2J2/1ZdmbpJJIaxgydjHYsSmjoyg21XMv+0IU9DEsvBOyJqV1e9dQ==} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -8138,6 +8156,9 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yanse@0.1.10: + resolution: {integrity: sha512-sSfkypRMWbsBzUcP3oe9xIhyTBqQRv7XvjICzSkciEPP4xmw/8i0slDqlvhVBz9WsSB40aaJvkJunaddl9viZA==} + yanse@0.1.9: resolution: {integrity: sha512-AKKZLY+TWENQOYEuQei07b4QFW4hCS6yyAbUiWBCjHOpkhzWUD8EehNAeI/8kwm5pvJQcmX2ojujPywF+DCheA==} @@ -9419,6 +9440,12 @@ snapshots: optionalDependencies: '@types/node': 20.19.27 + '@inquirerer/utils@3.1.1': + dependencies: + appstash: 0.2.7 + find-and-require-package-json: 0.8.4 + minimist: 1.2.8 + '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -11033,6 +11060,8 @@ snapshots: appstash@0.2.6: {} + appstash@0.2.7: {} + aproba@2.0.0: {} arg@4.1.3: {} @@ -12368,6 +12397,8 @@ snapshots: find-and-require-package-json@0.8.3: {} + find-and-require-package-json@0.8.4: {} + find-up@2.1.0: dependencies: locate-path: 2.0.0 @@ -12463,10 +12494,10 @@ snapshots: function-bind@1.1.2: {} - genomic@5.0.1: + genomic@5.0.2: dependencies: - appstash: 0.2.6 - inquirerer: 4.1.0 + appstash: 0.2.7 + inquirerer: 4.1.1 gensync@1.0.0-beta.2: {} @@ -12943,6 +12974,14 @@ snapshots: minimist: 1.2.8 yanse: 0.1.9 + inquirerer@4.1.1: + dependencies: + deepmerge: 4.3.1 + find-and-require-package-json: 0.8.4 + js-yaml: 4.1.1 + minimist: 1.2.8 + yanse: 0.1.10 + invariant@2.2.4: dependencies: loose-envify: 1.4.0 @@ -16059,6 +16098,8 @@ snapshots: yaml@2.8.2: {} + yanse@0.1.10: {} + yanse@0.1.9: {} yargs-parser@18.1.3: