Skip to content

Commit 2eaa23a

Browse files
louis-preclaudeseambot
authored
fix: Correct code sample errors and add tab formatting to CI (#1105)
* fix: Correct syntax errors and typos in hand-written code samples across guides Fixes 19 issues found during a code sample audit: - Wrong capability flag in quickstart cURL (can_remotely_lock → can_remotely_unlock) - Wrong cURL endpoints (access_methods/get → access_grants/create, devices/list → events/list) - Extra '>' in cURL URL (customer-portals) - Python syntax using Ruby-style ':' instead of '=' for kwargs (mobile-key-quick-start) - Wrong PHP class name (SeamSeamClient → Seam\SeamClient in nuki-locks) - Wrong C# method name (SystemsAcs.List → Acs.Systems.List) - Malformed JSON using '=' instead of ':' (filtering-connect-webviews) - JS object literal using '=' instead of ':' (hospitality guides) - JS variable name mismatch (connectedAccounts vs connected_accounts) - Missing JSON commas in output examples (multiple files) - Stray spaces before array index [0] (nest-thermostats) - Ruby placeholder using JS comment syntax (// → #) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: Add format-code-sample-tabs to CI format workflow Adds a codegen script that auto-fixes code sample tab order and titles in guide and brand-guide markdown files. Runs as part of the Format CI workflow, which auto-commits fixes on non-main branches. The canonical tab order and title mapping are defined in a shared module (codegen/lib/code-sample-tab-order.ts), imported by both the API reference codegen and the tab formatter — single source of truth. The script: - Reorders tabs to match API reference canonical order (JavaScript → cURL → Python → Ruby → PHP → C# → Java → Go → Seam CLI) - Normalizes tab titles ("cURL (bash)" → "cURL", "Javascript" → "JavaScript") - Ensures GitBook tab syncing works across all code blocks on a page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: Format code * fix: Include api-reference source files in tab formatter scope Adds codegen/source/docs/api-reference/ to the format-code-sample-tabs scan paths so hand-written api-reference pages (authentication.md, pagination.md) also get their tabs normalized. Fixes 6 remaining "cURL (bash)" → "cURL" instances. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: Generate docs * fix: Fix = vs : in JS/Ruby/PHP named args and missing PHP comma Fixes acs_system_ids using = instead of : in JavaScript object literals, Ruby keyword arguments, and PHP named parameters across encoder-related code samples. Also fixes a missing comma in PHP code in mapping-your-resources-to-seam-resources.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Seam Bot <seambot@getseam.com>
1 parent b43a58b commit 2eaa23a

125 files changed

Lines changed: 15319 additions & 13398 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/format.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ jobs:
2929
passphrase: ${{ secrets.GPG_PASSPHRASE }}
3030
- name: Setup
3131
uses: ./.github/actions/setup
32+
- name: Format code sample tabs
33+
run: npm run format-code-sample-tabs
3234
- name: Format
3335
run: npm run format
3436
- name: Commit

codegen/format-code-sample-tabs.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { readFileSync, writeFileSync } from 'node:fs'
2+
import { join } from 'node:path'
3+
4+
import { globSync } from 'glob'
5+
6+
import { tabTitleFixes, tabTitleOrder } from './lib/code-sample-tab-order.js'
7+
8+
const tabsBlockPattern = /\{%\s*tabs\s*%\}[\s\S]*?\{%\s*endtabs\s*%\}/g
9+
10+
const tabPattern =
11+
/\{%\s*tab\s+title="([^"]+)"\s*%\}([\s\S]*?)\{%\s*endtab\s*%\}/g
12+
13+
function canonicalIndex(title: string): number {
14+
const normalized = tabTitleFixes[title] ?? title
15+
return tabTitleOrder.get(normalized) ?? tabTitleOrder.size + 1
16+
}
17+
18+
function formatTabsBlock(block: string): { result: string; changed: boolean } {
19+
const tabs: Array<{ title: string; content: string }> = []
20+
for (const match of block.matchAll(tabPattern)) {
21+
tabs.push({ title: match[1] ?? '', content: match[2] ?? '' })
22+
}
23+
24+
if (tabs.length < 2) return { result: block, changed: false }
25+
26+
const originalTitles = tabs.map((t) => t.title)
27+
const sorted = [...tabs].sort(
28+
(a, b) => canonicalIndex(a.title) - canonicalIndex(b.title),
29+
)
30+
const sortedTitles = sorted.map((t) => t.title)
31+
const needsRename = originalTitles.some((t) => t in tabTitleFixes)
32+
const needsReorder = originalTitles.some((t, i) => t !== sortedTitles[i])
33+
34+
if (!needsReorder && !needsRename) return { result: block, changed: false }
35+
36+
const lines = ['{% tabs %}']
37+
for (const tab of sorted) {
38+
const title = tabTitleFixes[tab.title] ?? tab.title
39+
lines.push(`{% tab title="${title}" %}`)
40+
lines.push(tab.content.trimEnd())
41+
lines.push('{% endtab %}')
42+
lines.push('')
43+
}
44+
while (lines.at(-1) === '') lines.pop()
45+
lines.push('{% endtabs %}')
46+
47+
return { result: lines.join('\n'), changed: true }
48+
}
49+
50+
const dirs = [
51+
'docs/guides',
52+
'docs/brand-guides',
53+
'codegen/source/docs/api-reference',
54+
]
55+
const files = dirs.flatMap((dir) => globSync(join(dir, '**/*.md')))
56+
57+
let totalChanged = 0
58+
59+
for (const file of files) {
60+
const content = readFileSync(file, 'utf-8')
61+
let changed = false
62+
63+
const updated = content.replace(tabsBlockPattern, (block) => {
64+
const { result, changed: blockChanged } = formatTabsBlock(block)
65+
if (blockChanged) changed = true
66+
return result
67+
})
68+
69+
if (changed) {
70+
writeFileSync(file, updated)
71+
totalChanged++
72+
}
73+
}
74+
75+
if (totalChanged > 0) {
76+
// eslint-disable-next-line no-console
77+
console.log(`Formatted tabs in ${String(totalChanged)} file(s).`)
78+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { SdkName } from '@seamapi/blueprint'
2+
3+
/**
4+
* Canonical tab order for code samples across all documentation.
5+
* Used by the API reference codegen (api-endpoint.ts) and the
6+
* format-code-sample-tabs script to keep guide tabs in sync.
7+
*/
8+
export const supportedSdkOrder: SdkName[] = [
9+
'javascript',
10+
'curl',
11+
'python',
12+
'ruby',
13+
'php',
14+
'csharp',
15+
'java',
16+
'go',
17+
'seam_cli',
18+
]
19+
20+
/**
21+
* Maps SdkName values to their canonical display titles as used in
22+
* GitBook tab headers. Sourced from @seamapi/blueprint CodeSample titles.
23+
*/
24+
export const sdkDisplayTitle: Record<SdkName, string> = {
25+
javascript: 'JavaScript',
26+
curl: 'cURL',
27+
python: 'Python',
28+
ruby: 'Ruby',
29+
php: 'PHP',
30+
csharp: 'C#',
31+
java: 'Java',
32+
go: 'Go',
33+
seam_cli: 'Seam CLI',
34+
}
35+
36+
/** Reverse lookup: display title → canonical index. */
37+
export const tabTitleOrder: Map<string, number> = new Map(
38+
supportedSdkOrder.map((sdk, i) => [sdkDisplayTitle[sdk], i]),
39+
)
40+
41+
/** Known non-canonical title variants that should be normalized. */
42+
export const tabTitleFixes: Record<string, string> = {
43+
'cURL (bash)': 'cURL',
44+
Bash: 'cURL',
45+
Javascript: 'JavaScript',
46+
javascript: 'JavaScript',
47+
}

codegen/lib/layout/api-endpoint.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
} from '@seamapi/blueprint'
1414
import { capitalCase } from 'change-case'
1515

