Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/nice-pugs-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@cipherstash/stack-forge": minor
"@cipherstash/stack": minor
---

Improved CLI setup and initialization commands.
31 changes: 11 additions & 20 deletions packages/stack-forge/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,30 +110,21 @@ export async function setupCommand(options: SetupOptions = {}) {
process.exit(0)
}

// 3. Collect database URL
const databaseUrl = await p.text({
message: 'What is your database URL?',
placeholder: 'postgresql://user:password@localhost:5432/mydb',
defaultValue: process.env.DATABASE_URL,
initialValue: process.env.DATABASE_URL,
validate(value) {
if (!value || value.trim().length === 0) {
return 'Database URL is required.'
}
},
})

if (p.isCancel(databaseUrl)) {
p.cancel('Setup cancelled.')
process.exit(0)
}

// 4. Generate stash.config.ts
// 3. Generate stash.config.ts
const configContent = generateConfig(clientPath)
writeFileSync(configPath, configContent, 'utf-8')
p.log.success(`Created ${CONFIG_FILENAME}`)

// 5. Install EQL extensions
// 4. Install EQL extensions (only if DATABASE_URL is available)
if (!process.env.DATABASE_URL) {
p.note(
'Set DATABASE_URL in your environment, then run:\n npx stash-forge install',
'DATABASE_URL not set',
)
p.outro('CipherStash Forge setup complete!')
return
}

const shouldInstall = await p.confirm({
message: 'Install EQL extensions in your database now?',
initialValue: true,
Expand Down
96 changes: 58 additions & 38 deletions packages/stack/src/bin/commands/init/steps/install-forge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,70 @@ import {
detectPackageManager,
devInstallCommand,
isPackageInstalled,
prodInstallCommand,
} from '../utils.js'

const STACK_PACKAGE = '@cipherstash/stack'
const FORGE_PACKAGE = '@cipherstash/stack-forge'

/**
* Installs a package if not already present.
* Returns true if installed (or already was), false if skipped or failed.
*/
async function installIfNeeded(
packageName: string,
buildCommand: (pm: ReturnType<typeof detectPackageManager>, pkg: string) => string,
depLabel: string,
): Promise<boolean> {
if (isPackageInstalled(packageName)) {
p.log.success(`${packageName} is already installed.`)
return true
}

const pm = detectPackageManager()
const cmd = buildCommand(pm, packageName)

const install = await p.confirm({
message: `Install ${packageName} as a ${depLabel} dependency? (${cmd})`,
})

if (p.isCancel(install)) throw new CancelledError()

if (!install) {
p.log.info(`Skipping ${packageName} installation.`)
p.note(
`You can install it manually later:\n ${cmd}`,
'Manual Installation',
)
return false
}

const s = p.spinner()
s.start(`Installing ${packageName}...`)

try {
execSync(cmd, { cwd: process.cwd(), stdio: 'pipe' })
s.stop(`${packageName} installed successfully`)
return true
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
s.stop(`${packageName} installation failed`)
p.log.error(message)
p.note(`You can install it manually:\n ${cmd}`, 'Manual Installation')
return false
}
}

export const installForgeStep: InitStep = {
id: 'install-forge',
name: 'Install stack-forge',
name: 'Install stack dependencies',
async run(state: InitState, _provider: InitProvider): Promise<InitState> {
if (isPackageInstalled(FORGE_PACKAGE)) {
p.log.success(`${FORGE_PACKAGE} is already installed.`)
return { ...state, forgeInstalled: true }
}

const pm = detectPackageManager()
const cmd = devInstallCommand(pm, FORGE_PACKAGE)

const install = await p.confirm({
message: `Install ${FORGE_PACKAGE} as a dev dependency? (${cmd})`,
})

if (p.isCancel(install)) throw new CancelledError()

if (!install) {
p.log.info(`Skipping ${FORGE_PACKAGE} installation.`)
p.note(
`You can install it manually later:\n ${cmd}`,
'Manual Installation',
)
return { ...state, forgeInstalled: false }
}

const s = p.spinner()
s.start(`Installing ${FORGE_PACKAGE}...`)

try {
execSync(cmd, { cwd: process.cwd(), stdio: 'pipe' })
s.stop(`${FORGE_PACKAGE} installed successfully`)
return { ...state, forgeInstalled: true }
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
s.stop(`${FORGE_PACKAGE} installation failed`)
p.log.error(message)
p.note(`You can install it manually:\n ${cmd}`, 'Manual Installation')
return { ...state, forgeInstalled: false }
}
// Install @cipherstash/stack as a production dependency
const stackInstalled = await installIfNeeded(STACK_PACKAGE, prodInstallCommand, 'production')

// Install @cipherstash/stack-forge as a dev dependency
const forgeInstalled = await installIfNeeded(FORGE_PACKAGE, devInstallCommand, 'dev')

return { ...state, forgeInstalled, stackInstalled }
},
}
1 change: 1 addition & 0 deletions packages/stack/src/bin/commands/init/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface InitState {
connectionMethod?: ConnectionMethod
clientFilePath?: string
schemaGenerated?: boolean
stackInstalled?: boolean
forgeInstalled?: boolean
}

Expand Down
17 changes: 17 additions & 0 deletions packages/stack/src/bin/commands/init/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ export function detectPackageManager(): 'npm' | 'pnpm' | 'yarn' | 'bun' {
return 'npm'
}

/** Returns the install command for adding a production dependency with the given package manager. */
export function prodInstallCommand(
pm: ReturnType<typeof detectPackageManager>,
packageName: string,
): string {
switch (pm) {
case 'bun':
return `bun add ${packageName}`
case 'pnpm':
return `pnpm add ${packageName}`
case 'yarn':
return `yarn add ${packageName}`
case 'npm':
return `npm install ${packageName}`
}
}

/** Returns the install command for adding a dev dependency with the given package manager. */
export function devInstallCommand(
pm: ReturnType<typeof detectPackageManager>,
Expand Down
Loading