@gnar-engine/cli 1.0.3 → 1.0.4

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.
Files changed (160) hide show
  1. package/bootstrap/deploy.localdev.yml +18 -31
  2. package/bootstrap/secrets.localdev.yml +7 -12
  3. package/bootstrap/services/user/Dockerfile +1 -1
  4. package/package.json +1 -1
  5. package/src/config.js +2 -1
  6. package/src/dev/dev.service.js +44 -16
  7. package/src/scaffolder/commands.js +11 -4
  8. package/src/scaffolder/scaffolder.handler.js +228 -55
  9. package/templates/service/Dockerfile.hbs +4 -1
  10. package/templates/service/package.json.hbs +14 -16
  11. package/templates/service/{app.js.hbs → src/app.js.hbs} +8 -4
  12. package/templates/service/{commands → src/commands}/{{serviceName}}.handler.js.hbs +1 -2
  13. package/{bootstrap/services/agent/src/config.js → templates/service/src/mongodb.config.js.hbs} +11 -18
  14. package/templates/service/{config.js.hbs → src/mysql.config.js.hbs} +6 -0
  15. package/{bootstrap/services/agent/src/schema/Agent.schema.js → templates/service/src/schema/{{serviceName}}.schema.js.hbs} +3 -3
  16. package/templates/service/src/services/mongodb.{{serviceName}}.service.js.hbs +70 -0
  17. package/bootstrap/services/agent/Dockerfile +0 -23
  18. package/bootstrap/services/agent/notes.md +0 -28
  19. package/bootstrap/services/agent/package.json +0 -16
  20. package/bootstrap/services/agent/src/app.js +0 -52
  21. package/bootstrap/services/agent/src/commands/agent.handler.js +0 -104
  22. package/bootstrap/services/agent/src/controllers/http.controller.js +0 -44
  23. package/bootstrap/services/agent/src/controllers/message.controller.js +0 -51
  24. package/bootstrap/services/agent/src/db/migrations/01-init.js +0 -50
  25. package/bootstrap/services/agent/src/db/migrations/02-agent-service-init.js +0 -36
  26. package/bootstrap/services/agent/src/policies/agent.policy.js +0 -13
  27. package/bootstrap/services/agent/src/services/agent.service.js +0 -259
  28. package/bootstrap/services/agent/src/services/chatgpt.service.js +0 -46
  29. package/bootstrap/services/agent/src/services/manifest.service.js +0 -21
  30. package/bootstrap/services/portal/Dockerfile +0 -23
  31. package/bootstrap/services/portal/Dockerfile.remote +0 -40
  32. package/bootstrap/services/portal/README.md +0 -22
  33. package/bootstrap/services/portal/nginx.conf +0 -12
  34. package/bootstrap/services/portal/package.json +0 -59
  35. package/bootstrap/services/portal/public/favicon.ico +0 -0
  36. package/bootstrap/services/portal/public/gnar-white.png +0 -0
  37. package/bootstrap/services/portal/public/gnarengine-logo-black.png +0 -0
  38. package/bootstrap/services/portal/public/index.html +0 -43
  39. package/bootstrap/services/portal/public/logo192.png +0 -0
  40. package/bootstrap/services/portal/public/logo512.png +0 -0
  41. package/bootstrap/services/portal/public/manifest.json +0 -25
  42. package/bootstrap/services/portal/public/robots.txt +0 -3
  43. package/bootstrap/services/portal/src/App.js +0 -56
  44. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black.svg +0 -1
  45. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black_Green.svg +0 -1
  46. package/bootstrap/services/portal/src/assets/Logo_Anchord_White_Green.svg +0 -1
  47. package/bootstrap/services/portal/src/assets/activity.svg +0 -3
  48. package/bootstrap/services/portal/src/assets/arrow.svg +0 -3
  49. package/bootstrap/services/portal/src/assets/bin-white.svg +0 -3
  50. package/bootstrap/services/portal/src/assets/bin.svg +0 -3
  51. package/bootstrap/services/portal/src/assets/check.svg +0 -3
  52. package/bootstrap/services/portal/src/assets/chevron.svg +0 -3
  53. package/bootstrap/services/portal/src/assets/contact.svg +0 -3
  54. package/bootstrap/services/portal/src/assets/dots-vertical.svg +0 -5
  55. package/bootstrap/services/portal/src/assets/eye-off.svg +0 -3
  56. package/bootstrap/services/portal/src/assets/eye.svg +0 -4
  57. package/bootstrap/services/portal/src/assets/gnar-engine-black.svg +0 -47
  58. package/bootstrap/services/portal/src/assets/gnar-engine-white.svg +0 -47
  59. package/bootstrap/services/portal/src/assets/gnar_engine.svg +0 -3
  60. package/bootstrap/services/portal/src/assets/gnarengine-logo-black.png +0 -0
  61. package/bootstrap/services/portal/src/assets/home.svg +0 -3
  62. package/bootstrap/services/portal/src/assets/link.svg +0 -3
  63. package/bootstrap/services/portal/src/assets/lock.svg +0 -3
  64. package/bootstrap/services/portal/src/assets/package.svg +0 -4
  65. package/bootstrap/services/portal/src/assets/raffle.svg +0 -3
  66. package/bootstrap/services/portal/src/assets/settings.svg +0 -4
  67. package/bootstrap/services/portal/src/assets/shopping-bag.svg +0 -3
  68. package/bootstrap/services/portal/src/assets/user-black.svg +0 -3
  69. package/bootstrap/services/portal/src/assets/user.svg +0 -3
  70. package/bootstrap/services/portal/src/assets/users.svg +0 -3
  71. package/bootstrap/services/portal/src/assets/wallet.svg +0 -3
  72. package/bootstrap/services/portal/src/css/style.css +0 -1007
  73. package/bootstrap/services/portal/src/data/data.js +0 -70
  74. package/bootstrap/services/portal/src/features/attributeFormRow/AttributeFormRow.jsx +0 -32
  75. package/bootstrap/services/portal/src/features/billingShipping/BillingShipping.jsx +0 -160
  76. package/bootstrap/services/portal/src/features/crud/crudEdit.less +0 -230
  77. package/bootstrap/services/portal/src/features/crud/crudList.less +0 -134
  78. package/bootstrap/services/portal/src/features/crud/crudPage.less +0 -31
  79. package/bootstrap/services/portal/src/features/crudContact/CrudContactList.jsx +0 -108
  80. package/bootstrap/services/portal/src/features/crudContact/CrudContactSingle.jsx +0 -243
  81. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderList.jsx +0 -109
  82. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderSingle.jsx +0 -315
  83. package/bootstrap/services/portal/src/features/crudProducts/CrudProductList.jsx +0 -104
  84. package/bootstrap/services/portal/src/features/crudProducts/CrudProductSingle.jsx +0 -388
  85. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesList.jsx +0 -104
  86. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesSingle.jsx +0 -208
  87. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionList.jsx +0 -110
  88. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionSingle.jsx +0 -261
  89. package/bootstrap/services/portal/src/features/crudUser/CrudUserList.jsx +0 -107
  90. package/bootstrap/services/portal/src/features/crudUser/CrudUserSingle.jsx +0 -402
  91. package/bootstrap/services/portal/src/features/inventoryFormRow/InventoryFormRow.jsx +0 -30
  92. package/bootstrap/services/portal/src/features/lineItems/LineItems.jsx +0 -113
  93. package/bootstrap/services/portal/src/features/loginForm/LoginForm.jsx +0 -56
  94. package/bootstrap/services/portal/src/features/loginForm/loginForm.less +0 -56
  95. package/bootstrap/services/portal/src/features/notes/Notes.jsx +0 -18
  96. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetForm.jsx +0 -96
  97. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetRequestForm.jsx +0 -74
  98. package/bootstrap/services/portal/src/features/priceFormRow/PriceFormRow.jsx +0 -102
  99. package/bootstrap/services/portal/src/features/priceFormRow/priceFormRow.less +0 -24
  100. package/bootstrap/services/portal/src/features/raffleEntriesList/RaffleEntriesList.jsx +0 -99
  101. package/bootstrap/services/portal/src/features/raffleProductFormRow/RaffleProductFormRow.jsx +0 -46
  102. package/bootstrap/services/portal/src/features/sidebar/Sidebar.jsx +0 -64
  103. package/bootstrap/services/portal/src/features/sidebar/sidebar.less +0 -49
  104. package/bootstrap/services/portal/src/features/skus/Skus.jsx +0 -109
  105. package/bootstrap/services/portal/src/features/subscriptionSchedule/SubscriptionSchedule.jsx +0 -44
  106. package/bootstrap/services/portal/src/features/taxonomyFormRow/TaxonomyFormRow.jsx +0 -32
  107. package/bootstrap/services/portal/src/features/user/User.jsx +0 -54
  108. package/bootstrap/services/portal/src/features/user/user.less +0 -57
  109. package/bootstrap/services/portal/src/includes/utilities.js +0 -259
  110. package/bootstrap/services/portal/src/index.js +0 -14
  111. package/bootstrap/services/portal/src/layouts/CrudLayout.jsx +0 -50
  112. package/bootstrap/services/portal/src/layouts/LoginLayout.jsx +0 -17
  113. package/bootstrap/services/portal/src/layouts/PortalLayout.jsx +0 -48
  114. package/bootstrap/services/portal/src/layouts/loginLayout.less +0 -33
  115. package/bootstrap/services/portal/src/layouts/portalLayout.less +0 -67
  116. package/bootstrap/services/portal/src/pages/contacts/Contacts.jsx +0 -199
  117. package/bootstrap/services/portal/src/pages/dashboard/Dashboard.jsx +0 -17
  118. package/bootstrap/services/portal/src/pages/integrations/Integrations.jsx +0 -10
  119. package/bootstrap/services/portal/src/pages/login/Login.jsx +0 -15
  120. package/bootstrap/services/portal/src/pages/login/login.less +0 -10
  121. package/bootstrap/services/portal/src/pages/orders/Orders.jsx +0 -199
  122. package/bootstrap/services/portal/src/pages/passwordReset/PasswordResetPage.jsx +0 -15
  123. package/bootstrap/services/portal/src/pages/passwordResetRequest/PasswordResetRequestPage.jsx +0 -15
  124. package/bootstrap/services/portal/src/pages/payments/Payments.jsx +0 -10
  125. package/bootstrap/services/portal/src/pages/portal/Portal.jsx +0 -43
  126. package/bootstrap/services/portal/src/pages/products/Products.jsx +0 -212
  127. package/bootstrap/services/portal/src/pages/raffleEntries/RaffleEntries.jsx +0 -124
  128. package/bootstrap/services/portal/src/pages/raffles/Raffles.jsx +0 -186
  129. package/bootstrap/services/portal/src/pages/reports/Reports.jsx +0 -10
  130. package/bootstrap/services/portal/src/pages/settings/Settings.jsx +0 -10
  131. package/bootstrap/services/portal/src/pages/subscriptions/Subscriptions.jsx +0 -199
  132. package/bootstrap/services/portal/src/pages/users/Users.jsx +0 -193
  133. package/bootstrap/services/portal/src/pages/users/users.less +0 -25
  134. package/bootstrap/services/portal/src/slices/authSlice.js +0 -71
  135. package/bootstrap/services/portal/src/store/configureStore.js +0 -12
  136. package/bootstrap/services/portal/src/styles/global.less +0 -159
  137. package/bootstrap/services/portal/src/styles/inputs.less +0 -157
  138. package/bootstrap/services/portal/src/styles/main.less +0 -26
  139. package/bootstrap/services/portal/src/ui/collapsible/Collapsible.jsx +0 -97
  140. package/bootstrap/services/portal/src/ui/collapsible/collapsible.less +0 -23
  141. package/bootstrap/services/portal/src/ui/customCheckbox/CustomCheckbox.jsx +0 -17
  142. package/bootstrap/services/portal/src/ui/customCheckbox/customCheckbox.less +0 -42
  143. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelect.jsx +0 -63
  144. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelectPeriod.jsx +0 -63
  145. package/bootstrap/services/portal/src/ui/customSelect/CustomSelect.jsx +0 -63
  146. package/bootstrap/services/portal/src/ui/customSelect/customSelect.less +0 -92
  147. package/bootstrap/services/portal/src/ui/goBack/GoBack.jsx +0 -19
  148. package/bootstrap/services/portal/src/ui/loader/Loader.jsx +0 -12
  149. package/bootstrap/services/portal/src/ui/pagination/Pagination.jsx +0 -23
  150. package/bootstrap/services/portal/src/ui/repeater/Repeater.jsx +0 -29
  151. package/bootstrap/services/portal/src/ui/saveButton/SaveButton.jsx +0 -69
  152. package/bootstrap/services/portal/src/ui/saveButton/saveButton.less +0 -0
  153. package/bootstrap/services/user/src/db/seeders/development/02-portal-admin-user.js +0 -27
  154. package/templates/service/schema/{{serviceName}}.schema.js.hbs +0 -14
  155. /package/templates/service/{controllers → src/controllers}/http.controller.js.hbs +0 -0
  156. /package/templates/service/{controllers → src/controllers}/message.controller.js.hbs +0 -0
  157. /package/templates/service/{db → src/mysql.db}/migrations/01-init.js.hbs +0 -0
  158. /package/templates/service/{db → src/mysql.db}/migrations/02-{{lowerCase serviceName}}-service-init.js.hbs +0 -0
  159. /package/templates/service/{policies → src/policies}/{{serviceName}}.policy.js.hbs +0 -0
  160. /package/templates/service/{services/{{serviceName}}.service.js.hbs → src/services/mysql.{{serviceName}}.service.js.hbs} +0 -0
