primo-cli 0.1.3 → 0.1.4

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.
Files changed (38) hide show
  1. package/README.md +111 -39
  2. package/dist/commands/build.js +488 -272
  3. package/dist/commands/deploy.d.ts +1 -1
  4. package/dist/commands/deploy.js +293 -141
  5. package/dist/commands/dev.d.ts +2 -0
  6. package/dist/commands/dev.js +2007 -150
  7. package/dist/commands/init.d.ts +2 -2
  8. package/dist/commands/init.js +65 -43
  9. package/dist/commands/login.d.ts +1 -2
  10. package/dist/commands/login.js +24 -6
  11. package/dist/commands/new.js +161 -274
  12. package/dist/commands/pull-library.d.ts +7 -0
  13. package/dist/commands/pull-library.js +92 -0
  14. package/dist/commands/pull.d.ts +0 -1
  15. package/dist/commands/pull.js +160 -165
  16. package/dist/commands/push-library.d.ts +7 -0
  17. package/dist/commands/push-library.js +88 -0
  18. package/dist/commands/push.d.ts +2 -0
  19. package/dist/commands/push.js +358 -51
  20. package/dist/commands/validate.d.ts +1 -1
  21. package/dist/commands/validate.js +379 -161
  22. package/dist/index.js +109 -19
  23. package/dist/utils/binary.js +1 -1
  24. package/dist/utils/format.d.ts +12 -0
  25. package/dist/utils/format.js +98 -0
  26. package/dist/utils/head-svelte.d.ts +2 -0
  27. package/dist/utils/head-svelte.js +53 -0
  28. package/dist/utils/server-config.d.ts +19 -0
  29. package/dist/utils/server-config.js +49 -0
  30. package/dist/utils/site-config.d.ts +11 -0
  31. package/dist/utils/site-config.js +14 -0
  32. package/package.json +8 -4
  33. package/dist/commands/export.d.ts +0 -8
  34. package/dist/commands/export.js +0 -163
  35. package/dist/commands/import.d.ts +0 -9
  36. package/dist/commands/import.js +0 -118
  37. package/dist/commands/publish.d.ts +0 -6
  38. package/dist/commands/publish.js +0 -239
