Skip to content

Commit

Permalink
feat(template): add did wallet dapp template (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaomoziyi authored Sep 26, 2024
1 parent 5ba2196 commit ebe64dd
Show file tree
Hide file tree
Showing 72 changed files with 1,045 additions and 2,146 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.6 (2024-9-25)

- feat(template): add did wallet dapp template

## 0.9.5 (2024-9-23)

- fix: todo list template
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "create-blocklet",
"private": true,
"version": "0.9.5",
"version": "0.9.6",
"description": "",
"keywords": [],
"author": "",
Expand Down
70 changes: 70 additions & 0 deletions packages/create-app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ const templates = [
display: '[dapp] react + express + typescript',
color: yellow,
},
{
name: 'did-wallet-dapp',
display: '[dapp: did-wallet] Full stack app (react.js + express.js) with DID Wallet integration',
color: yellow,
},
{
name: 'todo-list-example',
display: '[dapp: todo-list] react + express + typescript + DID Spaces',
Expand Down Expand Up @@ -148,6 +153,68 @@ function checkDid(did = '') {
return true;
}

function extractContent(content, section) {
const regex = new RegExp(`## ${section}\\s*([\\s\\S]*?)(?=\\n## |$)`, 'i');
const match = content.match(regex);
return match ? match[1].trim() : '';
}

function mergeReadme(templateName, targetDir, isMonorepo = false) {
const ignoreTemplates = ['todo-list-example'];
if (ignoreTemplates.includes(templateName)) {
return;
}
const commonReadmePath = path.join(__dirname, 'templates', 'base-readme.md');
const templateReadmePath = path.join(__dirname, 'templates', templateName, 'README.md');
const targetReadmePath = isMonorepo
? path.join(targetDir, 'blocklets', templateName, 'README.md')
: path.join(targetDir, 'README.md');

let commonContent = fs.readFileSync(commonReadmePath, 'utf8');
const templateContent = fs.existsSync(templateReadmePath) ? fs.readFileSync(templateReadmePath, 'utf8') : '';

const templateSections = templateContent.match(/^## .+$/gm) || [];

templateSections.forEach((section) => {
const sectionName = section.replace('## ', '');
const templateSection = extractContent(templateContent, sectionName);

if (commonContent.includes(`## ${sectionName}`)) {
// If the section exists in the base readme
if (templateSection.trim()) {
// If the template section is not empty, replace the entire section
const sectionRegex = new RegExp(`## ${sectionName}[\\s\\S]*?(?=\\n## |$)`, 'g');
commonContent = commonContent.replace(sectionRegex, `\n\n## ${sectionName}\n\n${templateSection}`);
} else {
// If the template section is empty, remove the entire section including the title
const sectionRegex = new RegExp(`\n*## ${sectionName}[\\s\\S]*?(?=\n## |$)`, 'g');
commonContent = commonContent.replace(sectionRegex, '');
}
} else {
// If the section doesn't exist in the base readme, replace the corresponding variable
const variableRegex = new RegExp(`\\{${sectionName}\\}`, 'g');
commonContent = commonContent.replace(variableRegex, templateSection);
}
});

// Remove remaining unreplaced variables
commonContent = commonContent.replace(/\{[A-Z_]+\}\n*/g, '');

// Remove empty sections (sections with only title and no content)
commonContent = commonContent.replace(/\n*## [^\n]+\n+(?=## |$)/g, '');

// Ensure two newlines before each section
commonContent = commonContent.replace(/\n*(## [^\n]+)/g, '\n\n$1');

// Remove leading newlines
commonContent = commonContent.replace(/^\n+/, '');

// Remove multiple consecutive newlines, keeping at most two
commonContent = commonContent.replace(/\n{3,}/g, '\n\n');

// Write the merged README file
fs.writeFileSync(targetReadmePath, commonContent.trim());
}
async function init() {
const { version } = await fs.readJSONSync(path.resolve(__dirname, 'package.json'));
await echoBrand({ version });
Expand Down Expand Up @@ -382,6 +449,9 @@ async function init() {
}
})();

// merge readme
mergeReadme(templateName, root, !!mainBlocklet);

modifyPackage(
(pkg) => {
pkg.name = mainBlocklet ? finalTemplateName : name;
Expand Down
2 changes: 1 addition & 1 deletion packages/create-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-blocklet",
"version": "0.9.5",
"version": "0.9.6",
"exports": "./index.js",
"type": "module",
"repository": "[email protected]:blocklet/create-blocklet.git",
Expand Down
103 changes: 103 additions & 0 deletions packages/create-app/templates/base-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Getting Started with Create Blocklet

This project was bootstrapped with [Create Blocklet](https://github.com/blocklet/create-blocklet).

{PROJECT_DESCRIPTION}

## File Structure

{FILE_STRUCTURE}

## Development

1. Make sure you have [@blocklet/cli](https://www.npmjs.com/package/@blocklet/cli) installed

Blocklet needs blocklet server as a dependency. So you need to install it first.
`npm install -g @blocklet/cli`
See details in [ https://www.arcblock.io/docs/blocklet-developer/install-blocklet-cli#start-blocklet-server]( https://www.arcblock.io/docs/blocklet-developer/install-blocklet-cli#start-blocklet-server)

2. Init blocklet server & start blocklet server

Before starting an blocklet server, you need to init blocklet server.
`blocklet server init`
`blocklet server start`
See details in [https://www.arcblock.io/docs/blocklet-developer/install-blocklet-cli#start-blocklet-server](https://www.arcblock.io/docs/blocklet-developer/install-blocklet-cli#start-blocklet-server)

3. Go to the project directory `cd [name]`
4. Install dependencies: `npm install` or `yarn`
5. Start development server: `blocklet dev`

## Bundle

After developing a blocklet, you may need to bundle it. Use `npm run bundle` command.

## Deploy

- If you want to deploy this blocklet to local blocklet server, you can use `blocklet deploy .blocklet/bundle --app-id {appId}` command(Make sure the blocklet is bundled before deployment).
- appId is the id of the container you want to run on your server, you can see it in your server's dashboard
- If you want to deploy this blocklet to remote blocklet server, you can use the command below.

```shell
blocklet deploy .blocklet/bundle --endpoint {your blocklet server url} --access-key {blocklet server access key} --access-secret {blocklet server access secret}
```

## Upload to blocklet store

- If you want to upload the blocklet to any store for other users to download and use, you can following the following instructions.

Bump version at first.

```shell
npm run bump-version
```

Connect to a store, You may need some testnet tokens to deploy your blocklet, you can get some from https://faucet.abtnetwork.io/

```shell
blocklet connect https://test.store.blocklet.dev/
```

Upload a new version to a store.

> Make sure the blocklet is bundled before upload.
```shell
blocklet upload
```

Or you can simply use `npm run upload` command.

- You also can upload a new version to a store by Github CI.
Bump version at first.

```shell
npm run bump-version
```

Push your code to Github main/master branch, or make a pull request to the main/master branch.
The CI workflow will automatically upload a new version to a store.


## Q & A

1. Q: How to change a blocklet's logo?

Change the `logo.png` file root folder.

Or you can change the `logo` field in the `blocklet.yml` file.

> Make sure you have added the logo path to the `blocklet.yml` file `files` field.
{QA_SECTION}

## Learn More

- Full specification of `blocklet.yml`: [https://github.com/blocklet/blocklet-specification/blob/main/docs/meta.md](https://github.com/blocklet/blocklet-specification/blob/main/docs/meta.md)
- Full document of Blocklet Server & blocklet development: [https://www.arcblock.io/docs/blocklet-developer](https://www.arcblock.io/docs/blocklet-developer)



## License

The code is licensed under the Apache 2.0 license found in the
[LICENSE](LICENSE) file.
7 changes: 7 additions & 0 deletions packages/create-app/templates/did-wallet-dapp/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
root: true,
extends: '@arcblock/eslint-config',
globals: {
logger: true,
},
};
32 changes: 32 additions & 0 deletions packages/create-app/templates/did-wallet-dapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## PROJECT_DESCRIPTION

This is a full stack app (react.js + express.js) with DID Wallet integration. That means you can use your DID wallet to login and get a user session.

## FILE_STRUCTURE

- public/ - static files
- favicon.ico - favicon
- favicon.svg - favicon
- index.html - main html file, template for react
- screenshots/ - Screenshots
- api/ - Api side code
- hooks/ - blocklet lifecycle hooks
- libs/ - Api side libraries
- middlewares/ - Api side middlewares
- routes/ - Api side routes
- index.js - Api side entry point
- src/ - Client side code (A standard react app structure)
- .env - Environment variables
- .env.local - Local environment variables
- .eslintrc.js - ESLint configuration
- .gitignore - Git ignore file
- .prettierrc - Prettier configuration
- blocklet.md - Blocklet README
- blocklet.yml - Blocklet configuration
- LICENSE - License file
- logo.png - Blocklet logo file
- package.json - Npm package file
- README.md - A guide for this blocklet
- version - Version file


8 changes: 8 additions & 0 deletions packages/create-app/templates/did-wallet-dapp/api/dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { app, server } = require('./index');

import('vite-plugin-blocklet').then(({ setupClient }) => {
setupClient(app, {
server,
importMetaHot: import.meta.hot,
});
});
65 changes: 65 additions & 0 deletions packages/create-app/templates/did-wallet-dapp/api/functions/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* eslint-disable no-console */
const path = require('path');
const cors = require('cors');
const morgan = require('morgan');
const express = require('express');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const fallback = require('@blocklet/sdk/lib/middlewares/fallback');

const userRoutes = require('../routes/user');

const isProduction = process.env.NODE_ENV !== 'development';

// Create and config express application
const server = express();
server.use(cookieParser());
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({ extended: true }));
server.use(cors());

const router = express.Router();

userRoutes.init(router);

if (isProduction) {
server.use(
morgan((tokens, req, res) => {
const log = [
tokens.method(req, res),
tokens.url(req, res),
tokens.status(req, res),
tokens.res(req, res, 'content-length'),
'-',
tokens['response-time'](req, res),
'ms',
].join(' ');

if (isProduction) {
// Log only in AWS context to get back function logs
console.log(log);
}

return log;
}),
);
server.use(router);

const staticDir = path.resolve(__dirname, '../../', 'dist');
server.use(express.static(staticDir, { maxAge: '365d', index: false }));
server.use(fallback('index.html', { root: staticDir }));

server.use((req, res) => {
res.status(404).send('404 NOT FOUND');
});

// eslint-disable-next-line no-unused-vars
server.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
} else {
server.use(router);
}

module.exports = { server };
13 changes: 13 additions & 0 deletions packages/create-app/templates/did-wallet-dapp/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable no-console */
const dotenv = require('dotenv-flow');
dotenv.config();

const { server: app } = require('./functions/app');

const port = parseInt(process.env.BLOCKLET_PORT, 10) || 3030;
const server = app.listen(port, (err) => {
if (err) throw err;
console.log(`> app ready on ${port}`);
});

module.exports = { app, server };
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
30 changes: 30 additions & 0 deletions packages/create-app/templates/did-wallet-dapp/api/routes/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const AuthService = require('@blocklet/sdk/service/auth');
const middlewares = require('@blocklet/sdk/lib/middlewares');

const authClient = new AuthService();

module.exports = {
init(app) {
// middleware.user() is used to get the user info from the session, see more: https://www.arcblock.io/docs/blocklet-developer/blocklet-sdk#session
app.get('/api/user', middlewares.user(), async (req, res) => {
if (!req.user) {
res.json({ user: null });
return;
}
try {
// get user info from auth service
const { user } = await authClient.getUser(req.user.did);
user.role = user.role || req.user.role;
res.json({ user });
} catch (err) {
console.error(err);
res.json({ user: null });
}
});
app.get('/api/data', (req, res) => {
res.json({
message: 'Hello Blocklet!',
});
});
},
};
3 changes: 3 additions & 0 deletions packages/create-app/templates/did-wallet-dapp/blocklet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please update here

A did wallet dapp template.
Loading

0 comments on commit ebe64dd

Please sign in to comment.