diff --git a/.github/workflows/README.md b/.github/workflows/README.md index a9ead4a3386..45e7710da60 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -17,7 +17,7 @@ Nightly publishing is split into two coordinated jobs so that npm credentials ne - **`cd-github-releases.yml`** (GitHub Actions) runs nightly via cron (`0 8 * * *` UTC, ~12am PST) and on `workflow_dispatch`. It does **not** bump versions or push source changes to `main` — version bumps land on `main` through ordinary human-authored pull requests (for example, by running `npm run bump` locally and opening a PR). The cron is scheduled ~1 hour before the Azure CD pipeline (09:00 UTC) so any GitHub releases this job creates are picked up by that same night's publish run. The workflow has two jobs: 1. **`detect`** — checks out `main` with `fetch-depth: 0` and runs [`build/scripts/create-github-releases.mjs --check-only`](../../build/scripts/create-github-releases.mjs). The script walks the workspaces tree (no `npm ci` required), computes `${name}_v${version}` for every non-private workspace, and emits `hasMissingReleases=true` if any of those git tags do not yet exist. - 2. **`release`** runs only when missing releases exist. Installs Node, the Rust toolchain (for `cargo package`), and the npm workspace dependencies, builds the repo, then runs the script in default mode. For every missing release the script: packs the npm tarball into `publish_artifacts_npm/`, packs the paired Rust crate (if `crates//Cargo.toml` exists) into `publish_artifacts_crates/`, and creates the GitHub release with both assets attached via `gh release create --target `. The `gh` CLI creates the git tag atomically with the release, so "tag exists" and "release exists" are always the same fact — a failed release is safely retried on the next workflow run, with no orphan tag stranded behind. The script errors if a paired crate's version does not match the npm package's version — but this is purely a safety net: the [`postbump` hook in `beachball.config.js`](../../beachball.config.js) rewrites the crate's `Cargo.toml` (and the matching entry in `Cargo.lock`) automatically whenever `npm run bump` bumps the paired npm package, so the two stay in sync from the same commit. + 2. **`release`** runs only when missing releases exist. Installs Node, the Rust toolchain (for `cargo package`), and the npm workspace dependencies, builds the repo, then runs the script in default mode. For every missing release the script: packs the npm tarball into `publish_artifacts_npm/`, packs any paired Rust crates into `publish_artifacts_crates/`, and creates the GitHub release with all assets attached via `gh release create --target `. `@microsoft/fast-build` is a bundled release: it uses one npm package, one tag, and one GitHub release containing both `microsoft-fast-build` and `microsoft-fast-convert` crate assets. The `gh` CLI creates the git tag atomically with the release, so "tag exists" and "release exists" are always the same fact — a failed release is safely retried on the next workflow run, with no orphan tag stranded behind. The script errors if a paired crate's version does not match the npm package's version — but this is purely a safety net: the [`postbump` hook in `beachball.config.js`](../../beachball.config.js) rewrites each crate's `Cargo.toml` (and the matching entry in `Cargo.lock`) automatically whenever `npm run bump` bumps the paired npm package, so they stay in sync from the same commit. - **`azure-pipelines-cd.yml`** (Azure Pipelines) runs every night at **1am PST (`0 9 * * *` UTC)** with `always: true` so it still runs on no-op nights (it is checking external GitHub state, not repo commits). It is split into two stages so the heavy publish work is skipped on no-op nights: 1. **`Check`** — runs [`build/scripts/download-github-releases.mjs --check-only`](../../build/scripts/download-github-releases.mjs). The script walks the current publishable workspaces, keeps only workspaces whose current `${name}_v${version}` release tag exists, filters out tags that already have a `deployed/` counterpart, and emits Azure Pipelines output variables for the overall deployment decision, npm dist-tag, and each package-specific release tag. No network calls to GitHub, npm, or crates.io are needed. 2. **`Package`** — depends on `Check` and runs only when `needsDeployment == 'true'`. Conditional `DownloadGitHubRelease@0` tasks download undeployed release assets through the `fast` GitHub service connection, a shell step sorts them into `publish_artifacts_npm/` (`.tgz`) and `publish_artifacts_crates/` (`.crate`), configures npm to publish companion packages with the detected dist-tag, then `FAST.Release.PipelineTemplate.yml@fastPipelines` performs the actual `npm publish` / `cargo publish`. On success, the pipeline pushes a `deployed/` git marker tag for each release that was just published. The next nightly run will see those markers and skip the corresponding releases. @@ -33,7 +33,7 @@ The `npm run checkchange` command runs `build/scripts/check-publish-pipeline.mjs When adding a new non-private workspace that should publish through CD: 1. Ensure the workspace is included in the root `package.json` `workspaces` list and has a `name` and `version`. -2. If the package has a paired crate, place it at `crates//Cargo.toml`, where `` is the npm package name with the leading `@` removed and `/` replaced by `-`. For example, `@microsoft/fast-build` pairs with `crates/microsoft-fast-build/Cargo.toml`. +2. If the package has paired crate assets, place each crate at `crates//Cargo.toml`. By default, `` is the npm package name with the leading `@` removed and `/` replaced by `-`. `@microsoft/fast-build` is the special bundled release and pairs with both `crates/microsoft-fast-build/Cargo.toml` and `crates/microsoft-fast-convert/Cargo.toml`. 3. Add package-specific output variables to the `Package` stage in `azure-pipelines-cd.yml`. The output prefix is generated from the npm package name by converting `@microsoft/` to camel case. For example, `@microsoft/fast-foo` emits `fastFooNeedsDeployment` and `fastFooReleaseTag`. 4. Add a conditional `DownloadGitHubRelease@0` task for the package using the `fast` GitHub service connection, `defaultVersionType: 'specificTag'`, and the package's `$(ReleaseTag)` variable. 5. Confirm the artifact sorting step still covers the package assets. Packages should attach `.tgz` assets, and paired crates should also attach `.crate` assets. diff --git a/.github/workflows/ci-validate-rust.yml b/.github/workflows/ci-validate-rust.yml index 1e610ff5227..cf0c088b449 100644 --- a/.github/workflows/ci-validate-rust.yml +++ b/.github/workflows/ci-validate-rust.yml @@ -23,7 +23,18 @@ on: jobs: test_rust: if: github.event_name != 'pull_request' || !github.event.pull_request.draft + name: test_rust (${{ matrix.crate }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - crate: microsoft-fast-build + manifest-path: crates/microsoft-fast-build/Cargo.toml + cache-workspace: crates/microsoft-fast-build + - crate: microsoft-fast-convert + manifest-path: crates/microsoft-fast-convert/Cargo.toml + cache-workspace: crates/microsoft-fast-convert steps: - uses: actions/checkout@v4 @@ -34,7 +45,7 @@ jobs: - name: Cache Rust build artifacts uses: Swatinem/rust-cache@v2 with: - workspaces: crates/microsoft-fast-build + workspaces: ${{ matrix.cache-workspace }} - name: Run Rust tests - run: cargo test --manifest-path crates/microsoft-fast-build/Cargo.toml + run: cargo test --manifest-path ${{ matrix.manifest-path }} diff --git a/beachball.config.js b/beachball.config.js index da51b2dbeab..1f756bbd494 100644 --- a/beachball.config.js +++ b/beachball.config.js @@ -11,6 +11,14 @@ function npmNameToCrateName(npmName) { return npmName.replace(/^@/, "").replace(/\//g, "-"); } +const bundledCratesByPackage = new Map([ + ["@microsoft/fast-build", ["microsoft-fast-build", "microsoft-fast-convert"]], +]); + +function npmNameToCrateNames(npmName) { + return bundledCratesByPackage.get(npmName) ?? [npmNameToCrateName(npmName)]; +} + /** * Rewrite the `version = "..."` line for a specific `[package]` or * `[[package]]` block (matched by the crate name) within Cargo TOML @@ -66,8 +74,8 @@ function rewriteCargoVersion(content, crateName, newVersion, { manifest }) { } /** - * Beachball `postbump` hook: when an npm package with a paired Rust - * crate is bumped, rewrite the crate's `Cargo.toml` (and matching entry + * Beachball `postbump` hook: when an npm package with paired Rust + * crates is bumped, rewrite each crate's `Cargo.toml` (and matching entry * in `Cargo.lock`, if present) so the crate version stays in lock-step * with the npm version. * @@ -75,32 +83,33 @@ function rewriteCargoVersion(content, crateName, newVersion, { manifest }) { * Cargo updates land in the same PR as the package.json bump. */ function syncPairedCrateVersion(packagePath, name, version) { - const crateName = npmNameToCrateName(name); - const cargoTomlPath = join(__dirname, "crates", crateName, "Cargo.toml"); - if (!existsSync(cargoTomlPath)) return; - - const updatedToml = rewriteCargoVersion( - readFileSync(cargoTomlPath, "utf8"), - crateName, - version, - { manifest: true }, - ); - if (updatedToml !== null) { - writeFileSync(cargoTomlPath, updatedToml); - console.log(`[beachball] Synced ${cargoTomlPath} to ${version}`); - } + for (const crateName of npmNameToCrateNames(name)) { + const cargoTomlPath = join(__dirname, "crates", crateName, "Cargo.toml"); + if (!existsSync(cargoTomlPath)) continue; - const cargoLockPath = join(__dirname, "crates", crateName, "Cargo.lock"); - if (existsSync(cargoLockPath)) { - const updatedLock = rewriteCargoVersion( - readFileSync(cargoLockPath, "utf8"), + const updatedToml = rewriteCargoVersion( + readFileSync(cargoTomlPath, "utf8"), crateName, version, - { manifest: false }, + { manifest: true }, ); - if (updatedLock !== null) { - writeFileSync(cargoLockPath, updatedLock); - console.log(`[beachball] Synced ${cargoLockPath} to ${version}`); + if (updatedToml !== null) { + writeFileSync(cargoTomlPath, updatedToml); + console.log(`[beachball] Synced ${cargoTomlPath} to ${version}`); + } + + const cargoLockPath = join(__dirname, "crates", crateName, "Cargo.lock"); + if (existsSync(cargoLockPath)) { + const updatedLock = rewriteCargoVersion( + readFileSync(cargoLockPath, "utf8"), + crateName, + version, + { manifest: false }, + ); + if (updatedLock !== null) { + writeFileSync(cargoLockPath, updatedLock); + console.log(`[beachball] Synced ${cargoLockPath} to ${version}`); + } } } } diff --git a/build/scripts/create-github-releases.mjs b/build/scripts/create-github-releases.mjs index 0765db880a6..669e5b04257 100644 --- a/build/scripts/create-github-releases.mjs +++ b/build/scripts/create-github-releases.mjs @@ -11,19 +11,20 @@ * workspace's `package.json` (no `node_modules` required, so the * `--check-only` mode can run before `npm ci`). * 2. Skips workspaces whose package.json sets `private: true`. - * 3. For each remaining workspace, looks for a paired Rust crate at - * `crates//Cargo.toml`, where the crate name is derived - * from the npm name by dropping the leading `@` and replacing `/` - * with `-` (so `@microsoft/fast-build` -> `microsoft-fast-build`). - * When a pair exists, the script errors if the two versions are not - * identical, forcing the version-bump PR to keep them in sync. + * 3. For each remaining workspace, looks for paired Rust crates at + * `crates//Cargo.toml`. Most crate names are derived from + * the npm name by dropping the leading `@` and replacing `/` with `-`; + * `@microsoft/fast-build` bundles both `microsoft-fast-build` and + * `microsoft-fast-convert` into the same release. When a pair exists, + * the script errors if the two versions are not identical, forcing the + * version-bump PR to keep them in sync. * 4. Computes `tag = ${name}_v${version}` (matching beachball's tag * format) and skips the workspace if the git tag already exists * (idempotent across re-runs). * 5. Otherwise (when not `--check-only`) packs the npm tarball into * `publish_artifacts_npm/` with `npm pack`, optionally packs the - * paired crate into `publish_artifacts_crates/` with - * `cargo package`, and creates the GitHub release with both + * paired crates into `publish_artifacts_crates/` with + * `cargo package`, and creates the GitHub release with all * assets attached (`gh release create --target `). The `gh` * CLI creates the git tag atomically with the release, so the * tag and the release exist if and only if each other does. @@ -76,6 +77,14 @@ function npmNameToCrateName(npmName) { return npmName.replace(/^@/, "").replace(/\//g, "-"); } +const bundledCratesByPackage = new Map([ + ["@microsoft/fast-build", ["microsoft-fast-build", "microsoft-fast-convert"]], +]); + +function npmNameToCrateNames(npmName) { + return bundledCratesByPackage.get(npmName) ?? [npmNameToCrateName(npmName)]; +} + function shouldSkipCrates() { return process.env.FAST_RELEASE_SKIP_CRATES === "true"; } @@ -96,6 +105,29 @@ function readCargoTomlVersion(cargoTomlPath) { return null; } +function listPairedCrates(pkgName, pkgVersion) { + if (shouldSkipCrates()) return []; + + const crates = []; + for (const crateName of npmNameToCrateNames(pkgName)) { + const cargoTomlPath = join("crates", crateName, "Cargo.toml"); + if (!existsSync(cargoTomlPath)) continue; + + const crateVersion = readCargoTomlVersion(cargoTomlPath); + if (crateVersion !== pkgVersion) { + throw new Error( + `Version mismatch for ${pkgName}: package.json is ${pkgVersion} ` + + `but ${cargoTomlPath} is ${crateVersion}. ` + + "Update one to match the other.", + ); + } + + crates.push({ crateName, cargoTomlPath }); + } + + return crates; +} + function listPublishableWorkspaces() { const rootPkg = JSON.parse(readFileSync("package.json", "utf8")); const patterns = rootPkg.workspaces || []; @@ -123,25 +155,13 @@ function listPublishableWorkspaces() { if (pkg.private === true) continue; if (!pkg.name || !pkg.version) continue; - const crateName = npmNameToCrateName(pkg.name); - const cargoTomlPath = join("crates", crateName, "Cargo.toml"); - const hasCrate = existsSync(cargoTomlPath) && !shouldSkipCrates(); - - if (hasCrate) { - const crateVersion = readCargoTomlVersion(cargoTomlPath); - if (crateVersion !== pkg.version) { - throw new Error( - `Version mismatch for ${pkg.name}: package.json is ${pkg.version} but ${cargoTomlPath} is ${crateVersion}. Update one to match the other.`, - ); - } - } + const crates = listPairedCrates(pkg.name, pkg.version); workspaces.push({ location, name: pkg.name, version: pkg.version, - crateName: hasCrate ? crateName : null, - cargoTomlPath: hasCrate ? cargoTomlPath : null, + crates, }); } @@ -174,8 +194,11 @@ console.log(`Missing git tag / release: ${missing.length}`); if (missing.length > 0) { console.log("\nPackages that need a release:"); - for (const { name, version, crateName } of missing) { - const suffix = crateName ? ` (+ crate ${crateName})` : ""; + for (const { name, version, crates } of missing) { + const suffix = + crates.length > 0 + ? ` (+ crates ${crates.map(crate => crate.crateName).join(", ")})` + : ""; console.log(` - ${name}@${version}${suffix}`); } } @@ -201,7 +224,7 @@ mkdirSync(CRATES_DIR, { recursive: true }); let created = 0; let hasErrors = false; -for (const { name, version, location, crateName, cargoTomlPath } of missing) { +for (const { name, version, location, crates } of missing) { const tag = `${name}_v${version}`; const assets = []; @@ -216,7 +239,7 @@ for (const { name, version, location, crateName, cargoTomlPath } of missing) { ]); assets.push(join(NPM_DIR, JSON.parse(packJson)[0].filename)); - if (cargoTomlPath) { + for (const { crateName, cargoTomlPath } of crates) { console.log(`Packaging crate ${crateName}@${version}...`); run( "cargo", @@ -251,7 +274,7 @@ for (const { name, version, location, crateName, cargoTomlPath } of missing) { "", "Version bumps were landed via a regular pull request. The attached", "assets will be downloaded and published to npm" + - (cargoTomlPath ? " and crates.io" : "") + + (crates.length > 0 ? " and crates.io" : "") + " by the nightly Azure release pipeline.", ].join("\n"); diff --git a/build/scripts/download-github-releases.mjs b/build/scripts/download-github-releases.mjs index 35234e5f518..bb4eeda8642 100644 --- a/build/scripts/download-github-releases.mjs +++ b/build/scripts/download-github-releases.mjs @@ -27,6 +27,11 @@ * versions plus matching release tags. This keeps historical bare beachball tags * from being treated as deployable releases while still working on a * freshly-cloned 1ES agent with no `node_modules` or cargo registry. + * + * Most workspaces map to at most one crate by name convention. + * `@microsoft/fast-build` intentionally maps to both `microsoft-fast-build` + * and `microsoft-fast-convert`, but still uses one npm package release tag + * and one Azure download task. */ import { execFileSync } from "node:child_process"; @@ -62,6 +67,14 @@ function npmNameToCrateName(npmName) { return npmName.replace(/^@/, "").replace(/\//g, "-"); } +const bundledCratesByPackage = new Map([ + ["@microsoft/fast-build", ["microsoft-fast-build", "microsoft-fast-convert"]], +]); + +function npmNameToCrateNames(npmName) { + return bundledCratesByPackage.get(npmName) ?? [npmNameToCrateName(npmName)]; +} + function shouldSkipCrates() { return process.env.FAST_RELEASE_SKIP_CRATES === "true"; } @@ -88,6 +101,24 @@ function readCargoTomlVersion(cargoTomlPath) { return null; } +function validatePairedCrates(pkgName, pkgVersion) { + if (shouldSkipCrates()) return; + + for (const crateName of npmNameToCrateNames(pkgName)) { + const cargoTomlPath = join("crates", crateName, "Cargo.toml"); + if (!existsSync(cargoTomlPath)) continue; + + const crateVersion = readCargoTomlVersion(cargoTomlPath); + if (crateVersion !== pkgVersion) { + throw new Error( + `Version mismatch for ${pkgName}: package.json is ${pkgVersion} ` + + `but ${cargoTomlPath} is ${crateVersion}. ` + + "Update one to match the other.", + ); + } + } +} + function listPublishableWorkspaces() { const rootPkg = JSON.parse(readFileSync("package.json", "utf8")); const patterns = rootPkg.workspaces || []; @@ -115,18 +146,7 @@ function listPublishableWorkspaces() { if (pkg.private === true) continue; if (!pkg.name || !pkg.version) continue; - const crateName = npmNameToCrateName(pkg.name); - const cargoTomlPath = join("crates", crateName, "Cargo.toml"); - const hasCrate = existsSync(cargoTomlPath) && !shouldSkipCrates(); - - if (hasCrate) { - const crateVersion = readCargoTomlVersion(cargoTomlPath); - if (crateVersion !== pkg.version) { - throw new Error( - `Version mismatch for ${pkg.name}: package.json is ${pkg.version} but ${cargoTomlPath} is ${crateVersion}. Update one to match the other.`, - ); - } - } + validatePairedCrates(pkg.name, pkg.version); workspaces.push({ location, diff --git a/change/@microsoft-fast-build-40280b6e-6a6e-4d63-92ce-3158204cb0e0.json b/change/@microsoft-fast-build-40280b6e-6a6e-4d63-92ce-3158204cb0e0.json new file mode 100644 index 00000000000..44a4fb52a0c --- /dev/null +++ b/change/@microsoft-fast-build-40280b6e-6a6e-4d63-92ce-3158204cb0e0.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add FAST declarative template conversion to the fast CLI.", + "packageName": "@microsoft/fast-build", + "email": "7559015+janechu@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/change/@microsoft-fast-element-37f5e2c1-9e6e-4fc8-aee0-2e7dfb2d5a15.json b/change/@microsoft-fast-element-37f5e2c1-9e6e-4fc8-aee0-2e7dfb2d5a15.json new file mode 100644 index 00000000000..baca5688274 --- /dev/null +++ b/change/@microsoft-fast-element-37f5e2c1-9e6e-4fc8-aee0-2e7dfb2d5a15.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Update WebUI integration fixtures to generate templates with fast convert.", + "packageName": "@microsoft/fast-element", + "email": "7559015+janechu@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/crates/microsoft-fast-convert/Cargo.lock b/crates/microsoft-fast-convert/Cargo.lock new file mode 100644 index 00000000000..45d3a0bf751 --- /dev/null +++ b/crates/microsoft-fast-convert/Cargo.lock @@ -0,0 +1,114 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "microsoft-fast-convert" +version = "0.9.0" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] diff --git a/crates/microsoft-fast-convert/Cargo.toml b/crates/microsoft-fast-convert/Cargo.toml new file mode 100644 index 00000000000..0dc3fb33fee --- /dev/null +++ b/crates/microsoft-fast-convert/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "microsoft-fast-convert" +version = "0.9.0" +edition = "2021" +description = "Converter for FAST declarative HTML templates into WebUI prerelease HTML or FAST v3 TypeScript templates." +license = "MIT" +readme = "README.md" +homepage = "https://www.fast.design/" +repository = "https://github.com/microsoft/fast" +keywords = ["fast", "html", "template", "converter", "web-components"] +categories = ["template-engine", "web-programming"] + +[lib] +name = "microsoft_fast_convert" +path = "src/lib.rs" +crate-type = ["cdylib", "rlib"] + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" diff --git a/crates/microsoft-fast-convert/DESIGN.md b/crates/microsoft-fast-convert/DESIGN.md new file mode 100644 index 00000000000..4d1ade6d30c --- /dev/null +++ b/crates/microsoft-fast-convert/DESIGN.md @@ -0,0 +1,55 @@ +# Design — microsoft-fast-convert + +`microsoft-fast-convert` converts from FAST declarative syntax only. It accepts one FAST `` string, validates the supported subset, and emits either WebUI prerelease HTML or FAST v3 TypeScript source. + +## Pipeline + +```text +convert_template(template, syntax) + │ + ├─ parse syntax (`webui-prerelease` | `fast-v3-ts`) + ├─ validate one outer `` + ├─ extract exactly one inner `