import Vue from 'vue';
import type { Diagnostic } from '@codemirror/lint';
import { linter as createLinter } from '@codemirror/lint';
import { jsonParseLinter } from '@codemirror/lang-json';
import type { EditorView } from '@codemirror/view';
import * as esprima from 'esprima-next';
import type { Node } from 'estree';
import type { CodeNodeEditorLanguage } from 'n8n-workflow';

import { DEFAULT_LINTER_DELAY_IN_MS, DEFAULT_LINTER_SEVERITY } from './constants';
import { OFFSET_FOR_SCRIPT_WRAPPER } from './constants';
import { walk } from './utils';
import type { CodeNodeEditorMixin, RangeNode } from './types';

export const linterExtension = (Vue as CodeNodeEditorMixin).extend({
	methods: {
		createLinter(language: CodeNodeEditorLanguage) {
			switch (language) {
				case 'javaScript':
					return createLinter(this.lintSource, { delay: DEFAULT_LINTER_DELAY_IN_MS });
				case 'json':
					return createLinter(jsonParseLinter());
			}
			return undefined;
		},

		lintSource(editorView: EditorView): Diagnostic[] {
			const doc = editorView.state.doc.toString();
			const script = `module.exports = async function() {${doc}\n}()`;

			let ast: esprima.Program | null = null;

			try {
				ast = esprima.parseScript(script, { range: true });
			} catch (syntaxError) {
				let line;

				try {
					const lineAtError = editorView.state.doc.line(syntaxError.lineNumber - 1).text;

					// optional chaining operators currently unsupported by esprima-next
					if (['?.', ']?'].some((operator) => lineAtError.includes(operator))) return [];
				} catch {
					return [];
				}

				try {
					line = editorView.state.doc.line(syntaxError.lineNumber);

					return [
						{
							from: line.from,
							to: line.to,
							severity: DEFAULT_LINTER_SEVERITY,
							message: this.$locale.baseText('codeNodeEditor.linter.bothModes.syntaxError'),
						},
					];
				} catch {
					/**
					 * For invalid (e.g. half-written) n8n syntax, esprima errors with an off-by-one line number for the final line. In future, we should add full linting for n8n syntax before parsing JS.
					 */
					return [];
				}
			}

			const lintings: Diagnostic[] = [];

			/**
			 * Lint for incorrect `.item()` instead of `.item` in `runOnceForEachItem` mode
			 *
			 * $input.item() -> $input.item
			 */

			if (this.mode === 'runOnceForEachItem') {
				const isItemCall = (node: Node) =>
					node.type === 'CallExpression' &&
					node.callee.type === 'MemberExpression' &&
					node.callee.property.type === 'Identifier' &&
					node.callee.property.name === 'item';

				walk(ast, isItemCall).forEach((node) => {
					const [start, end] = this.getRange(node);

					lintings.push({
						from: start,
						to: end,
						severity: DEFAULT_LINTER_SEVERITY,
						message: this.$locale.baseText('codeNodeEditor.linter.allItems.itemCall'),
						actions: [
							{
								name: 'Fix',
								apply(view, _, to) {
									view.dispatch({ changes: { from: end - '()'.length, to } });
								},
							},
						],
					});
				});
			}

			/**
			 * Lint for `$json`, `$binary` and `$itemIndex` unavailable in `runOnceForAllItems` mode
			 *
			 * $json -> <removed>
			 */

			if (this.mode === 'runOnceForAllItems') {
				const isUnavailableVarInAllItems = (node: Node) =>
					node.type === 'Identifier' && ['$json', '$binary', '$itemIndex'].includes(node.name);

				walk(ast, isUnavailableVarInAllItems).forEach((node) => {
					const [start, end] = this.getRange(node);

					const varName = this.getText(node);

					if (!varName) return;

					const message = [
						`\`${varName}\``,
						this.$locale.baseText('codeNodeEditor.linter.allItems.unavailableVar'),
					].join(' ');

					lintings.push({
						from: start,
						to: end,
						severity: DEFAULT_LINTER_SEVERITY,
						message,
						actions: [
							{
								name: 'Remove',
								apply(view, from, to) {
									view.dispatch({ changes: { from, to } });
								},
							},
						],
					});
				});
			}

			/**
			 * Lint for `.item` unavailable in `$input` in `runOnceForAllItems` mode
			 *
			 * $input.item -> <removed>
			 */

			if (this.mode === 'runOnceForAllItems') {
				type TargetNode = RangeNode & { property: RangeNode };

				const isUnavailableInputItemAccess = (node: Node) =>
					node.type === 'MemberExpression' &&
					node.computed === false &&
					node.object.type === 'Identifier' &&
					node.object.name === '$input' &&
					node.property.type === 'Identifier' &&
					node.property.name === 'item';

				walk<TargetNode>(ast, isUnavailableInputItemAccess).forEach((node) => {
					const [start, end] = this.getRange(node.property);

					lintings.push({
						from: start,
						to: end,
						severity: DEFAULT_LINTER_SEVERITY,
						message: this.$locale.baseText('codeNodeEditor.linter.allItems.unavailableProperty'),
						actions: [
							{
								name: 'Remove',
								apply(view) {
									view.dispatch({ changes: { from: start - '.'.length, to: end } });
								},
							},
						],
					});
				});
			}

			/**
			 * Lint for `item` (legacy var from Function Item node) unavailable
			 * in `runOnceForAllItems` mode, unless user-defined `item`.
			 *
			 * item -> $input.all()
			 */
			if (this.mode === 'runOnceForAllItems' && !/(let|const|var) item (=|of)/.test(script)) {
				type TargetNode = RangeNode & { object: RangeNode & { name: string } };

				const isUnavailableLegacyItems = (node: Node) =>
					node.type === 'Identifier' && node.name === 'item';

				walk<TargetNode>(ast, isUnavailableLegacyItems).forEach((node) => {
					const [start, end] = this.getRange(node);

					lintings.push({
						from: start,
						to: end,
						severity: DEFAULT_LINTER_SEVERITY,
						message: this.$locale.baseText('codeNodeEditor.linter.allItems.unavailableItem'),
						actions: [
							{
								name: 'Fix',
								apply(view, from, to) {
									// prevent second insertion of unknown origin
									if (view.state.doc.toString().slice(from, to).includes('$input.all()')) {
										return;
									}

									view.dispatch({ changes: { from: start, to: end } });
									view.dispatch({ changes: { from, insert: '$input.all()' } });
								},
							},
						],
					});
				});
			}

			/**
			 * Lint for `items` (legacy var from Function node) unavailable
			 * in `runOnceForEachItem` mode, unless user-defined `items`.
			 *
			 * items -> $input.item
			 */
			if (this.mode === 'runOnceForEachItem' && !/(let|const|var) items =/.test(script)) {
				type TargetNode = RangeNode & { object: RangeNode & { name: string } };

				const isUnavailableLegacyItems = (node: Node) =>
					node.type === 'Identifier' && node.name === 'items';

				walk<TargetNode>(ast, isUnavailableLegacyItems).forEach((node) => {
					const [start, end] = this.getRange(node);

					lintings.push({
						from: start,
						to: end,
						severity: DEFAULT_LINTER_SEVERITY,
						message: this.$locale.baseText('codeNodeEditor.linter.eachItem.unavailableItems'),
						actions: [
							{
								name: 'Fix',
								apply(view, from, to) {
									// prevent second insertion of unknown origin
									if (view.state.doc.toString().slice(from, to).includes('$input.item')) {
										return;
									}

									view.dispatch({ changes: { from: start, to: end } });
									view.dispatch({ changes: { from, insert: '$input.item' } });
								},
							},
						],
					});
				});
			}

			/**
			 * Lint for `.first()`, `.last()`, `.all()` and `.itemMatching()`
			 * unavailable in `runOnceForEachItem` mode
			 *
			 * $input.first()
			 * $input.last()
			 * $input.all()
			 * $input.itemMatching()
			 */

			if (this.mode === 'runOnceForEachItem') {
				type TargetNode = RangeNode & { property: RangeNode & { name: string } };

				const isUnavailableMethodinEachItem = (node: Node) =>
					node.type === 'MemberExpression' &&
					node.computed === false &&
					node.object.type === 'Identifier' &&
					node.object.name === '$input' &&
					node.property.type === 'Identifier' &&
					['first', 'last', 'all', 'itemMatching'].includes(node.property.name);

				walk<TargetNode>(ast, isUnavailableMethodinEachItem).forEach((node) => {
					const [start, end] = this.getRange(node.property);

					const method = this.getText(node.property);

					if (!method) return;

					lintings.push({
						from: start,
						to: end,
						severity: DEFAULT_LINTER_SEVERITY,
						message: this.$locale.baseText('codeNodeEditor.linter.eachItem.unavailableMethod', {
							interpolate: { method },
						}),
					});
				});
			}

			/**
			 * Lint for `.itemMatching()` called with no argument in `runOnceForAllItems` mode
			 *
			 * $input.itemMatching()
			 */

			if (this.mode === 'runOnceForAllItems') {
				type TargetNode = RangeNode & { callee: RangeNode & { property: RangeNode } };

				const isItemMatchingCallWithoutArg = (node: Node) =>
					node.type === 'CallExpression' &&
					node.callee.type === 'MemberExpression' &&
					node.callee.property.type === 'Identifier' &&
					node.callee.property.name === 'itemMatching' &&
					node.arguments.length === 0;

				walk<TargetNode>(ast, isItemMatchingCallWithoutArg).forEach((node) => {
					const [start, end] = this.getRange(node.callee.property);

					lintings.push({
						from: start,
						to: end + '()'.length,
						severity: DEFAULT_LINTER_SEVERITY,
						message: this.$locale.baseText('codeNodeEditor.linter.allItems.itemMatchingNoArg'),
					});
				});
			}

			/**
			 * Lint for `$input.first()` or `$input.last()` called with argument in `runOnceForAllItems` mode
			 *
			 * $input.first(arg) -> $input.first()
			 * $input.last(arg) -> $input.last()
			 */

			if (this.mode === 'runOnceForAllItems') {
				type TargetNode = RangeNode & {
					callee: { property: { name: string } & RangeNode };
				};

				const inputFirstOrLastCalledWithArg = (node: Node) =>
					node.type === 'CallExpression' &&
					node.callee.type === 'MemberExpression' &&
					node.callee.computed === false &&
					node.callee.object.type === 'Identifier' &&
					node.callee.object.name === '$input' &&
					node.callee.property.type === 'Identifier' &&
					['first', 'last'].includes(node.callee.property.name) &&
					node.arguments.length !== 0;

				walk<TargetNode>(ast, inputFirstOrLastCalledWithArg).forEach((node) => {
					const [start, end] = this.getRange(node.callee.property);

					const message = [
						`\`$input.${node.callee.property.name}()\``,
						this.$locale.baseText('codeNodeEditor.linter.allItems.firstOrLastCalledWithArg'),
					].join(' ');

					lintings.push({
						from: start,
						to: end,
						severity: DEFAULT_LINTER_SEVERITY,
						message,
					});
				});
			}

			/**
			 * Lint for empty (i.e. no value) return
			 *
			 * return -> <no autofix>
			 */

			const isEmptyReturn = (node: Node) =>
				node.type === 'ReturnStatement' && node.argument === null;

			const emptyReturnMessage =
				this.mode === 'runOnceForAllItems'
					? this.$locale.baseText('codeNodeEditor.linter.allItems.emptyReturn')
					: this.$locale.baseText('codeNodeEditor.linter.eachItem.emptyReturn');

			walk<RangeNode>(ast, isEmptyReturn).forEach((node) => {
				const [start, end] = node.range.map((loc) => loc - OFFSET_FOR_SCRIPT_WRAPPER);

				lintings.push({
					from: start,
					to: end,
					severity: DEFAULT_LINTER_SEVERITY,
					message: emptyReturnMessage,
				});
			});

			/**
			 * Lint for array return in `runOnceForEachItem` mode
			 *
			 * return [] -> <no autofix>
			 */

			if (this.mode === 'runOnceForEachItem') {
				const isArrayReturn = (node: Node) =>
					node.type === 'ReturnStatement' &&
					node.argument !== null &&
					node.argument !== undefined &&
					node.argument.type === 'ArrayExpression';

				walk<RangeNode>(ast, isArrayReturn).forEach((node) => {
					const [start, end] = this.getRange(node);

					lintings.push({
						from: start,
						to: end,
						severity: DEFAULT_LINTER_SEVERITY,
						message: this.$locale.baseText('codeNodeEditor.linter.eachItem.returnArray'),
					});
				});
			}

			/**
			 * Lint for direct access to item property (i.e. not using `json`)
			 * in `runOnceForAllItems` mode
			 *
			 * item.myField = 123 -> item.json.myField = 123;
			 * const a = item.myField -> const a = item.json.myField;
			 */

			if (this.mode === 'runOnceForAllItems') {
				type TargetNode = RangeNode & {
					left: { declarations: Array<{ id: { type: string; name: string } }> };
				};

				const isForOfStatementOverN8nVar = (node: Node) =>
					node.type === 'ForOfStatement' &&
					node.left.type === 'VariableDeclaration' &&
					node.left.declarations.length === 1 &&
					node.left.declarations[0].type === 'VariableDeclarator' &&
					node.left.declarations[0].id.type === 'Identifier' &&
					node.right.type === 'CallExpression' &&
					node.right.callee.type === 'MemberExpression' &&
					node.right.callee.computed === false &&
					node.right.callee.object.type === 'Identifier' &&
					node.right.callee.object.name.startsWith('$'); // n8n var, e.g $input

				const found = walk<TargetNode>(ast, isForOfStatementOverN8nVar);

				if (found.length === 1) {
					const itemAlias = found[0].left.declarations[0].id.name;

					/**
					 * for (const item of $input.all()) {
					 * 	const item = {}; // shadow item
					 * }
					 */
					const isShadowItemVar = (node: Node) =>
						node.type === 'VariableDeclarator' &&
						node.id.type === 'Identifier' &&
						node.id.name === 'item' &&
						node.init !== null;

					const shadowFound = walk(ast, isShadowItemVar);

					let shadowStart: undefined | number;

					if (shadowFound.length > 0) {
						const [shadow] = shadowFound;
						const [_shadowStart] = this.getRange(shadow);
						shadowStart = _shadowStart;
					}

					const isDirectAccessToItem = (node: Node) =>
						node.type === 'MemberExpression' &&
						node.object.type === 'Identifier' &&
						node.object.name === itemAlias &&
						node.property.type === 'Identifier' &&
						!['json', 'binary'].includes(node.property.name);

					walk(ast, isDirectAccessToItem).forEach((node) => {
						const [start, end] = this.getRange(node);

						if (shadowStart && start > shadowStart) return; // skip shadow item

						const varName = this.getText(node);

						if (!varName) return;

						lintings.push({
							from: start,
							to: end,
							severity: DEFAULT_LINTER_SEVERITY,
							message: this.$locale.baseText(
								'codeNodeEditor.linter.bothModes.directAccess.itemProperty',
							),
							actions: [
								{
									name: 'Fix',
									apply(view, from, to) {
										// prevent second insertion of unknown origin
										if (view.state.doc.toString().slice(from, to).includes('.json')) return;

										view.dispatch({ changes: { from: from + itemAlias.length, insert: '.json' } });
									},
								},
							],
						});
					});
				}
			}

			/**
			 * Lint for direct access to item property (i.e. not using `json`)
			 * in `runOnceForEachItem` mode
			 *
			 * $input.item.myField = 123 -> $input.item.json.myField = 123;
			 * const a = $input.item.myField -> const a = $input.item.json.myField;
			 */

			if (this.mode === 'runOnceForEachItem') {
				type TargetNode = RangeNode & { object: { property: RangeNode } };

				const isDirectAccessToItemSubproperty = (node: Node) =>
					node.type === 'MemberExpression' &&
					node.object.type === 'MemberExpression' &&
					node.object.property.type === 'Identifier' &&
					node.object.property.name === 'item' &&
					node.property.type === 'Identifier' &&
					!['json', 'binary'].includes(node.property.name);

				walk<TargetNode>(ast, isDirectAccessToItemSubproperty).forEach((node) => {
					const varName = this.getText(node);

					if (!varName) return;

					const [start, end] = this.getRange(node);

					const [_, fixEnd] = this.getRange(node.object.property);

					lintings.push({
						from: start,
						to: end,
						severity: DEFAULT_LINTER_SEVERITY,
						message: this.$locale.baseText(
							'codeNodeEditor.linter.bothModes.directAccess.itemProperty',
						),
						actions: [
							{
								name: 'Fix',
								apply(view, from, to) {
									// prevent second insertion of unknown origin
									if (view.state.doc.toString().slice(from, to).includes('.json')) return;

									view.dispatch({ changes: { from: fixEnd, insert: '.json' } });
								},
							},
						],
					});
				});
			}

			/**
			 * Lint for direct access to `first()` or `last()` output (i.e. not using `json`)
			 *
			 * $input.first().myField -> $input.first().json.myField
			 */

			type TargetNode = RangeNode & { object: RangeNode };

			const isDirectAccessToFirstOrLastCall = (node: Node) =>
				node.type === 'MemberExpression' &&
				node.property.type === 'Identifier' &&
				!['json', 'binary'].includes(node.property.name) &&
				node.object.type === 'CallExpression' &&
				node.object.arguments.length === 0 &&
				node.object.callee.type === 'MemberExpression' &&
				node.object.callee.property.type === 'Identifier' &&
				['first', 'last'].includes(node.object.callee.property.name);

			walk<TargetNode>(ast, isDirectAccessToFirstOrLastCall).forEach((node) => {
				const [start, end] = this.getRange(node);

				const [_, fixEnd] = this.getRange(node.object);

				lintings.push({
					from: start,
					to: end,
					severity: DEFAULT_LINTER_SEVERITY,
					message: this.$locale.baseText(
						'codeNodeEditor.linter.bothModes.directAccess.firstOrLastCall',
					),
					actions: [
						{
							name: 'Fix',
							apply(view, from, to) {
								// prevent second insertion of unknown origin
								if (view.state.doc.toString().slice(from, to).includes('.json')) return;

								view.dispatch({ changes: { from: fixEnd, insert: '.json' } });
							},
						},
					],
				});
			});

			return lintings;
		},

		// ----------------------------------
		//            helpers
		// ----------------------------------

		getText(node: RangeNode) {
			if (!this.editor) return null;

			const [start, end] = this.getRange(node);

			return this.editor.state.doc.toString().slice(start, end);
		},

		getRange(node: RangeNode) {
			return node.range.map((loc) => loc - OFFSET_FOR_SCRIPT_WRAPPER);
		},
	},
});


