primo-cli 0.1.23 → 0.1.24
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/add.js +55 -99
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
|
+
import net from 'net';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { randomInt } from 'crypto';
|
|
4
5
|
import chalk from 'chalk';
|
|
@@ -60,6 +61,34 @@ export async function add_site(target, options) {
|
|
|
60
61
|
process.exit(1);
|
|
61
62
|
}
|
|
62
63
|
let server_config = await read_server_config(base_dir);
|
|
64
|
+
const port = server_config.port ?? parseInt(options.port, 10);
|
|
65
|
+
// `primo add` and `primo dev`'s sites-root watcher both mint a site_id for a
|
|
66
|
+
// newly-appeared folder. If dev is up when we add, the two race: the watcher
|
|
67
|
+
// adopts the folder under its own id while add writes a different id to
|
|
68
|
+
// site.yaml, leaving disk pointing at an id the CMS doesn't have. Refuse
|
|
69
|
+
// while ANYTHING holds the port so there is exactly one minter — the headless
|
|
70
|
+
// import below. This check runs BEFORE ensure_site_config so we never stamp a
|
|
71
|
+
// site_id we'd then strand. `primo dev` picks the folder up on its next start
|
|
72
|
+
// (or its watcher, if it's already been given an id).
|
|
73
|
+
//
|
|
74
|
+
// The guard is TCP occupancy, not a healthy-Primo response: a server that's
|
|
75
|
+
// still starting, an unhealthy Primo, or any other listener all own the port
|
|
76
|
+
// and would make the headless CMS below fail to bind — after we'd already
|
|
77
|
+
// minted. So we only proceed on a confirmed connection refusal (port free);
|
|
78
|
+
// an accepted connection OR an inconclusive probe both count as occupied. A
|
|
79
|
+
// healthy Primo just gets a more specific hint.
|
|
80
|
+
if (await is_port_occupied(port)) {
|
|
81
|
+
if (await is_server_running(port)) {
|
|
82
|
+
console.log(chalk.red(`A Primo server is running on port ${port}.`));
|
|
83
|
+
console.log(chalk.dim(` Stop it (Ctrl+C in its terminal), then re-run \`primo add ${target}\`.`));
|
|
84
|
+
console.log(chalk.dim(' `primo dev` imports the folder itself once it\'s restarted.'));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(chalk.red(`Port ${port} is in use, so \`primo add\` can't start a CMS to import into.`));
|
|
88
|
+
console.log(chalk.dim(` Free the port (stop whatever is on it), then re-run \`primo add ${target}\`.`));
|
|
89
|
+
}
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
63
92
|
const { config, created, minted } = await ensure_site_config(site_dir, folder_name);
|
|
64
93
|
if (created) {
|
|
65
94
|
console.log(chalk.dim(` created site.yaml (site_id ${config.site_id})`));
|
|
@@ -80,62 +109,11 @@ export async function add_site(target, options) {
|
|
|
80
109
|
await write_server_config(base_dir, server_config);
|
|
81
110
|
}
|
|
82
111
|
}
|
|
83
|
-
const port = server_config.port ?? parseInt(options.port, 10);
|
|
84
112
|
const api_url = `http://127.0.0.1:${port}`;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (reload.status === 'unreachable') {
|
|
90
|
-
// Older dev server or hot reload disabled (port+1 in use). Import
|
|
91
|
-
// directly against the running CMS — records land, but the dev
|
|
92
|
-
// server won't watch this site until it's restarted.
|
|
93
|
-
let timings = null;
|
|
94
|
-
try {
|
|
95
|
-
timings = await register_site(site_dir, api_url, config, port, server_config, base_dir);
|
|
96
|
-
}
|
|
97
|
-
catch (err) {
|
|
98
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
99
|
-
console.log(chalk.red(` ✗ ${config.name} could not be imported: ${message}`));
|
|
100
|
-
console.log(chalk.dim(` Fix the problem, then re-run \`primo add ${target}\`.`));
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
report_import(config.name, timings);
|
|
104
|
-
console.log(chalk.yellow(' The running dev server couldn\'t be reloaded — restart `primo dev` to watch this site.'));
|
|
105
|
-
console.log('');
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
// The health check only proves *a* Primo server is on this port — it
|
|
109
|
-
// could belong to a different workspace. The reload response's `known`
|
|
110
|
-
// list is the only reliable way to tell: PocketBase 404s record reads
|
|
111
|
-
// pre-setup even when the record exists, so we can't just probe the
|
|
112
|
-
// site_id. (Older dev servers don't send `known` — trust the reload.)
|
|
113
|
-
const known = reload.known?.find(site => site.site_id === config.site_id);
|
|
114
|
-
if (reload.known && !known) {
|
|
115
|
-
console.log(chalk.red(` A Primo server is running on port ${port}, but it isn't serving this workspace.`));
|
|
116
|
-
console.log(chalk.dim(' Stop it (or start `primo dev` in this workspace) and re-run `primo add`.'));
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
if (known?.blocked || reload.quarantined.includes(config.name)) {
|
|
120
|
-
console.log(chalk.yellow(` ${config.name} was found, but the dev server couldn't import it (e.g. duplicate IDs or a missing pages/index.yaml).`));
|
|
121
|
-
console.log(chalk.dim(' Check the `primo dev` logs, fix the problem, then re-run `primo add`.'));
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
console.log('');
|
|
125
|
-
if (reload.loaded === 0 && reload.known) {
|
|
126
|
-
console.log(` ${chalk.cyan(config.name)} is already registered with the running dev server.`);
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
console.log(chalk.green(` ✓ ${config.name} registered`));
|
|
130
|
-
}
|
|
131
|
-
const host = local_dev_host(config.name, port);
|
|
132
|
-
console.log(` ${chalk.dim('Edit:')} http://${host}/admin/site`);
|
|
133
|
-
console.log(` ${chalk.dim('Preview:')} http://${host}/`);
|
|
134
|
-
console.log('');
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
// No dev server running: boot the CMS binary headlessly against the
|
|
138
|
-
// workspace database, import, and shut it back down.
|
|
113
|
+
// No server is running (guaranteed — we refused above if one held the port),
|
|
114
|
+
// so boot the CMS binary headlessly against the workspace database, import,
|
|
115
|
+
// and shut it back down. This is the single minter/import path, which is why
|
|
116
|
+
// `primo add` and the dev watcher can't double-mint the same folder.
|
|
139
117
|
const spinner = ora('Starting CMS for one-time import...').start();
|
|
140
118
|
const binary_path = await ensure_binary();
|
|
141
119
|
const data_dir = await ensure_data_dir(base_dir);
|
|
@@ -202,14 +180,6 @@ async function register_site(site_dir, api_url, config, port, server_config, bas
|
|
|
202
180
|
const use_bootstrap = !await site_exists(api_url, config.site_id);
|
|
203
181
|
return await import_site_files(site_dir, api_url, config, port, server_config, use_bootstrap, base_dir);
|
|
204
182
|
}
|
|
205
|
-
function report_import(site_name, timings) {
|
|
206
|
-
if (timings === null || !timings.ok) {
|
|
207
|
-
console.log(chalk.red(` ✗ ${site_name} could not be imported — see the errors above.`));
|
|
208
|
-
process.exit(1);
|
|
209
|
-
}
|
|
210
|
-
console.log(chalk.green(` ✓ ${site_name} registered (${timings.mode})`));
|
|
211
|
-
report_warnings(timings);
|
|
212
|
-
}
|
|
213
183
|
function report_warnings(timings) {
|
|
214
184
|
if (timings.warning_count > 0) {
|
|
215
185
|
console.log(chalk.yellow(` ⚠ imported with ${timings.warning_count} warning${timings.warning_count === 1 ? '' : 's'} (details above)`));
|
|
@@ -291,43 +261,29 @@ async function ensure_site_config(site_dir, folder_name) {
|
|
|
291
261
|
}
|
|
292
262
|
return { config, created, minted };
|
|
293
263
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
clearTimeout(timeout);
|
|
309
|
-
}
|
|
310
|
-
if (!res.ok)
|
|
311
|
-
return { status: 'unreachable' };
|
|
312
|
-
const result = await res.json().catch(() => null);
|
|
313
|
-
if (!result)
|
|
314
|
-
return { status: 'unreachable' };
|
|
315
|
-
return {
|
|
316
|
-
status: 'ok',
|
|
317
|
-
loaded: result.loaded ?? 0,
|
|
318
|
-
quarantined: result.quarantined ?? [],
|
|
319
|
-
known: Array.isArray(result.known) ? result.known : undefined
|
|
264
|
+
// True when something is listening on the port — the guard the headless import
|
|
265
|
+
// actually depends on (the CMS below can't bind an occupied port). Distinct from
|
|
266
|
+
// is_server_running, which only reports whether a *healthy Primo* answered: a
|
|
267
|
+
// starting/unhealthy server or a foreign listener returns false there but still
|
|
268
|
+
// owns the port. Fail safe — an accepted connection means occupied, and any
|
|
269
|
+
// inconclusive result (timeout, unexpected error) is treated as occupied too, so
|
|
270
|
+
// we never mint a site_id ahead of a bind we can't actually make. Only an
|
|
271
|
+
// explicit connection refusal (ECONNREFUSED) counts as free.
|
|
272
|
+
async function is_port_occupied(port) {
|
|
273
|
+
return new Promise(resolve => {
|
|
274
|
+
const socket = new net.Socket();
|
|
275
|
+
const done = (occupied) => {
|
|
276
|
+
socket.destroy();
|
|
277
|
+
resolve(occupied);
|
|
320
278
|
};
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'site';
|
|
330
|
-
return `${slug}.localhost:${port}`;
|
|
279
|
+
socket.setTimeout(1000);
|
|
280
|
+
socket.once('connect', () => done(true));
|
|
281
|
+
socket.once('timeout', () => done(true));
|
|
282
|
+
socket.once('error', (err) => {
|
|
283
|
+
done(err.code !== 'ECONNREFUSED');
|
|
284
|
+
});
|
|
285
|
+
socket.connect(port, '127.0.0.1');
|
|
286
|
+
});
|
|
331
287
|
}
|
|
332
288
|
async function is_server_running(port) {
|
|
333
289
|
try {
|