openxiangda-cli 2.0.0-alpha.169 → 2.0.0-alpha.170
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/package.json +5 -5
- package/template/AGENTS.md +38 -24
- package/template/README.md +14 -17
- package/template/apps/web/e2e/component-defaults-fixture.css +15 -0
- package/template/apps/web/e2e/data-api-live-fixture.tsx +3 -3
- package/template/apps/web/e2e/field-protocol-fixture.tsx +48 -11
- package/template/apps/web/e2e/field-protocol.spec.ts +90 -0
- package/template/apps/web/e2e/mobile-files.spec.ts +93 -0
- package/template/apps/web/e2e/mobile-reference-fixture.tsx +314 -0
- package/template/apps/web/e2e/mobile-reference.spec.ts +360 -0
- package/template/apps/web/e2e/mobile-selection.spec.ts +199 -0
- package/template/apps/web/e2e/resource-experience-fixture.tsx +54 -9
- package/template/apps/web/e2e/resource-platform-mock.ts +202 -0
- package/template/apps/web/e2e/resources.spec.ts +77 -240
- package/template/apps/web/e2e/standard-admin.spec.ts +441 -0
- package/template/apps/web/e2e/workflow.spec.ts +2 -2
- package/template/apps/web/field-protocol.e2e.html +7 -0
- package/template/apps/web/mobile-reference.e2e.html +27 -0
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/resource-experience.e2e.html +1 -0
- package/template/apps/web/src/document.css +5 -0
- package/template/apps/web/src/main.tsx +1 -0
- package/template/apps/web/src/user-end/tailwind.css +1 -13
- package/template/apps/web/test/contracts.test.ts +7 -6
- package/template/apps/web/vite.config.ts +1 -0
- package/template/appspec/app.md +27 -1
- package/template/openxiangda.config.ts +2 -24
- package/template/package.json +3 -4
- package/template/apps/server/Dockerfile +0 -20
- package/template/apps/server/package.json +0 -27
- package/template/apps/server/src/app.module.ts +0 -23
- package/template/apps/server/src/main.ts +0 -5
- package/template/apps/server/test/smoke.test.ts +0 -17
- package/template/apps/server/tsconfig.json +0 -9
- package/template/apps/web/e2e/theme-contract-fixture.css +0 -15
- package/template/scripts/verify-template-budget.mjs +0 -49
package/template/appspec/app.md
CHANGED
|
@@ -6,7 +6,7 @@ status: active
|
|
|
6
6
|
---
|
|
7
7
|
# OpenXiangda 应用
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
本文件保留当前业务需求和已确认决定,帮助后续修改找回上下文;不作为额外发布门禁。资源、字段、权限和动作仍以
|
|
10
10
|
`openxiangda.config.ts` 与实时编译合同为准。
|
|
11
11
|
|
|
12
12
|
## 业务目标
|
|
@@ -34,3 +34,29 @@ AppSpec 是可选的业务意图辅助层,不是发布门禁。资源、字段
|
|
|
34
34
|
## 未确认问题
|
|
35
35
|
|
|
36
36
|
- 无。
|
|
37
|
+
|
|
38
|
+
## 业务任务与页面
|
|
39
|
+
|
|
40
|
+
| 用户要完成的任务 | 页面/入口 | PC/移动 | 消费的数据模型 | 标准 CRUD 或任务页 |
|
|
41
|
+
| --- | --- | --- | --- | --- |
|
|
42
|
+
|
|
43
|
+
<!-- 辅助表可以没有页面;多表共同完成一项业务时只规划需要的任务页。 -->
|
|
44
|
+
|
|
45
|
+
## 权限矩阵与确认
|
|
46
|
+
|
|
47
|
+
| 业务角色 | 任务/数据 | 可查看范围 | 新增 | 修改 | 删除 | 特殊动作/敏感字段 |
|
|
48
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
49
|
+
|
|
50
|
+
- 已确认的业务权限:
|
|
51
|
+
- 尚未确认的权限(不要擅自授予):
|
|
52
|
+
|
|
53
|
+
## 验收场景
|
|
54
|
+
|
|
55
|
+
| 场景 | 操作角色 | 前置数据 | 页面操作 | 预期结果 | 验证记录 |
|
|
56
|
+
| --- | --- | --- | --- | --- | --- |
|
|
57
|
+
|
|
58
|
+
<!-- 简单应用保留核心录入/查询场景即可。多角色增加无权访问与范围场景;流程增加审批流转场景。 -->
|
|
59
|
+
|
|
60
|
+
## 变更记录
|
|
61
|
+
|
|
62
|
+
<!-- 简单变更记录日期、业务原因和验收结果;复杂能力链接其 CAP/ChangeSpec,不重复维护多份真相。 -->
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { defineAdminNavigation, defineOpenXiangdaApp } from 'openxiangda/config';
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/** Start with business modules; select standard CRUD only for the models that need it. */
|
|
4
4
|
export default defineOpenXiangdaApp({
|
|
5
|
-
schemaVersion: 3,
|
|
6
5
|
app: { code: 'openxiangda-application', name: 'OpenXiangda 应用' },
|
|
7
6
|
frontend: {
|
|
8
7
|
root: 'apps/web',
|
|
@@ -52,26 +51,5 @@ export default defineOpenXiangdaApp({
|
|
|
52
51
|
},
|
|
53
52
|
admin: { navigation: defineAdminNavigation([]) },
|
|
54
53
|
},
|
|
55
|
-
|
|
56
|
-
root: 'apps/server',
|
|
57
|
-
runtime: 'node',
|
|
58
|
-
framework: 'nestjs',
|
|
59
|
-
enabled: false,
|
|
60
|
-
isolation: 'shared',
|
|
61
|
-
resourceProfile: 'light',
|
|
62
|
-
secrets: [],
|
|
63
|
-
operations: [],
|
|
64
|
-
},
|
|
65
|
-
platform: { root: 'platform' },
|
|
66
|
-
authz: {
|
|
67
|
-
capabilities: [],
|
|
68
|
-
roles: [],
|
|
69
|
-
scopeDimensions: [],
|
|
70
|
-
scopeSources: [],
|
|
71
|
-
dataPolicies: [],
|
|
72
|
-
authorizationTransitions: [],
|
|
73
|
-
},
|
|
74
|
-
data: { resources: [] },
|
|
75
|
-
events: { schemas: [], subscriptions: [], timers: [], dateTriggers: [] },
|
|
76
|
-
workflows: { definitions: [], bindings: [], activations: [], providers: [], editableParameters: [] },
|
|
54
|
+
modules: [],
|
|
77
55
|
});
|
package/template/package.json
CHANGED
|
@@ -12,18 +12,17 @@
|
|
|
12
12
|
"prepare": "pnpm workspace:build",
|
|
13
13
|
"workspace:build": "pnpm --filter @app/contracts build",
|
|
14
14
|
"predev": "pnpm workspace:build",
|
|
15
|
-
"dev": "
|
|
15
|
+
"dev": "openxiangda dev",
|
|
16
16
|
"dev:web": "pnpm --filter @app/web dev",
|
|
17
|
-
"dev:server": "pnpm --filter @app/server dev",
|
|
18
17
|
"precheck": "pnpm workspace:build",
|
|
19
|
-
"check": "pnpm --recursive check
|
|
18
|
+
"check": "pnpm --recursive check",
|
|
20
19
|
"pretest": "pnpm workspace:build",
|
|
21
20
|
"test": "pnpm --recursive test",
|
|
22
21
|
"test:e2e": "pnpm --filter @app/web test:e2e",
|
|
23
22
|
"build": "pnpm --recursive build"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
|
-
"openxiangda": "2.0.0-alpha.
|
|
25
|
+
"openxiangda": "2.0.0-alpha.96",
|
|
27
26
|
"typescript": "5.9.3"
|
|
28
27
|
}
|
|
29
28
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
FROM node:22-alpine AS build
|
|
2
|
-
WORKDIR /workspace
|
|
3
|
-
RUN corepack enable
|
|
4
|
-
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./
|
|
5
|
-
COPY apps/server/package.json apps/server/package.json
|
|
6
|
-
COPY packages/contracts/package.json packages/contracts/package.json
|
|
7
|
-
RUN node -e "const fs=require('node:fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); delete p.devDependencies; fs.writeFileSync('package.json', JSON.stringify(p));"
|
|
8
|
-
RUN pnpm --filter @app/server --filter @app/contracts install --no-frozen-lockfile --ignore-scripts
|
|
9
|
-
COPY apps/server apps/server
|
|
10
|
-
COPY packages/contracts packages/contracts
|
|
11
|
-
RUN pnpm --filter @app/server... build
|
|
12
|
-
RUN pnpm --filter @app/server deploy --prod --legacy --ignore-scripts /output
|
|
13
|
-
|
|
14
|
-
FROM node:22-alpine AS runtime
|
|
15
|
-
ENV NODE_ENV=production
|
|
16
|
-
WORKDIR /app
|
|
17
|
-
COPY --from=build --chown=1000:1000 /output /app
|
|
18
|
-
USER 1000:1000
|
|
19
|
-
EXPOSE 3000
|
|
20
|
-
CMD ["node", "dist/main.js"]
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@app/server",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "tsx watch src/main.ts",
|
|
8
|
-
"check": "tsc -p tsconfig.json --noEmit",
|
|
9
|
-
"test": "tsx --test test/*.test.ts",
|
|
10
|
-
"build": "tsc -p tsconfig.json",
|
|
11
|
-
"start": "node dist/main.js"
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"@app/contracts": "workspace:*",
|
|
15
|
-
"@nestjs/common": "11.1.29",
|
|
16
|
-
"@nestjs/core": "11.1.29",
|
|
17
|
-
"@nestjs/platform-fastify": "11.1.29",
|
|
18
|
-
"openxiangda": "2.0.0-alpha.95",
|
|
19
|
-
"reflect-metadata": "0.2.2",
|
|
20
|
-
"rxjs": "7.8.2"
|
|
21
|
-
},
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@types/node": "24.13.3",
|
|
24
|
-
"tsx": "4.23.12",
|
|
25
|
-
"typescript": "5.9.3"
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { Module } from '@nestjs/common';
|
|
2
|
-
import {
|
|
3
|
-
eventHandlerManifest,
|
|
4
|
-
eventSchemas,
|
|
5
|
-
eventSubscriptionCodes,
|
|
6
|
-
} from '@app/contracts';
|
|
7
|
-
import {
|
|
8
|
-
OpenXiangdaModule,
|
|
9
|
-
eventSigningSecretsFromEnvironment,
|
|
10
|
-
} from 'openxiangda/nest';
|
|
11
|
-
|
|
12
|
-
@Module({
|
|
13
|
-
imports: [
|
|
14
|
-
OpenXiangdaModule.forApplication({
|
|
15
|
-
eventHandlerManifest,
|
|
16
|
-
eventSchemas,
|
|
17
|
-
eventSigningSecrets: eventSigningSecretsFromEnvironment(
|
|
18
|
-
eventSubscriptionCodes
|
|
19
|
-
),
|
|
20
|
-
}),
|
|
21
|
-
],
|
|
22
|
-
})
|
|
23
|
-
export class AppModule {}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
-
import test from 'node:test';
|
|
4
|
-
|
|
5
|
-
test('the clean server template only owns platform bootstrap', () => {
|
|
6
|
-
const source = readFileSync(new URL('../src/app.module.ts', import.meta.url), 'utf8');
|
|
7
|
-
const main = readFileSync(new URL('../src/main.ts', import.meta.url), 'utf8');
|
|
8
|
-
assert.match(source, /OpenXiangdaModule\.forApplication/);
|
|
9
|
-
assert.doesNotMatch(source, /OPENXIANGDA_(?:APP_CODE|ENVIRONMENT_ID|OAUTH_CLIENT_ID)/);
|
|
10
|
-
assert.match(source, /eventHandlerManifest/);
|
|
11
|
-
assert.match(source, /eventSigningSecretsFromEnvironment/);
|
|
12
|
-
assert.match(main, /bootstrapOpenXiangdaApplication\(AppModule\)/);
|
|
13
|
-
assert.doesNotMatch(main, /NestFactory|FastifyAdapter/);
|
|
14
|
-
const legacyMarkers = ['oauth' + 'Client', 'Workflow', 'instrument' + '-context'];
|
|
15
|
-
assert.doesNotMatch(source, new RegExp(legacyMarkers.join('|')));
|
|
16
|
-
assert.doesNotMatch(source, /createData|updateData|deleteData|queryData/);
|
|
17
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
.theme-contract-page {
|
|
2
|
-
display: grid;
|
|
3
|
-
align-content: start;
|
|
4
|
-
gap: 16px;
|
|
5
|
-
padding: 24px;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.theme-contract-panel {
|
|
9
|
-
border: 1px solid var(--oxa-antd-color-border-secondary);
|
|
10
|
-
border-radius: var(--oxa-antd-border-radius);
|
|
11
|
-
background: var(--oxa-antd-color-bg-container);
|
|
12
|
-
padding: 24px;
|
|
13
|
-
color: var(--oxa-antd-color-text);
|
|
14
|
-
box-shadow: var(--oxa-antd-box-shadow-tertiary);
|
|
15
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
-
import { join, relative, resolve } from 'node:path';
|
|
3
|
-
|
|
4
|
-
const root = resolve(import.meta.dirname, '..');
|
|
5
|
-
const generated = /(?:^|\/)(?:node_modules|dist|coverage|playwright-report|test-results|\.openxiangda|\.turbo|\.turbopack|\.umi|\.umi-production)(?:\/|$)/;
|
|
6
|
-
const sourceExtensions = /\.(?:[cm]?[jt]sx?|css|html|json|md|ya?ml)$/;
|
|
7
|
-
const files = [];
|
|
8
|
-
function visit(directory) {
|
|
9
|
-
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
10
|
-
const path = join(directory, entry.name);
|
|
11
|
-
const name = relative(root, path).replaceAll('\\', '/');
|
|
12
|
-
if (
|
|
13
|
-
generated.test(name) ||
|
|
14
|
-
name === 'pnpm-lock.yaml' ||
|
|
15
|
-
name === 'packages/contracts/src/generated.ts'
|
|
16
|
-
) continue;
|
|
17
|
-
if (entry.isDirectory()) visit(path);
|
|
18
|
-
else if (entry.isFile() && sourceExtensions.test(name)) files.push(path);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
visit(root);
|
|
22
|
-
const bytes = files.reduce((total, file) => total + statSync(file).size, 0);
|
|
23
|
-
if (files.length > 50) throw new Error(`TEMPLATE_SOURCE_FILE_BUDGET_EXCEEDED:${files.length}>50`);
|
|
24
|
-
if (bytes > 500_000) throw new Error(`TEMPLATE_SOURCE_BYTE_BUDGET_EXCEEDED:${bytes}>500000`);
|
|
25
|
-
const productionFiles = files.filter(file => {
|
|
26
|
-
const name = relative(root, file).replaceAll('\\', '/');
|
|
27
|
-
return (
|
|
28
|
-
name === 'package.json' ||
|
|
29
|
-
name === 'openxiangda.config.ts' ||
|
|
30
|
-
name.startsWith('platform/') ||
|
|
31
|
-
(name.startsWith('apps/') &&
|
|
32
|
-
!name.includes('/test/') &&
|
|
33
|
-
!name.includes('/e2e/') &&
|
|
34
|
-
!name.includes('/scripts/'))
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
const source = productionFiles.map(file => readFileSync(file, 'utf8')).join('\n');
|
|
38
|
-
const agents = readFileSync(resolve(root, 'AGENTS.md'), 'utf8');
|
|
39
|
-
if (!agents.includes('pnpm openxiangda')) throw new Error('TEMPLATE_AGENT_PINNED_CLI_MISSING');
|
|
40
|
-
if (
|
|
41
|
-
!agents.includes("union of that user's application roles") ||
|
|
42
|
-
!agents.includes('Perspective projects only read visibility')
|
|
43
|
-
) {
|
|
44
|
-
throw new Error('TEMPLATE_AGENT_ROLE_UNION_BOUNDARY_MISSING');
|
|
45
|
-
}
|
|
46
|
-
for (const marker of ['@umijs/', 'openxiangda-' + 'admin', 'openxiangda-' + 'user', 'instrument_query', 'instrument_save', 'function.invoke', 'departmentScopeKey', 'instrumentScopeKey', 'adminUserIds', 'college-' + 'am', 'user-' + 'wang', 'dept-' + 'am-test']) {
|
|
47
|
-
if (source.includes(marker)) throw new Error(`TEMPLATE_FORBIDDEN_MARKER:${marker}`);
|
|
48
|
-
}
|
|
49
|
-
process.stdout.write(`Verified compact template: ${files.length} source files, ${bytes} bytes.\n`);
|