Skip to content

Commit 806c065

Browse files
committed
Merge remote-tracking branch 'origin/main' into relprep/1-feat-ts-engine-implement-registry-json
# Conflicts: # crates/hm-dsl-engine/src/detect.rs
2 parents b8f8880 + a65fcb7 commit 806c065

53 files changed

Lines changed: 1479 additions & 313 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/write-pipeline/SKILL.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Write, modify, or extend Harmont CI pipelines defined in `.hm/pipeline.py` (Pyth
2929
Read it carefully. It covers correct vs. incorrect approaches, when to use toolchains vs. raw shell, and common anti-patterns.
3030

3131
2. If you need the full API reference for a specific toolchain or feature, fetch the relevant page (append `.md` to any docs.harmont.dev URL for raw Markdown):
32-
- Toolchain reference: `https://docs.harmont.dev/pipeline-sdk/reference/toolchains/<name>.md` (rust, python, npm, go, cmake, zig, elixir, ruby, etc.)
32+
- Toolchain reference: `https://docs.harmont.dev/pipeline-sdk/reference/toolchains/<name>.md` (rust, python, js, go, cmake, zig, elixir, etc.)
3333
- Chains and steps: `https://docs.harmont.dev/pipeline-sdk/reference/chains.md`
3434
- Triggers: `https://docs.harmont.dev/pipeline-sdk/reference/triggers.md`
3535
- Caching: `https://docs.harmont.dev/pipeline-sdk/reference/cache.md`
@@ -40,15 +40,15 @@ Write, modify, or extend Harmont CI pipelines defined in `.hm/pipeline.py` (Pyth
4040
```
4141
WebFetch https://docs.harmont.dev/examples/<language>.md
4242
```
43-
Available examples: rust, go, cmake, zig, nextjs, python-uv, ruby, elixir
43+
Available examples: rust, go, cmake, zig, nextjs, python-uv, elixir
4444

4545
## Procedure
4646

47-
1. **Identify the project's language and build system.** Look at the project root for `Cargo.toml` (Rust), `package.json` (JS/TS), `pyproject.toml` or `setup.py` (Python), `go.mod` (Go), `CMakeLists.txt` (C/C++), `mix.exs` (Elixir), `build.zig` (Zig), `Gemfile` (Ruby).
47+
1. **Identify the project's language and build system.** Look at the project root for `Cargo.toml` (Rust), `package.json` (JS/TS), `pyproject.toml` or `setup.py` (Python), `go.mod` (Go), `CMakeLists.txt` (C/C++), `mix.exs` (Elixir), `build.zig` (Zig).
4848

4949
2. **Check for an existing pipeline.** Look for `.hm/pipeline.py` or `.hm/pipeline.ts`. If none exists, pick the DSL that matches the project's ecosystem before asking the user to confirm:
5050
- **TypeScript DSL** if the project already has `package.json`, `tsconfig.json`, or is primarily TypeScript/JavaScript (the team is already comfortable with the TS toolchain).
51-
- **Python DSL** for everything else — Rust, Go, C/C++, Elixir, Zig, Ruby, Python, or mixed-language projects (Python is the simpler, more universal choice).
51+
- **Python DSL** for everything else — Rust, Go, C/C++, Elixir, Zig, Python, or mixed-language projects (Python is the simpler, more universal choice).
5252
- Present your recommendation and rationale, then let the user override if they prefer the other DSL.
5353
Then either run `hm init --template <kind>` to scaffold or write the pipeline file directly.
5454

@@ -57,7 +57,7 @@ Write, modify, or extend Harmont CI pipelines defined in `.hm/pipeline.py` (Pyth
5757
4. **Write or modify the pipeline.** Follow the patterns guide strictly:
5858
- Prefer toolchains over raw `sh()` calls when a toolchain exists for the language.
5959
- Use `.fork()` for steps that can run in parallel.
60-
- Set triggers (`push`, `pull_request`, `schedule`) appropriate to the project.
60+
- Set triggers (`push`, `pull_request`) appropriate to the project.
6161
- Use `default_image: "ubuntu:24.04"` unless the project needs something specific.
6262
- Set `env: {"CI": "true"}` on the pipeline.
6363

.github/workflows/release.yml

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,46 @@ jobs:
3434
VERSION="${GITHUB_REF_NAME#v}"
3535
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
3636
37-
# Bump crate versions
37+
# Bump crate versions. Every workspace member that harmont-cli
38+
# depends on (directly or transitively) must be published, so each
39+
# one needs the tagged version stamped in.
3840
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm-util/Cargo.toml
3941
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm-pipeline-ir/Cargo.toml
42+
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm-config/Cargo.toml
4043
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm-plugin-protocol/Cargo.toml
44+
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm-render/Cargo.toml
45+
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm-vm/Cargo.toml
46+
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm-exec/Cargo.toml
4147
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm-plugin-cloud/Cargo.toml
4248
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm-dsl-engine/Cargo.toml
4349
sed -i "0,/version = \"0.0.0-dev\"/s//version = \"$VERSION\"/" crates/hm/Cargo.toml
4450
4551
# Rewrite workspace.dependencies pins so dependents resolve to the
4652
# tagged version (cargo publish strips path deps; the version field
4753
# is what consumers will receive).
48-
sed -i "s|hm-util = { path = \"crates/hm-util\", version = \"0.0.0-dev\" }|hm-util = { path = \"crates/hm-util\", version = \"$VERSION\" }|" Cargo.toml
49-
sed -i "s|hm-pipeline-ir = { path = \"crates/hm-pipeline-ir\", version = \"0.0.0-dev\" }|hm-pipeline-ir = { path = \"crates/hm-pipeline-ir\", version = \"$VERSION\" }|" Cargo.toml
54+
sed -i "s|hm-exec = { path = \"crates/hm-exec\", version = \"0.0.0-dev\" }|hm-exec = { path = \"crates/hm-exec\", version = \"$VERSION\" }|" Cargo.toml
5055
sed -i "s|hm-plugin-protocol = { path = \"crates/hm-plugin-protocol\", version = \"0.0.0-dev\" }|hm-plugin-protocol = { path = \"crates/hm-plugin-protocol\", version = \"$VERSION\" }|" Cargo.toml
5156
sed -i "s|hm-plugin-cloud = { path = \"crates/hm-plugin-cloud\", version = \"0.0.0-dev\" }|hm-plugin-cloud = { path = \"crates/hm-plugin-cloud\", version = \"$VERSION\" }|" Cargo.toml
57+
sed -i "s|hm-pipeline-ir = { path = \"crates/hm-pipeline-ir\", version = \"0.0.0-dev\" }|hm-pipeline-ir = { path = \"crates/hm-pipeline-ir\", version = \"$VERSION\" }|" Cargo.toml
58+
sed -i "s|hm-util = { path = \"crates/hm-util\", version = \"0.0.0-dev\" }|hm-util = { path = \"crates/hm-util\", version = \"$VERSION\" }|" Cargo.toml
59+
sed -i "s|hm-config = { path = \"crates/hm-config\", version = \"0.0.0-dev\" }|hm-config = { path = \"crates/hm-config\", version = \"$VERSION\" }|" Cargo.toml
5260
sed -i "s|hm-dsl-engine = { path = \"crates/hm-dsl-engine\", version = \"0.0.0-dev\" }|hm-dsl-engine = { path = \"crates/hm-dsl-engine\", version = \"$VERSION\" }|" Cargo.toml
61+
sed -i "s|hm-render = { path = \"crates/hm-render\", version = \"0.0.0-dev\" }|hm-render = { path = \"crates/hm-render\", version = \"$VERSION\" }|" Cargo.toml
62+
sed -i "s|hm-vm = { path = \"crates/hm-vm\", version = \"0.0.0-dev\" }|hm-vm = { path = \"crates/hm-vm\", version = \"$VERSION\" }|" Cargo.toml
5363
5464
cargo check --workspace --exclude hm-fixtures
5565
66+
- name: Publishability guard (dry-run package the whole graph)
67+
# Fail fast before any real `cargo publish` if the dependency graph
68+
# isn't publishable — e.g. a publishable crate depending on a
69+
# `publish = false` crate, or a missing version bump. `cargo package
70+
# --workspace` resolves sibling path deps locally (no index lookup,
71+
# unlike `cargo publish --dry-run`, which the not-yet-published deps
72+
# would fail), so it catches exactly the publish=false / unpublished-dep
73+
# class of regression. `--no-verify` skips the per-crate rebuild;
74+
# `cargo check --workspace` above already proved it compiles.
75+
run: cargo package --workspace --exclude hm-fixtures --allow-dirty --no-verify
76+
5677
- name: Publish hm-util
5778
run: |
5879
if curl -sf -A "harmont-release-ci (github-actions)" "https://crates.io/api/v1/crates/hm-util/$VERSION" > /dev/null 2>&1; then
@@ -75,6 +96,17 @@ jobs:
7596
- name: Wait for crates.io index
7697
run: sleep 30
7798

99+
- name: Publish hm-config
100+
run: |
101+
if curl -sf -A "harmont-release-ci (github-actions)" "https://crates.io/api/v1/crates/hm-config/$VERSION" > /dev/null 2>&1; then
102+
echo "hm-config@$VERSION already published, skipping"
103+
else
104+
cargo publish -p hm-config --token ${{ secrets.CRATES_IO_TOKEN }} --allow-dirty
105+
fi
106+
107+
- name: Wait for crates.io index
108+
run: sleep 30
109+
78110
- name: Publish hm-plugin-protocol
79111
run: |
80112
if curl -sf -A "harmont-release-ci (github-actions)" "https://crates.io/api/v1/crates/hm-plugin-protocol/$VERSION" > /dev/null 2>&1; then
@@ -86,6 +118,39 @@ jobs:
86118
- name: Wait for crates.io index
87119
run: sleep 30
88120

121+
- name: Publish hm-render
122+
run: |
123+
if curl -sf -A "harmont-release-ci (github-actions)" "https://crates.io/api/v1/crates/hm-render/$VERSION" > /dev/null 2>&1; then
124+
echo "hm-render@$VERSION already published, skipping"
125+
else
126+
cargo publish -p hm-render --token ${{ secrets.CRATES_IO_TOKEN }} --allow-dirty
127+
fi
128+
129+
- name: Wait for crates.io index
130+
run: sleep 30
131+
132+
- name: Publish hm-vm
133+
run: |
134+
if curl -sf -A "harmont-release-ci (github-actions)" "https://crates.io/api/v1/crates/hm-vm/$VERSION" > /dev/null 2>&1; then
135+
echo "hm-vm@$VERSION already published, skipping"
136+
else
137+
cargo publish -p hm-vm --token ${{ secrets.CRATES_IO_TOKEN }} --allow-dirty
138+
fi
139+
140+
- name: Wait for crates.io index
141+
run: sleep 30
142+
143+
- name: Publish hm-exec
144+
run: |
145+
if curl -sf -A "harmont-release-ci (github-actions)" "https://crates.io/api/v1/crates/hm-exec/$VERSION" > /dev/null 2>&1; then
146+
echo "hm-exec@$VERSION already published, skipping"
147+
else
148+
cargo publish -p hm-exec --token ${{ secrets.CRATES_IO_TOKEN }} --allow-dirty
149+
fi
150+
151+
- name: Wait for crates.io index
152+
run: sleep 30
153+
89154
- name: Publish hm-plugin-cloud
90155
run: |
91156
if curl -sf -A "harmont-release-ci (github-actions)" "https://crates.io/api/v1/crates/hm-plugin-cloud/$VERSION" > /dev/null 2>&1; then

.hm/ci.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,18 @@ def shared_base() -> hm.Step:
2020

2121
@hm.target()
2222
def rust_project(shared_base: hm.Target[hm.Step]) -> tuple[hm.Step, ...]:
23-
project = hm.rust.project(path=".", base=shared_base)
23+
# Build esbuild into the image first: hm-dsl-engine's build.rs shells out
24+
# to esbuild at compile time to embed the TypeScript SDK bundle. Without it
25+
# the build emits an 18-byte stub and the bundled_sources tests fail (CLI-37).
26+
# Installing Node here also lets the JS-runtime-gated render tests actually
27+
# run instead of self-skipping.
28+
ts_deps = hm.js.project(
29+
path="crates/hm-dsl-engine/harmont-ts",
30+
base=shared_base,
31+
).install()
32+
project = hm.rust.project(path=".", base=ts_deps)
2433
return hm.group([
25-
project.test(flags=("--lib",), packages=("harmont-cli",)),
26-
project.test(
27-
flags=("--test", "cmd_init"),
28-
packages=("harmont-cli",),
29-
label=":test_tube: init template roundtrip",
30-
),
34+
project.test(), # cargo test --workspace --locked — every package
3135
project.clippy(),
3236
project.fmt(),
3337
])

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
- **DSL:** Fix example Python pipelines to use current API (`hm.js.project()` instead of removed `hm.npm()`/`hm.bun()`) ([#77][pr77])
3737
- **DSL:** Use correct Zig download URL for >= 0.14.1 and bump default to 0.14.1 ([`1bf727e`][c1bf727e])
3838
- **CLI:** Fix `hm pipelines` returning errors on repos without pipeline files ([#34][pr34])
39+
- **CLI:** `hm init --force` no longer wipes the entire `.hm/` directory; it now overwrites only the target template file, preserving `config.toml` and any co-resident pipelines.
40+
- **CLI:** `hm init` no longer silently overwrites customized `.claude/skills/*/SKILL.md` files; edited skills are skipped with a warning unless `--force` is passed.
3941

4042
[pr33]: https://github.com/harmont-dev/harmont-cli/pull/33
4143
[pr34]: https://github.com/harmont-dev/harmont-cli/pull/34

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ def ci(project: hm.Target[PythonToolchain]) -> tuple[hm.Step, ...]:
132132
<summary><b>TypeScript</b></summary>
133133

134134
```typescript
135-
import { pipeline, push, type PipelineDefinition } from "harmont";
136-
import { python } from "harmont/toolchains";
135+
import { pipeline, push, type PipelineDefinition } from "@harmont/hm";
136+
import { python } from "@harmont/hm/toolchains";
137137

138138
const project = python({ path: "." });
139139

RELEASING.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,30 @@ test changes to the export script.
3636

3737
Versioning is **driven by git tags on the public mirror**. The release
3838
workflow in `.github/workflows/release.yml` triggers on any tag matching
39-
`v*`, seds the version from the tag into all three crates' `Cargo.toml`
40-
files plus the `workspace.dependencies` pins, and publishes
41-
`hm-plugin-protocol`, `hm-plugin-sdk`, and `harmont-cli` to crates.io in
42-
that order. The bundled WASM plugins (`hm-plugin-docker`,
43-
`hm-plugin-output-human`, `hm-plugin-output-json`, `hm-plugin-cloud`)
44-
and `hm-fixtures` are not published — they ship embedded inside the
45-
`hm` binary.
39+
`v*`, seds the version from the tag into every publishable crate's
40+
`Cargo.toml` plus the `workspace.dependencies` pins, runs
41+
`cargo package --workspace` as a fail-fast guard (it resolves sibling
42+
path deps locally, so it catches the `publish = false` /
43+
unpublished-dep class of regression without needing the deps on the
44+
index yet — which `cargo publish --dry-run` would), then publishes the
45+
crates to crates.io in dependency (topological) order:
46+
47+
```
48+
hm-util → hm-pipeline-ir → hm-config → hm-plugin-protocol → hm-render
49+
→ hm-vm → hm-exec → hm-plugin-cloud → hm-dsl-engine → harmont-cli
50+
```
51+
52+
`harmont-cli` (the `hm` binary) depends on every other crate, so they
53+
must all reach crates.io first — including `hm-vm`, which carries the
54+
local Docker backend. `hm-fixtures` is test-only and not published.
4655

4756
### Prerequisites (one-time)
4857

4958
- `CRATES_IO_TOKEN` set as a repository secret on
5059
https://github.com/harmont-dev/harmont-cli/settings/secrets/actions.
51-
Generate it from https://crates.io/me with the `publish-update` scope
52-
on `hm-plugin-protocol`, `hm-plugin-sdk`, and `harmont-cli`.
53-
- The three crates exist on crates.io (first publish only requires
54-
`publish-new` scope). After the initial publish, narrow the token to
55-
`publish-update`.
60+
Generate it from https://crates.io/me. The first release needs the
61+
`publish-new` scope (the crates do not yet exist on crates.io); after
62+
the initial publish, narrow the token to `publish-update`.
5663

5764
### Per-release procedure
5865

@@ -72,9 +79,8 @@ and `hm-fixtures` are not published — they ship embedded inside the
7279
https://github.com/harmont-dev/harmont-cli/actions/workflows/release.yml.
7380
Each crate's publish step skips if the version is already on
7481
crates.io, so re-running after a partial success is safe.
75-
5. After the workflow completes, verify on crates.io:
76-
- https://crates.io/crates/hm-plugin-protocol/1.2.3
77-
- https://crates.io/crates/hm-plugin-sdk/1.2.3
82+
5. After the workflow completes, verify the leaf crate on crates.io
83+
(it depends on all the others, so its presence implies theirs):
7884
- https://crates.io/crates/harmont-cli/1.2.3
7985

8086
### Tagging in the monorepo (optional)

crates/hm-config/src/creds.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,13 @@ fn load() -> CredentialFile {
3333
fn save(file: &CredentialFile) -> Result<()> {
3434
let p = path()?;
3535
let serialized = toml::to_string_pretty(file).context("serializing credentials")?;
36-
hm_util::os::fs::blocking::write_atomic_restricted(&p, serialized.as_bytes(), 0o600, 0o700)
37-
.with_context(|| format!("writing {}", p.display()))?;
36+
hm_util::os::fs::blocking::write_atomic_restricted(
37+
&p,
38+
serialized.as_bytes(),
39+
hm_util::os::fs::FileMode(0o600),
40+
hm_util::os::fs::DirMode(0o700),
41+
)
42+
.with_context(|| format!("writing {}", p.display()))?;
3843
Ok(())
3944
}
4045

0 commit comments

Comments
 (0)