@fern-api/fern-api-dev 4.47.0-8-ga7152010378 → 4.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/cli.cjs +412 -286
  2. package/package.json +1 -1
package/cli.cjs CHANGED
@@ -601952,7 +601952,7 @@ var AccessTokenPosthogManager = class {
601952
601952
  properties: {
601953
601953
  ...event,
601954
601954
  ...event.properties,
601955
- version: "4.47.0-8-ga7152010378",
601955
+ version: "4.48.0",
601956
601956
  usingAccessToken: true
601957
601957
  }
601958
601958
  });
@@ -602003,7 +602003,7 @@ var UserPosthogManager = class {
602003
602003
  distinctId: this.userId ?? await this.getPersistedDistinctId(),
602004
602004
  event: "CLI",
602005
602005
  properties: {
602006
- version: "4.47.0-8-ga7152010378",
602006
+ version: "4.48.0",
602007
602007
  ...event,
602008
602008
  ...event.properties,
602009
602009
  usingAccessToken: false,
@@ -825858,7 +825858,7 @@ var import_path54 = __toESM(require("path"), 1);
825858
825858
  var LOCAL_STORAGE_FOLDER4 = ".fern-dev";
825859
825859
  var LOGS_FOLDER_NAME = "logs";
825860
825860
  function getCliSource() {
825861
- const version7 = "4.47.0-8-ga7152010378";
825861
+ const version7 = "4.48.0";
825862
825862
  return `cli@${version7}`;
825863
825863
  }
825864
825864
  var DebugLogger = class {
@@ -835928,6 +835928,12 @@ var DocsPublishConflictError = class extends Error {
835928
835928
  this.name = "DocsPublishConflictError";
835929
835929
  }
835930
835930
  };
835931
+ async function unlockDeploy({ fdrOrigin, token, domain: domain3, basepath }) {
835932
+ try {
835933
+ await axios_default.post(`${fdrOrigin}/docs-deployment/unlock`, { domain: domain3, basepath }, { headers: { Authorization: `Bearer ${token}` } });
835934
+ } catch {
835935
+ }
835936
+ }
835931
835937
  async function calculateFileHash(absoluteFilePath) {
835932
835938
  const fileBuffer = await (0, import_promises92.readFile)(absoluteFilePath);
835933
835939
  return (0, import_crypto10.createHash)("sha256").update(new Uint8Array(fileBuffer)).digest("hex");
@@ -835965,274 +835971,303 @@ async function publishDocs({ token, organization, docsWorkspace, domain: domain3
835965
835971
  if (isBasepathAware) {
835966
835972
  context3.logger.debug("Experimental flag 'basepath-aware' is enabled - using basepath-aware S3 key format");
835967
835973
  }
835968
- const resolver2 = new DocsDefinitionResolver({
835969
- domain: domain3,
835970
- docsWorkspace,
835971
- ossWorkspaces,
835972
- apiWorkspaces,
835973
- taskContext: context3,
835974
- editThisPage,
835975
- uploadFiles: async (files) => {
835976
- const filesWithSanitizedPaths = files.map((file4) => ({
835977
- ...file4,
835978
- sanitizedPath: sanitizeRelativePathForS3(file4.relativeFilePath)
835979
- }));
835980
- const filesMap = new Map(filesWithSanitizedPaths.map((file4) => [file4.absoluteFilePath, file4]));
835981
- const sanitizedToAbsoluteMap = new Map(filesWithSanitizedPaths.map((file4) => [file4.sanitizedPath, file4.absoluteFilePath]));
835982
- const filesWithMimeType = filesWithSanitizedPaths.map((fileMetadata) => ({
835983
- ...fileMetadata,
835984
- mediaType: mime.lookup(fileMetadata.absoluteFilePath)
835985
- })).filter((fileMetadata) => fileMetadata.mediaType !== false);
835986
- const imagesToMeasure = filesWithMimeType.filter((file4) => MediaType.parse(file4.mediaType)?.isImage() ?? false).map((file4) => file4.absoluteFilePath);
835987
- const measuredImages = await measureImageSizes(imagesToMeasure, MEASURE_IMAGE_BATCH_SIZE, context3);
835988
- context3.logger.debug(`Hashing ${measuredImages.size} image files with concurrency ${HASH_CONCURRENCY}...`);
835989
- const hashImageStart = performance.now();
835990
- const imageArray = Array.from(measuredImages.values());
835991
- const hashedImages = await asyncPool2(HASH_CONCURRENCY, imageArray, async (image3) => {
835992
- const filePath = filesMap.get(image3.filePath);
835993
- if (filePath == null) {
835994
- return null;
835995
- }
835996
- const sanitizedPath = filePath.sanitizedPath;
835997
- const obj = {
835998
- filePath: FdrAPI_exports.docs.v1.write.FilePath(convertToFernHostRelativeFilePath(sanitizedPath)),
835999
- width: image3.width,
836000
- height: image3.height,
836001
- blurDataUrl: image3.blurDataUrl,
836002
- alt: void 0,
836003
- fileHash: await calculateFileHash(filePath.absoluteFilePath)
836004
- };
836005
- return obj;
836006
- });
836007
- const images = hashedImages.filter((img2) => img2 != null);
836008
- const hashImageTime = performance.now() - hashImageStart;
836009
- context3.logger.debug(`Hashed ${images.length} images in ${hashImageTime.toFixed(0)}ms`);
836010
- const nonImageFiles = filesWithSanitizedPaths.filter(({ absoluteFilePath }) => !measuredImages.has(absoluteFilePath));
836011
- context3.logger.debug(`Hashing ${nonImageFiles.length} non-image files with concurrency ${HASH_CONCURRENCY}...`);
836012
- const hashNonImageStart = performance.now();
836013
- const filepaths = await asyncPool2(HASH_CONCURRENCY, nonImageFiles, async (file4) => {
836014
- return {
836015
- path: FdrAPI_exports.docs.v1.write.FilePath(convertToFernHostRelativeFilePath(file4.sanitizedPath)),
836016
- fileHash: await calculateFileHash(file4.absoluteFilePath)
836017
- };
836018
- });
836019
- const hashNonImageTime = performance.now() - hashNonImageStart;
836020
- context3.logger.debug(`Hashed ${filepaths.length} non-image files in ${hashNonImageTime.toFixed(0)}ms`);
836021
- if (preview) {
836022
- const startDocsRegisterResponse = await fdr.docs.v2.write.startDocsPreviewRegister({
836023
- orgId: FdrAPI_exports.OrgId(organization),
836024
- authConfig: isPrivate ? { type: "private", authType: "sso" } : { type: "public" },
836025
- filepaths,
836026
- images,
836027
- basePath,
836028
- previewId: previewId != null ? sanitizePreviewId(previewId) : void 0
836029
- });
836030
- if (startDocsRegisterResponse.ok) {
836031
- urlToOutput = startDocsRegisterResponse.body.previewUrl;
836032
- docsRegistrationId = startDocsRegisterResponse.body.docsRegistrationId;
836033
- context3.logger.debug(`Received preview registration ID: ${docsRegistrationId}`);
836034
- if (skipUpload) {
836035
- context3.logger.debug("Skip-upload mode: skipping file uploads for docs preview");
836036
- } else {
836037
- const skippedSet = new Set(startDocsRegisterResponse.body.skippedFiles || []);
836038
- const urlsToUpload = Object.fromEntries(Object.entries(startDocsRegisterResponse.body.uploadUrls).filter(([filepath]) => !skippedSet.has(filepath)));
836039
- const uploadCount = Object.keys(urlsToUpload).length;
836040
- const skippedCount = skippedSet.size;
836041
- if (uploadCount > 0) {
836042
- context3.logger.debug(`Uploading ${uploadCount} files (${skippedCount} skipped)...`);
836043
- await uploadFiles(urlsToUpload, docsWorkspace.absoluteFilePath, context3, UPLOAD_FILE_BATCH_SIZE, sanitizedToAbsoluteMap);
835974
+ let deployLocked = false;
835975
+ const doUnlock = () => {
835976
+ if (deployLocked && !preview) {
835977
+ deployLocked = false;
835978
+ context3.logger.debug("Unlocking docs deploy due to early exit...");
835979
+ void unlockDeploy({ fdrOrigin, token: token.value, domain: domain3, basepath: basePath });
835980
+ }
835981
+ };
835982
+ const onSignal = async () => {
835983
+ if (deployLocked && !preview) {
835984
+ deployLocked = false;
835985
+ context3.logger.debug("Unlocking docs deploy due to signal...");
835986
+ await unlockDeploy({ fdrOrigin, token: token.value, domain: domain3, basepath: basePath });
835987
+ }
835988
+ process.exit(1);
835989
+ };
835990
+ process.on("SIGINT", onSignal);
835991
+ process.on("SIGTERM", onSignal);
835992
+ try {
835993
+ const resolver2 = new DocsDefinitionResolver({
835994
+ domain: domain3,
835995
+ docsWorkspace,
835996
+ ossWorkspaces,
835997
+ apiWorkspaces,
835998
+ taskContext: context3,
835999
+ editThisPage,
836000
+ uploadFiles: async (files) => {
836001
+ const filesWithSanitizedPaths = files.map((file4) => ({
836002
+ ...file4,
836003
+ sanitizedPath: sanitizeRelativePathForS3(file4.relativeFilePath)
836004
+ }));
836005
+ const filesMap = new Map(filesWithSanitizedPaths.map((file4) => [file4.absoluteFilePath, file4]));
836006
+ const sanitizedToAbsoluteMap = new Map(filesWithSanitizedPaths.map((file4) => [file4.sanitizedPath, file4.absoluteFilePath]));
836007
+ const filesWithMimeType = filesWithSanitizedPaths.map((fileMetadata) => ({
836008
+ ...fileMetadata,
836009
+ mediaType: mime.lookup(fileMetadata.absoluteFilePath)
836010
+ })).filter((fileMetadata) => fileMetadata.mediaType !== false);
836011
+ const imagesToMeasure = filesWithMimeType.filter((file4) => MediaType.parse(file4.mediaType)?.isImage() ?? false).map((file4) => file4.absoluteFilePath);
836012
+ const measuredImages = await measureImageSizes(imagesToMeasure, MEASURE_IMAGE_BATCH_SIZE, context3);
836013
+ context3.logger.debug(`Hashing ${measuredImages.size} image files with concurrency ${HASH_CONCURRENCY}...`);
836014
+ const hashImageStart = performance.now();
836015
+ const imageArray = Array.from(measuredImages.values());
836016
+ const hashedImages = await asyncPool2(HASH_CONCURRENCY, imageArray, async (image3) => {
836017
+ const filePath = filesMap.get(image3.filePath);
836018
+ if (filePath == null) {
836019
+ return null;
836020
+ }
836021
+ const sanitizedPath = filePath.sanitizedPath;
836022
+ const obj = {
836023
+ filePath: FdrAPI_exports.docs.v1.write.FilePath(convertToFernHostRelativeFilePath(sanitizedPath)),
836024
+ width: image3.width,
836025
+ height: image3.height,
836026
+ blurDataUrl: image3.blurDataUrl,
836027
+ alt: void 0,
836028
+ fileHash: await calculateFileHash(filePath.absoluteFilePath)
836029
+ };
836030
+ return obj;
836031
+ });
836032
+ const images = hashedImages.filter((img2) => img2 != null);
836033
+ const hashImageTime = performance.now() - hashImageStart;
836034
+ context3.logger.debug(`Hashed ${images.length} images in ${hashImageTime.toFixed(0)}ms`);
836035
+ const nonImageFiles = filesWithSanitizedPaths.filter(({ absoluteFilePath }) => !measuredImages.has(absoluteFilePath));
836036
+ context3.logger.debug(`Hashing ${nonImageFiles.length} non-image files with concurrency ${HASH_CONCURRENCY}...`);
836037
+ const hashNonImageStart = performance.now();
836038
+ const filepaths = await asyncPool2(HASH_CONCURRENCY, nonImageFiles, async (file4) => {
836039
+ return {
836040
+ path: FdrAPI_exports.docs.v1.write.FilePath(convertToFernHostRelativeFilePath(file4.sanitizedPath)),
836041
+ fileHash: await calculateFileHash(file4.absoluteFilePath)
836042
+ };
836043
+ });
836044
+ const hashNonImageTime = performance.now() - hashNonImageStart;
836045
+ context3.logger.debug(`Hashed ${filepaths.length} non-image files in ${hashNonImageTime.toFixed(0)}ms`);
836046
+ if (preview) {
836047
+ const startDocsRegisterResponse = await fdr.docs.v2.write.startDocsPreviewRegister({
836048
+ orgId: FdrAPI_exports.OrgId(organization),
836049
+ authConfig: isPrivate ? { type: "private", authType: "sso" } : { type: "public" },
836050
+ filepaths,
836051
+ images,
836052
+ basePath,
836053
+ previewId: previewId != null ? sanitizePreviewId(previewId) : void 0
836054
+ });
836055
+ if (startDocsRegisterResponse.ok) {
836056
+ urlToOutput = startDocsRegisterResponse.body.previewUrl;
836057
+ docsRegistrationId = startDocsRegisterResponse.body.docsRegistrationId;
836058
+ context3.logger.debug(`Received preview registration ID: ${docsRegistrationId}`);
836059
+ if (skipUpload) {
836060
+ context3.logger.debug("Skip-upload mode: skipping file uploads for docs preview");
836044
836061
  } else {
836045
- context3.logger.debug(`No files to upload (all ${skippedCount} up to date)`);
836062
+ const skippedSet = new Set(startDocsRegisterResponse.body.skippedFiles || []);
836063
+ const urlsToUpload = Object.fromEntries(Object.entries(startDocsRegisterResponse.body.uploadUrls).filter(([filepath]) => !skippedSet.has(filepath)));
836064
+ const uploadCount = Object.keys(urlsToUpload).length;
836065
+ const skippedCount = skippedSet.size;
836066
+ if (uploadCount > 0) {
836067
+ context3.logger.debug(`Uploading ${uploadCount} files (${skippedCount} skipped)...`);
836068
+ await uploadFiles(urlsToUpload, docsWorkspace.absoluteFilePath, context3, UPLOAD_FILE_BATCH_SIZE, sanitizedToAbsoluteMap);
836069
+ } else {
836070
+ context3.logger.debug(`No files to upload (all ${skippedCount} up to date)`);
836071
+ }
836046
836072
  }
836047
- }
836048
- return convertToFilePathPairs(startDocsRegisterResponse.body.uploadUrls, docsWorkspace.absoluteFilePath, sanitizedToAbsoluteMap);
836049
- } else {
836050
- return await startDocsRegisterFailed(startDocsRegisterResponse.error, context3, organization);
836051
- }
836052
- } else {
836053
- const startDocsRegisterResponse = await fdr.docs.v2.write.startDocsRegister({
836054
- domain: domain3,
836055
- customDomains,
836056
- authConfig,
836057
- apiId: FdrAPI_exports.ApiId(""),
836058
- orgId: FdrAPI_exports.OrgId(organization),
836059
- filepaths,
836060
- images,
836061
- ...isBasepathAware && { basepathAware: true }
836062
- });
836063
- if (startDocsRegisterResponse.ok) {
836064
- docsRegistrationId = startDocsRegisterResponse.body.docsRegistrationId;
836065
- context3.logger.debug(`Received production registration ID: ${docsRegistrationId}`);
836066
- const skippedCount = startDocsRegisterResponse.body.skippedFiles?.length || 0;
836067
- if (skippedCount > 0) {
836068
- context3.logger.info(`Skipped ${skippedCount} unchanged file${skippedCount === 1 ? "" : "s"} (already uploaded)`);
836069
- }
836070
- if (skipUpload) {
836071
- context3.logger.debug("Skip-upload mode: skipping file uploads for docs");
836073
+ return convertToFilePathPairs(startDocsRegisterResponse.body.uploadUrls, docsWorkspace.absoluteFilePath, sanitizedToAbsoluteMap);
836072
836074
  } else {
836073
- const skippedSet = new Set(startDocsRegisterResponse.body.skippedFiles || []);
836074
- const urlsToUpload = Object.fromEntries(Object.entries(startDocsRegisterResponse.body.uploadUrls).filter(([filepath]) => !skippedSet.has(filepath)));
836075
- const uploadCount = Object.keys(urlsToUpload).length;
836076
- if (uploadCount > 0) {
836077
- context3.logger.info(`\u2191 Uploading ${uploadCount} files...`);
836078
- await uploadFiles(urlsToUpload, docsWorkspace.absoluteFilePath, context3, UPLOAD_FILE_BATCH_SIZE, sanitizedToAbsoluteMap);
836075
+ return await startDocsRegisterFailed(startDocsRegisterResponse.error, context3, organization);
836076
+ }
836077
+ } else {
836078
+ const startDocsRegisterResponse = await fdr.docs.v2.write.startDocsRegister({
836079
+ domain: domain3,
836080
+ customDomains,
836081
+ authConfig,
836082
+ apiId: FdrAPI_exports.ApiId(""),
836083
+ orgId: FdrAPI_exports.OrgId(organization),
836084
+ filepaths,
836085
+ images,
836086
+ ...isBasepathAware && { basepathAware: true }
836087
+ });
836088
+ if (startDocsRegisterResponse.ok) {
836089
+ docsRegistrationId = startDocsRegisterResponse.body.docsRegistrationId;
836090
+ deployLocked = true;
836091
+ context3.logger.debug(`Received production registration ID: ${docsRegistrationId}`);
836092
+ const skippedCount = startDocsRegisterResponse.body.skippedFiles?.length || 0;
836093
+ if (skippedCount > 0) {
836094
+ context3.logger.info(`Skipped ${skippedCount} unchanged file${skippedCount === 1 ? "" : "s"} (already uploaded)`);
836095
+ }
836096
+ if (skipUpload) {
836097
+ context3.logger.debug("Skip-upload mode: skipping file uploads for docs");
836079
836098
  } else {
836080
- context3.logger.info("No files to upload (all up to date)");
836099
+ const skippedSet = new Set(startDocsRegisterResponse.body.skippedFiles || []);
836100
+ const urlsToUpload = Object.fromEntries(Object.entries(startDocsRegisterResponse.body.uploadUrls).filter(([filepath]) => !skippedSet.has(filepath)));
836101
+ const uploadCount = Object.keys(urlsToUpload).length;
836102
+ if (uploadCount > 0) {
836103
+ context3.logger.info(`\u2191 Uploading ${uploadCount} files...`);
836104
+ await uploadFiles(urlsToUpload, docsWorkspace.absoluteFilePath, context3, UPLOAD_FILE_BATCH_SIZE, sanitizedToAbsoluteMap);
836105
+ } else {
836106
+ context3.logger.info("No files to upload (all up to date)");
836107
+ }
836081
836108
  }
836109
+ return convertToFilePathPairs(startDocsRegisterResponse.body.uploadUrls, docsWorkspace.absoluteFilePath, sanitizedToAbsoluteMap);
836110
+ } else {
836111
+ return startDocsRegisterFailed(startDocsRegisterResponse.error, context3, organization);
836082
836112
  }
836083
- return convertToFilePathPairs(startDocsRegisterResponse.body.uploadUrls, docsWorkspace.absoluteFilePath, sanitizedToAbsoluteMap);
836084
- } else {
836085
- return startDocsRegisterFailed(startDocsRegisterResponse.error, context3, organization);
836086
836113
  }
836087
- }
836088
- },
836089
- registerApi: async ({ ir: ir15, snippetsConfig, playgroundConfig, apiName, workspace, graphqlOperations, graphqlTypes }) => {
836090
- let apiDefinition = convertIrToFdrApi({
836091
- ir: ir15,
836092
- snippetsConfig,
836093
- playgroundConfig,
836094
- graphqlOperations,
836095
- graphqlTypes,
836096
- context: context3,
836097
- apiNameOverride: apiName
836098
- });
836099
- const aiEnhancerConfig = getAIEnhancerConfig(withAiExamples, docsWorkspace.config.aiExamples?.style ?? docsWorkspace.config.experimental?.aiExampleStyleInstructions);
836100
- if (aiEnhancerConfig) {
836101
- const sources = workspace?.getSources();
836102
- const openApiSources = sources?.filter((source2) => source2.type === "openapi").map((source2) => ({
836103
- absoluteFilePath: source2.absoluteFilePath,
836104
- absoluteFilePathToOverrides: source2.absoluteFilePathToOverrides
836105
- }));
836106
- if (openApiSources == null || openApiSources.length === 0) {
836107
- context3.logger.debug("Skipping AI example enhancement: no OpenAPI source file paths available");
836108
- } else {
836109
- apiDefinition = await enhanceExamplesWithAI(apiDefinition, aiEnhancerConfig, context3, token, organization, openApiSources);
836110
- }
836111
- }
836112
- let dynamicIRsByLanguage;
836113
- let languagesWithExistingSdkDynamicIr = /* @__PURE__ */ new Set();
836114
- if (Object.keys(snippetsConfig).length === 0) {
836115
- context3.logger.debug(`No snippets configuration defined, skipping snippet generation...`);
836116
- } else if (!disableDynamicSnippets) {
836117
- const existingSdkDynamicIrs = await checkAndDownloadExistingSdkDynamicIRs({
836118
- fdr,
836119
- workspace,
836120
- organization,
836121
- context: context3,
836122
- snippetsConfig
836123
- });
836124
- if (existingSdkDynamicIrs && Object.keys(existingSdkDynamicIrs).length > 0) {
836125
- dynamicIRsByLanguage = existingSdkDynamicIrs;
836126
- languagesWithExistingSdkDynamicIr = new Set(Object.keys(existingSdkDynamicIrs));
836127
- context3.logger.debug(`Using existing SDK dynamic IRs for: ${Object.keys(existingSdkDynamicIrs).join(", ")}`);
836128
- }
836129
- const generatedDynamicIRs = await generateLanguageSpecificDynamicIRs({
836130
- workspace,
836131
- organization,
836132
- context: context3,
836114
+ },
836115
+ registerApi: async ({ ir: ir15, snippetsConfig, playgroundConfig, apiName, workspace, graphqlOperations, graphqlTypes }) => {
836116
+ let apiDefinition = convertIrToFdrApi({
836117
+ ir: ir15,
836133
836118
  snippetsConfig,
836134
- skipLanguages: languagesWithExistingSdkDynamicIr
836135
- });
836136
- if (generatedDynamicIRs) {
836137
- dynamicIRsByLanguage = {
836138
- ...dynamicIRsByLanguage,
836139
- ...generatedDynamicIRs
836140
- };
836141
- }
836142
- }
836143
- const response = await fdr.api.v1.register.registerApiDefinition({
836144
- orgId: FdrAPI_exports.OrgId(organization),
836145
- apiId: FdrAPI_exports.ApiId(apiName ?? ir15.apiName.originalName),
836146
- definition: apiDefinition,
836147
- definitionV2: void 0,
836148
- dynamicIRs: dynamicIRsByLanguage,
836149
- docsUrl
836150
- });
836151
- if (response.ok) {
836152
- context3.logger.debug(`Registered API Definition ${apiName}: ${response.body.apiDefinitionId}`);
836153
- if (response.body.dynamicIRs && dynamicIRsByLanguage) {
836154
- if (skipUpload) {
836155
- context3.logger.debug("Skip-upload mode: skipping dynamic IR uploads");
836119
+ playgroundConfig,
836120
+ graphqlOperations,
836121
+ graphqlTypes,
836122
+ context: context3,
836123
+ apiNameOverride: apiName
836124
+ });
836125
+ const aiEnhancerConfig = getAIEnhancerConfig(withAiExamples, docsWorkspace.config.aiExamples?.style ?? docsWorkspace.config.experimental?.aiExampleStyleInstructions);
836126
+ if (aiEnhancerConfig) {
836127
+ const sources = workspace?.getSources();
836128
+ const openApiSources = sources?.filter((source2) => source2.type === "openapi").map((source2) => ({
836129
+ absoluteFilePath: source2.absoluteFilePath,
836130
+ absoluteFilePathToOverrides: source2.absoluteFilePathToOverrides
836131
+ }));
836132
+ if (openApiSources == null || openApiSources.length === 0) {
836133
+ context3.logger.debug("Skipping AI example enhancement: no OpenAPI source file paths available");
836156
836134
  } else {
836157
- await uploadDynamicIRs({
836158
- dynamicIRs: dynamicIRsByLanguage,
836159
- dynamicIRUploadUrls: response.body.dynamicIRs,
836160
- context: context3,
836161
- apiId: response.body.apiDefinitionId
836162
- });
836135
+ apiDefinition = await enhanceExamplesWithAI(apiDefinition, aiEnhancerConfig, context3, token, organization, openApiSources);
836163
836136
  }
836164
836137
  }
836165
- return response.body.apiDefinitionId;
836166
- } else {
836167
- switch (response.error.error) {
836168
- case "UnauthorizedError":
836169
- case "UserNotInOrgError": {
836170
- return context3.failAndThrow("You do not have permissions to register the docs. Reach out to support@buildwithfern.com");
836138
+ let dynamicIRsByLanguage;
836139
+ let languagesWithExistingSdkDynamicIr = /* @__PURE__ */ new Set();
836140
+ if (Object.keys(snippetsConfig).length === 0) {
836141
+ context3.logger.debug(`No snippets configuration defined, skipping snippet generation...`);
836142
+ } else if (!disableDynamicSnippets) {
836143
+ const existingSdkDynamicIrs = await checkAndDownloadExistingSdkDynamicIRs({
836144
+ fdr,
836145
+ workspace,
836146
+ organization,
836147
+ context: context3,
836148
+ snippetsConfig
836149
+ });
836150
+ if (existingSdkDynamicIrs && Object.keys(existingSdkDynamicIrs).length > 0) {
836151
+ dynamicIRsByLanguage = existingSdkDynamicIrs;
836152
+ languagesWithExistingSdkDynamicIr = new Set(Object.keys(existingSdkDynamicIrs));
836153
+ context3.logger.debug(`Using existing SDK dynamic IRs for: ${Object.keys(existingSdkDynamicIrs).join(", ")}`);
836171
836154
  }
836172
- default: {
836173
- const errorDetails = extractErrorDetails(response.error);
836174
- context3.logger.error(`FDR registerApiDefinition failed. Error details:
836175
- ${JSON.stringify(errorDetails, void 0, 2)}`);
836176
- if (apiName != null) {
836177
- return context3.failAndThrow(`Failed to publish docs because API definition (${apiName}) could not be uploaded. Please contact support@buildwithfern.com`, errorDetails);
836155
+ const generatedDynamicIRs = await generateLanguageSpecificDynamicIRs({
836156
+ workspace,
836157
+ organization,
836158
+ context: context3,
836159
+ snippetsConfig,
836160
+ skipLanguages: languagesWithExistingSdkDynamicIr
836161
+ });
836162
+ if (generatedDynamicIRs) {
836163
+ dynamicIRsByLanguage = {
836164
+ ...dynamicIRsByLanguage,
836165
+ ...generatedDynamicIRs
836166
+ };
836167
+ }
836168
+ }
836169
+ const response = await fdr.api.v1.register.registerApiDefinition({
836170
+ orgId: FdrAPI_exports.OrgId(organization),
836171
+ apiId: FdrAPI_exports.ApiId(apiName ?? ir15.apiName.originalName),
836172
+ definition: apiDefinition,
836173
+ definitionV2: void 0,
836174
+ dynamicIRs: dynamicIRsByLanguage,
836175
+ docsUrl
836176
+ });
836177
+ if (response.ok) {
836178
+ context3.logger.debug(`Registered API Definition ${apiName}: ${response.body.apiDefinitionId}`);
836179
+ if (response.body.dynamicIRs && dynamicIRsByLanguage) {
836180
+ if (skipUpload) {
836181
+ context3.logger.debug("Skip-upload mode: skipping dynamic IR uploads");
836178
836182
  } else {
836179
- return context3.failAndThrow(`Failed to publish docs because API definition could not be uploaded. Please contact support@buildwithfern.com`, errorDetails);
836183
+ await uploadDynamicIRs({
836184
+ dynamicIRs: dynamicIRsByLanguage,
836185
+ dynamicIRUploadUrls: response.body.dynamicIRs,
836186
+ context: context3,
836187
+ apiId: response.body.apiDefinitionId
836188
+ });
836189
+ }
836190
+ }
836191
+ return response.body.apiDefinitionId;
836192
+ } else {
836193
+ switch (response.error.error) {
836194
+ case "UnauthorizedError":
836195
+ case "UserNotInOrgError": {
836196
+ return context3.failAndThrow("You do not have permissions to register the docs. Reach out to support@buildwithfern.com");
836197
+ }
836198
+ default: {
836199
+ const errorDetails = extractErrorDetails(response.error);
836200
+ context3.logger.error(`FDR registerApiDefinition failed. Error details:
836201
+ ${JSON.stringify(errorDetails, void 0, 2)}`);
836202
+ if (apiName != null) {
836203
+ return context3.failAndThrow(`Failed to publish docs because API definition (${apiName}) could not be uploaded. Please contact support@buildwithfern.com`, errorDetails);
836204
+ } else {
836205
+ return context3.failAndThrow(`Failed to publish docs because API definition could not be uploaded. Please contact support@buildwithfern.com`, errorDetails);
836206
+ }
836180
836207
  }
836181
836208
  }
836182
836209
  }
836183
- }
836184
- },
836185
- targetAudiences
836186
- });
836187
- context3.logger.info("Resolving docs definition...");
836188
- const resolveStart = performance.now();
836189
- let docsDefinition = await resolver2.resolve();
836190
- const resolveTime = performance.now() - resolveStart;
836191
- if (docsWorkspace.config.settings?.substituteEnvVars) {
836192
- context3.logger.debug("Applying environment variable substitution to docs definition...");
836193
- const { jsFiles, ...docsWithoutJsFiles } = docsDefinition;
836194
- const substitutedDocs = replaceEnvVariables(docsWithoutJsFiles, { onError: (e6) => context3.failAndThrow(e6) }, { substituteAsEmpty: false });
836195
- docsDefinition = { ...substitutedDocs, jsFiles };
836196
- }
836197
- const pageCount = Object.keys(docsDefinition.pages).length;
836198
- const apiWorkspaceCount = apiWorkspaces.length;
836199
- const resolveMemory = process.memoryUsage();
836200
- context3.logger.info(`Resolved docs definition in ${resolveTime.toFixed(0)}ms: ${pageCount} pages${apiWorkspaceCount ? `, ${apiWorkspaceCount} API workspaces` : ""}`);
836201
- context3.logger.debug(`Memory after resolve: RSS=${(resolveMemory.rss / 1024 / 1024).toFixed(2)}MB, Heap=${(resolveMemory.heapUsed / 1024 / 1024).toFixed(2)}MB`);
836202
- if (docsRegistrationId == null) {
836203
- return context3.failAndThrow("Failed to publish docs.", "Docs registration ID is missing.");
836204
- }
836205
- context3.logger.info("Publishing docs to FDR...");
836206
- const publishStart = performance.now();
836207
- const registerDocsResponse = await fdr.docs.v2.write.finishDocsRegister(DocsV1Write_exports.DocsRegistrationId(docsRegistrationId), {
836208
- docsDefinition,
836209
- excludeApis,
836210
- ...isBasepathAware && !preview && { basepathAware: true }
836211
- });
836212
- if (registerDocsResponse.ok) {
836213
- const publishTime = performance.now() - publishStart;
836214
- context3.logger.debug(`Docs published to FDR in ${publishTime.toFixed(0)}ms`);
836215
- const url3 = wrapWithHttps(urlToOutput);
836216
- await updateAiChatFromDocsDefinition({
836217
- docsDefinition,
836218
- isPreview: preview,
836219
- context: context3
836210
+ },
836211
+ targetAudiences
836220
836212
  });
836221
- const link3 = terminalLink(url3, url3);
836222
- context3.logger.info(source_default.green(`Published docs to ${link3}`));
836223
- return url3;
836224
- } else {
836225
- switch (registerDocsResponse.error.error) {
836226
- case "UnauthorizedError":
836227
- case "UserNotInOrgError":
836228
- return context3.failAndThrow("Insufficient permissions. Failed to publish docs to " + domain3);
836229
- case "DocsRegistrationIdNotFound":
836230
- return context3.failAndThrow("Failed to publish docs to " + domain3, `Docs registration ID ${docsRegistrationId} does not exist.`);
836231
- case "LibraryDocsJobInvalidForRegistrationError":
836232
- return context3.failAndThrow("Failed to publish docs to " + domain3, "Library docs job is invalid for registration. The job may not exist, may not be completed, or may belong to a different organization.");
836233
- default:
836234
- return context3.failAndThrow("Failed to publish docs to " + domain3, registerDocsResponse.error);
836213
+ context3.logger.info("Resolving docs definition...");
836214
+ const resolveStart = performance.now();
836215
+ let docsDefinition = await resolver2.resolve();
836216
+ const resolveTime = performance.now() - resolveStart;
836217
+ if (docsWorkspace.config.settings?.substituteEnvVars) {
836218
+ context3.logger.debug("Applying environment variable substitution to docs definition...");
836219
+ const { jsFiles, ...docsWithoutJsFiles } = docsDefinition;
836220
+ const substitutedDocs = replaceEnvVariables(docsWithoutJsFiles, { onError: (e6) => context3.failAndThrow(e6) }, { substituteAsEmpty: false });
836221
+ docsDefinition = { ...substitutedDocs, jsFiles };
836235
836222
  }
836223
+ const pageCount = Object.keys(docsDefinition.pages).length;
836224
+ const apiWorkspaceCount = apiWorkspaces.length;
836225
+ const resolveMemory = process.memoryUsage();
836226
+ context3.logger.info(`Resolved docs definition in ${resolveTime.toFixed(0)}ms: ${pageCount} pages${apiWorkspaceCount ? `, ${apiWorkspaceCount} API workspaces` : ""}`);
836227
+ context3.logger.debug(`Memory after resolve: RSS=${(resolveMemory.rss / 1024 / 1024).toFixed(2)}MB, Heap=${(resolveMemory.heapUsed / 1024 / 1024).toFixed(2)}MB`);
836228
+ if (docsRegistrationId == null) {
836229
+ doUnlock();
836230
+ return context3.failAndThrow("Failed to publish docs.", "Docs registration ID is missing.");
836231
+ }
836232
+ context3.logger.info("Publishing docs to FDR...");
836233
+ const publishStart = performance.now();
836234
+ const registerDocsResponse = await fdr.docs.v2.write.finishDocsRegister(DocsV1Write_exports.DocsRegistrationId(docsRegistrationId), {
836235
+ docsDefinition,
836236
+ excludeApis,
836237
+ ...isBasepathAware && !preview && { basepathAware: true }
836238
+ });
836239
+ if (registerDocsResponse.ok) {
836240
+ const publishTime = performance.now() - publishStart;
836241
+ context3.logger.debug(`Docs published to FDR in ${publishTime.toFixed(0)}ms`);
836242
+ const url3 = wrapWithHttps(urlToOutput);
836243
+ await updateAiChatFromDocsDefinition({
836244
+ docsDefinition,
836245
+ isPreview: preview,
836246
+ context: context3
836247
+ });
836248
+ const link3 = terminalLink(url3, url3);
836249
+ context3.logger.info(source_default.green(`Published docs to ${link3}`));
836250
+ return url3;
836251
+ } else {
836252
+ switch (registerDocsResponse.error.error) {
836253
+ case "UnauthorizedError":
836254
+ case "UserNotInOrgError":
836255
+ return context3.failAndThrow("Insufficient permissions. Failed to publish docs to " + domain3);
836256
+ case "DocsRegistrationIdNotFound":
836257
+ return context3.failAndThrow("Failed to publish docs to " + domain3, `Docs registration ID ${docsRegistrationId} does not exist.`);
836258
+ case "LibraryDocsJobInvalidForRegistrationError":
836259
+ return context3.failAndThrow("Failed to publish docs to " + domain3, "Library docs job is invalid for registration. The job may not exist, may not be completed, or may belong to a different organization.");
836260
+ default:
836261
+ return context3.failAndThrow("Failed to publish docs to " + domain3, registerDocsResponse.error);
836262
+ }
836263
+ }
836264
+ } catch (error50) {
836265
+ doUnlock();
836266
+ throw error50;
836267
+ } finally {
836268
+ deployLocked = false;
836269
+ process.removeListener("SIGINT", onSignal);
836270
+ process.removeListener("SIGTERM", onSignal);
836236
836271
  }
836237
836272
  }
836238
836273
  async function uploadFiles(filesToUpload, docsWorkspacePath, context3, batchSize, sanitizedToAbsoluteMap) {
@@ -836825,7 +836860,7 @@ var LegacyDocsPublisher = class {
836825
836860
  previewId: void 0,
836826
836861
  disableTemplates: void 0,
836827
836862
  skipUpload,
836828
- cliVersion: "4.47.0-8-ga7152010378"
836863
+ cliVersion: "4.48.0"
836829
836864
  });
836830
836865
  if (taskContext.getResult() === TaskResult.Failure) {
836831
836866
  return { success: false };
@@ -877838,6 +877873,8 @@ var DynamicTypeLiteralMapper5 = class {
877838
877873
  extendsInfo
877839
877874
  });
877840
877875
  }
877876
+ const hasAdditionalProperties = objectType2.additionalProperties === true;
877877
+ const useDefault = hasAdditionalProperties || this.context.canObjectTypeUseDefault(objectType2);
877841
877878
  const structFields = [];
877842
877879
  for (const property22 of objectType2.properties) {
877843
877880
  this.context.scopeError(property22.name.wireValue);
@@ -877865,7 +877902,11 @@ var DynamicTypeLiteralMapper5 = class {
877865
877902
  if (!hasValue) {
877866
877903
  const isOptional = this.context.isNullable(property22.typeReference);
877867
877904
  if (isOptional) {
877868
- propertyValue = rust_exports.Expression.none();
877905
+ if (useDefault) {
877906
+ propertyValue = void 0;
877907
+ } else {
877908
+ propertyValue = rust_exports.Expression.none();
877909
+ }
877869
877910
  } else {
877870
877911
  this.context.addScopedError(`Required field '${wireValue}' is missing`, Severity.Critical);
877871
877912
  propertyValue = rust_exports.Expression.raw("Default::default()");
@@ -877877,15 +877918,17 @@ var DynamicTypeLiteralMapper5 = class {
877877
877918
  });
877878
877919
  }
877879
877920
  }
877880
- structFields.push({
877881
- name: propertyName,
877882
- value: propertyValue
877883
- });
877921
+ if (propertyValue !== void 0) {
877922
+ structFields.push({
877923
+ name: propertyName,
877924
+ value: propertyValue
877925
+ });
877926
+ }
877884
877927
  } finally {
877885
877928
  this.context.unscopeError();
877886
877929
  }
877887
877930
  }
877888
- return rust_exports.Expression.structConstruction(structName, structFields);
877931
+ return rust_exports.Expression.structConstruction(structName, structFields, useDefault);
877889
877932
  }
877890
877933
  // Check if object extends another object (for flattening)
877891
877934
  getObjectExtensionInfo(objectType2) {
@@ -877924,31 +877967,40 @@ var DynamicTypeLiteralMapper5 = class {
877924
877967
  // Convert object that extends another object (handles flattening)
877925
877968
  convertExtendedObject({ objectType: objectType2, value, structName, extendsInfo }) {
877926
877969
  const structFields = [];
877927
- const baseStructFields = extendsInfo.baseObjectType.properties.map((property22) => {
877970
+ const baseHasAdditionalProperties = extendsInfo.baseObjectType.additionalProperties === true;
877971
+ const baseUseDefault = baseHasAdditionalProperties || this.context.canObjectTypeUseDefault(extendsInfo.baseObjectType);
877972
+ const baseStructFields = [];
877973
+ for (const property22 of extendsInfo.baseObjectType.properties) {
877928
877974
  this.context.scopeError(property22.name.wireValue);
877929
877975
  try {
877930
877976
  const propertyValue = value[property22.name.wireValue];
877931
- let fieldValue;
877932
877977
  if (propertyValue !== void 0) {
877933
- fieldValue = this.convert({
877934
- typeReference: property22.typeReference,
877935
- value: propertyValue
877978
+ baseStructFields.push({
877979
+ name: this.context.getPropertyName(property22.name.name),
877980
+ value: this.convert({
877981
+ typeReference: property22.typeReference,
877982
+ value: propertyValue
877983
+ })
877936
877984
  });
877937
877985
  } else if (property22.typeReference.type === "optional" || property22.typeReference.type === "nullable") {
877938
- fieldValue = rust_exports.Expression.raw("None");
877986
+ if (!baseUseDefault) {
877987
+ baseStructFields.push({
877988
+ name: this.context.getPropertyName(property22.name.name),
877989
+ value: rust_exports.Expression.raw("None")
877990
+ });
877991
+ }
877939
877992
  } else {
877940
- fieldValue = rust_exports.Expression.raw("Default::default()");
877993
+ baseStructFields.push({
877994
+ name: this.context.getPropertyName(property22.name.name),
877995
+ value: rust_exports.Expression.raw("Default::default()")
877996
+ });
877941
877997
  }
877942
- return {
877943
- name: this.context.getPropertyName(property22.name.name),
877944
- value: fieldValue
877945
- };
877946
877998
  } finally {
877947
877999
  this.context.unscopeError();
877948
878000
  }
877949
- });
878001
+ }
877950
878002
  const baseStructName = this.context.getStructName(extendsInfo.baseObjectType.declaration.name);
877951
- const baseStruct = rust_exports.Expression.structConstruction(baseStructName, baseStructFields);
878003
+ const baseStruct = rust_exports.Expression.structConstruction(baseStructName, baseStructFields, baseUseDefault);
877952
878004
  structFields.push({
877953
878005
  name: extendsInfo.baseFieldName,
877954
878006
  value: baseStruct
@@ -877972,7 +878024,9 @@ var DynamicTypeLiteralMapper5 = class {
877972
878024
  this.context.unscopeError();
877973
878025
  }
877974
878026
  });
877975
- return rust_exports.Expression.structConstruction(structName, structFields);
878027
+ const outerHasAdditionalProperties = objectType2.additionalProperties === true;
878028
+ const outerUseDefault = outerHasAdditionalProperties || this.context.canObjectTypeUseDefault(objectType2);
878029
+ return rust_exports.Expression.structConstruction(structName, structFields, outerUseDefault);
877976
878030
  }
877977
878031
  // Generate the field name for the flattened base object
877978
878032
  getBaseFieldName(baseName) {
@@ -878053,6 +878107,10 @@ var DynamicTypeLiteralMapper5 = class {
878053
878107
  this.context.addScopedError("Referenced union type not found or not an object", Severity.Critical);
878054
878108
  return rust_exports.Expression.reference(`${unionName}::${variantName}`);
878055
878109
  }
878110
+ if (referencedType.properties.length === 0) {
878111
+ const methodName = this.context.getPropertyName(unionVariant.discriminantValue.name);
878112
+ return rust_exports.Expression.raw(`${unionName}::${methodName}()`);
878113
+ }
878056
878114
  const convertedObject = this.convertObjectType({
878057
878115
  objectType: referencedType,
878058
878116
  value: discriminatedUnionTypeInstance.value
@@ -878797,6 +878855,69 @@ var DynamicSnippetsGeneratorContext7 = class _DynamicSnippetsGeneratorContext ex
878797
878855
  }
878798
878856
  return this.allParametersAreOptional(allProperties);
878799
878857
  }
878858
+ /**
878859
+ * Check if a dynamic IR object type will have Default derived by the model generator.
878860
+ * Mirrors the logic in StructGenerator.canDeriveDefault():
878861
+ * - All property types must support Default
878862
+ * - Extended types must support Default
878863
+ */
878864
+ canObjectTypeUseDefault(objectType2) {
878865
+ const extendsProperty = objectType2.extends;
878866
+ if (extendsProperty != null && extendsProperty.length > 0) {
878867
+ for (const baseTypeId of extendsProperty) {
878868
+ const baseType = this.ir.types[baseTypeId];
878869
+ if (!baseType || baseType.type !== "object" || !this.canObjectTypeUseDefault(baseType)) {
878870
+ return false;
878871
+ }
878872
+ }
878873
+ }
878874
+ return objectType2.properties.every((prop) => this.typeSupportsDefault(prop.typeReference));
878875
+ }
878876
+ /**
878877
+ * Check if a type reference maps to a Rust type that implements Default.
878878
+ * Mirrors the model generator's typeSupportsDefault logic.
878879
+ */
878880
+ typeSupportsDefault(typeRef, visited = /* @__PURE__ */ new Set()) {
878881
+ switch (typeRef.type) {
878882
+ case "primitive":
878883
+ return true;
878884
+ // All Rust primitives implement Default
878885
+ case "optional":
878886
+ case "nullable":
878887
+ return true;
878888
+ // Option<T> defaults to None
878889
+ case "list":
878890
+ case "map":
878891
+ case "set":
878892
+ return true;
878893
+ // Vec, HashMap, HashSet all implement Default
878894
+ case "unknown":
878895
+ return true;
878896
+ // serde_json::Value implements Default
878897
+ case "literal":
878898
+ return false;
878899
+ case "named": {
878900
+ const typeId = typeRef.value;
878901
+ if (visited.has(typeId)) {
878902
+ return false;
878903
+ }
878904
+ visited.add(typeId);
878905
+ const namedType = this.ir.types[typeId];
878906
+ if (!namedType) {
878907
+ return false;
878908
+ }
878909
+ if (namedType.type === "object") {
878910
+ return this.canObjectTypeUseDefault(namedType);
878911
+ }
878912
+ if (namedType.type === "alias") {
878913
+ return this.typeSupportsDefault(namedType.typeReference, visited);
878914
+ }
878915
+ return false;
878916
+ }
878917
+ default:
878918
+ return false;
878919
+ }
878920
+ }
878800
878921
  };
878801
878922
 
878802
878923
  // ../../../generators/rust/dynamic-snippets/lib/EndpointSnippetGenerator.js
@@ -879340,24 +879461,27 @@ var EndpointSnippetGenerator8 = class {
879340
879461
  }
879341
879462
  this.context.errors.unscope();
879342
879463
  const structName = this.getCorrectRequestStructName(endpoint3, request7);
879343
- const useDefault = false;
879464
+ const useDefault = this.context.canRequestUseDefault(request7);
879344
879465
  this.addMissingFields({
879345
879466
  request: request7,
879346
879467
  structFields,
879347
- providedFieldNames
879468
+ providedFieldNames,
879469
+ useDefault
879348
879470
  });
879349
879471
  return this.createStructExpression(structName, structFields, useDefault);
879350
879472
  }
879351
- addMissingFields({ request: request7, structFields, providedFieldNames }) {
879473
+ addMissingFields({ request: request7, structFields, providedFieldNames, useDefault }) {
879352
879474
  const allQueryParams = request7.queryParameters ?? [];
879353
879475
  for (const param of allQueryParams) {
879354
879476
  const fieldName = this.context.getPropertyName(param.name.name);
879355
879477
  if (!providedFieldNames.has(fieldName)) {
879356
879478
  if (this.context.isOptionalType(param.typeReference)) {
879357
- structFields.push({
879358
- name: fieldName,
879359
- value: rust_exports.Expression.raw("None")
879360
- });
879479
+ if (!useDefault) {
879480
+ structFields.push({
879481
+ name: fieldName,
879482
+ value: rust_exports.Expression.raw("None")
879483
+ });
879484
+ }
879361
879485
  } else {
879362
879486
  structFields.push({
879363
879487
  name: fieldName,
@@ -879371,10 +879495,12 @@ var EndpointSnippetGenerator8 = class {
879371
879495
  const fieldName = this.context.getPropertyName(param.name.name);
879372
879496
  if (!providedFieldNames.has(fieldName)) {
879373
879497
  if (this.context.isOptionalType(param.typeReference)) {
879374
- structFields.push({
879375
- name: fieldName,
879376
- value: rust_exports.Expression.raw("None")
879377
- });
879498
+ if (!useDefault) {
879499
+ structFields.push({
879500
+ name: fieldName,
879501
+ value: rust_exports.Expression.raw("None")
879502
+ });
879503
+ }
879378
879504
  } else {
879379
879505
  structFields.push({
879380
879506
  name: fieldName,
@@ -909849,7 +909975,7 @@ var CliContext = class {
909849
909975
  if (false) {
909850
909976
  this.logger.error("CLI_VERSION is not defined");
909851
909977
  }
909852
- return "4.47.0-8-ga7152010378";
909978
+ return "4.48.0";
909853
909979
  }
909854
909980
  getCliName() {
909855
909981
  if (false) {
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.47.0-8-ga7152010378",
2
+ "version": "4.48.0",
3
3
  "repository": {
4
4
  "type": "git",
5
5
  "url": "git+https://github.com/fern-api/fern.git",