@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.
@@ -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
- const manifests = getComponentManifestPaths(root);
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) => readWorkspaceComponentSafe(manifestPath))
1100
- .filter((component) => Boolean(component))
1101
- .filter((component) => {
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
- name: component.name,
1108
- type: normalizeComponentType(component),
1109
- elements: buildDebugElements(component),
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: includeExtraLibs ? collectDebugExtraLibs(context.cwd, componentKey) : [],
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(buildSandboxDebugStreamUrl(sandboxEndpoint), payload)) {
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(buildSandboxDebugUrl(sandboxEndpoint), payload);
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) => (item.status === 'conflict'
1238
- || item.status === 'missing-lock'
1239
- || item.status === 'remote-changed'
1240
- || item.status === 'safe-update'));
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 every local component with lock checks'),
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'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revoengine/cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "CLI package for the RevoEngine Platform API",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",