@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,76 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import * as cliSpinners from 'cli-spinners';
|
|
3
|
+
import {Spinner} from 'cli-spinners';
|
|
4
|
+
import {stringWidth, terminal, truncateString} from 'terminal-kit';
|
|
5
|
+
|
|
6
|
+
const SPINNERS = [
|
|
7
|
+
cliSpinners.dots,
|
|
8
|
+
cliSpinners.dots2,
|
|
9
|
+
cliSpinners.dots3,
|
|
10
|
+
cliSpinners.dots4,
|
|
11
|
+
cliSpinners.dots5,
|
|
12
|
+
cliSpinners.dots6,
|
|
13
|
+
cliSpinners.dots7,
|
|
14
|
+
cliSpinners.dots8,
|
|
15
|
+
cliSpinners.dots10,
|
|
16
|
+
cliSpinners.dots11
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export class TerminalSpinner {
|
|
20
|
+
private text = '';
|
|
21
|
+
private counter = 0;
|
|
22
|
+
private interval!: NodeJS.Timeout;
|
|
23
|
+
private spinner: Spinner;
|
|
24
|
+
|
|
25
|
+
constructor(private color: string = 'yellow', spinner?: Spinner) {
|
|
26
|
+
if (spinner) {
|
|
27
|
+
this.spinner = spinner;
|
|
28
|
+
} else {
|
|
29
|
+
this.spinner = SPINNERS[Math.floor(Math.random() * SPINNERS.length)];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public start(text: string) {
|
|
34
|
+
this.text = text;
|
|
35
|
+
terminal.on('resize', this.onResize).grabInput(true);
|
|
36
|
+
this.interval = setInterval(() => {
|
|
37
|
+
this.counter++;
|
|
38
|
+
terminal.saveCursor();
|
|
39
|
+
this.renderSpinner();
|
|
40
|
+
terminal.restoreCursor();
|
|
41
|
+
}, this.spinner.interval);
|
|
42
|
+
terminal.hideCursor();
|
|
43
|
+
this.render();
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public update(text: string, spinnerColor = this.color) {
|
|
48
|
+
this.text = text;
|
|
49
|
+
this.color = spinnerColor;
|
|
50
|
+
this.render();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public stop() {
|
|
54
|
+
terminal.off('resize', this.onResize).grabInput(false);
|
|
55
|
+
clearInterval(this.interval);
|
|
56
|
+
terminal.hideCursor(false).eraseDisplayBelow();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private onResize = () => {
|
|
60
|
+
this.render();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private renderSpinner() {
|
|
64
|
+
terminal(` ${chalk.keyword(this.color)(this.spinner.frames[this.counter % this.spinner.frames.length])} `);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private render() {
|
|
68
|
+
terminal.eraseDisplayBelow().saveCursor();
|
|
69
|
+
let str = this.text;
|
|
70
|
+
if (stringWidth(this.text) + 3 > terminal.width) {
|
|
71
|
+
str = truncateString(this.text, terminal.width - 4) + '…';
|
|
72
|
+
}
|
|
73
|
+
this.renderSpinner();
|
|
74
|
+
terminal(str).restoreCursor();
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/lib/build.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {Rivendell} from './Rivendell';
|
|
2
|
+
import BuildState = Rivendell.BuildState;
|
|
3
|
+
import BuildStatus = Rivendell.BuildStatus;
|
|
4
|
+
|
|
5
|
+
export type ProgressCallback = (state: BuildState) => any;
|
|
6
|
+
export type ErrorList = string[];
|
|
7
|
+
|
|
8
|
+
export async function watchBuild(
|
|
9
|
+
id: number,
|
|
10
|
+
progressCallback: ProgressCallback,
|
|
11
|
+
pollInterval: number = 2000
|
|
12
|
+
): Promise<ErrorList> {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const interval = setInterval(() => {
|
|
15
|
+
Rivendell.fetchBuild(id)
|
|
16
|
+
.then((build) => {
|
|
17
|
+
const state = build.state as BuildState;
|
|
18
|
+
if (state === BuildState.FINISHED) {
|
|
19
|
+
clearInterval(interval);
|
|
20
|
+
if (build.status as BuildStatus === BuildStatus.SUCCESS) {
|
|
21
|
+
resolve();
|
|
22
|
+
} else {
|
|
23
|
+
const errors = build.errors || [];
|
|
24
|
+
resolve(errors);
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
progressCallback(state);
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
.catch((error) => {
|
|
31
|
+
clearInterval(interval);
|
|
32
|
+
reject(error);
|
|
33
|
+
});
|
|
34
|
+
}, pollInterval);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as semver from 'semver';
|
|
6
|
+
import { ManifestDefinition } from 'oo-cli/dist/types/manifest';
|
|
7
|
+
import { TerminalOutput } from './TerminalOutput';
|
|
8
|
+
import { TerminalConfirm } from './TerminalConfirm';
|
|
9
|
+
import { TerminalPassthru } from './TeminalPassthru';
|
|
10
|
+
|
|
11
|
+
interface LastCheck {
|
|
12
|
+
date: Date;
|
|
13
|
+
upToDate: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const LAST_VERSION_CHECK_FILE = '.lastcheck';
|
|
17
|
+
const _24hours = 24 * 60 * 60 * 1000;
|
|
18
|
+
|
|
19
|
+
export async function checkForUpdate(manifest: ManifestDefinition): Promise<void> {
|
|
20
|
+
const lastCheck = getLastCheck();
|
|
21
|
+
if (lastCheck) {
|
|
22
|
+
const now = new Date();
|
|
23
|
+
if (now.getTime() - lastCheck.date.getTime() < _24hours && lastCheck.upToDate) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
let upToDate = true;
|
|
30
|
+
const latest = await fetchLatest();
|
|
31
|
+
if (latest) {
|
|
32
|
+
const installed = manifest.package?.version!;
|
|
33
|
+
if (semver.gt(latest, installed)) {
|
|
34
|
+
upToDate = false;
|
|
35
|
+
if (await TerminalConfirm.ask(chalk.yellow('A new version of OCP CLI is available. Update now?'))) {
|
|
36
|
+
TerminalPassthru.exec('yarn global upgrade @optimizely/ocp-cli --latest');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
setLastCheck(upToDate);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function fetchLatest() {
|
|
47
|
+
return TerminalOutput.exec('yarn info -s @optimizely/ocp-cli dist-tags.latest').stdout.trim();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getLastCheck(): LastCheck | undefined {
|
|
51
|
+
const file = path.join(os.homedir(), `.ocp/${LAST_VERSION_CHECK_FILE}`);
|
|
52
|
+
if (fs.existsSync(file)) {
|
|
53
|
+
const stat = fs.statSync(file);
|
|
54
|
+
const upToDate = fs.readFileSync(file, {encoding: 'utf-8'}) === 'true';
|
|
55
|
+
return {date: stat.mtime, upToDate};
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function setLastCheck(upToDate: boolean) {
|
|
61
|
+
const file = path.join(os.homedir(), `.ocp/${LAST_VERSION_CHECK_FILE}`);
|
|
62
|
+
fs.writeFileSync(file, upToDate.toString());
|
|
63
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {ChildProcess, spawn} from 'child_process';
|
|
2
|
+
import fetch from 'node-fetch';
|
|
3
|
+
import {DevLogger} from './logger';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
const logger = new DevLogger();
|
|
7
|
+
|
|
8
|
+
export class App {
|
|
9
|
+
private baseUrl?: string;
|
|
10
|
+
|
|
11
|
+
public async install() {
|
|
12
|
+
const response = await fetch(`${this.baseUrl}/lifecycle/install`, {method: 'post'});
|
|
13
|
+
const json = await response.json();
|
|
14
|
+
if (!json.success) {
|
|
15
|
+
throw new Error('App installation failed');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async fetchFunctionEndpoints(): Promise<string[]> {
|
|
20
|
+
const response = await fetch(`${this.baseUrl}/functions`);
|
|
21
|
+
const json = await response.json();
|
|
22
|
+
return Object.values(json);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public start(): Promise<ChildProcess> {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const app = spawn('yarn', ['run', 'start-app'], {shell: true});
|
|
28
|
+
process.on('exit', () => app.kill());
|
|
29
|
+
|
|
30
|
+
let started = false;
|
|
31
|
+
app.stdout.on('data', (data) => {
|
|
32
|
+
data.toString().trim().split('\n').forEach((str: string) => {
|
|
33
|
+
if (!started && str.startsWith('App listening on port')) {
|
|
34
|
+
started = true;
|
|
35
|
+
this.baseUrl = `http://localhost:${str.replace('App listening on port ', '')}`;
|
|
36
|
+
console.log(chalk.gray('App started successfully'));
|
|
37
|
+
console.log(chalk.gray(`App logs will be piped to ${logger.logFile}`));
|
|
38
|
+
resolve(app);
|
|
39
|
+
}
|
|
40
|
+
logger.log(str);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.stderr.on('data', (data) => {
|
|
45
|
+
data.toString().split('\n').forEach(logger.log);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
app.on('error', (e) => {
|
|
49
|
+
console.error(e);
|
|
50
|
+
reject('App failed to start');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
app.on('close', (code) => {
|
|
54
|
+
console.log(`app exited with code ${code}`);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './app';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import {Readable, Writable} from 'stream';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'NEVER';
|
|
7
|
+
|
|
8
|
+
export interface LogEntry {
|
|
9
|
+
level: LogLevel;
|
|
10
|
+
message: string;
|
|
11
|
+
stacktrace?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class DevLogger {
|
|
15
|
+
public readonly logFile: string;
|
|
16
|
+
private fileStream: fs.WriteStream;
|
|
17
|
+
private logs: Readable;
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
const logsDir = path.resolve(fs.realpathSync(process.cwd()), 'logs/');
|
|
21
|
+
if (!fs.existsSync(logsDir)) {
|
|
22
|
+
fs.mkdirSync(logsDir);
|
|
23
|
+
}
|
|
24
|
+
this.logFile = path.resolve(fs.realpathSync(process.cwd()), 'logs/dev.log');
|
|
25
|
+
|
|
26
|
+
this.logs = new Readable();
|
|
27
|
+
this.logs._read = () => {/* no op */};
|
|
28
|
+
this.fileStream = fs.createWriteStream(this.logFile, {encoding: 'utf8', flags: 'a+'});
|
|
29
|
+
|
|
30
|
+
const consoleWriter = new Writable({
|
|
31
|
+
write: (chunk, _encoding, next) => {
|
|
32
|
+
process.stdout.write(this.decorateLog(chunk.toString()));
|
|
33
|
+
next();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
this.logs.pipe(consoleWriter);
|
|
37
|
+
|
|
38
|
+
this.logs.pipe(this.fileStream);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public log = (line: string) => {
|
|
42
|
+
let json: any;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
json = JSON.parse(line);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.log(line);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const level = json.level;
|
|
52
|
+
const message = json.message;
|
|
53
|
+
const stacktrace = json.stacktrace;
|
|
54
|
+
|
|
55
|
+
if (level === 'NEVER') {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.logs.push(`[${level}] ${message}${message.endsWith('\n') ? '' : '\n'}`);
|
|
60
|
+
|
|
61
|
+
if (stacktrace !== undefined) {
|
|
62
|
+
this.logs.push(`${stacktrace}${stacktrace.endsWith('\n') ? '' : '\n'}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private decorateLog(message: string): string {
|
|
67
|
+
if (message.startsWith('[warn]')) {
|
|
68
|
+
return message.replace('[warn]', chalk.yellow('[warn]'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (message.startsWith('[error]')) {
|
|
72
|
+
return message.replace('[error]', chalk.red('[error]'));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return message;
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/lib/die.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {Rivendell} from './Rivendell';
|
|
3
|
+
|
|
4
|
+
export function formatBuildState(state: Rivendell.BuildState): string {
|
|
5
|
+
switch (state) {
|
|
6
|
+
case Rivendell.BuildState.NEW:
|
|
7
|
+
return chalk.yellow('NEW');
|
|
8
|
+
case Rivendell.BuildState.QUEUED:
|
|
9
|
+
return chalk.yellow('QUEUED');
|
|
10
|
+
case Rivendell.BuildState.RUNNING:
|
|
11
|
+
return chalk.yellow('BUILDING');
|
|
12
|
+
case Rivendell.BuildState.FINISHED:
|
|
13
|
+
return chalk.yellow('FINISHED');
|
|
14
|
+
|
|
15
|
+
default: {
|
|
16
|
+
console.error(chalk.red(`Unrecognized BuildState ${state} `));
|
|
17
|
+
return chalk.red('UNKNOWN');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {RivendellApi} from './RivendellApi';
|
|
2
|
+
import {MoriaApi} from './MoriaApi';
|
|
3
|
+
import RivendellApiError = RivendellApi.ApiError;
|
|
4
|
+
import MoriaApiError = MoriaApi.ApiError;
|
|
5
|
+
|
|
6
|
+
export function formatError(error: Error) {
|
|
7
|
+
if (error instanceof RivendellApiError) {
|
|
8
|
+
if (error.response?.status === 403) {
|
|
9
|
+
return 'Could not authenticate with the credential configured in ~/.ocp/credentials.json.';
|
|
10
|
+
} else {
|
|
11
|
+
return sanitize(errorMessageFromJsonResponse(error));
|
|
12
|
+
}
|
|
13
|
+
} else if (error instanceof MoriaApiError) {
|
|
14
|
+
return sanitize(errorMessageFromJsonResponse(error));
|
|
15
|
+
} else {
|
|
16
|
+
return sanitize(error.message);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function errorMessageFromJsonResponse(error: MoriaApiError | RivendellApiError) {
|
|
21
|
+
const contentType = error.response?.headers?.get('content-type');
|
|
22
|
+
if (contentType?.includes('application/json') && error.responseText) {
|
|
23
|
+
const response = JSON.parse(error.responseText);
|
|
24
|
+
// protocol is that detail will have either a `message` or an `invalids` child
|
|
25
|
+
if (response.detail) {
|
|
26
|
+
return response.detail.message
|
|
27
|
+
? response.detail.message
|
|
28
|
+
: response.detail.invalids
|
|
29
|
+
.map((violation: { [key: string]: string }) => `* ${violation.field} ${violation.reason}`)
|
|
30
|
+
.join('\n');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return error.message;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sanitize(body: any) {
|
|
38
|
+
return body.replace('FAILED_PRECONDITION: ', '').replace('INTERNAL: ', '');
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {Rivendell} from './Rivendell';
|
|
3
|
+
import JobStatus = Rivendell.JobStatus;
|
|
4
|
+
|
|
5
|
+
export function formatJobStatus(status: JobStatus): string {
|
|
6
|
+
switch (status) {
|
|
7
|
+
case JobStatus.PENDING:
|
|
8
|
+
return chalk.gray('PENDING');
|
|
9
|
+
case JobStatus.SCHEDULED:
|
|
10
|
+
return chalk.gray('SCHEDULED');
|
|
11
|
+
case JobStatus.RUNNING:
|
|
12
|
+
return chalk.yellow('RUNNING');
|
|
13
|
+
case JobStatus.COMPLETE:
|
|
14
|
+
return chalk.green('COMPLETE');
|
|
15
|
+
case JobStatus.TERMINATED:
|
|
16
|
+
return chalk.green('TERMINATED');
|
|
17
|
+
case JobStatus.ERROR:
|
|
18
|
+
return chalk.red('ERROR');
|
|
19
|
+
default: {
|
|
20
|
+
console.error(chalk.red(`Unrecognized JobStatus ${status}`));
|
|
21
|
+
return chalk.red('UNKNOWN');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {Rivendell} from './Rivendell';
|
|
3
|
+
import AppVersionState = Rivendell.AppVersionState;
|
|
4
|
+
import ReviewStatus = Rivendell.ReviewStatus;
|
|
5
|
+
|
|
6
|
+
export function formatReviewStatus(
|
|
7
|
+
reviewStatus: ReviewStatus,
|
|
8
|
+
state: AppVersionState = AppVersionState.PUBLISHED
|
|
9
|
+
): string {
|
|
10
|
+
if (state !== AppVersionState.PUBLISHED) {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
switch (reviewStatus) {
|
|
14
|
+
case ReviewStatus.NOT_STARTED:
|
|
15
|
+
return chalk.yellow('NOT_STARTED');
|
|
16
|
+
case ReviewStatus.IN_REVIEW:
|
|
17
|
+
return chalk.hex('#EE7600')('IN_REVIEW');
|
|
18
|
+
case ReviewStatus.APPROVED:
|
|
19
|
+
return chalk.green('APPROVED');
|
|
20
|
+
case ReviewStatus.NOT_REQUIRED:
|
|
21
|
+
return chalk.green('NOT_REQUIRED');
|
|
22
|
+
default: {
|
|
23
|
+
console.error(chalk.red(`Unrecognized ReviewStatus ${reviewStatus} `));
|
|
24
|
+
return chalk.red('UNKNOWN');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
export function timestampToDurationString(ms: number | undefined) {
|
|
3
|
+
if (!ms || ms <= 0) {
|
|
4
|
+
return '-';
|
|
5
|
+
}
|
|
6
|
+
const duration = ms / 1000;
|
|
7
|
+
const hours = Math.floor(duration / 3600);
|
|
8
|
+
const minutes = Math.floor((duration - (hours * 3600)) / 60);
|
|
9
|
+
const seconds = Math.trunc(duration - (hours * 3600) - (minutes * 60));
|
|
10
|
+
const out = [];
|
|
11
|
+
if (hours > 0) {
|
|
12
|
+
out.push(`${hours}h`);
|
|
13
|
+
}
|
|
14
|
+
if (minutes > 0) {
|
|
15
|
+
out.push(`${minutes}m`);
|
|
16
|
+
}
|
|
17
|
+
if (seconds > 0) {
|
|
18
|
+
out.push(`${seconds}s`);
|
|
19
|
+
}
|
|
20
|
+
return out.join(' ');
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {Rivendell} from './Rivendell';
|
|
3
|
+
|
|
4
|
+
export function formatVersionState(state: Rivendell.AppVersionState): string {
|
|
5
|
+
switch (state) {
|
|
6
|
+
case Rivendell.AppVersionState.NEW:
|
|
7
|
+
return chalk.yellow('NEW');
|
|
8
|
+
case Rivendell.AppVersionState.PUBLISHED:
|
|
9
|
+
return chalk.yellow('BUILT');
|
|
10
|
+
case Rivendell.AppVersionState.BUILD_FAILED:
|
|
11
|
+
return chalk.red('BUILD_FAILED');
|
|
12
|
+
case Rivendell.AppVersionState.STARTING:
|
|
13
|
+
return chalk.yellow('PUBLISHING');
|
|
14
|
+
case Rivendell.AppVersionState.START_FAILED:
|
|
15
|
+
return chalk.red('PUBLISHING_FAILED');
|
|
16
|
+
case Rivendell.AppVersionState.RUNNING:
|
|
17
|
+
return chalk.green('PUBLISHED');
|
|
18
|
+
case Rivendell.AppVersionState.STOPPING:
|
|
19
|
+
return chalk.yellow('UNPUBLISHING');
|
|
20
|
+
case Rivendell.AppVersionState.STOP_FAILED:
|
|
21
|
+
return chalk.red('UNPUBLISHING_FAILED');
|
|
22
|
+
case Rivendell.AppVersionState.STOPPED:
|
|
23
|
+
return chalk.red('UNPUBLISHED');
|
|
24
|
+
case Rivendell.AppVersionState.UPGRADING_RUNTIME:
|
|
25
|
+
return chalk.yellow('UPGRADING_RUNTIME');
|
|
26
|
+
default: {
|
|
27
|
+
console.error(chalk.red(`Unrecognized AppVersionState ${state} `));
|
|
28
|
+
return chalk.red('UNKNOWN');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {DotenvParseOutput, parse} from 'dotenv';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import {readAppYaml} from './AppContext';
|
|
4
|
+
import {getEnv} from './Config';
|
|
5
|
+
import {die} from './die';
|
|
6
|
+
|
|
7
|
+
export function gatherAppEnv(shard: string) {
|
|
8
|
+
return buildAppEnv(gatherEnvFiles(shard));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function validateAppEnvs(shards: string[]) {
|
|
12
|
+
const manifest = readAppYaml();
|
|
13
|
+
const expected = (manifest && manifest.environment) || [];
|
|
14
|
+
if (getEnv() === 'production') {
|
|
15
|
+
validateEnvFiles(shards, manifest);
|
|
16
|
+
}
|
|
17
|
+
validateEnvContents(shards, expected);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function validateEnvFiles(shards: string[], manifest: any) {
|
|
21
|
+
const errors: string[] = [];
|
|
22
|
+
if (manifest.meta.availability && manifest.meta.availability.includes('all')) {
|
|
23
|
+
if (shards.map((s) => `.env.${s}`).some((f) => fs.existsSync(f))) {
|
|
24
|
+
errors.push("Shard specific .env files are not allowed for 'all' availability");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (errors.length) {
|
|
28
|
+
die(errors.map((s) => `* ${s}`).join('\n'));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function validateEnvContents(shards: string[], expected: string[]) {
|
|
33
|
+
const errors: string[] = [];
|
|
34
|
+
for (const shard of shards) {
|
|
35
|
+
const files = gatherEnvFiles(shard);
|
|
36
|
+
errors.push(...validateAppEnv(buildAppEnv(files), expected, files));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (errors.length) {
|
|
40
|
+
die(errors.map((s) => `* ${s}`).join('\n'));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateAppEnv(appEnv: DotenvParseOutput, expected: string[], envFiles: string[]) {
|
|
45
|
+
const errors: string[] = [];
|
|
46
|
+
Object.keys(appEnv).forEach((name) => {
|
|
47
|
+
if (!expected.includes(name)) {
|
|
48
|
+
errors.push(`${envFiles}: One of the env files contains a variable not listed in the app.yml: ${name}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expected.forEach((name) => {
|
|
53
|
+
if (!appEnv[name]) {
|
|
54
|
+
errors.push(`${envFiles}: None of the env files provide a value for the required variable: ${name}`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return errors;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function gatherEnvFiles(shard: string) {
|
|
61
|
+
const files = ['.env', `.env.${getEnv()}`];
|
|
62
|
+
if (getEnv() === 'production') {
|
|
63
|
+
files.push(`.env.${shard}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildAppEnv(files: string[]) {
|
|
70
|
+
const appEnv: DotenvParseOutput = {};
|
|
71
|
+
files.filter((f) => fs.existsSync(f)).forEach((f) => {
|
|
72
|
+
Object.assign(appEnv, parse(fs.readFileSync(f)));
|
|
73
|
+
});
|
|
74
|
+
return appEnv;
|
|
75
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {terminal} from 'terminal-kit';
|
|
2
|
+
|
|
3
|
+
export function handleInterrupt() {
|
|
4
|
+
terminal.on('key', (name: string) => {
|
|
5
|
+
if (name === 'CTRL_C') {
|
|
6
|
+
terminal.grabInput(false);
|
|
7
|
+
terminal.hideCursor(false);
|
|
8
|
+
console.log('^C');
|
|
9
|
+
terminal.eraseDisplayBelow();
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {Rivendell} from './Rivendell';
|
|
2
|
+
import Job = Rivendell.Job;
|
|
3
|
+
|
|
4
|
+
export function jobRuntime(job: Job): number {
|
|
5
|
+
if (job.completedAt) {
|
|
6
|
+
return (new Date(job.completedAt).getTime() - new Date(job.triggeredAt).getTime());
|
|
7
|
+
} else if (job.terminatedAt) {
|
|
8
|
+
return (new Date(job.terminatedAt).getTime() - new Date(job.triggeredAt).getTime());
|
|
9
|
+
} else {
|
|
10
|
+
return (new Date().getTime() - new Date(job.triggeredAt).getTime());
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {default as parseDuration} from 'parse-duration';
|
|
2
|
+
import { fromUnixTime, parseISO } from 'date-fns';
|
|
3
|
+
|
|
4
|
+
const EPOCH_TIMESTAMP_REGEX = /^\d+$/;
|
|
5
|
+
|
|
6
|
+
export function parseDate(value: string): Date {
|
|
7
|
+
const iso = parseISO(value);
|
|
8
|
+
if (iso.getTime()) {
|
|
9
|
+
return iso;
|
|
10
|
+
} else if (EPOCH_TIMESTAMP_REGEX.test(value)) {
|
|
11
|
+
return fromUnixTime(parseInt(value, 10));
|
|
12
|
+
} else {
|
|
13
|
+
return new Date(Date.now() - parseDuration(value)!);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isValidDateOrEpochOrDurationString(value: string): boolean {
|
|
18
|
+
return EPOCH_TIMESTAMP_REGEX.test(value) ||
|
|
19
|
+
parseISO(value).getTime() ||
|
|
20
|
+
parseDuration(value) ? true : false;
|
|
21
|
+
}
|