pgpm 0.2.17 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/utils/index.js CHANGED
@@ -20,3 +20,6 @@ __exportStar(require("./display"), exports);
20
20
  __exportStar(require("./cli-error"), exports);
21
21
  __exportStar(require("./deployed-changes"), exports);
22
22
  __exportStar(require("./module-utils"), exports);
23
+ __exportStar(require("./npm-version"), exports);
24
+ __exportStar(require("./update-check"), exports);
25
+ __exportStar(require("./update-config"), exports);
@@ -0,0 +1,2 @@
1
+ export declare function fetchLatestVersion(pkgName: string, timeoutMs?: number): Promise<string | null>;
2
+ export declare const compareVersions: (current: string, latest: string) => number;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.compareVersions = void 0;
7
+ exports.fetchLatestVersion = fetchLatestVersion;
8
+ const child_process_1 = require("child_process");
9
+ const semver_1 = __importDefault(require("semver"));
10
+ const util_1 = require("util");
11
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
12
+ async function fetchLatestVersion(pkgName, timeoutMs = 5000) {
13
+ try {
14
+ const result = await execFileAsync('npm', ['view', pkgName, 'version', '--json'], {
15
+ timeout: timeoutMs,
16
+ windowsHide: true,
17
+ maxBuffer: 1024 * 1024
18
+ });
19
+ const stdout = typeof result === 'string'
20
+ ? result
21
+ : result?.stdout ?? '';
22
+ const parsed = parseVersionOutput(stdout);
23
+ return parsed ?? null;
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ const parseVersionOutput = (raw) => {
30
+ const trimmed = raw.trim();
31
+ if (!trimmed.length)
32
+ return null;
33
+ try {
34
+ const parsed = JSON.parse(trimmed);
35
+ if (typeof parsed === 'string')
36
+ return parsed;
37
+ if (Array.isArray(parsed) && parsed.length) {
38
+ return String(parsed[parsed.length - 1]);
39
+ }
40
+ }
41
+ catch {
42
+ // fall through to plain string parse
43
+ }
44
+ return trimmed || null;
45
+ };
46
+ const compareVersions = (current, latest) => {
47
+ const currentSemver = semver_1.default.coerce(current);
48
+ const latestSemver = semver_1.default.coerce(latest);
49
+ if (currentSemver && latestSemver) {
50
+ return semver_1.default.compare(currentSemver, latestSemver);
51
+ }
52
+ return current.localeCompare(latest);
53
+ };
54
+ exports.compareVersions = compareVersions;
@@ -0,0 +1,10 @@
1
+ import { UpdateCheckConfig } from '@launchql/types';
2
+ import { UpdateConfigOptions } from './update-config';
3
+ export interface CheckForUpdatesOptions extends UpdateConfigOptions {
4
+ pkgName?: string;
5
+ pkgVersion?: string;
6
+ command?: string;
7
+ now?: number;
8
+ updateCommand?: string;
9
+ }
10
+ export declare function checkForUpdates(options?: CheckForUpdatesOptions): Promise<UpdateCheckConfig | null>;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkForUpdates = checkForUpdates;
4
+ const logger_1 = require("@launchql/logger");
5
+ const types_1 = require("@launchql/types");
6
+ const package_1 = require("../package");
7
+ const npm_version_1 = require("./npm-version");
8
+ const update_config_1 = require("./update-config");
9
+ const log = new logger_1.Logger('update-check');
10
+ const shouldSkip = (command) => {
11
+ if (process.env.PGPM_SKIP_UPDATE_CHECK)
12
+ return true;
13
+ if (process.env.CI === 'true')
14
+ return true;
15
+ if (command === 'update')
16
+ return true;
17
+ return false;
18
+ };
19
+ async function checkForUpdates(options = {}) {
20
+ const { pkgName = types_1.UPDATE_PACKAGE_NAME, pkgVersion = (0, package_1.readAndParsePackageJson)().version, command, now = Date.now(), key = types_1.UPDATE_CHECK_APPSTASH_KEY, toolName, baseDir } = options;
21
+ if (shouldSkip(command)) {
22
+ return null;
23
+ }
24
+ try {
25
+ const existing = await (0, update_config_1.readUpdateConfig)({ toolName, baseDir, key });
26
+ let latestKnownVersion = existing?.latestKnownVersion ?? pkgVersion;
27
+ const needsCheck = (0, update_config_1.shouldCheck)(now, existing?.lastCheckedAt, types_1.UPDATE_CHECK_TTL_MS);
28
+ if (needsCheck) {
29
+ const fetched = await (0, npm_version_1.fetchLatestVersion)(pkgName);
30
+ if (fetched) {
31
+ latestKnownVersion = fetched;
32
+ }
33
+ await (0, update_config_1.writeUpdateConfig)({
34
+ lastCheckedAt: now,
35
+ latestKnownVersion
36
+ }, { toolName, baseDir, key });
37
+ }
38
+ const comparison = (0, npm_version_1.compareVersions)(pkgVersion, latestKnownVersion);
39
+ const isOutdated = comparison < 0;
40
+ if (isOutdated) {
41
+ const defaultUpdateCommand = pkgName === types_1.UPDATE_PACKAGE_NAME
42
+ ? 'Run pgpm update to upgrade.'
43
+ : `Run npm i -g ${pkgName}@latest to upgrade.`;
44
+ const updateInstruction = options.updateCommand ?? defaultUpdateCommand;
45
+ log.warn(`A new version of ${pkgName} is available (current ${pkgVersion}, latest ${latestKnownVersion}). ${updateInstruction}`);
46
+ await (0, update_config_1.writeUpdateConfig)({
47
+ lastCheckedAt: now,
48
+ latestKnownVersion
49
+ }, { toolName, baseDir, key });
50
+ }
51
+ return {
52
+ lastCheckedAt: now,
53
+ latestKnownVersion
54
+ };
55
+ }
56
+ catch (error) {
57
+ log.debug('Update check skipped due to error:', error);
58
+ return null;
59
+ }
60
+ }
@@ -0,0 +1,10 @@
1
+ import { UpdateCheckConfig } from '@launchql/types';
2
+ export interface UpdateConfigOptions {
3
+ toolName?: string;
4
+ baseDir?: string;
5
+ key?: string;
6
+ }
7
+ export declare const shouldCheck: (now: number, lastCheckedAt: number | undefined, ttlMs: number) => boolean;
8
+ export declare function readUpdateConfig(options?: UpdateConfigOptions): Promise<UpdateCheckConfig | null>;
9
+ export declare function writeUpdateConfig(config: UpdateCheckConfig, options?: UpdateConfigOptions): Promise<void>;
10
+ export declare const resolveUpdateConfigPath: (options?: UpdateConfigOptions) => string;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveUpdateConfigPath = exports.shouldCheck = void 0;
7
+ exports.readUpdateConfig = readUpdateConfig;
8
+ exports.writeUpdateConfig = writeUpdateConfig;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const appstash_1 = require("appstash");
12
+ const types_1 = require("@launchql/types");
13
+ const defaultToolName = 'pgpm';
14
+ const getConfigPath = (options = {}) => {
15
+ const toolName = options.toolName ?? defaultToolName;
16
+ const dirs = (0, appstash_1.appstash)(toolName, {
17
+ ensure: true,
18
+ baseDir: options.baseDir
19
+ });
20
+ const configDir = (0, appstash_1.resolve)(dirs, 'config');
21
+ if (!fs_1.default.existsSync(configDir)) {
22
+ fs_1.default.mkdirSync(configDir, { recursive: true });
23
+ }
24
+ const fileName = `${(options.key ?? types_1.UPDATE_CHECK_APPSTASH_KEY).replace(/[^a-z0-9-_]/gi, '_')}.json`;
25
+ return path_1.default.join(configDir, fileName);
26
+ };
27
+ const shouldCheck = (now, lastCheckedAt, ttlMs) => {
28
+ if (!lastCheckedAt)
29
+ return true;
30
+ return now - lastCheckedAt > ttlMs;
31
+ };
32
+ exports.shouldCheck = shouldCheck;
33
+ async function readUpdateConfig(options = {}) {
34
+ const configPath = getConfigPath(options);
35
+ if (!fs_1.default.existsSync(configPath)) {
36
+ return null;
37
+ }
38
+ try {
39
+ const contents = await fs_1.default.promises.readFile(configPath, 'utf8');
40
+ const parsed = JSON.parse(contents);
41
+ return parsed;
42
+ }
43
+ catch {
44
+ // Corrupted config – clear it
45
+ try {
46
+ await fs_1.default.promises.rm(configPath, { force: true });
47
+ }
48
+ catch {
49
+ // ignore
50
+ }
51
+ return null;
52
+ }
53
+ }
54
+ async function writeUpdateConfig(config, options = {}) {
55
+ const configPath = getConfigPath(options);
56
+ await fs_1.default.promises.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
57
+ }
58
+ // Exposed for testing to locate the config path for a given namespace/baseDir
59
+ const resolveUpdateConfigPath = (options = {}) => getConfigPath(options);
60
+ exports.resolveUpdateConfigPath = resolveUpdateConfigPath;