11import process from 'node:process' ;
22
3+ import { validateInputSchema } from '@apify/input_schema' ;
4+
35import { ApifyCommand } from '../lib/command-framework/apify-command.js' ;
46import { Args } from '../lib/command-framework/args.js' ;
5- import { LOCAL_CONFIG_PATH } from '../lib/consts.js' ;
6- import { readAndValidateInputSchema } from '../lib/input_schema.js' ;
7- import { success } from '../lib/outputs.js' ;
7+ import { CommandExitCodes , LOCAL_CONFIG_PATH } from '../lib/consts.js' ;
8+ import {
9+ readAndValidateInputSchema ,
10+ readInputSchema ,
11+ readStorageSchema ,
12+ validateDatasetSchema ,
13+ validateKvsSchema ,
14+ validateOutputSchema ,
15+ } from '../lib/input_schema.js' ;
16+ import { error , info , success } from '../lib/outputs.js' ;
17+ import { Ajv2019 } from '../lib/utils.js' ;
818
9- export class ValidateInputSchemaCommand extends ApifyCommand < typeof ValidateInputSchemaCommand > {
19+ export class ValidateSchemaCommand extends ApifyCommand < typeof ValidateSchemaCommand > {
1020 static override name = 'validate-schema' as const ;
1121
12- static override description = `Validates Actor input schema from one of these locations (in priority order):
13- 1. Object in '${ LOCAL_CONFIG_PATH } ' under "input" key
14- 2. JSON file path in '${ LOCAL_CONFIG_PATH } ' "input" key
15- 3. .actor/INPUT_SCHEMA.json
16- 4. INPUT_SCHEMA.json
22+ static override description = `Validates Actor schemas.
23+
24+ When a path argument is provided, validates only the input schema at that path.
1725
18- Optionally specify custom schema path to validate.` ;
26+ When no path is provided, validates all schemas found in '${ LOCAL_CONFIG_PATH } ':
27+ - Input schema (from "input" key or default locations)
28+ - Dataset schema (from "storages.dataset")
29+ - Output schema (from "output")
30+ - Key-Value Store schema (from "storages.keyValueStore")` ;
1931
2032 static override group = 'Local Actor Development' ;
2133
@@ -35,22 +47,115 @@ Optionally specify custom schema path to validate.`;
3547 static override args = {
3648 path : Args . string ( {
3749 required : false ,
38- description : ' Optional path to your INPUT_SCHEMA.json file. If not provided ./INPUT_SCHEMA.json is used.' ,
50+ description : ` Optional path to your INPUT_SCHEMA.json file. If not provided, validates all schemas in ' ${ LOCAL_CONFIG_PATH } '.` ,
3951 } ) ,
4052 } ;
4153
4254 static override hiddenAliases = [ 'vis' ] ;
4355
4456 async run ( ) {
57+ if ( this . args . path ) {
58+ await this . validateInputSchemaAtPath ( this . args . path ) ;
59+ return ;
60+ }
61+
62+ await this . validateAllSchemas ( ) ;
63+ }
64+
65+ private async validateInputSchemaAtPath ( forcePath : string ) {
4566 await readAndValidateInputSchema ( {
46- forcePath : this . args . path ,
67+ forcePath,
4768 cwd : process . cwd ( ) ,
48- getMessage : ( path ) =>
49- path
50- ? `Validating input schema at ${ path } `
51- : `Validating input schema embedded in '${ LOCAL_CONFIG_PATH } '` ,
69+ getMessage : ( path ) => `Validating input schema at ${ path ?? forcePath } ` ,
5270 } ) ;
5371
5472 success ( { message : 'Input schema is valid.' } ) ;
5573 }
74+
75+ private async validateAllSchemas ( ) {
76+ const cwd = process . cwd ( ) ;
77+ let foundAny = false ;
78+ let hasErrors = false ;
79+
80+ // Input schema — not using readAndValidateInputSchema here because it throws
81+ // when no schema is found; in the all-schemas scan, a missing input schema
82+ // should be silently skipped, not treated as an error.
83+ try {
84+ const { inputSchema, inputSchemaPath } = await readInputSchema ( { cwd, throwOnMissing : true } ) ;
85+
86+ if ( inputSchema ) {
87+ foundAny = true ;
88+
89+ const location = inputSchemaPath ? `at ${ inputSchemaPath } ` : `embedded in '${ LOCAL_CONFIG_PATH } '` ;
90+ info ( { message : `Validating input schema ${ location } ` } ) ;
91+
92+ const validator = new Ajv2019 ( { strict : false } ) ;
93+ validateInputSchema ( validator , inputSchema ) ;
94+ success ( { message : 'Input schema is valid.' } ) ;
95+ }
96+ } catch ( err ) {
97+ foundAny = true ;
98+ hasErrors = true ;
99+ error ( { message : ( err as Error ) . message } ) ;
100+ }
101+
102+ // Storage schemas (Dataset, Output, Key-Value Store)
103+ const storageSchemas = [
104+ {
105+ label : 'Dataset' ,
106+ read : ( ) => readStorageSchema ( { cwd, key : 'dataset' , label : 'Dataset' , throwOnMissing : true } ) ,
107+ validate : validateDatasetSchema ,
108+ } ,
109+ {
110+ label : 'Output' ,
111+ read : ( ) =>
112+ readStorageSchema ( {
113+ cwd,
114+ key : 'output' ,
115+ label : 'Output' ,
116+ getRef : ( config ) => config ?. output ,
117+ throwOnMissing : true ,
118+ } ) ,
119+ validate : validateOutputSchema ,
120+ } ,
121+ {
122+ label : 'Key-Value Store' ,
123+ read : ( ) =>
124+ readStorageSchema ( { cwd, key : 'keyValueStore' , label : 'Key-Value Store' , throwOnMissing : true } ) ,
125+ validate : validateKvsSchema ,
126+ } ,
127+ ] ;
128+
129+ for ( const { label, read, validate } of storageSchemas ) {
130+ try {
131+ const result = read ( ) ;
132+
133+ if ( result ) {
134+ foundAny = true ;
135+
136+ const location = result . schemaPath
137+ ? `at ${ result . schemaPath } `
138+ : `embedded in '${ LOCAL_CONFIG_PATH } '` ;
139+ info ( { message : `Validating ${ label } schema ${ location } ` } ) ;
140+
141+ validate ( result . schema ) ;
142+ success ( { message : `${ label } schema is valid.` } ) ;
143+ }
144+ } catch ( err ) {
145+ foundAny = true ;
146+ hasErrors = true ;
147+ error ( { message : ( err as Error ) . message } ) ;
148+ }
149+ }
150+
151+ if ( ! foundAny ) {
152+ throw new Error (
153+ `No schemas found. Make sure '${ LOCAL_CONFIG_PATH } ' exists and defines at least one schema.` ,
154+ ) ;
155+ }
156+
157+ if ( hasErrors ) {
158+ process . exitCode = CommandExitCodes . InvalidInput ;
159+ }
160+ }
56161}
0 commit comments