openxiangda 1.0.16 → 1.0.17
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 +7 -0
- package/lib/cli.js +1962 -118
- package/openxiangda-skills/SKILL.md +3 -0
- package/openxiangda-skills/references/connector-resources.md +68 -0
- package/openxiangda-skills/references/pages/page-sdk.md +1 -0
- package/openxiangda-skills/references/workspace-state.md +8 -2
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +5 -1
- package/package.json +2 -1
- package/packages/sdk/dist/runtime/index.cjs +58 -0
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +29 -1
- package/packages/sdk/dist/runtime/index.d.ts +29 -1
- package/packages/sdk/dist/runtime/index.mjs +58 -0
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
package/lib/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
3
4
|
const { spawnSync } = require('child_process');
|
|
4
5
|
const {
|
|
5
6
|
CONFIG_FILE,
|
|
@@ -44,6 +45,7 @@ async function main(argv) {
|
|
|
44
45
|
if (command === 'automation') return automation(rest);
|
|
45
46
|
if (command === 'permission') return permission(rest);
|
|
46
47
|
if (command === 'settings') return settings(rest);
|
|
48
|
+
if (command === 'resource') return resource(rest);
|
|
47
49
|
if (command === 'inspect') return inspect(rest);
|
|
48
50
|
if (command === 'skill') return skill(rest);
|
|
49
51
|
if (command === 'commands') return commands(rest);
|
|
@@ -63,7 +65,7 @@ Usage:
|
|
|
63
65
|
openxiangda env [--profile name]
|
|
64
66
|
openxiangda workspace init [dir] [--name package-name] [--install] [--profile name --app-type APP_XXX]
|
|
65
67
|
openxiangda workspace bind --profile <name> --app-type <APP_XXX>
|
|
66
|
-
openxiangda workspace publish --profile <name>
|
|
68
|
+
openxiangda workspace publish --profile <name> [--prune]
|
|
67
69
|
openxiangda app list [--profile name] [--json]
|
|
68
70
|
openxiangda app create <name> [--profile name] [--description text]
|
|
69
71
|
openxiangda app snapshot <APP_XXX> [--profile name] [--json]
|
|
@@ -90,6 +92,7 @@ Usage:
|
|
|
90
92
|
openxiangda permission page-group-list|page-group-create|page-group-bind
|
|
91
93
|
openxiangda permission form-group-list|form-group-create|form-group-bind
|
|
92
94
|
openxiangda settings get|save|indexes|indexes-save|data-management|data-management-save|public-access
|
|
95
|
+
openxiangda resource validate|plan|publish|pull [--profile name] [--json]
|
|
93
96
|
openxiangda inspect app|form|workflow|automation|permissions
|
|
94
97
|
openxiangda skill install [--agent codex] [--dest <skills-dir>] [--force] [--dry-run] [--json]
|
|
95
98
|
openxiangda skill status [--agent codex] [--dest <skills-dir>] [--json]
|
|
@@ -407,6 +410,11 @@ async function workspace(args) {
|
|
|
407
410
|
workflows: {},
|
|
408
411
|
automations: {},
|
|
409
412
|
menus: {},
|
|
413
|
+
roles: {},
|
|
414
|
+
connectors: {},
|
|
415
|
+
pagePermissionGroups: {},
|
|
416
|
+
formPermissionGroups: {},
|
|
417
|
+
formSettings: {},
|
|
410
418
|
},
|
|
411
419
|
updatedAt: new Date().toISOString(),
|
|
412
420
|
};
|
|
@@ -429,6 +437,10 @@ async function workspace(args) {
|
|
|
429
437
|
`/openxiangda-api/v1/apps/${encodeURIComponent(bound.appType)}/snapshot`
|
|
430
438
|
);
|
|
431
439
|
runWorkspacePublish(profileName, profile, bound.appType);
|
|
440
|
+
await publishResourcesForWorkspace(config, profileName, {
|
|
441
|
+
quiet: true,
|
|
442
|
+
prune: Boolean(flags.prune),
|
|
443
|
+
});
|
|
432
444
|
return;
|
|
433
445
|
}
|
|
434
446
|
|
|
@@ -1657,6 +1669,53 @@ async function settings(args) {
|
|
|
1657
1669
|
);
|
|
1658
1670
|
}
|
|
1659
1671
|
|
|
1672
|
+
async function resource(args) {
|
|
1673
|
+
const [subcommand, ...rest] = args;
|
|
1674
|
+
const { flags } = parseArgs(rest);
|
|
1675
|
+
const config = loadConfig();
|
|
1676
|
+
const profileName = flags.profile || config.currentProfile;
|
|
1677
|
+
|
|
1678
|
+
if (!['validate', 'plan', 'publish', 'pull'].includes(subcommand)) {
|
|
1679
|
+
fail('用法: openxiangda resource validate|plan|publish|pull [--profile name] [--json]');
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
if (subcommand === 'pull') {
|
|
1683
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1684
|
+
const result = await pullResources(config, target);
|
|
1685
|
+
if (flags.json) return writeJson(result);
|
|
1686
|
+
printResourceResult(result);
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
const manifest = loadWorkspaceResources();
|
|
1691
|
+
const validation = validateWorkspaceResources(manifest);
|
|
1692
|
+
if (subcommand === 'validate') {
|
|
1693
|
+
if (flags.json) return writeJson(validation);
|
|
1694
|
+
printResourceValidation(validation);
|
|
1695
|
+
if (validation.errors.length > 0) {
|
|
1696
|
+
fail(`资源配置校验失败: ${validation.errors.length} 个错误`);
|
|
1697
|
+
}
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
if (validation.errors.length > 0) {
|
|
1701
|
+
fail(`资源配置校验失败: ${validation.errors[0]}`);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1705
|
+
if (subcommand === 'plan') {
|
|
1706
|
+
const plan = await buildResourcePlan(config, target, manifest);
|
|
1707
|
+
if (flags.json) return writeJson(plan);
|
|
1708
|
+
printResourcePlan(plan);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
const result = await publishResourceManifest(config, target, manifest, {
|
|
1713
|
+
prune: Boolean(flags.prune),
|
|
1714
|
+
});
|
|
1715
|
+
if (flags.json) return writeJson(result);
|
|
1716
|
+
printResourceResult(result);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1660
1719
|
async function inspect(args) {
|
|
1661
1720
|
const [subcommand, ...rest] = args;
|
|
1662
1721
|
const { flags, positional } = parseArgs(rest);
|
|
@@ -1744,7 +1803,7 @@ async function commands(args) {
|
|
|
1744
1803
|
'platform add|list|use|remove',
|
|
1745
1804
|
'auth status|refresh|logout',
|
|
1746
1805
|
'env',
|
|
1747
|
-
'workspace init|bind|publish',
|
|
1806
|
+
'workspace init|bind|publish [--prune]',
|
|
1748
1807
|
'app list|create|snapshot',
|
|
1749
1808
|
'form list|create|bind|pull|publish',
|
|
1750
1809
|
'page list|publish|bind|releases|activate',
|
|
@@ -1755,6 +1814,7 @@ async function commands(args) {
|
|
|
1755
1814
|
'permission page-group-list|page-group-create|page-group-bind',
|
|
1756
1815
|
'permission form-group-list|form-group-create|form-group-bind|form-summary|menu-permissions',
|
|
1757
1816
|
'settings get|save|indexes|indexes-save|data-management|data-management-save|public-access|public-access-save|public-access-delete',
|
|
1817
|
+
'resource validate|plan|publish|pull',
|
|
1758
1818
|
'inspect app|form|workflow|automation|permissions',
|
|
1759
1819
|
'skill install|status',
|
|
1760
1820
|
],
|
|
@@ -1891,8 +1951,10 @@ function ensureResourceBuckets(bound) {
|
|
|
1891
1951
|
bound.resources.automations = bound.resources.automations || {};
|
|
1892
1952
|
bound.resources.menus = bound.resources.menus || {};
|
|
1893
1953
|
bound.resources.roles = bound.resources.roles || {};
|
|
1954
|
+
bound.resources.connectors = bound.resources.connectors || {};
|
|
1894
1955
|
bound.resources.pagePermissionGroups = bound.resources.pagePermissionGroups || {};
|
|
1895
1956
|
bound.resources.formPermissionGroups = bound.resources.formPermissionGroups || {};
|
|
1957
|
+
bound.resources.formSettings = bound.resources.formSettings || {};
|
|
1896
1958
|
}
|
|
1897
1959
|
|
|
1898
1960
|
function resolveFormUuid(bound, formKey, flags = {}) {
|
|
@@ -2033,106 +2095,80 @@ function resolveSettingsFormUuid(bound, formKey, flags = {}) {
|
|
|
2033
2095
|
}
|
|
2034
2096
|
|
|
2035
2097
|
function saveFormResource(target, formCode, formUuid, extra = {}) {
|
|
2036
|
-
target
|
|
2037
|
-
|
|
2038
|
-
...target.bound,
|
|
2039
|
-
baseUrl: target.profile.baseUrl,
|
|
2040
|
-
appType: target.appType,
|
|
2041
|
-
updatedAt: new Date().toISOString(),
|
|
2042
|
-
};
|
|
2043
|
-
const nextBound = target.state.profiles[target.profileName];
|
|
2044
|
-
ensureResourceBuckets(nextBound);
|
|
2045
|
-
nextBound.resources.forms[formCode] = {
|
|
2046
|
-
...(nextBound.resources.forms[formCode] || {}),
|
|
2047
|
-
...extra,
|
|
2098
|
+
saveStateResource(target, 'forms', formCode, {
|
|
2099
|
+
...pickStateFields(extra, []),
|
|
2048
2100
|
formUuid,
|
|
2049
|
-
|
|
2050
|
-
};
|
|
2051
|
-
saveProjectState(target.state);
|
|
2052
|
-
target.bound = nextBound;
|
|
2101
|
+
}, ['formUuid']);
|
|
2053
2102
|
}
|
|
2054
2103
|
|
|
2055
2104
|
function savePageResource(target, pageCode, pageId, extra = {}) {
|
|
2056
|
-
|
|
2057
|
-
target
|
|
2058
|
-
...
|
|
2059
|
-
baseUrl: target.profile.baseUrl,
|
|
2060
|
-
appType: target.appType,
|
|
2061
|
-
updatedAt: new Date().toISOString(),
|
|
2062
|
-
};
|
|
2063
|
-
const nextBound = target.state.profiles[target.profileName];
|
|
2064
|
-
ensureResourceBuckets(nextBound);
|
|
2065
|
-
nextBound.resources.pages[pageCode] = {
|
|
2066
|
-
...(nextBound.resources.pages[pageCode] || {}),
|
|
2067
|
-
...extra,
|
|
2105
|
+
const keys = ['pageId', 'routeKey', 'legacyFormUuid', 'formUuid'];
|
|
2106
|
+
saveStateResource(target, 'pages', pageCode, {
|
|
2107
|
+
...pickStateFields(extra, keys),
|
|
2068
2108
|
pageId,
|
|
2069
|
-
|
|
2070
|
-
};
|
|
2071
|
-
saveProjectState(target.state);
|
|
2072
|
-
target.bound = nextBound;
|
|
2109
|
+
}, keys);
|
|
2073
2110
|
}
|
|
2074
2111
|
|
|
2075
2112
|
function saveMenuResource(target, menuCode, menuId, extra = {}) {
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
...(
|
|
2087
|
-
...extra,
|
|
2113
|
+
const keys = [
|
|
2114
|
+
'menuId',
|
|
2115
|
+
'formUuid',
|
|
2116
|
+
'pageId',
|
|
2117
|
+
'pageCode',
|
|
2118
|
+
'routeKey',
|
|
2119
|
+
'legacyFormUuid',
|
|
2120
|
+
'parentId',
|
|
2121
|
+
];
|
|
2122
|
+
saveStateResource(target, 'menus', menuCode, {
|
|
2123
|
+
...pickStateFields(extra, keys),
|
|
2088
2124
|
menuId,
|
|
2089
|
-
|
|
2090
|
-
};
|
|
2091
|
-
saveProjectState(target.state);
|
|
2092
|
-
target.bound = nextBound;
|
|
2125
|
+
}, keys);
|
|
2093
2126
|
}
|
|
2094
2127
|
|
|
2095
2128
|
function saveWorkflowResource(target, workflowCode, workflowId, extra = {}) {
|
|
2096
|
-
|
|
2097
|
-
target
|
|
2098
|
-
...
|
|
2099
|
-
baseUrl: target.profile.baseUrl,
|
|
2100
|
-
appType: target.appType,
|
|
2101
|
-
updatedAt: new Date().toISOString(),
|
|
2102
|
-
};
|
|
2103
|
-
const nextBound = target.state.profiles[target.profileName];
|
|
2104
|
-
ensureResourceBuckets(nextBound);
|
|
2105
|
-
nextBound.resources.workflows[workflowCode] = {
|
|
2106
|
-
...(nextBound.resources.workflows[workflowCode] || {}),
|
|
2107
|
-
...extra,
|
|
2129
|
+
const keys = ['workflowId', 'formUuid'];
|
|
2130
|
+
saveStateResource(target, 'workflows', workflowCode, {
|
|
2131
|
+
...pickStateFields(extra, keys),
|
|
2108
2132
|
workflowId,
|
|
2109
|
-
|
|
2110
|
-
};
|
|
2111
|
-
saveProjectState(target.state);
|
|
2112
|
-
target.bound = nextBound;
|
|
2133
|
+
}, keys);
|
|
2113
2134
|
}
|
|
2114
2135
|
|
|
2115
2136
|
function saveAutomationResource(target, automationCode, automationId, extra = {}) {
|
|
2116
|
-
|
|
2117
|
-
target
|
|
2118
|
-
...
|
|
2119
|
-
baseUrl: target.profile.baseUrl,
|
|
2120
|
-
appType: target.appType,
|
|
2121
|
-
updatedAt: new Date().toISOString(),
|
|
2122
|
-
};
|
|
2123
|
-
const nextBound = target.state.profiles[target.profileName];
|
|
2124
|
-
ensureResourceBuckets(nextBound);
|
|
2125
|
-
nextBound.resources.automations[automationCode] = {
|
|
2126
|
-
...(nextBound.resources.automations[automationCode] || {}),
|
|
2127
|
-
...extra,
|
|
2137
|
+
const keys = ['automationId', 'formUuid'];
|
|
2138
|
+
saveStateResource(target, 'automations', automationCode, {
|
|
2139
|
+
...pickStateFields(extra, keys),
|
|
2128
2140
|
automationId,
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2141
|
+
}, keys);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
function saveRoleResource(target, roleCode, roleId) {
|
|
2145
|
+
saveStateResource(target, 'roles', roleCode, { roleId }, ['roleId']);
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
function saveConnectorResource(target, connectorCode, connectorId, extra = {}) {
|
|
2149
|
+
const apis = {};
|
|
2150
|
+
for (const [apiCode, value] of Object.entries(extra.apis || {})) {
|
|
2151
|
+
if (value?.apiId) apis[apiCode] = { apiId: value.apiId };
|
|
2152
|
+
}
|
|
2153
|
+
saveStateResource(target, 'connectors', connectorCode, {
|
|
2154
|
+
connectorId,
|
|
2155
|
+
...(Object.keys(apis).length > 0 ? { apis } : {}),
|
|
2156
|
+
}, ['connectorId', 'apis']);
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
function savePagePermissionGroupResource(target, groupCode, groupId) {
|
|
2160
|
+
saveStateResource(target, 'pagePermissionGroups', groupCode, { groupId }, ['groupId']);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
function saveFormPermissionGroupResource(target, groupCode, groupId, extra = {}) {
|
|
2164
|
+
const keys = ['groupId', 'formUuid'];
|
|
2165
|
+
saveStateResource(target, 'formPermissionGroups', groupCode, {
|
|
2166
|
+
...pickStateFields(extra, keys),
|
|
2167
|
+
groupId,
|
|
2168
|
+
}, keys);
|
|
2133
2169
|
}
|
|
2134
2170
|
|
|
2135
|
-
function
|
|
2171
|
+
function saveStateResource(target, bucket, code, value = {}, keys = Object.keys(value)) {
|
|
2136
2172
|
target.state.profiles = target.state.profiles || {};
|
|
2137
2173
|
target.state.profiles[target.profileName] = {
|
|
2138
2174
|
...target.bound,
|
|
@@ -2142,54 +2178,1862 @@ function saveRoleResource(target, roleCode, roleId, extra = {}) {
|
|
|
2142
2178
|
};
|
|
2143
2179
|
const nextBound = target.state.profiles[target.profileName];
|
|
2144
2180
|
ensureResourceBuckets(nextBound);
|
|
2145
|
-
nextBound.resources
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2181
|
+
const previous = nextBound.resources[bucket]?.[code] || {};
|
|
2182
|
+
nextBound.resources[bucket] = nextBound.resources[bucket] || {};
|
|
2183
|
+
nextBound.resources[bucket][code] = {
|
|
2184
|
+
...pickStateFields(previous, keys),
|
|
2185
|
+
...pickStateFields(value, keys),
|
|
2149
2186
|
updatedAt: new Date().toISOString(),
|
|
2150
2187
|
};
|
|
2151
2188
|
saveProjectState(target.state);
|
|
2152
2189
|
target.bound = nextBound;
|
|
2153
2190
|
}
|
|
2154
2191
|
|
|
2155
|
-
function
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2192
|
+
function pickStateFields(value, keys) {
|
|
2193
|
+
const result = {};
|
|
2194
|
+
for (const key of keys) {
|
|
2195
|
+
if (value?.[key] !== undefined && value[key] !== null && value[key] !== '') {
|
|
2196
|
+
result[key] = value[key];
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
return result;
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
const RESOURCE_SPECS = [
|
|
2203
|
+
{ key: 'roles', dir: 'roles', topFiles: ['roles.json'], pluralKeys: ['roles'] },
|
|
2204
|
+
{ key: 'connectors', dir: 'connectors', topFiles: ['connectors.json'], pluralKeys: ['connectors'] },
|
|
2205
|
+
{ key: 'menus', dir: 'menus', topFiles: ['menus.json'], pluralKeys: ['menus'] },
|
|
2206
|
+
{ key: 'workflows', dir: 'workflows', topFiles: ['workflows.json'], pluralKeys: ['workflows'] },
|
|
2207
|
+
{ key: 'automations', dir: 'automations', topFiles: ['automations.json'], pluralKeys: ['automations'] },
|
|
2208
|
+
{
|
|
2209
|
+
key: 'pagePermissionGroups',
|
|
2210
|
+
dir: path.join('permissions', 'page-groups'),
|
|
2211
|
+
topFiles: [path.join('permissions', 'page-groups.json')],
|
|
2212
|
+
pluralKeys: ['pagePermissionGroups', 'pageGroups'],
|
|
2213
|
+
},
|
|
2214
|
+
{
|
|
2215
|
+
key: 'formPermissionGroups',
|
|
2216
|
+
dir: path.join('permissions', 'form-groups'),
|
|
2217
|
+
topFiles: [path.join('permissions', 'form-groups.json')],
|
|
2218
|
+
pluralKeys: ['formPermissionGroups', 'formGroups'],
|
|
2219
|
+
},
|
|
2220
|
+
{
|
|
2221
|
+
key: 'formSettings',
|
|
2222
|
+
dir: path.join('settings', 'forms'),
|
|
2223
|
+
topFiles: [path.join('settings', 'forms.json')],
|
|
2224
|
+
pluralKeys: ['formSettings', 'forms'],
|
|
2225
|
+
},
|
|
2226
|
+
];
|
|
2227
|
+
|
|
2228
|
+
function loadWorkspaceResources() {
|
|
2229
|
+
const baseDir = path.join(process.cwd(), 'src', 'resources');
|
|
2230
|
+
const manifest = { baseDir };
|
|
2231
|
+
for (const spec of RESOURCE_SPECS) {
|
|
2232
|
+
manifest[spec.key] = readResourceItems(baseDir, spec);
|
|
2233
|
+
}
|
|
2234
|
+
return manifest;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
function readResourceItems(baseDir, spec) {
|
|
2238
|
+
const items = [];
|
|
2239
|
+
for (const relativeFile of spec.topFiles || []) {
|
|
2240
|
+
const filePath = path.join(baseDir, relativeFile);
|
|
2241
|
+
if (fs.existsSync(filePath)) {
|
|
2242
|
+
items.push(...readResourceItemsFromFile(filePath, spec));
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
const dirPath = path.join(baseDir, spec.dir);
|
|
2247
|
+
if (fs.existsSync(dirPath)) {
|
|
2248
|
+
for (const filePath of listJsonFiles(dirPath)) {
|
|
2249
|
+
items.push(...readResourceItemsFromFile(filePath, spec));
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return items;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
function readResourceItemsFromFile(filePath, spec) {
|
|
2256
|
+
let value;
|
|
2257
|
+
try {
|
|
2258
|
+
value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
2259
|
+
} catch (error) {
|
|
2260
|
+
return [
|
|
2261
|
+
{
|
|
2262
|
+
__invalid: true,
|
|
2263
|
+
__source: filePath,
|
|
2264
|
+
__error: `JSON 解析失败: ${error.message}`,
|
|
2265
|
+
},
|
|
2266
|
+
];
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
const defaultCode = path.basename(filePath, '.json');
|
|
2270
|
+
const values = extractResourceValues(value, spec);
|
|
2271
|
+
return values.map((item, index) => {
|
|
2272
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
2273
|
+
return {
|
|
2274
|
+
__invalid: true,
|
|
2275
|
+
__source: filePath,
|
|
2276
|
+
__error: '资源项必须是 JSON object',
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
const code =
|
|
2280
|
+
item.code ||
|
|
2281
|
+
item.resourceCode ||
|
|
2282
|
+
item.methodName ||
|
|
2283
|
+
item.formCode ||
|
|
2284
|
+
(values.length === 1 ? defaultCode : undefined);
|
|
2285
|
+
return {
|
|
2286
|
+
...item,
|
|
2287
|
+
...(code ? { code } : {}),
|
|
2288
|
+
__source: filePath,
|
|
2289
|
+
__dir: path.dirname(filePath),
|
|
2290
|
+
__index: index,
|
|
2291
|
+
};
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
function extractResourceValues(value, spec) {
|
|
2296
|
+
if (Array.isArray(value)) return value;
|
|
2297
|
+
if (!value || typeof value !== 'object') return [value];
|
|
2298
|
+
for (const key of spec.pluralKeys || []) {
|
|
2299
|
+
if (Array.isArray(value[key])) return value[key];
|
|
2300
|
+
}
|
|
2301
|
+
if (value.resource && typeof value.resource === 'object') return [value.resource];
|
|
2302
|
+
return [value];
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
function listJsonFiles(dirPath) {
|
|
2306
|
+
if (!fs.existsSync(dirPath)) return [];
|
|
2307
|
+
return fs
|
|
2308
|
+
.readdirSync(dirPath, { withFileTypes: true })
|
|
2309
|
+
.flatMap(entry => {
|
|
2310
|
+
const nextPath = path.join(dirPath, entry.name);
|
|
2311
|
+
if (entry.isDirectory()) return listJsonFiles(nextPath);
|
|
2312
|
+
return entry.isFile() && entry.name.endsWith('.json') ? [nextPath] : [];
|
|
2313
|
+
})
|
|
2314
|
+
.sort();
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function validateWorkspaceResources(manifest) {
|
|
2318
|
+
const errors = [];
|
|
2319
|
+
const warnings = [];
|
|
2320
|
+
const counts = {};
|
|
2321
|
+
for (const spec of RESOURCE_SPECS) {
|
|
2322
|
+
const items = manifest[spec.key] || [];
|
|
2323
|
+
counts[spec.key] = items.length;
|
|
2324
|
+
for (const item of items) {
|
|
2325
|
+
validateResourceItem(spec.key, item, errors, warnings);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
return {
|
|
2329
|
+
valid: errors.length === 0,
|
|
2330
|
+
errors,
|
|
2331
|
+
warnings,
|
|
2332
|
+
counts,
|
|
2333
|
+
baseDir: manifest.baseDir,
|
|
2162
2334
|
};
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
function validateResourceItem(kind, item, errors, warnings) {
|
|
2338
|
+
const label = resourceLabel(kind, item);
|
|
2339
|
+
if (item.__invalid) {
|
|
2340
|
+
errors.push(`${item.__source}: ${item.__error}`);
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
if (!item.code) errors.push(`${label}: 缺少 code`);
|
|
2344
|
+
|
|
2345
|
+
if (kind === 'connectors') {
|
|
2346
|
+
if (!item.name) errors.push(`${label}: 缺少 name`);
|
|
2347
|
+
if (!item.domain && !item.url) errors.push(`${label}: 缺少 domain 或 url`);
|
|
2348
|
+
if (!Array.isArray(item.apis) || item.apis.length === 0) {
|
|
2349
|
+
errors.push(`${label}: 缺少 apis`);
|
|
2350
|
+
} else {
|
|
2351
|
+
item.apis.forEach((api, index) => {
|
|
2352
|
+
if (!api.code && !api.methodName) errors.push(`${label}.apis[${index}]: 缺少 code`);
|
|
2353
|
+
if (!api.path) errors.push(`${label}.apis[${index}]: 缺少 path`);
|
|
2354
|
+
if (!api.method) errors.push(`${label}.apis[${index}]: 缺少 method`);
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
if (kind === 'roles' && !item.name) errors.push(`${label}: 缺少 name`);
|
|
2361
|
+
if (kind === 'menus' && !item.name) errors.push(`${label}: 缺少 name`);
|
|
2362
|
+
if (kind === 'workflows') {
|
|
2363
|
+
if (!item.formCode && !item.formUuid) errors.push(`${label}: 缺少 formCode 或 formUuid`);
|
|
2364
|
+
if (!item.definitionJson && !item.definitionFile) {
|
|
2365
|
+
errors.push(`${label}: 缺少 definitionJson 或 definitionFile`);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
if (kind === 'automations') {
|
|
2369
|
+
if (!item.name) errors.push(`${label}: 缺少 name`);
|
|
2370
|
+
if (!item.triggerConfig) errors.push(`${label}: 缺少 triggerConfig`);
|
|
2371
|
+
if (!item.definitionJson && !item.definitionFile) {
|
|
2372
|
+
errors.push(`${label}: 缺少 definitionJson 或 definitionFile`);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
if (kind === 'pagePermissionGroups' && !item.name) errors.push(`${label}: 缺少 name`);
|
|
2376
|
+
if (kind === 'formPermissionGroups') {
|
|
2377
|
+
if (!item.name) errors.push(`${label}: 缺少 name`);
|
|
2378
|
+
if (!item.formCode && !item.formUuid) errors.push(`${label}: 缺少 formCode 或 formUuid`);
|
|
2379
|
+
}
|
|
2380
|
+
if (kind === 'formSettings') {
|
|
2381
|
+
if (!item.formCode && !item.formUuid && !item.code) {
|
|
2382
|
+
errors.push(`${label}: 缺少 formCode、formUuid 或 code`);
|
|
2383
|
+
}
|
|
2384
|
+
if (
|
|
2385
|
+
item.settings === undefined &&
|
|
2386
|
+
item.indexes === undefined &&
|
|
2387
|
+
item.dataManagement === undefined &&
|
|
2388
|
+
item.publicAccess === undefined
|
|
2389
|
+
) {
|
|
2390
|
+
warnings.push(`${label}: 未声明 settings/indexes/dataManagement/publicAccess`);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
function resourceLabel(kind, item) {
|
|
2396
|
+
return `${kind}:${item.code || item.__source || item.__index || '?'}`;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
async function publishResourcesForWorkspace(config, profileName, options = {}) {
|
|
2400
|
+
const manifest = loadWorkspaceResources();
|
|
2401
|
+
const validation = validateWorkspaceResources(manifest);
|
|
2402
|
+
if (validation.errors.length > 0) {
|
|
2403
|
+
fail(`资源配置校验失败: ${validation.errors[0]}`);
|
|
2404
|
+
}
|
|
2405
|
+
const target = getWorkspaceTarget(config, profileName, {});
|
|
2406
|
+
return publishResourceManifest(config, target, manifest, options);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
async function buildResourcePlan(config, target, manifest) {
|
|
2410
|
+
const existing = await fetchExistingResourceMaps(config, target, manifest);
|
|
2411
|
+
const actions = [];
|
|
2412
|
+
addPlanActions(actions, 'role', manifest.roles, existing.roles, roleEquals);
|
|
2413
|
+
addPlanActions(actions, 'menu', manifest.menus, existing.menus, (item, current) => menuEquals(target.bound, item, current));
|
|
2414
|
+
addPlanActions(actions, 'connector', manifest.connectors, existing.connectors, connectorEquals);
|
|
2415
|
+
await addWorkflowPlanActions(config, target, actions, manifest.workflows, existing.workflows);
|
|
2416
|
+
await addAutomationPlanActions(config, target, actions, manifest.automations, existing.automations);
|
|
2417
|
+
addPlanActions(actions, 'pagePermissionGroup', manifest.pagePermissionGroups, existing.pagePermissionGroups, (item, current) => pagePermissionGroupEquals(target.bound, item, current));
|
|
2418
|
+
addPlanActions(actions, 'formPermissionGroup', manifest.formPermissionGroups, existing.formPermissionGroups, (item, current) => formPermissionGroupEquals(target.bound, item, current));
|
|
2419
|
+
for (const item of manifest.formSettings || []) {
|
|
2420
|
+
actions.push({ kind: 'formSetting', code: item.code || item.formCode || item.formUuid, action: 'update' });
|
|
2421
|
+
}
|
|
2422
|
+
return {
|
|
2423
|
+
appType: target.appType,
|
|
2424
|
+
profile: target.profileName,
|
|
2425
|
+
actions,
|
|
2426
|
+
summary: summarizeActions(actions),
|
|
2170
2427
|
};
|
|
2171
|
-
saveProjectState(target.state);
|
|
2172
|
-
target.bound = nextBound;
|
|
2173
2428
|
}
|
|
2174
2429
|
|
|
2175
|
-
function
|
|
2176
|
-
|
|
2177
|
-
target.state.profiles[target.profileName] = {
|
|
2178
|
-
...target.bound,
|
|
2179
|
-
baseUrl: target.profile.baseUrl,
|
|
2430
|
+
async function publishResourceManifest(config, target, manifest, options = {}) {
|
|
2431
|
+
const result = {
|
|
2180
2432
|
appType: target.appType,
|
|
2181
|
-
|
|
2433
|
+
profile: target.profileName,
|
|
2434
|
+
published: [],
|
|
2435
|
+
pruned: [],
|
|
2436
|
+
warnings: [],
|
|
2182
2437
|
};
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2438
|
+
if (isManifestEmpty(manifest)) {
|
|
2439
|
+
if (!options.quiet) result.warnings.push('未发现 src/resources 资源配置');
|
|
2440
|
+
if (!options.prune) return result;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
await publishRoleResources(config, target, manifest.roles || [], result);
|
|
2444
|
+
await publishFormSettingsResources(config, target, manifest.formSettings || [], result);
|
|
2445
|
+
await publishMenuResources(config, target, manifest.menus || [], result);
|
|
2446
|
+
await publishConnectorResources(config, target, manifest.connectors || [], result);
|
|
2447
|
+
await publishWorkflowResources(config, target, manifest.workflows || [], result);
|
|
2448
|
+
await publishAutomationResources(config, target, manifest.automations || [], result);
|
|
2449
|
+
await publishPagePermissionGroupResources(config, target, manifest.pagePermissionGroups || [], result);
|
|
2450
|
+
await publishFormPermissionGroupResources(config, target, manifest.formPermissionGroups || [], result);
|
|
2451
|
+
if (options.prune) {
|
|
2452
|
+
await pruneResourceManifest(config, target, manifest, result);
|
|
2453
|
+
}
|
|
2454
|
+
return result;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
function isManifestEmpty(manifest) {
|
|
2458
|
+
return RESOURCE_SPECS.every(spec => (manifest[spec.key] || []).length === 0);
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
async function fetchExistingResourceMaps(config, target, manifest) {
|
|
2462
|
+
const maps = {
|
|
2463
|
+
roles: new Map(),
|
|
2464
|
+
menus: new Map(),
|
|
2465
|
+
connectors: new Map(),
|
|
2466
|
+
workflows: new Map(),
|
|
2467
|
+
automations: new Map(),
|
|
2468
|
+
pagePermissionGroups: new Map(),
|
|
2469
|
+
formPermissionGroups: new Map(),
|
|
2190
2470
|
};
|
|
2191
|
-
|
|
2192
|
-
|
|
2471
|
+
|
|
2472
|
+
if ((manifest.roles || []).length > 0) {
|
|
2473
|
+
const data = await requestWithAuth(
|
|
2474
|
+
config,
|
|
2475
|
+
target.profileName,
|
|
2476
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`, {
|
|
2477
|
+
page: 1,
|
|
2478
|
+
limit: 1000,
|
|
2479
|
+
})
|
|
2480
|
+
);
|
|
2481
|
+
indexByCode(maps.roles, normalizeItems(data), item => item.code);
|
|
2482
|
+
}
|
|
2483
|
+
if ((manifest.menus || []).length > 0) {
|
|
2484
|
+
const data = await requestWithAuth(
|
|
2485
|
+
config,
|
|
2486
|
+
target.profileName,
|
|
2487
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`
|
|
2488
|
+
);
|
|
2489
|
+
indexByCode(maps.menus, flattenItems(normalizeItems(data)), item => item.resourceCode);
|
|
2490
|
+
}
|
|
2491
|
+
if ((manifest.connectors || []).length > 0) {
|
|
2492
|
+
const data = await requestWithAuth(
|
|
2493
|
+
config,
|
|
2494
|
+
target.profileName,
|
|
2495
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/connectors`
|
|
2496
|
+
);
|
|
2497
|
+
indexByCode(maps.connectors, normalizeItems(data), item => item.code || item.methodName);
|
|
2498
|
+
}
|
|
2499
|
+
if ((manifest.workflows || []).length > 0) {
|
|
2500
|
+
const data = await requestWithAuth(
|
|
2501
|
+
config,
|
|
2502
|
+
target.profileName,
|
|
2503
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows`, {
|
|
2504
|
+
page: 1,
|
|
2505
|
+
pageSize: 1000,
|
|
2506
|
+
})
|
|
2507
|
+
);
|
|
2508
|
+
indexByCode(maps.workflows, normalizeItems(data), item => item.resourceCode);
|
|
2509
|
+
}
|
|
2510
|
+
if ((manifest.automations || []).length > 0) {
|
|
2511
|
+
const data = await requestWithAuth(
|
|
2512
|
+
config,
|
|
2513
|
+
target.profileName,
|
|
2514
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations`, {
|
|
2515
|
+
page: 1,
|
|
2516
|
+
pageSize: 1000,
|
|
2517
|
+
})
|
|
2518
|
+
);
|
|
2519
|
+
indexByCode(maps.automations, normalizeItems(data), item => item.resourceCode);
|
|
2520
|
+
}
|
|
2521
|
+
if ((manifest.pagePermissionGroups || []).length > 0) {
|
|
2522
|
+
const data = await requestWithAuth(
|
|
2523
|
+
config,
|
|
2524
|
+
target.profileName,
|
|
2525
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups`, {
|
|
2526
|
+
page: 1,
|
|
2527
|
+
limit: 1000,
|
|
2528
|
+
})
|
|
2529
|
+
);
|
|
2530
|
+
indexByCode(maps.pagePermissionGroups, normalizeItems(data), item => item.resourceCode);
|
|
2531
|
+
}
|
|
2532
|
+
if ((manifest.formPermissionGroups || []).length > 0) {
|
|
2533
|
+
for (const formUuid of unique((manifest.formPermissionGroups || []).map(item => resolveManifestFormUuid(target.bound, item)).filter(Boolean))) {
|
|
2534
|
+
const data = await requestWithAuth(
|
|
2535
|
+
config,
|
|
2536
|
+
target.profileName,
|
|
2537
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups`, {
|
|
2538
|
+
page: 1,
|
|
2539
|
+
limit: 1000,
|
|
2540
|
+
})
|
|
2541
|
+
);
|
|
2542
|
+
indexByCode(maps.formPermissionGroups, normalizeItems(data), item => item.resourceCode);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
return maps;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function addPlanActions(actions, kind, desiredItems = [], existingMap, equals) {
|
|
2549
|
+
for (const item of desiredItems) {
|
|
2550
|
+
const code = item.code || item.resourceCode || item.methodName;
|
|
2551
|
+
const existing = existingMap.get(code);
|
|
2552
|
+
actions.push({
|
|
2553
|
+
kind,
|
|
2554
|
+
code,
|
|
2555
|
+
action: existing ? (equals(item, existing) ? 'noop' : 'update') : 'create',
|
|
2556
|
+
platformId: existing?.id,
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
async function addWorkflowPlanActions(config, target, actions, desiredItems = [], existingMap) {
|
|
2562
|
+
for (const item of desiredItems) {
|
|
2563
|
+
const code = item.code || item.resourceCode;
|
|
2564
|
+
const existing = existingMap.get(code);
|
|
2565
|
+
if (!existing) {
|
|
2566
|
+
actions.push({ kind: 'workflow', code, action: 'create' });
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2569
|
+
const detail = await requestOptionalWithAuth(
|
|
2570
|
+
config,
|
|
2571
|
+
target.profileName,
|
|
2572
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(existing.id)}`
|
|
2573
|
+
);
|
|
2574
|
+
actions.push({
|
|
2575
|
+
kind: 'workflow',
|
|
2576
|
+
code,
|
|
2577
|
+
action: workflowEquals(target.bound, item, detail || existing) ? 'noop' : 'update',
|
|
2578
|
+
platformId: existing.id,
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
async function addAutomationPlanActions(config, target, actions, desiredItems = [], existingMap) {
|
|
2584
|
+
for (const item of desiredItems) {
|
|
2585
|
+
const code = item.code || item.resourceCode;
|
|
2586
|
+
const existing = existingMap.get(code);
|
|
2587
|
+
if (!existing) {
|
|
2588
|
+
actions.push({ kind: 'automation', code, action: 'create' });
|
|
2589
|
+
continue;
|
|
2590
|
+
}
|
|
2591
|
+
const detail = await requestOptionalWithAuth(
|
|
2592
|
+
config,
|
|
2593
|
+
target.profileName,
|
|
2594
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(existing.id)}`
|
|
2595
|
+
);
|
|
2596
|
+
actions.push({
|
|
2597
|
+
kind: 'automation',
|
|
2598
|
+
code,
|
|
2599
|
+
action: automationEquals(target, item, detail || existing) ? 'noop' : 'update',
|
|
2600
|
+
platformId: existing.id,
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
function summarizeActions(actions) {
|
|
2606
|
+
return actions.reduce(
|
|
2607
|
+
(acc, item) => {
|
|
2608
|
+
acc[item.action] = (acc[item.action] || 0) + 1;
|
|
2609
|
+
return acc;
|
|
2610
|
+
},
|
|
2611
|
+
{ create: 0, update: 0, noop: 0, delete: 0 }
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
function normalizeItems(data) {
|
|
2616
|
+
if (!data) return [];
|
|
2617
|
+
if (Array.isArray(data)) return data;
|
|
2618
|
+
if (Array.isArray(data.items)) return data.items;
|
|
2619
|
+
if (Array.isArray(data.data)) return data.data;
|
|
2620
|
+
if (Array.isArray(data.data?.items)) return data.data.items;
|
|
2621
|
+
return [];
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
function flattenItems(items) {
|
|
2625
|
+
const result = [];
|
|
2626
|
+
const visit = item => {
|
|
2627
|
+
if (!item || typeof item !== 'object') return;
|
|
2628
|
+
result.push(item);
|
|
2629
|
+
for (const child of item.children || []) visit(child);
|
|
2630
|
+
};
|
|
2631
|
+
for (const item of items || []) visit(item);
|
|
2632
|
+
return result;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
function indexByCode(map, items, getCode) {
|
|
2636
|
+
for (const item of items || []) {
|
|
2637
|
+
const code = getCode(item);
|
|
2638
|
+
if (code) map.set(String(code), item);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
async function requestOptionalWithAuth(config, profileName, apiPath, options = {}) {
|
|
2643
|
+
try {
|
|
2644
|
+
return await requestWithAuth(config, profileName, apiPath, options);
|
|
2645
|
+
} catch (error) {
|
|
2646
|
+
if (Number(error?.apiCode) === 404 || String(error?.message || '').includes('HTTP 404')) {
|
|
2647
|
+
return null;
|
|
2648
|
+
}
|
|
2649
|
+
throw error;
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
async function publishRoleResources(config, target, roles, result) {
|
|
2654
|
+
for (const role of roles) {
|
|
2655
|
+
const existing = await findExistingRole(config, target, role.code);
|
|
2656
|
+
const body = {
|
|
2657
|
+
code: role.code,
|
|
2658
|
+
name: role.name || role.code,
|
|
2659
|
+
description: role.description || '',
|
|
2660
|
+
};
|
|
2661
|
+
const data = existing
|
|
2662
|
+
? await requestWithAuth(
|
|
2663
|
+
config,
|
|
2664
|
+
target.profileName,
|
|
2665
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles/${encodeURIComponent(existing.id)}`,
|
|
2666
|
+
{ method: 'PUT', body }
|
|
2667
|
+
)
|
|
2668
|
+
: await requestWithAuth(
|
|
2669
|
+
config,
|
|
2670
|
+
target.profileName,
|
|
2671
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`,
|
|
2672
|
+
{ method: 'POST', body }
|
|
2673
|
+
);
|
|
2674
|
+
if (data?.id) {
|
|
2675
|
+
saveRoleResource(target, role.code, data.id, { code: data.code, name: data.name });
|
|
2676
|
+
}
|
|
2677
|
+
if (Array.isArray(role.userIds) && role.userIds.length > 0 && data?.id) {
|
|
2678
|
+
await requestWithAuth(
|
|
2679
|
+
config,
|
|
2680
|
+
target.profileName,
|
|
2681
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles/${encodeURIComponent(data.id)}/users`,
|
|
2682
|
+
{ method: 'POST', body: { userIds: role.userIds } }
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
result.published.push({ kind: 'role', code: role.code, action: existing ? 'update' : 'create', id: data?.id });
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
async function findExistingRole(config, target, code) {
|
|
2690
|
+
const data = await requestWithAuth(
|
|
2691
|
+
config,
|
|
2692
|
+
target.profileName,
|
|
2693
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`, {
|
|
2694
|
+
code,
|
|
2695
|
+
page: 1,
|
|
2696
|
+
limit: 1,
|
|
2697
|
+
})
|
|
2698
|
+
);
|
|
2699
|
+
return normalizeItems(data).find(item => item.code === code);
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
async function publishFormSettingsResources(config, target, settingsItems, result) {
|
|
2703
|
+
for (const item of settingsItems) {
|
|
2704
|
+
const code = item.code || item.formCode || item.formUuid;
|
|
2705
|
+
const formUuid = resolveManifestFormUuid(target.bound, item, { fallbackToCode: true });
|
|
2706
|
+
if (!formUuid) fail(`表单设置 ${code} 无法解析 formUuid`);
|
|
2707
|
+
if (item.settings !== undefined) {
|
|
2708
|
+
await requestWithAuth(
|
|
2709
|
+
config,
|
|
2710
|
+
target.profileName,
|
|
2711
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/settings`,
|
|
2712
|
+
{ method: 'PUT', body: { settings: item.settings } }
|
|
2713
|
+
);
|
|
2714
|
+
}
|
|
2715
|
+
if (item.indexes !== undefined) {
|
|
2716
|
+
await requestWithAuth(
|
|
2717
|
+
config,
|
|
2718
|
+
target.profileName,
|
|
2719
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/field-indexes`,
|
|
2720
|
+
{ method: 'PUT', body: { indexes: item.indexes } }
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
if (item.dataManagement !== undefined) {
|
|
2724
|
+
await requestWithAuth(
|
|
2725
|
+
config,
|
|
2726
|
+
target.profileName,
|
|
2727
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/data-management`,
|
|
2728
|
+
{ method: 'PUT', body: { config: item.dataManagement } }
|
|
2729
|
+
);
|
|
2730
|
+
}
|
|
2731
|
+
if (item.publicAccess !== undefined) {
|
|
2732
|
+
const publicAccess =
|
|
2733
|
+
typeof item.publicAccess === 'object'
|
|
2734
|
+
? item.publicAccess
|
|
2735
|
+
: { isPublic: Boolean(item.publicAccess) };
|
|
2736
|
+
await requestWithAuth(
|
|
2737
|
+
config,
|
|
2738
|
+
target.profileName,
|
|
2739
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/public-access`,
|
|
2740
|
+
{
|
|
2741
|
+
method: publicAccess.isPublic === false ? 'DELETE' : 'PUT',
|
|
2742
|
+
body: {
|
|
2743
|
+
isPublic: publicAccess.isPublic !== false,
|
|
2744
|
+
description: publicAccess.description || '',
|
|
2745
|
+
},
|
|
2746
|
+
}
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
saveResourceEntry(target, 'formSettings', code, { formUuid });
|
|
2750
|
+
result.published.push({ kind: 'formSetting', code, action: 'update', formUuid });
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
async function publishMenuResources(config, target, menus, result) {
|
|
2755
|
+
for (const menuItem of menus) {
|
|
2756
|
+
const existing = await findExistingMenu(config, target, menuItem.code);
|
|
2757
|
+
const formUuid = resolveManifestFormUuid(target.bound, menuItem);
|
|
2758
|
+
const pageId = resolveManifestPageId(target.bound, menuItem);
|
|
2759
|
+
const parentId =
|
|
2760
|
+
resolveManifestMenuId(target.bound, menuItem.parentCode) ||
|
|
2761
|
+
menuItem.parentId ||
|
|
2762
|
+
null;
|
|
2763
|
+
const body = {
|
|
2764
|
+
resourceCode: menuItem.code,
|
|
2765
|
+
name: menuItem.name || menuItem.code,
|
|
2766
|
+
type: menuItem.type || 'nav',
|
|
2767
|
+
formUuid,
|
|
2768
|
+
pageId,
|
|
2769
|
+
parentId,
|
|
2770
|
+
sortOrder: menuItem.sortOrder,
|
|
2771
|
+
icon: menuItem.icon || null,
|
|
2772
|
+
isHidden: menuItem.isHidden,
|
|
2773
|
+
};
|
|
2774
|
+
const data = existing
|
|
2775
|
+
? await requestWithAuth(
|
|
2776
|
+
config,
|
|
2777
|
+
target.profileName,
|
|
2778
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus/${encodeURIComponent(existing.id)}`,
|
|
2779
|
+
{ method: 'PUT', body }
|
|
2780
|
+
)
|
|
2781
|
+
: await requestWithAuth(
|
|
2782
|
+
config,
|
|
2783
|
+
target.profileName,
|
|
2784
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`,
|
|
2785
|
+
{ method: 'POST', body }
|
|
2786
|
+
);
|
|
2787
|
+
if (data?.id) {
|
|
2788
|
+
saveMenuResource(target, menuItem.code, data.id, {
|
|
2789
|
+
resourceCode: menuItem.code,
|
|
2790
|
+
name: data.name,
|
|
2791
|
+
type: data.type,
|
|
2792
|
+
...(formUuid ? { formUuid } : {}),
|
|
2793
|
+
...(pageId ? { pageId } : {}),
|
|
2794
|
+
...(menuItem.pageCode ? { pageCode: menuItem.pageCode } : {}),
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
result.published.push({ kind: 'menu', code: menuItem.code, action: existing ? 'update' : 'create', id: data?.id });
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
async function findExistingMenu(config, target, code) {
|
|
2802
|
+
const data = await requestWithAuth(
|
|
2803
|
+
config,
|
|
2804
|
+
target.profileName,
|
|
2805
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`
|
|
2806
|
+
);
|
|
2807
|
+
const items = flattenItems(normalizeItems(data));
|
|
2808
|
+
const stateId = target.bound.resources?.menus?.[code]?.menuId;
|
|
2809
|
+
if (stateId) {
|
|
2810
|
+
const byState = items.find(item => item.id === stateId);
|
|
2811
|
+
if (byState) return byState;
|
|
2812
|
+
}
|
|
2813
|
+
return items.find(item => item.resourceCode === code);
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
async function publishConnectorResources(config, target, connectors, result) {
|
|
2817
|
+
if (connectors.length === 0) return;
|
|
2818
|
+
const body = { connectors: connectors.map(normalizeConnectorManifest) };
|
|
2819
|
+
const data = await requestWithAuth(
|
|
2820
|
+
config,
|
|
2821
|
+
target.profileName,
|
|
2822
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/connectors/actions/sync`,
|
|
2823
|
+
{ method: 'POST', body }
|
|
2824
|
+
);
|
|
2825
|
+
for (const item of data.data || []) {
|
|
2826
|
+
const apiEntries = {};
|
|
2827
|
+
for (const api of item.apis || []) {
|
|
2828
|
+
const apiCode = api.code || api.methodName;
|
|
2829
|
+
if (apiCode) apiEntries[apiCode] = { apiId: api.id, name: api.name };
|
|
2830
|
+
}
|
|
2831
|
+
saveConnectorResource(target, item.code, item.connector?.id, {
|
|
2832
|
+
code: item.code,
|
|
2833
|
+
name: item.connector?.name,
|
|
2834
|
+
apis: apiEntries,
|
|
2835
|
+
});
|
|
2836
|
+
result.published.push({
|
|
2837
|
+
kind: 'connector',
|
|
2838
|
+
code: item.code,
|
|
2839
|
+
action: item.created ? 'create' : 'update',
|
|
2840
|
+
id: item.connector?.id,
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
async function publishWorkflowResources(config, target, workflows, result) {
|
|
2846
|
+
for (const workflowItem of workflows) {
|
|
2847
|
+
const existing = await findExistingWorkflow(config, target, workflowItem.code);
|
|
2848
|
+
const definitionJson = await resolveManifestJson(
|
|
2849
|
+
config,
|
|
2850
|
+
target.profileName,
|
|
2851
|
+
workflowItem,
|
|
2852
|
+
'definitionJson',
|
|
2853
|
+
'definitionFile'
|
|
2854
|
+
);
|
|
2855
|
+
const viewJson = await resolveManifestJson(
|
|
2856
|
+
config,
|
|
2857
|
+
target.profileName,
|
|
2858
|
+
workflowItem,
|
|
2859
|
+
'viewJson',
|
|
2860
|
+
'viewFile',
|
|
2861
|
+
true
|
|
2862
|
+
);
|
|
2863
|
+
const formUuid = resolveManifestFormUuid(target.bound, workflowItem);
|
|
2864
|
+
const body = {
|
|
2865
|
+
resourceCode: workflowItem.code,
|
|
2866
|
+
formUuid,
|
|
2867
|
+
definitionJson,
|
|
2868
|
+
...(viewJson !== undefined ? { viewJson } : {}),
|
|
2869
|
+
};
|
|
2870
|
+
const data = existing
|
|
2871
|
+
? await requestWithAuth(
|
|
2872
|
+
config,
|
|
2873
|
+
target.profileName,
|
|
2874
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(existing.id)}`,
|
|
2875
|
+
{ method: 'PUT', body }
|
|
2876
|
+
)
|
|
2877
|
+
: await requestWithAuth(
|
|
2878
|
+
config,
|
|
2879
|
+
target.profileName,
|
|
2880
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows`,
|
|
2881
|
+
{ method: 'POST', body }
|
|
2882
|
+
);
|
|
2883
|
+
if (workflowItem.publish && data?.id) {
|
|
2884
|
+
await requestWithAuth(
|
|
2885
|
+
config,
|
|
2886
|
+
target.profileName,
|
|
2887
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(data.id)}/publish`,
|
|
2888
|
+
{ method: 'POST', body: { isPublished: true } }
|
|
2889
|
+
);
|
|
2890
|
+
}
|
|
2891
|
+
if (data?.id) saveWorkflowResource(target, workflowItem.code, data.id, { formUuid, resourceCode: workflowItem.code });
|
|
2892
|
+
result.published.push({ kind: 'workflow', code: workflowItem.code, action: existing ? 'update' : 'create', id: data?.id });
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
async function findExistingWorkflow(config, target, code) {
|
|
2897
|
+
const stateId = target.bound.resources?.workflows?.[code]?.workflowId;
|
|
2898
|
+
if (stateId) {
|
|
2899
|
+
const stateItem = await requestOptionalWithAuth(
|
|
2900
|
+
config,
|
|
2901
|
+
target.profileName,
|
|
2902
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(stateId)}`
|
|
2903
|
+
);
|
|
2904
|
+
if (stateItem?.id) return stateItem;
|
|
2905
|
+
}
|
|
2906
|
+
const data = await requestWithAuth(
|
|
2907
|
+
config,
|
|
2908
|
+
target.profileName,
|
|
2909
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows`, {
|
|
2910
|
+
resourceCode: code,
|
|
2911
|
+
page: 1,
|
|
2912
|
+
pageSize: 1000,
|
|
2913
|
+
})
|
|
2914
|
+
);
|
|
2915
|
+
return normalizeItems(data).find(item => item.resourceCode === code);
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
async function publishAutomationResources(config, target, automations, result) {
|
|
2919
|
+
for (const automationItem of automations) {
|
|
2920
|
+
const existing = await findExistingAutomation(config, target, automationItem.code);
|
|
2921
|
+
const definitionJson = await resolveManifestJson(
|
|
2922
|
+
config,
|
|
2923
|
+
target.profileName,
|
|
2924
|
+
automationItem,
|
|
2925
|
+
'definitionJson',
|
|
2926
|
+
'definitionFile'
|
|
2927
|
+
);
|
|
2928
|
+
const viewJson = await resolveManifestJson(
|
|
2929
|
+
config,
|
|
2930
|
+
target.profileName,
|
|
2931
|
+
automationItem,
|
|
2932
|
+
'viewJson',
|
|
2933
|
+
'viewFile',
|
|
2934
|
+
true
|
|
2935
|
+
);
|
|
2936
|
+
const automationPayload = resolveAutomationManifestPayload(target, automationItem);
|
|
2937
|
+
const body = {
|
|
2938
|
+
resourceCode: automationItem.code,
|
|
2939
|
+
name: automationItem.name || automationItem.code,
|
|
2940
|
+
description: automationItem.description || '',
|
|
2941
|
+
formUuid: automationPayload.formUuid,
|
|
2942
|
+
triggerConfig: automationPayload.triggerConfig,
|
|
2943
|
+
definitionJson,
|
|
2944
|
+
...(viewJson !== undefined ? { viewJson } : {}),
|
|
2945
|
+
...(automationItem.tags !== undefined ? { tags: Array.isArray(automationItem.tags) ? automationItem.tags.join(',') : automationItem.tags } : {}),
|
|
2946
|
+
...(automationItem.isEnabled !== undefined ? { isEnabled: Boolean(automationItem.isEnabled) } : {}),
|
|
2947
|
+
};
|
|
2948
|
+
const data = existing
|
|
2949
|
+
? await requestWithAuth(
|
|
2950
|
+
config,
|
|
2951
|
+
target.profileName,
|
|
2952
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(existing.id)}`,
|
|
2953
|
+
{ method: 'PUT', body }
|
|
2954
|
+
)
|
|
2955
|
+
: await requestWithAuth(
|
|
2956
|
+
config,
|
|
2957
|
+
target.profileName,
|
|
2958
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations`,
|
|
2959
|
+
{ method: 'POST', body }
|
|
2960
|
+
);
|
|
2961
|
+
if (automationItem.publish && data?.id) {
|
|
2962
|
+
await requestWithAuth(
|
|
2963
|
+
config,
|
|
2964
|
+
target.profileName,
|
|
2965
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(data.id)}/publish`,
|
|
2966
|
+
{ method: 'POST' }
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
if (automationItem.enable !== undefined && data?.id) {
|
|
2970
|
+
await requestWithAuth(
|
|
2971
|
+
config,
|
|
2972
|
+
target.profileName,
|
|
2973
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(data.id)}/${automationItem.enable ? 'enable' : 'disable'}`,
|
|
2974
|
+
{ method: 'POST' }
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
if (data?.id) saveAutomationResource(target, automationItem.code, data.id, { formUuid: automationPayload.formUuid, resourceCode: automationItem.code });
|
|
2978
|
+
result.published.push({ kind: 'automation', code: automationItem.code, action: existing ? 'update' : 'create', id: data?.id });
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
async function findExistingAutomation(config, target, code) {
|
|
2983
|
+
const stateId = target.bound.resources?.automations?.[code]?.automationId;
|
|
2984
|
+
if (stateId) {
|
|
2985
|
+
const stateItem = await requestOptionalWithAuth(
|
|
2986
|
+
config,
|
|
2987
|
+
target.profileName,
|
|
2988
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(stateId)}`
|
|
2989
|
+
);
|
|
2990
|
+
if (stateItem?.id) return stateItem;
|
|
2991
|
+
}
|
|
2992
|
+
const data = await requestWithAuth(
|
|
2993
|
+
config,
|
|
2994
|
+
target.profileName,
|
|
2995
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations`, {
|
|
2996
|
+
resourceCode: code,
|
|
2997
|
+
page: 1,
|
|
2998
|
+
pageSize: 1000,
|
|
2999
|
+
})
|
|
3000
|
+
);
|
|
3001
|
+
return normalizeItems(data).find(item => item.resourceCode === code);
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
async function publishPagePermissionGroupResources(config, target, groups, result) {
|
|
3005
|
+
for (const group of groups) {
|
|
3006
|
+
const existing = await findExistingPagePermissionGroup(config, target, group.code);
|
|
3007
|
+
const body = {
|
|
3008
|
+
resourceCode: group.code,
|
|
3009
|
+
name: group.name || group.code,
|
|
3010
|
+
roles: group.roles || [],
|
|
3011
|
+
menuFormUuids: resolvePagePermissionGroupTargets(target.bound, group),
|
|
3012
|
+
};
|
|
3013
|
+
const data = existing
|
|
3014
|
+
? await requestWithAuth(
|
|
3015
|
+
config,
|
|
3016
|
+
target.profileName,
|
|
3017
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups/${encodeURIComponent(existing.id)}`,
|
|
3018
|
+
{ method: 'PUT', body }
|
|
3019
|
+
)
|
|
3020
|
+
: await requestWithAuth(
|
|
3021
|
+
config,
|
|
3022
|
+
target.profileName,
|
|
3023
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups`,
|
|
3024
|
+
{ method: 'POST', body }
|
|
3025
|
+
);
|
|
3026
|
+
if (data?.id) savePagePermissionGroupResource(target, group.code, data.id, { name: data.name, resourceCode: group.code });
|
|
3027
|
+
result.published.push({ kind: 'pagePermissionGroup', code: group.code, action: existing ? 'update' : 'create', id: data?.id });
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
async function findExistingPagePermissionGroup(config, target, code) {
|
|
3032
|
+
const stateId = target.bound.resources?.pagePermissionGroups?.[code]?.groupId;
|
|
3033
|
+
if (stateId) {
|
|
3034
|
+
const stateItem = await requestOptionalWithAuth(
|
|
3035
|
+
config,
|
|
3036
|
+
target.profileName,
|
|
3037
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups/${encodeURIComponent(stateId)}`
|
|
3038
|
+
);
|
|
3039
|
+
if (stateItem?.id) return stateItem;
|
|
3040
|
+
}
|
|
3041
|
+
const data = await requestWithAuth(
|
|
3042
|
+
config,
|
|
3043
|
+
target.profileName,
|
|
3044
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups`, {
|
|
3045
|
+
resourceCode: code,
|
|
3046
|
+
page: 1,
|
|
3047
|
+
limit: 1000,
|
|
3048
|
+
})
|
|
3049
|
+
);
|
|
3050
|
+
return normalizeItems(data).find(item => item.resourceCode === code);
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
async function publishFormPermissionGroupResources(config, target, groups, result) {
|
|
3054
|
+
for (const group of groups) {
|
|
3055
|
+
const formUuid = resolveManifestFormUuid(target.bound, group);
|
|
3056
|
+
const existing = await findExistingFormPermissionGroup(config, target, group.code, formUuid);
|
|
3057
|
+
const body = {
|
|
3058
|
+
...withoutResourceMeta(group),
|
|
3059
|
+
resourceCode: group.code,
|
|
3060
|
+
formUuid,
|
|
3061
|
+
name: group.name || group.code,
|
|
3062
|
+
type: group.type || 'view',
|
|
3063
|
+
roles: group.roles || [],
|
|
3064
|
+
};
|
|
3065
|
+
delete body.code;
|
|
3066
|
+
delete body.formCode;
|
|
3067
|
+
delete body.form;
|
|
3068
|
+
const data = existing
|
|
3069
|
+
? await requestWithAuth(
|
|
3070
|
+
config,
|
|
3071
|
+
target.profileName,
|
|
3072
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups/${encodeURIComponent(existing.id)}`,
|
|
3073
|
+
{ method: 'PUT', body }
|
|
3074
|
+
)
|
|
3075
|
+
: await requestWithAuth(
|
|
3076
|
+
config,
|
|
3077
|
+
target.profileName,
|
|
3078
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups`,
|
|
3079
|
+
{ method: 'POST', body }
|
|
3080
|
+
);
|
|
3081
|
+
if (data?.id) saveFormPermissionGroupResource(target, group.code, data.id, { formUuid, name: data.name, resourceCode: group.code });
|
|
3082
|
+
result.published.push({ kind: 'formPermissionGroup', code: group.code, action: existing ? 'update' : 'create', id: data?.id });
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
async function findExistingFormPermissionGroup(config, target, code, formUuid) {
|
|
3087
|
+
const stateId = target.bound.resources?.formPermissionGroups?.[code]?.groupId;
|
|
3088
|
+
if (stateId) {
|
|
3089
|
+
const stateItem = await requestOptionalWithAuth(
|
|
3090
|
+
config,
|
|
3091
|
+
target.profileName,
|
|
3092
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups/${encodeURIComponent(stateId)}`
|
|
3093
|
+
);
|
|
3094
|
+
if (stateItem?.id) return stateItem;
|
|
3095
|
+
}
|
|
3096
|
+
const data = await requestWithAuth(
|
|
3097
|
+
config,
|
|
3098
|
+
target.profileName,
|
|
3099
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups`, {
|
|
3100
|
+
resourceCode: code,
|
|
3101
|
+
page: 1,
|
|
3102
|
+
limit: 1000,
|
|
3103
|
+
})
|
|
3104
|
+
);
|
|
3105
|
+
return normalizeItems(data).find(item => item.resourceCode === code);
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
async function pruneResourceManifest(config, target, manifest, result) {
|
|
3109
|
+
await pruneRoles(config, target, desiredCodes(manifest.roles), result);
|
|
3110
|
+
await pruneConnectors(config, target, desiredCodes(manifest.connectors), result);
|
|
3111
|
+
await pruneMenus(config, target, desiredCodes(manifest.menus), result);
|
|
3112
|
+
await pruneWorkflows(config, target, desiredCodes(manifest.workflows), result);
|
|
3113
|
+
await pruneAutomations(config, target, desiredCodes(manifest.automations), result);
|
|
3114
|
+
await prunePagePermissionGroups(
|
|
3115
|
+
config,
|
|
3116
|
+
target,
|
|
3117
|
+
desiredCodes(manifest.pagePermissionGroups),
|
|
3118
|
+
result
|
|
3119
|
+
);
|
|
3120
|
+
await pruneFormPermissionGroups(
|
|
3121
|
+
config,
|
|
3122
|
+
target,
|
|
3123
|
+
desiredCodes(manifest.formPermissionGroups),
|
|
3124
|
+
result
|
|
3125
|
+
);
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
function desiredCodes(items = []) {
|
|
3129
|
+
return new Set(
|
|
3130
|
+
items
|
|
3131
|
+
.map(item => item.code || item.resourceCode || item.methodName)
|
|
3132
|
+
.filter(Boolean)
|
|
3133
|
+
.map(String)
|
|
3134
|
+
);
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
async function pruneRoles(config, target, desired, result) {
|
|
3138
|
+
const data = await requestWithAuth(
|
|
3139
|
+
config,
|
|
3140
|
+
target.profileName,
|
|
3141
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`, {
|
|
3142
|
+
page: 1,
|
|
3143
|
+
limit: 1000,
|
|
3144
|
+
})
|
|
3145
|
+
);
|
|
3146
|
+
for (const role of normalizeItems(data)) {
|
|
3147
|
+
if (!role.code || desired.has(role.code) || role.isAppAdmin) continue;
|
|
3148
|
+
await pruneOne(config, target, result, 'role', role.code, role.id, async () => {
|
|
3149
|
+
await requestWithAuth(
|
|
3150
|
+
config,
|
|
3151
|
+
target.profileName,
|
|
3152
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles/${encodeURIComponent(role.id)}`,
|
|
3153
|
+
{ method: 'DELETE' }
|
|
3154
|
+
);
|
|
3155
|
+
});
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
async function pruneConnectors(config, target, desired, result) {
|
|
3160
|
+
const data = await requestWithAuth(
|
|
3161
|
+
config,
|
|
3162
|
+
target.profileName,
|
|
3163
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/connectors`
|
|
3164
|
+
);
|
|
3165
|
+
for (const connector of normalizeItems(data)) {
|
|
3166
|
+
const code = connector.code || connector.methodName;
|
|
3167
|
+
if (!code || desired.has(code)) continue;
|
|
3168
|
+
await pruneOne(config, target, result, 'connector', code, connector.id, async () => {
|
|
3169
|
+
await requestWithAuth(
|
|
3170
|
+
config,
|
|
3171
|
+
target.profileName,
|
|
3172
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/connectors/${encodeURIComponent(code)}`,
|
|
3173
|
+
{ method: 'DELETE' }
|
|
3174
|
+
);
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
async function pruneMenus(config, target, desired, result) {
|
|
3180
|
+
const data = await requestWithAuth(
|
|
3181
|
+
config,
|
|
3182
|
+
target.profileName,
|
|
3183
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`
|
|
3184
|
+
);
|
|
3185
|
+
const items = flattenItems(normalizeItems(data)).reverse();
|
|
3186
|
+
for (const menu of items) {
|
|
3187
|
+
const code = menu.resourceCode;
|
|
3188
|
+
if (!code || desired.has(code)) continue;
|
|
3189
|
+
await pruneOne(config, target, result, 'menu', code, menu.id, async () => {
|
|
3190
|
+
await requestWithAuth(
|
|
3191
|
+
config,
|
|
3192
|
+
target.profileName,
|
|
3193
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus/${encodeURIComponent(menu.id)}`,
|
|
3194
|
+
{ method: 'DELETE' }
|
|
3195
|
+
);
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
async function pruneWorkflows(config, target, desired, result) {
|
|
3201
|
+
const data = await requestWithAuth(
|
|
3202
|
+
config,
|
|
3203
|
+
target.profileName,
|
|
3204
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows`, {
|
|
3205
|
+
page: 1,
|
|
3206
|
+
pageSize: 1000,
|
|
3207
|
+
})
|
|
3208
|
+
);
|
|
3209
|
+
for (const workflow of normalizeItems(data)) {
|
|
3210
|
+
const code = workflow.resourceCode;
|
|
3211
|
+
if (!code || desired.has(code)) continue;
|
|
3212
|
+
await pruneOne(config, target, result, 'workflow', code, workflow.id, async () => {
|
|
3213
|
+
if (workflow.isPublished) {
|
|
3214
|
+
await requestWithAuth(
|
|
3215
|
+
config,
|
|
3216
|
+
target.profileName,
|
|
3217
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(workflow.id)}/publish`,
|
|
3218
|
+
{ method: 'POST', body: { isPublished: false } }
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
await requestWithAuth(
|
|
3222
|
+
config,
|
|
3223
|
+
target.profileName,
|
|
3224
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(workflow.id)}`,
|
|
3225
|
+
{ method: 'DELETE' }
|
|
3226
|
+
);
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
async function pruneAutomations(config, target, desired, result) {
|
|
3232
|
+
const data = await requestWithAuth(
|
|
3233
|
+
config,
|
|
3234
|
+
target.profileName,
|
|
3235
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations`, {
|
|
3236
|
+
page: 1,
|
|
3237
|
+
pageSize: 1000,
|
|
3238
|
+
})
|
|
3239
|
+
);
|
|
3240
|
+
for (const automation of normalizeItems(data)) {
|
|
3241
|
+
const code = automation.resourceCode;
|
|
3242
|
+
if (!code || desired.has(code)) continue;
|
|
3243
|
+
await pruneOne(config, target, result, 'automation', code, automation.id, async () => {
|
|
3244
|
+
if (automation.isPublished) {
|
|
3245
|
+
await requestWithAuth(
|
|
3246
|
+
config,
|
|
3247
|
+
target.profileName,
|
|
3248
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(automation.id)}/unpublish`,
|
|
3249
|
+
{ method: 'POST' }
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
await requestWithAuth(
|
|
3253
|
+
config,
|
|
3254
|
+
target.profileName,
|
|
3255
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(automation.id)}`,
|
|
3256
|
+
{ method: 'DELETE' }
|
|
3257
|
+
);
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
async function prunePagePermissionGroups(config, target, desired, result) {
|
|
3263
|
+
const data = await requestWithAuth(
|
|
3264
|
+
config,
|
|
3265
|
+
target.profileName,
|
|
3266
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups`, {
|
|
3267
|
+
page: 1,
|
|
3268
|
+
limit: 1000,
|
|
3269
|
+
})
|
|
3270
|
+
);
|
|
3271
|
+
for (const group of normalizeItems(data)) {
|
|
3272
|
+
const code = group.resourceCode;
|
|
3273
|
+
if (!code || desired.has(code)) continue;
|
|
3274
|
+
await pruneOne(config, target, result, 'pagePermissionGroup', code, group.id, async () => {
|
|
3275
|
+
await requestWithAuth(
|
|
3276
|
+
config,
|
|
3277
|
+
target.profileName,
|
|
3278
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups/${encodeURIComponent(group.id)}`,
|
|
3279
|
+
{ method: 'DELETE' }
|
|
3280
|
+
);
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
async function pruneFormPermissionGroups(config, target, desired, result) {
|
|
3286
|
+
const forms = await requestWithAuth(
|
|
3287
|
+
config,
|
|
3288
|
+
target.profileName,
|
|
3289
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms`
|
|
3290
|
+
);
|
|
3291
|
+
for (const form of normalizeItems(forms)) {
|
|
3292
|
+
const formUuid = form.formUuid;
|
|
3293
|
+
if (!formUuid) continue;
|
|
3294
|
+
const data = await requestWithAuth(
|
|
3295
|
+
config,
|
|
3296
|
+
target.profileName,
|
|
3297
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups`, {
|
|
3298
|
+
page: 1,
|
|
3299
|
+
limit: 1000,
|
|
3300
|
+
})
|
|
3301
|
+
);
|
|
3302
|
+
for (const group of normalizeItems(data)) {
|
|
3303
|
+
const code = group.resourceCode;
|
|
3304
|
+
if (!code || desired.has(code)) continue;
|
|
3305
|
+
await pruneOne(config, target, result, 'formPermissionGroup', code, group.id, async () => {
|
|
3306
|
+
await requestWithAuth(
|
|
3307
|
+
config,
|
|
3308
|
+
target.profileName,
|
|
3309
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups/${encodeURIComponent(group.id)}`,
|
|
3310
|
+
{ method: 'DELETE' }
|
|
3311
|
+
);
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
async function pruneOne(config, target, result, kind, code, id, deleter) {
|
|
3318
|
+
try {
|
|
3319
|
+
await deleter();
|
|
3320
|
+
removeStateResource(target, kind, code);
|
|
3321
|
+
result.pruned.push({ kind, code, id });
|
|
3322
|
+
} catch (error) {
|
|
3323
|
+
result.warnings.push(
|
|
3324
|
+
`prune ${kind}:${code} failed: ${error?.message || String(error)}`
|
|
3325
|
+
);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
function removeStateResource(target, kind, code) {
|
|
3330
|
+
const bucketByKind = {
|
|
3331
|
+
role: 'roles',
|
|
3332
|
+
connector: 'connectors',
|
|
3333
|
+
menu: 'menus',
|
|
3334
|
+
workflow: 'workflows',
|
|
3335
|
+
automation: 'automations',
|
|
3336
|
+
pagePermissionGroup: 'pagePermissionGroups',
|
|
3337
|
+
formPermissionGroup: 'formPermissionGroups',
|
|
3338
|
+
};
|
|
3339
|
+
const bucket = bucketByKind[kind];
|
|
3340
|
+
if (!bucket || !target.bound.resources?.[bucket]?.[code]) return;
|
|
3341
|
+
delete target.bound.resources[bucket][code];
|
|
3342
|
+
target.bound.updatedAt = new Date().toISOString();
|
|
3343
|
+
if (target.state.profiles?.[target.profileName]) {
|
|
3344
|
+
target.state.profiles[target.profileName] = target.bound;
|
|
3345
|
+
}
|
|
3346
|
+
saveProjectState(target.state);
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
async function pullResources(config, target) {
|
|
3350
|
+
const baseDir = path.join(process.cwd(), 'src', 'resources');
|
|
3351
|
+
const written = [];
|
|
3352
|
+
const pullLookups = buildPullResourceLookups(target.bound);
|
|
3353
|
+
const connectors = await requestWithAuth(
|
|
3354
|
+
config,
|
|
3355
|
+
target.profileName,
|
|
3356
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/connectors`
|
|
3357
|
+
);
|
|
3358
|
+
for (const connector of normalizeItems(connectors)) {
|
|
3359
|
+
const filePath = path.join(baseDir, 'connectors', `${connector.code || connector.methodName}.json`);
|
|
3360
|
+
writeResourceJsonFile(filePath, stripPulledResource(connector));
|
|
3361
|
+
written.push(path.relative(process.cwd(), filePath));
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
const roles = await requestWithAuth(
|
|
3365
|
+
config,
|
|
3366
|
+
target.profileName,
|
|
3367
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`, {
|
|
3368
|
+
page: 1,
|
|
3369
|
+
limit: 1000,
|
|
3370
|
+
})
|
|
3371
|
+
);
|
|
3372
|
+
for (const role of normalizeItems(roles)) {
|
|
3373
|
+
const filePath = path.join(baseDir, 'roles', `${role.code}.json`);
|
|
3374
|
+
writeResourceJsonFile(filePath, {
|
|
3375
|
+
code: role.code,
|
|
3376
|
+
name: role.name,
|
|
3377
|
+
description: role.description || '',
|
|
3378
|
+
});
|
|
3379
|
+
written.push(path.relative(process.cwd(), filePath));
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
const menus = await requestWithAuth(
|
|
3383
|
+
config,
|
|
3384
|
+
target.profileName,
|
|
3385
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`
|
|
3386
|
+
);
|
|
3387
|
+
const flatMenus = flattenItems(normalizeItems(menus));
|
|
3388
|
+
for (const menu of flatMenus) {
|
|
3389
|
+
if (menu.id && menu.resourceCode) pullLookups.menuCodeById.set(menu.id, menu.resourceCode);
|
|
3390
|
+
}
|
|
3391
|
+
for (const menu of flatMenus) {
|
|
3392
|
+
const code = menu.resourceCode || menu.id;
|
|
3393
|
+
const filePath = path.join(baseDir, 'menus', `${code}.json`);
|
|
3394
|
+
const formCode = pullLookups.formCodeByUuid.get(menu.formUuid);
|
|
3395
|
+
const pageCode = pullLookups.pageCodeById.get(menu.pageId);
|
|
3396
|
+
const parentCode = pullLookups.menuCodeById.get(menu.parentId);
|
|
3397
|
+
writeResourceJsonFile(filePath, {
|
|
3398
|
+
code,
|
|
3399
|
+
name: menu.name,
|
|
3400
|
+
type: menu.type,
|
|
3401
|
+
...(formCode ? { formCode } : menu.formUuid ? { formUuid: menu.formUuid } : {}),
|
|
3402
|
+
...(pageCode ? { pageCode } : menu.pageId ? { pageId: menu.pageId } : {}),
|
|
3403
|
+
...(parentCode ? { parentCode } : menu.parentId ? { parentId: menu.parentId } : {}),
|
|
3404
|
+
sortOrder: menu.sortOrder,
|
|
3405
|
+
icon: menu.icon || undefined,
|
|
3406
|
+
isHidden: menu.isHidden,
|
|
3407
|
+
});
|
|
3408
|
+
written.push(path.relative(process.cwd(), filePath));
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
const workflows = await requestWithAuth(
|
|
3412
|
+
config,
|
|
3413
|
+
target.profileName,
|
|
3414
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows`, {
|
|
3415
|
+
page: 1,
|
|
3416
|
+
pageSize: 1000,
|
|
3417
|
+
})
|
|
3418
|
+
);
|
|
3419
|
+
for (const workflow of normalizeItems(workflows)) {
|
|
3420
|
+
const detail = await requestWithAuth(
|
|
3421
|
+
config,
|
|
3422
|
+
target.profileName,
|
|
3423
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(workflow.id)}`
|
|
3424
|
+
);
|
|
3425
|
+
const code = detail.resourceCode || workflow.resourceCode || workflow.id;
|
|
3426
|
+
const filePath = path.join(baseDir, 'workflows', `${code}.json`);
|
|
3427
|
+
const formCode = pullLookups.formCodeByUuid.get(detail.formUuid);
|
|
3428
|
+
writeResourceJsonFile(filePath, {
|
|
3429
|
+
code,
|
|
3430
|
+
...(formCode ? { formCode } : detail.formUuid ? { formUuid: detail.formUuid } : {}),
|
|
3431
|
+
definitionJson: detail.definitionJson,
|
|
3432
|
+
viewJson: detail.viewJson || undefined,
|
|
3433
|
+
publish: Boolean(detail.isPublished),
|
|
3434
|
+
});
|
|
3435
|
+
written.push(path.relative(process.cwd(), filePath));
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
const automations = await requestWithAuth(
|
|
3439
|
+
config,
|
|
3440
|
+
target.profileName,
|
|
3441
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations`, {
|
|
3442
|
+
page: 1,
|
|
3443
|
+
pageSize: 1000,
|
|
3444
|
+
})
|
|
3445
|
+
);
|
|
3446
|
+
for (const automation of normalizeItems(automations)) {
|
|
3447
|
+
const detail = await requestWithAuth(
|
|
3448
|
+
config,
|
|
3449
|
+
target.profileName,
|
|
3450
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(automation.id)}`
|
|
3451
|
+
);
|
|
3452
|
+
const code = detail.resourceCode || automation.resourceCode || automation.id;
|
|
3453
|
+
const filePath = path.join(baseDir, 'automations', `${code}.json`);
|
|
3454
|
+
const formCode = pullLookups.formCodeByUuid.get(detail.formUuid);
|
|
3455
|
+
writeResourceJsonFile(filePath, {
|
|
3456
|
+
code,
|
|
3457
|
+
name: detail.name,
|
|
3458
|
+
description: detail.description || '',
|
|
3459
|
+
...(formCode ? { formCode } : detail.formUuid ? { formUuid: detail.formUuid } : {}),
|
|
3460
|
+
triggerConfig: detail.triggerConfig,
|
|
3461
|
+
definitionJson: detail.definitionJson,
|
|
3462
|
+
viewJson: detail.viewJson || undefined,
|
|
3463
|
+
tags: detail.tags || undefined,
|
|
3464
|
+
publish: Boolean(detail.isPublished),
|
|
3465
|
+
enable: Boolean(detail.isEnabled),
|
|
3466
|
+
});
|
|
3467
|
+
written.push(path.relative(process.cwd(), filePath));
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
const pageGroups = await requestWithAuth(
|
|
3471
|
+
config,
|
|
3472
|
+
target.profileName,
|
|
3473
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups`, {
|
|
3474
|
+
page: 1,
|
|
3475
|
+
limit: 1000,
|
|
3476
|
+
})
|
|
3477
|
+
);
|
|
3478
|
+
for (const group of normalizeItems(pageGroups)) {
|
|
3479
|
+
const code = group.resourceCode || group.id;
|
|
3480
|
+
const filePath = path.join(baseDir, 'permissions', 'page-groups', `${code}.json`);
|
|
3481
|
+
const targets = splitPagePermissionTargetsForManifest(group.menuFormUuids || [], pullLookups);
|
|
3482
|
+
writeResourceJsonFile(filePath, {
|
|
3483
|
+
code,
|
|
3484
|
+
name: group.name,
|
|
3485
|
+
roles: group.roles || [],
|
|
3486
|
+
...targets,
|
|
3487
|
+
});
|
|
3488
|
+
written.push(path.relative(process.cwd(), filePath));
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
const forms = await requestWithAuth(
|
|
3492
|
+
config,
|
|
3493
|
+
target.profileName,
|
|
3494
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms`
|
|
3495
|
+
);
|
|
3496
|
+
const stateFormCodesByUuid = new Map(
|
|
3497
|
+
Object.entries(target.bound.resources?.forms || {})
|
|
3498
|
+
.filter(([, entry]) => entry?.formUuid)
|
|
3499
|
+
.map(([code, entry]) => [entry.formUuid, code])
|
|
3500
|
+
);
|
|
3501
|
+
const formEntries = normalizeItems(forms).map(form => [
|
|
3502
|
+
stateFormCodesByUuid.get(form.formUuid) || form.formUuid,
|
|
3503
|
+
{ formUuid: form.formUuid },
|
|
3504
|
+
]);
|
|
3505
|
+
for (const [formCode, formEntry] of formEntries) {
|
|
3506
|
+
const formUuid = formEntry?.formUuid;
|
|
3507
|
+
if (!formUuid) continue;
|
|
3508
|
+
const formGroups = await requestWithAuth(
|
|
3509
|
+
config,
|
|
3510
|
+
target.profileName,
|
|
3511
|
+
apiPathWithQuery(`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups`, {
|
|
3512
|
+
page: 1,
|
|
3513
|
+
limit: 1000,
|
|
3514
|
+
})
|
|
3515
|
+
);
|
|
3516
|
+
for (const group of normalizeItems(formGroups)) {
|
|
3517
|
+
const code = group.resourceCode || group.id;
|
|
3518
|
+
const filePath = path.join(baseDir, 'permissions', 'form-groups', `${code}.json`);
|
|
3519
|
+
const mappedFormCode = pullLookups.formCodeByUuid.get(formUuid);
|
|
3520
|
+
const pulledGroup = stripPulledResource(group);
|
|
3521
|
+
if (mappedFormCode) delete pulledGroup.formUuid;
|
|
3522
|
+
writeResourceJsonFile(filePath, {
|
|
3523
|
+
...pulledGroup,
|
|
3524
|
+
code,
|
|
3525
|
+
...(mappedFormCode ? { formCode: mappedFormCode } : { formUuid }),
|
|
3526
|
+
});
|
|
3527
|
+
written.push(path.relative(process.cwd(), filePath));
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
const mappedFormCode = pullLookups.formCodeByUuid.get(formUuid);
|
|
3531
|
+
const formSetting = {
|
|
3532
|
+
code: mappedFormCode || formCode,
|
|
3533
|
+
...(mappedFormCode ? { formCode: mappedFormCode } : { formUuid }),
|
|
3534
|
+
};
|
|
3535
|
+
formSetting.settings = await requestWithAuth(
|
|
3536
|
+
config,
|
|
3537
|
+
target.profileName,
|
|
3538
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/settings`
|
|
3539
|
+
);
|
|
3540
|
+
formSetting.indexes = await requestWithAuth(
|
|
3541
|
+
config,
|
|
3542
|
+
target.profileName,
|
|
3543
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/field-indexes`
|
|
3544
|
+
);
|
|
3545
|
+
formSetting.dataManagement = await requestWithAuth(
|
|
3546
|
+
config,
|
|
3547
|
+
target.profileName,
|
|
3548
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/data-management`
|
|
3549
|
+
);
|
|
3550
|
+
formSetting.publicAccess = await requestWithAuth(
|
|
3551
|
+
config,
|
|
3552
|
+
target.profileName,
|
|
3553
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/public-access`
|
|
3554
|
+
);
|
|
3555
|
+
const filePath = path.join(baseDir, 'settings', 'forms', `${formCode}.json`);
|
|
3556
|
+
writeResourceJsonFile(filePath, formSetting);
|
|
3557
|
+
written.push(path.relative(process.cwd(), filePath));
|
|
3558
|
+
}
|
|
3559
|
+
return { appType: target.appType, profile: target.profileName, written };
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
function buildPullResourceLookups(bound = {}) {
|
|
3563
|
+
const formCodeByUuid = new Map();
|
|
3564
|
+
for (const [code, entry] of Object.entries(bound.resources?.forms || {})) {
|
|
3565
|
+
if (entry?.formUuid) formCodeByUuid.set(entry.formUuid, code);
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
const pageCodeById = new Map();
|
|
3569
|
+
for (const [code, entry] of Object.entries(bound.resources?.pages || {})) {
|
|
3570
|
+
if (entry?.pageId) pageCodeById.set(entry.pageId, code);
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
const menuCodeById = new Map();
|
|
3574
|
+
for (const [code, entry] of Object.entries(bound.resources?.menus || {})) {
|
|
3575
|
+
if (entry?.menuId) menuCodeById.set(entry.menuId, code);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
return { formCodeByUuid, pageCodeById, menuCodeById };
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
function splitPagePermissionTargetsForManifest(values = [], lookups) {
|
|
3582
|
+
const formCodes = [];
|
|
3583
|
+
const menuCodes = [];
|
|
3584
|
+
const menuFormUuids = [];
|
|
3585
|
+
for (const value of values || []) {
|
|
3586
|
+
if (lookups.formCodeByUuid.has(value)) {
|
|
3587
|
+
formCodes.push(lookups.formCodeByUuid.get(value));
|
|
3588
|
+
} else if (lookups.menuCodeById.has(value)) {
|
|
3589
|
+
menuCodes.push(lookups.menuCodeById.get(value));
|
|
3590
|
+
} else if (value) {
|
|
3591
|
+
menuFormUuids.push(value);
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
return {
|
|
3595
|
+
...(formCodes.length > 0 ? { formCodes: unique(formCodes) } : {}),
|
|
3596
|
+
...(menuCodes.length > 0 ? { menuCodes: unique(menuCodes) } : {}),
|
|
3597
|
+
...(menuFormUuids.length > 0 ? { menuFormUuids: unique(menuFormUuids) } : {}),
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
function writeResourceJsonFile(filePath, value) {
|
|
3602
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
3603
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
function stripPulledResource(value) {
|
|
3607
|
+
if (!value || typeof value !== 'object') return value;
|
|
3608
|
+
const next = { ...value };
|
|
3609
|
+
delete next.id;
|
|
3610
|
+
delete next.appType;
|
|
3611
|
+
delete next.createdAt;
|
|
3612
|
+
delete next.updatedAt;
|
|
3613
|
+
if (Array.isArray(next.apis)) {
|
|
3614
|
+
next.apis = next.apis.map(api => {
|
|
3615
|
+
const item = { ...api };
|
|
3616
|
+
delete item.id;
|
|
3617
|
+
delete item.connectorId;
|
|
3618
|
+
delete item.createdAt;
|
|
3619
|
+
delete item.updatedAt;
|
|
3620
|
+
return item;
|
|
3621
|
+
});
|
|
3622
|
+
}
|
|
3623
|
+
return next;
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
function normalizeConnectorManifest(connector) {
|
|
3627
|
+
const parsedUrl = parseConnectorUrl(connector);
|
|
3628
|
+
return {
|
|
3629
|
+
code: connector.code || connector.methodName,
|
|
3630
|
+
name: connector.name || connector.code || connector.methodName,
|
|
3631
|
+
description: connector.description || '',
|
|
3632
|
+
protocol: connector.protocol || parsedUrl.protocol || 'https',
|
|
3633
|
+
domain: connector.domain || parsedUrl.domain,
|
|
3634
|
+
baseUrl: connector.baseUrl !== undefined ? connector.baseUrl : parsedUrl.baseUrl,
|
|
3635
|
+
authType: connector.authType || 'none',
|
|
3636
|
+
authConfig: connector.authConfig,
|
|
3637
|
+
defaultHeaders: connector.defaultHeaders || connector.headers,
|
|
3638
|
+
userContext: connector.userContext,
|
|
3639
|
+
sensitiveLog: connector.sensitiveLog,
|
|
3640
|
+
apis: (connector.apis || []).map(api => ({
|
|
3641
|
+
code: api.code || api.methodName,
|
|
3642
|
+
name: api.name || api.code || api.methodName,
|
|
3643
|
+
description: api.description || '',
|
|
3644
|
+
path: api.path,
|
|
3645
|
+
method: String(api.method || 'GET').toUpperCase(),
|
|
3646
|
+
timeout: api.timeout,
|
|
3647
|
+
requestBodyType: api.requestBodyType,
|
|
3648
|
+
responseType: api.responseType,
|
|
3649
|
+
pathParams: api.pathParams || [],
|
|
3650
|
+
queryParams: api.queryParams || [],
|
|
3651
|
+
headerParams: api.headerParams || [],
|
|
3652
|
+
bodyParams: api.bodyParams || [],
|
|
3653
|
+
responseDefinition: api.responseDefinition,
|
|
3654
|
+
})),
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
function parseConnectorUrl(connector) {
|
|
3659
|
+
const rawUrl = connector.url;
|
|
3660
|
+
if (!rawUrl) return {};
|
|
3661
|
+
try {
|
|
3662
|
+
const url = new URL(rawUrl);
|
|
3663
|
+
const baseUrl = url.pathname && url.pathname !== '/' ? url.pathname.replace(/^\/+|\/+$/g, '') : '';
|
|
3664
|
+
return {
|
|
3665
|
+
protocol: url.protocol.replace(':', ''),
|
|
3666
|
+
domain: url.host,
|
|
3667
|
+
baseUrl,
|
|
3668
|
+
};
|
|
3669
|
+
} catch {
|
|
3670
|
+
return {};
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
async function resolveManifestJson(config, profileName, item, objectKey, fileKey, optional = false) {
|
|
3675
|
+
let value = item[objectKey];
|
|
3676
|
+
if (value === undefined && item[fileKey]) {
|
|
3677
|
+
const filePath = path.resolve(item.__dir || process.cwd(), item[fileKey]);
|
|
3678
|
+
value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
3679
|
+
}
|
|
3680
|
+
if (value === undefined) {
|
|
3681
|
+
if (optional) return undefined;
|
|
3682
|
+
fail(`${resourceLabel('resource', item)} 缺少 ${objectKey} 或 ${fileKey}`);
|
|
3683
|
+
}
|
|
3684
|
+
await resolveJsCodeSnapshotFiles(config, profileName, value, item.__dir || process.cwd(), new Map());
|
|
3685
|
+
return value;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
function resolveManifestFormUuid(bound, item, options = {}) {
|
|
3689
|
+
const formKey = item.formCode || item.form || (options.fallbackToCode ? item.code : undefined);
|
|
3690
|
+
if (formKey) {
|
|
3691
|
+
const mapped = bound.resources?.forms?.[formKey]?.formUuid;
|
|
3692
|
+
if (mapped) return mapped;
|
|
3693
|
+
}
|
|
3694
|
+
if (item.formUuid) return item.formUuid;
|
|
3695
|
+
return formKey;
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
function resolveManifestPageId(bound, item) {
|
|
3699
|
+
if (item.pageCode) {
|
|
3700
|
+
const mapped = bound.resources?.pages?.[item.pageCode]?.pageId;
|
|
3701
|
+
if (mapped) return mapped;
|
|
3702
|
+
}
|
|
3703
|
+
return item.pageId || item.pageCode;
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
function resolveManifestMenuId(bound, menuCode) {
|
|
3707
|
+
if (!menuCode) return undefined;
|
|
3708
|
+
return bound.resources?.menus?.[menuCode]?.menuId || menuCode;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
function resolveAutomationManifestPayload(target, automationItem) {
|
|
3712
|
+
const triggerConfig = {
|
|
3713
|
+
...(automationItem.triggerConfig || {}),
|
|
3714
|
+
};
|
|
3715
|
+
if (!triggerConfig.appType) triggerConfig.appType = target.appType;
|
|
3716
|
+
const formUuid = resolveManifestFormUuid(target.bound, automationItem) || triggerConfig.formUuid;
|
|
3717
|
+
if (formUuid && !triggerConfig.formUuid) {
|
|
3718
|
+
triggerConfig.formUuid = formUuid;
|
|
3719
|
+
}
|
|
3720
|
+
return { formUuid, triggerConfig };
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
function resolvePagePermissionGroupTargets(bound, group) {
|
|
3724
|
+
const direct = [
|
|
3725
|
+
...(Array.isArray(group.menuFormUuids) ? group.menuFormUuids : []),
|
|
3726
|
+
...(Array.isArray(group.menuIds) ? group.menuIds : []),
|
|
3727
|
+
];
|
|
3728
|
+
const fromMenus = (group.menuCodes || []).flatMap(code => resolveMenuPermissionTargets(bound, code));
|
|
3729
|
+
const fromForms = (group.formCodes || []).map(code => resolveOptionalFormUuid(bound, code));
|
|
3730
|
+
const fromPages = (group.pageCodes || []).flatMap(code => resolvePagePermissionTargets(bound, code));
|
|
3731
|
+
return unique([...direct, ...fromMenus, ...fromForms, ...fromPages].filter(Boolean));
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
function saveResourceEntry(target, bucket, code, extra = {}) {
|
|
3735
|
+
saveStateResource(target, bucket, code, pickStateFields(extra, ['formUuid']), [
|
|
3736
|
+
'formUuid',
|
|
3737
|
+
]);
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
function withoutResourceMeta(value) {
|
|
3741
|
+
const next = { ...(value || {}) };
|
|
3742
|
+
for (const key of Object.keys(next)) {
|
|
3743
|
+
if (key.startsWith('__')) delete next[key];
|
|
3744
|
+
}
|
|
3745
|
+
return next;
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
function roleEquals(desired, existing) {
|
|
3749
|
+
return (
|
|
3750
|
+
String(existing.name || '') === String(desired.name || desired.code || '') &&
|
|
3751
|
+
String(existing.description || '') === String(desired.description || '')
|
|
3752
|
+
);
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3755
|
+
function menuEquals(bound, desired, existing) {
|
|
3756
|
+
if (!existing) return false;
|
|
3757
|
+
const desiredFormUuid = resolveManifestFormUuid(bound, desired);
|
|
3758
|
+
const desiredPageId = resolveManifestPageId(bound, desired);
|
|
3759
|
+
const desiredParentId =
|
|
3760
|
+
resolveManifestMenuId(bound, desired.parentCode) || desired.parentId || null;
|
|
3761
|
+
return (
|
|
3762
|
+
String(existing.name || '') === String(desired.name || desired.code || '') &&
|
|
3763
|
+
String(existing.type || '') === String(desired.type || 'nav') &&
|
|
3764
|
+
String(existing.resourceCode || '') === String(desired.code || '') &&
|
|
3765
|
+
optionalScalarEquals(existing.formUuid, desiredFormUuid, hasAnyKey(desired, ['formCode', 'formUuid', 'form'])) &&
|
|
3766
|
+
optionalScalarEquals(existing.pageId, desiredPageId, hasAnyKey(desired, ['pageCode', 'pageId'])) &&
|
|
3767
|
+
optionalScalarEquals(existing.parentId || null, desiredParentId, hasAnyKey(desired, ['parentCode', 'parentId'])) &&
|
|
3768
|
+
optionalScalarEquals(existing.sortOrder, desired.sortOrder, desired.sortOrder !== undefined) &&
|
|
3769
|
+
optionalScalarEquals(existing.icon || null, desired.icon || null, desired.icon !== undefined) &&
|
|
3770
|
+
optionalScalarEquals(Boolean(existing.isHidden), Boolean(desired.isHidden), desired.isHidden !== undefined)
|
|
3771
|
+
);
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
function connectorEquals(desired, existing) {
|
|
3775
|
+
const normalizedDesired = normalizeConnectorManifest(desired);
|
|
3776
|
+
const normalizedExisting = normalizeConnectorManifest(existing);
|
|
3777
|
+
delete normalizedExisting.appType;
|
|
3778
|
+
return JSON.stringify(normalizedExisting) === JSON.stringify(normalizedDesired);
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
function pagePermissionGroupEquals(bound, desired, existing) {
|
|
3782
|
+
if (!existing) return false;
|
|
3783
|
+
const desiredTargets = resolvePagePermissionGroupTargets(bound, desired);
|
|
3784
|
+
return (
|
|
3785
|
+
String(existing.name || '') === String(desired.name || desired.code || '') &&
|
|
3786
|
+
String(existing.resourceCode || '') === String(desired.code || '') &&
|
|
3787
|
+
stringSetEquals(existing.roles || [], desired.roles || []) &&
|
|
3788
|
+
stringSetEquals(existing.menuFormUuids || [], desiredTargets)
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
function formPermissionGroupEquals(bound, desired, existing) {
|
|
3793
|
+
if (!existing) return false;
|
|
3794
|
+
const expected = normalizeFormPermissionGroupManifest(bound, desired);
|
|
3795
|
+
const current = pickObjectKeys(existing, Object.keys(expected));
|
|
3796
|
+
return stableStringifyForPlan(current, process.cwd()) === stableStringifyForPlan(expected, desired.__dir || process.cwd());
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
function normalizeFormPermissionGroupManifest(bound, group) {
|
|
3800
|
+
const formUuid = resolveManifestFormUuid(bound, group);
|
|
3801
|
+
const body = {
|
|
3802
|
+
...withoutResourceMeta(group),
|
|
3803
|
+
resourceCode: group.code,
|
|
3804
|
+
formUuid,
|
|
3805
|
+
name: group.name || group.code,
|
|
3806
|
+
type: group.type || 'view',
|
|
3807
|
+
roles: group.roles || [],
|
|
3808
|
+
};
|
|
3809
|
+
delete body.code;
|
|
3810
|
+
delete body.formCode;
|
|
3811
|
+
delete body.form;
|
|
3812
|
+
return stripUndefinedValues(body);
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
function optionalScalarEquals(existingValue, desiredValue, enabled) {
|
|
3816
|
+
if (!enabled) return true;
|
|
3817
|
+
return String(existingValue ?? '') === String(desiredValue ?? '');
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
function hasAnyKey(value, keys) {
|
|
3821
|
+
return keys.some(key => value?.[key] !== undefined);
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3824
|
+
function stringSetEquals(left = [], right = []) {
|
|
3825
|
+
return stableStringifyForPlan(sortStringValues(left), process.cwd()) ===
|
|
3826
|
+
stableStringifyForPlan(sortStringValues(right), process.cwd());
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
function sortStringValues(value = []) {
|
|
3830
|
+
return [...(value || [])].map(item => String(item)).sort();
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
function pickObjectKeys(source = {}, keys = []) {
|
|
3834
|
+
return keys.reduce((acc, key) => {
|
|
3835
|
+
acc[key] = source[key];
|
|
3836
|
+
return acc;
|
|
3837
|
+
}, {});
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
function stripUndefinedValues(value) {
|
|
3841
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
|
|
3842
|
+
return Object.entries(value).reduce((acc, [key, item]) => {
|
|
3843
|
+
if (item !== undefined) acc[key] = item;
|
|
3844
|
+
return acc;
|
|
3845
|
+
}, {});
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
function workflowEquals(bound, desired, existing) {
|
|
3849
|
+
if (!existing) return false;
|
|
3850
|
+
const desiredDefinition = resolveManifestPlainJson(desired, 'definitionJson', 'definitionFile');
|
|
3851
|
+
const desiredView = resolveManifestPlainJson(desired, 'viewJson', 'viewFile', true);
|
|
3852
|
+
const desiredFormUuid = resolveManifestFormUuid(bound, desired);
|
|
3853
|
+
return (
|
|
3854
|
+
String(existing.resourceCode || '') === String(desired.code || desired.resourceCode || '') &&
|
|
3855
|
+
String(existing.formUuid || '') === String(desiredFormUuid || '') &&
|
|
3856
|
+
jsonEqualsForPlan(existing.definitionJson, desiredDefinition, desired.__dir) &&
|
|
3857
|
+
optionalJsonEqualsForPlan(existing.viewJson, desiredView, desired.__dir) &&
|
|
3858
|
+
(desired.publish ? Boolean(existing.isPublished) === true : true)
|
|
3859
|
+
);
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
function automationEquals(target, desired, existing) {
|
|
3863
|
+
if (!existing) return false;
|
|
3864
|
+
const desiredDefinition = resolveManifestPlainJson(desired, 'definitionJson', 'definitionFile');
|
|
3865
|
+
const desiredView = resolveManifestPlainJson(desired, 'viewJson', 'viewFile', true);
|
|
3866
|
+
const automationPayload = resolveAutomationManifestPayload(target, desired);
|
|
3867
|
+
const desiredTags =
|
|
3868
|
+
desired.tags === undefined
|
|
3869
|
+
? undefined
|
|
3870
|
+
: Array.isArray(desired.tags)
|
|
3871
|
+
? desired.tags.join(',')
|
|
3872
|
+
: String(desired.tags);
|
|
3873
|
+
const desiredEnabled =
|
|
3874
|
+
desired.enable !== undefined
|
|
3875
|
+
? Boolean(desired.enable)
|
|
3876
|
+
: desired.isEnabled !== undefined
|
|
3877
|
+
? Boolean(desired.isEnabled)
|
|
3878
|
+
: undefined;
|
|
3879
|
+
|
|
3880
|
+
return (
|
|
3881
|
+
String(existing.resourceCode || '') === String(desired.code || desired.resourceCode || '') &&
|
|
3882
|
+
String(existing.name || '') === String(desired.name || desired.code || '') &&
|
|
3883
|
+
String(existing.description || '') === String(desired.description || '') &&
|
|
3884
|
+
String(existing.formUuid || '') === String(automationPayload.formUuid || '') &&
|
|
3885
|
+
jsonEqualsForPlan(existing.triggerConfig, automationPayload.triggerConfig, desired.__dir) &&
|
|
3886
|
+
jsonEqualsForPlan(existing.definitionJson, desiredDefinition, desired.__dir) &&
|
|
3887
|
+
optionalJsonEqualsForPlan(existing.viewJson, desiredView, desired.__dir) &&
|
|
3888
|
+
(desiredTags === undefined ? true : String(existing.tags || '') === desiredTags) &&
|
|
3889
|
+
(desired.publish ? Boolean(existing.isPublished) === true : true) &&
|
|
3890
|
+
(desiredEnabled === undefined ? true : Boolean(existing.isEnabled) === desiredEnabled)
|
|
3891
|
+
);
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
function resolveManifestPlainJson(item, objectKey, fileKey, optional = false) {
|
|
3895
|
+
if (item[objectKey] !== undefined) return item[objectKey];
|
|
3896
|
+
if (item[fileKey]) {
|
|
3897
|
+
const filePath = path.resolve(item.__dir || process.cwd(), item[fileKey]);
|
|
3898
|
+
try {
|
|
3899
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
3900
|
+
} catch (error) {
|
|
3901
|
+
fail(`${resourceLabel('resource', item)} 无法读取 ${fileKey}: ${error.message}`);
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
if (optional) return undefined;
|
|
3905
|
+
fail(`${resourceLabel('resource', item)} 缺少 ${objectKey} 或 ${fileKey}`);
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
function optionalJsonEqualsForPlan(existingValue, desiredValue, desiredBaseDir) {
|
|
3909
|
+
const normalizedExisting =
|
|
3910
|
+
existingValue === null || existingValue === undefined ? undefined : existingValue;
|
|
3911
|
+
const normalizedDesired =
|
|
3912
|
+
desiredValue === null || desiredValue === undefined ? undefined : desiredValue;
|
|
3913
|
+
return jsonEqualsForPlan(normalizedExisting, normalizedDesired, desiredBaseDir);
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
function jsonEqualsForPlan(existingValue, desiredValue, desiredBaseDir) {
|
|
3917
|
+
return (
|
|
3918
|
+
stableStringifyForPlan(existingValue, process.cwd()) ===
|
|
3919
|
+
stableStringifyForPlan(desiredValue, desiredBaseDir || process.cwd())
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3923
|
+
function stableStringifyForPlan(value, baseDir) {
|
|
3924
|
+
const normalized = normalizeJsonForPlan(value, baseDir, null);
|
|
3925
|
+
const result = JSON.stringify(normalized);
|
|
3926
|
+
return result === undefined ? 'undefined' : result;
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
function normalizeJsonForPlan(value, baseDir, keyName) {
|
|
3930
|
+
if (value === undefined || value === null) return value;
|
|
3931
|
+
if (Array.isArray(value)) {
|
|
3932
|
+
return value.map(item => normalizeJsonForPlan(item, baseDir, null));
|
|
3933
|
+
}
|
|
3934
|
+
if (typeof value !== 'object') return value;
|
|
3935
|
+
|
|
3936
|
+
if (keyName === 'sourceFile') {
|
|
3937
|
+
if (value.localPath) return normalizeLocalSourceFileForPlan(value.localPath, baseDir);
|
|
3938
|
+
if (value.sha256) return { sha256: value.sha256 };
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
const result = {};
|
|
3942
|
+
const hasLocalSourceFile =
|
|
3943
|
+
value.sourceFile && typeof value.sourceFile === 'object' && value.sourceFile.localPath;
|
|
3944
|
+
for (const key of Object.keys(value).sort()) {
|
|
3945
|
+
if (key.startsWith('__') || value[key] === undefined) continue;
|
|
3946
|
+
result[key] = normalizeJsonForPlan(value[key], baseDir, key);
|
|
3947
|
+
}
|
|
3948
|
+
if (hasLocalSourceFile) {
|
|
3949
|
+
result.code = result.code === undefined ? '' : result.code;
|
|
3950
|
+
result.runtimeMode = result.runtimeMode || 'trusted_node';
|
|
3951
|
+
result.sourceType = result.sourceType || 'file_snapshot';
|
|
3952
|
+
}
|
|
3953
|
+
return sortObjectForStableJson(result);
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
function normalizeLocalSourceFileForPlan(localPath, baseDir) {
|
|
3957
|
+
const rawLocalPath = String(localPath);
|
|
3958
|
+
const baseResolvedPath = path.resolve(baseDir || process.cwd(), rawLocalPath);
|
|
3959
|
+
const cwdResolvedPath = path.resolve(process.cwd(), rawLocalPath);
|
|
3960
|
+
const resolvedLocalPath = fs.existsSync(baseResolvedPath)
|
|
3961
|
+
? baseResolvedPath
|
|
3962
|
+
: cwdResolvedPath;
|
|
3963
|
+
const bundlePath = resolveJsCodeBundlePathForPlan(resolvedLocalPath);
|
|
3964
|
+
if (bundlePath && fs.existsSync(bundlePath)) {
|
|
3965
|
+
return {
|
|
3966
|
+
sha256: crypto
|
|
3967
|
+
.createHash('sha256')
|
|
3968
|
+
.update(fs.readFileSync(bundlePath))
|
|
3969
|
+
.digest('hex'),
|
|
3970
|
+
};
|
|
3971
|
+
}
|
|
3972
|
+
return {
|
|
3973
|
+
localPath: path.relative(process.cwd(), resolvedLocalPath).replace(/\\/g, '/'),
|
|
3974
|
+
};
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
function resolveJsCodeBundlePathForPlan(localPath) {
|
|
3978
|
+
const extension = path.extname(localPath).toLowerCase();
|
|
3979
|
+
if (extension && extension !== '.ts' && extension !== '.tsx') return null;
|
|
3980
|
+
const normalized = localPath.replace(/\\/g, '/');
|
|
3981
|
+
const matched = normalized.match(/src\/js-code-nodes\/([^/]+)\/index\.tsx?$/);
|
|
3982
|
+
if (!matched) return null;
|
|
3983
|
+
const workspaceRoot = findWorkspaceRoot(localPath);
|
|
3984
|
+
return path.join(workspaceRoot, 'dist', 'js-code-nodes', matched[1], 'index.cjs');
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3987
|
+
function sortObjectForStableJson(value) {
|
|
3988
|
+
return Object.keys(value)
|
|
3989
|
+
.sort()
|
|
3990
|
+
.reduce((acc, key) => {
|
|
3991
|
+
acc[key] = value[key];
|
|
3992
|
+
return acc;
|
|
3993
|
+
}, {});
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
function printResourceValidation(validation) {
|
|
3997
|
+
const lines = [
|
|
3998
|
+
`资源目录: ${path.relative(process.cwd(), validation.baseDir) || validation.baseDir}`,
|
|
3999
|
+
`校验状态: ${validation.valid ? '通过' : '失败'}`,
|
|
4000
|
+
];
|
|
4001
|
+
for (const [key, count] of Object.entries(validation.counts)) {
|
|
4002
|
+
lines.push(`- ${key}: ${count}`);
|
|
4003
|
+
}
|
|
4004
|
+
for (const warning of validation.warnings) lines.push(`Warning: ${warning}`);
|
|
4005
|
+
for (const error of validation.errors) lines.push(`Error: ${error}`);
|
|
4006
|
+
print(lines.join('\n'));
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
function printResourcePlan(plan) {
|
|
4010
|
+
const lines = [
|
|
4011
|
+
`profile: ${plan.profile}`,
|
|
4012
|
+
`appType: ${plan.appType}`,
|
|
4013
|
+
`summary: create=${plan.summary.create} update=${plan.summary.update} noop=${plan.summary.noop} delete=${plan.summary.delete}`,
|
|
4014
|
+
];
|
|
4015
|
+
for (const action of plan.actions) {
|
|
4016
|
+
lines.push(`- ${action.kind}:${action.code} ${action.action}${action.platformId ? ` (${action.platformId})` : ''}`);
|
|
4017
|
+
}
|
|
4018
|
+
print(lines.join('\n'));
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
function printResourceResult(result) {
|
|
4022
|
+
const lines = [`profile: ${result.profile}`, `appType: ${result.appType}`];
|
|
4023
|
+
if (result.written) {
|
|
4024
|
+
lines.push(`written: ${result.written.length}`);
|
|
4025
|
+
result.written.forEach(file => lines.push(`- ${file}`));
|
|
4026
|
+
}
|
|
4027
|
+
if (result.published) {
|
|
4028
|
+
lines.push(`published: ${result.published.length}`);
|
|
4029
|
+
result.published.forEach(item => lines.push(`- ${item.kind}:${item.code} ${item.action}${item.id ? ` (${item.id})` : ''}`));
|
|
4030
|
+
}
|
|
4031
|
+
if (result.pruned) {
|
|
4032
|
+
lines.push(`pruned: ${result.pruned.length}`);
|
|
4033
|
+
result.pruned.forEach(item => lines.push(`- ${item.kind}:${item.code}${item.id ? ` (${item.id})` : ''}`));
|
|
4034
|
+
}
|
|
4035
|
+
for (const warning of result.warnings || []) lines.push(`Warning: ${warning}`);
|
|
4036
|
+
print(lines.join('\n'));
|
|
2193
4037
|
}
|
|
2194
4038
|
|
|
2195
4039
|
function readJsonArg(value, label) {
|