@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.
- package/README.md +142 -1
- package/dist/api/types.d.ts +35 -0
- package/dist/api/types.js +47 -1
- package/dist/basecommand.d.ts +3 -1
- package/dist/basecommand.js +1 -1
- package/dist/commands/app/extension/deploy.js +2 -2
- package/dist/commands/app/extension/deployment/list.js +2 -2
- package/dist/commands/entities/create.d.ts +37 -0
- package/dist/commands/entities/create.js +532 -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 +103 -0
- package/dist/commands/entities/get.d.ts +19 -0
- package/dist/commands/entities/get.js +151 -0
- package/dist/commands/entities/update.d.ts +17 -0
- package/dist/commands/entities/update.js +86 -0
- package/dist/common.d.ts +1 -1
- package/dist/helpers/app/app-deployer.js +25 -0
- package/dist/helpers/app/{extension-package.js → extension-package-deployer.js} +1 -1
- package/dist/helpers/app/metadata-extractor.d.ts +1 -0
- package/dist/helpers/app/metadata-extractor.js +100 -0
- package/dist/helpers/entities/assortment-items.d.ts +10 -0
- package/dist/helpers/entities/assortment-items.js +86 -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 +84 -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/propertySpecifications.d.ts +26 -0
- package/dist/helpers/propertySpecifications/propertySpecifications.js +58 -0
- package/dist/helpers/propertySpecifications/propertySpecifications.spec.d.ts +1 -0
- package/dist/helpers/propertySpecifications/propertySpecifications.spec.js +266 -0
- package/oclif.manifest.json +341 -1
- package/package.json +5 -4
- /package/dist/helpers/app/{extension-package.d.ts → extension-package-deployer.d.ts} +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
class GetEntity extends basecommand_1.BaseCommand {
|
|
13
|
+
static description = '(BETA) Get an entity';
|
|
14
|
+
static examples = [`contrail entities get color -- id=123`];
|
|
15
|
+
static flags = {
|
|
16
|
+
...basecommand_1.BaseCommand.baseFlags,
|
|
17
|
+
outputFile: core_1.Flags.string({
|
|
18
|
+
char: 'o',
|
|
19
|
+
description: 'File to save the output (optional)',
|
|
20
|
+
required: false,
|
|
21
|
+
}),
|
|
22
|
+
skipHydrateRelationships: core_1.Flags.boolean({
|
|
23
|
+
char: 's',
|
|
24
|
+
description: 'Skip hydrating relationships when fetching the entity',
|
|
25
|
+
required: false,
|
|
26
|
+
default: false,
|
|
27
|
+
}),
|
|
28
|
+
relations: core_1.Flags.string({
|
|
29
|
+
char: 'r',
|
|
30
|
+
description: 'Comma-separated list of relations to include in the output',
|
|
31
|
+
required: false,
|
|
32
|
+
}),
|
|
33
|
+
propertySlugs: core_1.Flags.string({
|
|
34
|
+
char: 'p',
|
|
35
|
+
description: 'Comma-separated list of property slugs to include in the output',
|
|
36
|
+
required: false,
|
|
37
|
+
}),
|
|
38
|
+
paginated: core_1.Flags.boolean({
|
|
39
|
+
char: 'P',
|
|
40
|
+
description: 'If true, fetches all entities in a paginated manner',
|
|
41
|
+
required: false,
|
|
42
|
+
default: false,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
static args = {
|
|
46
|
+
typePath: core_1.Args.string({
|
|
47
|
+
description: 'The name of the entity type to fetch',
|
|
48
|
+
required: true,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
static strict = false;
|
|
52
|
+
async run() {
|
|
53
|
+
this.log('🚨 This command is in BETA and may change or be removed at any time.');
|
|
54
|
+
await (0, login_1.authUser)();
|
|
55
|
+
const { args, flags, argv } = await this.parse(GetEntity);
|
|
56
|
+
const { typePath } = args;
|
|
57
|
+
const { outputFile, propertySlugs, relations: relationString, skipHydrateRelationships, paginated: shouldUsePaginatedFind, } = flags;
|
|
58
|
+
SHOULD_USE_PAGINATED_FIND = shouldUsePaginatedFind;
|
|
59
|
+
const propertySpecificationArgs = argv
|
|
60
|
+
.slice(1, argv.length)
|
|
61
|
+
.filter(propertySpecifications_1.isValidPropertySpecification);
|
|
62
|
+
const propertySpecBySlug = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(propertySpecificationArgs);
|
|
63
|
+
const relations = relationString
|
|
64
|
+
? relationString
|
|
65
|
+
.split(',')
|
|
66
|
+
.map((r) => r.trim())
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
: [];
|
|
69
|
+
const propertySlugsArray = propertySlugs
|
|
70
|
+
? propertySlugs
|
|
71
|
+
.split(',')
|
|
72
|
+
.map((s) => s.trim())
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
: [];
|
|
75
|
+
try {
|
|
76
|
+
const response = await getEntityByTypePath(typePath, propertySpecBySlug, {
|
|
77
|
+
skipHydrateRelationships,
|
|
78
|
+
relations,
|
|
79
|
+
propertySlugs: propertySlugsArray,
|
|
80
|
+
});
|
|
81
|
+
if (outputFile) {
|
|
82
|
+
fs_1.default.writeFileSync(outputFile, JSON.stringify(response, null, 2));
|
|
83
|
+
this.log(`Output saved to ${outputFile}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.log(JSON.stringify(response, null, 2));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
this.error(`Error fetching entity: ${e.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.default = GetEntity;
|
|
95
|
+
async function getEntityByTypePath(typePath, propertySpec, options = {}) {
|
|
96
|
+
const { propertySlugs, relations, skipHydrateRelationships } = options;
|
|
97
|
+
const id = propertySpec?.get('id')?.toString() || undefined;
|
|
98
|
+
const { root: entityName } = (0, types_1.parseTypePath)(typePath);
|
|
99
|
+
let criteria = {};
|
|
100
|
+
if (entityName === 'custom-entity' && !id) {
|
|
101
|
+
criteria.typePath = typePath;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
criteria = convertPropertySpecIntoCriteria(propertySpec || new Map());
|
|
105
|
+
}
|
|
106
|
+
return SHOULD_USE_PAGINATED_FIND
|
|
107
|
+
? paginatedFind(entityName, { skipHydrateRelationships, relations, propertySlugs, criteria })
|
|
108
|
+
: find(entityName, { id, skipHydrateRelationships, relations, propertySlugs, criteria });
|
|
109
|
+
}
|
|
110
|
+
function convertPropertySpecIntoCriteria(propertySpec) {
|
|
111
|
+
const criteria = {};
|
|
112
|
+
propertySpec.forEach((value, slug) => {
|
|
113
|
+
criteria[slug] = value;
|
|
114
|
+
});
|
|
115
|
+
return criteria;
|
|
116
|
+
}
|
|
117
|
+
async function find(entityName, options = {}) {
|
|
118
|
+
const client = new sdk_1.Entities();
|
|
119
|
+
const { criteria, id, propertySlugs, skipHydrateRelationships, relations } = options;
|
|
120
|
+
const queryOptions = {
|
|
121
|
+
entityName,
|
|
122
|
+
skipHydrateRelationships,
|
|
123
|
+
relations,
|
|
124
|
+
};
|
|
125
|
+
if (propertySlugs && propertySlugs.length > 0) {
|
|
126
|
+
queryOptions.customParams = { propertySlugs: propertySlugs.join(',') };
|
|
127
|
+
}
|
|
128
|
+
return id ? client.get({ id, ...queryOptions }) : client.get({ criteria, ...queryOptions });
|
|
129
|
+
}
|
|
130
|
+
async function paginatedFind(entityName, options = {}) {
|
|
131
|
+
const client = new sdk_1.Entities();
|
|
132
|
+
const entities = [];
|
|
133
|
+
let nextPageKey = undefined;
|
|
134
|
+
while (true) {
|
|
135
|
+
const response = await client.get({
|
|
136
|
+
...options,
|
|
137
|
+
entityName,
|
|
138
|
+
apiVersion: sdk_1.API_VERSION.V2,
|
|
139
|
+
nextPageKey,
|
|
140
|
+
customParams: {
|
|
141
|
+
propertySlugs: options.propertySlugs?.join(','),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
nextPageKey = response.nextPageKey;
|
|
145
|
+
if (!response.results || response.results?.length === 0) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
entities.push(...response.results);
|
|
149
|
+
}
|
|
150
|
+
return entities;
|
|
151
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
9
|
+
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
static args: {
|
|
12
|
+
entityName: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
|
|
13
|
+
id: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
|
|
14
|
+
};
|
|
15
|
+
static strict: boolean;
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const basecommand_1 = require("../../basecommand");
|
|
5
|
+
const sdk_1 = require("@contrail/sdk");
|
|
6
|
+
const login_1 = require("../../auth/login");
|
|
7
|
+
const propertySpecifications_1 = require("../../helpers/propertySpecifications/propertySpecifications");
|
|
8
|
+
class UpdateEntity extends basecommand_1.BaseCommand {
|
|
9
|
+
static description = '(BETA) Update an entity';
|
|
10
|
+
static examples = ['contrail entities update color 123 -- name=obsidian hex=000000'];
|
|
11
|
+
static flags = {
|
|
12
|
+
...basecommand_1.BaseCommand.baseFlags,
|
|
13
|
+
skipHydrateRelationships: core_1.Flags.boolean({
|
|
14
|
+
char: 's',
|
|
15
|
+
description: 'Skip hydrating relationships when updating the entity',
|
|
16
|
+
required: false,
|
|
17
|
+
default: false,
|
|
18
|
+
}),
|
|
19
|
+
dryRun: core_1.Flags.boolean({
|
|
20
|
+
char: 'd',
|
|
21
|
+
description: 'If true, does not create the entity but simulates the creation',
|
|
22
|
+
required: false,
|
|
23
|
+
default: false,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
static args = {
|
|
27
|
+
entityName: core_1.Args.string({
|
|
28
|
+
description: 'The name of the entity type to update',
|
|
29
|
+
required: true,
|
|
30
|
+
}),
|
|
31
|
+
id: core_1.Args.string({
|
|
32
|
+
description: 'The unique identifier of the entity',
|
|
33
|
+
required: true,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
static strict = false;
|
|
37
|
+
async run() {
|
|
38
|
+
this.log('🚨 This command is in BETA and may change or be removed at any time.');
|
|
39
|
+
await (0, login_1.authUser)();
|
|
40
|
+
const { args, argv } = await this.parse(UpdateEntity);
|
|
41
|
+
const { id, entityName } = args;
|
|
42
|
+
const { dryRun: isDryRun, skipHydrateRelationships } = this.flags;
|
|
43
|
+
const propertySpecArgs = argv.slice(1, argv.length).filter(propertySpecifications_1.isValidPropertySpecification);
|
|
44
|
+
const propertySpec = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(propertySpecArgs);
|
|
45
|
+
if (propertySpec.size === 0) {
|
|
46
|
+
throw new Error('No valid properties provided for update');
|
|
47
|
+
}
|
|
48
|
+
if (propertySpec.has('id') && propertySpec.get('id') !== id) {
|
|
49
|
+
throw new Error(`Provided ID ${propertySpec.get('id')} does not match the entity ID ${id}`);
|
|
50
|
+
}
|
|
51
|
+
const changes = [...propertySpec.entries()].reduce((acc, [key, value]) => {
|
|
52
|
+
if (key === 'id') {
|
|
53
|
+
return acc;
|
|
54
|
+
}
|
|
55
|
+
acc[key] = value;
|
|
56
|
+
return acc;
|
|
57
|
+
}, {});
|
|
58
|
+
try {
|
|
59
|
+
const response = isDryRun
|
|
60
|
+
? await mockUpdateById(entityName, id, changes, { skipHydrateRelationships })
|
|
61
|
+
: await updateEntityById(entityName, id, changes, { skipHydrateRelationships });
|
|
62
|
+
const messagePrefix = isDryRun
|
|
63
|
+
? 'Dry run. Update skipped. Changes would be:'
|
|
64
|
+
: 'Entity updated successfully:';
|
|
65
|
+
this.log(`${messagePrefix}\n\n${JSON.stringify(response, null, 2)}`);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
this.error(`Error fetching entity: ${e.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.default = UpdateEntity;
|
|
73
|
+
async function updateEntityById(entityName, id, changes, options = {}) {
|
|
74
|
+
const client = new sdk_1.Entities();
|
|
75
|
+
const { skipHydrateRelationships } = options;
|
|
76
|
+
return client.update({ entityName, id, object: changes, skipHydrateRelationships });
|
|
77
|
+
}
|
|
78
|
+
async function mockUpdateById(entityName, id, changes, options = {}) {
|
|
79
|
+
const client = new sdk_1.Entities();
|
|
80
|
+
const { skipHydrateRelationships } = options;
|
|
81
|
+
const entity = await client.get({ entityName, id, skipHydrateRelationships });
|
|
82
|
+
if (!entity) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return { ...entity, ...changes };
|
|
86
|
+
}
|
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;
|
|
@@ -14,6 +14,7 @@ const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
|
14
14
|
const child_process_1 = tslib_1.__importDefault(require("child_process"));
|
|
15
15
|
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
16
16
|
const extensions_deployer_1 = require("./extensions-deployer");
|
|
17
|
+
const metadata_extractor_1 = require("./metadata-extractor");
|
|
17
18
|
class AppDeployer {
|
|
18
19
|
debug;
|
|
19
20
|
dryRun;
|
|
@@ -165,6 +166,25 @@ class AppDeployer {
|
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
168
|
exports.AppDeployer = AppDeployer;
|
|
169
|
+
async function createSourceArchive(tmpDir) {
|
|
170
|
+
logger_1.default.info('Creating source code archive...');
|
|
171
|
+
const sourceFiles = ['src/', 'package.json', 'app.yml'];
|
|
172
|
+
if (fs_1.default.existsSync('package-lock.json')) {
|
|
173
|
+
sourceFiles.push('package-lock.json');
|
|
174
|
+
}
|
|
175
|
+
const sourceDir = `${tmpDir}/source`;
|
|
176
|
+
fs_1.default.mkdirSync(sourceDir, { recursive: true });
|
|
177
|
+
sourceFiles.forEach((sourceFile) => {
|
|
178
|
+
if (!fs_1.default.existsSync(sourceFile))
|
|
179
|
+
return;
|
|
180
|
+
const isDirectory = fs_1.default.statSync(sourceFile).isDirectory();
|
|
181
|
+
const copyOptions = isDirectory ? { recursive: true } : undefined;
|
|
182
|
+
fs_1.default.cpSync(sourceFile, `${sourceDir}/${sourceFile}`, copyOptions);
|
|
183
|
+
});
|
|
184
|
+
await (0, zip_a_folder_1.zip)(`${sourceDir}/`, `${tmpDir}/source-code.zip`);
|
|
185
|
+
logger_1.default.info('Source code archive created.');
|
|
186
|
+
return `${tmpDir}/source-code.zip`;
|
|
187
|
+
}
|
|
168
188
|
async function createNodePackage(appIdentifier, debug, dryRun) {
|
|
169
189
|
// zip src code and package.json and package-lock.json files into a zip file
|
|
170
190
|
// and upload as a code package.
|
|
@@ -178,6 +198,8 @@ async function createNodePackage(appIdentifier, debug, dryRun) {
|
|
|
178
198
|
fs_1.default.cpSync('package-lock.json', 'tmp/package-lock.json');
|
|
179
199
|
}
|
|
180
200
|
fs_1.default.mkdirSync('tmp/node_modules', { recursive: true });
|
|
201
|
+
const buildInfoPath = await (0, metadata_extractor_1.createBuildMetadata)('tmp');
|
|
202
|
+
const sourceArchivePath = await createSourceArchive('tmp');
|
|
181
203
|
logger_1.default.info('Installing production dependencies started...');
|
|
182
204
|
child_process_1.default.execSync('npm install --omit=dev --prefix tmp/');
|
|
183
205
|
logger_1.default.info('Installing production dependencies done.');
|
|
@@ -199,6 +221,9 @@ async function createNodePackage(appIdentifier, debug, dryRun) {
|
|
|
199
221
|
logger_1.default.info('Copying Assets done.');
|
|
200
222
|
// including the app.yml file in the package at the dist level
|
|
201
223
|
fs_1.default.cpSync('app.yml', 'tmp/dist/app.yml');
|
|
224
|
+
// Copy build info and source code zip to dist
|
|
225
|
+
fs_1.default.cpSync(buildInfoPath, 'tmp/dist/build-info.json');
|
|
226
|
+
fs_1.default.cpSync(sourceArchivePath, 'tmp/dist/source-code.zip');
|
|
202
227
|
logger_1.default.info('Zipping package...');
|
|
203
228
|
await (0, zip_a_folder_1.zip)('tmp/', 'tmp.zip');
|
|
204
229
|
logger_1.default.info('Zipping package done.');
|
|
@@ -96,7 +96,7 @@ async function uploadExtensionPackage(appExtensionPackage, tmpDir, options) {
|
|
|
96
96
|
});
|
|
97
97
|
logger_1.default.debug(`${chalk_1.default.green.bold('EXTENSION')} Uploaded extension package to ${appExtensionPackage.uploadURL}`);
|
|
98
98
|
logger_1.default.info(`${chalk_1.default.green.bold('EXTENSION')} Extension package uploaded. ID: ${appExtensionPackage.id}`);
|
|
99
|
-
if (result.status
|
|
99
|
+
if (result.status < 200 || result.status >= 300) {
|
|
100
100
|
logger_1.default.error(`${chalk_1.default.red.bold('EXTENSION')} Failed to upload extension package. Status: ${result.status}`);
|
|
101
101
|
logger_1.default.error(JSON.stringify(result, null, 2));
|
|
102
102
|
throw new Error(`Failed to upload extension package. Status: ${result.status}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createBuildMetadata(tmpDir: string): string;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createBuildMetadata = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const os = tslib_1.__importStar(require("os"));
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
8
|
+
function createBuildMetadata(tmpDir) {
|
|
9
|
+
const buildTimestamp = new Date();
|
|
10
|
+
const systemInfo = {
|
|
11
|
+
nodeVersion: process.version,
|
|
12
|
+
platform: process.platform,
|
|
13
|
+
architecture: process.arch,
|
|
14
|
+
operatingSystem: os.type(),
|
|
15
|
+
osRelease: os.release(),
|
|
16
|
+
hostname: os.hostname(),
|
|
17
|
+
totalMemory: os.totalmem(),
|
|
18
|
+
freeMemory: os.freemem(),
|
|
19
|
+
cpuCount: os.cpus().length,
|
|
20
|
+
cpuModel: os.cpus()[0]?.model || 'unknown',
|
|
21
|
+
homeDirectory: os.homedir(),
|
|
22
|
+
tmpDirectory: os.tmpdir(),
|
|
23
|
+
processId: process.pid,
|
|
24
|
+
processTitle: process.title,
|
|
25
|
+
execPath: process.execPath,
|
|
26
|
+
workingDirectory: process.cwd(),
|
|
27
|
+
};
|
|
28
|
+
const environmentInfo = {
|
|
29
|
+
shell: process.env.SHELL,
|
|
30
|
+
term: process.env.TERM,
|
|
31
|
+
lang: process.env.LANG,
|
|
32
|
+
timezone: process.env.TZ || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
33
|
+
};
|
|
34
|
+
let gitInfo = {};
|
|
35
|
+
try {
|
|
36
|
+
const gitCommitHash = (0, child_process_1.execSync)('git rev-parse HEAD', {
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
timeout: 5000,
|
|
39
|
+
}).trim();
|
|
40
|
+
const gitBranch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
|
|
41
|
+
encoding: 'utf8',
|
|
42
|
+
timeout: 5000,
|
|
43
|
+
}).trim();
|
|
44
|
+
const gitRemoteUrl = (0, child_process_1.execSync)('git config --get remote.origin.url', {
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
timeout: 5000,
|
|
47
|
+
}).trim();
|
|
48
|
+
const gitCommitMessage = (0, child_process_1.execSync)('git log -1 --pretty=%B', {
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
timeout: 5000,
|
|
51
|
+
}).trim();
|
|
52
|
+
const gitAuthor = (0, child_process_1.execSync)('git log -1 --pretty=format:"%an <%ae>"', {
|
|
53
|
+
encoding: 'utf8',
|
|
54
|
+
timeout: 5000,
|
|
55
|
+
}).trim();
|
|
56
|
+
const gitCommitDate = (0, child_process_1.execSync)('git log -1 --pretty=format:"%ai"', {
|
|
57
|
+
encoding: 'utf8',
|
|
58
|
+
timeout: 5000,
|
|
59
|
+
}).trim();
|
|
60
|
+
const gitTagsOutput = (0, child_process_1.execSync)('git tag --points-at HEAD', {
|
|
61
|
+
encoding: 'utf8',
|
|
62
|
+
timeout: 5000,
|
|
63
|
+
}).trim();
|
|
64
|
+
const gitTags = gitTagsOutput ? gitTagsOutput.split('\n') : [];
|
|
65
|
+
gitInfo = {
|
|
66
|
+
commitHash: gitCommitHash,
|
|
67
|
+
shortCommitHash: gitCommitHash.substring(0, 8),
|
|
68
|
+
branch: gitBranch,
|
|
69
|
+
remoteUrl: gitRemoteUrl,
|
|
70
|
+
commitMessage: gitCommitMessage,
|
|
71
|
+
author: gitAuthor,
|
|
72
|
+
commitDate: gitCommitDate,
|
|
73
|
+
tags: gitTags,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
gitInfo = {
|
|
78
|
+
error: 'Git information unavailable',
|
|
79
|
+
message: error instanceof Error ? error.message : 'Unknown git error',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const buildInfo = {
|
|
83
|
+
buildTimestamp: buildTimestamp.toISOString(),
|
|
84
|
+
buildTimestampUnix: Math.floor(buildTimestamp.getTime() / 1000),
|
|
85
|
+
buildTool: 'contrail-cli',
|
|
86
|
+
buildUser: process.env.USER || process.env.USERNAME || 'unknown',
|
|
87
|
+
system: systemInfo,
|
|
88
|
+
environment: environmentInfo,
|
|
89
|
+
git: gitInfo,
|
|
90
|
+
metadata: {
|
|
91
|
+
schemaVersion: '1.0.0',
|
|
92
|
+
generatedBy: 'contrail-cli-app-deployer',
|
|
93
|
+
purpose: 'Build and deployment metadata for debugging and auditing',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const buildInfoPath = `${tmpDir}/build-info.json`;
|
|
97
|
+
fs_1.default.writeFileSync(buildInfoPath, JSON.stringify(buildInfo, null, 2));
|
|
98
|
+
return buildInfoPath;
|
|
99
|
+
}
|
|
100
|
+
exports.createBuildMetadata = createBuildMetadata;
|
|
@@ -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): Promise<Item[]>;
|
|
@@ -0,0 +1,86 @@
|
|
|
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) {
|
|
72
|
+
if (!assortmentId) {
|
|
73
|
+
throw new Error('Assortment ID is required to find items not in assortment');
|
|
74
|
+
}
|
|
75
|
+
const [assortmentItemsInAssortment, allItems] = await Promise.all([
|
|
76
|
+
getAssortmentItemsByAssortmentId(assortmentId),
|
|
77
|
+
(0, items_1.getAllItems)(),
|
|
78
|
+
]);
|
|
79
|
+
const assortmentItemIds = new Set(assortmentItemsInAssortment.map((ai) => ai.itemId));
|
|
80
|
+
const itemsNotInAssortment = allItems.filter((item) => !assortmentItemIds.has(item.id));
|
|
81
|
+
if (itemsNotInAssortment.length === 0) {
|
|
82
|
+
throw new Error(`All items are already in assortment ${assortmentId}`);
|
|
83
|
+
}
|
|
84
|
+
return itemsNotInAssortment.sort((a, b) => a.id.localeCompare(b.id)); // sorting ensures consistent order of response
|
|
85
|
+
}
|
|
86
|
+
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 getAllItems(): Promise<SparseItem[]>;
|
|
6
|
+
export declare function getItemFamilies(): 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;
|