@@ -3,7 +3,10 @@ import { assertNever, ContainerRunner } from "@fern-api/core-utils";
33import { AbsoluteFilePath , doesPathExist , join , RelativeFilePath } from "@fern-api/fs-utils" ;
44import { loggingExeca } from "@fern-api/logging-execa" ;
55import { TaskContext } from "@fern-api/task-context" ;
6+ import { createWriteStream } from "fs" ;
67import { copyFile , mkdir , readdir , readFile , rename , rmdir } from "fs/promises" ;
8+ import { basename } from "path" ;
9+ import { ZipFile } from "yazl" ;
710
811/** Directory (inside each generator's local output) where packaged artifacts are written. */
912export const PACK_OUTPUT_DIRECTORY = "fern-dist" ;
@@ -149,12 +152,19 @@ async function packOutputForLanguage({
149152 logArtifacts ( { distDir, context } ) ;
150153 return ;
151154 }
152- case "go" :
153- context . logger . warn (
154- "Go SDKs are distributed as source modules, so there is no package artifact to build. " +
155- "Share the output directory itself (e.g. as a zip), or reference it with a 'replace' directive in go.mod."
156- ) ;
155+ case "go" : {
156+ // Go modules have no binary package format ('go get' always fetches source), so the
157+ // shareable artifact is a zip of the module source. Consumers unzip it and reference
158+ // it with a 'replace' directive in go.mod.
159+ await mkdir ( distDir , { recursive : true } ) ;
160+ const zipName = `${ basename ( outputPath ) } -source.zip` ;
161+ await zipDirectory ( {
162+ sourceDir : outputPath ,
163+ zipPath : join ( distDir , RelativeFilePath . of ( zipName ) )
164+ } ) ;
165+ logArtifacts ( { distDir, context } ) ;
157166 return ;
167+ }
158168 case "swift" :
159169 context . logger . warn (
160170 "Swift SDKs are distributed as source packages (Swift Package Manager), so there is no package artifact to build. " +
@@ -217,6 +227,41 @@ async function runPackCommands({
217227 }
218228}
219229
230+ /**
231+ * Zips the contents of a directory (excluding fern-dist itself and any .git directory) into
232+ * `zipPath`. Runs in-process, so it behaves identically in host and docker pack modes and
233+ * requires no language toolchain.
234+ */
235+ async function zipDirectory ( {
236+ sourceDir,
237+ zipPath
238+ } : {
239+ sourceDir : AbsoluteFilePath ;
240+ zipPath : AbsoluteFilePath ;
241+ } ) : Promise < void > {
242+ const zip = new ZipFile ( ) ;
243+ const addEntries = async ( dir : AbsoluteFilePath , prefix : string ) : Promise < void > => {
244+ for ( const entry of await readdir ( dir , { withFileTypes : true } ) ) {
245+ if ( prefix === "" && ( entry . name === PACK_OUTPUT_DIRECTORY || entry . name === ".git" ) ) {
246+ continue ;
247+ }
248+ const entryPath = join ( dir , RelativeFilePath . of ( entry . name ) ) ;
249+ const entryName = prefix === "" ? entry . name : `${ prefix } /${ entry . name } ` ;
250+ if ( entry . isDirectory ( ) ) {
251+ await addEntries ( entryPath , entryName ) ;
252+ } else if ( entry . isFile ( ) ) {
253+ zip . addFile ( entryPath , entryName ) ;
254+ }
255+ }
256+ } ;
257+ await addEntries ( sourceDir , "" ) ;
258+ zip . end ( ) ;
259+ await new Promise < void > ( ( resolve , reject ) => {
260+ zip . outputStream . on ( "error" , reject ) ;
261+ zip . outputStream . pipe ( createWriteStream ( zipPath ) ) . on ( "close" , resolve ) . on ( "error" , reject ) ;
262+ } ) ;
263+ }
264+
220265async function removeDistDirIfEmpty ( outputPath : AbsoluteFilePath ) : Promise < void > {
221266 const distDir = join ( outputPath , RelativeFilePath . of ( PACK_OUTPUT_DIRECTORY ) ) ;
222267 try {
0 commit comments