Skip to content
Open
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
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: ci
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
test:
# ci-workflows python-ci @ main
uses: img2threejs/ci-workflows/.github/workflows/python-ci.yml@4b7a0612d2c52815792124a18ae3f9032a4f0a88
with:
python-version: '3.12'
test-command: npm test
21 changes: 21 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: publish
on:
push:
tags: ['v*']
permissions:
contents: read
jobs:
publish:
# ci-workflows npm-publish (feat/reusable-npm-publish); re-pin to the merged main SHA once img2threejs/ci-workflows#2 lands
uses: img2threejs/ci-workflows/.github/workflows/npm-publish.yml@27da7a68505591492cb4ef7358968890c6e39844
permissions:
contents: read
id-token: write
with:
tag: ${{ github.ref_name }}
version-file: package.json
node-version: '24'
python-version: '3.12'
test-command: npm test
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
change that breaks a conforming plugin bumps one of those and states the migration in
`docs/PLUGIN_CONTRACT.md` itself.

## [Unreleased]

### Added

- **`npm:` plugin source.** `img2 add npm:<name>[@<version>]` fetches a plugin from npm instead of
git — `npm view` resolves the version and `dist.integrity`, `npm pack` fetches the tarball, and
the harness extracts it itself (stripping the tarball's `package/` prefix) into the same
staging → validate → link flow git installs already go through. Default trust is the
`@img2threejs` scope, mirroring the `img2threejs/*` git org; anything else needs
`--allow-any-source`. The registry row keeps its four keys: `repo` becomes `npm:<name>`, `ref` is
the resolved version, `resolvedSha` is npm's `dist.integrity` (a `sha512-…` string, not a git SHA).
- **`img2 update [<id>] [--check]`.** Re-checks every registered plugin's source for something
newer — the newest npm version or the newest reachable git tag — and re-fetches it in place if
so, backing up the previous clone. A `link:` row is a local dev checkout and is left alone.
`--check` reports what is pending without fetching anything, exiting non-zero if there is any.
- Audited every git-specific assumption doctor/list/add made about a plugin directory: the
`.gitignore` covers `_img2_local.py` check now only runs when the plugin dir is actually a git
checkout (an npm-fetched or bare `--link`'d directory has no `.git` and no such hazard), and the
short-SHA display in `img2 add`/`img2 list` now shows a useful slice of an npm `sha512-…`
integrity string instead of just the literal `sha512-` prefix.
- `package.json`'s `files` field now excludes `__pycache__/`, `*.pyc`, and any stray `.omc/`
directory wherever they occur under `docs/`/`img2_core/` — `npm pack` does not consult
`.gitignore` for a directory explicitly listed in `files`, so without this a local dev artifact
left in the working tree would have shipped in the published tarball.
- CI: `.github/workflows/ci.yml` and `publish.yml` call the org's shared `img2threejs/ci-workflows`
reusable workflows (pinned by commit SHA, per org policy) instead of carrying inline workflow
logic — `ci.yml` runs the full test suite on every PR and push to `main`; `publish.yml`
publishes `img2` to npm (via an `NPM_TOKEN` secret, not OIDC trusted publishing) on a `vX.Y.Z`
tag push, after validating the tag against `package.json` and refusing install-time lifecycle
scripts. Both are pinned to a feature branch of `ci-workflows` pending
[img2threejs/ci-workflows#2](https://github.com/img2threejs/ci-workflows/pull/2); re-pin once
it merges.

## [0.2.3] — 2026-09-03

### Added
Expand Down
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,24 @@ it demonstrably catches the bug it guards against.
Tag `vX.Y.Z` (annotated) matching `package.json`; `img2 add` and self-installs resolve
the newest semver tag. Keep `docs/PLAN.md` phase status current when a release changes
what is true.

## Publishing

The harness itself ships to npm as the `img2` package. `.github/workflows/ci.yml` and
`publish.yml` are thin callers into the org's shared `img2threejs/ci-workflows` repo, pinned by
full commit SHA (org policy — never a branch or tag ref); re-pin to `ci-workflows`'s merged
`main` SHA once [img2threejs/ci-workflows#2](https://github.com/img2threejs/ci-workflows/pull/2)
lands, and again whenever a reusable workflow there changes in a way this repo needs.

To release: bump `package.json`'s `version` and add a `CHANGELOG.md` entry, tag `vX.Y.Z`
matching it, and push the tag. The reusable `npm-publish.yml` workflow validates the tag against
`package.json`, refuses `preinstall`/`install`/`postinstall`/`prepare` lifecycle scripts, runs
`npm test` in a job with no access to the npm token, then publishes with
`npm publish --provenance --access public`, authenticated by the **`NPM_TOKEN`** secret (a
granular npm automation token — this is a plain token, not OIDC trusted publishing). It no-ops if
the tagged version is already on the registry, and a prerelease tag (`v0.3.0-beta.1`) publishes
under its prerelease dist-tag (`beta`).

`NPM_TOKEN` must exist as an org or repo Actions secret before the first tag push — the reusable
workflow publishes straight from CI, so unlike a trusted-publishing setup there is no separate
manual first-publish step once the token is in place.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ npx github:img2threejs/img2 install # $IMG2_HOME (~/.img2), harness checkout
# (~/.local/bin, /opt/homebrew/bin, or /usr/local/bin —
# if none qualifies, install prints an alias to use instead)
img2 add img2threejs/plugin-img2glb # clone @ newest tag, pin SHA, link ~/.claude/skills/img2-img2glb
img2 add npm:@img2threejs/plugin-cs2 # npm instead of git: fetch @ newest version, pin dist.integrity
img2 list # registered plugins: id, version, ref, sha
img2 update # re-check every row's source for a newer version, fetch it
img2 doctor # fail-loud static audit of every row (--json for tooling)
img2 sync --check # generated index == manifests (CI-able)
img2 remove img2glb # unlink every host, move clone to backups, drop the row
Expand Down Expand Up @@ -87,14 +89,23 @@ Developing a plugin locally:
img2 add --link ~/src/plugin-hello-cube # symlink, no clone; ref/sha recorded as "local"
```

A plugin can also be distributed as an npm package instead of a git repo — `img2 add npm:<name>`
or `img2 add npm:<name>@<version>` (default source trust is the `@img2threejs` scope, same rule as
the `img2threejs/*` git org: anything else needs `--allow-any-source`). `ref` records the resolved
version and `resolvedSha` records npm's own `dist.integrity` (a `sha512-…` string) rather than a git
SHA — the harness's own notion of "pinned content hash" for a package it fetched instead of cloned.
`img2 update [<id>] [--check]` re-checks every row's source (npm version, git's newest semver tag) for
something newer and fetches it in place; `--check` reports what is pending without changing anything.

Layout under `$IMG2_HOME` (default `~/.img2`; the deprecated `IMG2THREEJS_HOME` is honoured
for one release with a warning):

```
harness/ canonical harness checkout (img2_core lives here)
plugins/<id>/ one clone (or --link symlink) per registry row
plugins/<id>/ one clone (or --link symlink, or npm fetch) per registry row
generated/ index.md + routes.json, regenerated by `img2 sync`
plugins.json the registry: flat rows {id, repo, ref, resolvedSha, addedAt}
repo is "org/repo", a git URL, "npm:<name>", or "link:<path>"
receipts.json what was linked where
backups/ displaced directories; nothing is ever deleted in place
```
Expand Down
Loading
Loading