@gnar-engine/cli 1.0.0 → 1.0.1

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 (230) hide show
  1. package/assets/gnar-engine-logo-white.svg +9 -0
  2. package/bootstrap/deploy.localdev.yml +51 -0
  3. package/bootstrap/secrets.localdev.yml +27 -0
  4. package/bootstrap/services/agent/Dockerfile +23 -0
  5. package/bootstrap/services/agent/notes.md +28 -0
  6. package/bootstrap/services/agent/package.json +16 -0
  7. package/bootstrap/services/agent/src/app.js +52 -0
  8. package/bootstrap/services/agent/src/commands/agent.handler.js +104 -0
  9. package/bootstrap/services/agent/src/config.js +52 -0
  10. package/bootstrap/services/agent/src/controllers/http.controller.js +44 -0
  11. package/bootstrap/services/agent/src/controllers/message.controller.js +51 -0
  12. package/bootstrap/services/agent/src/db/migrations/01-init.js +50 -0
  13. package/bootstrap/services/agent/src/db/migrations/02-agent-service-init.js +36 -0
  14. package/bootstrap/services/agent/src/policies/agent.policy.js +13 -0
  15. package/bootstrap/services/agent/src/schema/Agent.schema.js +17 -0
  16. package/bootstrap/services/agent/src/services/agent.service.js +259 -0
  17. package/bootstrap/services/agent/src/services/chatgpt.service.js +46 -0
  18. package/bootstrap/services/agent/src/services/manifest.service.js +21 -0
  19. package/bootstrap/services/control/Dockerfile +23 -0
  20. package/bootstrap/services/control/Dockerfile.prod +37 -0
  21. package/bootstrap/services/control/README.md +25 -0
  22. package/bootstrap/services/control/package.json +16 -0
  23. package/bootstrap/services/control/src/app.js +45 -0
  24. package/bootstrap/services/control/src/commands/control.handler.js +231 -0
  25. package/bootstrap/services/control/src/commands/service.handler.js +81 -0
  26. package/bootstrap/services/control/src/commands/task.handler.js +247 -0
  27. package/bootstrap/services/control/src/config.js +55 -0
  28. package/bootstrap/services/control/src/controllers/http.controller.js +228 -0
  29. package/bootstrap/services/control/src/controllers/message.controller.js +40 -0
  30. package/bootstrap/services/control/src/db/migrations/01-init.js +50 -0
  31. package/bootstrap/services/control/src/db/migrations/02-control-service-init.js +60 -0
  32. package/bootstrap/services/control/src/db/migrations/03-alter-tasks.js +29 -0
  33. package/bootstrap/services/control/src/policies/task.policy.js +53 -0
  34. package/bootstrap/services/control/src/schema/control.schema.js +42 -0
  35. package/bootstrap/services/control/src/services/registry.service.js +83 -0
  36. package/bootstrap/services/control/src/services/reset.service.js +28 -0
  37. package/bootstrap/services/control/src/services/task.service.js +153 -0
  38. package/bootstrap/services/control/src/tests/control.test.js +50 -0
  39. package/bootstrap/services/notification/Dockerfile +23 -0
  40. package/bootstrap/services/notification/Dockerfile.prod +37 -0
  41. package/bootstrap/services/notification/README.md +3 -0
  42. package/bootstrap/services/notification/package.json +34 -0
  43. package/bootstrap/services/notification/src/app.js +51 -0
  44. package/bootstrap/services/notification/src/commands/command-bus.js +20 -0
  45. package/bootstrap/services/notification/src/commands/handlers/control.handler.js +18 -0
  46. package/bootstrap/services/notification/src/commands/handlers/notification.handler.js +157 -0
  47. package/bootstrap/services/notification/src/config.js +15 -0
  48. package/bootstrap/services/notification/src/controllers/message.controller.js +82 -0
  49. package/bootstrap/services/notification/src/services/logger.service.js +16 -0
  50. package/bootstrap/services/notification/src/services/ses.service.js +23 -0
  51. package/bootstrap/services/notification/src/templates/admin-order-recieved.hbs +136 -0
  52. package/bootstrap/services/notification/src/templates/admin-subscription-failed.hbs +87 -0
  53. package/bootstrap/services/notification/src/templates/customer-order-recieved.hbs +132 -0
  54. package/bootstrap/services/notification/src/templates/customer-subscription-failed.hbs +77 -0
  55. package/bootstrap/services/notification/src/tests/notification.test.js +0 -0
  56. package/bootstrap/services/portal/Dockerfile +23 -0
  57. package/bootstrap/services/portal/Dockerfile.remote +40 -0
  58. package/bootstrap/services/portal/README.md +22 -0
  59. package/bootstrap/services/portal/nginx.conf +12 -0
  60. package/bootstrap/services/portal/package.json +59 -0
  61. package/bootstrap/services/portal/public/favicon.ico +0 -0
  62. package/bootstrap/services/portal/public/gnar-white.png +0 -0
  63. package/bootstrap/services/portal/public/gnarengine-logo-black.png +0 -0
  64. package/bootstrap/services/portal/public/index.html +43 -0
  65. package/bootstrap/services/portal/public/logo192.png +0 -0
  66. package/bootstrap/services/portal/public/logo512.png +0 -0
  67. package/bootstrap/services/portal/public/manifest.json +25 -0
  68. package/bootstrap/services/portal/public/robots.txt +3 -0
  69. package/bootstrap/services/portal/src/App.js +56 -0
  70. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black.svg +1 -0
  71. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black_Green.svg +1 -0
  72. package/bootstrap/services/portal/src/assets/Logo_Anchord_White_Green.svg +1 -0
  73. package/bootstrap/services/portal/src/assets/activity.svg +3 -0
  74. package/bootstrap/services/portal/src/assets/arrow.svg +3 -0
  75. package/bootstrap/services/portal/src/assets/bin-white.svg +3 -0
  76. package/bootstrap/services/portal/src/assets/bin.svg +3 -0
  77. package/bootstrap/services/portal/src/assets/check.svg +3 -0
  78. package/bootstrap/services/portal/src/assets/chevron.svg +3 -0
  79. package/bootstrap/services/portal/src/assets/contact.svg +3 -0
  80. package/bootstrap/services/portal/src/assets/dots-vertical.svg +5 -0
  81. package/bootstrap/services/portal/src/assets/eye-off.svg +3 -0
  82. package/bootstrap/services/portal/src/assets/eye.svg +4 -0
  83. package/bootstrap/services/portal/src/assets/gnar-engine-black.svg +47 -0
  84. package/bootstrap/services/portal/src/assets/gnar-engine-white.svg +47 -0
  85. package/bootstrap/services/portal/src/assets/gnar_engine.svg +3 -0
  86. package/bootstrap/services/portal/src/assets/gnarengine-logo-black.png +0 -0
  87. package/bootstrap/services/portal/src/assets/home.svg +3 -0
  88. package/bootstrap/services/portal/src/assets/link.svg +3 -0
  89. package/bootstrap/services/portal/src/assets/lock.svg +3 -0
  90. package/bootstrap/services/portal/src/assets/package.svg +4 -0
  91. package/bootstrap/services/portal/src/assets/raffle.svg +3 -0
  92. package/bootstrap/services/portal/src/assets/settings.svg +4 -0
  93. package/bootstrap/services/portal/src/assets/shopping-bag.svg +3 -0
  94. package/bootstrap/services/portal/src/assets/user-black.svg +3 -0
  95. package/bootstrap/services/portal/src/assets/user.svg +3 -0
  96. package/bootstrap/services/portal/src/assets/users.svg +3 -0
  97. package/bootstrap/services/portal/src/assets/wallet.svg +3 -0
  98. package/bootstrap/services/portal/src/css/style.css +1007 -0
  99. package/bootstrap/services/portal/src/data/data.js +70 -0
  100. package/bootstrap/services/portal/src/features/attributeFormRow/AttributeFormRow.jsx +32 -0
  101. package/bootstrap/services/portal/src/features/billingShipping/BillingShipping.jsx +160 -0
  102. package/bootstrap/services/portal/src/features/crud/crudEdit.less +230 -0
  103. package/bootstrap/services/portal/src/features/crud/crudList.less +134 -0
  104. package/bootstrap/services/portal/src/features/crud/crudPage.less +31 -0
  105. package/bootstrap/services/portal/src/features/crudContact/CrudContactList.jsx +108 -0
  106. package/bootstrap/services/portal/src/features/crudContact/CrudContactSingle.jsx +243 -0
  107. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderList.jsx +109 -0
  108. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderSingle.jsx +315 -0
  109. package/bootstrap/services/portal/src/features/crudProducts/CrudProductList.jsx +104 -0
  110. package/bootstrap/services/portal/src/features/crudProducts/CrudProductSingle.jsx +388 -0
  111. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesList.jsx +104 -0
  112. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesSingle.jsx +208 -0
  113. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionList.jsx +110 -0
  114. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionSingle.jsx +261 -0
  115. package/bootstrap/services/portal/src/features/crudUser/CrudUserList.jsx +107 -0
  116. package/bootstrap/services/portal/src/features/crudUser/CrudUserSingle.jsx +402 -0
  117. package/bootstrap/services/portal/src/features/inventoryFormRow/InventoryFormRow.jsx +30 -0
  118. package/bootstrap/services/portal/src/features/lineItems/LineItems.jsx +113 -0
  119. package/bootstrap/services/portal/src/features/loginForm/LoginForm.jsx +56 -0
  120. package/bootstrap/services/portal/src/features/loginForm/loginForm.less +56 -0
  121. package/bootstrap/services/portal/src/features/notes/Notes.jsx +18 -0
  122. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetForm.jsx +96 -0
  123. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetRequestForm.jsx +74 -0
  124. package/bootstrap/services/portal/src/features/priceFormRow/PriceFormRow.jsx +102 -0
  125. package/bootstrap/services/portal/src/features/priceFormRow/priceFormRow.less +24 -0
  126. package/bootstrap/services/portal/src/features/raffleEntriesList/RaffleEntriesList.jsx +99 -0
  127. package/bootstrap/services/portal/src/features/raffleProductFormRow/RaffleProductFormRow.jsx +46 -0
  128. package/bootstrap/services/portal/src/features/sidebar/Sidebar.jsx +64 -0
  129. package/bootstrap/services/portal/src/features/sidebar/sidebar.less +49 -0
  130. package/bootstrap/services/portal/src/features/skus/Skus.jsx +109 -0
  131. package/bootstrap/services/portal/src/features/subscriptionSchedule/SubscriptionSchedule.jsx +44 -0
  132. package/bootstrap/services/portal/src/features/taxonomyFormRow/TaxonomyFormRow.jsx +32 -0
  133. package/bootstrap/services/portal/src/features/user/User.jsx +54 -0
  134. package/bootstrap/services/portal/src/features/user/user.less +57 -0
  135. package/bootstrap/services/portal/src/includes/utilities.js +259 -0
  136. package/bootstrap/services/portal/src/index.js +14 -0
  137. package/bootstrap/services/portal/src/layouts/CrudLayout.jsx +50 -0
  138. package/bootstrap/services/portal/src/layouts/LoginLayout.jsx +17 -0
  139. package/bootstrap/services/portal/src/layouts/PortalLayout.jsx +48 -0
  140. package/bootstrap/services/portal/src/layouts/loginLayout.less +33 -0
  141. package/bootstrap/services/portal/src/layouts/portalLayout.less +67 -0
  142. package/bootstrap/services/portal/src/pages/contacts/Contacts.jsx +199 -0
  143. package/bootstrap/services/portal/src/pages/dashboard/Dashboard.jsx +17 -0
  144. package/bootstrap/services/portal/src/pages/integrations/Integrations.jsx +10 -0
  145. package/bootstrap/services/portal/src/pages/login/Login.jsx +15 -0
  146. package/bootstrap/services/portal/src/pages/login/login.less +10 -0
  147. package/bootstrap/services/portal/src/pages/orders/Orders.jsx +199 -0
  148. package/bootstrap/services/portal/src/pages/passwordReset/PasswordResetPage.jsx +15 -0
  149. package/bootstrap/services/portal/src/pages/passwordResetRequest/PasswordResetRequestPage.jsx +15 -0
  150. package/bootstrap/services/portal/src/pages/payments/Payments.jsx +10 -0
  151. package/bootstrap/services/portal/src/pages/portal/Portal.jsx +43 -0
  152. package/bootstrap/services/portal/src/pages/products/Products.jsx +212 -0
  153. package/bootstrap/services/portal/src/pages/raffleEntries/RaffleEntries.jsx +124 -0
  154. package/bootstrap/services/portal/src/pages/raffles/Raffles.jsx +186 -0
  155. package/bootstrap/services/portal/src/pages/reports/Reports.jsx +10 -0
  156. package/bootstrap/services/portal/src/pages/settings/Settings.jsx +10 -0
  157. package/bootstrap/services/portal/src/pages/subscriptions/Subscriptions.jsx +199 -0
  158. package/bootstrap/services/portal/src/pages/users/Users.jsx +193 -0
  159. package/bootstrap/services/portal/src/pages/users/users.less +25 -0
  160. package/bootstrap/services/portal/src/slices/authSlice.js +71 -0
  161. package/bootstrap/services/portal/src/store/configureStore.js +12 -0
  162. package/bootstrap/services/portal/src/styles/global.less +159 -0
  163. package/bootstrap/services/portal/src/styles/inputs.less +157 -0
  164. package/bootstrap/services/portal/src/styles/main.less +26 -0
  165. package/bootstrap/services/portal/src/ui/collapsible/Collapsible.jsx +97 -0
  166. package/bootstrap/services/portal/src/ui/collapsible/collapsible.less +23 -0
  167. package/bootstrap/services/portal/src/ui/customCheckbox/CustomCheckbox.jsx +17 -0
  168. package/bootstrap/services/portal/src/ui/customCheckbox/customCheckbox.less +42 -0
  169. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelect.jsx +63 -0
  170. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelectPeriod.jsx +63 -0
  171. package/bootstrap/services/portal/src/ui/customSelect/CustomSelect.jsx +63 -0
  172. package/bootstrap/services/portal/src/ui/customSelect/customSelect.less +92 -0
  173. package/bootstrap/services/portal/src/ui/goBack/GoBack.jsx +19 -0
  174. package/bootstrap/services/portal/src/ui/loader/Loader.jsx +12 -0
  175. package/bootstrap/services/portal/src/ui/pagination/Pagination.jsx +23 -0
  176. package/bootstrap/services/portal/src/ui/repeater/Repeater.jsx +29 -0
  177. package/bootstrap/services/portal/src/ui/saveButton/SaveButton.jsx +69 -0
  178. package/bootstrap/services/portal/src/ui/saveButton/saveButton.less +0 -0
  179. package/bootstrap/services/rabbit-mq/Dockerfile.prod +9 -0
  180. package/bootstrap/services/user/Dockerfile +23 -0
  181. package/bootstrap/services/user/Dockerfile.prod +37 -0
  182. package/bootstrap/services/user/README.md +8 -0
  183. package/bootstrap/services/user/package.json +16 -0
  184. package/bootstrap/services/user/src/app.js +45 -0
  185. package/bootstrap/services/user/src/commands/session.handler.js +23 -0
  186. package/bootstrap/services/user/src/commands/user.handler.js +286 -0
  187. package/bootstrap/services/user/src/config.js +73 -0
  188. package/bootstrap/services/user/src/controllers/http.controller.js +156 -0
  189. package/bootstrap/services/user/src/controllers/message.controller.js +51 -0
  190. package/bootstrap/services/user/src/db/migrations/01-init.js +50 -0
  191. package/bootstrap/services/user/src/db/migrations/02-user-service-init.js +63 -0
  192. package/bootstrap/services/user/src/db/migrations/03-unauth-sessions.js +43 -0
  193. package/bootstrap/services/user/src/db/seeders/development/01-root-user.js +29 -0
  194. package/bootstrap/services/user/src/db/seeders/development/02-portal-admin-user.js +27 -0
  195. package/bootstrap/services/user/src/db/seeders/production/01-root-user.js +29 -0
  196. package/bootstrap/services/user/src/policies/user.policy.js +81 -0
  197. package/bootstrap/services/user/src/schema/user.schema.js +69 -0
  198. package/bootstrap/services/user/src/services/authentication.service.js +127 -0
  199. package/bootstrap/services/user/src/services/session.service.js +58 -0
  200. package/bootstrap/services/user/src/services/user.service.js +130 -0
  201. package/bootstrap/services/user/src/tests/user.test.js +126 -0
  202. package/package.json +3 -7
  203. package/src/agent/agent.client.js +28 -0
  204. package/src/agent/commands.js +48 -0
  205. package/src/cli.js +30 -0
  206. package/src/config.js +8 -0
  207. package/src/control/commands.js +156 -0
  208. package/src/control/control.client.js +127 -0
  209. package/src/dev/commands.js +71 -0
  210. package/src/dev/dev.service.js +320 -0
  211. package/src/engine/infra.js +142 -0
  212. package/src/helpers/helpers.js +63 -0
  213. package/src/profiles/command.js +170 -0
  214. package/src/profiles/profiles.client.js +101 -0
  215. package/src/scaffolder/commands.js +123 -0
  216. package/src/scaffolder/scaffolder.handler.js +252 -0
  217. package/src/services/client.js +174 -0
  218. package/templates/service/Dockerfile.hbs +20 -0
  219. package/templates/service/app.js.hbs +38 -0
  220. package/templates/service/commands/{{serviceName}}.handler.js.hbs +97 -0
  221. package/templates/service/config.js.hbs +48 -0
  222. package/templates/service/controllers/http.controller.js.hbs +87 -0
  223. package/templates/service/controllers/message.controller.js.hbs +51 -0
  224. package/templates/service/db/migrations/01-init.js.hbs +50 -0
  225. package/templates/service/db/migrations/02-{{lowerCase serviceName}}-service-init.js.hbs +23 -0
  226. package/templates/service/package.json.hbs +18 -0
  227. package/templates/service/policies/{{serviceName}}.policy.js.hbs +49 -0
  228. package/templates/service/schema/{{serviceName}}.schema.js.hbs +14 -0
  229. package/templates/service/services/{{serviceName}}.service.js.hbs +32 -0
  230. package/dist/cli.js +0 -18