@@ -1,51 +1,38 @@
1
- # deploy.localdev.yml
2
-
3
- # - This file is used to orchestrate locally running containers for development
4
- # - environment: localdev is a named convention required for local development
5
-
6
1
  config:
7
- core-version: 1.0
2
+ core-version: 1
8
3
  architecture: microservice
9
4
  region: eu-west-2
10
5
  environment: localdev
11
6
  namespace: my-project
12
-
13
7
  services:
14
8
  - name: control
15
9
  listener_rules:
16
10
  paths:
17
11
  - /control
18
12
  - /tasks
19
- min-tasks: 1
20
- max-tasks: 1
13
+ min_tasks: 1
14
+ max_tasks: 1
21
15
  depends_on:
22
16
  - control-db
23
- command: ["npm", "run", "start:dev"]
24
- ports:
25
- - "4001:3000"
26
-
17
+ command:
18
+ - npm
19
+ - run
20
+ - start:dev
21
+ ports:
22
+ - '4001:3000'
27
23
  - name: user
28
24
  listener_rules:
29
25
  paths:
30
26
  - /authenticate
31
27
  - /users
32
- min-tasks: 1
33
- max-tasks: 1
34
- depends_on:
35
- - control-db
36
- command: ["npm", "run", "start:dev"]
37
- ports:
38
- - "4002:3000"
39
-
40
- - name: agent
41
- listener_rules:
42
- paths:
43
- - /agent
44
- min-tasks: 1
45
- max-tasks: 1
28
+ min_tasks: 1
29
+ max_tasks: 1
46
30
  depends_on:
