Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
env: {}
- package: pgpm/env
env: {}
- package: pgpm/cli
env: {}
- package: packages/cli
env: {}
- package: packages/client
Expand Down
139 changes: 139 additions & 0 deletions packages/cli/__tests__/upgrade-modules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
jest.setTimeout(60000);
process.env.PGPM_SKIP_UPDATE_CHECK = 'true';

import { PgpmPackage } from '@pgpmjs/core';
import * as fs from 'fs';
import * as path from 'path';

import { TestFixture } from '../test-utils';

describe('cmds:upgrade-modules - with initialized workspace and module', () => {
let fixture: TestFixture;
let workspaceDir: string;
let moduleDir: string;

beforeEach(async () => {
fixture = new TestFixture();

const workspaceName = 'my-workspace';
const moduleName = 'my-module';
workspaceDir = path.join(fixture.tempDir, workspaceName);
moduleDir = path.join(workspaceDir, 'packages', moduleName);

// Step 1: Create workspace
await fixture.runCmd({
_: ['init', 'workspace'],
cwd: fixture.tempDir,
name: workspaceName,
workspace: true,
});

// Step 2: Add module
await fixture.runCmd({
_: ['init'],
cwd: workspaceDir,
name: moduleName,
moduleName: moduleName,
extensions: ['uuid-ossp', 'plpgsql'],
});
});

afterEach(() => {
fixture.cleanup();
});

describe('when no modules are installed', () => {
it('reports no modules installed', async () => {
await fixture.runCmd({
_: ['upgrade-modules'],
cwd: moduleDir,
});

// Should complete without error
const mod = new PgpmPackage(moduleDir);
const result = mod.getInstalledModules();
expect(result.installed).toEqual([]);
});
});

describe('when modules are installed at latest version', () => {
beforeEach(async () => {
await fixture.runCmd({
_: ['install', '@pgpm-testing/[email protected]'],
cwd: moduleDir,
});
});

it('reports modules are up to date', async () => {
await fixture.runCmd({
_: ['upgrade-modules'],
cwd: moduleDir,
all: true,
});

const pkgJson = JSON.parse(
fs.readFileSync(path.join(moduleDir, 'package.json'), 'utf-8')
);
expect(pkgJson.dependencies['@pgpm-testing/base32']).toBe('1.2.0');
});
});

describe('when modules need upgrading (1.1.0 -> 1.2.0)', () => {
beforeEach(async () => {
await fixture.runCmd({
_: ['install', '@pgpm-testing/[email protected]'],
cwd: moduleDir,
});
});

it('dry run does not modify package.json', async () => {
await fixture.runCmd({
_: ['upgrade-modules'],
cwd: moduleDir,
'dry-run': true,
});

const pkgJson = JSON.parse(
fs.readFileSync(path.join(moduleDir, 'package.json'), 'utf-8')
);
expect(pkgJson.dependencies['@pgpm-testing/base32']).toBe('1.1.0');
});

it('--all flag upgrades from 1.1.0 to 1.2.0', async () => {
let pkgJson = JSON.parse(
fs.readFileSync(path.join(moduleDir, 'package.json'), 'utf-8')
);
expect(pkgJson.dependencies['@pgpm-testing/base32']).toBe('1.1.0');

await fixture.runCmd({
_: ['upgrade-modules'],
cwd: moduleDir,
all: true,
});

pkgJson = JSON.parse(
fs.readFileSync(path.join(moduleDir, 'package.json'), 'utf-8')
);
expect(pkgJson.dependencies['@pgpm-testing/base32']).toBe('1.2.0');
});

it('--modules flag filters to specific modules', async () => {
let pkgJson = JSON.parse(
fs.readFileSync(path.join(moduleDir, 'package.json'), 'utf-8')
);
expect(pkgJson.dependencies['@pgpm-testing/base32']).toBe('1.1.0');

await fixture.runCmd({
_: ['upgrade-modules'],
cwd: moduleDir,
modules: '@pgpm-testing/base32',
all: true,
});

pkgJson = JSON.parse(
fs.readFileSync(path.join(moduleDir, 'package.json'), 'utf-8')
);
expect(pkgJson.dependencies['@pgpm-testing/base32']).toBe('1.2.0');
});
});
});
File renamed without changes.
26 changes: 26 additions & 0 deletions pgpm/pgpm/README.md → pgpm/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Here are some useful commands for reference:
### Module Management

