primo-cli 0.1.7 → 0.1.8

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.
@@ -2103,7 +2103,18 @@ async function create_site_zip(dir, excluded_paths = new Set()) {
2103
2103
  }
2104
2104
  const site_json = path.join(dir, SITE_CONFIG_FILE);
2105
2105
  if (!is_excluded_path(SITE_CONFIG_FILE, excluded_paths)) {
2106
- archive.file(site_json, { name: SITE_CONFIG_FILE });
2106
+ // Strip `host` if present — `site.yaml` no longer carries it,
2107
+ // but older dirs pulled by previous CLI versions still do,
2108
+ // and the local CMS's /import endpoint persists whatever
2109
+ // host the zipped yaml declares, overwriting bootstrap's
2110
+ // *.localhost value and breaking preview routing.
2111
+ const sanitized = await read_site_yaml_without_host(site_json);
2112
+ if (sanitized !== null) {
2113
+ archive.append(sanitized, { name: SITE_CONFIG_FILE });
2114
+ }
2115
+ else {
2116
+ archive.file(site_json, { name: SITE_CONFIG_FILE });
2117
+ }
2107
2118
  }
2108
2119
  await archive.finalize();
2109
2120
  }
@@ -2113,6 +2124,22 @@ async function create_site_zip(dir, excluded_paths = new Set()) {
2113
2124
  })();
2114
2125
  });
2115
2126
  }
