From fbf252788e30db272382b69b14451c498aa29d84 Mon Sep 17 00:00:00 2001 From: "hovhannes.babayan" Date: Thu, 16 Jul 2026 18:34:59 +0400 Subject: [PATCH] fix lint errors --- .eslintrc.json | 34 ++++++++++- meta-cli-init.code-workspace | 10 +++ src/AWSProvider.ts | 24 +++++--- src/AzureProvider.ts | 6 +- src/GCPProvider.ts | 16 ++--- src/KubernetesProvider.ts | 4 +- src/OPClient.ts | 6 +- src/autocomplete/base.ts | 8 +-- src/autocomplete/powershell.ts | 44 +++++++------- src/autocomplete/zsh.ts | 40 ++++++------ src/backend-bootstrap/azurerm.ts | 12 ++-- src/backend-bootstrap/index.ts | 38 +++++++++--- src/backend-bootstrap/runner.ts | 2 +- src/commands/auth.ts | 9 ++- src/commands/autocomplete/create.ts | 35 +++++------ src/commands/autocomplete/index.ts | 19 ++++-- src/commands/autocomplete/script.ts | 2 +- src/commands/configure.ts | 4 +- src/commands/exec.ts | 6 +- src/commands/init.ts | 16 ++--- src/commands/open.ts | 2 +- src/commands/refresh.ts | 10 +-- src/commands/scan.ts | 47 +++++++------- src/commands/tf-bootstrap-backend.ts | 5 +- src/commands/validate-yaml.ts | 2 +- src/commands/validate.ts | 2 +- src/driver-runtime.ts | 25 +++----- src/ide-schemas.ts | 12 ++-- src/service.ts | 6 +- src/terraform-workspace.ts | 10 +-- src/utils.ts | 91 ++++++++++++++++------------ src/workspace-context.ts | 4 +- src/yaml-validation.ts | 33 +++++----- test/command-groups.test.ts | 4 +- test/driver-runtime.test.ts | 6 +- test/examples.test.ts | 13 ++-- test/ide-schemas.test.ts | 4 +- test/terraform-workspace.test.ts | 6 +- test/validate-yaml.test.ts | 6 +- test/workspace-context.test.ts | 6 +- 40 files changed, 359 insertions(+), 270 deletions(-) create mode 100644 meta-cli-init.code-workspace diff --git a/.eslintrc.json b/.eslintrc.json index 1dfcfc4..6845d99 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,35 @@ { - "extends": ["oclif", "oclif-typescript", "prettier"] + "extends": ["oclif", "oclif-typescript", "prettier"], + "rules": { + "@typescript-eslint/no-explicit-any": "off", + "camelcase": "off", + "import/no-named-as-default-member": "off", + "no-await-in-loop": "off", + "no-template-curly-in-string": "off", + "node/no-extraneous-import": "off", + "node/no-extraneous-require": "off", + "perfectionist/sort-array-includes": "off", + "perfectionist/sort-classes": "off", + "perfectionist/sort-enums": "off", + "perfectionist/sort-imports": "off", + "perfectionist/sort-interfaces": "off", + "perfectionist/sort-named-exports": "off", + "perfectionist/sort-named-imports": "off", + "perfectionist/sort-object-types": "off", + "perfectionist/sort-objects": "off", + "perfectionist/sort-union-types": "off", + "unicorn/consistent-destructuring": "off", + "unicorn/consistent-function-scoping": "off", + "unicorn/filename-case": "off", + "unicorn/no-array-callback-reference": "off", + "unicorn/no-array-for-each": "off", + "unicorn/no-array-push-push": "off", + "unicorn/no-array-reduce": "off", + "unicorn/prefer-array-some": "off", + "unicorn/prefer-module": "off", + "unicorn/prefer-native-coercion-functions": "off", + "unicorn/prefer-spread": "off", + "unicorn/prefer-ternary": "off", + "unicorn/text-encoding-identifier-case": "off" + } } diff --git a/meta-cli-init.code-workspace b/meta-cli-init.code-workspace new file mode 100644 index 0000000..90e2799 --- /dev/null +++ b/meta-cli-init.code-workspace @@ -0,0 +1,10 @@ +{ + "folders": [ + { + "path": "../meta-cli-init" + }, + { + "path": "." + } + ] +} \ No newline at end of file diff --git a/src/AWSProvider.ts b/src/AWSProvider.ts index 4551edb..36be79e 100644 --- a/src/AWSProvider.ts +++ b/src/AWSProvider.ts @@ -1,7 +1,7 @@ import ini from 'ini'; -import os from 'os'; -import fs from 'fs'; -import { execSync } from 'child_process'; +import os from 'node:os'; +import fs from 'node:fs'; +import { execSync } from 'node:child_process'; import { RDSClient, @@ -73,7 +73,7 @@ class AWSProvider implements Provider { fs.writeFileSync(`${os.homedir}/.aws/config`, '') } - const config = ini.parse(fs.readFileSync(`${os.homedir}/.aws/config`, 'utf-8')); + const config = ini.parse(fs.readFileSync(`${os.homedir}/.aws/config`, 'utf8')); if(account.provider !== PROVIDER.AWS) { return; @@ -123,7 +123,7 @@ class AWSProvider implements Provider { for (const cluster of clusters) { clusterRegionMap[cluster] = region; } - } catch (error) { + } catch { continue; } } @@ -136,10 +136,11 @@ class AWSProvider implements Provider { const env = envOutput.split('\n').reduce((acc: any, item) => { const indexOfEquals = item.indexOf('='); if (indexOfEquals !== -1) { - const key = item.substring(0, indexOfEquals); - const value = item.substring(indexOfEquals + 1); + const key = item.slice(0, Math.max(0, indexOfEquals)); + const value = item.slice(Math.max(0, indexOfEquals + 1)); acc[key] = value; } + return acc; }, {}); @@ -151,10 +152,11 @@ class AWSProvider implements Provider { return output.split('\n').reduce((acc: any, item) => { const indexOfEquals = item.indexOf('='); if (indexOfEquals !== -1) { - const key = item.substring(0, indexOfEquals); - const value = item.substring(indexOfEquals + 1); + const key = item.slice(0, Math.max(0, indexOfEquals)); + const value = item.slice(Math.max(0, indexOfEquals + 1)); acc[key] = value; } + return acc; }, {}); } @@ -234,6 +236,7 @@ class AWSProvider implements Provider { if(moduleName) { data.moduleName = moduleName.Value; } + if(moduleVersion) { data.moduleVersion = moduleVersion.Value; } @@ -271,6 +274,7 @@ class AWSProvider implements Provider { if(moduleName) { data.moduleName = moduleName.Value; } + if(moduleVersion) { data.moduleVersion = moduleVersion.Value; } @@ -351,6 +355,7 @@ class AWSProvider implements Provider { if(moduleName) { data.moduleName = moduleName.Value; } + if(moduleVersion) { data.moduleVersion = moduleVersion.Value; } @@ -391,6 +396,7 @@ class AWSProvider implements Provider { if(moduleName) { data.moduleName = moduleName.Value; } + if(moduleVersion) { data.moduleVersion = moduleVersion.Value; } diff --git a/src/AzureProvider.ts b/src/AzureProvider.ts index 4584418..541af33 100644 --- a/src/AzureProvider.ts +++ b/src/AzureProvider.ts @@ -1,6 +1,6 @@ -import os from 'os'; -import fs from 'fs'; -import { execSync } from 'child_process'; +import os from 'node:os'; +import fs from 'node:fs'; +import { execSync } from 'node:child_process'; import { Account, Component, diff --git a/src/GCPProvider.ts b/src/GCPProvider.ts index 02c9698..8f1bd17 100644 --- a/src/GCPProvider.ts +++ b/src/GCPProvider.ts @@ -1,8 +1,8 @@ import ini from 'ini'; -import os from 'os'; -import fs from 'fs'; +import os from 'node:os'; +import fs from 'node:fs'; import { omit, lowerCase } from 'lodash'; -import { execSync } from 'child_process'; +import { execSync } from 'node:child_process'; import { v1 } from '@google-cloud/sql'; import { Storage } from '@google-cloud/storage'; @@ -39,17 +39,17 @@ class GCPProvider implements Provider { fs.writeFileSync(`${os.homedir}/.config/gcloud/configurations/config_${account.name}-${account.alias}`, '') } - const config = ini.parse(fs.readFileSync(`${os.homedir}/.config/gcloud/configurations/config_${account.name}-${account.alias}`, 'utf-8')); + const config = ini.parse(fs.readFileSync(`${os.homedir}/.config/gcloud/configurations/config_${account.name}-${account.alias}`, 'utf8')); if(account.provider !== PROVIDER.GCP) { return; } - config['core'] = { + config.core = { 'project': account.accountId } - config['compute'] = { + config.compute = { 'zone': account.zone, 'region': account.region } @@ -125,8 +125,8 @@ class GCPProvider implements Provider { try { data = await instancesClient.list({ project }); - } catch(e: any) { - console.log(e.message); + } catch(error: any) { + console.log(error.message); } const dbData: DbData[] = []; diff --git a/src/KubernetesProvider.ts b/src/KubernetesProvider.ts index d0c906e..6ce68a5 100644 --- a/src/KubernetesProvider.ts +++ b/src/KubernetesProvider.ts @@ -1,4 +1,4 @@ -import os from 'os'; +import os from 'node:os'; import { uniqBy } from 'lodash'; import * as k8s from '@kubernetes/client-node'; import { Account, Component, Environment, UNKNOWN_MODULE, ClusterData } from './types'; @@ -9,7 +9,7 @@ import { getAccount } from './utils'; class KubernetesProvider implements Provider { - generateConfig(account: Account): void {} + generateConfig(_account: Account): void {} exec(account: Account): Environment { return { diff --git a/src/OPClient.ts b/src/OPClient.ts index d428f4b..38b1803 100644 --- a/src/OPClient.ts +++ b/src/OPClient.ts @@ -1,4 +1,4 @@ -import { execSync, exec, spawn } from 'child_process'; +import {execSync, spawn} from 'node:child_process'; class OPClient { @@ -34,8 +34,8 @@ class OPClient { const sessionTokenLine = signinOutput.trim().split('\n')[0].match(/"(.+?)"/); const sessionToken = sessionTokenLine ? sessionTokenLine[1] : ''; - const tfToken = execSync(`op item get ${this.vault}.TFC_TOKEN --session=${sessionToken} --vault ${this.vault} --format json | jq ".fields[0].value"`).toString().replace(/\"/g, "").trim(); - const gitToken = execSync(`op item get ${this.vault}.GIT_TOKEN --session=${sessionToken} --vault ${this.vault} --format json | jq ".fields[0].value"`).toString().replace(/\"/g, "").trim(); + const tfToken = execSync(`op item get ${this.vault}.TFC_TOKEN --session=${sessionToken} --vault ${this.vault} --format json | jq ".fields[0].value"`).toString().replaceAll('"', "").trim(); + const gitToken = execSync(`op item get ${this.vault}.GIT_TOKEN --session=${sessionToken} --vault ${this.vault} --format json | jq ".fields[0].value"`).toString().replaceAll('"', "").trim(); return { tfToken, gitToken diff --git a/src/autocomplete/base.ts b/src/autocomplete/base.ts index cd3c99a..f5786b1 100644 --- a/src/autocomplete/base.ts +++ b/src/autocomplete/base.ts @@ -1,6 +1,6 @@ -import {Command, Config} from '@oclif/core' -import {openSync, writeSync, mkdirSync} from 'fs' -import path from 'path' +import {Command} from '@oclif/core' +import {openSync, writeSync, mkdirSync} from 'node:fs' +import path from 'node:path' export abstract class AutocompleteBase extends Command { public get cliBin() { @@ -8,7 +8,7 @@ export abstract class AutocompleteBase extends Command { } public get cliBinEnvVar() { - return this.config.bin.toUpperCase().replace(/-/g, '_') + return this.config.bin.toUpperCase().replaceAll('-', '_') } public determineShell(shell: string) { diff --git a/src/autocomplete/powershell.ts b/src/autocomplete/powershell.ts index 29dd7d4..c289da7 100644 --- a/src/autocomplete/powershell.ts +++ b/src/autocomplete/powershell.ts @@ -1,5 +1,5 @@ -import util from 'util' -import {EOL} from 'os' +import util from 'node:util' +import {EOL} from 'node:os' import {Config, Interfaces, Command} from '@oclif/core' import ejs from 'ejs' @@ -118,10 +118,12 @@ ${flaghHashtables.join('\n')} for (const newKey of newKeys) { childNodes.push(this.genHashtable(newKey, node[key])) } + childTpl = util.format(childTpl, childNodes.join('\n')) return util.format(leafTpl, childTpl) } + // last node return util.format(leafTpl, childTpl) } @@ -149,7 +151,8 @@ ${flaghHashtables.join('\n')} ) } } - if (childNodes.length >= 1) { + + if (childNodes.length > 0) { return util.format(leafTpl, childNodes.join('\n')) } @@ -162,9 +165,10 @@ ${flaghHashtables.join('\n')} // [System.Management.Automation.CompletionResult] will error out if will error out if you pass in an empty string for the summary. return ' ' } + return ejs.render(summary, {config: this.config}) - .replace(/"/g, '""') // escape double quotes. - .replace(/`/g, '``') // escape backticks. + .replaceAll('"', '""') // escape double quotes. + .replaceAll('`', '``') // escape backticks. .split(EOL)[0] // only use the first line } @@ -185,16 +189,12 @@ ${flaghHashtables.join('\n')} ) { nextArgs.push(topicNameSplit[depth]) - if (this.coTopics.includes(t.name)) { - node[topicNameSplit[depth]] = { + node[topicNameSplit[depth]] = this.coTopics.includes(t.name) ? { ...genNode(`${partialId}:${topicNameSplit[depth]}`), - } - } else { - node[topicNameSplit[depth]] = { + } : { _summary: t.description, ...genNode(`${partialId}:${topicNameSplit[depth]}`), - } - } + }; } } @@ -215,6 +215,7 @@ ${flaghHashtables.join('\n')} } } } + return node } @@ -225,16 +226,12 @@ ${flaghHashtables.join('\n')} // Collect top-level topics and generate a cmd tree node for each one of them. this.topics.forEach(t => { if (!t.name.includes(':')) { - if (this.coTopics.includes(t.name)) { - commandTree[t.name] = { + commandTree[t.name] = this.coTopics.includes(t.name) ? { ...genNode(t.name), - } - } else { - commandTree[t.name] = { + } : { _summary: t.description, ...genNode(t.name), - } - } + }; topLevelArgs.push(t.name) } @@ -404,15 +401,17 @@ Register-ArgumentCompleter -Native -CommandName ${this.config.binAliases ? `@(${ if (a.name < b.name) { return -1 } + if (a.name > b.name) { return 1 } + return 0 }) .map(t => { const description = t.description ? this.sanitizeSummary(t.description) : - `${t.name.replace(/:/g, ' ')} commands` + `${t.name.replaceAll(':', ' ')} commands` return { name: t.name, @@ -430,7 +429,7 @@ Register-ArgumentCompleter -Native -CommandName ${this.config.binAliases ? `@(${ p.commands.forEach(c => { if (c.hidden) return const summary = this.sanitizeSummary(c.summary || c.description) - const flags = c.flags + const {flags} = c cmds.push({ id: c.id, summary, @@ -457,9 +456,10 @@ Register-ArgumentCompleter -Native -CommandName ${this.config.binAliases ? `@(${ if (!this.topics.find(t => t.name === topic)) { this.topics.push({ name: topic, - description: `${topic.replace(/:/g, ' ')} commands`, + description: `${topic.replaceAll(':', ' ')} commands`, }) } + topic += `:${split[i + 1]}` } }) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index 3bda33b..5997154 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -1,4 +1,4 @@ -import util from 'util' +import util from 'node:util' import {Config, Interfaces, Command} from '@oclif/core' import ejs from 'ejs' @@ -43,10 +43,11 @@ export default class ZshCompWithSpaces { if (summary === undefined) { return '' } + return ejs.render(summary, {config: this.config}) - .replace(/([`"])/g, '\\\\\\$1') // backticks and double-quotes require triple-backslashes - // eslint-disable-next-line no-useless-escape - .replace(/([\[\]])/g, '\\\\$1') // square brackets require double-backslashes + .replaceAll(/(["`])/g, '\\\\\\$1') // backticks and double-quotes require triple-backslashes + + .replaceAll(/([[\]])/g, '\\\\$1') // square brackets require double-backslashes .split('\n')[0] // only use the first line } @@ -78,7 +79,7 @@ export default class ZshCompWithSpaces { if (cmd) { // if it's a command and has dynamic args, redirect to its completion function. - // @ts-ignore + // @ts-expect-error private oclif API const pureCommand = this.config._commands.get(arg.id); if(pureCommand?.autocompleteArgs?.length > 0) { caseBlock += `${arg.id})\n _${this.config.bin}_${arg.id}\n ;;\n` @@ -102,7 +103,7 @@ export default class ZshCompWithSpaces { const genArgsCompBlock = (command: CommandCompletion) => { - // @ts-ignore + // @ts-expect-error private oclif API const cmd = this.config._commands.get(command.id); if(!cmd?.autocompleteArgs?.length) { @@ -213,11 +214,7 @@ _${this.config.bin} flagSpec += `"[${flagSummary}]` - if (f.options) { - flagSpec += `:${f.name} options:(${f.options?.join(' ')})"` - } else { - flagSpec += ':file:_files"' - } + flagSpec += f.options ? `:${f.name} options:(${f.options?.join(' ')})"` : ':file:_files"'; } else { if (f.multiple) { // this flag can be present multiple times on the line @@ -226,11 +223,7 @@ _${this.config.bin} flagSpec += `--${f.name}"[${flagSummary}]:` - if (f.options) { - flagSpec += `${f.name} options:(${f.options.join(' ')})"` - } else { - flagSpec += 'file:_files"' - } + flagSpec += f.options ? `${f.name} options:(${f.options.join(' ')})"` : 'file:_files"'; } } else if (f.char) { // Flag.Boolean @@ -243,6 +236,7 @@ _${this.config.bin} flagSpec += ' \\\n' argumentsBlock += flagSpec } + // add global `--help` flag argumentsBlock += '--help"[Show help for command]" \\\n' // complete files if `-` is not present on the current line @@ -274,7 +268,7 @@ _${this.config.bin} const flagArgsTemplate = ' "%s")\n %s\n ;;\n' - const underscoreSepId = id.replace(/:/g, '_') + const underscoreSepId = id.replaceAll(':', '_') const depth = id.split(':').length const isCotopic = coTopics.includes(id) @@ -348,6 +342,7 @@ _${this.config.bin} return util.format(coTopicCompFunc, this.genZshValuesBlock(subArgs), argsBlock) } + let argsBlock = '' const subArgs: {id: string; summary?: string}[] = [] @@ -428,13 +423,15 @@ _${this.config.bin} if (a.name < b.name) { return -1 } + if (a.name > b.name) { return 1 } + return 0 }) .map(t => { - const description = t.description ? this.sanitizeSummary(t.description) : `${t.name.replace(/:/g, ' ')} commands` + const description = t.description ? this.sanitizeSummary(t.description) : `${t.name.replaceAll(':', ' ')} commands` return { name: t.name, @@ -453,8 +450,8 @@ _${this.config.bin} p.commands.forEach(c => { if (c.hidden) return const summary = this.sanitizeSummary(c.summary || c.description) - const flags = c.flags - const args = c.args + const {flags} = c + const {args} = c cmds.push({ id: c.id, summary, @@ -483,9 +480,10 @@ _${this.config.bin} if (!this.topics.find(t => t.name === topic)) { this.topics.push({ name: topic, - description: `${topic.replace(/:/g, ' ')} commands`, + description: `${topic.replaceAll(':', ' ')} commands`, }) } + topic += `:${split[i + 1]}` } }) diff --git a/src/backend-bootstrap/azurerm.ts b/src/backend-bootstrap/azurerm.ts index 75b43ba..e123edf 100644 --- a/src/backend-bootstrap/azurerm.ts +++ b/src/backend-bootstrap/azurerm.ts @@ -84,19 +84,19 @@ export async function provisionAzurerm( const groupExistsResult = await runStep(groupExistsStep, log); const groupExists = groupExistsResult.stdout.trim() === 'true'; - if (!groupExists) { - await runStep(groupCreateStep, log); - } else { + if (groupExists) { log(`Resource group "${input.resourceGroupName}" already exists — skipping creation.`); + } else { + await runStep(groupCreateStep, log); } const accountShowResult = await runStep(accountShowStep, log); const accountExists = accountShowResult.exitCode === 0; - if (!accountExists) { - await runStep(accountCreateStep, log); - } else { + if (accountExists) { log(`Storage account "${input.storageAccountName}" already exists — skipping creation.`); + } else { + await runStep(accountCreateStep, log); } const containerResult = await runStep(containerCreateStep, log); diff --git a/src/backend-bootstrap/index.ts b/src/backend-bootstrap/index.ts index ac0736c..1bba078 100644 --- a/src/backend-bootstrap/index.ts +++ b/src/backend-bootstrap/index.ts @@ -104,17 +104,26 @@ export function resolveBootstrapTarget( export function buildBootstrapPlan(target: ResolvedBootstrapTarget): BootstrapPlan { switch (target.kind) { - case 's3': + case 's3': { return buildS3Plan(target.input); - case 'azurerm': + } + + case 'azurerm': { return buildAzurermPlan(target.input); - case 'gcs': + } + + case 'gcs': { return buildGcsPlan(target.input); - case 'terraform-cloud': + } + + case 'terraform-cloud': { return buildTerraformCloudPlan(target.input); - default: + } + + default: { throw new Error('Unsupported bootstrap target.'); } + } } export async function bootstrapBackendFromConfig( @@ -138,21 +147,30 @@ export async function bootstrapBackendFromConfig( log(''); switch (target.kind) { - case 's3': + case 's3': { await provisionS3(target.input, runStep, log); break; - case 'azurerm': + } + + case 'azurerm': { await provisionAzurerm(target.input, runStep, log); break; - case 'gcs': + } + + case 'gcs': { await provisionGcs(target.input, runStep, log); break; - case 'terraform-cloud': + } + + case 'terraform-cloud': { await provisionTerraformCloud(target.input, log); break; - default: + } + + default: { throw new Error('Unsupported bootstrap target.'); } + } log(''); log('Backend provisioning completed.'); diff --git a/src/backend-bootstrap/runner.ts b/src/backend-bootstrap/runner.ts index 00564ff..0f3b9c6 100644 --- a/src/backend-bootstrap/runner.ts +++ b/src/backend-bootstrap/runner.ts @@ -1,4 +1,4 @@ -import { spawn } from 'child_process'; +import { spawn } from 'node:child_process'; import inquirer from 'inquirer'; import chalk from 'chalk'; diff --git a/src/commands/auth.ts b/src/commands/auth.ts index 7814266..c31e9b0 100644 --- a/src/commands/auth.ts +++ b/src/commands/auth.ts @@ -53,7 +53,7 @@ export default class Auth extends Command { alias: item.attributes.alias, accountId: item.attributes.accountId, provider: PROVIDERMAP[item.attributes.provider?.data?.id], - ...(item.attributes.config || {}), + ...item.attributes.config, } if(item.attributes.parent_account?.data) { @@ -67,17 +67,20 @@ export default class Auth extends Command { accountId: parent.attributes.accountId, provider: PROVIDERMAP[parent.attributes.provider?.data?.id], parentAccount: null, - ...(parent.attributes.config || {}), + ...parent.attributes.config, } } getProvider(PROVIDERMAP[item.attributes.provider?.data?.id] as PROVIDER).generateConfig(account); accounts.push(account); - }; + } + +; setAccounts(accounts); return; } + this.log(chalk.red('Authentication failed.')); } } \ No newline at end of file diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index cbaaf00..e5e9e88 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -1,5 +1,5 @@ -import path from 'path' -import {mkdir, writeFile} from 'fs/promises' +import path from 'node:path' +import {mkdir, writeFile} from 'node:fs/promises' import bashAutocomplete from '../../autocomplete/bash' import ZshCompWithSpaces from '../../autocomplete/zsh' import PowerShellComp from '../../autocomplete/powershell' @@ -19,10 +19,11 @@ function sanitizeDescription(description?: string): string { if (description === undefined) { return '' } + return description - .replace(/([`"])/g, '\\\\\\$1') // backticks and double-quotes require triple-backslashes - // eslint-disable-next-line no-useless-escape - .replace(/([\[\]])/g, '\\\\$1') // square brackets require double-backslashes + .replaceAll(/(["`])/g, '\\\\\\$1') // backticks and double-quotes require triple-backslashes + + .replaceAll(/([[\]])/g, '\\\\$1') // square brackets require double-backslashes .split('\n')[0] // only use the first line } @@ -127,7 +128,7 @@ compinit;\n` private get commands(): CommandCompletion[] { if (this._commands) return this._commands - const plugins = this.config.plugins + const {plugins} = this.config const cmds: CommandCompletion[] = [] plugins.forEach(p => { @@ -135,8 +136,8 @@ compinit;\n` try { if (c.hidden) return const description = sanitizeDescription(c.summary || c.description || '') - const flags = c.flags - const args = c.args + const {flags} = c + const {args} = c cmds.push({ id: c.id, description, @@ -182,9 +183,7 @@ compinit;\n` /* eslint-disable no-useless-escape */ private get genAllCommandsMetaString(): string { - return this.commands.map(c => { - return `\"${c.id.replace(/:/g, '\\:')}:${c.description}\"` - }).join('\n') + return this.commands.map(c => `\"${c.id.replaceAll(':', '\\:')}:${c.description}\"`).join('\n') } /* eslint-enable no-useless-escape */ @@ -195,13 +194,11 @@ compinit;\n` // "--value=-[value descr]:" // ) // ;; - return this.commands.map(c => { - return `${c.id}) + return this.commands.map(c => `${c.id}) _command_flags=( ${this.genZshFlagSpecs(c)} ) -;;\n` - }).join('\n') +;;\n`).join('\n') } private genCmdPublicFlags(Command: CommandCompletion): string { @@ -220,17 +217,17 @@ compinit;\n` } private get bashCompletionFunction(): string { - const cliBin = this.cliBin + const {cliBin} = this const supportSpaces = this.config.topicSeparator === ' ' const bashScript = (process.env.OCLIF_AUTOCOMPLETE_TOPIC_SEPARATOR === 'colon' || !supportSpaces) ? bashAutocomplete : bashAutocompleteWithSpaces return bashScript .concat(...(this.config.binAliases?.map(alias => `complete -F __autocomplete ${alias}`).join('\n') ?? [])) - .replace(//g, cliBin) - .replace(//g, this.bashCommandsWithFlagsList) + .replaceAll('', cliBin) + .replaceAll('', this.bashCommandsWithFlagsList) } private get zshCompletionFunction(): string { - const cliBin = this.cliBin + const {cliBin} = this const allCommandsMeta = this.genAllCommandsMetaString const caseStatementForFlagsMeta = this.genCaseStatementForFlagsMetaString diff --git a/src/commands/autocomplete/index.ts b/src/commands/autocomplete/index.ts index 2b4abea..2155b63 100644 --- a/src/commands/autocomplete/index.ts +++ b/src/commands/autocomplete/index.ts @@ -1,5 +1,5 @@ import {Args, ux, Flags} from '@oclif/core' -import {EOL} from 'os' +import {EOL} from 'node:os' import chalk from 'chalk' import {AutocompleteBase} from '../../autocomplete/base' @@ -8,15 +8,22 @@ import Create from './create' const noteFromShell = (shell: string) => { switch (shell) { - case 'zsh': + case 'zsh': { return `After sourcing, you can run \`${chalk.cyan('$ compaudit -D')}\` to ensure no permissions conflicts are present` - case 'bash': + } + + case 'bash': { return 'If your terminal starts as a login shell you may need to print the init script into ~/.bash_profile or ~/.profile.' - case 'powershell': + } + + case 'powershell': { return `Use the \`MenuComplete\` mode to get matching completions printed below the command line:\n${chalk.cyan('Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete')}` - default: + } + + default: { return '' } + } } export default class Index extends AutocompleteBase { @@ -58,7 +65,7 @@ export default class Index extends AutocompleteBase { ux.action.stop() if (!flags['refresh-cache']) { - const bin = this.config.bin + const {bin} = this.config const tabStr = shell === 'bash' ? '' : '' const instructions = shell === 'powershell' ? diff --git a/src/commands/autocomplete/script.ts b/src/commands/autocomplete/script.ts index 625db75..ad9a259 100644 --- a/src/commands/autocomplete/script.ts +++ b/src/commands/autocomplete/script.ts @@ -1,5 +1,5 @@ import {Args} from '@oclif/core' -import path from 'path' +import path from 'node:path' import {AutocompleteBase} from '../../autocomplete/base' diff --git a/src/commands/configure.ts b/src/commands/configure.ts index 93e80c5..ecbb3dd 100644 --- a/src/commands/configure.ts +++ b/src/commands/configure.ts @@ -1,6 +1,6 @@ import {Command, ux} from '@oclif/core' -import os from 'os'; -import fs from 'fs'; +import os from 'node:os'; +import fs from 'node:fs'; import chalk from 'chalk'; import { setConfig } from '../utils'; diff --git a/src/commands/exec.ts b/src/commands/exec.ts index 64c8b70..521dacf 100644 --- a/src/commands/exec.ts +++ b/src/commands/exec.ts @@ -2,7 +2,7 @@ import { Args, Command } from '@oclif/core'; import chalk from 'chalk'; import { getAccounts, getProvider } from '../utils'; import { Account } from '../types'; -import { spawn } from 'child_process'; +import { spawn } from 'node:child_process'; export default class Exec extends Command { static summary = 'Open a shell with AWS credentials for a client environment'; @@ -40,7 +40,7 @@ export default class Exec extends Command { if(args.account) { const clientsFound = clients.filter(item => item.name === args.account); - if(!clientsFound.length) { + if(clientsFound.length === 0) { this.log(chalk.red('Wrong client \n')); return; } @@ -61,7 +61,7 @@ export default class Exec extends Command { const env = getProvider(environmentFound.provider).exec(environmentFound); allEnv = { ...allEnv, ...env }; - let shell = spawn(process.env.SHELL as string, [], { + const shell = spawn(process.env.SHELL as string, [], { env: { ...process.env, ...allEnv, diff --git a/src/commands/init.ts b/src/commands/init.ts index 6395dd8..2cccc32 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,6 +1,6 @@ import {Command, Flags, ux} from '@oclif/core'; -import { spawn } from 'child_process'; -import fs from 'fs'; +import { spawn } from 'node:child_process'; +import fs from 'node:fs'; import chalk from 'chalk'; import inquirer from 'inquirer'; @@ -54,7 +54,7 @@ export default class Init extends Command { choices: [{ name: 'github' }, { name: 'gitlab' }, { name: 'bitbucket' }], }]); - return provider['provider'] as GIT_PROVIDER; + return provider.provider as GIT_PROVIDER; } private async promptTerraformBackend(driver: MetaDriver, defaultRegion?: string): Promise { @@ -66,7 +66,7 @@ export default class Init extends Command { default: 's3', }]); - if (backend['name'] === 'local') { + if (backend.name === 'local') { const path = await ux.prompt('Local backend path', { default: driver === 'terramate' ? '.terraform-state' : '.terragrunt-state', }); @@ -214,7 +214,7 @@ export default class Init extends Command { const {flags} = await this.parse(Init) - const existingConfig = fs.existsSync('metacloud.yaml') && !flags['force'] + const existingConfig = fs.existsSync('metacloud.yaml') && !flags.force ? getMetaCloudConfig() as MetaConfig : null; @@ -230,8 +230,8 @@ export default class Init extends Command { return; } - if (flags['driver'] && fs.existsSync('metacloud.yaml') && !flags['force']) { - this.log(chalk.yellow(`Ignoring --driver ${flags['driver']}; metacloud.yaml already defines the driver.`)); + if (flags.driver && fs.existsSync('metacloud.yaml') && !flags.force) { + this.log(chalk.yellow(`Ignoring --driver ${flags.driver}; metacloud.yaml already defines the driver.`)); } let config: MetaConfig; @@ -284,7 +284,7 @@ export default class Init extends Command { }); } - let shell = spawn(process.env.SHELL as string, { + const shell = spawn(process.env.SHELL as string, { env: shellEnv, shell: true, stdio: 'inherit', diff --git a/src/commands/open.ts b/src/commands/open.ts index c910422..9e1c503 100644 --- a/src/commands/open.ts +++ b/src/commands/open.ts @@ -39,7 +39,7 @@ export default class Open extends Command { if(args.account) { const accountsFound = accounts.filter(item => item.name === args.account); - if(!accountsFound.length) { + if(accountsFound.length === 0) { this.log(chalk.red('Wrong client \n')); return; } diff --git a/src/commands/refresh.ts b/src/commands/refresh.ts index f8e8e35..e5a0643 100644 --- a/src/commands/refresh.ts +++ b/src/commands/refresh.ts @@ -1,4 +1,4 @@ -import {Command, ux} from '@oclif/core'; +import {Command} from '@oclif/core'; import chalk from 'chalk'; import { toLower } from 'lodash'; import BackendClient from '../BackendClient'; @@ -48,7 +48,7 @@ export default class Refresh extends Command { alias: item.attributes.alias, accountId: item.attributes.accountId, provider: PROVIDERMAP[item.attributes.provider?.data?.id], - ...(item.attributes.config || {}), + ...item.attributes.config, } if(item.attributes.parent_account?.data) { @@ -62,13 +62,15 @@ export default class Refresh extends Command { accountId: parent.attributes.accountId, provider: PROVIDERMAP[parent.attributes.provider?.data?.id], parentAccount: null, - ...(parent.attributes.config || {}), + ...parent.attributes.config, } } getProvider(PROVIDERMAP[item.attributes.provider?.data?.id] as PROVIDER).generateConfig(account); accounts.push(account); - }; + } + +; setAccounts(accounts); } diff --git a/src/commands/scan.ts b/src/commands/scan.ts index 56859d4..a1ed8a9 100644 --- a/src/commands/scan.ts +++ b/src/commands/scan.ts @@ -12,17 +12,15 @@ const fixRegex = (query: any): any => { const queryObj = cloneDeep(query); if (queryObj !== null && typeof queryObj === 'object') { - for (let key in queryObj) { - if (queryObj.hasOwnProperty(key)) { - // If the property is an object, recursively call the function - if (typeof queryObj[key] === 'object') { + for (const key of Object.keys(queryObj)) { + // If the property is an object, recursively call the function + if (typeof queryObj[key] === 'object') { queryObj[key] = fixRegex(queryObj[key]); } - } } - if ('$regex' in queryObj && ('$options' in queryObj || queryObj['$options'] === undefined)) { - return new RegExp(queryObj['$regex'], queryObj['$options'] || ''); + if ('$regex' in queryObj && ('$options' in queryObj || queryObj.$options === undefined)) { + return new RegExp(queryObj.$regex, queryObj.$options || ''); } } else if (Array.isArray(queryObj)) { // Iterate through each element if obj is an array @@ -42,7 +40,7 @@ const getAssociatedProject = (projectAccountData: any, component: Component) => const filter = fixRegex(projectAccount.attributes.filter); const filteredData = [component.rawData].filter(sift(filter)); - if(filteredData.length) { + if(filteredData.length > 0) { return projectAccount.attributes.project.data; } } @@ -146,7 +144,7 @@ export default class Scan extends Command { }, pagination: { page: 1, - pageSize: 10000 + pageSize: 10_000 }, populate: '*' }); @@ -194,7 +192,7 @@ export default class Scan extends Command { // find project let projectId = defaultProject.id; - if(projectAccountsData.data.length) { + if(projectAccountsData.data.length > 0) { const associatedProject = getAssociatedProject(projectAccountsData.data, component); if(associatedProject) { projectId = associatedProject.id; @@ -208,7 +206,18 @@ export default class Scan extends Command { logAssociate(component.rawData); } - if(!existingServices[component.identifier]) { + if(existingServices[component.identifier]) { + await client.put(`components/${existingServices[component.identifier]}`, { + data: { + project: projectId, + module: moduleId, + module_version: moduleVersion ? moduleVersion.id : null, + source: COMPONENT_SOURCE.SCANNER, + raw_data: component.rawData, + archived: false + } + }) + } else { await client.post('components', { data: { @@ -232,29 +241,19 @@ export default class Scan extends Command { } }); iterator++ - } else { - await client.put(`components/${existingServices[component.identifier]}`, { - data: { - project: projectId, - module: moduleId, - module_version: moduleVersion ? moduleVersion.id : null, - source: COMPONENT_SOURCE.SCANNER, - raw_data: component.rawData, - archived: false - } - }) } + delete existingServices[component.identifier]; log('Done.', 'success'); } - if(Object.keys(existingServices).length) { + if(Object.keys(existingServices).length > 0) { log(`Found ${Object.keys(existingServices).length} stale components.`); log('Archiving...'); } - for(const key in existingServices) { + for (const key of Object.keys(existingServices)) { await client.put(`components/${existingServices[key]}`, { data: { archived: true diff --git a/src/commands/tf-bootstrap-backend.ts b/src/commands/tf-bootstrap-backend.ts index a60a859..917d93e 100644 --- a/src/commands/tf-bootstrap-backend.ts +++ b/src/commands/tf-bootstrap-backend.ts @@ -1,6 +1,6 @@ import { Command, Flags } from '@oclif/core'; import chalk from 'chalk'; -import path from 'path'; +import path from 'node:path'; import { bootstrapBackendFromConfig, loadMetaCloudConfigFromDir } from '../backend-bootstrap'; import OPClient from '../OPClient'; @@ -65,9 +65,10 @@ export default class TfBootstrapBackend extends Command { } else { this.log(chalk.gray(` backend: ${config.terraformBackend?.name}`)); } + this.log(''); - let token = flags.token; + let {token} = flags; if ((driver === 'terraform-cloud') && !token && process.env.META_CLIENT_NAME) { try { ({ tfToken: token } = await new OPClient().getVariables()); diff --git a/src/commands/validate-yaml.ts b/src/commands/validate-yaml.ts index 8fe4542..c18f2c2 100644 --- a/src/commands/validate-yaml.ts +++ b/src/commands/validate-yaml.ts @@ -1,4 +1,4 @@ -import path from 'path'; +import path from 'node:path'; import { Command, Flags } from '@oclif/core'; import chalk from 'chalk'; diff --git a/src/commands/validate.ts b/src/commands/validate.ts index 628693e..5135ac0 100644 --- a/src/commands/validate.ts +++ b/src/commands/validate.ts @@ -1,4 +1,4 @@ -import { Command, Flags } from '@oclif/core'; +import {Command} from '@oclif/core'; import { DIR_FLAG, diff --git a/src/driver-runtime.ts b/src/driver-runtime.ts index fa89656..dbfc1fe 100644 --- a/src/driver-runtime.ts +++ b/src/driver-runtime.ts @@ -1,6 +1,6 @@ -import fs from 'fs'; -import path from 'path'; -import { spawn } from 'child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { spawn } from 'node:child_process'; import chalk from 'chalk'; @@ -251,11 +251,11 @@ function errorNeedsInitRetry(output: string): boolean { } function shellQuote(arg: string): string { - if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(arg)) { + if (/^[\w%+,./:=@-]+$/.test(arg)) { return arg; } - return `'${arg.replace(/'/g, `'\\''`)}'`; + return `'${arg.replaceAll('\'', `'\\''`)}'`; } function formatCommandInvocation(command: CommandInvocation): string { @@ -381,8 +381,7 @@ function buildExecutionPlan(input: { commands.push({ binary: 'terragrunt', args: ['--working-dir', targetDir, 'run', '--all', '--', 'validate', ...extraArgs], - }); - commands.push({ + }, { binary: 'terragrunt', args: ['--working-dir', targetDir, 'run', '--all', '--', 'init', '-backend=false'], }); @@ -396,8 +395,7 @@ function buildExecutionPlan(input: { commands.push({ binary: 'terragrunt', args, - }); - commands.push({ + }, { binary: 'terragrunt', args: ['--working-dir', targetDir, 'run', '--all', '--', 'init'], }); @@ -408,8 +406,7 @@ function buildExecutionPlan(input: { commands.push({ binary: 'terragrunt', args: ['--working-dir', setupDir, 'validate', ...extraArgs], - }); - commands.push({ + }, { binary: 'terragrunt', args: ['--working-dir', setupDir, 'init', '-backend=false'], }); @@ -417,8 +414,7 @@ function buildExecutionPlan(input: { commands.push({ binary: 'terragrunt', args: ['--working-dir', setupDir, action, ...extraArgs], - }); - commands.push({ + }, { binary: 'terragrunt', args: ['--working-dir', setupDir, 'init'], }); @@ -445,8 +441,7 @@ function buildExecutionPlan(input: { binary: 'terraform', args: [action === 'validate' ? 'validate' : action, ...extraArgs], cwd: setupDir, - }); - commands.push({ + }, { binary: 'terraform', args: action === 'validate' ? ['init', '-backend=false'] : ['init'], cwd: setupDir, diff --git a/src/ide-schemas.ts b/src/ide-schemas.ts index c82d1f8..c4f98ca 100644 --- a/src/ide-schemas.ts +++ b/src/ide-schemas.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { MetaDriver } from './utils'; @@ -52,7 +52,7 @@ export function getBundledSchemaDir(): string { export function findVscodeSettingsDir(startDir: string): string { let dir = path.resolve(startDir); - while (true) { + for (;;) { if (fs.existsSync(path.join(dir, '.vscode'))) { return path.join(dir, '.vscode'); } @@ -115,12 +115,12 @@ export function buildYamlSchemaMappings( : path.join(settingsDir, 'schemas', 'metacloud'); const mappings: Record = { - [path.posix.join(schemaBase.replace(/\\/g, '/'), 'metacloud.schema.json')]: METACLOUD_GLOBS, + [path.posix.join(schemaBase.replaceAll('\\', '/'), 'metacloud.schema.json')]: METACLOUD_GLOBS, }; if (driver === 'terramate' || driver === 'terragrunt') { - mappings[path.posix.join(schemaBase.replace(/\\/g, '/'), 'shared-anchors.schema.json')] = SHARED_ANCHOR_GLOBS; - mappings[path.posix.join(schemaBase.replace(/\\/g, '/'), 'workspace.schema.json')] = WORKSPACE_GLOBS; + mappings[path.posix.join(schemaBase.replaceAll('\\', '/'), 'shared-anchors.schema.json')] = SHARED_ANCHOR_GLOBS; + mappings[path.posix.join(schemaBase.replaceAll('\\', '/'), 'workspace.schema.json')] = WORKSPACE_GLOBS; } return mappings; diff --git a/src/service.ts b/src/service.ts index a84ed90..0933e29 100644 --- a/src/service.ts +++ b/src/service.ts @@ -12,7 +12,7 @@ async function getModuleByIdentifier(identifier: string) { } }); - if(!data.data.length) { + if(data.data.length === 0) { return false; } @@ -36,7 +36,7 @@ async function getOrCreateModuleVersion(moduleId: number, version: string) { } }); - if(data.data.length) { + if(data.data.length > 0) { data.data[0]; } @@ -70,7 +70,7 @@ async function getOrCreateDefaultProject(clientId: number, clientName: string) { populate: '*' }); - if(data.data.length) { + if(data.data.length > 0) { return data.data[0]; } diff --git a/src/terraform-workspace.ts b/src/terraform-workspace.ts index a91aa68..5549bb3 100644 --- a/src/terraform-workspace.ts +++ b/src/terraform-workspace.ts @@ -1,11 +1,11 @@ -import { spawn } from 'child_process'; +import { spawn } from 'node:child_process'; import chalk from 'chalk'; import { formatCommandInvocation } from './driver-runtime'; import { assertTerraformWorkspace, - META_CLOUD_TF, + } from './workspace-context'; export type TerraformWorkspaceAction = 'init' | 'plan' | 'apply' | 'destroy'; @@ -80,8 +80,10 @@ async function runTerraformWorkspace(input: { } export { - META_CLOUD_TF, - assertTerraformWorkspace, + + collectTerraformPassthroughArgs, runTerraformWorkspace, }; + +export {META_CLOUD_TF, assertTerraformWorkspace} from './workspace-context'; \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index be8c5b3..ded9e5a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ -import os from 'os'; -import fs from 'fs'; -import path from 'path'; +import os from 'node:os'; +import fs from 'node:fs'; +import path from 'node:path'; import { parse, stringify } from 'yaml'; import { Provider } from './Provider'; import { PROVIDER, GIT_PROVIDER, Account } from './types'; @@ -55,45 +55,55 @@ function metaConfigToYamlData(config: MetaConfig): {[key: string]: unknown} { }; if (driver === 'terraform-cloud') { - data['terraform_cloud_org'] = config.tfCloudOrg; - data['terraform_cloud_workspace'] = config.tfCloudWorkspace; + data.terraform_cloud_org = config.tfCloudOrg; + data.terraform_cloud_workspace = config.tfCloudWorkspace; if (config.gitProvider) { - data['git_provider'] = config.gitProvider; + data.git_provider = config.gitProvider; } + if (config.gitOrg) { - data['git_org'] = config.gitOrg; + data.git_org = config.gitOrg; } + if (config.gitRepo) { - data['git_repo'] = config.gitRepo; + data.git_repo = config.gitRepo; } } - if (typeof config.tfAutoApply !== 'undefined') { - data['auto_apply'] = config.tfAutoApply; + if (config.tfAutoApply !== undefined) { + data.auto_apply = config.tfAutoApply; } + if (config.yamlDir) { - data['yaml_dir'] = config.yamlDir; + data.yaml_dir = config.yamlDir; } + if (config.rootDir) { - data['root_dir'] = config.rootDir; + data.root_dir = config.rootDir; } + if (config.targetDir) { - data['target_dir'] = config.targetDir; + data.target_dir = config.targetDir; } + if (config.handlerVersion) { - data['handler_version'] = config.handlerVersion; + data.handler_version = config.handlerVersion; } + if (config.terraformBackend) { - data['terraform_backend'] = config.terraformBackend; + data.terraform_backend = config.terraformBackend; } + if (config.linkingMode) { - data['linking_mode'] = config.linkingMode; + data.linking_mode = config.linkingMode; } - if (typeof config.mockInputsEnabled !== 'undefined') { - data['mock_inputs_enabled'] = config.mockInputsEnabled; + + if (config.mockInputsEnabled !== undefined) { + data.mock_inputs_enabled = config.mockInputsEnabled; } - if (typeof config.stackIdPrefix !== 'undefined') { - data['stack_id_prefix'] = config.stackIdPrefix; + + if (config.stackIdPrefix !== undefined) { + data.stack_id_prefix = config.stackIdPrefix; } return data; @@ -101,21 +111,21 @@ function metaConfigToYamlData(config: MetaConfig): {[key: string]: unknown} { function normalizeMetaCloudConfig(data: {[key: string]: any}): MetaConfig { return { - driver: (data['driver'] || 'terraform-cloud') as MetaDriver, - tfCloudOrg: data['terraform_cloud_org'], - tfCloudWorkspace: data['terraform_cloud_workspace'], - gitProvider: data['git_provider'], - gitOrg: data['git_org'], - gitRepo: data['git_repo'], - tfAutoApply: data['auto_apply'], - yamlDir: data['yaml_dir'], - rootDir: data['root_dir'], - targetDir: data['target_dir'], - handlerVersion: data['handler_version'], - terraformBackend: data['terraform_backend'], - linkingMode: data['linking_mode'], - mockInputsEnabled: data['mock_inputs_enabled'], - stackIdPrefix: data['stack_id_prefix'], + driver: (data.driver || 'terraform-cloud') as MetaDriver, + tfCloudOrg: data.terraform_cloud_org, + tfCloudWorkspace: data.terraform_cloud_workspace, + gitProvider: data.git_provider, + gitOrg: data.git_org, + gitRepo: data.git_repo, + tfAutoApply: data.auto_apply, + yamlDir: data.yaml_dir, + rootDir: data.root_dir, + targetDir: data.target_dir, + handlerVersion: data.handler_version, + terraformBackend: data.terraform_backend, + linkingMode: data.linking_mode, + mockInputsEnabled: data.mock_inputs_enabled, + stackIdPrefix: data.stack_id_prefix, }; } @@ -170,6 +180,7 @@ function renderBackendConfig(config?: TerraformBackendConfig): string { for (const [key, value] of Object.entries(config.configs)) { lines.push(` ${key} = ${JSON.stringify(value)}`); } + lines.push(' }'); } @@ -219,7 +230,7 @@ module "metacloud" { git_repo = var.git_repo git_token = var.git_token - auto_apply = ${typeof config.tfAutoApply !== 'undefined' ? config.tfAutoApply : true} + auto_apply = ${config.tfAutoApply === undefined ? true : config.tfAutoApply} aws = { access_key_id = var.access_key_id @@ -239,7 +250,7 @@ function generateTerramateTF(config: MetaConfig): string { yamldir = "${pathModuleDir(config.yamlDir || ".")}" targetdir = "${pathModuleDir(config.targetDir || "_terraform")}" -${renderBackendConfig(config.terraformBackend)}${config.linkingMode ? ` linking_mode = "${config.linkingMode}"\n` : ''}${typeof config.mockInputsEnabled !== 'undefined' ? ` mock_inputs_enabled = ${config.mockInputsEnabled}\n` : ''}${typeof config.stackIdPrefix !== 'undefined' && config.stackIdPrefix !== null ? ` stack_id_prefix = "${config.stackIdPrefix}"\n` : ''}}`; +${renderBackendConfig(config.terraformBackend)}${config.linkingMode ? ` linking_mode = "${config.linkingMode}"\n` : ''}${config.mockInputsEnabled === undefined ? '' : ` mock_inputs_enabled = ${config.mockInputsEnabled}\n`}${config.stackIdPrefix !== undefined && config.stackIdPrefix !== null ? ` stack_id_prefix = "${config.stackIdPrefix}"\n` : ''}}`; } function generateTerragruntTF(config: MetaConfig): string { @@ -296,6 +307,7 @@ function getAccount(accountId: string): Account | false { if(!account) { return false; } + return account; } @@ -303,12 +315,15 @@ function getProvider(provider: PROVIDER): Provider { if(provider === PROVIDER.AWS) { return new AWSProvider(); } + if(provider === PROVIDER.KUBERNETES) { return new KubernetesProvider(); } + if(provider === PROVIDER.GCP) { return new GCPProvider(); } + if(provider === PROVIDER.AZURE) { return new AzureProvider(); } @@ -338,7 +353,7 @@ function getMetaCloudConfig(cwd: string = process.cwd()): MetaConfig|false { return false; } - const yaml = fs.readFileSync(configPath, 'utf-8'); + const yaml = fs.readFileSync(configPath, 'utf8'); const data = parse(yaml); return normalizeMetaCloudConfig(data); diff --git a/src/workspace-context.ts b/src/workspace-context.ts index f42f7f2..34d4e94 100644 --- a/src/workspace-context.ts +++ b/src/workspace-context.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { detectSetupDirs, DRIVER_DEFAULT_DIRS } from './driver-runtime'; import { GIT_PROVIDER } from './types'; diff --git a/src/yaml-validation.ts b/src/yaml-validation.ts index 6296adf..c23922d 100644 --- a/src/yaml-validation.ts +++ b/src/yaml-validation.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { parse } from 'yaml'; @@ -32,7 +32,7 @@ export type ValidateYamlResult = { }; const YAML_PARSE_OPTIONS = { uniqueKeys: false } as const; -const LINKED_REF_PATTERN = /\$\{([^}]+)\}/g; +const LINKED_REF_PATTERN = /\${([^}]+)}/g; const SHARED_CONFIG_PATTERN = /(^|\/)_\.ya?ml$/; const WORKSPACE_FILE_PATTERN = /\.ya?ml$/; @@ -65,6 +65,7 @@ function discoverYamlFiles(yamlDir: string): string[] { if (shouldSkipDirectory(entry.name)) { continue; } + walk(fullPath); continue; } @@ -73,7 +74,7 @@ function discoverYamlFiles(yamlDir: string): string[] { continue; } - const relPath = path.relative(yamlDir, fullPath).replace(/\\/g, '/'); + const relPath = path.relative(yamlDir, fullPath).replaceAll('\\', '/'); if (relPath.includes('.terraform')) { continue; } @@ -162,7 +163,7 @@ function effectiveLinkedPaths(workspace: ParsedWorkspace, workspacePaths: Set): void { - if (value == null) { + if (value === null || value === undefined) { return; } @@ -170,6 +171,7 @@ function collectLinkedReferences(value: unknown, references: Set): void for (const match of value.matchAll(LINKED_REF_PATTERN)) { references.add(normalizeLinkedSetupName(match[1])); } + return; } @@ -177,6 +179,7 @@ function collectLinkedReferences(value: unknown, references: Set): void for (const item of value) { collectLinkedReferences(item, references); } + return; } @@ -188,7 +191,7 @@ function collectLinkedReferences(value: unknown, references: Set): void } function parseLinkedWorkspaces(raw: Record): string[] { - const linked = raw['linked_workspaces']; + const linked = raw.linked_workspaces; if (!Array.isArray(linked)) { return []; } @@ -219,6 +222,7 @@ function detectLinkedCycles(workspaces: ParsedWorkspace[], workspacePaths: Set= 0) { cycles.push([...stack.slice(cycleStart), node]); } + return; } @@ -294,8 +298,7 @@ function validateMetaCloudFile(metacloudPath: string): ValidationIssue[] { } } - if (driver === 'terramate' || driver === 'terragrunt') { - if (!config.terraformBackend?.name) { + if ((driver === 'terramate' || driver === 'terragrunt') && !config.terraformBackend?.name) { issues.push({ severity: 'warning', file: metacloudPath, @@ -303,7 +306,6 @@ function validateMetaCloudFile(metacloudPath: string): ValidationIssue[] { message: `${driver} driver should define terraform_backend.name in metacloud.yaml.`, }); } - } if (driver === 'terramate' && config.linkingMode && !['remote_state', 'terramate_outputs_sharing'].includes(config.linkingMode)) { issues.push({ @@ -349,15 +351,15 @@ function parseWorkspaceFile(yamlDir: string, relFile: string): { workspace?: Par return { issues }; } - const source = raw['source']; - const version = raw['version']; - const hasSource = source != null && source !== ''; - const hasVersion = version != null && version !== ''; + const {source} = raw; + const {version} = raw; + const hasSource = source !== null && source !== undefined && source !== ''; + const hasVersion = version !== null && version !== undefined && version !== ''; const linkedWorkspaces = parseLinkedWorkspaces(raw); const linkedReferences = new Set(); - collectLinkedReferences(raw['variables'], linkedReferences); - collectLinkedReferences(raw['providers'], linkedReferences); + collectLinkedReferences(raw.variables, linkedReferences); + collectLinkedReferences(raw.providers, linkedReferences); if (!hasSource && !hasVersion) { if (linkedWorkspaces.length > 0 || linkedReferences.size > 0) { @@ -368,6 +370,7 @@ function parseWorkspaceFile(yamlDir: string, relFile: string): { workspace?: Par message: 'Linked setup references require source (and version for registry modules) after shared-config merge.', }); } + return { issues }; } diff --git a/test/command-groups.test.ts b/test/command-groups.test.ts index 37a14a6..26844a5 100644 --- a/test/command-groups.test.ts +++ b/test/command-groups.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { COMMAND_GROUPS, GROUPED_COMMAND_IDS } from '../src/command-groups'; diff --git a/test/driver-runtime.test.ts b/test/driver-runtime.test.ts index 33882d0..41028a4 100644 --- a/test/driver-runtime.test.ts +++ b/test/driver-runtime.test.ts @@ -1,6 +1,6 @@ -import fs from 'fs'; -import os from 'os'; -import path from 'path'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; import { expect } from 'chai'; diff --git a/test/examples.test.ts b/test/examples.test.ts index 2f4a6b0..0242356 100644 --- a/test/examples.test.ts +++ b/test/examples.test.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { expect } from 'chai'; import { parse } from 'yaml'; @@ -155,13 +155,13 @@ describe('examples', () => { it(`keeps ${driver}/${variant} metacloud.yaml in sync with buildMetaCloudConfigContent`, () => { const expected = buildMetaCloudConfigContent(config); - const actual = fs.readFileSync(path.join(exampleDir, 'metacloud.yaml'), 'utf-8'); + const actual = fs.readFileSync(path.join(exampleDir, 'metacloud.yaml'), 'utf8'); expect(actual).to.equal(expected); }); it(`parses ${driver}/${variant} metacloud.yaml into MetaConfig`, () => { - const yaml = fs.readFileSync(path.join(exampleDir, 'metacloud.yaml'), 'utf-8'); + const yaml = fs.readFileSync(path.join(exampleDir, 'metacloud.yaml'), 'utf8'); const parsed = normalizeMetaCloudConfig(parse(yaml) as {[key: string]: unknown}); expect(parsed.driver || 'terraform-cloud').to.equal(config.driver || 'terraform-cloud'); @@ -172,6 +172,7 @@ describe('examples', () => { expect(parsed.gitOrg).to.be.undefined; expect(parsed.gitRepo).to.be.undefined; } + expect(parsed.yamlDir).to.be.undefined; }); } @@ -179,7 +180,7 @@ describe('examples', () => { it('includes multi-group linked modules in terramate basic-s3-backend example', () => { const moduleC = fs.readFileSync( path.join(examplesDir, 'terramate', 'basic-s3-backend', 'group-1', 'module-c.yaml'), - 'utf-8', + 'utf8', ); expect(moduleC).to.contain('${group-0/module-a["first-string-variable"]}'); @@ -189,7 +190,7 @@ describe('examples', () => { it('includes multi-group linked modules in terragrunt basic-s3-backend example', () => { const moduleC = fs.readFileSync( path.join(examplesDir, 'terragrunt', 'basic-s3-backend', 'group-1', 'module-c.yaml'), - 'utf-8', + 'utf8', ); expect(moduleC).to.contain('linked_workspaces:'); diff --git a/test/ide-schemas.test.ts b/test/ide-schemas.test.ts index 1b3aca6..17d7e62 100644 --- a/test/ide-schemas.test.ts +++ b/test/ide-schemas.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { buildYamlSchemaMappings, diff --git a/test/terraform-workspace.test.ts b/test/terraform-workspace.test.ts index 323031a..709f68e 100644 --- a/test/terraform-workspace.test.ts +++ b/test/terraform-workspace.test.ts @@ -1,6 +1,6 @@ -import fs from 'fs'; -import os from 'os'; -import path from 'path'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; import { expect } from 'chai'; diff --git a/test/validate-yaml.test.ts b/test/validate-yaml.test.ts index 068f3aa..b1ea645 100644 --- a/test/validate-yaml.test.ts +++ b/test/validate-yaml.test.ts @@ -1,6 +1,6 @@ -import fs from 'fs'; -import os from 'os'; -import path from 'path'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; import { expect } from 'chai'; diff --git a/test/workspace-context.test.ts b/test/workspace-context.test.ts index 91328e9..3dd713c 100644 --- a/test/workspace-context.test.ts +++ b/test/workspace-context.test.ts @@ -1,6 +1,6 @@ -import fs from 'fs'; -import os from 'os'; -import path from 'path'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; import { expect } from 'chai';