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
240 changes: 120 additions & 120 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"scripts": {
"build": "tsc --noEmit && tsup",
"dev": "export $(grep -v '^#' .env.test | xargs) && (tsc --noEmit --watch --preserveWatchOutput & tsup --watch)",
"cli": "node dist/cli.js",
"cli": "export $(grep -v '^#' .env.test | xargs) && node dist/cli.js",
Comment thread
designcode marked this conversation as resolved.
"lint": "eslint src test",
"lint:fix": "eslint src test --fix",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
Expand Down Expand Up @@ -93,7 +93,7 @@
"@aws-sdk/credential-providers": "^3.1038.0",
"@smithy/shared-ini-file-loader": "^4.4.9",
"@tigrisdata/iam": "^2.1.1",
"@tigrisdata/storage": "^3.2.1",
"@tigrisdata/storage": "^3.4.0",
"commander": "^14.0.3",
"enquirer": "^2.4.1",
"jose": "^6.2.3",
Expand Down
17 changes: 6 additions & 11 deletions scripts/generate-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,12 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import * as YAML from 'yaml';

import type { CommandSpec, Specs } from '../src/types.js';

const ROOT = process.cwd();
const SPECS_PATH = join(ROOT, 'src/specs.yaml');
const OUTPUT_PATH = join(ROOT, 'src/command-registry.ts');

interface CommandSpec {
name: string;
alias?: string;
commands?: CommandSpec[];
default?: string;
}

interface Specs {
commands: CommandSpec[];
}

interface RegistryEntry {
key: string;
importName: string;
Expand Down Expand Up @@ -88,6 +79,10 @@ function collectEntries(
const entries: RegistryEntry[] = [];

for (const cmd of commands) {
// Removed commands have no implementation file by design — the
// cli-core intercepts them and prints a redirect message.
if (cmd.removed) continue;

const currentPath = [...parentPath, cmd.name];

if (cmd.commands && cmd.commands.length > 0) {
Expand Down
20 changes: 12 additions & 8 deletions scripts/update-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ function isImplemented(...parts: string[]): boolean {
return paths.some((p) => existsSync(p) && !p.includes('/_'));
}

// Check if a command or any of its nested subcommands are implemented
// Check if a command or any of its nested subcommands are implemented.
// Removed commands are tombstones and never count as "implemented" for
// docs purposes — they shouldn't appear in the rendered README.
function hasImplementation(cmd: CommandSpec, ...parentParts: string[]): boolean {
if (cmd.removed) return false;
const parts = [...parentParts, cmd.name];
if (isImplemented(...parts)) return true;
if (cmd.commands) {
Expand All @@ -49,7 +52,7 @@ function generateCommandSection(cmd: CommandSpec): string {

lines.push(`### \`${cmd.name}\`${aliasStr}`);
lines.push('');
lines.push(cmd.description);
lines.push(cmd.description ?? '');
lines.push('');
lines.push('```');
const usage = getCommandUsage(cmd);
Expand All @@ -58,7 +61,8 @@ function generateCommandSection(cmd: CommandSpec): string {
lines.push('```');
lines.push('');

const flags = cmd.arguments?.filter((a) => a.type !== 'positional') || [];
const flags =
cmd.arguments?.filter((a) => a.type !== 'positional' && !a.removed) || [];
if (flags.length > 0) {
lines.push('| Flag | Description |');
lines.push('|------|-------------|');
Expand Down Expand Up @@ -113,7 +117,7 @@ function generateResourceSection(

lines.push(`${headerLevel} \`${fullName}\`${aliasStr}`);
lines.push('');
lines.push(cmd.description);
lines.push(cmd.description ?? '');
lines.push('');

const subcommands = cmd.commands || [];
Expand Down Expand Up @@ -270,7 +274,7 @@ function generateDocs(specs: Specs): string {
}

// Resource management
const resourceCommands = ['organizations', 'access-keys', 'credentials', 'buckets', 'forks', 'snapshots', 'objects', 'iam'];
const resourceCommands = ['organizations', 'access-keys', 'credentials', 'buckets', 'snapshots', 'objects', 'iam'];
const implementedResources = resourceCommands.filter((c) => {
const cmd = specs.commands.find((s) => s.name === c);
if (!cmd) return false;
Expand Down Expand Up @@ -341,12 +345,12 @@ function generateDocs(specs: Specs): string {
}
}

// Buckets section (buckets, forks, snapshots)
const bucketRelated = ['buckets', 'forks', 'snapshots'].filter((c) => implementedResources.includes(c));
// Buckets section (buckets, snapshots)
const bucketRelated = ['buckets', 'snapshots'].filter((c) => implementedResources.includes(c));
if (bucketRelated.length > 0) {
lines.push('### Buckets');
lines.push('');
lines.push('Buckets are containers for objects. You can also create forks and snapshots of buckets.');
lines.push('Buckets are containers for objects. You can also create snapshots of buckets.');
lines.push('');

for (const cmdName of bucketRelated) {
Expand Down
6 changes: 3 additions & 3 deletions src/auth/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ export interface Auth0Config {
export function getAuth0Config(): Auth0Config {
const isDev = process.env.TIGRIS_ENV === 'development';
const domain = isDev
? 'auth-dev.tigris.dev'
? (process.env.AUTH0_DOMAIN ?? 'auth-storage.tigris.dev')
: (process.env.AUTH0_DOMAIN ?? 'auth.storage.tigrisdata.io');
const clientId = isDev
? 'JdJVYIyw0O1uHi5L5OJH903qaWBgd3gF'
? (process.env.AUTH0_CLIENT_ID ?? 'JdJVYIyw0O1uHi5L5OJH903qaWBgd3gF')
: (process.env.AUTH0_CLIENT_ID ?? 'DMejqeM3CQ4IqTjEcd3oA9eEiT40hn8D');
const audience = isDev
? 'https://tigris-api-dev'
? (process.env.AUTH0_AUDIENCE ?? 'https://tigris-api-dev')
: (process.env.AUTH0_AUDIENCE ?? 'https://tigris-os-api');
const claimsNamespace =
process.env.TIGRIS_CLAIMS_NAMESPACE ?? 'https://tigris';
Expand Down
131 changes: 106 additions & 25 deletions src/cli-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import { exitWithError } from '@utils/exit.js';
import { printDeprecated } from '@utils/messages.js';
import { Command as CommanderCommand } from 'commander';
import { Command as CommanderCommand, Option } from 'commander';

import type { Argument, CommandSpec, Specs } from './types.js';

Expand Down Expand Up @@ -164,6 +164,12 @@ export function commandHasAnyImplementation(
pathParts: string[],
hasImplementation: ImplementationChecker
): boolean {
// Removed commands are still registered so we can intercept and
// redirect users to the replacement instead of "unknown command".
if (command.removed) {
return true;
}

if (hasImplementation(pathParts)) {
return true;
}
Expand All @@ -181,22 +187,58 @@ export function commandHasAnyImplementation(
return false;
}

/**
* Print a redirect message and exit. Used for hard-removed commands
* and arguments. `subject` is the human-readable thing the user invoked
* (e.g. `tigris buckets set-ttl` or `--region`).
*/
function printRemovedAndExit(
subject: string,
replacedBy: string | undefined
): never {
const hint = replacedBy
? ` Use ${replacedBy} instead.`
: ' See the changelog for migration guidance.';
console.error(`${subject} was removed in this version.${hint}`);
process.exit(1);
}

/**
* Inspect parsed options for any argument the spec marks as removed.
* If the user supplied one, print the redirect and exit.
*/
function checkRemovedArguments(
args: Argument[] | undefined,
options: Record<string, unknown>
): void {
if (!args) return;
for (const arg of args) {
if (!arg.removed) continue;
const value = getOptionValue(options, arg.name, args);
if (value !== undefined) {
printRemovedAndExit(`--${arg.name}`, arg.replaced_by);
}
}
}

export function showCommandHelp(
specs: Specs,
command: CommandSpec,
pathParts: string[],
hasImplementation: ImplementationChecker
) {
const fullPath = pathParts.join(' ');
console.log(`\n${specs.name} ${fullPath} - ${command.description}\n`);
console.log(`\n${specs.name} ${fullPath} - ${command.description ?? ''}\n`);

if (command.commands && command.commands.length > 0) {
const availableCmds = command.commands.filter((cmd) =>
commandHasAnyImplementation(
cmd,
[...pathParts, cmd.name],
hasImplementation
)
const availableCmds = command.commands.filter(
(cmd) =>
!cmd.removed &&
commandHasAnyImplementation(
cmd,
[...pathParts, cmd.name],
hasImplementation
)
);

if (availableCmds.length > 0) {
Expand All @@ -208,14 +250,17 @@ export function showCommandHelp(
cmdPart += ` (${aliases.join(', ')})`;
}
const paddedCmdPart = cmdPart.padEnd(24);
console.log(`${paddedCmdPart}${cmd.description}`);
console.log(`${paddedCmdPart}${cmd.description ?? ''}`);
});
console.log();
}
}

const globalArgs = specs.definitions?.global_arguments ?? [];
const effectiveArgs = getEffectiveArguments(globalArgs, command.arguments);
const effectiveArgs = getEffectiveArguments(
globalArgs,
command.arguments
).filter((arg) => !arg.removed);
if (effectiveArgs.length > 0) {
console.log('Arguments:');
effectiveArgs.forEach((arg) => {
Expand Down Expand Up @@ -248,8 +293,10 @@ export function showMainHelp(
console.log('Usage: tigris [command] [options]\n');
console.log('Commands:');

const availableCommands = specs.commands.filter((cmd) =>
commandHasAnyImplementation(cmd, [cmd.name], hasImplementation)
Comment thread
cursor[bot] marked this conversation as resolved.
const availableCommands = specs.commands.filter(
(cmd) =>
!cmd.removed &&
commandHasAnyImplementation(cmd, [cmd.name], hasImplementation)
);

availableCommands.forEach((command: CommandSpec) => {
Expand All @@ -261,7 +308,7 @@ export function showMainHelp(
commandPart += ` (${aliases.join(', ')})`;
}
const paddedCommandPart = commandPart.padEnd(24);
console.log(`${paddedCommandPart}${command.description}`);
console.log(`${paddedCommandPart}${command.description ?? ''}`);
});
console.log(
`\nUse "${specs.name} <command> help" for more information about a command.`
Expand Down Expand Up @@ -319,7 +366,15 @@ export function addArgumentsToCommand(
arg.required || arg['required-when'] ? ' <value>' : ' [value]';
}

cmd.option(optionString, arg.description, arg.default);
if (arg.removed) {
// Register but hide from --help so commander still parses the
// value; the dispatch handler intercepts it post-parse.
cmd.addOption(
new Option(optionString, arg.description ?? '').hideHelp()
);
} else {
cmd.option(optionString, arg.description ?? '', arg.default);
}
}
});
}
Expand Down Expand Up @@ -493,13 +548,29 @@ export function registerCommands(
continue;
}

const cmd = parent.command(spec.name).description(spec.description);
const cmd = parent
.command(spec.name, spec.removed ? { hidden: true } : undefined)
.description(spec.description ?? '');

if (spec.alias) {
const aliases = Array.isArray(spec.alias) ? spec.alias : [spec.alias];
aliases.forEach((alias) => cmd.alias(alias));
}

// Removed commands: register a redirect-and-exit action; skip
// children, arguments, and help registration entirely.
if (spec.removed) {
cmd.allowUnknownOption(true);
cmd.allowExcessArguments(true);
cmd.action(() => {
printRemovedAndExit(
`${specs.name} ${currentPath.join(' ')}`,
spec.replaced_by
);
});
continue;
}

if (spec.commands && spec.commands.length > 0) {
// Has children - recurse
registerCommands(config, cmd, spec.commands, currentPath);
Expand All @@ -524,16 +595,21 @@ export function registerCommands(
hasImplementation
);

const extracted = extractArgumentValues(
allArguments,
positionalArgs,
options
);

if (
allArguments.length > 0 &&
!validateRequiredWhen(
allArguments,
extractArgumentValues(allArguments, positionalArgs, options)
)
!validateRequiredWhen(allArguments, extracted)
) {
return;
}

checkRemovedArguments(allArguments, extracted);

if (defaultCmd.deprecated && defaultCmd.messages?.onDeprecated) {
printDeprecated(defaultCmd.messages.onDeprecated);
}
Expand All @@ -542,7 +618,7 @@ export function registerCommands(
loadModule,
[...currentPath, defaultCmd.name],
positionalArgs,
extractArgumentValues(allArguments, positionalArgs, options)
extracted
);
});
}
Expand Down Expand Up @@ -570,16 +646,21 @@ export function registerCommands(
const options = args.pop();
const positionalArgs = args;

const extracted = extractArgumentValues(
spec.arguments || [],
positionalArgs,
options
);

if (
spec.arguments &&
!validateRequiredWhen(
spec.arguments,
extractArgumentValues(spec.arguments, positionalArgs, options)
)
!validateRequiredWhen(spec.arguments, extracted)
) {
return;
}

checkRemovedArguments(spec.arguments, extracted);

if (spec.deprecated && spec.messages?.onDeprecated) {
printDeprecated(spec.messages.onDeprecated);
}
Expand All @@ -588,7 +669,7 @@ export function registerCommands(
loadModule,
currentPath,
positionalArgs,
extractArgumentValues(spec.arguments || [], positionalArgs, options)
extracted
);
});
}
Expand Down
Loading