@mercury-fw/cli 0.28.3 → 0.28.4
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/CHANGELOG.md +7 -0
- package/README.md +2 -0
- package/dist/src/main.d.ts +6 -2
- package/dist/src/versions.d.ts +11 -0
- package/package.json +3 -3
- package/src/main.ts +54 -6
- package/src/versions.ts +42 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @mercury-fw/cli
|
|
2
2
|
|
|
3
|
+
## 0.28.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 65e78fe: `mfw create` (and `bun create mercury-agent`) checks the registry for a newer `@mercury-fw/cli` first, and when it's behind, as a copy left in Bun's bunx cache can be, it runs the same command through the newer version instead of writing an outdated app.
|
|
8
|
+
- @mercury-fw/core@0.28.4
|
|
9
|
+
|
|
3
10
|
## 0.28.3
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -60,6 +60,8 @@ bunx @mercury-fw/cli create my-agent --assistant-name Hermes --channels http --p
|
|
|
60
60
|
|
|
61
61
|
The framework packages get the CLI's own version (they're released together); each chosen plugin or channel gets its latest version on the registry, `https://registry.npmjs.org` unless `MFW_REGISTRY` names another.
|
|
62
62
|
|
|
63
|
+
Before anything else it asks the registry for the latest `@mercury-fw/cli`: a newer one than itself means it's a stale copy (Bun keeps the `create-mercury-agent` that `bun create` ran last in its cache, a release behind), so it says so and runs the same command through the newer version (`bunx @mercury-fw/cli@<newer> create …`), which writes the app instead. A registry that doesn't answer within a few seconds, or a newer version that can't be installed yet, is only a warning, and the CLI carries on with itself. Only CLIs from 0.28.4 on do this: a copy older than that, still in Bun's cache, needs one last `bun pm cache rm`, run from any folder with a `package.json` (Bun refuses it elsewhere).
|
|
64
|
+
|
|
63
65
|
### `mfw start [--no-cache]`
|
|
64
66
|
|
|
65
67
|
Builds the app's image and starts the app and Qdrant in the background (`docker compose up -d --build`). Docker's cache means only what changed gets rebuilt, and a running container is recreated only if its image or configuration changed, so it's also the command to run after changing a dependency, the Dockerfile or `.env`. `--no-cache` rebuilds everything from scratch (`docker compose build --no-cache`, then `up -d`), which is what refetches a tool plugin's CLI binary when a new release is out: a normal build keeps the cached one.
|
package/dist/src/main.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { type AppDeps } from "./app/commands.ts";
|
|
2
|
+
/** Runs `argv` with stdio inherited and `env` on top of this process's
|
|
3
|
+
* environment; returns its exit code. */
|
|
4
|
+
export type Relaunch = (argv: string[], env: Record<string, string>) => Promise<number>;
|
|
2
5
|
/** Runs `mfw` with `argv` (the arguments after the command name) and returns
|
|
3
6
|
* the exit code, printing to stdout/stderr. `bin.ts` and `create-mercury-agent`
|
|
4
7
|
* both call it; the app commands look for the app from `cwd` and run docker
|
|
5
|
-
* through `deps
|
|
6
|
-
export declare function main(argv: string[], { cwd, deps }?: {
|
|
8
|
+
* through `deps`; `relaunch` is how `create` hands over to a newer CLI. */
|
|
9
|
+
export declare function main(argv: string[], { cwd, deps, relaunch }?: {
|
|
7
10
|
cwd?: string;
|
|
8
11
|
deps?: AppDeps;
|
|
12
|
+
relaunch?: Relaunch;
|
|
9
13
|
}): Promise<number>;
|
package/dist/src/versions.d.ts
CHANGED
|
@@ -14,3 +14,14 @@ export declare function appVersions(packages: string[], opts: {
|
|
|
14
14
|
registry: string;
|
|
15
15
|
fetchFn?: typeof fetch;
|
|
16
16
|
}): Promise<Record<string, string>>;
|
|
17
|
+
/** The registry's `latest` `@mercury-fw/cli` when it's newer than `current`
|
|
18
|
+
* (this CLI's version by default), nothing otherwise: a CLI run from Bun's
|
|
19
|
+
* bunx cache can be behind the release it was meant to be. Rejects like
|
|
20
|
+
* `appVersions` when the registry can't answer, within `timeoutMs` (5s by
|
|
21
|
+
* default: this check runs on every create, also one needing no network). */
|
|
22
|
+
export declare function newerCli(opts: {
|
|
23
|
+
registry: string;
|
|
24
|
+
fetchFn?: typeof fetch;
|
|
25
|
+
current?: string;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}): Promise<string | undefined>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/cli",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"comment:shape": "The Mercury CLI (`mfw`). `mfw create <dir>` writes a new Mercury app from the template. The framework packages move in lockstep with it, so a new app gets them at the CLI's own version; plugins and channels at the registry's latest. The plugin and channel packages are devDependencies only for the catalog test. `main(argv)` is exported for create-mercury-agent.",
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@clack/prompts": "^1.8.1",
|
|
39
|
-
"@mercury-fw/core": "0.28.
|
|
39
|
+
"@mercury-fw/core": "0.28.4",
|
|
40
40
|
"commander": "^15.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@mercury-fw/channel-google-chat": "0.1.2",
|
|
44
44
|
"@mercury-fw/channel-http": "0.1.0",
|
|
45
|
-
"@mercury-fw/formatter": "0.28.
|
|
45
|
+
"@mercury-fw/formatter": "0.28.4",
|
|
46
46
|
"@mercury-fw/plugin-atlassian-admin": "0.1.0",
|
|
47
47
|
"@mercury-fw/plugin-bitbucket": "0.1.0",
|
|
48
48
|
"@mercury-fw/plugin-jira": "0.1.1",
|
package/src/main.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { CATALOG } from "./catalog.ts";
|
|
|
11
11
|
import { kebabCase } from "./naming.ts";
|
|
12
12
|
import { runProgram } from "./program.ts";
|
|
13
13
|
import { renderApp, selectionError } from "./render.ts";
|
|
14
|
-
import { appVersions, registryFrom } from "./versions.ts";
|
|
14
|
+
import { appVersions, cliVersion, newerCli, registryFrom } from "./versions.ts";
|
|
15
15
|
import { appCommands, terminalDeps, type AppDeps } from "./app/commands.ts";
|
|
16
16
|
import { findApp } from "./app/find-app.ts";
|
|
17
17
|
import { askAnswers, DEFAULT_ASSISTANT_NAME, DEFAULT_ROLE, type Answers } from "./wizard.ts";
|
|
@@ -28,8 +28,55 @@ function answersFromFlags(args: CreateArgs, defaultName: string): Answers {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/** `
|
|
32
|
-
|
|
31
|
+
/** Runs `argv` with stdio inherited and `env` on top of this process's
|
|
32
|
+
* environment; returns its exit code. */
|
|
33
|
+
export type Relaunch = (argv: string[], env: Record<string, string>) => Promise<number>;
|
|
34
|
+
|
|
35
|
+
/** The real relaunch: a child process on the user's terminal. */
|
|
36
|
+
const spawnRelaunch: Relaunch = async (argv, env) => {
|
|
37
|
+
const proc = Bun.spawn(argv, { stdin: "inherit", stdout: "inherit", stderr: "inherit", env: { ...process.env, ...env } });
|
|
38
|
+
return await proc.exited;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** When the registry has a newer `@mercury-fw/cli` than this one (a stale copy
|
|
42
|
+
* out of Bun's bunx cache), installs it (`bunx … --version`), then runs
|
|
43
|
+
* `create` again through it with the same arguments as typed and returns its
|
|
44
|
+
* exit code, whatever it is (a cancelled wizard included). `undefined` means
|
|
45
|
+
* carry on here: no newer CLI, a check skipped inside a relaunch
|
|
46
|
+
* (`MFW_SELF_UPDATED`), a registry that can't answer, or a newer CLI that
|
|
47
|
+
* can't be installed; the last two with a warning. */
|
|
48
|
+
async function relaunchIfStale(rawArgs: string[], relaunch: Relaunch): Promise<number | undefined> {
|
|
49
|
+
if (process.env.MFW_SELF_UPDATED) return undefined;
|
|
50
|
+
const registry = registryFrom(process.env.MFW_REGISTRY).replace(/\/+$/, "");
|
|
51
|
+
let newer: string | undefined;
|
|
52
|
+
try {
|
|
53
|
+
newer = await newerCli({ registry });
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error(`couldn't check for a newer mfw: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
if (newer === undefined) return undefined;
|
|
59
|
+
const cli = `@mercury-fw/cli@${newer}`;
|
|
60
|
+
const env = { MFW_SELF_UPDATED: newer, NPM_CONFIG_REGISTRY: registry };
|
|
61
|
+
let installed: number;
|
|
62
|
+
try {
|
|
63
|
+
installed = await relaunch(["bunx", cli, "--version"], env);
|
|
64
|
+
} catch {
|
|
65
|
+
installed = -1;
|
|
66
|
+
}
|
|
67
|
+
if (installed !== 0) {
|
|
68
|
+
console.error(`mfw ${cliVersion()} is behind the registry's ${newer}, which couldn't be installed: creating with ${cliVersion()}.`);
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
console.error(`mfw ${cliVersion()} is behind the registry's ${newer}: running ${newer} instead.`);
|
|
72
|
+
return relaunch(["bunx", cli, "create", ...rawArgs], env);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** `mfw create`: returns the exit code. `rawArgs` are the arguments after
|
|
76
|
+
* `create` as typed, for a relaunch. */
|
|
77
|
+
async function create(args: CreateArgs, rawArgs: string[], relaunch: Relaunch): Promise<number> {
|
|
78
|
+
const relaunched = await relaunchIfStale(rawArgs, relaunch);
|
|
79
|
+
if (relaunched !== undefined) return relaunched;
|
|
33
80
|
// The folder is created in kebab case, only its own name: the parent path is
|
|
34
81
|
// taken as typed. Its name is also the app name's default.
|
|
35
82
|
const typed = resolve(args.dir);
|
|
@@ -66,13 +113,14 @@ Next:
|
|
|
66
113
|
/** Runs `mfw` with `argv` (the arguments after the command name) and returns
|
|
67
114
|
* the exit code, printing to stdout/stderr. `bin.ts` and `create-mercury-agent`
|
|
68
115
|
* both call it; the app commands look for the app from `cwd` and run docker
|
|
69
|
-
* through `deps
|
|
116
|
+
* through `deps`; `relaunch` is how `create` hands over to a newer CLI. */
|
|
70
117
|
export async function main(
|
|
71
118
|
argv: string[],
|
|
72
|
-
{ cwd = process.cwd(), deps }: { cwd?: string; deps?: AppDeps } = {},
|
|
119
|
+
{ cwd = process.cwd(), deps, relaunch = spawnRelaunch }: { cwd?: string; deps?: AppDeps; relaunch?: Relaunch } = {},
|
|
73
120
|
): Promise<number> {
|
|
121
|
+
const rawCreateArgs = argv.slice(argv.indexOf("create") + 1);
|
|
74
122
|
return runProgram(argv, {
|
|
75
|
-
create,
|
|
123
|
+
create: (args) => create(args, rawCreateArgs, relaunch),
|
|
76
124
|
app: () => appCommands(findApp(cwd), deps ?? terminalDeps()),
|
|
77
125
|
});
|
|
78
126
|
}
|
package/src/versions.ts
CHANGED
|
@@ -23,6 +23,32 @@ export function cliVersion(): string {
|
|
|
23
23
|
return pkg.version;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** The registry's `latest` version of `name`. Rejects naming the package the
|
|
27
|
+
* registry doesn't have, or saying the registry can't be reached (also when
|
|
28
|
+
* it hasn't answered within `timeoutMs`, if given). */
|
|
29
|
+
async function latestVersion(
|
|
30
|
+
name: string,
|
|
31
|
+
opts: { registry: string; fetchFn?: typeof fetch; timeoutMs?: number },
|
|
32
|
+
): Promise<string> {
|
|
33
|
+
const registry = opts.registry.replace(/\/+$/, "");
|
|
34
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
35
|
+
let res: Response;
|
|
36
|
+
try {
|
|
37
|
+
const signal = opts.timeoutMs === undefined ? undefined : AbortSignal.timeout(opts.timeoutMs);
|
|
38
|
+
res = await fetchFn(`${registry}/${name.replace("/", "%2F")}/latest`, { signal });
|
|
39
|
+
} catch {
|
|
40
|
+
throw new Error(`Can't reach ${registry} to look up ${name}`);
|
|
41
|
+
}
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
throw new Error(`${name} is not on ${registry}`);
|
|
44
|
+
}
|
|
45
|
+
const body = (await res.json().catch(() => undefined)) as { version?: unknown } | undefined;
|
|
46
|
+
if (typeof body?.version !== "string") {
|
|
47
|
+
throw new Error(`${registry} gave no version for ${name}`);
|
|
48
|
+
}
|
|
49
|
+
return body.version;
|
|
50
|
+
}
|
|
51
|
+
|
|
26
52
|
/** Maps the framework packages to the CLI's version and each of `packages`
|
|
27
53
|
* (plugins and channels) to the registry's `latest`. Rejects naming the
|
|
28
54
|
* package the registry doesn't have, or saying the registry can't be reached. */
|
|
@@ -30,29 +56,25 @@ export async function appVersions(
|
|
|
30
56
|
packages: string[],
|
|
31
57
|
opts: { registry: string; fetchFn?: typeof fetch },
|
|
32
58
|
): Promise<Record<string, string>> {
|
|
33
|
-
const registry = opts.registry.replace(/\/+$/, "");
|
|
34
|
-
const fetchFn = opts.fetchFn ?? fetch;
|
|
35
59
|
const versions: Record<string, string> = Object.fromEntries(FRAMEWORK_PACKAGES.map((p) => [p, cliVersion()]));
|
|
36
|
-
const latest = await Promise.all(
|
|
37
|
-
packages.map(async (name) => {
|
|
38
|
-
let res: Response;
|
|
39
|
-
try {
|
|
40
|
-
res = await fetchFn(`${registry}/${name.replace("/", "%2F")}/latest`);
|
|
41
|
-
} catch {
|
|
42
|
-
throw new Error(`Can't reach ${registry} to look up ${name}`);
|
|
43
|
-
}
|
|
44
|
-
if (!res.ok) {
|
|
45
|
-
throw new Error(`${name} is not on ${registry}`);
|
|
46
|
-
}
|
|
47
|
-
const body = (await res.json().catch(() => undefined)) as { version?: unknown } | undefined;
|
|
48
|
-
if (typeof body?.version !== "string") {
|
|
49
|
-
throw new Error(`${registry} gave no version for ${name}`);
|
|
50
|
-
}
|
|
51
|
-
return [name, body.version] as const;
|
|
52
|
-
}),
|
|
53
|
-
);
|
|
60
|
+
const latest = await Promise.all(packages.map(async (name) => [name, await latestVersion(name, opts)] as const));
|
|
54
61
|
for (const [name, version] of latest) {
|
|
55
62
|
versions[name] = version;
|
|
56
63
|
}
|
|
57
64
|
return versions;
|
|
58
65
|
}
|
|
66
|
+
|
|
67
|
+
/** The registry's `latest` `@mercury-fw/cli` when it's newer than `current`
|
|
68
|
+
* (this CLI's version by default), nothing otherwise: a CLI run from Bun's
|
|
69
|
+
* bunx cache can be behind the release it was meant to be. Rejects like
|
|
70
|
+
* `appVersions` when the registry can't answer, within `timeoutMs` (5s by
|
|
71
|
+
* default: this check runs on every create, also one needing no network). */
|
|
72
|
+
export async function newerCli(opts: {
|
|
73
|
+
registry: string;
|
|
74
|
+
fetchFn?: typeof fetch;
|
|
75
|
+
current?: string;
|
|
76
|
+
timeoutMs?: number;
|
|
77
|
+
}): Promise<string | undefined> {
|
|
78
|
+
const latest = await latestVersion("@mercury-fw/cli", { ...opts, timeoutMs: opts.timeoutMs ?? 5000 });
|
|
79
|
+
return Bun.semver.order(latest, opts.current ?? cliVersion()) === 1 ? latest : undefined;
|
|
80
|
+
}
|