openxiangda-cli 2.0.0-alpha.89 → 2.0.0-alpha.91
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 +2 -2
- package/bin/run.js +1 -0
- package/dist/commands/skill.d.ts +26 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +43 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/create-workspace.d.ts.map +1 -1
- package/dist/create-workspace.js +3 -3
- package/dist/create-workspace.js.map +1 -1
- package/dist/toolchain-capsule.d.ts.map +1 -1
- package/dist/toolchain-capsule.js +4 -8
- package/dist/toolchain-capsule.js.map +1 -1
- package/package.json +6 -7
- package/template/AGENTS.md +2 -1
- package/template/README.md +3 -3
- package/template/apps/server/package.json +1 -1
- package/template/apps/server/src/app.module.ts +1 -1
- package/template/apps/server/src/main.ts +1 -1
- package/template/apps/web/e2e/client-fixture.ts +5 -0
- package/template/apps/web/e2e/data-api-live-fixture.tsx +19 -14
- package/template/apps/web/e2e/field-protocol-fixture.tsx +87 -20
- package/template/apps/web/e2e/field-protocol.spec.ts +86 -0
- package/template/apps/web/e2e/resources.spec.ts +20 -181
- package/template/apps/web/field-protocol.e2e.html +3 -0
- package/template/apps/web/package.json +2 -5
- package/template/apps/web/src/main.tsx +12 -60
- package/template/apps/web/test/contracts.test.ts +52 -104
- package/template/openxiangda.config.ts +1 -1
- package/template/package.json +1 -3
- package/template/packages/contracts/src/generated.ts +1 -0
- package/template/scripts/verify-template-budget.mjs +2 -2
- package/template/apps/web/src/AuthoritativeSelector.tsx +0 -661
- package/template/apps/web/src/FilePreviewPage.tsx +0 -324
- package/template/apps/web/src/RoleSubjectSelect.tsx +0 -128
- package/template/apps/web/src/Shell.tsx +0 -445
- package/template/apps/web/src/appearance.tsx +0 -147
- package/template/apps/web/src/components/platform-fields/AddressField.tsx +0 -338
- package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +0 -245
- package/template/apps/web/src/components/platform-fields/CascadeField.tsx +0 -49
- package/template/apps/web/src/components/platform-fields/DateTimeField.tsx +0 -148
- package/template/apps/web/src/components/platform-fields/JsonField.tsx +0 -77
- package/template/apps/web/src/components/platform-fields/LocationField.tsx +0 -164
- package/template/apps/web/src/components/platform-fields/PlatformDirectoryPicker.tsx +0 -673
- package/template/apps/web/src/components/platform-fields/ResourceReferenceField.tsx +0 -67
- package/template/apps/web/src/components/platform-fields/RichTextField.tsx +0 -196
- package/template/apps/web/src/components/platform-fields/SignatureField.tsx +0 -315
- package/template/apps/web/src/components/platform-fields/SubtableField.tsx +0 -553
- package/template/apps/web/src/components/platform-fields/address-value.ts +0 -80
- package/template/apps/web/src/components/platform-fields/cascade-value.ts +0 -66
- package/template/apps/web/src/components/platform-fields/directory-value.ts +0 -53
- package/template/apps/web/src/components/platform-fields/field-form-codec.ts +0 -98
- package/template/apps/web/src/components/platform-fields/location-value.ts +0 -87
- package/template/apps/web/src/components/platform-fields/resource-query.ts +0 -131
- package/template/apps/web/src/components/platform-fields/rich-text-value.ts +0 -59
- package/template/apps/web/src/components/platform-fields/subtable-value.ts +0 -187
- package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +0 -1184
- package/template/apps/web/src/components/resource/ResourceBatchActions.tsx +0 -326
- package/template/apps/web/src/components/resource/StandardResourcePages.tsx +0 -392
- package/template/apps/web/src/components/resource/SurfaceFields.tsx +0 -915
- package/template/apps/web/src/components/resource/generated-resource-definition.ts +0 -18
- package/template/apps/web/src/components/resource/resource-import.ts +0 -205
- package/template/apps/web/src/data-provider.ts +0 -72
- package/template/apps/web/src/platform-client.ts +0 -708
- package/template/apps/web/src/runtime-meta.ts +0 -75
- package/template/apps/web/src/runtime.tsx +0 -227
- package/template/apps/web/src/styles.css +0 -1941
- package/template/apps/web/test/address-field.test.ts +0 -66
- package/template/apps/web/test/cascade-value.test.ts +0 -68
- package/template/apps/web/test/directory-value.test.ts +0 -55
- package/template/apps/web/test/field-bounds.test.ts +0 -17
- package/template/apps/web/test/field-form-codec.test.ts +0 -85
- package/template/apps/web/test/location-field.test.ts +0 -83
- package/template/apps/web/test/resource-import.test.ts +0 -143
- package/template/apps/web/test/resource-query.test.ts +0 -144
- package/template/apps/web/test/rich-json-field.test.ts +0 -53
- package/template/apps/web/test/signature-serial-field.test.ts +0 -40
- package/template/apps/web/test/subtable-value.test.ts +0 -144
|
@@ -1,124 +1,72 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
3
3
|
import test from 'node:test';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { readRuntimeMount, resolveApplicationBasename } from 'openxiangda/core';
|
|
5
|
+
import {
|
|
6
|
+
resourceCodes,
|
|
7
|
+
resourceDefinitions,
|
|
8
|
+
} from '../../../packages/contracts/src/generated.js';
|
|
6
9
|
|
|
7
|
-
test('clean template
|
|
8
|
-
const config = readFileSync(
|
|
10
|
+
test('clean template contains only application declarations and one React entry', () => {
|
|
11
|
+
const config = readFileSync(
|
|
12
|
+
new URL('../../../openxiangda.config.ts', import.meta.url),
|
|
13
|
+
'utf8'
|
|
14
|
+
);
|
|
9
15
|
const source = readFileSync(new URL('../src/main.tsx', import.meta.url), 'utf8');
|
|
16
|
+
const productionSources = readdirSync(new URL('../src', import.meta.url), {
|
|
17
|
+
withFileTypes: true,
|
|
18
|
+
})
|
|
19
|
+
.filter(entry => entry.isFile())
|
|
20
|
+
.map(entry => entry.name)
|
|
21
|
+
.sort();
|
|
22
|
+
assert.deepEqual(productionSources, ['main.tsx']);
|
|
10
23
|
assert.deepEqual(Object.keys(resourceDefinitions).sort(), [...resourceCodes].sort());
|
|
11
24
|
assert.match(config, /data:\s*\{\s*resources:/);
|
|
12
|
-
assert.match(source, /
|
|
13
|
-
|
|
25
|
+
assert.match(source, /OpenXiangdaApplication/);
|
|
26
|
+
assert.match(source, /from 'openxiangda\/react'/);
|
|
27
|
+
assert.match(source, /openxiangda\/react\/styles\.css/);
|
|
28
|
+
const legacyMarkers = [
|
|
29
|
+
'instruments',
|
|
30
|
+
'colleges',
|
|
31
|
+
'仪器' + '资源管理',
|
|
32
|
+
'学院' + '字典',
|
|
33
|
+
'HGY',
|
|
34
|
+
'Instrument',
|
|
35
|
+
];
|
|
14
36
|
assert.doesNotMatch(`${config}\n${source}`, new RegExp(legacyMarkers.join('|')));
|
|
15
37
|
});
|
|
16
38
|
|
|
17
|
-
test('
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
assert.match(selector, /ResourceReferenceValue/);
|
|
39
|
-
assert.match(selector, /role="dialog"/);
|
|
40
|
-
assert.match(selector, /oxa-mobile-reference-sheet/);
|
|
41
|
-
assert.match(crud, /MobileResourceListPage/);
|
|
42
|
-
assert.match(crud, /fieldWritable/);
|
|
43
|
-
assert.match(crud, /exportResourceRows/);
|
|
44
|
-
assert.doesNotMatch(crud, /rows\.push|pageSize = 500|csvCell/);
|
|
45
|
-
assert.match(crud, /数据读取失败/);
|
|
46
|
-
assert.match(crud, /state\.list\.query\.refetch/);
|
|
47
|
-
assert.match(crud, /key=\{pageKey\}/);
|
|
48
|
-
assert.match(crud, /setQuery\(\{\}\)/);
|
|
49
|
-
const client = readFileSync(new URL('../src/platform-client.ts', import.meta.url), 'utf8');
|
|
50
|
-
const resourceQuery = readFileSync(new URL('../src/components/platform-fields/resource-query.ts', import.meta.url), 'utf8');
|
|
51
|
-
assert.match(client, /declaredFields\.has\(requestedSort\.field\)/);
|
|
52
|
-
assert.match(client, /SCHEMA_VERSIONS\.dataFieldSourceQuery/);
|
|
53
|
-
assert.match(client, /SCHEMA_VERSIONS\.dataExportRequest/);
|
|
54
|
-
assert.match(client, /`\$\{base\}\/export`/);
|
|
55
|
-
assert.match(client, /accept', 'text\/csv'/);
|
|
56
|
-
assert.match(client, /\/source\/query/);
|
|
57
|
-
assert.doesNotMatch(client, /operator:\s*'ilike'|operator:\s*'cs'|filters:/);
|
|
58
|
-
assert.match(resourceQuery, /if \(!declaredFields\.has\(fieldCode\)\) continue/);
|
|
59
|
-
assert.match(client, /\/api\/auth\/logout/);
|
|
60
|
-
assert.match(client, /x-openxiangda-role-session-id/);
|
|
61
|
-
assert.match(client, /requestApplicationApi/);
|
|
62
|
-
assert.doesNotMatch(client, /\/native\/current-user/);
|
|
63
|
-
const runtime = readFileSync(new URL('../src/runtime.tsx', import.meta.url), 'utf8');
|
|
64
|
-
assert.match(runtime, /selection_required/);
|
|
65
|
-
assert.match(runtime, /identityEpoch/);
|
|
66
|
-
assert.doesNotMatch(runtime, /focused_role|authorization-mode/);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('admin appearance is a bounded Ant Design theme with compact route chrome', () => {
|
|
70
|
-
const appearance = readFileSync(new URL('../src/appearance.tsx', import.meta.url), 'utf8');
|
|
71
|
-
const main = readFileSync(new URL('../src/main.tsx', import.meta.url), 'utf8');
|
|
72
|
-
const shell = readFileSync(new URL('../src/Shell.tsx', import.meta.url), 'utf8');
|
|
73
|
-
assert.match(appearance, /'system' \| 'light' \| 'dark'/);
|
|
74
|
-
assert.match(appearance, /antdTheme\.darkAlgorithm/);
|
|
75
|
-
assert.match(appearance, /antdTheme\.defaultAlgorithm/);
|
|
76
|
-
assert.match(appearance, /ConfigProvider/);
|
|
77
|
-
assert.match(appearance, /openxiangda\.admin\.appearance/);
|
|
78
|
-
assert.match(main, /<AppearanceProvider>/);
|
|
79
|
-
assert.match(main, /oxa-global-request-loading/);
|
|
80
|
-
assert.match(main, /path="\/admin"/);
|
|
81
|
-
assert.match(shell, /openxiangda\.admin\.route-history/);
|
|
82
|
-
assert.match(shell, /MAX_HISTORY_ITEMS = 8/);
|
|
83
|
-
assert.match(shell, /<Segmented/);
|
|
84
|
-
assert.match(shell, /跟随系统/);
|
|
85
|
-
assert.match(shell, /退出登录/);
|
|
86
|
-
assert.match(shell, /切换应用角色/);
|
|
87
|
-
assert.match(shell, /<Header className="oxa-topbar">\s*<Breadcrumb/);
|
|
88
|
-
assert.doesNotMatch(shell, /oxa-topbar-page/);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('standard desktop CRUD exposes compact filters, table tools, and lazy audit history', () => {
|
|
92
|
-
const pages = readFileSync(new URL('../src/components/resource/StandardResourcePages.tsx', import.meta.url), 'utf8');
|
|
93
|
-
const crud = readFileSync(new URL('../src/components/resource/GeneratedResourceCrud.tsx', import.meta.url), 'utf8');
|
|
94
|
-
assert.match(pages, /className="oxa-list-surface"/);
|
|
95
|
-
assert.doesNotMatch(pages, /className="oxa-page-heading"/);
|
|
96
|
-
assert.match(crud, /filtersExpanded/);
|
|
97
|
-
assert.match(crud, /更多筛选/);
|
|
98
|
-
assert.match(crud, /columnMenu/);
|
|
99
|
-
assert.match(crud, /densityMenu/);
|
|
100
|
-
assert.match(crud, /列设置/);
|
|
101
|
-
assert.match(crud, /密度/);
|
|
102
|
-
assert.match(crud, /ResourceImportButton/);
|
|
103
|
-
assert.match(crud, /ResourceBatchActions/);
|
|
104
|
-
const batch = readFileSync(new URL('../src/components/resource/ResourceBatchActions.tsx', import.meta.url), 'utf8');
|
|
105
|
-
assert.match(batch, /transactNativeData/);
|
|
106
|
-
assert.match(batch, /批量修改/);
|
|
107
|
-
assert.match(batch, /批量删除/);
|
|
108
|
-
assert.match(batch, /确认导入/);
|
|
109
|
-
assert.match(crud, /!auditExpanded/);
|
|
110
|
-
assert.match(crud, /<Collapse/);
|
|
111
|
-
assert.match(crud, /查看创建、更新和删除的字段变化/);
|
|
39
|
+
test('application manifests expose one direct OpenXiangda dependency', () => {
|
|
40
|
+
for (const relative of [
|
|
41
|
+
'../../../package.json',
|
|
42
|
+
'../package.json',
|
|
43
|
+
'../../server/package.json',
|
|
44
|
+
]) {
|
|
45
|
+
const manifest = JSON.parse(
|
|
46
|
+
readFileSync(new URL(relative, import.meta.url), 'utf8')
|
|
47
|
+
) as Record<string, Record<string, string>>;
|
|
48
|
+
const dependencies = Object.entries({
|
|
49
|
+
...(manifest.dependencies || {}),
|
|
50
|
+
...(manifest.devDependencies || {}),
|
|
51
|
+
...(manifest.peerDependencies || {}),
|
|
52
|
+
}).filter(([name]) =>
|
|
53
|
+
name === 'openxiangda' || name.startsWith('openxiangda-')
|
|
54
|
+
);
|
|
55
|
+
assert.deepEqual(
|
|
56
|
+
dependencies.map(([name]) => name),
|
|
57
|
+
dependencies.length ? ['openxiangda'] : []
|
|
58
|
+
);
|
|
59
|
+
}
|
|
112
60
|
});
|
|
113
61
|
|
|
114
62
|
test('runtime metadata stays mount-scoped', () => {
|
|
115
63
|
const mount = readRuntimeMount(
|
|
116
|
-
|
|
64
|
+
name =>
|
|
117
65
|
({
|
|
118
66
|
'openxiangda-runtime-base': '/view/example-app/',
|
|
119
67
|
'openxiangda-app-code': 'example-app',
|
|
120
68
|
'openxiangda-environment': 'preproduction',
|
|
121
|
-
}[name]
|
|
69
|
+
})[name]
|
|
122
70
|
);
|
|
123
71
|
assert.equal(resolveApplicationBasename(mount), '/view/example-app');
|
|
124
72
|
});
|
package/template/package.json
CHANGED
|
@@ -22,9 +22,7 @@
|
|
|
22
22
|
"build": "pnpm --recursive build"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"openxiangda
|
|
26
|
-
"openxiangda-contracts": "2.0.0-alpha.43",
|
|
27
|
-
"openxiangda-devkit-core": "2.0.0-alpha.56",
|
|
25
|
+
"openxiangda": "2.0.0-alpha.3",
|
|
28
26
|
"typescript": "5.9.3"
|
|
29
27
|
}
|
|
30
28
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by OpenXiangda 2.0 Native compiler. Do not edit by hand.
|
|
2
2
|
export const compilerContractVersion = "native-2" as const;
|
|
3
3
|
export const appCode = "openxiangda-application" as const;
|
|
4
|
+
export const appName = "OpenXiangda 应用" as const;
|
|
4
5
|
export const resourceCodes = [] as const;
|
|
5
6
|
export const resourceSurfaces = {} as const;
|
|
6
7
|
export const resourceDefinitions = {} as const;
|
|
@@ -20,7 +20,7 @@ function visit(directory) {
|
|
|
20
20
|
}
|
|
21
21
|
visit(root);
|
|
22
22
|
const bytes = files.reduce((total, file) => total + statSync(file).size, 0);
|
|
23
|
-
if (files.length >
|
|
23
|
+
if (files.length > 50) throw new Error(`TEMPLATE_SOURCE_FILE_BUDGET_EXCEEDED:${files.length}>50`);
|
|
24
24
|
if (bytes > 500_000) throw new Error(`TEMPLATE_SOURCE_BYTE_BUDGET_EXCEEDED:${bytes}>500000`);
|
|
25
25
|
const productionFiles = files.filter(file => {
|
|
26
26
|
const name = relative(root, file).replaceAll('\\', '/');
|
|
@@ -39,7 +39,7 @@ const agents = readFileSync(resolve(root, 'AGENTS.md'), 'utf8');
|
|
|
39
39
|
if (!agents.includes('pnpm openxiangda')) throw new Error('TEMPLATE_AGENT_PINNED_CLI_MISSING');
|
|
40
40
|
if (
|
|
41
41
|
!agents.includes("union of that user's application roles") ||
|
|
42
|
-
!agents.includes('
|
|
42
|
+
!agents.includes('does not transmit a RoleSession header')
|
|
43
43
|
) {
|
|
44
44
|
throw new Error('TEMPLATE_AGENT_ROLE_UNION_BOUNDARY_MISSING');
|
|
45
45
|
}
|