diff --git a/README.md b/README.md index 07b64dc..0ab2751 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ This repository holds the two public npm packages of AntelopeJS Cloud: - `@antelopejs/cloud-sdk` in [`sdk/`](sdk/README.md) is a typed client for the AntelopeJS Cloud API. Its types are generated from the OpenAPI spec in `sdk/openapi/`, which the package also ships. -- `@antelopejs/cloud` in [`cli/`](cli/README.md) is the `ajs-cloud` command-line client. It deploys a folder to AntelopeJS Cloud and operates it, through the SDK. +- `@antelopejs/cloud` in [`cli/`](cli/README.md) is the command-line client, run as `ajs cloud`. It deploys a folder to AntelopeJS Cloud and operates it, through the SDK. Both need Node.js 20.19 or later. ## Install -Install the CLI globally to get the `ajs-cloud` command: +Install the CLI globally to get the `ajs cloud` command, a plugin of the `ajs` command of `@antelopejs/core`: ```sh npm install -g @antelopejs/cloud @@ -26,10 +26,10 @@ npm install @antelopejs/cloud-sdk Log in once, then deploy from the folder of your app: ```sh -ajs-cloud login +ajs cloud login cd my-app -ajs-cloud deploy -ajs-cloud status +ajs cloud deploy +ajs cloud status ``` The first `deploy` creates the project and its service, then prints the public URL. The [CLI README](cli/README.md) lists every command and option. @@ -41,9 +41,9 @@ import { CloudApi, CloudApiError } from "@antelopejs/cloud-sdk"; const api = new CloudApi({ token: process.env.ANTELOPE_CLOUD_TOKEN! }); try { - console.log(await api.listProjects()); + console.log(await api.listProjects()); } catch (error) { - if (error instanceof CloudApiError) console.error(error.status, error.code); + if (error instanceof CloudApiError) console.error(error.status, error.code); } ``` @@ -62,8 +62,8 @@ const api = new CloudApi({ token, baseUrl: "https://api.example.test" }); To reach it from the CLI, pass `--api-url` or set `ANTELOPE_CLOUD_URL`. The flag wins over the variable. Log in against that API first, because a saved session belongs to the API it was opened on: ```sh -ajs-cloud login --api-url https://api.example.test -ANTELOPE_CLOUD_URL=https://api.example.test ajs-cloud status +ajs cloud login --api-url https://api.example.test +ANTELOPE_CLOUD_URL=https://api.example.test ajs cloud status ``` A command whose API differs from the one of the saved session stops before any request, with the error code `api_url_mismatch`. diff --git a/cli/README.md b/cli/README.md index 6f6e215..e652b72 100644 --- a/cli/README.md +++ b/cli/README.md @@ -1,7 +1,7 @@ # `@antelopejs/cloud` CLI -`ajs-cloud` deploys a folder to AntelopeJS Cloud and operates it: log in once, -run `ajs-cloud deploy`, and get a public URL. +`ajs cloud` deploys a folder to AntelopeJS Cloud and operates it: log in once, +run `ajs cloud deploy`, and get a public URL. ## Install @@ -14,18 +14,28 @@ npm install -g @antelopejs/cloud # or pnpm add -g @antelopejs/cloud -ajs-cloud --version +ajs cloud --version ``` -To run it without installing, `npx` fetches the latest version on each call: +`ajs cloud` is a plugin of the `ajs` command of `@antelopejs/core`: `ajs` +runs the `ajs-cloud` binary this package installs, which it finds in the +nearest `node_modules/.bin`, then on the `PATH`. A machine without the `ajs` +command gets it with `npm install -g @antelopejs/core`. The binary can also +be run by its own name. + +To run it without installing, `npx` fetches the latest version on each call. +Always give npx the scoped package name: ```sh npx @antelopejs/cloud login npx @antelopejs/cloud deploy ``` -The binary is `ajs-cloud`. The package also exports a Commander command -factory, meant to be mounted as `ajs cloud` by `@antelopejs/core`: +The hints the CLI prints name the command the way it was run: `ajs cloud …`, +or `npx @antelopejs/cloud …` when npx runs it. + +The package also exports a Commander command factory, for a program that +mounts the command itself: ```ts import { mountCloudCommand } from "@antelopejs/cloud/command"; @@ -34,17 +44,14 @@ import { Command } from "commander"; mountCloudCommand(new Command("ajs")); ``` -Released versions of core (1.4, 1.5) build the `ajs` command tree statically -and do not mount it yet: use `ajs-cloud` directly. - ## Quick start ```sh -ajs-cloud login # approve the login in your browser +ajs cloud login # approve the login in your browser cd my-app -ajs-cloud deploy # first deploy: creates the project and its service -ajs-cloud status -ajs-cloud logs web +ajs cloud deploy # first deploy: creates the project and its service +ajs cloud status +ajs cloud logs web ``` ## Commands @@ -61,23 +68,23 @@ ajs-cloud logs web | `logs ` | Prints the service's recent logs (last hour); `-f` follows them | | `rollback [revision]` | Rolls a service back to its previous revision, or to the given one | -`ajs-cloud --help` and `ajs-cloud --help` list every option, with +`ajs cloud --help` and `ajs cloud --help` list every option, with examples. ```sh -ajs-cloud link my-project --service api=apps/api --service app=apps/app -ajs-cloud link my-project --service web=. --service frontend=. --dockerfile frontend=frontend.Dockerfile # two services of one folder -ajs-cloud deploy # all services from the project root -ajs-cloud deploy api # one named service -cd apps/app && ajs-cloud deploy # inferred from the configured path -ajs-cloud logs api -f -ajs-cloud open [api] -ajs-cloud rollback api [revision-id] +ajs cloud link my-project --service api=apps/api --service app=apps/app +ajs cloud link my-project --service web=. --service frontend=. --dockerfile frontend=frontend.Dockerfile # two services of one folder +ajs cloud deploy # all services from the project root +ajs cloud deploy api # one named service +cd apps/app && ajs cloud deploy # inferred from the configured path +ajs cloud logs api -f +ajs cloud open [api] +ajs cloud rollback api [revision-id] ``` ### First deploy -In a folder that is not linked yet, `ajs-cloud deploy` creates what it needs +In a folder that is not linked yet, `ajs cloud deploy` creates what it needs instead of failing: 1. It checks that your workspace can deploy. A new workspace takes about a @@ -116,7 +123,7 @@ ask on, `deploy` stops before any write with exit code 2 and names the flags to pass: ```sh -ajs-cloud deploy --yes --project my-app --port 8080 +ajs cloud deploy --yes --project my-app --port 8080 ``` A project named with `--project` that already exists is deployed to as-is. @@ -130,7 +137,7 @@ deploys from it use its own link. Leave the folder out of the parent's upload with a `.cloudignore`: ```sh -cd frontend && ajs-cloud deploy frontend --yes --project my-app --port 3000 +cd frontend && ajs cloud deploy frontend --yes --project my-app --port 3000 ``` ### Following a deployment @@ -148,13 +155,13 @@ and ends with the total time and the public URL: Deployed web · 1m 31s Next steps - ajs-cloud status check where it is - ajs-cloud logs web -f read the service logs + ajs cloud status check where it is + ajs cloud logs web -f read the service logs https://my-app.antelopejs.io ``` The progress and the summary go to stderr; the public URL is the result, on -stdout, so `URL=$(ajs-cloud deploy -q)` captures it. +stdout, so `URL=$(ajs cloud deploy -q)` captures it. A new service's address is `-.antelopejs.io`, without repeating what the project name already says: a service named `web`, `app` or @@ -176,15 +183,15 @@ and a quiet phase is reported every 30 seconds (`--timeout ` to change it). The deployment itself keeps running: the command prints its console page and exits with an error. - **Ctrl+C** stops the CLI, not the deployment, and exits with code 130. The - CLI says so and prints how to follow it: `ajs-cloud status`, - `ajs-cloud logs -f`, and the deployment's console page. + CLI says so and prints how to follow it: `ajs cloud status`, + `ajs cloud logs -f`, and the deployment's console page. - `status` shows a deployment still on its way, for example `web deploying (queued, position in queue: 1)` while it builds, or `web deploying (rolling out)` once it is built. ## Output, languages and errors -`ajs-cloud` follows the output contract of `ajs` and its plugins: +`ajs cloud` follows the output contract of `ajs` and its plugins: - **stdout carries results only**: tables (`status`), key/value lines (`whoami`), log lines, URLs (`open`, `deploy`), help and `--version`. @@ -197,7 +204,7 @@ and a quiet phase is reported every 30 seconds ``` ✖ You are not logged in - → Run ajs-cloud login, then run the command again + → Run ajs cloud login, then run the command again ``` `--verbose` adds the stack trace of the underlying failure. @@ -208,7 +215,7 @@ and a quiet phase is reported every 30 seconds (`-y`), `--no-browser`, `--lang en|fr`, `--api-url`, `-q`/`--quiet` (results, warnings and errors only), `--verbose[=channels]` and `--no-color`. They read the same environment as `ajs`: `NO_COLOR`, `FORCE_COLOR`, - `ANTELOPEJS_QUIET`, `ANTELOPEJS_VERBOSE` and `CI`. `ajs-cloud help + `ANTELOPEJS_QUIET`, `ANTELOPEJS_VERBOSE` and `CI`. `ajs cloud help environment` lists them. - **Languages**: the messages of the Cloud CLI are in English or French (`--lang`, else `LANG`). The texts shared with `ajs` (usage errors, help @@ -234,7 +241,7 @@ the token itself stays valid until it expires or is revoked. `--api-url` takes precedence over `ANTELOPE_CLOUD_URL`. Login uses that endpoint and binds the saved session to it. Other commands use the saved endpoint when neither is set. If an override differs from the saved endpoint, they stop before -any API request with `api_url_mismatch`; run `ajs-cloud login --api-url ` +any API request with `api_url_mismatch`; run `ajs cloud login --api-url ` to authenticate with the intended endpoint first. The CLI never moves the saved token to the override endpoint. diff --git a/cli/src/actions.ts b/cli/src/actions.ts index 4893276..33d7c59 100644 --- a/cli/src/actions.ts +++ b/cli/src/actions.ts @@ -13,6 +13,7 @@ import { import { consoleUrl } from "./console-urls.js"; import { CliError } from "./errors.js"; import { format, message } from "./i18n.js"; +import { asInvoked } from "./invocation.js"; import { isNetworkFailure } from "./network.js"; import { feedback, record, result } from "./output.js"; import { @@ -179,7 +180,7 @@ function reportLogin( headline: message("loggedIn", options.lang), nextSteps: [ { - command: "ajs-cloud deploy", + command: asInvoked("ajs-cloud deploy"), description: message("loginNext", options.lang), }, ], @@ -370,7 +371,7 @@ export async function rollback( throw new CliError("rollback_revision_unavailable", serviceName); const values = { service: serviceName, revision: String(revision.number) }; const prompter = runtime.prompter({ - command: `ajs-cloud rollback ${serviceName}`, + command: asInvoked(`ajs-cloud rollback ${serviceName}`), defaultsFlag: YES_FLAG, }); const isConfirmed = await prompter.confirm({ diff --git a/cli/src/bin.ts b/cli/src/bin.ts index 9772795..68d19cb 100644 --- a/cli/src/bin.ts +++ b/cli/src/bin.ts @@ -19,6 +19,7 @@ import { } from "./error-messages.js"; import { isNumber, isRecord, isString } from "./guards.js"; import { ENVIRONMENT_TOPIC, formatEnvironmentHelp } from "./help.js"; +import { commandName } from "./invocation.js"; import type { GlobalOptions } from "./types.js"; interface JsonError { @@ -132,7 +133,7 @@ function exitOnBrokenPipe(): void { const args = normalizeVerboseArguments(process.argv.slice(2)); const isJson = optionArguments(args).some((arg) => JSON_FLAGS.includes(arg)); -const program = createCloudCommand().name("ajs-cloud"); +const program = createCloudCommand().name(commandName()); function language(): string | undefined { return ( diff --git a/cli/src/command.ts b/cli/src/command.ts index 7abce42..3c7966a 100644 --- a/cli/src/command.ts +++ b/cli/src/command.ts @@ -12,6 +12,7 @@ import { DEFAULT_DEPLOY_TIMEOUT_MINUTES } from "./constants.js"; import { isRecord, isString } from "./guards.js"; import { applyCloudHelp, ENVIRONMENT_TOPIC } from "./help.js"; import { language } from "./i18n.js"; +import { asInvoked } from "./invocation.js"; import { defaultRuntime } from "./runtime.js"; import type { DeployOptions, @@ -46,6 +47,25 @@ function packageVersion(): string { return manifest.version; } +/** Examples with each command written the way the person runs it. */ +function invoked(examples: HelpExample[]): HelpExample[] { + return examples.map((example) => ({ + ...example, + command: asInvoked(example.command), + })); +} + +function invokedExamples(examples: HelpExample[], width: number): string { + return formatExamples(invoked(examples), width); +} + +function withInvokedExamples( + command: Command, + examples: HelpExample[], +): ReturnType { + return withExamples(command, invoked(examples)); +} + const ROOT_EXAMPLES: HelpExample[] = [ { description: "Log in once, with your browser", command: "ajs-cloud login" }, { @@ -65,11 +85,11 @@ const ROOT_EXAMPLES: HelpExample[] = [ function describeHelpFooter(width: number): string { const pointer = - `Run ${unbreakable("ajs-cloud --help")} for its options and examples, and ` + - `${unbreakable(`ajs-cloud help ${ENVIRONMENT_TOPIC}`)} for the variables read from the environment.`; + `Run ${unbreakable(asInvoked("ajs-cloud --help"))} for its options and examples, and ` + + `${unbreakable(asInvoked(`ajs-cloud help ${ENVIRONMENT_TOPIC}`))} for the variables read from the environment.`; return [ "", - formatExamples(ROOT_EXAMPLES, width), + invokedExamples(ROOT_EXAMPLES, width), "", ...wrapText(pointer, width), ].join("\n"); @@ -98,7 +118,7 @@ function loginCommand(runtime: Runtime): Command { const options = globals(self); await (await actions(options)).login(runtime, options); }); - return withExamples(command, [ + return withInvokedExamples(command, [ { description: "Log in to AntelopeJS Cloud", command: "ajs-cloud login" }, { description: "Log in to another endpoint", @@ -165,7 +185,7 @@ function deployCommand(runtime: Runtime): Command { ).deploy(runtime, options, service, deployOptions); }, ); - return withExamples(command, [ + return withInvokedExamples(command, [ { description: "Deploy every service of the linked folder", command: "ajs-cloud deploy", @@ -206,7 +226,7 @@ function linkCommand(runtime: Runtime): Command { ).link(runtime, options, project, linkOptions); }, ); - return withExamples(command, [ + return withInvokedExamples(command, [ { description: "Link a monorepo with two services", command: @@ -261,7 +281,7 @@ function logsCommand(runtime: Runtime): Command { ).logs(runtime, options, service, logsOptions); }, ); - return withExamples(command, [ + return withInvokedExamples(command, [ { description: "The last hour of logs", command: "ajs-cloud logs web" }, { description: "Follow new lines", command: "ajs-cloud logs web -f" }, ]); @@ -291,7 +311,7 @@ function rollbackCommand(runtime: Runtime): Command { ).rollback(runtime, options, service, revision); }, ); - return withExamples(command, [ + return withInvokedExamples(command, [ { description: "Back to the previous revision", command: "ajs-cloud rollback web", diff --git a/cli/src/deploy.ts b/cli/src/deploy.ts index a778220..4fb2eec 100644 --- a/cli/src/deploy.ts +++ b/cli/src/deploy.ts @@ -12,6 +12,7 @@ import { consoleUrl } from "./console-urls.js"; import { CliError } from "./errors.js"; import { createFirstDeployment } from "./first-deploy.js"; import { message } from "./i18n.js"; +import { asInvoked } from "./invocation.js"; import { record } from "./output.js"; import { DeployProgress, progressData, TERMINAL_STATUSES } from "./progress.js"; import { @@ -92,8 +93,8 @@ function detach(run: DeployRun, followed: Followed): void { } const lang = run.options.lang; const hints: [string, string][] = [ - ["ajs-cloud status", message("detachedStatus", lang)], - [`ajs-cloud logs ${name} -f`, message("detachedLogs", lang)], + [asInvoked("ajs-cloud status"), message("detachedStatus", lang)], + [asInvoked(`ajs-cloud logs ${name} -f`), message("detachedLogs", lang)], ]; const width = Math.max(...hints.map(([command]) => command.length)) + HINT_GAP; @@ -318,7 +319,7 @@ async function prepare( } const api = cloudApi(credentials); const prompter = runtime.prompter({ - command: "ajs-cloud deploy", + command: asInvoked("ajs-cloud deploy"), acceptsDefaults: options.yes === true, defaultsFlag: YES_FLAG, }); @@ -354,11 +355,11 @@ function succeeded( durationMs, nextSteps: [ { - command: "ajs-cloud status", + command: asInvoked("ajs-cloud status"), description: message("detachedStatus", lang), }, { - command: `ajs-cloud logs ${first} -f`, + command: asInvoked(`ajs-cloud logs ${first} -f`), description: message("detachedLogs", lang), }, ], diff --git a/cli/src/error-messages.ts b/cli/src/error-messages.ts index 6fc5667..e2c7006 100644 --- a/cli/src/error-messages.ts +++ b/cli/src/error-messages.ts @@ -7,6 +7,7 @@ import { import { CliError } from "./errors.js"; import { isRecord, isString } from "./guards.js"; import { language } from "./i18n.js"; +import { asInvoked } from "./invocation.js"; import { isFetchFailure, NETWORK_ERROR, networkCause } from "./network.js"; import { PROBLEM_TEXTS, type ProblemText } from "./problems.js"; @@ -79,8 +80,10 @@ const API_PROBLEMS = new Map([ type ProblemValues = Readonly>; function fill(text: string, values: ProblemValues): string { - return text.replace(PLACEHOLDER_PATTERN, (placeholder, name: string) => - Object.hasOwn(values, name) ? values[name] : placeholder, + return asInvoked(text).replace( + PLACEHOLDER_PATTERN, + (placeholder, name: string) => + Object.hasOwn(values, name) ? values[name] : placeholder, ); } diff --git a/cli/src/i18n.ts b/cli/src/i18n.ts index 10592a6..3a5bdc5 100644 --- a/cli/src/i18n.ts +++ b/cli/src/i18n.ts @@ -1,3 +1,4 @@ +import { asInvoked } from "./invocation.js"; export type Language = "en" | "fr"; export type MessageKey = keyof typeof MESSAGES.en; @@ -172,7 +173,7 @@ export function language(value?: string): Language { } export function message(key: MessageKey, value?: string): string { - return MESSAGES[language(value)][key]; + return asInvoked(MESSAGES[language(value)][key]); } /** diff --git a/cli/src/invocation.ts b/cli/src/invocation.ts new file mode 100644 index 0000000..8037d07 --- /dev/null +++ b/cli/src/invocation.ts @@ -0,0 +1,35 @@ +/** The name of the binary, as the texts of this package write a command. */ +const BINARY = "ajs-cloud"; +/** How the command is run through the `ajs` CLI, which loads it as a plugin. */ +const PLUGIN_COMMAND = "ajs cloud"; +/** How the command is run without installing anything. */ +const NPX_COMMAND = "npx @antelopejs/cloud"; +/** What npm sets `npm_command` to while `npx` or `npm exec` runs a package. */ +const NPM_EXEC = "exec"; + +/** + * How the person at the terminal runs this command: `ajs cloud`, or + * `npx @antelopejs/cloud` when npx runs it. A hint must name a command that + * person has: one who came through npx has neither `ajs` nor `ajs-cloud` on + * the PATH, and a bare name passed to npx would fetch whatever package holds + * that name on the registry. + * + * @param env Environment of the process + */ +export function commandName(env: NodeJS.ProcessEnv = process.env): string { + return env.npm_command === NPM_EXEC ? NPX_COMMAND : PLUGIN_COMMAND; +} + +/** + * A text of this package with each command it names written the way the + * person runs it. The texts themselves write `ajs-cloud`, the binary. + * + * @param text Hint, example or message + * @param env Environment of the process + */ +export function asInvoked( + text: string, + env: NodeJS.ProcessEnv = process.env, +): string { + return text.replaceAll(BINARY, commandName(env)); +} diff --git a/cli/test/first-deploy.test.ts b/cli/test/first-deploy.test.ts index 37c4dba..2f1e60a 100644 --- a/cli/test/first-deploy.test.ts +++ b/cli/test/first-deploy.test.ts @@ -270,7 +270,7 @@ describe("first deploy from an unlinked folder", () => { await assert.rejects(run(current.runtime, ["deploy"]), (error) => { assert.ok(error instanceof CliError); assert.equal(error.code, "project_creation_forbidden"); - assert.match(told(error), /ajs-cloud link/); + assert.match(told(error), /ajs cloud link/); return true; }); assert.deepEqual(posted(PROJECTS_PATH), []); diff --git a/cli/test/invocation.test.ts b/cli/test/invocation.test.ts new file mode 100644 index 0000000..17d81ae --- /dev/null +++ b/cli/test/invocation.test.ts @@ -0,0 +1,28 @@ +import assert from "node:assert/strict"; +import { describe, test } from "node:test"; +import { asInvoked, commandName } from "../src/invocation.js"; + +describe("how a hint names the command", () => { + test("names the ajs plugin command by default", () => { + assert.equal(commandName({}), "ajs cloud"); + assert.equal(commandName({ npm_command: "run-script" }), "ajs cloud"); + assert.equal( + asInvoked("Run ajs-cloud login, then ajs-cloud deploy", {}), + "Run ajs cloud login, then ajs cloud deploy", + ); + }); + + test("names the scoped package when npx runs the command", () => { + const env = { npm_command: "exec" }; + assert.equal(commandName(env), "npx @antelopejs/cloud"); + assert.equal( + asInvoked("Run ajs-cloud login", env), + "Run npx @antelopejs/cloud login", + ); + }); + + test("never names a bare command to a person who came through npx", () => { + const hint = asInvoked("ajs-cloud status", { npm_command: "exec" }); + assert.doesNotMatch(hint, /(^|\s)ajs(-cloud)?(\s|$)/); + }); +}); diff --git a/cli/test/ux-commands.test.ts b/cli/test/ux-commands.test.ts index 2ae69ca..163f77a 100644 --- a/cli/test/ux-commands.test.ts +++ b/cli/test/ux-commands.test.ts @@ -74,7 +74,7 @@ describe("C7: help, version and session commands", () => { const result = await runBinary(["whoami"], current); assert.match( result.stderr, - /session has expired or was revoked\n(.*\n)*.*Run ajs-cloud login again/, + /session has expired or was revoked\n(.*\n)*.*Run ajs cloud login again/, ); }); diff --git a/cli/test/ux-progress.test.ts b/cli/test/ux-progress.test.ts index 172cc71..c894b5a 100644 --- a/cli/test/ux-progress.test.ts +++ b/cli/test/ux-progress.test.ts @@ -67,8 +67,8 @@ describe("C5: deploy progress", () => { "ℹ web: live · 0s", "Deployed web · 0ms", "Next steps", - " ajs-cloud status check where it is", - " ajs-cloud logs web -f read the service logs", + " ajs cloud status check where it is", + " ajs cloud logs web -f read the service logs", ]); assert.deepEqual(current.streams.stdout, ["https://web.example.test"]); }); @@ -189,7 +189,7 @@ describe("C5: deploy progress", () => { assert.equal(error.code, "deployment_timeout"); assert.match( told(error), - /still running on the platform.*ajs-cloud status/, + /still running on the platform.*ajs cloud status/, ); assert.deepEqual(error.details, [ "web: still queued · 0s", @@ -215,8 +215,8 @@ describe("C5: deploy progress", () => { binary.stderr(), /Stopped following: the deployment keeps running on the platform/, ); - assert.match(binary.stderr(), /ajs-cloud status/); - assert.match(binary.stderr(), /ajs-cloud logs web -f/); + assert.match(binary.stderr(), /ajs cloud status/); + assert.match(binary.stderr(), /ajs cloud logs web -f/); assert.match( binary.stderr(), /deployment\?project=project-1&service=service-web&deployment=deployment-1/, @@ -235,7 +235,7 @@ describe("C5: deploy progress", () => { run(current.runtime, ["deploy"]), (error: CliError) => { assert.equal(error.code, "deployment_failed"); - assert.match(told(error), /ajs-cloud logs web/); + assert.match(told(error), /ajs cloud logs web/); assert.equal( error.details[0], "Reason: cloud.errors.builds.submit_rejected", diff --git a/cli/test/ux-scripts.test.ts b/cli/test/ux-scripts.test.ts index 76ff7d9..e358b95 100644 --- a/cli/test/ux-scripts.test.ts +++ b/cli/test/ux-scripts.test.ts @@ -31,7 +31,7 @@ describe("readable errors", () => { title: 'The project has no service named "api"', reason: undefined, details: undefined, - fixes: ["Run ajs-cloud status to list its services"], + fixes: ["Run ajs cloud status to list its services"], exitCode: undefined, }, ); @@ -58,7 +58,7 @@ describe("readable errors", () => { 404, "cloud.errors.project.unknown_project", ); - assert.match(told(error), /no longer exists.*ajs-cloud link/); + assert.match(told(error), /no longer exists.*ajs cloud link/); }); test("an unknown API refusal still names its code", () => { @@ -94,7 +94,7 @@ describe("C9: scripts, pipes and exit codes", () => { test("a bare invocation prints the help on stdout and succeeds", async () => { const result = await runBinary([]); assert.equal(result.status, 0); - assert.match(result.stdout, /^ajs-cloud \d+\.\d+\.\d+/); + assert.match(result.stdout, /^ajs cloud \d+\.\d+\.\d+/); assert.match(result.stdout, /Commands:/); assert.equal(result.stderr, ""); }); @@ -117,7 +117,7 @@ describe("C9: scripts, pipes and exit codes", () => { }); const result = await runBinary(["rollback", "web"], current); assert.equal(result.status, 2); - assert.match(result.stderr, /ajs-cloud rollback web --yes/); + assert.match(result.stderr, /ajs cloud rollback web --yes/); assert.ok(!requests.some((request) => request.startsWith("POST "))); }); @@ -127,7 +127,7 @@ describe("C9: scripts, pipes and exit codes", () => { assert.equal(result.status, 2); assert.match( result.stderr, - /ajs-cloud deploy --project --port --yes/, + /ajs cloud deploy --project --port --yes/, ); assert.ok(!requests.some((request) => request.startsWith("POST "))); }); diff --git a/cli/test/ux.test.ts b/cli/test/ux.test.ts index 81ec155..1e034cc 100644 --- a/cli/test/ux.test.ts +++ b/cli/test/ux.test.ts @@ -73,7 +73,7 @@ describe("C2: signed out", () => { assert.equal(result.status, 1); assert.match( result.stderr, - /^\S+ You are not logged in\n {2}\S+ Run ajs-cloud login, then run the command again\n$/, + /^\S+ You are not logged in\n {2}\S+ Run ajs cloud login, then run the command again\n$/, ); assert.equal(result.stdout, ""); assert.deepEqual(requests, []); @@ -85,7 +85,7 @@ describe("C2: signed out", () => { const result = await runBinary(["status"], current); assert.match( result.stderr, - /not linked to a project\n.*Run ajs-cloud deploy to create one/, + /not linked to a project\n.*Run ajs cloud deploy to create one/, ); const json = await runBinary(["--json", "status"], current); assert.equal(JSON.parse(json.stderr).error.code, "project_not_linked"); @@ -116,7 +116,7 @@ describe("C3: login polling", () => { "workspace-1", ); assert.ok(current.output.includes("✔ Logged in · workspace-1")); - assert.ok(current.output.some((line) => line.includes("ajs-cloud deploy"))); + assert.ok(current.output.some((line) => line.includes("ajs cloud deploy"))); }); test("N5: names the workspace the session was approved for", async () => { @@ -273,7 +273,7 @@ describe("C4: workspace not ready", () => { await assert.rejects(run(current.runtime, ["deploy"]), (error) => { assert.ok(error instanceof CliError); assert.equal(error.code, "workspace_not_ready"); - assert.match(told(error), /Run ajs-cloud deploy again/); + assert.match(told(error), /Run ajs cloud deploy again/); return true; }); assert.ok(current.clock.now - startedAt >= WORKSPACE_WAIT_MS); diff --git a/scripts/check-repository-hosts.test.mjs b/scripts/check-repository-hosts.test.mjs index 43dbfb1..51bc9b9 100644 --- a/scripts/check-repository-hosts.test.mjs +++ b/scripts/check-repository-hosts.test.mjs @@ -123,10 +123,7 @@ describe("check-repository-hosts", () => { ]); mkdirSync(path.join(directory, "skills")); symlinkSync("../skills", path.join(directory, "skills-link")); - symlinkSync( - `../${CLUSTER}/notes`, - path.join(directory, "private-link"), - ); + symlinkSync(`../${CLUSTER}/notes`, path.join(directory, "private-link")); git(directory, ["add", "--all"]); git(directory, ["commit", "--quiet", "--message", "chore: link"]); const result = check(directory); diff --git a/sdk/README.md b/sdk/README.md index 8fad542..e39e848 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -1,6 +1,6 @@ # `@antelopejs/cloud-sdk` -A typed client for the AntelopeJS Cloud API: the client the `ajs-cloud` CLI +A typed client for the AntelopeJS Cloud API: the client the `ajs cloud` CLI uses, without the CLI. Its only runtime dependency is `openapi-fetch`. ## Install