Thanks for your interest! AskSQL is a pnpm monorepo of small, focused packages. This guide gets you from clone to green tests.
- Node.js ≥ 20
- pnpm (
npm i -g pnpm) - Optional, only for the live test suites: local PostgreSQL and MySQL, a Groq API key (or a local Ollama), and Google Chrome for the browser E2E tests.
pnpm install
pnpm typecheck # tsc -b across every package in the build graph
pnpm typecheck:all # adds the two extension packages, which are outside it and checked only by their own CI
pnpm build # ESM + .d.ts for every package
pnpm test # the full suite (see gating below)
pnpm format:check # prettier, as CI runs it
pnpm coverage # what CI runs instead of `test`; the coverage floor applies only with --coverage
pnpm test:packaged # installs the packed tarballs outside the workspace and exercises every export
pnpm test:schemas # identifier handling against real engines and hostile schemas; needs Postgres and MySQL
pnpm verify # all of the above in the order CI runs them; use this before pushingtest:packaged is the one gate the in-repo suite cannot stand in for: pnpm's workspace links
resolve a dependency that is used but never declared, and a consumer's install does not.
None of these touch the JetBrains plugin, which builds under Gradle. Its two suites are disjoint:
./gradlew test excludes the IntegrationTest category and -PintegrationTests=true runs only
that category, so a green run of one says nothing about the other. Run both when changing anything
the plugin shares with core, such as the routing predicates.
cd packages/jetbrains
./gradlew test # unit suite
./gradlew test -PintegrationTests=true # integration suite; needs DockerThe integration suite reaches Docker through Testcontainers, which reads the environment of the
Gradle daemon, not of your shell. On Colima, export DOCKER_HOST and then ./gradlew --stop,
or a daemon started earlier keeps failing with "Could not find a valid Docker environment".
| Path | What |
|---|---|
packages/core |
Engine: schema catalog, AST guard, NL->SQL pipeline, provider resolver |
packages/{postgres,mysql,sqlite,duckdb,oracle,mongodb} |
Database connectors (drivers are peer deps) |
packages/server |
Credential-holding sidecar (auth, server-side guard, SSE) |
packages/react / packages/widget |
UI surfaces |
packages/mcp |
Model Context Protocol tool definitions |
packages/vscode |
VS Code extension. Private, versioned and released on its own line - not part of the npm release |
packages/browser-extension |
Edge/Chrome extension (Manifest V3). Private, versioned and released on its own line - not part of the npm release |
packages/jetbrains |
JetBrains plugin. A standalone Gradle project with no package.json, invisible to the pnpm workspace and released on its own line |
examples/ |
Runnable end-to-end demos |
tests/ |
Cross-package integration + live tests |
Unit and guard tests run with no external services. The live suites self-skip
when their dependency is absent, so pnpm test is green on a bare checkout.
To exercise them, provide:
- Live databases -
ASKSQL_PG_URL(defaultpostgres://postgres:root@localhost:5432/asksql_test) andASKSQL_MYSQL_HOST/ASKSQL_MYSQL_PORT/ASKSQL_MYSQL_USER/ASKSQL_MYSQL_PASSWORD/ASKSQL_MYSQL_DB. SQLite and DuckDB are embedded. - A model -
GROQ_API_KEYfor the cloud matrix, orOLLAMA_URLfor a local model. Per-provider model overrides useASKSQL_<PROVIDER>_MODEL. - Browser E2E - a Chrome install; the tests drive it via
puppeteer-core.
pnpm test:schemas runs the engine's identifier handling against real databases using schemas
built to break the rules: mixed case, reserved words, spaces, unicode, names the SQL parser treats
as keywords. It needs no model, because this class of defect does not need one to surface and a
model would only make the result non-deterministic.
It exists because a mixed-case Postgres schema once failed every query and shipped that way for weeks. Every database test we owned used tables we had written ourselves, so they shared our blind spots by construction.
Two rules keep it honest, and both matter more than the pass count:
- On an engine that folds unquoted names, each fixture first asserts the bare form genuinely fails. A fixture that passes before the fix is testing nothing.
- An unreachable engine is reported as skipped, never as a pass. CI passes
--require=postgres,mysql,sqliteso the job cannot go green on the embedded engine alone when the service containers fail to start.
The security boundary is developed test-first: add or extend a case in the
guard-security / guard-fuzz suites before changing the guard.
- TypeScript strict; every new function parameter and return is typed.
- Fail loud - never swallow a decode/parse/decrypt error with a silent fallback.
- No internal references in code - no ticket, spec, or doc IDs in source, comments, or test names. This is a public codebase.
- UI changes work in both light and dark mode and show loading/empty/error states.
- Keep the public API surface intentional - export what hosts need, not internal helpers.
- Branch from
develop. pnpm build,pnpm format:check,pnpm coverageandpnpm test:packagedmust pass - that is what CI runs.- Describe what changed and why; link any related issue.
- Contributions are accepted under the project's Apache-2.0 license.
Versions are managed by changesets; publishing runs in CI so packages carry npm provenance.
pnpm changeset # pick the packages and the bump, describe the changeThe changeset file is committed with the PR. Do not hand-edit version in a
package.json - that skips the changelog and leaves the changeset state lying.
pnpm changeset:version # bumps versions + writes each package's CHANGELOG
git commit -am "Release: <summary>"
git push
git tag -a v0.1.2 -m "Release: <summary>" && git push origin v0.1.2Annotate the tag. Its message becomes the GitHub Release body; a lightweight tag leaves that body empty.
changeset:version runs tools/release-preflight.mjs first, which refuses to continue if any
package would be bumped to a major that no changeset asked for. That is not hypothetical:
changesets majors any package whose peer dependency receives a non-patch bump, so a changeset
saying '@asksql/server': minor once produced 1.0.0 because @asksql/sqlite went up a minor.
npm versions cannot be withdrawn, so this is checked before the numbers are written rather than
after. If a major genuinely is intended, declare it in a changeset and the preflight passes.
Two rules keep that trap shut, both enforced by tests/peer-ranges.test.ts:
- Never use
workspace:*(orworkspace:~) inpeerDependencies. pnpm replaces it with the exact current version on publish, so@asksql/serverwould demand one precise connector build and every connector release would put consumers into a peer conflict. Useworkspace:>=0.1.0: what a connector must satisfy is theConnectorinterface from@asksql/core, which is a normal dependency, so any published connector version is genuinely compatible. - Keep
onlyUpdatePeerDependentsWhenOutOfRangeset in.changeset/config.json. Without it changesets ignores the range entirely and majors on any non-patch peer bump.
The tag triggers .github/workflows/release.yml. It first refuses to continue unless the tag is
an ancestor of main and its version matches packages/core/package.json - npm cannot unpublish,
so a mistagged cut is not recoverable. Then it installs, builds, runs pnpm coverage, and waits
for an approval before publishing all eleven packages with provenance and creating the GitHub
Release. The VS Code extension and the browser extension are private: true, so changesets skips
both, and packages/jetbrains has no package.json at all. Each of the three is versioned and
released separately, on its own line.
Afterwards, anyone can verify what they installed:
npm audit signaturesSettings > Environments > New environment > npm-publish
| Setting | Value | Why |
|---|---|---|
Environment secret NPM_TOKEN |
npm Automation token | Scoped to this job, not readable by other workflows. |
| Required reviewers | a maintainer | Approval gate before publish. |
| Deployment branches and tags | Selected -> tag rule v* |
Only release tags reach the token. |
| Allow administrators to bypass | unchecked | Otherwise the approval gate does not apply to admins. |
| Prevent self-review | unchecked | A single maintainer must be able to approve their own release. |
Tag patterns are case-sensitive: v* matches v0.1.2, V* matches nothing. A
workflow_dispatch run must select a tag in the ref dropdown; a branch cannot
reach this environment.
workspace:* publishes as an exact pin ("@asksql/core": "0.1.1"). A core-only
fix would then reach nobody: installing @asksql/postgres@0.1.1 pulls exactly
@asksql/core@0.1.1, bug and all. workspace:^ publishes as ^0.1.2, so a patch to
core flows to every dependant without republishing all eleven.