Skip to content

Commit edbefdf

Browse files
authored
feat(java): Basic README.md generation (#6467)
* feat(java): Basic README.md generation * Add ReadmeConfigBuilder * Add java generator agent * Add generator agent to sdk generator context * Add java project and generator cli * Add Java adapter * Add seed pre-run command * Add cli file and features yml file * Work on dockerfile * Fix dockerfile and add package generation logic * Run fixtures * Java format * fix depcheck * Fix depcheck
1 parent 4081044 commit edbefdf

File tree

108 files changed

+2022
-11870
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+2022
-11870
lines changed

generators/java-v2/base/package.json

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "@fern-api/java-base",
3+
"version": "0.0.0",
4+
"repository": {
5+
"type": "git",
6+
"url": "https://github.com/fern-api/fern.git",
7+
"directory": "generators/java-v2/base"
8+
},
9+
"files": [
10+
"lib"
11+
],
12+
"type": "module",
13+
"source": "src/index.ts",
14+
"main": "lib/index.js",
15+
"types": "lib/index.d.ts",
16+
"sideEffects": false,
17+
"scripts": {
18+
"clean": "rm -rf ./lib && tsc --build --clean",
19+
"compile": "tsc --build",
20+
"test": "vitest --passWithNoTests --run",
21+
"test:update": "vitest --passWithNoTests --run -u",
22+
"lint:eslint": "eslint --max-warnings 0 . --ignore-pattern=../../../.eslintignore",
23+
"lint:eslint:fix": "yarn lint:eslint --fix",
24+
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
25+
"format:check": "prettier --check --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
26+
"depcheck": "depcheck",
27+
"dist:cli": "pnpm compile && tsup ./src/cli.ts --format cjs",
28+
"publish:cli": "pnpm dist:cli && cd dist && yarn npm publish",
29+
"dockerTagLatest": "pnpm dist:cli && docker build -f ./Dockerfile -t fernapi/fern-go-model:latest ../../.."
30+
},
31+
"devDependencies": {
32+
"@fern-api/base-generator": "workspace:*",
33+
"@fern-api/configs": "workspace:*",
34+
"@fern-api/fs-utils": "workspace:*",
35+
"@fern-api/java-ast": "workspace:*",
36+
"@fern-fern/ir-sdk": "^57.0.0",
37+
"@types/node": "18.15.3",
38+
"tsup": "^8.0.2",
39+
"depcheck": "^1.4.7",
40+
"eslint": "^8.56.0",
41+
"prettier": "^3.4.2",
42+
"typescript": "5.7.2",
43+
"vitest": "^2.1.9"
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { AbstractGeneratorCli, parseIR } from "@fern-api/base-generator";
2+
import { AbsoluteFilePath } from "@fern-api/fs-utils";
3+
import { BaseJavaCustomConfigSchema } from "@fern-api/java-ast";
4+
5+
import { IntermediateRepresentation } from "@fern-fern/ir-sdk/api";
6+
import * as IrSerialization from "@fern-fern/ir-sdk/serialization";
7+
8+
import { AbstractJavaGeneratorContext } from "../context/AbstractJavaGeneratorContext";
9+
10+
export abstract class AbstractJavaGeneratorCli<
11+
CustomConfig extends BaseJavaCustomConfigSchema,
12+
JavaGeneratorContext extends AbstractJavaGeneratorContext<CustomConfig>
13+
> extends AbstractGeneratorCli<CustomConfig, IntermediateRepresentation, JavaGeneratorContext> {
14+
/**
15+
* Parses the IR for the Java generators
16+
* @param irFilepath
17+
* @returns
18+
*/
19+
protected async parseIntermediateRepresentation(irFilepath: string): Promise<IntermediateRepresentation> {
20+
return await parseIR<IntermediateRepresentation>({
21+
absolutePathToIR: AbsoluteFilePath.of(irFilepath),
22+
parse: IrSerialization.IntermediateRepresentation.parse
23+
});
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { AbstractGeneratorContext, FernGeneratorExec, GeneratorNotificationService } from "@fern-api/base-generator";
2+
import { RelativeFilePath } from "@fern-api/fs-utils";
3+
import { BaseJavaCustomConfigSchema } from "@fern-api/java-ast";
4+
5+
import { IntermediateRepresentation } from "@fern-fern/ir-sdk/api";
6+
7+
export interface FileLocation {
8+
namespace: string;
9+
directory: RelativeFilePath;
10+
}
11+
12+
export abstract class AbstractJavaGeneratorContext<
13+
CustomConfig extends BaseJavaCustomConfigSchema
14+
> extends AbstractGeneratorContext {
15+
public constructor(
16+
public readonly ir: IntermediateRepresentation,
17+
public readonly config: FernGeneratorExec.config.GeneratorConfig,
18+
public readonly customConfig: CustomConfig,
19+
public readonly generatorNotificationService: GeneratorNotificationService
20+
) {
21+
super(config, generatorNotificationService);
22+
}
23+
}

generators/java-v2/base/src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { AbstractJavaGeneratorCli } from "./cli/AbstractJavaGeneratorCli";
2+
export { AbstractJavaGeneratorContext, type FileLocation } from "./context/AbstractJavaGeneratorContext";
3+
export { JavaProject } from "./project/JavaProject";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { mkdir } from "fs/promises";
2+
3+
import { AbstractProject, File } from "@fern-api/base-generator";
4+
import { AbsoluteFilePath } from "@fern-api/fs-utils";
5+
import { BaseJavaCustomConfigSchema } from "@fern-api/java-ast";
6+
7+
import { AbstractJavaGeneratorContext } from "../context/AbstractJavaGeneratorContext";
8+
9+
/**
10+
* In memory representation of a Java project.
11+
*/
12+
export class JavaProject extends AbstractProject<AbstractJavaGeneratorContext<BaseJavaCustomConfigSchema>> {
13+
private sourceFiles: File[] = [];
14+
15+
public constructor({ context }: { context: AbstractJavaGeneratorContext<BaseJavaCustomConfigSchema> }) {
16+
super(context);
17+
}
18+
19+
public addJavaFiles(file: File): void {
20+
this.sourceFiles.push(file);
21+
}
22+
23+
public async persist(): Promise<void> {
24+
this.context.logger.debug(`Writing java files to ${this.absolutePathToOutputDirectory}`);
25+
await this.writeJavaFiles({
26+
absolutePathToDirectory: this.absolutePathToOutputDirectory,
27+
files: this.sourceFiles
28+
});
29+
await this.writeRawFiles();
30+
this.context.logger.debug(`Successfully wrote java files to ${this.absolutePathToOutputDirectory}`);
31+
}
32+
33+
private async writeJavaFiles({
34+
absolutePathToDirectory,
35+
files
36+
}: {
37+
absolutePathToDirectory: AbsoluteFilePath;
38+
files: File[];
39+
}): Promise<AbsoluteFilePath> {
40+
await this.mkdir(absolutePathToDirectory);
41+
await Promise.all(files.map(async (file) => await file.write(absolutePathToDirectory)));
42+
return absolutePathToDirectory;
43+
}
44+
45+
private async mkdir(absolutePathToDirectory: AbsoluteFilePath): Promise<void> {
46+
this.context.logger.debug(`mkdir ${absolutePathToDirectory}`);
47+
await mkdir(absolutePathToDirectory, { recursive: true });
48+
}
49+
}

generators/java-v2/base/tsconfig.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "@fern-api/configs/tsconfig/main.json",
3+
"compilerOptions": { "composite": true, "outDir": "lib", "rootDir": "src" },
4+
"include": ["./src/**/*"],
5+
"references": [
6+
{ "path": "../../../packages/commons/core-utils" },
7+
{ "path": "../../../packages/commons/fs-utils" },
8+
{ "path": "../../../packages/commons/logging-execa" },
9+
{ "path": "../../base" },
10+
{ "path": "../ast" }
11+
]
12+
}

generators/java-v2/sdk/features.yml

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
features:
2+
- id: USAGE
3+
description: |
4+
Instantiate and use the client with the following:
5+
6+
- id: ENVIRONMENTS
7+
description: |
8+
You can choose between different environments by using the `option.WithBaseURL` option. You can configure any arbitrary base
9+
URL, which is particularly useful in test environments.
10+
11+
- id: PAGINATION
12+
description: |
13+
List endpoints are paginated. The SDK provides an iterator so that you can simply loop over the items. You can also iterate page-by-page.
14+
15+
- id: ERRORS
16+
description: |
17+
Structured error types are returned from API calls that return non-success status codes. These errors are compatible
18+
with the `errors.Is` and `errors.As` APIs, so you can access the error like so:
19+
20+
- id: REQUEST_OPTIONS
21+
description: |
22+
A variety of request options are included to adapt the behavior of the library, which includes configuring
23+
authorization tokens, or providing your own instrumented `*http.Client`.
24+
25+
These request options can either be
26+
specified on the client so that they're applied on every request, or for an individual request, like so:
27+
28+
> Providing your own `*http.Client` is recommended. Otherwise, the `http.DefaultClient` will be used,
29+
> and your client will wait indefinitely for a response (unless the per-request, context-based timeout
30+
> is used).
31+
32+
- id: RETRIES
33+
advanced: true
34+
description: |
35+
The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long
36+
as the request is deemed retryable and the number of retry attempts has not grown larger than the configured
37+
retry limit (default: 2).
38+
39+
A request is deemed retryable when any of the following HTTP status codes is returned:
40+
41+
- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout)
42+
- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests)
43+
- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors)
44+
45+
Use the `option.WithMaxAttempts` option to configure this behavior for the entire client or an individual request:
46+
47+
- id: TIMEOUTS
48+
advanced: true
49+
description: |
50+
Setting a timeout for each individual request is as simple as using the standard context library. Setting a one second timeout for an individual API call looks like the following:

