mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-09 16:35:27 +02:00
Bumps [@clack/prompts](https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts) from 0.11.0 to 1.7.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/bombshell-dev/clack/releases">@clack/prompts's releases</a>.</em></p> <blockquote> <h2><code>@clack/prompts</code><a href="https://github.com/1"><code>@1</code></a>.7.0</h2> <h3>Minor Changes</h3> <ul> <li><a href="https://redirect.github.com/bombshell-dev/clack/pull/574">#574</a> <a href="https://github.com/bombshell-dev/clack/commit/8f1c380683cfb5fb7a094610342046ae1603e28e"><code>8f1c380</code></a> Thanks <a href="https://github.com/dreyfus92"><code>@dreyfus92</code></a>! - Add <code>showInstructions</code> option to <code>select</code>, <code>multiselect</code>, and <code>groupMultiselect</code>. Keyboard hints remain shown by default; pass <code>showInstructions: false</code> to hide them.</li> </ul> <h3>Patch Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/582">#582</a> <a href="https://github.com/bombshell-dev/clack/commit/4b249539e28eecc16c13b3a1bd0ba447e9f38d06"><code>4b24953</code></a> Thanks <a href="https://github.com/43081j"><code>@43081j</code></a>! - Handle empty arrays in various prompts and utilities.</p> </li> <li> <p>Updated dependencies [<a href="https://github.com/bombshell-dev/clack/commit/4b249539e28eecc16c13b3a1bd0ba447e9f38d06"><code>4b24953</code></a>]:</p> <ul> <li><code>@clack/core</code><a href="https://github.com/1"><code>@1</code></a>.4.3</li> </ul> </li> </ul> <h2><code>@clack/prompts</code><a href="https://github.com/1"><code>@1</code></a>.6.0</h2> <h3>Minor Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/568">#568</a> <a href="https://github.com/bombshell-dev/clack/commit/f87933fb7b3f4c401b9e51a152b95cb8e7200fe5"><code>f87933f</code></a> Thanks <a href="https://github.com/florian-lefebvre"><code>@florian-lefebvre</code></a>! - Updates default formatter of <code>note()</code> to note dim lines anymore</p> <p>If you want the old behavior, provide a <code>format()</code> function:</p> <pre lang="diff"><code>import { note } from '@clack/prompts'; +import { styleText } from 'node:util'; <p>note( 'You can edit the file src/index.jsx', 'Next steps.'</p> <ul> <li>{ format: (text) => styleText('dim', text) } ); </code></pre></li> </ul> </li> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/567">#567</a> <a href="https://github.com/bombshell-dev/clack/commit/cc6aab50186cff8a02dc98e9cfd3897c29a33b15"><code>cc6aab5</code></a> Thanks <a href="https://github.com/dreyfus92"><code>@dreyfus92</code></a>! - Add keyboard instruction footers to <code>select</code>, <code>multiselect</code>, and <code>groupMultiselect</code> in the active state, matching autocomplete. No option — always shown.</p> </li> </ul> <h3>Patch Changes</h3> <ul> <li>Updated dependencies [<a href="https://github.com/bombshell-dev/clack/commit/2f2b52f77cbfa9af618c6d929249ab8395fc37a1"><code>2f2b52f</code></a>, <a href="https://github.com/bombshell-dev/clack/commit/e1b6ee71a76e17a3c33ba7ee6e5fb34e886233bb"><code>e1b6ee7</code></a>]: <ul> <li><code>@clack/core</code><a href="https://github.com/1"><code>@1</code></a>.4.2</li> </ul> </li> </ul> <h2><code>@clack/prompts</code><a href="https://github.com/1"><code>@1</code></a>.5.1</h2> <h3>Patch Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/548">#548</a> <a href="https://github.com/bombshell-dev/clack/commit/2356e97c1f46007ead55133c3a26910404ef1cfb"><code>2356e97</code></a> Thanks <a href="https://github.com/43081j"><code>@43081j</code></a>! - Remove sourcemaps and enable pretty-ish build output.</p> </li> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/546">#546</a> <a href="https://github.com/bombshell-dev/clack/commit/56e9d6707715bc858d9c2dbc444230b02813e809"><code>56e9d67</code></a> Thanks <a href="https://github.com/ghostdevv"><code>@ghostdevv</code></a>! - docs: add jsdoc for <code>date</code>, <code>limit-options</code>, and <code>messages</code></p> </li> <li> <p>Updated dependencies [<a href="https://github.com/bombshell-dev/clack/commit/2356e97c1f46007ead55133c3a26910404ef1cfb"><code>2356e97</code></a>]:</p> <ul> <li><code>@clack/core</code><a href="https://github.com/1"><code>@1</code></a>.4.1</li> </ul> </li> </ul> <h2><code>@clack/prompts</code><a href="https://github.com/1"><code>@1</code></a>.5.0</h2> <h3>Minor Changes</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/bombshell-dev/clack/blob/main/packages/prompts/CHANGELOG.md">@clack/prompts's changelog</a>.</em></p> <blockquote> <h2>1.7.0</h2> <h3>Minor Changes</h3> <ul> <li><a href="https://redirect.github.com/bombshell-dev/clack/pull/574">#574</a> <a href="https://github.com/bombshell-dev/clack/commit/8f1c380683cfb5fb7a094610342046ae1603e28e"><code>8f1c380</code></a> Thanks <a href="https://github.com/dreyfus92"><code>@dreyfus92</code></a>! - Add <code>showInstructions</code> option to <code>select</code>, <code>multiselect</code>, and <code>groupMultiselect</code>. Keyboard hints remain shown by default; pass <code>showInstructions: false</code> to hide them.</li> </ul> <h3>Patch Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/582">#582</a> <a href="https://github.com/bombshell-dev/clack/commit/4b249539e28eecc16c13b3a1bd0ba447e9f38d06"><code>4b24953</code></a> Thanks <a href="https://github.com/43081j"><code>@43081j</code></a>! - Handle empty arrays in various prompts and utilities.</p> </li> <li> <p>Updated dependencies [<a href="https://github.com/bombshell-dev/clack/commit/4b249539e28eecc16c13b3a1bd0ba447e9f38d06"><code>4b24953</code></a>]:</p> <ul> <li><code>@clack/core</code><a href="https://github.com/1"><code>@1</code></a>.4.3</li> </ul> </li> </ul> <h2>1.6.0</h2> <h3>Minor Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/568">#568</a> <a href="https://github.com/bombshell-dev/clack/commit/f87933fb7b3f4c401b9e51a152b95cb8e7200fe5"><code>f87933f</code></a> Thanks <a href="https://github.com/florian-lefebvre"><code>@florian-lefebvre</code></a>! - Updates default formatter of <code>note()</code> to note dim lines anymore</p> <p>If you want the old behavior, provide a <code>format()</code> function:</p> <pre lang="diff"><code>import { note } from '@clack/prompts'; +import { styleText } from 'node:util'; <p>note( 'You can edit the file src/index.jsx', 'Next steps.'</p> <ul> <li>{ format: (text) => styleText('dim', text) } ); </code></pre></li> </ul> </li> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/567">#567</a> <a href="https://github.com/bombshell-dev/clack/commit/cc6aab50186cff8a02dc98e9cfd3897c29a33b15"><code>cc6aab5</code></a> Thanks <a href="https://github.com/dreyfus92"><code>@dreyfus92</code></a>! - Add keyboard instruction footers to <code>select</code>, <code>multiselect</code>, and <code>groupMultiselect</code> in the active state, matching autocomplete. No option — always shown.</p> </li> </ul> <h3>Patch Changes</h3> <ul> <li>Updated dependencies [<a href="https://github.com/bombshell-dev/clack/commit/2f2b52f77cbfa9af618c6d929249ab8395fc37a1"><code>2f2b52f</code></a>, <a href="https://github.com/bombshell-dev/clack/commit/e1b6ee71a76e17a3c33ba7ee6e5fb34e886233bb"><code>e1b6ee7</code></a>]: <ul> <li><code>@clack/core</code><a href="https://github.com/1"><code>@1</code></a>.4.2</li> </ul> </li> </ul> <h2>1.5.1</h2> <h3>Patch Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/548">#548</a> <a href="https://github.com/bombshell-dev/clack/commit/2356e97c1f46007ead55133c3a26910404ef1cfb"><code>2356e97</code></a> Thanks <a href="https://github.com/43081j"><code>@43081j</code></a>! - Remove sourcemaps and enable pretty-ish build output.</p> </li> <li> <p><a href="https://redirect.github.com/bombshell-dev/clack/pull/546">#546</a> <a href="https://github.com/bombshell-dev/clack/commit/56e9d6707715bc858d9c2dbc444230b02813e809"><code>56e9d67</code></a> Thanks <a href="https://github.com/ghostdevv"><code>@ghostdevv</code></a>! - docs: add jsdoc for <code>date</code>, <code>limit-options</code>, and <code>messages</code></p> </li> <li> <p>Updated dependencies [<a href="https://github.com/bombshell-dev/clack/commit/2356e97c1f46007ead55133c3a26910404ef1cfb"><code>2356e97</code></a>]:</p> <ul> <li><code>@clack/core</code><a href="https://github.com/1"><code>@1</code></a>.4.1</li> </ul> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/bombshell-dev/clack/commit/dc5bce8aae84a57b5863124adfaa839c1db1fa23"><code>dc5bce8</code></a> [ci] release (<a href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/575">#575</a>)</li> <li><a href="https://github.com/bombshell-dev/clack/commit/4b249539e28eecc16c13b3a1bd0ba447e9f38d06"><code>4b24953</code></a> chore: enable strict index checks (<a href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/582">#582</a>)</li> <li><a href="https://github.com/bombshell-dev/clack/commit/8f1c380683cfb5fb7a094610342046ae1603e28e"><code>8f1c380</code></a> feat(prompts): add showInstructions opt-out (<a href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/574">#574</a>)</li> <li><a href="https://github.com/bombshell-dev/clack/commit/06c16c7b65b69dee44301e7e6c8f1110c6da4149"><code>06c16c7</code></a> chore: update license copyright to Bombshell contributors (<a href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/572">#572</a>)</li> <li><a href="https://github.com/bombshell-dev/clack/commit/0e700562dfb54bd693d98c5b7e28d34213fcb226"><code>0e70056</code></a> [ci] release (<a href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/562">#562</a>)</li> <li><a href="https://github.com/bombshell-dev/clack/commit/f87933fb7b3f4c401b9e51a152b95cb8e7200fe5"><code>f87933f</code></a> fix(prompts): do not dim note contents (<a href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/568">#568</a>)</li> <li><a href="https://github.com/bombshell-dev/clack/commit/cc6aab50186cff8a02dc98e9cfd3897c29a33b15"><code>cc6aab5</code></a> feat(prompts): add instructions footer for <code>select</code>, <code>multi-select</code> & `group-...</li> <li><a href="https://github.com/bombshell-dev/clack/commit/02ae19193ed1155a57fb3dfd830ecc356a1cc11a"><code>02ae191</code></a> [ci] release (<a href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/549">#549</a>)</li> <li><a href="https://github.com/bombshell-dev/clack/commit/56e9d6707715bc858d9c2dbc444230b02813e809"><code>56e9d67</code></a> docs: add jsdoc for <code>date</code>, <code>limit-options</code>, and <code>messages</code> (<a href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/546">#546</a>)</li> <li><a href="https://github.com/bombshell-dev/clack/commit/030ba4d776e5985050c0e2712da7008bd93b8847"><code>030ba4d</code></a> [ci] release (<a href="https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts/issues/539">#539</a>)</li> <li>Additional commits viewable in <a href="https://github.com/bombshell-dev/clack/commits/@clack/prompts@1.7.0/packages/prompts">compare view</a></li> </ul> </details> <details> <summary>Maintainer changes</summary> <p>This version was pushed to npm by <a href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new releaser for <code>@clack/prompts</code> since your current version.</p> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Priya Raman <priya@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing>
288 lines
11 KiB
TypeScript
288 lines
11 KiB
TypeScript
import path from "node:path";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import * as p from "@clack/prompts";
|
|
import {
|
|
buildEnvLabCleanupCommand,
|
|
collectEnvLabDoctorStatus,
|
|
envLabDoctorCommand,
|
|
resolveEnvLabCliInvocation,
|
|
resolveEnvLabSshStatePath,
|
|
} from "../commands/env-lab.js";
|
|
|
|
describe("env-lab command", () => {
|
|
it("resolves the default SSH fixture state path under the instance root", () => {
|
|
const statePath = resolveEnvLabSshStatePath("fixture-test");
|
|
|
|
expect(statePath).toContain(
|
|
path.join("instances", "fixture-test", "env-lab", "ssh-fixture", "state.json"),
|
|
);
|
|
});
|
|
|
|
it("reports doctor status for an instance without a running fixture", async () => {
|
|
const status = await collectEnvLabDoctorStatus({ instance: "fixture-test-missing" });
|
|
|
|
expect(status.statePath).toContain(
|
|
path.join("instances", "fixture-test-missing", "env-lab", "ssh-fixture", "state.json"),
|
|
);
|
|
expect(typeof status.ssh.supported).toBe("boolean");
|
|
expect(status.ssh.running).toBe(false);
|
|
expect(status.ssh.environment).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("env-lab cleanup command hint", () => {
|
|
const originalCwd = process.cwd();
|
|
|
|
afterEach(() => {
|
|
process.chdir(originalCwd);
|
|
});
|
|
|
|
// Resolve a source-checkout invocation for a fabricated checkout root. A source
|
|
// checkout runs this module from `<root>/src/commands/env-lab.ts`, so the
|
|
// resolver reads that layout and returns the tsx runner and source entry.
|
|
function sourceInvocation(root: string) {
|
|
return resolveEnvLabCliInvocation(path.join(root, "src", "commands", "env-lab.ts"));
|
|
}
|
|
|
|
// Resolve a bundled-build invocation for a fabricated package root. The bundled
|
|
// build runs this module from `<root>/dist/index.js`, so the resolver returns
|
|
// that file as the entry with no tsx runner.
|
|
function bundledInvocation(root: string) {
|
|
return resolveEnvLabCliInvocation(path.join(root, "dist", "index.js"));
|
|
}
|
|
|
|
// Split a command that uses POSIX single-quoting into its argument tokens. The
|
|
// parser reads a single-quoted span verbatim and reads `\'` outside a span as a
|
|
// literal single quote. This is the same rule a POSIX shell obeys, so a token
|
|
// list proves the shell reads the exact paths and runs no embedded command.
|
|
function tokenizePosix(command: string): string[] {
|
|
const tokens: string[] = [];
|
|
let current = "";
|
|
let started = false;
|
|
let inQuotes = false;
|
|
for (let index = 0; index < command.length; index += 1) {
|
|
const character = command[index];
|
|
if (inQuotes) {
|
|
if (character === "'") {
|
|
inQuotes = false;
|
|
} else {
|
|
current += character;
|
|
}
|
|
} else if (character === "'") {
|
|
inQuotes = true;
|
|
started = true;
|
|
} else if (character === "\\") {
|
|
index += 1;
|
|
current += command[index];
|
|
started = true;
|
|
} else if (character === " ") {
|
|
if (started) {
|
|
tokens.push(current);
|
|
current = "";
|
|
started = false;
|
|
}
|
|
} else {
|
|
current += character;
|
|
started = true;
|
|
}
|
|
}
|
|
if (started) {
|
|
tokens.push(current);
|
|
}
|
|
return tokens;
|
|
}
|
|
|
|
// Return the two path arguments from the `node` command.
|
|
function extractPaths(command: string): string[] {
|
|
const tokens = tokenizePosix(command);
|
|
return tokens.slice(1, 3);
|
|
}
|
|
|
|
it("resolves both CLI paths to absolute paths", () => {
|
|
const command = buildEnvLabCleanupCommand();
|
|
const paths = extractPaths(command);
|
|
|
|
expect(paths).toHaveLength(2);
|
|
for (const resolved of paths) {
|
|
expect(path.isAbsolute(resolved)).toBe(true);
|
|
}
|
|
expect(command.endsWith("env-lab down")).toBe(true);
|
|
});
|
|
|
|
it("points at the checked-out tsx runner and cli source entry", () => {
|
|
const [tsxBin, entry] = extractPaths(buildEnvLabCleanupCommand());
|
|
|
|
expect(tsxBin).toContain(
|
|
path.join("cli", "node_modules", "tsx", "dist", "cli.mjs"),
|
|
);
|
|
expect(entry).toContain(path.join("cli", "src", "index.ts"));
|
|
});
|
|
|
|
it("returns the same command from a checkout subdirectory", () => {
|
|
const fromRoot = buildEnvLabCleanupCommand();
|
|
|
|
// Simulate a contributor who runs `env-lab doctor` from a subdirectory of
|
|
// the checkout. A relative path would change with the working directory, so
|
|
// this asserts the command stays constant.
|
|
process.chdir(path.dirname(originalCwd));
|
|
const fromParent = buildEnvLabCleanupCommand();
|
|
process.chdir(originalCwd);
|
|
|
|
expect(fromParent).toBe(fromRoot);
|
|
});
|
|
|
|
it("never restores the unsafe pnpm invocation forms", () => {
|
|
const command = buildEnvLabCleanupCommand();
|
|
|
|
// The bare `pnpm paperclipai` script form is unsafe. The `pnpm exec` form
|
|
// does not resolve the CLI binary. Keep both out of the hint.
|
|
expect(command).not.toContain("pnpm paperclipai");
|
|
expect(command).not.toContain("pnpm exec paperclipai");
|
|
});
|
|
|
|
// A checkout path can hold shell metacharacters. A contributor copies the hint
|
|
// and pastes it into a shell. The hint must neutralize each metacharacter, so
|
|
// the shell reads the exact path and runs no embedded command. Each case below
|
|
// is a checkout root with one dangerous construct.
|
|
const dangerousRoots = [
|
|
{ label: "a dollar sign", root: "/tmp/env$lab/checkout" },
|
|
{ label: "command substitution", root: "/tmp/$(touch pwned)/checkout" },
|
|
{ label: "backticks", root: "/tmp/`touch pwned`/checkout" },
|
|
{ label: "a double quote", root: '/tmp/env"lab/checkout' },
|
|
{ label: "a single quote", root: "/tmp/env'lab/checkout" },
|
|
];
|
|
|
|
for (const { label, root } of dangerousRoots) {
|
|
it(`keeps a checkout path with ${label} inert in the cleanup hint`, () => {
|
|
const command = buildEnvLabCleanupCommand({ invocation: sourceInvocation(root) });
|
|
const tokens = tokenizePosix(command);
|
|
const tsxBin = path.join(root, "node_modules", "tsx", "dist", "cli.mjs");
|
|
const entry = path.join(root, "src", "index.ts");
|
|
|
|
// The shell reads the exact paths as single argument tokens. It does not
|
|
// split the paths or run the embedded construct.
|
|
expect(tokens).toEqual(["node", tsxBin, entry, "env-lab", "down"]);
|
|
|
|
// The old double-quoted form left `$(...)`, a backtick pair, and `$NAME`
|
|
// live. Do not restore it.
|
|
expect(command).not.toContain(`"${tsxBin}"`);
|
|
expect(command).not.toContain(`"${entry}"`);
|
|
});
|
|
}
|
|
|
|
it("forwards the inspected instance to the cleanup hint", () => {
|
|
const command = buildEnvLabCleanupCommand({
|
|
instance: "fixture-test",
|
|
invocation: sourceInvocation("/tmp/checkout"),
|
|
});
|
|
const tokens = tokenizePosix(command);
|
|
|
|
// The hint ends with `--instance <id>`, so it stops the fixture the doctor
|
|
// command diagnosed, not the default instance.
|
|
expect(tokens.slice(-2)).toEqual(["--instance", "fixture-test"]);
|
|
});
|
|
|
|
it("omits the instance flag when the doctor command uses the default instance", () => {
|
|
const command = buildEnvLabCleanupCommand({ invocation: sourceInvocation("/tmp/checkout") });
|
|
|
|
// Without a selected instance, `env-lab down` resolves the same default
|
|
// instance the doctor command inspected. Do not add an empty flag.
|
|
expect(command).not.toContain("--instance");
|
|
expect(command.endsWith("env-lab down")).toBe(true);
|
|
});
|
|
|
|
it("keeps an instance id with shell metacharacters inert", () => {
|
|
const command = buildEnvLabCleanupCommand({
|
|
instance: "$(touch pwned)",
|
|
invocation: sourceInvocation("/tmp/checkout"),
|
|
});
|
|
const tokens = tokenizePosix(command);
|
|
|
|
// The shell reads the instance id as one literal token and runs no embedded
|
|
// command.
|
|
expect(tokens.slice(-2)).toEqual(["--instance", "$(touch pwned)"]);
|
|
expect(command).not.toContain('"$(touch pwned)"');
|
|
});
|
|
|
|
it("runs the bundled dist entry directly, without the tsx runner", () => {
|
|
const command = buildEnvLabCleanupCommand({
|
|
invocation: bundledInvocation("/opt/pkg"),
|
|
});
|
|
const tokens = tokenizePosix(command);
|
|
|
|
// The published package ships one `dist/index.js` file and no tsx runner, so
|
|
// node runs that file directly.
|
|
expect(tokens).toEqual(["node", path.join("/opt/pkg", "dist", "index.js"), "env-lab", "down"]);
|
|
expect(command).not.toContain("tsx");
|
|
expect(command).not.toContain(path.join("src", "index.ts"));
|
|
});
|
|
|
|
it("keeps a bundled package path with shell metacharacters inert", () => {
|
|
const root = "/opt/$(touch pwned)/pkg";
|
|
const command = buildEnvLabCleanupCommand({ invocation: bundledInvocation(root) });
|
|
const tokens = tokenizePosix(command);
|
|
const entry = path.join(root, "dist", "index.js");
|
|
|
|
// The shell reads the exact bundled path as one token and runs no embedded
|
|
// command.
|
|
expect(tokens).toEqual(["node", entry, "env-lab", "down"]);
|
|
expect(command).not.toContain(`"${entry}"`);
|
|
});
|
|
});
|
|
|
|
describe("env-lab doctor cleanup hint instance", () => {
|
|
const originalInstanceId = process.env.PAPERCLIP_INSTANCE_ID;
|
|
|
|
afterEach(() => {
|
|
if (originalInstanceId === undefined) {
|
|
delete process.env.PAPERCLIP_INSTANCE_ID;
|
|
} else {
|
|
process.env.PAPERCLIP_INSTANCE_ID = originalInstanceId;
|
|
}
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
// Capture the cleanup hint the doctor prints. The doctor reports through
|
|
// `p.log`, so the test replaces each channel and reads the captured lines.
|
|
function captureDoctorMessages(): string[] {
|
|
const messages: string[] = [];
|
|
vi.spyOn(p.log, "message").mockImplementation((message?: string | string[]) => {
|
|
messages.push(Array.isArray(message) ? message.join("\n") : (message ?? ""));
|
|
});
|
|
vi.spyOn(p.log, "success").mockImplementation(() => {});
|
|
vi.spyOn(p.log, "warn").mockImplementation(() => {});
|
|
vi.spyOn(p.log, "info").mockImplementation(() => {});
|
|
return messages;
|
|
}
|
|
|
|
it("pins the PAPERCLIP_INSTANCE_ID instance when opts.instance is absent", async () => {
|
|
// The doctor diagnoses the instance that `PAPERCLIP_INSTANCE_ID` selects.
|
|
// The cleanup hint must target that instance, not the default instance.
|
|
process.env.PAPERCLIP_INSTANCE_ID = "env-selected-instance";
|
|
const messages = captureDoctorMessages();
|
|
|
|
await envLabDoctorCommand({ instance: undefined });
|
|
|
|
const cleanup = messages.find((message) => message.startsWith("Cleanup:"));
|
|
expect(cleanup).toBeDefined();
|
|
expect(cleanup).toContain("env-lab down");
|
|
expect(cleanup).toContain("--instance");
|
|
expect(cleanup).toContain("env-selected-instance");
|
|
});
|
|
|
|
it("pins the explicit instance over PAPERCLIP_INSTANCE_ID", async () => {
|
|
// An explicit `--instance` flag overrides the environment variable, so the
|
|
// hint targets the explicit instance the doctor inspected.
|
|
process.env.PAPERCLIP_INSTANCE_ID = "env-selected-instance";
|
|
const messages = captureDoctorMessages();
|
|
|
|
await envLabDoctorCommand({ instance: "explicit-instance" });
|
|
|
|
const cleanup = messages.find((message) => message.startsWith("Cleanup:"));
|
|
expect(cleanup).toBeDefined();
|
|
expect(cleanup).toContain("--instance");
|
|
expect(cleanup).toContain("explicit-instance");
|
|
expect(cleanup).not.toContain("env-selected-instance");
|
|
});
|
|
});
|