@revoengine/cli 1.0.10 → 1.0.11

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.
Files changed (79) hide show
  1. package/README.md +312 -33
  2. package/dist/src/cli.js +58 -7
  3. package/dist/src/client.d.ts +356 -5
  4. package/dist/src/client.js +803 -19
  5. package/dist/src/commands/auth.js +4 -2
  6. package/dist/src/commands/component.js +260 -141
  7. package/dist/src/commands/database-schemas.d.ts +2 -0
  8. package/dist/src/commands/database-schemas.js +188 -0
  9. package/dist/src/commands/database-views.d.ts +2 -0
  10. package/dist/src/commands/database-views.js +123 -0
  11. package/dist/src/commands/endpoints.js +114 -0
  12. package/dist/src/commands/env.js +44 -24
  13. package/dist/src/commands/events.d.ts +2 -0
  14. package/dist/src/commands/events.js +146 -0
  15. package/dist/src/commands/groups.d.ts +2 -0
  16. package/dist/src/commands/groups.js +169 -0
  17. package/dist/src/commands/index.d.ts +8 -0
  18. package/dist/src/commands/index.js +8 -0
  19. package/dist/src/commands/job-templates.d.ts +2 -0
  20. package/dist/src/commands/job-templates.js +101 -0
  21. package/dist/src/commands/metadata.js +29 -7
  22. package/dist/src/commands/project.js +10 -3
  23. package/dist/src/commands/role-groups.d.ts +2 -0
  24. package/dist/src/commands/role-groups.js +152 -0
  25. package/dist/src/commands/schedules.d.ts +2 -0
  26. package/dist/src/commands/schedules.js +141 -0
  27. package/dist/src/commands/terminal-service.d.ts +38 -0
  28. package/dist/src/commands/terminal-service.js +210 -0
  29. package/dist/src/commands/terminal.d.ts +22 -0
  30. package/dist/src/commands/terminal.js +511 -0
  31. package/dist/src/component-lock.d.ts +100 -2
  32. package/dist/src/component-lock.js +304 -15
  33. package/dist/src/config.d.ts +10 -2
  34. package/dist/src/config.js +49 -14
  35. package/dist/src/database-schema-artifacts.d.ts +7 -0
  36. package/dist/src/database-schema-artifacts.js +8 -0
  37. package/dist/src/env-sync.d.ts +0 -3
  38. package/dist/src/env-sync.js +3 -19
  39. package/dist/src/metadata-backfill.d.ts +16 -6
  40. package/dist/src/metadata-backfill.js +1069 -18
  41. package/dist/src/project.d.ts +2 -0
  42. package/dist/src/project.js +4 -17
  43. package/dist/src/prompt.js +10 -18
  44. package/dist/src/resource-metadata.d.ts +1 -0
  45. package/dist/src/resource-metadata.js +3 -0
  46. package/dist/src/resource-syncs/database-schema-sync.d.ts +117 -0
  47. package/dist/src/resource-syncs/database-schema-sync.js +2289 -0
  48. package/dist/src/resource-syncs/database-view-sync.d.ts +124 -0
  49. package/dist/src/resource-syncs/database-view-sync.js +1317 -0
  50. package/dist/src/resource-syncs/endpoint-sync.d.ts +96 -0
  51. package/dist/src/resource-syncs/endpoint-sync.js +1283 -0
  52. package/dist/src/resource-syncs/event-sync.d.ts +99 -0
  53. package/dist/src/resource-syncs/event-sync.js +949 -0
  54. package/dist/src/resource-syncs/group-sync.d.ts +86 -0
  55. package/dist/src/resource-syncs/group-sync.js +882 -0
  56. package/dist/src/resource-syncs/job-template-sync.d.ts +85 -0
  57. package/dist/src/resource-syncs/job-template-sync.js +782 -0
  58. package/dist/src/resource-syncs/role-group-sync.d.ts +83 -0
  59. package/dist/src/resource-syncs/role-group-sync.js +597 -0
  60. package/dist/src/resource-syncs/schedule-sync.d.ts +111 -0
  61. package/dist/src/resource-syncs/schedule-sync.js +1302 -0
  62. package/dist/src/resource-syncs/util.d.ts +19 -0
  63. package/dist/src/resource-syncs/util.js +116 -0
  64. package/dist/src/runtime-view.d.ts +1 -0
  65. package/dist/src/runtime-view.js +6 -1
  66. package/dist/src/sync-output.d.ts +38 -0
  67. package/dist/src/sync-output.js +131 -0
  68. package/dist/src/tracked-resources.d.ts +1 -1
  69. package/dist/src/tracked-resources.js +26 -2
  70. package/dist/src/types.d.ts +224 -0
  71. package/dist/src/ui.d.ts +3 -0
  72. package/dist/src/ui.js +67 -18
  73. package/dist/src/utils.d.ts +2 -0
  74. package/dist/src/utils.js +64 -0
  75. package/dist/src/workspace-component.d.ts +2 -0
  76. package/dist/src/workspace-component.js +34 -0
  77. package/dist/src/workspace-resource.d.ts +2 -0
  78. package/dist/src/workspace-resource.js +52 -0
  79. package/package.json +8 -3
