primo-cli 0.1.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.
@@ -0,0 +1,261 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import inquirer from 'inquirer';
6
+ import { exec, execSync, spawn } from 'child_process';
7
+ import { promisify } from 'util';
8
+ const exec_async = promisify(exec);
9
+ export async function deploy(options) {
10
+ const spinner = ora('Preparing deployment...').start();
11
+ try {
12
+ const site_dir = path.resolve(options.dir);
13
+ // Read pala.json
14
+ const config_path = path.join(site_dir, 'pala.json');
15
+ let config;
16
+ try {
17
+ const config_data = await fs.readFile(config_path, 'utf-8');
18
+ config = JSON.parse(config_data);
19
+ }
20
+ catch {
21
+ spinner.fail('No pala.json found. Initialize a site first.');
22
+ process.exit(1);
23
+ }
24
+ spinner.stop();
25
+ // Determine provider
26
+ let provider = options.provider;
27
+ if (!provider) {
28
+ const { selected_provider } = await inquirer.prompt([{
29
+ type: 'list',
30
+ name: 'selected_provider',
31
+ message: 'Choose deployment provider:',
32
+ choices: [
33
+ { name: 'Railway - Simple container hosting', value: 'railway' },
34
+ { name: 'Fly.io - Edge deployment', value: 'fly' }
35
+ ]
36
+ }]);
37
+ provider = selected_provider;
38
+ }
39
+ // Check if provider CLI is installed
40
+ const cli_installed = await check_provider_cli(provider);
41
+ if (!cli_installed) {
42
+ console.log('');
43
+ console.log(chalk.yellow(`${provider} CLI not found. Please install it first:`));
44
+ if (provider === 'railway') {
45
+ console.log(chalk.dim(' npm install -g @railway/cli'));
46
+ console.log(chalk.dim(' railway login'));
47
+ }
48
+ else {
49
+ console.log(chalk.dim(' curl -L https://fly.io/install.sh | sh'));
50
+ console.log(chalk.dim(' fly auth login'));
51
+ }
52
+ process.exit(1);
53
+ }
54
+ // Generate deployment files
55
+ spinner.start('Generating deployment files...');
56
+ await generate_dockerfile(site_dir, config);
57
+ await generate_fly_toml(site_dir, config);
58
+ spinner.succeed('Deployment files generated');
59
+ // Deploy based on provider
60
+ if (provider === 'railway') {
61
+ await deploy_to_railway(site_dir, config);
62
+ }
63
+ else {
64
+ await deploy_to_fly(site_dir, config);
65
+ }
66
+ }
67
+ catch (error) {
68
+ spinner.fail(`Deployment failed: ${error instanceof Error ? error.message : error}`);
69
+ process.exit(1);
70
+ }
71
+ }
72
+ async function check_provider_cli(provider) {
73
+ try {
74
+ if (provider === 'railway') {
75
+ execSync('railway --version', { stdio: 'ignore' });
76
+ }
77
+ else {
78
+ execSync('fly version', { stdio: 'ignore' });
79
+ }
80
+ return true;
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
86
+ async function generate_dockerfile(site_dir, config) {
87
+ const dockerfile = `# Pala CMS Deployment
88
+ FROM golang:1.22-alpine AS builder
89
+
90
+ # Install build dependencies
91
+ RUN apk add --no-cache git
92
+
93
+ # Build palacms (or download from releases)
94
+ WORKDIR /build
95
+ RUN git clone https://github.com/palacms/palacms.git . && \\
96
+ go build -o palacms .
97
+
98
+ # Runtime stage
99
+ FROM alpine:3.19
100
+
101
+ RUN apk add --no-cache ca-certificates
102
+
103
+ WORKDIR /app
104
+
105
+ # Copy binary
106
+ COPY --from=builder /build/palacms /app/palacms
107
+
108
+ # Copy site files
109
+ COPY blocks/ /app/pb_data/blocks/
110
+ COPY pages/ /app/pb_data/pages/
111
+ COPY page-types/ /app/pb_data/page-types/
112
+ COPY site/ /app/pb_data/site/
113
+ COPY uploads/ /app/pb_data/uploads/ 2>/dev/null || true
114
+ COPY pala.json /app/pb_data/
115
+
116
+ # Set permissions
117
+ RUN chmod +x /app/palacms
118
+
119
+ # Environment variables
120
+ ENV PB_DATA_DIR=/app/pb_data
121
+
122
+ # Expose port
123
+ EXPOSE 8080
124
+
125
+ # Start server
126
+ CMD ["/app/palacms", "serve", "--http", "0.0.0.0:8080"]
127
+ `;
128
+ await fs.writeFile(path.join(site_dir, 'Dockerfile'), dockerfile);
129
+ // Create .dockerignore
130
+ const dockerignore = `node_modules/
131
+ .git/
132
+ .pala-dev/
133
+ .vite-cache/
134
+ *.log
135
+ .DS_Store
136
+ `;
137
+ await fs.writeFile(path.join(site_dir, '.dockerignore'), dockerignore);
138
+ }
139
+ async function generate_fly_toml(site_dir, config) {
140
+ const app_name = config.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
141
+ const fly_toml = `# Fly.io configuration for ${config.name}
142
+ app = "${app_name}"
143
+ primary_region = "sjc"
144
+
145
+ [build]
146
+
147
+ [env]
148
+ PB_DATA_DIR = "/app/pb_data"
149
+
150
+ [http_service]
151
+ internal_port = 8080
152
+ force_https = true
153
+ auto_stop_machines = true
154
+ auto_start_machines = true
155
+ min_machines_running = 0
156
+
157
+ [[vm]]
158
+ memory = "512mb"
159
+ cpu_kind = "shared"
160
+ cpus = 1
161
+
162
+ [mounts]
163
+ source = "pb_data"
164
+ destination = "/app/pb_data"
165
+ `;
166
+ await fs.writeFile(path.join(site_dir, 'fly.toml'), fly_toml);
167
+ }
168
+ async function deploy_to_railway(site_dir, config) {
169
+ console.log('');
170
+ console.log(chalk.cyan('Deploying to Railway...'));
171
+ console.log('');
172
+ // Check if project exists or create new one
173
+ const spinner = ora('Setting up Railway project...').start();
174
+ try {
175
+ // Try to link existing project
176
+ try {
177
+ execSync('railway status', { cwd: site_dir, stdio: 'ignore' });
178
+ spinner.succeed('Linked to existing Railway project');
179
+ }
180
+ catch {
181
+ // No project linked, create new one
182
+ spinner.text = 'Creating new Railway project...';
183
+ execSync(`railway init --name "${config.name}"`, { cwd: site_dir, stdio: 'inherit' });
184
+ spinner.succeed('Created new Railway project');
185
+ }
186
+ }
187
+ catch (error) {
188
+ spinner.fail('Failed to set up Railway project');
189
+ throw error;
190
+ }
191
+ // Deploy
192
+ console.log('');
193
+ console.log(chalk.dim('Building and deploying...'));
194
+ console.log('');
195
+ const deploy_process = spawn('railway', ['up', '--detach'], {
196
+ cwd: site_dir,
197
+ stdio: 'inherit'
198
+ });
199
+ return new Promise((resolve, reject) => {
200
+ deploy_process.on('close', (code) => {
201
+ if (code === 0) {
202
+ console.log('');
203
+ console.log(chalk.green('✓ Deployment started!'));
204
+ console.log('');
205
+ console.log(chalk.dim(' Run `railway open` to view your deployment'));
206
+ console.log(chalk.dim(' Run `railway logs` to view logs'));
207
+ resolve();
208
+ }
209
+ else {
210
+ reject(new Error(`Railway deployment failed with code ${code}`));
211
+ }
212
+ });
213
+ });
214
+ }
215
+ async function deploy_to_fly(site_dir, config) {
216
+ console.log('');
217
+ console.log(chalk.cyan('Deploying to Fly.io...'));
218
+ console.log('');
219
+ const app_name = config.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
220
+ // Check if app exists
221
+ const spinner = ora('Checking Fly.io app...').start();
222
+ try {
223
+ execSync(`fly status --app ${app_name}`, { cwd: site_dir, stdio: 'ignore' });
224
+ spinner.succeed(`Found existing app: ${app_name}`);
225
+ }
226
+ catch {
227
+ // App doesn't exist, create it
228
+ spinner.text = 'Creating Fly.io app...';
229
+ execSync(`fly apps create ${app_name}`, { cwd: site_dir, stdio: 'inherit' });
230
+ // Create volume for persistent data
231
+ spinner.text = 'Creating persistent volume...';
232
+ execSync(`fly volumes create pb_data --size 1 --region sjc --app ${app_name}`, {
233
+ cwd: site_dir,
234
+ stdio: 'inherit'
235
+ });
236
+ spinner.succeed(`Created app: ${app_name}`);
237
+ }
238
+ // Deploy
239
+ console.log('');
240
+ console.log(chalk.dim('Building and deploying...'));
241
+ console.log('');
242
+ const deploy_process = spawn('fly', ['deploy'], {
243
+ cwd: site_dir,
244
+ stdio: 'inherit'
245
+ });
246
+ return new Promise((resolve, reject) => {
247
+ deploy_process.on('close', (code) => {
248
+ if (code === 0) {
249
+ console.log('');
250
+ console.log(chalk.green('✓ Deployment complete!'));
251
+ console.log('');
252
+ console.log(chalk.cyan(` https://${app_name}.fly.dev`));
253
+ console.log(chalk.cyan(` https://${app_name}.fly.dev/_/ (admin)`));
254
+ resolve();
255
+ }
256
+ else {
257
+ reject(new Error(`Fly deployment failed with code ${code}`));
258
+ }
259
+ });
260
+ });
261
+ }
@@ -0,0 +1,6 @@
1
+ interface DevOptions {
2
+ dir: string;
3
+ port: string;
4
+ }
5
+ export declare function dev_server(options: DevOptions): Promise<void>;
6
+ export {};