@revoengine/cli 1.0.7 → 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 +181 -19
- 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;
|
|
@@ -860,11 +937,22 @@ async function pushSingleComponent(context, manifestPath) {
|
|
|
860
937
|
async function pushAllComponents(context, options) {
|
|
861
938
|
const { cwd, println } = context;
|
|
862
939
|
const root = getWorkspaceRoot(cwd);
|
|
863
|
-
|
|
940
|
+
let manifests = getComponentManifestPaths(root);
|
|
864
941
|
if (manifests.length === 0) {
|
|
865
942
|
println(`No component.json files found in ${path.relative(cwd, root) || 'Components'}.`);
|
|
866
943
|
return [];
|
|
867
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
|
+
}
|
|
868
956
|
await confirmBulkAction(context, 'push', manifests.length, options.force || options.yes);
|
|
869
957
|
const startedAt = Date.now();
|
|
870
958
|
const results = [];
|
|
@@ -877,7 +965,7 @@ async function pushAllComponents(context, options) {
|
|
|
877
965
|
printSummary(context.println, 'Deployed', results, Date.now() - startedAt);
|
|
878
966
|
return results;
|
|
879
967
|
}
|
|
880
|
-
async function buildComponentPlan(context) {
|
|
968
|
+
async function buildComponentPlan(context, scope = { enabled: false, base: '', componentIds: new Set() }) {
|
|
881
969
|
const projectRoot = getProjectRoot(context.cwd);
|
|
882
970
|
const lock = readComponentLock(projectRoot);
|
|
883
971
|
const root = getWorkspaceRoot(context.cwd);
|
|
@@ -957,6 +1045,7 @@ async function buildComponentPlan(context) {
|
|
|
957
1045
|
status = 'conflict';
|
|
958
1046
|
reason = 'local and remote changed since lock';
|
|
959
1047
|
}
|
|
1048
|
+
const inScope = !scope.enabled || scope.componentIds.has(componentId);
|
|
960
1049
|
return {
|
|
961
1050
|
componentId,
|
|
962
1051
|
componentName,
|
|
@@ -968,12 +1057,20 @@ async function buildComponentPlan(context) {
|
|
|
968
1057
|
remoteHash,
|
|
969
1058
|
remoteVersion: remote ? componentVersion(remote.component) : lockEntry?.remoteVersion ?? null,
|
|
970
1059
|
reason,
|
|
1060
|
+
...(scope.enabled ? { inScope } : {}),
|
|
971
1061
|
};
|
|
972
1062
|
});
|
|
973
1063
|
return {
|
|
974
1064
|
schemaVersion: 1,
|
|
975
1065
|
generatedAt: new Date().toISOString(),
|
|
976
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
|
+
} : {}),
|
|
977
1074
|
counts: items.reduce((counts, item) => {
|
|
978
1075
|
counts[item.status] += 1;
|
|
979
1076
|
return counts;
|
|
@@ -1057,6 +1154,47 @@ function buildDebugElements(component) {
|
|
|
1057
1154
|
};
|
|
1058
1155
|
});
|
|
1059
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
|
+
}
|
|
1060
1198
|
function isDebugLibraryType(type) {
|
|
1061
1199
|
return type === 'CODE_TS_LIB' || type === 'CODE_JS_LIB';
|
|
1062
1200
|
}
|
|
@@ -1096,19 +1234,28 @@ function buildDebugPayload(cwd, component, input) {
|
|
|
1096
1234
|
}
|
|
1097
1235
|
function collectDebugExtraLibs(cwd, currentComponentId) {
|
|
1098
1236
|
return getComponentManifestPaths(getWorkspaceRoot(cwd))
|
|
1099
|
-
.map((manifestPath) =>
|
|
1100
|
-
|
|
1101
|
-
|
|
1237
|
+
.map((manifestPath) => {
|
|
1238
|
+
const component = readWorkspaceComponentSafe(manifestPath);
|
|
1239
|
+
return component ? { manifestPath, component } : null;
|
|
1240
|
+
})
|
|
1241
|
+
.filter((entry) => Boolean(entry))
|
|
1242
|
+
.filter(({ component }) => {
|
|
1102
1243
|
const type = normalizeComponentType(component);
|
|
1103
1244
|
const componentId = component.componentId || component.id || '';
|
|
1104
1245
|
return isDebugLibraryType(type) && componentId !== currentComponentId && Boolean(component.name);
|
|
1105
1246
|
})
|
|
1106
|
-
.map((component) => ({
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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),
|
|
1110
1254
|
}));
|
|
1111
1255
|
}
|
|
1256
|
+
function writeRawDebugRequestDump(options) {
|
|
1257
|
+
process.stderr.write(`[debug-request] ${JSON.stringify(options, null, 2)}\n`);
|
|
1258
|
+
}
|
|
1112
1259
|
function formatProcessLog(log) {
|
|
1113
1260
|
if (!log || typeof log !== 'object' || Array.isArray(log)) {
|
|
1114
1261
|
return String(log ?? '');
|
|
@@ -1168,21 +1315,34 @@ async function debugSingleComponent(context, componentId) {
|
|
|
1168
1315
|
const memory = parseDebugNumber(context.args, ['memory', 'm'], 128, 1024, 'Memory');
|
|
1169
1316
|
const stream = readBoolFlag(context.args, ['stream']);
|
|
1170
1317
|
const includeExtraLibs = !readBoolFlag(context.args, ['no-extra-libs']);
|
|
1318
|
+
const rawDebug = readBoolFlag(context.args, ['raw-debug', 'debug-raw', 'debug-payload']);
|
|
1171
1319
|
const profile = await context.client.me();
|
|
1172
1320
|
const sandboxEndpoint = extractSandboxEndpoint(profile);
|
|
1173
1321
|
if (!sandboxEndpoint) {
|
|
1174
1322
|
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run.');
|
|
1175
1323
|
}
|
|
1324
|
+
const extraLibs = includeExtraLibs ? collectDebugExtraLibs(context.cwd, componentKey) : [];
|
|
1176
1325
|
const payload = buildDebugPayload(context.cwd, component, {
|
|
1177
1326
|
inputs,
|
|
1178
1327
|
timeout,
|
|
1179
1328
|
memory,
|
|
1180
|
-
extraLibs:
|
|
1329
|
+
extraLibs: extraLibs.map((entry) => entry.payload),
|
|
1181
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
|
+
}
|
|
1182
1342
|
if (stream) {
|
|
1183
1343
|
let result;
|
|
1184
1344
|
let done = false;
|
|
1185
|
-
for await (const event of context.client.debugComponentStream(
|
|
1345
|
+
for await (const event of context.client.debugComponentStream(endpoint, payload)) {
|
|
1186
1346
|
if (event.event === 'log') {
|
|
1187
1347
|
process.stderr.write(`${formatProcessLog(event.data)}\n`);
|
|
1188
1348
|
}
|
|
@@ -1203,7 +1363,7 @@ async function debugSingleComponent(context, componentId) {
|
|
|
1203
1363
|
context.print(extractResultPayload(result));
|
|
1204
1364
|
return;
|
|
1205
1365
|
}
|
|
1206
|
-
const response = await context.client.debugComponent(
|
|
1366
|
+
const response = await context.client.debugComponent(endpoint, payload);
|
|
1207
1367
|
writeDebugOutputFile(context.cwd, response);
|
|
1208
1368
|
context.print(extractResultPayload(response));
|
|
1209
1369
|
}
|
|
@@ -1217,16 +1377,17 @@ export async function handleComponentCommand(context) {
|
|
|
1217
1377
|
const stale = readBoolFlag(args, ['stale', 's']);
|
|
1218
1378
|
const json = readBoolFlag(args, ['json']);
|
|
1219
1379
|
const strict = readBoolFlag(args, ['strict']);
|
|
1380
|
+
const changedScope = resolveChangedComponentScope(context);
|
|
1220
1381
|
if (subcommand === 'pull-all') {
|
|
1221
1382
|
await pullAllComponents(context, { force, stale, yes });
|
|
1222
1383
|
return;
|
|
1223
1384
|
}
|
|
1224
1385
|
if (subcommand === 'push-all') {
|
|
1225
|
-
await pushAllComponents(context, { force, yes });
|
|
1386
|
+
await pushAllComponents(context, { force, yes, scope: changedScope });
|
|
1226
1387
|
return;
|
|
1227
1388
|
}
|
|
1228
1389
|
if (subcommand === 'plan') {
|
|
1229
|
-
const plan = await buildComponentPlan(context);
|
|
1390
|
+
const plan = await buildComponentPlan(context, changedScope);
|
|
1230
1391
|
if (json) {
|
|
1231
1392
|
context.print(plan);
|
|
1232
1393
|
}
|
|
@@ -1234,10 +1395,11 @@ export async function handleComponentCommand(context) {
|
|
|
1234
1395
|
printPlan(context, plan);
|
|
1235
1396
|
}
|
|
1236
1397
|
if (strict) {
|
|
1237
|
-
const blocking = plan.items.filter((item) => (
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
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')));
|
|
1241
1403
|
if (blocking.length > 0) {
|
|
1242
1404
|
throw new Error(`Component plan is not safe: ${blocking.length} blocking item(s).`);
|
|
1243
1405
|
}
|
|
@@ -1270,7 +1432,7 @@ export async function handleComponentCommand(context) {
|
|
|
1270
1432
|
throw new Error('Provide --id <componentId> or --all.');
|
|
1271
1433
|
}
|
|
1272
1434
|
if (all) {
|
|
1273
|
-
await pushAllComponents(context, { force, yes });
|
|
1435
|
+
await pushAllComponents(context, { force, yes, scope: changedScope });
|
|
1274
1436
|
return;
|
|
1275
1437
|
}
|
|
1276
1438
|
const manifests = targets.flatMap((componentId) => findComponentManifestsById(context.cwd, componentId));
|
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'),
|