Skip to content

Commit 2d5ee5c

Browse files
razor-xclaude
andauthored
Port PHP SDK code generation from nextlove to Metalsmith (#446)
* Port PHP SDK route generation from nextlove to Metalsmith Replace the @seamapi/nextlove-sdk-generator based generation with the metalsmith + handlebars + @seamapi/blueprint architecture used by seamapi/javascript-http and seamapi/python. The generated output under src/ (SeamClient.php and Objects/*.php) is byte-identical. The blueprint drives the route, endpoint, and namespace iteration order. The raw OpenAPI spec is still consulted wherever the nextlove generator derived output from data the blueprint normalizes differently (integer vs number types, resource schema set and order, parameter and oneOf/allOf flattening, deeply nested namespaces); each of those spots carries a TODO to migrate to the blueprint once output is allowed to change, and the supporting code lives in files marked TEMPORARY. - Add codegen/ with the Metalsmith pipeline, plugin, context builders, and Handlebars layouts and partials - Remove generate-routes.js - Pin @seamapi/types at 1.827.0 for output parity and use @seamapi/blueprint 0.55.0 with @seamapi/smith - Add tsconfig.json, a one-line eslint.config.ts, and typecheck, lint, postlint, and preformat scripts matching seamapi/docs - Ignore *.hbs and CODEGEN.md in a new .prettierignore since the glimmer parser mangles PHP-flavored templates, and format .ts with the js rules - Ignore the generated CODEGEN.md seed and *.tsbuildinfo in .gitignore Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrUX4ghim2PDnBjXayuLDj * Update .gitignore for node * test: Remove tests that depend on the hosted fake Seam server The HttpError and Paginator tests ran against the hosted fake.seam.vc test server, so they fail whenever that service is unavailable (as it currently is, returning 500s) rather than testing the SDK. Remove those server-dependent tests and the Fixture helper. Keep testNonSeamError, which exercises the client's connection-error handling against an unreachable host and needs no Seam server. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrUX4ghim2PDnBjXayuLDj * test: Drop deleted Fixture reference from HttpErrorTest The previous commit deleted tests/Fixture.php but did not stage the HttpErrorTest edit that removes its Fixture usage, so CI still ran the old server-dependent tests and errored with "Class Tests\Fixture not found". Apply the intended HttpErrorTest, leaving only testNonSeamError. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrUX4ghim2PDnBjXayuLDj --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent de79cb9 commit 2d5ee5c

35 files changed

Lines changed: 7158 additions & 1093 deletions

.gitignore

Lines changed: 168 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,170 @@
1+
# Parts of this file were adapted from
2+
# GitHub’s collection of .gitignore file templates
3+
# which are Copyright (c) 2023 GitHub, Inc.
4+
# and released under the MIT License.
5+
# For more details, visit the project page:
6+
# https://github.com/github/gitignore
7+
8+
# Codegen
9+
CODEGEN.md
10+
11+
# Temporary development files
112
vendor
2-
node_modules
13+
tmp
14+
15+
# Build directories
16+
package
17+
18+
# Environment versions file
19+
.versions
20+
21+
# Tern
22+
.tern-project
23+
.tern-port
24+
25+
# npm config
26+
.npmrc
27+
28+
# Temporary development files
29+
tmp
30+
31+
# Yarn lockfile (only package-lock.json supported)
32+
yarn.lock
33+
34+
# Logs
35+
logs
36+
*.log
37+
npm-debug.log*
38+
yarn-debug.log*
39+
yarn-error.log*
40+
lerna-debug.log*
41+
.pnpm-debug.log*
42+
43+
# Diagnostic reports (https://nodejs.org/api/report.html)
44+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
45+
46+
# Runtime data
47+
pids
48+
*.pid
49+
*.seed
50+
*.pid.lock
51+
52+
# Directory for instrumented libs generated by jscoverage/JSCover
53+
lib-cov
54+
55+
# Coverage directory used by tools like istanbul
56+
coverage
57+
*.lcov
58+
59+
# nyc test coverage
60+
.nyc_output
61+
62+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
63+
.grunt
64+
65+
# Bower dependency directory (https://bower.io/)
66+
bower_components
67+
68+
# node-waf configuration
69+
.lock-wscript
70+
71+
# Compiled binary addons (https://nodejs.org/api/addons.html)
72+
build/Release
73+
74+
# Dependency directories
75+
node_modules/
76+
jspm_packages/
77+
78+
# Snowpack dependency directory (https://snowpack.dev/)
79+
web_modules/
80+
81+
# TypeScript cache
82+
*.tsbuildinfo
83+
84+
# Optional npm cache directory
85+
.npm
86+
87+
# Optional eslint cache
88+
.eslintcache
89+
90+
# Optional stylelint cache
91+
.stylelintcache
92+
93+
# Microbundle cache
94+
.rpt2_cache/
95+
.rts2_cache_cjs/
96+
.rts2_cache_es/
97+
.rts2_cache_umd/
98+
99+
# Optional REPL history
100+
.node_repl_history
101+
102+
# Output of 'npm pack'
103+
*.tgz
104+
105+
# Yarn Integrity file
106+
.yarn-integrity
107+
108+
# dotenv environment variable files
3109
.env
4-
.vscode
110+
.env.development.local
111+
.env.test.local
112+
.env.production.local
113+
.env.local
114+
115+
# parcel-bundler cache (https://parceljs.org/)
116+
.cache
117+
.parcel-cache
118+
119+
# Next.js build output
120+
.next
121+
out
122+
123+
# Nuxt.js build / generate output
124+
.nuxt
125+
dist
126+
127+
# Gatsby files
128+
.cache/
129+
# Comment in the public line in if your project uses Gatsby and not Next.js
130+
# https://nextjs.org/blog/next-9-1#public-directory-support
131+
# public
132+
133+
# vuepress build output
134+
.vuepress/dist
135+
136+
# vuepress v2.x temp and cache directory
137+
.temp
138+
.cache
139+
140+
# Docusaurus cache and generated files
141+
.docusaurus
142+
143+
# Serverless directories
144+
.serverless/
145+
146+
# FuseBox cache
147+
.fusebox/
148+
149+
# DynamoDB Local files
150+
.dynamodb/
151+
152+
# TernJS port file
153+
.tern-port
154+
155+
# Stores VSCode versions used for testing VSCode extensions
156+
.vscode-test
157+
158+
# yarn v2
159+
.yarn/cache
160+
.yarn/unplugged
161+
.yarn/build-state.yml
162+
.yarn/install-state.gz
163+
.pnp.*
164+
165+
# yalc
166+
.yalc/
167+
yalc.lock
168+
169+
# Codegen exceptions
170+
!codegen/lib/

.prettierignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Prettier exceptions
2+
3+
*.hbs
4+
CODEGEN.md

.prettierrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
"overrides": [
66
{
7-
"files": "**/*.{js,json,yml,md}",
7+
"files": "**/*.{js,ts,json,yml,md}",
88
"options": {
99
"semi": false,
1010
"singleQuote": true,

codegen/content/CODEGEN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Codegen

codegen/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default null

codegen/layouts/default.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{contents}}

codegen/layouts/object.hbs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Seam\Objects;
4+
5+
class {{className}}
6+
{
7+
public static function from_json(mixed $json): {{className}}|null
8+
{
9+
if (!$json) {
10+
return null;
11+
}
12+
return new self(
13+
{{#each fromJsonProps}}
14+
{{{this}}}
15+
{{/each}}
16+
);
17+
}
18+
19+
public function __construct(
20+
{{#each constructorParams}}
21+
{{{this}}}
22+
{{/each}}
23+
) {
24+
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class {{clientName}}Client
2+
{
3+
private SeamClient $seam;
4+
{{#if hasChildClients}}
5+
{{#each childClients}}
6+
public {{clientName}}Client ${{namespace}};
7+
{{/each}}
8+
{{else}}
9+
10+
{{/if}}
11+
public function __construct(SeamClient $seam)
12+
{
13+
$this->seam = $seam;
14+
{{#each childClients}}
15+
$this->{{namespace}} = new {{clientName}}Client($seam);
16+
{{/each}}
17+
}
18+
{{#each methods}}
19+
20+
{{> route-method}}
21+
{{/each}}
22+
{{#if isActionAttempts}}
23+
{{> poll-until-ready}}
24+
{{/if}}
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
public function poll_until_ready(string $action_attempt_id, float $timeout = 20.0): ActionAttempt
2+
{
3+
$seam = $this->seam;
4+
$time_waiting = 0.0;
5+
$polling_interval = 0.4;
6+
$action_attempt = $seam->action_attempts->get($action_attempt_id);
7+
8+
while ($action_attempt->status == 'pending') {
9+
$action_attempt = $seam->action_attempts->get(
10+
$action_attempt->action_attempt_id
11+
);
12+
if ($time_waiting > $timeout) {
13+
throw new ActionAttemptTimeoutError($action_attempt, $timeout);
14+
}
15+
$time_waiting += $polling_interval;
16+
usleep($polling_interval * 1000000);
17+
}
18+
19+
if ($action_attempt->status == 'error') {
20+
throw new ActionAttemptFailedError($action_attempt);
21+
}
22+
23+
return $action_attempt;
24+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
public function {{methodName}}({{{signatureParams}}}): {{returnType}} {
2+
{{#if hasParams}}
3+
$request_payload = [];
4+
5+
{{#each paramNames}}
6+
if (${{this}} !== null) {
7+
$request_payload["{{this}}"] = ${{this}};
8+
}
9+
{{/each}}
10+
11+
{{/if}}
12+
{{#unless returnsVoid}}$res = {{/unless}}$this->seam->request(
13+
"POST",
14+
"{{path}}",
15+
{{#if hasParams}}
16+
json: (object) $request_payload,
17+
{{/if}}
18+
);
19+
{{#if usesActionAttempt}}
20+
21+
if (!$wait_for_action_attempt) {
22+
return {{returnResource}}::from_json($res->{{returnPath}});
23+
}
24+
25+
$action_attempt = $this->seam->action_attempts->poll_until_ready($res->action_attempt->action_attempt_id);
26+
27+
return $action_attempt;
28+
{{else}}
29+
{{#if usesOnResponse}}
30+
31+
if ($on_response !== null) {
32+
$on_response($res);
33+
}
34+
{{/if}}
35+
{{#unless returnsVoid}}
36+
37+
{{#if isArrayResponse}}
38+
return array_map(fn ($r) => {{returnResource}}::from_json($r), $res->{{returnPath}});
39+
{{else}}
40+
return {{returnResource}}::from_json($res->{{returnPath}});
41+
{{/if}}
42+
{{/unless}}
43+
{{/if}}
44+
}

0 commit comments

Comments
 (0)