47
- - agent-db
48
- command: ["npm", "run", "start:dev"]
49
- ports:
50
- - "4003:3000"
31
+ - user-db
32
+ command:
33
+ - npm
34
+ - run
35
+ - start:dev
36
+ ports:
37
+ - '4002:3000'
51
38
 
@@ -7,21 +7,16 @@ services:
7
7
  MYSQL_HOST: control-db
8
8
  MYSQL_DATABASE: ge_control_db
9
9
  MYSQL_USER: ge_control_db_user
10
- MYSQL_PASSWORD: zWF85kfGJNRA9ayp
11
- MYSQL_RANDOM_ROOT_PASSWORD: wH0Pzkht75NvMd3b
10
+ MYSQL_PASSWORD: bL6I1ABJFDPO3JAR
11
+ MYSQL_RANDOM_ROOT_PASSWORD: GExtMVydbpIihS5f
12
12
  user:
13
13
  MYSQL_HOST: user-db
14
14
  MYSQL_DATABASE: ge_user_db
15
15
  MYSQL_USER: ge_user_db_user
16
- MYSQL_PASSWORD: WTEYWqs6wDBX459p
17
- MYSQL_RANDOM_ROOT_PASSWORD: GXEy5T7Cp2zuOTah
16
+ MYSQL_PASSWORD: 7cm0crGDTaJy9Sa2
17
+ MYSQL_RANDOM_ROOT_PASSWORD: APJW3b7teaRHTvcv
18
18
  ROOT_ADMIN_EMAIL: adam@gnar.co.uk