@@ -22,6 +22,9 @@ const VOLATILE_COMPONENT_FIELDS = new Set([
22
22
  'hash',
23
23
  'remoteHash',
24
24
  ]);
25
+ function isRecord(value) {
26
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
27
+ }
25
28
  function normalizeValue(value, fallback = null) {
26
29
  return value ?? fallback;
27
30
  }
@@ -86,6 +89,41 @@ export function normalizePortableComponentSource(component) {
86
89
  export function hashPortableComponentSource(component) {
87
90
  return hashStable(normalizePortableComponentSource(component));
88
91
  }
92
+ export const PORTABLE_COMPONENT_CONFIG_FIELDS = [
93
+ 'name',
94
+ 'category',
95
+ 'desc',
96
+ 'active',
97
+ 'type',
98
+ 'async',
99
+ ];
100
+ export function diffPortableComponentSource(source, target) {
101
+ const sourcePortable = normalizePortableComponentSource(source);
102
+ const targetPortable = normalizePortableComponentSource(target);
103
+ const configurationFields = PORTABLE_COMPONENT_CONFIG_FIELDS.filter((field) => sourcePortable[field] !== targetPortable[field]);
104
+ const elementsChanged = hashStable(sourcePortable.elements) !== hashStable(targetPortable.elements);
105
+ return {
106
+ configurationFields,
107
+ elementsChanged,
108
+ changedFields: [
109
+ ...configurationFields,
110
+ ...(elementsChanged ? ['elements'] : []),
111
+ ],
112
+ };
113
+ }
114
+ export function buildPortableComponentUpdatePayload(source, target, changedFields) {
115
+ if (!Number.isInteger(target.version)) {
116
+ throw new Error('target component version is required to update component configuration');
117
+ }
118
+ const sourcePortable = normalizePortableComponentSource(source);
119
+ const payload = { version: target.version };
120
+ for (const field of PORTABLE_COMPONENT_CONFIG_FIELDS) {
121
+ if (changedFields.includes(field)) {
122
+ payload[field] = sourcePortable[field];
123
+ }
124
+ }
125
+ return payload;
126
+ }
89
127
  export function getLockPath(projectRoot) {
90
128
  return path.join(projectRoot, REVO_LOCK_FILE);
91
129
  }
@@ -96,6 +134,14 @@ export function readComponentLock(projectRoot) {
96
134
  schemaVersion: 2,
97
135
  componentIdentity: COMPONENT_IDENTITY_BY_ID,
98
136
  components: {},
137
+ groups: {},
138
+ roleGroups: {},
139
+ jobTemplates: {},
140
+ schedules: {},
141
+ events: {},
142
+ endpoints: {},
143
+ databaseSchemas: {},
144
+ databaseViews: {},
99
145
  };
100
146
  }
101
147
  const raw = readJsonFile(lockPath);
@@ -115,20 +161,207 @@ export function readComponentLock(projectRoot) {
115
161
  : {}),
116
162
  componentId: String(entry.componentId || remoteComponentId),
117
163
  remoteComponentId,
164
+ ...(entry.environments && typeof entry.environments === 'object'
165
+ ? {
166
+ environments: Object.fromEntries(Object.entries(entry.environments)
167
+ .filter(([, baseline]) => (typeof baseline.remoteComponentId === 'string'
168
+ && typeof baseline.remoteHash === 'string'
169
+ && typeof baseline.syncedAt === 'string'))
170
+ .map(([envName, baseline]) => [envName, {
171
+ remoteComponentId: String(baseline.remoteComponentId),
172
+ remoteVersion: typeof baseline.remoteVersion === 'number' ? baseline.remoteVersion : null,
173
+ remoteHash: String(baseline.remoteHash),
174
+ syncedAt: String(baseline.syncedAt),
175
+ }])),
176
+ }
177
+ : {}),
118
178
  }];
119
179
  }));
