diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 70064c6..189db1e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,6 +16,11 @@ jobs: - name: Check out repository uses: actions/checkout@v7 + - name: Validate initial publication guards and Cargo license assets + run: | + node --test scripts/initial-publish-context.test.mjs + node scripts/cargo-package-assets.mjs --check + - name: Read supported specification revision id: specification shell: bash @@ -105,6 +110,9 @@ jobs: CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime run: cargo +stable test -p stack-engine --lib --target wasm32-wasip1 cross_target_numeric_fixture --locked + - name: Verify registry-ready formatter package + run: cargo +stable publish --dry-run -p stack-formatter --locked + - name: Run Clippy run: cargo +stable clippy --workspace --all-targets --all-features --locked -- -D warnings diff --git a/.github/workflows/initial-publish.yaml b/.github/workflows/initial-publish.yaml new file mode 100644 index 0000000..727d082 --- /dev/null +++ b/.github/workflows/initial-publish.yaml @@ -0,0 +1,75 @@ +name: Initial crates.io publish + +on: + workflow_dispatch: + inputs: + package: + description: Initial crate to publish (formatter first) + required: true + type: choice + options: [stack-formatter, stack-engine] + expected_sha: + description: Exact main commit whose CI has succeeded + required: true + type: string + +permissions: + contents: read + actions: read + +concurrency: + group: initial-crates-io-publish + cancel-in-progress: false + +defaults: + run: + shell: bash + +jobs: + publish: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + env: + PACKAGE_NAME: ${{ inputs.package }} + EXPECTED_SHA: ${{ inputs.expected_sha }} + steps: + - name: Reject unexpected dispatch context + run: | + test "$GITHUB_REPOSITORY" = stack-sh/engine + test "$GITHUB_REF" = refs/heads/main + [[ "$EXPECTED_SHA" =~ ^[0-9a-f]{40}$ ]] + test "$GITHUB_SHA" = "$EXPECTED_SHA" + [[ "$PACKAGE_NAME" = stack-formatter || "$PACKAGE_NAME" = stack-engine ]] + + - name: Check out the exact dispatch commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ github.sha }} + persist-credentials: false + + - name: Install the minimum supported Rust toolchain + run: rustup toolchain install 1.85.0 --profile minimal + + - name: Verify package identity and successful main CI + env: + GH_TOKEN: ${{ github.token }} + run: | + test "$(git rev-parse HEAD)" = "$EXPECTED_SHA" + cargo +1.85.0 metadata --no-deps --locked --format-version 1 > "$RUNNER_TEMP/package.json" + gh run list --repo stack-sh/engine --workflow ci.yaml --event push --branch main --commit "$EXPECTED_SHA" --limit 1 --json status,conclusion,headSha > "$RUNNER_TEMP/ci.json" + node scripts/initial-publish-context.mjs "$RUNNER_TEMP/package.json" "$RUNNER_TEMP/ci.json" + + - name: Require an unpublished crate name + run: | + code=$(curl --silent --show-error --max-time 30 --user-agent 'stack-sh/engine initial publication (https://github.com/stack-sh/engine)' --output "$RUNNER_TEMP/crate-state.json" --write-out '%{http_code}' https://crates.io/api/v1/crates/"$PACKAGE_NAME") + test "$code" = 404 + + - name: Verify the exact source package without credentials + run: cargo +1.85.0 publish --package "$PACKAGE_NAME" --registry crates-io --locked --dry-run + + - name: Publish the initial crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_INITIAL_PUBLISH_TOKEN }} + run: | + test -n "$CARGO_REGISTRY_TOKEN" + cargo +1.85.0 publish --package "$PACKAGE_NAME" --registry crates-io --locked diff --git a/Cargo.lock b/Cargo.lock index b3ea5b0..c458df8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,7 +191,8 @@ dependencies = [ [[package]] name = "stack-compiler" version = "0.1.0" -source = "git+https://github.com/stack-sh/compiler.git?rev=84ab5663a7f7c5b7dc0b5e9e2f04c8894ed02820#84ab5663a7f7c5b7dc0b5e9e2f04c8894ed02820" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdba45c0f7a85ef0ec0bfb766b387dbd90fc6141237ac74b559d08a424750bc6" [[package]] name = "stack-engine" @@ -229,7 +230,8 @@ dependencies = [ [[package]] name = "stack-theme" version = "0.5.0" -source = "git+https://github.com/stack-sh/theme.git?rev=7e208d6a3c90d255799f390a4e8b86248c73caee#7e208d6a3c90d255799f390a4e8b86248c73caee" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98e0418c819d61897beded19d5333b4f6c7a8dcf1be46d0a8c62409ff2e6db7" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index cb3e847..ac7051b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,6 @@ roxmltree = "=0.21.1" serde = { version = "=1.0.229", features = ["derive"] } serde_json = "=1.0.151" sha2 = "=0.11.0" -stack-compiler = { git = "https://github.com/stack-sh/compiler.git", rev = "84ab5663a7f7c5b7dc0b5e9e2f04c8894ed02820" } -stack-formatter = { path = "crates/stack-formatter" } -stack-theme = { git = "https://github.com/stack-sh/theme.git", rev = "7e208d6a3c90d255799f390a4e8b86248c73caee" } +stack-compiler = "=0.1.0" +stack-formatter = { path = "crates/stack-formatter", version = "=0.1.0" } +stack-theme = "=0.5.0" diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index e82a98c..23a695b 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -4,8 +4,8 @@ | Component | Revision | License | Source | Notes | | --- | --- | --- | --- | --- | -| `stack-compiler` | `84ab5663a7f7c5b7dc0b5e9e2f04c8894ed02820` | Apache-2.0 | | Unmodified Rust dependency; its license and notice obligations apply to distributions that include it. | -| `stack-theme` | `7e208d6a3c90d255799f390a4e8b86248c73caee` | Apache-2.0 | | Unmodified Rust dependency with repository-authored core assets and the asset-free provider-pack contract. | +| `stack-compiler` | `0.1.0 (crates.io)` | Apache-2.0 | | Unmodified Rust dependency; its license and notice obligations apply to distributions that include it. | +| `stack-theme` | `0.5.0 (crates.io)` | Apache-2.0 | | Unmodified Rust dependency with repository-authored core assets and the asset-free provider-pack contract. | | `roxmltree` | `0.21.1` | MIT OR Apache-2.0 | | Parses caller-owned processed provider SVG before allowlisted in-memory embedding. | | `sha2`, `digest`, `block-buffer`, `crypto-common`, `hybrid-array`, `const-oid`, `typenum` | `0.11.0`, `0.11.3`, `0.12.1`, `0.2.2`, `0.4.14`, `0.10.2`, `1.20.1` | MIT OR Apache-2.0 | | Verifies provider asset hashes and computes deterministic provider-pack revisions. | | `libc` / `cpufeatures` | `0.2.189`, `0.3.1` | MIT OR Apache-2.0 | , | Target-specific SHA-256 acceleration support. | diff --git a/crates/stack-engine-wasm/Cargo.toml b/crates/stack-engine-wasm/Cargo.toml index bde6c3f..edecb8f 100644 --- a/crates/stack-engine-wasm/Cargo.toml +++ b/crates/stack-engine-wasm/Cargo.toml @@ -7,6 +7,8 @@ license.workspace = true repository.workspace = true description = "Browser WebAssembly adapter for Stack diagram operations" +publish = false + [lib] crate-type = ["cdylib", "rlib"] diff --git a/crates/stack-engine-wasm/src/lib.rs b/crates/stack-engine-wasm/src/lib.rs index 8da4333..fd546aa 100644 --- a/crates/stack-engine-wasm/src/lib.rs +++ b/crates/stack-engine-wasm/src/lib.rs @@ -1361,7 +1361,7 @@ mod tests { #[test] fn provider_pack_helpers_match_the_native_engine_contract() -> Result<(), Box> { let source = b"stack 1.0 diagram \"Provider\" { node item \"Example Storage\" { kind queue icon \"example:storage\" } }"; - let packs = include_str!("../../../tests/fixtures/provider-pack-input.json"); + let packs = include_str!("../../stack-engine/tests/fixtures/provider-pack-input.json"); let checked = check_with_provider_packs_bytes(source, packs)?; let rendered = render_with_provider_packs_bytes(source, packs)?; assert!(checked.diagnostics.is_empty()); @@ -1393,7 +1393,7 @@ mod tests { assert_eq!(core.document_version, 6); assert!(core.items.is_empty()); - let packs = include_str!("../../../tests/fixtures/provider-pack-input.json"); + let packs = include_str!("../../stack-engine/tests/fixtures/provider-pack-input.json"); let provider = completion_with_provider_packs_text(source, 7, position, packs)?; assert_eq!(provider.document_version, 7); assert_eq!(provider.items[0].kind, CompletionKind::Icon); diff --git a/crates/stack-engine/Cargo.toml b/crates/stack-engine/Cargo.toml index 4042c36..d99e8c5 100644 --- a/crates/stack-engine/Cargo.toml +++ b/crates/stack-engine/Cargo.toml @@ -7,6 +7,12 @@ license.workspace = true repository.workspace = true description = "Pure execution facade for Stack diagram operations" +publish = ["crates-io"] +homepage = "https://stack-diagram.com/" +documentation = "https://docs.rs/stack-engine" +readme = "README.md" +include = ["src/**", "tests/fixtures/**", "tests/snapshots/**", "README.md", "LICENSE", "THIRD_PARTY_LICENSES.md"] + [features] conformance = [] diff --git a/crates/stack-engine/LICENSE b/crates/stack-engine/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/crates/stack-engine/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/crates/stack-engine/README.md b/crates/stack-engine/README.md new file mode 100644 index 0000000..6834e4f --- /dev/null +++ b/crates/stack-engine/README.md @@ -0,0 +1,12 @@ +# Stack Engine + +Pure Rust layout and SVG rendering for Stack diagrams. The engine reuses the registry-published Stack compiler, formatter, and theme catalog without host I/O or network access. + +```toml +[dependencies] +stack-engine = "=0.7.0" +``` + +Rust 1.85 or newer is supported. See the [API documentation](https://docs.rs/stack-engine) and [repository documentation](https://github.com/stack-sh/engine) for rendering, limits, and compatibility. The browser adapter is distributed separately as `@stack-sh/engine` on npm; this crate is the native library. + +The source package includes unit-test fixtures and license notices. Cross-repository conformance and layout-corpus integration tests run from the repository checkout in CI. Generated native and browser snapshots must remain unchanged when preparing a Cargo release. diff --git a/crates/stack-engine/THIRD_PARTY_LICENSES.md b/crates/stack-engine/THIRD_PARTY_LICENSES.md new file mode 100644 index 0000000..23a695b --- /dev/null +++ b/crates/stack-engine/THIRD_PARTY_LICENSES.md @@ -0,0 +1,45 @@ +# Third-party licenses + +## Runtime dependencies + +| Component | Revision | License | Source | Notes | +| --- | --- | --- | --- | --- | +| `stack-compiler` | `0.1.0 (crates.io)` | Apache-2.0 | | Unmodified Rust dependency; its license and notice obligations apply to distributions that include it. | +| `stack-theme` | `0.5.0 (crates.io)` | Apache-2.0 | | Unmodified Rust dependency with repository-authored core assets and the asset-free provider-pack contract. | +| `roxmltree` | `0.21.1` | MIT OR Apache-2.0 | | Parses caller-owned processed provider SVG before allowlisted in-memory embedding. | +| `sha2`, `digest`, `block-buffer`, `crypto-common`, `hybrid-array`, `const-oid`, `typenum` | `0.11.0`, `0.11.3`, `0.12.1`, `0.2.2`, `0.4.14`, `0.10.2`, `1.20.1` | MIT OR Apache-2.0 | | Verifies provider asset hashes and computes deterministic provider-pack revisions. | +| `libc` / `cpufeatures` | `0.2.189`, `0.3.1` | MIT OR Apache-2.0 | , | Target-specific SHA-256 acceleration support. | +| `serde` / `serde_core` | `1.0.229` | MIT OR Apache-2.0 | | Runtime catalog data types through `stack-theme`. | +| `serde_json` | `1.0.151` | MIT OR Apache-2.0 | | Runtime embedded-catalog decoding through `stack-theme`; also used by formatter conformance tests. | +| `itoa` | `1.0.18` | MIT OR Apache-2.0 | | Transitive runtime dependency of `serde_json`. | +| `memchr` | `2.8.3` | Unlicense OR MIT | | Transitive runtime dependency of `serde_json`. | +| `zmij` | `1.0.23` | MIT | | Transitive runtime dependency of `serde_json`. | +| `wasm-bindgen` / `wasm-bindgen-shared` | `0.2.127` | MIT OR Apache-2.0 | | JavaScript ABI and generated glue shipped by `@stack-sh/engine`. | +| `js-sys` | `0.3.104` | MIT OR Apache-2.0 | | Typed-array input and plain JavaScript result objects; built without default features. | +| `cfg-if` | `1.0.4` | MIT OR Apache-2.0 | | Transitive runtime dependency of `wasm-bindgen` and `js-sys`. | +| `once_cell` | `1.21.4` | MIT OR Apache-2.0 | | Transitive runtime dependency of `wasm-bindgen`. | + +## Build-only dependencies + +| Component | Version | License | Source | Notes | +| --- | --- | --- | --- | --- | +| `serde_derive` | `1.0.229` | MIT OR Apache-2.0 | | Procedural macro used to build `stack-theme`. | +| `proc-macro2` | `1.0.107` | MIT OR Apache-2.0 | | Transitive procedural-macro build dependency. | +| `quote` | `1.0.47` | MIT OR Apache-2.0 | | Transitive procedural-macro build dependency. | +| `syn` | `3.0.4` | MIT OR Apache-2.0 | | Transitive procedural-macro build dependency. | +| `unicode-ident` | `1.0.24` | (MIT OR Apache-2.0) AND Unicode-3.0 | | Transitive procedural-macro build dependency. | +| `wasm-bindgen-macro` / `wasm-bindgen-macro-support` | `0.2.127` | MIT OR Apache-2.0 | | Procedural macro and support code used to build the browser adapter. | +| `bumpalo` | `3.20.3` | MIT OR Apache-2.0 | | Transitive build dependency of `wasm-bindgen-macro-support`. | +| `rustversion` | `1.0.23` | MIT OR Apache-2.0 | | Transitive build dependency of `wasm-bindgen`. | +| `syn` | `2.0.119` | MIT OR Apache-2.0 | | Transitive procedural-macro build dependency of `wasm-bindgen`. | +| `wasm-bindgen-cli` | `0.2.127` | MIT OR Apache-2.0 | | Version-matched build tool; not shipped in the npm package. | +| `typescript` | `7.0.2` | Apache-2.0 | | Type-check tool; not shipped in the npm package. | +| `ajv` | `8.20.0` | MIT | | Validates the checked-in layout corpus schema during development and CI; not shipped in the npm package. | +| `fast-deep-equal` | `3.1.3` | MIT | | Transitive build-only dependency of `ajv`. | +| `fast-uri` | `3.1.7` | BSD-3-Clause | | Transitive build-only dependency of `ajv`. | +| `json-schema-traverse` | `1.0.0` | MIT | | Transitive build-only dependency of `ajv`. | +| `require-from-string` | `2.0.2` | MIT | | Transitive build-only dependency of `ajv`. | + +No third-party visual asset is bundled in a Stack Engine distribution. The bundled fallback and 30 explicit icons are Stack-authored Apache-2.0 assets from `stack-theme`. The npm package includes this inventory and the Apache-2.0, MIT, and Unicode-3.0 license texts required by its compiled dependency choices. + +Before publishing a native library, binary-derived artifact, or WASM package, this inventory must list the shipped dependencies and assets, their pinned versions, exact licenses, required license texts, attribution, modifications, and redistribution conditions. Build-only dependencies that are not shipped should be distinguished from distributed code. diff --git a/crates/stack-engine/src/language.rs b/crates/stack-engine/src/language.rs index bc1f244..cc498ba 100644 --- a/crates/stack-engine/src/language.rs +++ b/crates/stack-engine/src/language.rs @@ -376,9 +376,8 @@ mod tests { #[test] fn completion_includes_validated_provider_icons() -> Result<(), Box> { - let fixture: serde_json::Value = serde_json::from_str(include_str!( - "../../../tests/fixtures/provider-pack-input.json" - ))?; + let fixture: serde_json::Value = + serde_json::from_str(include_str!("../tests/fixtures/provider-pack-input.json"))?; let input = fixture .as_array() .and_then(|items| items.first()) diff --git a/crates/stack-engine/src/provider.rs b/crates/stack-engine/src/provider.rs index 4cf6cf7..9a22593 100644 --- a/crates/stack-engine/src/provider.rs +++ b/crates/stack-engine/src/provider.rs @@ -533,9 +533,8 @@ mod tests { } fn fixture_input() -> Result> { - let mut inputs: Vec = serde_json::from_str(include_str!( - "../../../tests/fixtures/provider-pack-input.json" - ))?; + let mut inputs: Vec = + serde_json::from_str(include_str!("../tests/fixtures/provider-pack-input.json"))?; inputs .pop() .ok_or_else(|| "missing provider fixture".into()) diff --git a/crates/stack-engine/tests/fixtures/provider-pack-input.json b/crates/stack-engine/tests/fixtures/provider-pack-input.json new file mode 100644 index 0000000..a3cc0bd --- /dev/null +++ b/crates/stack-engine/tests/fixtures/provider-pack-input.json @@ -0,0 +1,82 @@ +[ + { + "manifest": { + "$schema": "https://raw.githubusercontent.com/stack-sh/theme/main/schemas/provider-pack.schema.json", + "schemaVersion": "1.0", + "packVersion": "0.1.0", + "provider": { + "id": "example", + "name": "Example Cloud" + }, + "distributionMode": "user-imported", + "source": { + "pageUrl": "https://example.com/icons", + "archiveUrl": "https://example.com/icons.zip", + "archiveSha256": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "release": "fixture-1", + "retrievedAt": "2026-09-04", + "termsUrl": "https://example.com/terms", + "termsReviewedAt": "2026-09-04", + "reviewAfter": "2026-12-03", + "copyright": "Copyright Example Cloud", + "licenseId": "LicenseRef-Example-Icons", + "archiveLicenseIncluded": false + }, + "rights": { + "termsAcceptanceRequired": true, + "permittedOutputs": [ + "architecture-diagram" + ], + "redistribution": { + "cargo": false, + "npm": false, + "wasm": false, + "webAsset": false, + "nativeBinary": false, + "generatedOutput": true + }, + "processing": { + "localOnly": true, + "automaticDownload": false, + "serverUpload": false, + "preserveColors": true, + "preserveGeometry": true, + "productNameNearby": true + }, + "modificationPolicy": "visual-preservation-only" + }, + "notice": { + "attribution": "Example Cloud owns the fixture icon.", + "termsSummary": "Architecture diagram use only.", + "nonEndorsement": "Example Cloud does not endorse Stack." + }, + "icons": [ + { + "id": "example:storage", + "subject": "Object storage service", + "productName": "Example Storage", + "recommendedNodeKind": "storage", + "asset": { + "path": "assets/storage.svg", + "originalPath": "icons/storage.svg", + "viewBox": [ + 0, + 0, + 24, + 24 + ], + "originalSha256": "sha256:836062321764dff3fa57b77f24c5ea13a9cec8833bc3c1796c21a7af21f5d4ad", + "processedSha256": "sha256:836062321764dff3fa57b77f24c5ea13a9cec8833bc3c1796c21a7af21f5d4ad", + "transformations": [] + } + } + ] + }, + "assets": [ + { + "path": "assets/storage.svg", + "svg": "\n" + } + ] + } +] diff --git a/crates/stack-engine/tests/language_intelligence.rs b/crates/stack-engine/tests/language_intelligence.rs index 44c4d39..6eff1b2 100644 --- a/crates/stack-engine/tests/language_intelligence.rs +++ b/crates/stack-engine/tests/language_intelligence.rs @@ -33,9 +33,8 @@ fn language_intelligence_runtime_stays_within_budget() -> Result<(), Box = serde_json::from_str(include_str!( - "../../../tests/fixtures/provider-pack-input.json" - ))?; + let inputs: Vec = + serde_json::from_str(include_str!("fixtures/provider-pack-input.json"))?; let packs = inputs .into_iter() .map(|input| { diff --git a/crates/stack-formatter/Cargo.toml b/crates/stack-formatter/Cargo.toml index 0e65281..f2436e0 100644 --- a/crates/stack-formatter/Cargo.toml +++ b/crates/stack-formatter/Cargo.toml @@ -7,6 +7,12 @@ license.workspace = true repository.workspace = true description = "Canonical formatter for the Stack diagram language" +publish = ["crates-io"] +homepage = "https://stack-diagram.com/" +documentation = "https://docs.rs/stack-formatter" +readme = "README.md" +include = ["src/**", "tests/fixtures/**", "tests/snapshots/**", "README.md", "LICENSE", "THIRD_PARTY_LICENSES.md"] + [features] conformance = [] diff --git a/crates/stack-formatter/LICENSE b/crates/stack-formatter/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/crates/stack-formatter/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/crates/stack-formatter/README.md b/crates/stack-formatter/README.md new file mode 100644 index 0000000..b0f75bb --- /dev/null +++ b/crates/stack-formatter/README.md @@ -0,0 +1,10 @@ +# Stack Formatter + +The pure Rust canonical formatter for the Stack diagram language. It consumes `stack-compiler` from crates.io and does not perform filesystem or network I/O. + +```toml +[dependencies] +stack-formatter = "=0.1.0" +``` + +Rust 1.85 or newer is supported. See the [API documentation](https://docs.rs/stack-formatter) and the [canonical formatting contract](https://github.com/stack-sh/specification) for behavior. Cross-repository conformance tests require the pinned specification checkout and run in repository CI. diff --git a/crates/stack-formatter/THIRD_PARTY_LICENSES.md b/crates/stack-formatter/THIRD_PARTY_LICENSES.md new file mode 100644 index 0000000..23a695b --- /dev/null +++ b/crates/stack-formatter/THIRD_PARTY_LICENSES.md @@ -0,0 +1,45 @@ +# Third-party licenses + +## Runtime dependencies + +| Component | Revision | License | Source | Notes | +| --- | --- | --- | --- | --- | +| `stack-compiler` | `0.1.0 (crates.io)` | Apache-2.0 | | Unmodified Rust dependency; its license and notice obligations apply to distributions that include it. | +| `stack-theme` | `0.5.0 (crates.io)` | Apache-2.0 | | Unmodified Rust dependency with repository-authored core assets and the asset-free provider-pack contract. | +| `roxmltree` | `0.21.1` | MIT OR Apache-2.0 | | Parses caller-owned processed provider SVG before allowlisted in-memory embedding. | +| `sha2`, `digest`, `block-buffer`, `crypto-common`, `hybrid-array`, `const-oid`, `typenum` | `0.11.0`, `0.11.3`, `0.12.1`, `0.2.2`, `0.4.14`, `0.10.2`, `1.20.1` | MIT OR Apache-2.0 | | Verifies provider asset hashes and computes deterministic provider-pack revisions. | +| `libc` / `cpufeatures` | `0.2.189`, `0.3.1` | MIT OR Apache-2.0 | , | Target-specific SHA-256 acceleration support. | +| `serde` / `serde_core` | `1.0.229` | MIT OR Apache-2.0 | | Runtime catalog data types through `stack-theme`. | +| `serde_json` | `1.0.151` | MIT OR Apache-2.0 | | Runtime embedded-catalog decoding through `stack-theme`; also used by formatter conformance tests. | +| `itoa` | `1.0.18` | MIT OR Apache-2.0 | | Transitive runtime dependency of `serde_json`. | +| `memchr` | `2.8.3` | Unlicense OR MIT | | Transitive runtime dependency of `serde_json`. | +| `zmij` | `1.0.23` | MIT | | Transitive runtime dependency of `serde_json`. | +| `wasm-bindgen` / `wasm-bindgen-shared` | `0.2.127` | MIT OR Apache-2.0 | | JavaScript ABI and generated glue shipped by `@stack-sh/engine`. | +| `js-sys` | `0.3.104` | MIT OR Apache-2.0 | | Typed-array input and plain JavaScript result objects; built without default features. | +| `cfg-if` | `1.0.4` | MIT OR Apache-2.0 | | Transitive runtime dependency of `wasm-bindgen` and `js-sys`. | +| `once_cell` | `1.21.4` | MIT OR Apache-2.0 | | Transitive runtime dependency of `wasm-bindgen`. | + +## Build-only dependencies + +| Component | Version | License | Source | Notes | +| --- | --- | --- | --- | --- | +| `serde_derive` | `1.0.229` | MIT OR Apache-2.0 | | Procedural macro used to build `stack-theme`. | +| `proc-macro2` | `1.0.107` | MIT OR Apache-2.0 | | Transitive procedural-macro build dependency. | +| `quote` | `1.0.47` | MIT OR Apache-2.0 | | Transitive procedural-macro build dependency. | +| `syn` | `3.0.4` | MIT OR Apache-2.0 | | Transitive procedural-macro build dependency. | +| `unicode-ident` | `1.0.24` | (MIT OR Apache-2.0) AND Unicode-3.0 | | Transitive procedural-macro build dependency. | +| `wasm-bindgen-macro` / `wasm-bindgen-macro-support` | `0.2.127` | MIT OR Apache-2.0 | | Procedural macro and support code used to build the browser adapter. | +| `bumpalo` | `3.20.3` | MIT OR Apache-2.0 | | Transitive build dependency of `wasm-bindgen-macro-support`. | +| `rustversion` | `1.0.23` | MIT OR Apache-2.0 | | Transitive build dependency of `wasm-bindgen`. | +| `syn` | `2.0.119` | MIT OR Apache-2.0 | | Transitive procedural-macro build dependency of `wasm-bindgen`. | +| `wasm-bindgen-cli` | `0.2.127` | MIT OR Apache-2.0 | | Version-matched build tool; not shipped in the npm package. | +| `typescript` | `7.0.2` | Apache-2.0 | | Type-check tool; not shipped in the npm package. | +| `ajv` | `8.20.0` | MIT | | Validates the checked-in layout corpus schema during development and CI; not shipped in the npm package. | +| `fast-deep-equal` | `3.1.3` | MIT | | Transitive build-only dependency of `ajv`. | +| `fast-uri` | `3.1.7` | BSD-3-Clause | | Transitive build-only dependency of `ajv`. | +| `json-schema-traverse` | `1.0.0` | MIT | | Transitive build-only dependency of `ajv`. | +| `require-from-string` | `2.0.2` | MIT | | Transitive build-only dependency of `ajv`. | + +No third-party visual asset is bundled in a Stack Engine distribution. The bundled fallback and 30 explicit icons are Stack-authored Apache-2.0 assets from `stack-theme`. The npm package includes this inventory and the Apache-2.0, MIT, and Unicode-3.0 license texts required by its compiled dependency choices. + +Before publishing a native library, binary-derived artifact, or WASM package, this inventory must list the shipped dependencies and assets, their pinned versions, exact licenses, required license texts, attribution, modifications, and redistribution conditions. Build-only dependencies that are not shipped should be distinguished from distributed code. diff --git a/docs/cargo-releasing.md b/docs/cargo-releasing.md new file mode 100644 index 0000000..1c38f2b --- /dev/null +++ b/docs/cargo-releasing.md @@ -0,0 +1,14 @@ +# Cargo publication + +The native `stack-formatter` 0.1.0 and `stack-engine` 0.7.0 crates use exact registry dependencies. The workspace retains local paths for development, with registry versions declared for Cargo packaging. `stack-engine-wasm` is not published to crates.io; its browser output remains the npm package. + +## Initial publication + +1. Merge the release preparation and wait for both main CI jobs. Run `node scripts/cargo-package-assets.mjs --check` to verify package notices. +2. Set a short-lived `CARGO_INITIAL_PUBLISH_TOKEN` Actions secret with `publish-new` permission limited to the exact initial crate names. Never put the value into source, workflow inputs, issues, or logs. +3. Dispatch `initial-publish.yaml` on `main` with the full successful `expected_sha` and package `stack-formatter`. The workflow checks repository, ref, commit, package metadata, successful CI, and registry absence before a credential-free package dry run and publication. +4. Verify the formatter's registry version, checksum, source SHA, and registry-only consumer. Then dispatch the same workflow for `stack-engine`. Cargo verifies the engine package against the published formatter, compiler, and theme rather than Git dependencies. +5. Verify the engine registry artifact and a clean consumer on Rust 1.85 and stable. Record immutable `stack-formatter-v0.1.0` and `stack-engine-v0.7.0` source tags without replacing any npm release tag. +6. Remove the bootstrap GitHub secret and revoke the token after initial publications. Configure crate-specific trusted publishers before later releases. The bootstrap workflow cannot publish a second version and never persists a Cargo credential file. + +If Cargo times out after upload, inspect registry state before retrying. Never overwrite a version or tag: investigate failures and publish an explicitly versioned correction. No layout snapshot or language behavior is changed for registry packaging. diff --git a/packages/engine/THIRD_PARTY_LICENSES.md b/packages/engine/THIRD_PARTY_LICENSES.md index 8c73a28..2cbe7d6 100644 --- a/packages/engine/THIRD_PARTY_LICENSES.md +++ b/packages/engine/THIRD_PARTY_LICENSES.md @@ -4,8 +4,8 @@ | Component | Version / revision | Selected license | Source | | --- | --- | --- | --- | -| `stack-compiler` | `4a18fac42afc2256a1bb3a6ff13d12d732a391e7` | Apache-2.0 | | -| `stack-theme` | `2347315e6e86ab9d2708e05fd3f9b5f3d87e1241` | Apache-2.0 | | +| `stack-compiler` | `0.1.0 (crates.io)` | Apache-2.0 | | +| `stack-theme` | `0.5.0 (crates.io)` | Apache-2.0 | | | `roxmltree` | `0.21.1` | Apache-2.0 | | | `sha2`, `digest`, `block-buffer`, `crypto-common`, `hybrid-array`, `const-oid`, `typenum` | `0.11.0`, `0.11.3`, `0.12.1`, `0.2.2`, `0.4.14`, `0.10.2`, `1.20.1` | Apache-2.0 | | | `libc` / `cpufeatures` | `0.2.189`, `0.3.1` | Apache-2.0 | , | diff --git a/scripts/cargo-package-assets.mjs b/scripts/cargo-package-assets.mjs new file mode 100644 index 0000000..0d03ca4 --- /dev/null +++ b/scripts/cargo-package-assets.mjs @@ -0,0 +1,16 @@ +import assert from 'node:assert/strict'; +import { readFile, writeFile } from 'node:fs/promises'; + +for (const name of ['stack-formatter', 'stack-engine']) { + for (const file of ['LICENSE', 'THIRD_PARTY_LICENSES.md']) { + const source = await readFile(new URL(`../${file}`, import.meta.url)); + const target = new URL(`../crates/${name}/${file}`, import.meta.url); + if (process.argv.includes('--check')) assert.deepEqual(await readFile(target), source, `${name}/${file} drift`); + else await writeFile(target, source); + } +} + +const fixture = await readFile(new URL('../tests/fixtures/provider-pack-input.json', import.meta.url)); +const target = new URL('../crates/stack-engine/tests/fixtures/provider-pack-input.json', import.meta.url); +if (process.argv.includes('--check')) assert.deepEqual(await readFile(target), fixture, 'Cargo provider fixture drift'); +else await writeFile(target, fixture); diff --git a/scripts/initial-publish-context.mjs b/scripts/initial-publish-context.mjs new file mode 100644 index 0000000..e3c340f --- /dev/null +++ b/scripts/initial-publish-context.mjs @@ -0,0 +1,29 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +export function validateInitialPublish(metadata, runs, expectedSha, packageName) { + assert.match(expectedSha, /^[a-f0-9]{40}$/); + const versions = { 'stack-formatter': '0.1.0', 'stack-engine': '0.7.0' }; + assert.ok(Object.hasOwn(versions, packageName), 'Unsupported initial crate'); + const selected = metadata.packages.filter(crate => crate.name === packageName); + assert.equal(selected.length, 1, 'Expected one selected package'); + const crate = selected[0]; + assert.equal(crate.version, versions[packageName], 'Only the initial version may use this workflow'); + assert.ok(crate.dependencies.every(dependency => !dependency.source?.startsWith('git+')), 'Git dependencies cannot be published'); + assert.deepEqual(crate.publish, ['crates-io']); + assert.equal(crate.license, 'Apache-2.0'); + assert.equal(crate.rust_version, '1.85'); + assert.equal(runs.length, 1, 'The exact main commit needs a CI run'); + assert.equal(runs[0].headSha, expectedSha); + assert.equal(runs[0].status, 'completed'); + assert.equal(runs[0].conclusion, 'success'); +} + +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const metadata = JSON.parse(await readFile(process.argv[2], 'utf8')); + const runs = JSON.parse(await readFile(process.argv[3], 'utf8')); + validateInitialPublish(metadata, runs, process.env.EXPECTED_SHA, process.env.PACKAGE_NAME); + console.log('Initial package identity and exact-commit CI verified.'); +} diff --git a/scripts/initial-publish-context.test.mjs b/scripts/initial-publish-context.test.mjs new file mode 100644 index 0000000..9c0bdae --- /dev/null +++ b/scripts/initial-publish-context.test.mjs @@ -0,0 +1,33 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { validateInitialPublish } from './initial-publish-context.mjs'; + +const sha = 'a'.repeat(40); +const runs = [{ headSha: sha, status: 'completed', conclusion: 'success' }]; +const packages = [ + { name: 'stack-formatter', version: '0.1.0' }, + { name: 'stack-engine', version: '0.7.0' }, +].map(crate => ({ ...crate, publish: ['crates-io'], license: 'Apache-2.0', rust_version: '1.85', dependencies: [] })); + +test('accepts the two exact initial crates independently', () => { + for (const crate of packages) validateInitialPublish({ packages }, runs, sha, crate.name); +}); +test('rejects missing, stale, incomplete, and failed CI', () => { + for (const invalid of [[], [...runs, ...runs], [{ ...runs[0], headSha: 'b'.repeat(40) }], [{ ...runs[0], status: 'in_progress' }], [{ ...runs[0], conclusion: 'failure' }]]) { + assert.throws(() => validateInitialPublish({ packages }, invalid, sha, 'stack-engine')); + } +}); +test('rejects changed crate, version, registry, license, MSRV, and Git dependency', () => { + for (const crate of packages) { + for (const change of [{ version: '9.0.0' }, { publish: null }, { license: 'MIT' }, { rust_version: '1.86' }, { dependencies: [{ source: 'git+https://example.com/repo' }] }]) { + assert.throws(() => validateInitialPublish({ packages: [{ ...crate, ...change }] }, runs, sha, crate.name)); + } + assert.throws(() => validateInitialPublish({ packages: [] }, runs, sha, crate.name)); + } + assert.throws(() => validateInitialPublish({ packages }, runs, sha, 'stack-engine-wasm')); +}); +test('rejects non-immutable and malformed dispatch identities', () => { + for (const invalid of ['main', 'a'.repeat(39), 'A'.repeat(40), `${sha}\n`, undefined]) { + assert.throws(() => validateInitialPublish({ packages }, runs, invalid, 'stack-engine')); + } +}); diff --git a/tests/wasm.test.mjs b/tests/wasm.test.mjs index 5ba5b5f..edcc98c 100644 --- a/tests/wasm.test.mjs +++ b/tests/wasm.test.mjs @@ -23,7 +23,7 @@ const languageFixturePath = join( repositoryRoot, "tests/fixtures/language-intelligence-cases.json", ); -const providerFixturePath = join(repositoryRoot, "tests/fixtures/provider-pack-input.json"); +const providerFixturePath = join(repositoryRoot, "crates/stack-engine/tests/fixtures/provider-pack-input.json"); const wasmPath = join(repositoryRoot, "packages/engine/dist/stack_engine_bg.wasm"); const cases = JSON.parse(readFileSync(fixturePath, "utf8")); const languageCases = JSON.parse(readFileSync(languageFixturePath, "utf8"));