@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,20 @@
|
|
|
1
|
+
import { BaseCommand } from '../../basecommand';
|
|
2
|
+
export default class GetEntity extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
outputFile: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
|
+
skipHydrateRelationships: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
relations: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
9
|
+
propertySlugs: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
|
+
paginated: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
take: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
12
|
+
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
13
|
+
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
static args: {
|
|
16
|
+
typePath: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
|
|
17
|
+
};
|
|
18
|
+
static strict: boolean;
|
|
19
|
+
run(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
5
|
+
const core_1 = require("@oclif/core");
|
|
6
|
+
const basecommand_1 = require("../../basecommand");
|
|
7
|
+
const sdk_1 = require("@contrail/sdk");
|
|
8
|
+
const login_1 = require("../../auth/login");
|
|
9
|
+
const types_1 = require("../../api/types");
|
|
10
|
+
const propertySpecifications_1 = require("../../helpers/propertySpecifications/propertySpecifications");
|
|
11
|
+
let SHOULD_USE_PAGINATED_FIND = false;
|
|
12
|
+
let MAX_NUMBER_OF_ENTITIES_TO_FETCH = 1000;
|
|
13
|
+
class GetEntity extends basecommand_1.BaseCommand {
|
|
14
|
+
static description = '(BETA) Get an entity';
|
|
15
|
+
static examples = [
|
|
16
|
+
'contrail entities get color -- id=123',
|
|
17
|
+
'contrail entities get loader-processes -- createdOn=2025-09-15..2025-09-16',
|
|
18
|
+
'contrail entities get orders -- updatedAt=2025-09-15T04:00:00Z..',
|
|
19
|
+
'contrail entities get events -- timestamp=..2025-09-16T23:59:59Z',
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
...basecommand_1.BaseCommand.baseFlags,
|
|
23
|
+
outputFile: core_1.Flags.string({
|
|
24
|
+
char: 'o',
|
|
25
|
+
description: 'File to save the output (optional)',
|
|
26
|
+
required: false,
|
|
27
|
+
}),
|
|
28
|
+
skipHydrateRelationships: core_1.Flags.boolean({
|
|
29
|
+
char: 's',
|
|
30
|
+
description: 'Skip hydrating relationships when fetching the entity',
|
|
31
|
+
required: false,
|
|
32
|
+
default: false,
|
|
33
|
+
}),
|
|
34
|
+
relations: core_1.Flags.string({
|
|
35
|
+
char: 'r',
|
|
36
|
+
description: 'Comma-separated list of relations to include in the output',
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
propertySlugs: core_1.Flags.string({
|
|
40
|
+
char: 'p',
|
|
41
|
+
description: 'Comma-separated list of property slugs to include in the output',
|
|
42
|
+
required: false,
|
|
43
|
+
}),
|
|
44
|
+
paginated: core_1.Flags.boolean({
|
|
45
|
+
char: 'P',
|
|
46
|
+
description: 'If true, fetches all entities in a paginated manner',
|
|
47
|
+
required: false,
|
|
48
|
+
default: false,
|
|
49
|
+
}),
|
|
50
|
+
take: core_1.Flags.integer({
|
|
51
|
+
char: 't',
|
|
52
|
+
description: 'Number of entities to fetch in a single request (default is 100)',
|
|
53
|
+
required: false,
|
|
54
|
+
default: MAX_NUMBER_OF_ENTITIES_TO_FETCH,
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
57
|
+
static args = {
|
|
58
|
+
typePath: core_1.Args.string({
|
|
59
|
+
description: 'The name of the entity type to fetch',
|
|
60
|
+
required: true,
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
static strict = false;
|
|
64
|
+
async run() {
|
|
65
|
+
this.log('🚨 This command is in BETA and may change or be removed at any time.');
|
|
66
|
+
await (0, login_1.authUser)();
|
|
67
|
+
const { args, flags, argv } = await this.parse(GetEntity);
|
|
68
|
+
const { typePath } = args;
|
|
69
|
+
const { outputFile, propertySlugs, relations: relationString, skipHydrateRelationships, paginated: shouldUsePaginatedFind, take: numToTake, } = flags;
|
|
70
|
+
SHOULD_USE_PAGINATED_FIND = shouldUsePaginatedFind;
|
|
71
|
+
MAX_NUMBER_OF_ENTITIES_TO_FETCH = numToTake;
|
|
72
|
+
this.log('Will limit output to', MAX_NUMBER_OF_ENTITIES_TO_FETCH, 'entities');
|
|
73
|
+
const propertySpecificationArgs = argv
|
|
74
|
+
.slice(1, argv.length)
|
|
75
|
+
.filter(propertySpecifications_1.isValidPropertySpecification);
|
|
76
|
+
const propertySpecBySlug = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(propertySpecificationArgs);
|
|
77
|
+
const relations = relationString
|
|
78
|
+
? relationString
|
|
79
|
+
.split(',')
|
|
80
|
+
.map((r) => r.trim())
|
|
81
|
+
.filter(Boolean)
|
|
82
|
+
: [];
|
|
83
|
+
const propertySlugsArray = propertySlugs
|
|
84
|
+
? propertySlugs
|
|
85
|
+
.split(',')
|
|
86
|
+
.map((s) => s.trim())
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
: [];
|
|
89
|
+
try {
|
|
90
|
+
// TODO: Refactor this so it's easier to read. It was partially vibe-coded and Adam
|
|
91
|
+
// thinks it is hard to read. Consider breaking it into smaller functions.
|
|
92
|
+
if (SHOULD_USE_PAGINATED_FIND) {
|
|
93
|
+
const stream = streamPaginatedFind(typePath, {
|
|
94
|
+
skipHydrateRelationships,
|
|
95
|
+
relations,
|
|
96
|
+
propertySlugs: propertySlugsArray,
|
|
97
|
+
criteria: convertPropertySpecIntoCriteria(propertySpecBySlug),
|
|
98
|
+
});
|
|
99
|
+
const writer = outputFile ? fs_1.default.createWriteStream(outputFile) : process.stdout;
|
|
100
|
+
writer.write('[\n');
|
|
101
|
+
let first = true;
|
|
102
|
+
for await (const entity of stream) {
|
|
103
|
+
if (!first)
|
|
104
|
+
writer.write(',\n');
|
|
105
|
+
writer.write(JSON.stringify(entity, null, 2));
|
|
106
|
+
first = false;
|
|
107
|
+
}
|
|
108
|
+
writer.write('\n]\n');
|
|
109
|
+
if (outputFile)
|
|
110
|
+
this.log(`Output saved to ${outputFile}`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const response = await getEntityByTypePath(typePath, propertySpecBySlug, {
|
|
114
|
+
skipHydrateRelationships,
|
|
115
|
+
relations,
|
|
116
|
+
propertySlugs: propertySlugsArray,
|
|
117
|
+
});
|
|
118
|
+
if (outputFile) {
|
|
119
|
+
fs_1.default.writeFileSync(outputFile, JSON.stringify(response, null, 2));
|
|
120
|
+
this.log(`Output saved to ${outputFile}`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.log(JSON.stringify(response, null, 2));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
this.error(`Error fetching entity: ${e.message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.default = GetEntity;
|
|
133
|
+
async function getEntityByTypePath(typePath, propertySpec, options = {}) {
|
|
134
|
+
const { propertySlugs, relations, skipHydrateRelationships } = options;
|
|
135
|
+
const id = propertySpec?.get('id')?.toString() || undefined;
|
|
136
|
+
const { root: entityName } = (0, types_1.parseTypePath)(typePath);
|
|
137
|
+
let criteria = {};
|
|
138
|
+
if (entityName === 'custom-entity' && !id) {
|
|
139
|
+
criteria.typePath = typePath;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
criteria = convertPropertySpecIntoCriteria(propertySpec || new Map());
|
|
143
|
+
}
|
|
144
|
+
return find(entityName, { id, skipHydrateRelationships, relations, propertySlugs, criteria });
|
|
145
|
+
}
|
|
146
|
+
function convertPropertySpecIntoCriteria(propertySpec) {
|
|
147
|
+
const criteria = {};
|
|
148
|
+
propertySpec.forEach((value, slug) => {
|
|
149
|
+
criteria[slug] = value;
|
|
150
|
+
});
|
|
151
|
+
return criteria;
|
|
152
|
+
}
|
|
153
|
+
async function find(entityName, options = {}) {
|
|
154
|
+
const client = new sdk_1.Entities();
|
|
155
|
+
const { criteria, id, propertySlugs, skipHydrateRelationships, relations } = options;
|
|
156
|
+
const queryOptions = {
|
|
157
|
+
entityName,
|
|
158
|
+
skipHydrateRelationships,
|
|
159
|
+
relations,
|
|
160
|
+
};
|
|
161
|
+
if (propertySlugs && propertySlugs.length > 0) {
|
|
162
|
+
queryOptions.customParams = { propertySlugs: propertySlugs.join(',') };
|
|
163
|
+
}
|
|
164
|
+
return id ? client.get({ id, ...queryOptions }) : client.get({ criteria, ...queryOptions });
|
|
165
|
+
}
|
|
166
|
+
async function* streamPaginatedFind(typePath, options = {}) {
|
|
167
|
+
const client = new sdk_1.Entities();
|
|
168
|
+
let nextPageKey = undefined;
|
|
169
|
+
let count = 0;
|
|
170
|
+
const { root: entityName } = (0, types_1.parseTypePath)(typePath);
|
|
171
|
+
while (true) {
|
|
172
|
+
const opts = {
|
|
173
|
+
...options,
|
|
174
|
+
entityName,
|
|
175
|
+
apiVersion: sdk_1.API_VERSION.V2,
|
|
176
|
+
nextPageKey,
|
|
177
|
+
};
|
|
178
|
+
if (options.propertySlugs?.length > 0) {
|
|
179
|
+
opts.customParams = {
|
|
180
|
+
propertySlugs: options.propertySlugs.join(','),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const response = await client.get(opts);
|
|
184
|
+
nextPageKey = response.nextPageKey;
|
|
185
|
+
const results = response.results || [];
|
|
186
|
+
for (const result of results) {
|
|
187
|
+
if (count >= MAX_NUMBER_OF_ENTITIES_TO_FETCH)
|
|
188
|
+
return;
|
|
189
|
+
yield result;
|
|
190
|
+
count++;
|
|
191
|
+
}
|
|
192
|
+
// Update progress
|
|
193
|
+
console.log(`Fetched ${count} entities so far out of ${MAX_NUMBER_OF_ENTITIES_TO_FETCH}`);
|
|
194
|
+
if (!nextPageKey || results.length === 0 || count >= MAX_NUMBER_OF_ENTITIES_TO_FETCH) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Useful for troubleshooting
|
|
200
|
+
function logMemoryUsage(entityCount) {
|
|
201
|
+
const used = process.memoryUsage();
|
|
202
|
+
const toMB = (bytes) => (bytes / 1024 / 1024).toFixed(2);
|
|
203
|
+
console.error(`[${entityCount} entities] Memory Usage → RSS: ${toMB(used.rss)} MB | Heap: ${toMB(used.heapUsed)} MB / ${toMB(used.heapTotal)} MB`);
|
|
204
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseCommand } from '../../basecommand';
|
|
2
|
+
export default class UpdateEntity extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
skipHydrateRelationships: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
dryRun: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
inputFile: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
9
|
+
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
|
+
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
static args: {
|
|
13
|
+
entityName: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
|
|
14
|
+
id: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
|
|
15
|
+
};
|
|
16
|
+
static strict: boolean;
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const p_limit_1 = tslib_1.__importDefault(require("p-limit"));
|
|
5
|
+
const core_1 = require("@oclif/core");
|
|
6
|
+
const basecommand_1 = require("../../basecommand");
|
|
7
|
+
const sdk_1 = require("@contrail/sdk");
|
|
8
|
+
const login_1 = require("../../auth/login");
|
|
9
|
+
const propertySpecifications_1 = require("../../helpers/propertySpecifications/propertySpecifications");
|
|
10
|
+
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
11
|
+
class UpdateEntity extends basecommand_1.BaseCommand {
|
|
12
|
+
static description = '(BETA) Update an entity';
|
|
13
|
+
static examples = ['contrail entities update color 123 -- name=obsidian hex=000000'];
|
|
14
|
+
static flags = {
|
|
15
|
+
...basecommand_1.BaseCommand.baseFlags,
|
|
16
|
+
skipHydrateRelationships: core_1.Flags.boolean({
|
|
17
|
+
char: 's',
|
|
18
|
+
description: 'Skip hydrating relationships when updating the entity',
|
|
19
|
+
required: false,
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
dryRun: core_1.Flags.boolean({
|
|
23
|
+
char: 'd',
|
|
24
|
+
description: 'If true, does not create the entity but simulates the creation',
|
|
25
|
+
required: false,
|
|
26
|
+
default: false,
|
|
27
|
+
}),
|
|
28
|
+
inputFile: core_1.Flags.string({
|
|
29
|
+
char: 'i',
|
|
30
|
+
description: 'File containing properties to update the entity (optional)',
|
|
31
|
+
required: false,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
static args = {
|
|
35
|
+
entityName: core_1.Args.string({
|
|
36
|
+
description: 'The name of the entity type to update',
|
|
37
|
+
required: true,
|
|
38
|
+
}),
|
|
39
|
+
id: core_1.Args.string({
|
|
40
|
+
description: 'The unique identifier of the entity',
|
|
41
|
+
required: true,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
static strict = false;
|
|
45
|
+
async run() {
|
|
46
|
+
this.log('🚨 This command is in BETA and may change or be removed at any time.');
|
|
47
|
+
await (0, login_1.authUser)();
|
|
48
|
+
const { args, argv } = await this.parse(UpdateEntity);
|
|
49
|
+
const { id, entityName } = args;
|
|
50
|
+
const { dryRun: isDryRun, inputFile: inputFilename, skipHydrateRelationships } = this.flags;
|
|
51
|
+
const propertySpecArgs = argv.slice(1, argv.length).filter(propertySpecifications_1.isValidPropertySpecification);
|
|
52
|
+
const propertySpec = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(propertySpecArgs);
|
|
53
|
+
if (propertySpec.size === 0) {
|
|
54
|
+
throw new Error('No valid properties provided for update');
|
|
55
|
+
}
|
|
56
|
+
if (inputFilename && id) {
|
|
57
|
+
throw new Error('You cannot use --input and --id together. Please specify one or the other.');
|
|
58
|
+
}
|
|
59
|
+
const partialEntitiesArray = inputFilename
|
|
60
|
+
? (0, file_helpers_1.parseFileAsJsonArray)(inputFilename).map(({ id }) => ({ id }))
|
|
61
|
+
: [{ id }];
|
|
62
|
+
const results = {
|
|
63
|
+
idsSuccessfullyUpdated: new Set(),
|
|
64
|
+
idsFailedToUpdate: new Set(),
|
|
65
|
+
numEntriesSkipped: partialEntitiesArray.filter(({ id }) => !id).length,
|
|
66
|
+
};
|
|
67
|
+
const idsToUpdate = partialEntitiesArray.map(({ id }) => id).filter(Boolean);
|
|
68
|
+
propertySpec.delete('id'); // Remove 'id' from the property spec if it exists
|
|
69
|
+
const changes = [...propertySpec.entries()].reduce((acc, [key, value]) => {
|
|
70
|
+
acc[key] = value;
|
|
71
|
+
return acc;
|
|
72
|
+
}, {});
|
|
73
|
+
try {
|
|
74
|
+
async function updateEntity(id) {
|
|
75
|
+
const promise = isDryRun
|
|
76
|
+
? mockUpdateById(entityName, id, changes, { skipHydrateRelationships })
|
|
77
|
+
: updateEntityById(entityName, id, changes, { skipHydrateRelationships });
|
|
78
|
+
promise
|
|
79
|
+
.catch(() => {
|
|
80
|
+
results.idsFailedToUpdate.add(id);
|
|
81
|
+
})
|
|
82
|
+
.then(() => {
|
|
83
|
+
results.idsSuccessfullyUpdated.add(id);
|
|
84
|
+
});
|
|
85
|
+
return promise;
|
|
86
|
+
}
|
|
87
|
+
const limit = (0, p_limit_1.default)(5); // Limit concurrency to 5
|
|
88
|
+
const responses = await Promise.all(idsToUpdate.map((id) => limit(() => updateEntity(id))));
|
|
89
|
+
const messagePrefix = isDryRun
|
|
90
|
+
? 'Dry run. Update skipped. Changes would be:'
|
|
91
|
+
: 'Entity updated successfully:';
|
|
92
|
+
this.log(`${messagePrefix}\n\n${JSON.stringify(responses, null, 2)}`);
|
|
93
|
+
this.log(`\nIDs successfully updated: ${Array.from(results.idsSuccessfullyUpdated).join(', ')}`);
|
|
94
|
+
this.log(`IDs failed to update: ${Array.from(results.idsFailedToUpdate).join(', ')}`);
|
|
95
|
+
this.log(`Number of entries skipped due to missing IDs: ${results.numEntriesSkipped}`);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
this.error(`Error fetching entity: ${e.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.default = UpdateEntity;
|
|
103
|
+
async function updateEntityById(entityName, id, changes, options = {}) {
|
|
104
|
+
const client = new sdk_1.Entities();
|
|
105
|
+
const { skipHydrateRelationships } = options;
|
|
106
|
+
return client.update({ entityName, id, object: changes, skipHydrateRelationships });
|
|
107
|
+
}
|
|
108
|
+
async function mockUpdateById(entityName, id, changes, options = {}) {
|
|
109
|
+
const client = new sdk_1.Entities();
|
|
110
|
+
const { skipHydrateRelationships } = options;
|
|
111
|
+
const entity = await client.get({ entityName, id, skipHydrateRelationships });
|
|
112
|
+
if (!entity) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return { ...entity, ...changes };
|
|
116
|
+
}
|
|
@@ -115,20 +115,8 @@ async function uploadUsageInfoObjects(loadedUsageInfo) {
|
|
|
115
115
|
entityName: 'type-property-usage-info',
|
|
116
116
|
id: found.id,
|
|
117
117
|
object: {
|
|
118
|
-
editable: loadedUI
|
|
119
|
-
used: loadedUI
|
|
120
|
-
formulaFunction: loadedUI?.formulaFunction,
|
|
121
|
-
validationFunction: loadedUI?.validationFunction,
|
|
122
|
-
numberFormat: loadedUI?.numberFormat,
|
|
123
|
-
defaultValue: loadedUI?.defaultValue,
|
|
124
|
-
label: loadedUI?.label,
|
|
125
|
-
required: loadedUI?.required,
|
|
126
|
-
searchable: loadedUI?.searchable,
|
|
127
|
-
isUniqueProperty: loadedUI?.isUniqueProperty,
|
|
128
|
-
isUniquePropertyInFamily: loadedUI?.isUniquePropertyInFamily,
|
|
129
|
-
copyBehavior: loadedUI?.copyBehavior,
|
|
130
|
-
copyDefault: loadedUI?.copyDefault,
|
|
131
|
-
propertyLevel: loadedUI?.propertyLevel,
|
|
118
|
+
editable: loadedUI.editable,
|
|
119
|
+
used: loadedUI.used,
|
|
132
120
|
},
|
|
133
121
|
});
|
|
134
122
|
}
|
package/dist/common.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
export declare function getSeparatorByPlatform(): string;
|
|
3
3
|
export declare function delay(ms: number): Promise<NodeJS.Timeout>;
|
|
4
|
-
export declare function removeSlash(string:
|
|
4
|
+
export declare function removeSlash(string: string): string;
|
|
@@ -107,7 +107,6 @@ class AppDeployer {
|
|
|
107
107
|
if (appDeclaration?.accessGrants) {
|
|
108
108
|
accessGrants = await app_template_parsers_1.AccessGrantsParser.buildAccessGrants(appDeclaration);
|
|
109
109
|
}
|
|
110
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
111
110
|
if (appDeclaration.hasOwnProperty('dockerImageRef')) {
|
|
112
111
|
throw new Error('Docker apps are no longer supported. Please use a node app.');
|
|
113
112
|
}
|
|
@@ -202,11 +201,7 @@ async function createNodePackage(appIdentifier, debug, dryRun) {
|
|
|
202
201
|
logger_1.default.info('Installing production dependencies done.');
|
|
203
202
|
logger_1.default.info('Building package...');
|
|
204
203
|
try {
|
|
205
|
-
|
|
206
|
-
const buildConfigPath = fs_1.default.existsSync('tsconfig.build.json')
|
|
207
|
-
? 'tsconfig.build.json'
|
|
208
|
-
: 'tsconfig.json';
|
|
209
|
-
child_process_1.default.execSync(`npx tsc --project ${buildConfigPath} --outDir tmp/dist`);
|
|
204
|
+
child_process_1.default.execSync('npx tsc --outDir tmp/dist');
|
|
210
205
|
}
|
|
211
206
|
catch (e) {
|
|
212
207
|
logger_1.default.error(`${chalk_1.default.red.bold('APP')} Failed to build package.`);
|
|
@@ -233,7 +228,7 @@ async function createNodePackage(appIdentifier, debug, dryRun) {
|
|
|
233
228
|
return {};
|
|
234
229
|
}
|
|
235
230
|
logger_1.default.info('Uploading package to S3...');
|
|
236
|
-
|
|
231
|
+
let apps = await entitiesClient.get({ entityName: 'app' });
|
|
237
232
|
const thisApp = apps.find((x) => x.identifier === appIdentifier);
|
|
238
233
|
if (!thisApp) {
|
|
239
234
|
throw new Error('App not found');
|
|
@@ -243,7 +238,8 @@ async function createNodePackage(appIdentifier, debug, dryRun) {
|
|
|
243
238
|
object: { appId: thisApp.id },
|
|
244
239
|
});
|
|
245
240
|
// upload an s3 object using a signed url with an https put request
|
|
246
|
-
const
|
|
241
|
+
const readStream = fs_1.default.readFileSync('tmp.zip');
|
|
242
|
+
const postBody = Buffer.from(readStream);
|
|
247
243
|
// await axios.put(uploadUrlObject.signedUrl, postBody.toString());
|
|
248
244
|
await axios_1.default.request({
|
|
249
245
|
method: 'PUT',
|
|
@@ -119,7 +119,7 @@ async function createExtensionPackage(appId, extensionDir, options) {
|
|
|
119
119
|
await uploadExtensionPackage(appExtensionPackage, tmpDir, options);
|
|
120
120
|
logger_1.default.info(`${chalk_1.default.green.bold('COMPLETE')} Extension packaged and uploaded.`);
|
|
121
121
|
if (options.debug) {
|
|
122
|
-
logger_1.default.debug(
|
|
122
|
+
logger_1.default.debug("debug mode enabled. Not cleaning up tmp dir.");
|
|
123
123
|
}
|
|
124
124
|
else {
|
|
125
125
|
cleanUp(tmpDir);
|
|
@@ -103,13 +103,13 @@ exports.writeConfig = writeConfig;
|
|
|
103
103
|
async function getConfig() {
|
|
104
104
|
let fileContent;
|
|
105
105
|
try {
|
|
106
|
-
fileContent = (await node_fs_1.promises.readFile(configFileLocation)).toString();
|
|
106
|
+
fileContent = await (await node_fs_1.promises.readFile(configFileLocation)).toString();
|
|
107
107
|
}
|
|
108
108
|
catch {
|
|
109
109
|
logger_1.default.debug(`failed to open config file (${configFileLocation}) for reading.`);
|
|
110
110
|
logger_1.default.debug('trying to initialize config file');
|
|
111
111
|
await initializeVibeIQConfig();
|
|
112
|
-
fileContent = (await node_fs_1.promises.readFile(configFileLocation)).toString();
|
|
112
|
+
fileContent = await (await node_fs_1.promises.readFile(configFileLocation)).toString();
|
|
113
113
|
}
|
|
114
114
|
try {
|
|
115
115
|
const configFileJSON = JSON.parse(fileContent);
|
|
@@ -156,7 +156,7 @@ async function getConfigContent(fileName) {
|
|
|
156
156
|
const configFilePath = (0, node_path_1.join)(configDirectoryLocation, `${fileName}.json`);
|
|
157
157
|
let fileContent;
|
|
158
158
|
try {
|
|
159
|
-
fileContent = (await node_fs_1.promises.readFile(configFilePath)).toString();
|
|
159
|
+
fileContent = await (await node_fs_1.promises.readFile(configFilePath)).toString();
|
|
160
160
|
return JSON.parse(fileContent);
|
|
161
161
|
}
|
|
162
162
|
catch (error) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Item } from '@contrail/entity-types';
|
|
2
|
+
export declare function throwIfInvalidAssortmentId(assortmentId: string): Promise<void>;
|
|
3
|
+
export declare function createAssortmentItem(assortmentItem: {
|
|
4
|
+
assortmentId: string;
|
|
5
|
+
itemId: string;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}, { skipHydrateRelationships }?: {
|
|
8
|
+
skipHydrateRelationships: boolean;
|
|
9
|
+
}): Promise<any>;
|
|
10
|
+
export declare function findItemsNotInAssortment(assortmentId: string, count?: number): Promise<Item[]>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findItemsNotInAssortment = exports.createAssortmentItem = exports.throwIfInvalidAssortmentId = void 0;
|
|
4
|
+
const sdk_1 = require("@contrail/sdk");
|
|
5
|
+
const batchFetch_1 = require("./batchFetch");
|
|
6
|
+
const items_1 = require("./items");
|
|
7
|
+
async function throwIfInvalidAssortmentId(assortmentId) {
|
|
8
|
+
if (!(await doesAssortmentExistById(assortmentId))) {
|
|
9
|
+
throw new Error(`Assortment with ID ${assortmentId} does not exist`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.throwIfInvalidAssortmentId = throwIfInvalidAssortmentId;
|
|
13
|
+
async function doesAssortmentExistById(id) {
|
|
14
|
+
const client = new sdk_1.Entities();
|
|
15
|
+
const assortment = await client.get({ entityName: 'assortment', id });
|
|
16
|
+
return !!assortment;
|
|
17
|
+
}
|
|
18
|
+
async function createAssortmentItem(assortmentItem, { skipHydrateRelationships } = { skipHydrateRelationships: false }) {
|
|
19
|
+
const client = new sdk_1.Entities();
|
|
20
|
+
const { assortmentId, itemId } = assortmentItem;
|
|
21
|
+
// add item to assortment
|
|
22
|
+
await client.create({
|
|
23
|
+
entityName: 'assortment',
|
|
24
|
+
id: assortmentId,
|
|
25
|
+
relation: 'items',
|
|
26
|
+
object: { itemIds: [itemId] },
|
|
27
|
+
});
|
|
28
|
+
// Update assortment item with properties
|
|
29
|
+
const updated = await client.update({
|
|
30
|
+
entityName: 'assortment-item',
|
|
31
|
+
id: `${assortmentId}:${itemId}`,
|
|
32
|
+
object: assortmentItem,
|
|
33
|
+
skipHydrateRelationships,
|
|
34
|
+
});
|
|
35
|
+
return updated;
|
|
36
|
+
}
|
|
37
|
+
exports.createAssortmentItem = createAssortmentItem;
|
|
38
|
+
// Methods to find an item not in the assortment
|
|
39
|
+
const ASSORTMENT_ITEMS_CACHE_BY_ASSORTMENT_ID = new Map();
|
|
40
|
+
const ASSORTMENT_ITEMS_CACHE_BY_ITEM_ID = new Map();
|
|
41
|
+
async function cacheAssortmentItemsByAssortmentId(assortmentId) {
|
|
42
|
+
if (!assortmentId) {
|
|
43
|
+
throw new Error('Assortment ID is required to cache assortment items');
|
|
44
|
+
}
|
|
45
|
+
if (ASSORTMENT_ITEMS_CACHE_BY_ASSORTMENT_ID.has(assortmentId)) {
|
|
46
|
+
return; // Already cached
|
|
47
|
+
}
|
|
48
|
+
const assortmentItems = await (0, batchFetch_1.batchFetch)('assortment-item', {
|
|
49
|
+
criteria: { assortmentId },
|
|
50
|
+
customParams: { propertySlugs: 'id,assortmentId,itemId' },
|
|
51
|
+
});
|
|
52
|
+
ASSORTMENT_ITEMS_CACHE_BY_ASSORTMENT_ID.set(assortmentId, assortmentItems);
|
|
53
|
+
assortmentItems.forEach((item) => {
|
|
54
|
+
if (!ASSORTMENT_ITEMS_CACHE_BY_ITEM_ID.has(item.itemId)) {
|
|
55
|
+
ASSORTMENT_ITEMS_CACHE_BY_ITEM_ID.set(item.itemId, []);
|
|
56
|
+
}
|
|
57
|
+
ASSORTMENT_ITEMS_CACHE_BY_ITEM_ID.get(item.itemId).push(item);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function getAssortmentItemsByAssortmentId(assortmentId) {
|
|
61
|
+
if (!assortmentId) {
|
|
62
|
+
throw new Error('Assortment ID is required to get assortment items');
|
|
63
|
+
}
|
|
64
|
+
await cacheAssortmentItemsByAssortmentId(assortmentId);
|
|
65
|
+
const assortmentItems = ASSORTMENT_ITEMS_CACHE_BY_ASSORTMENT_ID.get(assortmentId);
|
|
66
|
+
if (!assortmentItems) {
|
|
67
|
+
throw new Error(`No assortment items found for assortment ID ${assortmentId}`);
|
|
68
|
+
}
|
|
69
|
+
return assortmentItems;
|
|
70
|
+
}
|
|
71
|
+
async function findItemsNotInAssortment(assortmentId, count) {
|
|
72
|
+
if (!assortmentId) {
|
|
73
|
+
throw new Error('Assortment ID is required to find items not in assortment');
|
|
74
|
+
}
|
|
75
|
+
const assortmentItemsInAssortment = await getAssortmentItemsByAssortmentId(assortmentId);
|
|
76
|
+
const items = await (0, items_1.getManyItems)(count + assortmentItemsInAssortment.length);
|
|
77
|
+
const assortmentItemIds = new Set(assortmentItemsInAssortment.map((ai) => ai.itemId));
|
|
78
|
+
const itemsNotInAssortment = items.filter((item) => !assortmentItemIds.has(item.id));
|
|
79
|
+
if (itemsNotInAssortment.length === 0) {
|
|
80
|
+
throw new Error(`All items are already in assortment ${assortmentId}`);
|
|
81
|
+
}
|
|
82
|
+
return itemsNotInAssortment.sort((a, b) => a.id.localeCompare(b.id)); // sorting ensures consistent order of response
|
|
83
|
+
}
|
|
84
|
+
exports.findItemsNotInAssortment = findItemsNotInAssortment;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AssortmentItem, Item, ProjectItem } from '@contrail/entity-types';
|
|
2
|
+
import { EntitiesClientGetOptions } from '@contrail/sdk';
|
|
3
|
+
export interface BatchFetchableEntityMap {
|
|
4
|
+
item: Item;
|
|
5
|
+
'project-item': ProjectItem;
|
|
6
|
+
'assortment-item': AssortmentItem;
|
|
7
|
+
}
|
|
8
|
+
export declare function batchFetch<K extends keyof BatchFetchableEntityMap>(entityName: K, options: EntitiesClientGetOptions): Promise<BatchFetchableEntityMap[K][]>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.batchFetch = void 0;
|
|
4
|
+
const sdk_1 = require("@contrail/sdk");
|
|
5
|
+
const util_1 = require("@contrail/util");
|
|
6
|
+
async function batchFetch(entityName, options) {
|
|
7
|
+
const response = await requestBatchFetch(entityName, options);
|
|
8
|
+
return parseBatchFetchResponse(response);
|
|
9
|
+
}
|
|
10
|
+
exports.batchFetch = batchFetch;
|
|
11
|
+
async function requestBatchFetch(entityName, options) {
|
|
12
|
+
const client = new sdk_1.Entities();
|
|
13
|
+
// NOTE: There is probably a helper somewhere else that manages this conversion
|
|
14
|
+
const optionsClone = { ...options };
|
|
15
|
+
if (optionsClone.criteria) {
|
|
16
|
+
optionsClone.where = optionsClone.criteria;
|
|
17
|
+
}
|
|
18
|
+
return client.create({
|
|
19
|
+
entityName: entityName,
|
|
20
|
+
suffix: 'batch-fetch',
|
|
21
|
+
object: optionsClone,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function parseBatchFetchResponse(response) {
|
|
25
|
+
if ('results' in response) {
|
|
26
|
+
return response.results;
|
|
27
|
+
}
|
|
28
|
+
else if ('resultsCompressedString' in response) {
|
|
29
|
+
const compressedString = response.resultsCompressedString;
|
|
30
|
+
const jsonData = (0, util_1.decompressFromString)(compressedString);
|
|
31
|
+
return jsonData;
|
|
32
|
+
}
|
|
33
|
+
else if ('resultsDownloadUrl' in response) {
|
|
34
|
+
const downloadUrl = response.resultsDownloadUrl;
|
|
35
|
+
const data = await fetch(downloadUrl).then((res) => res.json());
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
throw new Error('Invalid batch fetch response format');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./assortment-items"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./items"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./project-items"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Item } from '@contrail/entity-types';
|
|
2
|
+
export declare function throwIfInvalidItemId(itemId: string): Promise<void>;
|
|
3
|
+
export type SparseItem = Pick<Item, 'id' | 'familyId' | 'roles' | 'name'>;
|
|
4
|
+
export declare function getAllItemsUsingCacheFallback(): Promise<SparseItem[]>;
|
|
5
|
+
export declare function getManyItems(maxCount?: number): Promise<SparseItem[]>;
|
|
6
|
+
export declare function getItemFamilies(count: number): Promise<SparseItem[] | undefined>;
|
|
7
|
+
export declare function getFamilyName(item: SparseItem): Promise<string[] | undefined>;
|
|
8
|
+
export declare const Roles: readonly ["option", "family"];
|
|
9
|
+
export type Role = (typeof Roles)[number];
|
|
10
|
+
export declare function throwIfInvalidItemLevel(role: string): asserts role is Role;
|