120
180
  return {
121
181
  schemaVersion: 2,
122
182
  componentIdentity,
123
183
  components,
184
+ groups: Object.fromEntries(Object.entries(raw.groups && typeof raw.groups === 'object' ? raw.groups : {})
185
+ .filter(([, entry]) => (typeof entry.stableKey === 'string'
186
+ && typeof entry.path === 'string'
187
+ && typeof entry.sourceHash === 'string'
188
+ && entry.environments
189
+ && typeof entry.environments === 'object'))
190
+ .map(([key, entry]) => [key, {
191
+ stableKey: String(entry.stableKey),
192
+ path: String(entry.path),
193
+ sourceHash: String(entry.sourceHash),
194
+ ...((isRecord(entry.owner) && entry.owner.type === 'group' && typeof entry.owner.stableKey === 'string')
195
+ ? { owner: { type: 'group', stableKey: entry.owner.stableKey } }
196
+ : (isRecord(entry.owner) && entry.owner.type === 'manual-user')
197
+ ? { owner: { type: 'manual-user' } }
198
+ : (isRecord(entry.owner) && entry.owner.type === 'unresolved' && typeof entry.owner.reason === 'string')
199
+ ? { owner: { type: 'unresolved', reason: entry.owner.reason } }
200
+ : {}),
201
+ environments: Object.fromEntries(Object.entries(entry.environments)
202
+ .filter(([, baseline]) => typeof baseline.remoteHash === 'string' && typeof baseline.syncedAt === 'string')
203
+ .map(([envName, baseline]) => [envName, {
204
+ remoteHash: String(baseline.remoteHash),
205
+ syncedAt: String(baseline.syncedAt),
206
+ }])),
207
+ }])),
208
+ roleGroups: Object.fromEntries(Object.entries(raw.roleGroups && typeof raw.roleGroups === 'object' ? raw.roleGroups : {})
209
+ .filter(([, entry]) => (typeof entry.stableKey === 'string'
210
+ && typeof entry.path === 'string'
211
+ && typeof entry.sourceHash === 'string'
212
+ && entry.environments
213
+ && typeof entry.environments === 'object'))
214
+ .map(([key, entry]) => [key, {
215
+ stableKey: String(entry.stableKey),
216
+ path: String(entry.path),
217
+ sourceHash: String(entry.sourceHash),
218
+ environments: Object.fromEntries(Object.entries(entry.environments)
219
+ .filter(([, baseline]) => typeof baseline.remoteHash === 'string' && typeof baseline.syncedAt === 'string')
220
+ .map(([envName, baseline]) => [envName, {
221
+ remoteHash: String(baseline.remoteHash),
222
+ syncedAt: String(baseline.syncedAt),
223
+ }])),
224
+ }])),
225
+ jobTemplates: Object.fromEntries(Object.entries(raw.jobTemplates && typeof raw.jobTemplates === 'object' ? raw.jobTemplates : {})
226
+ .filter(([, entry]) => (typeof entry.stableKey === 'string'
227
+ && typeof entry.path === 'string'
228
+ && typeof entry.sourceHash === 'string'
229
+ && entry.environments
230
+ && typeof entry.environments === 'object'))
231
+ .map(([key, entry]) => [key, {
232
+ stableKey: String(entry.stableKey),
233
+ path: String(entry.path),
234
+ sourceHash: String(entry.sourceHash),
235
+ ...((isRecord(entry.migration) && (entry.migration.componentBinding === 'pinned-unsupported'
236
+ || entry.migration.executionPrincipal === 'excluded')) ? {
237
+ migration: {
238
+ ...(entry.migration.componentBinding === 'pinned-unsupported' ? { componentBinding: 'pinned-unsupported' } : {}),
239
+ ...(entry.migration.executionPrincipal === 'excluded' ? { executionPrincipal: 'excluded' } : {}),
240
+ },
241
+ } : {}),
242
+ environments: Object.fromEntries(Object.entries(entry.environments)
243
+ .filter(([, baseline]) => typeof baseline.remoteHash === 'string' && typeof baseline.syncedAt === 'string')
244
+ .map(([envName, baseline]) => [envName, {
245
+ remoteHash: String(baseline.remoteHash),
246
+ syncedAt: String(baseline.syncedAt),
247
+ }])),
248
+ }])),
249
+ schedules: Object.fromEntries(Object.entries(raw.schedules && typeof raw.schedules === 'object' ? raw.schedules : {})
250
+ .filter(([, entry]) => (typeof entry.stableKey === 'string'
251
+ && typeof entry.path === 'string'
252
+ && typeof entry.sourceHash === 'string'
253
+ && entry.environments
254
+ && typeof entry.environments === 'object'))
255
+ .map(([key, entry]) => [key, {
256
+ stableKey: String(entry.stableKey),
257
+ path: String(entry.path),
258
+ sourceHash: String(entry.sourceHash),
259
+ environments: Object.fromEntries(Object.entries(entry.environments)
260
+ .filter(([, baseline]) => typeof baseline.remoteHash === 'string' && typeof baseline.syncedAt === 'string')
261
+ .map(([envName, baseline]) => [envName, {
262
+ remoteHash: String(baseline.remoteHash),
263
+ syncedAt: String(baseline.syncedAt),
264
+ }])),
265
+ }])),
266
+ events: Object.fromEntries(Object.entries(raw.events && typeof raw.events === 'object' ? raw.events : {})
267
+ .filter(([, entry]) => (typeof entry.stableKey === 'string'
268
+ && typeof entry.path === 'string'
269
+ && typeof entry.sourceHash === 'string'
270
+ && entry.environments
271
+ && typeof entry.environments === 'object'))
272
+ .map(([key, entry]) => [key, {
273
+ stableKey: String(entry.stableKey),
274
+ path: String(entry.path),
275
+ sourceHash: String(entry.sourceHash),
276
+ environments: Object.fromEntries(Object.entries(entry.environments)
277
+ .filter(([, baseline]) => typeof baseline.remoteHash === 'string' && typeof baseline.syncedAt === 'string')
278
+ .map(([envName, baseline]) => [envName, {
279
+ remoteHash: String(baseline.remoteHash),
280
+ syncedAt: String(baseline.syncedAt),
281
+ }])),
282
+ }])),
283
+ endpoints: Object.fromEntries(Object.entries(raw.endpoints && typeof raw.endpoints === 'object' ? raw.endpoints : {})
284
+ .filter(([, entry]) => (typeof entry.stableKey === 'string'
285
+ && typeof entry.path === 'string'
286
+ && typeof entry.sourceHash === 'string'
287
+ && entry.environments
288
+ && typeof entry.environments === 'object'))
289
+ .map(([key, entry]) => [key, {
290
+ stableKey: String(entry.stableKey),
291
+ path: String(entry.path),
292
+ sourceHash: String(entry.sourceHash),
293
+ environments: Object.fromEntries(Object.entries(entry.environments)
294
+ .filter(([, baseline]) => typeof baseline.remoteHash === 'string' && typeof baseline.syncedAt === 'string')
295
+ .map(([envName, baseline]) => [envName, {
296
+ remoteHash: String(baseline.remoteHash),
297
+ syncedAt: String(baseline.syncedAt),
298
+ }])),
299
+ }])),
300
+ databaseSchemas: Object.fromEntries(Object.entries(raw.databaseSchemas && typeof raw.databaseSchemas === 'object' ? raw.databaseSchemas : {})
301
+ .filter(([, entry]) => (typeof entry.stableKey === 'string'
302
+ && typeof entry.path === 'string'
303
+ && typeof entry.sourceHash === 'string'
304
+ && entry.environments
305
+ && typeof entry.environments === 'object'))
306
+ .map(([key, entry]) => [key, {
307
+ stableKey: String(entry.stableKey),
308
+ path: String(entry.path),
309
+ sourceHash: String(entry.sourceHash),
310
+ ...(typeof entry.sourceRestricted === 'boolean' ? { sourceRestricted: entry.sourceRestricted } : {}),
311
+ ...(typeof entry.parentStableKey === 'string' ? { parentStableKey: entry.parentStableKey } : {}),
312
+ environments: Object.fromEntries(Object.entries(entry.environments)
313
+ .filter(([, baseline]) => typeof baseline.remoteHash === 'string')
314
+ .map(([envName, baseline]) => [envName, {
315
+ remoteHash: String(baseline.remoteHash),
316
+ ...(typeof baseline.tombstone?.deletedAt === 'string' && typeof baseline.tombstone.parentStableKey === 'string'
317
+ ? { tombstone: {
318
+ deletedAt: baseline.tombstone.deletedAt,
319
+ parentStableKey: baseline.tombstone.parentStableKey,
320
+ } }
321
+ : {}),
322
+ }])),
323
+ }])),
324
+ databaseViews: Object.fromEntries(Object.entries(raw.databaseViews && typeof raw.databaseViews === 'object' ? raw.databaseViews : {})
325
+ .filter(([, entry]) => (typeof entry.stableKey === 'string'
326
+ && typeof entry.path === 'string'
327
+ && typeof entry.sourceHash === 'string'
328
+ && entry.environments
329
+ && typeof entry.environments === 'object'))
330
+ .map(([key, entry]) => [key, {
331
+ stableKey: String(entry.stableKey),
332
+ path: String(entry.path),
333
+ sourceHash: String(entry.sourceHash),
334
+ environments: Object.fromEntries(Object.entries(entry.environments)
335
+ .filter(([, baseline]) => typeof baseline.remoteHash === 'string' && typeof baseline.syncedAt === 'string')
336
+ .map(([envName, baseline]) => [envName, {
337
+ remoteHash: String(baseline.remoteHash),
338
+ syncedAt: String(baseline.syncedAt),
339
+ }])),
340
+ }])),
124
341
  };