2127
+ async function read_site_yaml_without_host(site_yaml_path) {
2128
+ try {
2129
+ const raw = await fs.readFile(site_yaml_path, 'utf-8');
2130
+ const parsed = load_yaml(raw);
2131
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
2132
+ return null;
2133
+ const data = parsed;
2134
+ if (!('host' in data))
2135
+ return null;
2136
+ const { host: _, ...rest } = data;
2137
+ return dump_yaml(rest, { lineWidth: -1, noRefs: true });
2138
+ }
2139
+ catch {
2140
+ return null;
2141
+ }
2142
+ }
2116
2143
  async function sync_from_cms(site_dir, api_url, config, server_config, workspace_dir, sync_policy = { mode: 'both' }) {
2117
2144
  const response = await fetch_with_timeout(`${api_url}/api/palacms/export/${config.site_id}`, {}, 15000);
2118
2145
  if (!response.ok)
@@ -87,9 +87,6 @@ export async function new_site(options) {
87
87
  const config = {
88
88
  name: display_name,
89
89
  site_id: generate_id(),
90
- // Leave host empty for local dev - dev.ts will generate coffee-shop.localhost:3000
91
- // Only set host if it looks like a real domain (has a dot)
92
- host: site_name.includes('.') ? site_name : '',
93
90
  group: 'default'
94
91
  };
95
92
  await write_site_config(site_dir, config);
@@ -48,6 +48,15 @@ async function read_configured_server_config(dir) {
48
48
  return null;
49
49
  }
50
50
  }
51
+ function is_remote_server(server) {
52
+ try {
53
+ const hostname = new URL(server).hostname;
54
+ return hostname !== 'localhost' && hostname !== '127.0.0.1' && hostname !== '::1';
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ }
51
60
  export async function pull_site(options) {
52
61
  const spinner = ora('Connecting...').start();
53
62
  try {
@@ -79,21 +88,36 @@ export async function pull_site(options) {
79
88
  }
80
89
  // Decide root dir: explicit --output wins; if cwd already has a configured
81
90
  // server.yaml, pull in place; otherwise nest under server hostname.
91
+ // Refuse to auto-create a `localhost/` folder — the user almost certainly
92
+ // didn't mean that. Require --output or --server explicitly in that case.
93
+ if (!options.output && !used_configured && !is_remote_server(server)) {
94
+ spinner.fail(`Resolved to ${server} but no output dir was given. ` +
95
+ `Pass --server <url> or --output <dir> to pull a local server.`);
96
+ process.exit(1);
97
+ }
82
98
  const root_dir = options.output
83
99
  ? path.resolve(options.output)
84
100
  : used_configured
85
101
  ? process.cwd()
86
102
  : path.resolve(server_folder_name(server));
87
- await fs.mkdir(root_dir, { recursive: true });
88
- // List all sites
103
+ // Validate the server is reachable BEFORE creating the output dir, so a
104
+ // failed pull doesn't leave an empty folder behind.
89
105
  spinner.text = 'Fetching sites...';
90
- const sites_response = await fetch(`${server}/api/collections/sites/records?perPage=200`, {
91
- headers
92
- });
106
+ let sites_response;
107
+ try {
108
+ sites_response = await fetch(`${server}/api/collections/sites/records?perPage=200`, {
109
+ headers
110
+ });
111
+ }
112
+ catch (error) {
113
+ spinner.fail(`Could not reach ${server}: ${error instanceof Error ? error.message : error}`);
114
+ process.exit(1);
115
+ }
93
116
  if (!sites_response.ok) {
94
117
  spinner.fail(`Failed to fetch sites (${sites_response.status})`);
95
118
  process.exit(1);
96
119
  }
120
+ await fs.mkdir(root_dir, { recursive: true });
97
121
  const sites_data = await sites_response.json();
98
122
  const sites = sites_data.items || [];
99
123
  if (sites.length === 0) {
@@ -127,13 +151,15 @@ export async function pull_site(options) {
127
151
  }
128
152
  // Fetch site groups so server.yaml has them
129
153
  const site_groups = await fetch_site_groups(server, headers);
130
- // Preserve any existing server.yaml (port, format, server URL) and just
131
- // refresh site_groups from the source of truth.
154
+ // Preserve any existing server.yaml (port, format) and refresh
155
+ // site_groups from the source of truth. Also persist the server URL
156
+ // when it's a remote so subsequent bare `primo pull` runs in this dir
157
+ // don't fall back to localhost detection.
132
158
  const existing = await read_configured_server_config(root_dir);
133
159
  await write_server_config(root_dir, {
134
160
  ...existing,
135
- port: existing?.port ?? 3000,
136
- site_groups: site_groups.length > 0 ? site_groups : existing?.site_groups
161
+ site_groups: site_groups.length > 0 ? site_groups : existing?.site_groups,
162
+ server: existing?.server ?? (is_remote_server(server) ? server : undefined)
137
163
  });
138
164
  spinner.succeed(`Server pulled to ${chalk.cyan(root_dir)}`);
139
165
  console.log('');
@@ -170,7 +196,6 @@ async function pull_one_site(server, headers, site, site_dir, spinner) {
170
196
  await fs.unlink(temp_zip);
171
197
  await write_site_config(site_dir, {
172
198
  name: site.name || 'Imported Site',
173
- host: site.host || '',
174
199
  site_id: site.id,
175
200
  server,
176
201
  group: site.group
@@ -300,8 +300,6 @@ async function try_bootstrap_site(server, token, zip_buffer, config, site_id) {
300
300
  form.append('site_id', site_id);
301
301
  if (config?.name)
302
302
  form.append('name', config.name);
303
- if (config?.host)
304
- form.append('host', config.host);
305
303
  if (config?.group)
306
304
  form.append('group', config.group);
307
305
  form.append('file', new Blob([zip_buffer]), 'site.zip');
@@ -16,4 +16,5 @@ export declare function format_group_name(group_id: string): string;
16
16
  export declare function normalize_server_config(config: ServerConfig): ServerConfig;
17
17
  export declare function resolve_format_options(config: ServerConfig): FormatOptions;
18
18
  export declare function read_server_config(base_dir: string): Promise<ServerConfig>;
19
+ export declare const DEFAULT_PORT = 3000;
19
20
  export declare function write_server_config(base_dir: string, config: ServerConfig): Promise<void>;
@@ -44,6 +44,12 @@ export async function read_server_config(base_dir) {
44
44
  const config_data = await fs.readFile(get_server_config_path(base_dir), 'utf-8');
45
45
  return normalize_server_config(load_yaml(config_data));
46
46
  }
47
+ export const DEFAULT_PORT = 3000;
47
48
  export async function write_server_config(base_dir, config) {
48
- await fs.writeFile(get_server_config_path(base_dir), dump_yaml(normalize_server_config(config), { lineWidth: -1, noRefs: true }));
49
+ const normalized = normalize_server_config(config);
50
+ // Drop port from the file when it's the default — it's noise otherwise.
51
+ const for_write = normalized.port === DEFAULT_PORT
52
+ ? { ...normalized, port: undefined }
53
+ : normalized;
54
+ await fs.writeFile(get_server_config_path(base_dir), dump_yaml(for_write, { lineWidth: -1, noRefs: true }));
49
55
  }
@@ -1,7 +1,6 @@
1
1
  export interface SiteConfig {
2
2
  name: string;
3
3
  site_id: string;
4
- host?: string;
5
4
  server?: string;
6
5
  group?: string;
7
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primo-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Local development CLI for Primo",
5
5
  "type": "module",
6
6
  "bin": {