Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/logicsrc-web/public/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ EOF
*":$LOGICSRC_BIN:"*) : ;;
*) warn "add $LOGICSRC_BIN to your PATH: export PATH=\"$LOGICSRC_BIN:\$PATH\"";;
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() {
Expand Down
30 changes: 30 additions & 0 deletions apps/pwa/src/lib/cli-hint.mjs
Original file line number Diff line number Diff line change
@@ -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 &lt;team&gt; &lt;project&gt; &lt;env&gt; # share
logicsrc teams pull &lt;team&gt; &lt;project&gt; &lt;env&gt; # receive</pre>
</div></div>`;
11 changes: 2 additions & 9 deletions apps/pwa/src/routes/pages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,14 @@ import { page, footer, appBar, esc } from "../lib/html.mjs";
import { requireAuth, csrfInput } from "../lib/session.mjs";
import { createApiKey, listApiKeys, revokeApiKey } from "../lib/apikey.mjs";
import { requestOrigin } from "../lib/origin.mjs";
import { CLI_HINT } from "../lib/cli-hint.mjs";
import { config } from "../config.mjs";

export const pagesRouter = Router();

// placeholder replaced per-request (teamCard can't see req to render csrfInput)
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 &lt;team&gt; prod --env .env # share
logicsrc teams pull &lt;team&gt; prod --env .env # receive</pre>
</div></div>`;

async function teamCard(team, uid) {
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);
Expand Down Expand Up @@ -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>` : ""}
<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>`
: `<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)} &lt;project&gt; &lt;env&gt;</code></p>`}
</div></div>`;
}

Expand Down
51 changes: 51 additions & 0 deletions apps/pwa/test/cli-hint.test.mjs
Original file line number Diff line number Diff line change
@@ -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(/&lt;/g, "<")
.replace(/&gt;/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");
});
2 changes: 1 addition & 1 deletion packages/cli/src/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export async function teamsInviteAction(slug: string, email: string, options: {
export async function teamsAcceptAction(token: string, format: OutputFormat): Promise<void> {
const { client } = authedClient();
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);
}

Expand Down
Loading