125
342
  }
126
343
  export function writeComponentLock(projectRoot, lock) {
127
344
  const sortedComponents = Object.fromEntries(Object.entries(lock.components).sort(([left], [right]) => left.localeCompare(right)));
345
+ const sortedRoleGroups = Object.fromEntries(Object.entries(lock.roleGroups).sort(([left], [right]) => left.localeCompare(right)));
346
+ const sortedGroups = Object.fromEntries(Object.entries(lock.groups || {}).sort(([left], [right]) => left.localeCompare(right)));
347
+ const sortedJobTemplates = Object.fromEntries(Object.entries(lock.jobTemplates || {}).sort(([left], [right]) => left.localeCompare(right)));
348
+ const sortedSchedules = Object.fromEntries(Object.entries(lock.schedules || {}).sort(([left], [right]) => left.localeCompare(right)));
349
+ const sortedEvents = Object.fromEntries(Object.entries(lock.events || {}).sort(([left], [right]) => left.localeCompare(right)));
350
+ const sortedEndpoints = Object.fromEntries(Object.entries(lock.endpoints || {}).sort(([left], [right]) => left.localeCompare(right)));
351
+ const sortedDatabaseSchemas = Object.fromEntries(Object.entries(lock.databaseSchemas || {}).sort(([left], [right]) => left.localeCompare(right)));
352
+ const sortedDatabaseViews = Object.fromEntries(Object.entries(lock.databaseViews || {}).sort(([left], [right]) => left.localeCompare(right)));
128
353
  writeJsonFile(getLockPath(projectRoot), {
129
354
  schemaVersion: 2,
130
355
  componentIdentity: normalizeComponentIdentityConfig(lock.componentIdentity),
131
356
  components: sortedComponents,
357
+ ...(Object.keys(sortedGroups).length > 0 ? { groups: sortedGroups } : {}),
358
+ roleGroups: sortedRoleGroups,
359
+ jobTemplates: sortedJobTemplates,
360
+ ...(Object.keys(sortedSchedules).length > 0 ? { schedules: sortedSchedules } : {}),
361
+ ...(Object.keys(sortedEvents).length > 0 ? { events: sortedEvents } : {}),
362
+ ...(Object.keys(sortedEndpoints).length > 0 ? { endpoints: sortedEndpoints } : {}),
363
+ ...(Object.keys(sortedDatabaseSchemas).length > 0 ? { databaseSchemas: sortedDatabaseSchemas } : {}),
364
+ ...(Object.keys(sortedDatabaseViews).length > 0 ? { databaseViews: sortedDatabaseViews } : {}),
132
365
  });
133
366
  }
