Skip to content

Commit 2bd8637

Browse files
authored
feat(release): add Studio dev preview network (#414)
Add the canonical studio-dev preview network backed by the merged GenLayer JS Studio devnet chain, preserve stable Studionet behavior, and enforce safe RC publish-channel selection.
1 parent eacb62a commit 2bd8637

15 files changed

Lines changed: 172 additions & 39 deletions

‎.github/workflows/publish.yml‎

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ name: Publish Package to NPM
99
# Release. It never bumps or tags by itself.
1010
#
1111
# Stable tags such as v0.39.2 publish to npm's latest dist-tag.
12-
# Prerelease tags such as v0.40.0-clarke.1 publish to a matching
13-
# channel dist-tag (clarke) and create a GitHub prerelease.
12+
# Prerelease tags such as v0.40.0-rc.3 publish to a matching
13+
# channel dist-tag (rc) and create a GitHub prerelease.
1414
# Prereleases must never become latest.
1515
on:
1616
workflow_dispatch:
@@ -47,35 +47,9 @@ jobs:
4747
4848
TAG_VERSION="${GITHUB_REF_NAME#v}"
4949
PKG_VERSION="$(node -p "require('./package.json').version")"
50-
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
51-
echo "Tag ($TAG_VERSION) and package.json ($PKG_VERSION) disagree — refusing to publish." >&2
52-
echo "Re-cut the release via scripts/release.sh so the tag and the committed version match." >&2
53-
exit 1
54-
fi
55-
56-
IS_PRERELEASE=false
57-
NPM_DIST_TAG=latest
58-
if [[ "$TAG_VERSION" == *-* ]]; then
59-
IS_PRERELEASE=true
60-
PRERELEASE_SUFFIX="${TAG_VERSION#*-}"
61-
PRERELEASE_SUFFIX="${PRERELEASE_SUFFIX%%+*}"
62-
NPM_DIST_TAG="${PRERELEASE_SUFFIX%%.*}"
63-
64-
if [ -z "$NPM_DIST_TAG" ] || [ "$NPM_DIST_TAG" = "latest" ]; then
65-
echo "Invalid prerelease npm dist-tag: '$NPM_DIST_TAG'" >&2
66-
exit 1
67-
fi
68-
69-
if [[ "$NPM_DIST_TAG" =~ ^v?[0-9] ]]; then
70-
echo "Invalid prerelease npm dist-tag '$NPM_DIST_TAG': dist-tags must not look like versions." >&2
71-
exit 1
72-
fi
73-
fi
50+
node scripts/resolve-release-channel.mjs "$TAG_VERSION" "$PKG_VERSION" >> "$GITHUB_OUTPUT"
7451
7552
echo "Tag $GITHUB_REF_NAME matches package.json $PKG_VERSION."
76-
echo "npm dist-tag: $NPM_DIST_TAG"
77-
echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT"
78-
echo "npm_dist_tag=$NPM_DIST_TAG" >> "$GITHUB_OUTPUT"
7953
8054
- run: npm run build
8155

‎README.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ OPTIONS (add):
166166

167167
EXAMPLES:
168168
genlayer network set
169+
genlayer network set studio-dev
169170
genlayer network set testnet-bradbury
170171
genlayer network info
171172
genlayer network list
@@ -178,6 +179,8 @@ EXAMPLES:
178179
179180
Custom networks are stored by alias and can be selected anywhere a built-in
180181
network can, via `--network <alias>` (or `genlayer network set <alias>`).
182+
The built-in `studio-dev` preview points to `https://studio-dev.genlayer.com/api`
183+
on chain ID `61997`; stable `studionet` remains unchanged.
181184
182185
#### Deploy and Call Intelligent Contracts
183186

‎package-lock.json‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"eslint-config-prettier": "^10.0.0",
5858
"eslint-import-resolver-typescript": "^4.0.0",
5959
"eslint-plugin-import": "^2.29.1",
60-
"genlayer-js": "github:genlayerlabs/genlayer-js#8f72796efa6f1f52d420957bd253771c8583ca14",
60+
"genlayer-js": "github:genlayerlabs/genlayer-js#facd9e9dc9a289d0110fe3b5b1a14a2938fe6e01",
6161
"jsdom": "^26.0.0",
6262
"prettier": "^3.2.5",
6363
"release-it": "^19.0.0",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env node
2+
3+
import {fileURLToPath} from "node:url";
4+
import {resolve} from "node:path";
5+
6+
const DIST_TAG_PATTERN = /^[a-z][a-z0-9._-]*$/i;
7+
8+
export function resolveReleaseChannel(tagVersion, packageVersion) {
9+
if (!tagVersion || !packageVersion) {
10+
throw new Error("Usage: resolve-release-channel.mjs <tag-version> <package-version>");
11+
}
12+
if (tagVersion !== packageVersion) {
13+
throw new Error(
14+
`Tag (${tagVersion}) and package.json (${packageVersion}) disagree — refusing to publish.`,
15+
);
16+
}
17+
18+
const prerelease = tagVersion.includes("-");
19+
if (!prerelease) {
20+
return {isPrerelease: false, npmDistTag: "latest"};
21+
}
22+
23+
const suffix = tagVersion.slice(tagVersion.indexOf("-") + 1).split("+", 1)[0];
24+
const npmDistTag = suffix.split(".", 1)[0];
25+
if (
26+
!npmDistTag ||
27+
npmDistTag === "latest" ||
28+
!DIST_TAG_PATTERN.test(npmDistTag) ||
29+
/^v?[0-9]/i.test(npmDistTag)
30+
) {
31+
throw new Error(`Invalid prerelease npm dist-tag: '${npmDistTag}'`);
32+
}
33+
34+
return {isPrerelease: true, npmDistTag};
35+
}
36+
37+
function main() {
38+
try {
39+
const {isPrerelease, npmDistTag} = resolveReleaseChannel(
40+
process.argv[2],
41+
process.argv[3],
42+
);
43+
process.stdout.write(`is_prerelease=${isPrerelease}\n`);
44+
process.stdout.write(`npm_dist_tag=${npmDistTag}\n`);
45+
} catch (error) {
46+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
47+
process.exitCode = 1;
48+
}
49+
}
50+
51+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
52+
main();
53+
}

