create-river-miniapp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/bin/create-river-miniapp.js +9 -0
- package/lib/create.js +259 -0
- package/package.json +30 -0
- package/templates/html/README.md +21 -0
- package/templates/html/_gitignore +4 -0
- package/templates/html/index.html +13 -0
- package/templates/html/miniapp.config.json +15 -0
- package/templates/html/package.json +18 -0
- package/templates/html/public/icon.svg +11 -0
- package/templates/html/scripts/check-miniapp.mjs +28 -0
- package/templates/html/scripts/package-miniapp.mjs +78 -0
- package/templates/html/src/main.js +44 -0
- package/templates/html/src/style.css +74 -0
- package/templates/html/vite.config.js +5 -0
- package/templates/react/README.md +21 -0
- package/templates/react/_gitignore +4 -0
- package/templates/react/index.html +13 -0
- package/templates/react/miniapp.config.json +15 -0
- package/templates/react/package.json +23 -0
- package/templates/react/public/icon.svg +13 -0
- package/templates/react/scripts/check-miniapp.mjs +28 -0
- package/templates/react/scripts/package-miniapp.mjs +78 -0
- package/templates/react/src/App.jsx +48 -0
- package/templates/react/src/main.jsx +10 -0
- package/templates/react/src/style.css +74 -0
- package/templates/react/vite.config.js +7 -0
- package/templates/vue/README.md +21 -0
- package/templates/vue/_gitignore +4 -0
- package/templates/vue/index.html +13 -0
- package/templates/vue/miniapp.config.json +15 -0
- package/templates/vue/package.json +22 -0
- package/templates/vue/public/icon.svg +11 -0
- package/templates/vue/scripts/check-miniapp.mjs +28 -0
- package/templates/vue/scripts/package-miniapp.mjs +78 -0
- package/templates/vue/src/App.vue +39 -0
- package/templates/vue/src/main.js +5 -0
- package/templates/vue/src/style.css +74 -0
- package/templates/vue/vite.config.js +7 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="./icon.svg" />
|
|
7
|
+
<title>{{APP_NAME}}</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="./src/main.jsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "{{APP_ID}}",
|
|
3
|
+
"name": "{{APP_NAME}}",
|
|
4
|
+
"slug": "{{PROJECT_NAME}}",
|
|
5
|
+
"description": "Generated by create-river-miniapp (react template)",
|
|
6
|
+
"entry": "index.html",
|
|
7
|
+
"icon": "icon.svg",
|
|
8
|
+
"requires_auth": false,
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"bridge_version": "1.0.0",
|
|
11
|
+
"tags": [
|
|
12
|
+
"react",
|
|
13
|
+
"template"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"check": "node scripts/check-miniapp.mjs",
|
|
11
|
+
"package:miniapp": "npm run build && node scripts/package-miniapp.mjs",
|
|
12
|
+
"package:miniapp:no-build": "node scripts/package-miniapp.mjs"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"react": "^19.2.0",
|
|
16
|
+
"react-dom": "^19.2.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
20
|
+
"archiver": "^7.0.1",
|
|
21
|
+
"vite": "^7.3.1"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#12457A"/>
|
|
5
|
+
<stop offset="1" stop-color="#61DAFB"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="96" height="96" rx="22" fill="url(#g)"/>
|
|
9
|
+
<circle cx="48" cy="48" r="7" fill="#fff"/>
|
|
10
|
+
<ellipse cx="48" cy="48" rx="30" ry="11.5" fill="none" stroke="#fff" stroke-width="5"/>
|
|
11
|
+
<ellipse cx="48" cy="48" rx="11.5" ry="30" fill="none" stroke="#fff" stroke-width="5" transform="rotate(60 48 48)"/>
|
|
12
|
+
<ellipse cx="48" cy="48" rx="11.5" ry="30" fill="none" stroke="#fff" stroke-width="5" transform="rotate(-60 48 48)"/>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const root = process.cwd();
|
|
5
|
+
const configPath = path.join(root, 'miniapp.config.json');
|
|
6
|
+
const distIndex = path.join(root, 'dist', 'index.html');
|
|
7
|
+
|
|
8
|
+
if (!fs.existsSync(configPath)) {
|
|
9
|
+
console.error('Missing miniapp.config.json');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
14
|
+
const required = ['id', 'name', 'slug', 'entry', 'icon', 'bridge_version'];
|
|
15
|
+
for (const key of required) {
|
|
16
|
+
const value = String(config[key] ?? '').trim();
|
|
17
|
+
if (!value) {
|
|
18
|
+
console.error(`miniapp.config.json missing field: ${key}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!fs.existsSync(distIndex)) {
|
|
24
|
+
console.error('Missing dist/index.html. Please run `npm run build` first.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('Mini-app check passed.');
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import archiver from 'archiver';
|
|
5
|
+
|
|
6
|
+
const root = process.cwd();
|
|
7
|
+
const configPath = path.join(root, 'miniapp.config.json');
|
|
8
|
+
const packageJsonPath = path.join(root, 'package.json');
|
|
9
|
+
const distDir = path.join(root, 'dist');
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(configPath)) {
|
|
12
|
+
throw new Error('Missing miniapp.config.json');
|
|
13
|
+
}
|
|
14
|
+
if (!fs.existsSync(distDir)) {
|
|
15
|
+
throw new Error('Missing dist directory. Please run `npm run build` first.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
19
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
20
|
+
|
|
21
|
+
const safeId = String(config.id).replace(/[^\w.-]+/g, '_');
|
|
22
|
+
const outputRoot = path.join(root, 'miniapp_output');
|
|
23
|
+
const packageDir = path.join(outputRoot, 'packages');
|
|
24
|
+
const zipName = `${safeId}.zip`;
|
|
25
|
+
const zipPath = path.join(packageDir, zipName);
|
|
26
|
+
|
|
27
|
+
fs.mkdirSync(packageDir, { recursive: true });
|
|
28
|
+
|
|
29
|
+
await new Promise((resolve, reject) => {
|
|
30
|
+
const output = fs.createWriteStream(zipPath);
|
|
31
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
32
|
+
output.on('close', resolve);
|
|
33
|
+
output.on('error', reject);
|
|
34
|
+
archive.on('error', reject);
|
|
35
|
+
archive.pipe(output);
|
|
36
|
+
archive.directory(distDir, false);
|
|
37
|
+
archive.finalize();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const sha256 = crypto
|
|
41
|
+
.createHash('sha256')
|
|
42
|
+
.update(fs.readFileSync(zipPath))
|
|
43
|
+
.digest('hex');
|
|
44
|
+
|
|
45
|
+
const slug = String(config.slug || safeId);
|
|
46
|
+
const iconPath = `./miniapps/${slug}/${config.icon}`;
|
|
47
|
+
const manifest = {
|
|
48
|
+
version: '1.0.0',
|
|
49
|
+
updated_at: new Date().toISOString(),
|
|
50
|
+
apps: [
|
|
51
|
+
{
|
|
52
|
+
id: String(config.id),
|
|
53
|
+
name: String(config.name),
|
|
54
|
+
version: String(packageJson.version || '1.0.0'),
|
|
55
|
+
url: `./miniapps/${slug}/${config.entry}`,
|
|
56
|
+
icon: iconPath,
|
|
57
|
+
package_url: `./packages/${zipName}`,
|
|
58
|
+
package_sha256: sha256,
|
|
59
|
+
description: String(config.description || ''),
|
|
60
|
+
requires_auth: Boolean(config.requires_auth),
|
|
61
|
+
enabled: Boolean(config.enabled ?? true),
|
|
62
|
+
order: Number(config.order ?? 0),
|
|
63
|
+
bridge_version: String(config.bridge_version || '1.0.0'),
|
|
64
|
+
tags: Array.isArray(config.tags) ? config.tags : [],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
fs.mkdirSync(outputRoot, { recursive: true });
|
|
70
|
+
fs.writeFileSync(
|
|
71
|
+
path.join(outputRoot, 'miniapps.partial.json'),
|
|
72
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
73
|
+
'utf-8',
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
console.log(`Packed: ${zipPath}`);
|
|
77
|
+
console.log(`SHA256: ${sha256}`);
|
|
78
|
+
console.log('Manifest snippet: miniapp_output/miniapps.partial.json');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export default function App() {
|
|
4
|
+
const [output, setOutput] = useState('等待操作...');
|
|
5
|
+
|
|
6
|
+
const callBridge = async (action, payload = {}) => {
|
|
7
|
+
if (!window.RiverMiniApp || typeof window.RiverMiniApp.call !== 'function') {
|
|
8
|
+
setOutput(
|
|
9
|
+
JSON.stringify(
|
|
10
|
+
{ ok: false, message: 'Bridge 未注入,当前为普通浏览器模式。' },
|
|
11
|
+
null,
|
|
12
|
+
2,
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const result = await window.RiverMiniApp.call(action, payload);
|
|
19
|
+
setOutput(JSON.stringify({ ok: true, result }, null, 2));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
setOutput(JSON.stringify({ ok: false, message: String(error) }, null, 2));
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<main className="container">
|
|
27
|
+
<section className="card">
|
|
28
|
+
<h1>{{APP_NAME}}</h1>
|
|
29
|
+
<p>模板:React · 项目:{{PROJECT_NAME}}</p>
|
|
30
|
+
<div className="actions">
|
|
31
|
+
<button type="button" onClick={() => callBridge('getContext')}>
|
|
32
|
+
获取上下文
|
|
33
|
+
</button>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
onClick={() => callBridge('setTitle', { title: '{{APP_NAME}} 已连接' })}
|
|
37
|
+
>
|
|
38
|
+
修改标题
|
|
39
|
+
</button>
|
|
40
|
+
<button type="button" onClick={() => callBridge('close')}>
|
|
41
|
+
关闭小程序
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
44
|
+
<pre>{output}</pre>
|
|
45
|
+
</section>
|
|
46
|
+
</main>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
* {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
background: linear-gradient(160deg, #f6f8fc 0%, #eef3ff 100%);
|
|
13
|
+
color: #172033;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.container {
|
|
17
|
+
min-height: 100dvh;
|
|
18
|
+
display: grid;
|
|
19
|
+
place-items: center;
|
|
20
|
+
padding: 24px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.card {
|
|
24
|
+
width: min(680px, 100%);
|
|
25
|
+
border-radius: 20px;
|
|
26
|
+
background: #ffffffcc;
|
|
27
|
+
backdrop-filter: blur(10px);
|
|
28
|
+
border: 1px solid #dde6fa;
|
|
29
|
+
box-shadow: 0 20px 50px #12457a1a;
|
|
30
|
+
padding: 20px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h1 {
|
|
34
|
+
margin: 0 0 8px;
|
|
35
|
+
font-size: 28px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
p {
|
|
39
|
+
margin: 0 0 16px;
|
|
40
|
+
color: #4b607e;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.actions {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-wrap: wrap;
|
|
46
|
+
gap: 10px;
|
|
47
|
+
margin-bottom: 16px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
button {
|
|
51
|
+
border: 0;
|
|
52
|
+
border-radius: 12px;
|
|
53
|
+
padding: 10px 14px;
|
|
54
|
+
background: #12457a;
|
|
55
|
+
color: white;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
font-weight: 600;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
button:hover {
|
|
61
|
+
background: #0e3863;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pre {
|
|
65
|
+
margin: 0;
|
|
66
|
+
border-radius: 12px;
|
|
67
|
+
padding: 12px;
|
|
68
|
+
min-height: 120px;
|
|
69
|
+
overflow: auto;
|
|
70
|
+
background: #eff4ff;
|
|
71
|
+
border: 1px solid #d5e2fb;
|
|
72
|
+
font-size: 12px;
|
|
73
|
+
line-height: 1.45;
|
|
74
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# {{APP_NAME}}
|
|
2
|
+
|
|
3
|
+
由 `create-river-miniapp` 生成(Vue 模板)。
|
|
4
|
+
|
|
5
|
+
## 开发
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 打包
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm run package:miniapp
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
产物:
|
|
19
|
+
|
|
20
|
+
- `miniapp_output/packages/{{APP_ID}}.zip`
|
|
21
|
+
- `miniapp_output/miniapps.partial.json`
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="./icon.svg" />
|
|
7
|
+
<title>{{APP_NAME}}</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app"></div>
|
|
11
|
+
<script type="module" src="./src/main.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "{{APP_ID}}",
|
|
3
|
+
"name": "{{APP_NAME}}",
|
|
4
|
+
"slug": "{{PROJECT_NAME}}",
|
|
5
|
+
"description": "Generated by create-river-miniapp (vue template)",
|
|
6
|
+
"entry": "index.html",
|
|
7
|
+
"icon": "icon.svg",
|
|
8
|
+
"requires_auth": false,
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"bridge_version": "1.0.0",
|
|
11
|
+
"tags": [
|
|
12
|
+
"vue",
|
|
13
|
+
"template"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"check": "node scripts/check-miniapp.mjs",
|
|
11
|
+
"package:miniapp": "npm run build && node scripts/package-miniapp.mjs",
|
|
12
|
+
"package:miniapp:no-build": "node scripts/package-miniapp.mjs"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"vue": "^3.5.27"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
19
|
+
"archiver": "^7.0.1",
|
|
20
|
+
"vite": "^7.3.1"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#12457A"/>
|
|
5
|
+
<stop offset="1" stop-color="#42b883"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="96" height="96" rx="22" fill="url(#g)"/>
|
|
9
|
+
<path d="M48 16 24.7 56.3h14.7L48 41l8.6 15.3h14.7Z" fill="#41b883"/>
|
|
10
|
+
<path d="M48 16 39.4 31.3 48 46.6l8.6-15.3Z" fill="#34495e"/>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const root = process.cwd();
|
|
5
|
+
const configPath = path.join(root, 'miniapp.config.json');
|
|
6
|
+
const distIndex = path.join(root, 'dist', 'index.html');
|
|
7
|
+
|
|
8
|
+
if (!fs.existsSync(configPath)) {
|
|
9
|
+
console.error('Missing miniapp.config.json');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
14
|
+
const required = ['id', 'name', 'slug', 'entry', 'icon', 'bridge_version'];
|
|
15
|
+
for (const key of required) {
|
|
16
|
+
const value = String(config[key] ?? '').trim();
|
|
17
|
+
if (!value) {
|
|
18
|
+
console.error(`miniapp.config.json missing field: ${key}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!fs.existsSync(distIndex)) {
|
|
24
|
+
console.error('Missing dist/index.html. Please run `npm run build` first.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('Mini-app check passed.');
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import archiver from 'archiver';
|
|
5
|
+
|
|
6
|
+
const root = process.cwd();
|
|
7
|
+
const configPath = path.join(root, 'miniapp.config.json');
|
|
8
|
+
const packageJsonPath = path.join(root, 'package.json');
|
|
9
|
+
const distDir = path.join(root, 'dist');
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(configPath)) {
|
|
12
|
+
throw new Error('Missing miniapp.config.json');
|
|
13
|
+
}
|
|
14
|
+
if (!fs.existsSync(distDir)) {
|
|
15
|
+
throw new Error('Missing dist directory. Please run `npm run build` first.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
19
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
20
|
+
|
|
21
|
+
const safeId = String(config.id).replace(/[^\w.-]+/g, '_');
|
|
22
|
+
const outputRoot = path.join(root, 'miniapp_output');
|
|
23
|
+
const packageDir = path.join(outputRoot, 'packages');
|
|
24
|
+
const zipName = `${safeId}.zip`;
|
|
25
|
+
const zipPath = path.join(packageDir, zipName);
|
|
26
|
+
|
|
27
|
+
fs.mkdirSync(packageDir, { recursive: true });
|
|
28
|
+
|
|
29
|
+
await new Promise((resolve, reject) => {
|
|
30
|
+
const output = fs.createWriteStream(zipPath);
|
|
31
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
32
|
+
output.on('close', resolve);
|
|
33
|
+
output.on('error', reject);
|
|
34
|
+
archive.on('error', reject);
|
|
35
|
+
archive.pipe(output);
|
|
36
|
+
archive.directory(distDir, false);
|
|
37
|
+
archive.finalize();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const sha256 = crypto
|
|
41
|
+
.createHash('sha256')
|
|
42
|
+
.update(fs.readFileSync(zipPath))
|
|
43
|
+
.digest('hex');
|
|
44
|
+
|
|
45
|
+
const slug = String(config.slug || safeId);
|
|
46
|
+
const iconPath = `./miniapps/${slug}/${config.icon}`;
|
|
47
|
+
const manifest = {
|
|
48
|
+
version: '1.0.0',
|
|
49
|
+
updated_at: new Date().toISOString(),
|
|
50
|
+
apps: [
|
|
51
|
+
{
|
|
52
|
+
id: String(config.id),
|
|
53
|
+
name: String(config.name),
|
|
54
|
+
version: String(packageJson.version || '1.0.0'),
|
|
55
|
+
url: `./miniapps/${slug}/${config.entry}`,
|
|
56
|
+
icon: iconPath,
|
|
57
|
+
package_url: `./packages/${zipName}`,
|
|
58
|
+
package_sha256: sha256,
|
|
59
|
+
description: String(config.description || ''),
|
|
60
|
+
requires_auth: Boolean(config.requires_auth),
|
|
61
|
+
enabled: Boolean(config.enabled ?? true),
|
|
62
|
+
order: Number(config.order ?? 0),
|
|
63
|
+
bridge_version: String(config.bridge_version || '1.0.0'),
|
|
64
|
+
tags: Array.isArray(config.tags) ? config.tags : [],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
fs.mkdirSync(outputRoot, { recursive: true });
|
|
70
|
+
fs.writeFileSync(
|
|
71
|
+
path.join(outputRoot, 'miniapps.partial.json'),
|
|
72
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
73
|
+
'utf-8',
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
console.log(`Packed: ${zipPath}`);
|
|
77
|
+
console.log(`SHA256: ${sha256}`);
|
|
78
|
+
console.log('Manifest snippet: miniapp_output/miniapps.partial.json');
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
const output = ref('等待操作...');
|
|
5
|
+
|
|
6
|
+
const callBridge = async (action, payload = {}) => {
|
|
7
|
+
if (!window.RiverMiniApp || typeof window.RiverMiniApp.call !== 'function') {
|
|
8
|
+
output.value = JSON.stringify(
|
|
9
|
+
{ ok: false, message: 'Bridge 未注入,当前为普通浏览器模式。' },
|
|
10
|
+
null,
|
|
11
|
+
2,
|
|
12
|
+
);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const result = await window.RiverMiniApp.call(action, payload);
|
|
17
|
+
output.value = JSON.stringify({ ok: true, result }, null, 2);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
output.value = JSON.stringify({ ok: false, message: String(error) }, null, 2);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<main class="container">
|
|
26
|
+
<section class="card">
|
|
27
|
+
<h1>{{APP_NAME}}</h1>
|
|
28
|
+
<p>模板:Vue · 项目:{{PROJECT_NAME}}</p>
|
|
29
|
+
<div class="actions">
|
|
30
|
+
<button type="button" @click="callBridge('getContext')">获取上下文</button>
|
|
31
|
+
<button type="button" @click="callBridge('setTitle', { title: '{{APP_NAME}} 已连接' })">
|
|
32
|
+
修改标题
|
|
33
|
+
</button>
|
|
34
|
+
<button type="button" @click="callBridge('close')">关闭小程序</button>
|
|
35
|
+
</div>
|
|
36
|
+
<pre>{{ output }}</pre>
|
|
37
|
+
</section>
|
|
38
|
+
</main>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
* {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
background: linear-gradient(160deg, #f6f8fc 0%, #eef3ff 100%);
|
|
13
|
+
color: #172033;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.container {
|
|
17
|
+
min-height: 100dvh;
|
|
18
|
+
display: grid;
|
|
19
|
+
place-items: center;
|
|
20
|
+
padding: 24px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.card {
|
|
24
|
+
width: min(680px, 100%);
|
|
25
|
+
border-radius: 20px;
|
|
26
|
+
background: #ffffffcc;
|
|
27
|
+
backdrop-filter: blur(10px);
|
|
28
|
+
border: 1px solid #dde6fa;
|
|
29
|
+
box-shadow: 0 20px 50px #12457a1a;
|
|
30
|
+
padding: 20px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h1 {
|
|
34
|
+
margin: 0 0 8px;
|
|
35
|
+
font-size: 28px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
p {
|
|
39
|
+
margin: 0 0 16px;
|
|
40
|
+
color: #4b607e;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.actions {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-wrap: wrap;
|
|
46
|
+
gap: 10px;
|
|
47
|
+
margin-bottom: 16px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
button {
|
|
51
|
+
border: 0;
|
|
52
|
+
border-radius: 12px;
|
|
53
|
+
padding: 10px 14px;
|
|
54
|
+
background: #12457a;
|
|
55
|
+
color: white;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
font-weight: 600;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
button:hover {
|
|
61
|
+
background: #0e3863;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pre {
|
|
65
|
+
margin: 0;
|
|
66
|
+
border-radius: 12px;
|
|
67
|
+
padding: 12px;
|
|
68
|
+
min-height: 120px;
|
|
69
|
+
overflow: auto;
|
|
70
|
+
background: #eff4ff;
|
|
71
|
+
border: 1px solid #d5e2fb;
|
|
72
|
+
font-size: 12px;
|
|
73
|
+
line-height: 1.45;
|
|
74
|
+
}
|