stack/packages/stack-cli/src/commands/config-file.ts
BilalG1 57149bd84b
Stack CLI (#1227)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Added Stack CLI with authentication (login/logout) commands.
  * Added project management commands to list and create projects.
  * Added configuration management to pull and push project settings.
  * Added code execution capability to run JavaScript expressions.
  * Added initialization command for Stack Auth setup.

* **Tests**
  * Added comprehensive end-to-end test suite for CLI functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-03-09 13:24:15 -07:00

85 lines
2.8 KiB
TypeScript

import { Command } from "commander";
import * as path from "path";
import * as fs from "fs";
import { resolveAuth } from "../lib/auth.js";
import { getAdminProject } from "../lib/app.js";
import { CliError } from "../lib/errors.js";
function isPlainObject(value: unknown): value is Record<string, unknown> {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
return false;
}
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
export function registerConfigCommand(program: Command) {
const config = program
.command("config")
.description("Manage project configuration files");
config
.command("pull")
.description("Pull branch config to a local file")
.requiredOption("--config-file <path>", "Path to write config file (.js or .ts)")
.action(async (opts) => {
const flags = program.opts();
const auth = resolveAuth(flags);
const project = await getAdminProject(auth);
const configOverride = await project.getConfigOverride("branch");
const filePath = path.resolve(opts.configFile);
const ext = path.extname(filePath);
if (ext !== ".js" && ext !== ".ts") {
throw new CliError("Config file must have a .js or .ts extension.");
}
const json = JSON.stringify(configOverride, null, 2);
const content = ext === ".ts"
? `export const config = ${json} as const;\n`
: `export const config = ${json};\n`;
fs.writeFileSync(filePath, content);
console.log(`Config written to ${filePath}`);
});
config
.command("push")
.description("Push a local config file to branch config")
.requiredOption("--config-file <path>", "Path to config file (.js or .ts)")
.action(async (opts) => {
const flags = program.opts();
const auth = resolveAuth(flags);
const project = await getAdminProject(auth);
const filePath = path.resolve(opts.configFile);
const ext = path.extname(filePath);
if (ext !== ".js" && ext !== ".ts") {
throw new CliError("Config file must have a .js or .ts extension.");
}
if (!fs.existsSync(filePath)) {
throw new CliError(`Config file not found: ${filePath}`);
}
let configModule: { config?: unknown };
if (ext === ".ts") {
const { createJiti } = await import("jiti");
const jiti = createJiti(import.meta.url);
configModule = await jiti.import(filePath);
} else {
configModule = await import(filePath);
}
const config = configModule.config;
if (!isPlainObject(config)) {
throw new CliError("Config file must export a plain `config` object. Example: export const config = { ... };");
}
await project.replaceConfigOverride("branch", config);
console.log("Config pushed successfully.");
});
}