134
367
  export function componentLockKey(identity, component) {
@@ -137,14 +370,49 @@ export function componentLockKey(identity, component) {
137
370
  export function componentLockMatchesIdentity(lock, identity) {
138
371
  return sameComponentIdentityConfig(lock.componentIdentity, identity);
139
372
  }
140
- export function getComponentLockEntry(lock, identity, component) {
373
+ export function getComponentLockEntry(lock, identity, component, environmentName) {
141
374
  if (!componentLockMatchesIdentity(lock, identity)) {
142
375
  return null;
143
376
  }
144
377
  const key = componentLockKey(identity, component);
145
- return key ? lock.components[key] || null : null;
378
+ const entry = key ? lock.components[key] || null : null;
379
+ if (!entry || !environmentName) {
380
+ return entry;
381
+ }
382
+ const baseline = entry.environments?.[environmentName];
383
+ if (!baseline) {
384
+ return null;
385
+ }
386
+ return {
387
+ ...entry,
388
+ remoteComponentId: baseline.remoteComponentId,
389
+ remoteVersion: baseline.remoteVersion,
390
+ remoteHash: baseline.remoteHash,
391
+ pulledAt: baseline.syncedAt,
392
+ };
146
393
  }
147
- export function upsertComponentLockEntry(projectRoot, entry, identity = COMPONENT_IDENTITY_BY_ID) {
394
+ export function getComponentLockEntries(lock, identity, environmentName) {
395
+ if (!componentLockMatchesIdentity(lock, identity)) {
396
+ return {};
397
+ }
398
+ if (!environmentName) {
399
+ return lock.components;
400
+ }
401
+ return Object.fromEntries(Object.entries(lock.components).flatMap(([key, entry]) => {
402
+ const baseline = entry.environments?.[environmentName];
403
+ if (!baseline) {
404
+ return [];
405
+ }
406
+ return [[key, {
407
+ ...entry,
408
+ remoteComponentId: baseline.remoteComponentId,
409
+ remoteVersion: baseline.remoteVersion,
410
+ remoteHash: baseline.remoteHash,
411
+ pulledAt: baseline.syncedAt,
412
+ }]];
413
+ }));
414
+ }
415
+ export function upsertComponentLockEntry(projectRoot, entry, identity = COMPONENT_IDENTITY_BY_ID, environmentName) {
148
416
  const normalizedIdentity = normalizeComponentIdentityConfig(identity);
149
417
  const lock = readComponentLock(projectRoot);
150
418
  if (!componentLockMatchesIdentity(lock, normalizedIdentity)) {
@@ -170,21 +438,42 @@ export function upsertComponentLockEntry(projectRoot, entry, identity = COMPONEN
170
438
  : {}),
171
439
  };
172
440
  const existing = lock.components[key];
441
+ const storedEntry = environmentName
442
+ ? {
443
+ ...normalizedEntry,
444
+ remoteComponentId: existing?.remoteComponentId || normalizedEntry.remoteComponentId,
445
+ remoteVersion: existing?.remoteVersion ?? normalizedEntry.remoteVersion,
446
+ remoteHash: existing?.remoteHash || normalizedEntry.remoteHash,
447
+ environments: {
448
+ ...existing?.environments,
449
+ [environmentName]: {
450
+ remoteComponentId: normalizedEntry.remoteComponentId,
451
+ remoteVersion: normalizedEntry.remoteVersion,
452
+ remoteHash: normalizedEntry.remoteHash,
453
+ syncedAt: normalizedEntry.pulledAt,
454
+ },
455
+ },
456
+ }
457
+ : {
458
+ ...normalizedEntry,
459
+ ...(existing?.environments ? { environments: existing.environments } : {}),
460
+ };
173
461
  if (existing
174
- && existing.identityMode === normalizedEntry.identityMode
175
- && existing.metadataProperty === normalizedEntry.metadataProperty
176
- && existing.stableKey === normalizedEntry.stableKey
177
- && existing.componentId === normalizedEntry.componentId
178
- && existing.remoteComponentId === normalizedEntry.remoteComponentId
179
- && existing.componentName === normalizedEntry.componentName
180
- && existing.category === normalizedEntry.category
181
- && existing.path === normalizedEntry.path
182
- && existing.remoteVersion === normalizedEntry.remoteVersion
183
- && existing.remoteHash === normalizedEntry.remoteHash
184
- && existing.sourceHash === normalizedEntry.sourceHash) {
462
+ && existing.identityMode === storedEntry.identityMode
463
+ && existing.metadataProperty === storedEntry.metadataProperty
464
+ && existing.stableKey === storedEntry.stableKey
465
+ && existing.componentId === storedEntry.componentId
466
+ && existing.remoteComponentId === storedEntry.remoteComponentId
467
+ && existing.componentName === storedEntry.componentName
468
+ && existing.category === storedEntry.category
469
+ && existing.path === storedEntry.path
470
+ && existing.remoteVersion === storedEntry.remoteVersion
471
+ && existing.remoteHash === storedEntry.remoteHash
472
+ && existing.sourceHash === storedEntry.sourceHash
473
+ && JSON.stringify(existing.environments || {}) === JSON.stringify(storedEntry.environments || {})) {
185
474
  return;
186
475
  }
187
476
  lock.componentIdentity = normalizedIdentity;
188
- lock.components[key] = normalizedEntry;
477
+ lock.components[key] = storedEntry;
189
478
  writeComponentLock(projectRoot, lock);
190
479
  }
@@ -4,6 +4,7 @@ export type RuntimeConfig = {
4
4
  baseUrl: string;
5
5
  instance: string;
6
6
  token: string;
7
+ production?: true;
7
8
  };
8
9
  export type RuntimeConfigOptions = {
9
10
  baseUrl?: string;
@@ -15,10 +16,12 @@ export type StoredEnvironmentProfile = {
15
16
  baseUrl: string;
16
17
  token: string;
17
18
  instance?: string;
19
+ production?: true;
18
20
  };
19
21
  type StoredConfig = Omit<RuntimeConfig, 'instance'> & {
20
22
  instance?: string;
21
23
  environments?: Record<string, StoredEnvironmentProfile>;
24
+ activeEnvironment?: string;
22
25
  };
23
26
  export type AuthValidationState = {
24
27
  key: string;
@@ -54,14 +57,19 @@ export declare function loadStoredConfig(options?: {
54
57
  }): StoredConfig;
55
58
  export declare function listEnvironmentProfiles(): Record<string, StoredEnvironmentProfile>;
56
59
  export declare function getEnvironmentProfile(name: string): StoredEnvironmentProfile | null;
57
- export declare function saveEnvironmentProfile(name: string, profile: StoredEnvironmentProfile): void;
60
+ export declare const DEFAULT_ENV_NAME = "default";
61
+ export declare function getActiveEnvironmentName(): string;
62
+ export declare function setActiveEnvironmentName(name: string): void;
63
+ export declare function saveEnvironmentProfile(name: string, profile: StoredEnvironmentProfile, options?: {
64
+ activate?: boolean;
65
+ }): void;
58
66
  export declare function removeEnvironmentProfile(name: string): boolean;
59
67
  export declare function buildAuthValidationKey(runtime: RuntimeConfigOptions): string;
60
68
  export declare function readAuthValidationState(key?: string): AuthValidationState;
61
69
  export declare function saveAuthValidationState(state: AuthValidationState): void;
62
70
  export declare function clearAuthValidationState(key?: string): void;
63
- export declare const DEFAULT_ENV_NAME = "default";
64
71
  export declare function resolveRuntimeConfig(options?: RuntimeConfigOptions): {
72
+ production?: true | undefined;
65
73
  baseUrl: string;
66
74
  instance: string;
67
75
  token: string;
@@ -75,11 +75,13 @@ export function saveStoredConfig(nextConfig) {
75
75
  const current = readStoredConfigFile();
76
76
  const instance = nextConfig.instance || current.instance || '';
77
77
  const environments = nextConfig.environments ?? current.environments;
78
+ const activeEnvironment = nextConfig.activeEnvironment ?? current.activeEnvironment;
78
79
  const config = {
79
80
  baseUrl: nextConfig.baseUrl || current.baseUrl || DEFAULT_BASE_URL,
80
81
  token: nextConfig.token || current.token || '',
81
82
  ...(instance ? { instance } : {}),
82
83
  ...(environments && Object.keys(environments).length > 0 ? { environments } : {}),
84
+ ...(activeEnvironment && activeEnvironment !== DEFAULT_ENV_NAME ? { activeEnvironment } : {}),
83
85
  };
84
86
  writeJsonFile(configFile, config);
85
87
  removeFileIfExists(credentialsFile);
@@ -88,13 +90,15 @@ export function clearStoredConfig() {
88
90
  const { dir, configFile, credentialsFile, authStateFile } = getConfigPaths();
89
91
  // Logout clears the default login but must not destroy named environment
90
92
  // profiles stored alongside it.
91
- const environments = readStoredConfigFile().environments;
93
+ const stored = readStoredConfigFile();
94
+ const environments = stored.environments;
92
95
  if (environments && Object.keys(environments).length > 0) {
93
96
  ensureDirectory(dir);
94
97
  writeJsonFile(configFile, {
95
98
  baseUrl: DEFAULT_BASE_URL,
96
99
  token: '',
97
100
  environments,
101
+ ...(stored.activeEnvironment ? { activeEnvironment: stored.activeEnvironment } : {}),
98
102
  });
99
103
  for (const filePath of [credentialsFile, authStateFile]) {
100
104
  try {
@@ -181,6 +185,7 @@ function parseEnvironmentProfiles(value) {
181
185
  baseUrl: entry.baseUrl,
182
186
  token: entry.token,
183
187
  ...(typeof entry.instance === 'string' && isUuid(entry.instance) ? { instance: entry.instance } : {}),
188
+ ...(entry.production === true ? { production: true } : {}),
184
189
  };
185
190
  }
186
191
  return Object.keys(environments).length > 0 ? environments : null;
@@ -195,6 +200,11 @@ function parseFlatConfig(raw, credentials) {
195
200
  ? credentials.token
196
201
  : '';
197
202
  const environments = parseEnvironmentProfiles(raw.environments);
203
+ const activeEnvironment = typeof raw.activeEnvironment === 'string'
204
+ && raw.activeEnvironment !== DEFAULT_ENV_NAME
205
+ && environments?.[raw.activeEnvironment]
206
+ ? raw.activeEnvironment
207
+ : undefined;
198
208
  return {
199
209
  baseUrl: typeof raw.baseUrl === 'string'
200
210
  ? raw.baseUrl
@@ -203,6 +213,7 @@ function parseFlatConfig(raw, credentials) {
203
213
  : DEFAULT_BASE_URL,
204
214
  ...(typeof raw.instance === 'string' && isUuid(raw.instance) ? { instance: raw.instance } : {}),
205
215
  ...(environments ? { environments } : {}),
216
+ ...(activeEnvironment ? { activeEnvironment } : {}),
206
217
  token,
207
218
  };
208
219
  }
@@ -225,6 +236,11 @@ function parseMappedConfig(raw) {
225
236
  ? configuredDefault
226
237
  : Object.keys(instances)[0] || '';
227
238
  const environments = parseEnvironmentProfiles(raw.environments);
239
+ const activeEnvironment = typeof raw.activeEnvironment === 'string'
240
+ && raw.activeEnvironment !== DEFAULT_ENV_NAME
241
+ && environments?.[raw.activeEnvironment]
242
+ ? raw.activeEnvironment
243
+ : undefined;
228
244
  const selected = defaultInstance ? instances[defaultInstance] : undefined;
229
245
  const base = selected
230
246
  ? {
@@ -233,7 +249,11 @@ function parseMappedConfig(raw) {
233
249
  token: selected.token,
234
250
  }
235
251
  : emptyStoredConfigFile();
236
- return environments ? { ...base, environments } : base;
252
+ return {
253
+ ...base,
254
+ ...(environments ? { environments } : {}),
255
+ ...(activeEnvironment ? { activeEnvironment } : {}),
256
+ };
237
257
  }
238
258
  function parseLegacyConfig(config, credentials) {
239
259
  const baseUrl = isRecord(config) && typeof config.baseUrl === 'string'
@@ -319,9 +339,19 @@ export function listEnvironmentProfiles() {
319
339
  export function getEnvironmentProfile(name) {
320
340
  return listEnvironmentProfiles()[name] || null;
321
341
  }
322
- export function saveEnvironmentProfile(name, profile) {
342
+ export const DEFAULT_ENV_NAME = 'default';
343
+ export function getActiveEnvironmentName() {
344
+ return readStoredConfigFile().activeEnvironment || DEFAULT_ENV_NAME;
345
+ }
346
+ export function setActiveEnvironmentName(name) {
347
+ if (name !== DEFAULT_ENV_NAME && !getEnvironmentProfile(name)) {
348
+ throw new Error(`Unknown project "${name}". Run 'revo projects list' to see saved projects.`);
349
+ }
350
+ saveStoredConfig({ activeEnvironment: name });
351
+ }
352
+ export function saveEnvironmentProfile(name, profile, options = {}) {
323
353
  const environments = { ...listEnvironmentProfiles(), [name]: profile };
324
- saveStoredConfig({ environments });
354
+ saveStoredConfig({ environments, ...(options.activate ? { activeEnvironment: name } : {}) });
325
355
  }
326
356
  export function removeEnvironmentProfile(name) {
327
357
  const environments = listEnvironmentProfiles();
@@ -329,7 +359,10 @@ export function removeEnvironmentProfile(name) {
329
359
  return false;
330
360
  }
331
361
  delete environments[name];
332
- saveStoredConfig({ environments });
362
+ saveStoredConfig({
363
+ environments,
364
+ ...(getActiveEnvironmentName() === name ? { activeEnvironment: DEFAULT_ENV_NAME } : {}),
365
+ });
333
366
  return true;
334
367
  }
335
368
  export function buildAuthValidationKey(runtime) {
@@ -422,31 +455,33 @@ export function clearAuthValidationState(key) {
422
455
  }
423
456
  }
424
457
  }
425
- export const DEFAULT_ENV_NAME = 'default';
426
458
  export function resolveRuntimeConfig(options = {}) {
427
459
  const env = {
428
460
  baseUrl: resolveEnvValue(['REVO_URL', 'REVO_BASE_URL', 'REVOENGINE_URL', 'REVOENGINE_BASE_URL']),
429
461
  instance: resolveEnvValue(['REVO_INSTANCE', 'REVOENGINE_INSTANCE']),
430
462
  token: resolveEnvValue(['REVO_TOKEN', 'REVO_API_KEY', 'REVOENGINE_TOKEN', 'REVOENGINE_API_KEY']),
431
463
  };
432
- const stored = loadStoredConfig();
464
+ const stored = readStoredConfigFile();
433
465
  // A named profile is an explicit selection and resolves exclusively: no
434
466
  // fallback to env vars or the stored default, or --from/--to could silently
435
467
  // mix one environment's URL with another environment's token or instance.
436
- if (options.envName && options.envName !== DEFAULT_ENV_NAME) {
437
- const profile = getEnvironmentProfile(options.envName);
468
+ const envName = options.envName || stored.activeEnvironment || DEFAULT_ENV_NAME;
469
+ if (envName !== DEFAULT_ENV_NAME) {
470
+ const profile = stored.environments?.[envName];
438
471
  if (!profile) {
439
- throw new Error(`Unknown environment "${options.envName}". Run 'revo env list' to see saved environments.`);
472
+ throw new Error(`Unknown project "${envName}". Run 'revo projects list' to see saved projects.`);
440
473
  }
441
474
  return {
442
475
  baseUrl: options.baseUrl || profile.baseUrl || DEFAULT_BASE_URL,
443
- instance: options.instance || profile.instance || '',
476
+ instance: options.instance ?? (profile.instance || ''),
444
477
  token: options.token || profile.token || '',
478
+ ...(profile.production ? { production: true } : {}),
445
479
  };
446
480
  }
481
+ const defaultStored = mergeWithLegacy(stored);
447
482
  return {
448
- baseUrl: options.baseUrl || env.baseUrl || stored.baseUrl || DEFAULT_BASE_URL,
449
- instance: options.instance || env.instance || stored.instance || '',
450
- token: options.token || env.token || stored.token || '',
483
+ baseUrl: options.baseUrl || env.baseUrl || defaultStored.baseUrl || DEFAULT_BASE_URL,
484
+ instance: options.instance ?? (env.instance || defaultStored.instance || ''),
485
+ token: options.token || env.token || defaultStored.token || '',
451
486
  };
452
487
  }
@@ -0,0 +1,7 @@
1
+ export declare const DATABASE_BACKUP_CATEGORY = "Database Backup";
2
+ export declare function isDatabaseBackupArtifact(value: {
3
+ category?: unknown;
4
+ }): boolean;
5
+ export declare function excludeDatabaseBackupArtifacts<T extends {
6
+ category?: unknown;
7
+ }>(records: readonly T[]): T[];
@@ -0,0 +1,8 @@
1
+ export const DATABASE_BACKUP_CATEGORY = 'Database Backup';
2
+ export function isDatabaseBackupArtifact(value) {
3
+ return typeof value.category === 'string'
4
+ && value.category.trim() === DATABASE_BACKUP_CATEGORY;
5
+ }
6
+ export function excludeDatabaseBackupArtifacts(records) {
7
+ return records.filter((record) => !isDatabaseBackupArtifact(record));
8
+ }
@@ -69,8 +69,6 @@ export declare function buildStableKey(component: ComponentRecord, stableKeyName
69
69
  export declare function captureEnvSnapshot(client: RevoClient, envName: string, options?: {
70
70
  stableKeyName?: string;
71
71
  }): Promise<EnvSnapshot>;
72
- declare const PORTABLE_METADATA_FIELDS: readonly ["name", "category", "desc", "active", "type", "async"];
73
- export type PortableMetadataField = (typeof PORTABLE_METADATA_FIELDS)[number];
74
72
  export declare function buildEnvSyncPlan(source: EnvSnapshot, target: EnvSnapshot, options: {
75
73
  prune: boolean;
76
74
  scope?: EnvSyncScope;
@@ -83,4 +81,3 @@ export declare function applyEnvSyncPlan(input: {
83
81
  stableKeyName?: string;
84
82
  onItem?: (result: EnvApplyItemResult) => void;
85
83
  }): Promise<EnvApplyItemResult[]>;
86
- export {};