primo-cli 0.1.5 → 0.1.7
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/dist/commands/dev.js +42 -5
- package/dist/commands/pull-library.d.ts +1 -1
- package/dist/commands/pull-library.js +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.js +40 -14
- package/dist/index.js +19 -8
- package/package.json +1 -1
package/dist/commands/dev.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { createHash, randomInt } from 'crypto';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import ora from 'ora';
|
|
6
|
-
import { spawn } from 'child_process';
|
|
6
|
+
import { spawn, execFileSync } from 'child_process';
|
|
7
7
|
import archiver from 'archiver';
|
|
8
8
|
import extract from 'extract-zip';
|
|
9
9
|
import { dump as dump_yaml, load as load_yaml } from 'js-yaml';
|
|
@@ -420,18 +420,51 @@ async function kill_port(port) {
|
|
|
420
420
|
resolve(false);
|
|
421
421
|
return;
|
|
422
422
|
}
|
|
423
|
+
// Don't kill ourselves or our ancestors — lsof returns every PID
|
|
424
|
+
// holding the port, which on macOS includes parent processes that
|
|
425
|
+
// inherited the fd. SIGKILL'ing them takes this CLI down too.
|
|
426
|
+
const self_ancestry = get_self_ancestry();
|
|
427
|
+
let killed_any = false;
|
|
423
428
|
for (const pid of pid_list) {
|
|
429
|
+
const pid_num = parseInt(pid, 10);
|
|
430
|
+
if (self_ancestry.has(pid_num))
|
|
431
|
+
continue;
|
|
424
432
|
try {
|
|
425
|
-
process.kill(
|
|
433
|
+
process.kill(pid_num, 'SIGKILL');
|
|
434
|
+
killed_any = true;
|
|
426
435
|
}
|
|
427
436
|
catch {
|
|
428
437
|
// Process may have already exited
|
|
429
438
|
}
|
|
430
439
|
}
|
|
431
|
-
resolve(
|
|
440
|
+
resolve(killed_any);
|
|
432
441
|
});
|
|
433
442
|
});
|
|
434
443
|
}
|
|
444
|
+
function get_self_ancestry() {
|
|
445
|
+
const ancestry = new Set();
|
|
446
|
+
let pid = process.pid;
|
|
447
|
+
while (pid && pid > 1) {
|
|
448
|
+
ancestry.add(pid);
|
|
449
|
+
pid = get_parent_pid(pid);
|
|
450
|
+
if (pid && ancestry.has(pid))
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
return ancestry;
|
|
454
|
+
}
|
|
455
|
+
function get_parent_pid(pid) {
|
|
456
|
+
try {
|
|
457
|
+
const result = execFileSync('ps', ['-o', 'ppid=', '-p', String(pid)], {
|
|
458
|
+
encoding: 'utf-8',
|
|
459
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
460
|
+
});
|
|
461
|
+
const parsed = parseInt(String(result).trim(), 10);
|
|
462
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
435
468
|
// Fetch with timeout helper
|
|
436
469
|
async function fetch_with_timeout(url, options = {}, timeout_ms = 10000) {
|
|
437
470
|
const controller = new AbortController();
|
|
@@ -970,8 +1003,12 @@ export async function dev_server(options) {
|
|
|
970
1003
|
console.log(chalk.dim(' Press Ctrl+C to stop'));
|
|
971
1004
|
// Handle cleanup
|
|
972
1005
|
const cleanup = async () => {
|
|
973
|
-
if (is_cleaning_up)
|
|
974
|
-
|
|
1006
|
+
if (is_cleaning_up) {
|
|
1007
|
+
// Second Ctrl-C while we're still cleaning up — bail immediately
|
|
1008
|
+
// so the user isn't stuck waiting on an in-flight push/sync.
|
|
1009
|
+
console.log(chalk.dim('\n Force exit'));
|
|
1010
|
+
process.exit(130);
|
|
1011
|
+
}
|
|
975
1012
|
is_cleaning_up = true;
|
|
976
1013
|
console.log(chalk.dim('\n Shutting down...'));
|
|
977
1014
|
for (const watcher of watchers) {
|
|
@@ -40,7 +40,7 @@ export async function pull_library(options) {
|
|
|
40
40
|
if (token) {
|
|
41
41
|
headers.Authorization = `Bearer ${token}`;
|
|
42
42
|
}
|
|
43
|
-
const output_dir = path.resolve(options.output);
|
|
43
|
+
const output_dir = path.resolve(options.output || '.');
|
|
44
44
|
await fs.mkdir(output_dir, { recursive: true });
|
|
45
45
|
spinner.text = 'Exporting library...';
|
|
46
46
|
const response = await fetch(`${server}/api/palacms/export-library`, {
|
package/dist/commands/pull.d.ts
CHANGED
package/dist/commands/pull.js
CHANGED
|
@@ -6,7 +6,7 @@ import extract from 'extract-zip';
|
|
|
6
6
|
import { dump as dump_yaml, load as load_yaml } from 'js-yaml';
|
|
7
7
|
import { get_auth_token } from '../utils/auth.js';
|
|
8
8
|
import { write_site_config } from '../utils/site-config.js';
|
|
9
|
-
import { write_server_config } from '../utils/server-config.js';
|
|
9
|
+
import { read_server_config, write_server_config } from '../utils/server-config.js';
|
|
10
10
|
async function detect_server() {
|
|
11
11
|
const ports = [3000, 8080, 5173];
|
|
12
12
|
for (const port of ports) {
|
|
@@ -36,19 +36,40 @@ function server_folder_name(server) {
|
|
|
36
36
|
return 'primo-server';
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
async function read_configured_server(dir) {
|
|
40
|
+
const config = await read_configured_server_config(dir);
|
|
41
|
+
return config?.server || null;
|
|
42
|
+
}
|
|
43
|
+
async function read_configured_server_config(dir) {
|
|
44
|
+
try {
|
|
45
|
+
return await read_server_config(dir);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
39
51
|
export async function pull_site(options) {
|
|
40
52
|
const spinner = ora('Connecting...').start();
|
|
41
53
|
try {
|
|
42
|
-
// Resolve server (flag > local detect)
|
|
54
|
+
// Resolve server (flag > server.yaml in cwd > local detect)
|
|
43
55
|
let server;
|
|
56
|
+
let used_configured = false;
|
|
44
57
|
if (options.server) {
|
|
45
58
|
server = options.server.replace(/\/+$/, '');
|
|
46
59
|
}
|
|
47
60
|
else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
const configured = await read_configured_server(process.cwd());
|
|
62
|
+
if (configured) {
|
|
63
|
+
server = configured;
|
|
64
|
+
used_configured = true;
|
|
65
|
+
spinner.text = `Using ${server}`;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
spinner.text = 'Looking for local server...';
|
|
69
|
+
const detected = await detect_server();
|
|
70
|
+
server = (detected || 'http://localhost:3000').replace(/\/+$/, '');
|
|
71
|
+
spinner.text = `Using ${server}`;
|
|
72
|
+
}
|
|
52
73
|
}
|
|
53
74
|
// Auth (optional for local)
|
|
54
75
|
const token = options.token || await get_auth_token(server);
|
|
@@ -56,11 +77,13 @@ export async function pull_site(options) {
|
|
|
56
77
|
if (token) {
|
|
57
78
|
headers['Authorization'] = `Bearer ${token}`;
|
|
58
79
|
}
|
|
59
|
-
// Decide root dir:
|
|
60
|
-
//
|
|
61
|
-
const root_dir = options.output
|
|
62
|
-
? path.resolve(
|
|
63
|
-
:
|
|
80
|
+
// Decide root dir: explicit --output wins; if cwd already has a configured
|
|
81
|
+
// server.yaml, pull in place; otherwise nest under server hostname.
|
|
82
|
+
const root_dir = options.output
|
|
83
|
+
? path.resolve(options.output)
|
|
84
|
+
: used_configured
|
|
85
|
+
? process.cwd()
|
|
86
|
+
: path.resolve(server_folder_name(server));
|
|
64
87
|
await fs.mkdir(root_dir, { recursive: true });
|
|
65
88
|
// List all sites
|
|
66
89
|
spinner.text = 'Fetching sites...';
|
|
@@ -104,10 +127,13 @@ export async function pull_site(options) {
|
|
|
104
127
|
}
|
|
105
128
|
// Fetch site groups so server.yaml has them
|
|
106
129
|
const site_groups = await fetch_site_groups(server, headers);
|
|
107
|
-
//
|
|
130
|
+
// Preserve any existing server.yaml (port, format, server URL) and just
|
|
131
|
+
// refresh site_groups from the source of truth.
|
|
132
|
+
const existing = await read_configured_server_config(root_dir);
|
|
108
133
|
await write_server_config(root_dir, {
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
...existing,
|
|
135
|
+
port: existing?.port ?? 3000,
|
|
136
|
+
site_groups: site_groups.length > 0 ? site_groups : existing?.site_groups
|
|
111
137
|
});
|
|
112
138
|
spinner.succeed(`Server pulled to ${chalk.cyan(root_dir)}`);
|
|
113
139
|
console.log('');
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
2
5
|
import { Command } from 'commander';
|
|
3
6
|
import chalk from 'chalk';
|
|
4
7
|
import { init_workspace } from './commands/init.js';
|
|
@@ -12,11 +15,13 @@ import { login } from './commands/login.js';
|
|
|
12
15
|
import { validate_site } from './commands/validate.js';
|
|
13
16
|
import { deploy } from './commands/deploy.js';
|
|
14
17
|
import { build_site } from './commands/build.js';
|
|
18
|
+
const pkg_path = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
19
|
+
const pkg_version = JSON.parse(fs.readFileSync(pkg_path, 'utf-8')).version;
|
|
15
20
|
const program = new Command();
|
|
16
21
|
program
|
|
17
22
|
.name('primo')
|
|
18
23
|
.description('Build sites visually, edit them anywhere')
|
|
19
|
-
.version(
|
|
24
|
+
.version(pkg_version);
|
|
20
25
|
// Top-level help: prepend a Deploy-vs-build-vs-push decision tree so first-time
|
|
21
26
|
// users can pick a command without reading every description. Use 'before' so
|
|
22
27
|
// it only renders for the root program, not subcommand help.
|
|
@@ -91,22 +96,28 @@ ${chalk.bold('See also')}
|
|
|
91
96
|
`)
|
|
92
97
|
.action((server, options) => push_site({ ...options, server: server || options.server }));
|
|
93
98
|
program
|
|
94
|
-
.command('pull [server]')
|
|
95
|
-
.description('Pull entire server (all sites + library) to local files')
|
|
99
|
+
.command('pull [server] [dir]')
|
|
100
|
+
.description('Pull entire server (all sites + library) to local files (defaults to ./<server-hostname>)')
|
|
96
101
|
.option('-s, --server <url>', 'Server URL (auto-detects local)')
|
|
97
|
-
.option('-o, --output <dir>', 'Output directory (defaults to ./<server-hostname>)', '.')
|
|
98
102
|
.option('-t, --token <token>', 'Auth token')
|
|
99
|
-
.action((server, options) => pull_site({
|
|
103
|
+
.action((server, dir, options) => pull_site({
|
|
104
|
+
...options,
|
|
105
|
+
server: server || options.server,
|
|
106
|
+
output: dir
|
|
107
|
+
}));
|
|
100
108
|
const library = program
|
|
101
109
|
.command('library')
|
|
102
110
|
.description('Manage shared block library');
|
|
103
111
|
library
|
|
104
|
-
.command('pull [server]')
|
|
112
|
+
.command('pull [server] [dir]')
|
|
105
113
|
.description('Pull shared library to local files')
|
|
106
114
|
.option('-s, --server <url>', 'Server URL (auto-detects local)')
|
|
107
|
-
.option('-o, --output <dir>', 'Output directory', '.')
|
|
108
115
|
.option('-t, --token <token>', 'Auth token')
|
|
109
|
-
.action((server, options) => pull_library({
|
|
116
|
+
.action((server, dir, options) => pull_library({
|
|
117
|
+
...options,
|
|
118
|
+
server: server || options.server,
|
|
119
|
+
output: dir
|
|
120
|
+
}));
|
|
110
121
|
library
|
|
111
122
|
.command('push [server]')
|
|
112
123
|
.description('Push local shared library to hosted CMS')
|