ninepm 0.59.0 → 0.61.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 -0
- package/dist/index.d.ts +17 -1
- package/dist/index.js +88 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ Authenticate in your own browser. The CLI stores the token in native protected s
|
|
|
23
23
|
|
|
24
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
25
|
|
|
26
|
+
A key for CI or a server is minted from a signed-in machine with `9pm api-key create --name <name>`; the token is printed once. `9pm api-key ls` lists your active keys and `9pm api-key revoke <key-id>` retires one. Each account holds at most 10 such keys at a time (login tokens do not count). Store the key under any name but `NINEPM_*` when it lives in a 9pm app's own environment, and pass it with `--token`.
|
|
27
|
+
|
|
26
28
|
## Inventory
|
|
27
29
|
|
|
28
30
|
List the apps you have on 9pm.ai before deploying or modifying:
|
package/dist/index.d.ts
CHANGED
|
@@ -116,7 +116,7 @@ export interface CreatedServiceToken {
|
|
|
116
116
|
clientSecret: string;
|
|
117
117
|
}
|
|
118
118
|
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
119
|
-
export declare const CLI_VERSION = "0.
|
|
119
|
+
export declare const CLI_VERSION = "0.61.0";
|
|
120
120
|
type CredentialBackend = "macos-keychain" | "windows-dpapi" | "linux-secret-service" | "file-plaintext" | "none";
|
|
121
121
|
export interface StoredCredentialEntry {
|
|
122
122
|
token: string;
|
|
@@ -396,6 +396,7 @@ export interface DeviceAuthProgressReporter {
|
|
|
396
396
|
export declare function createDeviceAuthProgressReporter(stream?: NodeJS.WriteStream): DeviceAuthProgressReporter;
|
|
397
397
|
export declare function noteCliOutdatedAdvisory(response: Response, log?: (line: string) => void): void;
|
|
398
398
|
export declare function resetCliOutdatedAdvisoryForTests(): void;
|
|
399
|
+
export declare function dashboardUrlForApiUrl(apiUrl: string): string;
|
|
399
400
|
type CliCommand = {
|
|
400
401
|
kind: "deploy";
|
|
401
402
|
options: DeployOptions;
|
|
@@ -453,6 +454,21 @@ type CliCommand = {
|
|
|
453
454
|
site?: string;
|
|
454
455
|
apiUrl: string;
|
|
455
456
|
token: string;
|
|
457
|
+
} | {
|
|
458
|
+
kind: "api-key";
|
|
459
|
+
action: {
|
|
460
|
+
kind: "create";
|
|
461
|
+
name?: string;
|
|
462
|
+
json: boolean;
|
|
463
|
+
} | {
|
|
464
|
+
kind: "ls";
|
|
465
|
+
json: boolean;
|
|
466
|
+
} | {
|
|
467
|
+
kind: "revoke";
|
|
468
|
+
keyId: string;
|
|
469
|
+
};
|
|
470
|
+
apiUrl: string;
|
|
471
|
+
token: string;
|
|
456
472
|
} | {
|
|
457
473
|
kind: "files";
|
|
458
474
|
action: {
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { managedHostnamesIn, siblingCallWarning, siblingsOf } from "./sibling-ho
|
|
|
15
15
|
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";
|
|
16
16
|
import { buildFileManifest, readManifestFile } from "./manifest.js";
|
|
17
17
|
import { API_KEY_HELP, API_URL_HELP, CLI_CLIENT_ID, DEFAULT_API_URL, reportCliError } from "./cli-config.js";
|
|
18
|
-
export const CLI_VERSION = "0.
|
|
18
|
+
export const CLI_VERSION = "0.61.0";
|
|
19
19
|
const PUBLIC_DASHBOARD_URL = "https://9pm.ai/dashboard";
|
|
20
20
|
const TOKEN_SERVICE = "9pm";
|
|
21
21
|
const TOKEN_ACCOUNT = "default";
|
|
@@ -2178,6 +2178,22 @@ function withFetchDiagnostics(fetchFn) {
|
|
|
2178
2178
|
function normalizeApiUrl(value) {
|
|
2179
2179
|
return value.replace(/\/+$/g, "");
|
|
2180
2180
|
}
|
|
2181
|
+
// The dashboard that manages what this CLI just deployed lives on the same
|
|
2182
|
+
// control plane as the API it talked to: api-<env>.9pm.ai is served beside
|
|
2183
|
+
// <env>.9pm.ai, and api.9pm.ai beside the public site. An origin outside that
|
|
2184
|
+
// shape (local dev, an unparseable URL) names production, the only dashboard a
|
|
2185
|
+
// tester could hold an account on.
|
|
2186
|
+
export function dashboardUrlForApiUrl(apiUrl) {
|
|
2187
|
+
let host;
|
|
2188
|
+
try {
|
|
2189
|
+
host = new URL(normalizeApiUrl(apiUrl)).hostname.toLowerCase();
|
|
2190
|
+
}
|
|
2191
|
+
catch {
|
|
2192
|
+
return PUBLIC_DASHBOARD_URL;
|
|
2193
|
+
}
|
|
2194
|
+
const environment = /^api-([a-z0-9-]+)\.9pm\.ai$/.exec(host);
|
|
2195
|
+
return environment ? `https://${environment[1]}.9pm.ai/dashboard` : PUBLIC_DASHBOARD_URL;
|
|
2196
|
+
}
|
|
2181
2197
|
function pathFromUpload(upload, deploymentId) {
|
|
2182
2198
|
const marker = `/v1/uploads/${encodeURIComponent(deploymentId)}/`;
|
|
2183
2199
|
const index = upload.path.indexOf(marker);
|
|
@@ -2291,6 +2307,9 @@ export function parseArgs(argv) {
|
|
|
2291
2307
|
if (command === "support") {
|
|
2292
2308
|
return parseSupportArgs(argv.slice(1));
|
|
2293
2309
|
}
|
|
2310
|
+
if (command === "api-key" || command === "api-keys") {
|
|
2311
|
+
return parseApiKeyArgs(argv.slice(1));
|
|
2312
|
+
}
|
|
2294
2313
|
if (command !== "deploy") {
|
|
2295
2314
|
throw new Error(`Unknown command: ${command}`);
|
|
2296
2315
|
}
|
|
@@ -2975,6 +2994,30 @@ function readBaseApiOptions(flags) {
|
|
|
2975
2994
|
function readApiToken(flags) {
|
|
2976
2995
|
return readStringFlag(flags, "token") ?? process.env.NINEPM_API_KEY ?? readStoredCredential();
|
|
2977
2996
|
}
|
|
2997
|
+
function parseApiKeyArgs(argv) {
|
|
2998
|
+
const subcommand = argv[0];
|
|
2999
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
3000
|
+
throw new UsageError();
|
|
3001
|
+
}
|
|
3002
|
+
const flags = parseFlags(argv);
|
|
3003
|
+
const base = readBaseApiOptions(flags);
|
|
3004
|
+
const json = flags.has("json");
|
|
3005
|
+
if (subcommand === "create") {
|
|
3006
|
+
const name = readStringFlag(flags, "name");
|
|
3007
|
+
return { kind: "api-key", ...base, action: { kind: "create", ...(name ? { name } : {}), json } };
|
|
3008
|
+
}
|
|
3009
|
+
if (subcommand === "ls" || subcommand === "list") {
|
|
3010
|
+
return { kind: "api-key", ...base, action: { kind: "ls", json } };
|
|
3011
|
+
}
|
|
3012
|
+
if (subcommand === "revoke") {
|
|
3013
|
+
const keyId = argv[1];
|
|
3014
|
+
if (!keyId || keyId.startsWith("--")) {
|
|
3015
|
+
throw new Error("Usage: 9pm api-key revoke <key-id>");
|
|
3016
|
+
}
|
|
3017
|
+
return { kind: "api-key", ...base, action: { kind: "revoke", keyId } };
|
|
3018
|
+
}
|
|
3019
|
+
throw new Error(`Unknown api-key subcommand: ${subcommand}`);
|
|
3020
|
+
}
|
|
2978
3021
|
function parseSupportArgs(argv) {
|
|
2979
3022
|
// Normalize the -m short flag so parseFlags (which only handles --flags) sees it.
|
|
2980
3023
|
const normalized = argv.map((arg) => (arg === "-m" ? "--message" : arg));
|
|
@@ -3999,7 +4042,7 @@ async function main() {
|
|
|
3999
4042
|
if (queued) {
|
|
4000
4043
|
console.log(`Status: ${result.deployment.status}. Run 9pm apps to check when it becomes active.`);
|
|
4001
4044
|
}
|
|
4002
|
-
console.log(`Manage: ${
|
|
4045
|
+
console.log(`Manage: ${dashboardUrlForApiUrl(command.options.apiUrl)}${command.options.owner ? `?owner=${encodeURIComponent(command.options.owner)}` : ""}`);
|
|
4003
4046
|
// Gated the same way as the reachability probe above: a programmatic
|
|
4004
4047
|
// caller injecting fetchFn opted out of the CLI's own network side trips.
|
|
4005
4048
|
if (!command.options.fetchFn) {
|
|
@@ -4234,6 +4277,10 @@ async function main() {
|
|
|
4234
4277
|
await runSupportCommand(command);
|
|
4235
4278
|
return;
|
|
4236
4279
|
}
|
|
4280
|
+
if (command.kind === "api-key") {
|
|
4281
|
+
await runApiKeyCommand(command);
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4237
4284
|
await runAccessCommand(command);
|
|
4238
4285
|
}
|
|
4239
4286
|
catch (error) {
|
|
@@ -4246,6 +4293,42 @@ async function main() {
|
|
|
4246
4293
|
process.exitCode = 1;
|
|
4247
4294
|
}
|
|
4248
4295
|
}
|
|
4296
|
+
async function runApiKeyCommand(command) {
|
|
4297
|
+
const base = { apiUrl: command.apiUrl, token: command.token };
|
|
4298
|
+
const action = command.action;
|
|
4299
|
+
if (action.kind === "create") {
|
|
4300
|
+
const result = await apiRequest(base, "/v1/me/api-keys", {
|
|
4301
|
+
method: "POST",
|
|
4302
|
+
body: JSON.stringify(action.name ? { name: action.name } : {}),
|
|
4303
|
+
});
|
|
4304
|
+
if (action.json) {
|
|
4305
|
+
console.log(JSON.stringify({ apiKey: result.apiKey, token: result.token }));
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
console.log(`Created API key ${result.apiKey.id} (${result.apiKey.name}). The token is shown once:`);
|
|
4309
|
+
console.log(result.token);
|
|
4310
|
+
console.log("Store it in your CI or server secret store under any name but NINEPM_*, and pass it as --token or NINEPM_API_KEY.");
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
if (action.kind === "ls") {
|
|
4314
|
+
const result = await apiRequest(base, "/v1/me/api-keys", { method: "GET" });
|
|
4315
|
+
const live = result.apiKeys.filter((key) => !key.revokedAt);
|
|
4316
|
+
if (action.json) {
|
|
4317
|
+
console.log(JSON.stringify({ apiKeys: live }));
|
|
4318
|
+
return;
|
|
4319
|
+
}
|
|
4320
|
+
if (live.length === 0) {
|
|
4321
|
+
console.log("No active API keys.");
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
for (const key of live) {
|
|
4325
|
+
console.log(`${key.id}\t${key.name}\t${key.prefix}...\tcreated ${key.createdAt.slice(0, 10)}\tlast used ${key.lastUsedAt ? key.lastUsedAt.slice(0, 10) : "never"}`);
|
|
4326
|
+
}
|
|
4327
|
+
return;
|
|
4328
|
+
}
|
|
4329
|
+
const revoked = await apiRequest(base, `/v1/me/api-keys/${encodeURIComponent(action.keyId)}/revoke`, { method: "POST" });
|
|
4330
|
+
console.log(`Revoked API key ${revoked.apiKey.id} (${revoked.apiKey.name}).`);
|
|
4331
|
+
}
|
|
4249
4332
|
async function runSupportCommand(command) {
|
|
4250
4333
|
const subject = command.subject ?? (command.message.split("\n")[0] ?? command.message).slice(0, 200);
|
|
4251
4334
|
const body = {
|
|
@@ -4487,6 +4570,9 @@ function printUsage() {
|
|
|
4487
4570
|
9pm access service revoke <site> <service-token-id>
|
|
4488
4571
|
9pm access token create <site> [--name <name>]
|
|
4489
4572
|
9pm access token revoke <site> <token-id>
|
|
4573
|
+
9pm api-key create [--name <name>] [--json] mint a platform API key for CI or a server; the token is shown once
|
|
4574
|
+
9pm api-key ls [--json]
|
|
4575
|
+
9pm api-key revoke <key-id>
|
|
4490
4576
|
|
|
4491
4577
|
Environment:
|
|
4492
4578
|
NINEPM_API_URL ${API_URL_HELP}
|