scene-capability-engine 3.6.53 → 3.6.55
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/CHANGELOG.md +23 -0
- package/README.md +6 -1
- package/README.zh.md +6 -1
- package/bin/scene-capability-engine.js +2 -0
- package/docs/command-reference.md +29 -1
- package/docs/magicball-app-collection-phase-1.md +133 -0
- package/docs/magicball-cli-invocation-examples.md +40 -0
- package/docs/magicball-integration-doc-index.md +14 -6
- package/docs/magicball-integration-issue-tracker.md +42 -3
- package/docs/magicball-sce-adaptation-guide.md +36 -9
- package/docs/releases/README.md +2 -0
- package/docs/releases/v3.6.54.md +19 -0
- package/docs/releases/v3.6.55.md +18 -0
- package/docs/zh/releases/README.md +2 -0
- package/docs/zh/releases/v3.6.54.md +19 -0
- package/docs/zh/releases/v3.6.55.md +18 -0
- package/lib/app/collection-store.js +127 -0
- package/lib/app/install-apply-runner.js +192 -0
- package/lib/app/install-plan-service.js +410 -0
- package/lib/app/scene-workspace-store.js +132 -0
- package/lib/commands/app.js +281 -0
- package/lib/commands/device.js +194 -0
- package/lib/commands/scene.js +228 -0
- package/lib/device/current-device.js +158 -0
- package/lib/device/device-override-store.js +157 -0
- package/lib/problem/project-problem-projection.js +239 -0
- package/lib/workspace/collab-governance-audit.js +107 -0
- package/lib/workspace/collab-governance-gate.js +24 -4
- package/lib/workspace/takeover-baseline.js +76 -0
- package/package.json +1 -1
- package/template/.sce/README.md +1 -1
- package/template/.sce/config/problem-closure-policy.json +5 -0
- package/template/.sce/knowledge/problem/project-shared-problems.json +16 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
|
|
4
|
+
function normalizeString(value) {
|
|
5
|
+
if (typeof value !== 'string') {
|
|
6
|
+
return '';
|
|
7
|
+
}
|
|
8
|
+
return value.trim();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeStringArray(value) {
|
|
12
|
+
if (!Array.isArray(value)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const items = [];
|
|
17
|
+
for (const entry of value) {
|
|
18
|
+
const normalized = normalizeString(entry);
|
|
19
|
+
if (!normalized || seen.has(normalized)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
seen.add(normalized);
|
|
23
|
+
items.push(normalized);
|
|
24
|
+
}
|
|
25
|
+
return items;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeBoolean(value, fallback = false) {
|
|
29
|
+
if (typeof value === 'boolean') {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
return fallback;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeCollectionItem(item = {}) {
|
|
36
|
+
return {
|
|
37
|
+
app_id: normalizeString(item.app_id || item.appId) || null,
|
|
38
|
+
app_key: normalizeString(item.app_key || item.appKey) || null,
|
|
39
|
+
required: normalizeBoolean(item.required, false),
|
|
40
|
+
allow_local_remove: normalizeBoolean(item.allow_local_remove ?? item.allowLocalRemove, true),
|
|
41
|
+
priority: Number.isFinite(Number(item.priority)) ? Number(item.priority) : null,
|
|
42
|
+
default_entry: normalizeString(item.default_entry || item.defaultEntry) || null,
|
|
43
|
+
capability_tags: normalizeStringArray(item.capability_tags || item.capabilityTags),
|
|
44
|
+
metadata: item.metadata && typeof item.metadata === 'object' ? item.metadata : {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeAppCollection(raw = {}, filePath = '') {
|
|
49
|
+
const fileName = normalizeString(path.basename(filePath, path.extname(filePath)));
|
|
50
|
+
const collectionId = normalizeString(raw.collection_id || raw.collectionId || raw.id) || fileName;
|
|
51
|
+
if (!collectionId) {
|
|
52
|
+
throw new Error(`invalid app collection file: missing collection_id (${filePath})`);
|
|
53
|
+
}
|
|
54
|
+
const items = Array.isArray(raw.items)
|
|
55
|
+
? raw.items
|
|
56
|
+
.filter((item) => item && typeof item === 'object')
|
|
57
|
+
.map((item) => normalizeCollectionItem(item))
|
|
58
|
+
.filter((item) => item.app_id || item.app_key)
|
|
59
|
+
: [];
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
collection_id: collectionId,
|
|
63
|
+
name: normalizeString(raw.name) || collectionId,
|
|
64
|
+
description: normalizeString(raw.description) || null,
|
|
65
|
+
status: normalizeString(raw.status) || 'active',
|
|
66
|
+
tags: normalizeStringArray(raw.tags),
|
|
67
|
+
item_count: items.length,
|
|
68
|
+
items,
|
|
69
|
+
metadata: raw.metadata && typeof raw.metadata === 'object' ? raw.metadata : {},
|
|
70
|
+
source_file: filePath
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function listAppCollections(projectPath = process.cwd(), options = {}) {
|
|
75
|
+
const fileSystem = options.fileSystem || fs;
|
|
76
|
+
const collectionsDir = path.join(projectPath, '.sce', 'app', 'collections');
|
|
77
|
+
if (!await fileSystem.pathExists(collectionsDir)) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const entries = await fileSystem.readdir(collectionsDir);
|
|
82
|
+
const query = normalizeString(options.query).toLowerCase();
|
|
83
|
+
const status = normalizeString(options.status);
|
|
84
|
+
const normalized = [];
|
|
85
|
+
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (!entry.toLowerCase().endsWith('.json')) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const absolutePath = path.join(collectionsDir, entry);
|
|
91
|
+
const raw = await fileSystem.readJson(absolutePath);
|
|
92
|
+
const collection = normalizeAppCollection(raw, absolutePath);
|
|
93
|
+
if (status && collection.status !== status) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (query) {
|
|
97
|
+
const haystack = [
|
|
98
|
+
collection.collection_id,
|
|
99
|
+
collection.name,
|
|
100
|
+
collection.description,
|
|
101
|
+
...collection.tags
|
|
102
|
+
].join(' ').toLowerCase();
|
|
103
|
+
if (!haystack.includes(query)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
normalized.push(collection);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
normalized.sort((left, right) => left.collection_id.localeCompare(right.collection_id));
|
|
111
|
+
return normalized;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function getAppCollection(projectPath = process.cwd(), collectionRef = '', options = {}) {
|
|
115
|
+
const normalizedRef = normalizeString(collectionRef);
|
|
116
|
+
if (!normalizedRef) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const collections = await listAppCollections(projectPath, options);
|
|
120
|
+
return collections.find((item) => item.collection_id === normalizedRef || path.basename(item.source_file, '.json') === normalizedRef) || null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
normalizeAppCollection,
|
|
125
|
+
listAppCollections,
|
|
126
|
+
getAppCollection
|
|
127
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
function normalizeString(value) {
|
|
2
|
+
if (typeof value !== 'string') {
|
|
3
|
+
return '';
|
|
4
|
+
}
|
|
5
|
+
return value.trim();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function resolveAppRef(action = {}) {
|
|
9
|
+
return normalizeString(action.app_key) || normalizeString(action.app_id);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getBlockingReasons(plan = {}) {
|
|
13
|
+
const reasons = [];
|
|
14
|
+
const unresolvedCollections = Array.isArray(plan.unresolved_collections) ? plan.unresolved_collections : [];
|
|
15
|
+
if (unresolvedCollections.length > 0) {
|
|
16
|
+
reasons.push('unresolved-collections');
|
|
17
|
+
}
|
|
18
|
+
const skipActions = Array.isArray(plan.actions)
|
|
19
|
+
? plan.actions.filter((item) => normalizeString(item.decision) === 'skip')
|
|
20
|
+
: [];
|
|
21
|
+
if (skipActions.some((item) => normalizeString(item.reason) === 'app-bundle-not-found')) {
|
|
22
|
+
reasons.push('unresolved-app-bundles');
|
|
23
|
+
}
|
|
24
|
+
if (skipActions.some((item) => normalizeString(item.reason) === 'active-release-protected')) {
|
|
25
|
+
reasons.push('active-release-protected');
|
|
26
|
+
}
|
|
27
|
+
return reasons;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function verifyInstallCandidates(plan = {}, store) {
|
|
31
|
+
const failures = [];
|
|
32
|
+
const actions = Array.isArray(plan.actions) ? plan.actions : [];
|
|
33
|
+
for (const action of actions) {
|
|
34
|
+
if (normalizeString(action.decision) !== 'install') {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const appRef = resolveAppRef(action);
|
|
38
|
+
const graph = appRef ? await store.getAppBundleGraph(appRef) : null;
|
|
39
|
+
const metadata = graph && graph.bundle && graph.bundle.metadata && typeof graph.bundle.metadata === 'object'
|
|
40
|
+
? graph.bundle.metadata
|
|
41
|
+
: {};
|
|
42
|
+
const serviceCatalog = metadata.service_catalog && typeof metadata.service_catalog === 'object'
|
|
43
|
+
? metadata.service_catalog
|
|
44
|
+
: {};
|
|
45
|
+
const releases = Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases : [];
|
|
46
|
+
if (!graph || releases.length === 0) {
|
|
47
|
+
failures.push({
|
|
48
|
+
app_ref: appRef,
|
|
49
|
+
reason: 'no-installable-runtime-release'
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return failures;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function verifyActivateCandidates(plan = {}, store) {
|
|
57
|
+
const failures = [];
|
|
58
|
+
const actions = Array.isArray(plan.actions) ? plan.actions : [];
|
|
59
|
+
for (const action of actions) {
|
|
60
|
+
if (normalizeString(action.decision) !== 'activate') {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const appRef = resolveAppRef(action);
|
|
64
|
+
const graph = appRef ? await store.getAppBundleGraph(appRef) : null;
|
|
65
|
+
const metadata = graph && graph.bundle && graph.bundle.metadata && typeof graph.bundle.metadata === 'object'
|
|
66
|
+
? graph.bundle.metadata
|
|
67
|
+
: {};
|
|
68
|
+
const serviceCatalog = metadata.service_catalog && typeof metadata.service_catalog === 'object'
|
|
69
|
+
? metadata.service_catalog
|
|
70
|
+
: {};
|
|
71
|
+
const releases = Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases : [];
|
|
72
|
+
const installedReleaseId = normalizeString(action.installed_release_id);
|
|
73
|
+
if (!graph || !installedReleaseId || !releases.some((item) => normalizeString(item && item.release_id) === installedReleaseId)) {
|
|
74
|
+
failures.push({
|
|
75
|
+
app_ref: appRef,
|
|
76
|
+
reason: 'no-activatable-installed-release'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return failures;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sortExecutableActions(actions = []) {
|
|
84
|
+
const install = [];
|
|
85
|
+
const activate = [];
|
|
86
|
+
const uninstall = [];
|
|
87
|
+
for (const action of actions) {
|
|
88
|
+
const decision = normalizeString(action.decision);
|
|
89
|
+
if (decision === 'install') {
|
|
90
|
+
install.push(action);
|
|
91
|
+
} else if (decision === 'activate') {
|
|
92
|
+
activate.push(action);
|
|
93
|
+
} else if (decision === 'uninstall') {
|
|
94
|
+
uninstall.push(action);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return [...install, ...activate, ...uninstall];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function executeInstallPlan(plan = {}, options = {}) {
|
|
101
|
+
const store = options.store;
|
|
102
|
+
const executeInstall = options.executeInstall;
|
|
103
|
+
const executeActivate = options.executeActivate;
|
|
104
|
+
const executeUninstall = options.executeUninstall;
|
|
105
|
+
const dependencies = options.dependencies || {};
|
|
106
|
+
const commandOptions = options.commandOptions || {};
|
|
107
|
+
|
|
108
|
+
const blockingReasons = getBlockingReasons(plan);
|
|
109
|
+
if (blockingReasons.length > 0) {
|
|
110
|
+
return {
|
|
111
|
+
execute_supported: true,
|
|
112
|
+
executed: false,
|
|
113
|
+
blocked_reason: blockingReasons.join(','),
|
|
114
|
+
results: []
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const installPreflightFailures = await verifyInstallCandidates(plan, store);
|
|
119
|
+
const activatePreflightFailures = await verifyActivateCandidates(plan, store);
|
|
120
|
+
const preflightFailures = [...installPreflightFailures, ...activatePreflightFailures];
|
|
121
|
+
if (preflightFailures.length > 0) {
|
|
122
|
+
return {
|
|
123
|
+
execute_supported: true,
|
|
124
|
+
executed: false,
|
|
125
|
+
blocked_reason: 'install-preflight-failed',
|
|
126
|
+
preflight_failures: preflightFailures,
|
|
127
|
+
results: []
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const results = [];
|
|
132
|
+
const actions = sortExecutableActions(Array.isArray(plan.actions) ? plan.actions : []);
|
|
133
|
+
for (const action of actions) {
|
|
134
|
+
const decision = normalizeString(action.decision);
|
|
135
|
+
const appRef = resolveAppRef(action);
|
|
136
|
+
if (!appRef) {
|
|
137
|
+
return {
|
|
138
|
+
execute_supported: true,
|
|
139
|
+
executed: false,
|
|
140
|
+
blocked_reason: 'invalid-plan-action',
|
|
141
|
+
results
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const nestedOptions = {
|
|
145
|
+
app: appRef,
|
|
146
|
+
authLease: commandOptions.authLease,
|
|
147
|
+
authPassword: commandOptions.authPassword,
|
|
148
|
+
actor: commandOptions.actor,
|
|
149
|
+
json: true,
|
|
150
|
+
silent: true
|
|
151
|
+
};
|
|
152
|
+
if (decision === 'install') {
|
|
153
|
+
const payload = await executeInstall(nestedOptions, dependencies);
|
|
154
|
+
results.push({
|
|
155
|
+
app_ref: appRef,
|
|
156
|
+
decision,
|
|
157
|
+
success: true,
|
|
158
|
+
summary: payload.summary || null
|
|
159
|
+
});
|
|
160
|
+
} else if (decision === 'activate') {
|
|
161
|
+
const payload = await executeActivate({
|
|
162
|
+
...nestedOptions,
|
|
163
|
+
release: normalizeString(action.installed_release_id)
|
|
164
|
+
}, dependencies);
|
|
165
|
+
results.push({
|
|
166
|
+
app_ref: appRef,
|
|
167
|
+
decision,
|
|
168
|
+
success: true,
|
|
169
|
+
summary: payload.summary || null
|
|
170
|
+
});
|
|
171
|
+
} else if (decision === 'uninstall') {
|
|
172
|
+
const payload = await executeUninstall(nestedOptions, dependencies);
|
|
173
|
+
results.push({
|
|
174
|
+
app_ref: appRef,
|
|
175
|
+
decision,
|
|
176
|
+
success: true,
|
|
177
|
+
summary: payload.summary || null
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
execute_supported: true,
|
|
184
|
+
executed: true,
|
|
185
|
+
blocked_reason: null,
|
|
186
|
+
results
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
executeInstallPlan
|
|
192
|
+
};
|