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
57 changes: 57 additions & 0 deletions .github/workflows/publish-jsr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-License-Identifier: PMPL-1.0-or-later
# INT-04 (#181): publish the JS runtime packages to JSR (Deno/JSR-first
# per CLAUDE.md). Scaffolded by packaging prep — it is **manual-only**
# (`workflow_dispatch`) and does NOT run on push/merge. The owner
# dispatches it deliberately, package-by-package, after reviewing the
# `deno publish --dry-run` output. No secrets are required or committed:
# JSR uses GitHub OIDC (the `id-token: write` permission below); npm is
# a separate, later tail (NOT wired here — see docs/PACKAGING.adoc).
#
# Publish order is load-bearing: @hyperpolymath/affine-js MUST be
# published before @hyperpolymath/affinescript-tea (the latter depends
# on the former via an import map).

name: Publish to JSR (manual)

on:
workflow_dispatch:
inputs:
package:
description: "Which package to publish"
required: true
type: choice
options:
- affine-js
- affinescript-tea
dry_run:
description: "Dry run only (no publish)"
required: true
type: boolean
default: true

permissions:
contents: read
id-token: write # JSR OIDC; no token secret needed

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Resolve package directory
id: pkg
run: |
case "${{ inputs.package }}" in
affine-js) echo "dir=packages/affine-js" >> "$GITHUB_OUTPUT" ;;
affinescript-tea) echo "dir=affinescript-tea" >> "$GITHUB_OUTPUT" ;;
esac

Check failure

Code scanning / Semgrep OSS

Semgrep Finding: yaml.github-actions.security.run-shell-injection.run-shell-injection Error

Using variable interpolation ${...} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".
Comment on lines +46 to +50
- name: Dry run
working-directory: ${{ steps.pkg.outputs.dir }}
run: deno publish --dry-run
- name: Publish
if: ${{ inputs.dry_run == false }}
working-directory: ${{ steps.pkg.outputs.dir }}
run: deno publish
6 changes: 6 additions & 0 deletions affinescript-tea/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
"exports": {
".": "./mod.js"
},
"imports": {
"@hyperpolymath/affine-js/loader": "../packages/affine-js/loader.js"
},
"license": "PMPL-1.0-or-later",
"publish": {
"exclude": ["mod_test.js"]
},
"tasks": {
"test": "deno test --allow-read --allow-write --allow-run mod_test.js"
}
Expand Down
8 changes: 4 additions & 4 deletions affinescript-tea/mod.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import {
parseOwnershipSection,
readBytes,
} from "../packages/affine-js/loader.js";
} from "@hyperpolymath/affine-js/loader";

/**
* One model field, decoded from the `affinescript.tea_layout` section.
Expand Down Expand Up @@ -94,14 +94,14 @@ export class TeaApp {
/** @type {WebAssembly.Instance} */ #instance;
/** @type {WebAssembly.Memory} */ #memory;
/** @type {TeaLayout} */ #layout;
/** @type {import("../packages/affine-js/loader.js").OwnershipEntry[]} */ #ownership;
/** @type {import("@hyperpolymath/affine-js/loader").OwnershipEntry[]} */ #ownership;
/** Re-entrancy guard enforcing the Linear-msg invariant. */
#inCycle = false;

