Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: updates to typescript w/n the env command #6905

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
224 changes: 224 additions & 0 deletions src/commands/api-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import type { NetlifyAPI } from 'netlify'

import { Context } from './types.d.ts'

type ApiContext = Context | 'branch'

// Define the structure for the 'updated_by' field
interface UpdatedBy {
// Add specific properties here if known
// For now, we'll keep it generic
id: string;
full_name: string;
email: string;
avatar_url: string;
}

export type Value = Pick<EnvVarValue, 'value' | 'context' | 'context_parameter'>
// Define the structure for each item in the array

interface EnvVarValue {
value: string,
context: ApiContext,
context_parameter?: string,
id?: string,
}

export interface EnvVar {
key: string;
scopes: Scope[];
values: EnvVarValue[];
is_secret?: boolean;
updated_at?: string;
updated_by?: UpdatedBy;
}

interface GetEnvParams {
accountId: string,
siteId?: string,
context?: Context,
scope?: EnvironmentVariableScope
}

interface DeleteEnvVarValueParams {
accountId: string,
key: string,
id?: string,
siteId?: string
}

interface SetEnvVarValueBody {
context: string,
value: string,
contextParameter?: string,
}

interface SetEnvVarValueParams {
accountId: string,
key: string,
siteId?: string,
body: SetEnvVarValueBody
}

interface UpdateEnvVarBody {
key: string,
scopes: string[],
values: EnvVar[]
is_secret: boolean
}

interface UpdateEnvVarParams {
accountId: string,
key: string,
siteId?: string
body: EnvVar
}

