@pactor-app/cli 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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/cli.js +713 -0
- package/package.json +47 -0
- package/templates/app/README.md +18 -0
- package/templates/app/index.html +12 -0
- package/templates/app/package.json +27 -0
- package/templates/app/src/main.tsx +22 -0
- package/templates/app/src/pages/home.page.yaml +23 -0
- package/templates/app/tsconfig.json +16 -0
- package/templates/app/vite.config.ts +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pactor Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @pactor-app/cli
|
|
2
|
+
|
|
3
|
+
[Pactor](https://github.com/426-330/pactor) 的命令行工具(bin `pactor`):本地 DSL 开发工具链,覆盖校验、结构摘要、静态检查、Schema 导出、编译为 Runtime Envelope JSON 与脚手架生成。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @pactor-app/cli
|
|
9
|
+
# 或
|
|
10
|
+
pnpm add -g @pactor-app/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
要求 Node.js >= 18;零 React 依赖。
|
|
14
|
+
|
|
15
|
+
## 命令
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
pactor validate <files...> 校验 DSL 文件(page 走 Schema 校验,fragment 宽松检查 dsl + body)
|
|
19
|
+
pactor inspect <file> 输出 DSL 结构摘要(状态 / 数据源 / 组件计数 / 动作 / 权限 / 表达式)
|
|
20
|
+
pactor lint <paths...> 校验 + 静态检查规则(目录递归 .page/.fragment.yaml/.yml)
|
|
21
|
+
pactor schema [-o <file>] 输出 Page DSL JSON Schema(含 node / action 关联 Schema)
|
|
22
|
+
pactor build <src> -o <dir> 递归编译 DSL 为 Runtime Envelope JSON(<out>/<id>.json)
|
|
23
|
+
pactor create <name> [-t app] 从模板生成业务工程(vite + react + @pactor-app/app + @pactor-app/ui)
|
|
24
|
+
pactor --help 命令清单
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
退出码语义:
|
|
28
|
+
|
|
29
|
+
- `0`:成功(lint 仅 warning 也算成功)
|
|
30
|
+
- `1`:校验或 lint 失败(lint 存在 error)
|
|
31
|
+
- `2`:用法错误(缺参数、文件不存在、目标目录非空等)
|
|
32
|
+
|
|
33
|
+
lint 规则:
|
|
34
|
+
|
|
35
|
+
- `expression-syntax`(error):解析 DSL 中所有 `${...}` 模板与整体表达式
|
|
36
|
+
- `component-naming`(warning):组件 type 应为 PascalCase
|
|
37
|
+
- `permission-format`(warning):权限码应为 `resource:action` 形态
|
|
38
|
+
|
|
39
|
+
`preview` 命令在 V1 未实现(见设计文档第 84 节裁剪说明)。
|
|
40
|
+
|
|
41
|
+
## 文档
|
|
42
|
+
|
|
43
|
+
完整文档见 <https://github.com/426-330/pactor/tree/main/docs>(`pnpm docs:dev` 本地启动文档站)。
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
[MIT](../../LICENSE)
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { pathToFileURL } from "url";
|
|
5
|
+
|
|
6
|
+
// src/args.ts
|
|
7
|
+
var SHORT_VALUE_FLAGS = /* @__PURE__ */ new Set(["o", "t"]);
|
|
8
|
+
var UsageError = class extends Error {
|
|
9
|
+
};
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const positionals = [];
|
|
12
|
+
const flags = {};
|
|
13
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
14
|
+
const token = argv[i];
|
|
15
|
+
if (token.startsWith("--")) {
|
|
16
|
+
const body = token.slice(2);
|
|
17
|
+
const eq = body.indexOf("=");
|
|
18
|
+
if (eq >= 0) {
|
|
19
|
+
flags[body.slice(0, eq)] = body.slice(eq + 1);
|
|
20
|
+
} else {
|
|
21
|
+
flags[body] = true;
|
|
22
|
+
}
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (token.startsWith("-") && token.length > 1) {
|
|
26
|
+
const name = token.slice(1);
|
|
27
|
+
if (SHORT_VALUE_FLAGS.has(name)) {
|
|
28
|
+
const value = argv[i + 1];
|
|
29
|
+
if (value === void 0) {
|
|
30
|
+
throw new UsageError(`\u9009\u9879 -${name} \u7F3A\u5C11\u503C`);
|
|
31
|
+
}
|
|
32
|
+
flags[name] = value;
|
|
33
|
+
i += 1;
|
|
34
|
+
} else {
|
|
35
|
+
flags[name] = true;
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
positionals.push(token);
|
|
40
|
+
}
|
|
41
|
+
return { positionals, flags };
|
|
42
|
+
}
|
|
43
|
+
function flagValue(args, name) {
|
|
44
|
+
const value = args.flags[name];
|
|
45
|
+
return typeof value === "string" ? value : void 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/commands/build.ts
|
|
49
|
+
import { existsSync as existsSync2, mkdirSync, statSync as statSync2, writeFileSync } from "fs";
|
|
50
|
+
import { join as join2 } from "path";
|
|
51
|
+
|
|
52
|
+
// src/dsl-io.ts
|
|
53
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
54
|
+
import { join } from "path";
|
|
55
|
+
import { validatePageDsl } from "@pactor-app/schema";
|
|
56
|
+
import { parseDocument } from "yaml";
|
|
57
|
+
var DSL_FILE_RE = /\.(page|fragment)\.(yaml|yml)$/;
|
|
58
|
+
var FRAGMENT_FILE_RE = /\.fragment\.(yaml|yml)$/;
|
|
59
|
+
function isDslFile(path) {
|
|
60
|
+
return DSL_FILE_RE.test(path);
|
|
61
|
+
}
|
|
62
|
+
function isFragmentFile(path) {
|
|
63
|
+
return FRAGMENT_FILE_RE.test(path);
|
|
64
|
+
}
|
|
65
|
+
function readDslFile(file) {
|
|
66
|
+
const source = readFileSync(file, "utf8");
|
|
67
|
+
const doc = parseDocument(source);
|
|
68
|
+
if (doc.errors.length > 0) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
errors: doc.errors.map((error) => {
|
|
72
|
+
const pos = error.linePos?.[0];
|
|
73
|
+
const where = pos ? `\u884C ${pos.line} \u5217 ${pos.col}` : "\u672A\u77E5\u4F4D\u7F6E";
|
|
74
|
+
return { path: "/", message: `${where}: ${error.message}`, keyword: "yaml" };
|
|
75
|
+
})
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return { ok: true, data: doc.toJS() };
|
|
79
|
+
}
|
|
80
|
+
function validateDsl(data, isFragment) {
|
|
81
|
+
if (isFragment) {
|
|
82
|
+
const fragment = data;
|
|
83
|
+
if (fragment === null || typeof fragment !== "object" || !("dsl" in fragment) || !("body" in fragment)) {
|
|
84
|
+
return [{ path: "/", message: "\u5FC5\u987B\u5B58\u5728 dsl \u4E0E body \u5B57\u6BB5", keyword: "required" }];
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
const result = validatePageDsl(data);
|
|
89
|
+
return result.errors;
|
|
90
|
+
}
|
|
91
|
+
function collectDslFiles(paths) {
|
|
92
|
+
const files = [];
|
|
93
|
+
const missing = [];
|
|
94
|
+
const walk = (target) => {
|
|
95
|
+
if (!existsSync(target)) {
|
|
96
|
+
missing.push(target);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const stat = statSync(target);
|
|
100
|
+
if (stat.isDirectory()) {
|
|
101
|
+
for (const entry of readdirSync(target)) {
|
|
102
|
+
walk(join(target, entry));
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (isDslFile(target)) {
|
|
107
|
+
files.push(target);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
for (const path of paths) {
|
|
111
|
+
walk(path);
|
|
112
|
+
}
|
|
113
|
+
files.sort();
|
|
114
|
+
return { files, missing };
|
|
115
|
+
}
|
|
116
|
+
function collectStrings(data, basePath = "") {
|
|
117
|
+
const entries = [];
|
|
118
|
+
const walk = (value, path) => {
|
|
119
|
+
if (typeof value === "string") {
|
|
120
|
+
entries.push({ path: path === "" ? "/" : path, value });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
value.forEach((item, index) => walk(item, `${path}/${index}`));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (value !== null && typeof value === "object") {
|
|
128
|
+
for (const [key, item] of Object.entries(value)) {
|
|
129
|
+
walk(item, `${path}/${key}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
walk(data, basePath);
|
|
134
|
+
return entries;
|
|
135
|
+
}
|
|
136
|
+
function extractTemplateExpressions(value) {
|
|
137
|
+
const sources = [];
|
|
138
|
+
let i = 0;
|
|
139
|
+
let unterminated = false;
|
|
140
|
+
while (i < value.length) {
|
|
141
|
+
if (value[i] === "$" && value[i + 1] === "{") {
|
|
142
|
+
i += 2;
|
|
143
|
+
let depth = 1;
|
|
144
|
+
let quote;
|
|
145
|
+
let source = "";
|
|
146
|
+
let closed = false;
|
|
147
|
+
while (i < value.length) {
|
|
148
|
+
const c = value[i];
|
|
149
|
+
if (quote !== void 0) {
|
|
150
|
+
source += c;
|
|
151
|
+
if (c === "\\" && i + 1 < value.length) {
|
|
152
|
+
source += value[i + 1];
|
|
153
|
+
i += 2;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (c === quote) {
|
|
157
|
+
quote = void 0;
|
|
158
|
+
}
|
|
159
|
+
i += 1;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (c === "'" || c === '"') {
|
|
163
|
+
quote = c;
|
|
164
|
+
source += c;
|
|
165
|
+
i += 1;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (c === "{") {
|
|
169
|
+
depth += 1;
|
|
170
|
+
source += c;
|
|
171
|
+
i += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (c === "}") {
|
|
175
|
+
depth -= 1;
|
|
176
|
+
if (depth === 0) {
|
|
177
|
+
closed = true;
|
|
178
|
+
i += 1;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
source += c;
|
|
182
|
+
i += 1;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
source += c;
|
|
186
|
+
i += 1;
|
|
187
|
+
}
|
|
188
|
+
if (!closed) {
|
|
189
|
+
unterminated = true;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
sources.push(source);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
i += 1;
|
|
196
|
+
}
|
|
197
|
+
return { sources, unterminated };
|
|
198
|
+
}
|
|
199
|
+
function walkDslTree(root, visit, basePath = "/body") {
|
|
200
|
+
if (root === void 0 || root === null || typeof root !== "object") return;
|
|
201
|
+
visit(root, basePath);
|
|
202
|
+
if (Array.isArray(root.children)) {
|
|
203
|
+
root.children.forEach((child, index) => {
|
|
204
|
+
walkDslTree(child, visit, `${basePath}/children/${index}`);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/commands/build.ts
|
|
210
|
+
function runBuild(args, io) {
|
|
211
|
+
const src = args.positionals[0];
|
|
212
|
+
const outDir = flagValue(args, "o");
|
|
213
|
+
if (src === void 0 || outDir === void 0 || outDir === "") {
|
|
214
|
+
io.err("\u7528\u6CD5: pactor build <src> -o <dir>");
|
|
215
|
+
return 2;
|
|
216
|
+
}
|
|
217
|
+
if (!existsSync2(src) || !statSync2(src).isDirectory()) {
|
|
218
|
+
io.err(`\u6E90\u76EE\u5F55\u4E0D\u5B58\u5728: ${src}`);
|
|
219
|
+
return 2;
|
|
220
|
+
}
|
|
221
|
+
const { files } = collectDslFiles([src]);
|
|
222
|
+
if (files.length === 0) {
|
|
223
|
+
io.err(`\u672A\u627E\u5230 DSL \u6587\u4EF6: ${src}\uFF08.page/.fragment.yaml/.yml\uFF09`);
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
const compiled = [];
|
|
227
|
+
let failed = false;
|
|
228
|
+
for (const file of files) {
|
|
229
|
+
const read = readDslFile(file);
|
|
230
|
+
const errors = read.ok ? validateDsl(read.data, isFragmentFile(file)) : read.errors;
|
|
231
|
+
if (errors.length > 0) {
|
|
232
|
+
failed = true;
|
|
233
|
+
for (const error of errors) {
|
|
234
|
+
io.err(`${file}: ${error.path}: ${error.message} (${error.keyword})`);
|
|
235
|
+
}
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!read.ok) continue;
|
|
239
|
+
const isFragment = isFragmentFile(file);
|
|
240
|
+
const data = read.data;
|
|
241
|
+
const id = isFragment ? data.fragment.id : data.page.id;
|
|
242
|
+
compiled.push({
|
|
243
|
+
id,
|
|
244
|
+
envelope: {
|
|
245
|
+
id,
|
|
246
|
+
resourceType: isFragment ? "fragment" : "page",
|
|
247
|
+
version: 1,
|
|
248
|
+
dslVersion: data.dsl,
|
|
249
|
+
schema: data
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (failed) {
|
|
254
|
+
io.err("\u6784\u5EFA\u4E2D\u6B62: \u5B58\u5728\u6821\u9A8C\u5931\u8D25\u7684 DSL \u6587\u4EF6");
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
mkdirSync(outDir, { recursive: true });
|
|
258
|
+
for (const { id, envelope } of compiled) {
|
|
259
|
+
const target = join2(outDir, `${id}.json`);
|
|
260
|
+
writeFileSync(target, `${JSON.stringify(envelope, null, 2)}
|
|
261
|
+
`, "utf8");
|
|
262
|
+
io.out(`\u5DF2\u751F\u6210: ${target}`);
|
|
263
|
+
}
|
|
264
|
+
io.out(`\u6784\u5EFA\u5B8C\u6210: ${compiled.length} \u4E2A\u8D44\u6E90`);
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/commands/create.ts
|
|
269
|
+
import {
|
|
270
|
+
existsSync as existsSync3,
|
|
271
|
+
mkdirSync as mkdirSync2,
|
|
272
|
+
readdirSync as readdirSync2,
|
|
273
|
+
readFileSync as readFileSync2,
|
|
274
|
+
statSync as statSync3,
|
|
275
|
+
writeFileSync as writeFileSync2
|
|
276
|
+
} from "fs";
|
|
277
|
+
import { basename, dirname, join as join3, resolve } from "path";
|
|
278
|
+
import { fileURLToPath } from "url";
|
|
279
|
+
var APP_NAME_PLACEHOLDER = /__APP_NAME__/g;
|
|
280
|
+
function defaultTemplatesDir() {
|
|
281
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
282
|
+
for (const candidate of [
|
|
283
|
+
join3(here, "../templates/app"),
|
|
284
|
+
join3(here, "../../templates/app")
|
|
285
|
+
]) {
|
|
286
|
+
if (existsSync3(candidate)) return candidate;
|
|
287
|
+
}
|
|
288
|
+
return join3(here, "../templates/app");
|
|
289
|
+
}
|
|
290
|
+
function copyTemplate(src, dest, appName) {
|
|
291
|
+
mkdirSync2(dest, { recursive: true });
|
|
292
|
+
for (const entry of readdirSync2(src)) {
|
|
293
|
+
const from = join3(src, entry);
|
|
294
|
+
const to = join3(dest, entry);
|
|
295
|
+
if (statSync3(from).isDirectory()) {
|
|
296
|
+
copyTemplate(from, to, appName);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const content = readFileSync2(from, "utf8").replace(APP_NAME_PLACEHOLDER, appName);
|
|
300
|
+
writeFileSync2(to, content, "utf8");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function runCreate(args, io, templatesDir = defaultTemplatesDir()) {
|
|
304
|
+
const name = args.positionals[0];
|
|
305
|
+
if (name === void 0) {
|
|
306
|
+
io.err("\u7528\u6CD5: pactor create <name> [-t app]");
|
|
307
|
+
return 2;
|
|
308
|
+
}
|
|
309
|
+
const template = flagValue(args, "t") ?? "app";
|
|
310
|
+
if (template !== "app") {
|
|
311
|
+
io.err(`\u672A\u77E5\u6A21\u677F: ${template}\uFF08\u5F53\u524D\u4EC5\u652F\u6301 app\uFF09`);
|
|
312
|
+
return 2;
|
|
313
|
+
}
|
|
314
|
+
const target = resolve(name);
|
|
315
|
+
const appName = basename(target);
|
|
316
|
+
if (existsSync3(target)) {
|
|
317
|
+
if (!statSync3(target).isDirectory()) {
|
|
318
|
+
io.err(`\u76EE\u6807\u5DF2\u5B58\u5728\u4E14\u4E0D\u662F\u76EE\u5F55: ${target}`);
|
|
319
|
+
return 2;
|
|
320
|
+
}
|
|
321
|
+
if (readdirSync2(target).length > 0) {
|
|
322
|
+
io.err(`\u76EE\u6807\u76EE\u5F55\u975E\u7A7A\uFF0C\u62D2\u7EDD\u8986\u76D6: ${target}`);
|
|
323
|
+
return 2;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
copyTemplate(templatesDir, target, appName);
|
|
327
|
+
io.out(`\u5DF2\u521B\u5EFA\u5DE5\u7A0B: ${target}`);
|
|
328
|
+
io.out("\u4E0B\u4E00\u6B65:");
|
|
329
|
+
io.out(` cd ${name}`);
|
|
330
|
+
io.out(" pnpm install");
|
|
331
|
+
io.out(" pnpm dev");
|
|
332
|
+
return 0;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/commands/inspect.ts
|
|
336
|
+
import { existsSync as existsSync4, statSync as statSync4 } from "fs";
|
|
337
|
+
function permissionCodes(permission) {
|
|
338
|
+
if (permission === void 0) return [];
|
|
339
|
+
if (typeof permission === "string") return [permission];
|
|
340
|
+
if (Array.isArray(permission)) return permission;
|
|
341
|
+
return Array.isArray(permission.code) ? permission.code : [permission.code];
|
|
342
|
+
}
|
|
343
|
+
function actionList(config) {
|
|
344
|
+
if (config === void 0) return [];
|
|
345
|
+
return Array.isArray(config) ? config : [config];
|
|
346
|
+
}
|
|
347
|
+
function runInspect(args, io) {
|
|
348
|
+
if (args.positionals.length !== 1) {
|
|
349
|
+
io.err("\u7528\u6CD5: pactor inspect <file>");
|
|
350
|
+
return 2;
|
|
351
|
+
}
|
|
352
|
+
const file = args.positionals[0];
|
|
353
|
+
if (!existsSync4(file) || !statSync4(file).isFile()) {
|
|
354
|
+
io.err(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${file}`);
|
|
355
|
+
return 2;
|
|
356
|
+
}
|
|
357
|
+
const read = readDslFile(file);
|
|
358
|
+
if (!read.ok) {
|
|
359
|
+
for (const error of read.errors) {
|
|
360
|
+
io.err(`${file}: ${error.path}: ${error.message} (${error.keyword})`);
|
|
361
|
+
}
|
|
362
|
+
return 1;
|
|
363
|
+
}
|
|
364
|
+
const isFragment = isFragmentFile(file);
|
|
365
|
+
const data = read.data;
|
|
366
|
+
const lines = [];
|
|
367
|
+
if (isFragment) {
|
|
368
|
+
lines.push(`fragment: ${data.fragment?.id ?? "(\u672A\u77E5)"}`);
|
|
369
|
+
if (data.fragment?.title !== void 0) lines.push(`\u6807\u9898: ${data.fragment.title}`);
|
|
370
|
+
} else {
|
|
371
|
+
lines.push(`page: ${data.page?.id ?? "(\u672A\u77E5)"}`);
|
|
372
|
+
if (data.page?.title !== void 0) lines.push(`\u6807\u9898: ${data.page.title}`);
|
|
373
|
+
if (data.page?.type !== void 0) lines.push(`\u7C7B\u578B: ${data.page.type}`);
|
|
374
|
+
}
|
|
375
|
+
lines.push(`dslVersion: ${data.dsl ?? "(\u672A\u77E5)"}`);
|
|
376
|
+
const stateKeys = Object.keys(data.state ?? {});
|
|
377
|
+
lines.push(`\u72B6\u6001\u952E: ${stateKeys.length > 0 ? stateKeys.join(", ") : "(\u65E0)"}`);
|
|
378
|
+
const dataSources = Object.entries(data.dataSources ?? {});
|
|
379
|
+
lines.push(`\u6570\u636E\u6E90: ${dataSources.length} \u4E2A`);
|
|
380
|
+
for (const [name, config] of dataSources) {
|
|
381
|
+
const service = config.service !== void 0 ? `, service=${config.service}` : "";
|
|
382
|
+
lines.push(` - ${name} (type=${config.type}${service})`);
|
|
383
|
+
}
|
|
384
|
+
const componentCounts = /* @__PURE__ */ new Map();
|
|
385
|
+
const actions = [];
|
|
386
|
+
const permissions = [];
|
|
387
|
+
walkDslTree(data.body, (node, path) => {
|
|
388
|
+
componentCounts.set(node.type, (componentCounts.get(node.type) ?? 0) + 1);
|
|
389
|
+
for (const [event, config] of Object.entries(node.events ?? {})) {
|
|
390
|
+
for (const action of actionList(config)) {
|
|
391
|
+
actions.push(` - ${path} events.${event} \u2192 ${action.action}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
for (const code of permissionCodes(node.permission)) {
|
|
395
|
+
permissions.push(` - ${path} \u2192 ${code}`);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
lines.push(`\u7EC4\u4EF6: ${[...componentCounts.values()].reduce((a, b) => a + b, 0)} \u4E2A`);
|
|
399
|
+
for (const [type, count] of [...componentCounts.entries()].sort()) {
|
|
400
|
+
lines.push(` - ${type} x${count}`);
|
|
401
|
+
}
|
|
402
|
+
for (const [event, config] of Object.entries(data.lifecycle ?? {})) {
|
|
403
|
+
for (const action of actionList(config)) {
|
|
404
|
+
actions.push(` - lifecycle.${event} \u2192 ${action.action}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
for (const code of permissionCodes(data.page?.permission)) {
|
|
408
|
+
permissions.push(` - /page \u2192 ${code}`);
|
|
409
|
+
}
|
|
410
|
+
lines.push(`\u52A8\u4F5C: ${actions.length} \u4E2A`);
|
|
411
|
+
lines.push(...actions.length > 0 ? actions : [" (\u65E0)"]);
|
|
412
|
+
lines.push(`\u6743\u9650: ${permissions.length} \u4E2A`);
|
|
413
|
+
lines.push(...permissions.length > 0 ? permissions : [" (\u65E0)"]);
|
|
414
|
+
const expressions = [];
|
|
415
|
+
for (const entry of collectStrings(data)) {
|
|
416
|
+
const { sources } = extractTemplateExpressions(entry.value);
|
|
417
|
+
for (const source of sources) {
|
|
418
|
+
expressions.push(` - ${entry.path}: \${${source}}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
lines.push(`\u8868\u8FBE\u5F0F: ${expressions.length} \u4E2A`);
|
|
422
|
+
lines.push(...expressions.length > 0 ? expressions : [" (\u65E0)"]);
|
|
423
|
+
for (const line of lines) {
|
|
424
|
+
io.out(line);
|
|
425
|
+
}
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/commands/lint.ts
|
|
430
|
+
import { ExpressionError, hasExpression, parseExpression } from "@pactor-app/expression";
|
|
431
|
+
function permissionCodes2(permission) {
|
|
432
|
+
if (permission === void 0) return [];
|
|
433
|
+
if (typeof permission === "string") return [permission];
|
|
434
|
+
if (Array.isArray(permission)) return permission;
|
|
435
|
+
return Array.isArray(permission.code) ? permission.code : [permission.code];
|
|
436
|
+
}
|
|
437
|
+
function actionList2(config) {
|
|
438
|
+
if (config === void 0) return [];
|
|
439
|
+
return Array.isArray(config) ? config : [config];
|
|
440
|
+
}
|
|
441
|
+
var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
442
|
+
var PERMISSION_CODE_RE = /^[^:]+:[^:]+$/;
|
|
443
|
+
function lintExpressions(data, findings) {
|
|
444
|
+
for (const entry of collectStrings(data)) {
|
|
445
|
+
const bareExpression = entry.path.endsWith("/expression") && !hasExpression(entry.value);
|
|
446
|
+
if (!hasExpression(entry.value) && !bareExpression) continue;
|
|
447
|
+
if (bareExpression) {
|
|
448
|
+
try {
|
|
449
|
+
parseExpression(entry.value);
|
|
450
|
+
} catch (error) {
|
|
451
|
+
findings.push({
|
|
452
|
+
level: "error",
|
|
453
|
+
rule: "expression-syntax",
|
|
454
|
+
path: entry.path,
|
|
455
|
+
message: error instanceof ExpressionError ? error.message : String(error)
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const { sources, unterminated } = extractTemplateExpressions(entry.value);
|
|
461
|
+
if (unterminated) {
|
|
462
|
+
findings.push({
|
|
463
|
+
level: "error",
|
|
464
|
+
rule: "expression-syntax",
|
|
465
|
+
path: entry.path,
|
|
466
|
+
message: '\u672A\u95ED\u5408\u7684 "${"'
|
|
467
|
+
});
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
for (const source of sources) {
|
|
471
|
+
try {
|
|
472
|
+
parseExpression(source);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
findings.push({
|
|
475
|
+
level: "error",
|
|
476
|
+
rule: "expression-syntax",
|
|
477
|
+
path: entry.path,
|
|
478
|
+
message: `\u8868\u8FBE\u5F0F \${${source}} \u89E3\u6790\u5931\u8D25: ${error instanceof ExpressionError ? error.message : String(error)}`
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function lintComponentNaming(body, findings) {
|
|
485
|
+
walkDslTree(body, (node, path) => {
|
|
486
|
+
if (typeof node.type !== "string" || !PASCAL_CASE_RE.test(node.type)) {
|
|
487
|
+
findings.push({
|
|
488
|
+
level: "warning",
|
|
489
|
+
rule: "component-naming",
|
|
490
|
+
path: `${path}/type`,
|
|
491
|
+
message: `\u7EC4\u4EF6\u7C7B\u578B "${String(node.type)}" \u4E0D\u662F PascalCase`
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
function lintPermissionFormat(data, findings) {
|
|
497
|
+
const check = (codes, path) => {
|
|
498
|
+
for (const code of codes) {
|
|
499
|
+
if (!PERMISSION_CODE_RE.test(code)) {
|
|
500
|
+
findings.push({
|
|
501
|
+
level: "warning",
|
|
502
|
+
rule: "permission-format",
|
|
503
|
+
path,
|
|
504
|
+
message: `\u6743\u9650\u7801 "${code}" \u4E0D\u662F resource:action \u5F62\u6001`
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
check(permissionCodes2(data.page?.permission), "/page/permission");
|
|
510
|
+
walkDslTree(data.body, (node, path) => {
|
|
511
|
+
check(permissionCodes2(node.permission), `${path}/permission`);
|
|
512
|
+
for (const [event, config] of Object.entries(node.events ?? {})) {
|
|
513
|
+
for (const action of actionList2(config)) {
|
|
514
|
+
check(permissionCodes2(action.permission), `${path}/events/${event}/permission`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
for (const [event, config] of Object.entries(data.lifecycle ?? {})) {
|
|
519
|
+
for (const action of actionList2(config)) {
|
|
520
|
+
check(permissionCodes2(action.permission), `/lifecycle/${event}/permission`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function lintDslData(data, isFragment) {
|
|
525
|
+
const findings = [];
|
|
526
|
+
for (const error of validateDsl(data, isFragment)) {
|
|
527
|
+
findings.push({
|
|
528
|
+
level: "error",
|
|
529
|
+
rule: "schema",
|
|
530
|
+
path: error.path,
|
|
531
|
+
message: `${error.message} (${error.keyword})`
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
lintExpressions(data, findings);
|
|
535
|
+
const typed = data;
|
|
536
|
+
lintComponentNaming(typed.body, findings);
|
|
537
|
+
lintPermissionFormat(typed, findings);
|
|
538
|
+
return findings;
|
|
539
|
+
}
|
|
540
|
+
function runLint(args, io) {
|
|
541
|
+
if (args.positionals.length === 0) {
|
|
542
|
+
io.err("\u7528\u6CD5: pactor lint <paths...>");
|
|
543
|
+
return 2;
|
|
544
|
+
}
|
|
545
|
+
const { files, missing } = collectDslFiles(args.positionals);
|
|
546
|
+
for (const path of missing) {
|
|
547
|
+
io.err(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${path}`);
|
|
548
|
+
}
|
|
549
|
+
if (missing.length > 0) {
|
|
550
|
+
return 2;
|
|
551
|
+
}
|
|
552
|
+
if (files.length === 0) {
|
|
553
|
+
io.err("\u672A\u627E\u5230 DSL \u6587\u4EF6\uFF08.page/.fragment.yaml/.yml\uFF09");
|
|
554
|
+
return 2;
|
|
555
|
+
}
|
|
556
|
+
let errors = 0;
|
|
557
|
+
let warnings = 0;
|
|
558
|
+
for (const file of files) {
|
|
559
|
+
const read = readDslFile(file);
|
|
560
|
+
const findings = read.ok ? lintDslData(read.data, isFragmentFile(file)) : read.errors.map((error) => ({
|
|
561
|
+
level: "error",
|
|
562
|
+
rule: "yaml",
|
|
563
|
+
path: error.path,
|
|
564
|
+
message: error.message
|
|
565
|
+
}));
|
|
566
|
+
if (findings.length === 0) continue;
|
|
567
|
+
io.out(`${file}:`);
|
|
568
|
+
for (const finding of findings) {
|
|
569
|
+
io.out(` [${finding.level}] ${finding.rule} ${finding.path}: ${finding.message}`);
|
|
570
|
+
if (finding.level === "error") errors += 1;
|
|
571
|
+
else warnings += 1;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
io.out(`lint \u5B8C\u6210: ${files.length} \u4E2A\u6587\u4EF6\uFF0C${errors} \u4E2A error\uFF0C${warnings} \u4E2A warning`);
|
|
575
|
+
return errors > 0 ? 1 : 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/commands/schema.ts
|
|
579
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
580
|
+
import {
|
|
581
|
+
ACTION_CONFIG_SCHEMA_ID,
|
|
582
|
+
actionConfigSchema,
|
|
583
|
+
DSL_NODE_SCHEMA_ID,
|
|
584
|
+
dslNodeSchema,
|
|
585
|
+
PAGE_DSL_SCHEMA_ID,
|
|
586
|
+
pageDslSchema
|
|
587
|
+
} from "@pactor-app/schema";
|
|
588
|
+
function runSchema(args, io) {
|
|
589
|
+
const document = {
|
|
590
|
+
[PAGE_DSL_SCHEMA_ID]: pageDslSchema,
|
|
591
|
+
[DSL_NODE_SCHEMA_ID]: dslNodeSchema,
|
|
592
|
+
[ACTION_CONFIG_SCHEMA_ID]: actionConfigSchema
|
|
593
|
+
};
|
|
594
|
+
const json = JSON.stringify(document, null, 2);
|
|
595
|
+
const output = flagValue(args, "o");
|
|
596
|
+
if (output !== void 0) {
|
|
597
|
+
if (output === "") {
|
|
598
|
+
io.err("\u7528\u6CD5: pactor schema [-o <file>]");
|
|
599
|
+
return 2;
|
|
600
|
+
}
|
|
601
|
+
writeFileSync3(output, `${json}
|
|
602
|
+
`, "utf8");
|
|
603
|
+
io.out(`\u5DF2\u5199\u5165: ${output}`);
|
|
604
|
+
return 0;
|
|
605
|
+
}
|
|
606
|
+
io.out(json);
|
|
607
|
+
return 0;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/commands/validate.ts
|
|
611
|
+
import { existsSync as existsSync5, statSync as statSync5 } from "fs";
|
|
612
|
+
function runValidate(args, io) {
|
|
613
|
+
if (args.positionals.length === 0) {
|
|
614
|
+
io.err("\u7528\u6CD5: pactor validate <files...>");
|
|
615
|
+
return 2;
|
|
616
|
+
}
|
|
617
|
+
for (const file of args.positionals) {
|
|
618
|
+
if (!existsSync5(file) || !statSync5(file).isFile()) {
|
|
619
|
+
io.err(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${file}`);
|
|
620
|
+
return 2;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
let failed = 0;
|
|
624
|
+
for (const file of args.positionals) {
|
|
625
|
+
const read = readDslFile(file);
|
|
626
|
+
if (!read.ok) {
|
|
627
|
+
failed += 1;
|
|
628
|
+
for (const error of read.errors) {
|
|
629
|
+
io.err(`${file}: ${error.path}: ${error.message} (${error.keyword})`);
|
|
630
|
+
}
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
const errors = validateDsl(read.data, isFragmentFile(file));
|
|
634
|
+
if (errors.length > 0) {
|
|
635
|
+
failed += 1;
|
|
636
|
+
for (const error of errors) {
|
|
637
|
+
io.err(`${file}: ${error.path}: ${error.message} (${error.keyword})`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const passed = args.positionals.length - failed;
|
|
642
|
+
if (failed > 0) {
|
|
643
|
+
io.err(`\u6821\u9A8C\u5931\u8D25: ${failed} \u4E2A\u6587\u4EF6\u672A\u901A\u8FC7\uFF0C${passed} \u4E2A\u901A\u8FC7`);
|
|
644
|
+
return 1;
|
|
645
|
+
}
|
|
646
|
+
io.out(`\u6821\u9A8C\u901A\u8FC7: \u5171 ${passed} \u4E2A\u6587\u4EF6`);
|
|
647
|
+
return 0;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// src/cli.ts
|
|
651
|
+
var HELP_TEXT = `pactor \u2014 Pactor DSL \u547D\u4EE4\u884C\u5DE5\u5177
|
|
652
|
+
|
|
653
|
+
\u7528\u6CD5: pactor <command> [options]
|
|
654
|
+
|
|
655
|
+
\u547D\u4EE4:
|
|
656
|
+
pactor validate <files...> \u6821\u9A8C DSL \u6587\u4EF6\uFF08page \u5B8C\u6574\u6821\u9A8C\uFF0Cfragment \u5BBD\u677E\u68C0\u67E5\uFF09
|
|
657
|
+
pactor inspect <file> \u8F93\u51FA DSL \u7ED3\u6784\u6458\u8981\uFF08\u7EC4\u4EF6\u8BA1\u6570 / \u52A8\u4F5C / \u6743\u9650 / \u8868\u8FBE\u5F0F\uFF09
|
|
658
|
+
pactor lint <paths...> \u6821\u9A8C + \u9759\u6001\u68C0\u67E5\uFF08\u76EE\u5F55\u9012\u5F52 .page/.fragment.yaml\uFF09
|
|
659
|
+
pactor schema [-o <file>] \u8F93\u51FA Page DSL JSON Schema
|
|
660
|
+
pactor build <src> -o <dir> \u7F16\u8BD1 DSL \u4E3A Runtime Envelope JSON
|
|
661
|
+
pactor create <name> [-t app] \u4ECE\u6A21\u677F\u751F\u6210\u4E1A\u52A1\u5DE5\u7A0B
|
|
662
|
+
|
|
663
|
+
\u9000\u51FA\u7801: 0 \u6210\u529F\uFF1B1 \u6821\u9A8C/lint \u5931\u8D25\uFF1B2 \u7528\u6CD5\u9519\u8BEF
|
|
664
|
+
`;
|
|
665
|
+
function runCli(argv, io) {
|
|
666
|
+
const [command, ...rest] = argv;
|
|
667
|
+
if (command === void 0 || command === "--help" || command === "-h") {
|
|
668
|
+
io.out(HELP_TEXT);
|
|
669
|
+
return 0;
|
|
670
|
+
}
|
|
671
|
+
let args;
|
|
672
|
+
try {
|
|
673
|
+
args = parseArgs(rest);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
if (error instanceof UsageError) {
|
|
676
|
+
io.err(`\u53C2\u6570\u9519\u8BEF: ${error.message}`);
|
|
677
|
+
return 2;
|
|
678
|
+
}
|
|
679
|
+
throw error;
|
|
680
|
+
}
|
|
681
|
+
switch (command) {
|
|
682
|
+
case "validate":
|
|
683
|
+
return runValidate(args, io);
|
|
684
|
+
case "inspect":
|
|
685
|
+
return runInspect(args, io);
|
|
686
|
+
case "lint":
|
|
687
|
+
return runLint(args, io);
|
|
688
|
+
case "schema":
|
|
689
|
+
return runSchema(args, io);
|
|
690
|
+
case "build":
|
|
691
|
+
return runBuild(args, io);
|
|
692
|
+
case "create":
|
|
693
|
+
return runCreate(args, io);
|
|
694
|
+
default:
|
|
695
|
+
io.err(`\u672A\u77E5\u547D\u4EE4: ${command}`);
|
|
696
|
+
io.err(HELP_TEXT);
|
|
697
|
+
return 2;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
var isMain = process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
701
|
+
if (isMain) {
|
|
702
|
+
const io = {
|
|
703
|
+
out: (line) => process.stdout.write(`${line}
|
|
704
|
+
`),
|
|
705
|
+
err: (line) => process.stderr.write(`${line}
|
|
706
|
+
`)
|
|
707
|
+
};
|
|
708
|
+
process.exit(runCli(process.argv.slice(2), io));
|
|
709
|
+
}
|
|
710
|
+
export {
|
|
711
|
+
HELP_TEXT,
|
|
712
|
+
runCli
|
|
713
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pactor-app/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pactor CLI: local DSL toolchain (validate / inspect / lint / schema / build / create)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pactor",
|
|
7
|
+
"dsl",
|
|
8
|
+
"server-driven-ui",
|
|
9
|
+
"cli",
|
|
10
|
+
"toolchain"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/426-330/pactor.git",
|
|
16
|
+
"directory": "packages/cli"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/426-330/pactor/tree/main/packages/cli",
|
|
19
|
+
"bugs": "https://github.com/426-330/pactor/issues",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"pactor": "./dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"main": "./dist/cli.js",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"templates"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"yaml": "^2.9.0",
|
|
38
|
+
"@pactor-app/core": "0.1.0",
|
|
39
|
+
"@pactor-app/schema": "0.1.0",
|
|
40
|
+
"@pactor-app/expression": "0.1.0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# __APP_NAME__
|
|
2
|
+
|
|
3
|
+
由 `pactor create` 生成的 Pactor 业务工程:vite + react + @pactor-app/app + @pactor-app/ui + @pactor-app/vite-plugin。
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm install
|
|
7
|
+
pnpm dev
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
- `src/pages/home.page.yaml`:示例 Page DSL,经 @pactor-app/vite-plugin 在构建期转换为 JS 模块
|
|
11
|
+
- `src/main.tsx`:`createPactorApplication` + `StaticDslLoader`(本地 DSL 开发);接入服务端后改用 `api: { baseURL: '/api/runtime' }`
|
|
12
|
+
- 默认 UI kit 为 shadcn,`@pactor-app/ui/styles.css` 样式导入为必需步骤
|
|
13
|
+
|
|
14
|
+
## 说明
|
|
15
|
+
|
|
16
|
+
- 依赖版本为语义化占位(`^0.1.0` 等),请在 Pactor 发布后按实际版本对齐。
|
|
17
|
+
- 如需 `*.page.yaml` 的 TypeScript 类型声明,按 @pactor-app/vite-plugin 文档添加 `src/dsl-modules.d.ts`。
|
|
18
|
+
- 常用命令:`pactor validate src/pages/*.page.yaml`、`pactor lint src`、`pactor build src/pages -o dist/runtime`。
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
<title>__APP_NAME__</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__APP_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@pactor-app/app": "^0.1.0",
|
|
13
|
+
"@pactor-app/core": "^0.1.0",
|
|
14
|
+
"@pactor-app/resource": "^0.1.0",
|
|
15
|
+
"@pactor-app/ui": "^0.1.0",
|
|
16
|
+
"react": "^18.3.1",
|
|
17
|
+
"react-dom": "^18.3.1"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/react": "^18.3.12",
|
|
21
|
+
"@types/react-dom": "^18.3.1",
|
|
22
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
23
|
+
"@pactor-app/vite-plugin": "^0.1.0",
|
|
24
|
+
"typescript": "^5.6.3",
|
|
25
|
+
"vite": "^5.4.11"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createPactorApplication } from '@pactor-app/app';
|
|
2
|
+
import { StaticDslLoader } from '@pactor-app/resource';
|
|
3
|
+
// 默认 UI kit 为 shadcn(@pactor-app/ui),样式表导入为必需步骤
|
|
4
|
+
import '@pactor-app/ui/styles.css';
|
|
5
|
+
|
|
6
|
+
import homePage from './pages/home.page.yaml';
|
|
7
|
+
|
|
8
|
+
// 本地 DSL 开发:静态加载器直接喂入构建期 import 的页面;
|
|
9
|
+
// 接入服务端后改回 api: { baseURL: '/api/runtime' }(HttpDslResourceLoader)
|
|
10
|
+
const loader = new StaticDslLoader({
|
|
11
|
+
pages: { home: homePage },
|
|
12
|
+
bootstrap: {
|
|
13
|
+
app: { name: '__APP_NAME__' },
|
|
14
|
+
menus: [{ id: 'home', title: '首页', path: '/', pageId: 'home' }],
|
|
15
|
+
pageVersions: { home: 1 },
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
createPactorApplication({
|
|
20
|
+
appId: '__APP_NAME__',
|
|
21
|
+
loader,
|
|
22
|
+
}).mount('#root');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
dsl: '1.0'
|
|
2
|
+
page:
|
|
3
|
+
id: home
|
|
4
|
+
title: __APP_NAME__
|
|
5
|
+
type: custom
|
|
6
|
+
state:
|
|
7
|
+
keyword: ''
|
|
8
|
+
body:
|
|
9
|
+
type: Page
|
|
10
|
+
props:
|
|
11
|
+
title: __APP_NAME__
|
|
12
|
+
children:
|
|
13
|
+
- type: Alert
|
|
14
|
+
props:
|
|
15
|
+
type: info
|
|
16
|
+
message: 欢迎使用 Pactor!当前 keyword:${state.keyword}
|
|
17
|
+
- type: Card
|
|
18
|
+
props:
|
|
19
|
+
title: 示例卡片
|
|
20
|
+
children:
|
|
21
|
+
- type: Button
|
|
22
|
+
props:
|
|
23
|
+
text: 开始使用
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"strict": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|