@contrail/cli 3.3.3 → 3.5.0-alpha

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 (50) hide show
  1. package/README.md +150 -1
  2. package/dist/api/types.d.ts +35 -0
  3. package/dist/api/types.js +47 -1
  4. package/dist/auth/login.js +0 -8
  5. package/dist/basecommand.d.ts +3 -1
  6. package/dist/basecommand.js +1 -1
  7. package/dist/commands/app/extension/deploy.js +2 -4
  8. package/dist/commands/entities/create.d.ts +37 -0
  9. package/dist/commands/entities/create.js +555 -0
  10. package/dist/commands/entities/create.spec.d.ts +1 -0
  11. package/dist/commands/entities/create.spec.js +514 -0
  12. package/dist/commands/entities/delete.d.ts +16 -0
  13. package/dist/commands/entities/delete.js +118 -0
  14. package/dist/commands/entities/get.d.ts +20 -0
  15. package/dist/commands/entities/get.js +204 -0
  16. package/dist/commands/entities/update.d.ts +18 -0
  17. package/dist/commands/entities/update.js +116 -0
  18. package/dist/commands/types/uploadUsageInfo.js +2 -14
  19. package/dist/common.d.ts +1 -1
  20. package/dist/helpers/app/app-deployer.js +4 -8
  21. package/dist/helpers/app/extension-package-deployer.js +1 -1
  22. package/dist/helpers/config/config.js +3 -3
  23. package/dist/helpers/entities/assortment-items.d.ts +10 -0
  24. package/dist/helpers/entities/assortment-items.js +84 -0
  25. package/dist/helpers/entities/batchFetch.d.ts +8 -0
  26. package/dist/helpers/entities/batchFetch.js +41 -0
  27. package/dist/helpers/entities/index.d.ts +3 -0
  28. package/dist/helpers/entities/index.js +6 -0
  29. package/dist/helpers/entities/items.d.ts +10 -0
  30. package/dist/helpers/entities/items.js +102 -0
  31. package/dist/helpers/entities/project-items.d.ts +3 -0
  32. package/dist/helpers/entities/project-items.js +65 -0
  33. package/dist/helpers/file-helpers.d.ts +1 -0
  34. package/dist/helpers/file-helpers.js +19 -1
  35. package/dist/helpers/propertySpecifications/dateRanges.d.ts +38 -0
  36. package/dist/helpers/propertySpecifications/dateRanges.js +89 -0
  37. package/dist/helpers/propertySpecifications/dateRanges.spec.d.ts +1 -0
  38. package/dist/helpers/propertySpecifications/dateRanges.spec.js +95 -0
  39. package/dist/helpers/propertySpecifications/propertySpecifications.d.ts +27 -0
  40. package/dist/helpers/propertySpecifications/propertySpecifications.js +70 -0
  41. package/dist/helpers/propertySpecifications/propertySpecifications.spec.d.ts +1 -0
  42. package/dist/helpers/propertySpecifications/propertySpecifications.spec.js +266 -0
  43. package/dist/helpers/types/type-uploader.js +0 -7
  44. package/dist/interfaces.d.ts +0 -1
  45. package/dist/templates/typescript/README.md +1 -3
  46. package/dist/templates/typescript/assets/sample.json +1 -1
  47. package/dist/templates/typescript/eslintrc.json +4 -1
  48. package/dist/templates/typescript/tsconfig.json +21 -5
  49. package/oclif.manifest.json +361 -1
  50. package/package.json +6 -5
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.throwIfInvalidItemLevel = exports.Roles = exports.getFamilyName = exports.getItemFamilies = exports.getManyItems = exports.getAllItemsUsingCacheFallback = exports.throwIfInvalidItemId = void 0;
4
+ const sdk_1 = require("@contrail/sdk");
5
+ async function throwIfInvalidItemId(itemId) {
6
+ if (!(await doesItemExistById(itemId))) {
7
+ throw new Error(`Item with ID ${itemId} does not exist`);
8
+ }
9
+ }
10
+ exports.throwIfInvalidItemId = throwIfInvalidItemId;
11
+ async function doesItemExistById(id) {
12
+ const client = new sdk_1.Entities();
13
+ const item = await client.get({ entityName: 'item', id });
14
+ return !!item;
15
+ }
16
+ const ITEMS_CACHE_BY_ID = new Map();
17
+ async function getAllItemsUsingCacheFallback() {
18
+ if (ITEMS_CACHE_BY_ID.size > 0) {
19
+ return Array.from(ITEMS_CACHE_BY_ID.values());
20
+ }
21
+ const items = await getManyItems();
22
+ items.forEach((item) => {
23
+ ITEMS_CACHE_BY_ID.set(item.id, item);
24
+ });
25
+ return items;
26
+ }
27
+ exports.getAllItemsUsingCacheFallback = getAllItemsUsingCacheFallback;
28
+ async function getManyItems(maxCount = 10000) {
29
+ const client = new sdk_1.Entities();
30
+ // paginate to get all items
31
+ // Note: This will only return the first 100 assortments by default.
32
+ const items = [];
33
+ let nextPageKey = undefined;
34
+ while (true) {
35
+ const response = await client.get({
36
+ entityName: 'item',
37
+ apiVersion: sdk_1.API_VERSION.V2,
38
+ // customParams: { propertySlugs: 'id,familyId,roles,name' }, // not compatible with V2 for some reason
39
+ nextPageKey,
40
+ });
41
+ nextPageKey = response.nextPageKey;
42
+ if (response.results.length === 0) {
43
+ break; // No more assortments to fetch
44
+ }
45
+ items.push(...response.results);
46
+ if (items.length >= maxCount) {
47
+ console.warn(`Reached maximum item limit of ${maxCount}. Some items may not be included.`);
48
+ break; // Stop if we reach the maximum limit
49
+ }
50
+ }
51
+ return items;
52
+ }
53
+ exports.getManyItems = getManyItems;
54
+ async function getItemFamilies(count) {
55
+ return getItems({ roles: ['family'] }, count);
56
+ }
57
+ exports.getItemFamilies = getItemFamilies;
58
+ async function getFamilyName(item) {
59
+ if (!item.roles.includes('option')) {
60
+ return undefined;
61
+ }
62
+ if (!item.familyId) {
63
+ return undefined;
64
+ }
65
+ await ensureItemCacheIsPopulated();
66
+ const family = ITEMS_CACHE_BY_ID.get(item.familyId);
67
+ return family ? [family.name] : ['placeholder']; // placeholder if family not found
68
+ }
69
+ exports.getFamilyName = getFamilyName;
70
+ async function ensureItemCacheIsPopulated() {
71
+ if (ITEMS_CACHE_BY_ID.size === 0) {
72
+ await getAllItemsUsingCacheFallback();
73
+ }
74
+ }
75
+ exports.Roles = ['option', 'family'];
76
+ function throwIfInvalidItemLevel(role) {
77
+ if (!exports.Roles.includes(role)) {
78
+ throw new Error(`Invalid role: ${role}. Valid roles are: ${exports.Roles.join(', ')}`);
79
+ }
80
+ }
81
+ exports.throwIfInvalidItemLevel = throwIfInvalidItemLevel;
82
+ async function getItems(criteria, count = 100) {
83
+ const client = new sdk_1.Entities();
84
+ const items = [];
85
+ let nextPageKey = undefined;
86
+ do {
87
+ const response = await client.get({
88
+ entityName: 'item',
89
+ criteria,
90
+ nextPageKey,
91
+ apiVersion: sdk_1.API_VERSION.V2,
92
+ });
93
+ nextPageKey = response.nextPageKey;
94
+ if (response.results.length === 0) {
95
+ break; // No more items to fetch
96
+ }
97
+ const numItemsCanBeAdded = count - items.length;
98
+ const itemsToAdd = response.results.slice(0, numItemsCanBeAdded);
99
+ items.push(...itemsToAdd);
100
+ } while (nextPageKey && items.length < count);
101
+ return items;
102
+ }
@@ -0,0 +1,3 @@
1
+ import { Item } from '@contrail/entity-types';
2
+ export declare function throwIfInvalidProjectId(projectId: string): Promise<void>;
3
+ export declare function findItemsNotInProject(projectId: string): Promise<Item[]>;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findItemsNotInProject = exports.throwIfInvalidProjectId = void 0;
4
+ const sdk_1 = require("@contrail/sdk");
5
+ const batchFetch_1 = require("./batchFetch");
6
+ const items_1 = require("./items");
7
+ const PROJECT_ITEMS_CACHE_BY_PROJECT_ID = new Map();
8
+ const PROJECT_ITEMS_CACHE_BY_ITEM_ID = new Map();
9
+ async function throwIfInvalidProjectId(projectId) {
10
+ if (!(await doesProjectExistById(projectId))) {
11
+ throw new Error(`Project with ID ${projectId} does not exist`);
12
+ }
13
+ }
14
+ exports.throwIfInvalidProjectId = throwIfInvalidProjectId;
15
+ async function doesProjectExistById(id) {
16
+ const client = new sdk_1.Entities();
17
+ const project = await client
18
+ .get({
19
+ entityName: 'project',
20
+ id,
21
+ customParams: { propertySlugs: 'id' },
22
+ })
23
+ .catch((e) => {
24
+ console.error(e);
25
+ return null; // Return null if project does not exist
26
+ });
27
+ return !!project;
28
+ }
29
+ async function cacheProjectItemsByProjectId(projectId) {
30
+ if (PROJECT_ITEMS_CACHE_BY_PROJECT_ID.has(projectId)) {
31
+ return; // Already cached
32
+ }
33
+ const projectItems = await (0, batchFetch_1.batchFetch)('project-item', {
34
+ criteria: { projectId },
35
+ customParams: { propertySlugs: 'id,projectId,assortmentId' },
36
+ });
37
+ PROJECT_ITEMS_CACHE_BY_PROJECT_ID.set(projectId, projectItems);
38
+ projectItems.forEach((item) => {
39
+ if (!PROJECT_ITEMS_CACHE_BY_ITEM_ID.has(item.itemId)) {
40
+ PROJECT_ITEMS_CACHE_BY_ITEM_ID.set(item.itemId, []);
41
+ }
42
+ PROJECT_ITEMS_CACHE_BY_ITEM_ID.get(item.itemId).push(item);
43
+ });
44
+ }
45
+ async function getProjectItemsByProjectId(projectId) {
46
+ await cacheProjectItemsByProjectId(projectId);
47
+ const projectItems = PROJECT_ITEMS_CACHE_BY_PROJECT_ID.get(projectId);
48
+ if (!projectItems) {
49
+ throw new Error(`No project items found for project ID ${projectId}`);
50
+ }
51
+ return projectItems;
52
+ }
53
+ async function findItemsNotInProject(projectId) {
54
+ const [projectItemsInProject, allItems] = await Promise.all([
55
+ getProjectItemsByProjectId(projectId),
56
+ (0, items_1.getManyItems)(),
57
+ ]);
58
+ const projectItemIds = new Set(projectItemsInProject.map((item) => item.itemId));
59
+ const itemsNotInProject = allItems.filter((item) => !projectItemIds.has(item.id));
60
+ if (itemsNotInProject.length === 0) {
61
+ throw new Error(`All items are already in project ${projectId}`);
62
+ }
63
+ return itemsNotInProject.sort((a, b) => a.id.localeCompare(b.id)); // sorting ensures consistent order of response
64
+ }
65
+ exports.findItemsNotInProject = findItemsNotInProject;
@@ -1,2 +1,3 @@
1
1
  export declare function safeFileOverwrite(fileLocation: string, overwrite: boolean): Promise<boolean>;
