diff --git a/src/commands/actor/generate-schema-types.ts b/src/commands/actor/generate-schema-types.ts index 6450a0b66..fc25b5d5e 100644 --- a/src/commands/actor/generate-schema-types.ts +++ b/src/commands/actor/generate-schema-types.ts @@ -1,4 +1,4 @@ -import { mkdir, writeFile } from 'node:fs/promises'; +import { mkdir, stat, writeFile } from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; @@ -54,7 +54,9 @@ Reads the input schema from one of these locations (in priority order): 3. .actor/INPUT_SCHEMA.json 4. INPUT_SCHEMA.json -Optionally specify custom schema path to use.`; +Optionally specify a custom schema file path, or a directory path. +When a directory is provided, all schemas are discovered from it +just as if the command were run from that directory with no argument.`; static override flags = { output: Flags.string({ @@ -79,16 +81,34 @@ Optionally specify custom schema path to use.`; static override args = { path: Args.string({ required: false, - description: 'Optional path to the input schema file. If not provided, searches default locations.', + description: + 'Optional path to an input schema file or a directory containing Actor schemas. If a directory is given, all schema types are generated from it. If not provided, searches default locations in the current directory.', }), }; async run() { const cwd = process.cwd(); + let forcePath: string | undefined; + let effectiveCwd = cwd; + + if (this.args.path) { + const resolvedPath = path.resolve(cwd, this.args.path); + // Ignore stat errors (e.g. path does not exist); downstream will surface a clear message. + const isDirectory = await stat(resolvedPath) + .then((s) => s.isDirectory()) + .catch(() => false); + + if (isDirectory) { + effectiveCwd = resolvedPath; + } else { + forcePath = resolvedPath; + } + } + const { inputSchema } = await readAndValidateInputSchema({ - forcePath: this.args.path, - cwd, + forcePath, + cwd: effectiveCwd, getMessage: (schemaPath) => schemaPath ? `Generating types from input schema at ${schemaPath}` @@ -112,7 +132,7 @@ Optionally specify custom schema path to use.`; const result = await compile(stripTitles(schemaToCompile) as JSONSchema4, name, compileOptions); - const outputDir = path.resolve(cwd, this.flags.output); + const outputDir = path.resolve(effectiveCwd, this.flags.output); await mkdir(outputDir, { recursive: true }); const outputFile = path.join(outputDir, `${name}.ts`); @@ -120,12 +140,13 @@ Optionally specify custom schema path to use.`; success({ message: `Generated types written to ${outputFile}` }); - // When no custom path is provided, also generate types from additional schemas - if (!this.args.path) { + // When no specific file path is provided, also generate types from additional schemas + // (this includes both "no argument" and "directory argument" modes) + if (!forcePath) { const schemaResults = await Promise.allSettled([ - this.generateDatasetTypes({ cwd, outputDir, compileOptions }), - this.generateOutputTypes({ cwd, outputDir, compileOptions }), - this.generateKvsTypes({ cwd, outputDir, compileOptions }), + this.generateDatasetTypes({ cwd: effectiveCwd, outputDir, compileOptions }), + this.generateOutputTypes({ cwd: effectiveCwd, outputDir, compileOptions }), + this.generateKvsTypes({ cwd: effectiveCwd, outputDir, compileOptions }), ]); const schemaLabels = ['Dataset', 'Output', 'Key-Value Store']; diff --git a/test/local/commands/actor/generate-schema-types.test.ts b/test/local/commands/actor/generate-schema-types.test.ts index 51f5fe1d6..5f30b05b5 100644 --- a/test/local/commands/actor/generate-schema-types.test.ts +++ b/test/local/commands/actor/generate-schema-types.test.ts @@ -488,6 +488,90 @@ describe('apify actor generate-schema-types', () => { }); }); + describe('directory path argument', () => { + it('should discover and generate all schema types from a directory', async () => { + const projectDir = joinPath('my-actor-project'); + await mkdir(projectDir, { recursive: true }); + + await setupActorConfig(projectDir, { + datasetSchemaRef: validDatasetSchemaPath, + outputSchemaRef: validOutputSchemaPath, + kvsSchemaRef: validKvsSchemaPath, + }); + + await testRunCommand(ActorGenerateSchemaTypesCommand, { + args_path: projectDir, + }); + + // All schema types should be generated inside the project directory + const outputDir = join(projectDir, 'src', '.generated', 'actor'); + + const inputFile = await readFile(join(outputDir, 'input.ts'), 'utf-8'); + expect(inputFile).toContain('export interface'); + + const datasetFile = await readFile(join(outputDir, 'dataset.ts'), 'utf-8'); + expect(datasetFile).toContain('export interface'); + + const outputFile = await readFile(join(outputDir, 'output.ts'), 'utf-8'); + expect(outputFile).toContain('export interface'); + + const kvsFile = await readFile(join(outputDir, 'key-value-store.ts'), 'utf-8'); + expect(kvsFile).toContain('export interface'); + }); + + it('should discover input schema from default locations in the directory', async () => { + const projectDir = joinPath('default-locations-project'); + const actorDir = join(projectDir, '.actor'); + await mkdir(actorDir, { recursive: true }); + + // Place input schema at a default location without actor.json referencing it + const inputSchema = { + title: 'Test', + type: 'object', + schemaVersion: 1, + properties: { + query: { title: 'Query', description: 'Search query', type: 'string', editor: 'textfield' }, + }, + }; + + await writeFile(join(actorDir, 'INPUT_SCHEMA.json'), JSON.stringify(inputSchema)); + + await testRunCommand(ActorGenerateSchemaTypesCommand, { + args_path: projectDir, + }); + + const outputDir = join(projectDir, 'src', '.generated', 'actor'); + const generatedFile = await readFile(join(outputDir, 'input.ts'), 'utf-8'); + expect(generatedFile).toContain('export interface'); + expect(generatedFile).toContain('query'); + }); + + it('should output types inside the provided directory by default', async () => { + const projectDir = joinPath('output-location-project'); + await setupActorConfig(projectDir, {}); + + await testRunCommand(ActorGenerateSchemaTypesCommand, { + args_path: projectDir, + }); + + // Output should be inside the project directory, not in cwd + const outputFile = join(projectDir, 'src', '.generated', 'actor', 'input.ts'); + const generatedFile = await readFile(outputFile, 'utf-8'); + expect(generatedFile).toContain('export interface'); + }); + + it('should fail with clear error when directory has no schemas', async () => { + const emptyDir = joinPath('empty-project'); + await mkdir(emptyDir, { recursive: true }); + + await testRunCommand(ActorGenerateSchemaTypesCommand, { + args_path: emptyDir, + }); + + expect(lastErrorMessage()).include('Input schema has not been found'); + }); + }); + it('should write successful schemas and report error for the failing one', async () => { const outputDir = joinPath('partial-fail-output');