@revoengine/cli 1.0.7 → 1.0.9

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,13 +1057,22 @@ 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
  });
1063
+ const scopedItems = scope.enabled ? items.filter((item) => item.inScope) : items;
973
1064
  return {
974
1065
  schemaVersion: 1,
975
1066
  generatedAt: new Date().toISOString(),
976
1067
  workspaceRoot: path.relative(context.cwd, root) || 'Components',
977
- counts: items.reduce((counts, item) => {
1068
+ ...(scope.enabled ? {
1069
+ scope: {
1070
+ mode: 'changed',
1071
+ base: scope.base,
1072
+ componentIds: [...scope.componentIds].sort(),
1073
+ },
1074
+ } : {}),
1075
+ counts: scopedItems.reduce((counts, item) => {
978
1076
  counts[item.status] += 1;
979
1077
  return counts;
980
1078
  }, {
@@ -986,7 +1084,7 @@ async function buildComponentPlan(context) {
986
1084
  create: 0,
987
1085
  'missing-lock': 0,
988
1086
  }),
989
- items,
1087
+ items: scopedItems,
990
1088
  };
991
1089
  }
992
1090
  function printPlan(context, plan) {
@@ -1057,6 +1155,47 @@ function buildDebugElements(component) {
1057
1155
  };
1058
1156
  });
1059
1157
  }
1158
+ function resolveElementSourcePath(manifestPath, component, element) {
1159
+ const componentDir = path.dirname(manifestPath);
1160
+ const detailDirs = [
1161
+ path.join(componentDir, 'elements'),
1162
+ path.join(componentDir, 'details'),
1163
+ ].filter((dir, index, list) => list.indexOf(dir) === index);
1164
+ const extension = componentTypeToExtension(normalizeComponentType(component));
1165
+ const fileName = `${element.order}_${element.key}.${extension}`;
1166
+ for (const detailDir of detailDirs) {
1167
+ const preferredPath = path.join(detailDir, fileName);
1168
+ if (fs.existsSync(preferredPath)) {
1169
+ return preferredPath;
1170
+ }
1171
+ if (fs.existsSync(detailDir)) {
1172
+ const match = fs.readdirSync(detailDir).find((entry) => entry.startsWith(`${element.order}_${element.key}.`));
1173
+ if (match) {
1174
+ return path.join(detailDir, match);
1175
+ }
1176
+ }
1177
+ }
1178
+ return null;
1179
+ }
1180
+ function buildDebugSourceComponent(role, manifestPath, component) {
1181
+ return {
1182
+ role,
1183
+ componentId: component.componentId || component.id || '',
1184
+ componentName: component.name || '',
1185
+ type: normalizeComponentType(component),
1186
+ manifestPath,
1187
+ elements: [...(component.elements || [])].sort((left, right) => left.order - right.order).map((element) => {
1188
+ const sourcePath = resolveElementSourcePath(manifestPath, component, element);
1189
+ const details = element.details || '';
1190
+ return {
1191
+ key: element.key,
1192
+ order: element.order,
1193
+ sourcePath,
1194
+ bytes: Buffer.byteLength(details, 'utf8'),
1195
+ };
1196
+ }),
1197
+ };
1198
+ }
1060
1199
  function isDebugLibraryType(type) {
1061
1200
  return type === 'CODE_TS_LIB' || type === 'CODE_JS_LIB';
1062
1201
  }
@@ -1096,19 +1235,28 @@ function buildDebugPayload(cwd, component, input) {
1096
1235
  }
