@revoengine/cli 1.0.8 → 1.0.10

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,6 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { readJsonFile } from "./utils.js";
4
+ import { COMPONENT_IDENTITY_BY_ID, DEFAULT_COMPONENT_IDENTITY, DEFAULT_STABLE_KEY_NAME, normalizeComponentIdentityConfig, normalizeComponentIdentityMode, normalizeStableKeyName, } from "./resource-metadata.js";
5
+ import { TRACK_ALL_RESOURCES, normalizeTrackedResources, } from "./tracked-resources.js";
4
6
  export const REVO_PROJECT_DIR = '.revoengine';
5
7
  export const REVO_TYPES_DIR = path.join(REVO_PROJECT_DIR, 'types');
6
8
  export const REVO_TYPES_FILE = path.join(REVO_TYPES_DIR, 'revo.editor.d.ts');
@@ -180,6 +182,22 @@ function appendUnique(list, value) {
180
182
  }
181
183
  return list;
182
184
  }
185
+ function readResourceSelectionArgs(args) {
186
+ const values = [];
187
+ for (const name of ['resources', 'resource', 'tracked-resources', 'trackedResources']) {
188
+ const value = args[name];
189
+ if (typeof value === 'string') {
190
+ values.push(value);
191
+ }
192
+ else if (Array.isArray(value)) {
193
+ values.push(...value);
194
+ }
195
+ else if (value === true) {
196
+ throw new Error(`--${name} requires a value such as "all" or "component".`);
197
+ }
198
+ }
199
+ return values.length > 0 ? values : undefined;
200
+ }
183
201
  function writeJsonFile(filePath, value) {
184
202
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
185
203
  fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
@@ -187,10 +205,27 @@ function writeJsonFile(filePath, value) {
187
205
  export function resolveProjectInvocation(args) {
188
206
  const projectArgs = args._.slice(1);
189
207
  const [first, second, ...rest] = projectArgs;
208
+ const explicitStableKeyName = readString(args.stableKeyName)
209
+ || readString(args['stable-key-name']);
210
+ const stableKeyName = explicitStableKeyName
211
+ ? normalizeStableKeyName(explicitStableKeyName)
212
+ : undefined;
213
+ const explicitIdentityMode = readString(args.identityMode)
214
+ || readString(args['identity-mode']);
215
+ const identityMode = explicitIdentityMode
216
+ ? normalizeComponentIdentityMode(explicitIdentityMode)
217
+ : stableKeyName
218
+ ? 'stableKey'
219
+ : undefined;
220
+ const trackedResourceArgs = readResourceSelectionArgs(args);
221
+ const trackedResources = trackedResourceArgs ? normalizeTrackedResources(trackedResourceArgs) : undefined;
190
222
  if (!first) {
191
223
  return {
192
224
  action: 'init',
193
225
  extraArgs: [],
226
+ identityMode,
227
+ stableKeyName,
228
+ ...(trackedResources ? { trackedResources } : {}),
194
229
  };
195
230
  }
196
231
  if (first === 'init' || first === 'update') {
@@ -198,6 +233,9 @@ export function resolveProjectInvocation(args) {
198
233
  action: first,
199
234
  targetArg: second,
200
235
  extraArgs: rest,
236
+ identityMode,
237
+ stableKeyName,
238
+ ...(trackedResources ? { trackedResources } : {}),
201
239
  };
202
240
  }
203
241
  if (first === 'switch') {
@@ -207,6 +245,9 @@ export function resolveProjectInvocation(args) {
207
245
  action: 'init',
208
246
  targetArg: first,
209
247
  extraArgs: second ? [second, ...rest] : rest,
248
+ identityMode,
249
+ stableKeyName,
250
+ ...(trackedResources ? { trackedResources } : {}),
210
251
  };
211
252
  }
212
253
  function normalizeWorkspaceDirectory(projectRoot, workspaceRoot) {
@@ -363,6 +404,40 @@ export function readProjectMetadata(startDir = process.cwd()) {
363
404
  }
364
405
  return raw;
365
406
  }
407
+ export function readProjectStableKeyName(startDir = process.cwd()) {
408
+ const metadata = readProjectMetadata(startDir);
409
+ const identity = readProjectComponentIdentity(startDir);
410
+ return normalizeStableKeyName(identity.mode === 'stableKey'
411
+ ? identity.metadataProperty
412
+ : metadata?.stableKeyName || DEFAULT_STABLE_KEY_NAME);
413
+ }
414
+ export function readProjectComponentIdentity(startDir = process.cwd()) {
415
+ const metadata = readProjectMetadata(startDir);
416
+ if (!metadata) {
417
+ return COMPONENT_IDENTITY_BY_ID;
418
+ }
419
+ if (metadata.componentIdentity && isRecord(metadata.componentIdentity)) {
420
+ const mode = readString(metadata.componentIdentity.mode);
421
+ const metadataProperty = readString(metadata.componentIdentity.metadataProperty);
422
+ if (mode) {
423
+ return normalizeComponentIdentityConfig({
424
+ mode: normalizeComponentIdentityMode(mode),
425
+ ...(metadataProperty ? { metadataProperty } : {}),
426
+ });
427
+ }
428
+ }
429
+ if (metadata.stableKeyName) {
430
+ return normalizeComponentIdentityConfig({
431
+ mode: 'stableKey',
432
+ metadataProperty: metadata.stableKeyName,
433
+ }, DEFAULT_COMPONENT_IDENTITY);
434
+ }
435
+ return COMPONENT_IDENTITY_BY_ID;
436
+ }
437
+ export function readProjectTrackedResources(startDir = process.cwd()) {
438
+ const metadata = readProjectMetadata(startDir);
439
+ return normalizeTrackedResources(metadata?.trackedResources || TRACK_ALL_RESOURCES);
440
+ }
366
441
  export function resolveProjectRoot(startDir = process.cwd()) {
367
442
  const metadataFile = findProjectMetadataFile(startDir);
368
443
  return metadataFile ? path.dirname(path.dirname(metadataFile)) : '';
@@ -440,6 +515,8 @@ export function syncProjectFiles(layoutOrTargetDir, input) {
440
515
  libVersion: input.libVersion,
441
516
  hash: input.hash,
442
517
  lastSyncAt: input.lastSyncAt,
518
+ componentIdentity: normalizeComponentIdentityConfig(input.componentIdentity, DEFAULT_COMPONENT_IDENTITY),
519
+ trackedResources: normalizeTrackedResources(input.trackedResources || TRACK_ALL_RESOURCES),
443
520
  ...(workspaceDirectory !== '.' ? { workspace: workspaceDirectory } : {}),
444
521
  });
445
522
  const configResult = ensureProjectConfig(workspaceRoot, toPosixRelative(workspaceRoot, typesFile));
@@ -480,6 +557,9 @@ export function ensureProjectConfig(rootDir, typesInclude = REVO_TYPES_FILE) {
480
557
  if (!fs.existsSync(tsconfigPath) && !fs.existsSync(jsconfigPath)) {
481
558
  writeJsonFile(tsconfigPath, {
482
559
  compilerOptions: {
560
+ target: 'ES2022',
561
+ module: 'ESNext',
562
+ moduleDetection: 'force',
483
563
  allowJs: true,
484
564
  checkJs: false,
485
565
  noEmit: true,
@@ -499,6 +579,15 @@ export function ensureProjectConfig(rootDir, typesInclude = REVO_TYPES_FILE) {
499
579
  const filePath = fs.existsSync(tsconfigPath) ? tsconfigPath : jsconfigPath;
500
580
  const parsed = parseConfigFile(filePath);
501
581
  const compilerOptions = ensureObjectField(parsed, 'compilerOptions', filePath);
582
+ if (compilerOptions.target === undefined) {
583
+ compilerOptions.target = 'ES2022';
584
+ }
585
+ if (compilerOptions.module === undefined) {
586
+ compilerOptions.module = 'ESNext';
587
+ }
588
+ if (compilerOptions.moduleDetection === undefined) {
589
+ compilerOptions.moduleDetection = 'force';
590
+ }
502
591
  if (compilerOptions.allowJs === undefined) {
503
592
  compilerOptions.allowJs = true;
504
593
  }
@@ -0,0 +1,24 @@
1
+ import type { ComponentRecord } from './types.ts';
2
+ export declare const DEFAULT_STABLE_KEY_NAME = "stableKey";
3
+ export declare const NULL_CATEGORY_KEY = "__no_category__";
4
+ export type ComponentIdentityMode = 'componentId' | 'stableKey';
5
+ export type ComponentIdentityConfig = {
6
+ mode: ComponentIdentityMode;
7
+ metadataProperty?: string;
8
+ };
9
+ export declare const COMPONENT_IDENTITY_BY_ID: ComponentIdentityConfig;
10
+ export declare const DEFAULT_COMPONENT_IDENTITY: ComponentIdentityConfig;
11
+ export declare function normalizeStableKeyName(value: string | null | undefined): string;
12
+ export declare function normalizeComponentIdentityMode(value: string | null | undefined): ComponentIdentityMode;
13
+ export declare function normalizeComponentIdentityConfig(value: Partial<ComponentIdentityConfig> | null | undefined, fallback?: ComponentIdentityConfig): ComponentIdentityConfig;
14
+ export declare function sameComponentIdentityConfig(left: ComponentIdentityConfig, right: ComponentIdentityConfig): boolean;
15
+ export declare function readConfiguredResourceMetadataStableKey(component: ComponentRecord, stableKeyName?: string): string | null;
16
+ export declare function readComponentIdentityValue(component: ComponentRecord, identity: ComponentIdentityConfig): string | null;
17
+ export declare function readResourceMetadataStableKey(component: ComponentRecord, stableKeyName?: string): string | null;
18
+ export declare function buildStableKey(component: ComponentRecord, stableKeyName?: string): string;
19
+ export declare function buildStableKeyValue(component: ComponentRecord, stableKeyName?: string): string;
20
+ export declare function buildResourceIdentityStableKeyValue(component: ComponentRecord): string;
21
+ export declare function buildResourceMetadataWithStableKey(component: ComponentRecord, stableKeyName?: string): {
22
+ [x: string]: unknown;
23
+ };
24
+ export declare function sanitizeUserMetadata(value: unknown): Record<string, unknown>;
@@ -0,0 +1,129 @@
1
+ export const DEFAULT_STABLE_KEY_NAME = 'stableKey';
2
+ export const NULL_CATEGORY_KEY = '__no_category__';
3
+ export const COMPONENT_IDENTITY_BY_ID = {
4
+ mode: 'componentId',
5
+ };
6
+ export const DEFAULT_COMPONENT_IDENTITY = {
7
+ mode: 'stableKey',
8
+ metadataProperty: DEFAULT_STABLE_KEY_NAME,
9
+ };
10
+ function isRecord(value) {
11
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
12
+ }
13
+ function readString(value) {
14
+ return typeof value === 'string' && value.trim() ? value : null;
15
+ }
16
+ function normalizeKeyPart(value) {
17
+ return (value ?? '').trim().replace(/\s+/g, ' ').toLowerCase();
18
+ }
19
+ export function normalizeStableKeyName(value) {
20
+ const normalized = (value || DEFAULT_STABLE_KEY_NAME).trim();
21
+ if (!normalized) {
22
+ return DEFAULT_STABLE_KEY_NAME;
23
+ }
24
+ if (!/^[A-Za-z_$][A-Za-z0-9_$-]*$/.test(normalized)) {
25
+ throw new Error('Stable key metadata name must start with a letter, "_", or "$" and contain only letters, digits, "_", "$", or "-".');
26
+ }
27
+ return normalized;
28
+ }
29
+ export function normalizeComponentIdentityMode(value) {
30
+ const normalized = (value || '').trim().toLowerCase().replace(/[-_\s]/g, '');
31
+ if (normalized === 'stablekey' || normalized === 'metadata') {
32
+ return 'stableKey';
33
+ }
34
+ if (normalized === 'componentid' || normalized === 'id' || normalized === 'uuid') {
35
+ return 'componentId';
36
+ }
37
+ throw new Error('Component identity mode must be "stableKey" or "componentId".');
38
+ }
39
+ export function normalizeComponentIdentityConfig(value, fallback = COMPONENT_IDENTITY_BY_ID) {
40
+ const mode = value?.mode
41
+ ? normalizeComponentIdentityMode(value.mode)
42
+ : fallback.mode;
43
+ if (mode === 'componentId') {
44
+ return { mode };
45
+ }
46
+ return {
47
+ mode,
48
+ metadataProperty: normalizeStableKeyName(value?.metadataProperty || fallback.metadataProperty),
49
+ };
50
+ }
51
+ export function sameComponentIdentityConfig(left, right) {
52
+ const normalizedLeft = normalizeComponentIdentityConfig(left);
53
+ const normalizedRight = normalizeComponentIdentityConfig(right);
54
+ return normalizedLeft.mode === normalizedRight.mode
55
+ && normalizedLeft.metadataProperty === normalizedRight.metadataProperty;
56
+ }
57
+ function readConfiguredStableKeyFromRecord(value, stableKeyName) {
58
+ if (!isRecord(value)) {
59
+ return null;
60
+ }
61
+ return readString(value[stableKeyName])
62
+ ?? (isRecord(value.details) ? readString(value.details[stableKeyName]) : null);
63
+ }
64
+ function readAnyStableKeyFromRecord(value, stableKeyName) {
65
+ return readConfiguredStableKeyFromRecord(value, stableKeyName)
66
+ ?? (stableKeyName === DEFAULT_STABLE_KEY_NAME ? null : readConfiguredStableKeyFromRecord(value, DEFAULT_STABLE_KEY_NAME));
67
+ }
68
+ export function readConfiguredResourceMetadataStableKey(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
69
+ const fieldName = normalizeStableKeyName(stableKeyName);
70
+ return readConfiguredStableKeyFromRecord(component.metadata, fieldName)
71
+ ?? readConfiguredStableKeyFromRecord(component.metaData, fieldName)
72
+ ?? readConfiguredStableKeyFromRecord(component.resourceMetadata, fieldName);
73
+ }
74
+ export function readComponentIdentityValue(component, identity) {
75
+ const normalized = normalizeComponentIdentityConfig(identity);
76
+ if (normalized.mode === 'componentId') {
77
+ return readString(component.componentId) ?? readString(component.id);
78
+ }
79
+ return readConfiguredResourceMetadataStableKey(component, normalized.metadataProperty);
80
+ }
81
+ export function readResourceMetadataStableKey(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
82
+ const fieldName = normalizeStableKeyName(stableKeyName);
83
+ return readAnyStableKeyFromRecord(component.metadata, fieldName)
84
+ ?? readAnyStableKeyFromRecord(component.metaData, fieldName)
85
+ ?? readAnyStableKeyFromRecord(component.resourceMetadata, fieldName);
86
+ }
87
+ export function buildStableKey(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
88
+ const metadataStableKey = readResourceMetadataStableKey(component, stableKeyName);
89
+ if (metadataStableKey) {
90
+ return normalizeKeyPart(metadataStableKey);
91
+ }
92
+ const category = normalizeKeyPart(component.category) || NULL_CATEGORY_KEY;
93
+ const name = normalizeKeyPart(component.name);
94
+ return `${category}/${name}`;
95
+ }
96
+ export function buildStableKeyValue(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
97
+ const metadataStableKey = readResourceMetadataStableKey(component, stableKeyName);
98
+ if (metadataStableKey) {
99
+ return metadataStableKey;
100
+ }
101
+ return buildResourceIdentityStableKeyValue(component);
102
+ }
103
+ export function buildResourceIdentityStableKeyValue(component) {
104
+ const category = typeof component.category === 'string' && component.category.trim()
105
+ ? component.category.trim()
106
+ : NULL_CATEGORY_KEY;
107
+ const name = typeof component.name === 'string' ? component.name.trim() : '';
108
+ return `${category}/${name}`;
109
+ }
110
+ export function buildResourceMetadataWithStableKey(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
111
+ const fieldName = normalizeStableKeyName(stableKeyName);
112
+ return {
113
+ ...sanitizeUserMetadata(component.metadata),
114
+ [fieldName]: buildStableKeyValue(component, fieldName),
115
+ };
116
+ }
117
+ export function sanitizeUserMetadata(value) {
118
+ if (!isRecord(value)) {
119
+ return {};
120
+ }
121
+ const sanitized = {};
122
+ for (const [key, entry] of Object.entries(value)) {
123
+ if (key.startsWith('__')) {
124
+ continue;
125
+ }
126
+ sanitized[key] = entry;
127
+ }
128
+ return sanitized;
129
+ }
@@ -0,0 +1,7 @@
1
+ export declare const TRACK_ALL_RESOURCES = "all";
2
+ export declare const TRACKABLE_RESOURCE_TYPES: readonly ["component"];
3
+ export type TrackableResourceType = typeof TRACKABLE_RESOURCE_TYPES[number];
4
+ export type TrackedResource = typeof TRACK_ALL_RESOURCES | TrackableResourceType;
5
+ export declare function normalizeTrackedResources(value: string | string[] | null | undefined): TrackedResource[];
6
+ export declare function formatTrackedResources(resources: readonly TrackedResource[]): string;
7
+ export declare function shouldTrackResource(resources: readonly TrackedResource[], resource: TrackableResourceType): boolean;
@@ -0,0 +1,37 @@
1
+ export const TRACK_ALL_RESOURCES = 'all';
2
+ export const TRACKABLE_RESOURCE_TYPES = ['component'];
3
+ const RESOURCE_ALIASES = {
4
+ all: TRACK_ALL_RESOURCES,
5
+ '*': TRACK_ALL_RESOURCES,
6
+ component: 'component',
7
+ components: 'component',
8
+ };
9
+ export function normalizeTrackedResources(value) {
10
+ const rawValues = Array.isArray(value) ? value : value ? [value] : [TRACK_ALL_RESOURCES];
11
+ const normalized = [];
12
+ for (const rawValue of rawValues) {
13
+ for (const part of rawValue.split(',')) {
14
+ const key = part.trim().toLowerCase();
15
+ if (!key) {
16
+ continue;
17
+ }
18
+ const resource = RESOURCE_ALIASES[key];
19
+ if (!resource) {
20
+ throw new Error(`Unsupported tracked resource "${part.trim()}". Supported values: all, component.`);
21
+ }
22
+ if (!normalized.includes(resource)) {
23
+ normalized.push(resource);
24
+ }
25
+ }
26
+ }
27
+ if (normalized.length === 0 || normalized.includes(TRACK_ALL_RESOURCES)) {
28
+ return [TRACK_ALL_RESOURCES];
29
+ }
30
+ return normalized;
31
+ }
32
+ export function formatTrackedResources(resources) {
33
+ return resources.join(',');
34
+ }
35
+ export function shouldTrackResource(resources, resource) {
36
+ return resources.includes(TRACK_ALL_RESOURCES) || resources.includes(resource);
37
+ }
@@ -32,6 +32,9 @@ export type ComponentRecord = {
32
32
  active?: boolean;
33
33
  async?: boolean;
34
34
  version?: number;
35
+ metadata?: Record<string, unknown>;
36
+ metaData?: Record<string, unknown>;
37
+ resourceMetadata?: Record<string, unknown>;
35
38
  elements?: ComponentElementRecord[];
36
39
  [key: string]: unknown;
37
40
  };
package/dist/src/ui.js CHANGED
@@ -103,18 +103,27 @@ function renderCommandCatalog() {
103
103
  commandRow('revo auth login', 'Paste API key in the terminal'),
104
104
  commandRow('revo auth status', 'Show the current authentication status'),
105
105
  commandRow('revo auth logout', 'Remove stored credentials'),
106
- commandRow('revo project [path]', 'Initialize ambient editor types for JS and TS'),
106
+ commandRow('revo project [path] [--identity-mode <stableKey|componentId>] [--stable-key-name <name>] [--resources <list>]', 'Initialize editor types and component identity'),
107
107
  commandRow('revo project update [path]', 'Refresh editor types and project metadata'),
108
108
  commandRow('revo endpoints', 'List available API endpoints'),
109
109
  '',
110
110
  paintHeader('Components'),
111
111
  commandRow('revo component plan --all [--json] [--strict] [--changed --base <ref>]', 'Preview local/remote sync status'),
112
- commandRow('revo component pull <componentId...> [--force]', 'Pull one or more components safely'),
112
+ commandRow('revo component pull <identity...> [--force]', 'Pull by configured stable key or component ID'),
113
113
  commandRow('revo component pull --all [--yes] [--force]', 'Pull every available component safely'),
114
- commandRow('revo component push <componentId...>', 'Push one or more local components'),
114
+ commandRow('revo component push <identity...>', 'Push by configured stable key or component ID'),
115
115
  commandRow('revo component push --all [--yes] [--force] [--changed --base <ref>]', 'Push local components with lock checks'),
116
116
  commandRow('revo component debug <componentId> [BODY] [--stream] [--raw-debug]', 'Debug one local component against sandbox'),
117
117
  '',
118
+ paintHeader('Environments'),
119
+ commandRow('revo env add <name> [--url <url>] [--token <apiKey>]', 'Save a named environment profile'),
120
+ commandRow('revo env list [--json]', 'List saved environment profiles'),
121
+ commandRow('revo env remove <name>', 'Remove a saved environment profile'),
122
+ commandRow('revo env diff --from <env> --to <env> [--prune] [--json] [--strict]', 'Compare components across two environments'),
123
+ commandRow('revo env apply --from <env> --to <env> [--yes] [--prune] [--force]', 'Mirror components from one environment into another'),
124
+ commandRow('revo metadata plan --env <env> [--json]', 'Preview stable-key metadata backfill'),
125
+ commandRow('revo metadata apply --env <env> --yes', 'Populate missing stable-key metadata'),
126
+ '',
118
127
  paintHeader('Low-Level'),
119
128
  commandRow('revo search <CODE|SIMPLE> <term>', 'Search code references or all platform content'),
120
129
  commandRow('revo request <METHOD> <PATH> [BODY]', 'Make a raw API request with optional JSON body'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revoengine/cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "CLI package for the RevoEngine Platform API",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "scripts": {
12
12
  "build": "tsc -p tsconfig.build.json",
13
- "check": "node --input-type=module -e \"await import('./src/config.ts'); await import('./src/legacy.ts'); await import('./src/client.ts'); await import('./src/component-lock.ts'); await import('./src/ui.ts'); await import('./src/update-notifier.ts'); await import('./src/commands/auth.ts'); await import('./src/commands/component.ts'); await import('./src/commands/endpoints.ts'); await import('./src/commands/info.ts'); await import('./src/commands/request.ts'); await import('./src/commands/search.ts'); await import('./src/cli.ts'); await import('./src/index.ts'); await import('./bin/revo.ts'); await import('./scripts/dev.ts')\"",
13
+ "check": "node --input-type=module -e \"await import('./src/config.ts'); await import('./src/legacy.ts'); await import('./src/client.ts'); await import('./src/component-lock.ts'); await import('./src/resource-metadata.ts'); await import('./src/tracked-resources.ts'); await import('./src/metadata-backfill.ts'); await import('./src/ui.ts'); await import('./src/update-notifier.ts'); await import('./src/env-sync.ts'); await import('./src/commands/auth.ts'); await import('./src/commands/component.ts'); await import('./src/commands/endpoints.ts'); await import('./src/commands/env.ts'); await import('./src/commands/info.ts'); await import('./src/commands/metadata.ts'); await import('./src/commands/request.ts'); await import('./src/commands/search.ts'); await import('./src/cli.ts'); await import('./src/index.ts'); await import('./bin/revo.ts'); await import('./scripts/dev.ts')\"",
14
14
  "dev": "node ./scripts/dev.ts",
15
15
  "prepack": "npm run build",
16
16
  "test": "node --test ./test/*.test.ts"