Skip to content

Commit

Permalink
Counter template (#155)
Browse files Browse the repository at this point in the history
- Added a new Counter template which consists of frontend-counter-react and backend-counter packages.
- Reworked template initialization commands.
- Probably performed some refactoring which I don't remember about ;)
- Updated docs.
  • Loading branch information
kkomelin authored Jan 27, 2025
1 parent 071b694 commit 239c822
Show file tree
Hide file tree
Showing 207 changed files with 2,902 additions and 36 deletions.
17 changes: 6 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,13 @@ Before you begin, install the following:

2. Clone the resulting repo locally.

3. Choose a frontend framework:
3. Choose a template by running the corresponding init command:

For **React**:
```bash
pnpm frontend:init:react
```

For **Next.js**:

```bash
pnpm frontend:init:nextjs
```
| Template | Init command | Description |
| --- | --- | --- |
| Greeting (React) | `pnpm init:template:greeting-react` | A **default template** with a Move contract which utilizes Sui Object Display (NFT) and native Sui Randomness. Frontend is built with React, SWC, Vite, Tailwind. |
| Greeting (Next.js) | `pnpm init:template:greeting-next` | The Move part is the same as in Greetings (React), but the frontend is built with Next.js. |
| Counter (React) | `pnpm init:template:counter-react` | A lightweight template with a simple Move counter contract and a React frontend. |

### Option 2. Use CLI

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"lint": "pnpm frontend:lint",
"format": "pnpm frontend:format",

"frontend:init:react": "rm -rf ./packages/frontend-next",
"frontend:init:nextjs": "rm -rf ./packages/frontend && mv -f ./packages/frontend-next ./packages/frontend",
"init:template:greeting-react": "rm -rf ./packages/backend-counter && rm -rf ./packages/frontend-greeting-next && rm -rf ./packages/frontend-counter-react",
"init:template:greeting-next": "rm -rf ./packages/backend-counter && rm -rf ./packages/frontend && rm -rf ./packages/frontend-counter-react && mv -f ./packages/frontend-greeting-next ./packages/frontend",
"init:template:counter-react": "rm -rf ./packages/backend && rm -rf ./packages/frontend && rm -rf ./packages/frontend-greeting-next && mv -f ./packages/frontend-counter-react ./packages/frontend && mv -f ./packages/backend-counter ./packages/backend",

"frontend:dev": "pnpm --filter frontend dev",
"frontend:build": "pnpm --filter frontend build",
Expand Down
4 changes: 4 additions & 0 deletions packages/backend-counter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build
Move.lock
Suibase.toml
node_modules
3 changes: 3 additions & 0 deletions packages/backend-counter/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
scripts
build
3 changes: 3 additions & 0 deletions packages/backend-counter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Sui dApp Starter: Backend

Please find the root project [README](../../README.md).
37 changes: 37 additions & 0 deletions packages/backend-counter/move/counter/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "counter"
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move
# license = "" # e.g., "MIT", "GPL", "Apache 2.0"
# authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"]

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/devnet" }

# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`.
# Revision can be a branch, a tag, and a commit hash.
# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "counter" }

# For local dependencies use `local = path`. Path is relative to the package root
# Local = { local = "../path/to" }

# To resolve a version conflict and force a specific version for dependency
# override use `override = true`
# Override = { local = "../conflicting/version", override = true }

[addresses]
counter = "0x0"

# Named addresses will be accessible in Move as `@name`. They're also exported:
# for example, `std = "0x1"` is exported by the Standard Library.
# alice = "0xA11CE"

[dev-dependencies]
# The dev-dependencies section allows overriding dependencies for `--test` and
# `--dev` modes. You can introduce test-only dependencies here.
# Local = { local = "../path/to/dev-build" }

[dev-addresses]
# The dev-addresses section allows overwriting named addresses for the `--test`
# and `--dev` modes.
# alice = "0xB0B"

69 changes: 69 additions & 0 deletions packages/backend-counter/move/counter/sources/counter.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Konstantin Komelin and other contributors
// SPDX-License-Identifier: MIT

/// Module: counter
module counter::counter;

// === Imports ===

// === Constants ===

// === Errors ===

// === Structs ===

public struct Counter has key {
id: UID,
owner: address,
value: u64,
}

// === Events ===

// === Initializer ===

/// Create and share a Counter object.
public fun create(ctx: &mut TxContext) {
// Create the Counter object.
let counter = new(ctx);

// Share the Counter object with everyone.
transfer::share_object(counter);
}

// === Public-Mutative Functions ===

public fun increment(counter: &mut Counter) {
counter.value = counter.value + 1;
}

public fun decrement(counter: &mut Counter) {
counter.value = counter.value - 1;
}

// === Public-View Functions ===

/// Returns current value for the counter.
public fun value(g: &Counter): u64 {
g.value
}

// === Private Functions ===

/// Create a new Counter object.
fun new(ctx: &mut TxContext): Counter {
Counter {
id: object::new(ctx),
owner: ctx.sender(),
value: 0,
}
}

// === Test Functions ===

#[test_only]
/// Create a new Counter for tests.
public fun new_for_testing(ctx: &mut TxContext): Counter {
let counter = new(ctx);
counter
}
41 changes: 41 additions & 0 deletions packages/backend-counter/move/counter/tests/counter_tests.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Konstantin Komelin and other contributors
// SPDX-License-Identifier: MIT

#[test_only]
module counter::counter_tests;

use counter::counter;
use sui::test_scenario as ts;
use sui::test_utils;

#[test]
/// Tests increment and decrement of the counter.
fun test_counter() {
let user1 = @0x1;
let user2 = @0x2;

let mut ts = ts::begin(user1);

let mut c = counter::new_for_testing(ts.ctx());

assert!(counter::value(&c) == 0, 0);

ts.next_tx(user1);
counter::increment(&mut c);
assert!(counter::value(&c) == 1, 1);

ts.next_tx(user2);
counter::increment(&mut c);
assert!(counter::value(&c) == 2, 2);

ts.next_tx(user1);
counter::decrement(&mut c);
assert!(counter::value(&c) == 1, 3);

ts.next_tx(user2);
counter::decrement(&mut c);
assert!(counter::value(&c) == 0, 4);

test_utils::destroy(c);
ts.end();
}
50 changes: 50 additions & 0 deletions packages/backend-counter/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "backend-counter",
"private": true,
"version": "0.0.0",
"scripts": {
"build": "lsui move build -d -p ./move/counter",
"test": "lsui move test -d -p ./move/counter",
"copy-package-id": "node ./scripts/copy-package-id",
"localnet:start": "localnet start",
"localnet:stop": "localnet stop",
"localnet:status": "localnet status",
"localnet:faucet": "localnet faucet",
"localnet:regen": "localnet regen",
"localnet:update": "localnet update",
"localnet:address": "lsui client active-address",
"localnet:deploy": "localnet publish --path ${PWD}/move/counter && pnpm copy-package-id -n localnet",
"localnet:deploy:no-dependency-check": "localnet publish --path ${PWD}/move/counter --skip-dependency-verification && pnpm copy-package-id -n localnet",
"localnet:explorer:start": "sui-explorer-local start",
"localnet:explorer:stop": "sui-explorer-local stop",
"localnet:explorer:restart": "sui-explorer-local restart",
"devnet:start": "devnet start",
"devnet:stop": "devnet stop",
"devnet:status": "devnet status",
"devnet:update": "devnet update",
"devnet:links": "devnet links",
"devnet:address": "dsui client active-address",
"devnet:deploy": "devnet publish --path ${PWD}/move/counter && pnpm copy-package-id -n devnet",
"devnet:deploy:no-dependency-check": "devnet publish --path ${PWD}/move/counter --skip-dependency-verification && pnpm copy-package-id -n devnet",
"testnet:start": "testnet start",
"testnet:stop": "testnet stop",
"testnet:status": "testnet status",
"testnet:update": "testnet update",
"testnet:links": "testnet links",
"testnet:address": "tsui client active-address",
"testnet:deploy": "testnet publish --path ${PWD}/move/counter && pnpm copy-package-id -n testnet",
"testnet:deploy:no-dependency-check": "testnet publish --path ${PWD}/move/counter --skip-dependency-verification && pnpm copy-package-id -n testnet",
"mainnet:start": "mainnet start",
"mainnet:stop": "mainnet stop",
"mainnet:status": "mainnet status",
"mainnet:update": "mainnet update",
"mainnet:links": "mainnet links",
"mainnet:address": "msui client active-address",
"mainnet:deploy": "mainnet publish --path ${PWD}/move/counter && pnpm copy-package-id -n mainnet",
"mainnet:deploy:no-dependency-check": "mainnet publish --path ${PWD}/move/counter --skip-dependency-verification && pnpm copy-package-id -n mainnet"
},
"devDependencies": {
"env-file-rw": "^1.0.0",
"sui-explorer-local": "^2.4.0"
}
}
117 changes: 117 additions & 0 deletions packages/backend-counter/scripts/copy-package-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env node

