mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 22:37:29 +00:00
fix(pwa): the CLI hint printed commands that no longer run
The "Connect the CLI" card handed out:
LOGICSRC_API=https://app.logicsrc.com logicsrc login
logicsrc teams push <team> prod --env .env
logicsrc teams pull <team> prod --env .env
Two things are wrong with that, and both survived a release.
Since #109 addressed vaults as <team> <project> <env>, push and pull take
three positionals. The hint passes two, so pasting it exits with a missing-
argument error -- the card is not merely stale, it is broken.
The LOGICSRC_API prefix sets the variable to the value the CLI already
defaults to (DEFAULT_API_URL, #107), so on the hosted app it does nothing
while reading like a required step. It is now emitted only when the origin
is not the default, which is the case it exists for: self-hosting.
`--env .env` is dropped for the same reason -- it restates the option's own
default, and sitting next to the new <env> positional it made one flag and
one argument look like the same thing.
Same stale two-argument form fixed in the post-install hint (install.sh) and
the accept-invite message, and in the empty-vault-list prompt on the card.
CLI_HINT moves to src/lib/cli-hint.mjs so a test can assert on the rendered
commands without standing up express and the database, matching how the
other lib-level views are covered. The tests pin the argument count rather
than the prose: restyling the card stays free, dropping an argument does not.
apps/pwa: 13/13 pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ca182bc057
commit
aa4a690d60
5 changed files with 86 additions and 11 deletions
|
|
@ -109,7 +109,8 @@ EOF
|
||||||
*":$LOGICSRC_BIN:"*) : ;;
|
*":$LOGICSRC_BIN:"*) : ;;
|
||||||
*) warn "add $LOGICSRC_BIN to your PATH: export PATH=\"$LOGICSRC_BIN:\$PATH\"";;
|
*) warn "add $LOGICSRC_BIN to your PATH: export PATH=\"$LOGICSRC_BIN:\$PATH\"";;
|
||||||
esac
|
esac
|
||||||
printf '\n%s🔐 logicsrc installed.%s Next:\n logicsrc login\n logicsrc teams push <team> prod --env .env\n\n' "$G" "$X"
|
# <team> <project> <env> -- three positionals. Two exits with a usage error.
|
||||||
|
printf '\n%s🔐 logicsrc installed.%s Next:\n logicsrc login\n logicsrc teams push <team> <project> <env>\n\n' "$G" "$X"
|
||||||
}
|
}
|
||||||
|
|
||||||
do_uninstall() {
|
do_uninstall() {
|
||||||
|
|
|
||||||
30
apps/pwa/src/lib/cli-hint.mjs
Normal file
30
apps/pwa/src/lib/cli-hint.mjs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// The "Connect the CLI" card on the teams dashboard.
|
||||||
|
//
|
||||||
|
// It lives here rather than beside the page that renders it because it hands
|
||||||
|
// out commands people paste into a shell, and commands that are wrong are worse
|
||||||
|
// than absent. Keeping it dep-free is what lets a test assert on the rendered
|
||||||
|
// text without standing up express and the database.
|
||||||
|
import { esc } from "./html.mjs";
|
||||||
|
|
||||||
|
// Mirror of DEFAULT_API_URL in plugins/credential-sharing/src/identity.ts. The
|
||||||
|
// CLI already points here on its own, so telling a user to set LOGICSRC_API to
|
||||||
|
// this exact value is a no-op that reads like a required step. Only a
|
||||||
|
// self-hosted origin needs the prefix -- keep the two values in sync.
|
||||||
|
export const CLI_DEFAULT_API = "https://app.logicsrc.com";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} origin - the origin this request arrived on
|
||||||
|
* @returns {string} the card's HTML
|
||||||
|
*/
|
||||||
|
// Vaults are addressed as <team> <project> <env> -- three positionals. Anything
|
||||||
|
// shorter exits with "missing required argument", so a hint that omits one is
|
||||||
|
// not merely stale, it fails on paste. `--env <path>` is the local .env file
|
||||||
|
// and already defaults to .env; spelling it out here only invites confusion
|
||||||
|
// with the <env> positional next to it.
|
||||||
|
export const CLI_HINT = (origin) => `<div class="card" style="margin-bottom:22px"><div class="card-head"><span class="h">Connect the CLI</span><span class="pill on">end-to-end encrypted</span></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="dim" style="margin-top:0;font-size:.9rem">Secrets are encrypted on your machine — decrypt them with the <code>logicsrc</code> CLI, never here.</p>
|
||||||
|
<pre class="mono" style="background:var(--surface-2);border:1px solid var(--line);border-radius:8px;padding:12px;overflow:auto;font-size:.8rem;margin:0">${origin === CLI_DEFAULT_API ? "" : `LOGICSRC_API=${esc(origin)} `}logicsrc login
|
||||||
|
logicsrc teams push <team> <project> <env> # share
|
||||||
|
logicsrc teams pull <team> <project> <env> # receive</pre>
|
||||||
|
</div></div>`;
|
||||||
|
|
@ -8,6 +8,7 @@ import { page, footer, appBar, esc } from "../lib/html.mjs";
|
||||||
import { requireAuth, csrfInput } from "../lib/session.mjs";
|
import { requireAuth, csrfInput } from "../lib/session.mjs";
|
||||||
import { createApiKey, listApiKeys, revokeApiKey } from "../lib/apikey.mjs";
|
import { createApiKey, listApiKeys, revokeApiKey } from "../lib/apikey.mjs";
|
||||||
import { requestOrigin } from "../lib/origin.mjs";
|
import { requestOrigin } from "../lib/origin.mjs";
|
||||||
|
import { CLI_HINT } from "../lib/cli-hint.mjs";
|
||||||
import { config } from "../config.mjs";
|
import { config } from "../config.mjs";
|
||||||
|
|
||||||
export const pagesRouter = Router();
|
export const pagesRouter = Router();
|
||||||
|
|
@ -15,14 +16,6 @@ export const pagesRouter = Router();
|
||||||
// placeholder replaced per-request (teamCard can't see req to render csrfInput)
|
// placeholder replaced per-request (teamCard can't see req to render csrfInput)
|
||||||
const CSRF = "__CSRF__";
|
const CSRF = "__CSRF__";
|
||||||
|
|
||||||
const CLI_HINT = (origin) => `<div class="card" style="margin-bottom:22px"><div class="card-head"><span class="h">Connect the CLI</span><span class="pill on">end-to-end encrypted</span></div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="dim" style="margin-top:0;font-size:.9rem">Secrets are encrypted on your machine — decrypt them with the <code>logicsrc</code> CLI, never here.</p>
|
|
||||||
<pre class="mono" style="background:var(--surface-2);border:1px solid var(--line);border-radius:8px;padding:12px;overflow:auto;font-size:.8rem;margin:0">LOGICSRC_API=${esc(origin)} logicsrc login
|
|
||||||
logicsrc teams push <team> prod --env .env # share
|
|
||||||
logicsrc teams pull <team> prod --env .env # receive</pre>
|
|
||||||
</div></div>`;
|
|
||||||
|
|
||||||
async function teamCard(team, uid) {
|
async function teamCard(team, uid) {
|
||||||
const members = await all(`SELECT * FROM credshare_members WHERE team_id = ? ORDER BY created_at`, [team.id]);
|
const members = await all(`SELECT * FROM credshare_members WHERE team_id = ? ORDER BY created_at`, [team.id]);
|
||||||
const me = members.find((m) => m.user_id === uid);
|
const me = members.find((m) => m.user_id === uid);
|
||||||
|
|
@ -50,7 +43,7 @@ async function teamCard(team, uid) {
|
||||||
<input type="email" name="email" placeholder="teammate@example.com" required style="flex:1"><button class="btn">Invite</button></form>` : ""}
|
<input type="email" name="email" placeholder="teammate@example.com" required style="flex:1"><button class="btn">Invite</button></form>` : ""}
|
||||||
<div class="label" style="margin:18px 0 6px">Vaults</div>
|
<div class="label" style="margin:18px 0 6px">Vaults</div>
|
||||||
${vaults.length ? `<table><thead><tr><th>Vault</th><th>Secrets</th><th>Your access</th></tr></thead><tbody>${vaultRows.join("")}</tbody></table>`
|
${vaults.length ? `<table><thead><tr><th>Vault</th><th>Secrets</th><th>Your access</th></tr></thead><tbody>${vaultRows.join("")}</tbody></table>`
|
||||||
: `<p class="faint mono" style="font-size:.82rem">No vaults yet — create one from the CLI: <code>logicsrc teams push ${esc(team.slug)} prod</code></p>`}
|
: `<p class="faint mono" style="font-size:.82rem">No vaults yet — create one from the CLI: <code>logicsrc teams push ${esc(team.slug)} <project> <env></code></p>`}
|
||||||
</div></div>`;
|
</div></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
51
apps/pwa/test/cli-hint.test.mjs
Normal file
51
apps/pwa/test/cli-hint.test.mjs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
// The dashboard's "Connect the CLI" card kept printing commands that no longer
|
||||||
|
// ran. It survived two releases of drift: `logicsrc teams push <team> prod` is
|
||||||
|
// two positionals, and since vaults became <team> <project> <env> the CLI exits
|
||||||
|
// with a usage error on paste. It also told everyone to set LOGICSRC_API to the
|
||||||
|
// value the CLI already defaults to, which reads like a required step.
|
||||||
|
//
|
||||||
|
// A card that hands out commands is only useful if the commands run, so these
|
||||||
|
// pin the shape rather than the prose -- restyling the card is free, quietly
|
||||||
|
// dropping an argument is not.
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
|
||||||
|
import { CLI_HINT } from "../src/lib/cli-hint.mjs";
|
||||||
|
|
||||||
|
/** The commands themselves, with the entities decoded back to real syntax. */
|
||||||
|
const commands = (origin) =>
|
||||||
|
CLI_HINT(origin)
|
||||||
|
.match(/margin:0">([\s\S]*?)<\/pre>/)[1]
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.split("\n");
|
||||||
|
|
||||||
|
const HOSTED = "https://app.logicsrc.com";
|
||||||
|
|
||||||
|
test("push and pull carry all three vault positionals", () => {
|
||||||
|
for (const verb of ["push", "pull"]) {
|
||||||
|
const line = commands(HOSTED).find((l) => l.includes(`teams ${verb}`));
|
||||||
|
assert.ok(line, `no teams ${verb} line`);
|
||||||
|
assert.match(line, /teams (push|pull) <team> <project> <env>/);
|
||||||
|
// Guards the specific regression: two positionals used to be enough.
|
||||||
|
// Drop "logicsrc teams <verb>" and count only what follows.
|
||||||
|
const args = line.split("#")[0].trim().split(/\s+/).slice(3);
|
||||||
|
assert.equal(args.length, 3, `teams ${verb} needs 3 args, got ${args.join(" ")}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the local .env path is left at its default", () => {
|
||||||
|
// `--env <path>` defaults to .env in the CLI. Spelling it out next to the
|
||||||
|
// <env> positional made two unrelated things look like one.
|
||||||
|
assert.ok(!commands(HOSTED).some((l) => l.includes("--env")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the hosted origin needs no LOGICSRC_API prefix", () => {
|
||||||
|
const login = commands(HOSTED).find((l) => l.includes("logicsrc login"));
|
||||||
|
assert.equal(login, "logicsrc login");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a self-hosted origin still gets the prefix", () => {
|
||||||
|
const login = commands("http://localhost:8080").find((l) => l.includes("logicsrc login"));
|
||||||
|
assert.equal(login, "LOGICSRC_API=http://localhost:8080 logicsrc login");
|
||||||
|
});
|
||||||
|
|
@ -308,7 +308,7 @@ export async function teamsInviteAction(slug: string, email: string, options: {
|
||||||
export async function teamsAcceptAction(token: string, format: OutputFormat): Promise<void> {
|
export async function teamsAcceptAction(token: string, format: OutputFormat): Promise<void> {
|
||||||
const { client } = authedClient();
|
const { client } = authedClient();
|
||||||
const result = await client.acceptInvite(token);
|
const result = await client.acceptInvite(token);
|
||||||
console.error(`Joined ${result.team?.slug ?? "team"}. Ask a member to grant you a vault, then: logicsrc teams pull <team> <vault>`);
|
console.error(`Joined ${result.team?.slug ?? "team"}. Ask a member to grant you a vault, then: logicsrc teams pull <team> <project> <env>`);
|
||||||
print({ joined: result.team?.slug ?? null }, format);
|
print({ joined: result.team?.slug ?? null }, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue