slipway-cli 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # slipway
2
+
3
+ > The CLI for Slipway - the Sails-native deployment platform
4
+
5
+ ## Zero Dependencies
6
+
7
+ This CLI has **no npm dependencies**. It uses only Node.js 22+ built-ins:
8
+
9
+ - `node:util` → `parseArgs` for argument parsing
10
+ - `node:readline` → interactive prompts
11
+ - `node:fs` → config storage in `~/.slipway/`
12
+ - Native `fetch` → HTTP requests
13
+ - Native `WebSocket` → log streaming, Helm REPL
14
+
15
+ This means instant startup, no supply chain risk, and zero maintenance overhead.
16
+
17
+ ## Commands
18
+
19
+ ```bash
20
+ # Deploy (slide into production!)
21
+ slipway slide # Primary command
22
+ slipway launch # Alias
23
+ slipway deploy # Alias
24
+
25
+ # Apps
26
+ slipway app:create myapp
27
+ slipway app:list
28
+ slipway app:info myapp
29
+ slipway app:destroy myapp
30
+
31
+ # Databases (unified)
32
+ slipway db:create mydb --type=postgres
33
+ slipway db:link mydb myapp # Auto-sets DATABASE_URL
34
+ slipway db:connect mydb # Opens psql/mysql/redis-cli
35
+ slipway db:list
36
+ slipway db:backup mydb
37
+
38
+ # Domains
39
+ slipway domain:add myapp example.com
40
+ slipway domain:list myapp
41
+ slipway domain:remove myapp example.com
42
+
43
+ # Environment
44
+ slipway env:set myapp KEY=value
45
+ slipway env:list myapp
46
+
47
+ # Operations
48
+ slipway helm myapp # Sails REPL (like Tinkerwell)
49
+ slipway logs myapp -t # Tail logs
50
+ slipway dev # Local dev mode
51
+ ```
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ npm install -g slipway
57
+ # or
58
+ npx slipway
59
+ ```
60
+
61
+ ## Part of the Slipway Suite
62
+
63
+ - **Slipway Dashboard** - The web UI (root of this monorepo)
64
+ - **sails-hook-slipway** - The Sails hook for Bridge, Helm, telemetry
65
+
66
+ ---
67
+
68
+ *Where your apps slide into production.*
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "slipway-cli",
3
+ "version": "0.0.0",
4
+ "description": "CLI for Slipway - Deploy Sails apps with ease",
5
+ "type": "module",
6
+ "bin": {
7
+ "slipway": "./src/index.js"
8
+ },
9
+ "keywords": [
10
+ "slipway",
11
+ "sails",
12
+ "deploy",
13
+ "docker",
14
+ "cli"
15
+ ],
16
+ "author": "Kelvin Omereshone <kelvin@sailscasts.com>",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/sailscastshq/slipway.git",
21
+ "directory": "packages/cli"
22
+ },
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ }
29
+ }
@@ -0,0 +1,55 @@
1
+ import { c } from '../lib/colors.js'
2
+ import { api } from '../lib/api.js'
3
+ import { isLoggedIn } from '../lib/config.js'
4
+ import { error, requireProject, createSpinner } from '../lib/utils.js'
5
+
6
+ export default async function dbCreate(options, positionals) {
7
+ if (!isLoggedIn()) {
8
+ error('Not logged in. Run `slipway login` first.')
9
+ }
10
+
11
+ const name = positionals[0]
12
+ if (!name) {
13
+ error('Please provide a database name. Usage: slipway db:create <name>')
14
+ }
15
+
16
+ const project = requireProject()
17
+ const environment = options.env || 'production'
18
+ const dbType = options.type || 'postgresql'
19
+ const version = options.version || 'latest'
20
+
21
+ console.log()
22
+ console.log(` ${c.bold(c.highlight('Create Database'))}`)
23
+ console.log()
24
+
25
+ const spin = createSpinner(`Creating ${dbType} database "${name}"...`).start()
26
+
27
+ try {
28
+ const { service } = await api.services.create(project.project, environment, {
29
+ name,
30
+ type: dbType,
31
+ version
32
+ })
33
+
34
+ spin.succeed('Database created')
35
+ console.log()
36
+ console.log(` ${c.dim('Name:')} ${service.name}`)
37
+ console.log(` ${c.dim('Type:')} ${service.type}`)
38
+ console.log(` ${c.dim('Version:')} ${service.version}`)
39
+ console.log(` ${c.dim('Environment:')} ${environment}`)
40
+ console.log()
41
+
42
+ if (service.connectionUrl) {
43
+ console.log(` ${c.dim('Connection URL:')}`)
44
+ console.log(` ${c.highlight(service.connectionUrl)}`)
45
+ console.log()
46
+ }
47
+
48
+ console.log(` ${c.dim('The connection URL has been added to your environment variables.')}`)
49
+ console.log(` ${c.dim('Use')} ${c.highlight(`slipway db:url ${name}`)} ${c.dim('to retrieve it later.')}`)
50
+ console.log()
51
+ } catch (err) {
52
+ spin.fail('Failed to create database')
53
+ error(err.message)
54
+ }
55
+ }
@@ -0,0 +1,44 @@
1
+ import { c } from '../lib/colors.js'
2
+ import { api } from '../lib/api.js'
3
+ import { isLoggedIn } from '../lib/config.js'
4
+ import { error, requireProject, createSpinner } from '../lib/utils.js'
5
+
6
+ export default async function dbUrl(options, positionals) {
7
+ if (!isLoggedIn()) {
8
+ error('Not logged in. Run `slipway login` first.')
9
+ }
10
+
11
+ const name = positionals[0]
12
+ if (!name) {
13
+ error('Please provide a database name. Usage: slipway db:url <name>')
14
+ }
15
+
16
+ const project = requireProject()
17
+ const environment = options.env || 'production'
18
+
19
+ const spin = createSpinner('Fetching database URL...').start()
20
+
21
+ try {
22
+ const { services } = await api.services.list(project.project, environment)
23
+
24
+ const database = services.find(s => s.name === name)
25
+
26
+ if (!database) {
27
+ spin.fail('Database not found')
28
+ error(`No database named "${name}" found in ${environment} environment.`)
29
+ }
30
+
31
+ spin.stop()
32
+
33
+ console.log()
34
+ if (database.connectionUrl) {
35
+ console.log(database.connectionUrl)
36
+ } else {
37
+ console.log(` ${c.dim('No connection URL available for this database.')}`)
38
+ }
39
+ console.log()
40
+ } catch (err) {
41
+ spin.fail('Failed to fetch database URL')
42
+ error(err.message)
43
+ }
44
+ }
@@ -0,0 +1,72 @@
1
+ import { c } from '../lib/colors.js'
2
+ import { api } from '../lib/api.js'
3
+ import { isLoggedIn } from '../lib/config.js'
4
+ import { error, requireProject, table, statusColor, formatDate } from '../lib/utils.js'
5
+
6
+ export default async function deployments(options) {
7
+ if (!isLoggedIn()) {
8
+ error('Not logged in. Run `slipway login` first.')
9
+ }
10
+
11
+ const project = requireProject()
12
+
13
+ console.log()
14
+ console.log(` ${c.bold(c.highlight('Recent Deployments'))}`)
15
+ console.log()
16
+
17
+ try {
18
+ // Get environments first
19
+ const { environments } = await api.environments.list(project.project)
20
+
21
+ // Filter by environment if specified
22
+ const targetEnvs = options.env
23
+ ? environments.filter(e => e.slug === options.env)
24
+ : environments
25
+
26
+ if (targetEnvs.length === 0) {
27
+ console.log(` ${c.dim('No environments found.')}`)
28
+ return
29
+ }
30
+
31
+ // Collect deployments from each environment
32
+ const allDeployments = []
33
+ for (const env of targetEnvs) {
34
+ const { environment } = await api.environments.get(project.project, env.slug)
35
+ if (environment.deployments) {
36
+ environment.deployments.forEach(d => {
37
+ allDeployments.push({
38
+ ...d,
39
+ environment: env.slug
40
+ })
41
+ })
42
+ }
43
+ }
44
+
45
+ // Sort by date and limit
46
+ allDeployments.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0))
47
+ const limited = allDeployments.slice(0, parseInt(options.limit) || 10)
48
+
49
+ if (limited.length === 0) {
50
+ console.log(` ${c.dim('No deployments yet. Run `slipway deploy` to create one.')}`)
51
+ return
52
+ }
53
+
54
+ const rows = limited.map(d => [
55
+ d.id.substring(0, 8),
56
+ statusColor(d.status),
57
+ d.environment,
58
+ d.gitBranch || '-',
59
+ d.gitCommit ? d.gitCommit.substring(0, 7) : '-',
60
+ formatDate(d.createdAt)
61
+ ])
62
+
63
+ table(
64
+ ['ID', 'Status', 'Env', 'Branch', 'Commit', 'Date'],
65
+ rows
66
+ )
67
+
68
+ console.log()
69
+ } catch (err) {
70
+ error(err.message)
71
+ }
72
+ }
@@ -0,0 +1,53 @@
1
+ import { c } from '../lib/colors.js'
2
+ import { api } from '../lib/api.js'
3
+ import { isLoggedIn } from '../lib/config.js'
4
+ import { error, requireProject } from '../lib/utils.js'
5
+
6
+ export default async function envList(options) {
7
+ if (!isLoggedIn()) {
8
+ error('Not logged in. Run `slipway login` first.')
9
+ }
10
+
11
+ const project = requireProject()
12
+ const environment = options.env || 'production'
13
+
14
+ console.log()
15
+ console.log(` ${c.bold(c.highlight('Environment Variables'))}`)
16
+ console.log(` ${c.dim(`${project.project} / ${environment}`)}`)
17
+ console.log()
18
+
19
+ try {
20
+ const { environment: env } = await api.environments.get(project.project, environment)
21
+
22
+ const vars = env.envVars || {}
23
+ const keys = Object.keys(vars).sort()
24
+
25
+ if (keys.length === 0) {
26
+ console.log(` ${c.dim('No environment variables set.')}`)
27
+ console.log(` ${c.dim('Use')} ${c.highlight('slipway env:set KEY=value')} ${c.dim('to add variables.')}`)
28
+ console.log()
29
+ return
30
+ }
31
+
32
+ for (const key of keys) {
33
+ const value = vars[key]
34
+ // Mask sensitive values
35
+ const masked = shouldMask(key) ? '••••••••' : value
36
+ console.log(` ${c.dim(key + '=')}${masked}`)
37
+ }
38
+
39
+ console.log()
40
+ } catch (err) {
41
+ error(err.message)
42
+ }
43
+ }
44
+
45
+ // Keys that should have their values masked
46
+ function shouldMask(key) {
47
+ const sensitivePatterns = [
48
+ 'PASSWORD', 'SECRET', 'KEY', 'TOKEN', 'PRIVATE',
49
+ 'CREDENTIAL', 'AUTH', 'API_KEY', 'APIKEY'
50
+ ]
51
+ const upper = key.toUpperCase()
52
+ return sensitivePatterns.some(p => upper.includes(p))
53
+ }
@@ -0,0 +1,63 @@
1
+ import { c } from '../lib/colors.js'
2
+ import { api } from '../lib/api.js'
3
+ import { isLoggedIn } from '../lib/config.js'
4
+ import { error, requireProject, success, createSpinner } from '../lib/utils.js'
5
+
6
+ export default async function envSet(options, positionals) {
7
+ if (!isLoggedIn()) {
8
+ error('Not logged in. Run `slipway login` first.')
9
+ }
10
+
11
+ if (positionals.length === 0) {
12
+ error('Please provide at least one KEY=value pair. Usage: slipway env:set KEY=value')
13
+ }
14
+
15
+ const project = requireProject()
16
+ const environment = options.env || 'production'
17
+
18
+ // Parse KEY=value pairs
19
+ const vars = {}
20
+ for (const pair of positionals) {
21
+ const eqIndex = pair.indexOf('=')
22
+ if (eqIndex === -1) {
23
+ error(`Invalid format: "${pair}". Use KEY=value format.`)
24
+ }
25
+ const key = pair.substring(0, eqIndex)
26
+ const value = pair.substring(eqIndex + 1)
27
+
28
+ if (!key) {
29
+ error(`Invalid key in "${pair}". Key cannot be empty.`)
30
+ }
31
+
32
+ vars[key] = value
33
+ }
34
+
35
+ const spin = createSpinner('Setting environment variables...').start()
36
+
37
+ try {
38
+ // Get current environment
39
+ const { environment: env } = await api.environments.get(project.project, environment)
40
+
41
+ // Merge with existing vars
42
+ const updatedVars = { ...env.envVars, ...vars }
43
+
44
+ // Update environment
45
+ await api.environments.update(project.project, environment, {
46
+ envVars: updatedVars
47
+ })
48
+
49
+ spin.succeed('Environment variables updated')
50
+ console.log()
51
+
52
+ for (const key of Object.keys(vars)) {
53
+ console.log(` ${c.success('+')} ${key}`)
54
+ }
55
+
56
+ console.log()
57
+ console.log(` ${c.dim('Run')} ${c.highlight('slipway deploy')} ${c.dim('to apply changes.')}`)
58
+ console.log()
59
+ } catch (err) {
60
+ spin.fail('Failed to set environment variables')
61
+ error(err.message)
62
+ }
63
+ }
@@ -0,0 +1,62 @@
1
+ import { c } from '../lib/colors.js'
2
+ import { api } from '../lib/api.js'
3
+ import { isLoggedIn } from '../lib/config.js'
4
+ import { error, requireProject, createSpinner } from '../lib/utils.js'
5
+
6
+ export default async function envUnset(options, positionals) {
7
+ if (!isLoggedIn()) {
8
+ error('Not logged in. Run `slipway login` first.')
9
+ }
10
+
11
+ if (positionals.length === 0) {
12
+ error('Please provide at least one key to remove. Usage: slipway env:unset KEY1 KEY2')
13
+ }
14
+
15
+ const project = requireProject()
16
+ const environment = options.env || 'production'
17
+
18
+ const spin = createSpinner('Removing environment variables...').start()
19
+
20
+ try {
21
+ // Get current environment
22
+ const { environment: env } = await api.environments.get(project.project, environment)
23
+
24
+ // Remove specified keys
25
+ const updatedVars = { ...env.envVars }
26
+ const removed = []
27
+
28
+ for (const key of positionals) {
29
+ if (key in updatedVars) {
30
+ delete updatedVars[key]
31
+ removed.push(key)
32
+ }
33
+ }
34
+
35
+ if (removed.length === 0) {
36
+ spin.stop()
37
+ console.log()
38
+ console.log(` ${c.dim('No matching variables found to remove.')}`)
39
+ console.log()
40
+ return
41
+ }
42
+
43
+ // Update environment
44
+ await api.environments.update(project.project, environment, {
45
+ envVars: updatedVars
46
+ })
47
+
48
+ spin.succeed('Environment variables removed')
49
+ console.log()
50
+
51
+ for (const key of removed) {
52
+ console.log(` ${c.error('-')} ${key}`)
53
+ }
54
+
55
+ console.log()
56
+ console.log(` ${c.dim('Run')} ${c.highlight('slipway deploy')} ${c.dim('to apply changes.')}`)
57
+ console.log()
58
+ } catch (err) {
59
+ spin.fail('Failed to remove environment variables')
60
+ error(err.message)
61
+ }
62
+ }
@@ -0,0 +1,2 @@
1
+ // env command maps to env-list
2
+ export { default } from './env-list.js'
@@ -0,0 +1,38 @@
1
+ import { c } from '../lib/colors.js'
2
+ import { api } from '../lib/api.js'
3
+ import { isLoggedIn } from '../lib/config.js'
4
+ import { error, requireProject } from '../lib/utils.js'
5
+
6
+ export default async function environmentCreate(options, positionals) {
7
+ if (!isLoggedIn()) {
8
+ error('Not logged in. Run `slipway login` first.')
9
+ }
10
+
11
+ const project = requireProject()
12
+
13
+ const name = positionals[0]
14
+ if (!name) {
15
+ error('Please provide an environment name. Usage: slipway environment:create <name>')
16
+ }
17
+
18
+ try {
19
+ const data = { name }
20
+ if (options.production) data.isProduction = true
21
+ if (options.domain) data.domain = options.domain
22
+
23
+ const { environment } = await api.environments.create(project.project, data)
24
+
25
+ console.log()
26
+ console.log(`${c.success('✓')} Environment created`)
27
+ console.log()
28
+ console.log(` ${c.dim('Name:')} ${environment.name}`)
29
+ console.log(` ${c.dim('Slug:')} ${environment.slug}`)
30
+ console.log(` ${c.dim('Type:')} ${environment.isProduction ? 'production' : 'staging'}`)
31
+ if (environment.domain) {
32
+ console.log(` ${c.dim('Domain:')} ${environment.domain}`)
33
+ }
34
+ console.log()
35
+ } catch (err) {
36
+ error(err.message)
37
+ }
38
+ }
@@ -0,0 +1,43 @@
1
+ import { c } from '../lib/colors.js'
2
+ import { api } from '../lib/api.js'
3
+ import { isLoggedIn } from '../lib/config.js'
4
+ import { error, requireProject } from '../lib/utils.js'
5
+
6
+ export default async function environmentUpdate(options, positionals) {
7
+ if (!isLoggedIn()) {
8
+ error('Not logged in. Run `slipway login` first.')
9
+ }
10
+
11
+ const project = requireProject()
12
+
13
+ const slug = positionals[0]
14
+ if (!slug) {
15
+ error('Please provide an environment slug. Usage: slipway environment:update <slug> [options]')
16
+ }
17
+
18
+ const updates = {}
19
+ if (options.name !== undefined) updates.name = options.name
20
+ if (options.domain !== undefined) updates.domain = options.domain
21
+ if (options.production !== undefined) updates.isProduction = options.production
22
+
23
+ if (Object.keys(updates).length === 0) {
24
+ error('No updates provided. Use --name, --domain, or --production.')
25
+ }
26
+
27
+ try {
28
+ const { environment } = await api.environments.update(project.project, slug, updates)
29
+
30
+ console.log()
31
+ console.log(`${c.success('✓')} Environment updated`)
32
+ console.log()
33
+ console.log(` ${c.dim('Name:')} ${environment.name}`)
34
+ console.log(` ${c.dim('Slug:')} ${environment.slug}`)
35
+ console.log(` ${c.dim('Type:')} ${environment.isProduction ? 'production' : 'staging'}`)
36
+ if (environment.domain) {
37
+ console.log(` ${c.dim('Domain:')} ${environment.domain}`)
38
+ }
39
+ console.log()
40
+ } catch (err) {
41
+ error(err.message)
42
+ }
43
+ }
@@ -0,0 +1,47 @@
1
+ import { c } from '../lib/colors.js'
2
+ import { api } from '../lib/api.js'
3
+ import { isLoggedIn } from '../lib/config.js'
4
+ import { error, table, requireProject, formatDate } from '../lib/utils.js'
5
+
6
+ export default async function environments() {
7
+ if (!isLoggedIn()) {
8
+ error('Not logged in. Run `slipway login` first.')
9
+ }
10
+
11
+ const project = requireProject()
12
+
13
+ console.log()
14
+ console.log(` ${c.bold(c.highlight('Environments'))}`)
15
+ console.log(` ${c.dim(project.project)}`)
16
+ console.log()
17
+
18
+ try {
19
+ const { environments } = await api.environments.list(project.project)
20
+
21
+ if (environments.length === 0) {
22
+ console.log(` ${c.dim('No environments yet. Run `slipway environment:create` to create one.')}`)
23
+ console.log()
24
+ return
25
+ }
26
+
27
+ const rows = environments.map(env => [
28
+ env.name,
29
+ c.dim(env.slug),
30
+ env.isProduction ? c.warn('production') : c.info('staging'),
31
+ env.domain || c.dim('—'),
32
+ env.app ? c.success('running') : c.dim('no app'),
33
+ formatDate(env.createdAt)
34
+ ])
35
+
36
+ table(
37
+ ['Name', 'Slug', 'Type', 'Domain', 'Status', 'Created'],
38
+ rows
39
+ )
40
+
41
+ console.log()
42
+ console.log(` ${c.dim(`${environments.length} environment${environments.length !== 1 ? 's' : ''}`)}`)
43
+ console.log()
44
+ } catch (err) {
45
+ error(err.message)
46
+ }
47
+ }
@@ -0,0 +1,94 @@
1
+ import { join, basename } from 'node:path'
2
+ import { existsSync, readFileSync, appendFileSync } from 'node:fs'
3
+ import { c } from '../lib/colors.js'
4
+ import { api } from '../lib/api.js'
5
+ import { saveProjectConfig, getProjectConfig, isLoggedIn } from '../lib/config.js'
6
+ import { error, createSpinner, warn } from '../lib/utils.js'
7
+ import { prompt, confirm } from '../lib/prompt.js'
8
+
9
+ export default async function init(options) {
10
+ if (!isLoggedIn()) {
11
+ error('Not logged in. Run `slipway login` first.')
12
+ }
13
+
14
+ // Check if already initialized
15
+ const existing = getProjectConfig()
16
+ if (existing) {
17
+ error(`Already linked to project "${existing.project}". Use \`slipway link\` to switch projects.`)
18
+ }
19
+
20
+ // Check for package.json to determine project name
21
+ const packageJsonPath = join(process.cwd(), 'package.json')
22
+ let suggestedName = basename(process.cwd())
23
+
24
+ if (existsSync(packageJsonPath)) {
25
+ try {
26
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
27
+ suggestedName = pkg.name || suggestedName
28
+ } catch {
29
+ // Ignore parsing errors
30
+ }
31
+ }
32
+
33
+ console.log()
34
+ console.log(` ${c.bold(c.highlight('Initialize Slipway Project'))}`)
35
+ console.log()
36
+
37
+ // Get project details
38
+ const projectName = options.name || await prompt(' Project name', suggestedName)
39
+
40
+ // Check for Dockerfile
41
+ const dockerfilePath = join(process.cwd(), 'Dockerfile')
42
+ const hasDockerfile = existsSync(dockerfilePath)
43
+
44
+ if (!hasDockerfile) {
45
+ console.log()
46
+ warn('No Dockerfile found in current directory.')
47
+ console.log(` ${c.dim('Slipway requires a Dockerfile to build and deploy your app.')}`)
48
+ console.log()
49
+
50
+ const proceed = await confirm(' Continue anyway?', true)
51
+
52
+ if (!proceed) {
53
+ console.log(` ${c.dim('Run `slipway init` again after adding a Dockerfile.')}`)
54
+ return
55
+ }
56
+ }
57
+
58
+ const spin = createSpinner('Creating project...').start()
59
+
60
+ try {
61
+ // Create project via API
62
+ const { project } = await api.projects.create({
63
+ name: projectName,
64
+ dockerfilePath: 'Dockerfile'
65
+ })
66
+
67
+ // Save project config
68
+ saveProjectConfig({
69
+ project: project.slug,
70
+ projectId: project.id
71
+ })
72
+
73
+ spin.succeed('Project created')
74
+ console.log()
75
+ console.log(` ${c.dim('Project:')} ${project.name}`)
76
+ console.log(` ${c.dim('Slug:')} ${project.slug}`)
77
+ console.log(` ${c.dim('Environment:')} production`)
78
+ console.log()
79
+ console.log(` ${c.dim('Run')} ${c.highlight('slipway slide')} ${c.dim('to deploy your app.')}`)
80
+ console.log()
81
+
82
+ // Add .slipway.json to .gitignore if it exists
83
+ const gitignorePath = join(process.cwd(), '.gitignore')
84
+ if (existsSync(gitignorePath)) {
85
+ const gitignore = readFileSync(gitignorePath, 'utf8')
86
+ if (!gitignore.includes('.slipway.json')) {
87
+ appendFileSync(gitignorePath, '\n# Slipway\n.slipway.json\n')
88
+ }
89
+ }
90
+ } catch (err) {
91
+ spin.fail('Failed to create project')
92
+ error(err.message)
93
+ }
94
+ }