@gnar-engine/cli 1.0.5 → 1.0.7
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/bootstrap/deploy.localdev.yml +14 -0
- package/bootstrap/secrets.localdev.yml +7 -3
- package/bootstrap/services/notification/Dockerfile +2 -2
- package/bootstrap/services/notification/package.json +14 -32
- package/bootstrap/services/notification/src/app.js +50 -48
- package/bootstrap/services/notification/src/commands/notification.handler.js +96 -0
- package/bootstrap/services/notification/src/config.js +55 -12
- package/bootstrap/services/notification/src/controllers/http.controller.js +87 -0
- package/bootstrap/services/notification/src/controllers/message.controller.js +39 -70
- package/bootstrap/services/notification/src/db/migrations/01-init.js +50 -0
- package/bootstrap/services/notification/src/db/migrations/02-notification-service-init.js +23 -0
- package/bootstrap/services/notification/src/policies/notification.policy.js +49 -0
- package/bootstrap/services/notification/src/schema/notification.schema.js +17 -0
- package/bootstrap/services/notification/src/services/notification.service.js +32 -0
- package/bootstrap/services/portal/src/services/client.js +3 -0
- package/bootstrap/services/user/src/commands/user.handler.js +35 -18
- package/bootstrap/services/user/src/tests/commands/user.test.js +15 -6
- package/install-from-clone.sh +1 -1
- package/package.json +1 -1
- package/src/cli.js +0 -6
- package/src/config.js +13 -1
- package/src/dev/commands.js +7 -3
- package/src/dev/dev.service.js +192 -128
- package/src/helpers/helpers.js +24 -0
- package/src/profiles/command.js +41 -0
- package/src/profiles/profiles.client.js +23 -0
- package/src/scaffolder/commands.js +57 -1
- package/src/scaffolder/scaffolder.handler.js +127 -60
- package/src/services/docker.js +173 -0
- package/templates/entity/src/commands/{{entityName}}.handler.js.hbs +94 -0
- package/templates/entity/src/controllers/{{entityName}}.http.controller.js.hbs +87 -0
- package/templates/entity/src/mysql.db/migrations/03-{{entityName}}-entity-init.js.hbs +23 -0
- package/templates/entity/src/policies/{{entityName}}.policy.js.hbs +49 -0
- package/templates/entity/src/schema/{{entityName}}.schema.js.hbs +17 -0
- package/templates/entity/src/services/mongodb.{{entityName}}.service.js.hbs +70 -0
- package/templates/entity/src/services/mysql.{{entityName}}.service.js.hbs +27 -0
- package/bootstrap/services/notification/Dockerfile.prod +0 -37
- package/bootstrap/services/notification/README.md +0 -3
- package/bootstrap/services/notification/src/commands/command-bus.js +0 -20
- package/bootstrap/services/notification/src/commands/handlers/control.handler.js +0 -18
- package/bootstrap/services/notification/src/commands/handlers/notification.handler.js +0 -157
- package/bootstrap/services/notification/src/services/logger.service.js +0 -16
- package/bootstrap/services/notification/src/services/ses.service.js +0 -23
- package/bootstrap/services/notification/src/templates/admin-order-recieved.hbs +0 -136
- package/bootstrap/services/notification/src/templates/admin-subscription-failed.hbs +0 -87
- package/bootstrap/services/notification/src/templates/customer-order-recieved.hbs +0 -132
- package/bootstrap/services/notification/src/templates/customer-subscription-failed.hbs +0 -77
- package/bootstrap/services/notification/src/tests/notification.test.js +0 -0
package/src/dev/dev.service.js
CHANGED
|
@@ -3,9 +3,12 @@ import Docker from "dockerode";
|
|
|
3
3
|
import process from "process";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
import path from "path";
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
6
7
|
import yaml from "js-yaml";
|
|
7
8
|
import { gnarEngineCliConfig } from "../config.js";
|
|
8
|
-
import {
|
|
9
|
+
import { buildImage, createContainer, createBridgeNetwork } from "../services/docker.js";
|
|
10
|
+
import { directories } from "../config.js";
|
|
11
|
+
|
|
9
12
|
|
|
10
13
|
const docker = new Docker();
|
|
11
14
|
|
|
@@ -17,16 +20,18 @@ const docker = new Docker();
|
|
|
17
20
|
* @param {object} options
|
|
18
21
|
* @param {string} options.projectDir - The project directory
|
|
19
22
|
* @param {boolean} [options.build=false] - Whether to re-build images
|
|
20
|
-
* @param {boolean} [options.
|
|
23
|
+
* @param {boolean} [options.detach=false] - Whether to run containers in background
|
|
21
24
|
* @param {boolean} [options.coreDev=false] - Whether to run in core development mode (requires access to core source)
|
|
25
|
+
* @param {boolean} [options.bootstrapDev=false] - Whether to set the cli/src/bootstrap directory as the project directory
|
|
22
26
|
* @param {boolean} [options.test=false] - Whether to run tests with ephemeral databases
|
|
23
27
|
* @param {string} [options.testService=''] - The service to run tests for (only applicable if test=true)
|
|
24
28
|
* @param {boolean} [options.removeOrphans=true] - Whether to remove orphaned containers
|
|
29
|
+
* @param {boolean} [options.attachAll=false] - Attach all services including database and message queues for debugging
|
|
25
30
|
*/
|
|
26
|
-
export async function up({ projectDir, build = false,
|
|
31
|
+
export async function up({ projectDir, build = false, detach = false, coreDev = false, bootstrapDev = false, test = false, testService = '', removeOrphans = true, attachAll = false}) {
|
|
27
32
|
|
|
28
|
-
//
|
|
29
|
-
if (
|
|
33
|
+
// bootstrap dev
|
|
34
|
+
if (bootstrapDev) {
|
|
30
35
|
const fileDir = path.dirname(new URL(import.meta.url).pathname);
|
|
31
36
|
projectDir = path.resolve(fileDir, "../../bootstrap/");
|
|
32
37
|
}
|
|
@@ -56,50 +61,51 @@ export async function up({ projectDir, build = false, detached = false, coreDev
|
|
|
56
61
|
|
|
57
62
|
// create docker-compose.yml dynamically from parsed config and secrets
|
|
58
63
|
const dockerComposePath = path.join(gnarHiddenDir, "docker-compose.dev.yml");
|
|
59
|
-
const dockerCompose = await
|
|
64
|
+
const dockerCompose = await buildAndUpContainers({
|
|
60
65
|
config: parsedConfig.config,
|
|
61
66
|
secrets: parsedSecrets,
|
|
62
67
|
gnarHiddenDir: gnarHiddenDir,
|
|
63
68
|
projectDir: projectDir,
|
|
64
69
|
coreDev: coreDev,
|
|
70
|
+
bootstrapDev: bootstrapDev,
|
|
65
71
|
test: test,
|
|
66
|
-
testService: testService
|
|
72
|
+
testService: testService,
|
|
73
|
+
attachAll: attachAll
|
|
67
74
|
});
|
|
68
|
-
await fs.writeFile(dockerComposePath, yaml.dump(dockerCompose));
|
|
69
|
-
|
|
70
|
-
// up docker-compose
|
|
71
|
-
const args = ["-f", dockerComposePath, "up"];
|
|
72
|
-
|
|
73
|
-
if (build) {
|
|
74
|
-
args.push("--build");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (detached) {
|
|
78
|
-
args.push("-d");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (removeOrphans) {
|
|
82
|
-
args.push("--remove-orphans")
|
|
83
|
-
}
|
|
84
75
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
76
|
+
// // up docker-compose
|
|
77
|
+
// const args = ["-f", dockerComposePath, "up"];
|
|
78
|
+
//
|
|
79
|
+
// if (build) {
|
|
80
|
+
// args.push("--build");
|
|
81
|
+
// }
|
|
82
|
+
//
|
|
83
|
+
// if (detach) {
|
|
84
|
+
// args.push("-d");
|
|
85
|
+
// }
|
|
86
|
+
//
|
|
87
|
+
// if (removeOrphans) {
|
|
88
|
+
// args.push("--remove-orphans")
|
|
89
|
+
// }
|
|
90
|
+
|
|
91
|
+
// const processRef = spawn(
|
|
92
|
+
// "docker-compose",
|
|
93
|
+
// args,
|
|
94
|
+
// {
|
|
95
|
+
// cwd: projectDir,
|
|
96
|
+
// stdio: "inherit",
|
|
97
|
+
// shell: "/bin/sh"
|
|
98
|
+
// }
|
|
99
|
+
// );
|
|
100
|
+
//
|
|
101
|
+
// // handle exit
|
|
102
|
+
// const exitCode = await new Promise((resolve) => {
|
|
103
|
+
// processRef.on("close", resolve);
|
|
104
|
+
// });
|
|
105
|
+
//
|
|
106
|
+
// if (exitCode !== 0) {
|
|
107
|
+
// throw new Error(`docker-compose up exited with code ${exitCode}`);
|
|
108
|
+
// }
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
/**
|
|
@@ -111,11 +117,11 @@ export async function up({ projectDir, build = false, detached = false, coreDev
|
|
|
111
117
|
*/
|
|
112
118
|
export async function down({ projectDir, allContainers = false }) {
|
|
113
119
|
// list all containers
|
|
114
|
-
|
|
120
|
+
let containers = await docker.listContainers();
|
|
115
121
|
|
|
116
122
|
// filter containers by image name
|
|
117
123
|
if (!allContainers) {
|
|
118
|
-
|
|
124
|
+
containers = containers.filter(c => c.Image.includes("ge-localdev"));
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
if (containers.length === 0) {
|
|
@@ -137,6 +143,18 @@ export async function down({ projectDir, allContainers = false }) {
|
|
|
137
143
|
});
|
|
138
144
|
})
|
|
139
145
|
);
|
|
146
|
+
|
|
147
|
+
// remove each container
|
|
148
|
+
await Promise.all(
|
|
149
|
+
containers.map(c => {
|
|
150
|
+
const container = docker.getContainer(c.Id);
|
|
151
|
+
return container.remove({ force: true }).catch(err => {
|
|
152
|
+
console.error(`Failed to remove ${c.Names[0]}: ${err.message}`);
|
|
153
|
+
});
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
console.log('Containers stopped and removed.');
|
|
140
158
|
}
|
|
141
159
|
|
|
142
160
|
/**
|
|
@@ -214,17 +232,23 @@ export async function createDynamicNginxConf({ config, serviceConfDir, projectDi
|
|
|
214
232
|
* @param {string} gnarHiddenDir
|
|
215
233
|
* @param {string} projectDir
|
|
216
234
|
* @param {boolean} coreDev - Whether to volume mount the core source code
|
|
235
|
+
* @param {boolean} bootstrapDev - Whether to set the cli/src/bootstrap directory as the project directory
|
|
236
|
+
* @param {boolean} build - Whether to re-build images
|
|
217
237
|
* @param {boolean} test - Whether to run tests with ephemeral databases
|
|
218
238
|
* @param {string} testService - The service to run tests for (only applicable if test=true)
|
|
219
239
|
* @param {boolean} attachAll - Whether to attach to all containers' stdio (otherwise databases and message queue are detached)
|
|
220
240
|
*/
|
|
221
|
-
async function
|
|
241
|
+
async function buildAndUpContainers({ config, secrets, gnarHiddenDir, projectDir, coreDev = false, bootstrapDev = false, build = false, test = false, testService, attachAll = false }) {
|
|
242
|
+
|
|
222
243
|
let mysqlPortsCounter = 3306;
|
|
223
244
|
let mongoPortsCounter = 27017;
|
|
224
245
|
let mysqlHostsRequired = [];
|
|
225
246
|
let mongoHostsRequired = [];
|
|
226
247
|
const services = {};
|
|
227
248
|
|
|
249
|
+
console.log('======== g n a r e n g i n e ========');
|
|
250
|
+
console.log('⛏️ Starting development environment...');
|
|
251
|
+
|
|
228
252
|
// test mode env var adjustments
|
|
229
253
|
for (const svc of config.services) {
|
|
230
254
|
if (test) {
|
|
@@ -242,63 +266,92 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
242
266
|
}
|
|
243
267
|
}
|
|
244
268
|
|
|
269
|
+
// create bridge network
|
|
270
|
+
const networkName = `ge-${config.environment}-${config.namespace}`;
|
|
271
|
+
createBridgeNetwork({
|
|
272
|
+
name: networkName
|
|
273
|
+
})
|
|
274
|
+
|
|
245
275
|
// provision the provisioner service
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
276
|
+
const provisionerTag = `ge-${config.environment}-${config.namespace}-provisioner`;
|
|
277
|
+
|
|
278
|
+
if (build) {
|
|
279
|
+
await buildImage({
|
|
250
280
|
context: directories.provisioner,
|
|
251
|
-
dockerfile:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
PROVISIONER_SECRETS: JSON.stringify(secrets)
|
|
255
|
-
},
|
|
256
|
-
volumes: [
|
|
257
|
-
`${directories.provisioner}/src:/usr/gnar_engine/app/src`
|
|
258
|
-
],
|
|
259
|
-
restart: 'no',
|
|
260
|
-
attach: attachAll
|
|
281
|
+
dockerfile: 'Dockerfile',
|
|
282
|
+
imageTag: provisionerTag
|
|
283
|
+
});
|
|
261
284
|
}
|
|
262
285
|
|
|
286
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
287
|
+
const __dirname = path.dirname(__filename);
|
|
288
|
+
|
|
289
|
+
const provisionerBinds = [
|
|
290
|
+
`${path.resolve(__dirname, '../provisioner', 'src')}:/usr/gnar_engine/app/src`
|
|
291
|
+
];
|
|
292
|
+
|
|
263
293
|
if (coreDev) {
|
|
264
|
-
|
|
294
|
+
provisionerBinds.push(`${gnarEngineCliConfig.coreDevPath}:${gnarEngineCliConfig.corePath}`);
|
|
265
295
|
}
|
|
266
296
|
|
|
267
|
-
|
|
268
|
-
|
|
297
|
+
const provisioner = await createContainer({
|
|
298
|
+
name: provisionerTag,
|
|
299
|
+
image: provisionerTag,
|
|
300
|
+
env: {
|
|
301
|
+
PROVISIONER_SECRETS: JSON.stringify(secrets)
|
|
302
|
+
},
|
|
303
|
+
ports: {},
|
|
304
|
+
binds: provisionerBinds,
|
|
305
|
+
restart: 'no',
|
|
306
|
+
attach: attachAll,
|
|
307
|
+
network: networkName
|
|
308
|
+
});
|
|
309
|
+
services[provisionerTag] = provisioner;
|
|
310
|
+
|
|
311
|
+
// Nginx
|
|
312
|
+
const nginxName = `ge-${config.environment}-${config.namespace}-nginx`;
|
|
313
|
+
services[nginxName] = await createContainer({
|
|
314
|
+
name: nginxName,
|
|
269
315
|
image: 'nginx:latest',
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
"80:80",
|
|
273
|
-
"443:443"
|
|
274
|
-
],
|
|
275
|
-
volumes: [
|
|
316
|
+
ports: { 80: 80, 443: 443 },
|
|
317
|
+
binds: [
|
|
276
318
|
`${gnarHiddenDir}/nginx/nginx.conf:/etc/nginx/nginx.conf`,
|
|
277
319
|
`${gnarHiddenDir}/nginx/service_conf:/etc/nginx/service_conf`
|
|
278
320
|
],
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
321
|
+
attach: attachAll,
|
|
322
|
+
network: networkName
|
|
323
|
+
});
|
|
282
324
|
|
|
283
|
-
//
|
|
284
|
-
|
|
325
|
+
// Rabbit MQ
|
|
326
|
+
const rabbitMqName = `ge-${config.environment}-${config.namespace}-rabbitmq`;
|
|
327
|
+
services[rabbitMqName] = await createContainer({
|
|
328
|
+
name: rabbitMqName,
|
|
285
329
|
image: 'rabbitmq:management',
|
|
286
|
-
|
|
287
|
-
ports: [
|
|
288
|
-
"5672:5672",
|
|
289
|
-
"15672:15672"
|
|
290
|
-
],
|
|
291
|
-
environment: {
|
|
330
|
+
env: {
|
|
292
331
|
RABBITMQ_DEFAULT_USER: secrets.global.RABBITMQ_USER || '',
|
|
293
332
|
RABBITMQ_DEFAULT_PASS: secrets.global.RABBITMQ_PASS || ''
|
|
294
333
|
},
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
334
|
+
ports: { 5672: 5672, 15672: 15672 },
|
|
335
|
+
binds: [],
|
|
336
|
+
attach: attachAll,
|
|
337
|
+
network: networkName,
|
|
338
|
+
aliases: ['rabbitmq']
|
|
339
|
+
});
|
|
298
340
|
|
|
299
341
|
// services
|
|
300
342
|
for (const svc of config.services) {
|
|
301
343
|
|
|
344
|
+
// build service image
|
|
345
|
+
const svcTag = `ge-${config.environment}-${config.namespace}-${svc.name}`;
|
|
346
|
+
|
|
347
|
+
if (build) {
|
|
348
|
+
await buildImage({
|
|
349
|
+
context: path.resolve(projectDir, 'services', svc.name),
|
|
350
|
+
dockerfile: 'Dockerfile',
|
|
351
|
+
imageTag: svcTag
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
302
355
|
// env variables
|
|
303
356
|
const serviceEnvVars = secrets.services?.[svc.name] || {};
|
|
304
357
|
const localisedServiceEnvVars = {};
|
|
@@ -320,29 +373,35 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
320
373
|
}
|
|
321
374
|
}
|
|
322
375
|
|
|
323
|
-
// service block
|
|
324
|
-
services[`${svc.name}-service`] = {
|
|
325
|
-
container_name: `ge-${config.environment}-${config.namespace}-${svc.name}`,
|
|
326
|
-
image: `ge-${config.environment}-${config.namespace}-${svc.name}`,
|
|
327
|
-
build: {
|
|
328
|
-
context: projectDir,
|
|
329
|
-
dockerfile: `./services/${svc.name}/Dockerfile`
|
|
330
|
-
},
|
|
331
|
-
command: svc.command || [],
|
|
332
|
-
environment: env,
|
|
333
|
-
ports: svc.ports || [],
|
|
334
|
-
depends_on: svc.depends_on || [],
|
|
335
|
-
volumes: [
|
|
336
|
-
`${projectDir}/services/${svc.name}/src:/usr/gnar_engine/app/src`
|
|
337
|
-
],
|
|
338
|
-
restart: 'always'
|
|
339
|
-
};
|
|
340
|
-
|
|
341
376
|
// add the core source code mount if in coreDev mode
|
|
377
|
+
const serviceVolumes = [
|
|
378
|
+
`${path.resolve(projectDir, 'services', svc.name, 'src')}:/usr/gnar_engine/app/src`
|
|
379
|
+
];
|
|
380
|
+
|
|
342
381
|
if (coreDev) {
|
|
343
|
-
|
|
382
|
+
serviceVolumes.push(`${gnarEngineCliConfig.coreDevPath}:${gnarEngineCliConfig.corePath}`);
|
|
344
383
|
}
|
|
345
384
|
|
|
385
|
+
// split from "port:port" to { port: port }
|
|
386
|
+
const ports = {};
|
|
387
|
+
for (const portMapping of svc.ports || []) {
|
|
388
|
+
const [hostPort, containerPort] = portMapping.split(':').map(p => parseInt(p, 10));
|
|
389
|
+
ports[containerPort] = hostPort;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
services[svcTag] = await createContainer({
|
|
393
|
+
name: svcTag,
|
|
394
|
+
image: svcTag,
|
|
395
|
+
command: svc.command || [],
|
|
396
|
+
env: env,
|
|
397
|
+
ports: ports,
|
|
398
|
+
binds: serviceVolumes,
|
|
399
|
+
restart: 'always',
|
|
400
|
+
attach: true,
|
|
401
|
+
network: networkName,
|
|
402
|
+
aliases: [`${svc.name}-service`]
|
|
403
|
+
});
|
|
404
|
+
|
|
346
405
|
// check if mysql service required
|
|
347
406
|
if (
|
|
348
407
|
serviceEnvVars.MYSQL_HOST &&
|
|
@@ -367,29 +426,30 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
367
426
|
continue;
|
|
368
427
|
}
|
|
369
428
|
|
|
370
|
-
|
|
371
|
-
|
|
429
|
+
const mysqlContainerName = `ge-${config.environment}-${config.namespace}-${host}`;
|
|
430
|
+
services[mysqlContainerName] = await createContainer({
|
|
431
|
+
name: `ge-${config.environment}-${config.namespace}-${host}`,
|
|
372
432
|
image: 'mysql',
|
|
373
|
-
|
|
374
|
-
`${mysqlPortsCounter}:${mysqlPortsCounter}`
|
|
375
|
-
],
|
|
376
|
-
restart: 'always',
|
|
377
|
-
environment: {
|
|
433
|
+
env: {
|
|
378
434
|
MYSQL_HOST: host,
|
|
379
435
|
MYSQL_ROOT_PASSWORD: secrets.provision.MYSQL_ROOT_PASSWORD
|
|
380
436
|
},
|
|
381
|
-
|
|
437
|
+
ports: {
|
|
438
|
+
[mysqlPortsCounter]: mysqlPortsCounter
|
|
439
|
+
},
|
|
440
|
+
binds: [
|
|
382
441
|
`${gnarHiddenDir}/data/${host}-data:/var/lib/mysql`
|
|
383
442
|
],
|
|
384
|
-
|
|
385
|
-
|
|
443
|
+
restart: 'always',
|
|
444
|
+
attach: attachAll,
|
|
445
|
+
network: networkName,
|
|
446
|
+
aliases: [host]
|
|
447
|
+
});
|
|
386
448
|
|
|
387
449
|
mysqlPortsCounter++;
|
|
388
450
|
}
|
|
389
|
-
|
|
390
|
-
services['provisioner'].depends_on = [...new Set(mysqlHostsRequired)];
|
|
391
451
|
}
|
|
392
|
-
|
|
452
|
+
|
|
393
453
|
// add mongo hosts if required
|
|
394
454
|
if (mongoHostsRequired.length > 0) {
|
|
395
455
|
for (const host of mongoHostsRequired) {
|
|
@@ -397,33 +457,37 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
|
|
|
397
457
|
continue;
|
|
398
458
|
}
|
|
399
459
|
|
|
400
|
-
|
|
401
|
-
|
|
460
|
+
const mongoContainerName = `ge-${config.environment}-${config.namespace}-${host}`;
|
|
461
|
+
services[mongoContainerName] = await createContainer({
|
|
462
|
+
name: `ge-${config.environment}-${config.namespace}-${host}`,
|
|
402
463
|
image: 'mongo:latest',
|
|
403
|
-
|
|
404
|
-
`${mongoPortsCounter}:27017`
|
|
405
|
-
],
|
|
406
|
-
restart: 'always',
|
|
407
|
-
environment: {
|
|
464
|
+
env: {
|
|
408
465
|
MONGO_INITDB_ROOT_USERNAME: 'root',
|
|
409
466
|
MONGO_INITDB_ROOT_PASSWORD: secrets.provision.MONGO_ROOT_PASSWORD
|
|
410
467
|
},
|
|
411
|
-
|
|
468
|
+
ports: {
|
|
469
|
+
[mongoPortsCounter]: 27017
|
|
470
|
+
},
|
|
471
|
+
binds: [
|
|
412
472
|
`${gnarHiddenDir}/data/${host}-data:/data/db`,
|
|
413
|
-
'./mongo-init-scripts:/docker-entrypoint-initdb.d'
|
|
473
|
+
//'./mongo-init-scripts:/docker-entrypoint-initdb.d'
|
|
414
474
|
],
|
|
415
|
-
|
|
416
|
-
|
|
475
|
+
restart: 'always',
|
|
476
|
+
attach: attachAll,
|
|
477
|
+
network: networkName,
|
|
478
|
+
aliases: [host]
|
|
479
|
+
});
|
|
417
480
|
|
|
418
481
|
// increment mongo port for next service as required
|
|
419
482
|
mongoPortsCounter++;
|
|
420
483
|
}
|
|
421
484
|
}
|
|
422
485
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
services
|
|
426
|
-
|
|
486
|
+
// start the containers
|
|
487
|
+
Object.keys(services).forEach(async (key) => {
|
|
488
|
+
const container = services[key];
|
|
489
|
+
container.start();
|
|
490
|
+
});
|
|
427
491
|
}
|
|
428
492
|
|
|
429
493
|
/**
|
package/src/helpers/helpers.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
1
4
|
|
|
2
5
|
/**
|
|
3
6
|
* CLI helper functions
|
|
@@ -59,5 +62,26 @@ export const helpers = {
|
|
|
59
62
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
60
63
|
}
|
|
61
64
|
return result;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
getDbTypeFromSecrets: async (serviceName, projectDir) => {
|
|
68
|
+
let dbType;
|
|
69
|
+
const secretsPath = path.join(projectDir, "secrets.localdev.yml");
|
|
70
|
+
const parsedSecrets = yaml.load(await fs.readFile(secretsPath, "utf8"));
|
|
71
|
+
const serviceSecrets = parsedSecrets.services[serviceName.toLowerCase()];
|
|
72
|
+
|
|
73
|
+
Object.keys(serviceSecrets).forEach(key => {
|
|
74
|
+
if (key.toLowerCase().includes('host')) {
|
|
75
|
+
const host = serviceSecrets[key].toLowerCase();
|
|
76
|
+
|
|
77
|
+
if (host.includes('mongo')) {
|
|
78
|
+
dbType = 'mongodb';
|
|
79
|
+
} else if (host.includes('mysql')) {
|
|
80
|
+
dbType = 'mysql';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return dbType;
|
|
62
86
|
}
|
|
63
87
|
}
|
package/src/profiles/command.js
CHANGED
|
@@ -166,5 +166,46 @@ export function registerProfileCommand(program) {
|
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
+
// delete profile
|
|
170
|
+
profile
|
|
171
|
+
.command('delete <profileName>')
|
|
172
|
+
.description('Delete an existing profile')
|
|
173
|
+
.action(async (profileName) => {
|
|
174
|
+
const config = profiles.getAllProfiles();
|
|
175
|
+
const activeProfileName = config.activeProfile;
|
|
176
|
+
|
|
177
|
+
if (activeProfileName === profileName) {
|
|
178
|
+
console.error(`Cannot delete active profile "${profileName}". Please set another profile as active first.`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!config.profiles[profileName]) {
|
|
183
|
+
console.error(`Profile "${profileName}" not found.`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// confirm deletion with user
|
|
189
|
+
const confirmation = await inquirer.prompt([
|
|
190
|
+
{
|
|
191
|
+
type: 'confirm',
|
|
192
|
+
name: 'confirmDelete',
|
|
193
|
+
message: `Are you sure you want to delete profile "${profileName}"?`,
|
|
194
|
+
default: false,
|
|
195
|
+
},
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
if (!confirmation.confirmDelete) {
|
|
199
|
+
console.log('❌ Deletion cancelled.');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
profiles.deleteProfile({ profileName });
|
|
204
|
+
console.log(`✅ Profile "${profileName}" deleted successfully.`);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(error.message);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
169
210
|
program.addCommand(profile);
|
|
170
211
|
}
|
|
@@ -90,6 +90,29 @@ export const profiles = {
|
|
|
90
90
|
this.saveProfiles(allProfiles);
|
|
91
91
|
},
|
|
92
92
|
|
|
93
|
+
deleteProfile: function ({ profileName }) {
|
|
94
|
+
if (!profileName) {
|
|
95
|
+
throw new Error('Invalid profile name');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const allProfiles = this.getAllProfiles().profiles || {};
|
|
99
|
+
|
|
100
|
+
if (!allProfiles[profileName]) {
|
|
101
|
+
throw new Error(`Profile "${profileName}" not found`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const activeProfileName = this.getActiveProfile()?.name;
|
|
105
|
+
|
|
106
|
+
if (activeProfileName === profileName) {
|
|
107
|
+
throw new Error(`Cannot delete active profile "${profileName}". Please set another profile as active first.`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Prompt user to confirm deletion in the console
|
|
111
|
+
delete allProfiles[profileName];
|
|
112
|
+
|
|
113
|
+
this.saveProfiles(allProfiles);
|
|
114
|
+
},
|
|
115
|
+
|
|
93
116
|
saveProfiles: function (profilesObj) {
|
|
94
117
|
const dir = path.dirname(this.configPath);
|
|
95
118
|
if (!fs.existsSync(dir)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import { profiles } from '../profiles/profiles.client.js';
|
|
3
3
|
import { scaffolder } from './scaffolder.handler.js';
|
|
4
|
+
import { helpers } from '../helpers/helpers.js';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
|
|
6
7
|
export const registerScaffolderCommands = (program) => {
|
|
@@ -33,11 +34,16 @@ export const registerScaffolderCommands = (program) => {
|
|
|
33
34
|
}
|
|
34
35
|
]);
|
|
35
36
|
|
|
37
|
+
// validate absolute path, if it is not absolute, make it absolute
|
|
38
|
+
if (!path.isAbsolute(answers.projectDir)) {
|
|
39
|
+
answers.projectDir = path.join(process.cwd(), answers.projectDir);
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
// create the project
|
|
37
43
|
try {
|
|
38
44
|
scaffolder.createNewProject({
|
|
39
45
|
projectName: projectName,
|
|
40
|
-
projectDir: answers.projectDir,
|
|
46
|
+
projectDir: path.join('/', answers.projectDir),
|
|
41
47
|
rootAdminEmail: answers.rootAdminEmail
|
|
42
48
|
});
|
|
43
49
|
} catch (error) {
|
|
@@ -127,4 +133,54 @@ export const registerScaffolderCommands = (program) => {
|
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
});
|
|
136
|
+
|
|
137
|
+
create
|
|
138
|
+
.command('entity <entity>')
|
|
139
|
+
.description('📦 Create a new entity in an existing service')
|
|
140
|
+
.option('--in-service <serviceName>', 'The service in which to add the entity')
|
|
141
|
+
.action(async (entity, options) => {
|
|
142
|
+
// validate
|
|
143
|
+
if (!entity) {
|
|
144
|
+
console.error('❌ Please specify an entity name using gnar create entity <entityName> --in-service <serviceName>');
|
|
145
|
+
}
|
|
146
|
+
if (!options.inService) {
|
|
147
|
+
console.error('❌ Please specify the service using --in-service <serviceName>');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let activeProfile;
|
|
151
|
+
try {
|
|
152
|
+
activeProfile = profiles.getActiveProfile();
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('❌ No active profile found. Please create or set one using `gnar profile create` or `gnar profile set-active <profileName>`');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// create the entity
|
|
159
|
+
try {
|
|
160
|
+
// add trailing slash to project dir if missing
|
|
161
|
+
let projectDir = activeProfile.profile.PROJECT_DIR;
|
|
162
|
+
|
|
163
|
+
if (!activeProfile.profile.PROJECT_DIR.endsWith(path.sep)) {
|
|
164
|
+
projectDir += path.sep;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const dbType = await helpers.getDbTypeFromSecrets(options.inService, projectDir);
|
|
168
|
+
const serviceDir = path.join(projectDir, 'services', options.inService.toLowerCase());
|
|
169
|
+
|
|
170
|
+
console.log('Creating new entity in... ' + serviceDir);
|
|
171
|
+
|
|
172
|
+
scaffolder.createNewEntity({
|
|
173
|
+
entityName: entity,
|
|
174
|
+
inService: options.inService,
|
|
175
|
+
serviceDir: serviceDir,
|
|
176
|
+
database: dbType
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
console.log('Created entity ' + entity + ' in service ' + options.inService);
|
|
180
|
+
console.log('👉 Remember to add the new entities handler and controllers to your service\'s app.js');
|
|
181
|
+
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('❌ Error creating entity:', error.message);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
130
186
|
}
|