description: 'Whether to connect even if SSL certificate validation is not possible',

import type { ICredentialType, INodeProperties } from 'n8n-workflow';

export class GotifyApi implements ICredentialType {
	name = 'gotifyApi';

	displayName = 'Gotify API';

	documentationUrl = 'gotify';

	properties: INodeProperties[] = [
		{
			displayName: 'App API Token',
			name: 'appApiToken',
			type: 'string',
			default: '',
			description: '(Optional) Needed for message creation',
		},
		{
			displayName: 'Client API Token',
			name: 'clientApiToken',
			type: 'string',
			default: '',
			description: '(Optional) Needed for everything (delete, getAll) but message creation',
		},
		{
			displayName: 'URL',
			name: 'url',
			type: 'string',
			default: '',
			description: 'The URL of the Gotify host',
		},
	];
}


import type { INodeProperties } from 'n8n-workflow';

export const customerOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		noDataExpression: true,
		default: 'get',
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create a customer',
				action: 'Create a customer',
			},
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete a customer',
				action: 'Delete a customer',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get a customer',
				action: 'Get a customer',
			},
			{
				name: 'Get Many',
				value: 'getAll',
				description: 'Get many customers',
				action: 'Get many customers',
			},
			{
				name: 'Update',
				value: 'update',
				description: 'Update a customer',
				action: 'Update a customer',
			},
		],
		displayOptions: {
			show: {
				resource: ['customer'],
			},
		},
	},
];

