@adobe/aem-cli 15.0.0

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.
@@ -0,0 +1,143 @@
1
+ /*
2
+ * Copyright 2018 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import opn from 'open';
13
+ import chalk from 'chalk-template';
14
+ import fs from 'fs/promises';
15
+ import fse from 'fs-extra';
16
+ import { resetContext } from './fetch-utils.js';
17
+ import { AbstractCommand } from './abstract.cmd.js';
18
+
19
+ export class AbstractServerCommand extends AbstractCommand {
20
+ constructor(logger) {
21
+ super(logger);
22
+ this._httpPort = -1;
23
+ this._bindAddr = null;
24
+ this._tls = false;
25
+ this._tlsCertPath = undefined;
26
+ this._tlsKeyPath = undefined;
27
+ this._scheme = 'http';
28
+ this._stopping = false;
29
+ this._cache = null;
30
+ }
31
+
32
+ withHttpPort(p) {
33
+ this._httpPort = p;
34
+ return this;
35
+ }
36
+
37
+ withBindAddr(a) {
38
+ this._bindAddr = a;
39
+ return this;
40
+ }
41
+
42
+ withTLS(tlsKeyPath, tlsCertPath) {
43
+ if (tlsKeyPath && tlsCertPath) {
44
+ this._tls = true;
45
+ this._tlsKeyPath = tlsKeyPath;
46
+ this._tlsCertPath = tlsCertPath;
47
+ }
48
+ return this;
49
+ }
50
+
51
+ withOpen(o) {
52
+ this._open = o === 'false' ? false : o;
53
+ return this;
54
+ }
55
+
56
+ withCache(value) {
57
+ this._cache = value;
58
+ return this;
59
+ }
60
+
61
+ withKill(value) {
62
+ this._kill = value;
63
+ return this;
64
+ }
65
+
66
+ get project() {
67
+ return this._project;
68
+ }
69
+
70
+ async doStop() {
71
+ if (this._project) {
72
+ await this._project.stop();
73
+ delete this._project;
74
+ }
75
+ await resetContext();
76
+ }
77
+
78
+ async stop() {
79
+ if (this._stopping) {
80
+ return;
81
+ }
82
+ this._stopping = true;
83
+ await this.doStop();
84
+ this.emit('stopped', this);
85
+ }
86
+
87
+ async initSeverOptions() {
88
+ if (this._cache) {
89
+ await fse.ensureDir(this._cache);
90
+ this._project.withCacheDirectory(this._cache);
91
+ }
92
+
93
+ if (this._tls) {
94
+ if (
95
+ !this._tlsCertPath
96
+ || !this._tlsKeyPath
97
+ ) {
98
+ throw Error(chalk`{red If using TLS, you must provide both tls cert and tls key...one or both not found }`);
99
+ }
100
+ // read each file
101
+ try {
102
+ const key = await fs.readFile(this._tlsKeyPath);
103
+ const cert = await fs.readFile(this._tlsCertPath);
104
+ this._project.withTLS(key, cert);
105
+ // if all of that works, switch to https scheme
106
+ this._scheme = 'https';
107
+ } catch (e) {
108
+ throw Error(chalk`{red Unable to read the tls key key or cert file. }`);
109
+ }
110
+ }
111
+ if (this._httpPort >= 0) {
112
+ this._project.withHttpPort(this._httpPort);
113
+ }
114
+ if (this._bindAddr) {
115
+ this._project.withBindAddr(this._bindAddr);
116
+ }
117
+ }
118
+
119
+ async run() {
120
+ await this.init();
121
+ await this._project.start();
122
+ this.emit('started', this);
123
+ if (this._open) {
124
+ await this.open(this._open);
125
+ }
126
+ }
127
+
128
+ async open(href) {
129
+ let url;
130
+ try {
131
+ url = new URL(href.startsWith('/')
132
+ ? `${this._project.server.scheme}://${this._project.server.hostname}:${this._project.server.port}${href}`
133
+ : href);
134
+ } catch (e) {
135
+ throw Error('invalid argument for --open. either provide an relative url starting with \'/\' or an absolute http(s) url.');
136
+ }
137
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
138
+ throw Error(`refuse to open non http(s) url (--open): ${url}`);
139
+ }
140
+ this.log.info(`opening default browser: ${url.href}`);
141
+ await opn(url.href);
142
+ }
143
+ }
@@ -0,0 +1,42 @@
1
+ /*
2
+ * Copyright 2018 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import EventEmitter from 'events';
13
+ import { getOrCreateLogger } from './log-common.js';
14
+
15
+ export class AbstractCommand extends EventEmitter {
16
+ constructor(logger) {
17
+ super();
18
+ this._initialized = false;
19
+ this._logger = logger || getOrCreateLogger();
20
+ this._directory = process.cwd();
21
+ }
22
+
23
+ withDirectory(dir) {
24
+ this._directory = dir;
25
+ return this;
26
+ }
27
+
28
+ get log() {
29
+ return this._logger;
30
+ }
31
+
32
+ get directory() {
33
+ return this._directory;
34
+ }
35
+
36
+ async init() {
37
+ if (!this._initialized) {
38
+ this._initialized = true;
39
+ }
40
+ return this;
41
+ }
42
+ }
@@ -0,0 +1,74 @@
1
+ /*
2
+ * Copyright 2018 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import chalk from 'chalk-template';
13
+
14
+ const spinnerFrames = process.platform === 'win32'
15
+ ? ['-', '\\', '|', '/']
16
+ : ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
17
+
18
+ export const createSpinner = (msg) => {
19
+ const hideCursor = () => {
20
+ this.running = false;
21
+ // ensure we quit after ctrl+c and show cursor again
22
+ process.stdout.write('\u001b[?25h');
23
+ process.exit(0);
24
+ };
25
+ process.once('SIGINT', hideCursor);
26
+
27
+ return {
28
+ i: 0,
29
+ running: false,
30
+ s: process.stdout,
31
+ written: false,
32
+
33
+ run() {
34
+ if (this.running) {
35
+ if (msg) {
36
+ this.s.write(chalk`{cyan ${spinnerFrames[this.i]}} ${msg}`);
37
+ this.s.cursorTo(0);
38
+ } else {
39
+ this.s.write(chalk`{cyan ${spinnerFrames[this.i]}}`);
40
+ this.s.moveCursor(-1);
41
+ }
42
+ this.written = true;
43
+ this.i = (this.i + 1) % spinnerFrames.length;
44
+ setTimeout(this.run.bind(this), 100);
45
+ }
46
+ },
47
+
48
+ start() {
49
+ if (!this.s.moveCursor) {
50
+ return this;
51
+ }
52
+ this.s.write('\u001b[?25l');
53
+ this.running = true;
54
+ this.run();
55
+ return this;
56
+ },
57
+
58
+ stop() {
59
+ this.s.write('\u001b[?25h');
60
+ this.running = false;
61
+ if (this.written) {
62
+ this.s.clearLine(1);
63
+ }
64
+ process.removeListener('SIGINT', hideCursor);
65
+ return this;
66
+ },
67
+ };
68
+ };
69
+
70
+ export async function prompt(rl, question) {
71
+ return new Promise((resolve) => {
72
+ rl.question(question, resolve);
73
+ });
74
+ }
package/src/cli.js ADDED
@@ -0,0 +1,137 @@
1
+ /*
2
+ * Copyright 2018 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import yargs from 'yargs';
13
+ import camelcase from 'camelcase';
14
+ import path from 'path';
15
+ import chalk from 'chalk-template';
16
+ import { resetContext } from './fetch-utils.js';
17
+
18
+ const MIN_MSG = 'You need at least one command.';
19
+
20
+ function envAwareStrict(args, aliases) {
21
+ const specialKeys = ['$0', '--', '_'];
22
+
23
+ const hlxEnv = {};
24
+ Object
25
+ .keys(process.env)
26
+ .forEach((key) => {
27
+ if (key.startsWith('HLX_')) {
28
+ throw new Error(chalk`{red warning:} The environment prefix "HLX_" is not supported anymore. Please use "AEM_" instead.`);
29
+ }
30
+ if (key.startsWith('AEM_')) {
31
+ hlxEnv[camelcase(key.substring(4))] = key;
32
+ }
33
+ });
34
+
35
+ const unknown = [];
36
+ Object.keys(args).forEach((key) => {
37
+ if (specialKeys.indexOf(key) === -1 && !(key in hlxEnv) && !(key in aliases)) {
38
+ unknown.push(key);
39
+ }
40
+ });
41
+
42
+ if (unknown.length > 0) {
43
+ return unknown.length === 1 ? `Unknown argument: ${unknown[0]}` : `Unknown arguments: ${unknown.join(', ')}`;
44
+ }
45
+ if (path.basename(process.argv[1]) === 'hlx') {
46
+ return chalk`{red warning:} The "hlx" command is deprecated. Please use "aem" instead.`;
47
+ }
48
+ return true;
49
+ }
50
+
51
+ /**
52
+ * Adds the default logging options.
53
+ * @param argv Yargs
54
+ * @returns {Yargs} the args
55
+ */
56
+ function logArgs(argv) {
57
+ return argv
58
+ .option('log-file', {
59
+ alias: 'logFile',
60
+ describe: 'Log file (use "-" for stdout)',
61
+ type: 'string',
62
+ array: true,
63
+ default: '-',
64
+ })
65
+ .option('log-level', {
66
+ alias: 'logLevel',
67
+ describe: 'Log level',
68
+ type: 'string',
69
+ choices: ['silly', 'debug', 'verbose', 'info', 'warn', 'error'],
70
+ default: 'info',
71
+ });
72
+ }
73
+
74
+ export default class CLI {
75
+ constructor() {
76
+ this._failFn = (message, err, argv) => {
77
+ const msg = err && err.message ? err.message : message;
78
+ if (msg) {
79
+ // eslint-disable-next-line no-console
80
+ console.error(msg);
81
+ }
82
+ if (msg === MIN_MSG || /.*Unknown argument.*/.test(msg) || /.*Not enough non-option arguments:.*/.test(msg)) {
83
+ // eslint-disable-next-line no-console
84
+ console.error('\n%s', argv.help());
85
+ }
86
+ process.exit(1);
87
+ };
88
+ }
89
+
90
+ withCommandExecutor(name, exec) {
91
+ this._commands[name].executor = exec;
92
+ return this;
93
+ }
94
+
95
+ onFail(fn) {
96
+ this._failFn = fn;
97
+ return this;
98
+ }
99
+
100
+ async initCommands() {
101
+ if (!this._commands) {
102
+ this._commands = {};
103
+ for (const cmd of ['up', 'hack', 'import']) {
104
+ if (!this._commands[cmd]) {
105
+ // eslint-disable-next-line no-await-in-loop
106
+ this._commands[cmd] = (await import(`./${cmd}.js`)).default();
107
+ }
108
+ }
109
+ }
110
+ return this;
111
+ }
112
+
113
+ async run(args) {
114
+ await this.initCommands();
115
+ const argv = yargs();
116
+ Object.values(this._commands)
117
+ .forEach((cmd) => argv.command(cmd));
118
+
119
+ logArgs(argv)
120
+ .strictCommands(true)
121
+ .scriptName('aem')
122
+ .usage('Usage: $0 <command> [options]')
123
+ .parserConfiguration({ 'camel-case-expansion': false })
124
+ .env('AEM')
125
+ .check((a) => envAwareStrict(a, argv.parsed.aliases))
126
+ .showHelpOnFail(true)
127
+ .fail(this._failFn)
128
+ .exitProcess(args.indexOf('--get-yargs-completions') > -1)
129
+ .demandCommand(1, MIN_MSG)
130
+ .epilogue('use <command> --help to get command specific details.\n\nfor more information, find our manual at https://github.com/adobe/helix-cli')
131
+ .help()
132
+ .parse(args);
133
+
134
+ // reset fetch connections so that process can terminate
135
+ await resetContext();
136
+ }
137
+ }
@@ -0,0 +1,49 @@
1
+ /*
2
+ * Copyright 2018 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import chalk from 'chalk-template';
13
+ import semver from 'semver';
14
+ import GitUtils from '../git-utils.js';
15
+ import pkgJson from '../package.cjs';
16
+
17
+ /**
18
+ * Checks if the .env file is ignored by git.
19
+ * @param dir the current directory
20
+ * @returns {Promise<void>}
21
+ */
22
+ export async function validateDotEnv(dir = process.cwd()) {
23
+ if (await GitUtils.isIgnored(dir, '.env')) {
24
+ return;
25
+ }
26
+ process.stdout.write(chalk`
27
+ {yellowBright Warning:} Your {cyan '.env'} file is currently not ignored by git.
28
+ This is typically not good because it might contain secrets
29
+ which should never be stored in the git repository.
30
+
31
+ `);
32
+ }
33
+
34
+ /**
35
+ * Checks if the given version is supported.
36
+ * @param version {string} current node version
37
+ * @param stdout {WritableStream} to report the warninf
38
+ */
39
+ export function checkNodeVersion(version = process.version, stdout = process.stdout) {
40
+ const supported = pkgJson.engines.node;
41
+ if (!semver.satisfies(version, supported)) {
42
+ stdout.write(chalk`
43
+ {yellowBright Warning:} The current node version {cyan ${version}} does not satisfy
44
+ the supported version range {cyan ${supported}}.
45
+ You might encounter unexpected errors.
46
+
47
+ `);
48
+ }
49
+ }
@@ -0,0 +1,65 @@
1
+ /*
2
+ * Copyright 2021 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import { keepAlive } from '@adobe/fetch';
13
+ import { getProxyForUrl } from 'proxy-from-env';
14
+ import nodeFetch from 'node-fetch';
15
+ import { HttpProxyAgent } from 'http-proxy-agent';
16
+ import { HttpsProxyAgent } from 'https-proxy-agent';
17
+
18
+ const CONTEXT_CACHE = {
19
+ default: null,
20
+ insecure: null,
21
+ };
22
+
23
+ /**
24
+ * @type {ProxyHandler}
25
+ */
26
+ const httpProxyHandler = {
27
+ apply(target, thisArg, argArray) {
28
+ // check if HTTP proxy is defined for the url
29
+ const /** @type URL */ [url, init = {}] = argArray;
30
+ const href = String(url); // ensure string
31
+ const proxyUrl = getProxyForUrl(href);
32
+ if (proxyUrl) {
33
+ const agent = href.startsWith('https://')
34
+ ? new HttpsProxyAgent(proxyUrl)
35
+ : new HttpProxyAgent(proxyUrl);
36
+ // eslint-disable-next-line no-console
37
+ console.debug(`using proxy ${proxyUrl}`);
38
+ return nodeFetch(url, {
39
+ ...init,
40
+ agent,
41
+ });
42
+ }
43
+ return target.apply(thisArg, argArray);
44
+ },
45
+ };
46
+
47
+ // create global context that is used by all commands and can be reset for CLI to terminate
48
+ export function getFetch(allowUnauthorized) {
49
+ const cacheName = allowUnauthorized ? 'insecure' : 'default';
50
+ let cache = CONTEXT_CACHE[cacheName];
51
+ if (!cache) {
52
+ const context = keepAlive({ rejectUnauthorized: !allowUnauthorized });
53
+ cache = {
54
+ context,
55
+ fetch: new Proxy(context.fetch, httpProxyHandler),
56
+ };
57
+ CONTEXT_CACHE[cacheName] = cache;
58
+ }
59
+ return cache.fetch;
60
+ }
61
+
62
+ export async function resetContext() {
63
+ await CONTEXT_CACHE.default?.context.reset();
64
+ await CONTEXT_CACHE.insecure?.context.reset();
65
+ }