interface createEnvVarParams {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
interface createEnvVarParams {
interface CreateEnvVarParams {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been updated

accountId: string,
key?: string,
siteId?: string,
body: EnvVar[]
}

// Top-Level Interface
interface SiteInfo {
id: string;
state: string;
plan: string;
name: string;
custom_domain: string | null;
domain_aliases: string[];
branch_deploy_custom_domain: string | null;
deploy_preview_custom_domain: string | null;
password: string | null;
notification_email: string | null;
url: string;
ssl_url: string;
admin_url: string;
screenshot_url: string;
created_at: string;
updated_at: string;
user_id: string;
session_id: string;
ssl: boolean;
force_ssl: boolean | null;
managed_dns: boolean;
deploy_url: string;
published_deploy: PublishedDeploy;
account_id: string;
account_name: string;
account_slug: string;
git_provider?: string;
deploy_hook: string;
capabilities: Capabilities;
processing_settings: ProcessingSettings;
build_settings: BuildSettings;
id_domain: string;
default_hooks_data?: DefaultHooksData;
build_image: string;
prerender: string | null;
functions_region: string;
feature_flags: FeatureFlags;
}

// Published Deploy Interface
interface PublishedDeploy {
id: string;
site_id: string;
user_id: string;
build_id: string;
state: string;
name: string;
url: string;
ssl_url: string;
admin_url: string;
deploy_url: string;
deploy_ssl_url: string;
screenshot_url: string;
review_id: number | null;
draft: boolean;
required: string[];
required_functions: string[];
error_message: string;
branch: string;
commit_ref: string;
commit_url: string;
skipped: boolean | null;
created_at: string;
updated_at: string;
published_at: string;
title: string;
context: string;
locked: boolean | null;
review_url: string | null;
framework: string;
function_schedules: FunctionSchedule[] | [];
}

// Function Schedule Interface
interface FunctionSchedule {
name: string;
cron: string;
}

// Capabilities Interface
interface Capabilities {
[key: string]: Record<string, unknown>;
}

// Processing Settings Interface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need all these comments, they're all basically just saying the name of the interfaces are

interface ProcessingSettings {
html: HTMLProcessingSettings;
}

// HTML Processing Settings Interface
interface HTMLProcessingSettings {
pretty_urls: boolean;
}

// Build Settings Interface
interface BuildSettings {
id: number;
provider: string;
deploy_key_id: string;
repo_path: string;
repo_branch: string;
dir: string;
functions_dir: string;
cmd: string;
allowed_branches: string[];
public_repo: boolean;
private_logs: boolean;
repo_url: string;
env: EnvVariables;
installation_id: number;
stop_builds: boolean;
}

// Environment Variables Interface
interface EnvVariables {
[key: string]: string;
}

// Default Hooks Data Interface
interface DefaultHooksData {
access_token: string;
}

interface GetSiteParams {
siteId?: string,
feature_flags?: string
site_id?: string
}

export interface ExtendedNetlifyAPI extends NetlifyAPI {
getEnvVar(params: GetEnvVarParams): Promise<EnvVar>
getEnvVars( params: GetEnvParams): Promise<EnvVar[]>
deleteEnvVarValue( params: DeleteEnvVarValueParams ): Promise<void>
setEnvVarValue( params: SetEnvVarValueParams): Promise<EnvVar>
deleteEnvVar(params: DeleteEnvVarValueParams): Promise<void>
updateEnvVar(params: UpdateEnvVarParams): Promise<EnvVar>
createEnvVars(params: createEnvVarParams): Promise<EnvVar[]>
getSite(params: GetSiteParams): Promise<SiteInfo>
}
4 changes: 3 additions & 1 deletion src/commands/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const apiCommand = async (apiMethod: string, options: OptionValues, comma
const apiResponse = await api[apiMethod](payload)
logJson(apiResponse)
} catch (error_) {
error(error_)
if (error_ instanceof Error || typeof error_ === 'string') {
error(error_)
}
}
}
3 changes: 1 addition & 2 deletions src/commands/base-command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { isCI } from 'ci-info'

import { existsSync } from 'fs'
import { join, relative, resolve } from 'path'
import process from 'process'
Expand All @@ -8,6 +6,7 @@ import { format } from 'util'
import { DefaultLogger, Project } from '@netlify/build-info'
import { NodeFS, NoopLogger } from '@netlify/build-info/node'
import { resolveConfig } from '@netlify/config'
import { isCI } from 'ci-info'
import { Command, Help, Option } from 'commander'
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'debu... Remove this comment to see the full error message
import debug from 'debug'
Expand Down
43 changes: 24 additions & 19 deletions src/commands/env/env-clone.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { OptionValues } from 'commander'

import { chalk, log, error as logError } from '../../utils/command-helpers.js'
import { isAPIEnvError } from '../../utils/env/index.js'
import type { ExtendedNetlifyAPI } from '../api-types.d.ts'
import BaseCommand from '../base-command.js'
import { $TSFixMe } from '../types.js'

import { CloneEnvParams, EnvCloneOptions } from './types.js'

// @ts-expect-error TS(7006) FIXME: Parameter 'api' implicitly has an 'any' type.
const safeGetSite = async (api, siteId) => {
const safeGetSite = async (api: ExtendedNetlifyAPI, siteId: string) => {
try {
const data = await api.getSite({ siteId })
return { data }
Expand All @@ -17,14 +19,12 @@ const safeGetSite = async (api, siteId) => {
* Copies the env from a site configured with Envelope to a different site configured with Envelope
* @returns {Promise<boolean>}
*/
// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
const cloneEnvVars = async ({ api, siteFrom, siteTo }): Promise<boolean> => {
const cloneEnvVars = async ({ api, siteFrom, siteTo }: CloneEnvParams): Promise<boolean> => {
const [envelopeFrom, envelopeTo] = await Promise.all([
api.getEnvVars({ accountId: siteFrom.account_slug, siteId: siteFrom.id }),
api.getEnvVars({ accountId: siteTo.account_slug, siteId: siteTo.id }),
])

// @ts-expect-error TS(7031) FIXME: Binding element 'key' implicitly has an 'any' type... Remove this comment to see the full error message
const keysFrom = envelopeFrom.map(({ key }) => key)

if (keysFrom.length === 0) {
Expand All @@ -34,24 +34,21 @@ const cloneEnvVars = async ({ api, siteFrom, siteTo }): Promise<boolean> => {

const accountId = siteTo.account_slug
const siteId = siteTo.id
// @ts-expect-error TS(7031) FIXME: Binding element 'key' implicitly has an 'any' type... Remove this comment to see the full error message
const envVarsToDelete = envelopeTo.filter(({ key }) => keysFrom.includes(key))
// delete marked env vars in parallel
// @ts-expect-error TS(7031) FIXME: Binding element 'key' implicitly has an 'any' type... Remove this comment to see the full error message
await Promise.all(envVarsToDelete.map(({ key }) => api.deleteEnvVar({ accountId, siteId, key })))

// hit create endpoint
try {
await api.createEnvVars({ accountId, siteId, body: envelopeFrom })
} catch (error) {
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
throw error.json ? error.json.msg : error
} catch (error: unknown) {
if (isAPIEnvError(error)) throw error.json ? error.json.msg : error
}

return true
}

export const envClone = async (options: OptionValues, command: BaseCommand) => {
export const envClone = async (options: EnvCloneOptions, command: BaseCommand) => {
const { api, site } = command.netlify

if (!site.id && !options.from) {
Expand All @@ -61,8 +58,14 @@ export const envClone = async (options: OptionValues, command: BaseCommand) => {
return false
}

const sourceId = options.from || site.id

if (!sourceId) {
throw new Error('Site ID is required')
}

const siteId = {
from: options.from || site.id,
from: sourceId,
to: options.to,
}

Expand All @@ -81,13 +84,15 @@ export const envClone = async (options: OptionValues, command: BaseCommand) => {
return false
}

const success = await cloneEnvVars({ api, siteFrom, siteTo })
if (siteFrom && siteTo) {
const success = await cloneEnvVars({ api, siteFrom, siteTo })

if (!success) {
return false
}
if (!success) {
return false
}

log(`Successfully cloned environment variables from ${chalk.green(siteFrom.name)} to ${chalk.green(siteTo.name)}`)
log(`Successfully cloned environment variables from ${chalk.green(siteFrom.name)} to ${chalk.green(siteTo.name)}`)
}

return true
}
9 changes: 5 additions & 4 deletions src/commands/env/env-get.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { OptionValues } from 'commander'

import { chalk, log, logJson } from '../../utils/command-helpers.js'
import { AVAILABLE_CONTEXTS, getEnvelopeEnv } from '../../utils/env/index.js'
import BaseCommand from '../base-command.js'

export const envGet = async (name: string, options: OptionValues, command: BaseCommand) => {
import { EnvOptions } from './types.js'

export const envGet = async (name: string, options: EnvOptions, command: BaseCommand) => {
const { context, scope } = options
const { api, cachedConfig, site } = command.netlify
const siteId = site.id
Expand All @@ -15,6 +15,7 @@ export const envGet = async (name: string, options: OptionValues, command: BaseC
}

const { siteInfo } = cachedConfig

const env = await getEnvelopeEnv({ api, context, env: cachedConfig.env, key: name, scope, siteInfo })

const { value } = env[name] || {}
Expand All @@ -26,7 +27,7 @@ export const envGet = async (name: string, options: OptionValues, command: BaseC
}

if (!value) {
const contextType = AVAILABLE_CONTEXTS.includes(context) ? 'context' : 'branch'
const contextType = context === undefined ? 'branch' : AVAILABLE_CONTEXTS.includes(context)
const withContext = `in the ${chalk.magenta(context)} ${contextType}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this changed? It now returns a boolean instead of a string

const withScope = scope === 'any' ? '' : ` and the ${chalk.magenta(scope)} scope`
log(`No value set ${withContext}${withScope} for environment variable ${chalk.yellow(name)}`)
Expand Down
Loading
Loading