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",
|
"prepublishOnly": "node --run build",
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
"check": "publint && attw --pack --ignore-rules=cjs-resolves-to-esm --ignore-rules=no-resolution --ignore-rules=internal-resolution-error",
|
"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": {
|
"dependencies": {
|
||||||
"@emdash-cms/admin": "workspace:*",
|
"@emdash-cms/admin": "workspace:*",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { defineCommand } from "citty";
|
|||||||
import { consola } from "consola";
|
import { consola } from "consola";
|
||||||
|
|
||||||
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
||||||
import { output } from "../output.js";
|
import { configureOutputMode, output } from "../output.js";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Helpers
|
// Helpers
|
||||||
@@ -77,6 +77,7 @@ const listCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const result = await client.list(args.collection, {
|
const result = await client.list(args.collection, {
|
||||||
@@ -130,6 +131,7 @@ const getCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const item = await client.get(args.collection, args.id, {
|
const item = await client.get(args.collection, args.id, {
|
||||||
@@ -177,6 +179,7 @@ const createCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const data = await readInputData(args);
|
const data = await readInputData(args);
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
@@ -229,6 +232,7 @@ const updateCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const data = await readInputData(args);
|
const data = await readInputData(args);
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
@@ -270,6 +274,7 @@ const deleteCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
await client.delete(args.collection, args.id);
|
await client.delete(args.collection, args.id);
|
||||||
@@ -297,6 +302,7 @@ const publishCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
await client.publish(args.collection, args.id);
|
await client.publish(args.collection, args.id);
|
||||||
@@ -324,6 +330,7 @@ const unpublishCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
await client.unpublish(args.collection, args.id);
|
await client.unpublish(args.collection, args.id);
|
||||||
@@ -356,6 +363,7 @@ const scheduleCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
await client.schedule(args.collection, args.id, { at: args.at });
|
await client.schedule(args.collection, args.id, { at: args.at });
|
||||||
@@ -383,6 +391,7 @@ const restoreCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
await client.restore(args.collection, args.id);
|
await client.restore(args.collection, args.id);
|
||||||
@@ -410,6 +419,7 @@ const translationsCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const translations = await client.translations(args.collection, args.id);
|
const translations = await client.translations(args.collection, args.id);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
resolveCredentialKey,
|
resolveCredentialKey,
|
||||||
saveCredentials,
|
saveCredentials,
|
||||||
} from "../credentials.js";
|
} from "../credentials.js";
|
||||||
|
import { configureOutputMode } from "../output.js";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Types for discovery + device flow responses
|
// Types for discovery + device flow responses
|
||||||
@@ -423,6 +424,7 @@ export const whoamiCommand = defineCommand({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
const baseUrl = args.url || "http://localhost:4321";
|
const baseUrl = args.url || "http://localhost:4321";
|
||||||
|
|
||||||
// Resolve token: --token flag > EMDASH_TOKEN env > stored credentials
|
// Resolve token: --token flag > EMDASH_TOKEN env > stored credentials
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { defineCommand } from "citty";
|
|||||||
import { consola } from "consola";
|
import { consola } from "consola";
|
||||||
|
|
||||||
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
||||||
import { output } from "../output.js";
|
import { configureOutputMode, output } from "../output.js";
|
||||||
|
|
||||||
const listCommand = defineCommand({
|
const listCommand = defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
@@ -34,6 +34,7 @@ const listCommand = defineCommand({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -73,6 +74,7 @@ const uploadCommand = defineCommand({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const filename = basename(args.file);
|
const filename = basename(args.file);
|
||||||
|
|
||||||
@@ -108,6 +110,7 @@ const getCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -134,6 +137,7 @@ const deleteCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { defineCommand } from "citty";
|
|||||||
import { consola } from "consola";
|
import { consola } from "consola";
|
||||||
|
|
||||||
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
||||||
import { output } from "../output.js";
|
import { configureOutputMode, output } from "../output.js";
|
||||||
|
|
||||||
const listCommand = defineCommand({
|
const listCommand = defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
@@ -19,6 +19,7 @@ const listCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const menus = await client.menus();
|
const menus = await client.menus();
|
||||||
@@ -44,6 +45,7 @@ const getCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const menu = await client.menu(args.name);
|
const menu = await client.menu(args.name);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { defineCommand } from "citty";
|
|||||||
import { consola } from "consola";
|
import { consola } from "consola";
|
||||||
|
|
||||||
import { connectionArgs as commonArgs, createClientFromArgs } from "../client-factory.js";
|
import { connectionArgs as commonArgs, createClientFromArgs } from "../client-factory.js";
|
||||||
import { output } from "../output.js";
|
import { configureOutputMode, output } from "../output.js";
|
||||||
|
|
||||||
const listCommand = defineCommand({
|
const listCommand = defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
@@ -19,6 +19,7 @@ const listCommand = defineCommand({
|
|||||||
...commonArgs,
|
...commonArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const collections = await client.collections();
|
const collections = await client.collections();
|
||||||
@@ -44,6 +45,7 @@ const getCommand = defineCommand({
|
|||||||
...commonArgs,
|
...commonArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const collection = await client.collection(args.collection);
|
const collection = await client.collection(args.collection);
|
||||||
@@ -82,6 +84,7 @@ const createCommand = defineCommand({
|
|||||||
...commonArgs,
|
...commonArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const data = await client.createCollection({
|
const data = await client.createCollection({
|
||||||
@@ -117,6 +120,7 @@ const deleteCommand = defineCommand({
|
|||||||
...commonArgs,
|
...commonArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
if (!args.force) {
|
if (!args.force) {
|
||||||
const confirmed = await consola.prompt(`Delete collection "${args.collection}"?`, {
|
const confirmed = await consola.prompt(`Delete collection "${args.collection}"?`, {
|
||||||
@@ -170,6 +174,7 @@ const addFieldCommand = defineCommand({
|
|||||||
...commonArgs,
|
...commonArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const data = await client.createField(args.collection, {
|
const data = await client.createField(args.collection, {
|
||||||
@@ -206,6 +211,7 @@ const removeFieldCommand = defineCommand({
|
|||||||
...commonArgs,
|
...commonArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
await client.deleteField(args.collection, args.field);
|
await client.deleteField(args.collection, args.field);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { defineCommand } from "citty";
|
|||||||
import { consola } from "consola";
|
import { consola } from "consola";
|
||||||
|
|
||||||
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
||||||
import { output } from "../output.js";
|
import { configureOutputMode, output } from "../output.js";
|
||||||
|
|
||||||
export const searchCommand = defineCommand({
|
export const searchCommand = defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
@@ -38,6 +38,7 @@ export const searchCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const results = await client.search(args.query, {
|
const results = await client.search(args.query, {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { defineCommand } from "citty";
|
|||||||
import { consola } from "consola";
|
import { consola } from "consola";
|
||||||
|
|
||||||
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
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 */
|
/** Pattern to replace whitespace with hyphens for slug generation */
|
||||||
const WHITESPACE_PATTERN = /\s+/g;
|
const WHITESPACE_PATTERN = /\s+/g;
|
||||||
@@ -22,6 +22,7 @@ const listCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const taxonomies = await client.taxonomies();
|
const taxonomies = await client.taxonomies();
|
||||||
@@ -56,6 +57,7 @@ const termsCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const result = await client.terms(args.name, {
|
const result = await client.terms(args.name, {
|
||||||
@@ -97,6 +99,7 @@ const addTermCommand = defineCommand({
|
|||||||
...connectionArgs,
|
...connectionArgs,
|
||||||
},
|
},
|
||||||
async run({ args }) {
|
async run({ args }) {
|
||||||
|
configureOutputMode(args);
|
||||||
try {
|
try {
|
||||||
const client = createClientFromArgs(args);
|
const client = createClientFromArgs(args);
|
||||||
const label = args.name;
|
const label = args.name;
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ interface OutputArgs {
|
|||||||
json?: boolean;
|
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.
|
* 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 type { TestServerContext } from "../server.js";
|
||||||
import { assertNodeVersion, createTestServer } from "../server.js";
|
import { assertNodeVersion, createTestServer } from "../server.js";
|
||||||
|
|
||||||
const PORT = 4398;
|
const PORT = 4396;
|
||||||
const TIMEOUT = 60_000;
|
const TIMEOUT = 60_000;
|
||||||
|
|
||||||
/** Helper: raw fetch with auth headers */
|
/** Helper: raw fetch with auth headers */
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { execFile, spawn } from "node:child_process";
|
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 { promisify } from "node:util";
|
||||||
|
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
@@ -203,6 +204,12 @@ describe.sequential("Site smoke matrix", () => {
|
|||||||
async () => {
|
async () => {
|
||||||
await ensureBuilt();
|
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 baseUrl = `http://localhost:${site.port}`;
|
||||||
const serverProcess = spawn("pnpm", ["exec", "astro", "dev", "--port", String(site.port)], {
|
const serverProcess = spawn("pnpm", ["exec", "astro", "dev", "--port", String(site.port)], {
|
||||||
cwd: site.dir,
|
cwd: site.dir,
|
||||||
|
|||||||
Reference in New Issue
Block a user