@revoengine/cli 1.0.6 → 1.0.8
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/dist/src/commands/component.js +191 -19
- package/dist/src/types.d.ts +1 -0
- package/dist/src/ui.js +3 -3
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
3
4
|
import { ApiError, PermissionDeniedError } from "../client.js";
|
|
4
5
|
import { hashComponentSource, hashStable, readComponentLock, sanitizeComponentSource, upsertComponentLockEntry, } from "../component-lock.js";
|
|
5
6
|
import { buildSandboxDebugStreamUrl, buildSandboxDebugUrl, extractSandboxEndpoint, resolveProjectRoot, resolveProjectWorkspace, } from "../project.js";
|
|
@@ -297,6 +298,82 @@ function findComponentManifestsById(cwd, componentId) {
|
|
|
297
298
|
return getComponentIdFromPath(path.dirname(manifestPath)).includes(componentId);
|
|
298
299
|
});
|
|
299
300
|
}
|
|
301
|
+
function isRelativeInside(parent, child) {
|
|
302
|
+
const relative = path.relative(parent, child);
|
|
303
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
304
|
+
}
|
|
305
|
+
function readGitChangedPaths(cwd, workspaceRoot, base) {
|
|
306
|
+
const rootRelativePath = path.relative(cwd, workspaceRoot) || 'Components';
|
|
307
|
+
const args = base.includes('..')
|
|
308
|
+
? ['diff', '--name-only', base, '--', rootRelativePath]
|
|
309
|
+
: ['diff', '--name-only', `${base}...HEAD`, '--', rootRelativePath];
|
|
310
|
+
try {
|
|
311
|
+
return execFileSync('git', args, {
|
|
312
|
+
cwd,
|
|
313
|
+
encoding: 'utf8',
|
|
314
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
315
|
+
})
|
|
316
|
+
.split(/\r?\n/)
|
|
317
|
+
.map((entry) => entry.trim())
|
|
318
|
+
.filter(Boolean);
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
322
|
+
throw new Error(`Unable to resolve changed Revo components from git base "${base}": ${message}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function componentIdFromChangedPath(changedPath) {
|
|
326
|
+
const segments = changedPath.split(/[\\/]+/);
|
|
327
|
+
const componentsIndex = segments.indexOf('Components');
|
|
328
|
+
if (componentsIndex < 0) {
|
|
329
|
+
return '';
|
|
330
|
+
}
|
|
331
|
+
for (const segment of segments.slice(componentsIndex + 1)) {
|
|
332
|
+
const componentId = getComponentIdFromPath(segment);
|
|
333
|
+
if (componentId !== segment) {
|
|
334
|
+
return componentId;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return '';
|
|
338
|
+
}
|
|
339
|
+
function resolveChangedComponentScope(context) {
|
|
340
|
+
const enabled = readBoolFlag(context.args, ['changed', 'changed-only']);
|
|
341
|
+
const base = readFlag(context.args, ['base', 'git-base']) || 'origin/develop';
|
|
342
|
+
if (!enabled) {
|
|
343
|
+
return {
|
|
344
|
+
enabled,
|
|
345
|
+
base,
|
|
346
|
+
componentIds: new Set(),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const workspaceRoot = getWorkspaceRoot(context.cwd);
|
|
350
|
+
const changedPaths = readGitChangedPaths(context.cwd, workspaceRoot, base);
|
|
351
|
+
const manifestPaths = getComponentManifestPaths(workspaceRoot);
|
|
352
|
+
const componentIds = new Set();
|
|
353
|
+
for (const changedPath of changedPaths) {
|
|
354
|
+
const absoluteChangedPath = path.resolve(context.cwd, changedPath);
|
|
355
|
+
for (const manifestPath of manifestPaths) {
|
|
356
|
+
const componentDir = path.dirname(manifestPath);
|
|
357
|
+
if (!isRelativeInside(componentDir, absoluteChangedPath)) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const component = readWorkspaceComponentSafe(manifestPath);
|
|
361
|
+
const componentId = component ? componentIdOf(component) : '';
|
|
362
|
+
if (componentId) {
|
|
363
|
+
componentIds.add(componentId);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const deletedComponentId = componentIdFromChangedPath(changedPath);
|
|
367
|
+
if (deletedComponentId) {
|
|
368
|
+
componentIds.add(deletedComponentId);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
enabled,
|
|
373
|
+
base,
|
|
374
|
+
componentIds,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
300
377
|
function unwrapList(value) {
|
|
301
378
|
if (Array.isArray(value)) {
|
|
302
379
|
return value;
|
|
@@ -725,6 +802,9 @@ async function pushSingleComponent(context, manifestPath) {
|
|
|
725
802
|
throw new Error(`Missing component name in ${manifestPath}.`);
|
|
726
803
|
}
|
|
727
804
|
const elements = (component.elements || []).map((element) => ({
|
|
805
|
+
...(typeof element.componentElementId === 'string' && element.componentElementId
|
|
806
|
+
? { componentElementId: element.componentElementId }
|
|
807
|
+
: {}),
|
|
728
808
|
key: element.key,
|
|
729
809
|
desc: element.desc,
|
|
730
810
|
details: element.details || '',
|
|
@@ -785,6 +865,13 @@ async function pushSingleComponent(context, manifestPath) {
|
|
|
785
865
|
targetPath,
|
|
786
866
|
reason: 'no changes',
|
|
787
867
|
};
|
|
868
|
+
try {
|
|
869
|
+
const remote = unwrapComponent(await client.getComponent(componentId));
|
|
870
|
+
upsertLockFromRemote(context.cwd, remote, targetPath);
|
|
871
|
+
}
|
|
872
|
+
catch {
|
|
873
|
+
upsertLockFromRemote(context.cwd, component, targetPath);
|
|
874
|
+
}
|
|
788
875
|
printSyncStatus(println, {
|
|
789
876
|
status: 'Skipped',
|
|
790
877
|
direction: 'push',
|
|
@@ -850,11 +937,22 @@ async function pushSingleComponent(context, manifestPath) {
|
|
|
850
937
|
async function pushAllComponents(context, options) {
|
|
851
938
|
const { cwd, println } = context;
|
|
852
939
|
const root = getWorkspaceRoot(cwd);
|
|
853
|
-
|
|
940
|
+
let manifests = getComponentManifestPaths(root);
|
|
854
941
|
if (manifests.length === 0) {
|
|
855
942
|
println(`No component.json files found in ${path.relative(cwd, root) || 'Components'}.`);
|
|
856
943
|
return [];
|
|
857
944
|
}
|
|
945
|
+
if (options.scope?.enabled) {
|
|
946
|
+
manifests = manifests.filter((manifestPath) => {
|
|
947
|
+
const component = readWorkspaceComponentSafe(manifestPath);
|
|
948
|
+
const componentId = component ? componentIdOf(component) : '';
|
|
949
|
+
return componentId && options.scope?.componentIds.has(componentId);
|
|
950
|
+
});
|
|
951
|
+
if (manifests.length === 0) {
|
|
952
|
+
println(`No changed Revo components found for git base ${options.scope.base}.`);
|
|
953
|
+
return [];
|
|
954
|
+
}
|
|
955
|
+
}
|
|
858
956
|
await confirmBulkAction(context, 'push', manifests.length, options.force || options.yes);
|
|
859
957
|
const startedAt = Date.now();
|
|
860
958
|
const results = [];
|
|
@@ -867,7 +965,7 @@ async function pushAllComponents(context, options) {
|
|
|
867
965
|
printSummary(context.println, 'Deployed', results, Date.now() - startedAt);
|
|
868
966
|
return results;
|
|
869
967
|
}
|
|
870
|
-
async function buildComponentPlan(context) {
|
|
968
|
+
async function buildComponentPlan(context, scope = { enabled: false, base: '', componentIds: new Set() }) {
|
|
871
969
|
const projectRoot = getProjectRoot(context.cwd);
|
|
872
970
|
const lock = readComponentLock(projectRoot);
|
|
873
971
|
const root = getWorkspaceRoot(context.cwd);
|
|
@@ -947,6 +1045,7 @@ async function buildComponentPlan(context) {
|
|
|
947
1045
|
status = 'conflict';
|
|
948
1046
|
reason = 'local and remote changed since lock';
|
|
949
1047
|
}
|
|
1048
|
+
const inScope = !scope.enabled || scope.componentIds.has(componentId);
|
|
950
1049
|
return {
|
|
951
1050
|
componentId,
|
|
952
1051
|
componentName,
|
|
@@ -958,12 +1057,20 @@ async function buildComponentPlan(context) {
|
|
|
958
1057
|
remoteHash,
|
|
959
1058
|
remoteVersion: remote ? componentVersion(remote.component) : lockEntry?.remoteVersion ?? null,
|
|
960
1059
|
reason,
|
|
1060
|
+
...(scope.enabled ? { inScope } : {}),
|
|
961
1061
|
};
|
|
962
1062
|
});
|
|
963
1063
|
return {
|
|
964
1064
|
schemaVersion: 1,
|
|
965
1065
|
generatedAt: new Date().toISOString(),
|
|
966
1066
|
workspaceRoot: path.relative(context.cwd, root) || 'Components',
|
|
1067
|
+
...(scope.enabled ? {
|
|
1068
|
+
scope: {
|
|
1069
|
+
mode: 'changed',
|
|
1070
|
+
base: scope.base,
|
|
1071
|
+
componentIds: [...scope.componentIds].sort(),
|
|
1072
|
+
},
|
|
1073
|
+
} : {}),
|
|
967
1074
|
counts: items.reduce((counts, item) => {
|
|
968
1075
|
counts[item.status] += 1;
|
|
969
1076
|
return counts;
|
|
@@ -1047,6 +1154,47 @@ function buildDebugElements(component) {
|
|
|
1047
1154
|
};
|
|
1048
1155
|
});
|
|
1049
1156
|
}
|
|
1157
|
+
function resolveElementSourcePath(manifestPath, component, element) {
|
|
1158
|
+
const componentDir = path.dirname(manifestPath);
|
|
1159
|
+
const detailDirs = [
|
|
1160
|
+
path.join(componentDir, 'elements'),
|
|
1161
|
+
path.join(componentDir, 'details'),
|
|
1162
|
+
].filter((dir, index, list) => list.indexOf(dir) === index);
|
|
1163
|
+
const extension = componentTypeToExtension(normalizeComponentType(component));
|
|
1164
|
+
const fileName = `${element.order}_${element.key}.${extension}`;
|
|
1165
|
+
for (const detailDir of detailDirs) {
|
|
1166
|
+
const preferredPath = path.join(detailDir, fileName);
|
|
1167
|
+
if (fs.existsSync(preferredPath)) {
|
|
1168
|
+
return preferredPath;
|
|
1169
|
+
}
|
|
1170
|
+
if (fs.existsSync(detailDir)) {
|
|
1171
|
+
const match = fs.readdirSync(detailDir).find((entry) => entry.startsWith(`${element.order}_${element.key}.`));
|
|
1172
|
+
if (match) {
|
|
1173
|
+
return path.join(detailDir, match);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
function buildDebugSourceComponent(role, manifestPath, component) {
|
|
1180
|
+
return {
|
|
1181
|
+
role,
|
|
1182
|
+
componentId: component.componentId || component.id || '',
|
|
1183
|
+
componentName: component.name || '',
|
|
1184
|
+
type: normalizeComponentType(component),
|
|
1185
|
+
manifestPath,
|
|
1186
|
+
elements: [...(component.elements || [])].sort((left, right) => left.order - right.order).map((element) => {
|
|
1187
|
+
const sourcePath = resolveElementSourcePath(manifestPath, component, element);
|
|
1188
|
+
const details = element.details || '';
|
|
1189
|
+
return {
|
|
1190
|
+
key: element.key,
|
|
1191
|
+
order: element.order,
|
|
1192
|
+
sourcePath,
|
|
1193
|
+
bytes: Buffer.byteLength(details, 'utf8'),
|
|
1194
|
+
};
|
|
1195
|
+
}),
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1050
1198
|
function isDebugLibraryType(type) {
|
|
1051
1199
|
return type === 'CODE_TS_LIB' || type === 'CODE_JS_LIB';
|
|
1052
1200
|
}
|
|
@@ -1086,19 +1234,28 @@ function buildDebugPayload(cwd, component, input) {
|
|
|
1086
1234
|
}
|
|
1087
1235
|
function collectDebugExtraLibs(cwd, currentComponentId) {
|
|
1088
1236
|
return getComponentManifestPaths(getWorkspaceRoot(cwd))
|
|
1089
|
-
.map((manifestPath) =>
|
|
1090
|
-
|
|
1091
|
-
|
|
1237
|
+
.map((manifestPath) => {
|
|
1238
|
+
const component = readWorkspaceComponentSafe(manifestPath);
|
|
1239
|
+
return component ? { manifestPath, component } : null;
|
|
1240
|
+
})
|
|
1241
|
+
.filter((entry) => Boolean(entry))
|
|
1242
|
+
.filter(({ component }) => {
|
|
1092
1243
|
const type = normalizeComponentType(component);
|
|
1093
1244
|
const componentId = component.componentId || component.id || '';
|
|
1094
1245
|
return isDebugLibraryType(type) && componentId !== currentComponentId && Boolean(component.name);
|
|
1095
1246
|
})
|
|
1096
|
-
.map((component) => ({
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1247
|
+
.map(({ manifestPath, component }) => ({
|
|
1248
|
+
payload: {
|
|
1249
|
+
name: component.name,
|
|
1250
|
+
type: normalizeComponentType(component),
|
|
1251
|
+
elements: buildDebugElements(component),
|
|
1252
|
+
},
|
|
1253
|
+
source: buildDebugSourceComponent('extraLib', manifestPath, component),
|
|
1100
1254
|
}));
|
|
1101
1255
|
}
|
|
1256
|
+
function writeRawDebugRequestDump(options) {
|
|
1257
|
+
process.stderr.write(`[debug-request] ${JSON.stringify(options, null, 2)}\n`);
|
|
1258
|
+
}
|
|
1102
1259
|
function formatProcessLog(log) {
|
|
1103
1260
|
if (!log || typeof log !== 'object' || Array.isArray(log)) {
|
|
1104
1261
|
return String(log ?? '');
|
|
@@ -1158,21 +1315,34 @@ async function debugSingleComponent(context, componentId) {
|
|
|
1158
1315
|
const memory = parseDebugNumber(context.args, ['memory', 'm'], 128, 1024, 'Memory');
|
|
1159
1316
|
const stream = readBoolFlag(context.args, ['stream']);
|
|
1160
1317
|
const includeExtraLibs = !readBoolFlag(context.args, ['no-extra-libs']);
|
|
1318
|
+
const rawDebug = readBoolFlag(context.args, ['raw-debug', 'debug-raw', 'debug-payload']);
|
|
1161
1319
|
const profile = await context.client.me();
|
|
1162
1320
|
const sandboxEndpoint = extractSandboxEndpoint(profile);
|
|
1163
1321
|
if (!sandboxEndpoint) {
|
|
1164
1322
|
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run.');
|
|
1165
1323
|
}
|
|
1324
|
+
const extraLibs = includeExtraLibs ? collectDebugExtraLibs(context.cwd, componentKey) : [];
|
|
1166
1325
|
const payload = buildDebugPayload(context.cwd, component, {
|
|
1167
1326
|
inputs,
|
|
1168
1327
|
timeout,
|
|
1169
1328
|
memory,
|
|
1170
|
-
extraLibs:
|
|
1329
|
+
extraLibs: extraLibs.map((entry) => entry.payload),
|
|
1171
1330
|
});
|
|
1331
|
+
const endpoint = stream ? buildSandboxDebugStreamUrl(sandboxEndpoint) : buildSandboxDebugUrl(sandboxEndpoint);
|
|
1332
|
+
if (rawDebug) {
|
|
1333
|
+
writeRawDebugRequestDump({
|
|
1334
|
+
endpoint,
|
|
1335
|
+
cwd: context.cwd,
|
|
1336
|
+
workspaceRoot: getWorkspaceRoot(context.cwd),
|
|
1337
|
+
selected: buildDebugSourceComponent('selected', manifestPath, component),
|
|
1338
|
+
extraLibs: extraLibs.map((entry) => entry.source),
|
|
1339
|
+
payload,
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1172
1342
|
if (stream) {
|
|
1173
1343
|
let result;
|
|
1174
1344
|
let done = false;
|
|
1175
|
-
for await (const event of context.client.debugComponentStream(
|
|
1345
|
+
for await (const event of context.client.debugComponentStream(endpoint, payload)) {
|
|
1176
1346
|
if (event.event === 'log') {
|
|
1177
1347
|
process.stderr.write(`${formatProcessLog(event.data)}\n`);
|
|
1178
1348
|
}
|
|
@@ -1193,7 +1363,7 @@ async function debugSingleComponent(context, componentId) {
|
|
|
1193
1363
|
context.print(extractResultPayload(result));
|
|
1194
1364
|
return;
|
|
1195
1365
|
}
|
|
1196
|
-
const response = await context.client.debugComponent(
|
|
1366
|
+
const response = await context.client.debugComponent(endpoint, payload);
|
|
1197
1367
|
writeDebugOutputFile(context.cwd, response);
|
|
1198
1368
|
context.print(extractResultPayload(response));
|
|
1199
1369
|
}
|
|
@@ -1207,16 +1377,17 @@ export async function handleComponentCommand(context) {
|
|
|
1207
1377
|
const stale = readBoolFlag(args, ['stale', 's']);
|
|
1208
1378
|
const json = readBoolFlag(args, ['json']);
|
|
1209
1379
|
const strict = readBoolFlag(args, ['strict']);
|
|
1380
|
+
const changedScope = resolveChangedComponentScope(context);
|
|
1210
1381
|
if (subcommand === 'pull-all') {
|
|
1211
1382
|
await pullAllComponents(context, { force, stale, yes });
|
|
1212
1383
|
return;
|
|
1213
1384
|
}
|
|
1214
1385
|
if (subcommand === 'push-all') {
|
|
1215
|
-
await pushAllComponents(context, { force, yes });
|
|
1386
|
+
await pushAllComponents(context, { force, yes, scope: changedScope });
|
|
1216
1387
|
return;
|
|
1217
1388
|
}
|
|
1218
1389
|
if (subcommand === 'plan') {
|
|
1219
|
-
const plan = await buildComponentPlan(context);
|
|
1390
|
+
const plan = await buildComponentPlan(context, changedScope);
|
|
1220
1391
|
if (json) {
|
|
1221
1392
|
context.print(plan);
|
|
1222
1393
|
}
|
|
@@ -1224,10 +1395,11 @@ export async function handleComponentCommand(context) {
|
|
|
1224
1395
|
printPlan(context, plan);
|
|
1225
1396
|
}
|
|
1226
1397
|
if (strict) {
|
|
1227
|
-
const blocking = plan.items.filter((item) => (
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1398
|
+
const blocking = plan.items.filter((item) => ((!changedScope.enabled || item.inScope)
|
|
1399
|
+
&& (item.status === 'conflict'
|
|
1400
|
+
|| item.status === 'missing-lock'
|
|
1401
|
+
|| item.status === 'remote-changed'
|
|
1402
|
+
|| item.status === 'safe-update')));
|
|
1231
1403
|
if (blocking.length > 0) {
|
|
1232
1404
|
throw new Error(`Component plan is not safe: ${blocking.length} blocking item(s).`);
|
|
1233
1405
|
}
|
|
@@ -1260,7 +1432,7 @@ export async function handleComponentCommand(context) {
|
|
|
1260
1432
|
throw new Error('Provide --id <componentId> or --all.');
|
|
1261
1433
|
}
|
|
1262
1434
|
if (all) {
|
|
1263
|
-
await pushAllComponents(context, { force, yes });
|
|
1435
|
+
await pushAllComponents(context, { force, yes, scope: changedScope });
|
|
1264
1436
|
return;
|
|
1265
1437
|
}
|
|
1266
1438
|
const manifests = targets.flatMap((componentId) => findComponentManifestsById(context.cwd, componentId));
|
package/dist/src/types.d.ts
CHANGED
package/dist/src/ui.js
CHANGED
|
@@ -108,12 +108,12 @@ function renderCommandCatalog() {
|
|
|
108
108
|
commandRow('revo endpoints', 'List available API endpoints'),
|
|
109
109
|
'',
|
|
110
110
|
paintHeader('Components'),
|
|
111
|
-
commandRow('revo component plan --all [--json] [--strict]', 'Preview local/remote sync status'),
|
|
111
|
+
commandRow('revo component plan --all [--json] [--strict] [--changed --base <ref>]', 'Preview local/remote sync status'),
|
|
112
112
|
commandRow('revo component pull <componentId...> [--force]', 'Pull one or more components safely'),
|
|
113
113
|
commandRow('revo component pull --all [--yes] [--force]', 'Pull every available component safely'),
|
|
114
114
|
commandRow('revo component push <componentId...>', 'Push one or more local components'),
|
|
115
|
-
commandRow('revo component push --all [--yes] [--force]', 'Push
|
|
116
|
-
commandRow('revo component debug <componentId> [BODY] [--stream]', 'Debug one local component against sandbox'),
|
|
115
|
+
commandRow('revo component push --all [--yes] [--force] [--changed --base <ref>]', 'Push local components with lock checks'),
|
|
116
|
+
commandRow('revo component debug <componentId> [BODY] [--stream] [--raw-debug]', 'Debug one local component against sandbox'),
|
|
117
117
|
'',
|
|
118
118
|
paintHeader('Low-Level'),
|
|
119
119
|
commandRow('revo search <CODE|SIMPLE> <term>', 'Search code references or all platform content'),
|