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
128 changes: 87 additions & 41 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ name: Release
# A tag is the release, and THE TAG IS THE VERSION. Every package file in the
# tree carries the placeholder `0.0.0`; each job checks out the tagged commit
# and stamps what the tag names into its own throwaway checkout before it builds
# [release.version]. Nothing commits, nothing pushes, and no job checks out a
# branch. The stamped tree is a pure function of (tagged commit, tag), so
# [release.version]. Nothing pushes and no job checks out a branch; the pub.dev
# job alone commits, into its own throwaway checkout and only because pub
# refuses to validate an archive with uncommitted changes beside it. The
# repository is never written to. The stamped tree is a pure function of
# (tagged commit, tag), so
# `make version VERSION=1.2.3` on the tagged commit reproduces what shipped —
# nobody bumps a number before tagging, and no tag is ever refused for a file
# somebody forgot.
Expand Down Expand Up @@ -50,10 +53,40 @@ jobs:
name: Preflight
runs-on: ubuntu-22.04
timeout-minutes: 10
# The publishing credentials are scoped to this environment, and an
# environment's secrets are readable ONLY by a job that declares it — so the
# check below is meaningless anywhere else. It reported both as absent while
# they were set correctly, which is the one way a guard can be worse than no
# guard: refusing the release it was added to protect.
environment: release
outputs:
version: ${{ steps.version.outputs.version }}
publishing: ${{ github.event_name == 'push' }}
steps:
# FIRST, before a checkout even. `publish` and `publish-pub` are held to
# the same gates but run in PARALLEL, so a Marketplace credential that
# turns out to be missing at publish time is discovered with pub.dev
# already published — and a pub version cannot be withdrawn, only
# retracted. The credentials are therefore proven to EXIST here, where the
# whole cost of being wrong is a red tag. Presence only: a value is never
# printed, compared, or logged.
- name: The publishing credentials exist
if: github.event_name == 'push'
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
run: |
set -euo pipefail
missing=""
[ -n "${AZURE_CLIENT_ID:-}" ] || missing="${missing} AZURE_CLIENT_ID"
[ -n "${AZURE_TENANT_ID:-}" ] || missing="${missing} AZURE_TENANT_ID"
if [ -n "${missing}" ]; then
echo "::error::Release refused: no value for${missing}. The Marketplace publish exchanges GitHub OIDC for an Entra session and cannot run without them, and pub.dev publishes alongside it — so releasing now would burn a pub.dev version with no extension beside it [release.authentication]."
exit 1
fi
echo "AZURE_CLIENT_ID: present"
echo "AZURE_TENANT_ID: present"

- uses: actions/checkout@v4
with:
# The tagged commit, never a branch — `github.sha` on a tag push IS
Expand Down Expand Up @@ -112,8 +145,16 @@ jobs:
name: Verify
needs: preflight
uses: ./.github/workflows/ci.yml
# A called workflow may not request more than its caller grants, and GitHub
# checks that STATICALLY, before any job runs — an `if` that will never be
# true does not excuse the job from the check. ci.yml's `security` job asks
# for `pull-requests: write` to comment on a PR, so withholding it here does
# not skip that job, it refuses the whole run with `startup_failure` and no
# release starts at all. Granted, therefore, and unused: `security` is
# `if: github.event_name == 'pull_request'`, which a tag push never is.
permissions:
contents: read
pull-requests: write
with:
ref: ${{ inputs.ref || github.sha }}

Expand Down Expand Up @@ -186,10 +227,7 @@ jobs:
rust: x86_64-pc-windows-gnu
apt: gcc-mingw-w64-x86-64
cc: x86_64-w64-mingw32-gcc
- vsix: darwin-x64
os: macos-15-intel
rust: x86_64-apple-darwin
native: true
linker: x86_64-w64-mingw32-gcc
- vsix: darwin-arm64
os: macos-14
rust: aarch64-apple-darwin
Expand All @@ -213,14 +251,22 @@ jobs:
if: matrix.apt != ''
run: sudo apt-get update && sudo apt-get install --yes ${{ matrix.apt }}

- name: Point cargo and cc at it
# The C compiler and the LINKER are set separately because musl wants one
# and not the other. Rust ships its own musl libc and links that target
# self-contained; pointing the linker at Ubuntu's `musl-gcc` instead
# produced an x86_64 static-PIE that segfaulted (exit 139) the instant it
# was executed, so the binary was built, packaged, and dead. tree-sitter is
# still C and still needs CC_<triple>. Only a genuine cross-link — mingw —
# names a linker.
- name: Point cc at the cross toolchain
if: matrix.cc != ''
run: echo "CC_${{ matrix.rust }}=${{ matrix.cc }}" >> "$GITHUB_ENV"

