fix: resolve smoke test failures -- CLI JSON output, port collision, stale DBs

This commit is contained in:
Matt Kane
2026-04-02 15:30:36 +01:00
parent 01af46fb83
commit 8e28cfc5d6
12 changed files with 64 additions and 9 deletions

View File

@@ -149,7 +149,8 @@
"prepublishOnly": "node --run build",
"typecheck": "tsgo --noEmit",
"check": "publint && attw --pack --ignore-rules=cjs-resolves-to-esm --ignore-rules=no-resolution --ignore-rules=internal-resolution-error",
"test": "vitest"
"test": "vitest",
"test:smoke": "vitest run --config vitest.smoke.config.ts"
},
"dependencies": {
"@emdash-cms/admin": "workspace:*",

View File

@@ -10,7 +10,7 @@ import { defineCommand } from "citty";
import { consola } from "consola";
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
import { output } from "../output.js";
import { configureOutputMode, output } from "../output.js";
// ---------------------------------------------------------------------------
// Helpers
@@ -77,6 +77,7 @@ const listCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const result = await client.list(args.collection, {
@@ -130,6 +131,7 @@ const getCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const item = await client.get(args.collection, args.id, {
@@ -177,6 +179,7 @@ const createCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const data = await readInputData(args);
const client = createClientFromArgs(args);
@@ -229,6 +232,7 @@ const updateCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const data = await readInputData(args);
const client = createClientFromArgs(args);
@@ -270,6 +274,7 @@ const deleteCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
await client.delete(args.collection, args.id);
@@ -297,6 +302,7 @@ const publishCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
await client.publish(args.collection, args.id);
@@ -324,6 +330,7 @@ const unpublishCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
await client.unpublish(args.collection, args.id);
@@ -356,6 +363,7 @@ const scheduleCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
await client.schedule(args.collection, args.id, { at: args.at });
@@ -383,6 +391,7 @@ const restoreCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
await client.restore(args.collection, args.id);
@@ -410,6 +419,7 @@ const translationsCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const translations = await client.translations(args.collection, args.id);

View File

@@ -29,6 +29,7 @@ import {
resolveCredentialKey,
saveCredentials,
} from "../credentials.js";
import { configureOutputMode } from "../output.js";
// ---------------------------------------------------------------------------
// Types for discovery + device flow responses
@@ -423,6 +424,7 @@ export const whoamiCommand = defineCommand({
},
},
async run({ args }) {
configureOutputMode(args);
const baseUrl = args.url || "http://localhost:4321";
// Resolve token: --token flag > EMDASH_TOKEN env > stored credentials

View File

@@ -11,7 +11,7 @@ import { defineCommand } from "citty";
import { consola } from "consola";
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
import { output } from "../output.js";
import { configureOutputMode, output } from "../output.js";
const listCommand = defineCommand({
meta: {
@@ -34,6 +34,7 @@ const listCommand = defineCommand({
},
},
async run({ args }) {
configureOutputMode(args);
const client = createClientFromArgs(args);
try {
@@ -73,6 +74,7 @@ const uploadCommand = defineCommand({
},
},
async run({ args }) {
configureOutputMode(args);
const client = createClientFromArgs(args);
const filename = basename(args.file);
@@ -108,6 +110,7 @@ const getCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
const client = createClientFromArgs(args);
try {
@@ -134,6 +137,7 @@ const deleteCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
const client = createClientFromArgs(args);
try {

View File

@@ -8,7 +8,7 @@ import { defineCommand } from "citty";
import { consola } from "consola";
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
import { output } from "../output.js";
import { configureOutputMode, output } from "../output.js";
const listCommand = defineCommand({
meta: {
@@ -19,6 +19,7 @@ const listCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const menus = await client.menus();
@@ -44,6 +45,7 @@ const getCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const menu = await client.menu(args.name);

View File

@@ -8,7 +8,7 @@ import { defineCommand } from "citty";
import { consola } from "consola";
import { connectionArgs as commonArgs, createClientFromArgs } from "../client-factory.js";
import { output } from "../output.js";
import { configureOutputMode, output } from "../output.js";
const listCommand = defineCommand({
meta: {
@@ -19,6 +19,7 @@ const listCommand = defineCommand({
...commonArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const collections = await client.collections();
@@ -44,6 +45,7 @@ const getCommand = defineCommand({
...commonArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const collection = await client.collection(args.collection);
@@ -82,6 +84,7 @@ const createCommand = defineCommand({
...commonArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const data = await client.createCollection({
@@ -117,6 +120,7 @@ const deleteCommand = defineCommand({
...commonArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
if (!args.force) {
const confirmed = await consola.prompt(`Delete collection "${args.collection}"?`, {
@@ -170,6 +174,7 @@ const addFieldCommand = defineCommand({
...commonArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const data = await client.createField(args.collection, {
@@ -206,6 +211,7 @@ const removeFieldCommand = defineCommand({
...commonArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
await client.deleteField(args.collection, args.field);

View File

@@ -8,7 +8,7 @@ import { defineCommand } from "citty";
import { consola } from "consola";
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
import { output } from "../output.js";
import { configureOutputMode, output } from "../output.js";
export const searchCommand = defineCommand({
meta: {
@@ -38,6 +38,7 @@ export const searchCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const results = await client.search(args.query, {

View File

@@ -8,7 +8,7 @@ import { defineCommand } from "citty";
import { consola } from "consola";
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
import { output } from "../output.js";
import { configureOutputMode, output } from "../output.js";
/** Pattern to replace whitespace with hyphens for slug generation */
const WHITESPACE_PATTERN = /\s+/g;
@@ -22,6 +22,7 @@ const listCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const taxonomies = await client.taxonomies();
@@ -56,6 +57,7 @@ const termsCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const result = await client.terms(args.name, {
@@ -97,6 +99,7 @@ const addTermCommand = defineCommand({
...connectionArgs,
},
async run({ args }) {
configureOutputMode(args);
try {
const client = createClientFromArgs(args);
const label = args.name;

View File

@@ -4,6 +4,20 @@ interface OutputArgs {
json?: boolean;
}
/**
* Redirect consola output to stderr so it doesn't pollute JSON on stdout.
*
* Call this early in any command that uses `output()` with `--json`.
* Safe to call multiple times — only applies the redirect once.
*/
export function configureOutputMode(args: OutputArgs): void {
if (args.json || !process.stdout.isTTY) {
// Send all consola output to stderr so stdout is clean JSON
consola.options.stdout = process.stderr;
consola.options.stderr = process.stderr;
}
}
/**
* Output data as JSON or pretty-printed.
*

View File

@@ -14,7 +14,7 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest";
import type { TestServerContext } from "../server.js";
import { assertNodeVersion, createTestServer } from "../server.js";
const PORT = 4398;
const PORT = 4396;
const TIMEOUT = 60_000;
/** Helper: raw fetch with auth headers */

View File

@@ -1,5 +1,6 @@
import { execFile, spawn } from "node:child_process";
import { resolve } from "node:path";
import { rmSync } from "node:fs";
import { join, resolve } from "node:path";
import { promisify } from "node:util";
import { describe, expect, it } from "vitest";
@@ -203,6 +204,12 @@ describe.sequential("Site smoke matrix", () => {
async () => {
await ensureBuilt();
// Remove stale database files so each run starts fresh.
// SQLite demos use data.db; WAL/SHM sidecars may also exist.
for (const file of ["data.db", "data.db-wal", "data.db-shm"]) {
rmSync(join(site.dir, file), { force: true });
}
const baseUrl = `http://localhost:${site.port}`;
const serverProcess = spawn("pnpm", ["exec", "astro", "dev", "--port", String(site.port)], {
cwd: site.dir,