@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.
- package/README.md +150 -1
- package/dist/api/types.d.ts +35 -0
- package/dist/api/types.js +47 -1
- package/dist/auth/login.js +0 -8
- package/dist/basecommand.d.ts +3 -1
- package/dist/basecommand.js +1 -1
- package/dist/commands/app/extension/deploy.js +2 -4
- package/dist/commands/entities/create.d.ts +37 -0
- package/dist/commands/entities/create.js +555 -0
- package/dist/commands/entities/create.spec.d.ts +1 -0
- package/dist/commands/entities/create.spec.js +514 -0
- package/dist/commands/entities/delete.d.ts +16 -0
- package/dist/commands/entities/delete.js +118 -0
- package/dist/commands/entities/get.d.ts +20 -0
- package/dist/commands/entities/get.js +204 -0
- package/dist/commands/entities/update.d.ts +18 -0
- package/dist/commands/entities/update.js +116 -0
- package/dist/commands/types/uploadUsageInfo.js +2 -14
- package/dist/common.d.ts +1 -1
- package/dist/helpers/app/app-deployer.js +4 -8
- package/dist/helpers/app/extension-package-deployer.js +1 -1
- package/dist/helpers/config/config.js +3 -3
- package/dist/helpers/entities/assortment-items.d.ts +10 -0
- package/dist/helpers/entities/assortment-items.js +84 -0
- package/dist/helpers/entities/batchFetch.d.ts +8 -0
- package/dist/helpers/entities/batchFetch.js +41 -0
- package/dist/helpers/entities/index.d.ts +3 -0
- package/dist/helpers/entities/index.js +6 -0
- package/dist/helpers/entities/items.d.ts +10 -0
- package/dist/helpers/entities/items.js +102 -0
- package/dist/helpers/entities/project-items.d.ts +3 -0
- package/dist/helpers/entities/project-items.js +65 -0
- package/dist/helpers/file-helpers.d.ts +1 -0
- package/dist/helpers/file-helpers.js +19 -1
- package/dist/helpers/propertySpecifications/dateRanges.d.ts +38 -0
- package/dist/helpers/propertySpecifications/dateRanges.js +89 -0
- package/dist/helpers/propertySpecifications/dateRanges.spec.d.ts +1 -0
- package/dist/helpers/propertySpecifications/dateRanges.spec.js +95 -0
- package/dist/helpers/propertySpecifications/propertySpecifications.d.ts +27 -0
- package/dist/helpers/propertySpecifications/propertySpecifications.js +70 -0
- package/dist/helpers/propertySpecifications/propertySpecifications.spec.d.ts +1 -0
- package/dist/helpers/propertySpecifications/propertySpecifications.spec.js +266 -0
- package/dist/helpers/types/type-uploader.js +0 -7
- package/dist/interfaces.d.ts +0 -1
- package/dist/templates/typescript/README.md +1 -3
- package/dist/templates/typescript/assets/sample.json +1 -1
- package/dist/templates/typescript/eslintrc.json +4 -1
- package/dist/templates/typescript/tsconfig.json +21 -5
- package/oclif.manifest.json +361 -1
- 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,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,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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|