create-ax-trusted-plugin 1.0.5 → 1.0.6
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 +56 -56
- package/bin/index.mjs +111 -111
- package/package.json +27 -27
- package/template/README.md +33 -33
- package/template/manifest.json +17 -17
- package/template/package.json +27 -27
- package/template/src/index.ts +44 -42
- package/template/vite.config.ts +83 -83
package/README.md
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
# create-ax-trusted-plugin
|
|
2
|
-
|
|
3
|
-
可信(internal)插件**脚手架**:一条命令生成一个基于 `@ax-npm/host-trusted-sdk-v4` 的可信插件骨架
|
|
4
|
-
(默认「形态一」:ESM 直出 `activate` + 自带 Vue + 自定义元素面板 + 调 `map.flyTo` + 遥测订阅;
|
|
5
|
-
dev 走裸 `vite`,原生 sourcemap、无构建步)。
|
|
6
|
-
|
|
7
|
-
> 脚手架本身已发**公网 npm**,生成这步**零配置**;生成出的工程才走私有源(`192.168.1.209` GitLab)拉
|
|
8
|
-
> `@ax-npm/*` 依赖,届时只需一个 `GITLAB_TOKEN`(无需手建任何 `.npmrc`,工程自带)。
|
|
9
|
-
|
|
10
|
-
## 用法
|
|
11
|
-
```bash
|
|
12
|
-
# 交互式(缺参数会提示输入 id/标题/锚点)
|
|
13
|
-
npm create ax-trusted-plugin@latest my-panel
|
|
14
|
-
|
|
15
|
-
# 非交互(传参)
|
|
16
|
-
npm create ax-trusted-plugin@latest my-panel -- --title "我的面板" --anchor top-right
|
|
17
|
-
```
|
|
18
|
-
等价于直接跑 bin:`node bin/index.mjs my-panel --title "我的面板" --anchor top-right`。
|
|
19
|
-
|
|
20
|
-
生成 `my-panel/` 后(把 `GITLAB_TOKEN` 换成你的只读 PAT,按所用 shell 选一行):
|
|
21
|
-
```bash
|
|
22
|
-
cd my-panel
|
|
23
|
-
export GITLAB_TOKEN=<读权限 PAT> # bash / git-bash
|
|
24
|
-
# $env:GITLAB_TOKEN="<读权限 PAT>" # PowerShell
|
|
25
|
-
# set GITLAB_TOKEN=<读权限 PAT> # cmd
|
|
26
|
-
npm install
|
|
27
|
-
npm run dev
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## 参数
|
|
31
|
-
| 参数 | 说明 | 默认 |
|
|
32
|
-
|---|---|---|
|
|
33
|
-
| `<id>`(位置参数) | 插件 id,小写短横线 | 必填(缺则提示) |
|
|
34
|
-
| `--title` | 面板标题 | 由 id 推导 |
|
|
35
|
-
| `--anchor` | 浮层锚点 | `top-right` |
|
|
36
|
-
|
|
37
|
-
锚点可选:`top-left/top-right/top-center/bottom-left/bottom-right/left-center/right-center/center`。
|
|
38
|
-
|
|
39
|
-
## 生成内容
|
|
40
|
-
`template/` 经占位替换(`__ID__`/`__TITLE__`/`__ANCHOR__`)后落地:`src/{index.ts,Panel.vue,state.ts}`、
|
|
41
|
-
`manifest.json`(trust:internal、entry:main.js)、`vite.config.ts`(形态一 lib 构建 + dev 市场源桥)、
|
|
42
|
-
`package.json`、`tsconfig.json`、`.npmrc`、`.gitignore`、`README.md`。
|
|
43
|
-
|
|
44
|
-
## 发布(维护者)
|
|
45
|
-
脚手架包发**公网 npm**(`publishConfig` 已指向 `registry.npmjs.org`、`access: public`):
|
|
46
|
-
```bash
|
|
47
|
-
npm login --registry https://registry.npmjs.org/ # 首次
|
|
48
|
-
npm publish --otp=<6位验证码> # npm 强制 2FA,验证码来自验证器 App
|
|
49
|
-
```
|
|
50
|
-
- 若提示包名被占,改 `package.json` 的 `name`(及随之的 `npm create <name>` 用法)。
|
|
51
|
-
- 自动化/CI 免输码:在 npm 生成「可绕过 2FA 的 Granular Access Token」,
|
|
52
|
-
以 `//registry.npmjs.org/:_authToken=<token>` 注入后 `npm publish`。
|
|
53
|
-
|
|
54
|
-
> 注意:`template/` 内仍引用私网地址 `192.168.1.209`(RFC1918 不可路由,非机密),发公网后会公开可见。
|
|
55
|
-
|
|
56
|
-
源码仓:GitLab 项目 1244。
|
|
1
|
+
# create-ax-trusted-plugin
|
|
2
|
+
|
|
3
|
+
可信(internal)插件**脚手架**:一条命令生成一个基于 `@ax-npm/host-trusted-sdk-v4` 的可信插件骨架
|
|
4
|
+
(默认「形态一」:ESM 直出 `activate` + 自带 Vue + 自定义元素面板 + 调 `map.flyTo` + 遥测订阅;
|
|
5
|
+
dev 走裸 `vite`,原生 sourcemap、无构建步)。
|
|
6
|
+
|
|
7
|
+
> 脚手架本身已发**公网 npm**,生成这步**零配置**;生成出的工程才走私有源(`192.168.1.209` GitLab)拉
|
|
8
|
+
> `@ax-npm/*` 依赖,届时只需一个 `GITLAB_TOKEN`(无需手建任何 `.npmrc`,工程自带)。
|
|
9
|
+
|
|
10
|
+
## 用法
|
|
11
|
+
```bash
|
|
12
|
+
# 交互式(缺参数会提示输入 id/标题/锚点)
|
|
13
|
+
npm create ax-trusted-plugin@latest my-panel
|
|
14
|
+
|
|
15
|
+
# 非交互(传参)
|
|
16
|
+
npm create ax-trusted-plugin@latest my-panel -- --title "我的面板" --anchor top-right
|
|
17
|
+
```
|
|
18
|
+
等价于直接跑 bin:`node bin/index.mjs my-panel --title "我的面板" --anchor top-right`。
|
|
19
|
+
|
|
20
|
+
生成 `my-panel/` 后(把 `GITLAB_TOKEN` 换成你的只读 PAT,按所用 shell 选一行):
|
|
21
|
+
```bash
|
|
22
|
+
cd my-panel
|
|
23
|
+
export GITLAB_TOKEN=<读权限 PAT> # bash / git-bash
|
|
24
|
+
# $env:GITLAB_TOKEN="<读权限 PAT>" # PowerShell
|
|
25
|
+
# set GITLAB_TOKEN=<读权限 PAT> # cmd
|
|
26
|
+
npm install
|
|
27
|
+
npm run dev
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 参数
|
|
31
|
+
| 参数 | 说明 | 默认 |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `<id>`(位置参数) | 插件 id,小写短横线 | 必填(缺则提示) |
|
|
34
|
+
| `--title` | 面板标题 | 由 id 推导 |
|
|
35
|
+
| `--anchor` | 浮层锚点 | `top-right` |
|
|
36
|
+
|
|
37
|
+
锚点可选:`top-left/top-right/top-center/bottom-left/bottom-right/left-center/right-center/center`。
|
|
38
|
+
|
|
39
|
+
## 生成内容
|
|
40
|
+
`template/` 经占位替换(`__ID__`/`__TITLE__`/`__ANCHOR__`)后落地:`src/{index.ts,Panel.vue,state.ts}`、
|
|
41
|
+
`manifest.json`(trust:internal、entry:main.js)、`vite.config.ts`(形态一 lib 构建 + dev 市场源桥)、
|
|
42
|
+
`package.json`、`tsconfig.json`、`.npmrc`、`.gitignore`、`README.md`。
|
|
43
|
+
|
|
44
|
+
## 发布(维护者)
|
|
45
|
+
脚手架包发**公网 npm**(`publishConfig` 已指向 `registry.npmjs.org`、`access: public`):
|
|
46
|
+
```bash
|
|
47
|
+
npm login --registry https://registry.npmjs.org/ # 首次
|
|
48
|
+
npm publish --otp=<6位验证码> # npm 强制 2FA,验证码来自验证器 App
|
|
49
|
+
```
|
|
50
|
+
- 若提示包名被占,改 `package.json` 的 `name`(及随之的 `npm create <name>` 用法)。
|
|
51
|
+
- 自动化/CI 免输码:在 npm 生成「可绕过 2FA 的 Granular Access Token」,
|
|
52
|
+
以 `//registry.npmjs.org/:_authToken=<token>` 注入后 `npm publish`。
|
|
53
|
+
|
|
54
|
+
> 注意:`template/` 内仍引用私网地址 `192.168.1.209`(RFC1918 不可路由,非机密),发公网后会公开可见。
|
|
55
|
+
|
|
56
|
+
源码仓:GitLab 项目 1244。
|
package/bin/index.mjs
CHANGED
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// create-ax-trusted-plugin —— 可信(internal)插件脚手架(发布于公网 npm)。
|
|
3
|
-
// 用法:
|
|
4
|
-
// npm create ax-trusted-plugin <id> [-- --title "标题" --anchor top-right]
|
|
5
|
-
// node bin/index.mjs <id> --title "标题" --anchor top-right
|
|
6
|
-
// 把 template/ 复制到 <id>/,按 id/title/anchor 做占位替换(dotfile 以 _ 前缀存放,生成时改名)。
|
|
7
|
-
import { readdir, readFile, writeFile, mkdir, stat } from 'node:fs/promises';
|
|
8
|
-
import { existsSync } from 'node:fs';
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { dirname, join, resolve, relative } from 'node:path';
|
|
11
|
-
import { createInterface } from 'node:readline/promises';
|
|
12
|
-
|
|
13
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const TEMPLATE = resolve(here, '..', 'template');
|
|
15
|
-
|
|
16
|
-
const ANCHORS = [
|
|
17
|
-
'top-left', 'top-right', 'top-center',
|
|
18
|
-
'bottom-left', 'bottom-right',
|
|
19
|
-
'left-center', 'right-center', 'center',
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
function parseArgs(argv) {
|
|
23
|
-
const out = { _: [] };
|
|
24
|
-
for (let i = 0; i < argv.length; i++) {
|
|
25
|
-
const a = argv[i];
|
|
26
|
-
if (a.startsWith('--')) out[a.slice(2)] = argv[++i] ?? '';
|
|
27
|
-
else out._.push(a);
|
|
28
|
-
}
|
|
29
|
-
return out;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function toTitle(id) {
|
|
33
|
-
return id.replace(/[-_]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function prompt(rl, q, def) {
|
|
37
|
-
if (!process.stdin.isTTY) return def;
|
|
38
|
-
const ans = (await rl.question(`${q}${def ? ` (${def})` : ''}: `)).trim();
|
|
39
|
-
return ans || def;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// 复制 + 占位替换 + dotfile 改名(_gitignore→.gitignore、_npmrc→.npmrc)。
|
|
43
|
-
async function scaffold(srcDir, destDir, vars) {
|
|
44
|
-
await mkdir(destDir, { recursive: true });
|
|
45
|
-
for (const name of await readdir(srcDir)) {
|
|
46
|
-
const src = join(srcDir, name);
|
|
47
|
-
const s = await stat(src);
|
|
48
|
-
const outName = name === '_gitignore' ? '.gitignore' : name === '_npmrc' ? '.npmrc' : name;
|
|
49
|
-
const dest = join(destDir, outName);
|
|
50
|
-
if (s.isDirectory()) {
|
|
51
|
-
await scaffold(src, dest, vars);
|
|
52
|
-
} else {
|
|
53
|
-
let body = await readFile(src, 'utf8');
|
|
54
|
-
for (const [k, v] of Object.entries(vars)) body = body.split(k).join(v);
|
|
55
|
-
await writeFile(dest, body);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function main() {
|
|
61
|
-
const args = parseArgs(process.argv.slice(2));
|
|
62
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
63
|
-
try {
|
|
64
|
-
let id = args._[0] || args.id;
|
|
65
|
-
if (!id) id = await prompt(rl, '插件 id(小写短横线,如 my-panel)', '');
|
|
66
|
-
id = String(id || '').trim().toLowerCase();
|
|
67
|
-
if (!/^[a-z][a-z0-9-]*$/.test(id)) {
|
|
68
|
-
console.error('✗ 非法 id:须小写字母开头,仅含小写字母/数字/短横线。');
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const title = args.title || (await prompt(rl, '面板标题', toTitle(id)));
|
|
73
|
-
let anchor = args.anchor || (await prompt(rl, `锚点(${ANCHORS.join('/')})`, 'top-right'));
|
|
74
|
-
anchor = String(anchor).trim();
|
|
75
|
-
if (!ANCHORS.includes(anchor)) {
|
|
76
|
-
console.error(`✗ 非法 anchor:${anchor}。可选:${ANCHORS.join(', ')}`);
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const destDir = resolve(process.cwd(), args.dir || id);
|
|
81
|
-
if (existsSync(destDir)) {
|
|
82
|
-
console.error(`✗ 目标已存在:${destDir}`);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const vars = {
|
|
87
|
-
__ID__: id,
|
|
88
|
-
__TITLE__: title,
|
|
89
|
-
__ANCHOR__: anchor,
|
|
90
|
-
};
|
|
91
|
-
await scaffold(TEMPLATE, destDir, vars);
|
|
92
|
-
|
|
93
|
-
const rel = relative(process.cwd(), destDir) || '.';
|
|
94
|
-
console.log(`\n✓ 已生成可信插件:${rel}`);
|
|
95
|
-
console.log('\n下一步:');
|
|
96
|
-
console.log(` cd ${rel}`);
|
|
97
|
-
console.log(' # 设只读 PAT(按所用 shell 选一行):');
|
|
98
|
-
console.log(' export GITLAB_TOKEN=<读权限 PAT> # bash / git-bash');
|
|
99
|
-
console.log(' $env:GITLAB_TOKEN="<读权限 PAT>" # PowerShell');
|
|
100
|
-
console.log(' set GITLAB_TOKEN=<读权限 PAT> # cmd');
|
|
101
|
-
console.log(' npm install');
|
|
102
|
-
console.log(' npm run dev # 裸 vite(市场源,原生 sourcemap) + host-dev 真宿主,浏览器调试\n');
|
|
103
|
-
} finally {
|
|
104
|
-
rl.close();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
main().catch((e) => {
|
|
109
|
-
console.error(e);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// create-ax-trusted-plugin —— 可信(internal)插件脚手架(发布于公网 npm)。
|
|
3
|
+
// 用法:
|
|
4
|
+
// npm create ax-trusted-plugin <id> [-- --title "标题" --anchor top-right]
|
|
5
|
+
// node bin/index.mjs <id> --title "标题" --anchor top-right
|
|
6
|
+
// 把 template/ 复制到 <id>/,按 id/title/anchor 做占位替换(dotfile 以 _ 前缀存放,生成时改名)。
|
|
7
|
+
import { readdir, readFile, writeFile, mkdir, stat } from 'node:fs/promises';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { dirname, join, resolve, relative } from 'node:path';
|
|
11
|
+
import { createInterface } from 'node:readline/promises';
|
|
12
|
+
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const TEMPLATE = resolve(here, '..', 'template');
|
|
15
|
+
|
|
16
|
+
const ANCHORS = [
|
|
17
|
+
'top-left', 'top-right', 'top-center',
|
|
18
|
+
'bottom-left', 'bottom-right',
|
|
19
|
+
'left-center', 'right-center', 'center',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
const out = { _: [] };
|
|
24
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25
|
+
const a = argv[i];
|
|
26
|
+
if (a.startsWith('--')) out[a.slice(2)] = argv[++i] ?? '';
|
|
27
|
+
else out._.push(a);
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toTitle(id) {
|
|
33
|
+
return id.replace(/[-_]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function prompt(rl, q, def) {
|
|
37
|
+
if (!process.stdin.isTTY) return def;
|
|
38
|
+
const ans = (await rl.question(`${q}${def ? ` (${def})` : ''}: `)).trim();
|
|
39
|
+
return ans || def;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 复制 + 占位替换 + dotfile 改名(_gitignore→.gitignore、_npmrc→.npmrc)。
|
|
43
|
+
async function scaffold(srcDir, destDir, vars) {
|
|
44
|
+
await mkdir(destDir, { recursive: true });
|
|
45
|
+
for (const name of await readdir(srcDir)) {
|
|
46
|
+
const src = join(srcDir, name);
|
|
47
|
+
const s = await stat(src);
|
|
48
|
+
const outName = name === '_gitignore' ? '.gitignore' : name === '_npmrc' ? '.npmrc' : name;
|
|
49
|
+
const dest = join(destDir, outName);
|
|
50
|
+
if (s.isDirectory()) {
|
|
51
|
+
await scaffold(src, dest, vars);
|
|
52
|
+
} else {
|
|
53
|
+
let body = await readFile(src, 'utf8');
|
|
54
|
+
for (const [k, v] of Object.entries(vars)) body = body.split(k).join(v);
|
|
55
|
+
await writeFile(dest, body);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function main() {
|
|
61
|
+
const args = parseArgs(process.argv.slice(2));
|
|
62
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
63
|
+
try {
|
|
64
|
+
let id = args._[0] || args.id;
|
|
65
|
+
if (!id) id = await prompt(rl, '插件 id(小写短横线,如 my-panel)', '');
|
|
66
|
+
id = String(id || '').trim().toLowerCase();
|
|
67
|
+
if (!/^[a-z][a-z0-9-]*$/.test(id)) {
|
|
68
|
+
console.error('✗ 非法 id:须小写字母开头,仅含小写字母/数字/短横线。');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const title = args.title || (await prompt(rl, '面板标题', toTitle(id)));
|
|
73
|
+
let anchor = args.anchor || (await prompt(rl, `锚点(${ANCHORS.join('/')})`, 'top-right'));
|
|
74
|
+
anchor = String(anchor).trim();
|
|
75
|
+
if (!ANCHORS.includes(anchor)) {
|
|
76
|
+
console.error(`✗ 非法 anchor:${anchor}。可选:${ANCHORS.join(', ')}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const destDir = resolve(process.cwd(), args.dir || id);
|
|
81
|
+
if (existsSync(destDir)) {
|
|
82
|
+
console.error(`✗ 目标已存在:${destDir}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const vars = {
|
|
87
|
+
__ID__: id,
|
|
88
|
+
__TITLE__: title,
|
|
89
|
+
__ANCHOR__: anchor,
|
|
90
|
+
};
|
|
91
|
+
await scaffold(TEMPLATE, destDir, vars);
|
|
92
|
+
|
|
93
|
+
const rel = relative(process.cwd(), destDir) || '.';
|
|
94
|
+
console.log(`\n✓ 已生成可信插件:${rel}`);
|
|
95
|
+
console.log('\n下一步:');
|
|
96
|
+
console.log(` cd ${rel}`);
|
|
97
|
+
console.log(' # 设只读 PAT(按所用 shell 选一行):');
|
|
98
|
+
console.log(' export GITLAB_TOKEN=<读权限 PAT> # bash / git-bash');
|
|
99
|
+
console.log(' $env:GITLAB_TOKEN="<读权限 PAT>" # PowerShell');
|
|
100
|
+
console.log(' set GITLAB_TOKEN=<读权限 PAT> # cmd');
|
|
101
|
+
console.log(' npm install');
|
|
102
|
+
console.log(' npm run dev # 裸 vite(市场源,原生 sourcemap) + host-dev 真宿主,浏览器调试\n');
|
|
103
|
+
} finally {
|
|
104
|
+
rl.close();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
main().catch((e) => {
|
|
109
|
+
console.error(e);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
package/package.json
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-ax-trusted-plugin",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "可信(internal)插件脚手架:一条命令生成一个基于 @ax-npm/host-trusted-sdk-v4 的可信插件骨架(默认形态一:ESM 直出 activate + 自带 Vue,dev 裸 vite 原生 sourcemap)。用法:npm create ax-trusted-plugin <id>。生成器本身无机密,公网可装;生成出的工程仍走私有源拉 @ax-npm/* 依赖。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"keywords": [
|
|
8
|
-
"create-ax-trusted-plugin",
|
|
9
|
-
"scaffold",
|
|
10
|
-
"vue",
|
|
11
|
-
"plugin"
|
|
12
|
-
],
|
|
13
|
-
"bin": {
|
|
14
|
-
"create-ax-trusted-plugin": "bin/index.mjs"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"bin",
|
|
18
|
-
"template"
|
|
19
|
-
],
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=18"
|
|
22
|
-
},
|
|
23
|
-
"publishConfig": {
|
|
24
|
-
"registry": "https://registry.npmjs.org/",
|
|
25
|
-
"access": "public"
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "create-ax-trusted-plugin",
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "可信(internal)插件脚手架:一条命令生成一个基于 @ax-npm/host-trusted-sdk-v4 的可信插件骨架(默认形态一:ESM 直出 activate + 自带 Vue,dev 裸 vite 原生 sourcemap)。用法:npm create ax-trusted-plugin <id>。生成器本身无机密,公网可装;生成出的工程仍走私有源拉 @ax-npm/* 依赖。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"create-ax-trusted-plugin",
|
|
9
|
+
"scaffold",
|
|
10
|
+
"vue",
|
|
11
|
+
"plugin"
|
|
12
|
+
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"create-ax-trusted-plugin": "bin/index.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"template"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org/",
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/template/README.md
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
# __ID__
|
|
2
|
-
|
|
3
|
-
可信(internal)插件「形态一」:模块直接 `export activate`,壳 `import` 即用;进程内拿完整 `ctx`。
|
|
4
|
-
Vue 打进本插件 bundle(与宿主/其它插件隔离)。由 `create-ax-trusted-plugin` 脚手架生成。
|
|
5
|
-
|
|
6
|
-
## 开发
|
|
7
|
-
```bash
|
|
8
|
-
export GITLAB_TOKEN=<读权限 PAT> # bash / git-bash
|
|
9
|
-
# $env:GITLAB_TOKEN="<读权限 PAT>" # PowerShell(别用 set / %VAR%)
|
|
10
|
-
# set GITLAB_TOKEN=<读权限 PAT> # cmd
|
|
11
|
-
npm install
|
|
12
|
-
npm run dev # concurrently: vite(:5174,市场源) + host-dev 真宿主(:5173)
|
|
13
|
-
```
|
|
14
|
-
浏览器打开 `http://localhost:5173`,锚点出现「__TITLE__」面板。**改代码 → 刷新浏览器即生效**(原生 sourcemap)。
|
|
15
|
-
|
|
16
|
-
> dev 走裸 `vite`:`vite.config.ts` 的 `hostSourceBridge` 把单插件服务伪装成市场源,
|
|
17
|
-
> `/<id>/main.js` 实时转译 `src/index.ts`。多插件并行各自 `vite --port 5175/5176/…`。
|
|
18
|
-
|
|
19
|
-
## 构建 / 上架
|
|
20
|
-
```bash
|
|
21
|
-
npm run build # → dist/main.js(+ main.js.map)
|
|
22
|
-
```
|
|
23
|
-
把 `dist/main.js`(+ `.map`)+ `manifest.json` 部署到静态源,在宿主市场 `plugins.json` 登记本插件 id(及 `baseUrl`)即可加载。
|
|
24
|
-
|
|
25
|
-
## 关键约定
|
|
26
|
-
- 入口 `src/index.ts` 必须 `export function activate(ctx)`(可选 `elementTag`/`deactivate`);`manifest.entry`=`main.js`。
|
|
27
|
-
- `vue({customElement:true})` 把样式注入 shadow root;Vue 自带打包,不与宿主共享。
|
|
28
|
-
- 提供能力须命名空间化(`__ID__/...`)且在 `contributes.capabilities`
|
|
29
|
-
- 插件间不互相 import,只走 `ctx.commands` / `ctx.events`。
|
|
30
|
-
|
|
31
|
-
> 需要多个可信插件**共享单例** Vue / 跨边界共享响应式 store,或要省 Vue 体积时,才回退「形态二」
|
|
32
|
-
> (Module Federation):`vite.config` 改用 `@originjs/vite-plugin-federation`、`manifest` 加 `mfExpose`、
|
|
33
|
-
> dev 走 `vite build --watch` + 静态托管。详见 `@ax-npm/host-trusted-sdk-v4` 的 README 与《三角色开发方法.md》。
|
|
1
|
+
# __ID__
|
|
2
|
+
|
|
3
|
+
可信(internal)插件「形态一」:模块直接 `export activate`,壳 `import` 即用;进程内拿完整 `ctx`。
|
|
4
|
+
Vue 打进本插件 bundle(与宿主/其它插件隔离)。由 `create-ax-trusted-plugin` 脚手架生成。
|
|
5
|
+
|
|
6
|
+
## 开发
|
|
7
|
+
```bash
|
|
8
|
+
export GITLAB_TOKEN=<读权限 PAT> # bash / git-bash
|
|
9
|
+
# $env:GITLAB_TOKEN="<读权限 PAT>" # PowerShell(别用 set / %VAR%)
|
|
10
|
+
# set GITLAB_TOKEN=<读权限 PAT> # cmd
|
|
11
|
+
npm install
|
|
12
|
+
npm run dev # concurrently: vite(:5174,市场源) + host-dev 真宿主(:5173)
|
|
13
|
+
```
|
|
14
|
+
浏览器打开 `http://localhost:5173`,锚点出现「__TITLE__」面板。**改代码 → 刷新浏览器即生效**(原生 sourcemap)。
|
|
15
|
+
|
|
16
|
+
> dev 走裸 `vite`:`vite.config.ts` 的 `hostSourceBridge` 把单插件服务伪装成市场源,
|
|
17
|
+
> `/<id>/main.js` 实时转译 `src/index.ts`。多插件并行各自 `vite --port 5175/5176/…`。
|
|
18
|
+
|
|
19
|
+
## 构建 / 上架
|
|
20
|
+
```bash
|
|
21
|
+
npm run build # → dist/main.js(+ main.js.map)
|
|
22
|
+
```
|
|
23
|
+
把 `dist/main.js`(+ `.map`)+ `manifest.json` 部署到静态源,在宿主市场 `plugins.json` 登记本插件 id(及 `baseUrl`)即可加载。
|
|
24
|
+
|
|
25
|
+
## 关键约定
|
|
26
|
+
- 入口 `src/index.ts` 必须 `export function activate(ctx)`(可选 `elementTag`/`deactivate`);`manifest.entry`=`main.js`。
|
|
27
|
+
- `vue({customElement:true})` 把样式注入 shadow root;Vue 自带打包,不与宿主共享。
|
|
28
|
+
- 提供能力须命名空间化(`__ID__/...`)且在 `contributes.capabilities` 声明;消费(含**调自己提供的能力**)须在 `uses.capabilities` 声明——contributes 与 uses 是两道独立门,`execute` 一律过 uses 闸。
|
|
29
|
+
- 插件间不互相 import,只走 `ctx.commands` / `ctx.events`。
|
|
30
|
+
|
|
31
|
+
> 需要多个可信插件**共享单例** Vue / 跨边界共享响应式 store,或要省 Vue 体积时,才回退「形态二」
|
|
32
|
+
> (Module Federation):`vite.config` 改用 `@originjs/vite-plugin-federation`、`manifest` 加 `mfExpose`、
|
|
33
|
+
> dev 走 `vite build --watch` + 静态托管。详见 `@ax-npm/host-trusted-sdk-v4` 的 README 与《三角色开发方法.md》。
|
package/template/manifest.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "__ID__",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"trust": "internal",
|
|
5
|
-
"host": "^1.0.0",
|
|
6
|
-
"entry": "main.js",
|
|
7
|
-
"activationEvents": ["onStartup"],
|
|
8
|
-
"contributes": { "capabilities": ["__ID__/ping"] },
|
|
9
|
-
"uses": { "capabilities": ["map.flyTo"] },
|
|
10
|
-
"component": {
|
|
11
|
-
"anchor": "__ANCHOR__",
|
|
12
|
-
"width": 320,
|
|
13
|
-
"height": 220,
|
|
14
|
-
"title": "__TITLE__",
|
|
15
|
-
"visible": true
|
|
16
|
-
}
|
|
17
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"id": "__ID__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"trust": "internal",
|
|
5
|
+
"host": "^1.0.0",
|
|
6
|
+
"entry": "main.js",
|
|
7
|
+
"activationEvents": ["onStartup"],
|
|
8
|
+
"contributes": { "capabilities": ["__ID__/ping"] },
|
|
9
|
+
"uses": { "capabilities": ["map.flyTo", "__ID__/ping"] },
|
|
10
|
+
"component": {
|
|
11
|
+
"anchor": "__ANCHOR__",
|
|
12
|
+
"width": 320,
|
|
13
|
+
"height": 220,
|
|
14
|
+
"title": "__TITLE__",
|
|
15
|
+
"visible": true
|
|
16
|
+
}
|
|
17
|
+
}
|
package/template/package.json
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "__ID__",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"description": "可信(internal)插件「形态一」:ESM 直出 activate,壳 import 即用、Vue 自带打包(隔离)、拿完整 ctx。dev 走裸 vite(原生 sourcemap、无构建步)。只依赖 @ax-npm/host-trusted-sdk-v4。",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "vue-tsc --noEmit && vite build",
|
|
9
|
-
"serve": "vite",
|
|
10
|
-
"dev": "concurrently -k -n plugin,host -c blue,green \"vite\" \"host-dev --plugin http://localhost:5174\"",
|
|
11
|
-
"dev:electron": "concurrently -k -n plugin,host -c blue,green \"vite\" \"host-dev --electron --plugin http://localhost:5174\"",
|
|
12
|
-
"typecheck": "vue-tsc --noEmit"
|
|
13
|
-
},
|
|
14
|
-
"dependencies": {
|
|
15
|
-
"@ax-npm/host-trusted-sdk-v4": "^1.0.2",
|
|
16
|
-
"vue": "^3.4.0"
|
|
17
|
-
},
|
|
18
|
-
"devDependencies": {
|
|
19
|
-
"@ax-npm/host-dev-v4": "^1.0.7",
|
|
20
|
-
"@vitejs/plugin-vue": "^5.0.0",
|
|
21
|
-
"concurrently": "^9.0.0",
|
|
22
|
-
"electron": "^39.0.0",
|
|
23
|
-
"typescript": "^5.4.0",
|
|
24
|
-
"vite": "^5.2.0",
|
|
25
|
-
"vue-tsc": "^2.0.0"
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "__ID__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "可信(internal)插件「形态一」:ESM 直出 activate,壳 import 即用、Vue 自带打包(隔离)、拿完整 ctx。dev 走裸 vite(原生 sourcemap、无构建步)。只依赖 @ax-npm/host-trusted-sdk-v4。",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
9
|
+
"serve": "vite",
|
|
10
|
+
"dev": "concurrently -k -n plugin,host -c blue,green \"vite\" \"host-dev --plugin http://localhost:5174\"",
|
|
11
|
+
"dev:electron": "concurrently -k -n plugin,host -c blue,green \"vite\" \"host-dev --electron --plugin http://localhost:5174\"",
|
|
12
|
+
"typecheck": "vue-tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@ax-npm/host-trusted-sdk-v4": "^1.0.2",
|
|
16
|
+
"vue": "^3.4.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@ax-npm/host-dev-v4": "^1.0.7",
|
|
20
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
|
21
|
+
"concurrently": "^9.0.0",
|
|
22
|
+
"electron": "^39.0.0",
|
|
23
|
+
"typescript": "^5.4.0",
|
|
24
|
+
"vite": "^5.2.0",
|
|
25
|
+
"vue-tsc": "^2.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/template/src/index.ts
CHANGED
|
@@ -1,42 +1,44 @@
|
|
|
1
|
-
// 可信插件入口「形态一」:本模块直接导出 activate/elementTag(manifest.entry → main.js)。
|
|
2
|
-
// 宿主 loader `import(url)` 命中「直出 activate」分支,调 activate(ctx),据 elementTag +
|
|
3
|
-
// manifest.component 挂自定义元素。(业务代码与形态二一致,只差打包/加载方式。)
|
|
4
|
-
import { definePluginElement, type PluginCtx } from '@ax-npm/host-trusted-sdk-v4';
|
|
5
|
-
import Panel from './Panel.vue';
|
|
6
|
-
import { state, type FcsBasic } from './state';
|
|
7
|
-
|
|
8
|
-
export const elementTag = definePluginElement(Panel, { tag: 'plugin-__ID__' });
|
|
9
|
-
|
|
10
|
-
export function activate(ctx: PluginCtx) {
|
|
11
|
-
// ① 提供能力:id 须命名空间化 + 在 manifest.contributes.capabilities 声明。
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
state.flyToNote = '
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
// 可信插件入口「形态一」:本模块直接导出 activate/elementTag(manifest.entry → main.js)。
|
|
2
|
+
// 宿主 loader `import(url)` 命中「直出 activate」分支,调 activate(ctx),据 elementTag +
|
|
3
|
+
// manifest.component 挂自定义元素。(业务代码与形态二一致,只差打包/加载方式。)
|
|
4
|
+
import { definePluginElement, type PluginCtx } from '@ax-npm/host-trusted-sdk-v4';
|
|
5
|
+
import Panel from './Panel.vue';
|
|
6
|
+
import { state, type FcsBasic } from './state';
|
|
7
|
+
|
|
8
|
+
export const elementTag = definePluginElement(Panel, { tag: 'plugin-__ID__' });
|
|
9
|
+
|
|
10
|
+
export function activate(ctx: PluginCtx) {
|
|
11
|
+
// ① 提供能力:id 须命名空间化 + 在 manifest.contributes.capabilities 声明。
|
|
12
|
+
// 注意:contributes(提供)与 uses(调用)是两道独立门——execute 走 uses 闸,**调自己
|
|
13
|
+
// register 的能力也要在 manifest.uses.capabilities 声明**(见下方 ③ 的 __ID__/ping)。
|
|
14
|
+
ctx.commands.register('__ID__/ping', async () => 'pong @ ' + new Date().toLocaleTimeString());
|
|
15
|
+
|
|
16
|
+
// ② 订阅宿主遥测(广播)。
|
|
17
|
+
ctx.events.on('telemetry:fcs.basic', (p) => {
|
|
18
|
+
state.telemetry = p as FcsBasic;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// ③ Panel 是自定义元素,拿不到 ctx;经 window 事件把用户动作桥回 activate 作用域。
|
|
22
|
+
window.addEventListener('__ID__:ping', async () => {
|
|
23
|
+
state.lastPing = String(await ctx.commands.execute('__ID__/ping'));
|
|
24
|
+
});
|
|
25
|
+
window.addEventListener('__ID__:flyto', async () => {
|
|
26
|
+
try {
|
|
27
|
+
// 调宿主地图能力(需在 manifest.uses.capabilities 声明 map.flyTo)。
|
|
28
|
+
await ctx.commands.execute('map.flyTo', 116.397, 39.908);
|
|
29
|
+
state.flyToNote = '已飞至北京';
|
|
30
|
+
} catch (e) {
|
|
31
|
+
state.flyToNote = '失败:' + (e instanceof Error ? e.message : String(e));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
elementTag,
|
|
37
|
+
onVisible() {},
|
|
38
|
+
onHidden() {},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function deactivate() {
|
|
43
|
+
// 仅收自有资源;经 ctx 注册的命令/事件由运行时自动回收。
|
|
44
|
+
}
|
package/template/vite.config.ts
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
import { defineConfig } from 'vite';
|
|
2
|
-
import vue from '@vitejs/plugin-vue';
|
|
3
|
-
import { fileURLToPath, URL } from 'node:url';
|
|
4
|
-
import { readFileSync } from 'node:fs';
|
|
5
|
-
|
|
6
|
-
// 可信插件「形态一」(ESM 直出 activate)构建。与「形态二」(Module Federation 容器)对比:
|
|
7
|
-
// - 形态一:本模块 import(url) 后**直接导出 activate/elementTag**,壳 loader 命中直出分支。
|
|
8
|
-
// Vue 打进 bundle(自带依赖,与宿主/其它插件隔离 —— 宿主升级 Vue 波及不到本插件)。
|
|
9
|
-
// - 形态二:打成 federation remoteEntry,运行时与宿主**共享单例** Vue/Pinia。
|
|
10
|
-
// 只经 ctx 交互的内部插件用形态一**功能零损失**,且换来:dev 走裸 `vite`(实时转译、原生
|
|
11
|
-
// sourcemap、无构建步),build 产单文件 `main.js`。仅当需要跨边界共享响应式 store / 省体积
|
|
12
|
-
// 到单份 Vue 时才回退形态二(见 README 与《三角色开发方法.md》)。
|
|
13
|
-
//
|
|
14
|
-
// vue({ customElement: true }):SFC 以自定义元素模式编译,样式自动注入 shadow root,
|
|
15
|
-
// 故无独立 CSS 产物,单个 main.js 即完整插件。
|
|
16
|
-
|
|
17
|
-
const MANIFEST = JSON.parse(
|
|
18
|
-
readFileSync(fileURLToPath(new URL('./manifest.json', import.meta.url)), 'utf-8'),
|
|
19
|
-
) as { id: string };
|
|
20
|
-
const PLUGIN_ID = MANIFEST.id;
|
|
21
|
-
|
|
22
|
-
// —— dev 桥:让「单插件 vite 服务」对外长得像宿主的插件市场源 ——
|
|
23
|
-
// 宿主(LocalRegistry)会请求 /plugins.json、/<id>/manifest.json、/<id>/<entry>。
|
|
24
|
-
// 这里:① /plugins.json = [{ id }];② 把 /<id>/* 重写回 /*(插件本体在根目录);
|
|
25
|
-
// ③ manifest.entry 是 `main.js`(生产构建产物),dev 无此产物 → 把对它的请求
|
|
26
|
-
// 顶替为 `src/index.ts`,由 vite 实时转译(原生 sourcemap、无构建步)。
|
|
27
|
-
// 这样单跑 `vite` 即可被 harness/host-dev 当市场源加载,改代码刷新即生效。
|
|
28
|
-
function hostSourceBridge() {
|
|
29
|
-
return {
|
|
30
|
-
name: 'host-source-bridge',
|
|
31
|
-
configureServer(server: {
|
|
32
|
-
middlewares: { use: (fn: (req: any, res: any, next: () => void) => void) => void };
|
|
33
|
-
}) {
|
|
34
|
-
server.middlewares.use((req, res, next) => {
|
|
35
|
-
const url = (req.url || '').split('?')[0];
|
|
36
|
-
if (url === '/plugins.json') {
|
|
37
|
-
res.setHeader('content-type', 'application/json');
|
|
38
|
-
res.setHeader('access-control-allow-origin', '*');
|
|
39
|
-
res.end(JSON.stringify([{ id: PLUGIN_ID }]));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
// /<id>/xxx → /xxx(去掉宿主加的 id 前缀)
|
|
43
|
-
if (req.url === `/${PLUGIN_ID}` || req.url === `/${PLUGIN_ID}/`) {
|
|
44
|
-
req.url = '/';
|
|
45
|
-
} else if (req.url && req.url.startsWith(`/${PLUGIN_ID}/`)) {
|
|
46
|
-
req.url = req.url.slice(PLUGIN_ID.length + 1);
|
|
47
|
-
}
|
|
48
|
-
// 形态一 dev:把 main.js 入口请求顶替为可实时转译的源文件。
|
|
49
|
-
if ((req.url || '').split('?')[0] === '/main.js') req.url = '/src/index.ts';
|
|
50
|
-
next();
|
|
51
|
-
});
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default defineConfig({
|
|
57
|
-
plugins: [vue({ customElement: true }), hostSourceBridge()],
|
|
58
|
-
server: {
|
|
59
|
-
// 活跃集多插件并行时用 `vite --port 5175/5176/…` 覆盖,一份多源 plugins.json 列各端口。
|
|
60
|
-
port: 5174,
|
|
61
|
-
strictPort: true,
|
|
62
|
-
// 宿主(:5180 / app://)跨源拉清单/manifest/入口模块,需放开 CORS。
|
|
63
|
-
cors: { origin: '*' },
|
|
64
|
-
},
|
|
65
|
-
build: {
|
|
66
|
-
target: 'esnext',
|
|
67
|
-
outDir: 'dist',
|
|
68
|
-
emptyOutDir: true,
|
|
69
|
-
// 源码级调试可信插件产物:留 sourcemap、不混淆。
|
|
70
|
-
sourcemap: true,
|
|
71
|
-
minify: false,
|
|
72
|
-
// library 模式:入口导出(activate/elementTag/deactivate)即模块导出,壳 import(url) 直出。
|
|
73
|
-
lib: {
|
|
74
|
-
entry: fileURLToPath(new URL('./src/index.ts', import.meta.url)),
|
|
75
|
-
formats: ['es'],
|
|
76
|
-
fileName: () => 'main.js',
|
|
77
|
-
},
|
|
78
|
-
rollupOptions: {
|
|
79
|
-
// 保住入口导出签名,别被当可执行 chunk 摇掉 activate。
|
|
80
|
-
preserveEntrySignatures: 'strict',
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
});
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import vue from '@vitejs/plugin-vue';
|
|
3
|
+
import { fileURLToPath, URL } from 'node:url';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
// 可信插件「形态一」(ESM 直出 activate)构建。与「形态二」(Module Federation 容器)对比:
|
|
7
|
+
// - 形态一:本模块 import(url) 后**直接导出 activate/elementTag**,壳 loader 命中直出分支。
|
|
8
|
+
// Vue 打进 bundle(自带依赖,与宿主/其它插件隔离 —— 宿主升级 Vue 波及不到本插件)。
|
|
9
|
+
// - 形态二:打成 federation remoteEntry,运行时与宿主**共享单例** Vue/Pinia。
|
|
10
|
+
// 只经 ctx 交互的内部插件用形态一**功能零损失**,且换来:dev 走裸 `vite`(实时转译、原生
|
|
11
|
+
// sourcemap、无构建步),build 产单文件 `main.js`。仅当需要跨边界共享响应式 store / 省体积
|
|
12
|
+
// 到单份 Vue 时才回退形态二(见 README 与《三角色开发方法.md》)。
|
|
13
|
+
//
|
|
14
|
+
// vue({ customElement: true }):SFC 以自定义元素模式编译,样式自动注入 shadow root,
|
|
15
|
+
// 故无独立 CSS 产物,单个 main.js 即完整插件。
|
|
16
|
+
|
|
17
|
+
const MANIFEST = JSON.parse(
|
|
18
|
+
readFileSync(fileURLToPath(new URL('./manifest.json', import.meta.url)), 'utf-8'),
|
|
19
|
+
) as { id: string };
|
|
20
|
+
const PLUGIN_ID = MANIFEST.id;
|
|
21
|
+
|
|
22
|
+
// —— dev 桥:让「单插件 vite 服务」对外长得像宿主的插件市场源 ——
|
|
23
|
+
// 宿主(LocalRegistry)会请求 /plugins.json、/<id>/manifest.json、/<id>/<entry>。
|
|
24
|
+
// 这里:① /plugins.json = [{ id }];② 把 /<id>/* 重写回 /*(插件本体在根目录);
|
|
25
|
+
// ③ manifest.entry 是 `main.js`(生产构建产物),dev 无此产物 → 把对它的请求
|
|
26
|
+
// 顶替为 `src/index.ts`,由 vite 实时转译(原生 sourcemap、无构建步)。
|
|
27
|
+
// 这样单跑 `vite` 即可被 harness/host-dev 当市场源加载,改代码刷新即生效。
|
|
28
|
+
function hostSourceBridge() {
|
|
29
|
+
return {
|
|
30
|
+
name: 'host-source-bridge',
|
|
31
|
+
configureServer(server: {
|
|
32
|
+
middlewares: { use: (fn: (req: any, res: any, next: () => void) => void) => void };
|
|
33
|
+
}) {
|
|
34
|
+
server.middlewares.use((req, res, next) => {
|
|
35
|
+
const url = (req.url || '').split('?')[0];
|
|
36
|
+
if (url === '/plugins.json') {
|
|
37
|
+
res.setHeader('content-type', 'application/json');
|
|
38
|
+
res.setHeader('access-control-allow-origin', '*');
|
|
39
|
+
res.end(JSON.stringify([{ id: PLUGIN_ID }]));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// /<id>/xxx → /xxx(去掉宿主加的 id 前缀)
|
|
43
|
+
if (req.url === `/${PLUGIN_ID}` || req.url === `/${PLUGIN_ID}/`) {
|
|
44
|
+
req.url = '/';
|
|
45
|
+
} else if (req.url && req.url.startsWith(`/${PLUGIN_ID}/`)) {
|
|
46
|
+
req.url = req.url.slice(PLUGIN_ID.length + 1);
|
|
47
|
+
}
|
|
48
|
+
// 形态一 dev:把 main.js 入口请求顶替为可实时转译的源文件。
|
|
49
|
+
if ((req.url || '').split('?')[0] === '/main.js') req.url = '/src/index.ts';
|
|
50
|
+
next();
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default defineConfig({
|
|
57
|
+
plugins: [vue({ customElement: true }), hostSourceBridge()],
|
|
58
|
+
server: {
|
|
59
|
+
// 活跃集多插件并行时用 `vite --port 5175/5176/…` 覆盖,一份多源 plugins.json 列各端口。
|
|
60
|
+
port: 5174,
|
|
61
|
+
strictPort: true,
|
|
62
|
+
// 宿主(:5180 / app://)跨源拉清单/manifest/入口模块,需放开 CORS。
|
|
63
|
+
cors: { origin: '*' },
|
|
64
|
+
},
|
|
65
|
+
build: {
|
|
66
|
+
target: 'esnext',
|
|
67
|
+
outDir: 'dist',
|
|
68
|
+
emptyOutDir: true,
|
|
69
|
+
// 源码级调试可信插件产物:留 sourcemap、不混淆。
|
|
70
|
+
sourcemap: true,
|
|
71
|
+
minify: false,
|
|
72
|
+
// library 模式:入口导出(activate/elementTag/deactivate)即模块导出,壳 import(url) 直出。
|
|
73
|
+
lib: {
|
|
74
|
+
entry: fileURLToPath(new URL('./src/index.ts', import.meta.url)),
|
|
75
|
+
formats: ['es'],
|
|
76
|
+
fileName: () => 'main.js',
|
|
77
|
+
},
|
|
78
|
+
rollupOptions: {
|
|
79
|
+
// 保住入口导出签名,别被当可执行 chunk 摇掉 activate。
|
|
80
|
+
preserveEntrySignatures: 'strict',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|