export const customerFields: INodeProperties[] = [
	// ----------------------------------
	//       customer: create
	// ----------------------------------
	{
		displayName: 'Name',
		name: 'name',
		type: 'string',
		required: true,
		default: '',
		description: 'Full name or business name of the customer to create',
		displayOptions: {
			show: {
				resource: ['customer'],
				operation: ['create'],
			},
		},
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: ['customer'],
				operation: ['create'],
			},
		},
		options: [
			{
				displayName: 'Address',
				name: 'address',
				type: 'fixedCollection',
				description: 'Address of the customer to create',
				placeholder: 'Add Field',
				default: {},
				options: [
					{
						displayName: 'Details',
						name: 'details',
						values: [
							{
								displayName: 'Line 1',
								name: 'line1',
								description: 'Address line 1 (e.g. street, PO Box, or company name)',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Line 2',
								name: 'line2',
								description: 'Address line 2 (e.g. apartment, suite, unit, or building)',
								type: 'string',
								default: '',
							},
							{
								displayName: 'City',
								name: 'city',
								description: 'City, district, suburb, town, or village',
								type: 'string',
								default: '',
							},
							{
								displayName: 'State',
								name: 'state',
								description: 'State, county, province, or region',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Country',
								name: 'country',
								description:
									'Two-letter country code (ISO 3166-1 alpha-2)',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Postal Code',
								name: 'postal_code',
								description: 'ZIP or postal code',
								type: 'string',
								default: '',
							},
						],
					},
				],
			},
			{
				displayName: 'Description',
				name: 'description',
				type: 'string',
				default: '',
				description: 'Arbitrary text to describe the customer to create',
			},
			{
				displayName: 'Email',
				name: 'email',
				type: 'string',
				placeholder: 'name@email.com',
				default: '',
				description: 'Email of the customer to create',
			},
			{
				displayName: 'Metadata',
				name: 'metadata',
				type: 'fixedCollection',
				default: {},
				placeholder: 'Add Metadata Item',
				description: 'Set of key-value pairs to attach to the customer to create',
				typeOptions: {
					multipleValues: true,
				},
				options: [
					{
						displayName: 'Metadata Properties',
						name: 'metadataProperties',
						values: [
							{
								displayName: 'Key',
								name: 'key',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Value',
								name: 'value',
								type: 'string',
								default: '',
							},
						],
					},
				],
			},
			{
				displayName: 'Phone',
				name: 'phone',
				type: 'string',
				default: '',
				description: 'Telephone number of the customer to create',
			},
			{
				displayName: 'Shipping',
				name: 'shipping',
				type: 'fixedCollection',
				default: {},
				description: 'Shipping information for the customer',
				typeOptions: {
					multipleValues: true,
				},
				placeholder: 'Add Field',
				options: [
					{
						displayName: 'Shipping Properties',
						name: 'shippingProperties',
						values: [
							{
								displayName: 'Recipient Name',
								name: 'name',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Recipient Address',
								name: 'address',
								type: 'fixedCollection',
								default: {},
								placeholder: 'Add Address Details',
								options: [
									{
										displayName: 'Details',
										name: 'details',
										values: [
											{
												displayName: 'Line 1',
												name: 'line1',
												description: 'Address line 1 (e.g. street, PO Box, or company name)',
												type: 'string',
												default: '',
											},
											{
												displayName: 'Line 2',
												name: 'line2',
												description: 'Address line 2 (e.g. apartment, suite, unit, or building)',
												type: 'string',
												default: '',
											},
											{
												displayName: 'City',
												name: 'city',
												description: 'City, district, suburb, town, or village',
												type: 'string',
												default: '',
											},
											{
												displayName: 'State',
												name: 'state',
												description: 'State, county, province, or region',
												type: 'string',
												default: '',
											},
											{
												displayName: 'Country',
												name: 'country',
												description:
													'Two-letter country code (ISO 3166-1 alpha-2)',
												type: 'string',
												default: '',
											},
											{
												displayName: 'Postal Code',
												name: 'postal_code',
												description: 'ZIP or postal code',
												type: 'string',
												default: '',
											},
										],
									},
								],
							},
							{
								displayName: 'Recipient Phone',
								name: 'phone',
								type: 'string',
								default: '',
							},
						],
					},
				],
			},
		],
	},

	// ----------------------------------
	//          customer: delete
	// ----------------------------------
	{
		displayName: 'Customer ID',
		name: 'customerId',
		type: 'string',
		required: true,
		default: '',
		description: 'ID of the customer to delete',
		displayOptions: {
			show: {
				resource: ['customer'],
				operation: ['delete'],
			},
		},
	},

	// ----------------------------------
	//          customer: get
	// ----------------------------------
	{
		displayName: 'Customer ID',
		name: 'customerId',
		type: 'string',
		required: true,
		default: '',
		description: 'ID of the customer to retrieve',
		displayOptions: {
			show: {
				resource: ['customer'],
				operation: ['get'],
			},
		},
	},

	// ----------------------------------
	//       customer: getAll
	// ----------------------------------
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		default: false,
		description: 'Whether to return all results or only up to a given limit',
		displayOptions: {
			show: {
				resource: ['customer'],
				operation: ['getAll'],
			},
		},
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		default: 50,
		description: 'Max number of results to return',
		typeOptions: {
			minValue: 1,
			maxValue: 1000,
		},
		displayOptions: {
			show: {
				resource: ['customer'],
				operation: ['getAll'],
				returnAll: [false],
			},
		},
	},
	{
		displayName: 'Filters',
		name: 'filters',
		type: 'collection',
		placeholder: 'Add Filter',
		default: {},
		displayOptions: {
			show: {
				resource: ['customer'],
				operation: ['getAll'],
			},
		},
		options: [
			{
				displayName: 'Email',
				name: 'email',
				type: 'string',
				placeholder: 'name@email.com',
				default: '',
				description: "Customer's email to filter by",
			},
		],
	},

	// ----------------------------------
	//         customer: update
	// ----------------------------------
	{
		displayName: 'Customer ID',
		name: 'customerId',
		type: 'string',
		required: true,
		default: '',
		description: 'ID of the customer to update',
		displayOptions: {
			show: {
				resource: ['customer'],
				operation: ['update'],
			},
		},
	},
	{
		displayName: 'Update Fields',
		name: 'updateFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: ['customer'],
				operation: ['update'],
			},
		},
		options: [
			{
				displayName: 'Address',
				name: 'address',
				type: 'fixedCollection',
				description: 'Address of the customer to update',
				placeholder: 'Add Field',
				default: {},
				options: [
					{
						displayName: 'Details',
						name: 'details',
						values: [
							{
								displayName: 'Line 1',
								name: 'line1',
								description: 'Address line 1 (e.g. street, PO Box, or company name)',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Line 2',
								name: 'line2',
								description: 'Address line 2 (e.g. apartment, suite, unit, or building)',
								type: 'string',
								default: '',
							},
							{
								displayName: 'City',
								name: 'city',
								description: 'City, district, suburb, town, or village',
								type: 'string',
								default: '',
							},
							{
								displayName: 'State',
								name: 'state',
								description: 'State, county, province, or region',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Country',
								name: 'country',
								description:
									'Two-letter country code (ISO 3166-1 alpha-2)',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Postal Code',
								name: 'postal_code',
								description: 'ZIP or postal code',
								type: 'string',
								default: '',
							},
						],
					},
				],
			},
			{
				displayName: 'Description',
				name: 'description',
				type: 'string',
				default: '',
				description: 'Arbitrary text to describe the customer to create',
			},
			{
				displayName: 'Email',
				name: 'email',
				type: 'string',
				placeholder: 'name@email.com',
				default: '',
				description: 'Email of the customer to create',
			},
			{
				displayName: 'Metadata',
				name: 'metadata',
				type: 'fixedCollection',
				default: {},
				placeholder: 'Add Metadata Item',
				description: 'Set of key-value pairs to attach to the customer to create',
				typeOptions: {
					multipleValues: true,
				},
				options: [
					{
						displayName: 'Metadata Properties',
						name: 'metadataProperties',
						values: [
							{
								displayName: 'Key',
								name: 'key',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Value',
								name: 'value',
								type: 'string',
								default: '',
							},
						],
					},
				],
			},
			{
				displayName: 'Name',
				name: 'name',
				type: 'string',
				default: '',
				description: 'Full name or business name of the customer to create',
			},
			{
				displayName: 'Phone',
				name: 'phone',
				type: 'string',
				default: '',
				description: 'Telephone number of this customer',
			},
			{
				displayName: 'Shipping',
				name: 'shipping',
				type: 'fixedCollection',
				description: 'Shipping information for the customer',
				placeholder: 'Add Field',
				typeOptions: {
					multipleValues: true,
				},
				default: {},
				options: [
					{
						displayName: 'Shipping Properties',
						name: 'shippingProperties',
						values: [
							{
								displayName: 'Recipient Name',
								name: 'name',
								type: 'string',
								default: '',
								description: 'Name of the person who will receive the shipment',
							},
							{
								displayName: 'Recipient Address',
								name: 'address',
								type: 'fixedCollection',
								default: {},
								placeholder: 'Add Address Details',
								options: [
									{
										displayName: 'Details',
										name: 'details',
										values: [
											{
												displayName: 'Line 1',
												name: 'line1',
												description: 'Address line 1 (e.g. street, PO Box, or company name)',
												type: 'string',
												default: '',
											},
											{
												displayName: 'Line 2',
												name: 'line2',
												description: 'Address line 2 (e.g. apartment, suite, unit, or building)',
												type: 'string',
												default: '',
											},
											{
												displayName: 'City',
												name: 'city',
												description: 'City, district, suburb, town, or village',
												type: 'string',
												default: '',
											},
											{
												displayName: 'State',
												name: 'state',
												description: 'State, county, province, or region',
												type: 'string',
												default: '',
											},
											{
												displayName: 'Country',
												name: 'country',
												description:
													'Two-letter country code (ISO 3166-1 alpha-2)',
												type: 'string',
												default: '',
											},
											{
												displayName: 'Postal Code',
												name: 'postal_code',
												description: 'ZIP or postal code',
												type: 'string',
												default: '',
											},
										],
									},
								],
							},
							{
								displayName: 'Recipient Phone',
								name: 'phone',
								type: 'string',
								default: '',
								description: 'Phone number of the person who will receive the shipment',
							},
						],
					},
				],
			},
		],
	},
];


import { computed, reactive } from 'vue';
import { defineStore } from 'pinia';
import type { UsageState } from '@/Interface';
import { activateLicenseKey, getLicense, renewLicense } from '@/api/usage';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useUsersStore } from '@/stores/users.store';

export type UsageTelemetry = {
	instance_id: string;
	action: 'view_plans' | 'manage_plan' | 'add_activation_key' | 'desktop_view_plans';
	plan_name_current: string;
	usage: number;
	quota: number;
};

const DEFAULT_PLAN_NAME = 'Community';
const DEFAULT_STATE: UsageState = {
	loading: true,
	data: {
		usage: {
			executions: {
				limit: -1,
				value: 0,
				warningThreshold: 0.8,
			},
		},
		license: {
			planId: '',
			planName: DEFAULT_PLAN_NAME,
		},
	},
};

export const useUsageStore = defineStore('usage', () => {
	const rootStore = useRootStore();
	const settingsStore = useSettingsStore();
	const usersStore = useUsersStore();

	const state = reactive<UsageState>(DEFAULT_STATE);

	const planName = computed(() => state.data.license.planName || DEFAULT_PLAN_NAME);
	const planId = computed(() => state.data.license.planId);
	const executionLimit = computed(() => state.data.usage.executions.limit);
	const executionCount = computed(() => state.data.usage.executions.value);
	const executionPercentage = computed(() => (executionCount.value / executionLimit.value) * 100);
	const instanceId = computed(() => settingsStore.settings.instanceId);
	const managementToken = computed(() => state.data.managementToken);
	const appVersion = computed(() => settingsStore.settings.versionCli);
	const commonSubscriptionAppUrlQueryParams = computed(
		() => `instanceid=${instanceId.value}&version=${appVersion.value}`,
	);
	const subscriptionAppUrl = computed(() =>
		settingsStore.settings.license.environment === 'production'
			? 'https://subscription.n8n.io'
			: 'https://staging-subscription.n8n.io',
	);

	const setLoading = (loading: boolean) => {
		state.loading = loading;
	};

	const setData = (data: UsageState['data']) => {
		state.data = data;
	};

	const getLicenseInfo = async () => {
		const data = await getLicense(rootStore.getRestApiContext);
		setData(data);
	};

	const activateLicense = async (activationKey: string) => {
		const data = await activateLicenseKey(rootStore.getRestApiContext, { activationKey });
		setData(data);
		await settingsStore.getSettings();
	};

	const refreshLicenseManagementToken = async () => {
		try {
			const data = await renewLicense(rootStore.getRestApiContext);
			setData(data);
		} catch (error) {
			await getLicenseInfo();
		}
	};

	return {
		setLoading,
		getLicenseInfo,
		setData,
		activateLicense,
		refreshLicenseManagementToken,
		planName,
		planId,
		executionLimit,
		executionCount,
		executionPercentage,
		instanceId,
		managementToken,
		appVersion,
		isCloseToLimit: computed(() =>
			state.data.usage.executions.limit < 0
				? false
				: executionCount.value / executionLimit.value >=
				  state.data.usage.executions.warningThreshold,
		),
		viewPlansUrl: computed(
			() => `${subscriptionAppUrl.value}?${commonSubscriptionAppUrlQueryParams.value}`,
		),
		managePlanUrl: computed(
			() =>
				`${subscriptionAppUrl.value}/manage?token=${managementToken.value}&${commonSubscriptionAppUrlQueryParams.value}`,
		),
		canUserActivateLicense: computed(() => usersStore.canUserActivateLicense),
		isLoading: computed(() => state.loading),
		telemetryPayload: computed<UsageTelemetry>(() => ({
			instance_id: instanceId.value,
			action: 'view_plans',
			plan_name_current: planName.value,
			usage: executionCount.value,
			quota: executionLimit.value,
		})),
		isDesktop: computed(() => settingsStore.isDesktopDeployment),
	};
});