16+
import { supportedSdkOrder } from '../code-sample-tab-order.js'
1617
import type { PathMetadata } from '../path-metadata.js'
1718
import {
1819
type ApiRouteResource,
@@ -23,14 +24,18 @@ import {
2324
resourceSampleFilter,
2425
} from './api-route.js'
2526

26-
const supportedSdks: SdkName[] = [
27+
const apiReferenceSdks = new Set<SdkName>([
2728
'javascript',
2829
'curl',
2930
'python',
3031
'ruby',
3132
'php',
3233
'seam_cli',
33-
]
34+
])
35+
36+
const supportedSdks = supportedSdkOrder.filter((sdk) =>
37+
apiReferenceSdks.has(sdk),
38+
)
3439

3540
export interface ApiEndpointLayoutContext {
3641
description: string

codegen/source/docs/api-reference/authentication.md

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,38 @@ $ export SEAM_API_KEY=seam_test2bMS_94SrGUXuNR2JmJkjtvBQDg5c
1313
Next, run the following code to confirm that you are correctly authenticated:
1414

1515
{% tabs %}
16-
{% tab title="Python" %}
16+
{% tab title="JavaScript" %}
17+
1718
**Code:**
1819

19-
```python
20-
from seam import Seam
20+
```javascript
21+
import { Seam } from "seam";
2122

22-
seam = Seam() # Seam automatically uses your exported SEAM_API_KEY.
23+
const seam = new Seam(); // Seam automatically uses your exported SEAM_API_KEY.
2324

24-
workspace = seam.workspaces.get()
25-
pprint(workspace)
25+
const checkAuth = async () => {
26+
const workspace = await seam.workspaces.get();
27+
console.log(workspace);
28+
}
29+
30+
checkAuth();
2631
```
2732

2833
**Output:**
2934

30-
```
31-
Workspace(
32-
workspace_id='00000000-0000-0000-0000-000000000000',
33-
name='Sandbox',
34-
company_name='Acme',
35-
connect_partner_name='Acme',
36-
is_sandbox=True
37-
)
35+
```json
36+
{
37+
workspace_id: '00000000-0000-0000-0000-000000000000',
38+
name: 'Sandbox',
39+
company_name: 'Acme',
40+
connect_partner_name: 'Acme',
41+
is_sandbox": true
42+
}
3843
```
3944
{% endtab %}
4045

41-
{% tab title="cURL (bash)" %}
46+
{% tab title="cURL" %}
47+
4248
**Code:**
4349

4450
```bash
@@ -66,36 +72,34 @@ curl -X 'POST' \
6672
```
6773
{% endtab %}
6874

69-
{% tab title="JavaScript" %}
70-
**Code:**
75+
{% tab title="Python" %}
7176

72-
```javascript
73-
import { Seam } from "seam";
77+
**Code:**
7478

75-
const seam = new Seam(); // Seam automatically uses your exported SEAM_API_KEY.
79+
```python
80+
from seam import Seam
7681

77-
const checkAuth = async () => {
78-
const workspace = await seam.workspaces.get();
79-
console.log(workspace);
80-
}
82+
seam = Seam() # Seam automatically uses your exported SEAM_API_KEY.
8183

82-
checkAuth();
84+
workspace = seam.workspaces.get()
85+
pprint(workspace)
8386
```
8487

8588
**Output:**
8689

87-
```json
88-
{
89-
workspace_id: '00000000-0000-0000-0000-000000000000',
90-
name: 'Sandbox',
91-
company_name: 'Acme',
92-
connect_partner_name: 'Acme',
93-
is_sandbox": true
94-
}
90+
```
91+
Workspace(
92+
workspace_id='00000000-0000-0000-0000-000000000000',
93+
name='Sandbox',
94+
company_name='Acme',
95+
connect_partner_name='Acme',
96+
is_sandbox=True
97+
)
9598
```
9699
{% endtab %}
97100

98101
{% tab title="Ruby" %}
102+
99103
**Code:**
100104

101105
```ruby
@@ -123,6 +127,7 @@ puts workspace.inspect
123127
{% endtab %}
124128

125129
{% tab title="PHP" %}
130+
126131
**Code:**
127132

128133
```php
@@ -150,6 +155,7 @@ echo json_encode($workspace, JSON_PRETTY_PRINT);
150155
{% endtab %}
151156

152157
{% tab title="C#" %}
158+
153159
**Code:**
154160

155161
```csharp
@@ -174,7 +180,4 @@ Console.WriteLine(workspace);
174180
}
175181
```
176182
{% endtab %}
177-
178-
179-
180183
{% endtabs %}

0 commit comments

Comments
 (0)