Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): make namespacing truly namespaced #4573

Merged
merged 10 commits into from
Sep 9, 2024
Merged
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
21 changes: 21 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
- changelogEntry:
- summary: |
The Fern generators.yml configuration now supports a new format for namespacing APIs for additional flexibility:

```yml
api:
specs:
- openapi: path/to/v1/openapi
overrides: path/to/v1/overrides
namespace: v1
- openapi: path/to/v2/openapi
overrides: path/to/v2/overrides
namespace: v2
```

Through namespacing your API, you can have multiple objects and endpoints with the same name across different namespaces. You can think of them
as the equivalent to Python modules or TypeScript packages.
type: feat
irVersion: 53
version: 0.41.8

- changelogEntry:
- summary: |
Previously we weren't always awaiting PostHog API calls directly. Now the CLI
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/configuration/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ export const OPENAPI_DIRECTORY = "openapi";
export const CHANGELOG_DIRECTORY = "changelog";
export const ASYNCAPI_DIRECTORY = "asyncapi";
export const ROOT_API_FILENAME = "api.yml";
export const FERN_PACKAGE_MARKER_FILENAME = "__package__.yml";
export const FERN_PACKAGE_MARKER_FILENAME_NO_EXTENSION = "__package__";
export const FERN_PACKAGE_MARKER_FILENAME = `${FERN_PACKAGE_MARKER_FILENAME_NO_EXTENSION}.yml`;
export const DEPENDENCIES_FILENAME = "dependencies.yml";
export const GENERATORS_CONFIGURATION_FILENAME = "generators.yml";
export const DEPENDENCIES_CONFIGURATION_FILENAME = "dependencies.yml";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface SingleNamespaceAPIDefinition extends RawSchemas.WithEnvironment

export interface MultiNamespaceAPIDefinition extends RawSchemas.WithEnvironmentsSchema, RawSchemas.WithAuthSchema {
type: "multiNamespace";
rootDefinitions: APIDefinitionLocation[] | undefined;
definitions: Record<string, APIDefinitionLocation[]>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
GeneratorsConfiguration
} from "./GeneratorsConfiguration";
import { isRawProtobufAPIDefinitionSchema } from "./isRawProtobufAPIDefinitionSchema";
import { APIConfigurationSchemaInternal } from "./schemas/APIConfigurationSchema";
import { APIConfigurationSchemaInternal, APIConfigurationV2Schema } from "./schemas/APIConfigurationSchema";
import { GeneratorGroupSchema } from "./schemas/GeneratorGroupSchema";
import { GeneratorInvocationSchema } from "./schemas/GeneratorInvocationSchema";
import { GeneratorOutputSchema } from "./schemas/GeneratorOutputSchema";
Expand Down Expand Up @@ -240,55 +240,88 @@ async function parseAPIConfigurationToApiLocations(
return apiDefinitions;
}

async function parseApiConfigurationV2Schema({
apiConfiguration,
rawConfiguration
}: {
apiConfiguration: APIConfigurationV2Schema;
rawConfiguration: GeneratorsConfigurationSchema;
}): Promise<APIDefinition> {
const rootDefinitions: APIDefinitionLocation[] = [];
const namespacedDefinitions: Record<string, APIDefinitionLocation[]> = {};

for (const spec of apiConfiguration.specs) {
if (isOpenAPISchema(spec)) {
const definitionLocation: APIDefinitionLocation = {
schema: {
type: "oss",
path: spec.openapi
},
origin: undefined,
overrides: spec.overrides,
audiences: [],
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
}
};
if (spec.namespace == null) {
rootDefinitions.push(definitionLocation);
} else {
namespacedDefinitions[spec.namespace] ??= [];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
namespacedDefinitions[spec.namespace]!.push(definitionLocation);
}
armandobelardo marked this conversation as resolved.
Show resolved Hide resolved
}
}

const partialConfig = {
"auth-schemes":
apiConfiguration.auth != null
? Object.fromEntries(
Object.entries(rawConfiguration["auth-schemes"] ?? {}).filter(([name, _]) => {
if (apiConfiguration.auth == null) {
return false;
}
return visitRawApiAuth(apiConfiguration.auth, {
any: (any) => {
return any.any.includes(name);
},
single: (single) => {
return single === name;
}
});
})
)
: undefined,
...apiConfiguration
};

// No namespaces
if (Object.keys(namespacedDefinitions).length === 0) {
return {
type: "singleNamespace",
definitions: rootDefinitions,
...partialConfig
};
}
// Yes namespaces
return {
type: "multiNamespace",
rootDefinitions,
definitions: namespacedDefinitions,
...partialConfig
};
}

async function parseAPIConfiguration(
rawGeneratorsConfiguration: GeneratorsConfigurationSchema
): Promise<APIDefinition> {
const apiConfiguration = rawGeneratorsConfiguration.api;

if (apiConfiguration != null && isApiConfigurationV2Schema(apiConfiguration)) {
return {
type: "singleNamespace",
"auth-schemes":
apiConfiguration.auth != null
? Object.fromEntries(
Object.entries(rawGeneratorsConfiguration["auth-schemes"] ?? {}).filter(([name, _]) => {
if (apiConfiguration.auth == null) {
return false;
}
return visitRawApiAuth(apiConfiguration.auth, {
any: (any) => {
return any.any.includes(name);
},
single: (single) => {
return single === name;
}
});
})
)
: undefined,
...apiConfiguration,
definitions: apiConfiguration.specs
.map((spec): APIDefinitionLocation | undefined => {
if (isOpenAPISchema(spec)) {
return {
schema: {
type: "oss",
path: spec.openapi
},
origin: undefined,
overrides: spec.overrides,
audiences: [],
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
}
};
}
return undefined;
})
.filter(isNonNullish)
};
return parseApiConfigurationV2Schema({ apiConfiguration, rawConfiguration: rawGeneratorsConfiguration });
}

if (isPlainObject(apiConfiguration) && "namespaces" in apiConfiguration) {
Expand All @@ -301,6 +334,7 @@ async function parseAPIConfiguration(
}
return {
type: "multiNamespace",
rootDefinitions: undefined,
definitions: namespacedDefinitions
};
}
Expand Down
Loading
Loading