mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
fix(ci): resolve flaky api-command module resolution and migrate to self-hosted runner
Root cause: Bun's test runner fails to resolve exports from directory index.ts files during parallel test execution. The api-command/index.ts imports 8 subcommand modules — if any fail to load during concurrent test runs, the entire module fails with "Export not found." Fix: extract createApiCommandHandler into handler.ts (lightweight, only imports named-command-router). Test imports from handler.ts directly, bypassing the heavy index.ts that loads all subcommand modules. Also: - Remove stale api-command.ts barrel file - Use explicit /index import paths in source - Migrate CI validate to self-hosted runner with clean artifact step - Disable Bun cache (no-cache: true) on self-hosted runner
This commit is contained in:
@@ -6,16 +6,20 @@ on:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, linux, x64]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Clean stale artifacts
|
||||
run: rm -rf node_modules ui/node_modules dist
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.3.9'
|
||||
no-cache: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './api-command/index';
|
||||
@@ -0,0 +1,51 @@
|
||||
import { dispatchNamedCommand, type NamedCommandRoute } from '../named-command-router';
|
||||
|
||||
type ApiCommandHandler = (args: string[]) => Promise<void>;
|
||||
type ApiCommandHelpHandler = () => Promise<void>;
|
||||
type ApiCommandUnknownHandler = (command: string) => Promise<void>;
|
||||
|
||||
export interface ApiCommandDependencies {
|
||||
help: ApiCommandHelpHandler;
|
||||
unknown: ApiCommandUnknownHandler;
|
||||
create: ApiCommandHandler;
|
||||
list: ApiCommandHandler;
|
||||
discover: ApiCommandHandler;
|
||||
copy: ApiCommandHandler;
|
||||
export: ApiCommandHandler;
|
||||
import: ApiCommandHandler;
|
||||
remove: ApiCommandHandler;
|
||||
}
|
||||
|
||||
function createApiCommandRoutes(
|
||||
dependencies: ApiCommandDependencies
|
||||
): readonly NamedCommandRoute[] {
|
||||
return [
|
||||
{ name: 'create', handle: dependencies.create },
|
||||
{ name: 'list', handle: dependencies.list },
|
||||
{ name: 'discover', handle: dependencies.discover },
|
||||
{ name: 'copy', handle: dependencies.copy },
|
||||
{ name: 'export', handle: dependencies.export },
|
||||
{ name: 'import', handle: dependencies.import },
|
||||
{ name: 'remove', aliases: ['delete', 'rm'], handle: dependencies.remove },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for building an api-command handler with injectable dependencies.
|
||||
* Extracted from index.ts so tests can import without loading all subcommand modules.
|
||||
*/
|
||||
export function createApiCommandHandler(
|
||||
dependencies: ApiCommandDependencies
|
||||
): (args: string[]) => Promise<void> {
|
||||
const routes = createApiCommandRoutes(dependencies);
|
||||
|
||||
return async (args: string[]) => {
|
||||
await dispatchNamedCommand({
|
||||
args,
|
||||
routes,
|
||||
onHelp: dependencies.help,
|
||||
onUnknown: dependencies.unknown,
|
||||
allowEmptyHelp: true,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,31 +1,16 @@
|
||||
import { dispatchNamedCommand, type NamedCommandRoute } from '../named-command-router';
|
||||
import { handleApiCopyCommand } from './copy-command';
|
||||
import { handleApiCreateCommand } from './create-command';
|
||||
import { handleApiDiscoverCommand } from './discover-command';
|
||||
import { handleApiExportCommand } from './export-command';
|
||||
import { createApiCommandHandler, type ApiCommandDependencies } from './handler';
|
||||
import { showApiCommandHelp, showUnknownApiCommand } from './help';
|
||||
import { handleApiImportCommand } from './import-command';
|
||||
import { handleApiListCommand } from './list-command';
|
||||
import { handleApiRemoveCommand } from './remove-command';
|
||||
|
||||
export { createApiCommandHandler, type ApiCommandDependencies } from './handler';
|
||||
export { parseApiCommandArgs } from './shared';
|
||||
|
||||
type ApiCommandHandler = (args: string[]) => Promise<void>;
|
||||
type ApiCommandHelpHandler = () => Promise<void>;
|
||||
type ApiCommandUnknownHandler = (command: string) => Promise<void>;
|
||||
|
||||
export interface ApiCommandDependencies {
|
||||
help: ApiCommandHelpHandler;
|
||||
unknown: ApiCommandUnknownHandler;
|
||||
create: ApiCommandHandler;
|
||||
list: ApiCommandHandler;
|
||||
discover: ApiCommandHandler;
|
||||
copy: ApiCommandHandler;
|
||||
export: ApiCommandHandler;
|
||||
import: ApiCommandHandler;
|
||||
remove: ApiCommandHandler;
|
||||
}
|
||||
|
||||
const DEFAULT_API_COMMAND_DEPENDENCIES: ApiCommandDependencies = {
|
||||
help: showApiCommandHelp,
|
||||
unknown: showUnknownApiCommand,
|
||||
@@ -38,40 +23,6 @@ const DEFAULT_API_COMMAND_DEPENDENCIES: ApiCommandDependencies = {
|
||||
remove: handleApiRemoveCommand,
|
||||
};
|
||||
|
||||
function createApiCommandRoutes(
|
||||
dependencies: ApiCommandDependencies
|
||||
): readonly NamedCommandRoute[] {
|
||||
return [
|
||||
{ name: 'create', handle: dependencies.create },
|
||||
{ name: 'list', handle: dependencies.list },
|
||||
{ name: 'discover', handle: dependencies.discover },
|
||||
{ name: 'copy', handle: dependencies.copy },
|
||||
{ name: 'export', handle: dependencies.export },
|
||||
{ name: 'import', handle: dependencies.import },
|
||||
{ name: 'remove', aliases: ['delete', 'rm'], handle: dependencies.remove },
|
||||
];
|
||||
}
|
||||
|
||||
export function createApiCommandHandler(
|
||||
overrides: Partial<ApiCommandDependencies> = {}
|
||||
): (args: string[]) => Promise<void> {
|
||||
const dependencies: ApiCommandDependencies = {
|
||||
...DEFAULT_API_COMMAND_DEPENDENCIES,
|
||||
...overrides,
|
||||
};
|
||||
const routes = createApiCommandRoutes(dependencies);
|
||||
|
||||
return async (args: string[]) => {
|
||||
await dispatchNamedCommand({
|
||||
args,
|
||||
routes,
|
||||
onHelp: dependencies.help,
|
||||
onUnknown: dependencies.unknown,
|
||||
allowEmptyHelp: true,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleApiCommand(args: string[]): Promise<void> {
|
||||
await createApiCommandHandler()(args);
|
||||
await createApiCommandHandler(DEFAULT_API_COMMAND_DEPENDENCIES)(args);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Commands module barrel export
|
||||
*/
|
||||
|
||||
export { handleApiCommand } from './api-command';
|
||||
export { handleApiCommand } from './api-command/index';
|
||||
export { handleCleanupCommand } from './cleanup-command';
|
||||
export { handleCliproxyCommand } from './cliproxy-command';
|
||||
export { handleConfigCommand } from './config-command';
|
||||
|
||||
@@ -118,7 +118,7 @@ const ROOT_COMMAND_ROUTES: readonly NamedCommandRoute[] = [
|
||||
{
|
||||
name: 'api',
|
||||
handle: async (args) => {
|
||||
const { handleApiCommand } = await import('./api-command');
|
||||
const { handleApiCommand } = await import('./api-command/index');
|
||||
await handleApiCommand(args);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it } from 'bun:test';
|
||||
|
||||
import { createApiCommandHandler } from '../../../src/commands/api-command';
|
||||
import { createApiCommandHandler } from '../../../src/commands/api-command/handler';
|
||||
|
||||
let calls: string[] = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user