refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
|
|
|
import { join } from "node:path";
|
|
|
|
|
import os from "node:os";
|
|
|
|
|
import type { UpdateOptions } from "../src/utils/check-updates";
|
|
|
|
|
import { getLatestVersion } from "fast-npm-meta";
|
|
|
|
|
import { getUserAgent } from "package-manager-detector";
|
2025-04-19 08:00:45 +08:00
|
|
|
import {
|
|
|
|
|
checkForUpdates,
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
renderUpdateCommand,
|
|
|
|
|
} from "../src/utils/check-updates";
|
|
|
|
|
import { detectInstallerByPath } from "../src/utils/package-manager-detector";
|
fix: always load version from package.json at runtime (#909)
Note the high-level motivation behind this change is to avoid the need
to make temporary changes in the source tree in order to cut a release
build since that runs the risk of leaving things in an inconsistent
state in the event of a failure. The existing code:
```
import pkg from "../../package.json" assert { type: "json" };
```
did not work as intended because, as written, ESBuild would bake the
contents of the local `package.json` into the release build at build
time whereas we want it to read the contents at runtime so we can use
the `package.json` in the tree to build the code and later inject a
modified version into the release package with a timestamped build
version.
Changes:
* move `CLI_VERSION` out of `src/utils/session.ts` and into
`src/version.ts` so `../package.json` is a correct relative path both
from `src/version.ts` in the source tree and also in the final
`dist/cli.js` build output
* change `assert` to `with` in `import pkg` as apparently `with` became
standard in Node 22
* mark `"../package.json"` as external in `build.mjs` so the version is
not baked into the `.js` at build time
After using `pnpm stage-release` to build a release version, if I use
Node 22.0 to run Codex, I see the following printed to stderr at
startup:
```
(node:71308) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
```
Note it is a warning and does not prevent Codex from running.
In Node 22.12, the warning goes away, but the warning still appears in
Node 22.11. For Node 22, 22.15.0 is the current LTS version, so LTS
users will not see this.
Also, something about moving the definition of `CLI_VERSION` caused a
problem with the mocks in `check-updates.test.ts`. I asked Codex to fix
it, and it came up with the change to the test configs. I don't know
enough about vitest to understand what it did, but the tests seem
healthy again, so I'm going with it.
2025-05-12 21:27:15 -07:00
|
|
|
import { CLI_VERSION } from "../src/version";
|
2025-04-19 08:00:45 +08:00
|
|
|
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
// In-memory FS mock
|
2025-04-19 08:00:45 +08:00
|
|
|
let memfs: Record<string, string> = {};
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
vi.mock("node:fs/promises", async (importOriginal) => {
|
|
|
|
|
return {
|
|
|
|
|
...(await importOriginal()),
|
|
|
|
|
readFile: async (path: string) => {
|
|
|
|
|
if (!(path in memfs)) {
|
|
|
|
|
const err: any = new Error(
|
|
|
|
|
`ENOENT: no such file or directory, open '${path}'`,
|
|
|
|
|
);
|
|
|
|
|
err.code = "ENOENT";
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
return memfs[path];
|
|
|
|
|
},
|
|
|
|
|
writeFile: async (path: string, data: string) => {
|
|
|
|
|
memfs[path] = data;
|
|
|
|
|
},
|
|
|
|
|
rm: async (path: string) => {
|
|
|
|
|
delete memfs[path];
|
|
|
|
|
},
|
|
|
|
|
};
|
2025-04-19 08:00:45 +08:00
|
|
|
});
|
|
|
|
|
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
// Mock package name & CLI version
|
|
|
|
|
const MOCK_PKG = "my-pkg";
|
fix: always load version from package.json at runtime (#909)
Note the high-level motivation behind this change is to avoid the need
to make temporary changes in the source tree in order to cut a release
build since that runs the risk of leaving things in an inconsistent
state in the event of a failure. The existing code:
```
import pkg from "../../package.json" assert { type: "json" };
```
did not work as intended because, as written, ESBuild would bake the
contents of the local `package.json` into the release build at build
time whereas we want it to read the contents at runtime so we can use
the `package.json` in the tree to build the code and later inject a
modified version into the release package with a timestamped build
version.
Changes:
* move `CLI_VERSION` out of `src/utils/session.ts` and into
`src/version.ts` so `../package.json` is a correct relative path both
from `src/version.ts` in the source tree and also in the final
`dist/cli.js` build output
* change `assert` to `with` in `import pkg` as apparently `with` became
standard in Node 22
* mark `"../package.json"` as external in `build.mjs` so the version is
not baked into the `.js` at build time
After using `pnpm stage-release` to build a release version, if I use
Node 22.0 to run Codex, I see the following printed to stderr at
startup:
```
(node:71308) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
```
Note it is a warning and does not prevent Codex from running.
In Node 22.12, the warning goes away, but the warning still appears in
Node 22.11. For Node 22, 22.15.0 is the current LTS version, so LTS
users will not see this.
Also, something about moving the definition of `CLI_VERSION` caused a
problem with the mocks in `check-updates.test.ts`. I asked Codex to fix
it, and it came up with the change to the test configs. I don't know
enough about vitest to understand what it did, but the tests seem
healthy again, so I'm going with it.
2025-05-12 21:27:15 -07:00
|
|
|
vi.mock("../src/version", () => ({ CLI_VERSION: "1.0.0" }));
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
vi.mock("../package.json", () => ({ name: MOCK_PKG }));
|
|
|
|
|
vi.mock("../src/utils/package-manager-detector", async (importOriginal) => {
|
|
|
|
|
return {
|
|
|
|
|
...(await importOriginal()),
|
|
|
|
|
detectInstallerByPath: vi.fn(),
|
|
|
|
|
};
|
|
|
|
|
});
|
2025-04-19 08:00:45 +08:00
|
|
|
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
// Mock external services
|
|
|
|
|
vi.mock("fast-npm-meta", () => ({ getLatestVersion: vi.fn() }));
|
|
|
|
|
vi.mock("package-manager-detector", () => ({ getUserAgent: vi.fn() }));
|
2025-04-19 08:00:45 +08:00
|
|
|
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
describe("renderUpdateCommand()", () => {
|
|
|
|
|
it.each([
|
|
|
|
|
[{ manager: "npm", packageName: MOCK_PKG }, `npm install -g ${MOCK_PKG}`],
|
|
|
|
|
[{ manager: "pnpm", packageName: MOCK_PKG }, `pnpm add -g ${MOCK_PKG}`],
|
|
|
|
|
[{ manager: "bun", packageName: MOCK_PKG }, `bun add -g ${MOCK_PKG}`],
|
|
|
|
|
[{ manager: "yarn", packageName: MOCK_PKG }, `yarn global add ${MOCK_PKG}`],
|
|
|
|
|
[
|
|
|
|
|
{ manager: "deno", packageName: MOCK_PKG },
|
|
|
|
|
`deno install -g npm:${MOCK_PKG}`,
|
|
|
|
|
],
|
|
|
|
|
])("%s → command", async (options, cmd) => {
|
|
|
|
|
expect(renderUpdateCommand(options as UpdateOptions)).toBe(cmd);
|
2025-04-19 08:00:45 +08:00
|
|
|
});
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("checkForUpdates()", () => {
|
|
|
|
|
// Use a stable directory under the OS temp
|
|
|
|
|
const TMP = join(os.tmpdir(), "update-test-memfs");
|
|
|
|
|
const STATE_PATH = join(TMP, "update-check.json");
|
2025-04-19 08:00:45 +08:00
|
|
|
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
beforeEach(async () => {
|
|
|
|
|
memfs = {};
|
|
|
|
|
// Mock CONFIG_DIR to our TMP
|
|
|
|
|
vi.doMock("../src/utils/config", () => ({ CONFIG_DIR: TMP }));
|
2025-04-19 08:00:45 +08:00
|
|
|
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
// Freeze time so the 24h logic is deterministic
|
|
|
|
|
vi.useFakeTimers().setSystemTime(new Date("2025-01-01T00:00:00Z"));
|
|
|
|
|
vi.resetAllMocks();
|
2025-04-19 08:00:45 +08:00
|
|
|
});
|
|
|
|
|
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
afterEach(async () => {
|
|
|
|
|
vi.useRealTimers();
|
2025-04-19 08:00:45 +08:00
|
|
|
});
|
|
|
|
|
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
it("uses global installer when detected, ignoring local agent", async () => {
|
|
|
|
|
// seed old timestamp
|
|
|
|
|
const old = new Date("2000-01-01T00:00:00Z").toUTCString();
|
|
|
|
|
memfs[STATE_PATH] = JSON.stringify({ lastUpdateCheck: old });
|
|
|
|
|
|
|
|
|
|
// simulate registry says update available
|
|
|
|
|
vi.mocked(getLatestVersion).mockResolvedValue({ version: "2.0.0" } as any);
|
|
|
|
|
// local agent would be npm, but global detection wins
|
|
|
|
|
vi.mocked(getUserAgent).mockReturnValue("npm");
|
|
|
|
|
vi.mocked(detectInstallerByPath).mockReturnValue(Promise.resolve("pnpm"));
|
|
|
|
|
|
|
|
|
|
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
|
|
|
|
2025-04-19 08:00:45 +08:00
|
|
|
await checkForUpdates();
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
|
|
|
|
|
// should render using `pnpm` (global) rather than `npm`
|
|
|
|
|
expect(logSpy).toHaveBeenCalledOnce();
|
|
|
|
|
const output = logSpy.mock.calls.at(0)?.at(0);
|
|
|
|
|
expect(output).toContain("pnpm add -g"); // global branch used
|
|
|
|
|
// state updated
|
|
|
|
|
const newState = JSON.parse(memfs[STATE_PATH]!);
|
|
|
|
|
expect(newState.lastUpdateCheck).toBe(new Date().toUTCString());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("skips when lastUpdateCheck is still fresh (<frequency)", async () => {
|
|
|
|
|
// seed a timestamp 12h ago
|
|
|
|
|
const recent = new Date(Date.now() - 1000 * 60 * 60 * 12).toUTCString();
|
|
|
|
|
memfs[STATE_PATH] = JSON.stringify({ lastUpdateCheck: recent });
|
|
|
|
|
|
|
|
|
|
const versionSpy = vi.mocked(getLatestVersion);
|
2025-04-19 08:00:45 +08:00
|
|
|
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
|
2025-04-19 08:00:45 +08:00
|
|
|
await checkForUpdates();
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
|
|
|
|
|
expect(versionSpy).not.toHaveBeenCalled();
|
|
|
|
|
expect(logSpy).not.toHaveBeenCalled();
|
2025-04-19 08:00:45 +08:00
|
|
|
});
|
|
|
|
|
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
it("does not print when up-to-date", async () => {
|
|
|
|
|
vi.mocked(getLatestVersion).mockResolvedValue({
|
|
|
|
|
version: CLI_VERSION,
|
|
|
|
|
} as any);
|
|
|
|
|
vi.mocked(getUserAgent).mockReturnValue("npm");
|
|
|
|
|
vi.mocked(detectInstallerByPath).mockResolvedValue(undefined);
|
|
|
|
|
|
|
|
|
|
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
|
|
|
|
2025-04-19 08:00:45 +08:00
|
|
|
await checkForUpdates();
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
|
|
|
|
|
expect(logSpy).not.toHaveBeenCalled();
|
|
|
|
|
// but state still written
|
|
|
|
|
const state = JSON.parse(memfs[STATE_PATH]!);
|
|
|
|
|
expect(state.lastUpdateCheck).toBe(new Date().toUTCString());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("does not print when no manager detected at all", async () => {
|
|
|
|
|
vi.mocked(getLatestVersion).mockResolvedValue({ version: "2.0.0" } as any);
|
|
|
|
|
vi.mocked(detectInstallerByPath).mockResolvedValue(undefined);
|
|
|
|
|
vi.mocked(getUserAgent).mockReturnValue(null);
|
|
|
|
|
|
2025-04-19 08:00:45 +08:00
|
|
|
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
|
2025-04-19 08:00:45 +08:00
|
|
|
await checkForUpdates();
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
|
2025-04-19 08:00:45 +08:00
|
|
|
expect(logSpy).not.toHaveBeenCalled();
|
refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.
## Summary
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:
1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)
## What’s Changed
- **Registry‑based version check**
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
- Detect global installer first via `detectInstallerByPath()`
- Fallback to local detection via `getUserAgent()`
- **Module cleanup**
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**
1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`
3. Fetch & compare versions
4. Persist new timestamp immediately
5. Render & display styled box only when an update exists
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`
## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.
## Preview
* npm

* bun

## Simple Flow Chart
```mermaid
flowchart TD
A(Start) --> B[Read state]
B --> C{Recent check?}
C -- Yes --> Z[End]
C -- No --> D[Fetch latest version]
D --> E[Save check time]
E --> F{Version data OK?}
F -- No --> Z
F -- Yes --> G{Update available?}
G -- No --> Z
G -- Yes --> H{Global install?}
H -- Yes --> I[Select global manager]
H -- No --> K{Local install?}
K -- No --> Z
K -- Yes --> L[Select local manager]
I & L --> M[Render update message]
M --> N[Format with boxen]
N --> O[Print update]
O --> Z
```
2025-04-21 15:00:20 +08:00
|
|
|
// state still written
|
|
|
|
|
const state = JSON.parse(memfs[STATE_PATH]!);
|
|
|
|
|
expect(state.lastUpdateCheck).toBe(new Date().toUTCString());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("renders a box when a newer version exists and no global installer", async () => {
|
|
|
|
|
// old timestamp
|
|
|
|
|
const old = new Date("2000-01-01T00:00:00Z").toUTCString();
|
|
|
|
|
memfs[STATE_PATH] = JSON.stringify({ lastUpdateCheck: old });
|
|
|
|
|
|
|
|
|
|
vi.mocked(getLatestVersion).mockResolvedValue({ version: "2.0.0" } as any);
|
|
|
|
|
vi.mocked(detectInstallerByPath).mockResolvedValue(undefined);
|
|
|
|
|
vi.mocked(getUserAgent).mockReturnValue("bun");
|
|
|
|
|
|
|
|
|
|
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
|
|
|
|
|
|
|
|
await checkForUpdates();
|
|
|
|
|
|
|
|
|
|
expect(logSpy).toHaveBeenCalledOnce();
|
|
|
|
|
const output = logSpy.mock.calls[0]![0] as string;
|
|
|
|
|
expect(output).toContain("bun add -g");
|
|
|
|
|
expect(output).to.matchSnapshot();
|
|
|
|
|
// state updated
|
|
|
|
|
const state = JSON.parse(memfs[STATE_PATH]!);
|
|
|
|
|
expect(state.lastUpdateCheck).toBe(new Date().toUTCString());
|
2025-04-19 08:00:45 +08:00
|
|
|
});
|
|
|
|
|
});
|