@contrail/cli 3.1.8 → 3.3.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 (41) hide show
  1. package/README.md +142 -1
  2. package/dist/api/types.d.ts +35 -0
  3. package/dist/api/types.js +47 -1
  4. package/dist/basecommand.d.ts +3 -1
  5. package/dist/basecommand.js +1 -1
  6. package/dist/commands/app/extension/deploy.js +2 -2
  7. package/dist/commands/app/extension/deployment/list.js +2 -2
  8. package/dist/commands/entities/create.d.ts +37 -0
  9. package/dist/commands/entities/create.js +532 -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 +103 -0
  14. package/dist/commands/entities/get.d.ts +19 -0
  15. package/dist/commands/entities/get.js +151 -0
  16. package/dist/commands/entities/update.d.ts +17 -0
  17. package/dist/commands/entities/update.js +86 -0
  18. package/dist/common.d.ts +1 -1
  19. package/dist/helpers/app/app-deployer.js +25 -0
  20. package/dist/helpers/app/{extension-package.js → extension-package-deployer.js} +1 -1
  21. package/dist/helpers/app/metadata-extractor.d.ts +1 -0
  22. package/dist/helpers/app/metadata-extractor.js +100 -0
  23. package/dist/helpers/entities/assortment-items.d.ts +10 -0
  24. package/dist/helpers/entities/assortment-items.js +86 -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 +84 -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/propertySpecifications.d.ts +26 -0
  36. package/dist/helpers/propertySpecifications/propertySpecifications.js +58 -0
  37. package/dist/helpers/propertySpecifications/propertySpecifications.spec.d.ts +1 -0
  38. package/dist/helpers/propertySpecifications/propertySpecifications.spec.js +266 -0
  39. package/oclif.manifest.json +341 -1
  40. package/package.json +5 -4
  41. /package/dist/helpers/app/{extension-package.d.ts → extension-package-deployer.d.ts} +0 -0
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.throwIfInvalidItemLevel = exports.Roles = exports.getFamilyName = exports.getItemFamilies = exports.getAllItems = 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 getAllItems();
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 getAllItems() {
29
+ const client = new sdk_1.Entities();
30
+ const MAX_ITEMS = 100000;
31
+ // paginate to get all items
32
+ // Note: This will only return the first 100 assortments by default.
33
+ const items = [];
34
+ let nextPageKey = undefined;
35
+ while (true) {
36
+ const response = await client.get({
37
+ entityName: 'item',
38
+ apiVersion: sdk_1.API_VERSION.V2,
39
+ // customParams: { propertySlugs: 'id,familyId,roles,name' }, // not compatible with V2 for some reason
40
+ nextPageKey,
41
+ });
42
+ nextPageKey = response.nextPageKey;
43
+ if (response.results.length === 0) {
44
+ break; // No more assortments to fetch
45
+ }
46
+ items.push(...response.results);
47
+ if (items.length >= MAX_ITEMS) {
48
+ console.warn(`Reached maximum item limit of ${MAX_ITEMS}. Some items may not be included.`);
49
+ break; // Stop if we reach the maximum limit
50
+ }
51
+ }
52
+ return items;
53
+ }
54
+ exports.getAllItems = getAllItems;
55
+ async function getItemFamilies() {
56
+ const allItems = await getAllItemsUsingCacheFallback();
57
+ const allItemFamiles = allItems.filter(({ roles }) => roles.includes('family'));
58
+ return allItemFamiles;
59
+ }
60
+ exports.getItemFamilies = getItemFamilies;
61
+ async function getFamilyName(item) {
62
+ if (!item.roles.includes('option')) {
63
+ return undefined;
64
+ }
65
+ if (!item.familyId) {
66
+ return undefined;
67
+ }
68
+ await ensureItemCacheIsPopulated();
69
+ const family = ITEMS_CACHE_BY_ID.get(item.familyId);
70
+ return family ? [family.name] : ['placeholder']; // placeholder if family not found
71
+ }
72
+ exports.getFamilyName = getFamilyName;
73
+ async function ensureItemCacheIsPopulated() {
74
+ if (ITEMS_CACHE_BY_ID.size === 0) {
75
+ await getAllItemsUsingCacheFallback();
76
+ }
77
+ }
78
+ exports.Roles = ['option', 'family'];
79
+ function throwIfInvalidItemLevel(role) {
80
+ if (!exports.Roles.includes(role)) {
81
+ throw new Error(`Invalid role: ${role}. Valid roles are: ${exports.Roles.join(', ')}`);
82
+ }
83
+ }
84
+ exports.throwIfInvalidItemLevel = throwIfInvalidItemLevel;
@@ -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.getAllItems)(),
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,26 @@
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
+ */
18
+ export type PropertySpecification = {
19
+ slug: string;
20
+ value: string;
21
+ };
22
+ export type PropertySpecificationBySlug = Map<string, string | number | string[] | number[]>;
23
+ export type PropertySpecificationString = `${string}=${string}`;
24
+ export declare function parsePropertySpecification(spec: PropertySpecificationString): PropertySpecification;
25
+ export declare function parsePropertySpecificationsIntoMap(propertySpecifications: PropertySpecificationString[]): PropertySpecificationBySlug;
26
+ export declare function isValidPropertySpecification(maybeSpec: string): maybeSpec is PropertySpecificationString;
@@ -0,0 +1,58 @@
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
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.isValidPropertySpecification = exports.parsePropertySpecificationsIntoMap = exports.parsePropertySpecification = void 0;
21
+ function parsePropertySpecification(spec) {
22
+ const equalsIndex = spec.indexOf('=');
23
+ if (equalsIndex === -1) {
24
+ throw new Error(`Invalid property specification: ${spec}`);
25
+ }
26
+ const slug = spec.substring(0, equalsIndex);
27
+ const value = spec.substring(equalsIndex + 1);
28
+ if (!slug || !value) {
29
+ throw new Error(`Invalid property specification: ${spec}`);
30
+ }
31
+ return { slug, value };
32
+ }
33
+ exports.parsePropertySpecification = parsePropertySpecification;
34
+ function parsePropertySpecificationsIntoMap(propertySpecifications) {
35
+ const propertySpecBySlug = new Map();
36
+ for (const spec of propertySpecifications) {
37
+ const { slug, value } = parsePropertySpecification(spec);
38
+ if (slug === 'roles') {
39
+ // Special handling for roles, which can be an array
40
+ const rolesArray = value.split(',').map((role) => role.trim());
41
+ propertySpecBySlug.set(slug, rolesArray);
42
+ continue;
43
+ }
44
+ propertySpecBySlug.set(slug, value);
45
+ }
46
+ return propertySpecBySlug;
47
+ }
48
+ exports.parsePropertySpecificationsIntoMap = parsePropertySpecificationsIntoMap;
49
+ function isValidPropertySpecification(maybeSpec) {
50
+ try {
51
+ const { slug, value } = parsePropertySpecification(maybeSpec);
52
+ return !!slug && !!value;
53
+ }
54
+ catch (e) {
55
+ return false;
56
+ }
57
+ }
58
+ exports.isValidPropertySpecification = isValidPropertySpecification;
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const propertySpecifications_1 = require("./propertySpecifications");
4
+ describe('parsePropertySpecification', () => {
5
+ it('should parse a valid property specification', () => {
6
+ const spec = 'name=john';
7
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
8
+ expect(result).toEqual({
9
+ slug: 'name',
10
+ value: 'john',
11
+ });
12
+ });
13
+ it('should parse property specification with numeric value', () => {
14
+ const spec = 'id=123';
15
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
16
+ expect(result).toEqual({
17
+ slug: 'id',
18
+ value: '123',
19
+ });
20
+ });
21
+ it('should parse property specification with hex color value', () => {
22
+ const spec = 'hex=000000';
23
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
24
+ expect(result).toEqual({
25
+ slug: 'hex',
26
+ value: '000000',
27
+ });
28
+ });
29
+ it('should parse property specification with spaces in value', () => {
30
+ const spec = 'name=john doe';
31
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
32
+ expect(result).toEqual({
33
+ slug: 'name',
34
+ value: 'john doe',
35
+ });
36
+ });
37
+ it('should parse property specification with equals sign in value', () => {
38
+ const spec = 'formula=x=y+z';
39
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
40
+ expect(result).toEqual({
41
+ slug: 'formula',
42
+ value: 'x=y+z', // Now correctly keeps everything after first equals
43
+ });
44
+ });
45
+ it('should parse property specification with empty value', () => {
46
+ const spec = 'description=';
47
+ // Current implementation throws error for empty values
48
+ expect(() => (0, propertySpecifications_1.parsePropertySpecification)(spec)).toThrow('Invalid property specification: description=');
49
+ });
50
+ it('should throw error for spec without equals sign', () => {
51
+ const spec = 'invalidspec';
52
+ expect(() => (0, propertySpecifications_1.parsePropertySpecification)(spec)).toThrow('Invalid property specification: invalidspec');
53
+ });
54
+ it('should throw error for spec with only equals sign', () => {
55
+ const spec = '=';
56
+ expect(() => (0, propertySpecifications_1.parsePropertySpecification)(spec)).toThrow('Invalid property specification: =');
57
+ });
58
+ it('should throw error for spec with empty slug', () => {
59
+ const spec = '=value';
60
+ expect(() => (0, propertySpecifications_1.parsePropertySpecification)(spec)).toThrow('Invalid property specification: =value');
61
+ });
62
+ it('should parse property specification with special characters in slug', () => {
63
+ const spec = 'property_name=value';
64
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
65
+ expect(result).toEqual({
66
+ slug: 'property_name',
67
+ value: 'value',
68
+ });
69
+ });
70
+ it('should parse property specification with hyphenated slug', () => {
71
+ const spec = 'property-name=value';
72
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
73
+ expect(result).toEqual({
74
+ slug: 'property-name',
75
+ value: 'value',
76
+ });
77
+ });
78
+ });
79
+ describe('parsePropertySpecificationsIntoMap', () => {
80
+ it('should parse multiple property specifications into a map', () => {
81
+ const specs = ['name=obsidian', 'hex=000000', 'id=123'];
82
+ const result = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
83
+ expect(result).toBeInstanceOf(Map);
84
+ expect(result.get('name')).toBe('obsidian');
85
+ expect(result.get('hex')).toBe('000000');
86
+ expect(result.get('id')).toBe('123');
87
+ expect(result.size).toBe(3);
88
+ });
89
+ it('should handle empty array', () => {
90
+ const specs = [];
91
+ const result = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
92
+ expect(result).toBeInstanceOf(Map);
93
+ expect(result.size).toBe(0);
94
+ });
95
+ it('should handle single property specification', () => {
96
+ const specs = ['name=john'];
97
+ const result = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
98
+ expect(result).toBeInstanceOf(Map);
99
+ expect(result.get('name')).toBe('john');
100
+ expect(result.size).toBe(1);
101
+ });
102
+ it('should handle property specifications with complex values', () => {
103
+ const specs = [
104
+ 'description=This is a long description with spaces',
105
+ 'formula=x=y+z',
106
+ 'url=https://example.com',
107
+ ];
108
+ const result = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
109
+ expect(result.get('description')).toBe('This is a long description with spaces');
110
+ expect(result.get('formula')).toBe('x=y+z'); // Now correctly handles multiple equals
111
+ expect(result.get('url')).toBe('https://example.com');
112
+ expect(result.size).toBe(3);
113
+ });
114
+ it('should handle duplicate slugs by overwriting with last value', () => {
115
+ const specs = ['name=first', 'name=second', 'name=third'];
116
+ const result = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
117
+ expect(result.get('name')).toBe('third');
118
+ expect(result.size).toBe(1);
119
+ });
120
+ it('should handle property specifications with empty values', () => {
121
+ const specs = ['name=john', 'description=', 'notes='];
122
+ // Current implementation throws error for empty values
123
+ expect(() => (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs)).toThrow('Invalid property specification: description=');
124
+ });
125
+ it('should throw error for invalid property specifications', () => {
126
+ const specs = [
127
+ 'name=john',
128
+ 'invalid',
129
+ ];
130
+ expect(() => (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs)).toThrow('Invalid property specification: invalid');
131
+ });
132
+ });
133
+ describe('isValidPropertySpecification', () => {
134
+ it('should return true for valid property specifications', () => {
135
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('name=john')).toBe(true);
136
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('id=123')).toBe(true);
137
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('hex=000000')).toBe(true);
138
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('description=This is a description')).toBe(true);
139
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('formula=x=y+z')).toBe(true);
140
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('property_name=value')).toBe(true);
141
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('property-name=value')).toBe(true);
142
+ });
143
+ it('should return true for property specifications with empty values', () => {
144
+ // Current implementation rejects empty values
145
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('description=')).toBe(false);
146
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('notes=')).toBe(false);
147
+ });
148
+ it('should return false for invalid property specifications', () => {
149
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('invalid')).toBe(false);
150
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('')).toBe(false);
151
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('=')).toBe(false);
152
+ expect((0, propertySpecifications_1.isValidPropertySpecification)('=value')).toBe(false);
153
+ });
154
+ it('should return false for null or undefined', () => {
155
+ expect((0, propertySpecifications_1.isValidPropertySpecification)(null)).toBe(false);
156
+ expect((0, propertySpecifications_1.isValidPropertySpecification)(undefined)).toBe(false);
157
+ });
158
+ it('should return false for non-string values', () => {
159
+ expect((0, propertySpecifications_1.isValidPropertySpecification)(123)).toBe(false);
160
+ expect((0, propertySpecifications_1.isValidPropertySpecification)({})).toBe(false);
161
+ expect((0, propertySpecifications_1.isValidPropertySpecification)([])).toBe(false);
162
+ });
163
+ it('should act as type guard', () => {
164
+ const maybeSpec = 'name=john';
165
+ if ((0, propertySpecifications_1.isValidPropertySpecification)(maybeSpec)) {
166
+ // TypeScript should recognize maybeSpec as PropertySpecificationString
167
+ const parsed = (0, propertySpecifications_1.parsePropertySpecification)(maybeSpec);
168
+ expect(parsed.slug).toBe('name');
169
+ expect(parsed.value).toBe('john');
170
+ }
171
+ });
172
+ });
173
+ describe('integration tests', () => {
174
+ it('should work with the example from the comments', () => {
175
+ // Example: contrail entities get color -- id=123
176
+ const getSpecs = ['id=123'];
177
+ const getMap = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(getSpecs);
178
+ expect(getMap.get('id')).toBe('123');
179
+ expect(getMap.size).toBe(1);
180
+ });
181
+ it('should work with the update example from the comments', () => {
182
+ // Example: contrail entities update color 123 -- name=obsidian hex=000000
183
+ const updateSpecs = ['name=obsidian', 'hex=000000'];
184
+ const updateMap = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(updateSpecs);
185
+ expect(updateMap.get('name')).toBe('obsidian');
186
+ expect(updateMap.get('hex')).toBe('000000');
187
+ expect(updateMap.size).toBe(2);
188
+ });
189
+ it('should handle validation before parsing', () => {
190
+ const userInputs = ['name=john', 'invalid', 'id=123', '=empty'];
191
+ const validSpecs = userInputs.filter(propertySpecifications_1.isValidPropertySpecification);
192
+ expect(validSpecs).toEqual(['name=john', 'id=123']);
193
+ const map = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(validSpecs);
194
+ expect(map.get('name')).toBe('john');
195
+ expect(map.get('id')).toBe('123');
196
+ expect(map.size).toBe(2);
197
+ });
198
+ });
199
+ describe('edge cases and limitations', () => {
200
+ it('should handle multiple equals signs correctly', () => {
201
+ // This test shows that multiple equals signs are now handled correctly
202
+ const spec = 'calculation=2+2=4';
203
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
204
+ expect(result).toEqual({
205
+ slug: 'calculation',
206
+ value: '2+2=4', // now preserves everything after first equals
207
+ });
208
+ });
209
+ it('should handle the specific example equation=x+z=1', () => {
210
+ const spec = 'equation=x+z=1';
211
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
212
+ expect(result).toEqual({
213
+ slug: 'equation',
214
+ value: 'x+z=1',
215
+ });
216
+ });
217
+ it('should handle mathematical expressions with one equals', () => {
218
+ const spec = 'formula=x+y';
219
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
220
+ expect(result).toEqual({
221
+ slug: 'formula',
222
+ value: 'x+y',
223
+ });
224
+ });
225
+ it('should handle URLs without equals signs', () => {
226
+ const spec = 'url=https://example.com/path';
227
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
228
+ expect(result).toEqual({
229
+ slug: 'url',
230
+ value: 'https://example.com/path',
231
+ });
232
+ });
233
+ it('should handle JSON-like values', () => {
234
+ const spec = 'config={"key":"value"}';
235
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
236
+ expect(result).toEqual({
237
+ slug: 'config',
238
+ value: '{"key":"value"}',
239
+ });
240
+ });
241
+ it('should handle comma-separated values', () => {
242
+ const spec = 'tags=red,blue,green';
243
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
244
+ expect(result).toEqual({
245
+ slug: 'tags',
246
+ value: 'red,blue,green',
247
+ });
248
+ });
249
+ it('should handle escaped characters', () => {
250
+ const spec = 'message=Hello\\nWorld';
251
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
252
+ expect(result).toEqual({
253
+ slug: 'message',
254
+ value: 'Hello\\nWorld',
255
+ });
256
+ });
257
+ it('should handle whitespace in slugs', () => {
258
+ // This documents current behavior - spaces in slugs are allowed
259
+ const spec = 'property name=value';
260
+ const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
261
+ expect(result).toEqual({
262
+ slug: 'property name',
263
+ value: 'value',
264
+ });
265
+ });
266
+ });