mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 16:19:27 +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:
|
jobs:
|
||||||
validate:
|
validate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: [self-hosted, linux, x64]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Clean stale artifacts
|
||||||
|
run: rm -rf node_modules ui/node_modules dist
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: '1.3.9'
|
bun-version: '1.3.9'
|
||||||
|
no-cache: true
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
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 { handleApiCopyCommand } from './copy-command';
|
||||||
import { handleApiCreateCommand } from './create-command';
|
import { handleApiCreateCommand } from './create-command';
|
||||||
import { handleApiDiscoverCommand } from './discover-command';
|
import { handleApiDiscoverCommand } from './discover-command';
|
||||||
import { handleApiExportCommand } from './export-command';
|
import { handleApiExportCommand } from './export-command';
|
||||||
|
import { createApiCommandHandler, type ApiCommandDependencies } from './handler';
|
||||||
import { showApiCommandHelp, showUnknownApiCommand } from './help';
|
import { showApiCommandHelp, showUnknownApiCommand } from './help';
|
||||||
import { handleApiImportCommand } from './import-command';
|
import { handleApiImportCommand } from './import-command';
|
||||||
import { handleApiListCommand } from './list-command';
|
import { handleApiListCommand } from './list-command';
|
||||||
import { handleApiRemoveCommand } from './remove-command';
|
import { handleApiRemoveCommand } from './remove-command';
|
||||||
|
|
||||||
|
export { createApiCommandHandler, type ApiCommandDependencies } from './handler';
|
||||||
export { parseApiCommandArgs } from './shared';
|
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 = {
|
const DEFAULT_API_COMMAND_DEPENDENCIES: ApiCommandDependencies = {
|
||||||
help: showApiCommandHelp,
|
help: showApiCommandHelp,
|
||||||
unknown: showUnknownApiCommand,
|
unknown: showUnknownApiCommand,
|
||||||
@@ -38,40 +23,6 @@ const DEFAULT_API_COMMAND_DEPENDENCIES: ApiCommandDependencies = {
|
|||||||
remove: handleApiRemoveCommand,
|
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> {
|
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
|
* Commands module barrel export
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { handleApiCommand } from './api-command';
|
export { handleApiCommand } from './api-command/index';
|
||||||
export { handleCleanupCommand } from './cleanup-command';
|
export { handleCleanupCommand } from './cleanup-command';
|
||||||
export { handleCliproxyCommand } from './cliproxy-command';
|
export { handleCliproxyCommand } from './cliproxy-command';
|
||||||
export { handleConfigCommand } from './config-command';
|
export { handleConfigCommand } from './config-command';
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ const ROOT_COMMAND_ROUTES: readonly NamedCommandRoute[] = [
|
|||||||
{
|
{
|
||||||
name: 'api',
|
name: 'api',
|
||||||
handle: async (args) => {
|
handle: async (args) => {
|
||||||
const { handleApiCommand } = await import('./api-command');
|
const { handleApiCommand } = await import('./api-command/index');
|
||||||
await handleApiCommand(args);
|
await handleApiCommand(args);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it } from 'bun:test';
|
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[] = [];
|
let calls: string[] = [];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user