refactor(spec): PoC - TypeSpec#45
Open
dschmidt wants to merge 1 commit into
Open
Conversation
dschmidt
marked this pull request as ready for review
May 13, 2026 01:38
dschmidt
requested review from
butonic,
dragotin,
fschade,
kulmann,
micbar and
rhafer
as code owners
May 13, 2026 01:38
dschmidt
force-pushed
the
refactor/typespec-poc
branch
from
May 13, 2026 01:39
28cd0cd to
cb8bfd6
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a proof-of-concept migration of the Libre Graph API specification from a hand-maintained OpenAPI YAML to TypeSpec sources, with CI and local tooling updated so api/openapi-spec/v1.0.yaml becomes a generated build artifact.
Changes:
- Add a TypeSpec-based spec (
spec/*.tsp) covering drives, permissions, users, groups, education, invitations, tags, activities, and applications. - Add local build tooling (Makefile + scripts) to compile the TypeSpec spec and generate clients via the existing OpenAPI Generator image pin.
- Update Woodpecker pipelines to compile the spec before generating/publishing docs and before generating language clients; ignore generated spec output in git.
Reviewed changes
Copilot reviewed 31 out of 34 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
spec/main.tsp |
TypeSpec entrypoint wiring all domain files and service metadata. |
spec/tspconfig.yaml |
Configures OpenAPI3 emission to api/openapi-spec/v1.0.yaml. |
spec/common.models.tsp |
Adds shared cross-domain models (OData error, common query params). |
spec/common.templates.tsp |
Adds reusable TypeSpec templates for common operations and response shapes. |
spec/drives.models.tsp |
Ports drive + driveItem models, sharing/permission models, and shared drive query params. |
spec/drives.templates.tsp |
Introduces DriveItem operation templates to reduce sibling endpoint drift. |
spec/drives.routes.tsp |
Declares drive and driveItem routes, mounting the shared templates. |
spec/permissions.models.tsp |
Adds global role definition models for permission role endpoints. |
spec/permissions.templates.tsp |
Adds shared permission operation templates mounted at multiple parent paths. |
spec/permissions.routes.tsp |
Declares global permission roleDefinition routes. |
spec/users.tsp |
Ports user models and /users routes (including appRoleAssignments and photo). |
spec/me.tsp |
Ports /me routes (user, photo, drives, home drive convenience routes). |
spec/groups.tsp |
Ports group models and /groups routes (including members $ref endpoints). |
spec/education.tsp |
Ports education domain models and routes under /education/*. |
spec/invitations.tsp |
Ports invitation models and /invitations routes using shared templates. |
spec/tags.tsp |
Ports tags extension endpoints under /extensions/org.libregraph/tags. |
spec/activities.tsp |
Ports activities extension endpoint under /extensions/org.libregraph/activities. |
spec/applications.tsp |
Ports applications models and /applications routes. |
spec/package.json |
Adds Node/TypeSpec dependencies and build script for spec compilation. |
spec/package-lock.json |
Locks TypeSpec compiler/emitter dependency graph. |
spec/.gitignore |
Ignores node_modules/ and build/ under spec/. |
scripts/compile-spec.sh |
Adds a script to install deps (if needed) and run tsp compile. |
scripts/generate-go.sh |
Wraps OpenAPI Generator Go client generation in a reusable script. |
scripts/generate-typescript-axios.sh |
Wraps OpenAPI Generator TypeScript-Axios client generation in a reusable script. |
scripts/generate-php.sh |
Wraps OpenAPI Generator PHP client generation in a reusable script. |
scripts/generate-cpp-qt-client.sh |
Wraps OpenAPI Generator C++/Qt client generation in a reusable script. |
Makefile |
Adds local targets to compile spec and generate language clients using pinned generator images. |
.woodpecker/build-go.yaml |
Adds a compile-spec step and switches generation to the shared scripts. |
.woodpecker/build-typescript-axios.yaml |
Adds a compile-spec step and switches generation to the shared scripts. |
.woodpecker/build-php.yaml |
Adds a compile-spec step and switches generation to the shared scripts. |
.woodpecker/build-cpp-qt.yaml |
Adds a compile-spec step and switches generation to the shared scripts. |
.woodpecker/docs.yaml |
Adds a compile-spec step before docs deployment. |
.gitignore |
Ignores generated OpenAPI output and local build artifacts. |
Files not reviewed (1)
- spec/package-lock.json: Language not supported
Comments suppressed due to low confidence (1)
.woodpecker/docs.yaml:33
- The new
compile-specstep will createspec/node_modules, and the subsequentrsync -a ... .deploys the entire workspace. This will uploadnode_modules(and other local build artefacts) to the docs host, increasing deploy size/time and potentially exhausting storage. Add rsync excludes forspec/node_modules,spec/build,build/, etc., or delete those directories before rsync.
- name: compile-spec
image: *node_image
commands:
- sh scripts/compile-spec.sh
- name: deploy-staging
image: debian:latest
environment:
SSH_KEY:
from_secret: ssh-key-staging
REMOTE_HOST:
from_secret: remote-host-staging
when:
event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
commands:
- apt-get update
- apt-get install -y openssh-client rsync
- mkdir -p $HOME/.ssh
- ssh-keyscan -t rsa $REMOTE_HOST >> $HOME/.ssh/known_hosts
- echo "$SSH_KEY" > "$HOME/.ssh/id_rsa"
- chmod 0600 $HOME/.ssh/id_rsa
- rsync -a --exclude '.git' --delete . $REMOTE_HOST:/var/swagger/libre-graph-api
- name: deploy-production
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
dschmidt
added a commit
to dschmidt/libre-graph-api
that referenced
this pull request
May 13, 2026
- `.woodpecker/docs.yaml`: rsync now excludes `spec/node_modules`, `spec/build`, `build/` so the deploy host doesn't receive the TypeSpec compiler's install tree (which the new `compile-spec` step creates). - `scripts/compile-spec.sh`: reinstall when the lockfile is newer than `node_modules/.package-lock.json`, not only when `node_modules` is missing. Renovate-driven dependency bumps would otherwise be silently ignored by stale local caches. - Three docstring typos (`avalable` -> `available`, `And extension` -> `An extension`, `childrens` -> `children`).
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 34 changed files in this pull request and generated 6 comments.
Files not reviewed (1)
- spec/package-lock.json: Language not supported
Comments suppressed due to low confidence (1)
scripts/compile-spec.sh:14
- The install-marker check uses
node_modules/.package-lock.json, but the script never creates/updates that file afternpm ci. As written,npm ciwill rerun every timecompile-spec.shis invoked (because-ntis true when the marker file is missing). Consider creating/updating the marker after a successful install (e.g., copy the lockfile into place) or drop the marker logic and always runnpm ciin CI while keeping a separate local-cache strategy.
npm ci --prefer-offline --no-audit --no-fund
npx tsp compile .
dschmidt
added a commit
to dschmidt/libre-graph-api
that referenced
this pull request
May 13, 2026
- `Makefile`: pass `HOME=/tmp` + `npm_config_cache=/tmp/.npm` into the spec compile container so `npm ci` can write its cache/state when the host uid/gid doesn't match `/home/node` in `node:22-alpine`. - `spec/common.models.tsp`: move `SearchParam` here from `groups.tsp`. It's a generic OData `$search` query parameter and was creating an unnecessary `users.tsp -> groups.tsp` dependency. - `spec/permissions.routes.tsp`: fix the role-id path parameter in each `GetPermissionRoleDefinition` `@opExample` so it matches the role id in the corresponding response body. Also align the File Drop `allowedResourceActions` value (`upload/create`) with the list endpoint's example. - `spec/tags.tsp`: make the request body required on `assignTags` and `unassignTags` (the body schemas require `resourceId` and `tags`, so a body-less call can't succeed). Update `TagsOk`'s doc from "No content" to "Success" since the status code is 200 with empty body. - `spec/permissions.models.tsp`: typo `than can be used` -> `that can be used`.
dschmidt
added a commit
to dschmidt/libre-graph-api
that referenced
this pull request
Jul 2, 2026
- `.woodpecker/docs.yaml`: rsync now excludes `spec/node_modules`, `spec/build`, `build/` so the deploy host doesn't receive the TypeSpec compiler's install tree (which the new `compile-spec` step creates). - `scripts/compile-spec.sh`: reinstall when the lockfile is newer than `node_modules/.package-lock.json`, not only when `node_modules` is missing. Renovate-driven dependency bumps would otherwise be silently ignored by stale local caches. - Three docstring typos (`avalable` -> `available`, `And extension` -> `An extension`, `childrens` -> `children`).
dschmidt
added a commit
to dschmidt/libre-graph-api
that referenced
this pull request
Jul 2, 2026
- `Makefile`: pass `HOME=/tmp` + `npm_config_cache=/tmp/.npm` into the spec compile container so `npm ci` can write its cache/state when the host uid/gid doesn't match `/home/node` in `node:22-alpine`. - `spec/common.models.tsp`: move `SearchParam` here from `groups.tsp`. It's a generic OData `$search` query parameter and was creating an unnecessary `users.tsp -> groups.tsp` dependency. - `spec/permissions.routes.tsp`: fix the role-id path parameter in each `GetPermissionRoleDefinition` `@opExample` so it matches the role id in the corresponding response body. Also align the File Drop `allowedResourceActions` value (`upload/create`) with the list endpoint's example. - `spec/tags.tsp`: make the request body required on `assignTags` and `unassignTags` (the body schemas require `resourceId` and `tags`, so a body-less call can't succeed). Update `TagsOk`'s doc from "No content" to "Success" since the status code is 200 with empty body. - `spec/permissions.models.tsp`: typo `than can be used` -> `that can be used`.
dschmidt
force-pushed
the
refactor/typespec-poc
branch
from
July 2, 2026 08:44
81a09f9 to
d0b1f9b
Compare
api/openapi-spec/v1.0.yaml becomes a build artefact compiled from
spec/*.tsp; every client pipeline runs the compile step before
generating, and a Makefile + scripts cover local generation. Sibling
endpoints share templated operations (DriveItemOps, PermissionsOps,
LibreGraphOps) so identical routes cannot drift apart. Pins TypeSpec
1.13.0.
Semantic changes against the previous YAML:
- add GET /v1.0/drives/{drive-id}/root/children and
GET /v1.0/drives/{drive-id}/items/{item-id}/children
- CreateInvitation's request body is now required
- invitation ops gained the missing 'default: odata.error' response
- example blocks fixed to match their schemas (grantedToV2 as single
object, parentReference.driveId casing, @UI.Hidden,
onPremisesSamAccountName, {value: [...]} collection wrappers)
Generated Go and TypeScript clients are structurally unchanged.
dschmidt
force-pushed
the
refactor/typespec-poc
branch
from
July 2, 2026 08:47
d0b1f9b to
2827f60
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a PoC - please, don't shoot me 😂
Started out of curiosity: how would
libre-graph-api's spec read in TypeSpec, and can theop is …/ template mechanism catch the sibling-endpoint drift that easily happens with all the sibling APIs in MS Graph?api/openapi-spec/v1.0.yamlbecomes a build artefact regenerated fromspec/*.tsp; generated clients stay structurally unchanged.I know this kind of PR is sensitive without prior discussion, but to me it's easier to discuss actual code than assumptions. I don't expect it to be wanted right now - after having spent a bunch of hours on it I at least want to show it here for reference at a later point.
What it buys
Duplication reduction and drift prevention between sibling endpoints.
/v1.0/drives/{drive-id}/items/{item-id}/childrenand/v1.0/me/drive/root/childrenmount the samelistChildrenop with identical body, query and response. In hand-maintained YAML you keep those parallel by hand; here they're instantiations of the same template:It's immediately clear these endpoints do the same thing - no parsing parameters and responses to compare them by hand. Same story for the permission ops mounted at
/root/...and/items/{item-id}/..., which share a singlePermissionsOpsinterface. Implementing (some of) #44 would add exactly this kind of duplication.The single 6k-line YAML file is gone. Total line count stays roughly the same, but each per-domain file is a manageable size and much easier to navigate.
A few spec bugs fell out during the port:
CreateInvitation's request body was wrongly optional, a handful of invitation ops were missingdefault: odata.error, andGET /v1.0/drives/{drive-id}/root/childrenexists in MS Graph but was missing here (and in the graph service). The strict typing also caught a long tail of bugs in the originalexamples:blocks that openapi-generator-cli had silently been swallowing (grantedToV2as array vs single object,parentReference.driveIDvsdriveId,@UI.hiddenvs@UI.Hidden, bare arrays vs{value: [...]}wrappers, …); each example is fixed to match the schema clients actually code against.microsoft/typespec#10656came out of this port as a side effect (merged, released in the pinned 1.13.0).What it does not buy
Not a source-size win: the diff here comes out net-positive (
+7.6k / -6.3k) oncepackage-lock.json, the scripts, the Makefile and the@opExampledecorators are counted in. The gain is in future change cost, not in today's diff.Generated client surface is unchanged on the Go and TypeScript side; PHP and C++/Qt I didn't measure locally. A few
@friendlyNameinconsistencies inherited from the original spec stay in place - renaming would be client-breaking and is out of scope.What it costs
Every client pipeline now runs
tsp compilebefore the generator step, with Docker + node + the TypeSpec compiler as new dependencies. Anyone working on the spec needs to learn the template syntax and the file layout (conventions are documented in section comments).CI / build infrastructure
api/openapi-spec/v1.0.yamlis gitignored and regenerated in CI before every client-generate step. Plainmakecompiles the spec,make helplists the targets for local generation. Theopenapi-generator-cliimage pin stays in the.woodpecker/build-*.yamlfiles; Renovate keeps bumping it there and the Makefile greps the version out, so there's only one source of truth.Honest pitch
It might be worth it if we aim to extend libre-graph-api with new mount points - especially ones with siblings.