@gnar-engine/cli 1.0.7 → 1.0.10
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/services/control/Dockerfile +1 -4
- package/bootstrap/services/control/src/config.js +2 -1
- package/bootstrap/services/notification/Dockerfile +1 -4
- package/bootstrap/services/page/Dockerfile +1 -4
- package/bootstrap/services/page/src/config.js +5 -0
- package/bootstrap/services/portal/Dockerfile +1 -4
- package/bootstrap/services/user/Dockerfile +1 -4
- package/bootstrap/services/user/src/config.js +1 -0
- package/package.json +2 -1
- package/src/config.js +4 -4
- package/src/dev/commands.js +5 -1
- package/src/dev/dev.service.js +101 -46
- package/src/services/docker.js +27 -12
- package/templates/entity/src/services/mysql.{{entityName}}.service.js.hbs +79 -9
- package/templates/service/Dockerfile.hbs +1 -4
- package/templates/service/src/app.js.hbs +3 -7
- package/templates/service/src/mongodb.config.js.hbs +5 -0
- package/templates/service/src/mysql.config.js.hbs +2 -1
- package/templates/service/src/services/mysql.{{serviceName}}.service.js.hbs +78 -13
|
@@ -8,7 +8,7 @@ WORKDIR /usr/gnar_engine/app
|
|
|
8
8
|
ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
|
|
9
9
|
|
|
10
10
|
# Copy package.json and package-lock.json
|
|
11
|
-
COPY ./
|
|
11
|
+
COPY ./package*.json ./
|
|
12
12
|
|
|
13
13
|
# Install nodemon
|
|
14
14
|
RUN npm install -g nodemon
|
|
@@ -18,6 +18,3 @@ RUN npm install
|
|
|
18
18
|
|
|
19
19
|
# Expose the port the service will run on
|
|
20
20
|
EXPOSE 3000
|
|
21
|
-
|
|
22
|
-
# Start the application
|
|
23
|
-
CMD ["nodemon", "--watch", "./gnar_engine", "./gnar_engine/app.js"]
|
|
@@ -7,8 +7,9 @@ export const config = {
|
|
|
7
7
|
serviceName: 'controlService',
|
|
8
8
|
|
|
9
9
|
// environment
|
|
10
|
-
environment: process.env.CONTROL_NODE_ENV || '
|
|
10
|
+
environment: process.env.CONTROL_NODE_ENV || 'development',
|
|
11
11
|
runTests: process.env.CONTROL_RUN_TESTS || false,
|
|
12
|
+
resetDatabase: process.env.CONTROL_RESET_DATABASE || false,
|
|
12
13
|
|
|
13
14
|
// microservice | modular-monolith
|
|
14
15
|
architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
|
|
@@ -8,7 +8,7 @@ WORKDIR /usr/gnar_engine/app
|
|
|
8
8
|
ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
|
|
9
9
|
|
|
10
10
|
# Copy package.json and package-lock.json
|
|
11
|
-
COPY ./
|
|
11
|
+
COPY ./package*.json ./
|
|
12
12
|
|
|
13
13
|
# Install nodemon
|
|
14
14
|
RUN npm install -g nodemon
|
|
@@ -18,6 +18,3 @@ RUN npm install
|
|
|
18
18
|
|
|
19
19
|
# Expose the port the service will run on
|
|
20
20
|
EXPOSE 3000
|
|
21
|
-
|
|
22
|
-
# Start the application
|
|
23
|
-
CMD ["nodemon", "--watch", "./gnar_engine", "./gnar_engine/app.js"]
|
|
@@ -8,7 +8,7 @@ WORKDIR /usr/gnar_engine/app
|
|
|
8
8
|
ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
|
|
9
9
|
|
|
10
10
|
# Copy package.json and package-lock.json
|
|
11
|
-
COPY ./
|
|
11
|
+
COPY ./package*.json ./
|
|
12
12
|
|
|
13
13
|
# Install nodemon
|
|
14
14
|
RUN npm install -g nodemon
|
|
@@ -18,6 +18,3 @@ RUN npm install
|
|
|
18
18
|
|
|
19
19
|
# Expose the port the service will run on
|
|
20
20
|
EXPOSE 3000
|
|
21
|
-
|
|
22
|
-
# Start the application
|
|
23
|
-
CMD ["nodemon", "--watch", "./gnar_engine", "./gnar_engine/app.js"]
|
|
@@ -5,6 +5,11 @@ export const config = {
|
|
|
5
5
|
// service name
|
|
6
6
|
serviceName: 'pageService',
|
|
7
7
|
|
|
8
|
+
// environment
|
|
9
|
+
environment: process.env.PAGE_NODE_ENV || 'developnent',
|
|
10
|
+
runTests: process.env.PAGE_RUN_TESTS || false,
|
|
11
|
+
resetDatabase: process.env.PAGE_RESET_DATABASE || false,
|
|
12
|
+
|
|
8
13
|
// microservice | modular-monolith
|
|
9
14
|
architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
|
|
10
15
|
|
|
@@ -8,13 +8,10 @@ WORKDIR /usr/gnar_engine/app
|
|
|
8
8
|
ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
|
|
9
9
|
|
|
10
10
|
# Copy package.json and package-lock.json
|
|
11
|
-
COPY ./
|
|
11
|
+
COPY ./ ./
|
|
12
12
|
|
|
13
13
|
# Install app dependencies
|
|
14
14
|
RUN npm install
|
|
15
15
|
|
|
16
16
|
# Expose the port the service will run on
|
|
17
17
|
EXPOSE 5173
|
|
18
|
-
|
|
19
|
-
# Start the application
|
|
20
|
-
CMD ["npm", "run", "start:dev"]
|
|
@@ -8,7 +8,7 @@ WORKDIR /usr/gnar_engine/app
|
|
|
8
8
|
ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
|
|
9
9
|
|
|
10
10
|
# Copy package.json and package-lock.json
|
|
11
|
-
COPY ./
|
|
11
|
+
COPY ./package*.json ./
|
|
12
12
|
|
|
13
13
|
# Install nodemon
|
|
14
14
|
RUN npm install -g nodemon
|
|
@@ -18,6 +18,3 @@ RUN npm install
|
|
|
18
18
|
|
|
19
19
|
# Expose the port the service will run on
|
|
20
20
|
EXPOSE 3000
|
|
21
|
-
|
|
22
|
-
# Start the application
|
|
23
|
-
CMD ["nodemon", "--watch", "./gnar_engine", "./gnar_engine/app.js"]
|
|
@@ -9,6 +9,7 @@ export const config = {
|
|
|
9
9
|
// environment
|
|
10
10
|
environment: process.env.USER_NODE_ENV || 'dev',
|
|
11
11
|
runTests: process.env.USER_RUN_TESTS || false,
|
|
12
|
+
resetDatabase: process.env.USER_RESET_DATABASE || false,
|
|
12
13
|
|
|
13
14
|
// microservice | modular-monolith
|
|
14
15
|
architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gnar-engine/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Gnar Engine Development Framework CLI: Project bootstrap, scaffolder & control plane.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"handlebars": "^4.7.8",
|
|
31
31
|
"inquirer": "^12.5.2",
|
|
32
32
|
"js-yaml": "^4.1.0",
|
|
33
|
+
"tar-fs": "^3.1.1",
|
|
33
34
|
"uuid": "^11.1.0"
|
|
34
35
|
}
|
|
35
36
|
}
|
package/src/config.js
CHANGED
|
@@ -3,14 +3,14 @@ import { fileURLToPath } from 'url';
|
|
|
3
3
|
|
|
4
4
|
const __filename = fileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = path.dirname(__filename);
|
|
6
|
+
|
|
6
7
|
export const gnarEngineCliConfig = {
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
* The path the Gnar Engine service core should be found in the service containers
|
|
10
|
-
*/
|
|
9
|
+
// The path the Gnar Engine service core should be found in the service containers
|
|
11
10
|
corePath: '/usr/gnar_engine/app/node_modules/@gnar-engine/core',
|
|
12
|
-
coreDevPath: path.join(__dirname, '../../core'),
|
|
13
11
|
|
|
12
|
+
// The path to the core source code on the host machine (core dev mode)
|
|
13
|
+
coreDevPath: path.join(__dirname, '../../core'),
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export const directories = {
|
package/src/dev/commands.js
CHANGED
|
@@ -13,8 +13,10 @@ export function registerDevCommands(program) {
|
|
|
13
13
|
.option('-b, --build', 'Build without cache')
|
|
14
14
|
.option('-d, --detach', 'Run containers in background')
|
|
15
15
|
.option('-a, --attach-all', 'Attach all services including database and message queues for debugging')
|
|
16
|
-
.option('-t --test', 'Run
|
|
16
|
+
.option('-t --test', 'Run all tests with ephemeral databases *NOT IMPLEMENTED')
|
|
17
17
|
.option('--test-service <service>', 'Run the tests for the specified service with ephemeral databases (e.g. --test-service user)')
|
|
18
|
+
.option('--reset-databases, --reset-databases', 'Drop all service databases, re-running all migrations and seeders *NOT IMPLEMENTED')
|
|
19
|
+
.option('--reset-database <service>', 'Drop the specified service database, re-running all migrations and seeders (e.g. --reset-database user)')
|
|
18
20
|
.addOption(new Option('--core-dev').hideHelp())
|
|
19
21
|
.addOption(new Option('--bootstrap-dev').hideHelp())
|
|
20
22
|
.action(async (options) => {
|
|
@@ -44,6 +46,8 @@ export function registerDevCommands(program) {
|
|
|
44
46
|
bootstrapDev: options.bootstrapDev || false,
|
|
45
47
|
test: options.test || false,
|
|
46
48
|
testService: options.testService || '',
|
|
49
|
+
resetDatabases: options.resetDatabases || false,
|
|
50
|
+
resetDatabase: options.resetDatabase || '',
|
|
47
51
|
attachAll: options.attachAll || false
|
|
48
52
|
});
|
|
49
53
|
} catch (err) {
|
package/src/dev/dev.service.js
CHANGED
|
@@ -8,9 +8,11 @@ import yaml from "js-yaml";
|
|
|
8
8
|
import { gnarEngineCliConfig } from "../config.js";
|
|
9
9
|
import { buildImage, createContainer, createBridgeNetwork } from "../services/docker.js";
|
|
10
10
|
import { directories } from "../config.js";
|
|
11
|
-
|
|
11
|
+
import { exec } from 'child_process';
|
|
12
|
+
import { promisify } from 'util';
|
|
12
13
|
|
|
13
14
|
const docker = new Docker();
|
|
15
|
+
const execAsync = promisify(exec);
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Start the application locally
|
|
@@ -25,11 +27,25 @@ const docker = new Docker();
|
|
|
25
27
|
* @param {boolean} [options.bootstrapDev=false] - Whether to set the cli/src/bootstrap directory as the project directory
|
|
26
28
|
* @param {boolean} [options.test=false] - Whether to run tests with ephemeral databases
|
|
27
29
|
* @param {string} [options.testService=''] - The service to run tests for (only applicable if test=true)
|
|
30
|
+
* @param {boolean} [options.resetDatabases=false] - Whether to drop all service databases, re-running all migrations and seeders
|
|
31
|
+
* @param {string} [options.resetDatabase=''] - The service database to drop, re-running all migrations and seeders
|
|
28
32
|
* @param {boolean} [options.removeOrphans=true] - Whether to remove orphaned containers
|
|
29
33
|
* @param {boolean} [options.attachAll=false] - Attach all services including database and message queues for debugging
|
|
30
34
|
*/
|
|
31
|
-
export async function up({
|
|
32
|
-
|
|
35
|
+
export async function up({
|
|
36
|
+
projectDir,
|
|
37
|
+
build = false,
|
|
38
|
+
detach = false,
|
|
39
|
+
coreDev = false,
|
|
40
|
+
bootstrapDev = false,
|
|
41
|
+
test = false,
|
|
42
|
+
testService = '',
|
|
43
|
+
resetDatabases = false,
|
|
44
|
+
resetDatabase = '',
|
|
45
|
+
removeOrphans = true,
|
|
46
|
+
attachAll = false
|
|
47
|
+
}) {
|
|
48
|
+
|
|
33
49
|
// bootstrap dev
|
|
34
50
|
if (bootstrapDev) {
|
|
35
51
|
const fileDir = path.dirname(new URL(import.meta.url).pathname);
|
|
@@ -59,7 +75,7 @@ export async function up({ projectDir, build = false, detach = false, coreDev =
|
|
|
59
75
|
});
|
|
60
76
|
await fs.writeFile(nginxConfPath, nginxConf);
|
|
61
77
|
|
|
62
|
-
// create
|
|
78
|
+
// create and up containers
|
|
63
79
|
const dockerComposePath = path.join(gnarHiddenDir, "docker-compose.dev.yml");
|
|
64
80
|
const dockerCompose = await buildAndUpContainers({
|
|
65
81
|
config: parsedConfig.config,
|
|
@@ -70,42 +86,11 @@ export async function up({ projectDir, build = false, detach = false, coreDev =
|
|
|
70
86
|
bootstrapDev: bootstrapDev,
|
|
71
87
|
test: test,
|
|
72
88
|
testService: testService,
|
|
73
|
-
|
|
89
|
+
resetDatabases: resetDatabases,
|
|
90
|
+
resetDatabase: resetDatabase,
|
|
91
|
+
attachAll: attachAll,
|
|
92
|
+
build: build,
|
|
74
93
|
});
|
|
75
|
-
|
|
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
|
-
// }
|
|
109
94
|
}
|
|
110
95
|
|
|
111
96
|
/**
|
|
@@ -144,6 +129,15 @@ export async function down({ projectDir, allContainers = false }) {
|
|
|
144
129
|
})
|
|
145
130
|
);
|
|
146
131
|
|
|
132
|
+
// // remove bound mounts
|
|
133
|
+
// await Promise.all(
|
|
134
|
+
// containers.map(async c => {
|
|
135
|
+
// const container = docker.getContainer(c.Id);
|
|
136
|
+
// const containerInfo = await container.inspect();
|
|
137
|
+
// await removeBindMounts({ containerInfo: containerInfo });
|
|
138
|
+
// })
|
|
139
|
+
// );
|
|
140
|
+
|
|
147
141
|
// remove each container
|
|
148
142
|
await Promise.all(
|
|
149
143
|
containers.map(c => {
|
|
@@ -157,6 +151,26 @@ export async function down({ projectDir, allContainers = false }) {
|
|
|
157
151
|
console.log('Containers stopped and removed.');
|
|
158
152
|
}
|
|
159
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Remove bind mounts
|
|
156
|
+
*
|
|
157
|
+
* @param {object} containerInfo
|
|
158
|
+
*/
|
|
159
|
+
export async function removeBindMounts({containerInfo}) {
|
|
160
|
+
|
|
161
|
+
const binds = containerInfo.HostConfig.Binds || [];
|
|
162
|
+
|
|
163
|
+
for (const bind of binds) {
|
|
164
|
+
const hostPath = bind.split(':')[0];
|
|
165
|
+
try {
|
|
166
|
+
await execAsync(`sudo umount ${hostPath}`);
|
|
167
|
+
console.log(`Unmounted ${hostPath}`);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.warn(`Failed to unmount ${hostPath}: ${err.message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
160
174
|
/**
|
|
161
175
|
* Create dynamic nginx.conf file for running application locally
|
|
162
176
|
*
|
|
@@ -236,9 +250,24 @@ export async function createDynamicNginxConf({ config, serviceConfDir, projectDi
|
|
|
236
250
|
* @param {boolean} build - Whether to re-build images
|
|
237
251
|
* @param {boolean} test - Whether to run tests with ephemeral databases
|
|
238
252
|
* @param {string} testService - The service to run tests for (only applicable if test=true)
|
|
253
|
+
* @param {boolean} resetDatabases - Whether to drop all service databases, re-running all migrations and seeders
|
|
254
|
+
* @
|
|
239
255
|
* @param {boolean} attachAll - Whether to attach to all containers' stdio (otherwise databases and message queue are detached)
|
|
240
256
|
*/
|
|
241
|
-
async function buildAndUpContainers({
|
|
257
|
+
async function buildAndUpContainers({
|
|
258
|
+
config,
|
|
259
|
+
secrets,
|
|
260
|
+
gnarHiddenDir,
|
|
261
|
+
projectDir,
|
|
262
|
+
coreDev = false,
|
|
263
|
+
bootstrapDev = false,
|
|
264
|
+
build = false,
|
|
265
|
+
test = false,
|
|
266
|
+
testService,
|
|
267
|
+
resetDatabases = false,
|
|
268
|
+
resetDatabase = '',
|
|
269
|
+
attachAll = false
|
|
270
|
+
}) {
|
|
242
271
|
|
|
243
272
|
let mysqlPortsCounter = 3306;
|
|
244
273
|
let mongoPortsCounter = 27017;
|
|
@@ -248,9 +277,10 @@ async function buildAndUpContainers({ config, secrets, gnarHiddenDir, projectDir
|
|
|
248
277
|
|
|
249
278
|
console.log('======== g n a r e n g i n e ========');
|
|
250
279
|
console.log('⛏️ Starting development environment...');
|
|
251
|
-
|
|
252
|
-
//
|
|
280
|
+
|
|
281
|
+
// env var adjustments
|
|
253
282
|
for (const svc of config.services) {
|
|
283
|
+
// Tests
|
|
254
284
|
if (test) {
|
|
255
285
|
if (secrets.services?.[svc.name]?.MYSQL_HOST) {
|
|
256
286
|
secrets.services[svc.name].MYSQL_HOST = 'db-mysql-test';
|
|
@@ -264,6 +294,22 @@ async function buildAndUpContainers({ config, secrets, gnarHiddenDir, projectDir
|
|
|
264
294
|
}
|
|
265
295
|
}
|
|
266
296
|
}
|
|
297
|
+
|
|
298
|
+
// Reset all databases
|
|
299
|
+
if (resetDatabases) {
|
|
300
|
+
if (secrets.services?.[svc.name]) {
|
|
301
|
+
secrets.services[svc.name].RESET_DATABASE = true;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Reset specific database
|
|
306
|
+
else if (resetDatabase) {
|
|
307
|
+
if (svc.name === resetDatabase) {
|
|
308
|
+
if (secrets.services?.[svc.name]) {
|
|
309
|
+
secrets.services[svc.name].RESET_DATABASE = true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
267
313
|
}
|
|
268
314
|
|
|
269
315
|
// create bridge network
|
|
@@ -279,7 +325,8 @@ async function buildAndUpContainers({ config, secrets, gnarHiddenDir, projectDir
|
|
|
279
325
|
await buildImage({
|
|
280
326
|
context: directories.provisioner,
|
|
281
327
|
dockerfile: 'Dockerfile',
|
|
282
|
-
imageTag: provisionerTag
|
|
328
|
+
imageTag: provisionerTag,
|
|
329
|
+
nocache: false
|
|
283
330
|
});
|
|
284
331
|
}
|
|
285
332
|
|
|
@@ -348,7 +395,8 @@ async function buildAndUpContainers({ config, secrets, gnarHiddenDir, projectDir
|
|
|
348
395
|
await buildImage({
|
|
349
396
|
context: path.resolve(projectDir, 'services', svc.name),
|
|
350
397
|
dockerfile: 'Dockerfile',
|
|
351
|
-
imageTag: svcTag
|
|
398
|
+
imageTag: svcTag,
|
|
399
|
+
nocache: false
|
|
352
400
|
});
|
|
353
401
|
}
|
|
354
402
|
|
|
@@ -382,6 +430,13 @@ async function buildAndUpContainers({ config, secrets, gnarHiddenDir, projectDir
|
|
|
382
430
|
serviceVolumes.push(`${gnarEngineCliConfig.coreDevPath}:${gnarEngineCliConfig.corePath}`);
|
|
383
431
|
}
|
|
384
432
|
|
|
433
|
+
if (svc.extra_binds) {
|
|
434
|
+
svc.extra_binds.forEach((bind, index) => {
|
|
435
|
+
bind = `${gnarHiddenDir}/data/${svc.name}-data/bind-${index}/:${bind}`;
|
|
436
|
+
serviceVolumes.push(bind);
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
|
|
385
440
|
// split from "port:port" to { port: port }
|
|
386
441
|
const ports = {};
|
|
387
442
|
for (const portMapping of svc.ports || []) {
|
|
@@ -396,8 +451,8 @@ async function buildAndUpContainers({ config, secrets, gnarHiddenDir, projectDir
|
|
|
396
451
|
env: env,
|
|
397
452
|
ports: ports,
|
|
398
453
|
binds: serviceVolumes,
|
|
399
|
-
restart: '
|
|
400
|
-
attach: true,
|
|
454
|
+
restart: 'no',
|
|
455
|
+
attach: svc.detach ? !svc.detach : true,
|
|
401
456
|
network: networkName,
|
|
402
457
|
aliases: [`${svc.name}-service`]
|
|
403
458
|
});
|
package/src/services/docker.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Docker from 'dockerode';
|
|
2
2
|
import { Writable } from 'stream';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import tar from 'tar-fs';
|
|
4
5
|
import fs from 'fs';
|
|
5
6
|
|
|
6
7
|
const docker = new Docker();
|
|
@@ -12,24 +13,38 @@ const docker = new Docker();
|
|
|
12
13
|
* @param {string} options.context - The build context directory
|
|
13
14
|
* @param {string} options.dockerfile - The Dockerfile path relative to the context
|
|
14
15
|
* @param {string} options.imageTag - The tag to assign to the built image
|
|
16
|
+
* @param {boolean} [options.nocache=true] - Whether to build without cache
|
|
15
17
|
*/
|
|
16
|
-
export async function buildImage({ context, dockerfile, imageTag }) {
|
|
18
|
+
export async function buildImage({ context, dockerfile, imageTag, nocache = true }) {
|
|
17
19
|
|
|
18
20
|
console.log('Building image...', imageTag);
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
);
|
|
22
|
+
// Create a tar stream of the full context folder
|
|
23
|
+
const tarStream = tar.pack(context);
|
|
24
|
+
|
|
25
|
+
const stream = await docker.buildImage(tarStream, {
|
|
26
|
+
t: imageTag,
|
|
27
|
+
dockerfile,
|
|
28
|
+
nocache
|
|
29
|
+
});
|
|
30
30
|
|
|
31
31
|
await new Promise((resolve, reject) => {
|
|
32
|
-
docker.modem.followProgress(
|
|
32
|
+
docker.modem.followProgress(
|
|
33
|
+
stream,
|
|
34
|
+
(err, res) => {
|
|
35
|
+
if (err) return reject(err);
|
|
36
|
+
resolve(res);
|
|
37
|
+
},
|
|
38
|
+
(event) => {
|
|
39
|
+
if (event.stream) process.stdout.write(event.stream);
|
|
40
|
+
|
|
41
|
+
// Catch BuildKit-style errors
|
|
42
|
+
if (event.error) return reject(new Error(event.error));
|
|
43
|
+
if (event.errorDetail && event.errorDetail.message) {
|
|
44
|
+
return reject(new Error(event.errorDetail.message));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
);
|
|
33
48
|
});
|
|
34
49
|
|
|
35
50
|
console.log('Built image:', imageTag);
|
|
@@ -1,23 +1,93 @@
|
|
|
1
|
-
import { db } from '@gnar-engine/core';
|
|
1
|
+
import { db, utils } from '@gnar-engine/core';
|
|
2
2
|
|
|
3
3
|
export const {{entityName}} = {
|
|
4
4
|
async getById({ id }) {
|
|
5
5
|
const [result] = await db.query('SELECT * FROM {{lowerCasePlural entityName}} WHERE id = ?', [id]);
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
if (!result) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return db.sql.helpers.objectToCamelCase(result);
|
|
7
12
|
},
|
|
8
13
|
|
|
9
|
-
async getAll() {
|
|
10
|
-
|
|
14
|
+
async getAll({ pageSize = 100, pageNum = 1 }) {
|
|
15
|
+
pageSize = Number(pageSize);
|
|
16
|
+
pageNum = Number(pageNum);
|
|
17
|
+
const offset = (pageNum - 1) * pageSize;
|
|
18
|
+
|
|
19
|
+
const [rows] = await db.query(
|
|
20
|
+
'SELECT * FROM {{lowerCasePlural entityName}} LIMIT ? OFFSET ?',
|
|
21
|
+
[pageSize, offset]
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const [[{ total }]] = await db.query(
|
|
25
|
+
'SELECT COUNT(*) AS total FROM {{lowerCasePlural entityName}}'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
data: rows.map(row => db.sql.helpers.objectToCamelCase(row)),
|
|
30
|
+
pagination: {
|
|
31
|
+
pageSize,
|
|
32
|
+
pageNum,
|
|
33
|
+
total
|
|
34
|
+
}
|
|
35
|
+
}
|
|
11
36
|
},
|
|
12
37
|
|
|
13
38
|
async create(data) {
|
|
14
|
-
const
|
|
15
|
-
|
|
39
|
+
const id = utils.uuid();
|
|
40
|
+
|
|
41
|
+
// map columns and insert
|
|
42
|
+
const columns = ['id', ...Object.keys(data).map(db.sql.helpers.toSnake)];
|
|
43
|
+
const placeholders = columns.map(() => '?');
|
|
44
|
+
const values = [id, ...Object.values(data)];
|
|
45
|
+
|
|
46
|
+
const sql = `
|
|
47
|
+
INSERT INTO {{lowerCasePlural entityName}} (
|
|
48
|
+
${columns.join(', ')},
|
|
49
|
+
created_at,
|
|
50
|
+
updated_at
|
|
51
|
+
)
|
|
52
|
+
VALUES (
|
|
53
|
+
${placeholders.join(', ')},
|
|
54
|
+
CURRENT_TIMESTAMP,
|
|
55
|
+
CURRENT_TIMESTAMP
|
|
56
|
+
)
|
|
57
|
+
`;
|
|
58
|
+
await db.query(sql, [...values, id]);
|
|
59
|
+
|
|
60
|
+
// return result
|
|
61
|
+
const newItem = await this.getById({ id });
|
|
62
|
+
return db.sql.helpers.objectToCamelCase(newItem);
|
|
16
63
|
},
|
|
17
64
|
|
|
18
|
-
async update({ id,
|
|
19
|
-
|
|
20
|
-
|
|
65
|
+
async update({ id, updatedData }) {
|
|
66
|
+
if (!Object.keys(updatedData).length) {
|
|
67
|
+
return this.getById({ id });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle many to many relations
|
|
71
|
+
const newOwnerIds = updatedData.ownerIds ? updatedData.ownerIds : null;
|
|
72
|
+
delete updatedData.ownerIds;
|
|
73
|
+
|
|
74
|
+
// map columns and update
|
|
75
|
+
const columns = Object.keys(updatedData).map((key) => db.sql.helpers.toSnake(key));
|
|
76
|
+
const assignments = columns.map(col => `${col} = ?`);
|
|
77
|
+
const values = Object.values(updatedData);
|
|
78
|
+
|
|
79
|
+
const sql = `
|
|
80
|
+
UPDATE {{lowerCasePlural entityName}}
|
|
81
|
+
SET
|
|
82
|
+
${assignments.join(', ')},
|
|
83
|
+
updated_at = CURRENT_TIMESTAMP
|
|
84
|
+
WHERE id = ?
|
|
85
|
+
`;
|
|
86
|
+
await db.query(sql, [...values, id]);
|
|
87
|
+
|
|
88
|
+
// return result
|
|
89
|
+
const newItem = await this.getById({ id });
|
|
90
|
+
return db.sql.helpers.objectToCamelCase(newItem);
|
|
21
91
|
},
|
|
22
92
|
|
|
23
93
|
async delete({ id }) {
|
|
@@ -8,7 +8,7 @@ WORKDIR /usr/gnar_engine/app
|
|
|
8
8
|
ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
|
|
9
9
|
|
|
10
10
|
# Copy package.json and package-lock.json
|
|
11
|
-
COPY ./
|
|
11
|
+
COPY ./package*.json ./
|
|
12
12
|
|
|
13
13
|
# Install nodemon
|
|
14
14
|
RUN npm install -g nodemon
|
|
@@ -18,6 +18,3 @@ RUN npm install
|
|
|
18
18
|
|
|
19
19
|
# Expose the port the service will run on
|
|
20
20
|
EXPOSE 3000
|
|
21
|
-
|
|
22
|
-
# Start the application
|
|
23
|
-
CMD ["nodemon", "--watch", "./gnar_engine", "./gnar_engine/app.js"]
|
|
@@ -8,13 +8,9 @@ import { httpController as {{lowerCase serviceName}}PlatformHttpController } fro
|
|
|
8
8
|
*/
|
|
9
9
|
export const initService = async () => {
|
|
10
10
|
|
|
11
|
-
// Run migrations
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Run seeders
|
|
17
|
-
db.seeders.runSeeders({config});
|
|
11
|
+
// Run migrations & seeders
|
|
12
|
+
await db.migrations.runMigrations({config});
|
|
13
|
+
await db.seeders.runSeeders({config});
|
|
18
14
|
|
|
19
15
|
// Import command handlers after the command bus is initialised
|
|
20
16
|
await import('./commands/{{lowerCase serviceName}}.handler.js');
|
|
@@ -5,6 +5,11 @@ export const config = {
|
|
|
5
5
|
// service name
|
|
6
6
|
serviceName: '{{serviceName}}Service',
|
|
7
7
|
|
|
8
|
+
// environment
|
|
9
|
+
environment: process.env.{{upperCase serviceName}}_NODE_ENV || 'development',
|
|
10
|
+
runTests: process.env.{{upperCase serviceName}}_RUN_TESTS || false,
|
|
11
|
+
resetDatabase: process.env.{{upperCase serviceName}}_RESET_DATABASE || false,
|
|
12
|
+
|
|
8
13
|
// microservice | modular-monolith
|
|
9
14
|
architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
|
|
10
15
|
|
|
@@ -6,8 +6,9 @@ export const config = {
|
|
|
6
6
|
serviceName: '{{serviceName}}Service',
|
|
7
7
|
|
|
8
8
|
// environment
|
|
9
|
-
environment: process.env.{{upperCase serviceName}}_NODE_ENV || '
|
|
9
|
+
environment: process.env.{{upperCase serviceName}}_NODE_ENV || 'development',
|
|
10
10
|
runTests: process.env.{{upperCase serviceName}}_RUN_TESTS || false,
|
|
11
|
+
resetDatabase: process.env.{{upperCase serviceName}}_RESET_DATABASE || false,
|
|
11
12
|
|
|
12
13
|
// microservice | modular-monolith
|
|
13
14
|
architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
|
|
@@ -1,28 +1,93 @@
|
|
|
1
|
-
import { db } from '@gnar-engine/core';
|
|
1
|
+
import { db, utils } from '@gnar-engine/core';
|
|
2
2
|
|
|
3
3
|
export const {{serviceName}} = {
|
|
4
4
|
async getById({ id }) {
|
|
5
5
|
const [result] = await db.query('SELECT * FROM {{lowerCasePlural serviceName}} WHERE id = ?', [id]);
|
|
6
|
-
return result || null;
|
|
7
|
-
},
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
if (!result) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return db.sql.helpers.objectToCamelCase(result);
|
|
12
12
|
},
|
|
13
13
|
|
|
14
|
-
async getAll() {
|
|
15
|
-
|
|
14
|
+
async getAll({ pageSize = 100, pageNum = 1 }) {
|
|
15
|
+
pageSize = Number(pageSize);
|
|
16
|
+
pageNum = Number(pageNum);
|
|
17
|
+
const offset = (pageNum - 1) * pageSize;
|
|
18
|
+
|
|
19
|
+
const [rows] = await db.query(
|
|
20
|
+
'SELECT * FROM {{lowerCasePlural serviceName}} LIMIT ? OFFSET ?',
|
|
21
|
+
[pageSize, offset]
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const [[{ total }]] = await db.query(
|
|
25
|
+
'SELECT COUNT(*) AS total FROM {{lowerCasePlural serviceName}}'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
data: rows.map(row => db.sql.helpers.objectToCamelCase(row)),
|
|
30
|
+
pagination: {
|
|
31
|
+
pageSize,
|
|
32
|
+
pageNum,
|
|
33
|
+
total
|
|
34
|
+
}
|
|
35
|
+
}
|
|
16
36
|
},
|
|
17
37
|
|
|
18
38
|
async create(data) {
|
|
19
|
-
const
|
|
20
|
-
|
|
39
|
+
const id = utils.uuid();
|
|
40
|
+
|
|
41
|
+
// map columns and insert
|
|
42
|
+
const columns = ['id', ...Object.keys(data).map(db.sql.helpers.toSnake)];
|
|
43
|
+
const placeholders = columns.map(() => '?');
|
|
44
|
+
const values = [id, ...Object.values(data)];
|
|
45
|
+
|
|
46
|
+
const sql = `
|
|
47
|
+
INSERT INTO {{lowerCasePlural serviceName}} (
|
|
48
|
+
${columns.join(', ')},
|
|
49
|
+
created_at,
|
|
50
|
+
updated_at
|
|
51
|
+
)
|
|
52
|
+
VALUES (
|
|
53
|
+
${placeholders.join(', ')},
|
|
54
|
+
CURRENT_TIMESTAMP,
|
|
55
|
+
CURRENT_TIMESTAMP
|
|
56
|
+
)
|
|
57
|
+
`;
|
|
58
|
+
await db.query(sql, [...values, id]);
|
|
59
|
+
|
|
60
|
+
// return result
|
|
61
|
+
const newItem = await this.getById({ id });
|
|
62
|
+
return db.sql.helpers.objectToCamelCase(newItem);
|
|
21
63
|
},
|
|
22
64
|
|
|
23
|
-
async update({ id,
|
|
24
|
-
|
|
25
|
-
|
|
65
|
+
async update({ id, updatedData }) {
|
|
66
|
+
if (!Object.keys(updatedData).length) {
|
|
67
|
+
return this.getById({ id });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle many to many relations
|
|
71
|
+
const newOwnerIds = updatedData.ownerIds ? updatedData.ownerIds : null;
|
|
72
|
+
delete updatedData.ownerIds;
|
|
73
|
+
|
|
74
|
+
// map columns and update
|
|
75
|
+
const columns = Object.keys(updatedData).map((key) => db.sql.helpers.toSnake(key));
|
|
76
|
+
const assignments = columns.map(col => `${col} = ?`);
|
|
77
|
+
const values = Object.values(updatedData);
|
|
78
|
+
|
|
79
|
+
const sql = `
|
|
80
|
+
UPDATE {{lowerCasePlural serviceName}}
|
|
81
|
+
SET
|
|
82
|
+
${assignments.join(', ')},
|
|
83
|
+
updated_at = CURRENT_TIMESTAMP
|
|
84
|
+
WHERE id = ?
|
|
85
|
+
`;
|
|
86
|
+
await db.query(sql, [...values, id]);
|
|
87
|
+
|
|
88
|
+
// return result
|
|
89
|
+
const newItem = await this.getById({ id });
|
|
90
|
+
return db.sql.helpers.objectToCamelCase(newItem);
|
|
26
91
|
},
|
|
27
92
|
|
|
28
93
|
async delete({ id }) {
|