Skip to content

Commit

Permalink
chore(cli): seed writes out resovled-snippet-template files (#4628)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi authored Sep 12, 2024
1 parent d6ccbc9 commit c42567d
Show file tree
Hide file tree
Showing 178 changed files with 18,186 additions and 1,483 deletions.
1 change: 1 addition & 0 deletions packages/cli/configuration/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const DEFAULT_API_WORSPACE_FOLDER_NAME = "api";
export const FERNIGNORE_FILENAME = ".fernignore";
export const SNIPPET_JSON_FILENAME = "snippet.json";
export const SNIPPET_TEMPLATES_JSON_FILENAME = "snippet-templates.json";
export const RESOLVED_SNIPPET_TEMPLATES_MD = "resolved-snippet-templates.md";
export const DEFAULT_GROUP_GENERATORS_CONFIG_KEY = "default-group";

export const DOCS_DIRECTORY = "docs";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@fern-api/workspace-loader": "workspace:*",
"@fern-fern/fiddle-sdk": "0.0.584",
"@fern-fern/generator-exec-sdk": "^0.0.898",
"@fern-api/sdk": "0.12.3",
"chalk": "^5.3.0",
"decompress": "^4.2.1",
"tmp-promise": "^3.0.3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ import {
migrateIntermediateRepresentationForGenerator,
migrateIntermediateRepresentationThroughVersion
} from "@fern-api/ir-migrations";
import { SourceConfig } from "@fern-api/ir-sdk";
import { IntermediateRepresentation, SourceConfig } from "@fern-api/ir-sdk";
import { TaskContext } from "@fern-api/task-context";
import { FernWorkspace } from "@fern-api/workspace-loader";

export declare namespace getIntermediateRepresentation {
interface Return {
latest: IntermediateRepresentation;
migrated: unknown;
}
}