generators/java-v2/sdk/package.json

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@fern-api/java-sdk",
3+
"version": "0.0.0",
4+
"repository": {
5+
"type": "git",
6+
"url": "https://github.com/fern-api/fern.git",
7+
"directory": "generators/java-v2/sdk"
8+
},
9+
"files": [
10+
"lib"
11+
],
12+
"type": "module",
13+
"source": "src/index.ts",
14+
"main": "lib/index.js",
15+
"types": "lib/index.d.ts",
16+
"sideEffects": false,
17+
"scripts": {
18+
"clean": "rm -rf ./lib && tsc --build --clean",
19+
"compile": "tsc --build",
20+
"test": "vitest --passWithNoTests --run",
21+
"test:update": "vitest --passWithNoTests --run -u",
22+
"lint:eslint": "eslint --max-warnings 0 . --ignore-pattern=../../../.eslintignore",
23+
"lint:eslint:fix": "yarn lint:eslint --fix",
24+
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
25+
"format:check": "prettier --check --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
26+
"depcheck": "depcheck",
27+
"dist:cli": "pnpm compile && tsup ./src/cli.ts --format cjs",
28+
"publish:cli": "pnpm dist:cli && cd dist && yarn npm publish",
29+
"dockerTagLatest": "pnpm dist:cli && docker build -f ./Dockerfile -t fernapi/fern-java-sdk:latest ../../.."
30+
},
31+
"devDependencies": {
32+
"@fern-api/java-ast": "workspace:*",
33+
"@fern-api/java-base": "workspace:*",
34+
"@fern-api/base-generator": "workspace:*",
35+
"@fern-api/configs": "workspace:*",
36+
"@fern-api/fs-utils": "workspace:*",
37+
"@fern-api/logger": "workspace:*",
38+
"@fern-fern/generator-cli-sdk": "0.0.17",
39+
"@fern-fern/generator-exec-sdk": "^0.0.1021",
40+
"@fern-fern/ir-sdk": "^57.0.0",
41+
"@types/node": "18.15.3",
42+
"tsup": "^8.0.2",
43+
"depcheck": "^1.4.7",
44+
"eslint": "^8.56.0",
45+
"prettier": "^3.4.2",
46+
"typescript": "5.7.2",
47+
"vitest": "^2.1.9"
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { AbstractGeneratorAgent } from "@fern-api/base-generator";
2+
import { Logger } from "@fern-api/logger";
3+
4+
import { FernGeneratorCli } from "@fern-fern/generator-cli-sdk";
5+
import { FernGeneratorExec } from "@fern-fern/generator-exec-sdk";
6+
7+
import { SdkGeneratorContext } from "./SdkGeneratorContext";
8+
import { ReadmeConfigBuilder } from "./readme/ReadmeConfigBuilder";
9+
10+
export class JavaGeneratorAgent extends AbstractGeneratorAgent<SdkGeneratorContext> {
11+
private readmeConfigBuilder: ReadmeConfigBuilder;
12+
13+
public constructor({
14+
logger,
15+
config,
16+
readmeConfigBuilder
17+
}: {
18+
logger: Logger;
19+
config: FernGeneratorExec.GeneratorConfig;
20+
readmeConfigBuilder: ReadmeConfigBuilder;
21+
}) {
22+
super({ logger, config });
23+
this.readmeConfigBuilder = readmeConfigBuilder;
24+
}
25+
26+
public getReadmeConfig(
27+
args: AbstractGeneratorAgent.ReadmeConfigArgs<SdkGeneratorContext>
28+
): FernGeneratorCli.ReadmeConfig {
29+
return this.readmeConfigBuilder.build({
30+
context: args.context,
31+
remote: args.remote,
32+
featureConfig: args.featureConfig,
33+
endpointSnippets: args.endpointSnippets
34+
});
35+
}
36+
37+
public getLanguage(): FernGeneratorCli.Language {
38+
return FernGeneratorCli.Language.Java;
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { BaseJavaCustomConfigSchema } from "@fern-api/java-ast";
2+
3+
export const SdkCustomConfigSchema: typeof BaseJavaCustomConfigSchema = BaseJavaCustomConfigSchema;
4+
5+
export type SdkCustomConfigSchema = BaseJavaCustomConfigSchema;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { File, GeneratorNotificationService } from "@fern-api/base-generator";
2+
import { RelativeFilePath } from "@fern-api/fs-utils";
3+
import { AbstractJavaGeneratorCli } from "@fern-api/java-base";
4+
5+
import { FernGeneratorExec } from "@fern-fern/generator-exec-sdk";
6+
import { Endpoint } from "@fern-fern/generator-exec-sdk/api";
7+
import { IntermediateRepresentation } from "@fern-fern/ir-sdk/api";
8+
9+
import { SdkCustomConfigSchema } from "./SdkCustomConfig";
10+
import { SdkGeneratorContext } from "./SdkGeneratorContext";
11+
12+
export class SdkGeneratorCLI extends AbstractJavaGeneratorCli<SdkCustomConfigSchema, SdkGeneratorContext> {
13+
protected constructContext({
14+
ir,
15+
customConfig,
16+
generatorConfig,
17+
generatorNotificationService
18+
}: {
19+
ir: IntermediateRepresentation;
20+
customConfig: SdkCustomConfigSchema;
21+
generatorConfig: FernGeneratorExec.GeneratorConfig;
22+
generatorNotificationService: GeneratorNotificationService;
23+
}): SdkGeneratorContext {
24+
return new SdkGeneratorContext(ir, generatorConfig, customConfig, generatorNotificationService);
25+
}
26+
27+
protected parseCustomConfigOrThrow(customConfig: unknown): SdkCustomConfigSchema {
28+
const parsed = customConfig != null ? SdkCustomConfigSchema.parse(customConfig) : undefined;
29+
if (parsed != null) {
30+
return parsed;
31+
}
32+
return {};
33+
}
34+
35+
protected async publishPackage(context: SdkGeneratorContext): Promise<void> {
36+
await this.generate(context);
37+
}
38+
39+
protected async writeForGithub(context: SdkGeneratorContext): Promise<void> {
40+
await this.generate(context);
41+
}
42+
43+
protected async writeForDownload(context: SdkGeneratorContext): Promise<void> {
44+
await this.generate(context);
45+
}
46+
47+
protected async generate(context: SdkGeneratorContext): Promise<void> {
48+
if (context.config.output.snippetFilepath != null) {
49+
try {
50+
await this.generateReadme({
51+
context,
52+
// TODO(ajgateno): Support and require snippets
53+
endpointSnippets: []
54+
});
55+
} catch (e) {
56+
context.logger.warn("Failed to generate README.md, this is OK.");
57+
}
58+
}
59+
60+
await context.project.persist();
61+
}
62+
63+
private async generateReadme({
64+
context,
65+
endpointSnippets
66+
}: {
67+
context: SdkGeneratorContext;
68+
endpointSnippets: Endpoint[];
69+
}): Promise<void> {
70+
const content = await context.generatorAgent.generateReadme({ context, endpointSnippets });
71+
context.project.addRawFiles(
72+
new File(context.generatorAgent.README_FILENAME, RelativeFilePath.of("."), content)
73+
);
74+
}
75+
}

0 commit comments

Comments
 (0)