19
19
  ROOT_ADMIN_USERNAME: engineadmin
20
- ROOT_ADMIN_PASSWORD: n0eYk6aoiYAeSur1
21
- ROOT_ADMIN_API_KEY: qydfeMcyutzIXBvHjrB2qGL9hTdco81X
22
- agent:
23
- MYSQL_HOST: agent-db
24
- MYSQL_DATABASE: ge_agent_db
25
- MYSQL_USER: ge_agent_db_user
26
- MYSQL_PASSWORD: 4BARYvygGSv6BRPH
27
- MYSQL_RANDOM_ROOT_PASSWORD: s27o951anIt8Lqmy
20
+ ROOT_ADMIN_PASSWORD: MOF4TWiFh4TXOHcL
21
+ ROOT_ADMIN_API_KEY: JPpmhQN0bs8f5x2jN5ypCU6Ww6VETrW2
22
+
@@ -1,4 +1,4 @@
1
- # Dockerfile for Product Service
1
+ # Dockerfile for User Service
2
2
  FROM node:20-alpine
3
3
 
4
4
  # Set the working directory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnar-engine/cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Gnar Engine Development Framework CLI: Project bootstrap, scaffolder & control plane.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/config.js CHANGED
@@ -4,5 +4,6 @@ export const gnarEngineCliConfig = {
4
4
  /**
5
5
  * The path the Gnar Engine service core should be found in the service containers
6
6
  */
7
- corePath: '/usr/gnar_engine/app/gnarengine-service-core'
7
+ corePath: '/usr/gnar_engine/app/node_modules/@gnar-engine/core'
8
+
8
9
  }
@@ -20,6 +20,13 @@ const docker = new Docker();
20
20
  * @param {boolean} [options.coreDev=false] - Whether to run in core development mode (requires access to core source)
21
21
  */
