Skip to content

Commit

Permalink
feat(cli): fern check now confirms IR version is compatible between g…
Browse files Browse the repository at this point in the history
…enerator and cli (#4629)
  • Loading branch information
armandobelardo authored Sep 15, 2024
1 parent 3332469 commit 7bd1fb9
Show file tree
Hide file tree
Showing 23 changed files with 442 additions and 11 deletions.
17 changes: 17 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
- changelogEntry:
- summary: |
Running `fern check` will now check to confirm that the generator versions you are running are compatible with your Fern CLI version.
Each version of SDK generators depends on a version of a libary that is exported by the Fern CLI, and as a result, each generator has a minimum
compatible version of the Fern CLI. As an example, if you were to run `fern check` while leveraging `fernapi/fern-python-sdk` version `2.0.0`, on CLI version `0.1.3`, you'd receive the following error:
`The generator fernapi/fern-python-sdk requires CLI version 0.23.0-rc4 or later (current version: 0.1.3-rc0).`
Indicating that you must upgrade your CLI in order to leverage the current generator.
added:
- Running `fern check` will now check to confirm that the generator versions you are running are compatible with your Fern CLI version.
- Fern commands now print out generator upgrades, in addition to CLI upgrades.
type: feat
irVersion: 53
version: 0.41.14-rc1

- changelogEntry:
- summary: |
The Fern CLI now safely handles a npx file exists error by retrying the command on failure.
Expand Down
1 change: 1 addition & 0 deletions packages/cli/configuration/src/generators-yml/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export {
OPENAPI_LOCATION_KEY,
type GeneratorsConfigurationSchema
} from "./schemas/GeneratorsConfigurationSchema";
export { type GeneratorGroupSchema } from "./schemas/GeneratorGroupSchema";
export { type ReadmeEndpointObjectSchema } from "./schemas/ReadmeEndpointObjectSchema";
export { type ReadmeEndpointSchema } from "./schemas/ReadmeEndpointSchema";
export { type ReadmeSchema } from "./schemas/ReadmeSchema";
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/fern-definition/validator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
},
"dependencies": {
"@fern-api/configuration": "workspace:*",
"@fern-api/core": "workspace:*",
"@fern-api/core-utils": "workspace:*",
"@fern-api/fs-utils": "workspace:*",
"@fern-api/ir-generator": "workspace:*",
Expand All @@ -37,6 +38,7 @@
"@fern-api/task-context": "workspace:*",
"@fern-api/workspace-loader": "workspace:*",
"@fern-api/fern-definition-schema": "workspace:*",
"@fern-api/semver-utils": "workspace:*",
"chalk": "^5.3.0",
"lodash-es": "^4.17.21",
"strip-ansi": "^7.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export type GeneratorsYmlFileAstVisitor<R = void | Promise<void>> = {

export interface GeneratorsYmlFileAstNodeTypes {
file: generatorsYml.GeneratorsConfigurationSchema;
generatorInvocation: {
invocation: generatorsYml.GeneratorInvocationSchema;
cliVersion: string;
};
}

export type GeneratorsYmlFileAstNodeVisitor<K extends keyof GeneratorsYmlFileAstNodeTypes, R = void | Promise<void>> = (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import { generatorsYml } from "@fern-api/configuration";
import { GeneratorsYmlFileAstVisitor } from "./GeneratorsYmlAstVisitor";
import { noop, visitObject } from "@fern-api/core-utils";
import { visitGeneratorGroups } from "./visitors/visitGeneratorGroups";

export async function visitGeneratorsYamlAst(
contents: generatorsYml.GeneratorsConfigurationSchema,
cliVersion: string,
visitor: Partial<GeneratorsYmlFileAstVisitor>
): Promise<void> {
await visitor.file?.(contents, []);
await visitObject(contents, {
"auth-schemes": noop,
api: noop,
whitelabel: noop,
metadata: noop,
readme: noop,
"default-group": noop,
reviewers: noop,
openapi: noop,
"openapi-overrides": noop,
"spec-origin": noop,
"async-api": noop,
"api-settings": noop,
groups: async (groups) => {
await visitGeneratorGroups({ groups, visitor, nodePath: ["groups"], cliVersion });
}
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { generatorsYml } from "@fern-api/configuration";
import { NodePath } from "@fern-api/fern-definition-schema";
import { GeneratorsYmlFileAstVisitor } from "../GeneratorsYmlAstVisitor";

export async function visitGeneratorGroups({
groups,
visitor,
nodePath,
cliVersion
}: {
groups: Record<string, generatorsYml.GeneratorGroupSchema> | undefined;
visitor: Partial<GeneratorsYmlFileAstVisitor>;
nodePath: NodePath;
cliVersion: string;
}): Promise<void> {
if (groups == null) {
return;
}
for (const [groupName, group] of Object.entries(groups)) {
await visitGeneratorGroup({ group, visitor, nodePath: [...nodePath, groupName], cliVersion });
}
}

async function visitGeneratorGroup({
group,
visitor,
nodePath,
cliVersion
}: {
group: generatorsYml.GeneratorGroupSchema;
visitor: Partial<GeneratorsYmlFileAstVisitor>;
nodePath: NodePath;
cliVersion: string;
}): Promise<void> {
await Promise.all(
group.generators.map(
async (generator, idx) =>
await visitor.generatorInvocation?.({ invocation: generator, cliVersion }, [
...nodePath,
"generators",
idx.toString(),
generator.name
])
)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { RelativeFilePath } from "@fern-api/fs-utils";
import { NodePath } from "@fern-api/fern-definition-schema";
import { GeneratorsYmlFileAstNodeTypes, GeneratorsYmlFileAstNodeVisitor } from "./ast";
import { RuleVisitors } from "./Rule";
import { ValidationViolation } from "./ValidationViolation";
import { generatorsYml } from "@fern-api/configuration";
import { GeneratorsYmlFileAstVisitor } from "./ast/GeneratorsYmlAstVisitor";

export function createGeneratorsYmlAstVisitorForRules({
relativeFilepath,
contents,
allRuleVisitors,
addViolations
}: {
relativeFilepath: RelativeFilePath;
contents: generatorsYml.GeneratorsConfigurationSchema;
allRuleVisitors: RuleVisitors[];
addViolations: (newViolations: ValidationViolation[]) => void;
}): GeneratorsYmlFileAstVisitor {
function createAstNodeVisitor<K extends keyof GeneratorsYmlFileAstNodeTypes>(
nodeType: K
): Record<K, GeneratorsYmlFileAstNodeVisitor<K>> {
const visit: GeneratorsYmlFileAstNodeVisitor<K> = async (
node: GeneratorsYmlFileAstNodeTypes[K],
nodePath: NodePath
) => {
for (const ruleVisitors of allRuleVisitors) {
const visitFromRule = ruleVisitors.generatorsYml?.[nodeType];
if (visitFromRule != null) {
const ruleViolations = await visitFromRule(node, { relativeFilepath, contents });
addViolations(
ruleViolations.map((violation) => ({
severity: violation.severity,
relativeFilepath,
nodePath,
message: violation.message
}))
);
}
}
};

return { [nodeType]: visit } as Record<K, GeneratorsYmlFileAstNodeVisitor<K>>;
}

return {
...createAstNodeVisitor("file"),
...createAstNodeVisitor("generatorInvocation")
};
}
4 changes: 3 additions & 1 deletion packages/cli/fern-definition/validator/src/getAllRules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Rule } from "./Rule";
import { CompatibleIrVersionsRule } from "./rules/compatible-ir-versions";
import { ImportFileExistsRule } from "./rules/import-file-exists";
import { MatchingEnvironmentUrlsRule } from "./rules/matching-environment-urls";
import { NoCircularImportsRule } from "./rules/no-circular-imports";
Expand Down Expand Up @@ -85,7 +86,8 @@ export function getAllRules(): Rule[] {
ValidStreamConditionRule,
ValidVersionRule,
NoUnusedGenericRule,
ValidGenericRule
ValidGenericRule,
CompatibleIrVersionsRule
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils";
import { getViolationsForRule } from "../../../testing-utils/getViolationsForRule";
import { ValidationViolation } from "../../../ValidationViolation";
import { CompatibleIrVersionsRule } from "../compatible-ir-versions";

describe("compatible-ir-versions", () => {
it("simple failure", async () => {
process.env.DEFAULT_FDR_ORIGIN = "https://registry-dev2.buildwithfern.com";
const violations = await getViolationsForRule({
rule: CompatibleIrVersionsRule,
absolutePathToWorkspace: join(
AbsoluteFilePath.of(__dirname),
RelativeFilePath.of("fixtures"),
RelativeFilePath.of("simple")
),
cliVersion: "0.1.3-rc0"
});

const expectedViolations: ValidationViolation[] = [
{
severity: "error",
relativeFilepath: RelativeFilePath.of("generators.yml"),
nodePath: ["groups", "python-sdk", "generators", "0", "fernapi/fern-python-sdk"],
message:
"The generator fernapi/fern-python-sdk requires CLI version 0.23.0-rc4 or later (current version: 0.1.3-rc0). Please run `fern upgrade` to upgrade your CLI version and use this generator."
}
];

expect(violations).toEqual(expectedViolations);
});

it("simple success", async () => {
process.env.DEFAULT_FDR_ORIGIN = "https://registry-dev2.buildwithfern.com";
const violations = await getViolationsForRule({
rule: CompatibleIrVersionsRule,
absolutePathToWorkspace: join(
AbsoluteFilePath.of(__dirname),
RelativeFilePath.of("fixtures"),
RelativeFilePath.of("simple")
),
// Latest CLI at the time of writing, so should definitely work
cliVersion: "0.41.10"
});

const expectedViolations: ValidationViolation[] = [];

expect(violations).toEqual(expectedViolations);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: simple-api
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
types:
_InvalidType:
properties:
a: string

ValidType:
properties:
b: string

service:
base-path: /
auth: false
endpoints:
exampleEndpoint:
path: ""
method: POST
request:
name: ExampleEndpointRequest
query-parameters:
some-query-param: optional<string>
body:
properties:
foo: integer
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
groups:
python-sdk:
audiences:
- external
generators:
- name: fernapi/fern-python-sdk
version: 2.0.0
output:
location: pypi
package-name: fern-api
token: ${PYPI_TOKEN}
github:
repository: fern-api/python-sdk
license: MIT
config:
client_class_name: Fern
Loading

0 comments on commit 7bd1fb9

Please sign in to comment.