Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,23 @@ jobs:

- run: npm ci

- name: Verify tag matches package.json version
- name: Verify tag and resolve npm dist-tag
id: version
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
PKG_VERSION="$(node -p "require('./package.json').version")"
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
echo "Tag ($TAG_VERSION) and package.json ($PKG_VERSION) disagree — refusing to publish." >&2
echo "Re-cut the release via scripts/release.sh so the tag and the committed version match." >&2
if [ "${GITHUB_REF_TYPE:-}" != "tag" ]; then
echo "Publish must run from a git tag. Current ref type: ${GITHUB_REF_TYPE:-unknown}" >&2
exit 1
fi

TAG_VERSION="${GITHUB_REF_NAME#v}"
PKG_VERSION="$(node -p "require('./package.json').version")"
node scripts/resolve-release-channel.mjs "$TAG_VERSION" "$PKG_VERSION" >> "$GITHUB_OUTPUT"
echo "Tag $GITHUB_REF_NAME matches package.json $PKG_VERSION."

- run: npm run build

- name: Publish to npm
run: npm publish --provenance --access public
run: npm publish --provenance --access public --tag "${{ steps.version.outputs.npm_dist_tag }}"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Expand All @@ -63,6 +65,17 @@ jobs:
if [ -z "$NOTES" ]; then
NOTES="Release $GITHUB_REF_NAME"
fi

NOTES="$NOTES

npm install genlayer-js@${{ steps.version.outputs.npm_dist_tag }}"

RELEASE_FLAGS=()
if [ "${{ steps.version.outputs.is_prerelease }}" = "true" ]; then
RELEASE_FLAGS+=(--prerelease)
fi

gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--notes "$NOTES"
--notes "$NOTES" \
"${RELEASE_FLAGS[@]}"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ const txHash = await client.writeContract({
});
```

Available networks: `"localnet"`, `"studionet"`, `"testnetAsimov"`, `"testnetBradbury"`.
Available networks: `"localnet"`, `"studionet"`, `"studioDevnet"`, `"testnetAsimov"`, `"testnetBradbury"`.

> **Note:** If the wallet is on the wrong chain when you call `writeContract`, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call `client.connect()` to resolve this.

Expand Down
2 changes: 1 addition & 1 deletion docs/api-references/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ const txHash = await client.writeContract({
});
```

Available networks: `"localnet"`, `"studionet"`, `"testnetAsimov"`, `"testnetBradbury"`.
Available networks: `"localnet"`, `"studionet"`, `"studioDevnet"`, `"testnetAsimov"`, `"testnetBradbury"`.

> **Note:** If the wallet is on the wrong chain when you call `writeContract`, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call `client.connect()` to resolve this.

Expand Down
2 changes: 1 addition & 1 deletion docs/api-references/types.TypeAlias.Network.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Type Alias: Network

> **Network** = `"localnet"` \| `"studionet"` \| `"testnetAsimov"` \| `"testnetBradbury"` \| `"mainnet"`
> **Network** = `"localnet"` \| `"studionet"` \| `"studioDevnet"` \| `"testnetAsimov"` \| `"testnetBradbury"` \| `"mainnet"`

Defined in: [types/network.ts:1](https://github.com/genlayerlabs/genlayer-js/blob/eaba6adec6803bdd0b4968e3f0763cf22107acd1/src/types/network.ts#L1)
53 changes: 53 additions & 0 deletions scripts/resolve-release-channel.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env node

import {fileURLToPath} from "node:url";
import {resolve} from "node:path";

const DIST_TAG_PATTERN = /^[a-z][a-z0-9._-]*$/i;

export function resolveReleaseChannel(tagVersion, packageVersion) {
if (!tagVersion || !packageVersion) {
throw new Error("Usage: resolve-release-channel.mjs <tag-version> <package-version>");
}
if (tagVersion !== packageVersion) {
throw new Error(
`Tag (${tagVersion}) and package.json (${packageVersion}) disagree — refusing to publish.`,
);
}

const prerelease = tagVersion.includes("-");
if (!prerelease) {
return {isPrerelease: false, npmDistTag: "latest"};
}

const suffix = tagVersion.slice(tagVersion.indexOf("-") + 1).split("+", 1)[0];
const npmDistTag = suffix.split(".", 1)[0];
if (
!npmDistTag ||
npmDistTag === "latest" ||
!DIST_TAG_PATTERN.test(npmDistTag) ||
/^v?[0-9]/i.test(npmDistTag)
) {
throw new Error(`Invalid prerelease npm dist-tag: '${npmDistTag}'`);
}

return {isPrerelease: true, npmDistTag};
}

function main() {
try {
const {isPrerelease, npmDistTag} = resolveReleaseChannel(
process.argv[2],
process.argv[3],
);
process.stdout.write(`is_prerelease=${isPrerelease}\n`);
process.stdout.write(`npm_dist_tag=${npmDistTag}\n`);
} catch (error) {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
process.exitCode = 1;
}
}

if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
main();
}
1 change: 1 addition & 0 deletions src/chains/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// src/chains/index.ts
export {localnet} from "./localnet";
export {studionet} from "./studionet";
export {studioDevnet} from "./studioDevnet";
export {testnetAsimov} from "./testnetAsimov";
export {testnetBradbury} from "./testnetBradbury";
24 changes: 24 additions & 0 deletions src/chains/studioDevnet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {defineChain} from "viem";
import {GenLayerChain} from "@/types";
import {studionet} from "./studionet";