/**
* The script copies the deployed package ID from the corresponding Suibase network file to .env.local of the frontend package,
* which is then read by the app.
*
* The default network is localnet. To change it, pass "-n [NETWORK_TYPE]" through console.
*/

const { promises } = require("node:fs");
const { homedir } = require("node:os");
const path = require("node:path");
const EnvFileWriter = require("env-file-rw").default;

const DEPLOYED_MODULE_NAME = "counter";

const main = async () => {
const network = getNetworkFromArgs();

// Read package ID from Suibase packageId file.
const packageId = await readPackageId(network);

// Create .env.local file if it doesn't exist.
const targetFile = targetFilePath();
await createFileIfNecessary(targetFile);

// Check whether the frontend is Next.js-powered to decide what environment variable name format to use.
const isNextJs = await isNextJsProject();

// Add Move package ID to .env.local or update it if it already exists.
await setEnvVar(
targetFile,
`${isNextJs ? "NEXT_PUBLIC" : "VITE"}_${network.toUpperCase()}_CONTRACT_PACKAGE_ID`,
packageId
);
};

const sourceFilePath = (network, deployedModuleName) => {
return path.join(
homedir(),
`/suibase/workdirs/${network}/published-data/${deployedModuleName}/most-recent/package-id.json`
);
};

