Publishing Packages#
Jac projects publish to PyPI as standard Python wheels -- no pyproject.toml, no setuptools, no build backend. The jac build --as wheel command reads your jac.toml and produces a PEP 427-compliant .whl that pip install consumes directly. Anyone can then pip install your package, whether or not they use Jac.
This page covers the end-to-end flow: declaring metadata, building a wheel, testing it, and uploading it.
Overview#
The publishing pipeline has three steps:
- Declare package metadata in the
[project]section ofjac.toml. - Build a wheel with
jac build --as wheel-- it lands indist/. - Upload the wheel to PyPI with
twine(upload is intentionally out of scope forjac).
Jac builds the wheel itself: it generates the METADATA, WHEEL, RECORD, top_level.txt, and entry_points.txt files and packs them into a reproducible ZIP archive. The result is indistinguishable from a wheel produced by hatch or setuptools.
1. Declare package metadata#
All publishing metadata lives in the [project] section of jac.toml. Only name and version are strictly required; everything else improves the PyPI listing.
[project]
name = "mylib"
version = "1.0.0"
description = "A handy Jac library"
license = "MIT"
readme = "README.md"
requires-python = ">=3.12"
keywords = ["jac", "jaseci", "ai"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Framework :: Jac",
]
authors = [{ name = "Your Name", email = "you@example.com" }]
maintainers = [{ name = "Your Name", email = "you@example.com" }]
[project.urls]
homepage = "https://example.com"
repository = "https://github.com/you/mylib"
issues = "https://github.com/you/mylib/issues"
[dependencies]
jaclang = ">=0.15.1"
requests = ">=2.28.0"
Classifiers appear as Classifier: headers in the wheel's METADATA and control how your package is displayed and filtered on PyPI (license badge, Python version tags, topic categories). Browse the full list at pypi.org/classifiers.
classifiers must be a TOML array
Writing classifiers as a plain string instead of an array is a TOML type
error and will produce malformed wheel metadata. Always use [...] syntax
as shown above.
Runtime dependencies declared under [dependencies] are written into the wheel's METADATA as Requires-Dist entries, so pip install mylib pulls them in automatically. [dev-dependencies] are not installed by default -- they ship as a dev extra (pip install mylib[dev]). [optional-dependencies.<group>] become wheel extras (pip install mylib[<group>]).
See the Configuration Reference for the full field list.
Migrated from [package]?
Releases before jaclang 0.15 used a separate [package] section for publishing metadata. It has been merged into [project]. If you have an old jac.toml, rename [package] → [project] and [package.include] → [project.include]; plain [package] tables are no longer read.
Controlling what ships#
By default jac build --as wheel collects a single directory named after the project (mylib/, with hyphens converted to underscores). Override this with [project.include]:
[project.include]
packages = ["mylib", "mylib_extras"]
[project.include.data]
# Extra non-source files to bundle, per package
mylib = ["templates/**/*", "data/*.json"]
.jac, .py, .pyi, .lark, py.typed, and .jir files are included by default. Build artifacts (.jac/, __pycache__/, dist/, virtualenvs, .git/, *.egg-info/) are always excluded. See [project.include] for the full pattern reference.
Single-file modules
[project.include] packages matches directories. A package that is a single top-level .py/.jac file is not currently collected -- put your code in a directory (a __init__.jac is enough) before bundling.
Console scripts#
Declare CLI commands with [entrypoints]:
[entrypoints.scripts] is written as [console_scripts] in the wheel. Other [entrypoints.<group>] tables are written through to the wheel metadata verbatim for consumers that use importlib.metadata.entry_points(); Jac itself no longer loads any entry-point group at startup.
Consumers who install your package into a Jac project (jac install mylib) can run its console-script with jac x mylib under the jac runtime, without it being on their shell PATH.
2. Build the wheel#
This writes dist/<name>-<version>-py3-none-any.whl. Build to a different directory with -o:
jac build --as wheel ships .jir bytecode files only if they already exist in your source tree -- it does not regenerate them. Shipped bytecode is keyed by Python version and validated against a source hash; on a consumer running a different Python version (or if the bytecode is missing or stale), the runtime transparently recompiles the bundled .jac source on first import -- a mismatch never breaks the package.
Wheels are reproducible: every ZIP entry uses a fixed timestamp, so the same source produces a byte-identical wheel.
3. Test before uploading#
Always install the wheel into a clean environment before publishing:
python -m venv test_env
source test_env/bin/activate
pip install dist/mylib-1.0.0-py3-none-any.whl
python -c "import mylib" # or exercise your console script
deactivate
4. Upload to PyPI#
jac does not upload -- use twine:
pip install twine
# Upload to TestPyPI first to verify the listing renders correctly
twine upload --repository testpypi dist/*
# Then publish to the real index
twine upload dist/*
In CI, authenticate with an API token: twine upload dist/* -u __token__ -p "$PYPI_TOKEN".
Publishing to npm (npmjs.org)#
Client-side Jac libraries can also be published to npm so JavaScript and TypeScript projects can npm install them -- whether or not they use Jac. jac build --as npm reads the same jac.toml and produces an npm-compatible .tgz: it compiles your client modules to JavaScript (ES modules), generates package.json, and emits .d.ts TypeScript declarations.
jac build --as npm # -> dist/<name>-<version>.tgz
jac build --as wheel # build the wheel; run both commands for wheel + npm tarball
What goes in the package:
- Compiled JavaScript. Every
.cl.jac(and plain.jac) client module under[project.include]compiles to a sibling.js, preserving the import structure.def:pub/glob:pubsymbols become ESM exports. -
package.json. Built from[project](name,version,description,license,keywords,repository) plus[dependencies.npm]. Override npm-specific fields under[npm]: -
TypeScript declarations. A
.d.tsis generated for each module andpackage.jsontypes/exportspoint at the entry's declarations, so TypeScript consumers get full type-checking. Function signatures,obj/nodeclasses, and globals are all typed; JSDoc is also embedded in the.js.
Pure client code only
npm packages must be standalone client code. A module that crosses a server boundary (a sv import/call) can't run as a plain npm install, so jac build --as npm rejects it with a clear error. Keep server-coupled code in your app, not in the published library.
The runtime dependency#
Libraries that use JSX or the reactive API (createSignal, createEffect, …) reference the Jac client runtime. The build wires an import { … } from "@jaseci/runtime" into those modules and adds @jaseci/runtime to dependencies automatically. The runtime is a normal, React-independent npm package; modules that explicitly import from react instead get react/react-dom added to peerDependencies.
The @jaseci/runtime package itself is produced by an internal maintenance build (run when cutting a jaclang release), not by a user-facing jac build projection -- it emits @jaseci/runtime at the current jaclang version.
Upload to npm#
jac does not upload -- use the npm CLI:
npm pack dist/<name>-<version>.tgz # optional: inspect the contents
npm publish dist/<name>-<version>.tgz --access public
In CI, authenticate with an automation token via NODE_AUTH_TOKEN.
Editable installs#
While developing a library locally, install it in editable mode so changes are picked up without rebuilding:
This installs the project's runtime dependencies and writes a complete .dist-info/ directory into site-packages, so pip show mylib and pip list report it correctly -- all without a pyproject.toml. You can also editable-install a cloned dependency from anywhere:
See Also#
jac build-- command reference (--as wheel|npm)jac install-- installing dependencies and editable installs- Configuration Reference -- every
jac.tomlfield - Plugins (Removed) -- why Jac no longer has a plugin system