diff --git a/packages/cli/generation/local-generation/local-workspace-runner/package.json b/packages/cli/generation/local-generation/local-workspace-runner/package.json index c6f82b6e1fd..c1847ab233f 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/package.json +++ b/packages/cli/generation/local-generation/local-workspace-runner/package.json @@ -63,12 +63,14 @@ "@fern-fern/generator-exec-sdk": "^0.0.1167", "chalk": "^5.3.0", "decompress": "^4.2.1", + "js-yaml": "^4.1.0", "prettier2": "npm:prettier@^2.7.1", "tmp-promise": "^3.0.3" }, "devDependencies": { "@fern-api/configs": "workspace:*", "@types/decompress": "^4.2.7", + "@types/js-yaml": "^4.0.5", "@types/node": "18.15.3", "@types/prettier2": "npm:@types/prettier@^2.7.3", "depcheck": "^1.4.7", diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts index e6a0bba5030..ecfe8765bf0 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts @@ -204,6 +204,12 @@ export async function writeFilesToDiskAndRunGenerator({ runner }); + await writeFernMetadata({ + outputDirectory: absolutePathToTmpOutputDirectory, + generatorInvocation, + context + }); + const taskHandler = new LocalTaskHandler({ context, absolutePathToLocalOutput, @@ -264,3 +270,41 @@ function getSourceConfig(workspace: FernWorkspace): SourceConfig { function getDockerDestinationForSource(source: IdentifiableSource): string { return `${DOCKER_SOURCES_DIRECTORY}/${source.id}`; } + +function getCliVersion(): string { + return process.env.CLI_VERSION ?? "unknown"; +} + +interface FernMetadata { + cliVersion: string; + generatorName: string; + generatorVersion: string; + generatorsYml?: { + config?: unknown; + }; +} + +async function writeFernMetadata({ + outputDirectory, + generatorInvocation, + context +}: { + outputDirectory: AbsoluteFilePath; + generatorInvocation: generatorsYml.GeneratorInvocation; + context: TaskContext; +}): Promise { + const cliVersion = getCliVersion(); + + const metadata: FernMetadata = { + cliVersion, + generatorName: generatorInvocation.name, + generatorVersion: generatorInvocation.version, + generatorsYml: { + config: generatorInvocation.config + } + }; + + const metadataPath = join(outputDirectory, ".fern.metadata.json"); + await writeFile(metadataPath, JSON.stringify(metadata, null, 2)); + context.logger.debug(`Wrote Fern metadata to: ${metadataPath}`); +} diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/RemoteTaskHandler.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/RemoteTaskHandler.ts index 38b17614ab4..f9f43930191 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/RemoteTaskHandler.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/RemoteTaskHandler.ts @@ -8,7 +8,7 @@ import axios from "axios"; import chalk from "chalk"; import decompress from "decompress"; import { createWriteStream } from "fs"; -import { mkdir, rm } from "fs/promises"; +import { mkdir, rm, writeFile } from "fs/promises"; import path from "path"; import { pipeline } from "stream/promises"; import terminalLink from "terminal-link"; @@ -101,7 +101,8 @@ export class RemoteTaskHandler { await downloadFilesForTask({ s3PreSignedReadUrl: finishedStatus.s3PreSignedReadUrlV2, absolutePathToLocalOutput, - context: this.context + context: this.context, + generatorInvocation: this.generatorInvocation }); } } @@ -152,11 +153,13 @@ export class RemoteTaskHandler { async function downloadFilesForTask({ s3PreSignedReadUrl, absolutePathToLocalOutput, - context + context, + generatorInvocation }: { s3PreSignedReadUrl: string; absolutePathToLocalOutput: AbsoluteFilePath; context: InteractiveTaskContext; + generatorInvocation: generatorsYml.GeneratorInvocation; }) { try { await downloadZipForTask({ @@ -164,6 +167,12 @@ async function downloadFilesForTask({ absolutePathToLocalOutput }); + await writeFernMetadata({ + outputDirectory: absolutePathToLocalOutput, + generatorInvocation, + context + }); + context.logger.info(chalk.green(`Downloaded to ${absolutePathToLocalOutput}`)); } catch (e) { context.failAndThrow("Failed to download files", e); @@ -209,3 +218,41 @@ function convertLogLevel(logLevel: FernFiddle.LogLevel): LogLevel { return LogLevel.Info; } } + +function getCliVersion(): string { + return process.env.CLI_VERSION ?? "unknown"; +} + +interface FernMetadata { + cliVersion: string; + generatorName: string; + generatorVersion: string; + generatorsYml?: { + config?: unknown; + }; +} + +async function writeFernMetadata({ + outputDirectory, + generatorInvocation, + context +}: { + outputDirectory: AbsoluteFilePath; + generatorInvocation: generatorsYml.GeneratorInvocation; + context: InteractiveTaskContext; +}): Promise { + const cliVersion = getCliVersion(); + + const metadata: FernMetadata = { + cliVersion, + generatorName: generatorInvocation.name, + generatorVersion: generatorInvocation.version, + generatorsYml: { + config: generatorInvocation.config + } + }; + + const metadataPath = join(outputDirectory, RelativeFilePath.of(".fern.metadata.json")); + await writeFile(metadataPath, JSON.stringify(metadata, null, 2)); + context.logger.debug(`Wrote Fern metadata to: ${metadataPath}`); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6064641c370..ed5ee6f95e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6307,6 +6307,9 @@ importers: decompress: specifier: ^4.2.1 version: 4.2.1 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 prettier2: specifier: npm:prettier@^2.7.1 version: prettier@2.8.8 @@ -6320,6 +6323,9 @@ importers: '@types/decompress': specifier: ^4.2.7 version: 4.2.7 + '@types/js-yaml': + specifier: ^4.0.5 + version: 4.0.9 '@types/node': specifier: 18.15.3 version: 18.15.3 @@ -7747,7 +7753,7 @@ importers: devDependencies: vitest: specifier: ^2.1.9 - version: 2.1.9(@types/node@18.19.129)(jsdom@20.0.3)(msw@2.11.3(@types/node@18.19.129)(typescript@5.9.2))(sass@1.93.2)(terser@5.44.0) + version: 2.1.9(@types/node@18.19.129)(jsdom@20.0.3)(msw@2.11.3(@types/node@18.19.129)(typescript@5.8.3))(sass@1.93.2)(terser@5.44.0) packages/core: dependencies: @@ -17698,15 +17704,6 @@ snapshots: msw: 2.11.3(@types/node@18.19.129)(typescript@5.8.3) vite: 5.4.20(@types/node@18.19.129)(sass@1.93.2)(terser@5.44.0) - '@vitest/mocker@2.1.9(msw@2.11.3(@types/node@18.19.129)(typescript@5.9.2))(vite@5.4.20(@types/node@18.19.129)(sass@1.93.2)(terser@5.44.0))': - dependencies: - '@vitest/spy': 2.1.9 - estree-walker: 3.0.3 - magic-string: 0.30.19 - optionalDependencies: - msw: 2.11.3(@types/node@18.19.129)(typescript@5.9.2) - vite: 5.4.20(@types/node@18.19.129)(sass@1.93.2)(terser@5.44.0) - '@vitest/pretty-format@2.1.9': dependencies: tinyrainbow: 1.2.0 @@ -20988,33 +20985,6 @@ snapshots: - '@types/node' optional: true - msw@2.11.3(@types/node@18.19.129)(typescript@5.9.2): - dependencies: - '@bundled-es-modules/cookie': 2.0.1 - '@bundled-es-modules/statuses': 1.0.1 - '@inquirer/confirm': 5.1.18(@types/node@18.19.129) - '@mswjs/interceptors': 0.39.7 - '@open-draft/deferred-promise': 2.2.0 - '@types/cookie': 0.6.0 - '@types/statuses': 2.0.6 - graphql: 16.11.0 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - rettime: 0.7.0 - strict-event-emitter: 0.5.1 - tough-cookie: 6.0.0 - type-fest: 4.41.0 - until-async: 3.0.2 - yargs: 17.7.2 - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - '@types/node' - optional: true - multimatch@5.0.0: dependencies: '@types/minimatch': 3.0.5 @@ -23206,42 +23176,6 @@ snapshots: - supports-color - terser - vitest@2.1.9(@types/node@18.19.129)(jsdom@20.0.3)(msw@2.11.3(@types/node@18.19.129)(typescript@5.9.2))(sass@1.93.2)(terser@5.44.0): - dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(msw@2.11.3(@types/node@18.19.129)(typescript@5.9.2))(vite@5.4.20(@types/node@18.19.129)(sass@1.93.2)(terser@5.44.0)) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.3.3 - debug: 4.4.3 - expect-type: 1.2.2 - magic-string: 0.30.19 - pathe: 1.1.2 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinypool: 1.1.1 - tinyrainbow: 1.2.0 - vite: 5.4.20(@types/node@18.19.129)(sass@1.93.2)(terser@5.44.0) - vite-node: 2.1.9(@types/node@18.19.129)(sass@1.93.2)(terser@5.44.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 18.19.129 - jsdom: 20.0.3 - transitivePeerDependencies: - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - vscode-jsonrpc@6.0.0: {} vscode-languageclient@7.0.0: