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
package/lib/commands/app.js
CHANGED
|
@@ -3,6 +3,11 @@ const chalk = require('chalk');
|
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const { ensureWriteAuthorization } = require('../security/write-authorization');
|
|
5
5
|
const { getSceStateStore } = require('../state/sce-state-store');
|
|
6
|
+
const { listAppCollections, getAppCollection } = require('../app/collection-store');
|
|
7
|
+
const { buildCollectionApplyPlan } = require('../app/install-plan-service');
|
|
8
|
+
const { executeInstallPlan } = require('../app/install-apply-runner');
|
|
9
|
+
const { getCurrentDeviceProfile } = require('../device/current-device');
|
|
10
|
+
const { loadDeviceOverride } = require('../device/device-override-store');
|
|
6
11
|
const { loadAppRegistryConfig, saveAppRegistryConfig } = require('../app/registry-config');
|
|
7
12
|
const { syncBundleRegistry, syncServiceCatalog } = require('../app/registry-sync-service');
|
|
8
13
|
|
|
@@ -49,6 +54,9 @@ function createStore(dependencies = {}) {
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
function printPayload(payload, options = {}, title = 'App Bundle') {
|
|
57
|
+
if (options.silent === true) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
52
60
|
if (options.json) {
|
|
53
61
|
console.log(JSON.stringify(payload, null, 2));
|
|
54
62
|
return;
|
|
@@ -175,6 +183,36 @@ function buildRuntimeSummary(graph = {}) {
|
|
|
175
183
|
};
|
|
176
184
|
}
|
|
177
185
|
|
|
186
|
+
function buildAppInstallStateItem(bundle = {}, currentDevice = null) {
|
|
187
|
+
const state = getRuntimeProjectionState({ bundle });
|
|
188
|
+
const installation = state.installation && typeof state.installation === 'object'
|
|
189
|
+
? state.installation
|
|
190
|
+
: {};
|
|
191
|
+
const installationMachineId = normalizeString(installation.machine_id) || null;
|
|
192
|
+
let machineScope = 'unspecified';
|
|
193
|
+
|
|
194
|
+
if (installationMachineId && currentDevice && currentDevice.device_id) {
|
|
195
|
+
machineScope = installationMachineId === currentDevice.device_id ? 'current-device' : 'other-device';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
app_id: bundle.app_id || null,
|
|
200
|
+
app_key: bundle.app_key || null,
|
|
201
|
+
app_name: bundle.app_name || null,
|
|
202
|
+
environment: bundle.environment || null,
|
|
203
|
+
bundle_status: bundle.status || null,
|
|
204
|
+
install_status: state.installStatus,
|
|
205
|
+
installed_release_id: state.installedReleaseId,
|
|
206
|
+
active_release_id: state.activeReleaseId,
|
|
207
|
+
install_root: installation.install_root || null,
|
|
208
|
+
machine_id: installationMachineId,
|
|
209
|
+
device_hostname: normalizeString(installation.hostname) || null,
|
|
210
|
+
machine_scope: machineScope,
|
|
211
|
+
release_count: state.releases.length,
|
|
212
|
+
status: state.installStatus
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
178
216
|
function buildEngineeringSummary(graph = {}) {
|
|
179
217
|
const bundle = graph.bundle || {};
|
|
180
218
|
const engineeringProject = graph.engineering_project || {};
|
|
@@ -381,6 +419,141 @@ async function runAppBundleRegisterCommand(options = {}, dependencies = {}) {
|
|
|
381
419
|
return payload;
|
|
382
420
|
}
|
|
383
421
|
|
|
422
|
+
async function runAppCollectionListCommand(options = {}, dependencies = {}) {
|
|
423
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
424
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
425
|
+
const collections = await listAppCollections(projectPath, {
|
|
426
|
+
fileSystem,
|
|
427
|
+
query: options.query,
|
|
428
|
+
status: options.status
|
|
429
|
+
});
|
|
430
|
+
const limit = normalizePositiveInteger(options.limit, 100, 1000);
|
|
431
|
+
const items = collections.slice(0, limit).map((item) => ({
|
|
432
|
+
collection_id: item.collection_id,
|
|
433
|
+
name: item.name,
|
|
434
|
+
description: item.description,
|
|
435
|
+
status: item.status,
|
|
436
|
+
item_count: item.item_count,
|
|
437
|
+
tags: item.tags,
|
|
438
|
+
source_file: item.source_file
|
|
439
|
+
}));
|
|
440
|
+
|
|
441
|
+
const payload = {
|
|
442
|
+
mode: 'app-collection-list',
|
|
443
|
+
generated_at: new Date().toISOString(),
|
|
444
|
+
query: {
|
|
445
|
+
limit,
|
|
446
|
+
status: normalizeString(options.status) || null,
|
|
447
|
+
query: normalizeString(options.query) || null
|
|
448
|
+
},
|
|
449
|
+
summary: {
|
|
450
|
+
total: items.length
|
|
451
|
+
},
|
|
452
|
+
items,
|
|
453
|
+
view_model: {
|
|
454
|
+
type: 'table',
|
|
455
|
+
columns: ['collection_id', 'name', 'status', 'item_count', 'source_file']
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
printPayload(payload, options, 'App Collection List');
|
|
459
|
+
return payload;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function runAppCollectionShowCommand(options = {}, dependencies = {}) {
|
|
463
|
+
const collectionRef = normalizeString(options.collection);
|
|
464
|
+
if (!collectionRef) {
|
|
465
|
+
throw new Error('--collection is required');
|
|
466
|
+
}
|
|
467
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
468
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
469
|
+
const collection = await getAppCollection(projectPath, collectionRef, {
|
|
470
|
+
fileSystem
|
|
471
|
+
});
|
|
472
|
+
if (!collection) {
|
|
473
|
+
throw new Error(`app collection not found: ${collectionRef}`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const payload = {
|
|
477
|
+
mode: 'app-collection-show',
|
|
478
|
+
generated_at: new Date().toISOString(),
|
|
479
|
+
query: {
|
|
480
|
+
collection: collectionRef
|
|
481
|
+
},
|
|
482
|
+
summary: {
|
|
483
|
+
collection_id: collection.collection_id,
|
|
484
|
+
name: collection.name,
|
|
485
|
+
status: collection.status,
|
|
486
|
+
item_count: collection.item_count
|
|
487
|
+
},
|
|
488
|
+
collection
|
|
489
|
+
};
|
|
490
|
+
printPayload(payload, options, 'App Collection Show');
|
|
491
|
+
return payload;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function runAppCollectionApplyCommand(options = {}, dependencies = {}) {
|
|
495
|
+
const collectionRef = normalizeString(options.collection);
|
|
496
|
+
if (!collectionRef) {
|
|
497
|
+
throw new Error('--collection is required');
|
|
498
|
+
}
|
|
499
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
500
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
501
|
+
const store = createStore(dependencies);
|
|
502
|
+
const currentDevice = await getCurrentDeviceProfile(projectPath, {
|
|
503
|
+
fileSystem,
|
|
504
|
+
persistIfMissing: false
|
|
505
|
+
});
|
|
506
|
+
const deviceOverride = await loadDeviceOverride(projectPath, { fileSystem });
|
|
507
|
+
const plan = await buildCollectionApplyPlan(projectPath, {
|
|
508
|
+
fileSystem,
|
|
509
|
+
store,
|
|
510
|
+
collectionRef,
|
|
511
|
+
currentDevice,
|
|
512
|
+
deviceOverride
|
|
513
|
+
});
|
|
514
|
+
const execution = options.execute
|
|
515
|
+
? await executeInstallPlan(plan, {
|
|
516
|
+
store,
|
|
517
|
+
executeInstall: runAppRuntimeInstallCommand,
|
|
518
|
+
executeActivate: runAppRuntimeActivateCommand,
|
|
519
|
+
executeUninstall: runAppRuntimeUninstallCommand,
|
|
520
|
+
dependencies,
|
|
521
|
+
commandOptions: options
|
|
522
|
+
})
|
|
523
|
+
: {
|
|
524
|
+
execute_supported: true,
|
|
525
|
+
executed: false,
|
|
526
|
+
blocked_reason: null,
|
|
527
|
+
results: []
|
|
528
|
+
};
|
|
529
|
+
const payload = {
|
|
530
|
+
mode: 'app-collection-apply',
|
|
531
|
+
generated_at: new Date().toISOString(),
|
|
532
|
+
execute_supported: execution.execute_supported,
|
|
533
|
+
executed: execution.executed,
|
|
534
|
+
execution_blocked_reason: execution.blocked_reason,
|
|
535
|
+
execution: {
|
|
536
|
+
results: execution.results,
|
|
537
|
+
preflight_failures: execution.preflight_failures || []
|
|
538
|
+
},
|
|
539
|
+
current_device: currentDevice,
|
|
540
|
+
device_override: deviceOverride,
|
|
541
|
+
summary: {
|
|
542
|
+
source_type: plan.source.type,
|
|
543
|
+
source_id: plan.source.id,
|
|
544
|
+
desired_app_count: plan.desired_apps.length,
|
|
545
|
+
install_count: plan.counts.install,
|
|
546
|
+
activate_count: plan.counts.activate,
|
|
547
|
+
uninstall_count: plan.counts.uninstall,
|
|
548
|
+
keep_count: plan.counts.keep,
|
|
549
|
+
skip_count: plan.counts.skip
|
|
550
|
+
},
|
|
551
|
+
plan
|
|
552
|
+
};
|
|
553
|
+
printPayload(payload, options, 'App Collection Apply');
|
|
554
|
+
return payload;
|
|
555
|
+
}
|
|
556
|
+
|
|
384
557
|
async function runAppEngineeringShowCommand(options = {}, dependencies = {}) {
|
|
385
558
|
const appRef = normalizeString(options.app);
|
|
386
559
|
if (!appRef) {
|
|
@@ -687,6 +860,10 @@ async function runAppRuntimeInstallCommand(options = {}, dependencies = {}) {
|
|
|
687
860
|
await ensureAuthorized('app:runtime:install', options, dependencies);
|
|
688
861
|
const projectPath = dependencies.projectPath || process.cwd();
|
|
689
862
|
const fileSystem = dependencies.fileSystem || fs;
|
|
863
|
+
const currentDevice = await getCurrentDeviceProfile(projectPath, {
|
|
864
|
+
fileSystem,
|
|
865
|
+
persistIfMissing: true
|
|
866
|
+
});
|
|
690
867
|
const { store, graph } = await requireAppGraph(appRef, dependencies);
|
|
691
868
|
const nextPayload = graphToRegisterPayload(graph);
|
|
692
869
|
const serviceCatalog = nextPayload.metadata && nextPayload.metadata.service_catalog && typeof nextPayload.metadata.service_catalog === 'object'
|
|
@@ -705,6 +882,8 @@ async function runAppRuntimeInstallCommand(options = {}, dependencies = {}) {
|
|
|
705
882
|
status: 'installed',
|
|
706
883
|
install_root: installRoot,
|
|
707
884
|
release_id: releaseId,
|
|
885
|
+
machine_id: currentDevice.device_id,
|
|
886
|
+
hostname: currentDevice.hostname,
|
|
708
887
|
installed_at: new Date().toISOString(),
|
|
709
888
|
source: 'sce app runtime install'
|
|
710
889
|
};
|
|
@@ -773,7 +952,12 @@ async function runAppRuntimeUninstallCommand(options = {}, dependencies = {}) {
|
|
|
773
952
|
throw new Error('--app is required');
|
|
774
953
|
}
|
|
775
954
|
await ensureAuthorized('app:runtime:uninstall', options, dependencies);
|
|
955
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
776
956
|
const fileSystem = dependencies.fileSystem || fs;
|
|
957
|
+
const currentDevice = await getCurrentDeviceProfile(projectPath, {
|
|
958
|
+
fileSystem,
|
|
959
|
+
persistIfMissing: true
|
|
960
|
+
});
|
|
777
961
|
const { store, graph } = await requireAppGraph(appRef, dependencies);
|
|
778
962
|
const nextPayload = graphToRegisterPayload(graph);
|
|
779
963
|
const state = getRuntimeProjectionState(graph);
|
|
@@ -800,6 +984,8 @@ async function runAppRuntimeUninstallCommand(options = {}, dependencies = {}) {
|
|
|
800
984
|
status: 'not-installed',
|
|
801
985
|
release_id: null,
|
|
802
986
|
install_root: null,
|
|
987
|
+
machine_id: currentDevice.device_id,
|
|
988
|
+
hostname: currentDevice.hostname,
|
|
803
989
|
uninstalled_at: new Date().toISOString(),
|
|
804
990
|
source: 'sce app runtime uninstall',
|
|
805
991
|
previous_release_id: targetReleaseId
|
|
@@ -819,6 +1005,53 @@ async function runAppRuntimeUninstallCommand(options = {}, dependencies = {}) {
|
|
|
819
1005
|
return payload;
|
|
820
1006
|
}
|
|
821
1007
|
|
|
1008
|
+
async function runAppInstallStateListCommand(options = {}, dependencies = {}) {
|
|
1009
|
+
const store = createStore(dependencies);
|
|
1010
|
+
const currentDevice = await getCurrentDeviceProfile(dependencies.projectPath || process.cwd(), {
|
|
1011
|
+
fileSystem: dependencies.fileSystem || fs,
|
|
1012
|
+
persistIfMissing: false
|
|
1013
|
+
});
|
|
1014
|
+
const bundles = await store.listAppBundles({
|
|
1015
|
+
limit: normalizePositiveInteger(options.limit, 100, 1000),
|
|
1016
|
+
status: options.status,
|
|
1017
|
+
environment: options.environment,
|
|
1018
|
+
workspaceId: options.workspaceId,
|
|
1019
|
+
query: options.query
|
|
1020
|
+
});
|
|
1021
|
+
const installStatusFilter = normalizeString(options.installStatus);
|
|
1022
|
+
const items = (Array.isArray(bundles) ? bundles : [])
|
|
1023
|
+
.map((bundle) => buildAppInstallStateItem(bundle, currentDevice))
|
|
1024
|
+
.filter((item) => !installStatusFilter || item.install_status === installStatusFilter);
|
|
1025
|
+
|
|
1026
|
+
const payload = {
|
|
1027
|
+
mode: 'app-install-state-list',
|
|
1028
|
+
generated_at: new Date().toISOString(),
|
|
1029
|
+
query: {
|
|
1030
|
+
limit: normalizePositiveInteger(options.limit, 100, 1000),
|
|
1031
|
+
status: normalizeString(options.status) || null,
|
|
1032
|
+
environment: normalizeString(options.environment) || null,
|
|
1033
|
+
workspace_id: normalizeString(options.workspaceId) || null,
|
|
1034
|
+
query: normalizeString(options.query) || null,
|
|
1035
|
+
install_status: installStatusFilter || null
|
|
1036
|
+
},
|
|
1037
|
+
current_device: currentDevice,
|
|
1038
|
+
summary: {
|
|
1039
|
+
total: items.length,
|
|
1040
|
+
installed_count: items.filter((item) => item.install_status === 'installed').length,
|
|
1041
|
+
not_installed_count: items.filter((item) => item.install_status !== 'installed').length,
|
|
1042
|
+
active_count: items.filter((item) => Boolean(item.active_release_id)).length,
|
|
1043
|
+
current_device_id: currentDevice.device_id
|
|
1044
|
+
},
|
|
1045
|
+
items,
|
|
1046
|
+
view_model: {
|
|
1047
|
+
type: 'table',
|
|
1048
|
+
columns: ['app_id', 'app_key', 'app_name', 'install_status', 'installed_release_id', 'active_release_id', 'machine_scope', 'environment']
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
printPayload(payload, options, 'App Install State');
|
|
1052
|
+
return payload;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
822
1055
|
function safeRun(handler, options = {}, context = 'app command') {
|
|
823
1056
|
Promise.resolve(handler(options))
|
|
824
1057
|
.catch((error) => {
|
|
@@ -868,6 +1101,34 @@ function registerAppCommands(program) {
|
|
|
868
1101
|
.option('--json', 'Print machine-readable JSON output')
|
|
869
1102
|
.action((options) => safeRun(runAppBundleRegisterCommand, options, 'app bundle register'));
|
|
870
1103
|
|
|
1104
|
+
const collection = app
|
|
1105
|
+
.command('collection')
|
|
1106
|
+
.description('Inspect file-backed app collection intent definitions');
|
|
1107
|
+
|
|
1108
|
+
collection
|
|
1109
|
+
.command('list')
|
|
1110
|
+
.description('List app collection definitions from .sce/app/collections')
|
|
1111
|
+
.option('--limit <n>', 'Maximum rows', '100')
|
|
1112
|
+
.option('--status <status>', 'Filter by collection status')
|
|
1113
|
+
.option('--query <text>', 'Free-text query against id/name/description/tags')
|
|
1114
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1115
|
+
.action((options) => safeRun(runAppCollectionListCommand, options, 'app collection list'));
|
|
1116
|
+
|
|
1117
|
+
collection
|
|
1118
|
+
.command('show')
|
|
1119
|
+
.description('Show one app collection definition')
|
|
1120
|
+
.requiredOption('--collection <id>', 'Collection id or file basename')
|
|
1121
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1122
|
+
.action((options) => safeRun(runAppCollectionShowCommand, options, 'app collection show'));
|
|
1123
|
+
|
|
1124
|
+
collection
|
|
1125
|
+
.command('apply')
|
|
1126
|
+
.description('Build a plan-first apply diff for one app collection')
|
|
1127
|
+
.requiredOption('--collection <id>', 'Collection id or file basename')
|
|
1128
|
+
.option('--execute', 'Reserved for future explicit execution; currently returns a blocked plan')
|
|
1129
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1130
|
+
.action((options) => safeRun(runAppCollectionApplyCommand, options, 'app collection apply'));
|
|
1131
|
+
|
|
871
1132
|
const registry = app
|
|
872
1133
|
.command('registry')
|
|
873
1134
|
.description('Manage remote app bundle and service catalog registry configuration');
|
|
@@ -965,6 +1226,22 @@ function registerAppCommands(program) {
|
|
|
965
1226
|
.option('--json', 'Print machine-readable JSON output')
|
|
966
1227
|
.action((options) => safeRun(runAppRuntimeUninstallCommand, options, 'app runtime uninstall'));
|
|
967
1228
|
|
|
1229
|
+
const installState = app
|
|
1230
|
+
.command('install-state')
|
|
1231
|
+
.description('Inspect current device install state across app bundles');
|
|
1232
|
+
|
|
1233
|
+
installState
|
|
1234
|
+
.command('list')
|
|
1235
|
+
.description('List current device install state across app bundles')
|
|
1236
|
+
.option('--limit <n>', 'Maximum rows', '100')
|
|
1237
|
+
.option('--status <status>', 'Filter by app bundle status')
|
|
1238
|
+
.option('--environment <env>', 'Filter by environment')
|
|
1239
|
+
.option('--workspace-id <id>', 'Filter by workspace id')
|
|
1240
|
+
.option('--query <text>', 'Free-text query against id/key/name')
|
|
1241
|
+
.option('--install-status <status>', 'Filter by install status')
|
|
1242
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
1243
|
+
.action((options) => safeRun(runAppInstallStateListCommand, options, 'app install-state list'));
|
|
1244
|
+
|
|
968
1245
|
const engineering = app
|
|
969
1246
|
.command('engineering')
|
|
970
1247
|
.description('Manage engineering project projection for one app bundle');
|
|
@@ -1020,6 +1297,9 @@ module.exports = {
|
|
|
1020
1297
|
runAppBundleListCommand,
|
|
1021
1298
|
runAppBundleShowCommand,
|
|
1022
1299
|
runAppBundleRegisterCommand,
|
|
1300
|
+
runAppCollectionListCommand,
|
|
1301
|
+
runAppCollectionShowCommand,
|
|
1302
|
+
runAppCollectionApplyCommand,
|
|
1023
1303
|
runAppRegistryStatusCommand,
|
|
1024
1304
|
runAppRegistryConfigureCommand,
|
|
1025
1305
|
runAppRegistrySyncBundlesCommand,
|
|
@@ -1030,6 +1310,7 @@ module.exports = {
|
|
|
1030
1310
|
runAppRuntimeInstallCommand,
|
|
1031
1311
|
runAppRuntimeActivateCommand,
|
|
1032
1312
|
runAppRuntimeUninstallCommand,
|
|
1313
|
+
runAppInstallStateListCommand,
|
|
1033
1314
|
runAppEngineeringShowCommand,
|
|
1034
1315
|
runAppEngineeringAttachCommand,
|
|
1035
1316
|
runAppEngineeringHydrateCommand,
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { ensureWriteAuthorization } = require('../security/write-authorization');
|
|
5
|
+
const { getCurrentDeviceProfile } = require('../device/current-device');
|
|
6
|
+
const { loadDeviceOverride, upsertDeviceOverride } = require('../device/device-override-store');
|
|
7
|
+
|
|
8
|
+
function normalizeString(value) {
|
|
9
|
+
if (typeof value !== 'string') {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
return value.trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function printDevicePayload(payload, options = {}) {
|
|
16
|
+
if (options.json) {
|
|
17
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(chalk.blue('Current Device'));
|
|
22
|
+
console.log(` ID: ${payload.summary.device_id}`);
|
|
23
|
+
console.log(` Label: ${payload.summary.label}`);
|
|
24
|
+
console.log(` Hostname: ${payload.summary.hostname}`);
|
|
25
|
+
console.log(` Platform: ${payload.summary.platform}`);
|
|
26
|
+
console.log(` Arch: ${payload.summary.arch}`);
|
|
27
|
+
console.log(` User: ${payload.summary.user}`);
|
|
28
|
+
console.log(` Capability Tags: ${payload.summary.capability_tag_count}`);
|
|
29
|
+
console.log(` Identity Source: ${payload.summary.identity_source}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function printDeviceOverridePayload(payload, options = {}) {
|
|
33
|
+
if (options.json) {
|
|
34
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(chalk.blue('Device Override'));
|
|
39
|
+
console.log(` Source File: ${payload.summary.source_file || '(not created)'}`);
|
|
40
|
+
console.log(` Removed Apps: ${payload.summary.removed_app_count}`);
|
|
41
|
+
console.log(` Added Apps: ${payload.summary.added_app_count}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function ensureAuthorized(action, options = {}, dependencies = {}) {
|
|
45
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
46
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
47
|
+
const env = dependencies.env || process.env;
|
|
48
|
+
await ensureWriteAuthorization(action, {
|
|
49
|
+
authLease: options.authLease,
|
|
50
|
+
authPassword: options.authPassword,
|
|
51
|
+
actor: options.actor
|
|
52
|
+
}, {
|
|
53
|
+
projectPath,
|
|
54
|
+
fileSystem,
|
|
55
|
+
env
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function runDeviceCurrentCommand(options = {}, dependencies = {}) {
|
|
60
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
61
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
62
|
+
const device = await getCurrentDeviceProfile(projectPath, {
|
|
63
|
+
fileSystem,
|
|
64
|
+
persistIfMissing: false
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const payload = {
|
|
68
|
+
mode: 'device-current',
|
|
69
|
+
generated_at: new Date().toISOString(),
|
|
70
|
+
summary: {
|
|
71
|
+
device_id: device.device_id,
|
|
72
|
+
label: device.label,
|
|
73
|
+
hostname: device.hostname,
|
|
74
|
+
platform: device.platform,
|
|
75
|
+
arch: device.arch,
|
|
76
|
+
user: device.user,
|
|
77
|
+
capability_tag_count: Array.isArray(device.capability_tags) ? device.capability_tags.length : 0,
|
|
78
|
+
identity_source: device.identity_source
|
|
79
|
+
},
|
|
80
|
+
device
|
|
81
|
+
};
|
|
82
|
+
printDevicePayload(payload, options);
|
|
83
|
+
return payload;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function runDeviceOverrideShowCommand(options = {}, dependencies = {}) {
|
|
87
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
88
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
89
|
+
const override = await loadDeviceOverride(projectPath, { fileSystem });
|
|
90
|
+
const payload = {
|
|
91
|
+
mode: 'device-override-show',
|
|
92
|
+
generated_at: new Date().toISOString(),
|
|
93
|
+
summary: {
|
|
94
|
+
source_file: override.source_file,
|
|
95
|
+
removed_app_count: Array.isArray(override.removed_apps) ? override.removed_apps.length : 0,
|
|
96
|
+
added_app_count: Array.isArray(override.added_apps) ? override.added_apps.length : 0
|
|
97
|
+
},
|
|
98
|
+
override
|
|
99
|
+
};
|
|
100
|
+
printDeviceOverridePayload(payload, options);
|
|
101
|
+
return payload;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function runDeviceOverrideUpsertCommand(options = {}, dependencies = {}) {
|
|
105
|
+
const inputFile = normalizeString(options.input);
|
|
106
|
+
if (!inputFile) {
|
|
107
|
+
throw new Error('--input is required');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await ensureAuthorized('device:override:upsert', options, dependencies);
|
|
111
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
112
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
113
|
+
const resolvedInput = path.isAbsolute(inputFile)
|
|
114
|
+
? inputFile
|
|
115
|
+
: path.join(projectPath, inputFile);
|
|
116
|
+
const patch = await fileSystem.readJson(resolvedInput);
|
|
117
|
+
const override = await upsertDeviceOverride(projectPath, patch, { fileSystem });
|
|
118
|
+
const payload = {
|
|
119
|
+
mode: 'device-override-upsert',
|
|
120
|
+
success: true,
|
|
121
|
+
generated_at: new Date().toISOString(),
|
|
122
|
+
input_file: resolvedInput,
|
|
123
|
+
summary: {
|
|
124
|
+
source_file: override.source_file,
|
|
125
|
+
removed_app_count: Array.isArray(override.removed_apps) ? override.removed_apps.length : 0,
|
|
126
|
+
added_app_count: Array.isArray(override.added_apps) ? override.added_apps.length : 0
|
|
127
|
+
},
|
|
128
|
+
override
|
|
129
|
+
};
|
|
130
|
+
printDeviceOverridePayload(payload, options);
|
|
131
|
+
return payload;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function safeRun(handler, options = {}, context = 'device command') {
|
|
135
|
+
Promise.resolve(handler(options))
|
|
136
|
+
.catch((error) => {
|
|
137
|
+
if (options.json) {
|
|
138
|
+
console.log(JSON.stringify({ success: false, error: error.message }, null, 2));
|
|
139
|
+
} else {
|
|
140
|
+
console.error(chalk.red(`${context} failed:`), error.message);
|
|
141
|
+
}
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function registerDeviceCommands(program) {
|
|
147
|
+
const device = program
|
|
148
|
+
.command('device')
|
|
149
|
+
.description('Inspect current device identity, capability tags, and local override state');
|
|
150
|
+
|
|
151
|
+
device
|
|
152
|
+
.command('current')
|
|
153
|
+
.description('Show current device identity and capability tags')
|
|
154
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
155
|
+
.action(async (options) => {
|
|
156
|
+
try {
|
|
157
|
+
await runDeviceCurrentCommand(options);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (options.json) {
|
|
160
|
+
console.log(JSON.stringify({ success: false, error: error.message }, null, 2));
|
|
161
|
+
} else {
|
|
162
|
+
console.error(chalk.red('device current failed:'), error.message);
|
|
163
|
+
}
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const override = device
|
|
169
|
+
.command('override')
|
|
170
|
+
.description('Inspect and update local device override policy');
|
|
171
|
+
|
|
172
|
+
override
|
|
173
|
+
.command('show')
|
|
174
|
+
.description('Show current local device override policy')
|
|
175
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
176
|
+
.action((options) => safeRun(runDeviceOverrideShowCommand, options, 'device override show'));
|
|
177
|
+
|
|
178
|
+
override
|
|
179
|
+
.command('upsert')
|
|
180
|
+
.description('Merge local device override fields from JSON input')
|
|
181
|
+
.requiredOption('--input <path>', 'JSON file with override fields to upsert')
|
|
182
|
+
.option('--auth-lease <lease-id>', 'Write authorization lease id')
|
|
183
|
+
.option('--auth-password <password>', 'Inline auth password if policy allows')
|
|
184
|
+
.option('--actor <actor>', 'Audit actor override')
|
|
185
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
186
|
+
.action((options) => safeRun(runDeviceOverrideUpsertCommand, options, 'device override upsert'));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
runDeviceCurrentCommand,
|
|
191
|
+
runDeviceOverrideShowCommand,
|
|
192
|
+
runDeviceOverrideUpsertCommand,
|
|
193
|
+
registerDeviceCommands
|
|
194
|
+
};
|