Skip to content

Commit

Permalink
fix import/header issue. (#317)
Browse files Browse the repository at this point in the history
* fix import/header issue.

* v9.2.0

* fix: update snapshots, add more details for testing in readme

---------

Co-authored-by: Oscar Bazaldua <[email protected]>
  • Loading branch information
didiergarcia and oscb authored Mar 9, 2024
1 parent 9749df3 commit d838295
Show file tree
Hide file tree
Showing 9 changed files with 1,463 additions and 1,494 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
runs-on: 'ubuntu-latest'
strategy:
matrix:
node-version: [ 14, 16, 18 ]
node-version: [ 18, 20, 21 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
14
18
97 changes: 61 additions & 36 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Have an idea for improving Typewriter? [Submit an issue first](https://github.co

## Developing on Typewriter

Typewriter is written using [OCLIF](https://oclif.io).
Typewriter is written using [OCLIF](https://oclif.io).

### Build and run locally

Expand All @@ -20,15 +20,29 @@ $ yarn
# Test your Typewriter installation by regenerating Typewriter's typewriter client.
$ yarn build
# Develop and test using OCLIFs dev runner to test any of your changes without transpiling
# This will build Typewriter's own TrackingPlan (src/telemetry/plan.json) with the root dir configuration (typewriter.yml)
$ ./bin/dev build -m prod -u
# You can run any command and debug locally using the `bin/dev` command:
$ mkdir myOwnClient && cd myOwnClient
$ ../bin/dev init # To initialize a new TW client
$ ../bind/dev build # To build this client
```

### Running Tests

Running the integration tests are heavily recommended for local development as they do not mess up with local clients nor you have to run the initialization wizard and setup a test account/tracking plan.

The tests run against all combinations of SDKs and Languages supported with a large TrackingPlan that contains complex values. They are snapshot tests that check against the expected output

```sh
$ yarn test
$ yarn test # Run all tests
$ yarn test --updateSnapshot # Update the snapshots automatically after making a change in outputs
```

The Tracking Plan and configuration used in tests is contained in `test/env`

The output is written to `test-env` under specific directories for each language, SDK and build mode.

### Deploying

You can deploy a new version to [`npm`](https://www.npmjs.com/package/typewriter) by running:
Expand Down Expand Up @@ -59,10 +73,14 @@ import {
SwiftTargetLanguage,
TargetLanguage,
Type,
} from 'quicktype-core';
import { OptionValues } from 'quicktype-core/dist/RendererOptions';
import { camelCase } from 'quicktype-core/dist/support/Strings';
import { emitMultiline, executeRenderPlan, makeNameForTopLevelWithPrefixAndSuffix } from './quicktype-utils';
} from "quicktype-core";
import { OptionValues } from "quicktype-core/dist/RendererOptions";
import { camelCase } from "quicktype-core/dist/support/Strings";
import {
emitMultiline,
executeRenderPlan,
makeNameForTopLevelWithPrefixAndSuffix,
} from "./quicktype-utils";

// We extend the Quicktype renderer for the language we will output, SwiftRenderer here for Swift
class TypewriterSwiftRenderer extends SwiftRenderer {
Expand All @@ -71,7 +89,7 @@ class TypewriterSwiftRenderer extends SwiftRenderer {
targetLanguage: TargetLanguage,
renderContext: RenderContext,
typescriptOptions: OptionValues<any>,
protected readonly typewriterOptions: QuicktypeTypewriterSettings,
protected readonly typewriterOptions: QuicktypeTypewriterSettings
) {
super(targetLanguage, renderContext, typescriptOptions);
}
Expand All @@ -89,7 +107,11 @@ class TypewriterSwiftRenderer extends SwiftRenderer {
}

// Override makeNameForTopLevel, this is the function that defines the names for our top level classes, the events in our case. We add custom prefixes and suffixes support through this!
makeNameForTopLevel(t: Type, givenName: string, maybeNamedType: Type | undefined): Name {
makeNameForTopLevel(
t: Type,
givenName: string,
maybeNamedType: Type | undefined
): Name {
return makeNameForTopLevelWithPrefixAndSuffix(
// This is important, we do this to bind `this` as the internal Quicktype implementation relies on it
(...args) => {
Expand All @@ -98,7 +120,7 @@ class TypewriterSwiftRenderer extends SwiftRenderer {
this.typewriterOptions,
t,
givenName,
maybeNamedType,
maybeNamedType
);
}
}
Expand All @@ -110,21 +132,23 @@ Now it's time to create our own `TargetLanguage`. Again this is just boilerplate
// We extend the TargetLanguage class for the language we will output, here for Swift
class TypewriterSwiftLanguage extends SwiftTargetLanguage {
// override the constructor to receive our typewriter options
constructor(protected readonly typewriterOptions: QuicktypeTypewriterSettings) {
constructor(
protected readonly typewriterOptions: QuicktypeTypewriterSettings
) {
super();
}

// override the makeRenderer to use the Renderer class we defined before
protected makeRenderer(
renderContext: RenderContext,
untypedOptionValues: { [name: string]: any },
untypedOptionValues: { [name: string]: any }
): TypewriterSwiftRenderer {
return new TypewriterSwiftRenderer(
this,
renderContext,
// This part is somewhat tricky, `swiftOptions` is an object defined quicktype-core each languague has its own object, it is a good idea to take a peek at quicktype to figure out what's its name. f.e. https://github.com/quicktype/quicktype/blob/b481ea541c93b7e3ca01aaa65d4ec72492fdf699/src/quicktype-core/language/Swift.ts#L48
getOptionValues(swiftOptions, untypedOptionValues),
this.typewriterOptions,
this.typewriterOptions
);
}
}
Expand All @@ -141,63 +165,61 @@ We are done with Quicktype's boilerplate code. Let's get to our actual implement
A simple template will look like this, iterating over all the types and outputing the functions for each one of them:

```hbs
import Segment
extension Analytics {
{{#type}}
func {{functionName}}(properties: {{typeName}}) {
self.track(event: "{{eventName}}", properties: properties)
}
{{/type}}
import Segment extension Analytics {
{{#type}}
func
{{functionName}}(properties:
{{typeName}}) { self.track(event: "{{eventName}}", properties: properties) }
{{/type}}
}
```

Time to wrap it up, as we mentioned each language generator just needs to implement [`LanguageGenerator`](src/languages/types.ts) as we mentioned, but you don't have to manually implement the properties with quicktype. We can use [`createQuicktypeLanguageGenerator`](src/languages/quicktype-utils.ts) to create a generator for us with all the pieces:

```ts
export const swift = createQuicktypeLanguageGenerator({
name: 'swift',
name: "swift",
// We pass in the class we created before for our language
quicktypeLanguage: TypewriterSwiftLanguage,
// We define in this array the SDKs we support and where the templates for each one are located
supportedSDKs: [
{
name: 'Analytics.Swift',
id: 'swift',
templatePath: 'templates/swift/analytics.hbs',
name: "Analytics.Swift",
id: "swift",
templatePath: "templates/swift/analytics.hbs",
},
// You can also define an empty SDK for generating types without additional code
{
name: 'None (Types and validation only)',
id: 'none',
name: "None (Types and validation only)",
id: "none",
},
],
// We pass in any default values for the options
defaultOptions: {
'just-types': true,
"just-types": true,
},
// You can also add unsupported options for quicktype, that way they won't show up during configuration nor let the user set them in the config file
unsupportedOptions: ['framework'],
// Customize here how your functionNames and typeNames should look like,
unsupportedOptions: ["framework"],
// Customize here how your functionNames and typeNames should look like,
nameModifiers: {
functionName: camelCase,
}
},
});
```

Finally let's add the language to the supported languages so that it shows up during the config wizard and it gets generated during build: add your exported language to the package exports `src/languages/index.ts`:

```ts
export { swift } from './swift';
export { swift } from "./swift";
```

In `src/hooks/prerun/load-languages.ts` add this instance:

```ts
import { Hook } from '@oclif/core';
import { kotlin, supportedLanguages, swift, typescript } from '../../languages';
import { Hook } from "@oclif/core";
import { kotlin, supportedLanguages, swift, typescript } from "../../languages";

const hook: Hook<'init'> = async function (opts) {
const hook: Hook<"init"> = async function (opts) {
// We inject any new languages plugins might support here
supportedLanguages.push(swift, kotlin, typescript);
};
Expand Down Expand Up @@ -227,7 +249,7 @@ If you need to do something more specific with Quicktype's rendering the `Render
}
```

In `Quicktype` each `Renderer` might have custom functions to emit parts of the generated types. It is always a good idea to take a peek at the available methods in the class you're extending.
In `Quicktype` each `Renderer` might have custom functions to emit parts of the generated types. It is always a good idea to take a peek at the available methods in the class you're extending.

`ConvenienceRenderer` is a superclass that all renderers inherit and has most of the basic functionality you will need. Some pretty handy functions are:

Expand Down Expand Up @@ -284,7 +306,10 @@ export interface LanguageGenerator {
* @param options header, sdk and additional renderer options (optional)
* @returns generated code as string
*/
generate: (rules: SegmentAPI.RuleMetadata[], options: GeneratorOptions) => Promise<string>;
generate: (
rules: SegmentAPI.RuleMetadata[],
options: GeneratorOptions
) => Promise<string>;
}
```

Expand Down
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@
<br>
<br>
<br>

<a href="https://circleci.com/gh/segmentio/typewriter">
<img src="https://circleci.com/gh/segmentio/typewriter.svg?style=svg&circle-token=8c1e734c99bdc08170e12d85af7a371900e33e96" alt="CircleCI Status">
</a>
<a href="http://www.npmjs.com/package/typewriter">
<img src="https://img.shields.io/npm/v/typewriter.svg" alt="NPM Version">
</a>
Expand Down Expand Up @@ -48,14 +44,13 @@ For more instructions on setting up your `typewriter` client, such as adding it
- To submit a bug report or feature request, [file an issue here](issues).
- To develop on `typewriter` or propose support for a new language, see [our contributors documentation](./.github/CONTRIBUTING.md).


## Migrating from v7

Check the instructions on our [documentation](https://segment.com/docs/protocols/typewriter)

- You'll need to change your Segment Config API Token for a Public API Token
- v8 doesn't support **Analytics-iOS** nor **Analytics-Android**. We recommend using [Analytics-Swift]() and [Analytics-Kotlin]() instead which are supported.
If you need to use these libraries you can run v7 specifying the version with your commands:
If you need to use these libraries you can run v7 specifying the version with your commands:

```sh
$ npx typewriter@7 build
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typewriter",
"version": "9.1.0",
"version": "9.2.0",
"description": "A compiler for generating strongly typed analytics clients via Segment Protocols",
"repository": "ssh://[email protected]/segmentio/typewriter.git",
"author": "Oscar Bazaldua <[email protected]>",
Expand Down Expand Up @@ -106,7 +106,7 @@
"version": "oclif readme && git add README.md"
},
"engines": {
"node": ">=14"
"node": ">=18"
},
"keywords": [
"oclif",
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/commands/__snapshots__/build.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -925,8 +925,7 @@ exports.default = new Proxy(clientAPI, {
`;

exports[`build builds client Language: kotlin, SDK:kotlin 1`] = `
"com.segment.analytics.kotlin.core.Analytics
// This client was automatically generated by Segment Typewriter. ** Do Not Edit **
"// This client was automatically generated by Segment Typewriter. ** Do Not Edit **
// To update this file, run:
// npx typewriter

Expand All @@ -936,6 +935,7 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import com.segment.analytics.kotlin.core.Analytics

/**
* Validates that clients properly sanitize event names.
Expand Down
2 changes: 1 addition & 1 deletion src/languages/kotlin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ class TypewriterKotlinRenderer extends KotlinXRenderer {

// We add our import for Analytics here, Kotlin is more strict about import order so we have to do it outside the templates
override emitHeader(): void {
this.emitLine('com.segment.analytics.kotlin.core.Analytics');
super.emitHeader();
this.emitLine('import com.segment.analytics.kotlin.core.Analytics');
}

override emitSource(givenOutputFilename: string): void {
Expand Down
2 changes: 1 addition & 1 deletion src/telemetry/segment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ function withTypewriterContext<P extends Record<string, any>, T extends TrackMes
...(message.context || {}),
typewriter: {
language: 'typescript',
version: '9.1.0',
version: '9.2.0',
},
},
}
Expand Down
Loading

0 comments on commit d838295

Please sign in to comment.