Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# compiled output
dist
tmp
/out-tsc
out-tsc

# dependencies
node_modules
Expand Down
35 changes: 18 additions & 17 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,40 @@
"@types/express": "^5.0.3",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "catalog:",
"@types/supertest": "^7.2.0",
"@types/pluralize": "^0.0.33",
"@types/qs": "^6.9.15",
"@types/signale": "^1.4.7",
"@types/semver": "catalog:",
"@types/signale": "^1.4.7",
"@types/supertest": "^7.2.0",
"semver": "catalog:",
"supertest": "^7.1.4",
"vitest": "catalog:",
"semver": "catalog:"
"vitest": "catalog:"
},
"dependencies": {
"@api7/adc-sdk": "workspace:*",
"@api7/adc-backend-api7": "workspace:*",
"@api7/adc-backend-apisix": "workspace:*",
"@api7/adc-backend-apisix-standalone": "workspace:*",
"@api7/adc-converter-openapi": "workspace:*",
"@api7/adc-differ": "workspace:*",
"express": "^5.1.0",
"winston": "^3.17.0",
"@api7/adc-sdk": "workspace:*",
"agentkeepalive": "^4.6.0",
"ajv-draft-04": "catalog:",
"axios": "catalog:",
"rxjs": "catalog:",
"lodash-es": "catalog:",
"zod": "catalog:",
"pluralize": "^8.0.0",
"listr2": "catalog:",
"commander": "^14.0.3",
"chalk": "^5.6.2",
"commander": "^14.0.3",
"dotenv": "^17.3.1",
"express": "^5.1.0",
"glob": "^13.0.0",
"js-yaml": "catalog:",
"listr2": "catalog:",
"lodash-es": "catalog:",
"parse-duration": "^2.1.5",
"pluralize": "^8.0.0",
"qs": "^6.14.1",
"dotenv": "^17.3.1",
"agentkeepalive": "^4.6.0",
"rxjs": "catalog:",
"signale": "^1.4.0",
"glob": "^13.0.0",
"js-yaml": "catalog:"
"winston": "^3.17.0",
"zod": "catalog:"
},
"nx": {
"name": "cli",
Expand Down
2 changes: 2 additions & 0 deletions apps/cli/src/command/diff.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { writeFile } from 'node:fs/promises';
import {
DiffResourceTask,
ExperimentalRemoteStateFileTask,
FetchPluginSchemasTask,
LintTask,
LoadLocalConfigurationTask,
} from '../tasks';
Expand Down Expand Up @@ -64,6 +65,7 @@ export const DiffCommand = new BackendCommand<DiffOptions>(
const tasks = new Listr<TaskContext, typeof SignaleRenderer>(
[
InitializeBackendTask(opts.backend, opts),
opts.lint ? FetchPluginSchemasTask() : { task: () => undefined },
LoadLocalConfigurationTask(
opts.file,
opts.labelSelector,
Expand Down
56 changes: 52 additions & 4 deletions apps/cli/src/command/lint.command.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
import * as ADCSDK from '@api7/adc-sdk';
import { Option } from 'commander';
import { Listr } from 'listr2';

import { LintTask, LoadLocalConfigurationTask } from '../tasks';
import {
FetchPluginSchemasTask,
LintTask,
LoadLocalConfigurationTask,
} from '../tasks';
import { InitializeBackendTask } from '../tasks/init_backend';
import { SignaleRenderer } from '../utils/listr';
import { BaseCommand } from './helper';

export const LintCommand = new BaseCommand('lint')
.description(
'Lint the local configuration file(s) to ensure it meets ADC requirements.',
'Lint the local configuration file(s) to ensure it meets ADC requirements.\n\nOptionally, provide backend connection parameters to also validate plugin configurations against the backend\'s plugin schemas.',
)
.summary('lint the local configuration')
.option(
'-f, --file <file-path>',
'file to lint',
(filePath, files: Array<string> = []) => files.concat(filePath),
)
.addOption(
new Option('--backend <backend>', 'type of backend to validate plugins against')
.choices(['apisix', 'api7ee']),
)
.addOption(
new Option('--server <string>', 'HTTP address of the backend'),
)
.addOption(
new Option(
'--token <string>',
'token for ADC to connect to the backend',
),
)
.addOption(
new Option(
'--gateway-group <string>',
'gateway group to operate on (only for "api7ee" backend)',
)
.default('default'),
)
.addExamples([
{
title: 'Lint the specified configuration file',
Expand All @@ -23,12 +50,33 @@ export const LintCommand = new BaseCommand('lint')
title: 'Lint multiple configuration files',
command: 'adc lint -f service-a.yaml -f service-b.yaml',
},
{
title: 'Lint with plugin validation against an APISIX backend',
command: 'adc lint -f adc.yaml --backend apisix --server http://localhost:9180 --token edd1c9f034335f136f87ad84b625c8f1',
},
{
title: 'Lint with plugin validation against an API7 EE backend',
command: 'adc lint -f adc.yaml --backend api7ee --server https://dashboard.example.com --token <token>',
},
])
.action(async () => {
const opts = LintCommand.optsWithGlobals();
const useBackend = !!opts.backend;

const tasks = new Listr(
[LoadLocalConfigurationTask(opts.file, {}), LintTask()],
[
...(useBackend
? [
InitializeBackendTask(opts.backend, {
...opts,
cacheKey: 'lint',
} as ADCSDK.BackendOptions),
FetchPluginSchemasTask(),
]
: []),
LoadLocalConfigurationTask(opts.file, {}),
LintTask(),
],
{
renderer: SignaleRenderer,
rendererOptions: { verbose: opts.verbose },
Expand All @@ -37,7 +85,7 @@ export const LintCommand = new BaseCommand('lint')

try {
await tasks.run();
} catch (err) {
} catch {
process.exit(1);
}
});
2 changes: 2 additions & 0 deletions apps/cli/src/command/sync.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { lastValueFrom, toArray } from 'rxjs';
import {
DiffResourceTask,
ExperimentalRemoteStateFileTask,
FetchPluginSchemasTask,
LintTask,
LoadLocalConfigurationTask,
LoadRemoteConfigurationTask,
Expand Down Expand Up @@ -80,6 +81,7 @@ export const SyncCommand = new BackendCommand<SyncOption>(
const tasks = new Listr<TaskContext, typeof SignaleRenderer>(
[
InitializeBackendTask(opts.backend, opts),
opts.lint ? FetchPluginSchemasTask() : { task: () => undefined },
LoadLocalConfigurationTask(
opts.file,
opts.labelSelector,
Expand Down
49 changes: 47 additions & 2 deletions apps/cli/src/linter/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,51 @@
import { Configuration } from '@api7/adc-sdk';
import { ConfigurationSchema } from '@api7/adc-sdk/schema';
import { z } from 'zod';

export const check = (config: Configuration) => {
return ConfigurationSchema.safeParse(config);
import { validatePlugins } from './plugin-validator';
import type { LintError, LintOptions, LintResult } from './types';

export type { LintError, LintOptions, LintResult } from './types';

const zodIssuesToLintErrors = (issues: z.ZodIssue[]): LintError[] =>
issues.map(({ path, message, code, ...rest }) => ({
path,
message,
code,
...rest,
}));
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export const check = (
config: Configuration,
opts?: LintOptions,
): LintResult => {
// Phase 1: core resource structure validation (local, no network)
const result = ConfigurationSchema.safeParse(config);
if (!result.success) {
return {
success: false,
errors: zodIssuesToLintErrors(result.error.issues),
};
}

// Phase 2: plugin config validation (requires plugin schemas from backend)
if (opts?.pluginSchemas) {
const pluginErrors = validatePlugins(
result.data as Configuration,
opts.pluginSchemas,
);
if (pluginErrors.length > 0) {
return {
success: false,
errors: pluginErrors,
data: result.data,
};
}
}

return {
success: true,
errors: [],
data: result.data,
};
};
Loading
Loading