Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions generators/swift/base/src/AsIs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,44 @@ function createSourceAsIsFiles(): SourceAsIsFileDefinitionsById {
return result;
}

/**
* A resolved definition of a static root-level file (e.g. CONTRIBUTING.md).
*/
export interface RootAsIsFileDefinition {
filename: string;
loadContents: () => Promise<string>;
}

const RootAsIsFileSpecs = {
Contributing: {
filename: "CONTRIBUTING.md"
}
} as const;

export type RootAsIsFileId = keyof typeof RootAsIsFileSpecs;

export type RootAsIsFileDefinitionsById = {
[K in RootAsIsFileId]: RootAsIsFileDefinition;
};

export const RootAsIsFiles: RootAsIsFileDefinitionsById = createRootAsIsFiles();

function createRootAsIsFiles(): RootAsIsFileDefinitionsById {
const result = {} as RootAsIsFileDefinitionsById;

for (const [key, spec] of entries(RootAsIsFileSpecs)) {
result[key] = {
filename: spec.filename,
loadContents: () => {
const absolutePath = join(__dirname, "asIs", "Root", spec.filename);
return readFile(absolutePath, "utf-8");
}
};
}

return result;
}

const TestAsIsFileSpecs: Record<string, swift.AsIsFileSpec<string>> = {};

export type TestAsIsFileId = keyof typeof TestAsIsFileSpecs;
Expand Down
129 changes: 129 additions & 0 deletions generators/swift/base/src/asIs/Root/CONTRIBUTING.md
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no AsIs pattern to do this with copy/paste file?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AsIs pattern in Swift (SourceAsIsFiles/addSourceAsIsFile) is designed for .swift source files that get compiled into the module — they include symbol registration with the NameRegistry and are placed under Sources/. CONTRIBUTING.md is a root-level markdown file, so it follows the same addRootFiles(new File(...)) pattern used by README.md, reference.md, and Package.swift.

This also matches how C# and TypeScript handle it — both use a ContributingGenerator class with addRawFiles/addRootFiles rather than an AsIs file.

That said, if you'd prefer a different approach (e.g. a static .md file that gets copied), happy to refactor.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin it shouldn't be difficult to extend this to allow root AsIs I think. We could just add RootAsIsFiles to AsIs.ts and then generate them in SdkGeneratorCLI generateRootFiles()? Could we not do that?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — refactored to use the AsIs pattern. I've:

  1. Added RootAsIsFiles to generators/swift/base/src/AsIs.ts — a simpler variant of the existing AsIs registry (just filename + loadContents, no symbols/directory needed for root files)
  2. Placed the static CONTRIBUTING.md at generators/swift/base/src/asIs/Root/CONTRIBUTING.md
  3. Updated SdkGeneratorCli.ts to load via RootAsIsFiles.Contributing.loadContents() in generateRootAsIsFiles(), called in parallel with README/reference generation
  4. Removed the ContributingGenerator class entirely

This makes it easy to add more root AsIs files in the future — just add an entry to RootAsIsFileSpecs and drop the file in asIs/Root/.

Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Contributing

Thanks for your interest in contributing to this SDK! This document provides guidelines for contributing to the project.

## Getting Started

### Prerequisites

- Swift 5.9+
- Swift Package Manager

### Installation

Install the project dependencies:

```bash
swift package resolve
```

### Building

Build the project:

```bash
swift build
```

### Testing

Run the test suite:

```bash
swift test
```

### Formatting

Format the code:

```bash
swift format .
```

Or using SwiftFormat:

```bash
swiftformat .
```

### Linting

Lint the code:

```bash
swiftlint
```

## About Generated Code

