stack/examples/demo/cli-sim.mjs
BilalG1 59547ef4ec
Detect conflicting Hexclave and Stack env vars (#1604)
Summary: Detects conflicting non-empty HEXCLAVE_* and STACK_* values
across shared env helpers, dashboard public envs, generated SDK env
access, Docker scripts, CLI/docs/examples, and related tests.
Verification: pnpm test run packages/shared/src/utils/env.test.tsx
apps/dashboard/src/lib/env.test.tsx packages/cli/src/lib/auth.test.ts;
targeted lint/typecheck across touched workspaces; bash -n/node --check
for changed scripts; node
docker/local-emulator/generate-env-development.mjs --check.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Detects and blocks conflicting `HEXCLAVE_*` and `STACK_*` env vars
across the monorepo. Prefers `HEXCLAVE_*`, falls back to `STACK_*` when
empty, and fails fast when both are set to different values.

- **New Features**
- Added conflict-aware env resolvers used across apps, CLI, docs,
examples, and Docker (build/runtime).
- Validates critical vars (e.g., database connection, API/dashboard
URLs, emulator flags, tokens) and ignores post-build sentinel values.
- Prisma, Next.js, and Docker startup now error on mismatched values;
CLI enforces project ID/key conflicts; tests added.

- **Migration**
- If both names are set with different values, builds/tests/scripts will
error. Set only `HEXCLAVE_*` or make both equal.
- Update `.env`, CI secrets, and Docker envs to use `HEXCLAVE_*`. Keep
`STACK_*` only as a temporary fallback.

<sup>Written for commit 4d63fa3bad.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1604?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-06-16 10:57:59 -07:00

99 lines
3.2 KiB
JavaScript

#!/usr/bin/env node
/** Minimal `stack login` flow for local demos. Usage: `node cli-sim.mjs` */
function resolveHexclaveStackEnvVar(hexclaveName, stackName) {
const hexclaveValue = process.env[hexclaveName];
const stackValue = process.env[stackName];
if (hexclaveValue && stackValue && hexclaveValue !== stackValue) {
throw new Error(`Environment variables ${hexclaveName} and ${stackName} are both set to different values. Remove one of them or set them to the same value.`);
}
return hexclaveValue || stackValue || undefined;
}
const API_URL = resolveHexclaveStackEnvVar("HEXCLAVE_API_URL", "STACK_API_URL") || "http://localhost:8102";
const APP_URL = resolveHexclaveStackEnvVar("HEXCLAVE_APP_URL", "STACK_APP_URL") || "http://localhost:8103";
const PROJECT_ID = "internal";
const PUBLISHABLE_KEY = "this-publishable-client-key-is-for-local-development-only";
const headers = {
"Content-Type": "application/json",
"x-stack-access-type": "client",
"x-stack-project-id": PROJECT_ID,
"x-stack-publishable-client-key": PUBLISHABLE_KEY,
};
async function main() {
console.log("=== Hexclave CLI Simulator ===\n");
console.log(`API: ${API_URL}`);
console.log(`App: ${APP_URL}\n`);
console.log("Initiating CLI auth...");
const initRes = await fetch(`${API_URL}/api/v1/auth/cli`, {
method: "POST",
headers,
body: JSON.stringify({
expires_in_millis: 1000 * 60 * 10,
}),
});
if (!initRes.ok) {
console.error(`Failed to initiate: ${initRes.status} ${await initRes.text()}`);
process.exit(1);
}
const { polling_code, login_code, expires_at } = await initRes.json();
console.log(`\n${"=".repeat(40)}`);
console.log(` Verification Code: ${login_code}`);
console.log(`${"=".repeat(40)}\n`);
console.log(`Open this URL in your browser:\n`);
console.log(` ${APP_URL}/handler/cli-auth-confirm?login_code=${encodeURIComponent(login_code)}\n`);
console.log(`Expires: ${new Date(expires_at).toLocaleTimeString()}`);
console.log(`\nWaiting for browser authorization...`);
const POLL_INTERVAL = 2000;
let attempts = 0;
while (true) {
attempts++;
const pollRes = await fetch(`${API_URL}/api/v1/auth/cli/poll`, {
method: "POST",
headers,
body: JSON.stringify({ polling_code }),
});
if (!pollRes.ok) {
console.error(`Poll failed: ${pollRes.status} ${await pollRes.text()}`);
process.exit(1);
}
const result = await pollRes.json();
if (result.status === "success") {
console.log(`\nLogin successful! (after ${attempts} poll attempts)`);
console.log(`Refresh token: ${result.refresh_token.slice(0, 20)}...`);
console.log("\nIn a real CLI, this token would be saved to ~/.config/hexclave/credentials.json");
break;
}
if (result.status === "expired") {
console.error("\nAuth session expired. Please try again.");
process.exit(1);
}
if (result.status === "used") {
console.error("\nThis auth token was already used.");
process.exit(1);
}
process.stdout.write(".");
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
}
}
main().catch((err) => {
console.error("Error:", err);
process.exit(1);
});