import type { INodeProperties } from 'n8n-workflow';

export const checklistItemOperations: INodeProperties[] = [
	// ----------------------------------
	//         checklistItem
	// ----------------------------------
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		noDataExpression: true,
		displayOptions: {
			show: {
				resource: ['checklistItem'],
			},
		},
		options: [
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete a checklist item',
				action: 'Delete a checklist item',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get a checklist item',
				action: 'Get a checklist item',
			},
			{
				name: 'Update',
				value: 'update',
				description: 'Update a checklist item',
				action: 'Update a checklist item',
			},
		],
		default: 'getAll',
	},
];

export const checklistItemFields: INodeProperties[] = [
	// ----------------------------------
	//         checklistItem:delete
	// ----------------------------------
	{
		displayName: 'Board Name or ID',
		name: 'boardId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getBoards',
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['delete'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the board that card belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'List Name or ID',
		name: 'listId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getLists',
			loadOptionsDependsOn: ['boardId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['delete'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the list that card belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'Card Name or ID',
		name: 'cardId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getCards',
			loadOptionsDependsOn: ['boardId', 'listId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['delete'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the card that checklistItem belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'Checklist Name or ID',
		name: 'checklistId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getChecklists',
			loadOptionsDependsOn: ['boardId', 'cardId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['delete'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the checklistItem that card belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'Checklist Item Name or ID',
		name: 'checklistItemId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getChecklistItems',
			loadOptionsDependsOn: ['boardId', 'cardId', 'checklistId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['delete'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the checklistItem item to get. Choose from the list, or specify an ID using an expression.',
	},

	// ----------------------------------
	//         checklistItem:get
	// ----------------------------------
	{
		displayName: 'Board Name or ID',
		name: 'boardId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getBoards',
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['get'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the board that card belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'List Name or ID',
		name: 'listId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getLists',
			loadOptionsDependsOn: ['boardId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['get'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the list that card belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'Card Name or ID',
		name: 'cardId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getCards',
			loadOptionsDependsOn: ['boardId', 'listId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['get'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the card that checklistItem belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'Checklist ID',
		name: 'checklistId',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['get'],
				resource: ['checklistItem'],
			},
		},
		description: 'The ID of the checklistItem that card belongs to',
	},
	{
		displayName: 'Checklist Item Name or ID',
		name: 'checklistItemId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getChecklistItems',
			loadOptionsDependsOn: ['boardId', 'cardId', 'checklistId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['get'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the checklistItem item to get. Choose from the list, or specify an ID using an expression.',
	},

	// ----------------------------------
	//         checklistItem:update
	// ----------------------------------
	{
		displayName: 'Board Name or ID',
		name: 'boardId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getBoards',
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['update'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the board that card belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'List Name or ID',
		name: 'listId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getLists',
			loadOptionsDependsOn: ['boardId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['update'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the list that card belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'Card Name or ID',
		name: 'cardId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getCards',
			loadOptionsDependsOn: ['boardId', 'listId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['update'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the card that checklistItem belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'CheckList Name or ID',
		name: 'checklistId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getChecklists',
			loadOptionsDependsOn: ['boardId', 'cardId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['update'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the checklistItem that card belongs to. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'Checklist Item Name or ID',
		name: 'checklistItemId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getChecklistItems',
			loadOptionsDependsOn: ['boardId', 'cardId', 'checklistId'],
		},
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: ['update'],
				resource: ['checklistItem'],
			},
		},
		description:
			'The ID of the checklistItem item to update. Choose from the list, or specify an ID using an expression.',
	},
	{
		displayName: 'Update Fields',
		name: 'updateFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: ['update'],
				resource: ['checklistItem'],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Title',
				name: 'title',
				type: 'string',
				default: '',
				description: 'The new title for the checklistItem item',
			},
			{
				displayName: 'Finished',
				name: 'isFinished',
				type: 'boolean',
				default: false,
				description: 'Whether the item is checked',
			},
		],
	},
];


uri: uri || `${credentials.url}${path}`,

if (Object.keys(body as IDataObject).length === 0) {

rejectUnauthorized: credentials.ignoreSSLIssues as boolean,


Hey founders! Your business doesn't need you to operate

7 no-code workflow automations for Amazon Web Services


import type {
	IExecuteFunctions,
	IDataObject,
	ILoadOptionsFunctions,
	INodeExecutionData,
	INodePropertyOptions,
	INodeType,
	INodeTypeDescription,
} from 'n8n-workflow';

import { deepLApiRequest } from './GenericFunctions';

import { textOperations } from './TextDescription';

export class DeepL implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'DeepL',
		name: 'deepL',
		icon: 'file:deepl.svg',
		group: ['input', 'output'],
		version: 1,
		description: 'Translate data using DeepL',
		subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
		defaults: {
			name: 'DeepL',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'deepLApi',
				required: true,
			},
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				noDataExpression: true,
				options: [
					{
						name: 'Language',
						value: 'language',
					},
				],
				default: 'language',
			},
			{
				displayName: 'Operation',
				name: 'operation',
				type: 'options',
				noDataExpression: true,
				displayOptions: {
					show: {
						resource: ['language'],
					},
				},
				options: [
					{
						name: 'Translate',
						value: 'translate',
						description: 'Translate data',
						action: 'Translate a language',
					},
				],
				default: 'translate',
			},
			...textOperations,
		],
	};

	methods = {
		loadOptions: {
			async getLanguages(this: ILoadOptionsFunctions) {
				const returnData: INodePropertyOptions[] = [];
				const languages = await deepLApiRequest.call(
					this,
					'GET',
					'/languages',
					{},
					{ type: 'target' },
				);
				for (const language of languages) {
					returnData.push({
						name: language.name,
						value: language.language,
					});
				}

				returnData.sort((a, b) => {
					if (a.name < b.name) {
						return -1;
					}
					if (a.name > b.name) {
						return 1;
					}
					return 0;
				});

				return returnData;
			},
		},
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();
		const length = items.length;

		const responseData: INodeExecutionData[] = [];

		for (let i = 0; i < length; i++) {
			try {
				const resource = this.getNodeParameter('resource', i);
				const operation = this.getNodeParameter('operation', i);
				const additionalFields = this.getNodeParameter('additionalFields', i);
				if (resource === 'language') {
					if (operation === 'translate') {
						let body: IDataObject = {};
						const text = this.getNodeParameter('text', i) as string;
						const translateTo = this.getNodeParameter('translateTo', i) as string;
						body = { target_lang: translateTo, text } as IDataObject;

						if (additionalFields.sourceLang !== undefined) {
							body.source_lang = ['EN-GB', 'EN-US'].includes(additionalFields.sourceLang as string)
								? 'EN'
								: additionalFields.sourceLang;
						}

						const { translations } = await deepLApiRequest.call(this, 'GET', '/translate', body);
						const [translation] = translations;
						const translationJsonArray = this.helpers.returnJsonArray(translation as IDataObject[]);
						const executionData = this.helpers.constructExecutionMetaData(translationJsonArray, {
							itemData: { item: i },
						});
						responseData.push(...executionData);
					}
				}
			} catch (error) {
				if (this.continueOnFail()) {
					const executionErrorData = {
						json: {} as IDataObject,
						error: error.message,
						itemIndex: i,
					};
					responseData.push(executionErrorData as INodeExecutionData);
					continue;
				}
				throw error;
			}
		}

		return [responseData];
	}
}


<Box mi='neg-x24' pi='x24' flexShrink={1} flexGrow={1} ref={ref} overflow='hidden'>

keep keep keep keep replace keep keep keep keep keep

import { Scrollbars, ScrollValues } from 'rc-scrollbars';
import React, {
	MutableRefObject,
	CSSProperties,
	useMemo,
	memo,
	forwardRef,
	ReactNode,
	ReactElement,
} from 'react';

const styleDefault = {
	width: '100%',
	height: '100%',
	flexGrow: 1,
	willChange: 'transform',
	overflowY: 'hidden',
};

export type CustomScrollbarsProps = {
	style?: CSSProperties;
	children?: ReactNode;
	onScroll?: (values: ScrollValues) => void;
	renderView?: typeof Scrollbars.defaultProps.renderView;
	renderTrackHorizontal?: typeof Scrollbars.defaultProps.renderTrackHorizontal;
};

const ScrollableContentWrapper = forwardRef<HTMLElement, CustomScrollbarsProps>(
	function WrappedComponent({ children, style, onScroll, renderView }, ref) {
		const scrollbarsStyle = useMemo(() => ({ ...style, ...styleDefault }), [
			style,
		]) as CSSProperties;

		return (
			<Scrollbars
				autoHide
				autoHideTimeout={2000}
				autoHideDuration={500}
				style={scrollbarsStyle}
				onScrollFrame={onScroll}
				renderView={renderView}
				renderTrackHorizontal={(props): ReactElement => (
					<div {...props} className='track-horizontal' style={{ display: 'none' }} />
				)}
				renderThumbVertical={({ style, ...props }): JSX.Element => (
					<div
						{...props}
						style={{ ...style, backgroundColor: 'rgba(0, 0, 0, 0.5)', borderRadius: '7px' }}
					/>
				)}
				children={children}
				ref={(sRef): void => {
					if (ref && sRef) {
						if (typeof ref === 'function') {
							ref(sRef.view ?? null);
							return;
						}

						(ref as MutableRefObject<HTMLElement | undefined>).current = sRef.view;
					}
				}}
			/>
		);
	},
);

export default memo(ScrollableContentWrapper);


/* eslint no-use-before-define:0 */
import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import { Mongo } from 'meteor/mongo';
import { Log } from 'meteor/logging';
import _ from 'underscore';
import s from 'underscore.string';
import moment from 'moment';

import { Info } from '../../utils';
/*
	Adds migration capabilities. Migrations are defined like:

	Migrations.add({
		up: function() {}, //*required* code to run to migrate upwards
		version: 1, //*required* number to identify migration order
		down: function() {}, //*optional* code to run to migrate downwards
		name: 'Something' //*optional* display name for the migration
	});

	The ordering of migrations is determined by the version you set.

	To run the migrations, set the MIGRATION_VERSION environment variable to either
	'latest' or the version number you want to migrate to. Optionally, append
	',exit' if you want the migrations to exit the meteor process, e.g if you're
	migrating from a script (remember to pass the --once parameter).

	e.g:
	MIGRATION_VERSION="latest" mrt # ensure we'll be at the latest version and run the app
	MIGRATION_VERSION="latest,exit" mrt --once # ensure we'll be at the latest version and exit
	MIGRATION_VERSION="2,exit" mrt --once # migrate to version 2 and exit
	MIGRATION_VERSION="2,rerun,exit" mrt --once # rerun migration script for version 2 and exit

	Note: Migrations will lock ensuring only 1 app can be migrating at once. If
	a migration crashes, the control record in the migrations collection will
	remain locked and at the version it was at previously, however the db could
	be in an inconsistant state.
*/

// since we'll be at version 0 by default, we should have a migration set for it.
const DefaultMigration = {
	version: 0,
	up() {
		// @TODO: check if collection "migrations" exist
		// If exists, rename and rerun _migrateTo
	},
};

export const Migrations = {
	_list: [DefaultMigration],
	options: {
		// false disables logging
		log: true,
		// null or a function
		logger: null,
		// enable/disable info log "already at latest."
		logIfLatest: true,
		// lock will be valid for this amount of minutes
		lockExpiration: 5,
		// retry interval in seconds
		retryInterval: 10,
		// max number of attempts to retry unlock
		maxAttempts: 30,
		// migrations collection name
		collectionName: 'migrations',
		// collectionName: "rocketchat_migrations"
	},
	config(opts) {
		this.options = _.extend({}, this.options, opts);
	},
};

Migrations._collection = new Mongo.Collection(Migrations.options.collectionName);

/* Create a box around messages for displaying on a console.log */
function makeABox(message, color = 'red') {
	if (!_.isArray(message)) {
		message = message.split('\n');
	}
	const len = _(message).reduce(function(memo, msg) {
		return Math.max(memo, msg.length);
	}, 0) + 4;
	const text = message.map((msg) => '|'[color] + s.lrpad(msg, len)[color] + '|'[color]).join('\n');
	const topLine = '+'[color] + s.pad('', len, '-')[color] + '+'[color];
	const separator = '|'[color] + s.pad('', len, '') + '|'[color];
	const bottomLine = '+'[color] + s.pad('', len, '-')[color] + '+'[color];
	return `\n${ topLine }\n${ separator }\n${ text }\n${ separator }\n${ bottomLine }\n`;
}

/*
	Logger factory function. Takes a prefix string and options object
	and uses an injected `logger` if provided, else falls back to
	Meteor's `Log` package.
	Will send a log object to the injected logger, on the following form:
		message: String
		level: String (info, warn, error, debug)
		tag: 'Migrations'
*/
function createLogger(prefix) {
	check(prefix, String);

	// Return noop if logging is disabled.
	if (Migrations.options.log === false) {
		return function() {};
	}

	return function(level, message) {
		check(level, Match.OneOf('info', 'error', 'warn', 'debug'));
		check(message, Match.OneOf(String, [String]));

		const logger = Migrations.options && Migrations.options.logger;

		if (logger && _.isFunction(logger)) {
			logger({
				level,
				message,
				tag: prefix,
			});
		} else {
			Log[level]({
				message: `${ prefix }: ${ message }`,
			});
		}
	};
}

// collection holding the control record

const log = createLogger('Migrations');

['info', 'warn', 'error', 'debug'].forEach(function(level) {
	log[level] = _.partial(log, level);
});

// if (process.env.MIGRATE)
//   Migrations.migrateTo(process.env.MIGRATE);

// Add a new migration:
// {up: function *required
//  version: Number *required
//  down: function *optional
//  name: String *optional
// }
Migrations.add = function(migration) {
	if (typeof migration.up !== 'function') { throw new Meteor.Error('Migration must supply an up function.'); }

	if (typeof migration.version !== 'number') { throw new Meteor.Error('Migration must supply a version number.'); }

	if (migration.version <= 0) { throw new Meteor.Error('Migration version must be greater than 0'); }

	// Freeze the migration object to make it hereafter immutable
	Object.freeze(migration);

	this._list.push(migration);
	this._list = _.sortBy(this._list, function(m) {
		return m.version;
	});
};

// Attempts to run the migrations using command in the form of:
// e.g 'latest', 'latest,exit', 2
// use 'XX,rerun' to re-run the migration at that version
Migrations.migrateTo = function(command) {
	if (_.isUndefined(command) || command === '' || this._list.length === 0) { throw new Error(`Cannot migrate using invalid command: ${ command }`); }

	let version;
	let subcommands;
	if (typeof command === 'number') {
		version = command;
	} else {
		version = command.split(',')[0];
		subcommands = command.split(',').slice(1);
	}

	const { maxAttempts, retryInterval } = Migrations.options;
	let migrated;
	for (let attempts = 1; attempts <= maxAttempts; attempts++) {
		if (version === 'latest') {
			migrated = this._migrateTo(_.last(this._list).version);
		} else {
			migrated = this._migrateTo(parseInt(version), subcommands.includes('rerun'));
		}
		if (migrated) {
			break;
		} else {
			let willRetry;
			if (attempts < maxAttempts) {
				willRetry = ` Trying again in ${ retryInterval } seconds.`;
				Meteor._sleepForMs(retryInterval * 1000);
			} else {
				willRetry = '';
			}
			console.log(`Not migrating, control is locked. Attempt ${ attempts }/${ maxAttempts }.${ willRetry }`.yellow);
		}
	}
	if (!migrated) {
		const control = this._getControl(); // Side effect: upserts control document.
		console.log(makeABox([
			'ERROR! SERVER STOPPED',
			'',
			'Your database migration control is locked.',
			'Please make sure you are running the latest version and try again.',
			'If the problem persists, please contact support.',
			'',
			`This Rocket.Chat version: ${ Info.version }`,
			`Database locked at version: ${ control.version }`,
			`Database target version: ${ version === 'latest' ? _.last(this._list).version : version }`,
			'',
			`Commit: ${ Info.commit.hash }`,
			`Date: ${ Info.commit.date }`,
			`Branch: ${ Info.commit.branch }`,
			`Tag: ${ Info.commit.tag }`,
		]));
		process.exit(1);
	}

	// remember to run meteor with --once otherwise it will restart
	if (subcommands.includes('exit')) { process.exit(0); }
};

// just returns the current version
Migrations.getVersion = function() {
	return this._getControl().version;
};

// migrates to the specific version passed in
Migrations._migrateTo = function(version, rerun) {
	const self = this;
	const control = this._getControl(); // Side effect: upserts control document.
	let currentVersion = control.version;

	if (lock() === false) {
		// log.info('Not migrating, control is locked.');
		// Warning
		return false;
	}

	if (rerun) {
		log.info(`Rerunning version ${ version }`);
		migrate('up', this._findIndexByVersion(version));
		log.info('Finished migrating.');
		unlock();
		return true;
	}

	if (currentVersion === version) {
		if (this.options.logIfLatest) {
			log.info(`Not migrating, already at version ${ version }`);
		}
		unlock();
		return true;
	}

	const startIdx = this._findIndexByVersion(currentVersion);
	const endIdx = this._findIndexByVersion(version);

	// log.info('startIdx:' + startIdx + ' endIdx:' + endIdx);
	log.info(`Migrating from version ${ this._list[startIdx].version } -> ${ this._list[endIdx].version }`);

	// run the actual migration
	function migrate(direction, idx) {
		const migration = self._list[idx];

		if (typeof migration[direction] !== 'function') {
			unlock();
			throw new Meteor.Error(`Cannot migrate ${ direction } on version ${ migration.version }`);
		}

		function maybeName() {
			return migration.name ? ` (${ migration.name })` : '';
		}

		log.info(`Running ${ direction }() on version ${ migration.version }${ maybeName() }`);

		try {
			migration[direction](migration);
		} catch (e) {
			console.log(makeABox([
				'ERROR! SERVER STOPPED',
				'',
				'Your database migration failed:',
				e.message,
				'',
				'Please make sure you are running the latest version and try again.',
				'If the problem persists, please contact support.',
				'',
				`This Rocket.Chat version: ${ Info.version }`,
				`Database locked at version: ${ control.version }`,
				`Database target version: ${ version }`,
				'',
				`Commit: ${ Info.commit.hash }`,
				`Date: ${ Info.commit.date }`,
				`Branch: ${ Info.commit.branch }`,
				`Tag: ${ Info.commit.tag }`,
			]));
			console.log(e.stack);
			process.exit(1);
		}
	}

	// Returns true if lock was acquired.
	function lock() {
		const date = new Date();
		const dateMinusInterval = moment(date).subtract(self.options.lockExpiration, 'minutes').toDate();
		const build = Info ? Info.build.date : date;

		// This is atomic. The selector ensures only one caller at a time will see
		// the unlocked control, and locking occurs in the same update's modifier.
		// All other simultaneous callers will get false back from the update.
		return self._collection.update({
			_id: 'control',
			$or: [{
				locked: false,
			}, {
				lockedAt: {
					$lt: dateMinusInterval,
				},
			}, {
				buildAt: {
					$ne: build,
				},
			}],
		}, {
			$set: {
				locked: true,
				lockedAt: date,
				buildAt: build,
			},
		}) === 1;
	}

	// Side effect: saves version.
	function unlock() {
		self._setControl({
			locked: false,
			version: currentVersion,
		});
	}

	if (currentVersion < version) {
		for (let i = startIdx; i < endIdx; i++) {
			migrate('up', i + 1);
			currentVersion = self._list[i + 1].version;
			self._setControl({
				locked: true,
				version: currentVersion,
			});
		}
	} else {
		for (let i = startIdx; i > endIdx; i--) {
			migrate('down', i);
			currentVersion = self._list[i - 1].version;
			self._setControl({
				locked: true,
				version: currentVersion,
			});
		}
	}

	unlock();
	log.info('Finished migrating.');
};

// gets the current control record, optionally creating it if non-existant
Migrations._getControl = function() {
	const control = this._collection.findOne({
		_id: 'control',
	});

	return control || this._setControl({
		version: 0,
		locked: false,
	});
};

// sets the control record
Migrations._setControl = function(control) {
	// be quite strict
	check(control.version, Number);
	check(control.locked, Boolean);

	this._collection.update({
		_id: 'control',
	}, {
		$set: {
			version: control.version,
			locked: control.locked,
		},
	}, {
		upsert: true,
	});

	return control;
};

// returns the migration index in _list or throws if not found
Migrations._findIndexByVersion = function(version) {
	for (let i = 0; i < this._list.length; i++) {
		if (this._list[i].version === version) { return i; }
	}

	throw new Meteor.Error(`Can't find migration version ${ version }`);
};

// reset (mainly intended for tests)
Migrations._reset = function() {
	this._list = [{
		version: 0,
		up() {},
	}];
	this._collection.remove({});
};


import { expect } from 'chai';

import { getCredentials, api, request, credentials } from '../../../data/api-data.js';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';

describe('LIVECHAT - office hours', function() {
	this.retries(0);

	before((done) => getCredentials(done));

	before((done) => {
		updateSetting('Livechat_enabled', true).then(done);
	});

	describe('livechat/office-hours', () => {
		it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => {
			updatePermission('view-livechat-business-hours', []).then(() => {
				request.get(api('livechat/office-hours'))
					.set(credentials)
					.expect('Content-Type', 'application/json')
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body.error).to.be.equal('error-not-authorized');
					})
					.end(done);
			});
		});
		it('should return an array of office hours', (done) => {
			updatePermission('view-livechat-business-hours', ['admin']).then(() => {
				request.get(api('livechat/office-hours'))
					.set(credentials)
					.expect('Content-Type', 'application/json')
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success', true);
						expect(res.body).to.have.property('officeHours');
						expect(res.body.officeHours).to.be.an('array');
					})
					.end(done);
			});
		});
	});
});


