Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
run: python -m pip install --upgrade build twine
- name: Build sdist and wheel from sdist
run: python -m build
- name: Verify distribution file allowlists
run: python scripts/check_distribution_files.py
- name: Check distribution metadata
run: python -m twine check --strict dist/*
- name: Install wheel with published dependencies
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ jobs:
run: python -m pip install --upgrade build twine
- name: Build sdist and wheel from the sdist
run: python -m build
- name: Verify distribution file allowlists
run: python scripts/check_distribution_files.py
- name: Check distribution metadata
run: python -m twine check --strict dist/*
- name: Install the wheel
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.2.0

- Add the binding-aware source compiler, deterministic ES module emitter and original-source diagnostics/maps.
- Compile explicitly supplied local modules and re-exports with selected helper and notice closure.
- Add lazy coroutine/Promise awaits and named browser worker/host capabilities with stop/restart.
- Verify the approved corpus across CPython, Node and Chromium and record scoped payload/startup/memory measurements.
- Preserve the JavaScript runtime namespace and existing Fetch/JSON dependency ranges.

## 0.1.0

- Introduce the case-sensitive `JavaScript` namespace with Object, sparse Array,
Expand Down
10 changes: 10 additions & 0 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,13 @@ helpers, maps, notices, metadata/harness and compressed payloads, with raw warm
build, fresh/warm context startup and separate dedicated-worker V8 heap samples.
These synchronous compiler fixtures do not include the browser-host/PWA shell;
complete application and future interpreter measurements remain separate scopes.

## 0.2.0 publication plan

[REL-03](https://github.com/outside-labs/Python-JavaScript/issues/38) publishes
the merged compiler, module/async/browser profiles, conformance and measurements.
See the [release contract](docs/release-0.2.0.md). Runtime companion dependency
ranges remain unchanged; compiler and browser assets are in the distributions.
Strict artifact allowlists, installed-wheel/sdist and browser checks, passing
final main CI, PyPI/workflow hash comparison and a fresh PyPI install are gates.
The independent browser source interpreter remains a separate pocket-python task.
40 changes: 23 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ case-sensitive `JavaScript` namespace. An alpha compatibility layer with a
documented subset of ECMAScript behavior.

```sh
python -m pip install Python-JavaScript==0.1.0
python -m pip install Python-JavaScript==0.2.0
```

```python
Expand Down Expand Up @@ -78,19 +78,26 @@ indices follow JavaScript rules: `values[-1]` is a named property, and
`values.at(-1)` accesses the last element. `Array(3)` creates three holes;
`Array.of(3)` creates one element containing 3.

The next program adds our Python-to-JavaScript compiler, a browser target and
CPython/client parity, coordinated with Shimify source reduction and pocket-python
assembly. See [the implementation plan](IMPLEMENTATION_PLAN.md). A separately
staged independent interpreter is tracked by pocket-python. v0.1.0
provides the runtime foundation; it does not translate source or execute
JavaScript. Prototypes, descriptors, Date, RegExp, Map/Set, typed arrays, browser
globals and other unlisted APIs remain future work. Development is tracked in
the [issues](https://github.com/outside-labs/Python-JavaScript/issues) and
[Project](https://github.com/orgs/outside-labs/projects/11).
Version 0.2.0 also includes a bounded Python-to-JavaScript compiler: a
[binding-aware front end](docs/compiler-front-end.md), [ES module emitter](docs/compiler-emitter.md),
[local-module compiler](docs/compiler-modules.md), [async profile](docs/compiler-async.md)
and [browser worker/host target](docs/compiler-browser.md). Python parses source
at build time; generated modules and selected helpers execute on the client.
The separate pocket-python interpreter will parse newly entered Python in the browser.

```python
from JavaScript.emitter import compile_source

The development checkout now includes a [binding-aware compiler front end](docs/compiler-front-end.md)
that validates source and produces typed IR. The emitter and [local-module compiler](docs/compiler-modules.md)
produce executable ES modules; browser adapters remain a subsequent milestone.
artifact = compile_source("def count(text):\n return len(text)", filename="counter.py")
# artifact.files contains emitted JavaScript, source map and provenance notice.
```

This alpha supports the explicit [compiler language](docs/compiler-language.md),
not arbitrary Python dependencies or complete ECMAScript. Prototypes, descriptors,
Date, RegExp, Map/Set, typed arrays and unlisted APIs remain future work. See
[0.2.0 release notes](docs/release-0.2.0.md), the [implementation plan](IMPLEMENTATION_PLAN.md),
[issues](https://github.com/outside-labs/Python-JavaScript/issues) and
[Project](https://github.com/orgs/outside-labs/projects/11).

## Development

Expand All @@ -105,9 +112,8 @@ when Node is absent locally; CI and release builds require it. Runtime code
needs only Python and the two declared companions. Tests make no external
network requests.

The development [compiler emitter](docs/compiler-emitter.md) produces deterministic
ES modules for the approved single-module Python profile. This API is not part
of the published 0.1.0 distribution.
The [compiler emitter](docs/compiler-emitter.md) produces deterministic
ES modules for the approved single-module Python profile. The compiler APIs are included in the 0.2.0 distribution.

The explicit [async compiler profile](docs/compiler-async.md) adds lazy coroutines,
awaited Promise values, callbacks and context cancellation. The [browser target](docs/compiler-browser.md) adds named DOM controls, callbacks,
Expand All @@ -118,4 +124,4 @@ with `uv run python examples/browser_text_metrics.py --output build/browser-text
It uses our emitted modules and helpers in a worker; source parsing remains the
separate independent-interpreter milestone.

The development compiler has a versioned [CPython/Node/browser parity suite](docs/compiler-conformance.md) with typed results, source-map error checks and explicit unsupported boundaries.
The compiler has a versioned [CPython/Node/browser parity suite](docs/compiler-conformance.md) with typed results, source-map error checks and explicit unsupported boundaries.
3 changes: 1 addition & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,4 @@ token belongs in the repository or workflow.
run the examples/tests, and compare published SHA256 hashes with build artifacts.

Publishing a GitHub release triggers upload; a tag push alone does not. PyPI
versions are immutable. Repository visibility stays private; published artifacts
include package source. No open-source license has been selected for this release.
versions are immutable. The repository is public; published artifacts include the selected package source. No open-source license has been selected; existing provenance notices grant no additional rights.
2 changes: 1 addition & 1 deletion docs/compiler-async.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Compiled async profile

The development APIs `parse_source`, `compile_source` and `compile_modules`
The 0.2.0 APIs `parse_source`, `compile_source` and `compile_modules`
accept `async_enabled=True`. Without it, async syntax remains rejected. Published
0.1.0 artifacts predate these compiler APIs. Single-module artifacts identify
`python-browser-async/1` and ABI `python-browser-async-esm/1`; local-module artifacts
Expand Down
6 changes: 3 additions & 3 deletions docs/compiler-browser.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Compiled browser target

The development checkout provides `JavaScript.browser.compile_browser`. It
Version 0.2.0 provides `JavaScript.browser.compile_browser`. It
compiles verified [local source modules](compiler-modules.md), enables the
[async profile](compiler-async.md), and records the complete local worker/host
asset closure. It is not included in the published 0.1.0 distribution.
asset closure. It is included in 0.2.0; the published 0.1.0 distribution predates this API.

The target is `python-browser-modules-host-async/1`, compiler ABI
`python-browser-host-esm/1`, using host protocol `pocket-python-host` version 1.
Expand Down Expand Up @@ -31,7 +31,7 @@ The builder reads the approved text_metrics corpus, emits selected local modules
source maps/notices, the host/worker/protocol modules, a manifest and a small HTML
entry. It requires a new output directory and preserves existing outputs.
The same computation can be imported under CPython in a separate server entry;
client entrypoints and host adapters are explicit. E2E-01 owns the complete
client entrypoints and host adapters are explicit. The pocket-python shared-application example provides the complete
server/client packaging demonstration.

## Python namespace and entry
Expand Down
2 changes: 1 addition & 1 deletion docs/compiler-conformance.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Compiler compatibility matrix: compiler-parity/1

The development compiler runs the same approved source corpus under CPython
The 0.2.0 compiler runs the same approved source corpus under CPython
3.14.7 and as identical emitted ES modules in Node 24.19.0 and a Chromium module
worker. This is a bounded ahead-of-time Python profile. The independent source
interpreter is a separate pocket-python implementation. Published 0.1.0 predates
Expand Down
6 changes: 3 additions & 3 deletions docs/compiler-emitter.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Experimental ES module emitter

The development checkout exposes `JavaScript.emitter.compile_source`. It
Version 0.2.0 exposes `JavaScript.emitter.compile_source`. It
validates source through the python-browser/1 front end, lowers the immutable IR
and returns a CompiledArtifact without executing input or writing files. The
published 0.1.0 package predates this compiler implementation.
Expand All @@ -16,7 +16,7 @@ print(artifact.manifest["application_exports"]) # ['count']
```

`files` is a tuple of immutable OutputFile(path, data, role) values. The caller
can inspect or pass those bytes to the future pocket-python application builder.
can inspect or pass those bytes to the pocket-python 0.2 application builder.
The manifest bytes are separate from their own file/hash closure and can be
saved as PYTHON-JAVASCRIPT.json. Every recorded file has exact size, SHA-256 and
role; imports, notices and selected helper dependencies are explicit. Current
Expand Down Expand Up @@ -83,7 +83,7 @@ runtime is embedded. The repository has no declared code license; this notice
does not choose or grant one. Redistribution licensing remains a release decision.

Run `python -m unittest discover -s tests -p 'test_compiler*.py' -v` with the
installed development checkout and Node 24.19.0. Tests compare CPython and emitted
installed package and Node 24.19.0. Tests compare CPython and emitted
Node output/errors/evaluation order, the single-module text_metrics application,
precision/Unicode/tuple/default behavior and an independent Node source-map
consumer. Local modules and async browser capabilities have separate adopted
Expand Down
4 changes: 2 additions & 2 deletions docs/compiler-front-end.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Compiler front end

The development checkout provides JavaScript.compiler.parse_source(source,
Version 0.2.0 provides JavaScript.compiler.parse_source(source,
filename=...) for the adopted python-browser/1 profile. It parses, binds and
validates source without running target code, then returns an immutable ModuleIR.
It does not emit or execute JavaScript yet; EMIT-01 owns that next step.
Use `JavaScript.emitter.compile_source` to emit executable JavaScript from this IR.

The API uses only the declared Python runtime and standard library. A ModuleIR
records profile/schema identity, SHA-256 of UTF-8 input, stable lexical binding
Expand Down
2 changes: 1 addition & 1 deletion docs/compiler-language.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Compiler source language contract

PARSE-01 adopts `python-browser/1` as the initial language profile, using Python
3.14 grammar and semantics for ordinary Python constructs. The development checkout implements its front end and
3.14 grammar and semantics for ordinary Python constructs. Version 0.2.0 implements its front end and
[ES module emitter](compiler-emitter.md). The released 0.1.0 package predates
these APIs and provides Python-side JavaScript compatibility objects.

Expand Down
2 changes: 1 addition & 1 deletion docs/compiler-measurements.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Reproducing compiler measurements

PERF-01 measures the existing helper selection in the development compiler. It
PERF-01 measures the existing helper selection in the 0.2.0 compiler. It
compares required helpers with every helper available to the same language
profile; it does not add an optimization or enable otherwise unsupported syntax.
The complete positive compiler corpus must match CPython in both variants, in
Expand Down
2 changes: 1 addition & 1 deletion docs/compiler-modules.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Compiling local source modules

The development checkout provides `JavaScript.modules.compile_modules`. The
Version 0.2.0 provides `JavaScript.modules.compile_modules`. The
published 0.1.0 package predates this API. `python-browser-modules/1` extends the
[single-module language](compiler-language.md) with explicit named source imports.

Expand Down
48 changes: 48 additions & 0 deletions docs/release-0.2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Python-JavaScript 0.2.0

An experimental minor release for Python 3.14+. The case-sensitive `JavaScript`
compatibility namespace and the existing Fetch/JSON companion dependency ranges
are retained. No new runtime dependency is introduced.

## Compiler and browser target

The distribution now includes `JavaScript.compiler.parse_source`,
`JavaScript.emitter.compile_source`, `JavaScript.modules.compile_modules` and
`JavaScript.browser.compile_browser`, plus the matching `.mjs` worker/host assets.
They parse/bind a documented Python subset at build time and produce deterministic
ES modules, selected compatibility helpers, original-source maps, hashes and
provenance notices. They never import or execute the target source to compile it.

Adopted profiles cover Python large integers, code-point strings and tuples,
short-circuit/evaluation order, plain functions with immutable defaults, verified
named local modules/re-exports, and explicit async/browser host capabilities.
Named controls, text output, callbacks and exact URL-granted Fetch execute through
the versioned browser worker protocol. Stop/restart releases the worker/session.

The [compiler language](compiler-language.md), [modules](compiler-modules.md),
[async](compiler-async.md) and [browser target](compiler-browser.md) documents
specify the exact boundaries. CPython/Node/Chromium differential reports and
component measurements cover the approved corpus; measurements are fixture and
engine specific and do not establish a general size/speed advantage.

## Limits

This is ahead-of-time compilation. It does not parse newly entered Python in a
browser or delegate to a Python interpreter. Arbitrary packages/stdlib, classes,
descriptors, dynamic imports, unrestricted host attributes and unlisted Python or
ECMAScript APIs remain unsupported. Unsupported syntax is rejected with original
source diagnostics. Compiled applications require the documented external argument
representations and are not instruction/memory sandboxed.

The repository has no declared code license; this release retains that metadata.
Original helper provenance and the borrowed fixture's MIT notice are preserved;
provenance does not grant additional redistribution rights.

## Publication gates

Require installed-wheel and installed-sdist tests on Linux/macOS/Windows, full
approved browser conformance, strict distribution allowlists/metadata, and final
main CI. Publish immutable `v0.2.0` through the existing `pypi` Trusted Publisher,
compare PyPI hashes with workflow artifacts, then test a fresh installation and
compile/run a representative module before closing
[REL-03](https://github.com/outside-labs/Python-JavaScript/issues/38).
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "Python-JavaScript"
version = "0.1.0"
description = "JavaScript-style objects, methods and functions for Python"
version = "0.2.0"
description = "JavaScript-style Python values and a bounded Python-to-JavaScript browser compiler"
readme = "README.md"
authors = [
{ name = "ellainng", email = "299958882+ellainng@users.noreply.github.com" }
Expand Down Expand Up @@ -39,6 +39,8 @@ source-include = [
"tests/fixtures/licenses/SHIMIFY-LICENSE.txt",
"docs/compatibility.md",
"docs/compiler*.md",
"docs/release-*.md",
"scripts/check_distribution_files.py",
"scripts/install_distribution.py",
"scripts/compiler_browser_smoke.py",
"scripts/compiler_conformance.py",
Expand Down
53 changes: 53 additions & 0 deletions scripts/check_distribution_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Check exact publication contents and declared companion dependency metadata."""

from email.parser import BytesParser
from pathlib import Path
import sys
import tarfile
import tomllib
import zipfile


def main():
root = Path(__file__).resolve().parents[1]
project_data = tomllib.loads((root / 'pyproject.toml').read_text(encoding='utf-8'))
project = project_data['project']
version = project['version']
directory = Path(sys.argv[1]) if sys.argv[1:] else root / 'dist'
prefix = f'python_javascript-{version}'
modules = {p.relative_to(root / 'src').as_posix(): p for p in (root / 'src/JavaScript').rglob('*')
if p.is_file() and (p.suffix in {'.py', '.mjs'} or p.name == 'py.typed')}
wheels = list(directory.glob('*.whl'))
sources = list(directory.glob('*.tar.gz'))
assert len(wheels) == len(sources) == 1, 'Expected exactly one wheel and sdist'
assert wheels[0].name == prefix + '-py3-none-any.whl'
assert sources[0].name == prefix + '.tar.gz'
info = prefix + '.dist-info/'
with zipfile.ZipFile(wheels[0]) as archive:
names = [item.filename for item in archive.infolist() if not item.is_dir()]
assert len(names) == len(set(names)), 'Duplicate wheel files'
assert set(names) == set(modules) | {info + name for name in ['METADATA', 'WHEEL', 'RECORD']}, 'Unexpected wheel files'
for name, path in modules.items():
assert archive.read(name) == path.read_bytes(), name
metadata = BytesParser().parsebytes(archive.read(info + 'METADATA'))
assert metadata['Name'] == project['name'] and metadata['Version'] == version
assert metadata['Requires-Python'] == project['requires-python']
expected_dependencies = {'javascript-fetch-api>=0.2.0,<0.3.0', 'javascript-json>=0.1.0,<0.2.0'}
assert set(metadata.get_all('Requires-Dist', [])) == expected_dependencies
allowed = {'PKG-INFO', 'pyproject.toml', 'pyproject.toml.orig', 'README.md'}
allowed |= {'src/' + name for name in modules}
for pattern in project_data['tool']['uv']['build-backend']['source-include']:
allowed |= {p.relative_to(root).as_posix() for p in root.glob(pattern) if p.is_file()}
with tarfile.open(sources[0]) as archive:
files = [item for item in archive.getmembers() if item.isfile()]
names = [item.name for item in files]
assert len(names) == len(set(names)), 'Duplicate sdist files'
assert all(item.isdir() or item.isfile() for item in archive.getmembers()), 'Nonregular sdist entry'
assert set(names) == {prefix + '/' + name for name in allowed}, 'Unexpected sdist files'
for name in allowed - {'PKG-INFO', 'pyproject.toml', 'pyproject.toml.orig'}:
assert archive.extractfile(prefix + '/' + name).read() == (root / name).read_bytes(), name
print(f'Verified {len(modules)} package files and {len(allowed)} source-distribution files for {version}')


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading