diff --git a/.agents/upstream-tracking.md b/.agents/upstream-tracking.md index c8bd387a..8d425112 100644 --- a/.agents/upstream-tracking.md +++ b/.agents/upstream-tracking.md @@ -7,18 +7,19 @@ Upstream repos fall into three groups, and the group decides the procedure. | | Vendored (submodule) | Watched | Reference | |---|---|---|---| | Which | `motoko`, `internetidentity`, `examples` | the `watched` array in `.sources/upstream.json` | the `reference` array | -| Why | the build opens their files | a release can silently invalidate a lot of published content | drawn on too lightly for a weekly issue | +| Why | the build opens their files | a release can silently invalidate a lot of published content | drawn on too lightly, or another check already catches the drift | | Pin | the gitlink | `pinned` in `upstream.json` | none; verify against the latest release | -| Notified | yes | yes | no | +| Release issue | only `examples`, and only when a quoted file moved | yes | no | Deciding between the last two is a judgment about blast radius, and the `why` field on each `reference` entry records the footprint that decided it. Promote an entry to `watched` if its footprint grows. `motoko` and `internetidentity` have their own weekly sync workflows that open -the bump PR directly. `examples` and the `watched` repos are covered by the -weekly **Upstream release check**, which opens an issue. `reference` repos are -not checked at all. +the bump PR directly. The `watched` repos are covered by the weekly **Upstream +release check**, which opens an issue whenever one moves. That same check covers +`examples`, but opens an issue only when the range touched a file a `snippet=` +quotes. `reference` repos are not checked at all. ## Why only three are vendored @@ -167,10 +168,14 @@ When `icp-cli` moves to a new minor: Only the project maintainer bumps submodule refs. -`examples` tracks a branch and is checked by the same **Upstream release check** -workflow, which opens an issue when the gitlink falls behind that branch. Its pin -lives in git, so `upstream.json` records only the branch to compare against and -what a bump affects. +`examples` is checked, but only for the case the build cannot see. A `snippet=` +path or `#region` marker that stops resolving already fails the build. What the +build cannot notice is an example being corrected upstream while still +resolving, which leaves the docs quoting old code with everything green. So its +entry carries `pathFilter: "snippets"`: the check intersects the files changed +between the pinned gitlink and the branch head with every file a `snippet=` +quotes, and opens an issue only when they overlap, naming the files. Any other +commit on that repo stays silent. `motoko` and `internetidentity` are not in `upstream.json`: `sync-motoko.yml` and `sync-ii-spec.yml` already check for a new release, run the sync, and open the diff --git a/.sources/examples b/.sources/examples index b4fe175c..42c474dd 160000 --- a/.sources/examples +++ b/.sources/examples @@ -1 +1 @@ -Subproject commit b4fe175c14b3755698d56ece8c4dd353824538bb +Subproject commit 42c474ddf77dbe54f8a5dbe80827559a9c1da691 diff --git a/.sources/upstream.json b/.sources/upstream.json index 0e8de111..454d8522 100644 --- a/.sources/upstream.json +++ b/.sources/upstream.json @@ -2,37 +2,44 @@ "$comment": [ "Every upstream this repo tracks, in three groups.", "", - "`vendored` are submodules whose pin has no sync workflow of its own. Their pin", - "is the gitlink in git, not a field here, so it has one source of truth; the entry", - "records which branch to compare against and what a bump affects. `motoko` and", - "`internetidentity` are absent on purpose: sync-motoko.yml and sync-ii-spec.yml", - "already check for a new release and open the bump PR themselves.", - "", "`watched` are repos where a release can silently invalidate a lot of published", "content, so they get a pinned ref and a weekly issue when they move. Verify", "claims against the pinned ref, not against main, so a review is reproducible.", "", + "`vendored` are submodules whose pin has no sync workflow of its own. Their pin", + "is the gitlink, so git stays the single source of truth. `motoko` and", + "`internetidentity` are absent on purpose: sync-motoko.yml and sync-ii-spec.yml", + "already release-check and open the bump PR themselves.", + "", "`reference` are repos the docs draw on too lightly to be worth a weekly issue:", "each `why` records the footprint that decided it. They carry no pin. Verify", - "against the latest release on demand; the surface is small enough that the next", - "edit to those pages catches any drift. Promote one to `watched` if its footprint", - "grows.", + "against the latest release on demand. Promote one if that changes.", "", - "scripts/check-upstream-releases.mjs checks `vendored` and `watched` only, and", + "scripts/check-upstream-releases.mjs checks `watched` and `vendored`, and", ".github/workflows/upstream-releases.yml opens an issue per repo that moved.", "See AGENTS.md 'Source material' and .agents/upstream-tracking.md.", "", "Fields (watched):", " repo owner/name on GitHub", + " name optional, when one repo releases several things independently", " pinned the ref docs are currently verified against", " track where releases appear: 'release' (git tags matching tagPattern),", " 'crate' (crates.io, needs `crate`), 'npm' (npm registry, needs", - " `package`), or 'commit' (no releases; the default branch head).", - " Entries in `vendored` use 'branch' and name the branch instead.", + " `package`), or 'commit' (no releases; the default branch head)", " tagPattern anchored JS regex selecting the tags that count as a release", " verify file whose diff between the two refs is the review payload", " affects what to re-check in docs/ when this moves", - " reference authoritative published docs for the topic" + " reference authoritative published docs for the topic, deliberately the", + " living version rather than a pinned one: an issue exists because a", + " NEW release shipped, so a maintainer opening it wants the docs for", + " that release, not for the version being replaced. This is why the", + " icp-cli entry uses the bare root while AGENTS.md requires a", + " versioned slug in docs/ pages, and why every other entry here", + " points at docs.rs/latest, mops.one, js.icp.build or a branch.", + "", + "Fields (vendored): path, repo, track 'branch', branch, affects, and an optional", + "pathFilter. 'snippets' reports only when the range touched a file quoted by a", + "`snippet=` in docs/, so an unrelated commit on an active repo stays silent." ], "vendored": [ { @@ -40,22 +47,24 @@ "repo": "dfinity/examples", "track": "branch", "branch": "master", - "affects": "Every `snippet=` path and `#region` marker resolved at build time by plugins/remark-snippet.mjs. A renamed region is a build error, so verify each one after bumping." + "pathFilter": "snippets", + "affects": "Bump the submodule and re-read the affected pages: the example moved on, so the code the docs quote is behind. The build already fails if a `snippet=` path or `#region` marker stops resolving, so this reports the case the build cannot see, an example that changed while still resolving.", + "reference": "https://github.com/dfinity/examples" } ], "watched": [ { "repo": "dfinity/icp-cli", - "pinned": "v1.1.0", + "pinned": "v1.3.0", "track": "release", "tagPattern": "^v\\d+\\.\\d+\\.\\d+$", "verify": "docs/reference/cli.md", - "affects": "Every `icp` command and flag in docs/. A new minor also moves the cli.internetcomputer.org link slug (AGENTS.md 'Linking rules'). NOTE: the doc links were moved to the 1.3 slug in #354, but the commands themselves were last verified against v1.1.0, so the first bump of this pin carries that verification pass.", + "affects": "Every `icp` command and flag in docs/. A new minor also moves the cli.internetcomputer.org link slug (AGENTS.md 'Linking rules'), so check the slug procedure in .agents/upstream-tracking.md when the minor changes.", "reference": "https://cli.internetcomputer.org/" }, { "repo": "dfinity/motoko-core", - "pinned": "v2.4.0", + "pinned": "v2.6.1", "track": "release", "tagPattern": "^v\\d+\\.\\d+\\.\\d+$", "affects": "Every `mo:core` import and API signature in Motoko code blocks.", @@ -64,7 +73,7 @@ }, { "repo": "dfinity/cdk-rs", - "pinned": "0.20.1", + "pinned": "0.20.2", "track": "crate", "crate": "ic-cdk", "affects": "`ic-cdk` APIs in Rust code blocks. The repo stopped tagging releases (its newest bare-semver tag is two minors behind the published crate), so the crate version on crates.io is the release identity. Read the sections newer than the pin in https://github.com/dfinity/cdk-rs/blob/master/ic-cdk/CHANGELOG.md, then grep docs/ for the symbols they name. `ic-cdk-timers` and `ic-cdk-executor` version separately; check whether they moved too.", @@ -72,7 +81,7 @@ }, { "repo": "dfinity/icp-js-core", - "pinned": "v5.4.0", + "pinned": "v6.1.0", "track": "release", "tagPattern": "^v\\d+\\.\\d+\\.\\d+$", "verify": "CHANGELOG.md", @@ -81,7 +90,7 @@ }, { "repo": "dfinity/icp-js-canisters", - "pinned": "3.5.0", + "pinned": "3.6.0", "track": "npm", "package": "@icp-sdk/canisters", "affects": "`@icp-sdk/canisters` APIs, notably `AssetManager` in references/application-canisters.md and guides/frontends/asset-canister.md, which states a minimum of 3.5.0. This repo's semver tags lag its published versions, so npm is the release signal. Changelog: https://github.com/dfinity/icp-js-canisters/blob/main/CHANGELOG.md", @@ -90,7 +99,7 @@ { "repo": "dfinity/icp-cli-recipes", "name": "asset-canister", - "pinned": "asset-canister-v2.2.1", + "pinned": "asset-canister-v2.3.0", "track": "release", "tagPattern": "^asset-canister-v\\d+\\.\\d+\\.\\d+$", "affects": "Cited in 10 places across docs/, including references/application-canisters.md and guides/frontends/asset-canister.md. The legacy frontend recipe; static-site supersedes it. Each recipe in this repo tags and releases on its own prefix, so it gets its own entry: a single pattern across prefixes would compare `rust-` against `static-site-` textually and only ever surface one of them.", @@ -108,7 +117,7 @@ { "repo": "dfinity/icp-cli-recipes", "name": "rust", - "pinned": "rust-v3.3.0", + "pinned": "rust-v3.4.0", "track": "release", "tagPattern": "^rust-v\\d+\\.\\d+\\.\\d+$", "affects": "Cited in 6 places as the Rust canister recipe in icp.yaml examples. Each recipe in this repo tags and releases on its own prefix, so it gets its own entry: a single pattern across prefixes would compare `rust-` against `static-site-` textually and only ever surface one of them.", @@ -117,7 +126,7 @@ { "repo": "dfinity/icp-cli-recipes", "name": "motoko", - "pinned": "motoko-v5.0.0", + "pinned": "motoko-v5.1.0", "track": "release", "tagPattern": "^motoko-v\\d+\\.\\d+\\.\\d+$", "affects": "Cited in 3 places as the Motoko canister recipe in icp.yaml examples. Each recipe in this repo tags and releases on its own prefix, so it gets its own entry: a single pattern across prefixes would compare `rust-` against `static-site-` textually and only ever surface one of them.", @@ -126,7 +135,7 @@ { "repo": "dfinity/icp-cli-recipes", "name": "prebuilt", - "pinned": "prebuilt-v2.0.0", + "pinned": "prebuilt-v2.1.0", "track": "release", "tagPattern": "^prebuilt-v\\d+\\.\\d+\\.\\d+$", "affects": "Cited once, in guides/canister-management/reproducible-builds.md. Each recipe in this repo tags and releases on its own prefix, so it gets its own entry: a single pattern across prefixes would compare `rust-` against `static-site-` textually and only ever surface one of them.", diff --git a/AGENTS.md b/AGENTS.md index f5900d83..1d9a3b7f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -229,9 +229,10 @@ git submodule update --init --depth 1 # do NOT use --recursive | Code examples (`snippet=`, ``) | `.sources/examples/` | Pinned versions: [`.sources/VERSIONS`](.sources/VERSIONS). `motoko` and -`internetidentity` are release-checked and synced by their own workflows; -`examples` tracks a branch and is checked by the weekly **Upstream release -check**. Canister IDs and code patterns are in the skills (see "Skills"). +`internetidentity` are release-checked and synced by their own workflows. +`examples` is checked weekly, but reports only when a commit touches a file a +`snippet=` quotes; the build already fails if such a path or `#region` marker +stops resolving. Canister IDs and code patterns are in the skills (see "Skills"). **Watched** are the repos where a release can silently invalidate a lot of what is published, so [`.sources/upstream.json`](.sources/upstream.json) records the diff --git a/docs/getting-started/project-structure.mdx b/docs/getting-started/project-structure.mdx index 894f9d58..61e3b455 100644 --- a/docs/getting-started/project-structure.mdx +++ b/docs/getting-started/project-structure.mdx @@ -87,7 +87,7 @@ The Motoko backend uses the `@dfinity/motoko` recipe, which compiles via `mops b ```yaml name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" ``` The recipe takes no source or Candid configuration. Instead, the source file (and optionally the Candid file and compiler flags) are declared in a `mops.toml` at the project root. The `[canisters]` key must match the canister `name`: @@ -106,7 +106,7 @@ The Rust backend uses the `@dfinity/rust` recipe: ```yaml name: backend recipe: - type: "@dfinity/rust@v3.3.0" + type: "@dfinity/rust@v3.4.0" configuration: shrink: true candid: backend.did @@ -123,7 +123,7 @@ The frontend uses the `@dfinity/asset-canister` recipe, which builds the fronten ```yaml name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: build: - npm install diff --git a/docs/guides/canister-calls/candid.mdx b/docs/guides/canister-calls/candid.mdx index cf38a8a0..b32de164 100644 --- a/docs/guides/canister-calls/candid.mdx +++ b/docs/guides/canister-calls/candid.mdx @@ -110,7 +110,7 @@ You can also provide a hand-written `.did` file by setting the `candid` field fo canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" ``` Declare the source and Candid file under the canister's entry in `mops.toml`: @@ -166,7 +166,7 @@ Reference the generated `.did` file in your `icp.yaml`: canisters: - name: my_canister recipe: - type: "@dfinity/rust@v3.3.0" + type: "@dfinity/rust@v3.4.0" configuration: candid: src/my_canister/my_canister.did ``` diff --git a/docs/guides/canister-calls/inter-canister-calls.mdx b/docs/guides/canister-calls/inter-canister-calls.mdx index 0d1cdc5e..8b9a5c95 100644 --- a/docs/guides/canister-calls/inter-canister-calls.mdx +++ b/docs/guides/canister-calls/inter-canister-calls.mdx @@ -201,7 +201,7 @@ Deployment order does not matter: `icp deploy` creates all canisters first, then **Init arguments.** Accept the target `Principal` as an `#[init]` argument and store it. This avoids the environment variable lookup at call time but requires passing the ID at every deploy and upgrade: ```bash -TARGET_ID=$(icp canister id counter) +TARGET_ID=$(icp canister status counter -i) icp deploy my_canister --argument "(principal \"$TARGET_ID\")" ``` diff --git a/docs/guides/canister-management/large-wasm.md b/docs/guides/canister-management/large-wasm.md index eeb9bdeb..9ed9c6fd 100644 --- a/docs/guides/canister-management/large-wasm.md +++ b/docs/guides/canister-management/large-wasm.md @@ -34,7 +34,7 @@ The Rust and prebuilt recipes expose a `compress` flag that gzip-compresses the canisters: - name: backend recipe: - type: "@dfinity/rust@v3.3.0" + type: "@dfinity/rust@v3.4.0" configuration: shrink: true compress: true diff --git a/docs/guides/canister-management/logs.md b/docs/guides/canister-management/logs.md index 85727496..f0dfd4ae 100644 --- a/docs/guides/canister-management/logs.md +++ b/docs/guides/canister-management/logs.md @@ -155,7 +155,7 @@ You can configure log visibility per canister in `icp.yaml` so it is applied on canisters: - name: backend recipe: - type: "@dfinity/rust@v3.3.0" + type: "@dfinity/rust@v3.4.0" settings: log_visibility: controllers # "controllers" | "public" | allowed_viewers object ``` diff --git a/docs/guides/canister-management/optimization.md b/docs/guides/canister-management/optimization.md index d280082d..90268576 100644 --- a/docs/guides/canister-management/optimization.md +++ b/docs/guides/canister-management/optimization.md @@ -26,7 +26,7 @@ This guide covers the main tools and techniques available: canisters: - name: backend recipe: - type: "@dfinity/rust@v3.3.0" + type: "@dfinity/rust@v3.4.0" configuration: shrink: true ``` diff --git a/docs/guides/canister-management/reproducible-builds.md b/docs/guides/canister-management/reproducible-builds.md index 5f26b566..87872dc5 100644 --- a/docs/guides/canister-management/reproducible-builds.md +++ b/docs/guides/canister-management/reproducible-builds.md @@ -187,7 +187,7 @@ If you have already built a Wasm and computed its hash, you can deploy it using canisters: - name: my-canister recipe: - type: "@dfinity/prebuilt@v2.0.0" + type: "@dfinity/prebuilt@v2.1.0" configuration: path: dist/my-canister.wasm sha256: d7c1aba0de1d7152897aeca49bd5fe89a174b076a0ee1cc3b9e45fcf6bde71a6 diff --git a/docs/guides/chain-fusion/bitcoin.mdx b/docs/guides/chain-fusion/bitcoin.mdx index a4487114..f0f82fcd 100644 --- a/docs/guides/chain-fusion/bitcoin.mdx +++ b/docs/guides/chain-fusion/bitcoin.mdx @@ -47,6 +47,7 @@ import Blob "mo:core/Blob"; import Nat8 "mo:core/Nat8"; import Array "mo:core/Array"; import Runtime "mo:core/Runtime"; +import Nat "mo:core/Nat"; persistent actor Self { @@ -63,11 +64,11 @@ persistent actor Self { let bytes = Blob.toArray(Principal.toBlob(p)); let size = bytes.size(); let sub = Array.tabulate(32, func(i : Nat) : Nat8 { - if (i == 0) { Nat8.fromNat(size) } + if (i == 0) { Nat.toNat8(size) } else if (i <= size) { bytes[i - 1] } else { 0 } }); - Blob.fromArray(sub) + Array.toBlob(sub) }; public shared ({ caller }) func getDepositAddress() : async Text { diff --git a/docs/guides/chain-fusion/chain-fusion-signer.md b/docs/guides/chain-fusion/chain-fusion-signer.md index 631ea161..bf81396a 100644 --- a/docs/guides/chain-fusion/chain-fusion-signer.md +++ b/docs/guides/chain-fusion/chain-fusion-signer.md @@ -14,7 +14,7 @@ The signer charges callers in cycles for each API call. You pre-approve the sign ## Prerequisites - An ICP identity with cycles in the [Cycles Ledger](../../references/system-canisters.md#cycles-ledger) (`um5iw-rqaaa-aaaaq-qaaba-cai`) -- icp-cli installed and authenticated (`icp identity whoami`) +- icp-cli installed and authenticated (`icp identity principal`) - For offline address derivation: Node.js and `npx` ## Approve payment diff --git a/docs/guides/chain-fusion/ethereum.mdx b/docs/guides/chain-fusion/ethereum.mdx index 637edfe3..4dfbaf9d 100644 --- a/docs/guides/chain-fusion/ethereum.mdx +++ b/docs/guides/chain-fusion/ethereum.mdx @@ -237,6 +237,7 @@ To read an ERC-20 balance, use `eth_call` with the `balanceOf(address)` function import EvmRpc "canister:evm_rpc"; import Runtime "mo:core/Runtime"; import Text "mo:core/Text"; +import Char "mo:core/Char"; persistent actor { @@ -290,7 +291,7 @@ persistent actor { switch (chars.next(), chars.next()) { case (?"0", ?"x") { var rest = ""; - for (c in chars) { rest #= Text.fromChar(c) }; + for (c in chars) { rest #= Char.toText(c) }; rest; }; case _ { hex }; @@ -593,7 +594,7 @@ Add the EVM RPC canister to your `icp.yaml` as a pre-built canister for local de canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: evm_rpc build: steps: diff --git a/docs/guides/chain-fusion/exchange-rates.mdx b/docs/guides/chain-fusion/exchange-rates.mdx index 4a86040b..4ef4f769 100644 --- a/docs/guides/chain-fusion/exchange-rates.mdx +++ b/docs/guides/chain-fusion/exchange-rates.mdx @@ -24,6 +24,7 @@ In Motoko, declare the XRC actor interface inline and use the `(with cycles = am import Cycles "mo:core/Cycles"; import Float "mo:core/Float"; import Int "mo:core/Int"; +import Nat "mo:core/Nat"; import Nat32 "mo:core/Nat32"; import Nat64 "mo:core/Nat64"; @@ -93,8 +94,8 @@ persistent actor { switch result { case (#Ok rate) { - let scale = Float.fromInt(Int.pow(10, Nat32.toNat(rate.metadata.decimals))); - ?(Float.fromInt(Nat64.toNat(rate.rate)) / scale) + let scale = Int.toFloat(Int.pow(10, Nat32.toNat(rate.metadata.decimals))); + ?(Int.toFloat(Nat64.toNat(rate.rate)) / scale) }; case (#Err err) { // handle specific errors as needed (see Error handling section below) @@ -198,7 +199,7 @@ For reliability, use the start of the **previous minute** rather than the curren ```motoko -let oneMinuteAgo : Nat64 = (Nat64.fromNat(Int.abs(Time.now())) / 1_000_000_000) - 60; +let oneMinuteAgo : Nat64 = (Nat.toNat64(Int.abs(Time.now())) / 1_000_000_000) - 60; let request : GetExchangeRateRequest = { base_asset = { symbol = "ICP"; class_ = #Cryptocurrency }; diff --git a/docs/guides/digital-assets/chain-key-tokens.mdx b/docs/guides/digital-assets/chain-key-tokens.mdx index d20b4c39..5bc9e9d4 100644 --- a/docs/guides/digital-assets/chain-key-tokens.mdx +++ b/docs/guides/digital-assets/chain-key-tokens.mdx @@ -220,11 +220,11 @@ persistent actor Self { let bytes = Blob.toArray(Principal.toBlob(p)); let size = bytes.size(); let sub = Array.tabulate(32, func(i : Nat) : Nat8 { - if (i == 0) { Nat8.fromNat(size) } + if (i == 0) { Nat.toNat8(size) } else if (i <= size) { bytes[i - 1] } else { 0 } }); - Blob.fromArray(sub) + Array.toBlob(sub) }; // Withdraw ckBTC to a Bitcoin address (minimum 50,000 satoshis) @@ -234,7 +234,7 @@ persistent actor Self { let minterPrincipal = Principal.fromText("mqygn-kiaaa-aaaar-qaadq-cai"); // Set created_at_time for deduplication: two identical approvals within 24h // would both execute without this. Omit if you intentionally allow retries. - let now = ?Nat64.fromNat(Int.abs(Time.now())); + let now = ?Nat.toNat64(Int.abs(Time.now())); // Step 1: approve the minter to spend ckBTC (amount + fee) let approveResult = await ckbtcLedger.icrc2_approve({ diff --git a/docs/guides/digital-assets/ledgers.mdx b/docs/guides/digital-assets/ledgers.mdx index 56384eb7..967cce16 100644 --- a/docs/guides/digital-assets/ledgers.mdx +++ b/docs/guides/digital-assets/ledgers.mdx @@ -73,7 +73,7 @@ persistent actor { /// Transfer tokens from this canister's default account. /// WARNING: Add access control in production. public func sendTokens(to : Principal, amount : Nat) : async Nat { - let now = Nat64.fromNat(Int.abs(Time.now())); + let now = Nat.toNat64(Int.abs(Time.now())); let result = await icpLedger.icrc1_transfer({ from_subaccount = null; to = { owner = to; subaccount = null }; @@ -180,7 +180,7 @@ Without `created_at_time` (set to `null`), every submission is treated as a new Set `created_at_time` to the current nanosecond timestamp to enable deduplication: -- **Motoko**: `Nat64.fromNat(Int.abs(Time.now()))` (as shown in `sendTokens` above) +- **Motoko**: `Nat.toNat64(Int.abs(Time.now()))` (as shown in `sendTokens` above) - **Rust**: `ic_cdk::api::time()` (as shown in `send_tokens` above) Two boundary errors to handle alongside the normal transfer errors: @@ -319,7 +319,7 @@ persistent actor { }; public func approveSpender(spender : Principal, amount : Nat) : async Nat { - let now = Nat64.fromNat(Int.abs(Time.now())); + let now = Nat.toNat64(Int.abs(Time.now())); let result = await icpLedger.icrc2_approve({ from_subaccount = null; spender = { owner = spender; subaccount = null }; @@ -338,7 +338,7 @@ persistent actor { /// WARNING: Add access control in production. public func transferFrom(from : Principal, to : Principal, amount : Nat) : async Nat { - let now = Nat64.fromNat(Int.abs(Time.now())); + let now = Nat.toNat64(Int.abs(Time.now())); let result = await icpLedger.icrc2_transfer_from({ spender_subaccount = null; from = { owner = from; subaccount = null }; @@ -445,6 +445,7 @@ import Principal "mo:core/Principal"; import Blob "mo:core/Blob"; import Array "mo:core/Array"; import Nat8 "mo:core/Nat8"; +import Nat "mo:core/Nat"; type Account = { owner : Principal; subaccount : ?Blob }; @@ -452,11 +453,11 @@ type Account = { owner : Principal; subaccount : ?Blob }; func depositAccount(canister : Principal, user : Principal) : Account { let bytes = Blob.toArray(Principal.toBlob(user)); let subaccount = Array.tabulate(32, func(i) { - if (i == 0) { Nat8.fromNat(bytes.size()) } + if (i == 0) { Nat.toNat8(bytes.size()) } else if (i <= bytes.size()) { bytes[i - 1] } else { 0 } }); - { owner = canister; subaccount = ?Blob.fromArray(subaccount) } + { owner = canister; subaccount = ?Array.toBlob(subaccount) } }; ``` diff --git a/docs/guides/frontends/asset-canister.md b/docs/guides/frontends/asset-canister.md index 0421b902..fa50b32f 100644 --- a/docs/guides/frontends/asset-canister.md +++ b/docs/guides/frontends/asset-canister.md @@ -44,7 +44,7 @@ Define an asset canister using the `@dfinity/asset-canister` recipe in your `icp canisters: - name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: dir: dist build: @@ -64,7 +64,7 @@ For a fullstack project with a backend canister, list both in the same `icp.yaml canisters: - name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: dir: dist build: @@ -72,7 +72,7 @@ canisters: - npm run build - name: backend recipe: - type: "@dfinity/rust@v3.3.0" + type: "@dfinity/rust@v3.4.0" ``` For more on project configuration, see [Project structure](../../getting-started/project-structure.md). diff --git a/docs/guides/frontends/frameworks.md b/docs/guides/frontends/frameworks.md index c883ebaf..46b83a6e 100644 --- a/docs/guides/frontends/frameworks.md +++ b/docs/guides/frontends/frameworks.md @@ -36,7 +36,7 @@ The [hello-world template](../../getting-started/project-structure.md) uses Reac canisters: - name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: build: - npm install @@ -214,7 +214,7 @@ export default { canisters: - name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: build: - npm install @@ -246,7 +246,7 @@ This outputs static files to the `out/` directory. canisters: - name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: build: - npm install @@ -271,7 +271,7 @@ Export your game from Unity Editor: **File → Build Settings → WebGL → Buil canisters: - name: unity_webgl_template_assets recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: dir: dist build: @@ -293,7 +293,7 @@ Export your game from Godot Editor: **Project → Export → HTML5 → Export Pr canisters: - name: godot_html5_assets recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: dir: dist build: @@ -330,7 +330,7 @@ For sites with no backend canister (portfolios, landing pages, documentation): canisters: - name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: build: - npm install diff --git a/docs/guides/governance/testing.md b/docs/guides/governance/testing.md index d36dcd72..432898e3 100644 --- a/docs/guides/governance/testing.md +++ b/docs/guides/governance/testing.md @@ -213,11 +213,11 @@ Add the SNS root canister as an **additional** controller of each app canister. ```bash # Locally: icp canister settings update test \ - --add-controller $(icp canister id sns_root) + --add-controller $(icp canister status sns_root -i) # On mainnet: icp canister settings update test \ - --add-controller $(icp canister id sns_root -e ic) \ + --add-controller $(icp canister status sns_root -e ic -i) \ -e ic ``` @@ -231,7 +231,7 @@ export DEVELOPER_NEURON_ID="" # is where dfx stores PEM files. If you created your identity with icp-cli, the path # may differ: check ~/.config/icp/identity/ or the path shown by `icp identity export`. export PEM_FILE="$HOME/.config/dfx/identity/$(icp identity default)/identity.pem" -export CID="$(icp canister id test -e ic)" +export CID="$(icp canister status test -e ic -i)" ``` Submit the registration proposal: diff --git a/docs/guides/security/data-integrity-and-authenticity.md b/docs/guides/security/data-integrity-and-authenticity.md index eaa5d62c..f4222a77 100644 --- a/docs/guides/security/data-integrity-and-authenticity.md +++ b/docs/guides/security/data-integrity-and-authenticity.md @@ -91,6 +91,7 @@ import Debug "mo:core/Debug"; import Text "mo:core/Text"; import Nat64 "mo:core/Nat64"; import Array "mo:core/Array"; +import Nat "mo:core/Nat"; import CertTree "mo:ic-certification/CertTree"; import CV "mo:cbor/Value"; import CborEncoder "mo:cbor/Encoder"; @@ -157,11 +158,11 @@ actor CertifiedVariable { func encodeUser(user : User) : Blob { let bytes : CV.Value = #majorType5([ (#majorType3("name"), #majorType3(user.name)), - (#majorType3("age"), #majorType0(Nat64.fromNat(Nat8.toNat(user.age)))), + (#majorType3("age"), #majorType0(Nat.toNat64(Nat8.toNat(user.age)))), ]); let #ok(encoded_user) = CborEncoder.encode(bytes); - return Blob.fromArray(encoded_user); + return Array.toBlob(encoded_user); }; func decodeUser(bytes : Blob) : User { @@ -182,7 +183,7 @@ actor CertifiedVariable { let age = switch (age_tag) { case (?age_value) { let #majorType0(age) = age_value.1; - Nat8.fromNat(Nat64.toNat(age)); + Nat.toNat8(Nat64.toNat(age)); }; case (null) { Debug.trap("Decoding failed for age"); @@ -198,9 +199,9 @@ actor CertifiedVariable { func blobOfNat64(n : Nat64) : Blob { let byteMask : Nat64 = 0xff; func byte(x : Nat64) : Nat8 { - Nat8.fromNat(Nat64.toNat(x)); + Nat.toNat8(Nat64.toNat(x)); }; - Blob.fromArray([ + Array.toBlob([ byte(((byteMask << 56) & n) >> 56), byte(((byteMask << 48) & n) >> 48), byte(((byteMask << 40) & n) >> 40), diff --git a/docs/references/application-canisters.md b/docs/references/application-canisters.md index a787cfa9..e7ff05f2 100644 --- a/docs/references/application-canisters.md +++ b/docs/references/application-canisters.md @@ -19,7 +19,7 @@ Asset canisters are deployed per-project. There is no global asset canister ID: canisters: - name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/asset-canister@v2.3.0" configuration: dir: dist build: diff --git a/scripts/check-upstream-releases.mjs b/scripts/check-upstream-releases.mjs index 0f3049a6..88c2398a 100644 --- a/scripts/check-upstream-releases.mjs +++ b/scripts/check-upstream-releases.mjs @@ -3,9 +3,12 @@ * Check every upstream in `.sources/upstream.json` for a newer ref than the one * the docs are pinned to, and write an issue body for each that moved. * - * Covers both groups in that file: `vendored` submodules whose pin has no sync - * workflow of its own (their pin is read from the gitlink, so git stays the - * single source of truth), and `watched` repos that are not vendored at all. + * Covers two groups. `watched` repos are compared against the ref recorded in + * that file. `vendored` submodules are compared gitlink against branch head, so + * git stays the single source of truth for their pin; an entry may narrow that + * to the paths the docs actually depend on via `pathFilter`. `motoko` and + * `internetidentity` are in neither group: their own sync workflows + * release-check them and open the bump PR. * * A watched repo declares where its releases actually appear: `release` (git * tags matching a pattern), `crate` (crates.io) or `npm` (the npm registry) for @@ -27,8 +30,8 @@ */ import { execFileSync } from 'node:child_process'; -import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; -import { join, resolve } from 'node:path'; +import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'node:fs'; +import { join, resolve, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = resolve(fileURLToPath(import.meta.url), '..', '..'); @@ -92,6 +95,73 @@ function defaultBranchHead(repo) { return { branch, sha }; } +/** + * Language-to-directory map, mirroring LANG_TO_DIR in plugins/remark-snippet.mjs. + * It is copied rather than imported because that plugin pulls in remark + * dependencies and this script must run in the workflow, which installs none. + * Keep the two in step; an unknown language here fails the check rather than + * silently reporting fewer files than the docs actually quote. + */ +const LANG_TO_DIR = { + rust: 'rust', + rs: 'rust', + motoko: 'motoko', + mo: 'motoko', + javascript: 'hosting', + js: 'hosting', + typescript: 'hosting', + ts: 'hosting', +}; + +/** + * Every file in the examples repo that a `snippet=` in docs/ quotes, as a + * repo-relative path. The attribute is relative to `.sources/examples//`, + * where the directory comes from the fence's language, so both parts are needed + * to reconstruct the path the compare API reports. + */ +function snippetPaths() { + const out = new Set(); + const walk = (dir) => { + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) walk(full); + else if (/\.mdx?$/.test(entry)) { + const text = readFileSync(full, 'utf8'); + for (const m of text.matchAll(/^```(\w+)[^\n]*\ssnippet="([^"#]+)/gm)) { + const [, lang, path] = m; + const dir = LANG_TO_DIR[lang]; + if (!dir) { + throw new Error( + `${relative(ROOT, full)}: snippet in an unmapped language "${lang}"; ` + + `add it to LANG_TO_DIR here and in plugins/remark-snippet.mjs` + ); + } + out.add(`${dir}/${path}`); + } + } + } + }; + walk(join(ROOT, 'docs')); + return out; +} + +/** + * Files changed between two refs, via the compare API. Returns null when the + * answer cannot be trusted (request failed, or the response was truncated at + * the API's 300-file cap), so callers report rather than assume nothing moved. + */ +async function changedFiles(repo, base, head) { + const headers = { Accept: 'application/vnd.github+json' }; + const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN; + if (token) headers.Authorization = `Bearer ${token}`; + const res = await fetch(`https://api.github.com/repos/${repo}/compare/${base}...${head}`, { headers }); + if (!res.ok) return null; + const body = await res.json(); + if (!Array.isArray(body.files)) return null; + if (body.files.length >= 300) return null; + return new Set(body.files.map((f) => f.filename)); +} + function gitlinkSha(path) { // The committed submodule pointer, readable without initializing the // submodule, so this works on a bare checkout in CI. @@ -176,8 +246,8 @@ function slugFor(entry) { return entry.name ? `${base}-${entry.name}` : base; } -function checkVendored(entry) { - const { path, repo, branch, affects } = entry; +async function checkVendored(entry) { + const { path, repo, branch, affects, pathFilter, reference } = entry; const pinnedSha = gitlinkSha(path); if (!pinnedSha) throw new Error(`${path}: not a submodule in this commit`); const headSha = branchHead(repo, branch); @@ -186,6 +256,22 @@ function checkVendored(entry) { // Short SHAs are for the table and the issue title; anything a machine // consumes (the compare link, the checkout command) gets the full SHA. + let touched; + if (pathFilter === 'snippets') { + // A commit on an active examples repo is not news by itself. What matters + // is whether it touched a file the docs quote: an improvement upstream + // leaves our copy stale while the build stays green, since the build only + // catches a path or region that stopped resolving. + const wanted = snippetPaths(); + const changed = await changedFiles(repo, pinnedSha, headSha); + if (changed) { + touched = [...wanted].filter((f) => changed.has(f)); + if (touched.length === 0) return null; + } + // changed === null: the comparison could not be trusted, so fall through + // and report rather than assume nothing moved. + } + const pinned = pinnedSha.slice(0, 7); const latest = headSha.slice(0, 7); const name = path.replace(/^\.sources\//, ''); @@ -197,10 +283,15 @@ function checkVendored(entry) { `| Pinned (gitlink) | \`${pinned}\` |`, `| Branch head | \`${latest}\` |`, `| Compare | https://github.com/${repo}/compare/${pinnedSha}...${headSha} |`, + ...(reference ? [`| Published reference | ${reference} |`] : []), '', '## What to re-check', '', - affects ?? 'No notes recorded for this submodule.', + touched?.length + ? 'Files the docs quote that changed in this range:\n\n' + + touched.map((f) => `- \`${f}\``).join('\n') + + '\n\n' + (affects ?? '') + : affects ?? 'No notes recorded for this submodule.', '', '## How to close this', '', @@ -379,7 +470,7 @@ let failed = false; for (const entry of vendored) { try { - const r = checkVendored(entry); + const r = await checkVendored(entry); if (r) { results.push(r); console.error(`behind: ${entry.path} -> ${entry.repo}@${entry.branch}`);