diff --git a/packages/cli/README.md b/packages/cli/README.md index 8e26f382..a3ecda7e 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -4,6 +4,7 @@ [![npm version](https://img.shields.io/npm/v/@shelve/cli?color=black)](https://npmjs.com/package/@shelve/cli) [![npm downloads](https://img.shields.io/npm/dm/@shelve/cli?color=black)](https://npmjs.com/package/@shelve/cli) +[![license](https://img.shields.io/github/license/HugoRCD/shelve?color=black)](https://github.com/HugoRCD/shelve/blob/main/LICENSE) @@ -19,7 +20,7 @@ npm install -g @shelve/cli ## Configuration -Configuration is loaded by [unjs/c12](https://github.com/unjs/c12) from cwd. You can use either `shelve.config.json`, `shelve.config.{ts,js,mjs,cjs}` or use the `shelve` field in `package.json`. +Configuration is loaded by [unjs/c12](https://github.com/unjs/c12) from cwd. You can use either `shelve.config.json`, `shelve.config.{ts,js,mjs,cjs}`, but running the CLI without any configuration will create a `shelve.config.json` file. You have the option to create a `shelve.config.ts` file to enable type checking and autocompletion. The file should contain the following content: ```ts title="shelve.config.ts" @@ -107,6 +108,6 @@ Made by [@HugoRCD](https://github.com/HugoRCD) and [community](https://github.co --- -_🤖 auto updated with [automd](https://automd.unjs.io) (last updated: Fri Sep 13 2024)_ +_🤖 auto updated with [automd](https://automd.unjs.io) (last updated: Fri Sep 20 2024)_ diff --git a/packages/cli/package.json b/packages/cli/package.json index e79df4d7..0290aafa 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@shelve/cli", - "version": "2.2.0", + "version": "2.2.1", "description": "The command-line interface for Shelve", "homepage": "https://shelve.hrcd.fr", "bugs": { diff --git a/packages/cli/src/commands/pull.ts b/packages/cli/src/commands/pull.ts index e8e3536f..30aa445d 100644 --- a/packages/cli/src/commands/pull.ts +++ b/packages/cli/src/commands/pull.ts @@ -39,7 +39,8 @@ export function pullCommand(program: Command): void { const projectData = await getProjectByName(project) const variables = await getEnvVariables(projectData.id, environment) - await createEnvFile(pullMethod, envFileName, variables) + await createEnvFile({ method: pullMethod, envFileName, variables }) outro(`Successfully pulled variable from ${environment} environment`) + process.exit(0) }) } diff --git a/packages/cli/src/commands/push.ts b/packages/cli/src/commands/push.ts index c1860c15..645216c9 100644 --- a/packages/cli/src/commands/push.ts +++ b/packages/cli/src/commands/push.ts @@ -40,7 +40,8 @@ export function pushCommand(program: Command): void { const projectData = await getProjectByName(project) const variables = await getEnvFile() - await pushEnvFile(variables, projectData.id, environment) + await pushEnvFile({ variables, projectId: projectData.id, environment }) outro(`Successfully pushed variable to ${environment} environment`) + process.exit(0) }) } diff --git a/packages/cli/src/utils/api.ts b/packages/cli/src/utils/api.ts index 52f555a0..b957da9a 100644 --- a/packages/cli/src/utils/api.ts +++ b/packages/cli/src/utils/api.ts @@ -1,10 +1,30 @@ import { ofetch } from 'ofetch' +import { isCancel, password } from '@clack/prompts' import { getConfig } from './config' +import { mergeEnvFile } from './env' import { onCancel } from './index' +async function getToken(): Promise { + const token = await password({ + message: 'Please provide a valid token (you can generate one on https://shelve.hrcd.fr/app/tokens)', + validate(value) { + if (value.length === 0) return `Value is required!` + }, + }) + + if (isCancel(token)) onCancel('Operation cancelled.') + + await mergeEnvFile([{ key: 'SHELVE_TOKEN', value: token }]) + + return token +} + export async function useApi(): Promise { const { config } = await getConfig() - const { token, url } = config + const { url } = config + let { token } = config + + if (!token) token = await getToken() const sanitizedUrl = url.replace(/\/+$/, '') // remove trailing slash const baseURL = `${sanitizedUrl}/api` diff --git a/packages/cli/src/utils/config.ts b/packages/cli/src/utils/config.ts index ef01dffa..674e5f24 100644 --- a/packages/cli/src/utils/config.ts +++ b/packages/cli/src/utils/config.ts @@ -4,6 +4,7 @@ import { loadConfig, setupDotenv, type ConfigLayer } from 'c12' import consola from 'consola' import { SHELVE_JSON_SCHEMA, ShelveConfig } from '@shelve/types' import { getProjects } from './project' +import { getKeyValue } from './env' import { onCancel } from './index' export async function createShelveConfig(projectName?: string): Promise { @@ -89,6 +90,9 @@ export async function loadShelveConfig(): Promise { process.exit(0) } + const envToken = await getKeyValue('SHELVE_TOKEN') + if (envToken) config.token = envToken + if (!config.token) { consola.error('You need to provide a token') process.exit(0) diff --git a/packages/cli/src/utils/env.ts b/packages/cli/src/utils/env.ts index 3017029e..de88e82a 100644 --- a/packages/cli/src/utils/env.ts +++ b/packages/cli/src/utils/env.ts @@ -1,7 +1,7 @@ import fs from 'fs' -import type { Env, Environment, VariablesCreateInput } from '@shelve/types' +import type { CreateEnvFileInput, Env, PushEnvFileInput, VariablesCreateInput } from '@shelve/types' import { spinner } from '@clack/prompts' -import { loadShelveConfig } from './config' +import { getConfig, loadShelveConfig } from './config' import { useApi } from './api' import { onCancel } from './index' @@ -11,8 +11,20 @@ export function isEnvFileExist(envFileName: string): boolean { return fs.existsSync(envFileName) } +export async function getKeyValue(key: string): Promise { + const { config } = await getConfig() + const { envFileName } = config + const envFile = await getEnvFile() + const value = envFile.find((item) => item.key === key)?.value + if (!value) { + onCancel(`Key ${key} not found in ${envFileName}`) + } + return value +} + export async function mergeEnvFile(variables: Env[] = []): Promise { - const { envFileName } = await loadShelveConfig() + const { config } = await getConfig() + const { envFileName } = config s.start(`Merging ${envFileName} file`) const envFile = await getEnvFile() envFile.push(...variables) @@ -22,10 +34,11 @@ export async function mergeEnvFile(variables: Env[] = []): Promise { s.stop(`Merging ${envFileName} file`) } -export async function createEnvFile(pullMethod: string, envFileName: string, variables: Env[] = []): Promise { +export async function createEnvFile(input: CreateEnvFileInput): Promise { + const { method, envFileName, variables } = input const envFileExist = isEnvFileExist(envFileName) try { - if (envFileExist && pullMethod === 'merge') { + if (envFileExist && method === 'merge') { await mergeEnvFile(variables) return } @@ -41,11 +54,13 @@ export async function createEnvFile(pullMethod: string, envFileName: string, var } export async function getEnvFile(): Promise { - const { envFileName } = await loadShelveConfig() + const { config } = await getConfig() + const { envFileName } = config const isExist = fs.existsSync(envFileName) if (isExist) { const envFile = fs.readFileSync(envFileName, 'utf8') const envFileContent = envFile.split('\n').filter((item) => item && !item.startsWith('#')).join('\n') + if (!envFileContent) return [] return envFileContent.split('\n').map((item) => { const [key, value] = item.split('=') if (!key || !value) { @@ -70,7 +85,8 @@ export async function getEnvVariables(projectId: number, environment: string): P } } -export async function pushEnvFile(variables: Env[], projectId: number, environment: Environment): Promise { +export async function pushEnvFile(input: PushEnvFileInput): Promise { + const { variables, projectId, environment } = input const api = await useApi() s.start('Pushing variables') diff --git a/packages/types/src/Cli.ts b/packages/types/src/Cli.ts index 3289f3f5..2673b435 100644 --- a/packages/types/src/Cli.ts +++ b/packages/types/src/Cli.ts @@ -1,3 +1,5 @@ +import { Env, Environment } from './Variables' + export const SHELVE_JSON_SCHEMA = 'https://raw.githubusercontent.com/HugoRCD/shelve/main/packages/types/shelveConfigSchema.json' export type ShelveConfig = { @@ -47,3 +49,39 @@ export type ShelveConfig = { * */ envFileName: string } + +export type CreateEnvFileInput = { + /** + * The method to use for .env file (overwrite or append) + * Overwrite will replace all existing variables in Shelve app with the ones in the .env file + * Merge will append the .env file to the existing variables in Shelve app + * + * @default 'overwrite' + * */ + method: 'overwrite' | 'merge' + /** + * Name of your env file + * + * @default '.env' + * */ + envFileName: string + /** + * The variables to create in the .env file + * */ + variables: Env[] +} + +export type PushEnvFileInput = { + /** + * The variables to push to Shelve + * */ + variables: Env[] + /** + * The project ID + * */ + projectId: number + /** + * The environment to push the variables to + * */ + environment: Environment +}