@gnar-engine/cli 1.0.7 → 1.0.8

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.
@@ -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 ./services/control/package*.json ./
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 ./services/notification/package*.json ./
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 ./services/page/package*.json ./
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,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 ./services/portal/ ./
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 ./services/user/package*.json ./
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"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnar-engine/cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
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 = {
@@ -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 the tests with ephemeral databases')
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) {
@@ -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({ projectDir, build = false, detach = false, coreDev = false, bootstrapDev = false, test = false, testService = '', removeOrphans = true, attachAll = false}) {
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 docker-compose.yml dynamically from parsed config and secrets
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
- attachAll: attachAll
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({ config, secrets, gnarHiddenDir, projectDir, coreDev = false, bootstrapDev = false, build = false, test = false, testService, attachAll = false }) {
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
- // test mode env var adjustments
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
 
@@ -396,8 +444,8 @@ async function buildAndUpContainers({ config, secrets, gnarHiddenDir, projectDir
396
444
  env: env,
397
445
  ports: ports,
398
446
  binds: serviceVolumes,
399
- restart: 'always',
400
- attach: true,
447
+ restart: 'no',
448
+ attach: svc.detach ? !svc.detach : true,
401
449
  network: networkName,
402
450
  aliases: [`${svc.name}-service`]
403
451
  });
@@ -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
- const tarStream = await docker.buildImage(
21
- {
22
- context: context,
23
- src: fs.readdirSync(context)
24
- },
25
- {
26
- t: imageTag,
27
- dockerfile
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(tarStream, (err) => (err ? reject(err) : resolve()));
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);
@@ -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 ./services/{{serviceName}}/package*.json ./
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"]