22
22
  export async function up({ projectDir, build = false, detached = false, coreDev = false }) {
23
+
24
+ // core dev
25
+ if (coreDev) {
26
+ const fileDir = path.dirname(new URL(import.meta.url).pathname);
27
+ projectDir = path.resolve(fileDir, "../../bootstrap/");
28
+ }
29
+
23
30
  // parse config
24
31
  const configPath = path.join(projectDir, "deploy.localdev.yml");
25
32
  const secretsPath = path.join(projectDir, "secrets.localdev.yml");
@@ -126,10 +133,12 @@ export async function down({ projectDir, allContainers = false }) {
126
133
  export async function createDynamicNginxConf({ config, outputPath }) {
127
134
  // Start with the static parts of nginx.conf
128
135
  let nginxConf = `
129
- http {
130
- server {
131
- listen 80;
132
- server_name ${config.namespace};
136
+ events { worker_connections 1024; }
137
+
138
+ http {
139
+ server {
140
+ listen 80;
141
+ server_name ${config.namespace};
133
142
  `;
134
143
 
135
144
  // Loop over each service
@@ -155,8 +164,8 @@ export async function createDynamicNginxConf({ config, outputPath }) {
155
164
 
156
165
  // Close server and http blocks
157
166
  nginxConf += `
167
+ }
158
168
  }
159
- }
160
169
  `;
161
170
 
162
171
  return nginxConf;
@@ -241,7 +250,7 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
241
250
 
242
251
  // add the core source code mount if in coreDeve mode
243
252
  if (coreDev) {
244
- services[`${svc.name}-service`].volumes.push(`../../../gnar-engine-core:${gnarEngineCliConfig.corePath}`);
253
+ services[`${svc.name}-service`].volumes.push(`../../../core/:${gnarEngineCliConfig.corePath}`);
245
254
  }
246
255
 
247
256
  // add a mysql instance if required
@@ -277,33 +286,52 @@ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, proj
277
286
 
278
287
  // add a mongodb instance if required
279
288
  if (
280
- serviceEnvVars.MONGO_URL &&
289
+ serviceEnvVars.MONGO_HOST &&
281
290
  serviceEnvVars.MONGO_ROOT_PASSWORD &&
291
+ serviceEnvVars.MONGO_DATABASE &&
282
292
  serviceEnvVars.MONGO_USER &&
283
293
  serviceEnvVars.MONGO_PASSWORD
284
294
  ) {
285
- services[`${svc.name}-mongo`] = {
295
+ // add mongo init scripts to hidden dir
296
+ fs.mkdir(path.join(gnarHiddenDir, 'mongo-init-scripts'), { recursive: true });
297
+ const mongoInitScript = `
298
+ db = db.getSiblingDB("invoice_db");
299
+ db.createUser({
300
+ user: "${serviceEnvVars.MONGO_USER}",
301
+ pwd: "${serviceEnvVars.MONGO_PASSWORD}",
302
+ roles: [{ role: "readWrite", db: "${serviceEnvVars.MONGO_DATABASE}" }]
303
+ });
304
+
305
+ print("Created user ${serviceEnvVars.MONGO_USER} with access to database ${serviceEnvVars.MONGO_DATABASE}");
306
+ `;
307
+ await fs.writeFile(path.join(gnarHiddenDir, 'mongo-init-scripts', `${svc.name}-init.js`), mongoInitScript);
308
+
309
+ // create mongo service
310
+ const mongoUrl = `mongodb://${serviceEnvVars.MONGO_USER}:${serviceEnvVars.MONGO_PASSWORD}@${serviceEnvVars.MONGO_HOST}:27017/${serviceEnvVars.MONGO_DATABASE}`;
311
+
312
+ services[`${svc.name}-db`] = {
286
313
  container_name: `ge-${config.environment}-${config.namespace}-${svc.name}-mongo`,
287
- image: `ge-${config.environment}-${config.namespace}-${svc.name}-mongo`,
314
+ image: 'mongo:latest',
288
315
  ports: [
289
- `${mongoPortsCounter}:${mongoPortsCounter}`
316
+ `${mongoPortsCounter}:27017`
290
317
  ],
291
318
  restart: 'always',
292
319
  environment: {
293
- [`${svc.name.toUpperCase()}_MONGO_URL`]: serviceEnvVars.MONGO_URL,
294
- [`${svc.name.toUpperCase()}_MONGO_ROOT_PASSWORD`]: serviceEnvVars.MONGO_ROOT_PASSWORD,
295
- [`${svc.name.toUpperCase()}_MONGO_USER`]: serviceEnvVars.MONGO_USER,
296
- [`${svc.name.toUpperCase()}_MONGO_PASSWORD`]: serviceEnvVars.MONGO_PASSWORD,
320
+ MONGO_INITDB_ROOT_USERNAME: 'root',
321
+ MONGO_INITDB_ROOT_PASSWORD: serviceEnvVars.MONGO_ROOT_PASSWORD,
322
+ MONGO_INITDB_DATABASE: serviceEnvVars.MONGO_DATABASE,
323
+ DB_USER: serviceEnvVars.MONGO_USER,
324
+ DB_PASSWORD: serviceEnvVars.MONGO_PASSWORD,
297
325
  },
298
326
  volumes: [
299
- `${gnarHiddenDir}/Data/${svc.name}-mongo-data:/data/db`
327
+ `${gnarHiddenDir}/data/${svc.name}-mongo-data:/data/db`,
328
+ './mongo-init-scripts:/docker-entrypoint-initdb.d'
300
329
  ]
301
330
  };
302
331
 
303
332
  // increment mongo port for next service as required
304
333
  mongoPortsCounter++;
305
334
  }
306
-
307
335
  }
308
336
 
309
337
  return {
@@ -48,9 +48,9 @@ export const registerScaffolderCommands = (program) => {
48
48
  create
49
49
  .command('service <service>')
50
50
  .description('📦 Create a new service: back-end|front-end')
51
- .action(async (options) => {
51
+ .action(async (service) => {
52
52
  // validate
53
- if (!options.service) {
53
+ if (!service) {
54
54
  console.error('❌ Please specify a service name using gnar create service <serviceName>');
55
55
  }
56
56
 
@@ -95,10 +95,17 @@ export const registerScaffolderCommands = (program) => {
95
95
  try {
96
96
  console.log('Creating new service in... ' + activeProfile.profile.PROJECT_DIR);
97
97
 
98
+ // add trailing slash to project dir if missing
99
+ let projectDir = activeProfile.profile.PROJECT_DIR;
100
+
101
+ if (!activeProfile.profile.PROJECT_DIR.endsWith(path.sep)) {
102
+ projectDir += path.sep;
103
+ }
104
+
98
105
  scaffolder.createNewService({
99
- serviceName: options.service,
106
+ serviceName: service,
100
107
  database: backendAnswers.database,
101
- projectDir: activeProfile.profile.PROJECT_DIR
108
+ projectDir: projectDir
102
109
  });
103
110
 
104
111
  } catch (error) {
@@ -38,47 +38,78 @@ export const scaffolder = {
38
38
  baseDir: templatesDir
39
39
  });
40
40
 
41
- console.log('Template files:', templateFiles);
42
-
43
41
  // Register Handlebars helpers
44
42
  Object.entries(helpers).forEach(([name, fn]) => {
45
43
  Handlebars.registerHelper(name, fn);
46
44
  });
47
45
 
48
- // Write the files to the service directory
49
- templateFiles.forEach(file => {
50
- let sourcePath;
51
- let targetPath;
52
- const templateArgs = {
53
- serviceName,
54
- database
55
- };
56
-
57
- switch (file.extension) {
58
- case '.hbs':
59
- // Compile the Handlebars template for content
60
- const templateContent = fs.readFileSync(file.fullPath, 'utf8');
61
- const compiledTemplate = Handlebars.compile(templateContent);
62
- const renderedContent = compiledTemplate(templateArgs);
63
-
64
- // Compile the Handlebars template for the filename (excluding .hbs)
65
- const filenameTemplate = Handlebars.compile(file.relativePath.replace(/\.hbs$/, ''));
66
- const renderedFilename = filenameTemplate(templateArgs);
67
- targetPath = path.join(serviceDir, renderedFilename);
68
-
69
- // Ensure directory exists
70
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
71
- fs.writeFileSync(targetPath, renderedContent, 'utf8');
72
- break;
73
- default:
74
- // By default, copy the file to the service directory
75
- sourcePath = file.fullPath;
76
- targetPath = path.join(serviceDir, file.relativePath);
77
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
78
- fs.copyFileSync(sourcePath, targetPath);
79
- break;
46
+ // Write the files to the service directory
47
+ templateFiles.forEach(file => {
48
+ let sourcePath;
49
+ let targetPath;
50
+ const templateArgs = {
51
+ serviceName,
52
+ database
53
+ };
54
+
55
+ let fileRelativePath = file.relativePath;
56
+
57
+ // Database specific
58
+ if (fileRelativePath.includes('mongodb.')) {
59
+ if (database !== 'mongodb') {
60
+ return;
61
+ } else {
62
+ fileRelativePath = fileRelativePath.replace('mongodb.', '');
80
63
  }
81
- });
64
+ }
65
+
66
+ if (fileRelativePath.includes('mysql.')) {
67
+ if (database !== 'mysql') {
68
+ return;
69
+ } else {
70
+ fileRelativePath = fileRelativePath.replace('mysql.', '');
71
+ }
72
+ }
73
+
74
+ switch (file.extension) {
75
+ case '.hbs':
76
+ // Compile the Handlebars template for content
77
+ const templateContent = fs.readFileSync(file.fullPath, 'utf8');
78
+ const compiledTemplate = Handlebars.compile(templateContent);
79
+ const renderedContent = compiledTemplate(templateArgs);
80
+
81
+ // Compile the Handlebars template for the filename (excluding .hbs)
82
+ const filenameTemplate = Handlebars.compile(fileRelativePath.replace(/\.hbs$/, ''));
83
+ const renderedFilename = filenameTemplate(templateArgs);
84
+ targetPath = path.join(serviceDir, renderedFilename);
85
+
86
+ // Ensure directory exists
87
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
88
+ fs.writeFileSync(targetPath, renderedContent, 'utf8');
89
+ break;
90
+ default:
91
+ // By default, copy the file to the service directory
92
+ sourcePath = file.fullPath;
93
+ targetPath = path.join(serviceDir, fileRelativePath);
94
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
95
+ fs.copyFileSync(sourcePath, targetPath);
96
+ break;
97
+ }
98
+ });
99
+
100
+ // Scaffold deploy.yml
101
+ scaffolder.scaffoldServiceDeployYml({
102
+ deployPath: path.join(projectDir, 'deploy.localdev.yml'),
103
+ serviceName: serviceName,
104
+ database: database
105
+ });
106
+
107
+ // Scaffold secrets.yml
108
+ scaffolder.scaffoldServiceSecrets({
109
+ secretsPath: path.join(projectDir, 'secrets.localdev.yml'),
110
+ serviceName: serviceName,
111
+ database: database
112
+ });
82
113
 
83
114
  return {
84
115
  message: `Service "${serviceName}" created successfully at ${serviceDir}`,
@@ -198,27 +229,15 @@ export const scaffolder = {
198
229
  // create random secrets
199
230
  if (file.relativePath === 'secrets.localdev.yml') {
200
231
  console.log('Creating random secrets: secrets.localdev.yml');
201
- const secretsPath = path.join(bootstrapDir, 'secrets.localdev.yml');
202
- const rawSecrets = fs.readFileSync(secretsPath, 'utf8');
203
- const parsedSecrets = yaml.load(rawSecrets);
204
-
205
- // generate random passwords
206
- Object.keys(parsedSecrets.services).forEach(serviceName => {
207
- Object.keys(parsedSecrets.services[serviceName]).forEach(key => {
208
- if (key.toLowerCase().includes('pass')) {
209
- parsedSecrets.services[serviceName][key] = helpers.generateRandomString(16);
210
- }
211
- });
212
- });
213
-
214
- // set random root api key
215
- cliApiKey = helpers.generateRandomString(32);
216
- parsedSecrets.services.user.ROOT_ADMIN_API_KEY = cliApiKey;
217
- parsedSecrets.services.user.ROOT_ADMIN_EMAIL = rootAdminEmail || 'admin@' + projectName + '.local';
218
232
 
219
- // save updated secrets file
220
- const newSecretsContent = yaml.dump(parsedSecrets);
221
- fs.writeFileSync(secretsPath, newSecretsContent, 'utf8');
233
+ const scaffoldedSecrets = scaffolder.scaffoldProjectSecrets({
234
+ secretsPath: sourcePath,
235
+ bootstrapDir: path.dirname(sourcePath),
236
+ projectName: projectName,
237
+ rootAdminEmail: rootAdminEmail
238
+ });
239
+ cliApiKey = scaffoldedSecrets.cliApiKey;
240
+ sourcePath = path.join(path.dirname(sourcePath), 'secrets.localdev.yml.temp');
222
241
  }
223
242
 
224
243
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
@@ -248,5 +267,159 @@ export const scaffolder = {
248
267
 
249
268
  console.log('g n a r e n g i n e - Created new project: ' + projectName);
250
269
  console.log('Run `gnar dev up` to start the development server');
270
+ },
271
+
272
+ /**
273
+ * Scaffold secrets for a new project
274
+ *
275
+ * @param {object} param
276
+ * @param {string} param.bootstrapDir - The bootstrap directory path
277
+ * @param {string} param.projectName - The project name
278
+ * @param {string} param.rootAdminEmail - The root admin email
279
+ * @returns {object} - An object containing the CLI API key
280
+ */
281
+ scaffoldProjectSecrets: function ({bootstrapDir, projectName, rootAdminEmail}) {
282
+ const secretsPath = path.join(bootstrapDir, 'secrets.localdev.yml');
283
+ const rawSecrets = fs.readFileSync(secretsPath, 'utf8');
284
+ const parsedSecrets = yaml.load(rawSecrets);
285
+
286
+ // generate random passwords
287
+ Object.keys(parsedSecrets.services).forEach(serviceName => {
288
+ Object.keys(parsedSecrets.services[serviceName]).forEach(key => {
289
+ if (key.toLowerCase().includes('pass')) {
290
+ parsedSecrets.services[serviceName][key] = helpers.generateRandomString(16);
291
+ }
292
+ });
293
+ });
294
+
295
+ // set random root api key
296
+ const cliApiKey = helpers.generateRandomString(32);
297
+ parsedSecrets.services.user.ROOT_ADMIN_API_KEY = cliApiKey;
298
+ parsedSecrets.services.user.ROOT_ADMIN_EMAIL = rootAdminEmail || 'admin@' + projectName + '.local';
299
+
300
+ // save updated secrets file to a temp file version (so we don't overwrite the original in templates)
301
+ const newSecretsContent = yaml.dump(parsedSecrets);
302
+ fs.writeFileSync(secretsPath + '.temp', newSecretsContent, 'utf8');
303
+
304
+ return { cliApiKey };
305
+ },
306
+
307
+ /**
308
+ * Scaffold secrets for a new service
309
+ *
310
+ * @param {object} param
311
+ * @param {string} param.secretsPath - The path to the secrets file
312
+ * @param {string} param.serviceName - The service name
313
+ * @param {string} param.database - The database type
314
+ */
315
+ scaffoldServiceSecrets: function ({secretsPath, serviceName, database}) {
316
+ const rawSecrets = fs.readFileSync(secretsPath, 'utf8');
317
+ const parsedSecrets = yaml.load(rawSecrets);
318
+
319
+ if (!parsedSecrets.services) {
320
+ parsedSecrets.services = {};
321
+ }
322
+
323
+ parsedSecrets.services[serviceName] = {};
324
+
325
+ // generate random passwords
326
+ switch (database) {
327
+ case 'mysql':
328
+ parsedSecrets.services[serviceName]['MYSQL_USER'] = serviceName + '_user';
329
+ parsedSecrets.services[serviceName]['MYSQL_PASSWORD'] = helpers.generateRandomString(16);
330
+ parsedSecrets.services[serviceName]['MYSQL_DATABASE'] = serviceName + '_db';
331
+ parsedSecrets.services[serviceName]['MYSQL_HOST'] = serviceName + '-db';
332
+ parsedSecrets.services[serviceName]['MYSQL_RANDOM_ROOT_PASSWORD'] = helpers.generateRandomString(16);
333
+ break;
334
+ case 'mongodb':
335
+ const mongoPassword = helpers.generateRandomString(16);
336
+ const mongoRootPassword = helpers.generateRandomString(16);
337
+ const mongoUser = serviceName + '_user';
338
+ const mongoDatabase = serviceName + '_db';
339
+ const mongoHost = serviceName + '-db';
340
+ const mongoUrl = `mongodb://${mongoUser}:${mongoPassword}@${mongoHost}:27017/${mongoDatabase}`;
341
+
342
+ parsedSecrets.services[serviceName]['MONGO_URL'] = mongoUrl;
343
+ parsedSecrets.services[serviceName]['MONGO_USER'] = mongoUser;
344
+ parsedSecrets.services[serviceName]['MONGO_PASSWORD'] = mongoPassword;
345
+ parsedSecrets.services[serviceName]['MONGO_ROOT_PASSWORD'] = mongoRootPassword;
346
+ parsedSecrets.services[serviceName]['MONGO_DATABASE'] = mongoDatabase;
347
+ parsedSecrets.services[serviceName]['MONGO_HOST'] = mongoHost;
348
+ break;
349
+ default:
350
+ throw new Error(`Unsupported database type in secret scaffolder: ${database}`);
351
+ }
352
+
353
+ // save updated secrets file
354
+ const newSecretsContent = yaml.dump(parsedSecrets);
355
+ fs.writeFileSync(secretsPath, newSecretsContent, 'utf8');
356
+ },
357
+
358
+ /**
359
+ * Scaffold deploy.yml for a new service
360
+ *
361
+ * @param {object} param
362
+ * @param {string} param.deployPath - The path to the deploy.yml file
363
+ * @param {string} param.serviceName - The service name
364
+ * @param {string} param.database - The database type
365
+ * @param {number} [param.hostPort=null] - The host port (optional)
366
+ */
367
+ scaffoldServiceDeployYml: function ({deployPath, serviceName, database, hostPort = null}) {
368
+ // parse existing deploy.yml
369
+ let deploy = yaml.load(fs.readFileSync(deployPath, 'utf8'));
370
+
371
+ // don't duplicate if service already exists
372
+ const existingService = deploy.config.services.find(svc => svc.name === serviceName);
373
+
374
+ if (existingService) {
375
+ return;
376
+ }
377
+
378
+ // get next available host port if not prescribed
379
+ if (!hostPort) {
380
+ const hostPorts = [];
381
+
382
+ deploy.config.services.forEach(svc => {
383
+ if (svc.ports && svc.ports.length > 0) {
384
+ svc.ports.forEach(portMapping => {
385
+ const [hostPort, containerPort] = portMapping.split(':').map(p => parseInt(p, 10));
386
+ hostPorts.push(hostPort);
387
+ });
388
+ }
389
+ });
390
+
391
+ hostPorts.sort((a, b) => a - b);
392
+ hostPort = hostPorts[hostPorts.length - 1] + 1;
393
+ }
394
+
395
+ // prepare new service config
396
+ const serviceConfig = {
397
+ name: serviceName,
398
+ listener_rules: {
399
+ paths: [
400
+ `/${serviceName}`
401
+ ]
402
+ },
403
+ min_tasks: 1,
404
+ max_tasks: 1,
405
+ command: ["npm", "run", "start:dev"],
406
+ ports: [
407
+ `${hostPort}:3000`
408
+ ]
409
+ }
410
+
411
+ // add database service if required
412
+ if (database) {
413
+ serviceConfig.depends_on = [
414
+ `${serviceName}-db`
415
+ ]
416
+ }
417
+
418
+ // add to deploy config
419
+ deploy.config.services.push(serviceConfig);
420
+
421
+ // write deploy.yml file
422
+ const deployYmlContent = yaml.dump(deploy);
423
+ fs.writeFileSync(deployPath, deployYmlContent, 'utf8');
251
424
  }
252
425
  }
@@ -1,9 +1,12 @@
1
1
  # Dockerfile for {{pascalCase serviceName}} Service
2
- FROM gnarengine/service-core:1.0.0
2
+ FROM node:20-alpine
3
3
 
4
4
  # Set the working directory
5
5
  WORKDIR /usr/gnar_engine/app
6
6
 
7
+ # Define a global env var
8
+ ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
9
+
7
10
  # Copy package.json and package-lock.json
8
11
  COPY ./services/{{serviceName}}/package*.json ./
9
12
 
@@ -1,18 +1,16 @@
1
1
  {
2
- "name": "{{lowerCase serviceName}}-service",
3
- "version": "1.0.0",
4
- "description": "{{pascalCase serviceName}} microservice for Gnar Engine",
5
- "main": "src/app.js",
6
- "scripts": {
7
- "start": "node src/app.js",
8
- "dev": "nodemon src/app.js"
9
- },
10
- "dependencies": {
11
- "@gnar-engine/core": "^1.0.1"
12
- },
13
- "devDependencies": {
14
- "nodemon": "^3.0.0"
15
- },
16
- "author": "Gnar Software",
17
- "license": "MIT"
2
+ "name": "{{lowerCase serviceName}}-service",
3
+ "version": "1.0.0",
4
+ "description": "{{pascalCase serviceName}} microservice for Gnar Engine",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node ./src/app.js",
8
+ "start:dev": "nodemon --watch ./src ./src/app.js",
9
+ "test": "jest --watchAll --verbose"
10
+ },
11
+ "author": "Gnar Software Ltd.",
12
+ "license": "ISC",
13
+ "dependencies": {
14
+ "@gnar-engine/core": "^1.0.1"
15
+ }
18
16
  }
@@ -1,7 +1,7 @@
1
1
  import { message, http, logger, db } from '@gnar-engine/core';
2
2
  import { config } from './config.js';
3
3
  import { messageHandlers } from './controllers/message.controller.js';
4
- import { httpController as {{pascalCase serviceName}}PlatformHttpController } from './controllers/http.controller.js';
4
+ import { httpController as {{lowerCase serviceName}}PlatformHttpController } from './controllers/http.controller.js';
5
5
 
6
6
  /**
7
7
  * Initialise service
@@ -9,11 +9,15 @@ import { httpController as {{pascalCase serviceName}}PlatformHttpController } fr
9
9
  export const initService = async () => {
10
10
 
11
11
  // Run migrations
12
- db.migrations.runMigrations({config});
12
+ if (config.db.type == 'mysql') {
13
+ db.migrations.runMigrations({config});
14
+ }
15
+
16
+ // Run seeders
13
17
  db.seeders.runSeeders({config});
14
18
 
15
19
  // Import command handlers after the command bus is initialised
16
- await import('./commands/{{lowerCasePlural serviceName}}.handler.js');
20
+ await import('./commands/{{lowerCase serviceName}}.handler.js');
17
21
  // Add more handlers as needed
18
22
 
19
23
  // Initialise and register message handlers
@@ -25,7 +29,7 @@ export const initService = async () => {
25
29
  // Register http routes
26
30
  await http.registerRoutes({
27
31
  controllers: [
28
- {{pascalCase serviceName}}PlatformHttpController,
32
+ {{lowerCase serviceName}}PlatformHttpController,
29
33
  ]
30
34
  });
31
35