ninepm 0.65.0 → 0.68.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 +2 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +71 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ Deploy a first-class Worker runtime app:
|
|
|
106
106
|
|
|
107
107
|
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.
|
|
108
108
|
|
|
109
|
-
Choose which files are public with `--assets <dir>`: only the files under that directory are served, from the app's root (`public/index.html` answers on `/`), and nothing else in the deploy directory is uploaded. Use `--no-assets` when the entry serves every response itself. The app remembers the choice, so later deploys need not repeat the flag; pass the other flag, or another directory, to change it — there is no way back to publishing everything. A worker app that has never chosen serves **every file in the deploy directory except the entry** publicly, server helpers and leftovers like `index.js.orig` included; every deploy of such an app prints a warning naming those files, and `9pm deploy --check` lists every file a deploy would make public.
|
|
109
|
+
Choose which files are public with `--assets <dir>`: only the files under that directory are served, from the app's root (`public/index.html` answers on `/`), and nothing else in the deploy directory is uploaded. Use `--no-assets` when the entry serves every response itself. The app remembers the choice, so later deploys need not repeat the flag; pass the other flag, or another directory, to change it — there is no way back to publishing everything. A worker app that has never chosen serves **every file in the deploy directory except the entry** publicly, server helpers and leftovers like `index.js.orig` included; every deploy of such an app prints a warning naming those files, and `9pm deploy --check` lists every file a deploy would make public. Its summary counts the files scanned apart from the files selected for upload (the entry and the public files); `--check` itself uploads nothing.
|
|
110
110
|
|
|
111
111
|
Deploy a container app from a local Docker build:
|
|
112
112
|
|
|
@@ -221,6 +221,7 @@ Options:
|
|
|
221
221
|
- `--name <name>` sets the app display name
|
|
222
222
|
- `--slug <slug>` sets the URL-safe app slug
|
|
223
223
|
- `--with-db` requests managed storage for the app
|
|
224
|
+
- `--with-auth` gives the app 9pm-managed end-user sign-in (email one-time codes) and implies `--with-db`. Once enabled it stays on for every later deploy of the app, with or without the flag; it can't be turned off
|
|
224
225
|
- `--database-name <name>` customizes the requested database name
|
|
225
226
|
- `--binding <name>` customizes the database binding name
|
|
226
227
|
- `--environment <name>` sets deployment environment metadata
|
package/dist/index.d.ts
CHANGED
|
@@ -38,10 +38,17 @@ export interface DeployResult {
|
|
|
38
38
|
project: Project;
|
|
39
39
|
deployment: Deployment;
|
|
40
40
|
database: AppDatabase | null;
|
|
41
|
+
databases: BoundDatabase[];
|
|
41
42
|
uploaded: number;
|
|
42
43
|
url: string;
|
|
43
44
|
containerImageBytes?: number;
|
|
44
45
|
}
|
|
46
|
+
export interface BoundDatabase {
|
|
47
|
+
bindingName: string;
|
|
48
|
+
name: string | null;
|
|
49
|
+
}
|
|
50
|
+
export declare function boundDatabasesForDeployment(deployment: Deployment, provisioned: AppDatabase | null): BoundDatabase[];
|
|
51
|
+
export declare function deployDatabaseClause(databases: readonly BoundDatabase[]): string | null;
|
|
45
52
|
export interface ApiOptions {
|
|
46
53
|
apiUrl: string;
|
|
47
54
|
token: string;
|
|
@@ -117,7 +124,7 @@ export interface CreatedServiceToken {
|
|
|
117
124
|
clientSecret: string;
|
|
118
125
|
}
|
|
119
126
|
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
120
|
-
export declare const CLI_VERSION = "0.
|
|
127
|
+
export declare const CLI_VERSION = "0.68.0";
|
|
121
128
|
type CredentialBackend = "macos-keychain" | "windows-dpapi" | "linux-secret-service" | "file-plaintext" | "none";
|
|
122
129
|
export interface StoredCredentialEntry {
|
|
123
130
|
token: string;
|
|
@@ -191,6 +198,10 @@ export interface DeployCheckResult {
|
|
|
191
198
|
path: string;
|
|
192
199
|
url: string;
|
|
193
200
|
}>;
|
|
201
|
+
upload?: {
|
|
202
|
+
files: number;
|
|
203
|
+
bytes: number;
|
|
204
|
+
};
|
|
194
205
|
}
|
|
195
206
|
export type DetectedFramework = "nextjs" | "astro" | "sveltekit" | "vite";
|
|
196
207
|
export declare function detectFramework(directory: string): DetectedFramework | null;
|
|
@@ -230,6 +241,7 @@ export declare function measureWorkerModuleFiles(root: string, modulePaths: read
|
|
|
230
241
|
compressedBytes: number;
|
|
231
242
|
}>;
|
|
232
243
|
export declare function checkDeployDirectory(options: DeployOptions): Promise<DeployCheckResult>;
|
|
244
|
+
export declare function deployCheckSummary(check: DeployCheckResult, publicAssets: string | undefined): string;
|
|
233
245
|
export declare function whoami(options: ApiOptions): Promise<WhoamiResult>;
|
|
234
246
|
export declare function publicWhoamiResult(result: WhoamiResult): PublicWhoamiResult;
|
|
235
247
|
export type CredentialRevocation = {
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,40 @@ import { MAX_STATIC_ASSET_FILE_BYTES, MAX_WORKER_ENTRY_COMPRESSED_BYTES, MAX_WOR
|
|
|
16
16
|
import { buildFileManifest, readManifestFile } from "./manifest.js";
|
|
17
17
|
import { NO_PUBLIC_ASSETS, normalizeAssetsDirectory, privateLookingPaths, publicAssetPath } from "./public-assets.js";
|
|
18
18
|
import { API_KEY_HELP, API_URL_HELP, CLI_CLIENT_ID, DEFAULT_API_URL, reportCliError } from "./cli-config.js";
|
|
19
|
-
|
|
19
|
+
// What the deployment binds, read from the deployment the server returned (#1949). The
|
|
20
|
+
// server binds every provisioned database of the app on each publish whatever flags were
|
|
21
|
+
// passed, so `--with-db` says nothing about the result. Only a deployment carrying no
|
|
22
|
+
// `databases` record at all - the local mock target, or a server that predates recording
|
|
23
|
+
// it for this runtime - falls back to what this run itself provisioned.
|
|
24
|
+
export function boundDatabasesForDeployment(deployment, provisioned) {
|
|
25
|
+
const recorded = deployment.metadata["databases"];
|
|
26
|
+
if (!Array.isArray(recorded)) {
|
|
27
|
+
return provisioned ? [{ bindingName: provisioned.bindingName, name: provisioned.name }] : [];
|
|
28
|
+
}
|
|
29
|
+
const bound = [];
|
|
30
|
+
for (const entry of recorded) {
|
|
31
|
+
if (!entry || typeof entry !== "object") {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const record = entry;
|
|
35
|
+
if (typeof record["bindingName"] !== "string") {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const name = typeof record["name"] === "string" ? record["name"] : provisioned && record["id"] === provisioned.id ? provisioned.name : null;
|
|
39
|
+
bound.push({ bindingName: record["bindingName"], name });
|
|
40
|
+
}
|
|
41
|
+
return bound;
|
|
42
|
+
}
|
|
43
|
+
// The database clause of the deploy summary, or null when nothing is bound - the summary
|
|
44
|
+
// then says nothing about a database rather than "no database".
|
|
45
|
+
export function deployDatabaseClause(databases) {
|
|
46
|
+
if (databases.length === 0) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const listed = databases.map((database) => (database.name ? `${database.bindingName}:${database.name}` : database.bindingName)).join(", ");
|
|
50
|
+
return `${databases.length === 1 ? "database" : "databases"} ${listed}`;
|
|
51
|
+
}
|
|
52
|
+
export const CLI_VERSION = "0.68.0";
|
|
20
53
|
const PUBLIC_DASHBOARD_URL = "https://9pm.ai/dashboard";
|
|
21
54
|
const TOKEN_SERVICE = "9pm";
|
|
22
55
|
const TOKEN_ACCOUNT = "default";
|
|
@@ -442,6 +475,7 @@ async function runDeploy(initialOptions, slugHolder) {
|
|
|
442
475
|
project,
|
|
443
476
|
deployment: finalized,
|
|
444
477
|
database,
|
|
478
|
+
databases: boundDatabasesForDeployment(finalized, database),
|
|
445
479
|
uploaded: 1,
|
|
446
480
|
url: finalized.url,
|
|
447
481
|
containerImageBytes: image.size,
|
|
@@ -637,6 +671,7 @@ async function runDeploy(initialOptions, slugHolder) {
|
|
|
637
671
|
project,
|
|
638
672
|
deployment: finalized,
|
|
639
673
|
database,
|
|
674
|
+
databases: boundDatabasesForDeployment(finalized, database),
|
|
640
675
|
uploaded,
|
|
641
676
|
url: finalized.url,
|
|
642
677
|
};
|
|
@@ -812,9 +847,12 @@ export async function checkDeployDirectory(options) {
|
|
|
812
847
|
// #1847: `--check` has no project, so it cannot see a choice made on an earlier deploy;
|
|
813
848
|
// it reports what this invocation's flags would publish, and says so for the legacy case.
|
|
814
849
|
let publicFiles;
|
|
850
|
+
let upload;
|
|
815
851
|
if (serverEntry) {
|
|
816
852
|
const selection = options.publicAssets ?? null;
|
|
817
853
|
publicFiles = workerPublicFiles(manifest.map((file) => file.path), serverEntry, selection);
|
|
854
|
+
const selected = uploadedFiles(manifest, serverEntry, selection);
|
|
855
|
+
upload = { files: selected.length, bytes: selected.reduce((sum, file) => sum + file.size, 0) };
|
|
818
856
|
if (selection !== null && selection !== NO_PUBLIC_ASSETS && publicFiles.length === 0) {
|
|
819
857
|
errors.push(missingAssetsDirectoryMessage(selection));
|
|
820
858
|
}
|
|
@@ -847,8 +885,26 @@ export async function checkDeployDirectory(options) {
|
|
|
847
885
|
bytes: totalBytes,
|
|
848
886
|
...(serverCode ? { serverCode } : {}),
|
|
849
887
|
...(publicFiles ? { publicFiles } : {}),
|
|
888
|
+
...(upload ? { upload } : {}),
|
|
850
889
|
};
|
|
851
890
|
}
|
|
891
|
+
// The `--check` success line. Static and bundle deploys upload every file they scan, so one
|
|
892
|
+
// count is the whole story there. An entry-based worker deploy uploads only the entry and
|
|
893
|
+
// its public files, so it reports both counts; with no --assets/--no-assets, a choice the
|
|
894
|
+
// app saved on an earlier deploy applies on the server, which a local check cannot see.
|
|
895
|
+
export function deployCheckSummary(check, publicAssets) {
|
|
896
|
+
if (!check.upload) {
|
|
897
|
+
return `Deploy check passed: ${check.files} file(s), ${check.bytes} byte(s).`;
|
|
898
|
+
}
|
|
899
|
+
const publicCount = check.publicFiles?.length ?? 0;
|
|
900
|
+
const breakdown = publicAssets === NO_PUBLIC_ASSETS ? "the entry only" : `the entry + ${publicCount} public file(s)`;
|
|
901
|
+
const summary = `Deploy check passed: ${check.files} file(s) scanned, ${check.upload.files} selected for upload (${breakdown}), ` +
|
|
902
|
+
`${check.upload.bytes.toLocaleString("en-US")} byte(s) selected.`;
|
|
903
|
+
if (publicAssets !== undefined) {
|
|
904
|
+
return summary;
|
|
905
|
+
}
|
|
906
|
+
return `${summary}\n Counted from the flags supplied (no --assets or --no-assets). If this app saved a public-assets choice on an earlier deploy, that choice applies on deploy and only the entry and the files it makes public are uploaded.`;
|
|
907
|
+
}
|
|
852
908
|
export async function whoami(options) {
|
|
853
909
|
return apiRequest(options, "/v1/me", { method: "GET" });
|
|
854
910
|
}
|
|
@@ -2305,7 +2361,14 @@ function buildContainerImage(directory) {
|
|
|
2305
2361
|
const tempDir = mkdtempSync(path.join(os.tmpdir(), "9pm-container-"));
|
|
2306
2362
|
const tag = `9pm-local-${Date.now()}-${process.pid}`;
|
|
2307
2363
|
const imagePath = path.join(tempDir, "image.tar");
|
|
2308
|
-
|
|
2364
|
+
// Build without Buildx's default provenance attestation. With it, Docker Desktop saves the
|
|
2365
|
+
// image as an index carrying an attestation manifest, and the push to Cloudflare's registry
|
|
2366
|
+
// through `wrangler containers push` fails with "blob unknown to registry"; the same image
|
|
2367
|
+
// without the attestation pushes (#1906: two of two attempts each way). The environment
|
|
2368
|
+
// variable rather than `--provenance=false`: it is what that test pushed, and a builder that
|
|
2369
|
+
// has no attestations - the legacy non-Buildx `docker build` - ignores an unknown variable
|
|
2370
|
+
// where it would reject an unknown flag. SBOM generation is untouched: it is off by default.
|
|
2371
|
+
runDocker(["build", "--platform", "linux/amd64", "-t", tag, root], "build container image", { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" });
|
|
2309
2372
|
const inspected = spawnSync("docker", ["image", "inspect", tag, "--format", "{{.Os}}/{{.Architecture}}"], { encoding: "utf8" });
|
|
2310
2373
|
if (inspected.status !== 0) {
|
|
2311
2374
|
throw new Error(`Failed to inspect container image: ${inspected.stderr || inspected.stdout}`.trim());
|
|
@@ -2318,8 +2381,8 @@ function buildContainerImage(directory) {
|
|
|
2318
2381
|
const sha256 = createHash("sha256").update(readFileSync(imagePath)).digest("hex");
|
|
2319
2382
|
return { tag, path: imagePath, size, sha256 };
|
|
2320
2383
|
}
|
|
2321
|
-
function runDocker(args, action) {
|
|
2322
|
-
const result = spawnSync("docker", args, { stdio: "inherit" });
|
|
2384
|
+
function runDocker(args, action, extraEnv = {}) {
|
|
2385
|
+
const result = spawnSync("docker", args, { stdio: "inherit", env: { ...process.env, ...extraEnv } });
|
|
2323
2386
|
if (result.status !== 0) {
|
|
2324
2387
|
throw new Error(`Failed to ${action}.`);
|
|
2325
2388
|
}
|
|
@@ -4389,7 +4452,7 @@ async function main() {
|
|
|
4389
4452
|
console.log("Deploy check passed: Dockerfile found and Docker is available.");
|
|
4390
4453
|
}
|
|
4391
4454
|
else {
|
|
4392
|
-
console.log(
|
|
4455
|
+
console.log(deployCheckSummary(check, command.options.publicAssets));
|
|
4393
4456
|
if (check.serverCode) {
|
|
4394
4457
|
// The compressed server-code figure, printed on success as well as on
|
|
4395
4458
|
// warning/rejection, so the number the size limit is enforced on is always
|
|
@@ -4438,10 +4501,10 @@ async function main() {
|
|
|
4438
4501
|
const siteFlag = slug ? ` --site ${quoteSiteArg(slug)}` : "";
|
|
4439
4502
|
throw new Error(`${base}\n\nStuck? Report it with context: 9pm support${siteFlag} -m "<what happened>"`);
|
|
4440
4503
|
}
|
|
4441
|
-
const
|
|
4504
|
+
const databaseClause = deployDatabaseClause(result.databases);
|
|
4442
4505
|
const queued = result.deployment.status === "queued" || result.deployment.status === "publishing";
|
|
4443
4506
|
console.log(`${queued ? "Queued" : "Deployed"} ${result.project.slug} -> ${result.url}`);
|
|
4444
|
-
console.log(`Deployment ${result.deployment.id} ${result.containerImageBytes ? `uploaded ${formatBytesForCli(result.containerImageBytes)} image` : `uploaded ${result.uploaded} file(s)`}
|
|
4507
|
+
console.log(`Deployment ${result.deployment.id} ${result.containerImageBytes ? `uploaded ${formatBytesForCli(result.containerImageBytes)} image` : `uploaded ${result.uploaded} file(s)`}${databaseClause ? `, ${databaseClause}` : ""}.`);
|
|
4445
4508
|
if (queued) {
|
|
4446
4509
|
console.log(`Status: ${result.deployment.status}. Run 9pm apps to check when it becomes active.`);
|
|
4447
4510
|
}
|
|
@@ -5028,7 +5091,7 @@ Container deploys:
|
|
|
5028
5091
|
Requires Docker locally. The CLI builds linux/amd64, exports the image, uploads it through a short-lived session, then finalizes a queued deployment.
|
|
5029
5092
|
The app must listen on 0.0.0.0:$PORT - always read the PORT env var; on some placements the platform holds the selected --port and hands the app a different one. Use --health-path for a cheap readiness endpoint.
|
|
5030
5093
|
With --with-db, containers receive NINEPM_SQL_URL and NINEPM_DB_BINDINGS for the managed SQL bridge, plus NINEPM_SQL_TOKEN where that bridge requires one - send it as "Authorization: Bearer <token>" or every query is refused.
|
|
5031
|
-
With --with-auth, the app gets 9pm-managed end-user accounts (email one-time codes); it implies --with-db and is one-way
|
|
5094
|
+
With --with-auth, the app gets 9pm-managed end-user accounts (email one-time codes); it implies --with-db and is one-way: once enabled it stays on for every later deploy of the app, with or without the flag.
|
|
5032
5095
|
With --with-email, a signed-in end user can send app invitation emails; it implies --with-auth and works on public apps only.
|
|
5033
5096
|
9pm env set <app> KEY=value [KEY2=value2 ...] [--var] (secret by default; --var = readable config)
|
|
5034
5097
|
9pm env set <app> --from-file <path> [--var] (KEY=value lines; # comments, export prefix, quotes ok)
|