/**
* @param {WebAssembly.Instance} instance
* @param {TeaLayout} layout
* @param {import("../packages/affine-js/loader.js").OwnershipEntry[]} ownership
* @param {import("@hyperpolymath/affine-js/loader").OwnershipEntry[]} ownership
*/
constructor(instance, layout, ownership) {
this.#instance = instance;
Expand Down Expand Up @@ -152,7 +152,7 @@ export class TeaApp {
* The ownership annotations. `affinescript_update`'s `msg` parameter is
* Linear (kind `"linear"`) — the host-visible proof that a message is
* consumed exactly once per update cycle.
* @type {import("../packages/affine-js/loader.js").OwnershipEntry[]}
* @type {import("@hyperpolymath/affine-js/loader").OwnershipEntry[]}
*/
get ownership() {
return this.#ownership;
Expand Down
7 changes: 5 additions & 2 deletions docs/ECOSYSTEM.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,11 @@ S1..S6; legacy preview1 stdout path is the default until S6
(reversible-in-progress). S3+ HARD-GATED on S2 toolchain provisioning
(`wasm-tools`/`wasm-component-ld`, absent). WIT world of record:
`wit/affinescript.wit`
|INT-04 |Publish compiler + runtime to JSR (then npm) |#181 |open, S2
(blocked by INT-01)
|INT-04 |Publish compiler + runtime to JSR (then npm) |#181 |runtime
packaging READY (affine-js + affinescript-tea JSR dry-run green;
manual-only `publish-jsr.yml`; docs/PACKAGING.adoc). INT-01 dep
cleared. NOT published (owner-gated: needs explicit go + JSR/npm
auth). Compiler-binary distribution = design fork #260
|INT-05 |Loader-driven multi-module app bundling |ledger-only |planned
(blocked by INT-02)
|INT-06 |Server-side runtime profile (on INT-03 WASI p2) |ledger-only
Expand Down
66 changes: 66 additions & 0 deletions docs/PACKAGING.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: PMPL-1.0-or-later
// SPDX-FileCopyrightText: 2026 hyperpolymath
= AffineScript Packaging & Distribution (INT-04)
:toc: macro
:icons: font

INT-04 (#181). This documents what is publish-ready, the order, and the
one open design question. *Nothing here publishes anything*: the
workflow is manual-only and the owner dispatches it after review.

toc::[]

== JSR-publishable units (Deno/JSR-first)

The JS runtime packages are JSR-publishable today. `deno publish
--dry-run` succeeds for both (verified during INT-04 prep):

[cols="2,2,3"]
|===
|Package |Spec |Notes

|`packages/affine-js` |`@hyperpolymath/affine-js` |Loader + marshal +
runtime + ownership accessor. Tests excluded via `publish.exclude`.
|`affinescript-tea` |`@hyperpolymath/affinescript-tea` |TEA runtime.
Depends on `@hyperpolymath/affine-js/loader` (import map) — see order.
|===

*Publish order is load-bearing*: `affine-js` **must** publish before
`affinescript-tea` (the latter resolves `@hyperpolymath/affine-js/loader`).

`affinescript-dom` is AffineScript source (`.affine`), not a JS package
— it is consumed by the compiler, not JSR; out of scope for JSR.

=== Slow types

The JS entrypoints emit JSR's `unsupported-javascript-entrypoint`
(slow-types) warning — expected for a JS-with-JSDoc package; it
publishes, only `@hyperpolymath/affine-js`'s fast-check is skipped.
`types.d.ts` already provides the consumer-facing contract. Converting
entrypoints is out of scope for INT-04 (tracked separately if desired).

== How to publish (owner, manual)

. Review `deno publish --dry-run` (CI: `Publish to JSR (manual)` with
`dry_run: true`, or locally `cd <pkg> && deno publish --dry-run`).
. Dispatch `.github/workflows/publish-jsr.yml` for `affine-js` with
`dry_run: false` (JSR OIDC via `id-token: write` — no token secret).
. Then dispatch it for `affinescript-tea`.

== npm tail

"then npm" (#181) is a *separate, later* step and is **not** wired by
this prep. If/when needed it follows the existing owner-sanctioned npm
pattern (`.github/workflows/affine-vscode-publish.yml`, `NPM_TOKEN`
scoped automation token) — a deliberate exception to Deno-first.

== Open design question — the compiler itself

#181 says "publish *compiler* + runtime". The runtime is the JS
packages above. The **compiler is a native OCaml binary** — it is *not*
a JSR or npm package. "Publishing the compiler" therefore needs a
release-binary strategy decision (GitHub Releases artifacts, Guix/Nix
channel, or a thin JSR/npm shim that fetches a pinned binary). This is
a one-way-door design choice and is filed as a separate issue rather
than guessed here. `affinescript-lsp` distribution (INT-10) depends on
that decision.
3 changes: 2 additions & 1 deletion docs/TECH-DEBT.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ follow-up
|INT-03 |WASI preview2 / host I/O |S1 |#180 ADR-015 accepted (full
Component-Model re-target, staged S1..S6); S3+ hard-gated on S2
toolchain (`wasm-tools`/`wasm-component-ld`)
|INT-04 |Publish to JSR/npm |S2 |open #181 (◄ INT-01)
|INT-04 |Publish to JSR/npm |S2 |#181 packaging READY (dry-run green,
manual workflow); owner-gated publish; compiler-binary = #260
|INT-07 |`affinescript-tea` runtime |S2 |#182 runtime + run loop shipped
(TeaApp/parseTeaLayout, Linear-msg enforced); INT-01 cleared (#253)
|INT-08 |DOM reconciler |S2 |#183 implemented + compiles; `.as`→`.affine`
Expand Down
5 changes: 4 additions & 1 deletion packages/affine-js/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"./marshal": "./marshal.js",
"./runtime": "./runtime.js"
},
"license": "PMPL-1.0-or-later"
"license": "PMPL-1.0-or-later",
"publish": {
"exclude": ["loader_test.js"]
}
}
Loading