**Important**: Most files in this SDK are automatically generated by [Fern](https://buildwithfern.com) from the API definition. Direct modifications to generated files will be overwritten the next time the SDK is generated.

### Generated Files

The following directories contain generated code:
- `Sources/` - API client classes and types
- Most Swift files in the project

### How to Customize

If you need to customize the SDK, you have two options:

#### Option 1: Use `.fernignore`

For custom code that should persist across SDK regenerations:

1. Create a `.fernignore` file in the project root
2. Add file patterns for files you want to preserve (similar to `.gitignore` syntax)
3. Add your custom code to those files

Files listed in `.fernignore` will not be overwritten when the SDK is regenerated.

For more information, see the [Fern documentation on custom code](https://buildwithfern.com/learn/sdks/overview/custom-code).

#### Option 2: Contribute to the Generator

If you want to change how code is generated for all users of this SDK:

1. The Swift SDK generator lives in the [Fern repository](https://github.com/fern-api/fern)
2. Generator code is located at `generators/swift/`
3. Follow the [Fern contributing guidelines](https://github.com/fern-api/fern/blob/main/CONTRIBUTING.md)
4. Submit a pull request with your changes to the generator

This approach is best for:
- Bug fixes in generated code
- New features that would benefit all users
- Improvements to code generation patterns

## Making Changes

### Workflow

1. Create a new branch for your changes
2. Make your modifications
3. Run tests to ensure nothing breaks: `swift test`
4. Run formatting: `swift format .` or `swiftformat .`
5. Run linting: `swiftlint`
6. Build the project: `swift build`
7. Commit your changes with a clear commit message
8. Push your branch and create a pull request

### Commit Messages

Write clear, descriptive commit messages that explain what changed and why.

### Code Style

This project uses automated code formatting and linting. Run `swift format .` or `swiftformat .` before committing to ensure your code meets the project's style guidelines.

## Questions or Issues?

If you have questions or run into issues:

1. Check the [Fern documentation](https://buildwithfern.com)
2. Search existing [GitHub issues](https://github.com/fern-api/fern/issues)
3. Open a new issue if your question hasn't been addressed

## License

By contributing to this project, you agree that your contributions will be licensed under the same license as the project.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- summary: |
Generate CONTRIBUTING.md for Swift SDKs.
type: feat
14 changes: 12 additions & 2 deletions generators/swift/sdk/src/SdkGeneratorCli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { File, GeneratorError, GeneratorNotificationService, getWireValue } from "@fern-api/base-generator";
import { assertNever, entries, extractErrorMessage, noop } from "@fern-api/core-utils";
import { join, RelativeFilePath } from "@fern-api/fs-utils";
import { AbstractSwiftGeneratorCli, SourceTemplateFiles, TestTemplateFiles } from "@fern-api/swift-base";
import { AbstractSwiftGeneratorCli, RootAsIsFiles, SourceTemplateFiles, TestTemplateFiles } from "@fern-api/swift-base";
import { sanitizeSelf, swift } from "@fern-api/swift-codegen";
import { DynamicSnippetsGenerator } from "@fern-api/swift-dynamic-snippets";
import {
Expand Down Expand Up @@ -88,7 +88,8 @@ export class SdkGeneratorCLI extends AbstractSwiftGeneratorCli<SdkCustomConfigSc

await Promise.all([
this.generateReadme(context, dynamicIr, sharedSnippetsGenerator),
this.generateReference(context, sharedSnippetsGenerator)
this.generateReference(context, sharedSnippetsGenerator),
this.generateRootAsIsFiles(context)
]);
}

Expand Down Expand Up @@ -134,6 +135,15 @@ export class SdkGeneratorCLI extends AbstractSwiftGeneratorCli<SdkCustomConfigSc
}
}

private async generateRootAsIsFiles(context: SdkGeneratorContext): Promise<void> {
if (!context.config.whitelabel) {
const content = await RootAsIsFiles.Contributing.loadContents();
context.project.addRootFiles(
new File(RootAsIsFiles.Contributing.filename, RelativeFilePath.of(""), content)
);
}
}

private generateSnippets(
dynamicIr: FernIr.dynamic.DynamicIntermediateRepresentation,
snippetsGenerator: DynamicSnippetsGenerator
Expand Down
57 changes: 57 additions & 0 deletions generators/swift/sdk/src/__test__/ContributingGenerator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { RootAsIsFiles } from "@fern-api/swift-base";
import { describe, expect, it } from "vitest";

describe("CONTRIBUTING.md root AsIs file", () => {
it("contains Swift commands", async () => {
const result = await RootAsIsFiles.Contributing.loadContents();
expect(result).toContain("swift build");
expect(result).toContain("swift test");
expect(result).toContain("swift format");
expect(result).toContain("swiftformat .");
expect(result).toContain("swiftlint");
});

it("includes all expected sections", async () => {
const result = await RootAsIsFiles.Contributing.loadContents();
expect(result).toContain("# Contributing");
expect(result).toContain("## Getting Started");
expect(result).toContain("### Prerequisites");
expect(result).toContain("### Installation");
expect(result).toContain("### Building");
expect(result).toContain("### Testing");
expect(result).toContain("### Formatting");
expect(result).toContain("### Linting");
expect(result).toContain("## About Generated Code");
expect(result).toContain("## Making Changes");
expect(result).toContain("## Questions or Issues?");
expect(result).toContain("## License");
});

it("includes Fern-specific information", async () => {
const result = await RootAsIsFiles.Contributing.loadContents();
expect(result).toContain("Fern");
expect(result).toContain(".fernignore");
expect(result).toContain("buildwithfern.com");
expect(result).toContain("github.com/fern-api/fern");
});

it("references Swift generator path", async () => {
const result = await RootAsIsFiles.Contributing.loadContents();
expect(result).toContain("generators/swift/");
});

it("includes Swift prerequisites", async () => {
const result = await RootAsIsFiles.Contributing.loadContents();
expect(result).toContain("Swift 5.9+");
expect(result).toContain("Swift Package Manager");
});

it("has correct filename", () => {
expect(RootAsIsFiles.Contributing.filename).toBe("CONTRIBUTING.md");
});

it("generates full content snapshot", async () => {
const result = await RootAsIsFiles.Contributing.loadContents();
expect(result).toMatchSnapshot();
});
});
Loading