Merge pull request #105 from emdash-cms/fix/smoke-test-failures
fix: resolve smoke test failures -- CLI JSON output, port collision, stale DBs
This commit is contained in:
5
.changeset/six-pugs-juggle.md
Normal file
5
.changeset/six-pugs-juggle.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"emdash": patch
|
||||
---
|
||||
|
||||
Fix CLI `--json` flag so JSON output is clean. Previously, `consola.success()` and other log messages leaked into stdout alongside the JSON data, making it unparseable by scripts. Log messages now go to stderr when `--json` is set.
|
||||
@@ -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:*",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -9,5 +9,11 @@ export default defineConfig({
|
||||
"tests/integration/cli/**/*.test.ts",
|
||||
"tests/integration/client/**/*.test.ts",
|
||||
],
|
||||
// Smoke tests boot real Astro dev servers in beforeAll hooks.
|
||||
// Default hookTimeout (10s) is too short -- server startup +
|
||||
// migrations + seed can take 30-60s, especially on first run
|
||||
// when pnpm build hasn't been cached.
|
||||
testTimeout: 30_000,
|
||||
hookTimeout: 120_000,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user