@@ -1,9 +0,0 @@
1
- interface ImportOptions {
2
- server?: string;
3
- site?: string;
4
- dir: string;
5
- token?: string;
6
- preview?: boolean;
7
- }
8
- export declare function import_site(options: ImportOptions): Promise<void>;
9
- export {};
@@ -1,118 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import chalk from 'chalk';
4
- import ora from 'ora';
5
- import archiver from 'archiver';
6
- import { get_auth_token } from '../utils/auth.js';
7
- export async function import_site(options) {
8
- const spinner = ora('Reading local files...').start();
9
- try {
10
- const site_dir = path.resolve(options.dir);
11
- // Read pala.json for server/site info
12
- const config_path = path.join(site_dir, 'pala.json');
13
- let config = null;
14
- try {
15
- const config_data = await fs.readFile(config_path, 'utf-8');
16
- config = JSON.parse(config_data);
17
- }
18
- catch {
19
- // No config file, must provide options
20
- }
21
- const server = options.server || config?.server;
22
- const site_id = options.site || config?.site_id;
23
- if (!server) {
24
- spinner.fail('Server URL required. Use --server or ensure pala.json has server field.');
25
- process.exit(1);
26
- }
27
- if (!site_id) {
28
- spinner.fail('Site ID required. Use --site or ensure pala.json has site_id field.');
29
- process.exit(1);
30
- }
31
- // Get auth token
32
- const token = options.token || await get_auth_token(server);
33
- if (!token) {
34
- spinner.fail('Authentication required. Use --token or run `pala login` first.');
35
- process.exit(1);
36
- }
37
- // Create ZIP of the site directory
38
- spinner.text = 'Packaging files...';
39
- const zip_buffer = await create_zip(site_dir);
40
- // Send to server
41
- const endpoint = options.preview
42
- ? `${server}/api/palacms/import/${site_id}/preview`
43
- : `${server}/api/palacms/import/${site_id}`;
44
- spinner.text = options.preview ? 'Previewing changes...' : 'Importing changes...';
45
- const form_data = new FormData();
46
- form_data.append('file', new Blob([zip_buffer]), 'site.zip');
47
- const response = await fetch(endpoint, {
48
- method: 'POST',
49
- headers: {
50
- 'Authorization': `Bearer ${token}`
51
- },
52
- body: form_data
53
- });
54
- if (!response.ok) {
55
- const error = await response.text();
56
- spinner.fail(`Import failed: ${error}`);
57
- process.exit(1);
58
- }
59
- const result = await response.json();
60
- if (options.preview) {
61
- spinner.succeed('Preview complete');
62
- console.log('');
63
- print_diff(result.diff);
64
- console.log('');
65
- console.log(chalk.dim(' Run without --preview to apply these changes'));
66
- }
67
- else {
68
- spinner.succeed('Import complete');
69
- console.log('');
70
- print_diff(result.diff);
71
- }
72
- }
73
- catch (error) {
74
- spinner.fail(`Import failed: ${error instanceof Error ? error.message : error}`);
75
- process.exit(1);
76
- }
77
- }
78
- async function create_zip(dir) {
79
- return new Promise((resolve, reject) => {
80
- const archive = archiver('zip', { zlib: { level: 9 } });
81
- const chunks = [];
82
- archive.on('data', chunk => chunks.push(chunk));
83
- archive.on('end', () => resolve(Buffer.concat(chunks)));
84
- archive.on('error', reject);
85
- // Add directories
86
- const dirs_to_include = ['blocks', 'page-types', 'pages', 'site', 'uploads'];
87
- for (const subdir of dirs_to_include) {
88
- const full_path = path.join(dir, subdir);
89
- archive.directory(full_path, subdir);
90
- }
91
- // Add pala.json
92
- archive.file(path.join(dir, 'pala.json'), { name: 'pala.json' });
93
- archive.finalize();
94
- });
95
- }
96
- function print_diff(diff) {
97
- let has_changes = false;
98
- for (const [section, changes] of Object.entries(diff)) {
99
- const { added, modified, deleted } = changes;
100
- if (added.length === 0 && modified.length === 0 && deleted.length === 0) {
101
- continue;
102
- }
103
- has_changes = true;
104
- console.log(chalk.bold(` ${section}:`));
105
- for (const item of added) {
106
- console.log(chalk.green(` + ${item}`));
107
- }
108
- for (const item of modified) {
109
- console.log(chalk.yellow(` ~ ${item}`));
110
- }
111
- for (const item of deleted) {
112
- console.log(chalk.red(` - ${item}`));
113
- }
114
- }
115
- if (!has_changes) {
116
- console.log(chalk.dim(' No changes detected'));
117
- }
118
- }
@@ -1,6 +0,0 @@
1
- interface PublishOptions {
2
- dir: string;
3
- provider?: string;
4
- }
5
- export declare function publish(options: PublishOptions): Promise<void>;
6
- export {};
@@ -1,239 +0,0 @@
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 { execSync, spawn } from 'child_process';
7
- export async function publish(options) {
8
- const spinner = ora('Preparing deployment...').start();
9
- try {
10
- const site_dir = path.resolve(options.dir);
11
- // Read primo.json
12
- const config_path = path.join(site_dir, 'primo.json');
13
- let config;
14
- try {
15
- const config_data = await fs.readFile(config_path, 'utf-8');
16
- config = JSON.parse(config_data);
17
- }
18
- catch {
19
- spinner.fail('No primo.json found. Run `primo new` first.');
20
- process.exit(1);
21
- }
22
- spinner.stop();
23
- // Determine provider
24
- let provider = options.provider;
25
- if (!provider) {
26
- const { selected_provider } = await inquirer.prompt([{
27
- type: 'list',
28
- name: 'selected_provider',
29
- message: 'Where do you want to deploy?',
30
- choices: [
31
- { name: 'Railway', value: 'railway' },
32
- { name: 'Fly.io', value: 'fly' }
33
- ]
34
- }]);
35
- provider = selected_provider;
36
- }
37
- // Check if provider CLI is installed
38
- const cli_installed = await check_provider_cli(provider);
39
- if (!cli_installed) {
40
- console.log('');
41
- console.log(chalk.yellow(`${provider} CLI not found. Install it first:`));
42
- if (provider === 'railway') {
43
- console.log(chalk.dim(' npm install -g @railway/cli'));
44
- console.log(chalk.dim(' railway login'));
45
- }
46
- else {
47
- console.log(chalk.dim(' curl -L https://fly.io/install.sh | sh'));
48
- console.log(chalk.dim(' fly auth login'));
49
- }
50
- process.exit(1);
51
- }
52
- // Generate deployment files
53
- spinner.start('Generating deployment files...');
54
- await generate_dockerfile(site_dir, config);
55
- if (provider === 'fly') {
56
- await generate_fly_toml(site_dir, config);
57
- }
58
- spinner.succeed('Deployment files generated');
59
- // Deploy
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
- RUN apk add --no-cache git
91
-
92
- WORKDIR /build
93
- RUN git clone https://github.com/palacms/palacms.git . && \\
94
- go build -o palacms .
95
-
96
- FROM alpine:3.19
97
-
98
- RUN apk add --no-cache ca-certificates
99
-
100
- WORKDIR /app
101
-
102
- COPY --from=builder /build/palacms /app/palacms
103
-
104
- COPY blocks/ /app/pb_data/blocks/
105
- COPY pages/ /app/pb_data/pages/
106
- COPY page-types/ /app/pb_data/page-types/
107
- COPY site/ /app/pb_data/site/
108
- COPY uploads/ /app/pb_data/uploads/ 2>/dev/null || true
109
- COPY primo.json /app/pb_data/
110
-
111
- RUN chmod +x /app/palacms
112
-
113
- ENV PB_DATA_DIR=/app/pb_data
114
-
115
- EXPOSE 8080
116
-
117
- CMD ["/app/palacms", "serve", "--http", "0.0.0.0:8080"]
118
- `;
119
- await fs.writeFile(path.join(site_dir, 'Dockerfile'), dockerfile);
120
- const dockerignore = `node_modules/
121
- .git/
122
- .primo/
123
- *.log
124
- .DS_Store
125
- `;
126
- await fs.writeFile(path.join(site_dir, '.dockerignore'), dockerignore);
127
- }
128
- async function generate_fly_toml(site_dir, config) {
129
- const app_name = config.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
130
- const fly_toml = `app = "${app_name}"
131
- primary_region = "sjc"
132
-
133
- [build]
134
-
135
- [env]
136
- PB_DATA_DIR = "/app/pb_data"
137
-
138
- [http_service]
139
- internal_port = 8080
140
- force_https = true
141
- auto_stop_machines = true
142
- auto_start_machines = true
143
- min_machines_running = 0
144
-
145
- [[vm]]
146
- memory = "512mb"
147
- cpu_kind = "shared"
148
- cpus = 1
149
-
150
- [mounts]
151
- source = "pb_data"
152
- destination = "/app/pb_data"
153
- `;
154
- await fs.writeFile(path.join(site_dir, 'fly.toml'), fly_toml);
155
- }
156
- async function deploy_to_railway(site_dir, config) {
157
- console.log('');
158
- console.log(chalk.cyan('Deploying to Railway...'));
159
- const spinner = ora('Setting up Railway project...').start();
160
- try {
161
- try {
162
- execSync('railway status', { cwd: site_dir, stdio: 'ignore' });
163
- spinner.succeed('Linked to existing Railway project');
164
- }
165
- catch {
166
- spinner.text = 'Creating new Railway project...';
167
- execSync(`railway init --name "${config.name}"`, { cwd: site_dir, stdio: 'inherit' });
168
- spinner.succeed('Created new Railway project');
169
- }
170
- }
171
- catch (error) {
172
- spinner.fail('Failed to set up Railway project');
173
- throw error;
174
- }
175
- console.log('');
176
- console.log(chalk.dim('Building and deploying...'));
177
- console.log('');
178
- const deploy_process = spawn('railway', ['up', '--detach'], {
179
- cwd: site_dir,
180
- stdio: 'inherit'
181
- });
182
- return new Promise((resolve, reject) => {
183
- deploy_process.on('close', (code) => {
184
- if (code === 0) {
185
- console.log('');
186
- console.log(chalk.green('✓ Deployment started'));
187
- console.log('');
188
- console.log(chalk.dim(' railway open - view deployment'));
189
- console.log(chalk.dim(' railway logs - view logs'));
190
- resolve();
191
- }
192
- else {
193
- reject(new Error(`Railway deployment failed with code ${code}`));
194
- }
195
- });
196
- });
197
- }
198
- async function deploy_to_fly(site_dir, config) {
199
- console.log('');
200
- console.log(chalk.cyan('Deploying to Fly.io...'));
201
- const app_name = config.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
202
- const spinner = ora('Checking Fly.io app...').start();
203
- try {
204
- execSync(`fly status --app ${app_name}`, { cwd: site_dir, stdio: 'ignore' });
205
- spinner.succeed(`Found existing app: ${app_name}`);
206
- }
207
- catch {
208
- spinner.text = 'Creating Fly.io app...';
209
- execSync(`fly apps create ${app_name}`, { cwd: site_dir, stdio: 'inherit' });
210
- spinner.text = 'Creating persistent volume...';
211
- execSync(`fly volumes create pb_data --size 1 --region sjc --app ${app_name}`, {
212
- cwd: site_dir,
213
- stdio: 'inherit'
214
- });
215
- spinner.succeed(`Created app: ${app_name}`);
216
- }
217
- console.log('');
218
- console.log(chalk.dim('Building and deploying...'));
219
- console.log('');
220
- const deploy_process = spawn('fly', ['deploy'], {
221
- cwd: site_dir,
222
- stdio: 'inherit'
223
- });
224
- return new Promise((resolve, reject) => {
225
- deploy_process.on('close', (code) => {
226
- if (code === 0) {
227
- console.log('');
228
- console.log(chalk.green('✓ Deployed'));
229
- console.log('');
230
- console.log(chalk.cyan(` https://${app_name}.fly.dev`));
231
- console.log(chalk.cyan(` https://${app_name}.fly.dev/_/ (admin)`));
232
- resolve();
233
- }
234
- else {
235
- reject(new Error(`Fly deployment failed with code ${code}`));
236
- }
237
- });
238
- });
239
- }