@rivi-software/radiotools-sdk-generator 0.2.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.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env bun
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { generateSdks, listTargets } from "./engine";
6
+ function repoRootFromImportMeta(metaUrl) {
7
+ const here = dirname(fileURLToPath(metaUrl));
8
+ return join(here, "..", "..", "..");
9
+ }
10
+ function parseArgs(argv) {
11
+ const [command = "help", ...rest] = argv;
12
+ let target = "all";
13
+ let dryRun = false;
14
+ for (let i = 0; i < rest.length; i += 1) {
15
+ const arg = rest[i];
16
+ if (arg === "--target" && rest[i + 1]) {
17
+ target = rest[i + 1];
18
+ i += 1;
19
+ continue;
20
+ }
21
+ if (arg === "--dry-run")
22
+ dryRun = true;
23
+ }
24
+ return { command, target, dryRun };
25
+ }
26
+ const { command, target, dryRun } = parseArgs(process.argv.slice(2));
27
+ const repoRoot = repoRootFromImportMeta(import.meta.url);
28
+ switch (command) {
29
+ case "generate":
30
+ generateSdks({ repoRoot, target, dryRun });
31
+ break;
32
+ case "list":
33
+ for (const entry of listTargets()) {
34
+ console.log(`${entry.id}\t${entry.description}`);
35
+ }
36
+ break;
37
+ default:
38
+ console.log(`Usage:
39
+ bun src/cli.ts generate [--target <id>|all] [--dry-run]
40
+ bun src/cli.ts list
41
+
42
+ Targets: ${listTargets().map((t) => t.id).join(", ")}, all`);
43
+ break;
44
+ }
@@ -0,0 +1,3 @@
1
+ import type { GenerateOptions, SdkTarget } from "./types";
2
+ export declare function listTargets(): SdkTarget[];
3
+ export declare function generateSdks(options: GenerateOptions): void;
package/dist/engine.js ADDED
@@ -0,0 +1,60 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, isAbsolute, join } from "node:path";
3
+ import { csharpTarget } from "./targets/csharp";
4
+ import { goTarget } from "./targets/go";
5
+ import { kotlinTarget } from "./targets/kotlin";
6
+ import { nextTarget } from "./targets/next";
7
+ import { openapiTarget } from "./targets/openapi";
8
+ import { phpTarget } from "./targets/php";
9
+ import { pythonTarget } from "./targets/python";
10
+ import { reactTarget } from "./targets/react";
11
+ import { rubyTarget } from "./targets/ruby";
12
+ import { svelteTarget } from "./targets/svelte";
13
+ import { swiftTarget } from "./targets/swift";
14
+ import { vueTarget } from "./targets/vue";
15
+ const targets = [
16
+ pythonTarget,
17
+ reactTarget,
18
+ vueTarget,
19
+ svelteTarget,
20
+ goTarget,
21
+ csharpTarget,
22
+ rubyTarget,
23
+ phpTarget,
24
+ openapiTarget,
25
+ kotlinTarget,
26
+ swiftTarget,
27
+ nextTarget,
28
+ ];
29
+ export function listTargets() {
30
+ return targets;
31
+ }
32
+ function resolveTargets(target) {
33
+ if (target === "all")
34
+ return targets;
35
+ const match = targets.find((entry) => entry.id === target);
36
+ if (!match) {
37
+ throw new Error(`Unknown target "${target}". Available: ${targets.map((t) => t.id).join(", ")}, all`);
38
+ }
39
+ return [match];
40
+ }
41
+ function writeGeneratedFile(baseDir, file, dryRun) {
42
+ const fullPath = isAbsolute(file.path) ? file.path : join(baseDir, file.path);
43
+ if (dryRun) {
44
+ console.log(`[dry-run] would write ${fullPath} (${file.contents.length} bytes)`);
45
+ return;
46
+ }
47
+ mkdirSync(dirname(fullPath), { recursive: true });
48
+ writeFileSync(fullPath, file.contents, "utf8");
49
+ console.log(`wrote ${fullPath}`);
50
+ }
51
+ export function generateSdks(options) {
52
+ const selected = resolveTargets(options.target);
53
+ for (const target of selected) {
54
+ const outDir = target.outputDir(options.repoRoot);
55
+ const files = target.generate(options.repoRoot);
56
+ for (const file of files) {
57
+ writeGeneratedFile(outDir, file, options.dryRun ?? false);
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,2 @@
1
+ export { generateSdks, listTargets } from "./engine";
2
+ export type { GenerateOptions, GeneratedFile, GeneratorTarget, SdkTarget } from "./types";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { generateSdks, listTargets } from "./engine";
@@ -0,0 +1,13 @@
1
+ export type OpenApiComponents = {
2
+ schemas: Record<string, Record<string, unknown>>;
3
+ };
4
+ /** JSON Schema components generated from `@rivi-software/radiotools-sdk` Zod types (`sdkSchemas`). */
5
+ export declare function buildZodOpenApiComponents(): OpenApiComponents;
6
+ export declare const operationResponseSchemas: Record<string, {
7
+ schema: string;
8
+ nullable?: boolean;
9
+ description: string;
10
+ }>;
11
+ export declare function jsonSchemaRef(componentName: string): {
12
+ $ref: string;
13
+ };
@@ -0,0 +1,47 @@
1
+ import { sdkSchemas } from "@rivi-software/radiotools-sdk";
2
+ import { z } from "zod";
3
+ function stripMeta(schema) {
4
+ const { $schema: _, ...rest } = schema;
5
+ return rest;
6
+ }
7
+ /** JSON Schema components generated from `@rivi-software/radiotools-sdk` Zod types (`sdkSchemas`). */
8
+ export function buildZodOpenApiComponents() {
9
+ const schemas = {};
10
+ for (const [name, zodSchema] of Object.entries(sdkSchemas)) {
11
+ const json = z.toJSONSchema(zodSchema, {
12
+ io: "output",
13
+ reused: "inline",
14
+ unrepresentable: "any",
15
+ });
16
+ schemas[name] = stripMeta(json);
17
+ }
18
+ return { schemas };
19
+ }
20
+ export const operationResponseSchemas = {
21
+ getStation: { schema: "PublicStation", description: "Published station metadata" },
22
+ getSchemas: { schema: "PublicSchemaList", description: "Content type schemas" },
23
+ getContent: { schema: "PublishedCollection", description: "Published content page" },
24
+ getContentRecord: { schema: "PublishedRecord", description: "One published record" },
25
+ getSchedule: { schema: "PublicSchedule", description: "Broadcast schedule" },
26
+ getPrograms: { schema: "PublicPrograms", description: "Program summaries" },
27
+ getCurrentBroadcast: { schema: "PublicCurrentBroadcast", description: "Current broadcast" },
28
+ getNowPlaying: { schema: "PublicNowPlaying", description: "Now playing" },
29
+ getTrackHistory: { schema: "PublicTrackHistory", description: "Track history page" },
30
+ getWeather: {
31
+ schema: "WeatherPublic",
32
+ nullable: true,
33
+ description: "Weather snapshot when enabled",
34
+ },
35
+ getTraffic: {
36
+ schema: "TrafficPublic",
37
+ nullable: true,
38
+ description: "Traffic snapshot when enabled",
39
+ },
40
+ reportPlayback: {
41
+ schema: "PlaybackObservationResult",
42
+ description: "Playback ingest acknowledgement",
43
+ },
44
+ };
45
+ export function jsonSchemaRef(componentName) {
46
+ return { $ref: `#/components/schemas/${componentName}` };
47
+ }
@@ -0,0 +1,2 @@
1
+ export declare function snakeCase(name: string): string;
2
+ export declare function pascalCase(name: string): string;
@@ -0,0 +1,6 @@
1
+ export function snakeCase(name) {
2
+ return name.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
3
+ }
4
+ export function pascalCase(name) {
5
+ return name.charAt(0).toUpperCase() + name.slice(1);
6
+ }
@@ -0,0 +1,5 @@
1
+ /** Client reads with no required parameters — safe for simple hooks/composables/stores. */
2
+ export declare const ZERO_ARG_READ_METHODS: readonly ["getStation", "getCurrentBroadcast", "getNowPlaying", "getWeather", "getTraffic"];
3
+ export type ZeroArgReadMethod = (typeof ZERO_ARG_READ_METHODS)[number];
4
+ export declare function isZeroArgReadMethod(name: string): name is ZeroArgReadMethod;
5
+ export declare function hookNameFromMethod(method: ZeroArgReadMethod): string;
@@ -0,0 +1,14 @@
1
+ /** Client reads with no required parameters — safe for simple hooks/composables/stores. */
2
+ export const ZERO_ARG_READ_METHODS = [
3
+ "getStation",
4
+ "getCurrentBroadcast",
5
+ "getNowPlaying",
6
+ "getWeather",
7
+ "getTraffic",
8
+ ];
9
+ export function isZeroArgReadMethod(name) {
10
+ return ZERO_ARG_READ_METHODS.includes(name);
11
+ }
12
+ export function hookNameFromMethod(method) {
13
+ return `use${method.charAt(0).toUpperCase()}${method.slice(1)}`;
14
+ }
@@ -0,0 +1,2 @@
1
+ import type { SdkTarget } from "../types";
2
+ export declare const csharpTarget: SdkTarget;
@@ -0,0 +1,137 @@
1
+ import { listenerSdkManifest } from "@rivi-software/radiotools-contract";
2
+ function renderCsharpClient() {
3
+ const playbackPath = listenerSdkManifest.integrations.playback.path;
4
+ return `// Generated by @rivi-software/radiotools-sdk-generator — do not edit by hand.
5
+ using System.Net.Http.Headers;
6
+ using System.Net.Http.Json;
7
+ using System.Text.Json;
8
+
9
+ namespace RadioTools.Sdk;
10
+
11
+ public sealed class StationClient : IDisposable
12
+ {
13
+ private readonly HttpClient _http;
14
+ private readonly string _baseUrl;
15
+ private readonly string _stationSlug;
16
+ private readonly string? _apiToken;
17
+ private readonly string? _ingestToken;
18
+
19
+ public StationClient(string baseUrl, string stationSlug, string? apiToken = null, string? ingestToken = null, HttpClient? http = null)
20
+ {
21
+ if (string.IsNullOrWhiteSpace(apiToken) && string.IsNullOrWhiteSpace(ingestToken))
22
+ throw new ArgumentException("apiToken or ingestToken is required");
23
+ _baseUrl = baseUrl.TrimEnd('/');
24
+ _stationSlug = stationSlug;
25
+ _apiToken = apiToken;
26
+ _ingestToken = ingestToken;
27
+ _http = http ?? new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
28
+ }
29
+
30
+ public static StationClient FromEnvironment()
31
+ {
32
+ var baseUrl = Environment.GetEnvironmentVariable("RADIOTOOLS_BASE_URL") ?? "";
33
+ var slug = Environment.GetEnvironmentVariable("RADIOTOOLS_STATION_SLUG") ?? "";
34
+ var api = Environment.GetEnvironmentVariable("RADIOTOOLS_API_TOKEN");
35
+ var ingest = Environment.GetEnvironmentVariable("RADIOTOOLS_INGEST_TOKEN");
36
+ if (string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(slug))
37
+ throw new InvalidOperationException("Set RADIOTOOLS_BASE_URL and RADIOTOOLS_STATION_SLUG");
38
+ return new StationClient(baseUrl, slug, api, ingest);
39
+ }
40
+
41
+ public async Task<JsonElement> GetStationAsync(CancellationToken cancellationToken = default)
42
+ {
43
+ var path = $"/api/v1/stations/{Uri.EscapeDataString(_stationSlug)}";
44
+ return await GetJsonAsync(path, ingest: false, cancellationToken);
45
+ }
46
+
47
+ public async Task<JsonElement?> GetWeatherAsync(CancellationToken cancellationToken = default)
48
+ {
49
+ var station = await GetStationAsync(cancellationToken);
50
+ if (!station.TryGetProperty("resources", out var resources)) return null;
51
+ if (!resources.TryGetProperty("weather", out var hrefProp)) return null;
52
+ var href = hrefProp.GetString();
53
+ if (string.IsNullOrEmpty(href)) return null;
54
+ return await GetJsonAsync(href, ingest: false, cancellationToken);
55
+ }
56
+
57
+ public async Task<JsonElement?> GetTrafficAsync(CancellationToken cancellationToken = default)
58
+ {
59
+ var station = await GetStationAsync(cancellationToken);
60
+ if (!station.TryGetProperty("resources", out var resources)) return null;
61
+ if (!resources.TryGetProperty("traffic", out var hrefProp)) return null;
62
+ var href = hrefProp.GetString();
63
+ if (string.IsNullOrEmpty(href)) return null;
64
+ return await GetJsonAsync(href, ingest: false, cancellationToken);
65
+ }
66
+
67
+ public async Task<JsonElement> ReportPlaybackAsync(object observation, string? stationId = null, CancellationToken cancellationToken = default)
68
+ {
69
+ var query = stationId is not null
70
+ ? $"?stationId={Uri.EscapeDataString(stationId)}"
71
+ : $"?stationSlug={Uri.EscapeDataString(_stationSlug)}";
72
+ var url = $"{_baseUrl}${playbackPath}{query}";
73
+ using var request = new HttpRequestMessage(HttpMethod.Post, url)
74
+ {
75
+ Content = JsonContent.Create(observation),
76
+ };
77
+ ApplyAuth(request, ingest: true);
78
+ using var response = await _http.SendAsync(request, cancellationToken);
79
+ response.EnsureSuccessStatusCode();
80
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
81
+ return await JsonSerializer.DeserializeAsync<JsonElement>(stream, cancellationToken: cancellationToken)
82
+ ?? throw new InvalidOperationException("empty playback response");
83
+ }
84
+
85
+ private async Task<JsonElement> GetJsonAsync(string path, bool ingest, CancellationToken cancellationToken)
86
+ {
87
+ var url = path.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? path : $"{_baseUrl}{path}";
88
+ using var request = new HttpRequestMessage(HttpMethod.Get, url);
89
+ ApplyAuth(request, ingest);
90
+ using var response = await _http.SendAsync(request, cancellationToken);
91
+ response.EnsureSuccessStatusCode();
92
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
93
+ return await JsonSerializer.DeserializeAsync<JsonElement>(stream, cancellationToken: cancellationToken)
94
+ ?? throw new InvalidOperationException("empty JSON response");
95
+ }
96
+
97
+ private void ApplyAuth(HttpRequestMessage request, bool ingest)
98
+ {
99
+ var token = ingest ? _ingestToken : _apiToken;
100
+ if (string.IsNullOrWhiteSpace(token))
101
+ throw new InvalidOperationException("Missing bearer token");
102
+ request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
103
+ }
104
+
105
+ public void Dispose() => _http.Dispose();
106
+ }
107
+ `;
108
+ }
109
+ export const csharpTarget = {
110
+ id: "csharp",
111
+ description: ".NET HttpClient stubs (packages/sdk-dotnet)",
112
+ outputDir: (repoRoot) => `${repoRoot}/packages/sdk-dotnet/src`,
113
+ generate: () => [
114
+ { path: "StationClient.cs", contents: renderCsharpClient() },
115
+ {
116
+ path: "../RadioTools.Sdk.csproj",
117
+ contents: `<Project Sdk="Microsoft.NET.Sdk">
118
+ <PropertyGroup>
119
+ <TargetFramework>net8.0</TargetFramework>
120
+ <ImplicitUsings>enable</ImplicitUsings>
121
+ <Nullable>enable</Nullable>
122
+ <RootNamespace>RadioTools.Sdk</RootNamespace>
123
+ <AssemblyName>RadioTools.Sdk</AssemblyName>
124
+ <Version>0.2.0</Version>
125
+ </PropertyGroup>
126
+ </Project>
127
+ `,
128
+ },
129
+ {
130
+ path: "../README.md",
131
+ contents: `# RadioTools.Sdk (.NET)
132
+
133
+ Generated listener REST stubs. Regenerate with \`bun run --filter @rivi-software/radiotools-sdk-generator generate --target csharp\`.
134
+ `,
135
+ },
136
+ ],
137
+ };
@@ -0,0 +1,2 @@
1
+ import type { SdkTarget } from "../types";
2
+ export declare const goTarget: SdkTarget;
@@ -0,0 +1,185 @@
1
+ import { listenerSdkManifest } from "@rivi-software/radiotools-contract";
2
+ function renderGoClient() {
3
+ const manifest = listenerSdkManifest;
4
+ return `// Generated by @rivi-software/radiotools-sdk-generator — do not edit by hand.
5
+ package radiotools
6
+
7
+ import (
8
+ "bytes"
9
+ "context"
10
+ "encoding/json"
11
+ "fmt"
12
+ "io"
13
+ "net/http"
14
+ "net/url"
15
+ "os"
16
+ "strings"
17
+ "time"
18
+ )
19
+
20
+ type StationClient struct {
21
+ BaseURL string
22
+ StationSlug string
23
+ APIToken string
24
+ IngestToken string
25
+ HTTPClient *http.Client
26
+ }
27
+
28
+ func NewStationClient(baseURL, stationSlug, apiToken, ingestToken string) (*StationClient, error) {
29
+ if apiToken == "" && ingestToken == "" {
30
+ return nil, fmt.Errorf("apiToken or ingestToken is required")
31
+ }
32
+ return &StationClient{
33
+ BaseURL: strings.TrimRight(baseURL, "/"),
34
+ StationSlug: stationSlug,
35
+ APIToken: apiToken,
36
+ IngestToken: ingestToken,
37
+ HTTPClient: &http.Client{Timeout: 30 * time.Second},
38
+ }, nil
39
+ }
40
+
41
+ func StationClientFromEnv() (*StationClient, error) {
42
+ baseURL := strings.TrimSpace(os.Getenv("RADIOTOOLS_BASE_URL"))
43
+ slug := strings.TrimSpace(os.Getenv("RADIOTOOLS_STATION_SLUG"))
44
+ apiToken := strings.TrimSpace(os.Getenv("RADIOTOOLS_API_TOKEN"))
45
+ ingestToken := strings.TrimSpace(os.Getenv("RADIOTOOLS_INGEST_TOKEN"))
46
+ if baseURL == "" || slug == "" {
47
+ return nil, fmt.Errorf("set RADIOTOOLS_BASE_URL and RADIOTOOLS_STATION_SLUG")
48
+ }
49
+ return NewStationClient(baseURL, slug, apiToken, ingestToken)
50
+ }
51
+
52
+ func (c *StationClient) getJSON(ctx context.Context, path string, ingest bool) (map[string]any, error) {
53
+ target := path
54
+ if !strings.HasPrefix(path, "http") {
55
+ target = c.BaseURL + path
56
+ }
57
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil)
58
+ if err != nil {
59
+ return nil, err
60
+ }
61
+ token := c.APIToken
62
+ if ingest {
63
+ token = c.IngestToken
64
+ }
65
+ if token == "" {
66
+ return nil, fmt.Errorf("missing bearer token")
67
+ }
68
+ req.Header.Set("Authorization", "Bearer "+token)
69
+ res, err := c.HTTPClient.Do(req)
70
+ if err != nil {
71
+ return nil, err
72
+ }
73
+ defer res.Body.Close()
74
+ if res.StatusCode >= 400 {
75
+ body, _ := io.ReadAll(res.Body)
76
+ return nil, fmt.Errorf("GET %s: %s (%s)", target, res.Status, strings.TrimSpace(string(body)))
77
+ }
78
+ var payload map[string]any
79
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
80
+ return nil, err
81
+ }
82
+ return payload, nil
83
+ }
84
+
85
+ func (c *StationClient) GetStation(ctx context.Context) (map[string]any, error) {
86
+ path := fmt.Sprintf("/api/v1/stations/%s", url.PathEscape(c.StationSlug))
87
+ return c.getJSON(ctx, path, false)
88
+ }
89
+
90
+ func (c *StationClient) GetWeather(ctx context.Context) (map[string]any, error) {
91
+ station, err := c.GetStation(ctx)
92
+ if err != nil {
93
+ return nil, err
94
+ }
95
+ resources, _ := station["resources"].(map[string]any)
96
+ if resources == nil {
97
+ return nil, nil
98
+ }
99
+ href, _ := resources["weather"].(string)
100
+ if href == "" {
101
+ return nil, nil
102
+ }
103
+ return c.getJSON(ctx, href, false)
104
+ }
105
+
106
+ func (c *StationClient) GetTraffic(ctx context.Context) (map[string]any, error) {
107
+ station, err := c.GetStation(ctx)
108
+ if err != nil {
109
+ return nil, err
110
+ }
111
+ resources, _ := station["resources"].(map[string]any)
112
+ if resources == nil {
113
+ return nil, nil
114
+ }
115
+ href, _ := resources["traffic"].(string)
116
+ if href == "" {
117
+ return nil, nil
118
+ }
119
+ return c.getJSON(ctx, href, false)
120
+ }
121
+
122
+ func (c *StationClient) ReportPlayback(ctx context.Context, observation map[string]any, stationID string) (map[string]any, error) {
123
+ q := url.Values{}
124
+ if stationID != "" {
125
+ q.Set("stationId", stationID)
126
+ } else {
127
+ q.Set("stationSlug", c.StationSlug)
128
+ }
129
+ target := c.BaseURL + "${manifest.integrations.playback.path}?" + q.Encode()
130
+ body, err := json.Marshal(observation)
131
+ if err != nil {
132
+ return nil, err
133
+ }
134
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, target, bytes.NewReader(body))
135
+ if err != nil {
136
+ return nil, err
137
+ }
138
+ if c.IngestToken == "" {
139
+ return nil, fmt.Errorf("ingest token required")
140
+ }
141
+ req.Header.Set("Authorization", "Bearer "+c.IngestToken)
142
+ req.Header.Set("Content-Type", "application/json")
143
+ res, err := c.HTTPClient.Do(req)
144
+ if err != nil {
145
+ return nil, err
146
+ }
147
+ defer res.Body.Close()
148
+ if res.StatusCode >= 400 {
149
+ raw, _ := io.ReadAll(res.Body)
150
+ return nil, fmt.Errorf("POST playback: %s (%s)", res.Status, strings.TrimSpace(string(raw)))
151
+ }
152
+ var payload map[string]any
153
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
154
+ return nil, err
155
+ }
156
+ return payload, nil
157
+ }
158
+ `;
159
+ }
160
+ export const goTarget = {
161
+ id: "go",
162
+ description: "Go net/http client stubs (packages/sdk-go)",
163
+ outputDir: (repoRoot) => `${repoRoot}/packages/sdk-go/radiotools`,
164
+ generate: () => [
165
+ { path: "client.go", contents: renderGoClient() },
166
+ {
167
+ path: "../go.mod",
168
+ contents: `module github.com/radiotools/radiotools-sdk-go
169
+
170
+ go 1.22
171
+ `,
172
+ },
173
+ {
174
+ path: "../README.md",
175
+ contents: `# radiotools-sdk-go
176
+
177
+ Generated Go client stubs for RadioTools listener contract v${listenerSdkManifest.contractVersion}.
178
+
179
+ Regenerate: \`bun run --filter @rivi-software/radiotools-sdk-generator generate --target go\`
180
+
181
+ For GraphQL, WebSocket, and ETag behavior use \`@rivi-software/radiotools-sdk\` (TypeScript).
182
+ `,
183
+ },
184
+ ],
185
+ };
@@ -0,0 +1,2 @@
1
+ import type { SdkTarget } from "../types";
2
+ export declare const kotlinTarget: SdkTarget;