Skip to content

Commit 68626b3

Browse files
authored
Merge pull request #323 from cipherstash/cli-fix-1
chore: cli cleanup
2 parents 8020f28 + 5245cd7 commit 68626b3

File tree

5 files changed

+93
-58
lines changed

5 files changed

+93
-58
lines changed

.changeset/nice-pugs-retire.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@cipherstash/stack-forge": minor
3+
"@cipherstash/stack": minor
4+
---
5+
6+
Improved CLI setup and initialization commands.

packages/stack-forge/src/commands/init.ts

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -110,30 +110,21 @@ export async function setupCommand(options: SetupOptions = {}) {
110110
process.exit(0)
111111
}
112112

113-
// 3. Collect database URL
114-
const databaseUrl = await p.text({
115-
message: 'What is your database URL?',
116-
placeholder: 'postgresql://user:password@localhost:5432/mydb',
117-
defaultValue: process.env.DATABASE_URL,
118-
initialValue: process.env.DATABASE_URL,
119-
validate(value) {
120-
if (!value || value.trim().length === 0) {
121-
return 'Database URL is required.'
122-
}
123-
},
124-
})
125-
126-
if (p.isCancel(databaseUrl)) {
127-
p.cancel('Setup cancelled.')
128-
process.exit(0)
129-
}
130-
131-
// 4. Generate stash.config.ts
113+
// 3. Generate stash.config.ts
132114
const configContent = generateConfig(clientPath)
133115
writeFileSync(configPath, configContent, 'utf-8')
134116
p.log.success(`Created ${CONFIG_FILENAME}`)
135117

136-
// 5. Install EQL extensions
118+
// 4. Install EQL extensions (only if DATABASE_URL is available)
119+
if (!process.env.DATABASE_URL) {
120+
p.note(
121+
'Set DATABASE_URL in your environment, then run:\n npx stash-forge install',
122+
'DATABASE_URL not set',
123+
)
124+
p.outro('CipherStash Forge setup complete!')
125+
return
126+
}
127+
137128
const shouldInstall = await p.confirm({
138129
message: 'Install EQL extensions in your database now?',
139130
initialValue: true,

packages/stack/src/bin/commands/init/steps/install-forge.ts

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,70 @@ import {
66
detectPackageManager,
77
devInstallCommand,
88
isPackageInstalled,
9+
prodInstallCommand,
910
} from '../utils.js'
1011

12+
const STACK_PACKAGE = '@cipherstash/stack'
1113
const FORGE_PACKAGE = '@cipherstash/stack-forge'
1214

15+
/**
16+
* Installs a package if not already present.
17+
* Returns true if installed (or already was), false if skipped or failed.
18+
*/
19+
async function installIfNeeded(
20+
packageName: string,
21+
buildCommand: (pm: ReturnType<typeof detectPackageManager>, pkg: string) => string,
22+
depLabel: string,
23+
): Promise<boolean> {
24+
if (isPackageInstalled(packageName)) {
25+
p.log.success(`${packageName} is already installed.`)
26+
return true
27+
}
28+
29+
const pm = detectPackageManager()
30+
const cmd = buildCommand(pm, packageName)
31+
32+
const install = await p.confirm({
33+
message: `Install ${packageName} as a ${depLabel} dependency? (${cmd})`,
34+
})
35+
36+
if (p.isCancel(install)) throw new CancelledError()
37+
38+
if (!install) {
39+
p.log.info(`Skipping ${packageName} installation.`)
40+
p.note(
41+
`You can install it manually later:\n ${cmd}`,
42+
'Manual Installation',
43+
)
44+
return false
45+
}
46+
47+
const s = p.spinner()
48+
s.start(`Installing ${packageName}...`)
49+
50+
try {
51+
execSync(cmd, { cwd: process.cwd(), stdio: 'pipe' })
52+
s.stop(`${packageName} installed successfully`)
53+
return true
54+
} catch (err) {
55+
const message = err instanceof Error ? err.message : String(err)
56+
s.stop(`${packageName} installation failed`)
57+
p.log.error(message)
58+
p.note(`You can install it manually:\n ${cmd}`, 'Manual Installation')
59+
return false
60+
}
61+
}
62+
1363
export const installForgeStep: InitStep = {
1464
id: 'install-forge',
15-
name: 'Install stack-forge',
65+
name: 'Install stack dependencies',
1666
async run(state: InitState, _provider: InitProvider): Promise<InitState> {
17-
if (isPackageInstalled(FORGE_PACKAGE)) {
18-
p.log.success(`${FORGE_PACKAGE} is already installed.`)
19-
return { ...state, forgeInstalled: true }
20-
}
21-
22-
const pm = detectPackageManager()
23-
const cmd = devInstallCommand(pm, FORGE_PACKAGE)
24-
25-
const install = await p.confirm({
26-
message: `Install ${FORGE_PACKAGE} as a dev dependency? (${cmd})`,
27-
})
28-
29-
if (p.isCancel(install)) throw new CancelledError()
30-
31-
if (!install) {
32-
p.log.info(`Skipping ${FORGE_PACKAGE} installation.`)
33-
p.note(
34-
`You can install it manually later:\n ${cmd}`,
35-
'Manual Installation',
36-
)
37-
return { ...state, forgeInstalled: false }
38-
}
39-
40-
const s = p.spinner()
41-
s.start(`Installing ${FORGE_PACKAGE}...`)
42-
43-
try {
44-
execSync(cmd, { cwd: process.cwd(), stdio: 'pipe' })
45-
s.stop(`${FORGE_PACKAGE} installed successfully`)
46-
return { ...state, forgeInstalled: true }
47-
} catch (err) {
48-
const message = err instanceof Error ? err.message : String(err)
49-
s.stop(`${FORGE_PACKAGE} installation failed`)
50-
p.log.error(message)
51-
p.note(`You can install it manually:\n ${cmd}`, 'Manual Installation')
52-
return { ...state, forgeInstalled: false }
53-
}
67+
// Install @cipherstash/stack as a production dependency
68+
const stackInstalled = await installIfNeeded(STACK_PACKAGE, prodInstallCommand, 'production')
69+
70+
// Install @cipherstash/stack-forge as a dev dependency
71+
const forgeInstalled = await installIfNeeded(FORGE_PACKAGE, devInstallCommand, 'dev')
72+
73+
return { ...state, forgeInstalled, stackInstalled }
5474
},
5575
}

packages/stack/src/bin/commands/init/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface InitState {
2121
connectionMethod?: ConnectionMethod
2222
clientFilePath?: string
2323
schemaGenerated?: boolean
24+
stackInstalled?: boolean
2425
forgeInstalled?: boolean
2526
}
2627

packages/stack/src/bin/commands/init/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,23 @@ export function detectPackageManager(): 'npm' | 'pnpm' | 'yarn' | 'bun' {
2424
return 'npm'
2525
}
2626

27+
/** Returns the install command for adding a production dependency with the given package manager. */
28+
export function prodInstallCommand(
29+
pm: ReturnType<typeof detectPackageManager>,
30+
packageName: string,
31+
): string {
32+
switch (pm) {
33+
case 'bun':
34+
return `bun add ${packageName}`
35+
case 'pnpm':
36+
return `pnpm add ${packageName}`
37+
case 'yarn':
38+
return `yarn add ${packageName}`
39+
case 'npm':
40+
return `npm install ${packageName}`
41+
}
42+
}
43+
2744
/** Returns the install command for adding a dev dependency with the given package manager. */
2845
export function devInstallCommand(
2946
pm: ReturnType<typeof detectPackageManager>,

0 commit comments

Comments
 (0)