@fern-api/generator-cli 0.4.6 → 0.5.2

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.
package/bin/cli ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("../dist/cli.js");
@@ -158,6 +158,8 @@ interface GithubRemote {
158
158
  repoUrl: string;
159
159
  /** The token used to clone the GitHub repository. */
160
160
  installationToken: string;
161
+ /** The branch to clone from. If not specified, the default branch will be used. */
162
+ branch?: string;
161
163
  }
162
164
 
163
165
  /**
@@ -168,7 +170,7 @@ interface GithubRemote {
168
170
  * A single feature supported by a generator (e.g. PAGINATION).
169
171
  */
170
172
  interface ReadmeFeature extends FeatureSpec {
171
- snippets?: string[];
173
+ snippets?: Snippet[];
172
174
  /**
173
175
  * If true, the feature block should be rendered even if we don't receive a snippet for it.
174
176
  * This is useful for features that are always supported, but might not require a snippet
@@ -177,6 +179,44 @@ interface ReadmeFeature extends FeatureSpec {
177
179
  snippetsAreOptional: boolean;
178
180
  }
179
181
 
182
+ /**
183
+ * This file was auto-generated by Fern from our API Definition.
184
+ */
185
+
186
+ /**
187
+ * A snippet that can be either a plain string (treated as code in the generator's language),
188
+ * markdown content, or a code block with optional language override.
189
+ * Plain strings are supported for backwards compatibility.
190
+ */
191
+ type Snippet = string | MarkdownSnippet | CodeSnippet;
192
+
193
+ /**
194
+ * This file was auto-generated by Fern from our API Definition.
195
+ */
196
+ /**
197
+ * A markdown snippet that will be rendered as-is without code fences.
198
+ */
199
+ interface MarkdownSnippet {
200
+ /** Discriminator field to identify this as a markdown snippet. */
201
+ type: "markdown";
202
+ content: string;
203
+ }
204
+
205
+ /**
206
+ * This file was auto-generated by Fern from our API Definition.
207
+ */
208
+ /**
209
+ * A code snippet that will be wrapped in a code fence with the specified language.
210
+ * If no language is specified, the generator's language will be used.
211
+ */
212
+ interface CodeSnippet {
213
+ /** Discriminator field to identify this as a code snippet. */
214
+ type: "code";
215
+ content: string;
216
+ /** The language for the code fence. If not specified, the generator's language will be used. */
217
+ language?: string;
218
+ }
219
+
180
220
  /**
181
221
  * This file was auto-generated by Fern from our API Definition.
182
222
  */
@@ -6088,6 +6088,11 @@ var ClonedRepository = class _ClonedRepository {
6088
6088
  this.clonePath = clonePath;
6089
6089
  this.git = git;
6090
6090
  }
6091
+ async setUserAndEmail({ name, email }) {
6092
+ await this.git.cwd(this.clonePath);
6093
+ await this.git.addConfig("user.name", name);
6094
+ await this.git.addConfig("user.email", email);
6095
+ }
6091
6096
  static createAtPath(clonePath) {
6092
6097
  return new _ClonedRepository({
6093
6098
  clonePath,
@@ -6156,9 +6161,78 @@ var ClonedRepository = class _ClonedRepository {
6156
6161
  await this.git.cwd(this.clonePath);
6157
6162
  await this.git.pull("origin", branch);
6158
6163
  }
6164
+ async pullWithMerge(branch) {
6165
+ await this.git.cwd(this.clonePath);
6166
+ await this.git.raw(["pull", "origin", branch, "--no-rebase"]);
6167
+ }
6159
6168
  async push() {
6160
6169
  await this.git.cwd(this.clonePath);
6161
- await this.git.push();
6170
+ const currentBranch = await this.getCurrentBranch();
6171
+ try {
6172
+ await this.git.push();
6173
+ } catch (err) {
6174
+ const msg = String(err);
6175
+ const isNoUpstream = msg.includes("has no upstream branch") || msg.includes("no upstream branch");
6176
+ if (isNoUpstream) {
6177
+ await this.pushUpstream(currentBranch);
6178
+ return;
6179
+ }
6180
+ throw err;
6181
+ }
6182
+ }
6183
+ async pushWithMergingRemote() {
6184
+ await this.git.cwd(this.clonePath);
6185
+ const currentBranch = await this.getCurrentBranch();
6186
+ const isRemoteReject = (msg) => msg.includes("fetch first") || msg.includes("Updates were rejected") || msg.includes("non-fast-forward");
6187
+ const isDivergent = (msg) => msg.includes("divergent branches") || msg.includes("You have divergent branches");
6188
+ try {
6189
+ await this.push();
6190
+ } catch (err) {
6191
+ const errMsg = String(err);
6192
+ if (isRemoteReject(errMsg)) {
6193
+ try {
6194
+ await this.pull(currentBranch);
6195
+ await this.push();
6196
+ return;
6197
+ } catch (pullErr) {
6198
+ const pullMsg = String(pullErr);
6199
+ if (isDivergent(pullMsg)) {
6200
+ try {
6201
+ await this.pullWithMerge(currentBranch);
6202
+ await this.push();
6203
+ return;
6204
+ } catch (mergeErr) {
6205
+ throw err;
6206
+ }
6207
+ } else {
6208
+ throw err;
6209
+ }
6210
+ }
6211
+ } else {
6212
+ throw err;
6213
+ }
6214
+ }
6215
+ }
6216
+ async pushWithRebasingRemote() {
6217
+ await this.git.cwd(this.clonePath);
6218
+ const currentBranch = await this.getCurrentBranch();
6219
+ const isRemoteReject = (msg) => msg.includes("fetch first") || msg.includes("Updates were rejected") || msg.includes("non-fast-forward");
6220
+ try {
6221
+ await this.push();
6222
+ } catch (err) {
6223
+ const errMsg = String(err);
6224
+ if (isRemoteReject(errMsg)) {
6225
+ try {
6226
+ await this.git.raw(["pull", "origin", currentBranch, "--rebase"]);
6227
+ await this.push();
6228
+ return;
6229
+ } catch (pullErr) {
6230
+ throw new Error(`Push failed: ${errMsg}. Attempted to rebase but that also failed: ${String(pullErr)}`);
6231
+ }
6232
+ } else {
6233
+ throw err;
6234
+ }
6235
+ }
6162
6236
  }
6163
6237
  async isRemoteEmpty() {
6164
6238
  await this.git.cwd(this.clonePath);
@@ -6177,6 +6251,18 @@ var ClonedRepository = class _ClonedRepository {
6177
6251
  await this.git.checkoutLocalBranch(branch);
6178
6252
  }
6179
6253
  }
6254
+ async checkoutRemoteBranch(branch) {
6255
+ await this.git.cwd(this.clonePath);
6256
+ try {
6257
+ await this.git.checkout(branch);
6258
+ } catch (_error) {
6259
+ try {
6260
+ await this.git.checkout(["-b", branch, `origin/${branch}`]);
6261
+ } catch (_remoteError) {
6262
+ await this.git.checkoutLocalBranch(branch);
6263
+ }
6264
+ }
6265
+ }
6180
6266
  async pushUpstream(branch) {
6181
6267
  await this.git.cwd(this.clonePath);
6182
6268
  await this.git.push("origin", branch, { "--set-upstream": null });
@@ -6777,11 +6863,12 @@ var Language = {
6777
6863
  };
6778
6864
 
6779
6865
  // src/utils/Writer.ts
6780
- var import_fs = __toESM(require("fs"), 1);
6866
+ var import_fs = __toESM(require("fs"));
6781
6867
  var Writer = class {
6782
6868
  async writeCodeBlock(language, content) {
6783
6869
  await this.writeLine(`\`\`\`${language}`);
6784
- await this.write(content);
6870
+ const normalizedContent = content.endsWith("\n") ? content : content + "\n";
6871
+ await this.write(normalizedContent);
6785
6872
  await this.writeLine("```");
6786
6873
  }
6787
6874
  async writeLine(content) {
@@ -7124,7 +7211,23 @@ var ReadmeGenerator = class {
7124
7211
  if (index > 0) {
7125
7212
  await writer.writeLine();
7126
7213
  }
7127
- await writer.writeCodeBlock(this.readmeConfig.language.type, snippet);
7214
+ if (typeof snippet === "string") {
7215
+ await writer.writeCodeBlock(this.readmeConfig.language.type, snippet);
7216
+ } else {
7217
+ switch (snippet.type) {
7218
+ case "markdown":
7219
+ await writer.writeLine(snippet.content);
7220
+ break;
7221
+ case "code":
7222
+ await writer.writeCodeBlock(
7223
+ snippet.language ?? this.readmeConfig.language.type,
7224
+ snippet.content
7225
+ );
7226
+ break;
7227
+ default:
7228
+ assertNever(snippet);
7229
+ }
7230
+ }
7128
7231
  }
7129
7232
  if (feature.addendum != null) {
7130
7233
  await writer.writeLine(feature.addendum);
@@ -7160,6 +7263,15 @@ var ReadmeGenerator = class {
7160
7263
  githubRepository: this.readmeConfig.remote.repoUrl,
7161
7264
  installationToken: this.readmeConfig.remote.installationToken
7162
7265
  });
7266
+ if (this.readmeConfig.remote.branch != null) {
7267
+ try {
7268
+ await clonedRepository.checkout(this.readmeConfig.remote.branch);
7269
+ } catch (error) {
7270
+ console.warn(
7271
+ `Failed to checkout branch ${this.readmeConfig.remote.branch}, using default branch instead`
7272
+ );
7273
+ }
7274
+ }
7163
7275
  return await clonedRepository.getReadme();
7164
7276
  }
7165
7277
  return void 0;
@@ -7974,7 +8086,11 @@ ${content}
7974
8086
  return content.map(({ text, location }) => this.wrapInLink(text, location)).join("");
7975
8087
  }
7976
8088
  wrapInLink(content, link) {
7977
- return link != null ? `<a href="${link.path}">${content}</a>` : content;
8089
+ const encodedContent = this.htmlEncode(content);
8090
+ return link != null ? `<a href="${link.path}">${encodedContent}</a>` : encodedContent;
8091
+ }
8092
+ htmlEncode(content) {
8093
+ return content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
7978
8094
  }
7979
8095
  };
7980
8096
 
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
@@ -6066,9 +6065,9 @@ function resolve(...paths) {
6066
6065
  }
6067
6066
 
6068
6067
  // src/cli.ts
6069
- var import_fs6 = __toESM(require("fs"), 1);
6068
+ var import_fs6 = __toESM(require("fs"));
6070
6069
  var import_promises6 = require("fs/promises");
6071
- var import_path7 = __toESM(require("path"), 1);
6070
+ var import_path7 = __toESM(require("path"));
6072
6071
 
6073
6072
  // ../../node_modules/.pnpm/yargs@17.7.2/node_modules/yargs/build/lib/yerror.js
6074
6073
  var YError = class _YError extends Error {
@@ -12227,6 +12226,11 @@ var ClonedRepository = class _ClonedRepository {
12227
12226
  this.clonePath = clonePath;
12228
12227
  this.git = git;
12229
12228
  }
12229
+ async setUserAndEmail({ name, email }) {
12230
+ await this.git.cwd(this.clonePath);
12231
+ await this.git.addConfig("user.name", name);
12232
+ await this.git.addConfig("user.email", email);
12233
+ }
12230
12234
  static createAtPath(clonePath) {
12231
12235
  return new _ClonedRepository({
12232
12236
  clonePath,
@@ -12295,9 +12299,78 @@ var ClonedRepository = class _ClonedRepository {
12295
12299
  await this.git.cwd(this.clonePath);
12296
12300
  await this.git.pull("origin", branch);
12297
12301
  }
12302
+ async pullWithMerge(branch) {
12303
+ await this.git.cwd(this.clonePath);
12304
+ await this.git.raw(["pull", "origin", branch, "--no-rebase"]);
12305
+ }
12298
12306
  async push() {
12299
12307
  await this.git.cwd(this.clonePath);
12300
- await this.git.push();
12308
+ const currentBranch = await this.getCurrentBranch();
12309
+ try {
12310
+ await this.git.push();
12311
+ } catch (err) {
12312
+ const msg = String(err);
12313
+ const isNoUpstream = msg.includes("has no upstream branch") || msg.includes("no upstream branch");
12314
+ if (isNoUpstream) {
12315
+ await this.pushUpstream(currentBranch);
12316
+ return;
12317
+ }
12318
+ throw err;
12319
+ }
12320
+ }
12321
+ async pushWithMergingRemote() {
12322
+ await this.git.cwd(this.clonePath);
12323
+ const currentBranch = await this.getCurrentBranch();
12324
+ const isRemoteReject = (msg) => msg.includes("fetch first") || msg.includes("Updates were rejected") || msg.includes("non-fast-forward");
12325
+ const isDivergent = (msg) => msg.includes("divergent branches") || msg.includes("You have divergent branches");
12326
+ try {
12327
+ await this.push();
12328
+ } catch (err) {
12329
+ const errMsg = String(err);
12330
+ if (isRemoteReject(errMsg)) {
12331
+ try {
12332
+ await this.pull(currentBranch);
12333
+ await this.push();
12334
+ return;
12335
+ } catch (pullErr) {
12336
+ const pullMsg = String(pullErr);
12337
+ if (isDivergent(pullMsg)) {
12338
+ try {
12339
+ await this.pullWithMerge(currentBranch);
12340
+ await this.push();
12341
+ return;
12342
+ } catch (mergeErr) {
12343
+ throw err;
12344
+ }
12345
+ } else {
12346
+ throw err;
12347
+ }
12348
+ }
12349
+ } else {
12350
+ throw err;
12351
+ }
12352
+ }
12353
+ }
12354
+ async pushWithRebasingRemote() {
12355
+ await this.git.cwd(this.clonePath);
12356
+ const currentBranch = await this.getCurrentBranch();
12357
+ const isRemoteReject = (msg) => msg.includes("fetch first") || msg.includes("Updates were rejected") || msg.includes("non-fast-forward");
12358
+ try {
12359
+ await this.push();
12360
+ } catch (err) {
12361
+ const errMsg = String(err);
12362
+ if (isRemoteReject(errMsg)) {
12363
+ try {
12364
+ await this.git.raw(["pull", "origin", currentBranch, "--rebase"]);
12365
+ await this.push();
12366
+ return;
12367
+ } catch (pullErr) {
12368
+ throw new Error(`Push failed: ${errMsg}. Attempted to rebase but that also failed: ${String(pullErr)}`);
12369
+ }
12370
+ } else {
12371
+ throw err;
12372
+ }
12373
+ }
12301
12374
  }
12302
12375
  async isRemoteEmpty() {
12303
12376
  await this.git.cwd(this.clonePath);
@@ -12316,6 +12389,18 @@ var ClonedRepository = class _ClonedRepository {
12316
12389
  await this.git.checkoutLocalBranch(branch);
12317
12390
  }
12318
12391
  }
12392
+ async checkoutRemoteBranch(branch) {
12393
+ await this.git.cwd(this.clonePath);
12394
+ try {
12395
+ await this.git.checkout(branch);
12396
+ } catch (_error) {
12397
+ try {
12398
+ await this.git.checkout(["-b", branch, `origin/${branch}`]);
12399
+ } catch (_remoteError) {
12400
+ await this.git.checkoutLocalBranch(branch);
12401
+ }
12402
+ }
12403
+ }
12319
12404
  async pushUpstream(branch) {
12320
12405
  await this.git.cwd(this.clonePath);
12321
12406
  await this.git.push("origin", branch, { "--set-upstream": null });
@@ -12916,11 +13001,12 @@ var Language = {
12916
13001
  };
12917
13002
 
12918
13003
  // src/utils/Writer.ts
12919
- var import_fs5 = __toESM(require("fs"), 1);
13004
+ var import_fs5 = __toESM(require("fs"));
12920
13005
  var Writer = class {
12921
13006
  async writeCodeBlock(language, content) {
12922
13007
  await this.writeLine(`\`\`\`${language}`);
12923
- await this.write(content);
13008
+ const normalizedContent = content.endsWith("\n") ? content : content + "\n";
13009
+ await this.write(normalizedContent);
12924
13010
  await this.writeLine("```");
12925
13011
  }
12926
13012
  async writeLine(content) {
@@ -13263,7 +13349,23 @@ var ReadmeGenerator = class {
13263
13349
  if (index > 0) {
13264
13350
  await writer.writeLine();
13265
13351
  }
13266
- await writer.writeCodeBlock(this.readmeConfig.language.type, snippet);
13352
+ if (typeof snippet === "string") {
13353
+ await writer.writeCodeBlock(this.readmeConfig.language.type, snippet);
13354
+ } else {
13355
+ switch (snippet.type) {
13356
+ case "markdown":
13357
+ await writer.writeLine(snippet.content);
13358
+ break;
13359
+ case "code":
13360
+ await writer.writeCodeBlock(
13361
+ snippet.language ?? this.readmeConfig.language.type,
13362
+ snippet.content
13363
+ );
13364
+ break;
13365
+ default:
13366
+ assertNever(snippet);
13367
+ }
13368
+ }
13267
13369
  }
13268
13370
  if (feature.addendum != null) {
13269
13371
  await writer.writeLine(feature.addendum);
@@ -13299,6 +13401,15 @@ var ReadmeGenerator = class {
13299
13401
  githubRepository: this.readmeConfig.remote.repoUrl,
13300
13402
  installationToken: this.readmeConfig.remote.installationToken
13301
13403
  });
13404
+ if (this.readmeConfig.remote.branch != null) {
13405
+ try {
13406
+ await clonedRepository.checkout(this.readmeConfig.remote.branch);
13407
+ } catch (error) {
13408
+ console.warn(
13409
+ `Failed to checkout branch ${this.readmeConfig.remote.branch}, using default branch instead`
13410
+ );
13411
+ }
13412
+ }
13302
13413
  return await clonedRepository.getReadme();
13303
13414
  }
13304
13415
  return void 0;
@@ -14093,7 +14204,11 @@ ${content}
14093
14204
  return content.map(({ text, location }) => this.wrapInLink(text, location)).join("");
14094
14205
  }
14095
14206
  wrapInLink(content, link) {
14096
- return link != null ? `<a href="${link.path}">${content}</a>` : content;
14207
+ const encodedContent = this.htmlEncode(content);
14208
+ return link != null ? `<a href="${link.path}">${encodedContent}</a>` : encodedContent;
14209
+ }
14210
+ htmlEncode(content) {
14211
+ return content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
14097
14212
  }
14098
14213
  };
14099
14214
 
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@fern-api/generator-cli",
3
- "version": "0.4.6",
3
+ "version": "0.5.2",
4
4
  "repository": {
5
5
  "type": "git",
6
- "url": "https://github.com/fern-api/fern"
6
+ "url": "git+https://github.com/fern-api/fern.git",
7
+ "directory": "packages/generator-cli"
7
8
  },
8
- "type": "module",
9
- "main": "dist/api.cjs",
9
+ "type": "commonjs",
10
+ "main": "dist/api.js",
10
11
  "source": "src/index.ts",
11
- "types": "dist/api.d.cts",
12
+ "types": "dist/api.d.ts",
12
13
  "bin": {
13
- "generator-cli": "dist/cli.cjs"
14
+ "generator-cli": "bin/cli"
14
15
  },
15
- "files": ["dist"],
16
+ "files": ["bin", "dist"],
16
17
  "scripts": {
17
- "clean": "rm -rf ./dist && tsc --build --clean",
18
- "compile": "tsup && pnpm compile:api:dts && echo '#!/usr/bin/env node' | cat - dist/cli.cjs > dist/tmp && mv dist/tmp dist/cli.cjs",
19
- "compile:api:dts": "tsup ./src/api.ts --format cjs --dts --dts-only",
18
+ "clean": "rm -rf ./lib && rm -rf ./dist && tsc --build --clean",
20
19
  "depcheck": "depcheck",
20
+ "dist:cli": "node build.mjs",
21
21
  "test": "vitest --run --passWithNoTests --globals --disable-console-intercept",
22
22
  "test:update": "vitest -u --run --passWithNoTests --globals --disable-console-intercept"
23
23
  },