underpost 2.8.646 → 2.8.652

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/src/cli/deploy.js CHANGED
@@ -13,15 +13,31 @@ import { shellExec } from '../server/process.js';
13
13
  import fs from 'fs-extra';
14
14
  import dotenv from 'dotenv';
15
15
  import { DataBaseProvider } from '../db/DataBaseProvider.js';
16
+ import UnderpostRootEnv from './env.js';
17
+ import UnderpostCluster from './cluster.js';
16
18
 
17
19
  const logger = loggerFactory(import.meta);
18
20
 
19
21
  class UnderpostDeploy {
20
22
  static NETWORK = {};
21
23
  static API = {
22
- sync(deployList) {
24
+ sync(deployList, { versions, replicas }) {
23
25
  const deployGroupId = 'dd.tmp';
24
26
  fs.writeFileSync(`./engine-private/deploy/${deployGroupId}`, deployList, 'utf8');
27
+ const totalPods = deployList.split(',').length * versions.split(',').length * parseInt(replicas);
28
+ const limitFactor = 0.95;
29
+ const reserveFactor = 0.35;
30
+ const resources = UnderpostCluster.API.getResourcesCapacity();
31
+ const memory = parseInt(resources.memory.value / totalPods);
32
+ const cpu = parseInt(resources.cpu.value / totalPods);
33
+ UnderpostRootEnv.API.set(
34
+ 'resources.requests.memory',
35
+ `${parseInt(memory * reserveFactor)}${resources.memory.unit}`,
36
+ );
37
+ UnderpostRootEnv.API.set('resources.requests.cpu', `${parseInt(cpu * reserveFactor)}${resources.cpu.unit}`);
38
+ UnderpostRootEnv.API.set('resources.limits.memory', `${parseInt(memory * limitFactor)}${resources.memory.unit}`);
39
+ UnderpostRootEnv.API.set('resources.limits.cpu', `${parseInt(cpu * limitFactor)}${resources.cpu.unit}`);
40
+ UnderpostRootEnv.API.set('total-pods', totalPods);
25
41
  return getDataDeploy({
26
42
  buildSingleReplica: true,
27
43
  deployGroupId,
@@ -35,52 +51,43 @@ class UnderpostDeploy {
35
51
  await Config.build(undefined, 'proxy', deployList);
36
52
  return buildPortProxyRouter(env === 'development' ? 80 : 443, buildProxyRouter());
37
53
  },
38
- async buildManifest(deployList, env, version) {
39
- for (const _deployId of deployList.split(',')) {
40
- const deployId = _deployId.trim();
41
- if (!deployId) continue;
42
- const confServer = loadReplicas(
43
- JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
44
- 'proxy',
45
- );
46
- const router = await UnderpostDeploy.API.routerFactory(deployId, env);
47
- const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
48
- const { fromPort, toPort } = deployRangePortFactory(router);
49
-
50
- fs.mkdirSync(`./engine-private/conf/${deployId}/build/${env}`, { recursive: true });
51
- if (env === 'development') fs.mkdirSync(`./manifests/deployment/${deployId}-${env}`, { recursive: true });
52
-
53
- logger.info('port range', { deployId, fromPort, toPort });
54
- // const customImg = `underpost-engine:${version && typeof version === 'string' ? version : '0.0.0'}`;
55
- // lifecycle:
56
- // postStart:
57
- // exec:
58
- // command:
59
- // - /bin/sh
60
- // - -c
61
- // - >
62
- // sleep 20 &&
63
- // npm install -g underpost &&
64
- // underpost secret underpost --create-from-file /etc/config/.env.${env}
65
- const deploymentYamlParts = `apiVersion: apps/v1
54
+ deploymentYamlServiceFactory({ deployId, env, port, deploymentVersions }) {
55
+ return deploymentVersions
56
+ .map(
57
+ (version, i) => ` - name: ${deployId}-${env}-${version}-service
58
+ port: ${port}
59
+ weight: ${i === 0 ? 100 : 0}
60
+ `,
61
+ )
62
+ .join('');
63
+ },
64
+ deploymentYamlPartsFactory({ deployId, env, suffix, resources, replicas }) {
65
+ return `apiVersion: apps/v1
66
66
  kind: Deployment
67
67
  metadata:
68
- name: ${deployId}-${env}
68
+ name: ${deployId}-${env}-${suffix}
69
69
  labels:
70
- app: ${deployId}-${env}
70
+ app: ${deployId}-${env}-${suffix}
71
71
  spec:
72
- replicas: 2
72
+ replicas: ${replicas}
73
73
  selector:
74
74
  matchLabels:
75
- app: ${deployId}-${env}
75
+ app: ${deployId}-${env}-${suffix}
76
76
  template:
77
77
  metadata:
78
78
  labels:
79
- app: ${deployId}-${env}
79
+ app: ${deployId}-${env}-${suffix}
80
80
  spec:
81
81
  containers:
82
- - name: ${deployId}-${env}
82
+ - name: ${deployId}-${env}-${suffix}
83
83
  image: localhost/debian:underpost
84
+ resources:
85
+ requests:
86
+ memory: "${resources.requests.memory}"
87
+ cpu: "${resources.requests.cpu}"
88
+ limits:
89
+ memory: "${resources.limits.memory}"
90
+ cpu: "${resources.limits.cpu}"
84
91
  command:
85
92
  - /bin/sh
86
93
  - -c
@@ -96,27 +103,50 @@ spec:
96
103
  - name: config-volume
97
104
  configMap:
98
105
  name: underpost-config
99
- # image: localhost/${deployId}-${env}:${version && typeof version === 'string' ? version : '0.0.0'}
100
106
  ---
101
107
  apiVersion: v1
102
108
  kind: Service
103
109
  metadata:
104
- name: ${deployId}-${env}-service
110
+ name: ${deployId}-${env}-${suffix}-service
105
111
  spec:
106
112
  selector:
107
- app: ${deployId}-${env}
113
+ app: ${deployId}-${env}-${suffix}
108
114
  ports:
109
- type: LoadBalancer`.split('ports:');
110
- deploymentYamlParts[1] =
111
- buildKindPorts(fromPort, toPort) +
112
- ` type: LoadBalancer
113
- `;
115
+ {{ports}} type: LoadBalancer`;
116
+ },
117
+ async buildManifest(deployList, env, options) {
118
+ const resources = UnderpostDeploy.API.resourcesFactory();
119
+ const replicas = options.replicas;
114
120
 
115
- fs.writeFileSync(
116
- `./engine-private/conf/${deployId}/build/${env}/deployment.yaml`,
117
- deploymentYamlParts.join(`ports:
118
- `),
121
+ for (const _deployId of deployList.split(',')) {
122
+ const deployId = _deployId.trim();
123
+ if (!deployId) continue;
124
+ const confServer = loadReplicas(
125
+ JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
126
+ 'proxy',
119
127
  );
128
+ const router = await UnderpostDeploy.API.routerFactory(deployId, env);
129
+ const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
130
+ const { fromPort, toPort } = deployRangePortFactory(router);
131
+ const deploymentVersions = options.versions.split(',');
132
+ fs.mkdirSync(`./engine-private/conf/${deployId}/build/${env}`, { recursive: true });
133
+ if (env === 'development') fs.mkdirSync(`./manifests/deployment/${deployId}-${env}`, { recursive: true });
134
+
135
+ logger.info('port range', { deployId, fromPort, toPort });
136
+
137
+ let deploymentYamlParts = '';
138
+ for (const deploymentVersion of deploymentVersions) {
139
+ deploymentYamlParts += `---
140
+ ${UnderpostDeploy.API.deploymentYamlPartsFactory({
141
+ deployId,
142
+ env,
143
+ suffix: deploymentVersion,
144
+ resources,
145
+ replicas,
146
+ }).replace('{{ports}}', buildKindPorts(fromPort, toPort))}
147
+ `;
148
+ }
149
+ fs.writeFileSync(`./engine-private/conf/${deployId}/build/${env}/deployment.yaml`, deploymentYamlParts, 'utf8');
120
150
 
121
151
  let proxyYaml = '';
122
152
  let secretYaml = '';
@@ -163,8 +193,13 @@ spec:
163
193
  - prefix: ${path}
164
194
  enableWebsockets: true
165
195
  services:
166
- - name: ${deployId}-${env}-service
167
- port: ${port}`;
196
+ ${UnderpostDeploy.API.deploymentYamlServiceFactory({
197
+ deployId,
198
+ env,
199
+ port,
200
+ deploymentVersions:
201
+ options.traffic && typeof options.traffic === 'string' ? options.traffic.split(',') : ['blue'],
202
+ })}`;
168
203
  }
169
204
  }
170
205
  const yamlPath = `./engine-private/conf/${deployId}/build/${env}/proxy.yaml`;
@@ -196,8 +231,10 @@ spec:
196
231
  infoUtil: false,
197
232
  expose: false,
198
233
  cert: false,
199
- version: '',
234
+ versions: '',
235
+ traffic: '',
200
236
  dashboardUpdate: false,
237
+ replicas: '',
201
238
  },
202
239
  ) {
203
240
  if (options.infoUtil === true)
@@ -208,8 +245,10 @@ kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
208
245
  `);
209
246
  if (deployList === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`))
210
247
  deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
211
- if (options.sync) UnderpostDeploy.API.sync(deployList);
212
- if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env, options.version);
248
+ if (!(options.versions && typeof options.versions === 'string')) options.versions = 'blue,green';
249
+ if (!options.replicas) options.replicas = 2;
250
+ if (options.sync) UnderpostDeploy.API.sync(deployList, options);
251
+ if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env, options);
213
252
  if (options.infoRouter === true) logger.info('router', await UnderpostDeploy.API.routerFactory(deployList, env));
214
253
  if (options.dashboardUpdate === true) await UnderpostDeploy.API.updateDashboardData(deployList, env, options);
215
254
  if (options.infoRouter === true) return;
@@ -236,8 +275,11 @@ kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
236
275
  shellExec(`sudo kubectl port-forward -n default svc/${svc.NAME} ${port}:${port}`, { async: true });
237
276
  continue;
238
277
  }
239
- shellExec(`sudo kubectl delete svc ${deployId}-${env}-service`);
240
- shellExec(`sudo kubectl delete deployment ${deployId}-${env}`);
278
+
279
+ for (const version of options.versions.split(',')) {
280
+ shellExec(`sudo kubectl delete svc ${deployId}-${env}-${version}-service`);
281
+ shellExec(`sudo kubectl delete deployment ${deployId}-${env}-${version}`);
282
+ }
241
283
 
242
284
  const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
243
285
  for (const host of Object.keys(confServer)) {
@@ -315,6 +357,19 @@ kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
315
357
 
316
358
  return result;
317
359
  },
360
+ resourcesFactory() {
361
+ return {
362
+ requests: {
363
+ memory: UnderpostRootEnv.API.get('resources.requests.memory'),
364
+ cpu: UnderpostRootEnv.API.get('resources.requests.cpu'),
365
+ },
366
+ limits: {
367
+ memory: UnderpostRootEnv.API.get('resources.limits.memory'),
368
+ cpu: UnderpostRootEnv.API.get('resources.limits.cpu'),
369
+ },
370
+ totalPods: UnderpostRootEnv.API.get('total-pods'),
371
+ };
372
+ },
318
373
  async updateDashboardData(deployList, env, options) {
319
374
  try {
320
375
  const deployId = process.env.DEFAULT_DEPLOY_ID;
@@ -0,0 +1,300 @@
1
+ import dotenv from 'dotenv';
2
+ import { Command } from 'commander';
3
+ import Underpost from '../index.js';
4
+ import { getUnderpostRootPath, loadConf } from '../server/conf.js';
5
+ import fs from 'fs-extra';
6
+ import { commitData } from '../client/components/core/CommonJs.js';
7
+ import { shellExec } from '../server/process.js';
8
+
9
+ const underpostRootPath = getUnderpostRootPath();
10
+ fs.existsSync(`${underpostRootPath}/.env`)
11
+ ? dotenv.config({ path: `${underpostRootPath}/.env`, override: true })
12
+ : dotenv.config();
13
+
14
+ const program = new Command();
15
+
16
+ program.name('underpost').description(`underpost ci/cd cli ${Underpost.version}`).version(Underpost.version);
17
+
18
+ program
19
+ .command('new')
20
+ .argument('<app-name>', 'Application name')
21
+ .description('Create a new project')
22
+ .action(Underpost.repo.new);
23
+
24
+ program
25
+ .command('start')
26
+ .argument('<deploy-id>', 'Deploy configuration id')
27
+ .argument('[env]', 'Optional environment, for default is development')
28
+ .option('--run', 'Run app servers and monitor health server')
29
+ .option('--build', 'Build app client')
30
+ .action(Underpost.start.callback)
31
+ .description('Start up server, build pipelines, or services');
32
+
33
+ program
34
+ .command('clone')
35
+ .argument(`<uri>`, 'e.g. username/repository')
36
+ .option('--bare', 'Clone only .git files')
37
+ .description('Clone github repository')
38
+ .action(Underpost.repo.clone);
39
+
40
+ program
41
+ .command('pull')
42
+ .argument('<path>', 'Absolute or relative directory')
43
+ .argument(`<uri>`, 'e.g. username/repository')
44
+ .description('Pull github repository')
45
+ .action(Underpost.repo.pull);
46
+
47
+ program
48
+ .command('cmt')
49
+ .argument('<path>', 'Absolute or relative directory')
50
+ .argument(`<commit-type>`, `Options: ${Object.keys(commitData)}`)
51
+ .argument(`[module-tag]`, 'Optional set module tag')
52
+ .argument(`[message]`, 'Optional set additional message')
53
+ .option('--empty', 'Allow empty files')
54
+ .option('--copy', 'Copy to clipboard message')
55
+ .option('--info', 'Info commit types')
56
+ .description('Commit github repository')
57
+ .action(Underpost.repo.commit);
58
+
59
+ program
60
+ .command('push')
61
+ .argument('<path>', 'Absolute or relative directory')
62
+ .argument(`<uri>`, 'e.g. username/repository')
63
+ .option('-f', 'Force push overwriting repository')
64
+ .description('Push github repository')
65
+ .action(Underpost.repo.push);
66
+
67
+ program
68
+ .command('env')
69
+ .argument('<deploy-id>', `deploy configuration id, if 'clean' restore default`)
70
+ .argument('[env]', 'Optional environment, for default is production')
71
+ .description('Set environment variables files and conf related to <deploy-id>')
72
+ .action(loadConf);
73
+
74
+ program
75
+ .command('config')
76
+ .argument('operator', `Options: ${Object.keys(Underpost.env)}`)
77
+ .argument('[key]', 'Config key')
78
+ .argument('[value]', 'Config value')
79
+ .description(`Manage configuration, operators`)
80
+ .action((...args) => Underpost.env[args[0]](args[1], args[2]));
81
+
82
+ program
83
+ .command('root')
84
+ .description('Get npm root path')
85
+ .action(() => console.log(getNpmRootPath()));
86
+
87
+ program
88
+ .command('cluster')
89
+ .argument('[pod-name]', 'Optional pod name filter')
90
+ .option('--reset', `Delete all clusters and prune all data and caches`)
91
+ .option('--mariadb', 'Init with mariadb statefulset')
92
+ .option('--mongodb', 'Init with mongodb statefulset')
93
+ .option('--mongodb4', 'Init with mongodb 4.4 service')
94
+ .option('--valkey', 'Init with valkey service')
95
+ .option('--contour', 'Init with project contour base HTTPProxy and envoy')
96
+ .option('--cert-manager', 'Init with letsencrypt-prod ClusterIssuer')
97
+ .option('--info', 'Get all kinds objects deployed')
98
+ .option('--full', 'Init with all statefulsets and services available')
99
+ .option('--ns-use <ns-name>', 'Switches current context to namespace')
100
+ .option('--dev', 'init with dev cluster')
101
+ .option('--list-pods', 'Display list pods information')
102
+ .option('--info-capacity', 'display current total machine capacity info')
103
+ .option('--info-capacity-pod', 'display current machine capacity pod info')
104
+ .action(Underpost.cluster.init)
105
+ .description('Manage cluster, for default initialization base kind cluster');
106
+
107
+ program
108
+ .command('deploy')
109
+ .argument('<deploy-list>', 'Deploy id list, e.g. default-a,default-b')
110
+ .argument('[env]', 'Optional environment, for default is development')
111
+ .option('--remove', 'Delete deployments and services')
112
+ .option('--sync', 'Sync deployments env, ports, and replicas')
113
+ .option('--info-router', 'Display router structure')
114
+ .option('--expose', 'Expose service match deploy-list')
115
+ .option('--info-util', 'Display kubectl util management commands')
116
+ .option('--cert', 'Reset tls/ssl certificate secrets')
117
+ .option('--build-manifest', 'Build kind yaml manifests: deployments, services, proxy and secrets')
118
+ .option('--dashboard-update', 'Update dashboard instance data with current router config')
119
+ .option('--replicas <replicas>', 'Set custom number of replicas')
120
+ .option('--versions <deployment-versions>', 'Comma separated custom deployment versions')
121
+ .option('--traffic <traffic-versions>', 'Comma separated custom deployment traffic')
122
+ .description('Manage deployment, for default deploy development pods')
123
+ .action(Underpost.deploy.callback);
124
+
125
+ program
126
+ .command('secret')
127
+ .argument('<platform>', `Options: ${Object.keys(Underpost.secret)}`)
128
+ .option('--init', 'Init secrets platform environment')
129
+ .option('--create-from-file <path-env-file>', 'Create secret from env file')
130
+ .option('--list', 'Lists secrets')
131
+ // .option('--delete [secret-key]', 'Delete key secret, if not set, are default delete all')
132
+ // .option('--create [secret-key] [secret-value]', 'Create secret key, with secret value')
133
+ .description(`Manage secrets`)
134
+ .action((...args) => {
135
+ if (args[1].createFromFile) return Underpost.secret[args[0]].createFromEnvFile(args[1].createFromFile);
136
+ if (args[1].list) return Underpost.secret[args[0]].list();
137
+ if (args[1].init) return Underpost.secret[args[0]].init();
138
+ });
139
+
140
+ program
141
+ .command('dockerfile-image-build')
142
+ .option('--path [path]', 'Dockerfile path')
143
+ .option('--image-name [image-name]', 'Set image name')
144
+ .option('--image-path [image-path]', 'Set tar image path')
145
+ .option('--dockerfile-name [dockerfile-name]', 'set Dockerfile name')
146
+ .option('--podman-save', 'Export tar file from podman')
147
+ .option('--kind-load', 'Import tar image to Kind cluster')
148
+ .option('--secrets', 'Dockerfile env secrets')
149
+ .option('--secrets-path [secrets-path]', 'Dockerfile custom path env secrets')
150
+ .option('--no-cache', 'Build without using cache')
151
+ .description('Build image from Dockerfile')
152
+ .action(Underpost.image.dockerfile.build);
153
+
154
+ program
155
+ .command('dockerfile-pull-base-images')
156
+ .description('Pull underpost dockerfile images requirements')
157
+ .action(Underpost.image.dockerfile.pullBaseImages);
158
+
159
+ program
160
+ .command('install')
161
+ .description('Fast import underpost npm dependencies')
162
+ .action(() => {
163
+ fs.copySync(`${underpostRootPath}/node_modules`, './node_modules');
164
+ });
165
+
166
+ program
167
+ .command('db')
168
+ .argument('<deploy-list>', 'Deploy id list, e.g. default-a,default-b')
169
+ .option('--import', 'Import container backups from repositories')
170
+ .option('--export', 'Export container backups to repositories')
171
+ .option('--pod-name <pod-name>', 'Optional pod context')
172
+ .option('--collections <collections>', 'Comma separated collections')
173
+ .option('--out-path <out-path>', 'Custom out path backup')
174
+ .option('--drop', 'Drop databases')
175
+ .option('--preserveUUID', 'Preserve Ids')
176
+ .option('--git', 'Upload to github')
177
+ .option('--hosts <hosts>', 'Comma separated hosts')
178
+ .option('--paths <paths>', 'Comma separated paths')
179
+ .option('--ns <ns-name>', 'Optional name space context')
180
+ .description('Manage databases')
181
+ .action(Underpost.db.callback);
182
+
183
+ program
184
+ .command('script')
185
+ .argument('operator', `Options: ${Object.keys(Underpost.script)}`)
186
+ .argument('<script-name>', 'Script name')
187
+ .argument('[script-value]', 'Literal command, or path')
188
+ .option('--itc', 'Inside container execution context')
189
+ .option('--itc-path', 'Inside container path options')
190
+ .option('--ns <ns-name>', 'Options name space context')
191
+ .option('--pod-name <pod-name>')
192
+ .description(
193
+ 'Supports a number of built-in underpost global scripts and their preset life cycle events as well as arbitrary scripts',
194
+ )
195
+ .action((...args) => Underpost.script[args[0]](args[1], args[2], args[3]));
196
+
197
+ program
198
+ .command('cron')
199
+ .argument('[deploy-list]', 'Deploy id list, e.g. default-a,default-b')
200
+ .argument('[job-list]', `Deploy id list, e.g. ${Object.keys(Underpost.cron)}, for default all available jobs`)
201
+ .option('--itc', 'Inside container execution context')
202
+ .option('--init', 'Init cron jobs for cron job default deploy id')
203
+ .option('--git', 'Upload to github')
204
+ .option('--dashboard-update', 'Update dashboard cron data with current jobs config')
205
+ .description('Cron jobs management')
206
+ .action(Underpost.cron.callback);
207
+
208
+ program
209
+ .command('fs')
210
+ .argument('[path]', 'Absolute or relative directory')
211
+ .option('--rm', 'Remove file')
212
+ .option('--git', 'Current git changes')
213
+ .option('--recursive', 'Upload files recursively')
214
+ .option('--deploy-id <deploy-id>', 'Deploy configuration id')
215
+ .option('--pull', 'Download file')
216
+ .option('--force', 'Force action')
217
+ .description('File storage management, for default upload file')
218
+ .action(Underpost.fs.callback);
219
+
220
+ program
221
+ .command('test')
222
+ .argument('[deploy-list]', 'Deploy id list, e.g. default-a,default-b')
223
+ .description('Manage Test, for default run current underpost default test')
224
+ .option('--itc', 'Inside container execution context')
225
+ .option('--sh', 'Copy to clipboard, container entrypoint shell command')
226
+ .option('--logs', 'Display container logs')
227
+ .option('--pod-name <pod-name>')
228
+ .option('--pod-status <pod-status>')
229
+ .option('--kind-type <kind-type>')
230
+ .action(Underpost.test.callback);
231
+
232
+ program
233
+ .command('monitor')
234
+ .argument('<deploy-id>', 'Deploy configuration id')
235
+ .argument('[env]', 'Optional environment, for default is development')
236
+ .option('--ms-interval <ms-interval>', 'Custom ms interval delta time')
237
+ .option('--now', 'Exec immediately monitor script')
238
+ .option('--single', 'Disable recurrence')
239
+ .option('--type <type>', 'Set custom monitor type')
240
+ .description('Monitor health server management')
241
+ .action(Underpost.monitor.callback);
242
+
243
+ const buildCliDoc = () => {
244
+ let md = shellExec(`node bin help`, { silent: true, stdout: true }).split('Options:');
245
+ const baseOptions =
246
+ `## ${md[0].split(`\n`)[2]}
247
+
248
+ ### Usage: ` +
249
+ '`' +
250
+ md[0].split(`\n`)[0].split('Usage: ')[1] +
251
+ '`' +
252
+ `
253
+ ` +
254
+ '```\n Options:' +
255
+ md[1] +
256
+ ' \n```';
257
+ md =
258
+ baseOptions +
259
+ `
260
+
261
+ ## Commands:
262
+ `;
263
+ program.commands.map((o) => {
264
+ md +=
265
+ `
266
+
267
+ ` +
268
+ '### `' +
269
+ o._name +
270
+ '` :' +
271
+ `
272
+ ` +
273
+ '```\n ' +
274
+ shellExec(`node bin help ${o._name}`, { silent: true, stdout: true }) +
275
+ ' \n```' +
276
+ `
277
+ `;
278
+ });
279
+ fs.writeFileSync(`./src/client/public/nexodev/docs/references/Command Line Interface.md`, md, 'utf8');
280
+ fs.writeFileSync(`./cli.md`, md, 'utf8');
281
+ const readmeSplit = `pwa-microservices-template</a>`;
282
+ const readme = fs.readFileSync(`./README.md`, 'utf8').split(readmeSplit);
283
+ fs.writeFileSync(
284
+ './README.md',
285
+ readme[0] +
286
+ readmeSplit +
287
+ `
288
+
289
+ ` +
290
+ baseOptions +
291
+ `
292
+
293
+ <a target="_top" href="https://github.com/underpostnet/pwa-microservices-template/blob/master/cli.md">See complete CLI Docs here.</a>
294
+
295
+ `,
296
+ 'utf8',
297
+ );
298
+ };
299
+
300
+ export { program, buildCliDoc };