ninepm 0.0.1 → 0.26.0
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 +234 -5
- package/dist/cli-config.d.ts +5 -0
- package/dist/cli-config.js +17 -0
- package/dist/cli-types.d.ts +131 -0
- package/dist/cli-types.js +7 -0
- package/dist/deploy-description.d.ts +11 -0
- package/dist/deploy-description.js +31 -0
- package/dist/deploy-limits.d.ts +7 -0
- package/dist/deploy-limits.js +30 -0
- package/dist/index.d.ts +441 -0
- package/dist/index.js +2530 -0
- package/dist/manifest.d.ts +4 -0
- package/dist/manifest.js +134 -0
- package/package.json +35 -8
package/README.md
CHANGED
|
@@ -1,8 +1,237 @@
|
|
|
1
|
-
# 9pm
|
|
1
|
+
# 9pm.ai CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for deploying built applications to 9pm.ai.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
it via the instructions at https://9pm.ai.
|
|
5
|
+
This package contains only client-side deploy code. It does not include the 9pm.ai control plane, infrastructure provisioning code, migrations, staging config, operator tokens, or smoke-test scripts.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm i -g ninepm # persistent `9pm` binary
|
|
11
|
+
npx ninepm <args> # run without installing
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Configure
|
|
15
|
+
|
|
16
|
+
Authenticate in your own browser. The CLI stores the token in native protected storage: macOS Keychain, Windows DPAPI, or Linux Secret Service when available. If native storage is unavailable, plaintext file storage is opt-in with `--allow-file-credential-store` or `NINEPM_ALLOW_FILE_CREDENTIAL_STORE=1`.
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
9pm login
|
|
20
|
+
9pm whoami
|
|
21
|
+
9pm doctor
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For CI, set `NINEPM_API_KEY` yourself in the CI secret store. `--token` and `NINEPM_API_KEY` still override the stored login token.
|
|
25
|
+
|
|
26
|
+
## Inventory
|
|
27
|
+
|
|
28
|
+
List the apps you have on 9pm.ai before deploying or modifying:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
9pm apps
|
|
32
|
+
9pm apps --json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The slug column is the handle you pass to `access`, `delete`, and `files --project`.
|
|
36
|
+
|
|
37
|
+
## Versioning
|
|
38
|
+
|
|
39
|
+
Check installed vs published CLI version:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
9pm --version # installed
|
|
43
|
+
9pm latest # installed vs latest, prints update one-liner when behind
|
|
44
|
+
9pm doctor # surfaces the same drift line alongside credential/account health
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Upgrade:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npm i -g ninepm@latest
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Deploy
|
|
54
|
+
|
|
55
|
+
Deploy a static directory:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
9pm deploy . --name my-app --slug my-app
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Deploy a built framework output directory:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npm run build
|
|
65
|
+
9pm deploy ./dist --check
|
|
66
|
+
9pm deploy ./dist --name my-app --slug my-app
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Deploy with a managed SQL database:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
9pm deploy ./dist --name my-app --slug my-app --with-db
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Attach an optional short description to the deploy (shown next to the version in the dashboard; up to 200 characters):
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
9pm deploy ./dist --name my-app --slug my-app --description "Fix login redirect"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The CLI prints the public app URL after upload and finalization.
|
|
82
|
+
|
|
83
|
+
Store and share working files:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
9pm files put ./report.pdf
|
|
87
|
+
9pm files share <file-id> --expires 24h
|
|
88
|
+
9pm files put ./notes.md --project my-app --path notes/release.md
|
|
89
|
+
9pm files share <file-id> --project my-app --login-required
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Personal files and project files both count toward the account file storage limit. Share links expire and are capped at 7 days. Login-required links are owner-only.
|
|
93
|
+
|
|
94
|
+
Deploy a first-class Worker runtime app:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
9pm deploy ./examples/worker-app --name worker-app --slug worker-app --runtime worker --entry src/index.js --with-db
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The `--runtime worker` entry must be a bundled fetch-compatible JavaScript module with a default export. With `--with-db`, the app receives `env.DB` and should use SQLite-compatible prepared statements.
|
|
101
|
+
|
|
102
|
+
Deploy a container app from a local Docker build:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
9pm deploy . --check --runtime container --port 8080
|
|
106
|
+
9pm deploy . --name container-app --slug container-app --runtime container --port 8080 --health-path /health --region auto
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Container deploys require Docker locally. The CLI builds a `linux/amd64` image, exports it, checks image size against the per-app limit and account retained-image quota, uploads through a short-lived session, and finalizes a queued deployment. The container must listen on `0.0.0.0:$PORT` or the selected `--port`; set `--health-path` when the app has a cheap readiness endpoint.
|
|
110
|
+
|
|
111
|
+
Use `--with-db` when the container needs managed SQL. The app receives `NINEPM_SQL_URL` and `NINEPM_DB_BINDINGS`; send `POST` requests to `NINEPM_SQL_URL` with `{ "binding": "DB", "sql": "SELECT 1", "params": [] }`. For local development, use the app's normal local database adapter behind a repository layer and keep deployed SQL compatible with SQLite.
|
|
112
|
+
|
|
113
|
+
Regions are provider-neutral: `auto`, `us-east`, `us-west`, `europe`, `asia`, `south-america`, `middle-east`, `oceania`, `africa`.
|
|
114
|
+
|
|
115
|
+
When a container app fails to come up or you want to investigate it after the fact, retrieve its recent logs:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
9pm logs my-app # recent container logs + the failure reason, if any
|
|
119
|
+
9pm logs my-app --lines 50 # last N lines
|
|
120
|
+
9pm logs my-app --json # machine-readable
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`9pm logs` reports the latest container deployment's status, health, the failure reason, and the captured startup output when a deploy failed to come up. It is read-only and does not affect deploys.
|
|
124
|
+
|
|
125
|
+
Delete an app, its managed database, managed container app, and retained image:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
9pm delete my-app --confirm my-app
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Access Modes
|
|
132
|
+
|
|
133
|
+
Apps are public by default.
|
|
134
|
+
|
|
135
|
+
One-time PIN with email/domain allowlist:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
9pm access allow email my-app dev@example.com
|
|
139
|
+
9pm access allow domain my-app example.com
|
|
140
|
+
9pm access otp my-app
|
|
141
|
+
9pm access revoke email my-app dev@example.com
|
|
142
|
+
9pm access revoke domain my-app example.com
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Identity provider access:
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
9pm access idp my-app --provider google
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Machine access:
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
9pm access service create my-app --name backend
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Return to public:
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
9pm access public my-app
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Create an app-scoped runtime token for server-side allowlist management:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
9pm access token create my-app --name production-app-server
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Use `9pm_app_...` runtime tokens only from server-side code. They can list, add, and remove email/domain grants for one app. They cannot deploy, create databases, create API keys, or manage another app.
|
|
170
|
+
Service token secrets returned by `access service create` are also server-side secrets.
|
|
171
|
+
|
|
172
|
+
## Commands
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
9pm --version
|
|
176
|
+
9pm deploy [directory] --name <name> [--slug <slug>] [--with-db]
|
|
177
|
+
9pm deploy [directory] --check [--runtime worker --entry <path>]
|
|
178
|
+
9pm deploy [directory] --runtime container --port 8080 [--health-path /health] [--region auto] [--with-db]
|
|
179
|
+
9pm signup
|
|
180
|
+
9pm login
|
|
181
|
+
9pm logout
|
|
182
|
+
9pm doctor [--json]
|
|
183
|
+
9pm whoami
|
|
184
|
+
9pm apps [--json]
|
|
185
|
+
9pm latest [--json]
|
|
186
|
+
9pm files list [--project <site>]
|
|
187
|
+
9pm files put <local-path> [--path <remote-path>] [--project <site>]
|
|
188
|
+
9pm files get <file-id> [--output <path>] [--project <site>]
|
|
189
|
+
9pm files share <file-id> [--expires 24h] [--login-required] [--project <site>]
|
|
190
|
+
9pm files delete <file-id> [--project <site>]
|
|
191
|
+
9pm deploy [directory] --runtime worker --entry <path> [--with-db]
|
|
192
|
+
9pm delete <site> --confirm <slug>
|
|
193
|
+
9pm access show <site>
|
|
194
|
+
9pm access public <site>
|
|
195
|
+
9pm access otp <site>
|
|
196
|
+
9pm access idp <site> [--provider google|github|entra]
|
|
197
|
+
9pm access allow email <site> <email>
|
|
198
|
+
9pm access allow domain <site> <domain>
|
|
199
|
+
9pm access revoke email <site> <email>
|
|
200
|
+
9pm access revoke domain <site> <domain>
|
|
201
|
+
9pm access service create <site> [--name <name>] [--duration 8760h]
|
|
202
|
+
9pm access service revoke <site> <service-token-id>
|
|
203
|
+
9pm access token create <site> [--name <name>]
|
|
204
|
+
9pm access token revoke <site> <token-id>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Options:
|
|
208
|
+
|
|
209
|
+
- `--api-url <url>` overrides `NINEPM_API_URL`
|
|
210
|
+
- `--token <key>` overrides `NINEPM_API_KEY`
|
|
211
|
+
- `--allow-file-credential-store` permits plaintext fallback storage for `login` and `signup` when native protected storage is unavailable
|
|
212
|
+
- `--name <name>` sets the app display name
|
|
213
|
+
- `--slug <slug>` sets the URL-safe app slug
|
|
214
|
+
- `--with-db` requests managed storage for the app
|
|
215
|
+
- `--database-name <name>` customizes the requested database name
|
|
216
|
+
- `--binding <name>` customizes the database binding name
|
|
217
|
+
- `--environment <name>` sets deployment environment metadata
|
|
218
|
+
- `--runtime <static|worker|container>` selects static asset hosting, app-owned Worker code, or local Docker container deployment
|
|
219
|
+
- `--entry <path>` selects the Worker runtime server entry file
|
|
220
|
+
- `--port <port>` selects the HTTP port exposed by a container app
|
|
221
|
+
- `--health-path <path>` optionally lets 9pm.ai check container health before marking it healthy
|
|
222
|
+
- `--region <region>` optionally selects provider-neutral container placement
|
|
223
|
+
- `--project <site>` scopes file commands to an app
|
|
224
|
+
- `--path <remote-path>` stores a file at a specific path
|
|
225
|
+
- `--output <path>` writes a downloaded file to disk
|
|
226
|
+
- `--expires <duration>` sets public share-link expiry, capped at 7 days
|
|
227
|
+
- `--login-required` creates an owner-only share link
|
|
228
|
+
- `--confirm <slug>` confirms app deletion by requiring the slug
|
|
229
|
+
- `--provider <google|github|entra>` selects a configured identity provider
|
|
230
|
+
- `--duration <duration>` sets a service token duration
|
|
231
|
+
|
|
232
|
+
Access command options:
|
|
233
|
+
|
|
234
|
+
- `--api-url <url>` overrides `NINEPM_API_URL`
|
|
235
|
+
- `--token <key>` overrides `NINEPM_API_KEY`
|
|
236
|
+
- `--name <name>` names an app-scoped runtime token
|
|
237
|
+
- `--expires-at <iso-date>` optionally expires an app-scoped runtime token
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const DEFAULT_API_URL = "https://api.9pm.ai";
|
|
2
|
+
export declare const CLI_CLIENT_ID = "ninepm";
|
|
3
|
+
export declare const API_URL_HELP = "Control plane API URL";
|
|
4
|
+
export declare const API_KEY_HELP = "API key from 9pm.ai; overrides stored login";
|
|
5
|
+
export declare function reportCliError(error: unknown): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Per-entry CLI configuration. This is the ONLY file that intentionally differs
|
|
2
|
+
// between the shipped ninepm tarball (this copy) and the in-repo dev CLI
|
|
3
|
+
// (src/cli/cli-config.ts). Everything else in this package is generated from
|
|
4
|
+
// src/cli by scripts/generate-cli-package.mjs, so behavior changes land once.
|
|
5
|
+
// Keep the two cli-config.ts files in deliberate sync only for the values below.
|
|
6
|
+
//
|
|
7
|
+
// Tarball variant: targets the production control plane and keeps output terse.
|
|
8
|
+
export const DEFAULT_API_URL = "https://api.9pm.ai";
|
|
9
|
+
// Value recorded in deployment metadata's `cli` field.
|
|
10
|
+
export const CLI_CLIENT_ID = "ninepm";
|
|
11
|
+
// Help text for the NINEPM_API_URL / NINEPM_API_KEY environment variables.
|
|
12
|
+
export const API_URL_HELP = "Control plane API URL";
|
|
13
|
+
export const API_KEY_HELP = "API key from 9pm.ai; overrides stored login";
|
|
14
|
+
// Render an error to stderr.
|
|
15
|
+
export function reportCliError(error) {
|
|
16
|
+
console.error(error instanceof Error ? error.message : error);
|
|
17
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export type DeploymentStatus = "pending_upload" | "queued" | "publishing" | "active" | "failed";
|
|
2
|
+
export type AppDatabaseStatus = "requested" | "provisioned" | "failed";
|
|
3
|
+
export type AccessMode = "public" | "private_otp" | "private_idp" | "private_service";
|
|
4
|
+
export type AccessGrantType = "email" | "domain";
|
|
5
|
+
export type AccessProviderKey = "google" | "github" | "entra";
|
|
6
|
+
export type AccessProvisioningStatus = "not_configured" | "active" | "failed";
|
|
7
|
+
export type AppRuntime = "static" | "worker" | "container";
|
|
8
|
+
export type ContainerRegion = "auto" | "us-east" | "us-west" | "europe" | "asia" | "south-america" | "middle-east" | "oceania" | "africa";
|
|
9
|
+
export type ManagedFileScope = "user" | "project";
|
|
10
|
+
export type ManagedFileShareMode = "public" | "login_required";
|
|
11
|
+
export interface Project {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
slug: string;
|
|
15
|
+
activeDeploymentId: string | null;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export interface FileManifestEntry {
|
|
20
|
+
path: string;
|
|
21
|
+
size: number;
|
|
22
|
+
sha256: string;
|
|
23
|
+
contentType: string;
|
|
24
|
+
}
|
|
25
|
+
export interface Deployment {
|
|
26
|
+
id: string;
|
|
27
|
+
projectId: string;
|
|
28
|
+
version: number;
|
|
29
|
+
status: DeploymentStatus;
|
|
30
|
+
environment: string;
|
|
31
|
+
url: string;
|
|
32
|
+
manifest: FileManifestEntry[];
|
|
33
|
+
metadata: Record<string, unknown>;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
finalizedAt: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface ContainerImageUploadInstruction {
|
|
38
|
+
uploadUrl: string;
|
|
39
|
+
completeUrl?: string;
|
|
40
|
+
method: "PUT";
|
|
41
|
+
expiresAt: string;
|
|
42
|
+
maxBytes: number;
|
|
43
|
+
partBytes?: number;
|
|
44
|
+
headers: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
export interface AppDatabase {
|
|
47
|
+
id: string;
|
|
48
|
+
projectId: string;
|
|
49
|
+
name: string;
|
|
50
|
+
bindingName: string;
|
|
51
|
+
status: AppDatabaseStatus;
|
|
52
|
+
providerDatabaseId: string | null;
|
|
53
|
+
providerDatabaseName: string | null;
|
|
54
|
+
migrationsStatus: string;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
}
|
|
58
|
+
export interface ManagedFileObject {
|
|
59
|
+
id: string;
|
|
60
|
+
projectId: string | null;
|
|
61
|
+
scope: ManagedFileScope;
|
|
62
|
+
path: string;
|
|
63
|
+
size: number;
|
|
64
|
+
contentType: string;
|
|
65
|
+
sha256: string;
|
|
66
|
+
createdAt: string;
|
|
67
|
+
updatedAt: string;
|
|
68
|
+
deletedAt: string | null;
|
|
69
|
+
}
|
|
70
|
+
export interface ManagedFileShare {
|
|
71
|
+
id: string;
|
|
72
|
+
fileId: string;
|
|
73
|
+
projectId: string | null;
|
|
74
|
+
mode: ManagedFileShareMode;
|
|
75
|
+
expiresAt: string;
|
|
76
|
+
revokedAt: string | null;
|
|
77
|
+
createdAt: string;
|
|
78
|
+
}
|
|
79
|
+
export interface ManagedFileUsage {
|
|
80
|
+
usedBytes: number;
|
|
81
|
+
quotaBytes: number;
|
|
82
|
+
}
|
|
83
|
+
export interface AppAccessSettings {
|
|
84
|
+
projectId: string;
|
|
85
|
+
mode: AccessMode;
|
|
86
|
+
grantsVersion: number;
|
|
87
|
+
accessAppId: string | null;
|
|
88
|
+
accessAppAud: string | null;
|
|
89
|
+
accessPolicyId: string | null;
|
|
90
|
+
accessHostname: string | null;
|
|
91
|
+
providerKeys: AccessProviderKey[];
|
|
92
|
+
allowedIdps: string[];
|
|
93
|
+
provisioningStatus: AccessProvisioningStatus;
|
|
94
|
+
provisioningError: string | null;
|
|
95
|
+
createdAt: string;
|
|
96
|
+
updatedAt: string;
|
|
97
|
+
}
|
|
98
|
+
export interface AccessGrant {
|
|
99
|
+
id: string;
|
|
100
|
+
projectId: string;
|
|
101
|
+
type: AccessGrantType;
|
|
102
|
+
value: string;
|
|
103
|
+
revokedAt: string | null;
|
|
104
|
+
createdAt: string;
|
|
105
|
+
updatedAt: string;
|
|
106
|
+
}
|
|
107
|
+
export interface AppAccessToken {
|
|
108
|
+
id: string;
|
|
109
|
+
projectId: string;
|
|
110
|
+
name: string;
|
|
111
|
+
prefix: string;
|
|
112
|
+
expiresAt: string | null;
|
|
113
|
+
revokedAt: string | null;
|
|
114
|
+
createdAt: string;
|
|
115
|
+
}
|
|
116
|
+
export interface AppAccessServiceToken {
|
|
117
|
+
id: string;
|
|
118
|
+
projectId: string;
|
|
119
|
+
name: string;
|
|
120
|
+
clientId: string;
|
|
121
|
+
duration: string;
|
|
122
|
+
expiresAt: string | null;
|
|
123
|
+
revokedAt: string | null;
|
|
124
|
+
createdAt: string;
|
|
125
|
+
}
|
|
126
|
+
export interface UploadInstruction {
|
|
127
|
+
path: string;
|
|
128
|
+
method: "PUT";
|
|
129
|
+
uploadUrl: string;
|
|
130
|
+
headers: Record<string, string>;
|
|
131
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-cli-package.mjs from the in-repo CLI source -- do not edit.
|
|
2
|
+
// Edit src/cli or src/shared instead; this is regenerated on typecheck/test/build/pack.
|
|
3
|
+
// Public, platform-opaque shapes the CLI exchanges with the control plane API.
|
|
4
|
+
// Single source of truth for both the in-repo dev CLI (src/cli) and the shipped
|
|
5
|
+
// ninepm tarball (packages/cli), which receives a generated copy at build time
|
|
6
|
+
// (scripts/generate-cli-package.mjs). Keep free of server-only fields and imports.
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const MAX_DEPLOY_DESCRIPTION_LENGTH = 200;
|
|
2
|
+
export declare class DeployDescriptionTooLongError extends Error {
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Normalize a raw description value into what we persist:
|
|
7
|
+
* - non-strings, undefined, and whitespace-only -> null ("no description")
|
|
8
|
+
* - otherwise trimmed
|
|
9
|
+
* Throws {@link DeployDescriptionTooLongError} if the trimmed value exceeds the cap.
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeDeployDescription(value: unknown): string | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-cli-package.mjs from the in-repo CLI source -- do not edit.
|
|
2
|
+
// Edit src/cli or src/shared instead; this is regenerated on typecheck/test/build/pack.
|
|
3
|
+
// A deploy description is a short, optional, human-readable note attached to a
|
|
4
|
+
// deployment. Both the CLI and the control plane normalize through here
|
|
5
|
+
// so the rules stay identical on either side of the API.
|
|
6
|
+
export const MAX_DEPLOY_DESCRIPTION_LENGTH = 200;
|
|
7
|
+
export class DeployDescriptionTooLongError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(`Deploy description must be at most ${MAX_DEPLOY_DESCRIPTION_LENGTH} characters.`);
|
|
10
|
+
this.name = "DeployDescriptionTooLongError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Normalize a raw description value into what we persist:
|
|
15
|
+
* - non-strings, undefined, and whitespace-only -> null ("no description")
|
|
16
|
+
* - otherwise trimmed
|
|
17
|
+
* Throws {@link DeployDescriptionTooLongError} if the trimmed value exceeds the cap.
|
|
18
|
+
*/
|
|
19
|
+
export function normalizeDeployDescription(value) {
|
|
20
|
+
if (typeof value !== "string") {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const trimmed = value.trim();
|
|
24
|
+
if (trimmed.length === 0) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (trimmed.length > MAX_DEPLOY_DESCRIPTION_LENGTH) {
|
|
28
|
+
throw new DeployDescriptionTooLongError();
|
|
29
|
+
}
|
|
30
|
+
return trimmed;
|
|
31
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const MAX_STATIC_ASSET_FILE_BYTES: number;
|
|
2
|
+
export declare function formatMebibytes(bytes: number): string;
|
|
3
|
+
export declare function assetFileTooLargeMessage(filePath: string, size: number): string;
|
|
4
|
+
export declare function oversizedAssets<T extends {
|
|
5
|
+
path: string;
|
|
6
|
+
size: number;
|
|
7
|
+
}>(files: readonly T[]): T[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-cli-package.mjs from the in-repo CLI source -- do not edit.
|
|
2
|
+
// Edit src/cli or src/shared instead; this is regenerated on typecheck/test/build/pack.
|
|
3
|
+
// Single source of truth for the per-file static-asset size limit, shared by the
|
|
4
|
+
// control plane (the create-deployment guard) and the CLI (`--check` and the
|
|
5
|
+
// real-deploy preflight). Keep this module dependency-free: it is copied verbatim
|
|
6
|
+
// into the shipped ninepm tarball, so it must not import server-only or
|
|
7
|
+
// Cloudflare code.
|
|
8
|
+
// Individual static/worker assets are capped at 25 MiB. A file above this can't
|
|
9
|
+
// be served by the platform at all (inlining it into the Worker script blows the
|
|
10
|
+
// finalize CPU/script-size budget — the #169 failure), so it is rejected up front:
|
|
11
|
+
// at `9pm deploy --check`, before the project is created on a real deploy (#195),
|
|
12
|
+
// and at create time on the server.
|
|
13
|
+
export const MAX_STATIC_ASSET_FILE_BYTES = 25 * 1024 * 1024;
|
|
14
|
+
// Mebibyte-accurate formatter for the size-limit messaging. The general-purpose
|
|
15
|
+
// formatBytes() labels 1024²-based values "MB"; here we say "MiB" so the number,
|
|
16
|
+
// the unit, and the actual binary threshold agree (the cap is 25 MiB, not the
|
|
17
|
+
// 26.2 MB that "25 MB" would imply).
|
|
18
|
+
export function formatMebibytes(bytes) {
|
|
19
|
+
const mib = Math.max(0, bytes) / (1024 * 1024);
|
|
20
|
+
// Whole numbers read cleanly (25 MiB); fractional sizes get one decimal.
|
|
21
|
+
return Number.isInteger(mib) ? `${mib} MiB` : `${mib.toFixed(1)} MiB`;
|
|
22
|
+
}
|
|
23
|
+
export function assetFileTooLargeMessage(filePath, size) {
|
|
24
|
+
return `File "${filePath}" is ${formatMebibytes(size)}; individual files are limited to ${formatMebibytes(MAX_STATIC_ASSET_FILE_BYTES)}.`;
|
|
25
|
+
}
|
|
26
|
+
// Manifest entries that exceed the per-file limit. A structural type keeps this
|
|
27
|
+
// module free of cross-tree type imports so it copies cleanly into the CLI.
|
|
28
|
+
export function oversizedAssets(files) {
|
|
29
|
+
return files.filter((file) => file.size > MAX_STATIC_ASSET_FILE_BYTES);
|
|
30
|
+
}
|