const STUDIO_DEV_JSON_RPC_URL = "https://studio-dev.genlayer.com/api";

/**
* Preview Studio deployment used to qualify the next Studio and SDK releases.
*
* Keep this separate from `studionet`: preview releases must not silently move
* the stable Studio endpoint or chain id for existing SDK consumers.
*/
export const studioDevnet: GenLayerChain = defineChain({
...studionet,
id: 61_997,
name: "GenLayer Studio Devnet",
rpcUrls: {
default: {
http: [STUDIO_DEV_JSON_RPC_URL],
},
},
// The stable Studio explorer does not index this preview deployment.
blockExplorers: undefined,
});
8 changes: 7 additions & 1 deletion src/types/network.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export type Network = "localnet" | "studionet" | "testnetAsimov" | "testnetBradbury" | "mainnet";
export type Network =
| "localnet"
| "studionet"
| "studioDevnet"
| "testnetAsimov"
| "testnetBradbury"
| "mainnet";
2 changes: 2 additions & 0 deletions src/wallet/connect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {localnet} from "@/chains/localnet";
import {studionet} from "@/chains/studionet";
import {studioDevnet} from "@/chains/studioDevnet";
import {testnetAsimov} from "@/chains/testnetAsimov";
import {testnetBradbury} from "@/chains/testnetBradbury";
import {GenLayerClient, GenLayerChain} from "@/types";
Expand All @@ -10,6 +11,7 @@ import {snapID} from "@/config/snapID";
const networks = {
localnet,
studionet,
studioDevnet,
testnetAsimov,
testnetBradbury,
};
Expand Down
32 changes: 32 additions & 0 deletions tests/chains-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {describe, expect, it} from "vitest";
import {studioDevnet, studionet} from "../src/chains";

describe("Studio chain presets", () => {
it("exports the dedicated Studio preview deployment", () => {
expect(studioDevnet).toMatchObject({
id: 61_997,
isStudio: true,
name: "GenLayer Studio Devnet",
rpcUrls: {
default: {
http: ["https://studio-dev.genlayer.com/api"],
},
},
testnet: true,
});
expect(studioDevnet.blockExplorers).toBeUndefined();
expect(studioDevnet.consensusMainContract?.address).toBe(
studionet.consensusMainContract?.address,
);
expect(studioDevnet.consensusDataContract?.address).toBe(
studionet.consensusDataContract?.address,
);
});

it("does not move the stable Studio preset", () => {
expect(studionet.id).toBe(61_999);
expect(studionet.rpcUrls.default.http).toEqual([
"https://studio.genlayer.com/api",
]);
});
});
36 changes: 36 additions & 0 deletions tests/release-channel.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {describe, expect, it} from "vitest";
import {resolveReleaseChannel} from "../scripts/resolve-release-channel.mjs";

describe("release channel policy", () => {
it("publishes stable releases to latest", () => {
expect(resolveReleaseChannel("2.0.0", "2.0.0")).toEqual({
isPrerelease: false,
npmDistTag: "latest",
});
});

it("publishes RC releases to rc without moving latest", () => {
expect(resolveReleaseChannel("2.0.0-rc.1", "2.0.0-rc.1")).toEqual({
isPrerelease: true,
npmDistTag: "rc",
});
});

it("rejects mismatched tags", () => {
expect(() => resolveReleaseChannel("2.0.0-rc.1", "1.1.8")).toThrow(
"disagree",
);
});

it("rejects prereleases that could target latest", () => {
expect(() => resolveReleaseChannel("2.0.0-latest.1", "2.0.0-latest.1")).toThrow(
"Invalid prerelease npm dist-tag",
);
});

it("rejects version-like prerelease channels", () => {
expect(() => resolveReleaseChannel("2.0.0-2.1", "2.0.0-2.1")).toThrow(
"Invalid prerelease npm dist-tag",
);
});
});
Loading