‎src/commands/staking/wizard.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,8 @@ export class ValidatorWizardAction extends StakingAction {
329329
}
330330

331331
const currentNetwork = this.getConfigByKey("network");
332-
// Exclude studionet - not compatible with staking
333-
const excludedNetworks = ["studionet"];
332+
// Studio deployments are not compatible with staking.
333+
const excludedNetworks = ["studionet", "studio-dev"];
334334
const networks = Object.entries(BUILT_IN_NETWORKS)
335335
.filter(([alias]) => !excludedNetworks.includes(alias))
336336
.map(([alias, chain]) => ({

‎src/lib/actions/BaseAction.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import chalk from "chalk";
55
import inquirer from "inquirer";
66
import { inspect } from "util";
77
import {createClient, createAccount} from "genlayer-js";
8-
import {localnet, studionet, testnetAsimov, testnetBradbury} from "genlayer-js/chains";
8+
import {
9+
localnet,
10+
studioDevnet,
11+
studionet,
12+
testnetAsimov,
13+
testnetBradbury,
14+
} from "genlayer-js/chains";
915
import type {GenLayerClient, GenLayerChain, Hash, Address, Account} from "genlayer-js/types";
1016
import {
1117
applyCustomNetworkProfile,
@@ -23,6 +29,7 @@ import {JsonRpcClient} from "../clients/jsonRpcClient";
2329
export const BUILT_IN_NETWORKS: Record<string, GenLayerChain> = {
2430
"localnet": localnet,
2531
"studionet": studionet,
32+
"studio-dev": studioDevnet,
2633
"testnet-asimov": testnetAsimov,
2734
"testnet-bradbury": testnetBradbury,
2835
};

‎tests/actions/customNetworkProfiles.test.ts‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {tmpdir} from "os";
66
import {NetworkActions} from "../../src/commands/network/setNetwork";
77
import {resolveNetwork} from "../../src/lib/actions/BaseAction";
88
import {parseDeploymentObject} from "../../src/lib/networks/customNetworks";
9-
import {testnetBradbury} from "genlayer-js/chains";
9+
import {studioDevnet, studionet, testnetBradbury} from "genlayer-js/chains";
1010
import {StakingAction} from "../../src/commands/staking/StakingAction";
1111

1212
const ADDR_1 = "0x1111111111111111111111111111111111111111";
@@ -42,6 +42,21 @@ describe("custom network profiles", () => {
4242
fs.rmSync(tempHome, {recursive: true, force: true});
4343
});
4444

45+
test("studio-dev is a dedicated built-in without moving stable studionet", () => {
46+
const preview = resolveNetwork("studio-dev");
47+
const stable = resolveNetwork("studionet");
48+
49+
expect(preview).toBe(studioDevnet);
50+
expect(preview.id).toBe(61_997);
51+
expect(preview.rpcUrls.default.http).toEqual(["https://studio-dev.genlayer.com/api"]);
52+
expect(preview.consensusMainContract?.address).toBe(studionet.consensusMainContract?.address);
53+
expect(preview.consensusDataContract?.address).toBe(studionet.consensusDataContract?.address);
54+
55+
expect(stable).toBe(studionet);
56+
expect(stable.id).toBe(61_999);
57+
expect(stable.rpcUrls.default.http).toEqual(["https://studio.genlayer.com/api"]);
58+
});
59+
4560
test("network add stores flags-only overrides", async () => {
4661
await action.addNetwork("bradbury-clarke", {
4762
base: "testnet-bradbury",
@@ -177,7 +192,7 @@ describe("custom network profiles", () => {
177192
expect(failSpy).toHaveBeenNthCalledWith(
178193
2,
179194
"Failed to add custom network profile",
180-
"Base network must be one of: localnet, studionet, testnet-asimov, testnet-bradbury",
195+
"Base network must be one of: localnet, studionet, studio-dev, testnet-asimov, testnet-bradbury",
181196
);
182197
expect(failSpy).toHaveBeenNthCalledWith(
183198
3,

‎tests/actions/setNetwork.test.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import {describe, test, vi, beforeEach, afterEach, expect} from "vitest";
22
import {NetworkActions} from "../../src/commands/network/setNetwork";
33
import {ConfigFileManager} from "../../src/lib/config/ConfigFileManager";
44
import inquirer from "inquirer";
5-
import {localnet, studionet, testnetAsimov, testnetBradbury} from "genlayer-js/chains";
5+
import {
6+
localnet,
7+
studioDevnet,
8+
studionet,
9+
testnetAsimov,
10+
testnetBradbury,
11+
} from "genlayer-js/chains";
612

713
vi.mock("../../src/lib/config/ConfigFileManager");
814
vi.mock("inquirer");
@@ -59,6 +65,15 @@ describe("NetworkActions", () => {
5965
);
6066
});
6167

68+
test("setNetwork method sets studio-dev by alias", async () => {
69+
await networkActions.setNetwork("studio-dev");
70+
71+
expect(networkActions["writeConfig"]).toHaveBeenCalledWith("network", "studio-dev");
72+
expect(networkActions["succeedSpinner"]).toHaveBeenCalledWith(
73+
`Network successfully set to ${studioDevnet.name}`,
74+
);
75+
});
76+
6277
test("setNetwork method sets testnet-asimov by name", async () => {
6378
await networkActions.setNetwork(testnetAsimov.name);
6479

@@ -108,6 +123,7 @@ describe("NetworkActions", () => {
108123
choices: [
109124
{name: localnet.name, value: "localnet"},
110125
{name: studionet.name, value: "studionet"},
126+
{name: studioDevnet.name, value: "studio-dev"},
111127
{name: testnetAsimov.name, value: "testnet-asimov"},
112128
{name: testnetBradbury.name, value: "testnet-bradbury"},
113129
],

‎tests/actions/staking.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ vi.mock("genlayer-js", () => ({
3535
vi.mock("genlayer-js/chains", () => ({
3636
localnet: {id: 1, name: "localnet", rpcUrls: {default: {http: ["http://localhost:8545"]}}},
3737
studionet: {id: 2, name: "studionet", rpcUrls: {default: {http: ["https://studionet.genlayer.com"]}}},
38+
studioDevnet: {id: 61997, name: "studio-dev", rpcUrls: {default: {http: ["https://studio-dev.genlayer.com/api"]}}},
3839
testnetAsimov: {
3940
id: 3,
4041
name: "testnet-asimov",

0 commit comments

Comments
 (0)