@@ -0,0 +1,156 @@
1
+ // control/commands.js
2
+ import { Command } from 'commander';
3
+ import { control } from './control.client.js';
4
+ import inquirer from 'inquirer';
5
+
6
+ export function registerControlCommands(program) {
7
+ const controlCmd = new Command('control').description('🎛️ Control related commands');
8
+
9
+ controlCmd
10
+ .command('migrate')
11
+ .description('📦 Run migrations')
12
+ .option('-a, --all', 'Run all migrations')
13
+ .option('-s, --service <service>', 'Run migrations for a specific service')
14
+ .option('-m, --migration <migration>', 'Run a specific migration (must also include service)')
15
+ .action(async (options) => {
16
+ let response = {};
17
+
18
+ if (options.all) {
19
+ console.log('📦 Running all migrations...');
20
+ response = await control.runMigrations();
21
+ } else {
22
+ response.error = 'No options provided';
23
+ }
24
+
25
+ if (response.error) {
26
+ console.error('❌ ' + response.error);
27
+ } else {
28
+ console.log('✅ ' + response.message);
29
+ }
30
+ });
31
+
32
+ controlCmd
33
+ .command('seed')
34
+ .description('🌱 Run seeders')
35
+ .option('-a, --all', 'Run all seeders')
36
+ .option('-s, --service <service>', 'Run seeders for a specific service')
37
+ .option('-S, --seed <seed>', 'Run a specific seed (must also include service)')
38
+ .action(async (options) => {
39
+ let response = {};
40
+
41
+ if (options.all) {
42
+ console.log('🌱 Running all seeders...');
43
+ response = await control.runSeeders();
44
+ } else {
45
+ response.error = 'No options provided';
46
+ }
47
+
48
+ if (response.error) {
49
+ console.error('❌ ' + response.error);
50
+ } else {
51
+ console.log('✅ ' + response.message);
52
+ }
53
+ });
54
+
55
+ controlCmd
56
+ .command('reset')
57
+ .description('📦 Full Reset. Drop all data to return to initial state with root user only (development mode only)')
58
+ .option('-a, --all', 'Reset everything')
59
+ .option('-s, --service <service>', 'Run migrations for a specific service')
60
+ .action(async (options) => {
61
+ let response = {};
62
+
63
+ if (options.all) {
64
+ const confirmation = await inquirer.prompt([
65
+ {
66
+ type: 'confirm',
67
+ name: 'confirmReset',
68
+ message: '⚠️ Are you sure you want to run a full reset? This will delete all data.',
69
+ default: false,
70
+ },
71
+ ]);
72
+
73
+ if (!confirmation.confirmReset) {
74
+ console.log('❌ Reset cancelled.');
75
+ return;
76
+ }
77
+
78
+ console.log('📦 Running full reset...');
79
+ response = await control.runReset();
80
+ } else {
81
+ response.error = 'No options provided';
82
+ }
83
+
84
+ if (response.error) {
85
+ console.error('❌ ' + response.error);
86
+ } else {
87
+ console.log('✅ ' + response.message);
88
+ }
89
+ });
90
+
91
+ controlCmd
92
+ .command('get-tasks')
93
+ .description('🛠️ Get tasks')
94
+ .option('-s, --status <status>', 'Task status (default: scheduled)')
95
+ .action(async (options) => {
96
+ if (!options.status) options.status = 'scheduled';
97
+
98
+ console.log('🛠️ Getting tasks with status: ' + options.status);
99
+ const response = await control.getTasks(options.status);
100
+
101
+ if (response.error) {
102
+ console.error('❌ ' + response.error);
103
+ } else {
104
+ console.log(JSON.stringify(response.tasks, null, 2));
105
+ console.log('✅ Got tasks ' + response.tasks.length);
106
+ }
107
+ });
108
+
109
+ controlCmd
110
+ .command('execute-tasks')
111
+ .description('🛠️ Execute tasks')
112
+ .option('-s, --status <status>', 'Task status (default: scheduled)')
113
+ .action(async (options) => {
114
+ if (!options.status) options.status = 'scheduled';
115
+
116
+ console.log('🛠️ Executing tasks with status: ' + options.status);
117
+ const response = await control.handleTaskBatch(options.status);
118
+
119
+ if (response.error) {
120
+ console.error('❌ ' + response.error);
121
+ } else {
122
+ console.log('✅ ' + response.message);
123
+ }
124
+ });
125
+
126
+ controlCmd
127
+ .command('delete-task')
128
+ .description('🛠️ Delete task')
129
+ .requiredOption('-id, --id <id>', 'Task ID')
130
+ .action(async (options) => {
131
+ console.log('🛠️ Deleting task with id: ' + options.id);
132
+ const response = await control.deleteTask(options.id);
133
+
134
+ if (response.error) {
135
+ console.error('❌ ' + response.error);
136
+ } else {
137
+ console.log('✅ ' + response.numDeletedTasks + ' ' + response.message);
138
+ }
139
+ });
140
+
141
+ controlCmd
142
+ .command('delete-failed-tasks')
143
+ .description('🛠️ Delete failed tasks')
144
+ .action(async () => {
145
+ console.log('🛠️ Deleting failed tasks');
146
+ const response = await control.deleteFailedTasks();
147
+
148
+ if (response.error) {
149
+ console.error('❌ ' + response.error);
150
+ } else {
151
+ console.log('✅ ' + response.numDeletedTasks + ' ' + response.message);
152
+ }
153
+ });
154
+
155
+ program.addCommand(controlCmd);
156
+ }
@@ -0,0 +1,127 @@
1
+ import client, { initAxiosClient } from '../services/client.js';
2
+ import { profiles } from "../profiles/profiles.client.js";
3
+
4
+ export const control = {
5
+
6
+ // Run all migrations
7
+ runMigrations: async () => {
8
+ try {
9
+ await initAxiosClient();
10
+ const response = await client.post('/control/migrations', {});
11
+
12
+ if (response.status !== 200) {
13
+ throw new Error("Failed to run migrations");
14
+ }
15
+
16
+ return response.data;
17
+ } catch (error) {
18
+ return {error: "Migrations failed: " + error};
19
+ }
20
+ },
21
+
22
+ // Run seeders
23
+ runSeeders: async () => {
24
+ try {
25
+ await initAxiosClient();
26
+ const response = await client.post('/control/seeders', {});
27
+
28
+ if (response.status !== 200) {
29
+ throw new Error("Failed to run seeders");
30
+ }
31
+
32
+ return response.data;
33
+ } catch (error) {
34
+ return {error: "Seeders failed: " + error};
35
+ }
36
+ },
37
+
38
+ // Run full reset
39
+ runReset: async () => {
40
+ try {
41
+ await initAxiosClient();
42
+ const response = await client.post('/control/reset', {});
43
+
44
+ if (response.status !== 200) {
45
+ throw new Error("Failed to run reset");
46
+ }
47
+
48
+ return response.data;
49
+ } catch (error) {
50
+ return {error: "Reset failed: " + error};
51
+ }
52
+ },
53
+
54
+ // Get tasks
55
+ getTasks: async (status) => {
56
+ try {
57
+ await initAxiosClient();
58
+ const response = await client.get(`/tasks/?status=${status}`);
59
+
60
+ if (response.status !== 200) {
61
+ throw new Error("Failed to get tasks");
62
+ }
63
+
64
+ return response.data;
65
+ } catch (error) {
66
+ return {error: "Failed to get tasks: " + error};
67
+ }
68
+ },
69
+
70
+ // Execute tasks
71
+ handleTaskBatch: async (status) => {
72
+ try {
73
+ await initAxiosClient();
74
+ console.log(`Executing task batch...`);
75
+ const response = await client.post(`/tasks/execute-batch`, {
76
+ status: status
77
+ });
78
+
79
+ if (response.status !== 200) {
80
+ throw new Error("Failed to execute task batch");
81
+ }
82
+
83
+ if (response.data.errors && response.data.errors.length > 0) {
84
+ throw new Error("Task batch run but had errors: " + JSON.stringify(response.data.errors, null, 2));
85
+ }
86
+
87
+ return response.data;
88
+ } catch (error) {
89
+ return {error: "Task execution failed: " + error};
90
+ }
91
+
92
+ },
93
+
94
+ // Delete task by id
95
+ deleteTask: async (id) => {
96
+ try {
97
+ await initAxiosClient();
98
+ console.log(`Deleting task with id ${id}...`);
99
+ const response = await client.delete(`/tasks/${id}`);
100
+
101
+ if (response.status !== 200) {
102
+ throw new Error("Failed to delete task");
103
+ }
104
+
105
+ return response.data;
106
+ } catch (error) {
107
+ return {error: "Failed to delete task: " + error};
108
+ }
109
+ },
110
+
111
+ // Delete failed tasks
112
+ deleteFailedTasks: async () => {
113
+ try {
114
+ await initAxiosClient();
115
+ console.log(`Deleting failed tasks...`);
116
+ const response = await client.post(`/tasks/delete-failed`, {});
117
+
118
+ if (response.status !== 200) {
119
+ throw new Error("Failed to delete failed tasks");
120
+ }
121
+
122
+ return response.data;
123
+ } catch (error) {
124
+ return {error: "Failed to delete failed tasks: " + error};
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,71 @@
1
+ // control/commands.js
2
+ import { Command, Option } from 'commander';
3
+ import { profiles } from '../profiles/profiles.client.js';
4
+ import { up, down } from './dev.service.js';
5
+ import path from 'path';
6
+
7
+ export function registerDevCommands(program) {
8
+ const devCmd = new Command('dev').description('🛠️ Start Gnar Engine Development Environment');
9
+
10
+ devCmd
11
+ .command('up')
12
+ .description('🛠️ Up Development Containers')
13
+ .option('-b, --build', 'Ruild without cache')
14
+ .option('-d, --detach', 'Run containers in background')
15
+ .addOption(new Option('--core-dev').hideHelp())
16
+ .action(async (options) => {
17
+ let response = {};
18
+
19
+ // Get active profile directory
20
+ const { profile: activeProfile } = profiles.getActiveProfile();
21
+
22
+ if (!activeProfile) {
23
+ response.error = 'No active profile found';
24
+ return;
25
+ }
26
+
27
+ // Change to the active profile directory
28
+ const projectDir = activeProfile.PROJECT_DIR;
29
+
30
+ try {
31
+ up({
32
+ projectDir: projectDir,
33
+ build: options.build || false,
34
+ detach: options.detach || false,
35
+ coreDev: options.coreDev || false
36
+ });
37
+ } catch (err) {
38
+ console.error("❌ Error running containers:", err.message);
39
+ process.exit(1);
40
+ }
41
+ });
42
+
43
+ devCmd
44
+ .command('down')
45
+ .description('🛠️ Down Development Containers')
46
+ .option('-a, --all-containers', 'Stop all running containers (not just Gnar Engine ones)')
47
+ .action(async (options) => {
48
+ // Get active profile directory
49
+ const { profile: activeProfile } = profiles.getActiveProfile();
50
+
51
+ if (!activeProfile) {
52
+ console.error('No active profile found');
53
+ return;
54
+ }
55
+
56
+ // Change to the active profile directory
57
+ const projectDir = activeProfile.PROJECT_DIR;
58
+
59
+ try {
60
+ down({
61
+ projectDir: projectDir,
62
+ allContainers: options.allContainers || false
63
+ });
64
+ } catch (err) {
65
+ console.error("❌ Error running containers:", err.message);
66
+ process.exit(1);
67
+ }
68
+ });
69
+
70
+ program.addCommand(devCmd);
71
+ }
@@ -0,0 +1,320 @@
1
+ import { spawn } from "child_process";
2
+ import Docker from "dockerode";
3
+ import process from "process";
4
+ import fs from "fs/promises";
5
+ import path from "path";
6
+ import yaml from "js-yaml";
7
+ import { gnarEngineCliConfig } from "../config.js";
8
+
9
+ const docker = new Docker();
10
+
11
+ /**
12
+ * Start the application locally
13
+ * - Creates a dynamic docker-compose file based on deploy.localdev.yml and secrets.localdev.yml
14
+ * - Runs docker-compose up
15
+ *
16
+ * @param {object} options
17
+ * @param {string} options.projectDir - The project directory
18
+ * @param {boolean} [options.build=false] - Whether to re-build images
19
+ * @param {boolean} [options.detached=false] - Whether to run containers in background
20
+ * @param {boolean} [options.coreDev=false] - Whether to run in core development mode (requires access to core source)
21
+ */
22
+ export async function up({ projectDir, build = false, detached = false, coreDev = false }) {
23
+ // parse config
24
+ const configPath = path.join(projectDir, "deploy.localdev.yml");
25
+ const secretsPath = path.join(projectDir, "secrets.localdev.yml");
26
+
27
+ const parsedConfig = yaml.load(await fs.readFile(configPath, "utf8"));
28
+ const parsedSecrets = yaml.load(await fs.readFile(secretsPath, "utf8"));
29
+
30
+ // assert .gnarengine directory in projectDir
31
+ const gnarHiddenDir = path.join(projectDir, ".gnarengine");
32
+ await assertGnarEngineHiddenDir(gnarHiddenDir);
33
+
34
+ // create nginx.conf dynamically from configPath
35
+ const nginxConfPath = path.join(gnarHiddenDir, "nginx", "nginx.conf");
36
+ const nginxConf = await createDynamicNginxConf({
37
+ config: parsedConfig.config
38
+ });
39
+ await fs.writeFile(nginxConfPath, nginxConf);
40
+
41
+ // create docker-compose.yml dynamically from parsed config and secrets
42
+ const dockerComposePath = path.join(gnarHiddenDir, "docker-compose.dev.yml");
43
+ const dockerCompose = await createDynamicDockerCompose({
44
+ config: parsedConfig.config,
45
+ secrets: parsedSecrets,
46
+ gnarHiddenDir: gnarHiddenDir,
47
+ projectDir: projectDir,
48
+ coreDev: coreDev
49
+ });
50
+ await fs.writeFile(dockerComposePath, yaml.dump(dockerCompose));
51
+
52
+ // up docker-compose
53
+ const args = ["-f", dockerComposePath, "up"];
54
+
55
+ if (build) {
56
+ args.push("--build");
57
+ }
58
+
59
+ if (detached) {
60
+ args.push("-d");
61
+ }
62
+
63
+ const processRef = spawn(
64
+ "docker-compose",
65
+ args,
66
+ {
67
+ cwd: projectDir,
68
+ stdio: "inherit",
69
+ shell: "/bin/sh"
70
+ }
71
+ );
72
+
73
+ // handle exit
74
+ const exitCode = await new Promise((resolve) => {
75
+ processRef.on("close", resolve);
76
+ });
77
+
78
+ if (exitCode !== 0) {
79
+ throw new Error(`docker-compose up exited with code ${exitCode}`);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Down the containers
85
+ *
86
+ * @param {object} options
87
+ * @param {string} options.projectDir - The project directory
88
+ * @param {boolean} [options.allContainers=false] - Stop all running containers (not just Gnar Engine ones)
89
+ */
90
+ export async function down({ projectDir, allContainers = false }) {
91
+ // list all containers
92
+ const containers = await docker.listContainers();
93
+
94
+ // filter containers by image name
95
+ if (!allContainers) {
96
+ const containers = containers.filter(c => c.Image.includes("ge-dev"));
97
+ }
98
+
99
+ if (containers.length === 0) {
100
+ console.log("No running containers found.");
101
+ return;
102
+ }
103
+
104
+ console.log('Stopping containers...');
105
+ containers.forEach(c => {
106
+ console.log(` - ${c.Names[0]} (${c.Id})`);
107
+ });
108
+
109
+ // stop each container
110
+ await Promise.all(
111
+ containers.map(c => {
112
+ const container = docker.getContainer(c.Id);
113
+ return container.stop().catch(err => {
114
+ console.error(`Failed to stop ${c.Names[0]}: ${err.message}`);
115
+ });
116
+ })
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Create dynamic nginx.conf file for running application locally
122
+ *
123
+ * @param {object} config
124
+ * @param {string} outputPath - where to write nginx.conf
125
+ */
126
+ export async function createDynamicNginxConf({ config, outputPath }) {
127
+ // Start with the static parts of nginx.conf
128
+ let nginxConf = `
129
+ http {
130
+ server {
131
+ listen 80;
132
+ server_name ${config.namespace};
133
+ `;
134
+
135
+ // Loop over each service
136
+ for (const service of config.services || []) {
137
+ const serviceName = service.name;
138
+ const paths = service.listener_rules?.paths || [];
139
+ const containerPort = service.ports && service.ports.length > 0 ? service.ports[0].split(':')[1] : '3000';
140
+
141
+ for (const p of paths) {
142
+ // normalize path without trailing slash
143
+ const cleanPath = p.replace(/\/+$/, '');
144
+
145
+ // build location block
146
+ nginxConf += `
147
+ # ${serviceName} service
148
+ location ${cleanPath} {
149
+ rewrite ^${cleanPath}$ ${cleanPath}/ break;
150
+ proxy_pass http://${serviceName}-service:${containerPort}${cleanPath};
151
+ }
152
+ `;
153
+ }
154
+ }
155
+
156
+ // Close server and http blocks
157
+ nginxConf += `
158
+ }
159
+ }
160
+ `;
161
+
162
+ return nginxConf;
163
+ }
164
+
165
+ /**
166
+ * Create dynamic docker compose file for running application locally
167
+ *
168
+ * @param {object} config
169
+ * @param {object} secrets
170
+ * @param {string} gnarHiddenDir
171
+ * @param {string} projectDir
172
+ * @param {boolean} coreDev - Whether to volume mount the core source code
173
+ */
174
+ async function createDynamicDockerCompose({ config, secrets, gnarHiddenDir, projectDir, coreDev = false }) {
175
+ let mysqlPortsCounter = 3306;
176
+ let mongoPortsCounter = 27017;
177
+ const services = {};
178
+
179
+ // nginx
180
+ services['nginx'] = {
181
+ image: 'nginx:latest',
182
+ container_name: `ge-${config.environment}-${config.namespace}-nginx`,
183
+ ports: [
184
+ "80:80",
185
+ "443:443"
186
+ ],
187
+ volumes: [
188
+ `${gnarHiddenDir}/nginx/nginx.conf:/etc/nginx/nginx.conf`
189
+ ],
190
+ restart: 'always'
191
+ }
192
+
193
+ // rabbit
194
+ services['rabbitmq'] = {
195
+ image: 'rabbitmq:management',
196
+ container_name: `ge-${config.environment}-${config.namespace}-rabbitmq`,
197
+ ports: [
198
+ "5672:5672",
199
+ "15672:15672"
200
+ ],
201
+ environment: {
202
+ RABBITMQ_DEFAULT_USER: secrets.global.RABBITMQ_USER || '',
203
+ RABBITMQ_DEFAULT_PASS: secrets.global.RABBITMQ_PASS || ''
204
+ },
205
+ restart: 'always'
206
+ }
207
+
208
+ // services
209
+ for (const svc of config.services) {
210
+
211
+ // env variables
212
+ const serviceEnvVars = secrets.services?.[svc.name] || {};
213
+ const localisedServiceEnvVars = {};
214
+
215
+ for (const [key, value] of Object.entries(serviceEnvVars)) {
216
+ localisedServiceEnvVars[svc.name.toUpperCase() + '_' + key] = value;
217
+ }
218
+
219
+ const env = {
220
+ ...(secrets.global || {}),
221
+ ...(localisedServiceEnvVars || {})
222
+ };
223
+
224
+ // service block
225
+ services[`${svc.name}-service`] = {
226
+ container_name: `ge-${config.environment}-${config.namespace}-${svc.name}`,
227
+ image: `ge-${config.environment}-${config.namespace}-${svc.name}`,
228
+ build: {
229
+ context: projectDir,
230
+ dockerfile: `./services/${svc.name}/Dockerfile`
231
+ },
232
+ command: svc.command || [],
233
+ environment: env,
234
+ ports: svc.ports || [],
235
+ depends_on: svc.depends_on || [],
236
+ volumes: [
237
+ `${projectDir}/services/${svc.name}/src:/usr/gnar_engine/app/src`
238
+ ],
239
+ restart: 'always'
240
+ };
241
+
242
+ // add the core source code mount if in coreDeve mode
243
+ if (coreDev) {
244
+ services[`${svc.name}-service`].volumes.push(`../../../gnar-engine-core:${gnarEngineCliConfig.corePath}`);
245
+ }
246
+
247
+ // add a mysql instance if required
248
+ if (
249
+ serviceEnvVars.MYSQL_HOST &&
250
+ serviceEnvVars.MYSQL_DATABASE &&
251
+ serviceEnvVars.MYSQL_USER &&
252
+ serviceEnvVars.MYSQL_PASSWORD &&
253
+ serviceEnvVars.MYSQL_RANDOM_ROOT_PASSWORD
254
+ ) {
255
+ services[`${svc.name}-db`] = {
256
+ container_name: `ge-${config.environment}-${config.namespace}-${svc.name}-db`,
257
+ image: 'mysql',
258
+ ports: [
259
+ `${mysqlPortsCounter}:${mysqlPortsCounter}`
260
+ ],
261
+ restart: 'always',
262
+ environment: {
263
+ MYSQL_HOST: serviceEnvVars.MYSQL_HOST,
264
+ MYSQL_DATABASE: serviceEnvVars.MYSQL_DATABASE,
265
+ MYSQL_USER: serviceEnvVars.MYSQL_USER,
266
+ MYSQL_PASSWORD: serviceEnvVars.MYSQL_PASSWORD,
267
+ MYSQL_RANDOM_ROOT_PASSWORD: serviceEnvVars.MYSQL_RANDOM_ROOT_PASSWORD,
268
+ },
269
+ volumes: [
270
+ `${gnarHiddenDir}/data/${svc.name}-db-data:/var/lib/mysql`
271
+ ]
272
+ };
273
+
274
+ // increment mysql port for next service as required
275
+ mysqlPortsCounter++;
276
+ }
277
+
278
+ // add a mongodb instance if required
279
+ if (
280
+ serviceEnvVars.MONGO_URL &&
281
+ serviceEnvVars.MONGO_ROOT_PASSWORD &&
282
+ serviceEnvVars.MONGO_USER &&
283
+ serviceEnvVars.MONGO_PASSWORD
284
+ ) {
285
+ services[`${svc.name}-mongo`] = {
286
+ container_name: `ge-${config.environment}-${config.namespace}-${svc.name}-mongo`,
287
+ image: `ge-${config.environment}-${config.namespace}-${svc.name}-mongo`,
288
+ ports: [
289
+ `${mongoPortsCounter}:${mongoPortsCounter}`
290
+ ],
291
+ restart: 'always',
292
+ 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,
297
+ },
298
+ volumes: [
299
+ `${gnarHiddenDir}/Data/${svc.name}-mongo-data:/data/db`
300
+ ]
301
+ };
302
+
303
+ // increment mongo port for next service as required
304
+ mongoPortsCounter++;
305
+ }
306
+
307
+ }
308
+
309
+ return {
310
+ version: "3.9",
311
+ services
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Assert the .gnarengine directory in the project directory
317
+ */
318
+ async function assertGnarEngineHiddenDir(gnarHiddenDir) {
319
+ await fs.mkdir(gnarHiddenDir, { recursive: true });
320
+ }