@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,142 @@
1
+
2
+ import {
3
+ EC2Client,
4
+ RunInstancesCommand,
5
+ CreateVolumeCommand,
6
+ AttachVolumeCommand,
7
+ DescribeInstancesCommand,
8
+ } from "@aws-sdk/client-ec2";
9
+ import { profiles } from '../profiles/profiles.client';
10
+
11
+
12
+ export const engineInfra = {
13
+
14
+ ec2: null,
15
+
16
+ initialiseClient: ({ availabilityZone }) => {
17
+ if (!engineInfra.ec2) {
18
+ engineInfra.ec2 = new EC2Client({ region: availabilityZone });
19
+ }
20
+
21
+ return engineInfra.ec2;
22
+ },
23
+
24
+ initialiseInstance: async ({ availabilityZone, instanceType, ami, size }) => {
25
+ try {
26
+ engineInfra.ec2 = await new EC2Client({
27
+ region: availabilityZone
28
+ });
29
+
30
+ const volumeId = await engineInfra.createVolume({
31
+ availabilityZone,
32
+ size
33
+ });
34
+
35
+ const instanceId = await engineInfra.createInstance({
36
+ ami,
37
+ instanceType,
38
+ availabilityZone,
39
+ volumeId
40
+ });
41
+
42
+ profiles.setEngineInstanceId({
43
+ instanceId
44
+ });
45
+
46
+ await engineInfra.waitForInstanceRunning({
47
+ instanceId
48
+ })
49
+ } catch (error) {
50
+ console.error('Error initialising gnar engine agent instance', error);
51
+ }
52
+ },
53
+
54
+ createVolume: async ({availabilityZone, size}) => {
55
+ const params = {
56
+ AvailabilityZone: availabilityZone,
57
+ Size: size || 50,
58
+ VolumeType: "gp3",
59
+ };
60
+ const data = await engineinfra.ec2.send(new CreateVolumeCommand(params));
61
+ return data.VolumeId;
62
+ },
63
+
64
+ createInstance: async ({ami, instanceType, availabilityZone, volumeId}) => {
65
+ const params = {
66
+ ImageId: ami,
67
+ InstanceType: instanceType || "g5.xlarge",
68
+ MinCount: 1,
69
+ MaxCount: 1,
70
+ Placement: {
71
+ AvailabilityZone: availabilityZone
72
+ },
73
+ BlockDeviceMappings: [{
74
+ DeviceName: "/dev/nvme1n1", // Additional volume mount
75
+ Ebs: { VolumeId: volumeId, DeleteOnTermination: true },
76
+ }],
77
+ TagSpecifications: [{
78
+ ResourceType: "instance",
79
+ Tags: [{ Key: "Name", Value: "gnar-engine-agent" }],
80
+ }]
81
+ };
82
+
83
+ const data = await engineInfra.ec2.send(new RunInstancesCommand(params));
84
+ return data.Instances[0].InstanceId;
85
+ },
86
+
87
+ waitForInstanceRunning: async ({instanceId}) => {
88
+ while (true) {
89
+ const desc = await engineInfra.ec2.send(
90
+ new DescribeInstancesCommand({ InstanceIds: [instanceId] })
91
+ );
92
+
93
+ const state = desc.Reservations[0].Instances[0].State.Name;
94
+ console.log(`Instance ${instanceId} is ${state}`);
95
+
96
+ if (state === "running") {
97
+ console.log('Instance ready!');
98
+ break;
99
+ }
100
+
101
+ await new Promise((r) => setTimeout(r, 5000));
102
+ }
103
+ },
104
+
105
+ startInstanceSession: async ({timeout, instanceId}) => {
106
+ // start instance
107
+ engineInfra.initialiseClient({ region });
108
+
109
+ // Start instance
110
+ await engineInfra.ec2.send(new StartInstancesCommand({
111
+ InstanceIds: [instanceId],
112
+ }));
113
+ console.log(`Instance ${instanceId} is starting...`);
114
+
115
+ // Wait for running state (reuse your existing method)
116
+ await engineInfra.waitForInstanceRunning({ instanceId });
117
+
118
+ // tag instance with shutdown timestamp
119
+ engineInfra.tagInstanceWithShutdown({
120
+ timeout,
121
+ instanceId
122
+ });
123
+ },
124
+
125
+ tagInstanceWithShutdown: async ({timeout, instanceId}) => {
126
+ const now = Math.floor(Date.now() / 1000);
127
+ const shutdownTimestamp = now + timeout * 60;
128
+
129
+ await engineInfra.ec2.send(new CreateTagsCommand({
130
+ Resources: [instanceId],
131
+ Tags: [{
132
+ Key: "shutdown-timestamp",
133
+ Value: shutdownTimestamp.toString(),
134
+ }],
135
+ }));
136
+ },
137
+
138
+ addCrontabShutdownCheck: async ({instanceId}) => {
139
+
140
+
141
+ }
142
+ }
@@ -0,0 +1,63 @@
1
+
2
+ /**
3
+ * CLI helper functions
4
+ */
5
+ export const helpers = {
6
+
7
+ assertTrailingSlash: (path) => {
8
+ if (!path.endsWith('/')) {
9
+ return path + '/';
10
+ }
11
+ return path;
12
+ },
13
+
14
+ pascalCase: (str) => {
15
+ return str
16
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
17
+ .replace(/^(.)/, (_, c) => c.toUpperCase());
18
+ },
19
+
20
+ pascalCasePlural: (str) => {
21
+ const plural = helpers.lowerCasePlural(str);
22
+ return helpers.pascalCase(plural);
23
+ },
24
+
25
+ lowerCase: (str) => {
26
+ return str.toLowerCase();
27
+ },
28
+
29
+ upperCase: (str) => {
30
+ return str.toUpperCase();
31
+ },
32
+
33
+ plural: (str) => {
34
+ if (str.endsWith('y')) {
35
+ return str.slice(0, -1).toLowerCase() + 'ies';
36
+ }
37
+ return str + 's';
38
+ },
39
+
40
+ lowerCasePlural: (str) => {
41
+ // A very simple pluralization. For production, use a proper pluralization lib.
42
+ if (str.endsWith('y')) {
43
+ return str.slice(0, -1).toLowerCase() + 'ies';
44
+ }
45
+ return str.toLowerCase() + 's';
46
+ },
47
+
48
+ capitaliseFirstLetter: (str) => {
49
+ if (typeof str !== 'string' || str.length === 0) {
50
+ return str;
51
+ }
52
+ return str.charAt(0).toUpperCase() + str.slice(1);
53
+ },
54
+
55
+ generateRandomString: (length) => {
56
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
57
+ let result = '';
58
+ for (let i = 0; i < length; i++) {
59
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
60
+ }
61
+ return result;
62
+ }
63
+ }
@@ -0,0 +1,170 @@
1
+ // init/commands.js
2
+ import { Command } from 'commander';
3
+ import { profiles } from './profiles.client.js';
4
+ import inquirer from 'inquirer';
5
+ import fs from 'fs';
6
+ import os from 'os';
7
+ import path from 'path';
8
+
9
+
10
+ export function registerProfileCommand(program) {
11
+ const profile = new Command('profile').description('👤 Manage CLI profiles');
12
+
13
+ // profile get all
14
+ profile
15
+ .command('get-all')
16
+ .description('List all profiles')
17
+ .action(() => {
18
+ const config = profiles.getAllProfiles();
19
+
20
+ if (!config || !config.profiles || Object.keys(config.profiles).length === 0) {
21
+ console.error('No profiles found. Please create a profile using gnar profile create');
22
+ return;
23
+ }
24
+
25
+ Object.keys(config.profiles).forEach((p) => {
26
+ if (config.activeProfile == p) {
27
+ console.log('- ' + p + ' (active)');
28
+ } else {
29
+ console.log('- ' + p);
30
+ }
31
+ });
32
+ });
33
+
34
+ // profile get active
35
+ profile
36
+ .command('get-active')
37
+ .description('Show the active profile')
38
+ .action(() => {
39
+ try {
40
+ const {name, profile} = profiles.getActiveProfile();
41
+ console.log(`Active profile: ${name}`);
42
+ } catch (error) {
43
+ console.error('No active profile found. Please set one using `gnar profile set-active <profileName>`');
44
+ }
45
+ });
46
+
47
+ // profile set active <profileName>
48
+ profile
49
+ .command('set-active')
50
+ .description('Select active profile')
51
+ .action(() => {
52
+ try {
53
+ const allProfiles = profiles.getAllProfiles();
54
+ const choices = Object.keys(allProfiles.profiles).map((p) => {
55
+ if (allProfiles.activeProfile == p) {
56
+ return { name: p + ' (active)', value: p };
57
+ } else {
58
+ return { name: p, value: p };
59
+ }
60
+ });
61
+
62
+ inquirer.prompt([
63
+ {
64
+ type: 'list',
65
+ name: 'profileName',
66
+ message: 'Select a profile to set as active:',
67
+ choices: choices
68
+ }
69
+ ]).then((answers) => {
70
+ const profileName = answers.profileName;
71
+ profiles.setActiveProfile({
72
+ profileName: profileName
73
+ });
74
+ console.log(`✅ Profile "${profileName}" set as active.`);
75
+ });
76
+
77
+ return;
78
+ } catch (error) {
79
+ console.error(`❌ Error setting active profile: ${error.message}`);
80
+ }
81
+ });
82
+
83
+ // profile create
84
+ profile
85
+ .command('create')
86
+ .description('Create a new profile')
87
+ .action(async () => {
88
+ const answers = await inquirer.prompt([
89
+ { name: 'profile', message: 'Profile name', default: 'mywebsite:local' },
90
+ { name: 'CLI_API_URL', message: 'API URL', default: 'http://localhost' },
91
+ { name: 'CLI_API_USERNAME', message: 'API Username', default: 'gnarlyroot' },
92
+ { name: 'CLI_API_KEY', message: 'API Key' },
93
+ { name: 'PROJECT_DIR', message: 'Project directory', default: process.cwd() },
94
+ { name: 'AWS_ACCESS_KEY_ID', message: 'AWS Access Key' },
95
+ { name: 'AWS_SECRET_ACCESS_KEY', message: 'AWS Secret Access Key' },
96
+ { name: 'AWS_REGION', message: 'AWS Region' },
97
+ {
98
+ type: 'confirm',
99
+ name: 'setActive',
100
+ message: 'Set this profile as active?',
101
+ default: true,
102
+ },
103
+ ]);
104
+
105
+ const profileName = answers.profile;
106
+ const config = {
107
+ CLI_API_URL: answers.CLI_API_URL,
108
+ CLI_API_USERNAME: answers.CLI_API_USERNAME,
109
+ CLI_API_KEY: answers.CLI_API_KEY,
110
+ PROJECT_DIR: answers.PROJECT_DIR || process.cwd(),
111
+ AWS_ACCESS_KEY_ID: answers.AWS_ACCESS_KEY_ID,
112
+ AWS_SECRET_ACCESS_KEY: answers.AWS_SECRET_ACCESS_KEY,
113
+ AWS_REGION: answers.AWS_REGION
114
+ };
115
+
116
+ // save profile to config file
117
+ profiles.createProfile({
118
+ profileName: profileName,
119
+ config: config
120
+ });
121
+
122
+ // set active profile if required
123
+ if (answers.setActive) {
124
+ console.log('setting active', );
125
+ profiles.setActiveProfile({
126
+ profileName: profileName
127
+ });
128
+ }
129
+ });
130
+
131
+ // update profile
132
+ profile
133
+ .command('update <profileName>')
134
+ .description('Update an existing profile')
135
+ .action(async (profileName) => {
136
+ const allProfiles = profiles.getAllProfiles();
137
+
138
+ if (!allProfiles.profiles[profileName]) {
139
+ console.error(`Profile "${profileName}" not found.`);
140
+ return;
141
+ }
142
+
143
+ const currentProfile = allProfiles.profiles[profileName];
144
+ const answers = await inquirer.prompt([
145
+ { name: 'CLI_API_URL', message: 'API URL', default: currentProfile.CLI_API_URL },
146
+ { name: 'CLI_API_USERNAME', message: 'API Username', default: currentProfile.CLI_API_USERNAME },
147
+ { name: 'CLI_API_KEY', message: 'API Key', default: currentProfile.CLI_API_KEY },
148
+ { name: 'PROJECT_DIR', message: 'Project directory', default: currentProfile.PROJECT_DIR || process.cwd() },
149
+ { name: 'AWS_ACCESS_KEY_ID', message: 'AWS Access Key', default: currentProfile.AWS_ACCESS_KEY_ID },
150
+ { name: 'AWS_SECRET_ACCESS_KEY', message: 'AWS Secret Access Key', default: currentProfile.AWS_SECRET_ACCESS_KEY },
151
+ { name: 'AWS_REGION', message: 'AWS Region', default: currentProfile.AWS_REGION }
152
+ ]);
153
+
154
+ // update profile
155
+ profiles.updateProfile({
156
+ profileName: profileName,
157
+ config: {
158
+ CLI_API_URL: answers.CLI_API_URL,
159
+ CLI_API_USERNAME: answers.CLI_API_USERNAME,
160
+ CLI_API_KEY: answers.CLI_API_KEY,
161
+ PROJECT_DIR: answers.PROJECT_DIR || process.cwd(),
162
+ AWS_ACCESS_KEY_ID: answers.AWS_ACCESS_KEY_ID,
163
+ AWS_SECRET_ACCESS_KEY: answers.AWS_SECRET_ACCESS_KEY,
164
+ AWS_REGION: answers.AWS_REGION
165
+ }
166
+ });
167
+ });
168
+
169
+ program.addCommand(profile);
170
+ }
@@ -0,0 +1,101 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ /**
6
+ * Gnar Engine CLI Profile Client
7
+ */
8
+ export const profiles = {
9
+
10
+ configPath: path.join(os.homedir(), '.gnarengine', 'config.json'),
11
+
12
+ getAllProfiles: function (ignoreNotFound = false) {
13
+ if (!fs.existsSync(this.configPath)) {
14
+ if (!ignoreNotFound) {
15
+ console.error(`Config file not found at ${this.configPath}`);
16
+ }
17
+ return {};
18
+ }
19
+
20
+ const config = JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
21
+ return config || {};
22
+ },
23
+
24
+ getActiveProfile: function () {
25
+ const allProfiles = this.getAllProfiles();
26
+
27
+ if (!allProfiles || Object.keys(allProfiles).length === 0) {
28
+ console.error('No profiles found');
29
+ return null;
30
+ }
31
+
32
+ if (!fs.existsSync(this.configPath)) {
33
+ if (!ignoreNotFound) {
34
+ console.error(`Config file not found at ${this.configPath}`);
35
+ }
36
+ return {};
37
+ }
38
+
39
+ const config = JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
40
+ const activeProfile = config.activeProfile;
41
+
42
+ return {
43
+ name: activeProfile,
44
+ profile: allProfiles.profiles[activeProfile]
45
+ };
46
+ },
47
+
48
+ setActiveProfile: function ({ profileName }) {
49
+ if (!fs.existsSync(this.configPath)) {
50
+ console.error(`Config file not found at ${this.configPath}`);
51
+ return;
52
+ }
53
+
54
+ const config = JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
55
+ config.activeProfile = profileName;
56
+ fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2));
57
+ },
58
+
59
+ createProfile: function ({ profileName, config }) {
60
+ if (!profileName || !config.CLI_API_URL || !config.CLI_API_USERNAME) {
61
+ throw new Error('Invalid profile data');
62
+ }
63
+
64
+ const ignoreNotFound = true;
65
+ const allProfiles = this.getAllProfiles(ignoreNotFound).profiles || {};
66
+
67
+ if (allProfiles[profileName]) {
68
+ throw new Error(`Profile "${profileName}" already exists`);
69
+ }
70
+
71
+ allProfiles[profileName] = config;
72
+
73
+ this.saveProfiles(allProfiles);
74
+ return allProfiles[profileName];
75
+ },
76
+
77
+ updateProfile: function ({ profileName, config }) {
78
+ if (!profileName || !config.CLI_API_URL || !config.CLI_API_USERNAME || !config.CLI_API_KEY) {
79
+ throw new Error('Invalid profile data');
80
+ }
81
+
82
+ const allProfiles = this.getAllProfiles().profiles || {};
83
+
84
+ if (!allProfiles[profileName]) {
85
+ throw new Error(`Profile "${profileName}" not found`);
86
+ }
87
+
88
+ allProfiles[profileName] = config;
89
+
90
+ this.saveProfiles(allProfiles);
91
+ },
92
+
93
+ saveProfiles: function (profilesObj) {
94
+ const dir = path.dirname(this.configPath);
95
+ if (!fs.existsSync(dir)) {
96
+ fs.mkdirSync(dir, { recursive: true });
97
+ }
98
+
99
+ fs.writeFileSync(this.configPath, JSON.stringify({profiles: profilesObj}, null, 2));
100
+ }
101
+ };
@@ -0,0 +1,123 @@
1
+ import inquirer from 'inquirer';
2
+ import { profiles } from '../profiles/profiles.client.js';
3
+ import { scaffolder } from './scaffolder.handler.js';
4
+ import path from 'path';
5
+
6
+ export const registerScaffolderCommands = (program) => {
7
+
8
+ const create = program.command('create').description('📦 Scaffold new services and components');
9
+
10
+ create
11
+ .command('project <projectName>')
12
+ .description('🚀 Create a new project')
13
+ .action(async (projectName, options) => {
14
+ // validate
15
+ if (!projectName) {
16
+ console.error('❌ Please specify a project name using gnar create project <projectName>');
17
+ return;
18
+ }
19
+
20
+ // options
21
+ const answers = await inquirer.prompt([
22
+ {
23
+ type: 'input',
24
+ name: 'projectDir',
25
+ message: 'Choose directory to create project in',
26
+ default: path.join(process.cwd())
27
+ },
28
+ {
29
+ type: 'input',
30
+ name: 'rootAdminEmail',
31
+ message: 'Root Admin Email',
32
+ default: ''
33
+ }
34
+ ]);
35
+
36
+ // create the project
37
+ try {
38
+ scaffolder.createNewProject({
39
+ projectName: projectName,
40
+ projectDir: answers.projectDir,
41
+ rootAdminEmail: answers.rootAdminEmail
42
+ });
43
+ } catch (error) {
44
+ console.error('❌ Error creating project:', error.message);
45
+ }
46
+ });
47
+
48
+ create
49
+ .command('service <service>')
50
+ .description('📦 Create a new service: back-end|front-end')
51
+ .action(async (options) => {
52
+ // validate
53
+ if (!options.service) {
54
+ console.error('❌ Please specify a service name using gnar create service <serviceName>');
55
+ }
56
+
57
+ let activeProfile;
58
+ try {
59
+ activeProfile = profiles.getActiveProfile();
60
+ } catch (error) {
61
+ console.error('❌ No active profile found. Please create or set one using `gnar profile create` or `gnar profile set-active <profileName>`');
62
+ return;
63
+ }
64
+
65
+ // prompt for service details
66
+ const serviceTypeAnswer = await inquirer.prompt([
67
+ {
68
+ type: 'list',
69
+ name: 'serviceType',
70
+ message: 'Service Type',
71
+ choices: [
72
+ { name: 'Back-End', value: 'backend' },
73
+ { name: 'Front-End', value: 'frontend' }
74
+ ],
75
+ default: 'backend'
76
+ }
77
+ ]);
78
+
79
+ // back-end service
80
+ if (serviceTypeAnswer.serviceType === 'backend') {
81
+ const backendAnswers = await inquirer.prompt([
82
+ {
83
+ type: 'list',
84
+ name: 'database',
85
+ message: 'Database',
86
+ choices: [
87
+ { name: 'MYSQL', value: 'mysql' },
88
+ { name: 'Mongo DB', value: 'mongodb' }
89
+ ],
90
+ default: 'mongodb'
91
+ }
92
+ ]);
93
+
94
+ // create the service
95
+ try {
96
+ console.log('Creating new service in... ' + activeProfile.profile.PROJECT_DIR);
97
+
98
+ scaffolder.createNewService({
99
+ serviceName: options.service,
100
+ database: backendAnswers.database,
101
+ projectDir: activeProfile.profile.PROJECT_DIR
102
+ });
103
+
104
+ } catch (error) {
105
+ console.error('❌ Error creating service:', error.message);
106
+ }
107
+ }
108
+
109
+ // front-end service
110
+ else {
111
+ try {
112
+ console.log('Creating new service in... ' + activeProfile.profile.PROJECT_DIR);
113
+
114
+ scaffolder.createNewFrontEndService({
115
+ serviceName: options.service,
116
+ projectDir: activeProfile.profile.PROJECT_DIR
117
+ });
118
+ } catch (error) {
119
+ console.error('❌ Error creating service:', error.message);
120
+ }
121
+ }
122
+ });
123
+ }