2
2
  export declare function parseCSVData(data: string): any;
3
+ export declare function parseFileAsJsonArray(fileName: string): any[];
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseCSVData = exports.safeFileOverwrite = void 0;
3
+ exports.parseFileAsJsonArray = exports.parseCSVData = exports.safeFileOverwrite = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const fs_1 = tslib_1.__importDefault(require("fs"));
5
6
  const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
6
7
  const logger_1 = tslib_1.__importDefault(require("../logger"));
7
8
  const sync_1 = require("csv-parse/sync");
@@ -34,3 +35,20 @@ function parseCSVData(data) {
34
35
  });
35
36
  }
36
37
  exports.parseCSVData = parseCSVData;
38
+ function parseFileAsJsonArray(fileName) {
39
+ if (!fs_1.default.existsSync(fileName)) {
40
+ throw new Error(`File not found: ${fileName}`);
41
+ }
42
+ const fileContent = fs_1.default.readFileSync(fileName, 'utf-8');
43
+ try {
44
+ const jsonArray = JSON.parse(fileContent);
45
+ if (!Array.isArray(jsonArray)) {
46
+ throw new Error(`File content is not a valid JSON array: ${fileName}`);
47
+ }
48
+ return jsonArray;
49
+ }
50
+ catch (error) {
51
+ throw new Error(`Error parsing JSON from file ${fileName}: ${error.message}`);
52
+ }
53
+ }
54
+ exports.parseFileAsJsonArray = parseFileAsJsonArray;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Date Range Parsing Utilities
3
+ *
4
+ * Handles parsing date range syntax like:
5
+ * - createdOn=2025-09-15..2025-09-16 (date range)
6
+ * - createdOn=2025-09-15T04:00:00Z..2025-09-16T03:59:59Z (timestamp range)
7
+ * - createdOn=2025-09-15.. (from date onwards)
8
+ * - createdOn=..2025-09-16 (until date)
9
+ */
10
+ export type DateRange = {
11
+ start?: Date;
12
+ end?: Date;
13
+ };
14
+ export type DateRangeSpecification = {
15
+ slug: string;
16
+ range: DateRange;
17
+ };
18
+ /**
19
+ * Checks if a property specification contains date range syntax (..)
20
+ */
21
+ export declare function isDateRangeSpecification(spec: string): boolean;
22
+ /**
23
+ * Parses a date range specification into its components
24
+ * @param spec - The specification string (e.g., "createdOn=2025-09-15..2025-09-16")
25
+ */
26
+ export declare function parseDateRangeSpecification(spec: string): DateRangeSpecification;
27
+ /**
28
+ * Parses a date string, handling both date-only and full timestamp formats
29
+ * @param dateStr - Date string (e.g., "2025-09-15" or "2025-09-15T04:00:00Z")
30
+ * @param boundType - Whether this is the 'start' or 'end' of a range (affects date-only handling)
31
+ */
32
+ export declare function parseDate(dateStr: string, boundType?: 'start' | 'end'): Date;
33
+ /**
34
+ * Converts a date range to JSURL BETWEEN format
35
+ * @param range - The date range object
36
+ * @param isEndOfRange - Whether this date represents the end of a range (affects date-only handling)
37
+ */
38
+ export declare function convertDateRangeToJSURL(range: DateRange): string;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ /**
3
+ * Date Range Parsing Utilities
4
+ *
5
+ * Handles parsing date range syntax like:
6
+ * - createdOn=2025-09-15..2025-09-16 (date range)
7
+ * - createdOn=2025-09-15T04:00:00Z..2025-09-16T03:59:59Z (timestamp range)
8
+ * - createdOn=2025-09-15.. (from date onwards)
9
+ * - createdOn=..2025-09-16 (until date)
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.convertDateRangeToJSURL = exports.parseDate = exports.parseDateRangeSpecification = exports.isDateRangeSpecification = void 0;
13
+ /**
14
+ * Checks if a property specification contains date range syntax (..)
15
+ */
16
+ function isDateRangeSpecification(spec) {
17
+ return spec.includes('=') && spec.includes('..');
18
+ }
19
+ exports.isDateRangeSpecification = isDateRangeSpecification;
20
+ /**
21
+ * Parses a date range specification into its components
22
+ * @param spec - The specification string (e.g., "createdOn=2025-09-15..2025-09-16")
23
+ */
24
+ function parseDateRangeSpecification(spec) {
25
+ const equalsIndex = spec.indexOf('=');
26
+ if (equalsIndex === -1) {
27
+ throw new Error(`Invalid date range specification: ${spec}`);
28
+ }
29
+ const slug = spec.substring(0, equalsIndex);
30
+ const rangeValue = spec.substring(equalsIndex + 1);
31
+ if (!slug || !rangeValue || !rangeValue.includes('..')) {
32
+ throw new Error(`Invalid date range specification: ${spec}`);
33
+ }
34
+ const [startStr, endStr] = rangeValue.split('..');
35
+ const range = {
36
+ start: startStr.trim() ? parseDate(startStr.trim(), 'start') : undefined,
37
+ end: endStr.trim() ? parseDate(endStr.trim(), 'end') : undefined,
38
+ };
39
+ // At least one bound must be specified
40
+ if (!range.start && !range.end) {
41
+ throw new Error(`Date range must have at least one bound: ${spec}`);
42
+ }
43
+ return { slug, range };
44
+ }
45
+ exports.parseDateRangeSpecification = parseDateRangeSpecification;
46
+ /**
47
+ * Parses a date string, handling both date-only and full timestamp formats
48
+ * @param dateStr - Date string (e.g., "2025-09-15" or "2025-09-15T04:00:00Z")
49
+ * @param boundType - Whether this is the 'start' or 'end' of a range (affects date-only handling)
50
+ */
51
+ function parseDate(dateStr, boundType = 'start') {
52
+ if (!dateStr.includes('T')) {
53
+ if (boundType === 'start') {
54
+ dateStr = `${dateStr}T00:00:00.000Z`;
55
+ }
56
+ else {
57
+ dateStr = `${dateStr}T23:59:59.999Z`;
58
+ }
59
+ }
60
+ const date = new Date(dateStr);
61
+ if (isNaN(date.getTime())) {
62
+ throw new Error(`Invalid date format: ${dateStr}`);
63
+ }
64
+ return date;
65
+ }
66
+ exports.parseDate = parseDate;
67
+ /**
68
+ * Converts a date range to JSURL BETWEEN format
69
+ * @param range - The date range object
70
+ * @param isEndOfRange - Whether this date represents the end of a range (affects date-only handling)
71
+ */
72
+ function convertDateRangeToJSURL(range) {
73
+ if (!range.start && !range.end) {
74
+ throw new Error('Date range must have at least one bound');
75
+ }
76
+ if (range.start && range.end) {
77
+ const startISO = range.start.toISOString();
78
+ const endISO = range.end.toISOString();
79
+ return `BETWEEN ${startISO} and ${endISO}`;
80
+ }
81
+ else if (range.start) {
82
+ return `>= ${range.start.toISOString()}`;
83
+ }
84
+ else {
85
+ const endISO = range.end.toISOString();
86
+ return `<= ${endISO}`;
87
+ }
88
+ }
89
+ exports.convertDateRangeToJSURL = convertDateRangeToJSURL;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const dateRanges_1 = require("./dateRanges");
4
+ describe('Date Range Specifications', () => {
5
+ describe('isDateRangeSpecification', () => {
6
+ it('should identify valid date range specifications', () => {
7
+ expect((0, dateRanges_1.isDateRangeSpecification)('createdOn=2025-09-15..2025-09-16')).toBe(true);
8
+ expect((0, dateRanges_1.isDateRangeSpecification)('updatedAt=2025-09-15T04:00:00Z..2025-09-16T03:59:59Z')).toBe(true);
9
+ expect((0, dateRanges_1.isDateRangeSpecification)('date=2025-09-15..')).toBe(true);
10
+ expect((0, dateRanges_1.isDateRangeSpecification)('date=..2025-09-16')).toBe(true);
11
+ });
12
+ it('should reject non-date-range specifications', () => {
13
+ expect((0, dateRanges_1.isDateRangeSpecification)('id=123')).toBe(false);
14
+ expect((0, dateRanges_1.isDateRangeSpecification)('name=test')).toBe(false);
15
+ expect((0, dateRanges_1.isDateRangeSpecification)('invalid')).toBe(false);
16
+ expect((0, dateRanges_1.isDateRangeSpecification)('field=value')).toBe(false);
17
+ });
18
+ });
19
+ describe('parseDate', () => {
20
+ it('should parse ISO timestamps correctly', () => {
21
+ const date = (0, dateRanges_1.parseDate)('2025-09-15T04:00:00Z');
22
+ expect(date.toISOString()).toBe('2025-09-15T04:00:00.000Z');
23
+ });
24
+ it('should parse date-only strings as start of day', () => {
25
+ const date = (0, dateRanges_1.parseDate)('2025-09-15', 'start');
26
+ expect(date.toISOString()).toBe('2025-09-15T00:00:00.000Z');
27
+ });
28
+ it('should parse date-only strings as end of day for end bound', () => {
29
+ const date = (0, dateRanges_1.parseDate)('2025-09-15', 'end');
30
+ expect(date.toISOString()).toBe('2025-09-15T23:59:59.999Z');
31
+ });
32
+ it('should throw for invalid date strings', () => {
33
+ expect(() => (0, dateRanges_1.parseDate)('invalid-date')).toThrow('Invalid date format');
34
+ expect(() => (0, dateRanges_1.parseDate)('2025-13-50')).toThrow('Invalid date format');
35
+ });
36
+ });
37
+ describe('parseDateRangeSpecification', () => {
38
+ it('should parse full date ranges', () => {
39
+ const result = (0, dateRanges_1.parseDateRangeSpecification)('createdOn=2025-09-15..2025-09-16');
40
+ expect(result.slug).toBe('createdOn');
41
+ expect(result.range.start).toEqual(new Date('2025-09-15T00:00:00.000Z'));
42
+ expect(result.range.end).toEqual(new Date('2025-09-16T23:59:59.999Z'));
43
+ });
44
+ it('should parse open-ended start ranges', () => {
45
+ const result = (0, dateRanges_1.parseDateRangeSpecification)('createdOn=2025-09-15..');
46
+ expect(result.slug).toBe('createdOn');
47
+ expect(result.range.start).toEqual(new Date('2025-09-15T00:00:00.000Z'));
48
+ expect(result.range.end).toBeUndefined();
49
+ });
50
+ it('should parse open-ended end ranges', () => {
51
+ const result = (0, dateRanges_1.parseDateRangeSpecification)('createdOn=..2025-09-16');
52
+ expect(result.slug).toBe('createdOn');
53
+ expect(result.range.start).toBeUndefined();
54
+ expect(result.range.end).toEqual(new Date('2025-09-16T23:59:59.999Z'));
55
+ });
56
+ it('should parse timestamp ranges', () => {
57
+ const result = (0, dateRanges_1.parseDateRangeSpecification)('createdOn=2025-09-15T04:00:00Z..2025-09-16T03:59:59Z');
58
+ expect(result.slug).toBe('createdOn');
59
+ expect(result.range.start).toEqual(new Date('2025-09-15T04:00:00.000Z'));
60
+ expect(result.range.end).toEqual(new Date('2025-09-16T03:59:59.000Z'));
61
+ });
62
+ it('should throw for invalid specifications', () => {
63
+ expect(() => (0, dateRanges_1.parseDateRangeSpecification)('invalid')).toThrow('Invalid date range specification');
64
+ expect(() => (0, dateRanges_1.parseDateRangeSpecification)('field=..')).toThrow('Date range must have at least one bound');
65
+ expect(() => (0, dateRanges_1.parseDateRangeSpecification)('field=invalid..date')).toThrow('Invalid date format');
66
+ });
67
+ });
68
+ describe('convertDateRangeToJSURL', () => {
69
+ it('should convert full ranges to BETWEEN format', () => {
70
+ const range = {
71
+ start: new Date('2025-09-15T00:00:00.000Z'),
72
+ end: new Date('2025-09-16T23:59:59.999Z'),
73
+ };
74
+ const result = (0, dateRanges_1.convertDateRangeToJSURL)(range);
75
+ expect(result).toBe('BETWEEN 2025-09-15T00:00:00.000Z and 2025-09-16T23:59:59.999Z');
76
+ });
77
+ it('should convert start-only ranges to >= format', () => {
78
+ const range = {
79
+ start: new Date('2025-09-15T00:00:00.000Z'),
80
+ };
81
+ const result = (0, dateRanges_1.convertDateRangeToJSURL)(range);
82
+ expect(result).toBe('>= 2025-09-15T00:00:00.000Z');
83
+ });
84
+ it('should convert end-only ranges to <= format', () => {
85
+ const range = {
86
+ end: new Date('2025-09-16T23:59:59.999Z'),
87
+ };
88
+ const result = (0, dateRanges_1.convertDateRangeToJSURL)(range);
89
+ expect(result).toBe('<= 2025-09-16T23:59:59.999Z');
90
+ });
91
+ it('should throw for empty ranges', () => {
92
+ expect(() => (0, dateRanges_1.convertDateRangeToJSURL)({})).toThrow('Date range must have at least one bound');
93
+ });
94
+ });
95
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Property Specification
3
+ *
4
+ * Property Specifications is an argument pattern used to express that
5
+ * a specific property has a specific value. In GET requests, this is used
6
+ * to specify the query parameters. In PUT requests, this is used to
7
+ * specify the properties to update.
8
+ *
9
+ * Each follows the format: slug=value
10
+ *
11
+ * It is typically used as the argv in a command.
12
+ *
13
+ * Examples:
14
+ *
15
+ * contrail entities get color -- id=123
16
+ * contrail entities update color 123 -- name=obsidian hex=000000
17
+ * contrail entities get loader-processes -- createdOn=2025-09-15..2025-09-16
18
+ */
19
+ export type PropertySpecification = {
20
+ slug: string;
21
+ value: string;
22
+ };
23
+ export type PropertySpecificationBySlug = Map<string, string | number | string[] | number[]>;
24
+ export type PropertySpecificationString = `${string}=${string}`;
25
+ export declare function parsePropertySpecification(spec: PropertySpecificationString): PropertySpecification;
26
+ export declare function parsePropertySpecificationsIntoMap(propertySpecifications: PropertySpecificationString[]): PropertySpecificationBySlug;
27
+ export declare function isValidPropertySpecification(maybeSpec: string): maybeSpec is PropertySpecificationString;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Property Specification
4
+ *
5
+ * Property Specifications is an argument pattern used to express that
6
+ * a specific property has a specific value. In GET requests, this is used
7
+ * to specify the query parameters. In PUT requests, this is used to
8
+ * specify the properties to update.
9
+ *
10
+ * Each follows the format: slug=value
11
+ *
12
+ * It is typically used as the argv in a command.
13
+ *
14
+ * Examples:
15
+ *
16
+ * contrail entities get color -- id=123
17
+ * contrail entities update color 123 -- name=obsidian hex=000000
18
+ * contrail entities get loader-processes -- createdOn=2025-09-15..2025-09-16
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.isValidPropertySpecification = exports.parsePropertySpecificationsIntoMap = exports.parsePropertySpecification = void 0;
22
+ const dateRanges_1 = require("./dateRanges");
23
+ function parsePropertySpecification(spec) {
24
+ const equalsIndex = spec.indexOf('=');
25
+ if (equalsIndex === -1) {
26
+ throw new Error(`Invalid property specification: ${spec}`);
27
+ }
28
+ const slug = spec.substring(0, equalsIndex);
29
+ const value = spec.substring(equalsIndex + 1);
30
+ if (!slug || !value) {
31
+ throw new Error(`Invalid property specification: ${spec}`);
32
+ }
33
+ return { slug, value };
34
+ }
35
+ exports.parsePropertySpecification = parsePropertySpecification;
36
+ function parsePropertySpecificationsIntoMap(propertySpecifications) {
37
+ const propertySpecBySlug = new Map();
38
+ for (const spec of propertySpecifications) {
39
+ if ((0, dateRanges_1.isDateRangeSpecification)(spec)) {
40
+ const { slug, range } = (0, dateRanges_1.parseDateRangeSpecification)(spec);
41
+ const jsUrlValue = (0, dateRanges_1.convertDateRangeToJSURL)(range);
42
+ propertySpecBySlug.set(slug, jsUrlValue);
43
+ continue;
44
+ }
45
+ const { slug, value } = parsePropertySpecification(spec);
46
+ if (slug === 'roles') {
47
+ // Special handling for roles, which can be an array
48
+ const rolesArray = value.split(',').map((role) => role.trim());
49
+ propertySpecBySlug.set(slug, rolesArray);
50
+ continue;
51
+ }
52
+ propertySpecBySlug.set(slug, value);
53
+ }
54
+ return propertySpecBySlug;
55
+ }
56
+ exports.parsePropertySpecificationsIntoMap = parsePropertySpecificationsIntoMap;
57
+ function isValidPropertySpecification(maybeSpec) {
58
+ try {
59
+ if ((0, dateRanges_1.isDateRangeSpecification)(maybeSpec)) {
60
+ (0, dateRanges_1.parseDateRangeSpecification)(maybeSpec);
61
+ return true;
62
+ }
63
+ const { slug, value } = parsePropertySpecification(maybeSpec);
64
+ return !!slug && !!value;
65
+ }
66
+ catch (e) {
67
+ return false;
68
+ }
69
+ }
70
+ exports.isValidPropertySpecification = isValidPropertySpecification;