<template name="cmsPage">

					<button class="rc-button rc-button--nude"></button>

					{{{page}}}

</template>


onScroll?: (values: ScrollValues) => void;


import './methods/saveNotificationSettings';
import PushNotification from './lib/PushNotification';

export {
	PushNotification,
};


Copyright (c) 2015-2020 Rocket.Chat Technologies Corp.

Portions of this software are licensed as follows:

* All content that resides under the "ee/" directory of this repository, if
  that directory exists, is licensed under the license defined in "ee/LICENSE".
* All third-party components incorporated into the Rocket.Chat Software are
  licensed under the original license provided by the owner of the applicable
  component.
* Content outside of the above-mentioned directories or restrictions above is
  available under the MIT (Expat) license as defined below.

The MIT License (MIT)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


const ScrollableContentWrapper = forwardRef<HTMLElement, CustomScrollbarsProps>(

function WrappedComponent({ children, style, onScroll, renderView }, ref) {

const scrollbarsStyle = useMemo(() => ({ ...style, ...styleDefault }), [

function WrappedComponent({ children, style, onScroll, overflowX, renderView }, ref) {


import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { ServiceConfiguration } from 'meteor/service-configuration';

import { settings } from '../../settings';
import { CustomOAuth } from '../../custom-oauth';
import { callbacks } from '../../callbacks';

const config = {
	serverURL: '',
	authorizePath: '/m/oauth2/auth/',
	tokenPath: '/m/oauth2/token/',
	identityPath: '/m/oauth2/api/me/',
	scope: 'basic',
	addAutopublishFields: {
		forLoggedInUser: ['services.dolphin'],
		forOtherUsers: ['services.dolphin.name'],
	},
	accessTokenParam: 'access_token',
};

const Dolphin = new CustomOAuth('dolphin', config);

function DolphinOnCreateUser(options, user) {
	if (user && user.services && user.services.dolphin && user.services.dolphin.NickName) {
		user.username = user.services.dolphin.NickName;
	}
	return user;
}

if (Meteor.isServer) {
	Meteor.startup(() =>
		settings.get('Accounts_OAuth_Dolphin_URL', (key, value) => {
			config.serverURL = value;
			return Dolphin.configure(config);
		}),
	);

	if (settings.get('Accounts_OAuth_Dolphin_URL')) {
		const data = {
			buttonLabelText: settings.get('Accounts_OAuth_Dolphin_button_label_text'),
			buttonColor: settings.get('Accounts_OAuth_Dolphin_button_color'),
			buttonLabelColor: settings.get('Accounts_OAuth_Dolphin_button_label_color'),
			clientId: settings.get('Accounts_OAuth_Dolphin_id'),
			secret: settings.get('Accounts_OAuth_Dolphin_secret'),
			serverURL: settings.get('Accounts_OAuth_Dolphin_URL'),
			loginStyle: settings.get('Accounts_OAuth_Dolphin_login_style'),
		};

		ServiceConfiguration.configurations.upsert({ service: 'dolphin' }, { $set: data });
	}

	callbacks.add('beforeCreateUser', DolphinOnCreateUser, callbacks.priority.HIGH, 'dolphin');
} else {
	Meteor.startup(() =>
		Tracker.autorun(function() {
			if (settings.get('Accounts_OAuth_Dolphin_URL')) {
				config.serverURL = settings.get('Accounts_OAuth_Dolphin_URL');
				return Dolphin.configure(config);
			}
		}),
	);
}


import { BaseRaw } from '../../../../../app/models/server/raw/BaseRaw';
import LivechatPriority from '../models/LivechatPriority';

export class LivechatPriorityRaw extends BaseRaw {
	findOneByIdOrName(_idOrName, options) {
		const query = {
			$or: [{
				_id: _idOrName,
			}, {
				name: _idOrName,
			}],
		};

		return this.findOne(query, options);
	}
}

export default new LivechatPriorityRaw(LivechatPriority.model.rawCollection());


import { hasAllPermission } from '../../authorization';
import { registerAdminSidebarItem } from '../../../client/views/admin';

registerAdminSidebarItem({
	href: 'admin-mailer',
	i18nLabel: 'Mailer',
	icon: 'mail',
	permissionGranted: () => hasAllPermission('access-mailer'),
});


renderTrackHorizontal={(props): ReactElement => (

<div {...props} className='track-horizontal' style={{ display: 'none' }} />

renderThumbVertical={({ style, ...props }): JSX.Element => (

keep keep keep keep keep replace replace replace keep keep keep


import { HTTP } from 'meteor/http';
import { Meteor } from 'meteor/meteor';

import { settings } from '../../../app/settings/server';
import { getWorkspaceAccessToken } from '../../../app/cloud/server';
import { INpsVote } from '../../../definition/INps';

type NPSResultPayload = {
	total: number;
	votes: INpsVote[];
}

export const sendNpsResults = Meteor.bindEnvironment(function sendNpsResults(npsId: string, data: NPSResultPayload) {
	const token: string = getWorkspaceAccessToken();
	if (!token) {
		return false;
	}

	const npsUrl = settings.get('Nps_Url');

	try {
		return HTTP.post(`${ npsUrl }/v1/surveys/${ npsId }/results`, {
			headers: {
				Authorization: `Bearer ${ token }`,
			},
			data,
		});
	} catch (e) {
		console.error(e);
		return false;
	}
});


import React, { memo, useCallback, useEffect } from 'react';
import { useSubscription } from 'use-subscription';

import { menu, SideNav, Layout } from '../../../app/ui-utils/client';
import Sidebar from '../../components/Sidebar';
import { useRoutePath, useCurrentRoute } from '../../contexts/RouterContext';
import { useTranslation } from '../../contexts/TranslationContext';
import SettingsProvider from '../../providers/SettingsProvider';
import { itemsSubscription } from './sidebarItems';

const AccountSidebar = () => {
	const t = useTranslation();

	const items = useSubscription(itemsSubscription);

	const closeFlex = useCallback(() => {
		if (Layout.isEmbedded()) {
			menu.close();
			return;
		}

		SideNav.closeFlex();
	}, []);

	const [currentRouteName, ...rest] = useCurrentRoute();
	const currentPath = useRoutePath(currentRouteName, ...rest);

	useEffect(() => {
		if (currentRouteName !== 'account') {
			SideNav.closeFlex();
		}
	}, [currentRouteName]);

	// TODO: uplift this provider
	return (
		<SettingsProvider privileged>
			<Sidebar>
				<Sidebar.Header onClose={closeFlex} title={t('Account')} />
				<Sidebar.Content>
					<Sidebar.ItemsAssembler items={items} currentPath={currentPath} />
				</Sidebar.Content>
			</Sidebar>
		</SettingsProvider>
	);
};

export default memo(AccountSidebar);


import React from 'react';

import { useUserId } from '../../contexts/UserContext';
import RoomMembers from './contextualBar/RoomMembers';
import UserInfo from './contextualBar/UserInfo';
import { useRoom } from './providers/RoomProvider';
import { useTab, useTabBarClose, useTabContext } from './providers/ToolboxProvider';

const getUid = (room, ownUserId) => {
	if (room.uids.length === 1) {
		return room.uids[0];
	}

	const uid = room.uids.filter((uid) => uid !== ownUserId).shift();

	// Self DMs used to be created with the userId duplicated.
	// Sometimes rooms can have 2 equal uids, but it's a self DM.
	return uid ?? room.uids[0];
};

const MemberListRouter = ({ rid }) => {
	const username = useTabContext();
	const room = useRoom();
	const onClickClose = useTabBarClose();
	const ownUserId = useUserId();
	const tab = useTab();

	const isMembersList = tab.id === 'members-list' || tab.id === 'user-info-group';

	if (isMembersList && !username) {
		return <RoomMembers rid={rid} />;
	}

	return (
		<UserInfo
			width='100%'
			{...(username ? { username } : { uid: getUid(room, ownUserId) })}
			onClose={onClickClose}
			rid={rid}
		/>
	);
};

export default MemberListRouter;


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`LsCommand in a Yarn workspace should use package.json/workspaces setting 1`] = `
"package-1 v1.0.0
package-2 v1.0.0 "
`;

exports[`LsCommand in a basic repo does not list changes for ignored packages 1`] = `"package-1 v1.0.0 "`;

exports[`LsCommand in a basic repo lists changes for a given scope 1`] = `"package-1 v1.0.0 "`;

exports[`LsCommand in a basic repo should list packages 1`] = `
"package-1 v1.0.0
package-2 v1.0.0
package-3 v1.0.0
package-4 v1.0.0
package-5 v1.0.0 (private)"
`;

exports[`LsCommand in a repo with packages outside of packages/ should list packages 1`] = `
"package-1 v1.0.0
package-2 v1.0.0
package-3 v1.0.0 "
`;

exports[`LsCommand with --include-filtered-dependencies should list packages, including filtered ones 1`] = `
"@test/package-2 v1.0.0
@test/package-1 v1.0.0 "
`;

exports[`LsCommand with --json should list packages as json objects 1`] = `
Array [
  Object {
    "name": "package-1",
    "private": false,
    "version": "1.0.0",
  },
  Object {
    "name": "package-2",
    "private": false,
    "version": "1.0.0",
  },
  Object {
    "name": "package-3",
    "private": false,
    "version": "1.0.0",
  },
  Object {
    "name": "package-4",
    "private": false,
    "version": "1.0.0",
  },
  Object {
    "name": "package-5",
    "private": true,
    "version": "1.0.0",
  },
]
`;

exports[`LsCommand with an undefined version should list packages 1`] = `"package-1 MISSING "`;


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`lerna publish exit 0 when no updates: stderr 1`] = `
lerna info version __TEST_VERSION__
lerna info current version 1.0.0
lerna info Checking for updated packages...
lerna info Comparing with v1.0.0.
lerna info Checking for prereleased packages...
lerna info No updated packages to publish.
`;

exports[`lerna publish fixed mode --conventional-commits --force-publish=* 1`] = `

Changes:
 - package-1: 1.0.0 => 1.0.1
 - package-2: 1.0.0 => 1.0.1

`;

exports[`lerna publish fixed mode --conventional-commits --force-publish=* 2`] = `

Changes:
 - package-1: 1.0.1 => 1.1.0
 - package-2: 1.0.1 => 1.1.0

`;

exports[`lerna publish fixed mode --conventional-commits --force-publish=* 3`] = `

Changes:
 - package-1: 1.1.0 => 1.2.0
 - package-2: 1.1.0 => 1.2.0

`;

exports[`lerna publish fixed mode --conventional-commits --force-publish=*: commit 1`] = `
v1.0.1

`;

exports[`lerna publish fixed mode --conventional-commits --force-publish=*: commit 2`] = `
v1.1.0

`;

exports[`lerna publish fixed mode --conventional-commits --force-publish=*: commit 3`] = `
v1.2.0

`;

exports[`lerna publish fixed mode --conventional-commits --force-publish=*: packages 1`] = `
Array [
  Object {
    name: package-1,
    version: 1.0.1,
  },
  Object {
    name: package-2,
    version: 1.0.1,
  },
]
`;

exports[`lerna publish fixed mode --conventional-commits --force-publish=*: packages 2`] = `
Array [
  Object {
    foo: true,
    name: package-1,
    version: 1.1.0,
  },
  Object {
    name: package-2,
    version: 1.1.0,
  },
]
`;

exports[`lerna publish fixed mode --conventional-commits --force-publish=*: packages 3`] = `
Array [
  Object {
    foo: true,
    name: package-1,
    version: 1.2.0,
  },
  Object {
    bar: true,
    name: package-2,
    version: 1.2.0,
  },
]
`;

exports[`lerna publish fixed mode --conventional-commits recommends versions for each publish 1`] = `

Changes:
 - package-1: 1.0.0 => 1.0.1
 - package-2: 1.0.0 => 1.0.1

`;

exports[`lerna publish fixed mode --conventional-commits recommends versions for each publish 2`] = `

Changes:
 - package-1: 1.0.1 => 1.1.0

`;

exports[`lerna publish fixed mode --conventional-commits recommends versions for each publish 3`] = `

Changes:
 - package-2: 1.1.0 => 1.2.0

`;

exports[`lerna publish fixed mode --conventional-commits recommends versions for each publish: commit 1`] = `
v1.0.1

`;

exports[`lerna publish fixed mode --conventional-commits recommends versions for each publish: commit 2`] = `
v1.1.0

`;

exports[`lerna publish fixed mode --conventional-commits recommends versions for each publish: commit 3`] = `
v1.2.0

`;

exports[`lerna publish fixed mode --conventional-commits recommends versions for each publish: packages 1`] = `
Array [
  Object {
    name: package-1,
    version: 1.0.1,
  },
  Object {
    name: package-2,
    version: 1.0.1,
  },
]
`;

exports[`lerna publish fixed mode --conventional-commits recommends versions for each publish: packages 2`] = `
Array [
  Object {
    foobar: true,
    name: package-1,
    version: 1.1.0,
  },
  Object {
    name: package-2,
    version: 1.0.1,
  },
]
`;

exports[`lerna publish fixed mode --conventional-commits recommends versions for each publish: packages 3`] = `
Array [
  Object {
    foobar: true,
    name: package-1,
    version: 1.1.0,
  },
  Object {
    baz: true,
    name: package-2,
    version: 1.2.0,
  },
]
`;

exports[`lerna publish updates fixed versions: commit 1`] = `
v1.0.1

`;

exports[`lerna publish updates fixed versions: packages 1`] = `
Array [
  Object {
    name: package-1,
    version: 1.0.1,
  },
  Object {
    dependencies: Object {
      package-1: ^1.0.1,
    },
    name: package-2,
    version: 1.0.1,
  },
  Object {
    devDependencies: Object {
      package-2: ^1.0.1,
    },
    name: package-3,
    peerDependencies: Object {
      package-2: ^1.0.0,
    },
    version: 1.0.1,
  },
  Object {
    dependencies: Object {
      package-1: ^0.0.0,
    },
    name: package-4,
    version: 1.0.1,
  },
  Object {
    dependencies: Object {
      package-1: ^1.0.1,
    },
    name: package-5,
    private: true,
    version: 1.0.1,
  },
]
`;

exports[`lerna publish updates fixed versions: stderr 1`] = `
lerna info version __TEST_VERSION__
lerna info current version 1.0.0
lerna info Checking for updated packages...
lerna info Comparing with initial commit.
lerna info Checking for prereleased packages...
lerna info auto-confirmed
`;

exports[`lerna publish updates fixed versions: stdout 1`] = `

Changes:
 - package-1: 1.0.0 => 1.0.1
 - package-2: 1.0.0 => 1.0.1
 - package-3: 1.0.0 => 1.0.1
 - package-4: 1.0.0 => 1.0.1
 - package-5: 1.0.0 => 1.0.1 (private)

`;

exports[`lerna publish updates independent versions: commit 1`] = `
Publish

 - package-1@2.0.0
 - package-2@3.0.0
 - package-3@4.0.0
 - package-4@5.0.0
 - package-5@6.0.0

`;

exports[`lerna publish updates independent versions: packages 1`] = `
Array [
  Object {
    name: package-1,
    version: 2.0.0,
  },
  Object {
    dependencies: Object {
      package-1: ^2.0.0,
    },
    name: package-2,
    version: 3.0.0,
  },
  Object {
    devDependencies: Object {
      package-2: ^3.0.0,
    },
    name: package-3,
    version: 4.0.0,
  },
  Object {
    dependencies: Object {
      package-1: ^0.0.0,
    },
    name: package-4,
    version: 5.0.0,
  },
  Object {
    dependencies: Object {
      package-3: ^4.0.0,
    },
    name: package-5,
    private: true,
    version: 6.0.0,
  },
]
`;

exports[`lerna publish updates independent versions: stderr 1`] = `
lerna info version __TEST_VERSION__
lerna info versioning independent
lerna info Checking for updated packages...
lerna info Comparing with initial commit.
lerna info Checking for prereleased packages...
lerna info auto-confirmed
`;

exports[`lerna publish updates independent versions: stdout 1`] = `

Changes:
 - package-1: 1.0.0 => 2.0.0
 - package-2: 2.0.0 => 3.0.0
 - package-3: 3.0.0 => 4.0.0
 - package-4: 4.0.0 => 5.0.0
 - package-5: 5.0.0 => 6.0.0 (private)

`;

exports[`lerna publish uses detault suffix with canary flag: stderr 1`] = `
lerna info version __TEST_VERSION__
lerna info canary enabled
lerna info current version 1.0.0
lerna info Checking for updated packages...
lerna info Comparing with initial commit.
lerna info Checking for prereleased packages...
lerna info auto-confirmed
`;

exports[`lerna publish uses detault suffix with canary flag: stdout 1`] = `

Changes:
 - package-1: 1.0.0 => 1.1.0-alpha.hash
 - package-2: 1.0.0 => 1.1.0-alpha.hash
 - package-3: 1.0.0 => 1.1.0-alpha.hash
 - package-4: 1.0.0 => 1.1.0-alpha.hash
 - package-5: 1.0.0 => 1.1.0-alpha.hash (private)

`;

exports[`lerna publish uses meta suffix from canary flag: stderr 1`] = `
lerna info version __TEST_VERSION__
lerna info canary enabled
lerna info current version 1.0.0
lerna info Checking for updated packages...
lerna info Comparing with initial commit.
lerna info Checking for prereleased packages...
lerna info auto-confirmed
`;

exports[`lerna publish uses meta suffix from canary flag: stdout 1`] = `

Changes:
 - package-1: 1.0.0 => 1.1.0-beta.hash
 - package-2: 1.0.0 => 1.1.0-beta.hash
 - package-3: 1.0.0 => 1.1.0-beta.hash
 - package-4: 1.0.0 => 1.1.0-beta.hash
 - package-5: 1.0.0 => 1.1.0-beta.hash (private)

`;


exports[`LsCommand in a basic repo does not list changes for ignored packages 1`] = `"package-1 v1.0.0 "`;

exports[`LsCommand in a basic repo does not list changes for ignored packages 1`] = `"package-1 v1.0.0"`;


exports[`LsCommand in a basic repo lists changes for a given scope 1`] = `"package-1 v1.0.0 "`;

exports[`LsCommand in a basic repo should list packages 1`] = `

exports[`LsCommand in a basic repo lists changes for a given scope 1`] = `"package-1 v1.0.0"`;


"use strict";

const _ = require("lodash");

module.exports = filterFlags;

/**
 * Passed argv from yargs, return an object that contains _only_
 * what was passed on the command line, omitting undefined values
 * and yargs spam.
 */
function filterFlags(argv) {
  return _.omit(_.omitBy(argv, _.isNil), ["h", "help", "v", "version", "$0"]);
}


"use strict";

const _ = require("lodash");
const log = require("npmlog");

module.exports = loggingOutput;

// clear logs between tests
afterEach(() => {
  log.record.length = 0;
});

const getVisibleMessages = _.flow(
  list =>
    _.filter(
      list,
      m =>
        // select all info, warn, and error logs
        log.levels[m.level] >= log.levels.info
    ),
  list => _.map(list, "message"),
  // remove empty logs ("newline")
  _.compact
);

function loggingOutput() {
  return getVisibleMessages(log.record);
}


keep keep keep replace replace replace keep keep keep

package-2 v1.0.0
package-3 v1.0.0
package-4 v1.0.0


lerna

Tool for managing JavaScript projects with multiple packages

npm test -- --coverage --verbose && npm run integration

cross-env LC_ALL=en-US NODE_ENV=test jest --config jest.integration.js

eslint . --ignore-path .gitignore --cache

cross-env LC_ALL=en-US NODE_ENV=test jest --watchAll --no-watchman

npm run integration -- --watchAll --no-watchman


exports[`LsCommand in a repo with packages outside of packages/ should list packages 1`] = `

exports[`LsCommand with --include-filtered-dependencies should list packages, including filtered ones 1`] = `

keep keep keep replace replace keep keep keep keep


exports[`LsCommand with --json should list packages as json objects 1`] = `

keep keep keep replace keep keep keep keep keep


"use strict";

const execa = require("execa");
// const fs = require("fs-extra");
// const globby = require("globby");
const normalizeNewline = require("normalize-newline");
const writeJsonFile = require("write-json-file");
const loadJsonFile = require("load-json-file");
const path = require("path");

const { LERNA_BIN } = require("../helpers/constants");
const initFixture = require("../helpers/initFixture");
const loadPkgManifests = require("../helpers/loadPkgManifests");

const lastCommitMessage = cwd =>
  execa.stdout("git", ["log", "-1", "--format=%B"], { cwd }).then(normalizeNewline);

const lastCommitId = cwd =>
  execa.stdout("git", ["rev-parse", "HEAD"], { cwd }).then(line => line.substring(0, 8));

async function pkgManifestsAndCommitMsg(cwd) {
  return Promise.all([loadPkgManifests(cwd), lastCommitMessage(cwd)]);
}

async function commitChangeToPackage(cwd, packageName, commitMsg, data) {
  const packageJSONPath = path.join(cwd, "packages", packageName, "package.json");
  const pkg = await loadJsonFile(packageJSONPath);
  await writeJsonFile(packageJSONPath, Object.assign(pkg, data));
  await execa("git", ["add", "."], { cwd });
  return execa("git", ["commit", "-m", commitMsg], { cwd });
}

describe("lerna publish", () => {
  test("exit 0 when no updates", async () => {
    const cwd = await initFixture("PublishCommand/normal");
    await execa("git", ["tag", "-a", "v1.0.0", "-m", "v1.0.0"], { cwd });

    const args = ["publish"];

    const { stdout, stderr, code } = await execa(LERNA_BIN, args, { cwd });

    expect(code).toBe(0);
    expect(stdout).toBe("");
    expect(stderr).toMatchSnapshot("stderr");
  });

  test("updates fixed versions", async () => {
    const cwd = await initFixture("PublishCommand/normal");
    const args = ["publish", "--skip-npm", "--cd-version=patch", "--yes"];

    const { stdout, stderr } = await execa(LERNA_BIN, args, { cwd });
    expect(stdout).toMatchSnapshot("stdout");
    expect(stderr).toMatchSnapshot("stderr");

    const [allPackageJsons, commitMessage] = await Promise.all([
      loadPkgManifests(cwd),
      lastCommitMessage(cwd),
    ]);

    expect(allPackageJsons).toMatchSnapshot("packages");
    expect(commitMessage).toMatchSnapshot("commit");
  });

  test("uses detault suffix with canary flag", async () => {
    const cwd = await initFixture("PublishCommand/normal");
    const args = ["publish", "--canary", "--skip-npm", "--yes"];

    const { stdout, stderr } = await execa(LERNA_BIN, args, { cwd });
    const hash = await lastCommitId(cwd);
    expect(stdout.replace(new RegExp(hash, "g"), "hash")).toMatchSnapshot("stdout");
    expect(stderr).toMatchSnapshot("stderr");
  });

  test("uses meta suffix from canary flag", async () => {
    const cwd = await initFixture("PublishCommand/normal");
    const args = ["publish", "--canary=beta", "--skip-npm", "--yes"];

    const { stdout, stderr } = await execa(LERNA_BIN, args, { cwd });
    const hash = await lastCommitId(cwd);
    expect(stdout.replace(new RegExp(hash, "g"), "hash")).toMatchSnapshot("stdout");
    expect(stderr).toMatchSnapshot("stderr");
  });

  test("updates independent versions", async () => {
    const cwd = await initFixture("PublishCommand/independent");
    const args = ["publish", "--skip-npm", "--cd-version=major", "--yes"];

    const { stdout, stderr } = await execa(LERNA_BIN, args, { cwd });
    expect(stdout).toMatchSnapshot("stdout");
    expect(stderr).toMatchSnapshot("stderr");

    const [allPackageJsons, commitMessage] = await Promise.all([
      loadPkgManifests(cwd),
      lastCommitMessage(cwd),
    ]);

    expect(allPackageJsons).toMatchSnapshot("packages");
    expect(commitMessage).toMatchSnapshot("commit");
  });

  test("fixed mode --conventional-commits recommends versions for each publish", async () => {
    const cwd = await initFixture("PublishCommand/normal-no-inter-dependencies", "chore: Init repo");
    const runPublish = () =>
      execa.stdout(
        LERNA_BIN,
        [
          "publish",
          "--conventional-commits",
          // "--skip-git", Note: git is not skipped to ensure creating tags for each publish execution works
          "--skip-npm",
          "--yes",
        ],
        { cwd }
      );

    // initial publish
    expect(await runPublish()).toMatchSnapshot(); // 1

    const [initialAllPackageJsons, initialCommitMessage] = await pkgManifestsAndCommitMsg(cwd);

    expect(initialAllPackageJsons).toMatchSnapshot("packages");
    expect(initialCommitMessage).toMatchSnapshot("commit");

    await commitChangeToPackage(cwd, "package-1", "feat: Add foobar feature", { foobar: true });

    // second publish adding the first feature
    expect(await runPublish()).toMatchSnapshot(); // 2

    const [firstFeatPackageJsons, firstFeatCommitMessage] = await pkgManifestsAndCommitMsg(cwd);

    expect(firstFeatPackageJsons).toMatchSnapshot("packages");
    expect(firstFeatCommitMessage).toMatchSnapshot("commit");

    await commitChangeToPackage(cwd, "package-2", "feat: Add baz feature", { baz: true });

    // third publish adding a second feature
    expect(await runPublish()).toMatchSnapshot(); // 3

    const [secondFeatPackageJsons, secondFeatCommitMessage] = await pkgManifestsAndCommitMsg(cwd);

    expect(secondFeatPackageJsons).toMatchSnapshot("packages");
    expect(secondFeatCommitMessage).toMatchSnapshot("commit");
  });

  test("fixed mode --conventional-commits --force-publish=*", async () => {
    const cwd = await initFixture("PublishCommand/normal-no-inter-dependencies", "chore: Init repo");
    const runPublish = () =>
      execa.stdout(
        LERNA_BIN,
        [
          "publish",
          "--force-publish=*",
          "--conventional-commits",
          // "--skip-git", Note: git is not skipped to ensure creating tags for each publish execution works
          "--skip-npm",
          "--yes",
        ],
        { cwd }
      );

    // initial publish
    expect(await runPublish()).toMatchSnapshot(); // 1

    const [initialAllPackageJsons, initialCommitMessage] = await pkgManifestsAndCommitMsg(cwd);

    expect(initialAllPackageJsons).toMatchSnapshot("packages");
    expect(initialCommitMessage).toMatchSnapshot("commit");

    await commitChangeToPackage(cwd, "package-1", "feat: Add foo feature", { foo: true });

    // second publish adding the first feature
    expect(await runPublish()).toMatchSnapshot(); // 2

    const [firstFeatPackageJsons, firstFeatCommitMessage] = await pkgManifestsAndCommitMsg(cwd);

    expect(firstFeatPackageJsons).toMatchSnapshot("packages");
    expect(firstFeatCommitMessage).toMatchSnapshot("commit");

    await commitChangeToPackage(cwd, "package-2", "feat: Add bar feature", { bar: true });

    // third publish adding a second feature
    expect(await runPublish()).toMatchSnapshot(); // 3

    const [secondFeatPackageJsons, secondFeatCommitMessage] = await pkgManifestsAndCommitMsg(cwd);

    expect(secondFeatPackageJsons).toMatchSnapshot("packages");
    expect(secondFeatCommitMessage).toMatchSnapshot("commit");
  });

  /* TODO: stabilize timestamp and commit sha of changelog output
  test("fixed mode --conventional-commits changelog", async () => {
    const cwd = await initFixture("PublishCommand/normal", "feat: init repo");
    const args = ["publish", "--conventional-commits", "--skip-git", "--skip-npm", "--yes"];

    await commitChangeToPackage(cwd, "package-1", "feat(package-1): Add foo feature", { foo: true });
    await commitChangeToPackage(cwd, "package-1", "fix(package-1): Fix foo feature", { foo: false });
    await commitChangeToPackage(cwd, "package-2", "fix(package-2): Fix bar feature", { bar: true });
    await commitChangeToPackage(
      cwd,
      "package-3",
      "feat(package-3): Add baz feature\n\nBREAKING CHANGE: ... more information...",
      { baz: true },
    );

    const { stdout, stderr } = await execa(LERNA_BIN, args, { cwd });
    expect(stdout).toMatchSnapshot("stdout");
    expect(stderr).toMatchSnapshot("stderr");

    const [allPackageJsons, changelogFiles] = await Promise.all([
      loadPkgManifests(cwd),
      globby(["CHANGELOG.md"], { cwd, absolute: true, matchBase: true }).then(changelogs =>
        Promise.all(changelogs.map(async fp => [fp, await fs.readFile(fp, "utf8")])),
      ),
    ]);

    expect(allPackageJsons).toMatchSnapshot("packages");
    expect(changelogFiles).toMatchSnapshot("changelog");
  });
  */

  /* TODO: stabilize timestamp of and commit sha changelog output
  test("independent mode --conventional-commits changelog", async () => {
    const cwd = await initFixture("PublishCommand/independent", "feat: init repo");
    const args = ["publish", "--conventional-commits", "--skip-git", "--skip-npm", "--yes"];

    await commitChangeToPackage(cwd, "package-1", "feat(package-1): Add foo feature", { foo: true });
    await commitChangeToPackage(cwd, "package-1", "fix(package-1): Fix foo feature", { foo: false });
    await commitChangeToPackage(cwd, "package-2", "fix(package-2): Fix bar feature", { bar: true });
    await commitChangeToPackage(
      cwd,
      "package-3",
      "feat(package-3): Add baz feature\n\nBREAKING CHANGE: ... more information...",
      { baz: true },
    );

    const { stdout, stderr } = await execa(LERNA_BIN, args, { cwd });
    expect(stdout).toMatchSnapshot("stdout");
    expect(stderr).toMatchSnapshot("stderr");

    const [allPackageJsons, changelogFiles] = await Promise.all([
      loadPkgManifests(cwd),
      globby(["CHANGELOG.md"], { cwd, absolute: true, matchBase: true }).then(changelogs =>
        Promise.all(changelogs.map(async fp => [fp, await fs.readFile(fp, "utf8")])),
      ),
    ]);

    expect(allPackageJsons).toMatchSnapshot("packages");
    expect(changelogFiles).toMatchSnapshot("changelog");
  });
  */
});


"use strict";

const execa = require("execa");
const fs = require("fs-extra");
const globby = require("globby");
const normalizePath = require("normalize-path");
const path = require("path");
const tempy = require("tempy");

const { LERNA_BIN } = require("../helpers/constants");
const initFixture = require("../helpers/initFixture");
const copyFixture = require("../helpers/copyFixture");

describe("lerna bootstrap", () => {
  const npmTest = cwd =>
    execa(
      LERNA_BIN,
      [
        "run",
        "test",
        "--",
        // arguments to npm test
        "--silent",
        "--onload-script=false",
      ],
      { cwd }
    );

  describe("from CLI", () => {
    test("bootstraps all packages", async () => {
      const cwd = await initFixture("BootstrapCommand/integration");
      const args = ["bootstrap"];

      const stderr = await execa.stderr(LERNA_BIN, args, { cwd });
      expect(stderr).toMatchSnapshot("stderr");

      const { stdout } = await npmTest(cwd);
      expect(stdout).toMatchSnapshot("stdout");
    });

    test("respects ignore flag", async () => {
      const cwd = await initFixture("BootstrapCommand/integration");
      const args = ["bootstrap", "--ignore", "@integration/package-1"];

      const stderr = await execa.stderr(LERNA_BIN, args, { cwd });
      expect(stderr).toMatchSnapshot("stderr");
    });

    test("git repo check is ignored by default", async () => {
      const cwd = tempy.directory();
      await copyFixture(cwd, "BootstrapCommand/integration");
      const args = ["bootstrap"];

      const stderr = await execa.stderr(LERNA_BIN, args, { cwd });
      expect(stderr).toMatchSnapshot("stderr");
    });

    test("--npm-client yarn", async () => {
      const cwd = await initFixture("BootstrapCommand/integration");
      const args = ["bootstrap", "--npm-client", "yarn"];

      const stderr = await execa.stderr(LERNA_BIN, args, { cwd });
      expect(stderr).toMatchSnapshot("stderr");

      const lockfiles = await globby(["package-*/yarn.lock"], { cwd }).then(globbed =>
        globbed.map(fp => normalizePath(fp))
      );
      expect(lockfiles).toMatchSnapshot("lockfiles");

      const { stdout } = await npmTest(cwd);
      expect(stdout).toMatchSnapshot("stdout");
    });

    test("passes remaining arguments to npm client", async () => {
      const cwd = await initFixture("BootstrapCommand/npm-client-args-1");
      const args = ["bootstrap", "--npm-client", path.resolve(cwd, "npm"), "--", "--no-optional"];

      await execa(LERNA_BIN, args, { cwd });

      const npmDebugLog = fs.readFileSync(path.resolve(cwd, "npm-debug.log")).toString();
      expect(npmDebugLog).toMatchSnapshot();
    });

    test("passes remaining arguments + npmClientArgs to npm client", async () => {
      const cwd = await initFixture("BootstrapCommand/npm-client-args-2");
      const args = ["bootstrap", "--npm-client", path.resolve(cwd, "npm"), "--", "--no-optional"];

      await execa(LERNA_BIN, args, { cwd });

      const npmDebugLog = fs.readFileSync(path.resolve(cwd, "npm-debug.log")).toString();
      expect(npmDebugLog).toMatchSnapshot();
    });
  });
});


exports[`LsCommand with an undefined version should list packages 1`] = `"package-1 MISSING "`;

exports[`LsCommand with an undefined version should list packages 1`] = `"package-1 MISSING"`;

"use strict";

const chalk = require("chalk");
const normalizeNewline = require("normalize-newline");
const output = require("../../src/utils/output");

jest.mock("../../src/utils/output");

// keep snapshots stable cross-platform
chalk.enabled = false;

module.exports = consoleOutput;

function consoleOutput() {
  return output.mock.calls.map(args => normalizeNewline(args[0])).join("\n");
}


return output.mock.calls.map(args => normalizeNewline(args[0])).join("\n");

return output.mock.calls
.map(args =>
normalizeNewline(args[0])
.split("\n")
.map(line => line.trimRight())
.join("\n")
)
.join("\n");


#!/usr/bin/env node

const msg = require("./");

console.log("cli", msg);