1097
1236
  function collectDebugExtraLibs(cwd, currentComponentId) {
1098
1237
  return getComponentManifestPaths(getWorkspaceRoot(cwd))
1099
- .map((manifestPath) => readWorkspaceComponentSafe(manifestPath))
1100
- .filter((component) => Boolean(component))
1101
- .filter((component) => {
1238
+ .map((manifestPath) => {
1239
+ const component = readWorkspaceComponentSafe(manifestPath);
1240
+ return component ? { manifestPath, component } : null;
1241
+ })
1242
+ .filter((entry) => Boolean(entry))
1243
+ .filter(({ component }) => {
1102
1244
  const type = normalizeComponentType(component);
1103
1245
  const componentId = component.componentId || component.id || '';
1104
1246
  return isDebugLibraryType(type) && componentId !== currentComponentId && Boolean(component.name);
1105
1247
  })
1106
- .map((component) => ({
1107
- name: component.name,
1108
- type: normalizeComponentType(component),
1109
- elements: buildDebugElements(component),
1248
+ .map(({ manifestPath, component }) => ({
1249
+ payload: {
1250
+ name: component.name,
1251
+ type: normalizeComponentType(component),
1252
+ elements: buildDebugElements(component),
1253
+ },
1254
+ source: buildDebugSourceComponent('extraLib', manifestPath, component),
1110
1255
  }));
1111
1256
  }
1257
+ function writeRawDebugRequestDump(options) {
1258
+ process.stderr.write(`[debug-request] ${JSON.stringify(options, null, 2)}\n`);
1259
+ }
1112
1260
  function formatProcessLog(log) {
1113
1261
  if (!log || typeof log !== 'object' || Array.isArray(log)) {
1114
1262
  return String(log ?? '');
@@ -1168,21 +1316,34 @@ async function debugSingleComponent(context, componentId) {
1168
1316
  const memory = parseDebugNumber(context.args, ['memory', 'm'], 128, 1024, 'Memory');
1169
1317
  const stream = readBoolFlag(context.args, ['stream']);
1170
1318
  const includeExtraLibs = !readBoolFlag(context.args, ['no-extra-libs']);
1319
+ const rawDebug = readBoolFlag(context.args, ['raw-debug', 'debug-raw', 'debug-payload']);
1171
1320
  const profile = await context.client.me();
1172
1321
  const sandboxEndpoint = extractSandboxEndpoint(profile);
1173
1322
  if (!sandboxEndpoint) {
1174
1323
  throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run.');
1175
1324
  }
1325
+ const extraLibs = includeExtraLibs ? collectDebugExtraLibs(context.cwd, componentKey) : [];
1176
1326
  const payload = buildDebugPayload(context.cwd, component, {
1177
1327
  inputs,
1178
1328
  timeout,
1179
1329
  memory,
1180
- extraLibs: includeExtraLibs ? collectDebugExtraLibs(context.cwd, componentKey) : [],
1330
+ extraLibs: extraLibs.map((entry) => entry.payload),
1181
1331
  });
1332
+ const endpoint = stream ? buildSandboxDebugStreamUrl(sandboxEndpoint) : buildSandboxDebugUrl(sandboxEndpoint);
1333
+ if (rawDebug) {
1334
+ writeRawDebugRequestDump({
1335
+ endpoint,
1336
+ cwd: context.cwd,
1337
+ workspaceRoot: getWorkspaceRoot(context.cwd),
1338
+ selected: buildDebugSourceComponent('selected', manifestPath, component),
1339
+ extraLibs: extraLibs.map((entry) => entry.source),
1340
+ payload,
1341
+ });
1342
+ }
1182
1343
  if (stream) {
1183
1344
  let result;
1184
1345
  let done = false;
1185
- for await (const event of context.client.debugComponentStream(buildSandboxDebugStreamUrl(sandboxEndpoint), payload)) {
1346
+ for await (const event of context.client.debugComponentStream(endpoint, payload)) {
1186
1347
  if (event.event === 'log') {
1187
1348
  process.stderr.write(`${formatProcessLog(event.data)}\n`);
1188
1349
  }
@@ -1203,7 +1364,7 @@ async function debugSingleComponent(context, componentId) {
1203
1364
  context.print(extractResultPayload(result));
1204
1365
  return;
1205
1366
  }
1206
- const response = await context.client.debugComponent(buildSandboxDebugUrl(sandboxEndpoint), payload);
1367
+ const response = await context.client.debugComponent(endpoint, payload);
1207
1368
  writeDebugOutputFile(context.cwd, response);
1208
1369
  context.print(extractResultPayload(response));
1209
1370
  }
@@ -1217,16 +1378,17 @@ export async function handleComponentCommand(context) {
1217
1378
  const stale = readBoolFlag(args, ['stale', 's']);
1218
1379
  const json = readBoolFlag(args, ['json']);
1219
1380
  const strict = readBoolFlag(args, ['strict']);
1381
+ const changedScope = resolveChangedComponentScope(context);
1220
1382
  if (subcommand === 'pull-all') {
1221
1383
  await pullAllComponents(context, { force, stale, yes });
1222
1384
  return;
1223
1385
  }
1224
1386
  if (subcommand === 'push-all') {
1225
- await pushAllComponents(context, { force, yes });
1387
+ await pushAllComponents(context, { force, yes, scope: changedScope });
1226
1388
  return;
1227
1389
  }
1228
1390
  if (subcommand === 'plan') {
1229
- const plan = await buildComponentPlan(context);
1391
+ const plan = await buildComponentPlan(context, changedScope);
1230
1392
  if (json) {
1231
1393
  context.print(plan);
1232
1394
  }
@@ -1234,10 +1396,11 @@ export async function handleComponentCommand(context) {
1234
1396
  printPlan(context, plan);
1235
1397
  }
1236
1398
  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'));
1399
+ const blocking = plan.items.filter((item) => ((!changedScope.enabled || item.inScope)
1400
+ && (item.status === 'conflict'
1401
+ || item.status === 'missing-lock'
1402
+ || item.status === 'remote-changed'
1403
+ || item.status === 'safe-update')));
1241
1404
  if (blocking.length > 0) {
1242
1405
  throw new Error(`Component plan is not safe: ${blocking.length} blocking item(s).`);
1243
1406
  }
@@ -1270,7 +1433,7 @@ export async function handleComponentCommand(context) {
1270
1433
  throw new Error('Provide --id <componentId> or --all.');
1271
1434
  }
1272
1435
  if (all) {
1273
- await pushAllComponents(context, { force, yes });
1436
+ await pushAllComponents(context, { force, yes, scope: changedScope });
1274
1437
  return;
1275
1438
  }
1276
1439
  const manifests = targets.flatMap((componentId) => findComponentManifestsById(context.cwd, componentId));
@@ -23,7 +23,6 @@ export declare function normalizeComponentSource(component: ComponentRecord): {
23
23
  desc: string | null;
24
24
  active: boolean;
25
25
  type: string;
26
- compiler: string;
27
26
  async: boolean;
28
27
  elements: {
29
28
  key: string;
@@ -60,7 +60,6 @@ export function normalizeComponentSource(component) {
60
60
  desc: normalizeValue(source.desc),
61
61
  active: source.active ?? true,
62
62
  type: source.type || '',
63
- compiler: source.compiler || '',
64
63
  async: Boolean(source.async),
65
64
  elements: [...(source.elements || [])]
66
65
  .map((element) => ({
@@ -102,6 +101,16 @@ export function writeComponentLock(projectRoot, lock) {
102
101
  }
103
102
  export function upsertComponentLockEntry(projectRoot, entry) {
104
103
  const lock = readComponentLock(projectRoot);
104
+ const existing = lock.components[entry.componentId];
105
+ if (existing
106
+ && existing.componentName === entry.componentName
107
+ && existing.category === entry.category
108
+ && existing.path === entry.path
109
+ && existing.remoteVersion === entry.remoteVersion
110
+ && existing.remoteHash === entry.remoteHash
111
+ && existing.sourceHash === entry.sourceHash) {
112
+ return;
113
+ }
105
114
  lock.components[entry.componentId] = entry;
106
115
  writeComponentLock(projectRoot, lock);
107
116
  }
@@ -316,8 +316,13 @@ function readAuthValidationStateStore() {
316
316
  if (!fs.existsSync(authStateFile)) {
317
317
  return emptyAuthValidationStateStore();
318
318
  }
319
- const raw = readJsonFile(authStateFile);
320
- return parseAuthValidationStateStore(raw);
319
+ try {
320
+ const raw = readJsonFile(authStateFile);
321
+ return parseAuthValidationStateStore(raw);
322
+ }
323
+ catch {
324
+ return emptyAuthValidationStateStore();
325
+ }
321
326
  }
322
327
  export function readAuthValidationState(key) {
323
328
  const store = readAuthValidationStateStore();
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.9",
4
4
  "description": "CLI package for the RevoEngine Platform API",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",