ninepm 0.53.0 → 0.55.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/dist/cli-types.d.ts +7 -0
- package/dist/deploy-limits.js +8 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +67 -1
- package/package.json +1 -1
package/dist/cli-types.d.ts
CHANGED
|
@@ -16,6 +16,13 @@ export interface Project {
|
|
|
16
16
|
createdAt: string;
|
|
17
17
|
updatedAt: string;
|
|
18
18
|
}
|
|
19
|
+
export interface AppUserFilesUsage {
|
|
20
|
+
usedBytes: number;
|
|
21
|
+
pendingBytes: number;
|
|
22
|
+
ceilingBytes: number;
|
|
23
|
+
ceilingIsDefault: boolean;
|
|
24
|
+
fileCount: number;
|
|
25
|
+
}
|
|
19
26
|
export interface FileManifestEntry {
|
|
20
27
|
path: string;
|
|
21
28
|
size: number;
|
package/dist/deploy-limits.js
CHANGED
|
@@ -33,7 +33,14 @@ export function formatKilobytes(bytes) {
|
|
|
33
33
|
return Number.isInteger(kb) ? `${kb} kB` : `${kb.toFixed(1)} kB`;
|
|
34
34
|
}
|
|
35
35
|
export function assetFileTooLargeMessage(filePath, size) {
|
|
36
|
-
|
|
36
|
+
// Carries the remedy as well as the ceiling, and only a remedy that holds for EVERY
|
|
37
|
+
// oversized file: this runs over the whole manifest, so the file may be the configured
|
|
38
|
+
// worker entry, where "leave it out" produces a missing-entry failure instead. Shrinking
|
|
39
|
+
// is the one option that is always available. Deliberately silent on HOW — that depends
|
|
40
|
+
// on the file, and "compress the image" reads as advice from something that has looked
|
|
41
|
+
// at it.
|
|
42
|
+
return (`File "${filePath}" is ${formatMebibytes(size)}; individual files are limited to ${formatMebibytes(MAX_STATIC_ASSET_FILE_BYTES)}. ` +
|
|
43
|
+
"Shrink the file before deploying again.");
|
|
37
44
|
}
|
|
38
45
|
// Manifest entries that exceed the per-file limit. A structural type keeps this
|
|
39
46
|
// module free of cross-tree type imports so it copies cleanly into the CLI.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Agent } from "undici";
|
|
3
|
-
import type { AccessGrant, AccessGrantType, AccessProviderKey, AppAccessSettings, AppAccessServiceToken, AppAccessToken, AppDatabase, AppRuntime, ContainerImageUploadInstruction, ContainerRegion, Deployment, ManagedFileObject, ManagedFileShare, ManagedFileUsage, Project } from "./cli-types.js";
|
|
3
|
+
import type { AccessGrant, AccessGrantType, AccessProviderKey, AppAccessSettings, AppAccessServiceToken, AppAccessToken, AppDatabase, AppRuntime, AppUserFilesUsage, ContainerImageUploadInstruction, ContainerRegion, Deployment, ManagedFileObject, ManagedFileShare, ManagedFileUsage, Project } from "./cli-types.js";
|
|
4
4
|
export interface DeployOptions {
|
|
5
5
|
directory: string;
|
|
6
6
|
apiUrl: string;
|
|
@@ -114,7 +114,7 @@ export interface CreatedServiceToken {
|
|
|
114
114
|
clientSecret: string;
|
|
115
115
|
}
|
|
116
116
|
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
117
|
-
export declare const CLI_VERSION = "0.
|
|
117
|
+
export declare const CLI_VERSION = "0.55.0";
|
|
118
118
|
type CredentialBackend = "macos-keychain" | "windows-dpapi" | "linux-secret-service" | "file-plaintext" | "none";
|
|
119
119
|
export interface StoredCredentialEntry {
|
|
120
120
|
token: string;
|
|
@@ -351,6 +351,9 @@ export declare function signupDevice(options: {
|
|
|
351
351
|
}>;
|
|
352
352
|
export declare function expiredDeviceAuthMessage(purpose: string, userCode: string, lifetimeMs: number, verificationUri: string): string;
|
|
353
353
|
export declare function listManagedFiles(options: FileCommandOptions): Promise<ManagedFileListResult>;
|
|
354
|
+
export declare function getAppUserFilesUsage(options: ApiOptions & {
|
|
355
|
+
site: string;
|
|
356
|
+
}): Promise<AppUserFilesUsage>;
|
|
354
357
|
export declare function putManagedFile(options: FilePutOptions): Promise<ManagedFilePutResult>;
|
|
355
358
|
export declare function getManagedFile(options: FileGetOptions): Promise<{
|
|
356
359
|
fileId: string;
|
|
@@ -450,6 +453,10 @@ type CliCommand = {
|
|
|
450
453
|
action: {
|
|
451
454
|
kind: "list";
|
|
452
455
|
project?: string;
|
|
456
|
+
} | {
|
|
457
|
+
kind: "usage";
|
|
458
|
+
project?: string;
|
|
459
|
+
json: boolean;
|
|
453
460
|
} | {
|
|
454
461
|
kind: "put";
|
|
455
462
|
localPath: string;
|
|
@@ -589,6 +596,7 @@ export declare function classifyBrowserOpen(input: {
|
|
|
589
596
|
error?: unknown;
|
|
590
597
|
}): BrowserOpenResult;
|
|
591
598
|
export declare function hasNoGraphicalDisplay(platform: string, env: Record<string, string | undefined>): boolean;
|
|
599
|
+
export declare function formatAppUserFilesUsageLine(slug: string, usage: AppUserFilesUsage): string;
|
|
592
600
|
export declare class UsageError extends Error {
|
|
593
601
|
}
|
|
594
602
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { MAX_DEPLOY_DESCRIPTION_LENGTH, normalizeDeployDescription } from "./dep
|
|
|
14
14
|
import { MAX_STATIC_ASSET_FILE_BYTES, MAX_WORKER_ENTRY_COMPRESSED_BYTES, MAX_WORKER_SCRIPT_COMPRESSED_BYTES, WORKER_ENTRY_COMPRESSED_WARN_BYTES, assetFileTooLargeMessage, formatMebibytes, formatMegabytes, oversizedAssets, workerBundleNearLimitMessage, workerBundleTooLargeMessage, workerEntryNearLimitMessage, workerEntryTooLargeMessage, } from "./deploy-limits.js";
|
|
15
15
|
import { buildFileManifest, readManifestFile } from "./manifest.js";
|
|
16
16
|
import { API_KEY_HELP, API_URL_HELP, CLI_CLIENT_ID, DEFAULT_API_URL, reportCliError } from "./cli-config.js";
|
|
17
|
-
export const CLI_VERSION = "0.
|
|
17
|
+
export const CLI_VERSION = "0.55.0";
|
|
18
18
|
const PUBLIC_DASHBOARD_URL = "https://9pm.ai/dashboard";
|
|
19
19
|
const TOKEN_SERVICE = "9pm";
|
|
20
20
|
const TOKEN_ACCOUNT = "default";
|
|
@@ -1450,6 +1450,11 @@ async function apiRequest(options, route, init) {
|
|
|
1450
1450
|
export async function listManagedFiles(options) {
|
|
1451
1451
|
return apiRequest(options, filesRoute(options), { method: "GET" });
|
|
1452
1452
|
}
|
|
1453
|
+
// Per-app user-file usage + ceiling (files an app's signed-in end users uploaded through
|
|
1454
|
+
// ctx.files) — a different endpoint from the owner's own managed-file library above.
|
|
1455
|
+
export async function getAppUserFilesUsage(options) {
|
|
1456
|
+
return apiRequest(options, `/v1/sites/${encodeURIComponent(options.site)}/files/usage`, { method: "GET" });
|
|
1457
|
+
}
|
|
1453
1458
|
export async function putManagedFile(options) {
|
|
1454
1459
|
const fetchFn = options.fetchFn ?? fetch;
|
|
1455
1460
|
const apiUrl = normalizeApiUrl(options.apiUrl);
|
|
@@ -2426,6 +2431,15 @@ function parseFilesArgs(argv) {
|
|
|
2426
2431
|
if (subcommand === "list") {
|
|
2427
2432
|
return { kind: "files", ...base, action: { kind: "list", ...withProject } };
|
|
2428
2433
|
}
|
|
2434
|
+
if (subcommand === "usage") {
|
|
2435
|
+
// Per-app user-file usage. --project scopes to one app; omitting it entirely is the
|
|
2436
|
+
// account-wide overview. But a present-but-empty --project/--site is a typo, not the
|
|
2437
|
+
// overview: reject it rather than silently fanning out over every app.
|
|
2438
|
+
if ((flags.has("project") || flags.has("site")) && !project) {
|
|
2439
|
+
throw new Error("--project needs an app slug, e.g. `9pm files usage --project my-app`.");
|
|
2440
|
+
}
|
|
2441
|
+
return { kind: "files", ...base, action: { kind: "usage", ...withProject, json: flags.has("json") } };
|
|
2442
|
+
}
|
|
2429
2443
|
if (subcommand === "put") {
|
|
2430
2444
|
const localPath = argv[1];
|
|
2431
2445
|
if (!localPath || localPath.startsWith("--")) {
|
|
@@ -3831,6 +3845,19 @@ function formatBytesForCli(value) {
|
|
|
3831
3845
|
}
|
|
3832
3846
|
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)}GB`;
|
|
3833
3847
|
}
|
|
3848
|
+
// One `9pm files usage` row: "<slug>\t<used> / <ceiling> (<pct>%)\t<n> files", mirroring
|
|
3849
|
+
// the `used of total (percent%)` disk-usage style.
|
|
3850
|
+
export function formatAppUserFilesUsageLine(slug, usage) {
|
|
3851
|
+
const ceiling = usage.ceilingIsDefault ? `${formatBytesForCli(usage.ceilingBytes)} (default)` : formatBytesForCli(usage.ceilingBytes);
|
|
3852
|
+
// Used-over-ceiling has no meaningful percent — a zero-byte ceiling with files, or a
|
|
3853
|
+
// ceiling lowered below current usage — so say "over limit" instead of a misleading 0%.
|
|
3854
|
+
const status = usage.usedBytes > usage.ceilingBytes
|
|
3855
|
+
? "over limit"
|
|
3856
|
+
: usage.ceilingBytes > 0
|
|
3857
|
+
? `${Math.round((usage.usedBytes / usage.ceilingBytes) * 100)}%`
|
|
3858
|
+
: "0%";
|
|
3859
|
+
return `${slug}\t${formatBytesForCli(usage.usedBytes)} / ${ceiling} (${status})\t${usage.fileCount} files`;
|
|
3860
|
+
}
|
|
3834
3861
|
// Human remaining time for the device-auth wait (#509). Ceil so a window with
|
|
3835
3862
|
// any time left never reads "0s", and clamp so a passed deadline reads "0s"
|
|
3836
3863
|
// rather than a negative.
|
|
@@ -4285,6 +4312,44 @@ async function runFilesCommand(command) {
|
|
|
4285
4312
|
console.log(`Usage: ${formatBytesForCli(listed.usage.usedBytes)} / ${formatBytesForCli(listed.usage.quotaBytes)}`);
|
|
4286
4313
|
return;
|
|
4287
4314
|
}
|
|
4315
|
+
if (command.action.kind === "usage") {
|
|
4316
|
+
const action = command.action;
|
|
4317
|
+
if (action.project) {
|
|
4318
|
+
const usage = await getAppUserFilesUsage({ ...base, site: action.project });
|
|
4319
|
+
if (action.json) {
|
|
4320
|
+
console.log(JSON.stringify({ project: action.project, ...usage }, null, 2));
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
console.log(formatAppUserFilesUsageLine(action.project, usage));
|
|
4324
|
+
return;
|
|
4325
|
+
}
|
|
4326
|
+
// No --project: usage across every app the account owns. A dedicated overview, so the
|
|
4327
|
+
// per-app fan-out (one usage call each, in parallel) lives here rather than slowing the
|
|
4328
|
+
// plain `9pm apps` inventory.
|
|
4329
|
+
const { projects } = await listSites(base);
|
|
4330
|
+
// Isolate per-app failures: one app erroring (a transient 5xx, or a project deleted
|
|
4331
|
+
// between listSites and here) must not blank the rest of the table.
|
|
4332
|
+
const rows = await Promise.all(projects.map(async (p) => {
|
|
4333
|
+
try {
|
|
4334
|
+
return { slug: p.slug, usage: await getAppUserFilesUsage({ ...base, site: p.id }) };
|
|
4335
|
+
}
|
|
4336
|
+
catch {
|
|
4337
|
+
return { slug: p.slug, usage: null };
|
|
4338
|
+
}
|
|
4339
|
+
}));
|
|
4340
|
+
if (action.json) {
|
|
4341
|
+
console.log(JSON.stringify(rows.map((r) => (r.usage ? { project: r.slug, ...r.usage } : { project: r.slug, unavailable: true })), null, 2));
|
|
4342
|
+
return;
|
|
4343
|
+
}
|
|
4344
|
+
if (rows.length === 0) {
|
|
4345
|
+
console.log("No apps. Run `9pm deploy` to create one.");
|
|
4346
|
+
return;
|
|
4347
|
+
}
|
|
4348
|
+
for (const row of rows) {
|
|
4349
|
+
console.log(row.usage ? formatAppUserFilesUsageLine(row.slug, row.usage) : `${row.slug}\t(usage unavailable)`);
|
|
4350
|
+
}
|
|
4351
|
+
return;
|
|
4352
|
+
}
|
|
4288
4353
|
if (command.action.kind === "put") {
|
|
4289
4354
|
const result = await putManagedFile({ ...base, ...project, localPath: command.action.localPath, ...(command.action.remotePath ? { remotePath: command.action.remotePath } : {}) });
|
|
4290
4355
|
console.log(`Stored ${result.file.path} (${formatBytesForCli(result.file.size)}).`);
|
|
@@ -4346,6 +4411,7 @@ function printUsage() {
|
|
|
4346
4411
|
9pm latest [--json]
|
|
4347
4412
|
9pm support -m "<what happened>" [--subject <subject>] [--site <app>]
|
|
4348
4413
|
9pm files list [--project <site>]
|
|
4414
|
+
9pm files usage [--project <site>] [--json]
|
|
4349
4415
|
9pm files put <local-path> [--path <remote-path>] [--project <site>]
|
|
4350
4416
|
9pm files get <file-id> [--output <path>] [--project <site>]
|
|
4351
4417
|
9pm files share <file-id> [--expires 24h] [--public] [--project <site>]
|