export async function getIntermediateRepresentation({
workspace,
audiences,
Expand All @@ -26,7 +33,7 @@ export async function getIntermediateRepresentation({
version: string | undefined;
packageName: string | undefined;
sourceConfig: SourceConfig | undefined;
}): Promise<unknown> {
}): Promise<getIntermediateRepresentation.Return> {
const intermediateRepresentation = await generateIntermediateRepresentation({
workspace,
audiences,
Expand Down Expand Up @@ -58,5 +65,8 @@ export async function getIntermediateRepresentation({
version: generatorInvocation.version
}
});
return migratedIntermediateRepresentation;
return {
latest: intermediateRepresentation,
migrated: migratedIntermediateRepresentation
};
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Audiences, generatorsYml } from "@fern-api/configuration";
import { runDocker } from "@fern-api/docker-utils";
import { AbsoluteFilePath, waitUntilPathExists } from "@fern-api/fs-utils";
import { ApiDefinitionSource, SourceConfig } from "@fern-api/ir-sdk";
import { ApiDefinitionSource, IntermediateRepresentation, SourceConfig } from "@fern-api/ir-sdk";
import { TaskContext } from "@fern-api/task-context";
import { FernWorkspace, IdentifiableSource } from "@fern-api/workspace-loader";
import * as FernGeneratorExecParsing from "@fern-fern/generator-exec-sdk/serialization";
Expand All @@ -18,6 +18,7 @@ import { getIntermediateRepresentation } from "./getIntermediateRepresentation";
import { LocalTaskHandler } from "./LocalTaskHandler";

export interface GeneratorRunResponse {
ir: IntermediateRepresentation;
/* Path to the generated IR */
absolutePathToIr: AbsoluteFilePath;
/* Path to the generated config.json */
Expand Down Expand Up @@ -59,14 +60,20 @@ export async function writeFilesToDiskAndRunGenerator({
generateOauthClients: boolean;
generatePaginatedClients: boolean;
}): Promise<GeneratorRunResponse> {
const absolutePathToIr = await writeIrToFile({
const { latest, migrated } = await getIntermediateRepresentation({
workspace,
audiences,
generatorInvocation,
workspaceTempDir,
context,
irVersionOverride,
outputVersionOverride
packageName: generatorsYml.getPackageName({ generatorInvocation }),
version: outputVersionOverride,
sourceConfig: getSourceConfig(workspace)
});
const absolutePathToIr = await writeIrToFile({
workspaceTempDir,
context,
ir: migrated
});
context.logger.debug("Wrote IR to: " + absolutePathToIr);

Expand Down Expand Up @@ -131,43 +138,26 @@ export async function writeFilesToDiskAndRunGenerator({

return {
absolutePathToIr,
absolutePathToConfigJson: absolutePathToWriteConfigJson
absolutePathToConfigJson: absolutePathToWriteConfigJson,
ir: latest
};
}

async function writeIrToFile({
workspace,
audiences,
generatorInvocation,
ir,
workspaceTempDir,
context,
irVersionOverride,
outputVersionOverride
context
}: {
workspace: FernWorkspace;
audiences: Audiences;
generatorInvocation: generatorsYml.GeneratorInvocation;
ir: unknown;
workspaceTempDir: DirectoryResult;
context: TaskContext;
irVersionOverride: string | undefined;
outputVersionOverride: string | undefined;
}): Promise<AbsoluteFilePath> {
const intermediateRepresentation = await getIntermediateRepresentation({
workspace,
audiences,
generatorInvocation,
context,
irVersionOverride,
packageName: generatorsYml.getPackageName({ generatorInvocation }),
version: outputVersionOverride,
sourceConfig: getSourceConfig(workspace)
});
context.logger.debug("Migrated IR");
const irFile = await tmp.file({
tmpdir: workspaceTempDir.path
});
const absolutePathToIr = AbsoluteFilePath.of(irFile.path);
await writeFile(absolutePathToIr, JSON.stringify(intermediateRepresentation, undefined, 4));
await writeFile(absolutePathToIr, JSON.stringify(ir, undefined, 4));
context.logger.debug(`Wrote IR to ${absolutePathToIr}`);
return absolutePathToIr;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { generatorsYml, SNIPPET_JSON_FILENAME, SNIPPET_TEMPLATES_JSON_FILENAME } from "@fern-api/configuration";
import {
generatorsYml,
RESOLVED_SNIPPET_TEMPLATES_MD,
SNIPPET_JSON_FILENAME,
SNIPPET_TEMPLATES_JSON_FILENAME
} from "@fern-api/configuration";
import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils";
import { TaskContext } from "@fern-api/task-context";
import { FernWorkspace } from "@fern-api/workspace-loader";
import chalk from "chalk";
import { writeFilesToDiskAndRunGenerator } from "./runGenerator";
import { getWorkspaceTempDir } from "./runLocalGenerationForWorkspace";
import { readFile } from "fs/promises";
import { Fern, Template } from "@fern-api/sdk";
import { HttpEndpoint } from "@fern-api/ir-sdk";
import { writeFile } from "fs/promises";
import { IntermediateRepresentation } from "@fern-api/ir-sdk";
import * as prettier from "prettier";

export async function runLocalGenerationForSeed({
organization,
Expand Down Expand Up @@ -35,7 +46,23 @@ export async function runLocalGenerationForSeed({
"Cannot generate because output location is not local-file-system"
);
} else {
await writeFilesToDiskAndRunGenerator({
const absolutePathToLocalSnippetTemplateJSON = generatorInvocation.absolutePathToLocalOutput
? AbsoluteFilePath.of(
join(
generatorInvocation.absolutePathToLocalOutput,
RelativeFilePath.of(SNIPPET_TEMPLATES_JSON_FILENAME)
)
)
: undefined;
const absolutePathToResolvedSnipppetTemplates = generatorInvocation.absolutePathToLocalOutput
? AbsoluteFilePath.of(
join(
generatorInvocation.absolutePathToLocalOutput,
RelativeFilePath.of(RESOLVED_SNIPPET_TEMPLATES_MD)
)
)
: undefined;
const { ir } = await writeFilesToDiskAndRunGenerator({
organization,
absolutePathToFernConfig,
workspace,
Expand All @@ -49,14 +76,7 @@ export async function runLocalGenerationForSeed({
)
)
: undefined,
absolutePathToLocalSnippetTemplateJSON: generatorInvocation.absolutePathToLocalOutput
? AbsoluteFilePath.of(
join(
generatorInvocation.absolutePathToLocalOutput,
RelativeFilePath.of(SNIPPET_TEMPLATES_JSON_FILENAME)
)
)
: undefined,
absolutePathToLocalSnippetTemplateJSON,
audiences: generatorGroup.audiences,
workspaceTempDir,
keepDocker,
Expand All @@ -67,6 +87,17 @@ export async function runLocalGenerationForSeed({
generateOauthClients: true,
generatePaginatedClients: true
});
if (
absolutePathToLocalSnippetTemplateJSON != null &&
absolutePathToResolvedSnipppetTemplates != null
) {
await writeResolvedSnippetsJson({
absolutePathToLocalSnippetTemplateJSON,
absolutePathToResolvedSnipppetTemplates,
ir,
generatorInvocation
});
}
interactiveTaskContext.logger.info(
chalk.green("Wrote files to " + generatorInvocation.absolutePathToLocalOutput)
);
Expand All @@ -80,6 +111,101 @@ export async function runLocalGenerationForSeed({
}
}

export async function writeResolvedSnippetsJson({
absolutePathToResolvedSnipppetTemplates,
absolutePathToLocalSnippetTemplateJSON,
ir,
generatorInvocation
}: {
absolutePathToResolvedSnipppetTemplates: AbsoluteFilePath;
absolutePathToLocalSnippetTemplateJSON: AbsoluteFilePath;
ir: IntermediateRepresentation;
generatorInvocation: generatorsYml.GeneratorInvocation;
}): Promise<void> {
const endpointSnippetTemplates: Record<string, Fern.SnippetRegistryEntry> = {};
if (absolutePathToLocalSnippetTemplateJSON != null) {
const contents = (await readFile(absolutePathToLocalSnippetTemplateJSON)).toString();
const parsed = JSON.parse(contents);
if (Array.isArray(parsed)) {
for (const template of parsed) {
const entry: Fern.SnippetRegistryEntry = template;
if (entry.endpointId.identifierOverride != null) {
endpointSnippetTemplates[entry.endpointId.identifierOverride] = entry;
}
}
}
}

const irEndpoints: Record<string, HttpEndpoint> = Object.fromEntries(
Object.entries(ir.services)
.flatMap(([_, service]) => service.endpoints)
.map((endpoint) => [endpoint.id, endpoint])
);

const snippets: string[] = [];
for (const [endpointId, snippetTemplate] of Object.entries(endpointSnippetTemplates)) {
const template = Template.from(snippetTemplate);
const irEndpoint = irEndpoints[endpointId];
if (irEndpoint == null) {
continue;
}
for (const example of [...irEndpoint.userSpecifiedExamples, ...irEndpoint.autogeneratedExamples]) {
try {
const snippet = template.resolve({
headers: [
...(example.example?.serviceHeaders ?? []),
...(example.example?.endpointHeaders ?? [])
].map((header) => {
return {
name: header.name.wireValue,
value: header.value.jsonExample
};
}),
pathParameters: [
...(example.example?.rootPathParameters ?? []),
...(example.example?.servicePathParameters ?? []),
...(example.example?.endpointPathParameters ?? [])
].map((parameter) => {
return {
name: parameter.name.originalName,
value: parameter.value.jsonExample
};
}),
queryParameters: [...(example.example?.queryParameters ?? [])].map((parameter) => {
return {
name: parameter.name.wireValue,
value: parameter.value.jsonExample
};
}),
requestBody: example.example?.request
});
switch (snippet.type) {
case "typescript":
try {
snippets.push(prettier.format(snippet.client, { parser: "babel" }));
} catch {
snippets.push(snippet.client);
}
break;
case "python":
snippets.push(snippet.sync_client);
break;
}
} catch (err) {}
}
}
let resovledMd = "";
for (const snippet of snippets) {
resovledMd += `\`\`\`${generatorInvocation.language}
${snippet}
\`\`\`
\n\n`;
}
if (resovledMd.length > 0) {
await writeFile(absolutePathToResolvedSnipppetTemplates, resovledMd);
}
}

export async function writeIrAndConfigJson({
organization,
absolutePathToFernConfig,
Expand Down
32 changes: 32 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c42567d

Please sign in to comment.