@zerct/zerct 0.1.8 → 0.1.10
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/README.md +2 -1
- package/bin/zerct.js +56 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,8 @@ From a full-stack repo root, the same deploy command discovers nested
|
|
|
22
22
|
|
|
23
23
|
Agents can also inspect API capabilities, account identity, usage, account
|
|
24
24
|
activity, apps, complete app overviews, deploys, builds, app/deploy/build logs,
|
|
25
|
-
env metadata, custom domains, and billing portal links
|
|
25
|
+
env metadata, custom domains, domain verification, and billing portal links
|
|
26
|
+
through the same CLI.
|
|
26
27
|
|
|
27
28
|
On first deploy, the CLI opens browser login, waits for GitHub or Google, stores
|
|
28
29
|
the Zerct session in the OS credential store when available, and continues the
|
package/bin/zerct.js
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSy
|
|
|
4
4
|
import { homedir } from 'node:os'
|
|
5
5
|
import path from 'node:path'
|
|
6
6
|
|
|
7
|
-
const VERSION = '0.1.
|
|
7
|
+
const VERSION = '0.1.10'
|
|
8
8
|
const DEFAULT_API_URL = 'https://api.zerct.com'
|
|
9
9
|
const ARCHIVE_LIMIT_BYTES = 48 * 1024 * 1024
|
|
10
10
|
const SESSION_DIR = '.zerct'
|
|
@@ -82,6 +82,7 @@ Usage:
|
|
|
82
82
|
zerct env delete --app <app_id> KEY [--api <url>] [--json]
|
|
83
83
|
zerct domains list --app <app_id> [--api <url>] [--json]
|
|
84
84
|
zerct domains add --app <app_id> <domain> [--api <url>] [--json]
|
|
85
|
+
zerct domains verify --app <app_id> <domain> [--api <url>] [--json]
|
|
85
86
|
zerct domains delete --app <app_id> <domain> [--api <url>] [--json]
|
|
86
87
|
zerct billing [portal] [--api <url>] [--json]
|
|
87
88
|
|
|
@@ -447,12 +448,14 @@ async function deploy(projectDir, cli) {
|
|
|
447
448
|
throw agentError('invalid_database_target', 'Static frontends cannot attach managed Postgres directly.', 'Deploy a Rust backend with managed Postgres and call it from the frontend.', cli.json)
|
|
448
449
|
}
|
|
449
450
|
const token = await readOrLoginToken(project.dir, cli)
|
|
451
|
+
await preflightDeployLimits([project], cli, token, cli.database)
|
|
450
452
|
const result = await deployProject(project.dir, cli, token, cli.database)
|
|
451
453
|
printDeployResult(result, cli)
|
|
452
454
|
return
|
|
453
455
|
}
|
|
454
456
|
|
|
455
457
|
const token = await readOrLoginToken(projectDir, cli)
|
|
458
|
+
await preflightDeployLimits(projects, cli, token, cli.database)
|
|
456
459
|
const results = []
|
|
457
460
|
if (!cli.json) {
|
|
458
461
|
console.log(`deploying ${projects.length} projects`)
|
|
@@ -474,6 +477,50 @@ async function deploy(projectDir, cli) {
|
|
|
474
477
|
printWorkspaceDeployResults(projectDir, results, cli)
|
|
475
478
|
}
|
|
476
479
|
|
|
480
|
+
async function preflightDeployLimits(projects, cli, token, databaseRequested) {
|
|
481
|
+
const [usageResponse, appsResponse] = await Promise.all([
|
|
482
|
+
apiRequest(cli, 'GET', '/v1/usage', token, null),
|
|
483
|
+
apiRequest(cli, 'GET', '/v1/apps', token, null)
|
|
484
|
+
])
|
|
485
|
+
const usage = usageResponse?.usage || {}
|
|
486
|
+
const limits = usageResponse?.limits || {}
|
|
487
|
+
const apps = Array.isArray(appsResponse?.apps) ? appsResponse.apps : []
|
|
488
|
+
const existingApps = new Map(apps.map((app) => [app.name, app]))
|
|
489
|
+
let newProjects = 0
|
|
490
|
+
let newDatabases = 0
|
|
491
|
+
|
|
492
|
+
for (const project of projects) {
|
|
493
|
+
if (!project.name || project.kind === 'unknown') {
|
|
494
|
+
continue
|
|
495
|
+
}
|
|
496
|
+
const existing = existingApps.get(project.name)
|
|
497
|
+
if (!existing) {
|
|
498
|
+
newProjects += 1
|
|
499
|
+
}
|
|
500
|
+
if (databaseRequested && project.kind === 'rust_backend' && !existing?.databaseStorageMib) {
|
|
501
|
+
newDatabases += 1
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (newProjects > 0 && Number(usage.appCount) + newProjects > Number(limits.projects)) {
|
|
506
|
+
throw agentError(
|
|
507
|
+
'payment_required',
|
|
508
|
+
`Project limit reached: ${usage.appCount}/${limits.projects} projects are already used.`,
|
|
509
|
+
'Redeploy an existing app by reusing its `name` in zerct.toml, or run `npx @zerct/zerct billing` to open Stripe Checkout before creating another project.',
|
|
510
|
+
cli.json
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (newDatabases > 0 && Number(usage.databaseCount) + newDatabases > Number(limits.managedDatabases)) {
|
|
515
|
+
throw agentError(
|
|
516
|
+
'payment_required',
|
|
517
|
+
`Managed Postgres limit reached: ${usage.databaseCount}/${limits.managedDatabases} databases are already used.`,
|
|
518
|
+
'Redeploy an app that already has managed Postgres, deploy without `--database`, or run `npx @zerct/zerct billing` to open Stripe Checkout.',
|
|
519
|
+
cli.json
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
477
524
|
async function deployProject(projectDir, cli, token, wantsDatabase) {
|
|
478
525
|
const report = runDoctor(projectDir)
|
|
479
526
|
if (!report.ok) {
|
|
@@ -681,13 +728,18 @@ async function domainsCommand(cli) {
|
|
|
681
728
|
printJsonOrPretty(cli, response)
|
|
682
729
|
return
|
|
683
730
|
}
|
|
731
|
+
if (action === 'verify') {
|
|
732
|
+
const response = await apiRequest(cli, 'POST', `/v1/apps/${encodeURIComponent(app)}/domains/${encodeURIComponent(domain)}/verify`, token, null)
|
|
733
|
+
printJsonOrPretty(cli, response)
|
|
734
|
+
return
|
|
735
|
+
}
|
|
684
736
|
if (action === 'delete') {
|
|
685
737
|
const response = await apiRequest(cli, 'DELETE', `/v1/apps/${encodeURIComponent(app)}/domains/${encodeURIComponent(domain)}`, token, null)
|
|
686
738
|
printJsonOrPretty(cli, response)
|
|
687
739
|
return
|
|
688
740
|
}
|
|
689
741
|
|
|
690
|
-
throw agentError('unknown_command', 'Unknown domains command.', 'Use `domains list`, `domains add`, or `domains delete`.', cli.json)
|
|
742
|
+
throw agentError('unknown_command', 'Unknown domains command.', 'Use `domains list`, `domains add`, `domains verify`, or `domains delete`.', cli.json)
|
|
691
743
|
}
|
|
692
744
|
|
|
693
745
|
async function billing(cli) {
|
|
@@ -1283,9 +1335,9 @@ function deployProjectInfo(dir, rootDir) {
|
|
|
1283
1335
|
const relative = path.relative(rootDir, dir).replace(/\\/gu, '/') || '.'
|
|
1284
1336
|
try {
|
|
1285
1337
|
const config = parseZerctToml(readFileSync(path.join(dir, 'zerct.toml'), 'utf8'), dir)
|
|
1286
|
-
return { dir, relative, kind: config.kind }
|
|
1338
|
+
return { dir, relative, name: config.name || '', kind: config.kind }
|
|
1287
1339
|
} catch (_error) {
|
|
1288
|
-
return { dir, relative, kind: 'unknown' }
|
|
1340
|
+
return { dir, relative, name: '', kind: 'unknown' }
|
|
1289
1341
|
}
|
|
1290
1342
|
}
|
|
1291
1343
|
|