- name: Point cargo's linker at it
if: matrix.linker != ''
run: |
triple=$(echo "${{ matrix.rust }}" | tr 'a-z-' 'A-Z_')
{
echo "CC_${{ matrix.rust }}=${{ matrix.cc }}"
echo "CARGO_TARGET_${triple}_LINKER=${{ matrix.cc }}"
} >> "$GITHUB_ENV"
echo "CARGO_TARGET_${triple}_LINKER=${{ matrix.linker }}" >> "$GITHUB_ENV"

# VERSION stamps the bundle and reaches the compiler as DMX_VERSION, so
# the manifest, the changelog the marketplace page shows, and the binary's
Expand Down Expand Up @@ -330,7 +376,7 @@ jobs:
# The complete set, named once. A platform missing from here is a platform
# that silently stops being published.
BUNDLES: >-
dmx-darwin-arm64.vsix dmx-darwin-x64.vsix
dmx-darwin-arm64.vsix
dmx-linux-arm64.vsix dmx-linux-x64.vsix
dmx-alpine-arm64.vsix dmx-alpine-x64.vsix
dmx-win32-x64.vsix dmx-universal.vsix
Expand Down Expand Up @@ -368,18 +414,15 @@ jobs:
set -euo pipefail
npm ci --prefix src/editors/vscode

# Azure DevOps' fixed first-party resource is what `vsce` authenticates
# against. Despite the environment variable name required by `vsce`,
# this is a short-lived OIDC-derived access token, not a stored PAT.
marketplace_token="$(az account get-access-token \
--resource 499b84ac-1321-427f-aa17-267ca6975798 \
--query accessToken -o tsv)"
echo "::add-mask::${marketplace_token}"
export VSCE_PAT="${marketplace_token}"

# `--azure-credential` hands vsce the Entra session `azure/login`
# established from this job's OIDC token, and vsce acquires what it
# needs itself. Nothing mints, masks, exports or stores a token: there
# is no VSCE_PAT in this release, and no Marketplace PAT exists to
# expire, leak or rotate [release.authentication].
for bundle in $BUNDLES; do
echo "Publishing bundles/$bundle"
npx --prefix src/editors/vscode vsce publish \
--azure-credential \
--skip-duplicate \
--packagePath "bundles/$bundle"
done
Expand Down Expand Up @@ -475,21 +518,6 @@ jobs:
working-directory: src/dart_packages/dmx
run: dart pub get

# The archive pub would receive, before it receives it. `--dry-run` fails
# on anything that would fail the real publish — a missing licence, a file
# the package cannot carry, an unresolvable constraint.
#
# BEFORE the stamp, deliberately. `--dry-run` exits non-zero on any
# warning, and one of pub's warnings is "you have uncommitted changes" —
# which the stamp is, by design. So the archive's SHAPE is proven here on
# the clean tagged checkout, where that warning cannot fire, and the
# version it carries is proven by the steps below. Nothing about
# publishability depends on which version string is in the file.
- name: Prove the archive is publishable
if: steps.published.outputs.skip != 'true'
working-directory: src/dart_packages/dmx
run: dart pub publish --dry-run

# `lib/src/version.dart` is GENERATED from the pubspec by this package's
# own macro worker, so stamping the pubspec means regenerating it — a
# package whose `DmxPackage.version` names the previous release is a
Expand Down Expand Up @@ -518,12 +546,30 @@ jobs:
make version VERSION="$VERSION"
make dart-package-version VERSION="$VERSION" DMX="$PWD/bundle/dmx"
git diff --stat
# Committed into this throwaway checkout, never pushed and never on a
# branch. `--dry-run` below exits non-zero on ANY warning, and "you
# have uncommitted changes" is one of them — so the stamp has to land
# in a commit for the archive to be provable at all.
git -c user.email=actions@github.com -c user.name="github-actions" \
commit -qam "stamp $VERSION"

# The archive pub is about to receive, exactly as it will receive it:
# stamped, so the version pub validates against is the version being
# published. `--dry-run` fails on anything that would fail the real
# publish — a missing licence, a file the package cannot carry, an
# unresolvable constraint, a changelog that never mentions this version.
#
# It ran BEFORE the stamp until v0.2.0, on the reasoning that nothing
# about publishability depends on the version string. It does: pub checks
# the changelog against the CURRENT version, so the `0.0.0` placeholder
# failed it every time, and the step could never once have passed.
- name: Prove the archive is publishable
if: steps.published.outputs.skip != 'true'
working-directory: src/dart_packages/dmx
run: dart pub publish --dry-run

# `--force` rather than a second `--dry-run`: the tree is deliberately
# dirty now, and pub treats that as a warning, not an error. Proven by
# `scripts/version.test.mjs` and `test/stamp_version_test.dart`, which
# cover what was written; proven again by the `--version` check above,
# which is the released binary reporting the released number.
# `--force` only to skip the interactive confirmation: the tree is clean
# and the archive is the one proven directly above.
- name: Publish
if: steps.published.outputs.skip != 'true'
working-directory: src/dart_packages/dmx
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,4 @@ examples/dmx_sqlite_example/tool/dmx/app.db
# "autoMemoryEnabled": false setting ([AGENT-AUTOMEMORY]). Claude Code auto-ignores
# this file by default, so re-include it explicitly. Keep this LAST so it wins.
!.claude/settings.local.json
.mcp.json
18 changes: 11 additions & 7 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@
"rust-analyzer.testExplorer": true,
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.allTargets": true,