- `pgpm install` - Install database modules as dependencies
- `pgpm upgrade-modules` - Upgrade installed modules to latest versions
- `pgpm extension` - Interactively manage module dependencies
- `pgpm tag` - Version your changes with tags

Expand Down Expand Up @@ -239,6 +240,31 @@ pgpm install @pgpm/base32
pgpm install @pgpm/base32 @pgpm/faker
```

#### `pgpm upgrade-modules`

Upgrade installed pgpm modules to their latest versions from npm.

```bash
# Interactive selection of modules to upgrade
pgpm upgrade-modules

# Upgrade all installed modules without prompting
pgpm upgrade-modules --all

# Preview available upgrades without making changes
pgpm upgrade-modules --dry-run

# Upgrade specific modules
pgpm upgrade-modules --modules @pgpm/base32,@pgpm/faker
```

**Options:**

- `--all` - Upgrade all modules without prompting
- `--dry-run` - Show what would be upgraded without making changes
- `--modules <names>` - Comma-separated list of specific modules to upgrade
- `--cwd <directory>` - Working directory (default: current directory)

#### `pgpm extension`

Interactively manage module dependencies.
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions pgpm/pgpm/src/commands.ts → pgpm/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import migrate from './commands/migrate';
import _package from './commands/package';
import plan from './commands/plan';
import updateCmd from './commands/update';
import upgradeModules from './commands/upgrade-modules';
import remove from './commands/remove';
import renameCmd from './commands/rename';
import revert from './commands/revert';
Expand Down Expand Up @@ -64,6 +65,7 @@ export const createPgpmCommandMap = (skipPgTeardown: boolean = false): Record<st
analyze: pgt(analyze),
rename: pgt(renameCmd),
'test-packages': pgt(testPackages),
'upgrade-modules': pgt(upgradeModules),
cache,
update: updateCmd
};
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
159 changes: 159 additions & 0 deletions pgpm/cli/src/commands/upgrade-modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { PgpmPackage } from '@pgpmjs/core';
import { Logger } from '@pgpmjs/logger';
import { CLIOptions, Inquirerer, OptionValue, Question } from 'inquirerer';
import { ParsedArgs } from 'minimist';
import { fetchLatestVersion } from '../utils/npm-version';

const log = new Logger('upgrade-modules');

const upgradeModulesUsageText = `
Upgrade Modules Command:

pgpm upgrade-modules [OPTIONS]

Upgrade installed pgpm modules to their latest versions from npm.

Options:
--help, -h Show this help message
--cwd <directory> Working directory (default: current directory)
--all Upgrade all modules without prompting
--dry-run Show what would be upgraded without making changes
--modules <names> Comma-separated list of specific modules to upgrade

