impel-cli 0.18.15-beta.6 → 0.18.15-beta.8
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/README.md +5 -0
- package/package.json +1 -1
- package/src/apps.js +25 -12
- package/src/commands/converge.js +18 -3
- package/src/commands/update.js +23 -11
- package/src/updates.js +57 -10
package/README.md
CHANGED
|
@@ -223,6 +223,11 @@ impel update --no-recovery # disable recovery for this run
|
|
|
223
223
|
schema verified by that CLI. It does not mean cloning an untested moving vendor
|
|
224
224
|
release.
|
|
225
225
|
|
|
226
|
+
Stable installations follow npm's `latest` tag. Prerelease installations made
|
|
227
|
+
from `impel-cli@next` remain on the `next` channel during `impel update`. If npm
|
|
228
|
+
metadata is temporarily unavailable, update preserves the installed CLI build
|
|
229
|
+
and continues tenant reconciliation without attempting a downgrade.
|
|
230
|
+
|
|
226
231
|
## Status and diagnosis
|
|
227
232
|
|
|
228
233
|
```sh
|
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -269,7 +269,7 @@ export const CURRENT_CONFIG_VERSION = 26;
|
|
|
269
269
|
// — which is what made every `impel update` re-trigger macOS permission
|
|
270
270
|
// prompts. Bump this ONLY when a code change alters the bytes of a built
|
|
271
271
|
// bundle; leave it alone for changes that don't touch bundle contents.
|
|
272
|
-
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-31.
|
|
272
|
+
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-31.2";
|
|
273
273
|
|
|
274
274
|
/** Parse the tenant's install manifest, or null when absent/corrupt. */
|
|
275
275
|
export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
|
|
@@ -347,7 +347,7 @@ export function bundleIsCurrent(status, {
|
|
|
347
347
|
compatibility.vendorSignaturesPreserved === true
|
|
348
348
|
&& compatibility.resourceRoot === CHATGPT_RESOURCE_ROOT
|
|
349
349
|
&& compatibility.patches?.fastModeForGatewayAuth > 0
|
|
350
|
-
&& compatibility.patches?.tenantDisplayNamePreload ===
|
|
350
|
+
&& compatibility.patches?.tenantDisplayNamePreload === 2
|
|
351
351
|
)),
|
|
352
352
|
);
|
|
353
353
|
}
|
|
@@ -1772,7 +1772,7 @@ function writeVendoredChatGPTBundleLocked(paths, vendorPath, gatewayUrl, homeDir
|
|
|
1772
1772
|
patches: {
|
|
1773
1773
|
fastModeForGatewayAuth: fastModePatchCount,
|
|
1774
1774
|
desktopAPIForGatewayAuth: 0,
|
|
1775
|
-
tenantDisplayNamePreload:
|
|
1775
|
+
tenantDisplayNamePreload: 2,
|
|
1776
1776
|
},
|
|
1777
1777
|
vendorSignaturesPreserved: true,
|
|
1778
1778
|
resourceRoot: CHATGPT_RESOURCE_ROOT,
|
|
@@ -2299,6 +2299,7 @@ secure_codex_home
|
|
|
2299
2299
|
BROWSER_DATA=${shellQuote(paths.chatgpt.browserData)}
|
|
2300
2300
|
mkdir -p "$BROWSER_DATA"
|
|
2301
2301
|
export IMPEL_APP_DISPLAY_NAME=${shellQuote(paths.chatgpt.displayName)}
|
|
2302
|
+
export IMPEL_APP_BUNDLE_ID=${shellQuote(paths.chatgpt.bundleIdentifier)}
|
|
2302
2303
|
PRELOAD="$HERE/../Resources/impel-chatgpt-preload.cjs"
|
|
2303
2304
|
export NODE_OPTIONS="--require=\\\"$PRELOAD\\\""
|
|
2304
2305
|
# Electron resolves resources from the Impel wrapper when the nested app is
|
|
@@ -2313,13 +2314,14 @@ function vendoredChatGPTDisplayNamePreload() {
|
|
|
2313
2314
|
return `"use strict";
|
|
2314
2315
|
|
|
2315
2316
|
const displayName = process.env.IMPEL_APP_DISPLAY_NAME;
|
|
2316
|
-
|
|
2317
|
+
const bundleIdentifier = process.env.IMPEL_APP_BUNDLE_ID;
|
|
2318
|
+
if (process.type === "browser" && displayName && bundleIdentifier) {
|
|
2317
2319
|
const Module = require("node:module");
|
|
2318
2320
|
const path = require("node:path");
|
|
2319
2321
|
const load = Module._load;
|
|
2320
2322
|
let objc;
|
|
2321
2323
|
let appKit;
|
|
2322
|
-
let
|
|
2324
|
+
let launchServicesIdentified = false;
|
|
2323
2325
|
let activationPolicyRefreshed = false;
|
|
2324
2326
|
|
|
2325
2327
|
const loadNativeBridge = () => {
|
|
@@ -2331,20 +2333,31 @@ if (process.type === "browser" && displayName) {
|
|
|
2331
2333
|
return { objc, appKit };
|
|
2332
2334
|
};
|
|
2333
2335
|
|
|
2334
|
-
const
|
|
2335
|
-
if (
|
|
2336
|
+
const setLaunchServicesApplicationIdentity = () => {
|
|
2337
|
+
if (launchServicesIdentified) return;
|
|
2336
2338
|
try {
|
|
2337
2339
|
const native = loadNativeBridge();
|
|
2338
2340
|
new native.objc.NobjcLibrary(
|
|
2339
2341
|
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices",
|
|
2340
2342
|
);
|
|
2341
2343
|
const title = native.appKit.NSString.stringWithUTF8String$(displayName);
|
|
2344
|
+
const identifier = native.appKit.NSString.stringWithUTF8String$(bundleIdentifier);
|
|
2342
2345
|
const info = native.appKit.NSMutableDictionary.dictionaryWithDictionary$(
|
|
2343
2346
|
native.appKit.NSBundle.mainBundle().infoDictionary(),
|
|
2344
2347
|
);
|
|
2345
2348
|
for (const key of ["CFBundleName", "CFBundleDisplayName"]) {
|
|
2346
2349
|
info.setObject$forKey$(title, native.appKit.NSString.stringWithUTF8String$(key));
|
|
2347
2350
|
}
|
|
2351
|
+
// The signed vendor executable runs from the nested ChatGPT.app, so
|
|
2352
|
+
// LaunchServices initially checks it in as com.openai.codex. When the
|
|
2353
|
+
// native app is also open, Dock groups both processes under ChatGPT even
|
|
2354
|
+
// though LSDisplayName was already tenant-branded. Check the process in
|
|
2355
|
+
// again with the unique outer-wrapper identity to keep each tenant's
|
|
2356
|
+
// Dock item distinct while preserving the vendor executable signature.
|
|
2357
|
+
info.setObject$forKey$(
|
|
2358
|
+
identifier,
|
|
2359
|
+
native.appKit.NSString.stringWithUTF8String$("CFBundleIdentifier"),
|
|
2360
|
+
);
|
|
2348
2361
|
native.objc.callFunction(
|
|
2349
2362
|
"_LSSetApplicationLaunchServicesServerConnectionStatus",
|
|
2350
2363
|
{ returns: "v", args: ["Q", "@"] },
|
|
@@ -2368,7 +2381,7 @@ if (process.type === "browser" && displayName) {
|
|
|
2368
2381
|
title,
|
|
2369
2382
|
null,
|
|
2370
2383
|
);
|
|
2371
|
-
|
|
2384
|
+
launchServicesIdentified = status === 0;
|
|
2372
2385
|
} catch {
|
|
2373
2386
|
// Keep the signed vendor app usable if LaunchServices changes this
|
|
2374
2387
|
// private-but-established process-title API in a future macOS release.
|
|
@@ -2403,7 +2416,7 @@ if (process.type === "browser" && displayName) {
|
|
|
2403
2416
|
const setName = loaded.app.setName.bind(loaded.app);
|
|
2404
2417
|
setName(displayName);
|
|
2405
2418
|
loaded.app.setName = () => setName(displayName);
|
|
2406
|
-
|
|
2419
|
+
setLaunchServicesApplicationIdentity();
|
|
2407
2420
|
setNativeApplicationMenuTitle();
|
|
2408
2421
|
const setApplicationMenu = loaded.Menu.setApplicationMenu.bind(loaded.Menu);
|
|
2409
2422
|
loaded.Menu.setApplicationMenu = (menu) => {
|
|
@@ -2414,11 +2427,11 @@ if (process.type === "browser" && displayName) {
|
|
|
2414
2427
|
loaded.app.setActivationPolicy("accessory");
|
|
2415
2428
|
setTimeout(() => {
|
|
2416
2429
|
loaded.app.setActivationPolicy("regular");
|
|
2417
|
-
|
|
2418
|
-
|
|
2430
|
+
launchServicesIdentified = false;
|
|
2431
|
+
setLaunchServicesApplicationIdentity();
|
|
2419
2432
|
setApplicationMenu(menu);
|
|
2420
2433
|
setNativeApplicationMenuTitle();
|
|
2421
|
-
},
|
|
2434
|
+
}, 100);
|
|
2422
2435
|
}
|
|
2423
2436
|
setTimeout(setNativeApplicationMenuTitle, 0);
|
|
2424
2437
|
return result;
|
package/src/commands/converge.js
CHANGED
|
@@ -13,6 +13,8 @@ import { promptText } from "../prompt.js";
|
|
|
13
13
|
import { describeCliFailure, preparePlatformClis } from "../platformSetup.js";
|
|
14
14
|
import { restoreNativeProfiles } from "./use.js";
|
|
15
15
|
|
|
16
|
+
const RETRYABLE_TENANT_DISCOVERY_ERROR = /could not reach|request timed out|fetch failed|ECONNRESET|ECONNABORTED|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|network error|socket/iu;
|
|
17
|
+
|
|
16
18
|
function confirmed(answer) {
|
|
17
19
|
return /^(?:y|yes)$/iu.test(String(answer || "").trim());
|
|
18
20
|
}
|
|
@@ -44,6 +46,7 @@ export async function cmdConverge(argv = [], overrides = {}) {
|
|
|
44
46
|
platform: process.platform,
|
|
45
47
|
environment: process.env,
|
|
46
48
|
preparePlatformClis,
|
|
49
|
+
discoveryLogger: console,
|
|
47
50
|
...overrides,
|
|
48
51
|
};
|
|
49
52
|
if (overrides.prepareWindowsClis && !overrides.preparePlatformClis) {
|
|
@@ -58,10 +61,22 @@ export async function cmdConverge(argv = [], overrides = {}) {
|
|
|
58
61
|
let listing;
|
|
59
62
|
try {
|
|
60
63
|
listing = await io.fetchTenants(config);
|
|
64
|
+
// Tenant discovery is the entry gate for the whole convergence. A single
|
|
65
|
+
// transient timeout should not force a human to rerun every update step.
|
|
61
66
|
} catch (error) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
if (!RETRYABLE_TENANT_DISCOVERY_ERROR.test(String(error?.message || error))) {
|
|
68
|
+
console.error(`impel update: tenant discovery failed (${redactSecretText(error?.message || error)})`);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
io.discoveryLogger.log("Tenants: discovery request failed transiently; retrying once…");
|
|
73
|
+
try {
|
|
74
|
+
listing = await io.fetchTenants(config);
|
|
75
|
+
} catch (retryError) {
|
|
76
|
+
console.error(`impel update: tenant discovery failed (${redactSecretText(retryError?.message || retryError)})`);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
65
80
|
}
|
|
66
81
|
const selected = selectDefaultTenant(listing, { currentTenantId: config.tenantId });
|
|
67
82
|
config.tenantId = selected.id;
|
package/src/commands/update.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isNewerVersion,
|
|
19
19
|
refreshUpdateCache,
|
|
20
20
|
updateInstallSpec,
|
|
21
|
+
updateTagForVersion,
|
|
21
22
|
writeUpdateCache,
|
|
22
23
|
} from "../updates.js";
|
|
23
24
|
|
|
@@ -200,30 +201,41 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
const current = io.installedVersion();
|
|
203
|
-
const
|
|
204
|
+
const updateTag = updateTagForVersion(current);
|
|
205
|
+
const installSpec = updateInstallSpec(updateTag);
|
|
206
|
+
const remote = await io.progress(
|
|
207
|
+
"Checking npm for impel-cli updates",
|
|
208
|
+
() => io.fetchRemoteVersion({ tag: updateTag }),
|
|
209
|
+
);
|
|
204
210
|
if (remote) io.writeCache({ remoteVersion: remote, checkedAt: Date.now() });
|
|
205
211
|
|
|
206
212
|
console.log(`impel-cli v${current ?? "?"}`);
|
|
207
|
-
console.log(`npm
|
|
213
|
+
console.log(`npm ${updateTag}: ${remote ? `v${remote}` : "unknown — registry check failed"}`);
|
|
208
214
|
|
|
209
215
|
const updateAvailable = Boolean(current && remote && isNewerVersion(remote, current));
|
|
210
216
|
const upToDate = Boolean(current && remote && !updateAvailable);
|
|
211
217
|
if (flags.check) {
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
if (!remote) {
|
|
219
|
+
console.log("Update status unavailable; the installed CLI was not changed.");
|
|
220
|
+
process.exitCode = 1;
|
|
221
|
+
} else {
|
|
222
|
+
console.log(upToDate ? "Up to date." : "Update available: run `impel update`.");
|
|
223
|
+
}
|
|
214
224
|
return;
|
|
215
225
|
}
|
|
216
226
|
|
|
217
227
|
// ── CLI ────────────────────────────────────────────────────────────────
|
|
218
|
-
if (
|
|
228
|
+
if (!remote) {
|
|
229
|
+
console.warn("CLI: npm update check unavailable; keeping the installed build.");
|
|
230
|
+
} else if (upToDate) {
|
|
219
231
|
console.log("CLI: already up to date.");
|
|
220
232
|
} else {
|
|
221
|
-
console.log(
|
|
222
|
-
if (!await io.progress(
|
|
233
|
+
console.log(`CLI: installing the npm ${updateTag} build…`);
|
|
234
|
+
if (!await io.progress(`Installing the npm ${updateTag} impel-cli build`, () => io.selfUpdate(installSpec))) {
|
|
223
235
|
console.error("impel update: `npm install -g` failed; the CLI was not updated.");
|
|
224
236
|
if (io.platform === "win32") {
|
|
225
237
|
console.error(" Verify `npm --version` in PowerShell, then retry `impel update`.");
|
|
226
|
-
console.error(
|
|
238
|
+
console.error(` Manual recovery: \`npm install --global ${installSpec}\`.`);
|
|
227
239
|
}
|
|
228
240
|
const config = io.loadConfig();
|
|
229
241
|
let recovered = false;
|
|
@@ -239,7 +251,7 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
239
251
|
platform: io.platform,
|
|
240
252
|
architecture: process.arch,
|
|
241
253
|
step: "install.impel_cli",
|
|
242
|
-
command: `npm install --global ${
|
|
254
|
+
command: `npm install --global ${installSpec}`,
|
|
243
255
|
message: `The npm-verified global ${RUNTIME_BRAND.cli.packageName} update failed.`,
|
|
244
256
|
},
|
|
245
257
|
config,
|
|
@@ -265,7 +277,7 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
265
277
|
}
|
|
266
278
|
},
|
|
267
279
|
retryStep: async () => {
|
|
268
|
-
updateState.ok = io.selfUpdate(
|
|
280
|
+
updateState.ok = io.selfUpdate(installSpec) === true;
|
|
269
281
|
return updateState.ok;
|
|
270
282
|
},
|
|
271
283
|
},
|
|
@@ -294,7 +306,7 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
294
306
|
console.error(`impel update: npm reported success but the running CLI still resolves v${postInstall ?? "?"} (expected v${remote}).`);
|
|
295
307
|
console.error(` Running CLI: ${CLI_BIN}`);
|
|
296
308
|
console.error(" This usually means the `impel` on PATH belongs to a different npm prefix than `npm prefix -g`.");
|
|
297
|
-
console.error(
|
|
309
|
+
console.error(` Fix: run \`npm prefix -g\`, confirm it owns the \`impel\` shim on PATH, then \`npm install --global ${installSpec}\` there.`);
|
|
298
310
|
process.exitCode = 1;
|
|
299
311
|
return;
|
|
300
312
|
}
|
package/src/updates.js
CHANGED
|
@@ -36,8 +36,19 @@ export function updateRegistry() {
|
|
|
36
36
|
).replace(/\/+$/u, "");
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
function normalizeUpdateTag(tag) {
|
|
40
|
+
if (tag === "latest" || tag === "next") return tag;
|
|
41
|
+
throw new Error(`unsupported npm update tag "${tag}"`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Keep prerelease installs on npm's prerelease channel. */
|
|
45
|
+
export function updateTagForVersion(version) {
|
|
46
|
+
const parsed = validVersion(version) ? version.match(VERSION_RE) : null;
|
|
47
|
+
return parsed?.[4] ? "next" : "latest";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function updateInstallSpec(tag = "latest") {
|
|
51
|
+
return `${updatePackage()}@${normalizeUpdateTag(tag)}`;
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
export function installedVersion() {
|
|
@@ -88,15 +99,17 @@ export function isNewerVersion(candidate, current) {
|
|
|
88
99
|
function fetchRemoteVersionWithNpm({
|
|
89
100
|
packageName,
|
|
90
101
|
registry,
|
|
102
|
+
tag,
|
|
91
103
|
timeoutMs,
|
|
92
104
|
spawnSyncImpl = spawnSync,
|
|
93
105
|
environment = process.env,
|
|
94
106
|
platform = process.platform,
|
|
95
107
|
}) {
|
|
96
108
|
try {
|
|
109
|
+
const target = tag === "latest" ? packageName : `${packageName}@${tag}`;
|
|
97
110
|
const invocation = nativeCommandInvocation(
|
|
98
111
|
"npm",
|
|
99
|
-
["view",
|
|
112
|
+
["view", target, "version", "--json", "--registry", registry],
|
|
100
113
|
environment,
|
|
101
114
|
platform,
|
|
102
115
|
);
|
|
@@ -122,39 +135,71 @@ function fetchRemoteVersionWithNpm({
|
|
|
122
135
|
export async function fetchRemoteVersion({
|
|
123
136
|
packageName = updatePackage(),
|
|
124
137
|
registry = updateRegistry(),
|
|
138
|
+
tag = "latest",
|
|
125
139
|
timeoutMs = 10_000,
|
|
126
140
|
fetchImpl = null,
|
|
141
|
+
fallbackToNpm = fetchImpl === null,
|
|
127
142
|
spawnSyncImpl = spawnSync,
|
|
128
143
|
environment = process.env,
|
|
129
144
|
platform = process.platform,
|
|
130
145
|
} = {}) {
|
|
146
|
+
const normalizedTag = normalizeUpdateTag(tag);
|
|
131
147
|
const normalizedRegistry = String(registry).replace(/\/+$/u, "");
|
|
132
148
|
if (!fetchImpl && normalizedRegistry !== DEFAULT_UPDATE_REGISTRY) {
|
|
133
149
|
return fetchRemoteVersionWithNpm({
|
|
134
150
|
packageName,
|
|
135
151
|
registry: normalizedRegistry,
|
|
152
|
+
tag: normalizedTag,
|
|
153
|
+
timeoutMs,
|
|
154
|
+
spawnSyncImpl,
|
|
155
|
+
environment,
|
|
156
|
+
platform,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Windows npm commonly has proxy, certificate, or registry settings that
|
|
160
|
+
// Node's bare fetch does not inherit. Prefer the already-working npm client
|
|
161
|
+
// there, then retain the direct registry request as a fallback.
|
|
162
|
+
if (!fetchImpl && platform === "win32") {
|
|
163
|
+
const npmVersion = fetchRemoteVersionWithNpm({
|
|
164
|
+
packageName,
|
|
165
|
+
registry: normalizedRegistry,
|
|
166
|
+
tag: normalizedTag,
|
|
136
167
|
timeoutMs,
|
|
137
168
|
spawnSyncImpl,
|
|
138
169
|
environment,
|
|
139
170
|
platform,
|
|
140
171
|
});
|
|
172
|
+
if (npmVersion) return npmVersion;
|
|
141
173
|
}
|
|
142
174
|
const controller = new AbortController();
|
|
143
175
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
176
|
+
let remoteVersion = null;
|
|
144
177
|
try {
|
|
145
178
|
const encodedName = encodeURIComponent(packageName);
|
|
146
|
-
const response = await (fetchImpl || globalThis.fetch)(`${normalizedRegistry}/${encodedName}
|
|
179
|
+
const response = await (fetchImpl || globalThis.fetch)(`${normalizedRegistry}/${encodedName}/${normalizedTag}`, {
|
|
147
180
|
headers: { accept: "application/json" },
|
|
148
181
|
signal: controller.signal,
|
|
149
182
|
});
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
183
|
+
if (response.ok) {
|
|
184
|
+
const metadata = await response.json();
|
|
185
|
+
remoteVersion = validVersion(metadata?.version) ? metadata.version : null;
|
|
186
|
+
}
|
|
153
187
|
} catch {
|
|
154
|
-
|
|
188
|
+
// npm is the authoritative fallback below. It also honors npm's proxy and
|
|
189
|
+
// registry configuration, which is especially important on Windows.
|
|
155
190
|
} finally {
|
|
156
191
|
clearTimeout(timeout);
|
|
157
192
|
}
|
|
193
|
+
if (remoteVersion || !fallbackToNpm) return remoteVersion;
|
|
194
|
+
return fetchRemoteVersionWithNpm({
|
|
195
|
+
packageName,
|
|
196
|
+
registry: normalizedRegistry,
|
|
197
|
+
tag: normalizedTag,
|
|
198
|
+
timeoutMs,
|
|
199
|
+
spawnSyncImpl,
|
|
200
|
+
environment,
|
|
201
|
+
platform,
|
|
202
|
+
});
|
|
158
203
|
}
|
|
159
204
|
|
|
160
205
|
export function readUpdateCache() {
|
|
@@ -198,7 +243,9 @@ export function cacheIsFresh(cache, now = Date.now()) {
|
|
|
198
243
|
export async function refreshUpdateCache(dependencies = {}) {
|
|
199
244
|
const fetchLatest = dependencies.fetchRemoteVersion || fetchRemoteVersion;
|
|
200
245
|
const writeCache = dependencies.writeCache || writeUpdateCache;
|
|
201
|
-
const
|
|
246
|
+
const currentVersion = (dependencies.installedVersion || installedVersion)();
|
|
247
|
+
const tag = updateTagForVersion(currentVersion);
|
|
248
|
+
const remoteVersion = await fetchLatest({ tag });
|
|
202
249
|
if (!remoteVersion) {
|
|
203
250
|
try {
|
|
204
251
|
writeCache({ lastFailedAt: Date.now() });
|
|
@@ -207,7 +254,7 @@ export async function refreshUpdateCache(dependencies = {}) {
|
|
|
207
254
|
}
|
|
208
255
|
return null;
|
|
209
256
|
}
|
|
210
|
-
return writeCache({ remoteVersion, checkedAt: Date.now() });
|
|
257
|
+
return writeCache({ remoteVersion, updateTag: tag, checkedAt: Date.now() });
|
|
211
258
|
}
|
|
212
259
|
|
|
213
260
|
/** One-line human notice when npm has a newer stable package. */
|