@optimizely/ocp-cli 1.0.0-beta.2
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/LICENSE +201 -0
- package/README.md +18 -0
- package/bin/opti.js +3 -0
- package/dist/commands/accounts/Whoami.d.ts +3 -0
- package/dist/commands/accounts/Whoami.js +34 -0
- package/dist/commands/accounts/Whoami.js.map +1 -0
- package/dist/commands/accounts/Whois.d.ts +6 -0
- package/dist/commands/accounts/Whois.js +65 -0
- package/dist/commands/accounts/Whois.js.map +1 -0
- package/dist/commands/app/BaseBuildCommand.d.ts +23 -0
- package/dist/commands/app/BaseBuildCommand.js +227 -0
- package/dist/commands/app/BaseBuildCommand.js.map +1 -0
- package/dist/commands/app/Init.d.ts +20 -0
- package/dist/commands/app/Init.js +285 -0
- package/dist/commands/app/Init.js.map +1 -0
- package/dist/commands/app/Logs.d.ts +19 -0
- package/dist/commands/app/Logs.js +230 -0
- package/dist/commands/app/Logs.js.map +1 -0
- package/dist/commands/app/Package.d.ts +4 -0
- package/dist/commands/app/Package.js +51 -0
- package/dist/commands/app/Package.js.map +1 -0
- package/dist/commands/app/Prepare.d.ts +8 -0
- package/dist/commands/app/Prepare.js +112 -0
- package/dist/commands/app/Prepare.js.map +1 -0
- package/dist/commands/app/Register.d.ts +7 -0
- package/dist/commands/app/Register.js +58 -0
- package/dist/commands/app/Register.js.map +1 -0
- package/dist/commands/app/Validate.d.ts +4 -0
- package/dist/commands/app/Validate.js +27 -0
- package/dist/commands/app/Validate.js.map +1 -0
- package/dist/commands/availability/List.d.ts +4 -0
- package/dist/commands/availability/List.js +47 -0
- package/dist/commands/availability/List.js.map +1 -0
- package/dist/commands/directory/Info.d.ts +7 -0
- package/dist/commands/directory/Info.js +92 -0
- package/dist/commands/directory/Info.js.map +1 -0
- package/dist/commands/directory/Install.d.ts +6 -0
- package/dist/commands/directory/Install.js +54 -0
- package/dist/commands/directory/Install.js.map +1 -0
- package/dist/commands/directory/List.d.ts +8 -0
- package/dist/commands/directory/List.js +102 -0
- package/dist/commands/directory/List.js.map +1 -0
- package/dist/commands/directory/ListFunctions.d.ts +7 -0
- package/dist/commands/directory/ListFunctions.js +77 -0
- package/dist/commands/directory/ListFunctions.js.map +1 -0
- package/dist/commands/directory/ListGlobalFunctions.d.ts +6 -0
- package/dist/commands/directory/ListGlobalFunctions.js +72 -0
- package/dist/commands/directory/ListGlobalFunctions.js.map +1 -0
- package/dist/commands/directory/ListInstalls.d.ts +8 -0
- package/dist/commands/directory/ListInstalls.js +81 -0
- package/dist/commands/directory/ListInstalls.js.map +1 -0
- package/dist/commands/directory/Publish.d.ts +8 -0
- package/dist/commands/directory/Publish.js +180 -0
- package/dist/commands/directory/Publish.js.map +1 -0
- package/dist/commands/directory/Status.d.ts +5 -0
- package/dist/commands/directory/Status.js +60 -0
- package/dist/commands/directory/Status.js.map +1 -0
- package/dist/commands/directory/Uninstall.d.ts +6 -0
- package/dist/commands/directory/Uninstall.js +50 -0
- package/dist/commands/directory/Uninstall.js.map +1 -0
- package/dist/commands/directory/Unpublish.d.ts +10 -0
- package/dist/commands/directory/Unpublish.js +181 -0
- package/dist/commands/directory/Unpublish.js.map +1 -0
- package/dist/commands/directory/Uprade.d.ts +8 -0
- package/dist/commands/directory/Uprade.js +100 -0
- package/dist/commands/directory/Uprade.js.map +1 -0
- package/dist/commands/env/GetEnvironment.d.ts +3 -0
- package/dist/commands/env/GetEnvironment.js +28 -0
- package/dist/commands/env/GetEnvironment.js.map +1 -0
- package/dist/commands/env/SetEnvironment.d.ts +4 -0
- package/dist/commands/env/SetEnvironment.js +63 -0
- package/dist/commands/env/SetEnvironment.js.map +1 -0
- package/dist/commands/jobs/List.d.ts +21 -0
- package/dist/commands/jobs/List.js +268 -0
- package/dist/commands/jobs/List.js.map +1 -0
- package/dist/commands/jobs/RuntimeStatus.d.ts +5 -0
- package/dist/commands/jobs/RuntimeStatus.js +65 -0
- package/dist/commands/jobs/RuntimeStatus.js.map +1 -0
- package/dist/commands/jobs/Terminate.d.ts +5 -0
- package/dist/commands/jobs/Terminate.js +45 -0
- package/dist/commands/jobs/Terminate.js.map +1 -0
- package/dist/commands/jobs/Trigger.d.ts +8 -0
- package/dist/commands/jobs/Trigger.js +58 -0
- package/dist/commands/jobs/Trigger.js.map +1 -0
- package/dist/commands/review/List.d.ts +5 -0
- package/dist/commands/review/List.js +81 -0
- package/dist/commands/review/List.js.map +1 -0
- package/dist/commands/review/Open.d.ts +4 -0
- package/dist/commands/review/Open.js +42 -0
- package/dist/commands/review/Open.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/AppContext.d.ts +9 -0
- package/dist/lib/AppContext.js +43 -0
- package/dist/lib/AppContext.js.map +1 -0
- package/dist/lib/AppPackager.d.ts +8 -0
- package/dist/lib/AppPackager.js +40 -0
- package/dist/lib/AppPackager.js.map +1 -0
- package/dist/lib/AppUpdater.d.ts +9 -0
- package/dist/lib/AppUpdater.js +155 -0
- package/dist/lib/AppUpdater.js.map +1 -0
- package/dist/lib/AppUploader.d.ts +8 -0
- package/dist/lib/AppUploader.js +36 -0
- package/dist/lib/AppUploader.js.map +1 -0
- package/dist/lib/Config.d.ts +15 -0
- package/dist/lib/Config.js +47 -0
- package/dist/lib/Config.js.map +1 -0
- package/dist/lib/EnvironmentalOutput.d.ts +1 -0
- package/dist/lib/EnvironmentalOutput.js +21 -0
- package/dist/lib/EnvironmentalOutput.js.map +1 -0
- package/dist/lib/Moria.d.ts +51 -0
- package/dist/lib/Moria.js +30 -0
- package/dist/lib/Moria.js.map +1 -0
- package/dist/lib/MoriaApi.d.ts +22 -0
- package/dist/lib/MoriaApi.js +75 -0
- package/dist/lib/MoriaApi.js.map +1 -0
- package/dist/lib/Rivendell.d.ts +351 -0
- package/dist/lib/Rivendell.js +328 -0
- package/dist/lib/Rivendell.js.map +1 -0
- package/dist/lib/RivendellApi.d.ts +22 -0
- package/dist/lib/RivendellApi.js +90 -0
- package/dist/lib/RivendellApi.js.map +1 -0
- package/dist/lib/Shards.d.ts +3 -0
- package/dist/lib/Shards.js +34 -0
- package/dist/lib/Shards.js.map +1 -0
- package/dist/lib/StringUtils.d.ts +1 -0
- package/dist/lib/StringUtils.js +9 -0
- package/dist/lib/StringUtils.js.map +1 -0
- package/dist/lib/TeminalPassthru.d.ts +5 -0
- package/dist/lib/TeminalPassthru.js +12 -0
- package/dist/lib/TeminalPassthru.js.map +1 -0
- package/dist/lib/TerminalConfirm.d.ts +3 -0
- package/dist/lib/TerminalConfirm.js +26 -0
- package/dist/lib/TerminalConfirm.js.map +1 -0
- package/dist/lib/TerminalInput.d.ts +32 -0
- package/dist/lib/TerminalInput.js +207 -0
- package/dist/lib/TerminalInput.js.map +1 -0
- package/dist/lib/TerminalMenu.d.ts +34 -0
- package/dist/lib/TerminalMenu.js +186 -0
- package/dist/lib/TerminalMenu.js.map +1 -0
- package/dist/lib/TerminalOutput.d.ts +5 -0
- package/dist/lib/TerminalOutput.js +12 -0
- package/dist/lib/TerminalOutput.js.map +1 -0
- package/dist/lib/TerminalSpinner.d.ts +15 -0
- package/dist/lib/TerminalSpinner.js +71 -0
- package/dist/lib/TerminalSpinner.js.map +1 -0
- package/dist/lib/build.d.ts +5 -0
- package/dist/lib/build.js +35 -0
- package/dist/lib/build.js.map +1 -0
- package/dist/lib/checkForUpdate.d.ts +2 -0
- package/dist/lib/checkForUpdate.js +58 -0
- package/dist/lib/checkForUpdate.js.map +1 -0
- package/dist/lib/dev/app.d.ts +8 -0
- package/dist/lib/dev/app.js +53 -0
- package/dist/lib/dev/app.js.map +1 -0
- package/dist/lib/dev/index.d.ts +1 -0
- package/dist/lib/dev/index.js +14 -0
- package/dist/lib/dev/index.js.map +1 -0
- package/dist/lib/dev/logger.d.ts +15 -0
- package/dist/lib/dev/logger.js +58 -0
- package/dist/lib/dev/logger.js.map +1 -0
- package/dist/lib/die.d.ts +5 -0
- package/dist/lib/die.js +14 -0
- package/dist/lib/die.js.map +1 -0
- package/dist/lib/directoryExists.d.ts +1 -0
- package/dist/lib/directoryExists.js +9 -0
- package/dist/lib/directoryExists.js.map +1 -0
- package/dist/lib/formatBuildState.d.ts +2 -0
- package/dist/lib/formatBuildState.js +23 -0
- package/dist/lib/formatBuildState.js.map +1 -0
- package/dist/lib/formatError.d.ts +1 -0
- package/dist/lib/formatError.js +45 -0
- package/dist/lib/formatError.js.map +1 -0
- package/dist/lib/formatJobStatus.d.ts +3 -0
- package/dist/lib/formatJobStatus.js +28 -0
- package/dist/lib/formatJobStatus.js.map +1 -0
- package/dist/lib/formatReviewStatus.d.ts +4 -0
- package/dist/lib/formatReviewStatus.js +28 -0
- package/dist/lib/formatReviewStatus.js.map +1 -0
- package/dist/lib/formatTimstamp.d.ts +1 -0
- package/dist/lib/formatTimstamp.js +25 -0
- package/dist/lib/formatTimstamp.js.map +1 -0
- package/dist/lib/formatVersionState.d.ts +2 -0
- package/dist/lib/formatVersionState.js +35 -0
- package/dist/lib/formatVersionState.js.map +1 -0
- package/dist/lib/gatherAppEnv.d.ts +3 -0
- package/dist/lib/gatherAppEnv.js +71 -0
- package/dist/lib/gatherAppEnv.js.map +1 -0
- package/dist/lib/handleInterrupt.d.ts +1 -0
- package/dist/lib/handleInterrupt.js +17 -0
- package/dist/lib/handleInterrupt.js.map +1 -0
- package/dist/lib/jobRuntime.d.ts +3 -0
- package/dist/lib/jobRuntime.js +16 -0
- package/dist/lib/jobRuntime.js.map +1 -0
- package/dist/lib/parseDate.d.ts +2 -0
- package/dist/lib/parseDate.js +26 -0
- package/dist/lib/parseDate.js.map +1 -0
- package/dist/lib/templating/TemplateRenderer.d.ts +13 -0
- package/dist/lib/templating/TemplateRenderer.js +62 -0
- package/dist/lib/templating/TemplateRenderer.js.map +1 -0
- package/dist/lib/templating/fetchTemplatesManifest.d.ts +1 -0
- package/dist/lib/templating/fetchTemplatesManifest.js +10 -0
- package/dist/lib/templating/fetchTemplatesManifest.js.map +1 -0
- package/dist/lib/templating/types.d.ts +27 -0
- package/dist/lib/templating/types.js +3 -0
- package/dist/lib/templating/types.js.map +1 -0
- package/dist/oo-cli.manifest.json +1142 -0
- package/dist/test/setup.d.ts +0 -0
- package/dist/test/setup.js +4 -0
- package/dist/test/setup.js.map +1 -0
- package/package.json +94 -0
- package/src/commands/accounts/Whoami.ts +19 -0
- package/src/commands/accounts/Whois.ts +51 -0
- package/src/commands/app/BaseBuildCommand.ts +266 -0
- package/src/commands/app/Init.ts +303 -0
- package/src/commands/app/Logs.ts +241 -0
- package/src/commands/app/Package.ts +39 -0
- package/src/commands/app/Prepare.ts +108 -0
- package/src/commands/app/Register.ts +41 -0
- package/src/commands/app/Validate.ts +13 -0
- package/src/commands/availability/List.ts +37 -0
- package/src/commands/directory/Info.ts +83 -0
- package/src/commands/directory/Install.ts +37 -0
- package/src/commands/directory/List.ts +96 -0
- package/src/commands/directory/ListFunctions.ts +60 -0
- package/src/commands/directory/ListGlobalFunctions.ts +54 -0
- package/src/commands/directory/ListInstalls.ts +73 -0
- package/src/commands/directory/Publish.ts +179 -0
- package/src/commands/directory/Status.ts +45 -0
- package/src/commands/directory/Uninstall.ts +32 -0
- package/src/commands/directory/Unpublish.ts +173 -0
- package/src/commands/directory/Uprade.ts +85 -0
- package/src/commands/env/GetEnvironment.ts +14 -0
- package/src/commands/env/SetEnvironment.ts +52 -0
- package/src/commands/jobs/List.ts +278 -0
- package/src/commands/jobs/RuntimeStatus.ts +49 -0
- package/src/commands/jobs/Terminate.ts +29 -0
- package/src/commands/jobs/Trigger.ts +41 -0
- package/src/commands/review/List.ts +76 -0
- package/src/commands/review/Open.ts +28 -0
- package/src/index.ts +15 -0
- package/src/lib/AppContext.ts +47 -0
- package/src/lib/AppPackager.ts +47 -0
- package/src/lib/AppUpdater.ts +177 -0
- package/src/lib/AppUploader.ts +39 -0
- package/src/lib/Config.ts +60 -0
- package/src/lib/EnvironmentalOutput.ts +18 -0
- package/src/lib/Moria.ts +66 -0
- package/src/lib/MoriaApi.ts +86 -0
- package/src/lib/Rivendell.ts +572 -0
- package/src/lib/RivendellApi.ts +99 -0
- package/src/lib/Shards.ts +37 -0
- package/src/lib/StringUtils.ts +4 -0
- package/src/lib/TeminalPassthru.ts +7 -0
- package/src/lib/TerminalConfirm.ts +27 -0
- package/src/lib/TerminalInput.ts +236 -0
- package/src/lib/TerminalMenu.ts +221 -0
- package/src/lib/TerminalOutput.ts +7 -0
- package/src/lib/TerminalSpinner.ts +76 -0
- package/src/lib/build.ts +36 -0
- package/src/lib/checkForUpdate.ts +63 -0
- package/src/lib/dev/app.ts +58 -0
- package/src/lib/dev/index.ts +1 -0
- package/src/lib/dev/logger.ts +77 -0
- package/src/lib/die.ts +10 -0
- package/src/lib/directoryExists.ts +5 -0
- package/src/lib/formatBuildState.ts +20 -0
- package/src/lib/formatError.ts +39 -0
- package/src/lib/formatJobStatus.ts +24 -0
- package/src/lib/formatReviewStatus.ts +27 -0
- package/src/lib/formatTimstamp.ts +21 -0
- package/src/lib/formatVersionState.ts +31 -0
- package/src/lib/gatherAppEnv.ts +75 -0
- package/src/lib/handleInterrupt.ts +13 -0
- package/src/lib/jobRuntime.ts +12 -0
- package/src/lib/parseDate.ts +21 -0
- package/src/lib/templating/TemplateRenderer.ts +65 -0
- package/src/lib/templating/fetchTemplatesManifest.ts +6 -0
- package/src/lib/templating/types.ts +30 -0
- package/src/test/setup.ts +2 -0
- package/src/types/columnify.d.ts +27 -0
- package/src/types/gitignore-parser.d.ts +11 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import {APP_ID_FORMAT, VENDOR_FORMAT, VERSION_FORMAT} from '@zaiusinc/app-sdk';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as columnify from 'columnify';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as marked from 'marked';
|
|
6
|
+
import * as MarkedTerminal from 'marked-terminal';
|
|
7
|
+
import fetch from 'node-fetch';
|
|
8
|
+
import {command, help, namespace, optional, param} from 'oo-cli';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import {terminal} from 'terminal-kit';
|
|
11
|
+
import {handleInterrupt} from '../../lib/handleInterrupt';
|
|
12
|
+
import {TerminalPassthru} from '../../lib/TeminalPassthru';
|
|
13
|
+
import {fetchTemplatesManifest} from '../../lib/templating/fetchTemplatesManifest';
|
|
14
|
+
import {TemplateRenderer} from '../../lib/templating/TemplateRenderer';
|
|
15
|
+
import {TemplateManifest, TemplateVariable} from '../../lib/templating/types';
|
|
16
|
+
import {TerminalConfirm} from '../../lib/TerminalConfirm';
|
|
17
|
+
import {TerminalInput} from '../../lib/TerminalInput';
|
|
18
|
+
import {TerminalMenu} from '../../lib/TerminalMenu';
|
|
19
|
+
import {TerminalSpinner} from '../../lib/TerminalSpinner';
|
|
20
|
+
import titleCase = require('title-case');
|
|
21
|
+
import { getDependencyFileUrl } from '../../lib/Config';
|
|
22
|
+
import {Rivendell} from '../../lib/Rivendell';
|
|
23
|
+
import {die} from '../../lib/die';
|
|
24
|
+
|
|
25
|
+
@namespace('app')
|
|
26
|
+
export class CreateCommand {
|
|
27
|
+
@param
|
|
28
|
+
@optional
|
|
29
|
+
@help('The app id / directory to create')
|
|
30
|
+
public id?: string;
|
|
31
|
+
|
|
32
|
+
private variables!: TemplateVariable[];
|
|
33
|
+
private variableId = 0;
|
|
34
|
+
private manifest!: TemplateManifest;
|
|
35
|
+
private substitutions: {[key: string]: string} = {};
|
|
36
|
+
private warnings: any[] = [];
|
|
37
|
+
|
|
38
|
+
@command
|
|
39
|
+
@help('Create a new app project')
|
|
40
|
+
public async init() {
|
|
41
|
+
handleInterrupt();
|
|
42
|
+
this.manifest = await fetchTemplatesManifest();
|
|
43
|
+
|
|
44
|
+
// start with base variables
|
|
45
|
+
this.variables = this.manifest.baseTemplateVariables;
|
|
46
|
+
|
|
47
|
+
// setting developer vendor
|
|
48
|
+
this.variables.find((v) => v.name === 'vendor')!.suggest = (await Rivendell.whoami()).vendor;
|
|
49
|
+
|
|
50
|
+
this.variables.push({
|
|
51
|
+
name: 'template',
|
|
52
|
+
question: 'Select a template project'
|
|
53
|
+
});
|
|
54
|
+
if (!await this.askQuestion()) {
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async renderTemplate() {
|
|
60
|
+
const {app_id, template: templateName} = this.substitutions;
|
|
61
|
+
delete this.substitutions.template;
|
|
62
|
+
|
|
63
|
+
let fullPath: string | undefined;
|
|
64
|
+
if (fs.readdirSync(process.cwd()).length === 0) {
|
|
65
|
+
if (await TerminalConfirm.ask(`Use current directory (${chalk.yellow(process.cwd())})?`)) {
|
|
66
|
+
fullPath = process.cwd();
|
|
67
|
+
console.log(chalk.gray(`Using ${chalk.yellow(fullPath)}`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!fullPath) {
|
|
72
|
+
const suggestion = app_id.replace(/_/g, '-');
|
|
73
|
+
let dirName = await TerminalInput.ask(`Project directory name:`, {defaultValue: suggestion});
|
|
74
|
+
while (fs.existsSync(path.resolve(dirName)) && fs.readdirSync(path.resolve(dirName)).length !== 0) {
|
|
75
|
+
console.log(chalk.red(`Directory ${path.resolve(dirName)} is not empty!`));
|
|
76
|
+
dirName = await TerminalInput.ask(`Project directory name:`, {defaultValue: suggestion});
|
|
77
|
+
}
|
|
78
|
+
fullPath = path.resolve(dirName);
|
|
79
|
+
console.log(chalk.gray('Creating directory', chalk.yellow(fullPath)));
|
|
80
|
+
fs.mkdirSync(fullPath);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// add latest sdk versions to the substitutions
|
|
84
|
+
const spinner = new TerminalSpinner().start(chalk.gray('Checking latest SDK versions...'));
|
|
85
|
+
await this.getSDKVersions();
|
|
86
|
+
|
|
87
|
+
const template = this.manifest.templates.find((t) => t.name === templateName);
|
|
88
|
+
await new TemplateRenderer(fullPath, template!, (status) => spinner.update(status))
|
|
89
|
+
.render(this.substitutions);
|
|
90
|
+
|
|
91
|
+
spinner.stop();
|
|
92
|
+
|
|
93
|
+
console.log(chalk.gray('Performing initial Yarn install'));
|
|
94
|
+
const result = TerminalPassthru.exec('yarn');
|
|
95
|
+
if (result.status !== 0) {
|
|
96
|
+
console.log(chalk.red(
|
|
97
|
+
'There was a problem running yarn. Make sure yarn is installed and then run '
|
|
98
|
+
+ `${chalk.white('yarn install')} from ${chalk.white(fullPath)} to install dependencies.`
|
|
99
|
+
));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (this.warnings.length > 0) {
|
|
103
|
+
this.warnings.forEach((warning) => console.warn(warning));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
marked.setOptions({renderer: new MarkedTerminal()});
|
|
107
|
+
console.log('\n' + marked(
|
|
108
|
+
`New OCP app project created at \`${fullPath}\`\n` +
|
|
109
|
+
'- View README.md for information on getting started\n' +
|
|
110
|
+
'- Check out the [documentation](https://docs.developers.optimizely.com/optimizely-connect-platform/docs)'
|
|
111
|
+
));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private get variable() {
|
|
115
|
+
return this.variables[this.variableId];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private get templateOptions() {
|
|
119
|
+
return columnify(this.manifest.templates, {columns: ['name', 'sp', 'summary']}).split('\n').slice(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private get question() {
|
|
123
|
+
if (this.suggestion) {
|
|
124
|
+
return `${this.variable.question} [${chalk.gray(this.suggestion)}]`;
|
|
125
|
+
}
|
|
126
|
+
if (this.variable.hint) {
|
|
127
|
+
return `${this.variable.question} (${chalk.gray(this.variable.hint)})`;
|
|
128
|
+
}
|
|
129
|
+
return this.variable.question;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async askQuestion() {
|
|
133
|
+
if (this.variables.length <= this.variableId) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (this.variable.hidden) {
|
|
138
|
+
await this.onAnswer(undefined, this.variable.suggest || '');
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// special case questions:
|
|
143
|
+
switch (this.variable.name) {
|
|
144
|
+
case 'template':
|
|
145
|
+
const index = (await TerminalMenu.ask(`${this.question}:`, this.templateOptions, {layout: 'row'})).id as number;
|
|
146
|
+
await this.onAnswer(undefined, this.manifest.templates[index].name);
|
|
147
|
+
break;
|
|
148
|
+
|
|
149
|
+
case 'category':
|
|
150
|
+
const categories = ['Commerce Platform', 'Point of Sale', 'Lead Capture', 'Advertising', 'Marketing',
|
|
151
|
+
'Channel', 'Loyalty & Rewards', 'Customer Experience', 'Analytics & Reporting', 'Surveys & Feedback',
|
|
152
|
+
'Reviews & Ratings', 'Content Management', 'Data Quality & Enrichment', 'Productivity', 'CRM',
|
|
153
|
+
'Accounting & Finance', 'CDP / DMP', 'Attribution & Linking', 'Testing & Utilities',
|
|
154
|
+
'Personalization & Content', 'Offers', 'Merchandising & Products', 'Site & Content Experience',
|
|
155
|
+
'Subscriptions'].sort();
|
|
156
|
+
await this.onAnswer(undefined, (await TerminalMenu.ask(`${this.question}:`, categories)).text);
|
|
157
|
+
break;
|
|
158
|
+
|
|
159
|
+
// standard questions
|
|
160
|
+
default:
|
|
161
|
+
await this.onAnswer(undefined, await TerminalInput.ask(
|
|
162
|
+
`${this.question}:`, {getHint: this.getHint, validate: this.validateInput}
|
|
163
|
+
));
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private onAnswer = async (error: any, input: string) => {
|
|
169
|
+
if (error) {
|
|
170
|
+
terminal.eraseDisplayBelow().nextLine(1);
|
|
171
|
+
console.error(chalk.red(error));
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
input = input.trim();
|
|
176
|
+
if (this.suggestion && input === '') {
|
|
177
|
+
input = this.suggestion;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!this.validateInput(input)) {
|
|
181
|
+
if (this.variable.required && input.length === 0) {
|
|
182
|
+
terminal.red(' This value is required').nextLine(1);
|
|
183
|
+
} else {
|
|
184
|
+
terminal.red(` Invalid value for ${this.variable.name}`).nextLine(1);
|
|
185
|
+
}
|
|
186
|
+
if (this.variable.hidden) {
|
|
187
|
+
die(`Invalid value of '${this.variable.name}' field: ${input}. Contact support`);
|
|
188
|
+
} else {
|
|
189
|
+
await this.askQuestion();
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
if (!this.variable.hidden) {
|
|
193
|
+
terminal(chalk.gray(this.question + ': ')).white(input).nextLine(1);
|
|
194
|
+
}
|
|
195
|
+
this.substitutions[this.variable.name] = input;
|
|
196
|
+
this.variableId++;
|
|
197
|
+
if (!await this.askQuestion()) {
|
|
198
|
+
await this.renderTemplate();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private getHint = (input: string): string => {
|
|
204
|
+
if (input.trim() === '' && this.suggestion) {
|
|
205
|
+
return `[Enter] to accept ${this.suggestion}`;
|
|
206
|
+
}
|
|
207
|
+
switch (this.variable.name) {
|
|
208
|
+
case 'display_name':
|
|
209
|
+
if (input.length === 0) {
|
|
210
|
+
return chalk.yellow('Display Name is required');
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
case 'app_id':
|
|
215
|
+
if (!input) {
|
|
216
|
+
return chalk.yellow('App ID is required');
|
|
217
|
+
} else if (!input.match(/^[a-z]/)) {
|
|
218
|
+
return chalk.yellow('must start with a lower case letter');
|
|
219
|
+
} else if (!input.match(/^[a-z_0-9]+$/)) {
|
|
220
|
+
return chalk.yellow('can only contain lower case letters, numbers, and underscores');
|
|
221
|
+
} else if (input.match(/_$/)) {
|
|
222
|
+
return chalk.yellow('should not end with an underscore');
|
|
223
|
+
} else if (input.length < 3 || input.length > 32) {
|
|
224
|
+
return chalk.yellow('must be between 3 and 32 characters long');
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
case 'version':
|
|
229
|
+
if (!input.match(VERSION_FORMAT)) {
|
|
230
|
+
return chalk.yellow('must be semver. -beta, -dev, -private pre-releases are allowed');
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
case 'vendor':
|
|
235
|
+
if (!input.match(/^[a-z]/)) {
|
|
236
|
+
return chalk.yellow('must start with a lower case letter');
|
|
237
|
+
} else if (!input.match(VENDOR_FORMAT)) {
|
|
238
|
+
return chalk.yellow('must be snake_case');
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
return '';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private validateInput = (input: string): boolean => {
|
|
246
|
+
if (!input) {
|
|
247
|
+
input = this.suggestion;
|
|
248
|
+
}
|
|
249
|
+
switch (this.variable.name) {
|
|
250
|
+
case 'app_id':
|
|
251
|
+
return APP_ID_FORMAT.test(input);
|
|
252
|
+
case 'version':
|
|
253
|
+
return VERSION_FORMAT.test(input);
|
|
254
|
+
case 'vendor':
|
|
255
|
+
return VENDOR_FORMAT.test(input);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (this.variable.required && input.length === 0) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private get suggestion(): string {
|
|
265
|
+
switch (this.variable.name) {
|
|
266
|
+
case 'display_name':
|
|
267
|
+
if (this.id || fs.readdirSync(process.cwd()).length === 0) {
|
|
268
|
+
const dir = titleCase(this.id || path.basename(process.cwd()).replace(/[^a-zA-Z0-9]+/g, ' ').trim());
|
|
269
|
+
if (dir && this.validateInput(dir)) {
|
|
270
|
+
return dir;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
|
|
275
|
+
case 'app_id':
|
|
276
|
+
const appId = (this.substitutions['display_name'] || '').toLowerCase().replace(/[^0-9a-z_]+/g, '_');
|
|
277
|
+
if (appId.length > 0 && this.validateInput(appId)) {
|
|
278
|
+
return appId;
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
return this.variable.suggest || '';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private getLatestSDKVersion(versions: {[key: string]: {[key: string]: string}}) {
|
|
286
|
+
const major = Object.keys(versions).map((x) => parseInt(x, 10)).sort().reverse()[0].toString();
|
|
287
|
+
return versions[major].stable;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private async getSDKVersions() {
|
|
291
|
+
try {
|
|
292
|
+
// TODO: use semver to only get the latest allowable by the templates to avoid major version incompatibilities
|
|
293
|
+
const deps = await fetch(getDependencyFileUrl('node18'));
|
|
294
|
+
const json = await deps.json() as any;
|
|
295
|
+
this.substitutions['app_sdk_version'] = this.getLatestSDKVersion(json['@zaiusinc/app-sdk']);
|
|
296
|
+
this.substitutions['node_sdk_version'] = this.getLatestSDKVersion(json['@zaiusinc/node-sdk']);
|
|
297
|
+
} catch (e) {
|
|
298
|
+
this.warnings.push('Error fetching sdk versions, defaulting to latest: ', e.message);
|
|
299
|
+
this.substitutions['app_sdk_version'] = 'latest';
|
|
300
|
+
this.substitutions['node_sdk_version'] = 'latest';
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
import {command, defaultValue, flag, help, multiple, namespace, option} from 'oo-cli';
|
|
4
|
+
import {isValidDateOrEpochOrDurationString, parseDate} from '../../lib/parseDate';
|
|
5
|
+
import {die} from '../../lib/die';
|
|
6
|
+
import {handleInterrupt} from '../../lib/handleInterrupt';
|
|
7
|
+
import {TerminalSpinner} from '../../lib/TerminalSpinner';
|
|
8
|
+
import {Moria} from '../../lib/Moria';
|
|
9
|
+
import {Rivendell} from '../../lib/Rivendell';
|
|
10
|
+
import {formatError} from '../../lib/formatError';
|
|
11
|
+
import LoggingSearchCriteria = Moria.LoggingSearchCriteria;
|
|
12
|
+
import LoggingSearchResult = Moria.LoggingSearchResult;
|
|
13
|
+
import LogEntry = Moria.LogEntry;
|
|
14
|
+
import LogLevel = Moria.LogLevel;
|
|
15
|
+
|
|
16
|
+
const TAIL_CONFIG = Object.freeze({
|
|
17
|
+
// Each pass of the tail query will cover at least tailminWindowMs.
|
|
18
|
+
// This is done to prevent gaps that may occur (i.e. should encourage at least once delivery).
|
|
19
|
+
minWindowMs: 30000,
|
|
20
|
+
|
|
21
|
+
// Each pass of the query will be up to Date.now - tailLag
|
|
22
|
+
// This is done to minimize ordering issue due to log ingest lag
|
|
23
|
+
lagMs: 15000,
|
|
24
|
+
|
|
25
|
+
queryIntervalMs: 5000,
|
|
26
|
+
|
|
27
|
+
// Size of the dedupe cache.
|
|
28
|
+
dedupeCacheSize: 5000
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Maximum page size for search results.
|
|
32
|
+
const PAGE_SIZE = 5000;
|
|
33
|
+
|
|
34
|
+
type LogEntryCallback = (entry: LogEntry) => void;
|
|
35
|
+
|
|
36
|
+
@namespace('app')
|
|
37
|
+
export class LogsCommand {
|
|
38
|
+
@option
|
|
39
|
+
@help('The App ID (e.g. my_app). Either App ID or Build ID must be specified.')
|
|
40
|
+
public appId?: string;
|
|
41
|
+
|
|
42
|
+
@option
|
|
43
|
+
@help('The ID of the build. Either App ID or Build ID must be specified. Build IDs are printed by \'ocp app prepare\' command.')
|
|
44
|
+
public buildId?: string;
|
|
45
|
+
|
|
46
|
+
@option
|
|
47
|
+
@help('The app version (e.g. 1.0.0)')
|
|
48
|
+
public appVersion?: string;
|
|
49
|
+
|
|
50
|
+
@option
|
|
51
|
+
@help('The Tracker ID of the ODP account')
|
|
52
|
+
public trackerId?: string;
|
|
53
|
+
|
|
54
|
+
@option
|
|
55
|
+
@help('Show logs for given execution of a job. Use \'ocp jobs list\' command to get the job ID')
|
|
56
|
+
public jobId?: string;
|
|
57
|
+
|
|
58
|
+
@option
|
|
59
|
+
@help(`Filter app logs by log level. Valid values: ${Object.values(LogLevel).join(', ')}`)
|
|
60
|
+
@defaultValue('debug')
|
|
61
|
+
public level!: string;
|
|
62
|
+
|
|
63
|
+
@option
|
|
64
|
+
@help('A start time as an ISO string, an epoch timestamp, or relative string (i.e. "5m" for 5 minutes.) Default: 24h')
|
|
65
|
+
@defaultValue('24h')
|
|
66
|
+
public from!: string;
|
|
67
|
+
|
|
68
|
+
@option
|
|
69
|
+
@help('A end time as an ISO string, an epoch timestamp, or relative string (i.e. "5m" for 5 minutes)')
|
|
70
|
+
public to?: string;
|
|
71
|
+
|
|
72
|
+
@flag
|
|
73
|
+
@help('Tails the logs')
|
|
74
|
+
@defaultValue(false)
|
|
75
|
+
public tail!: boolean;
|
|
76
|
+
|
|
77
|
+
@option
|
|
78
|
+
@help('String to search for in log messages. Can be specified multiple times.')
|
|
79
|
+
@multiple
|
|
80
|
+
public search?: string[];
|
|
81
|
+
|
|
82
|
+
@option('a')
|
|
83
|
+
@help('The availability zone that will be targeted (default: us)')
|
|
84
|
+
private availability: string = '';
|
|
85
|
+
|
|
86
|
+
@command
|
|
87
|
+
@help('Fetch app logs')
|
|
88
|
+
public async logs() {
|
|
89
|
+
handleInterrupt();
|
|
90
|
+
if (!isValidDateOrEpochOrDurationString(this.from)) {
|
|
91
|
+
die(chalk.red('Invalid "--from" input, should be an ISO string,' +
|
|
92
|
+
'an epoch timestamp, or relative string (i.e. "5m" for 5 minutes.'));
|
|
93
|
+
}
|
|
94
|
+
const fromDate = parseDate(this.from);
|
|
95
|
+
|
|
96
|
+
let toDate: Date | undefined;
|
|
97
|
+
|
|
98
|
+
if (!this.appId && !this.buildId && !this.jobId) {
|
|
99
|
+
die('Either App ID, Build ID or Job ID must be specified.');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if ((this.appId && this.buildId) || (this.appId && this.jobId) ||
|
|
103
|
+
(this.buildId && this.jobId) || (this.appId && this.buildId && this.jobId)) {
|
|
104
|
+
die('Only App ID, Build ID or Job ID should be specified.');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (this.appId && !this.trackerId) {
|
|
108
|
+
die('Tracker ID must be specified if App ID is specified.');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.tail) {
|
|
112
|
+
if (this.to) {
|
|
113
|
+
die('Cannot specify --tail if specifying --to');
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
if (this.to && !isValidDateOrEpochOrDurationString(this.to)) {
|
|
117
|
+
die(chalk.red('Invalid "--to" input, should be an ISO string, ' +
|
|
118
|
+
'an epoch timestamp, or relative string (i.e. "5m" for 5 minutes.'));
|
|
119
|
+
}
|
|
120
|
+
toDate = this.to ? parseDate(this.to) : new Date();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.availability = this.availability ? this.availability : 'us';
|
|
124
|
+
const shard = (await Rivendell.shards()).find((_shard) => _shard.id === this.availability);
|
|
125
|
+
if (!shard) {
|
|
126
|
+
die('Could not find the specified availability zone.');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this.level) {
|
|
130
|
+
if (!Object.values(LogLevel).includes(this.level.toUpperCase() as LogLevel)) {
|
|
131
|
+
die(`Invalid log level. Valid values: ${Object.values(LogLevel).join(', ')}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
if (this.tail) {
|
|
137
|
+
await this.tailLogs(shard!, parseDate('5m'));
|
|
138
|
+
} else {
|
|
139
|
+
const spinner = new TerminalSpinner().start('');
|
|
140
|
+
try {
|
|
141
|
+
await this.dumpLogs(shard!, fromDate, this.printLogEntry, toDate);
|
|
142
|
+
} finally {
|
|
143
|
+
spinner.stop();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
die(formatError(e));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private printLogEntry = (entry: LogEntry) => {
|
|
152
|
+
const columns = [
|
|
153
|
+
new Date(entry.time).toISOString(),
|
|
154
|
+
this.formatLevel(entry.level),
|
|
155
|
+
entry.message
|
|
156
|
+
];
|
|
157
|
+
console.log(columns.join(' '));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private formatLevel = (level: LogLevel) => {
|
|
161
|
+
const padding = 5;
|
|
162
|
+
switch (level.toUpperCase()) {
|
|
163
|
+
case LogLevel.DEBUG: {
|
|
164
|
+
return chalk.blue('DEBUG'.padEnd(padding));
|
|
165
|
+
}
|
|
166
|
+
case LogLevel.INFO: {
|
|
167
|
+
return chalk.gray('INFO'.padEnd(padding));
|
|
168
|
+
}
|
|
169
|
+
case LogLevel.WARN: {
|
|
170
|
+
return chalk.yellow('WARN'.padEnd(padding));
|
|
171
|
+
}
|
|
172
|
+
case LogLevel.ERROR: {
|
|
173
|
+
return chalk.red('ERROR'.padEnd(padding));
|
|
174
|
+
}
|
|
175
|
+
default:
|
|
176
|
+
return '';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async dumpLogs(
|
|
181
|
+
shard: Rivendell.Shard,
|
|
182
|
+
from: Date | number,
|
|
183
|
+
cb: LogEntryCallback,
|
|
184
|
+
to?: Date | number
|
|
185
|
+
): Promise<number> {
|
|
186
|
+
let result: LoggingSearchResult;
|
|
187
|
+
let count = 0;
|
|
188
|
+
const request = this.buildRequest(from, to);
|
|
189
|
+
do {
|
|
190
|
+
result = await Moria.searchLogging(request, shard);
|
|
191
|
+
result.entries.forEach(cb);
|
|
192
|
+
request.cursor = result.cursor;
|
|
193
|
+
count += result.entries.length;
|
|
194
|
+
} while (result.entries.length > 0);
|
|
195
|
+
return count;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async tailLogs(shard: Rivendell.Shard, from: Date) {
|
|
199
|
+
const dedupeCache: Set<string> = new Set();
|
|
200
|
+
let start = from.valueOf();
|
|
201
|
+
|
|
202
|
+
const callback = (entry: LogEntry) => {
|
|
203
|
+
if (!dedupeCache.has(entry.id)) {
|
|
204
|
+
this.printLogEntry(entry);
|
|
205
|
+
dedupeCache.add(entry.id);
|
|
206
|
+
if (dedupeCache.size > TAIL_CONFIG.dedupeCacheSize) {
|
|
207
|
+
dedupeCache.delete(dedupeCache.entries().next().value);
|
|
208
|
+
}
|
|
209
|
+
start = Math.max(start, entry.time);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const spinner = new TerminalSpinner().start('');
|
|
214
|
+
|
|
215
|
+
const query = async () => {
|
|
216
|
+
const end = Date.now() - TAIL_CONFIG.lagMs;
|
|
217
|
+
start = Math.min(start, end + TAIL_CONFIG.minWindowMs);
|
|
218
|
+
await this.dumpLogs(shard, start, callback, end);
|
|
219
|
+
spinner.update(chalk.gray('Waiting for more logs...'));
|
|
220
|
+
setTimeout(query, TAIL_CONFIG.queryIntervalMs);
|
|
221
|
+
};
|
|
222
|
+
await query();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private buildRequest(from: Date | number, to?: Date | number): LoggingSearchCriteria {
|
|
226
|
+
return {
|
|
227
|
+
context: {
|
|
228
|
+
appId: this.appId,
|
|
229
|
+
appVersion: this.appVersion,
|
|
230
|
+
jobId: this.jobId,
|
|
231
|
+
trackerId: this.trackerId,
|
|
232
|
+
buildId: this.buildId,
|
|
233
|
+
},
|
|
234
|
+
pageSize: PAGE_SIZE,
|
|
235
|
+
start: typeof from === 'number' ? from : from.valueOf(),
|
|
236
|
+
end: to ? (typeof to === 'number' ? to : to.valueOf()) : undefined,
|
|
237
|
+
level: this.level as LogLevel,
|
|
238
|
+
terms: this.search
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import {command, defaultValue, help, namespace, param} from 'oo-cli';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import {appContext} from '../../lib/AppContext';
|
|
5
|
+
import {AppPackager} from '../../lib/AppPackager';
|
|
6
|
+
import {die} from '../../lib/die';
|
|
7
|
+
import {directoryExists} from '../../lib/directoryExists';
|
|
8
|
+
|
|
9
|
+
@namespace('app')
|
|
10
|
+
export class PackageCommand {
|
|
11
|
+
@param
|
|
12
|
+
@help('The root directory of the app')
|
|
13
|
+
@defaultValue('./')
|
|
14
|
+
public path!: string;
|
|
15
|
+
|
|
16
|
+
@command
|
|
17
|
+
@help('Prepare a package for manual upload')
|
|
18
|
+
public package() {
|
|
19
|
+
const workingDir = process.cwd();
|
|
20
|
+
if (!directoryExists(this.path)) {
|
|
21
|
+
die('Specified path to app does not exist');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
process.chdir(this.path);
|
|
25
|
+
|
|
26
|
+
const context = appContext();
|
|
27
|
+
if (!context.appId || !context.version) {
|
|
28
|
+
die('Unable to read App ID or version info. Ensure you are uploading the right directory.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (context) {
|
|
33
|
+
const packageData = new AppPackager('./').package();
|
|
34
|
+
const zipFileName = `${context.appId}@${context.version}.zip`;
|
|
35
|
+
fs.writeFileSync(path.join(workingDir, zipFileName), packageData);
|
|
36
|
+
console.log(`Created package ${zipFileName}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {command, flag, help, namespace} from 'oo-cli';
|
|
3
|
+
import {appContext} from '../../lib/AppContext';
|
|
4
|
+
import {PublishCommand} from '../directory/Publish';
|
|
5
|
+
import {BaseBuildCommand} from './BaseBuildCommand';
|
|
6
|
+
import {TerminalConfirm} from '../../lib/TerminalConfirm';
|
|
7
|
+
import * as semver from 'semver';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import {Rivendell} from '../../lib/Rivendell';
|
|
11
|
+
import AppReview = Rivendell.AppReview;
|
|
12
|
+
|
|
13
|
+
@namespace('app')
|
|
14
|
+
export class PrepareCommand extends BaseBuildCommand {
|
|
15
|
+
@flag
|
|
16
|
+
@help('Automatically publish if the upload is successful')
|
|
17
|
+
public publish!: boolean;
|
|
18
|
+
|
|
19
|
+
@flag('use-previous-app-env-values')
|
|
20
|
+
@help('Use app env values (.env file) from the previous version of the app. Any values in local .env file will be ignored')
|
|
21
|
+
public usePreviousAppEnvValues: boolean = false;
|
|
22
|
+
|
|
23
|
+
@flag('bump-dev-version')
|
|
24
|
+
@help('Bump dev version before building. Allows quickly testing dev versions')
|
|
25
|
+
public bumpDevVersion!: boolean;
|
|
26
|
+
|
|
27
|
+
@command
|
|
28
|
+
@help('Validate, package, upload and build an app to prepare for publishing')
|
|
29
|
+
public async prepare() {
|
|
30
|
+
const context = appContext();
|
|
31
|
+
if (!context.appId || !context.version) {
|
|
32
|
+
console.log(chalk.gray('App id or version missing in the manifest\n'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const requiresReview = await this.requiresReview(context.version);
|
|
36
|
+
if (requiresReview) {
|
|
37
|
+
const isInReview = await this.isInReview(context.appId, context.version);
|
|
38
|
+
const reviewMessage = isInReview ?
|
|
39
|
+
chalk.gray(
|
|
40
|
+
'The app version is already in review. This will update the review and the app can\'t be published until it is approved. ' +
|
|
41
|
+
`${chalk.yellow('Are you sure?')}`
|
|
42
|
+
) :
|
|
43
|
+
chalk.gray(
|
|
44
|
+
'You are uploading a release version of the app. ' +
|
|
45
|
+
'This will start a review process and the app can\'t be published until it is approved. ' +
|
|
46
|
+
`${chalk.yellow('Are you sure?')}`
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const reviewConfirmation = await TerminalConfirm.ask(reviewMessage);
|
|
50
|
+
if (!reviewConfirmation) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (context.version && this.bumpDevVersion) {
|
|
56
|
+
if (this.isDevVersion(context.version)) {
|
|
57
|
+
const bumpedVersion = semver.parse(context.version)?.inc('prerelease', 'dev').format();
|
|
58
|
+
if (!bumpedVersion) {
|
|
59
|
+
console.log(chalk.red(`Cannot parse app version: ${context.version}`));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(chalk.gray(`Bumping dev version to ${bumpedVersion}`));
|
|
64
|
+
|
|
65
|
+
this.updateVersionInAppManifest(context.version, bumpedVersion);
|
|
66
|
+
context.version = bumpedVersion;
|
|
67
|
+
} else {
|
|
68
|
+
console.log(chalk.yellow('Not a dev version. Ignoring --bump-dev-version flag'));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await this.run(true, true, this.usePreviousAppEnvValues);
|
|
73
|
+
await this.reviewAppVersion(context.appId as string, context.version as string);
|
|
74
|
+
|
|
75
|
+
if (this.publish) {
|
|
76
|
+
const cmd = new PublishCommand();
|
|
77
|
+
cmd.appVersion = `${context.appId}@${context.version}`;
|
|
78
|
+
await cmd.publish();
|
|
79
|
+
} else {
|
|
80
|
+
if (requiresReview) {
|
|
81
|
+
const appVersion = `${context.appId}@${context.version}`;
|
|
82
|
+
const appReview = await Rivendell.getReviewUrl(appVersion);
|
|
83
|
+
const reviewUrl = (appReview as AppReview).url;
|
|
84
|
+
|
|
85
|
+
console.log(chalk.gray(`\nYour app has been sent to review. Your code will be available in a private repository on GitHub, ` +
|
|
86
|
+
`and you will receive an invitation from GitHub by email for access.` +
|
|
87
|
+
`It is important you accept this invitation in order to receive communications about your app from the review team.`));
|
|
88
|
+
console.log(chalk.gray(`\nOnce the review is approved, your app can be deployed to app directory.\n`));
|
|
89
|
+
console.log(chalk.gray(`You can run ${chalk.white(`ocp review open ${appVersion}`)} to open the review URL at any time.`));
|
|
90
|
+
console.log(chalk.gray(`The URL for your repo is ${chalk.white(`${reviewUrl}`)}\n`));
|
|
91
|
+
} else {
|
|
92
|
+
console.log(chalk.gray(
|
|
93
|
+
`\nUse ${chalk.white(
|
|
94
|
+
`ocp directory publish ${context.appId}@${context.version}`
|
|
95
|
+
)} to publish and release your app\n`
|
|
96
|
+
));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// explicitly exit because we're using terminal kit
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private updateVersionInAppManifest(oldVersion: string, newVersion: string) {
|
|
105
|
+
const file = path.join(process.cwd(), 'app.yml');
|
|
106
|
+
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(new RegExp(`(version\\s*:\\s*)${oldVersion}`), `$1${newVersion}`));
|
|
107
|
+
}
|
|
108
|
+
}
|