// The Dart tests live in the example package, not at the workspace root.
"dart.testAdditionalArgs": [],

// The Dart in this repo is the example package [editor.extension.autostart].
"dmx.paths": ["examples/storefront/lib"],
"dmx.paths": [
"examples/storefront/lib"
],
// Off *here only*: the `dmx: watch` task above already starts a watcher on
// folder open, and this repo is the one place both mechanisms are present.
// Consumers get `true`, which is the whole point of the extension. To try the
// auto-start, stop that task and run "dmx: Restart Watcher".
"dmx.autoStart": false,

"[rust]": { "editor.formatOnSave": true },
"files.watcherExclude": { "**/target/**": true }
}
"[rust]": {
"editor.formatOnSave": true
},
"files.watcherExclude": {
"**/target/**": true
},
"deslop.topOffenders.groupBy": "type"
}
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,12 @@ vsix-universal: extension version ## Package the no-binary VSIX every other plat
@# with one would hand every unmatched platform the wrong architecture.
$(RM) $(EXTENSION_DIR)/bin
cp LICENSE $(EXTENSION_DIR)/LICENSE
@# `vsce` opens the --out path, it does not create the directory holding it.
@# `vsix` gets that directory for free from `build`; this target carries no
@# binary and so builds nothing, which on a fresh checkout — a release
@# runner, every time — means target/ does not exist and packaging dies with
@# ENOENT after the bundle was assembled.
@$(MKDIR) $(CURDIR)/$(TARGET_DIR)
cd $(EXTENSION_DIR) && npx vsce package --out $(CURDIR)/$(TARGET_DIR)/dmx-universal.vsix

# Full clean rebuild-and-reinstall cycle for the VS Code extension
Expand Down Expand Up @@ -450,6 +456,6 @@ rebuild: ## From nothing: clean, rebuild the binary, and package the VSIX
corpus: ## Generate every golden sample and prove it is valid Dart
@$(RM) $(CORPUS_DIR) && $(MKDIR) $(CORPUS_DIR)/lib
@cp $(GOLDEN_DIR)/*.dart $(CORPUS_DIR)/lib/
@printf 'name: dmx_corpus\nenvironment:\n sdk: ^3.0.0\ndependencies:\n dmx:\n path: $(CURDIR)/src/dart_packages/dmx\n' > $(CORPUS_DIR)/pubspec.yaml
@printf 'name: dmx_corpus\nenvironment:\n sdk: ^3.0.0\ndependencies:\n dmx: ^0.3.0\n' > $(CORPUS_DIR)/pubspec.yaml
cargo run $(CRATE) --quiet -- build $(CORPUS_DIR)/lib --insert-regions
cd $(CORPUS_DIR) && dart pub get && dart analyze --fatal-infos
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Neither input leaves the tab.
## Install

**VS Code** — install **dmx — Dart code generation** from the
[Marketplace](https://marketplace.visualstudio.com/items?itemName=nimblesite.dmx),
[Marketplace](https://marketplace.visualstudio.com/items?itemName=Nimblesite.dmx),
or from a terminal:

```bash
Expand Down
2 changes: 2 additions & 0 deletions docs/messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ Version control is the team's choice. Commit generated files when a checkout sho
## Calls to action

- **[Try dmx in your browser—no install](https://dmx.dev/playground.html)**
- **[Install the VS Code extension](vscode:extension/Nimblesite.dmx)** — the `vscode:` link opens the extension in the editor. Pair it with the Marketplace link below rather than using either alone: the `vscode:` link does nothing for a reader who has no VS Code installed, and the Marketplace page is the only one a browser can render.
- **[View on the Marketplace](https://marketplace.visualstudio.com/items?itemName=Nimblesite.dmx)** — the publisher is `Nimblesite`, capitalised. The lowercase `nimblesite.dmx` form is what the gallery API accepts and the web page 404s on, so it must never appear in a link.
- **See code generation on every save**
- **Shape your team's model template**
- **Build a custom Dart macro**
Expand Down
3 changes: 1 addition & 2 deletions examples/dmx_openapi_example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ environment:
sdk: ^3.0.0

dependencies:
dmx:
path: ../../src/dart_packages/dmx
dmx: ^0.3.0

dev_dependencies:
test: ^1.25.0
3 changes: 1 addition & 2 deletions examples/dmx_sqlite_example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ environment:
sdk: ^3.0.0

dependencies:
dmx:
path: ../../src/dart_packages/dmx
dmx: ^0.3.0

dev_dependencies:
test: ^1.25.0
Loading
Loading