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,6 +1,6 @@
1
1
  interface DeployOptions {
2
- dir: string;
3
2
  provider?: string;
3
+ dryRun?: boolean;
4
4
  }
5
5
  export declare function deploy(options: DeployOptions): Promise<void>;
6
6
  export {};
@@ -2,66 +2,144 @@ import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
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();
5
+ import { select } from '@inquirer/prompts';
6
+ import { execSync, spawn } from 'child_process';
7
+ import { read_server_config, write_server_config, get_server_config_path, SERVER_CONFIG_FILE } from '../utils/server-config.js';
8
+ import { read_site_config, get_site_config_path } from '../utils/site-config.js';
9
+ async function path_exists(p) {
11
10
  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;
11
+ await fs.access(p);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ async function inventory_workspace(root_dir) {
19
+ const server_config_path = get_server_config_path(root_dir);
20
+ if (!await path_exists(server_config_path)) {
21
+ console.log('');
22
+ console.log(chalk.red(`No ${SERVER_CONFIG_FILE} found in ${root_dir}.`));
23
+ console.log('');
24
+ console.log(chalk.dim(' primo deploy must be run from a workspace root.'));
25
+ console.log(chalk.dim(' A workspace root is the directory containing server.yaml.'));
26
+ console.log(chalk.dim(' Run `primo init` to create one, or `cd` into an existing workspace.'));
27
+ console.log('');
28
+ process.exit(1);
29
+ }
30
+ // Confirm parseable; we don't need the contents but failing fast is helpful.
31
+ await read_server_config(root_dir);
32
+ const sites = [];
33
+ const sites_root = path.join(root_dir, 'sites');
34
+ if (await path_exists(sites_root)) {
35
+ const entries = await fs.readdir(sites_root, { withFileTypes: true });
36
+ for (const entry of entries) {
37
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
38
+ continue;
39
+ const site_dir = path.join(sites_root, entry.name);
40
+ if (!await path_exists(get_site_config_path(site_dir)))
41
+ continue;
42
+ try {
43
+ const config = await read_site_config(site_dir);
44
+ sites.push({ dir: site_dir, config });
45
+ }
46
+ catch {
47
+ // Skip sites with invalid config
48
+ }
49
+ }
50
+ }
51
+ const has_library = await path_exists(path.join(root_dir, 'library'));
52
+ return {
53
+ root_dir,
54
+ server_config_path,
55
+ sites,
56
+ has_library
57
+ };
58
+ }
59
+ function normalize_provider(raw) {
60
+ if (typeof raw !== 'string')
61
+ return undefined;
62
+ const v = raw.trim().toLowerCase();
63
+ if (v === 'railway')
64
+ return 'railway';
65
+ if (v === 'fly' || v === 'fly.io' || v === 'flyio')
66
+ return 'fly';
67
+ return undefined;
68
+ }
69
+ function workspace_app_name(root_dir) {
70
+ return path.basename(root_dir).toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-') || 'primo-workspace';
71
+ }
72
+ export async function deploy(options) {
73
+ const root_dir = process.cwd();
74
+ const inventory = await inventory_workspace(root_dir);
75
+ if (options.dryRun) {
76
+ print_dry_run(inventory, options.provider);
77
+ return;
78
+ }
79
+ let provider = normalize_provider(options.provider);
80
+ if (!provider) {
16
81
  try {
17
- const config_data = await fs.readFile(config_path, 'utf-8');
18
- config = JSON.parse(config_data);
82
+ const choice = await select({
83
+ message: 'Where do you want to deploy?',
84
+ choices: [
85
+ { name: 'Railway', value: 'railway' },
86
+ { name: 'Fly.io', value: 'fly' }
87
+ ]
88
+ });
89
+ provider = normalize_provider(choice);
19
90
  }
20
91
  catch {
21
- spinner.fail('No pala.json found. Initialize a site first.');
92
+ // User cancelled (Ctrl+C / Esc) exit quietly.
93
+ console.log('');
22
94
  process.exit(1);
23
95
  }
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;
96
+ }
97
+ if (!provider) {
98
+ console.log('');
99
+ console.log(chalk.red('No provider selected.'));
100
+ console.log(chalk.dim(' Pass -p railway or -p fly, or pick one from the prompt.'));
101
+ console.log('');
102
+ process.exit(1);
103
+ }
104
+ const cli_installed = await check_provider_cli(provider);
105
+ if (!cli_installed) {
106
+ console.log('');
107
+ console.log(chalk.yellow(`${provider} CLI not found. Install it first:`));
108
+ if (provider === 'railway') {
109
+ console.log(chalk.dim(' npm install -g @railway/cli'));
110
+ console.log(chalk.dim(' railway login'));
38
111
  }
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);
112
+ else {
113
+ console.log(chalk.dim(' curl -L https://fly.io/install.sh | sh'));
114
+ console.log(chalk.dim(' fly auth login'));
115
+ }
116
+ process.exit(1);
117
+ }
118
+ const provider_logged_in = await check_provider_auth(provider);
119
+ if (!provider_logged_in) {
120
+ console.log('');
121
+ console.log(chalk.red(`Not logged in to ${provider}.`));
122
+ if (provider === 'railway') {
123
+ console.log(chalk.dim(' Run `railway login` and try again.'));
124
+ }
125
+ else {
126
+ console.log(chalk.dim(' Run `fly auth login` and try again.'));
127
+ }
128
+ console.log('');
129
+ process.exit(1);
130
+ }
131
+ const spinner = ora('Generating deployment files...').start();
132
+ try {
133
+ await generate_dockerfile(inventory);
134
+ if (provider === 'fly') {
135
+ await generate_fly_toml(inventory);
53
136
  }
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
137
  spinner.succeed('Deployment files generated');
59
- // Deploy based on provider
60
138
  if (provider === 'railway') {
61
- await deploy_to_railway(site_dir, config);
139
+ await deploy_to_railway(inventory);
62
140
  }
63
141
  else {
64
- await deploy_to_fly(site_dir, config);
142
+ await deploy_to_fly(inventory);
65
143
  }
66
144
  }
67
145
  catch (error) {
@@ -69,6 +147,52 @@ export async function deploy(options) {
69
147
  process.exit(1);
70
148
  }
71
149
  }
150
+ function print_dry_run(inventory, provider) {
151
+ console.log('');
152
+ console.log(chalk.bold('Deploy preview (dry-run)'));
153
+ console.log('');
154
+ console.log(` Workspace: ${chalk.cyan(inventory.root_dir)}`);
155
+ console.log(` Provider: ${chalk.cyan(provider || '(prompted at deploy time)')}`);
156
+ console.log(` Image: ${chalk.cyan(PRIMO_SERVER_IMAGE)}`);
157
+ console.log('');
158
+ console.log(chalk.bold(' Will create on the provider:'));
159
+ console.log(` ${chalk.green('+')} A service running ${PRIMO_SERVER_IMAGE}`);
160
+ console.log(` ${chalk.green('+')} A persistent volume mounted at /app/pb_data`);
161
+ if (provider === 'fly') {
162
+ console.log(` ${chalk.green('+')} fly.toml in this workspace`);
163
+ }
164
+ console.log(` ${chalk.green('+')} Dockerfile in this workspace`);
165
+ console.log('');
166
+ console.log(chalk.bold(' Will be uploaded after first boot via primo push:'));
167
+ console.log(` ${chalk.dim('—')} ${SERVER_CONFIG_FILE}`);
168
+ if (inventory.has_library) {
169
+ console.log(` ${chalk.dim('—')} library/`);
170
+ }
171
+ else {
172
+ console.log(chalk.dim(' (no library/ directory found)'));
173
+ }
174
+ if (inventory.sites.length > 0) {
175
+ console.log(` ${chalk.dim('—')} sites/ (${inventory.sites.length} site${inventory.sites.length === 1 ? '' : 's'})`);
176
+ for (const site of inventory.sites) {
177
+ const slug = path.basename(site.dir);
178
+ console.log(chalk.dim(` - ${slug} (${site.config.name})`));
179
+ }
180
+ }
181
+ else {
182
+ console.log(chalk.yellow(' (no sites found under sites/)'));
183
+ }
184
+ console.log(chalk.dim(' (first push bootstraps; subsequent pushes update incrementally)'));
185
+ console.log('');
186
+ if (provider) {
187
+ console.log(chalk.dim(` Auth: run \`${provider === 'railway' ? 'railway whoami' : 'fly auth whoami'}\` to confirm provider login.`));
188
+ }
189
+ else {
190
+ console.log(chalk.dim(' Auth: provider not selected; pass -p railway|fly to dry-run with auth context.'));
191
+ }
192
+ console.log('');
193
+ console.log(chalk.dim(' No files were written. Run without --dry-run to deploy.'));
194
+ console.log('');
195
+ }
72
196
  async function check_provider_cli(provider) {
73
197
  try {
74
198
  if (provider === 'railway') {
@@ -83,70 +207,59 @@ async function check_provider_cli(provider) {
83
207
  return false;
84
208
  }
85
209
  }
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
210
+ async function check_provider_auth(provider) {
211
+ try {
212
+ if (provider === 'railway') {
213
+ execSync('railway whoami', { stdio: 'ignore' });
214
+ }
215
+ else {
216
+ execSync('fly auth whoami', { stdio: 'ignore' });
217
+ }
218
+ return true;
219
+ }
220
+ catch {
221
+ return false;
222
+ }
223
+ }
224
+ // Pinned to the upstream-published image. palacms's main.yml workflow
225
+ // publishes branch tags for whitelisted prefixes (main, feature/**, rc/**),
226
+ // with slashes slugified to dashes (feature/local-dev-cli → :feature-local-dev-cli).
227
+ // Bump to a release tag (:v3.0.0) when palacms cuts a stable release.
228
+ const PRIMO_SERVER_IMAGE = 'ghcr.io/palacms/palacms:feature-local-dev-cli';
229
+ async function generate_dockerfile(inventory) {
230
+ // One-line Dockerfile: pull the published palacms image and run it
231
+ // unchanged. Workspace data (server.yaml, sites/, library/) is uploaded
232
+ // after deploy via `primo push`, which calls /api/palacms/bootstrap on
233
+ // first push (server with no sites) and /api/palacms/import/<id> on
234
+ // subsequent pushes. The volume mounted at /app/pb_data persists the
235
+ // SQLite database between restarts.
236
+ void inventory;
237
+ const dockerfile = `# Primo CMS deployment (generated by primo deploy)
238
+ FROM ${PRIMO_SERVER_IMAGE}
123
239
  EXPOSE 8080
124
-
125
- # Start server
126
- CMD ["/app/palacms", "serve", "--http", "0.0.0.0:8080"]
127
240
  `;
128
- await fs.writeFile(path.join(site_dir, 'Dockerfile'), dockerfile);
129
- // Create .dockerignore
241
+ await fs.writeFile(path.join(inventory.root_dir, 'Dockerfile'), dockerfile);
242
+ // .dockerignore keeps the build context small (Railway uploads cwd to its
243
+ // builder). Nothing in the workspace gets baked into the image — we just
244
+ // FROM the upstream image directly.
130
245
  const dockerignore = `node_modules/
131
246
  .git/
132
- .pala-dev/
133
- .vite-cache/
247
+ .primo/
134
248
  *.log
135
249
  .DS_Store
136
250
  `;
137
- await fs.writeFile(path.join(site_dir, '.dockerignore'), dockerignore);
251
+ await fs.writeFile(path.join(inventory.root_dir, '.dockerignore'), dockerignore);
138
252
  }
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}"
253
+ async function generate_fly_toml(inventory) {
254
+ const app_name = workspace_app_name(inventory.root_dir);
255
+ // palacms binds 0.0.0.0:8080 in its CMD; mount /app/pb_data on a persistent
256
+ // volume so the SQLite db + uploads survive restarts. Auto-start/stop keeps
257
+ // the small instance free-tier-friendly.
258
+ const fly_toml = `app = "${app_name}"
143
259
  primary_region = "sjc"
144
260
 
145
261
  [build]
146
262
 
147
- [env]
148
- PB_DATA_DIR = "/app/pb_data"
149
-
150
263
  [http_service]
151
264
  internal_port = 8080
152
265
  force_https = true
@@ -163,24 +276,21 @@ primary_region = "sjc"
163
276
  source = "pb_data"
164
277
  destination = "/app/pb_data"
165
278
  `;
166
- await fs.writeFile(path.join(site_dir, 'fly.toml'), fly_toml);
279
+ await fs.writeFile(path.join(inventory.root_dir, 'fly.toml'), fly_toml);
167
280
  }
168
- async function deploy_to_railway(site_dir, config) {
281
+ async function deploy_to_railway(inventory) {
169
282
  console.log('');
170
283
  console.log(chalk.cyan('Deploying to Railway...'));
171
- console.log('');
172
- // Check if project exists or create new one
173
284
  const spinner = ora('Setting up Railway project...').start();
285
+ const project_name = workspace_app_name(inventory.root_dir);
174
286
  try {
175
- // Try to link existing project
176
287
  try {
177
- execSync('railway status', { cwd: site_dir, stdio: 'ignore' });
288
+ execSync('railway status', { cwd: inventory.root_dir, stdio: 'ignore' });
178
289
  spinner.succeed('Linked to existing Railway project');
179
290
  }
180
291
  catch {
181
- // No project linked, create new one
182
292
  spinner.text = 'Creating new Railway project...';
183
- execSync(`railway init --name "${config.name}"`, { cwd: site_dir, stdio: 'inherit' });
293
+ execSync(`railway init --name "${project_name}"`, { cwd: inventory.root_dir, stdio: 'inherit' });
184
294
  spinner.succeed('Created new Railway project');
185
295
  }
186
296
  }
@@ -188,74 +298,116 @@ async function deploy_to_railway(site_dir, config) {
188
298
  spinner.fail('Failed to set up Railway project');
189
299
  throw error;
190
300
  }
191
- // Deploy
192
301
  console.log('');
193
302
  console.log(chalk.dim('Building and deploying...'));
194
303
  console.log('');
195
304
  const deploy_process = spawn('railway', ['up', '--detach'], {
196
- cwd: site_dir,
305
+ cwd: inventory.root_dir,
197
306
  stdio: 'inherit'
198
307
  });
199
308
  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 {
309
+ deploy_process.on('close', async (code) => {
310
+ if (code !== 0) {
210
311
  reject(new Error(`Railway deployment failed with code ${code}`));
312
+ return;
313
+ }
314
+ // Generate (or fetch existing) public domain so the user can hit
315
+ // the server immediately. railway domain is idempotent — calling
316
+ // it on a service that already has a generated domain returns the
317
+ // same URL.
318
+ const domain = await try_railway_domain(inventory.root_dir);
319
+ const url = domain ? `https://${domain}` : undefined;
320
+ if (url) {
321
+ await record_workspace_server(inventory.root_dir, url);
211
322
  }
323
+ print_post_deploy_next_steps('railway', url);
324
+ resolve();
212
325
  });
213
326
  });
214
327
  }
215
- async function deploy_to_fly(site_dir, config) {
328
+ async function try_railway_domain(cwd) {
329
+ try {
330
+ const out = execSync('railway domain --json', { cwd, stdio: ['ignore', 'pipe', 'ignore'] }).toString();
331
+ const parsed = JSON.parse(out);
332
+ const raw = parsed.domain || parsed.url;
333
+ if (!raw)
334
+ return undefined;
335
+ // railway sometimes returns bare hostname, sometimes full URL
336
+ return raw.replace(/^https?:\/\//, '').replace(/\/+$/, '');
337
+ }
338
+ catch {
339
+ return undefined;
340
+ }
341
+ }
342
+ async function record_workspace_server(root_dir, url) {
343
+ try {
344
+ const config = await read_server_config(root_dir);
345
+ if (config.server === url)
346
+ return;
347
+ await write_server_config(root_dir, { ...config, server: url });
348
+ }
349
+ catch {
350
+ // Don't fail the deploy if we can't write the file — user can add
351
+ // the server URL manually.
352
+ }
353
+ }
354
+ function print_post_deploy_next_steps(provider, url) {
216
355
  console.log('');
217
- console.log(chalk.cyan('Deploying to Fly.io...'));
356
+ console.log(chalk.green(' Deployment started'));
357
+ console.log('');
358
+ if (url) {
359
+ console.log(chalk.bold(' URL: ') + chalk.cyan(url));
360
+ console.log(chalk.dim(' (saved as `server:` in server.yaml — primo push/login pick it up automatically)'));
361
+ console.log('');
362
+ }
363
+ console.log(chalk.bold('Next steps'));
218
364
  console.log('');
219
- const app_name = config.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
220
- // Check if app exists
365
+ if (provider === 'railway') {
366
+ console.log(chalk.dim(' Railway needs one manual setting the CLI can\'t set for you:'));
367
+ console.log(chalk.dim(' Settings → Volumes → mount on /app/pb_data (size 1GB+)'));
368
+ console.log('');
369
+ }
370
+ console.log(chalk.dim(' Then upload your workspace into the deployed server:'));
371
+ console.log(chalk.dim(` primo push${url ? '' : ' -s <url>'}`));
372
+ console.log(chalk.dim(' (first push bootstraps the sites; later pushes update them incrementally)'));
373
+ console.log('');
374
+ }
375
+ async function deploy_to_fly(inventory) {
376
+ console.log('');
377
+ console.log(chalk.cyan('Deploying to Fly.io...'));
378
+ const app_name = workspace_app_name(inventory.root_dir);
221
379
  const spinner = ora('Checking Fly.io app...').start();
222
380
  try {
223
- execSync(`fly status --app ${app_name}`, { cwd: site_dir, stdio: 'ignore' });
381
+ execSync(`fly status --app ${app_name}`, { cwd: inventory.root_dir, stdio: 'ignore' });
224
382
  spinner.succeed(`Found existing app: ${app_name}`);
225
383
  }
226
384
  catch {
227
- // App doesn't exist, create it
228
385
  spinner.text = 'Creating Fly.io app...';
229
- execSync(`fly apps create ${app_name}`, { cwd: site_dir, stdio: 'inherit' });
230
- // Create volume for persistent data
386
+ execSync(`fly apps create ${app_name}`, { cwd: inventory.root_dir, stdio: 'inherit' });
231
387
  spinner.text = 'Creating persistent volume...';
232
388
  execSync(`fly volumes create pb_data --size 1 --region sjc --app ${app_name}`, {
233
- cwd: site_dir,
389
+ cwd: inventory.root_dir,
234
390
  stdio: 'inherit'
235
391
  });
236
392
  spinner.succeed(`Created app: ${app_name}`);
237
393
  }
238
- // Deploy
239
394
  console.log('');
240
395
  console.log(chalk.dim('Building and deploying...'));
241
396
  console.log('');
242
397
  const deploy_process = spawn('fly', ['deploy'], {
243
- cwd: site_dir,
398
+ cwd: inventory.root_dir,
244
399
  stdio: 'inherit'
245
400
  });
246
401
  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 {
402
+ deploy_process.on('close', async (code) => {
403
+ if (code !== 0) {
257
404
  reject(new Error(`Fly deployment failed with code ${code}`));
405
+ return;
258
406
  }
407
+ const url = `https://${app_name}.fly.dev`;
408
+ await record_workspace_server(inventory.root_dir, url);
409
+ print_post_deploy_next_steps('fly', url);
410
+ resolve();
259
411
  });
260
412
  });
261
413
  }
@@ -1,6 +1,8 @@
1
1
  interface DevOptions {
2
2
  dir: string;
3
3
  port: string;
4
+ force?: boolean;
5
+ author?: string;
4
6
  }
5
7
  export declare function dev_server(options: DevOptions): Promise<void>;
6
8
  export {};