Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions bin/crawlproof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
meetsNodeFloor,
resolveRunner,
vendorBin,
wantsSelfUpdate,
} from '../src/crawlproof.ts';
import { spawnInherit } from '../src/codeburn.ts';
import { isMain } from '../src/is-main.ts';
Expand All @@ -41,9 +42,14 @@ import { isMain } from '../src/is-main.ts';
*/
const OURS = new Set(['--self-update', '--self-where']);


async function main(argv: string[]): Promise<number> {
const flags = new Set(argv.filter((argument) => OURS.has(argument)));
const rest = argv.filter((argument) => !OURS.has(argument));
const wantsUpdate = wantsSelfUpdate(argv);
const given = wantsUpdate ? argv.slice(1) : argv;

const flags = new Set(given.filter((argument) => OURS.has(argument)));
if (wantsUpdate) flags.add('--self-update');
const rest = given.filter((argument) => !OURS.has(argument));

if (!meetsNodeFloor(process.versions.node)) {
process.stderr.write(
Expand Down
19 changes: 19 additions & 0 deletions src/crawlproof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,25 @@ export function hasCoinpaySession(env: NodeJS.ProcessEnv = process.env): boolean
return existsSync(join(env.HOME ?? homedir(), '.coinpay.json'));
}

/**
* The plain words this wrapper claims for itself.
*
* Everything else goes to the dashboard untouched. These are the exception,
* deliberately and narrowly: installing is the wrapper's job and can never come
* to mean something upstream, and `update` is the word `cli-tools update`
* already uses, so typing it here and getting the dashboard's usage back is the
* command being wrong rather than the person.
*/
export const UPDATE_WORDS = new Set(['update', 'upgrade', 'self-update']);

/**
* Only the FIRST argument counts, so the word can never swallow some later
* subcommand's own argument (`crawlproof ads budget update` stays upstream's).
*/
export function wantsSelfUpdate(argv: readonly string[]): boolean {
return UPDATE_WORDS.has(argv[0] ?? '');
}

/** Whether a CrawlProof API token is reachable without the caller exporting one. */
export function hasToken(env: NodeJS.ProcessEnv = process.env): boolean {
if (env.CRAWLPROOF_TOKEN?.trim()) return true;
Expand Down
22 changes: 22 additions & 0 deletions test/crawlproof.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
resolveRunner,
vendorBin,
vendorRoot,
wantsSelfUpdate,
} from '../src/crawlproof.ts';
import { mkdtempSync, readFileSync, rmSync, existsSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
Expand Down Expand Up @@ -135,6 +136,27 @@ describe('meetsNodeFloor', () => {
});
});

describe('the update verb', () => {
it('claims the words that mean "install the latest"', () => {
expect(wantsSelfUpdate(['update'])).toBe(true);
expect(wantsSelfUpdate(['upgrade'])).toBe(true);
expect(wantsSelfUpdate(['self-update'])).toBe(true);
});

it('leaves every other first word to the dashboard', () => {
for (const word of ['dashboard', 'stats', 'ad', 'ads', 'help', 'version', '--json']) {
expect(wantsSelfUpdate([word])).toBe(false);
}
expect(wantsSelfUpdate([])).toBe(false);
});

it('only claims it in first position, so a subcommand argument is safe', () => {
// `crawlproof ads budget update` must reach upstream untouched.
expect(wantsSelfUpdate(['ads', 'budget', 'update'])).toBe(false);
expect(wantsSelfUpdate(['stats', 'update'])).toBe(false);
});
});

describe('credential probes', () => {
it('sees a token in the environment', () => {
expect(hasToken({ CRAWLPROOF_TOKEN: 'crp_x' })).toBe(true);
Expand Down
Loading