Examples:
pgpm upgrade-modules Interactive selection of modules to upgrade
pgpm upgrade-modules --all Upgrade all installed modules
pgpm upgrade-modules --dry-run Preview available upgrades
pgpm upgrade-modules --modules @pgpm/base32,@pgpm/faker Upgrade specific modules
`;

interface ModuleUpdateInfo {
name: string;
currentVersion: string;
latestVersion: string | null;
hasUpdate: boolean;
}

async function fetchModuleVersions(
installedVersions: Record<string, string>
): Promise<ModuleUpdateInfo[]> {
const moduleNames = Object.keys(installedVersions);
const results: ModuleUpdateInfo[] = [];

for (const name of moduleNames) {
const currentVersion = installedVersions[name];
const latestVersion = await fetchLatestVersion(name);

results.push({
name,
currentVersion,
latestVersion,
hasUpdate: latestVersion !== null && latestVersion !== currentVersion
});
}

return results;
}

export default async (
argv: Partial<ParsedArgs>,
prompter: Inquirerer,
_options: CLIOptions
) => {
if (argv.help || argv.h) {
console.log(upgradeModulesUsageText);
process.exit(0);
}

const { cwd = process.cwd() } = argv;
const dryRun = Boolean(argv['dry-run']);
const upgradeAll = Boolean(argv.all);
const specificModules = argv.modules
? String(argv.modules).split(',').map(m => m.trim())
: undefined;

const project = new PgpmPackage(cwd);

if (!project.isInModule()) {
throw new Error('You must run this command inside a PGPM module.');
}

const { installed, installedVersions } = project.getInstalledModules();

if (installed.length === 0) {
log.info('No pgpm modules are installed in this module.');
return;
}

log.info(`Found ${installed.length} installed module(s). Checking for updates...`);

const moduleVersions = await fetchModuleVersions(installedVersions);
const modulesWithUpdates = moduleVersions.filter(m => m.hasUpdate);

if (modulesWithUpdates.length === 0) {
log.success('All modules are already up to date.');
return;
}

log.info(`\n${modulesWithUpdates.length} module(s) have updates available:\n`);
for (const mod of modulesWithUpdates) {
log.info(` ${mod.name}: ${mod.currentVersion} -> ${mod.latestVersion}`);
}
console.log('');

if (dryRun) {
log.info('Dry run - no changes made.');
return;
}

let modulesToUpgrade: string[];

if (upgradeAll) {
modulesToUpgrade = modulesWithUpdates.map(m => m.name);
} else if (specificModules) {
modulesToUpgrade = modulesWithUpdates
.filter(m => specificModules.includes(m.name))
.map(m => m.name);

if (modulesToUpgrade.length === 0) {
log.warn('None of the specified modules have updates available.');
return;
}
} else {
const options = modulesWithUpdates.map(mod => ({
name: mod.name,
value: mod.name,
message: `${mod.name} (${mod.currentVersion} -> ${mod.latestVersion})`
}));

const questions: Question[] = [
{
name: 'selectedModules',
message: 'Select modules to upgrade:',
type: 'checkbox',
options: options.map(o => o.message),
default: options.map(o => o.message)
}
];

const answers = await prompter.prompt(argv, questions);
const selectedOptions = (answers.selectedModules as OptionValue[])
.filter(opt => opt.selected)
.map(opt => opt.name);

modulesToUpgrade = modulesWithUpdates
.filter(mod => selectedOptions.includes(`${mod.name} (${mod.currentVersion} -> ${mod.latestVersion})`))
.map(m => m.name);

if (modulesToUpgrade.length === 0) {
log.info('No modules selected for upgrade.');
return;
}
}

log.info(`\nUpgrading ${modulesToUpgrade.length} module(s)...`);

await project.upgradeModules({ modules: modulesToUpgrade });

log.success('\nUpgrade complete!');
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ exports[`installModule() installs a package and updates package.json dependencie
"deploy/schemas/base32/procedures/encode.sql",
"deploy/schemas/base32/schema.sql",
"jest.config.js",
"launchql-base32.control",
"package.json",
"pgpm-base32.control",
"pgpm.plan",
"revert/schemas/base32/procedures/decode.sql",
"revert/schemas/base32/procedures/encode.sql",
"revert/schemas/base32/schema.sql",
"sql/launchql-base32--0.4.6.sql",
"sql/pgpm-base32--0.15.2.sql",
"verify/schemas/base32/procedures/decode.sql",
"verify/schemas/base32/procedures/encode.sql",
"verify/schemas/base32/schema.sql",
Expand All @@ -31,7 +31,7 @@ exports[`installModule() installs a package and updates package.json dependencie
comment = 'totp extension'
default_version = '0.0.1'
module_pathname = '$libdir/totp'
requires = 'launchql-base32,pgcrypto,pgpm-verify,plpgsql,uuid-ossp'
requires = 'pgcrypto,pgpm-base32,pgpm-verify,plpgsql,uuid-ossp'
relocatable = false
superuser = false
"
Expand Down
Loading