const targetFilePath = () => {
return path.join(process.cwd(), "../frontend/.env.local");
};

const getNetworkFromArgs = () => {
const arg = process.argv.slice(2);

switch (arg[0]) {
case "-n":
return arg[1];

default:
return "localnet";
}
};

/**
* Read package ID from SuiBase packageId file.
*
* @param {string} network
* @returns
*/
const readPackageId = async (network) => {
const sourceFile = sourceFilePath(network, DEPLOYED_MODULE_NAME);
const data = await promises.readFile(sourceFile, "utf8");
return JSON.parse(data)[0];
};

/**
* Create a file if it doesn't exist.
*
* @param {string} filePath
* @returns
*/
const createFileIfNecessary = async (filePath) => {
try {
await promises.writeFile(filePath, "", { flag: "wx" });
} catch {}
};

/**
* Set the environment variable in the .env.local file.
*
* @param {string} envFilePath
* @param {string} name
* @param {string} value
* @returns
*/
const setEnvVar = async (envFilePath, name, value) => {
const envFileWriter = new EnvFileWriter(envFilePath, false);
await envFileWriter.parse();
envFileWriter.set(name, value);
await envFileWriter.save();
};

/**
* Check if next.config.ts exists in the frontend package.
*
* @returns {boolean}
*/
const isNextJsProject = async () => {
try {
await promises.stat(path.join(process.cwd(), "../frontend/next.config.ts"));
return true;
} catch {
return false;
}
};

// Main entry point.
main().catch((e) => {
console.error(e);
});
2 changes: 2 additions & 0 deletions packages/frontend-counter-react/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_APP_NAME="Sui dApp Starter"
VITE_APP_DESCRIPTION="Full-Stack Sui Starter on Steroids"
19 changes: 19 additions & 0 deletions packages/frontend-counter-react/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
File renamed without changes.
Loading

0 comments on commit 239c822

Please sign in to comment.