@gnar-engine/cli 1.0.2 → 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.
- package/bootstrap/deploy.localdev.yml +18 -31
- package/bootstrap/secrets.localdev.yml +7 -12
- package/bootstrap/services/user/Dockerfile +1 -1
- package/install.sh +32 -0
- package/package.json +1 -1
- package/src/config.js +2 -1
- package/src/dev/dev.service.js +44 -16
- package/src/scaffolder/commands.js +11 -4
- package/src/scaffolder/scaffolder.handler.js +228 -55
- package/templates/service/Dockerfile.hbs +4 -1
- package/templates/service/package.json.hbs +14 -16
- package/templates/service/{app.js.hbs → src/app.js.hbs} +8 -4
- package/templates/service/{commands → src/commands}/{{serviceName}}.handler.js.hbs +1 -2
- package/{bootstrap/services/agent/src/config.js → templates/service/src/mongodb.config.js.hbs} +11 -18
- package/templates/service/{config.js.hbs → src/mysql.config.js.hbs} +6 -0
- package/{bootstrap/services/agent/src/schema/Agent.schema.js → templates/service/src/schema/{{serviceName}}.schema.js.hbs} +3 -3
- package/templates/service/src/services/mongodb.{{serviceName}}.service.js.hbs +70 -0
- package/bootstrap/services/agent/Dockerfile +0 -23
- package/bootstrap/services/agent/notes.md +0 -28
- package/bootstrap/services/agent/package.json +0 -16
- package/bootstrap/services/agent/src/app.js +0 -52
- package/bootstrap/services/agent/src/commands/agent.handler.js +0 -104
- package/bootstrap/services/agent/src/controllers/http.controller.js +0 -44
- package/bootstrap/services/agent/src/controllers/message.controller.js +0 -51
- package/bootstrap/services/agent/src/db/migrations/01-init.js +0 -50
- package/bootstrap/services/agent/src/db/migrations/02-agent-service-init.js +0 -36
- package/bootstrap/services/agent/src/policies/agent.policy.js +0 -13
- package/bootstrap/services/agent/src/services/agent.service.js +0 -259
- package/bootstrap/services/agent/src/services/chatgpt.service.js +0 -46
- package/bootstrap/services/agent/src/services/manifest.service.js +0 -21
- package/bootstrap/services/portal/Dockerfile +0 -23
- package/bootstrap/services/portal/Dockerfile.remote +0 -40
- package/bootstrap/services/portal/README.md +0 -22
- package/bootstrap/services/portal/nginx.conf +0 -12
- package/bootstrap/services/portal/package.json +0 -59
- package/bootstrap/services/portal/public/favicon.ico +0 -0
- package/bootstrap/services/portal/public/gnar-white.png +0 -0
- package/bootstrap/services/portal/public/gnarengine-logo-black.png +0 -0
- package/bootstrap/services/portal/public/index.html +0 -43
- package/bootstrap/services/portal/public/logo192.png +0 -0
- package/bootstrap/services/portal/public/logo512.png +0 -0
- package/bootstrap/services/portal/public/manifest.json +0 -25
- package/bootstrap/services/portal/public/robots.txt +0 -3
- package/bootstrap/services/portal/src/App.js +0 -56
- package/bootstrap/services/portal/src/assets/Logo_Anchord_Black.svg +0 -1
- package/bootstrap/services/portal/src/assets/Logo_Anchord_Black_Green.svg +0 -1
- package/bootstrap/services/portal/src/assets/Logo_Anchord_White_Green.svg +0 -1
- package/bootstrap/services/portal/src/assets/activity.svg +0 -3
- package/bootstrap/services/portal/src/assets/arrow.svg +0 -3
- package/bootstrap/services/portal/src/assets/bin-white.svg +0 -3
- package/bootstrap/services/portal/src/assets/bin.svg +0 -3
- package/bootstrap/services/portal/src/assets/check.svg +0 -3
- package/bootstrap/services/portal/src/assets/chevron.svg +0 -3
- package/bootstrap/services/portal/src/assets/contact.svg +0 -3
- package/bootstrap/services/portal/src/assets/dots-vertical.svg +0 -5
- package/bootstrap/services/portal/src/assets/eye-off.svg +0 -3
- package/bootstrap/services/portal/src/assets/eye.svg +0 -4
- package/bootstrap/services/portal/src/assets/gnar-engine-black.svg +0 -47
- package/bootstrap/services/portal/src/assets/gnar-engine-white.svg +0 -47
- package/bootstrap/services/portal/src/assets/gnar_engine.svg +0 -3
- package/bootstrap/services/portal/src/assets/gnarengine-logo-black.png +0 -0
- package/bootstrap/services/portal/src/assets/home.svg +0 -3
- package/bootstrap/services/portal/src/assets/link.svg +0 -3
- package/bootstrap/services/portal/src/assets/lock.svg +0 -3
- package/bootstrap/services/portal/src/assets/package.svg +0 -4
- package/bootstrap/services/portal/src/assets/raffle.svg +0 -3
- package/bootstrap/services/portal/src/assets/settings.svg +0 -4
- package/bootstrap/services/portal/src/assets/shopping-bag.svg +0 -3
- package/bootstrap/services/portal/src/assets/user-black.svg +0 -3
- package/bootstrap/services/portal/src/assets/user.svg +0 -3
- package/bootstrap/services/portal/src/assets/users.svg +0 -3
- package/bootstrap/services/portal/src/assets/wallet.svg +0 -3
- package/bootstrap/services/portal/src/css/style.css +0 -1007
- package/bootstrap/services/portal/src/data/data.js +0 -70
- package/bootstrap/services/portal/src/features/attributeFormRow/AttributeFormRow.jsx +0 -32
- package/bootstrap/services/portal/src/features/billingShipping/BillingShipping.jsx +0 -160
- package/bootstrap/services/portal/src/features/crud/crudEdit.less +0 -230
- package/bootstrap/services/portal/src/features/crud/crudList.less +0 -134
- package/bootstrap/services/portal/src/features/crud/crudPage.less +0 -31
- package/bootstrap/services/portal/src/features/crudContact/CrudContactList.jsx +0 -108
- package/bootstrap/services/portal/src/features/crudContact/CrudContactSingle.jsx +0 -243
- package/bootstrap/services/portal/src/features/crudOrder/CrudOrderList.jsx +0 -109
- package/bootstrap/services/portal/src/features/crudOrder/CrudOrderSingle.jsx +0 -315
- package/bootstrap/services/portal/src/features/crudProducts/CrudProductList.jsx +0 -104
- package/bootstrap/services/portal/src/features/crudProducts/CrudProductSingle.jsx +0 -388
- package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesList.jsx +0 -104
- package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesSingle.jsx +0 -208
- package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionList.jsx +0 -110
- package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionSingle.jsx +0 -261
- package/bootstrap/services/portal/src/features/crudUser/CrudUserList.jsx +0 -107
- package/bootstrap/services/portal/src/features/crudUser/CrudUserSingle.jsx +0 -402
- package/bootstrap/services/portal/src/features/inventoryFormRow/InventoryFormRow.jsx +0 -30
- package/bootstrap/services/portal/src/features/lineItems/LineItems.jsx +0 -113
- package/bootstrap/services/portal/src/features/loginForm/LoginForm.jsx +0 -56
- package/bootstrap/services/portal/src/features/loginForm/loginForm.less +0 -56
- package/bootstrap/services/portal/src/features/notes/Notes.jsx +0 -18
- package/bootstrap/services/portal/src/features/passwordReset/PasswordResetForm.jsx +0 -96
- package/bootstrap/services/portal/src/features/passwordReset/PasswordResetRequestForm.jsx +0 -74
- package/bootstrap/services/portal/src/features/priceFormRow/PriceFormRow.jsx +0 -102
- package/bootstrap/services/portal/src/features/priceFormRow/priceFormRow.less +0 -24
- package/bootstrap/services/portal/src/features/raffleEntriesList/RaffleEntriesList.jsx +0 -99
- package/bootstrap/services/portal/src/features/raffleProductFormRow/RaffleProductFormRow.jsx +0 -46
- package/bootstrap/services/portal/src/features/sidebar/Sidebar.jsx +0 -64
- package/bootstrap/services/portal/src/features/sidebar/sidebar.less +0 -49
- package/bootstrap/services/portal/src/features/skus/Skus.jsx +0 -109
- package/bootstrap/services/portal/src/features/subscriptionSchedule/SubscriptionSchedule.jsx +0 -44
- package/bootstrap/services/portal/src/features/taxonomyFormRow/TaxonomyFormRow.jsx +0 -32
- package/bootstrap/services/portal/src/features/user/User.jsx +0 -54
- package/bootstrap/services/portal/src/features/user/user.less +0 -57
- package/bootstrap/services/portal/src/includes/utilities.js +0 -259
- package/bootstrap/services/portal/src/index.js +0 -14
- package/bootstrap/services/portal/src/layouts/CrudLayout.jsx +0 -50
- package/bootstrap/services/portal/src/layouts/LoginLayout.jsx +0 -17
- package/bootstrap/services/portal/src/layouts/PortalLayout.jsx +0 -48
- package/bootstrap/services/portal/src/layouts/loginLayout.less +0 -33
- package/bootstrap/services/portal/src/layouts/portalLayout.less +0 -67
- package/bootstrap/services/portal/src/pages/contacts/Contacts.jsx +0 -199
- package/bootstrap/services/portal/src/pages/dashboard/Dashboard.jsx +0 -17
- package/bootstrap/services/portal/src/pages/integrations/Integrations.jsx +0 -10
- package/bootstrap/services/portal/src/pages/login/Login.jsx +0 -15
- package/bootstrap/services/portal/src/pages/login/login.less +0 -10
- package/bootstrap/services/portal/src/pages/orders/Orders.jsx +0 -199
- package/bootstrap/services/portal/src/pages/passwordReset/PasswordResetPage.jsx +0 -15
- package/bootstrap/services/portal/src/pages/passwordResetRequest/PasswordResetRequestPage.jsx +0 -15
- package/bootstrap/services/portal/src/pages/payments/Payments.jsx +0 -10
- package/bootstrap/services/portal/src/pages/portal/Portal.jsx +0 -43
- package/bootstrap/services/portal/src/pages/products/Products.jsx +0 -212
- package/bootstrap/services/portal/src/pages/raffleEntries/RaffleEntries.jsx +0 -124
- package/bootstrap/services/portal/src/pages/raffles/Raffles.jsx +0 -186
- package/bootstrap/services/portal/src/pages/reports/Reports.jsx +0 -10
- package/bootstrap/services/portal/src/pages/settings/Settings.jsx +0 -10
- package/bootstrap/services/portal/src/pages/subscriptions/Subscriptions.jsx +0 -199
- package/bootstrap/services/portal/src/pages/users/Users.jsx +0 -193
- package/bootstrap/services/portal/src/pages/users/users.less +0 -25
- package/bootstrap/services/portal/src/slices/authSlice.js +0 -71
- package/bootstrap/services/portal/src/store/configureStore.js +0 -12
- package/bootstrap/services/portal/src/styles/global.less +0 -159
- package/bootstrap/services/portal/src/styles/inputs.less +0 -157
- package/bootstrap/services/portal/src/styles/main.less +0 -26
- package/bootstrap/services/portal/src/ui/collapsible/Collapsible.jsx +0 -97
- package/bootstrap/services/portal/src/ui/collapsible/collapsible.less +0 -23
- package/bootstrap/services/portal/src/ui/customCheckbox/CustomCheckbox.jsx +0 -17
- package/bootstrap/services/portal/src/ui/customCheckbox/customCheckbox.less +0 -42
- package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelect.jsx +0 -63
- package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelectPeriod.jsx +0 -63
- package/bootstrap/services/portal/src/ui/customSelect/CustomSelect.jsx +0 -63
- package/bootstrap/services/portal/src/ui/customSelect/customSelect.less +0 -92
- package/bootstrap/services/portal/src/ui/goBack/GoBack.jsx +0 -19
- package/bootstrap/services/portal/src/ui/loader/Loader.jsx +0 -12
- package/bootstrap/services/portal/src/ui/pagination/Pagination.jsx +0 -23
- package/bootstrap/services/portal/src/ui/repeater/Repeater.jsx +0 -29
- package/bootstrap/services/portal/src/ui/saveButton/SaveButton.jsx +0 -69
- package/bootstrap/services/portal/src/ui/saveButton/saveButton.less +0 -0
- package/bootstrap/services/user/src/db/seeders/development/02-portal-admin-user.js +0 -27
- package/templates/service/schema/{{serviceName}}.schema.js.hbs +0 -14
- /package/templates/service/{controllers → src/controllers}/http.controller.js.hbs +0 -0
- /package/templates/service/{controllers → src/controllers}/message.controller.js.hbs +0 -0
- /package/templates/service/{db → src/mysql.db}/migrations/01-init.js.hbs +0 -0
- /package/templates/service/{db → src/mysql.db}/migrations/02-{{lowerCase serviceName}}-service-init.js.hbs +0 -0
- /package/templates/service/{policies → src/policies}/{{serviceName}}.policy.js.hbs +0 -0
- /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
|
|
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
|
-
|
|
20
|
-
|
|
13
|
+
min_tasks: 1
|
|
14
|
+
max_tasks: 1
|
|
21
15
|
depends_on:
|
|
22
16
|
- control-db
|
|
23
|
-
command:
|
|
24
|
-
|
|
25
|
-
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
-
|
|
48
|
-
command:
|
|
49
|
-
|
|
50
|
-
-
|
|
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:
|
|
11
|
-
MYSQL_RANDOM_ROOT_PASSWORD:
|
|
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:
|
|
17
|
-
MYSQL_RANDOM_ROOT_PASSWORD:
|
|
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:
|
|
21
|
-
ROOT_ADMIN_API_KEY:
|
|
22
|
-
|
|
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
|
+
|
package/install.sh
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
PACKAGE_NAME="@gnar-engine/cli"
|
|
5
|
+
TARGET_DIR="$HOME/.gnarengine"
|
|
6
|
+
|
|
7
|
+
echo "Installing $PACKAGE_NAME locally for user $USER..."
|
|
8
|
+
|
|
9
|
+
# Create the global config directory
|
|
10
|
+
mkdir -p "$TARGET_DIR"
|
|
11
|
+
cd "$TARGET_DIR"
|
|
12
|
+
|
|
13
|
+
# Install the package
|
|
14
|
+
npm install "$PACKAGE_NAME" --no-audit --no-fund
|
|
15
|
+
|
|
16
|
+
# Get the local bin path
|
|
17
|
+
BIN_PATH="$TARGET_DIR/node_modules/.bin"
|
|
18
|
+
|
|
19
|
+
# Add to shell PATH if not already present
|
|
20
|
+
if ! grep -q "$BIN_PATH" "$HOME/.bashrc" 2>/dev/null; then
|
|
21
|
+
echo "export PATH=\"$BIN_PATH:\$PATH\"" >> "$HOME/.bashrc"
|
|
22
|
+
echo "Added $BIN_PATH to PATH in ~/.bashrc"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# For zsh users (macOS default)
|
|
26
|
+
if [ -f "$HOME/.zshrc" ] && ! grep -q "$BIN_PATH" "$HOME/.zshrc"; then
|
|
27
|
+
echo "export PATH=\"$BIN_PATH:\$PATH\"" >> "$HOME/.zshrc"
|
|
28
|
+
echo "Added $BIN_PATH to PATH in ~/.zshrc"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "G n a r E n g i n e - Installation complete! Please restart your terminal and run 'gnar --help' to verify."
|
|
32
|
+
|
package/package.json
CHANGED
package/src/config.js
CHANGED
package/src/dev/dev.service.js
CHANGED
|
@@ -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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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(`../../../
|
|
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.
|
|
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
|
-
|
|
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:
|
|
314
|
+
image: 'mongo:latest',
|
|
288
315
|
ports: [
|
|
289
|
-
`${mongoPortsCounter}
|
|
316
|
+
`${mongoPortsCounter}:27017`
|
|
290
317
|
],
|
|
291
318
|
restart: 'always',
|
|
292
319
|
environment: {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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}/
|
|
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 (
|
|
51
|
+
.action(async (service) => {
|
|
52
52
|
// validate
|
|
53
|
-
if (!
|
|
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:
|
|
106
|
+
serviceName: service,
|
|
100
107
|
database: backendAnswers.database,
|
|
101
|
-
projectDir:
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
}
|