@gnar-engine/cli 1.0.4 → 1.0.6
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 +44 -3
- package/bootstrap/secrets.localdev.yml +20 -5
- package/bootstrap/services/control/src/config.js +4 -0
- package/bootstrap/services/notification/Dockerfile +2 -2
- package/bootstrap/services/notification/package.json +14 -32
- package/bootstrap/services/notification/src/app.js +50 -48
- package/bootstrap/services/notification/src/commands/notification.handler.js +96 -0
- package/bootstrap/services/notification/src/config.js +55 -12
- package/bootstrap/services/notification/src/controllers/http.controller.js +87 -0
- package/bootstrap/services/notification/src/controllers/message.controller.js +39 -70
- package/bootstrap/services/notification/src/db/migrations/01-init.js +50 -0
- package/bootstrap/services/notification/src/db/migrations/02-notification-service-init.js +23 -0
- package/bootstrap/services/notification/src/policies/notification.policy.js +49 -0
- package/bootstrap/services/notification/src/schema/notification.schema.js +17 -0
- package/bootstrap/services/notification/src/services/notification.service.js +32 -0
- package/bootstrap/services/page/Dockerfile +23 -0
- package/bootstrap/services/page/package.json +16 -0
- package/bootstrap/services/page/src/app.js +50 -0
- package/bootstrap/services/page/src/commands/block.handler.js +94 -0
- package/bootstrap/services/page/src/commands/page.handler.js +167 -0
- package/bootstrap/services/page/src/config.js +62 -0
- package/bootstrap/services/page/src/controllers/block.http.controller.js +87 -0
- package/bootstrap/services/page/src/controllers/message.controller.js +51 -0
- package/bootstrap/services/page/src/controllers/page.http.controller.js +89 -0
- package/bootstrap/services/page/src/policies/block.policy.js +50 -0
- package/bootstrap/services/page/src/policies/page.policy.js +49 -0
- package/bootstrap/services/page/src/schema/page.schema.js +139 -0
- package/bootstrap/services/page/src/services/block.service.js +83 -0
- package/bootstrap/services/page/src/services/page.service.js +83 -0
- package/bootstrap/services/portal/Dockerfile +20 -0
- package/bootstrap/services/portal/README.md +73 -0
- package/bootstrap/services/portal/index.html +13 -0
- package/bootstrap/services/portal/nginx.conf +5 -0
- package/bootstrap/services/portal/package.json +33 -0
- package/bootstrap/services/portal/public/vite.svg +1 -0
- package/bootstrap/services/portal/react-router.config.js +7 -0
- package/bootstrap/services/portal/src/App.jsx +16 -0
- package/bootstrap/services/portal/src/assets/gnar-engine-white-logo.svg +9 -0
- package/bootstrap/services/portal/src/assets/icon-agent.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-cog.svg +4 -0
- package/bootstrap/services/portal/src/assets/icon-delete.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-home.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-padlock.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-page.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-reports.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-user.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-users.svg +3 -0
- package/bootstrap/services/portal/src/assets/login-green-rad-back-1.jpg +0 -0
- package/bootstrap/services/portal/src/assets/react.svg +1 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.jsx +85 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.less +59 -0
- package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.jsx +81 -0
- package/bootstrap/services/portal/src/components/LoginForm/LoginForm.jsx +58 -0
- package/bootstrap/services/portal/src/components/PageBlockSwitch/PageBlockSwitch.jsx +129 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.jsx +33 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.less +37 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.jsx +19 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.less +22 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.jsx +33 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.less +21 -0
- package/bootstrap/services/portal/src/css/style.css +711 -0
- package/bootstrap/services/portal/src/data/pages.data.js +10 -0
- package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.jsx +65 -0
- package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.less +102 -0
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.jsx +115 -0
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.less +43 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.jsx +124 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.less +0 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.jsx +52 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.less +70 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.jsx +18 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.less +37 -0
- package/bootstrap/services/portal/src/elements/SaveButton/SaveButton.jsx +45 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.jsx +63 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.less +23 -0
- package/bootstrap/services/portal/src/elements/TextInput/TextInput.jsx +17 -0
- package/bootstrap/services/portal/src/layouts/Card/Card.jsx +15 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.jsx +29 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.less +49 -0
- package/bootstrap/services/portal/src/main.jsx +51 -0
- package/bootstrap/services/portal/src/pages/BlockSinglePage/BlockSinglePage.jsx +277 -0
- package/bootstrap/services/portal/src/pages/BlocksPage/BlocksPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.jsx +11 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.less +0 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.jsx +21 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.less +51 -0
- package/bootstrap/services/portal/src/pages/PageSinglePage/PageSinglePage.jsx +338 -0
- package/bootstrap/services/portal/src/pages/PagesPage/PagesPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.jsx +9 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.less +0 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.jsx +25 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.less +0 -0
- package/bootstrap/services/portal/src/services/block.js +28 -0
- package/bootstrap/services/portal/src/services/client.js +70 -0
- package/bootstrap/services/portal/src/services/gravatar.js +14 -0
- package/bootstrap/services/portal/src/services/page.js +28 -0
- package/bootstrap/services/portal/src/services/storage.js +62 -0
- package/bootstrap/services/portal/src/services/user.js +41 -0
- package/bootstrap/services/portal/src/slices/authSlice.js +101 -0
- package/bootstrap/services/portal/src/store/configureStore.js +10 -0
- package/bootstrap/services/portal/src/style/cards.less +57 -0
- package/bootstrap/services/portal/src/style/global.less +204 -0
- package/bootstrap/services/portal/src/style/icons.less +21 -0
- package/bootstrap/services/portal/src/style/inputs.less +52 -0
- package/bootstrap/services/portal/src/style/main.less +28 -0
- package/bootstrap/services/portal/src/utils/utils.js +9 -0
- package/bootstrap/services/portal/vite.config.js +12 -0
- package/bootstrap/services/user/src/app.js +6 -1
- package/bootstrap/services/user/src/commands/user.handler.js +35 -21
- package/bootstrap/services/user/src/config.js +5 -1
- package/bootstrap/services/user/src/policies/user.policy.js +3 -1
- package/bootstrap/services/user/src/tests/commands/user.test.js +31 -0
- package/install-from-clone.sh +30 -0
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/config.js +8 -0
- package/src/dev/commands.js +11 -3
- package/src/dev/dev.service.js +164 -64
- package/src/helpers/helpers.js +24 -0
- package/src/profiles/command.js +41 -0
- package/src/profiles/profiles.client.js +23 -0
- package/src/provisioner/Dockerfile +27 -0
- package/src/provisioner/package.json +19 -0
- package/src/provisioner/src/app.js +56 -0
- package/src/provisioner/src/services/mongodb.js +58 -0
- package/src/provisioner/src/services/mysql.js +51 -0
- package/src/provisioner/src/services/secrets.js +84 -0
- package/src/scaffolder/commands.js +58 -2
- package/src/scaffolder/scaffolder.handler.js +164 -72
- package/templates/entity/src/commands/{{entityName}}.handler.js.hbs +94 -0
- package/templates/entity/src/controllers/{{entityName}}.http.controller.js.hbs +87 -0
- package/templates/entity/src/mysql.db/migrations/03-{{entityName}}-entity-init.js.hbs +23 -0
- package/templates/entity/src/policies/{{entityName}}.policy.js.hbs +49 -0
- package/templates/entity/src/schema/{{entityName}}.schema.js.hbs +17 -0
- package/templates/entity/src/services/mongodb.{{entityName}}.service.js.hbs +70 -0
- package/templates/entity/src/services/mysql.{{entityName}}.service.js.hbs +27 -0
- package/templates/service/src/app.js.hbs +12 -1
- package/templates/service/src/commands/{{serviceName}}.handler.js.hbs +1 -1
- package/templates/service/src/mongodb.config.js.hbs +5 -1
- package/templates/service/src/mysql.config.js.hbs +4 -0
- package/bootstrap/services/notification/Dockerfile.prod +0 -37
- package/bootstrap/services/notification/README.md +0 -3
- package/bootstrap/services/notification/src/commands/command-bus.js +0 -20
- package/bootstrap/services/notification/src/commands/handlers/control.handler.js +0 -18
- package/bootstrap/services/notification/src/commands/handlers/notification.handler.js +0 -157
- package/bootstrap/services/notification/src/services/logger.service.js +0 -16
- package/bootstrap/services/notification/src/services/ses.service.js +0 -23
- package/bootstrap/services/notification/src/templates/admin-order-recieved.hbs +0 -136
- package/bootstrap/services/notification/src/templates/admin-subscription-failed.hbs +0 -87
- package/bootstrap/services/notification/src/templates/customer-order-recieved.hbs +0 -132
- package/bootstrap/services/notification/src/templates/customer-subscription-failed.hbs +0 -77
- package/bootstrap/services/user/src/tests/user.test.js +0 -126
- /package/bootstrap/services/{notification/src/tests/notification.test.js → portal/src/components/CustomSelect/CustomSelect.less} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mysqlService } from './services/mysql.js';
|
|
2
|
+
import { mongoService } from './services/mongodb.js';
|
|
3
|
+
import { secrets } from './services/secrets.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialise service
|
|
7
|
+
*/
|
|
8
|
+
export const initService = async () => {
|
|
9
|
+
|
|
10
|
+
console.log('G n a r E n g i n e | Provisioner provisioning databases...');
|
|
11
|
+
|
|
12
|
+
let provisionerSecrets;
|
|
13
|
+
|
|
14
|
+
// get all secrets
|
|
15
|
+
try {
|
|
16
|
+
provisionerSecrets = JSON.parse(process.env.PROVISIONER_SECRETS);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('Error parsing provisioner secrets', error);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// collate databases to provision from secrets
|
|
23
|
+
const mysqlDatabases = secrets.collateMysqlDatabases(provisionerSecrets);
|
|
24
|
+
const mongoDatabases = secrets.collateMongoDatabases(provisionerSecrets);
|
|
25
|
+
|
|
26
|
+
// provision mysql databases
|
|
27
|
+
if (mysqlDatabases) {
|
|
28
|
+
for (const [key, value] of Object.entries(mysqlDatabases)) {
|
|
29
|
+
mysqlService.provisionDatabase({
|
|
30
|
+
host: value.host,
|
|
31
|
+
database: value.database,
|
|
32
|
+
user: value.user,
|
|
33
|
+
password: value.password,
|
|
34
|
+
rootPassword: provisionerSecrets.provision.MYSQL_ROOT_PASSWORD
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
console.log('No MySQL databases to provision.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (mongoDatabases) {
|
|
42
|
+
for (const [key, value] of Object.entries(mongoDatabases)) {
|
|
43
|
+
mongoService.provisionDatabase({
|
|
44
|
+
host: value.host,
|
|
45
|
+
database: value.database,
|
|
46
|
+
user: value.user,
|
|
47
|
+
password: value.password,
|
|
48
|
+
rootPassword: provisionerSecrets.provision.MONGO_ROOT_PASSWORD
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
console.log('No MongoDB databases to provision.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
initService();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { MongoClient } from 'mongodb';
|
|
2
|
+
|
|
3
|
+
const retryInterval = 5000;
|
|
4
|
+
const maxRetries = 5;
|
|
5
|
+
|
|
6
|
+
let db;
|
|
7
|
+
|
|
8
|
+
export const mongoService = {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Provision database and users
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} params - The parameters object
|
|
14
|
+
* @param {string} host - The database host
|
|
15
|
+
* @param {string} database - The database name
|
|
16
|
+
* @param {string} user - The database user
|
|
17
|
+
* @param {string} password - The database user password
|
|
18
|
+
* @param {string} rootPassword - The root user password
|
|
19
|
+
* @param {number} [port=27017] - The database port
|
|
20
|
+
*/
|
|
21
|
+
provisionDatabase: async ({host, database, user, password, rootPassword, port = 27017}) => {
|
|
22
|
+
|
|
23
|
+
const connectionUrl = `mongodb://root:${rootPassword}@${host}:${port}/admin`;
|
|
24
|
+
let retries = 0;
|
|
25
|
+
|
|
26
|
+
while (retries < maxRetries) {
|
|
27
|
+
try {
|
|
28
|
+
const dbClient = await MongoClient.connect(connectionUrl);
|
|
29
|
+
db = dbClient.db(database);
|
|
30
|
+
|
|
31
|
+
const existingUsers = await db.command({ usersInfo: 1 });
|
|
32
|
+
|
|
33
|
+
if (!existingUsers.users.some(u => u.user === user)) {
|
|
34
|
+
await db.command({
|
|
35
|
+
createUser: user,
|
|
36
|
+
pwd: password,
|
|
37
|
+
roles: [{ role: "readWrite", db: database }]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(`Successfully provisioned MongoDB database: ${database} and user: ${user}`);
|
|
42
|
+
await dbClient.close();
|
|
43
|
+
return;
|
|
44
|
+
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`Failed provisioning Mongo database "${database}" for user "${user}" ": ${error.message}`);
|
|
47
|
+
retries++;
|
|
48
|
+
|
|
49
|
+
if (retries >= maxRetries) {
|
|
50
|
+
console.error(`Max retries reached. Could not provision database "${database}".`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
|
|
3
|
+
const retryInterval = 5000;
|
|
4
|
+
const maxRetries = 5;
|
|
5
|
+
|
|
6
|
+
export const mysqlService = {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Provision database and users
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} params - The parameters object
|
|
12
|
+
* @param {string} host - The database host
|
|
13
|
+
* @param {string} database - The database name
|
|
14
|
+
* @param {string} user - The database user
|
|
15
|
+
* @param {string} password - The database user password
|
|
16
|
+
* @param {string} rootPassword - The root user password
|
|
17
|
+
*/
|
|
18
|
+
provisionDatabase: async ({host, database, user, password, rootPassword}) => {
|
|
19
|
+
let retries = 0;
|
|
20
|
+
|
|
21
|
+
while (retries < maxRetries) {
|
|
22
|
+
try {
|
|
23
|
+
const conn = await mysql.createConnection({
|
|
24
|
+
host: host || 'db-mysql',
|
|
25
|
+
user: 'root',
|
|
26
|
+
password: rootPassword
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await conn.query(`CREATE DATABASE IF NOT EXISTS ${mysql.escapeId(database)};`);
|
|
30
|
+
await conn.query(`CREATE USER IF NOT EXISTS ${mysql.escape(user)}@'%' IDENTIFIED BY ${mysql.escape(password)};`);
|
|
31
|
+
await conn.query(`GRANT ALL PRIVILEGES ON ${mysql.escapeId(database)}.* TO ${mysql.escape(user)}@'%';`);
|
|
32
|
+
await conn.query(`FLUSH PRIVILEGES;`);
|
|
33
|
+
|
|
34
|
+
console.log(`Successfully provisioned MySQL database: ${database} and user: ${user}`);
|
|
35
|
+
await conn.end();
|
|
36
|
+
return;
|
|
37
|
+
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(`Failed provisioning MySQL database "${database}" for user "${user}" ": ${error.message}`);
|
|
40
|
+
retries++;
|
|
41
|
+
|
|
42
|
+
if (retries >= maxRetries) {
|
|
43
|
+
console.error(`Max retries reached. Could not provision database "${database}".`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export const secrets = {
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Collate MySQL databases from provisioner secrets
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} provisionerSecrets - The provisioner secrets object
|
|
9
|
+
* @returns {Object} - Collated MySQL databases
|
|
10
|
+
*/
|
|
11
|
+
collateMysqlDatabases: (provisionerSecrets) => {
|
|
12
|
+
|
|
13
|
+
const mysqlDatabases = {};
|
|
14
|
+
|
|
15
|
+
for (const [serviceKey, service] of Object.entries(provisionerSecrets.services)) {
|
|
16
|
+
for (const key of Object.keys(service)) {
|
|
17
|
+
if (key.startsWith('MYSQL_')) {
|
|
18
|
+
mysqlDatabases[serviceKey] = true;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const [key, value] of Object.entries(mysqlDatabases)) {
|
|
25
|
+
try {
|
|
26
|
+
mysqlDatabases[key] = {};
|
|
27
|
+
mysqlDatabases[key].host = provisionerSecrets.services[key].MYSQL_HOST;
|
|
28
|
+
mysqlDatabases[key].database = provisionerSecrets.services[key].MYSQL_DATABASE;
|
|
29
|
+
mysqlDatabases[key].user = provisionerSecrets.services[key].MYSQL_USER;
|
|
30
|
+
mysqlDatabases[key].password = provisionerSecrets.services[key].MYSQL_PASSWORD;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(`Missing database credentials for ${key} service. Please include: MYSQL_DATABASE, MYSQL_USER, and MYSQL_PASSWORD.`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!provisionerSecrets.provision.MYSQL_ROOT_PASSWORD) {
|
|
38
|
+
console.error('Missing MYSQL_ROOT_PASSWORD in provisioner secrets. Cannot provision databases.');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return mysqlDatabases;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Collate MongoDB databases from provisioner secrets
|
|
47
|
+
*
|
|
48
|
+
* @param {Object} provisionerSecrets - The provisioner secrets object
|
|
49
|
+
* @returns {Object} - Collated MongoDB databases
|
|
50
|
+
*/
|
|
51
|
+
collateMongoDatabases: (provisionerSecrets) => {
|
|
52
|
+
|
|
53
|
+
const mongoDatabases = {};
|
|
54
|
+
|
|
55
|
+
for (const [serviceKey, service] of Object.entries(provisionerSecrets.services)) {
|
|
56
|
+
for (const key of Object.keys(service)) {
|
|
57
|
+
if (key.startsWith('MONGO_')) {
|
|
58
|
+
mongoDatabases[serviceKey] = true;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const [key, value] of Object.entries(mongoDatabases)) {
|
|
65
|
+
try {
|
|
66
|
+
mongoDatabases[key] = {};
|
|
67
|
+
mongoDatabases[key].host = provisionerSecrets.services[key].MONGO_HOST;
|
|
68
|
+
mongoDatabases[key].database = provisionerSecrets.services[key].MONGO_DATABASE;
|
|
69
|
+
mongoDatabases[key].user = provisionerSecrets.services[key].MONGO_USER;
|
|
70
|
+
mongoDatabases[key].password = provisionerSecrets.services[key].MONGO_PASSWORD;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`Missing database credentials for ${key} service. Please include: MONGO_DATABASE, MONGO_USER, and MONGO_PASSWORD.`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!provisionerSecrets.provision.MONGO_ROOT_PASSWORD) {
|
|
78
|
+
console.error('Missing MONGO_ROOT_PASSWORD in provisioner secrets. Cannot provision databases.');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return mongoDatabases;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import { profiles } from '../profiles/profiles.client.js';
|
|
3
3
|
import { scaffolder } from './scaffolder.handler.js';
|
|
4
|
+
import { helpers } from '../helpers/helpers.js';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
|
|
6
7
|
export const registerScaffolderCommands = (program) => {
|
|
@@ -33,11 +34,16 @@ export const registerScaffolderCommands = (program) => {
|
|
|
33
34
|
}
|
|
34
35
|
]);
|
|
35
36
|
|
|
37
|
+
// validate absolute path, if it is not absolute, make it absolute
|
|
38
|
+
if (!path.isAbsolute(answers.projectDir)) {
|
|
39
|
+
answers.projectDir = path.join(process.cwd(), answers.projectDir);
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
// create the project
|
|
37
43
|
try {
|
|
38
44
|
scaffolder.createNewProject({
|
|
39
45
|
projectName: projectName,
|
|
40
|
-
projectDir: answers.projectDir,
|
|
46
|
+
projectDir: path.join('/', answers.projectDir),
|
|
41
47
|
rootAdminEmail: answers.rootAdminEmail
|
|
42
48
|
});
|
|
43
49
|
} catch (error) {
|
|
@@ -119,7 +125,7 @@ export const registerScaffolderCommands = (program) => {
|
|
|
119
125
|
console.log('Creating new service in... ' + activeProfile.profile.PROJECT_DIR);
|
|
120
126
|
|
|
121
127
|
scaffolder.createNewFrontEndService({
|
|
122
|
-
serviceName:
|
|
128
|
+
serviceName: service,
|
|
123
129
|
projectDir: activeProfile.profile.PROJECT_DIR
|
|
124
130
|
});
|
|
125
131
|
} catch (error) {
|
|
@@ -127,4 +133,54 @@ export const registerScaffolderCommands = (program) => {
|
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
});
|
|
136
|
+
|
|
137
|
+
create
|
|
138
|
+
.command('entity <entity>')
|
|
139
|
+
.description('📦 Create a new entity in an existing service')
|
|
140
|
+
.option('--in-service <serviceName>', 'The service in which to add the entity')
|
|
141
|
+
.action(async (entity, options) => {
|
|
142
|
+
// validate
|
|
143
|
+
if (!entity) {
|
|
144
|
+
console.error('❌ Please specify an entity name using gnar create entity <entityName> --in-service <serviceName>');
|
|
145
|
+
}
|
|
146
|
+
if (!options.inService) {
|
|
147
|
+
console.error('❌ Please specify the service using --in-service <serviceName>');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let activeProfile;
|
|
151
|
+
try {
|
|
152
|
+
activeProfile = profiles.getActiveProfile();
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('❌ No active profile found. Please create or set one using `gnar profile create` or `gnar profile set-active <profileName>`');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// create the entity
|
|
159
|
+
try {
|
|
160
|
+
// add trailing slash to project dir if missing
|
|
161
|
+
let projectDir = activeProfile.profile.PROJECT_DIR;
|
|
162
|
+
|
|
163
|
+
if (!activeProfile.profile.PROJECT_DIR.endsWith(path.sep)) {
|
|
164
|
+
projectDir += path.sep;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const dbType = await helpers.getDbTypeFromSecrets(options.inService, projectDir);
|
|
168
|
+
const serviceDir = path.join(projectDir, 'services', options.inService.toLowerCase());
|
|
169
|
+
|
|
170
|
+
console.log('Creating new entity in... ' + serviceDir);
|
|
171
|
+
|
|
172
|
+
scaffolder.createNewEntity({
|
|
173
|
+
entityName: entity,
|
|
174
|
+
inService: options.inService,
|
|
175
|
+
serviceDir: serviceDir,
|
|
176
|
+
database: dbType
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
console.log('Created entity ' + entity + ' in service ' + options.inService);
|
|
180
|
+
console.log('👉 Remember to add the new entities handler and controllers to your service\'s app.js');
|
|
181
|
+
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('❌ Error creating entity:', error.message);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
130
186
|
}
|
|
@@ -3,8 +3,11 @@ import fs from 'fs';
|
|
|
3
3
|
import yaml from 'js-yaml';
|
|
4
4
|
import { profiles } from '../profiles/profiles.client.js';
|
|
5
5
|
import { helpers } from '../helpers/helpers.js';
|
|
6
|
+
import { directories } from '../config.js';
|
|
6
7
|
import Handlebars from 'handlebars';
|
|
7
8
|
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* Gnar Engine Scaffolder
|
|
10
13
|
*/
|
|
@@ -32,69 +35,22 @@ export const scaffolder = {
|
|
|
32
35
|
fs.mkdirSync(serviceDir, { recursive: true });
|
|
33
36
|
|
|
34
37
|
// Get all files in the templates directory
|
|
35
|
-
const templatesDir = path.join(import.meta.dirname, '../../templates/service');
|
|
36
38
|
const templateFiles = scaffolder.getAllTemplateFiles({
|
|
37
|
-
dir:
|
|
38
|
-
baseDir:
|
|
39
|
+
dir: directories.scaffolderServiceTemplates,
|
|
40
|
+
baseDir: directories.scaffolderServiceTemplates
|
|
39
41
|
});
|
|
40
42
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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.', '');
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (fileRelativePath.includes('mysql.')) {
|
|
67
|
-
if (database !== 'mysql') {
|
|
68
|
-
return;
|
|
69
|
-
} else {
|
|
70
|
-
fileRelativePath = fileRelativePath.replace('mysql.', '');
|
|
71
|
-
}
|
|
72
|
-
}
|
|
43
|
+
// scaffold the hbs templates
|
|
44
|
+
const templateArgs = {
|
|
45
|
+
serviceName,
|
|
46
|
+
database
|
|
47
|
+
};
|
|
73
48
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
}
|
|
49
|
+
scaffolder.scaffoldHandlebarTemplates({
|
|
50
|
+
templateFiles: templateFiles,
|
|
51
|
+
serviceDir: serviceDir,
|
|
52
|
+
database: database,
|
|
53
|
+
templateArgs: templateArgs
|
|
98
54
|
});
|
|
99
55
|
|
|
100
56
|
// Scaffold deploy.yml
|
|
@@ -136,6 +92,25 @@ export const scaffolder = {
|
|
|
136
92
|
|
|
137
93
|
// Create the service directory
|
|
138
94
|
fs.mkdirSync(serviceDir, { recursive: true });
|
|
95
|
+
|
|
96
|
+
// Add to deploy.yml
|
|
97
|
+
scaffolder.scaffoldServiceDeployYml({
|
|
98
|
+
deployPath: path.join(projectDir, 'deploy.localdev.yml'),
|
|
99
|
+
serviceName: serviceName,
|
|
100
|
+
database: null
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Scaffold secrets
|
|
104
|
+
scaffolder.scaffoldServiceSecrets({
|
|
105
|
+
secretsPath: path.join(projectDir, 'secrets.localdev.yml'),
|
|
106
|
+
serviceName: serviceName,
|
|
107
|
+
database: null
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
message: `Front-end service "${serviceName}" created successfully at ${serviceDir}`,
|
|
112
|
+
servicePath: serviceDir
|
|
113
|
+
};
|
|
139
114
|
},
|
|
140
115
|
|
|
141
116
|
/**
|
|
@@ -152,8 +127,10 @@ export const scaffolder = {
|
|
|
152
127
|
|
|
153
128
|
entries.forEach(entry => {
|
|
154
129
|
const fullPath = path.join(dir, entry.name);
|
|
130
|
+
// if the full path contains src, the 'data' directory can be included
|
|
131
|
+
const includeDataDir = fullPath.includes(`${path.sep}src${path.sep}`) && fullPath.includes('data') ? true : false;
|
|
155
132
|
if (entry.isDirectory()) {
|
|
156
|
-
if (entry.name !== 'node_modules' && entry.name !== 'data') {
|
|
133
|
+
if (entry.name !== 'node_modules' && (entry.name !== 'data' || includeDataDir)) {
|
|
157
134
|
if (!entry.name.startsWith('.') || entry.name == '.gnarengine') {
|
|
158
135
|
scaffolder.getAllTemplateFiles({
|
|
159
136
|
dir: fullPath,
|
|
@@ -284,13 +261,17 @@ export const scaffolder = {
|
|
|
284
261
|
const parsedSecrets = yaml.load(rawSecrets);
|
|
285
262
|
|
|
286
263
|
// generate random passwords
|
|
287
|
-
|
|
288
|
-
Object.keys(parsedSecrets.services
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
264
|
+
try {
|
|
265
|
+
Object.keys(parsedSecrets.services).forEach(serviceName => {
|
|
266
|
+
Object.keys(parsedSecrets.services[serviceName]).forEach(key => {
|
|
267
|
+
if (key.toLowerCase().includes('pass')) {
|
|
268
|
+
parsedSecrets.services[serviceName][key] = helpers.generateRandomString(16);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
292
271
|
});
|
|
293
|
-
})
|
|
272
|
+
} catch (error) {
|
|
273
|
+
throw new Error('Error generating random passwords for project secrets: ' + error.message);
|
|
274
|
+
}
|
|
294
275
|
|
|
295
276
|
// set random root api key
|
|
296
277
|
const cliApiKey = helpers.generateRandomString(32);
|
|
@@ -328,15 +309,14 @@ export const scaffolder = {
|
|
|
328
309
|
parsedSecrets.services[serviceName]['MYSQL_USER'] = serviceName + '_user';
|
|
329
310
|
parsedSecrets.services[serviceName]['MYSQL_PASSWORD'] = helpers.generateRandomString(16);
|
|
330
311
|
parsedSecrets.services[serviceName]['MYSQL_DATABASE'] = serviceName + '_db';
|
|
331
|
-
parsedSecrets.services[serviceName]['MYSQL_HOST'] =
|
|
332
|
-
parsedSecrets.services[serviceName]['MYSQL_RANDOM_ROOT_PASSWORD'] = helpers.generateRandomString(16);
|
|
312
|
+
parsedSecrets.services[serviceName]['MYSQL_HOST'] = 'db-mysql';
|
|
333
313
|
break;
|
|
334
314
|
case 'mongodb':
|
|
335
315
|
const mongoPassword = helpers.generateRandomString(16);
|
|
336
316
|
const mongoRootPassword = helpers.generateRandomString(16);
|
|
337
317
|
const mongoUser = serviceName + '_user';
|
|
338
318
|
const mongoDatabase = serviceName + '_db';
|
|
339
|
-
const mongoHost =
|
|
319
|
+
const mongoHost = 'db-mongo';
|
|
340
320
|
const mongoUrl = `mongodb://${mongoUser}:${mongoPassword}@${mongoHost}:27017/${mongoDatabase}`;
|
|
341
321
|
|
|
342
322
|
parsedSecrets.services[serviceName]['MONGO_URL'] = mongoUrl;
|
|
@@ -347,8 +327,9 @@ export const scaffolder = {
|
|
|
347
327
|
parsedSecrets.services[serviceName]['MONGO_HOST'] = mongoHost;
|
|
348
328
|
break;
|
|
349
329
|
default:
|
|
350
|
-
|
|
351
|
-
|
|
330
|
+
// no db
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
352
333
|
|
|
353
334
|
// save updated secrets file
|
|
354
335
|
const newSecretsContent = yaml.dump(parsedSecrets);
|
|
@@ -411,7 +392,7 @@ export const scaffolder = {
|
|
|
411
392
|
// add database service if required
|
|
412
393
|
if (database) {
|
|
413
394
|
serviceConfig.depends_on = [
|
|
414
|
-
|
|
395
|
+
`db-${database}`
|
|
415
396
|
]
|
|
416
397
|
}
|
|
417
398
|
|
|
@@ -421,5 +402,116 @@ export const scaffolder = {
|
|
|
421
402
|
// write deploy.yml file
|
|
422
403
|
const deployYmlContent = yaml.dump(deploy);
|
|
423
404
|
fs.writeFileSync(deployPath, deployYmlContent, 'utf8');
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Create new entity in existing service
|
|
409
|
+
*
|
|
410
|
+
* @param {object} param
|
|
411
|
+
* @param {string} param.entityName - The name of the entity to create
|
|
412
|
+
* @param {string} param.inService - The service in which to add the entity
|
|
413
|
+
* @param {string} param.serviceDir - The service directory where the entity will be created
|
|
414
|
+
* @param {string} param.database - The database type (e.g., 'mysql', 'mongodb')
|
|
415
|
+
* @returns {object} - An object containing a success message
|
|
416
|
+
*/
|
|
417
|
+
createNewEntity: ({ entityName, inService, serviceDir, database }) => {
|
|
418
|
+
|
|
419
|
+
entityName = entityName.toLowerCase();
|
|
420
|
+
|
|
421
|
+
// validate serviceDir exists
|
|
422
|
+
if (!fs.existsSync(serviceDir)) {
|
|
423
|
+
throw new Error(`Service directory "${serviceDir}" does not exist`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Get all files in the templates directory
|
|
427
|
+
const templateFiles = scaffolder.getAllTemplateFiles({
|
|
428
|
+
dir: directories.scaffolderEntityTemplates,
|
|
429
|
+
baseDir: directories.scaffolderEntityTemplates
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// scaffold the hbs templates
|
|
433
|
+
const templateArgs = {
|
|
434
|
+
entityName: entityName,
|
|
435
|
+
serviceName: inService,
|
|
436
|
+
database: database
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
scaffolder.scaffoldHandlebarTemplates({
|
|
440
|
+
templateFiles: templateFiles,
|
|
441
|
+
serviceDir: serviceDir,
|
|
442
|
+
database: database,
|
|
443
|
+
templateArgs: templateArgs
|
|
444
|
+
});
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Scaffold handlebar templates
|
|
449
|
+
*
|
|
450
|
+
* @param {object} param
|
|
451
|
+
* @param {string} param.templateFiles - The list of template files
|
|
452
|
+
* @param {string} param.serviceDir - The service directory
|
|
453
|
+
* @param {string} param.serviceName - The service name
|
|
454
|
+
* @param {string} param.database - The database type
|
|
455
|
+
* @param {object} param.templateArgs - The template arguments
|
|
456
|
+
*/
|
|
457
|
+
scaffoldHandlebarTemplates: function ({ templateFiles, serviceDir, serviceName, database, templateArgs }) {
|
|
458
|
+
try {
|
|
459
|
+
// Register Handlebars helpers
|
|
460
|
+
Object.entries(helpers).forEach(([name, fn]) => {
|
|
461
|
+
Handlebars.registerHelper(name, fn);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Write the files to the service directory
|
|
465
|
+
templateFiles.forEach(file => {
|
|
466
|
+
let sourcePath;
|
|
467
|
+
let targetPath;
|
|
468
|
+
|
|
469
|
+
let fileRelativePath = file.relativePath;
|
|
470
|
+
|
|
471
|
+
// Database specific
|
|
472
|
+
if (fileRelativePath.includes('mongodb.')) {
|
|
473
|
+
if (database !== 'mongodb') {
|
|
474
|
+
return;
|
|
475
|
+
} else {
|
|
476
|
+
fileRelativePath = fileRelativePath.replace('mongodb.', '');
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (fileRelativePath.includes('mysql.')) {
|
|
481
|
+
if (database !== 'mysql') {
|
|
482
|
+
return;
|
|
483
|
+
} else {
|
|
484
|
+
fileRelativePath = fileRelativePath.replace('mysql.', '');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
switch (file.extension) {
|
|
489
|
+
case '.hbs':
|
|
490
|
+
// Compile the Handlebars template for content
|
|
491
|
+
const templateContent = fs.readFileSync(file.fullPath, 'utf8');
|
|
492
|
+
const compiledTemplate = Handlebars.compile(templateContent);
|
|
493
|
+
const renderedContent = compiledTemplate(templateArgs);
|
|
494
|
+
|
|
495
|
+
// Compile the Handlebars template for the filename (excluding .hbs)
|
|
496
|
+
const filenameTemplate = Handlebars.compile(fileRelativePath.replace(/\.hbs$/, ''));
|
|
497
|
+
const renderedFilename = filenameTemplate(templateArgs);
|
|
498
|
+
targetPath = path.join(serviceDir, renderedFilename);
|
|
499
|
+
|
|
500
|
+
// Ensure directory exists
|
|
501
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
502
|
+
fs.writeFileSync(targetPath, renderedContent, 'utf8');
|
|
503
|
+
break;
|
|
504
|
+
default:
|
|
505
|
+
// By default, copy the file to the service directory
|
|
506
|
+
sourcePath = file.fullPath;
|
|
507
|
+
targetPath = path.join(serviceDir, fileRelativePath);
|
|
508
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
509
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
} catch (error) {
|
|
514
|
+
throw new Error('Error scaffolding Handlebars templates: ' + error.message);
|
|
515
|
+
}
|
|
424
516
|
}
|
|
425
517
|
}
|