Skip to content

Commit 7f17153

Browse files
designcodeclaude
andauthored
refactor: extract shared helpers, remove dead code, and auto-inject global CLI arguments (#53)
* chore: package audit and remove dead code * refactor: fix imports Renames modules, fold configs where they belong. Uses tsconfig paths for cleaner imports * refactor: extract shared auth and error-handling helpers Consolidate ~250 lines of duplicated boilerplate across 35+ command files into 4 shared helpers: failWithError, getOAuthIAMConfig, getStorageConfigWithOrg, and requireOAuthLogin. Delete access-keys/config.ts (moved to auth/iam.ts). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * build: trigger patch release on refactor commits Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: auto-inject global arguments and add getFormat helper Define --json, --format, and --yes once in specs.yaml and auto-inject them into every leaf command via getEffectiveArguments(). Replace the repeated 3-line json/format boilerplate across 52 files with a single getFormat() call. Unify --force flags to --yes globally. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f5e5bd7 commit 7f17153

81 files changed

Lines changed: 3066 additions & 2906 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

eslint.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import eslint from "@eslint/js";
2+
import simpleImportSort from "eslint-plugin-simple-import-sort";
23
import tseslint from "typescript-eslint";
34

45
export default tseslint.config(
56
eslint.configs.recommended,
67
...tseslint.configs.recommended,
78
{
9+
plugins: { "simple-import-sort": simpleImportSort },
810
rules: {
911
"@typescript-eslint/no-unused-vars": "error",
1012
"@typescript-eslint/no-explicit-any": "warn",
1113
"@typescript-eslint/explicit-function-return-type": "off",
1214
"@typescript-eslint/explicit-module-boundary-types": "off",
1315
"@typescript-eslint/no-inferrable-types": "off",
16+
"@typescript-eslint/consistent-type-imports": [
17+
"error",
18+
{ prefer: "type-imports", fixStyle: "separate-type-imports" },
19+
],
20+
"simple-import-sort/imports": "error",
21+
"simple-import-sort/exports": "error",
1422
"prefer-const": "error",
1523
"no-var": "error",
1624
},

package-lock.json

Lines changed: 1214 additions & 546 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"lint:fix": "eslint src --fix",
3030
"format": "prettier --write \"src/**/*.ts\"",
3131
"format:check": "prettier --check \"src/**/*.ts\"",
32-
"test": "vitest run test/utils test/cli-core.test.ts test/specs-completeness.test.ts",
32+
"test": "vitest run test/utils test/auth test/cli-core.test.ts test/specs-completeness.test.ts",
3333
"test:watch": "vitest",
3434
"test:all": "vitest run",
3535
"test:integration": "vitest run test/cli.test.ts",
@@ -73,42 +73,46 @@
7373
}
7474
],
7575
"plugins": [
76-
"@semantic-release/commit-analyzer",
76+
["@semantic-release/commit-analyzer", {
77+
"releaseRules": [
78+
{ "type": "refactor", "release": "patch" }
79+
]
80+
}],
7781
"@semantic-release/release-notes-generator",
7882
"@semantic-release/github",
7983
"@semantic-release/npm"
8084
]
8185
},
8286
"dependencies": {
83-
"@aws-sdk/client-s3": "^3.1000.0",
84-
"@aws-sdk/credential-providers": "^3.1000.0",
85-
"@smithy/shared-ini-file-loader": "^4.4.5",
86-
"@tigrisdata/iam": "^1.3.0",
87-
"@tigrisdata/storage": "^2.15.5",
87+
"@aws-sdk/credential-providers": "^3.1018.0",
88+
"@smithy/shared-ini-file-loader": "^4.4.7",
89+
"@tigrisdata/iam": "^1.4.1",
90+
"@tigrisdata/storage": "^2.15.6",
8891
"axios": "^1.13.6",
8992
"commander": "^14.0.3",
9093
"enquirer": "^2.4.1",
91-
"jose": "^6.1.3",
94+
"jose": "^6.2.2",
9295
"open": "^11.0.0",
93-
"yaml": "^2.8.2"
96+
"yaml": "^2.8.3"
9497
},
9598
"devDependencies": {
96-
"@commitlint/cli": "^20.4.2",
97-
"@commitlint/config-conventional": "^20.4.2",
99+
"@commitlint/cli": "^20.5.0",
100+
"@commitlint/config-conventional": "^20.5.0",
98101
"@eslint/js": "^10.0.1",
99102
"@semantic-release/changelog": "^6.0.3",
100103
"@semantic-release/git": "^10.0.1",
101104
"@types/node": "^22.19.11",
102105
"dotenv": "^17.3.1",
103-
"eslint": "^10.0.2",
106+
"eslint": "^10.1.0",
107+
"eslint-plugin-simple-import-sort": "^12.1.1",
104108
"husky": "^9.1.7",
105109
"prettier": "^3.8.1",
106110
"publint": "^0.3.18",
107111
"semantic-release": "^25.0.3",
108112
"tsup": "^8.5.1",
109113
"tsx": "^4.21.0",
110114
"typescript": "^5.9.3",
111-
"typescript-eslint": "^8.56.1",
112-
"vitest": "^4.0.18"
115+
"typescript-eslint": "^8.57.2",
116+
"vitest": "^4.1.2"
113117
}
114118
}

src/auth/client.ts

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,73 @@
44
*/
55

66
import axios from 'axios';
7-
import open from 'open';
87
import { createRemoteJWKSet, jwtVerify } from 'jose';
9-
import type {
10-
TokenSet,
11-
IdTokenClaims,
12-
TigrisNamespace,
13-
TigrisOrg,
14-
OrganizationInfo,
15-
} from './types.js';
16-
import { getAuth0Config, TIGRIS_CLAIMS_NAMESPACE } from './config.js';
8+
import open from 'open';
9+
10+
import type { OrganizationInfo, TokenSet } from './storage.js';
1711
import {
18-
storeTokens,
19-
getTokens,
2012
clearTokens,
21-
storeOrganizations,
2213
getOrganizations,
14+
getTokens,
2315
storeLoginMethod,
16+
storeOrganizations,
17+
storeTokens,
2418
} from './storage.js';
2519

20+
/**
21+
* Auth0 configuration for CLI authentication
22+
*/
23+
export interface Auth0Config {
24+
domain: string;
25+
clientId: string;
26+
audience: string;
27+
}
28+
29+
/**
30+
* Get Auth0 configuration from environment variables or defaults
31+
*/
32+
export function getAuth0Config(): Auth0Config {
33+
const isDev = process.env.TIGRIS_ENV === 'development';
34+
const domain = isDev
35+
? 'auth-dev.tigris.dev'
36+
: (process.env.AUTH0_DOMAIN ?? 'auth.storage.tigrisdata.io');
37+
const clientId = isDev
38+
? 'JdJVYIyw0O1uHi5L5OJH903qaWBgd3gF'
39+
: (process.env.AUTH0_CLIENT_ID ?? 'DMejqeM3CQ4IqTjEcd3oA9eEiT40hn8D');
40+
const audience = isDev
41+
? 'https://tigris-api-dev'
42+
: (process.env.AUTH0_AUDIENCE ?? 'https://tigris-os-api');
43+
44+
return { domain, clientId, audience };
45+
}
46+
47+
/**
48+
* Custom claims namespace for Tigris
49+
*/
50+
export const TIGRIS_CLAIMS_NAMESPACE =
51+
process.env.TIGRIS_CLAIMS_NAMESPACE || 'https://tigris';
52+
53+
/**
54+
* OAuth-specific types
55+
*/
56+
export interface IdTokenClaims {
57+
sub: string;
58+
email?: string;
59+
email_verified?: boolean;
60+
[key: string]: unknown;
61+
}
62+
63+
interface TigrisOrg {
64+
id: string;
65+
name: string;
66+
slug: string;
67+
}
68+
69+
interface TigrisNamespace {
70+
ns?: (string | TigrisOrg)[];
71+
organizations?: (string | TigrisOrg)[];
72+
}
73+
2674
interface DeviceCodeResponse {
2775
device_code: string;
2876
user_code: string;

src/auth/config.ts

Lines changed: 0 additions & 68 deletions
This file was deleted.

src/auth/fly.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import axios from 'axios';
2-
import type { OrganizationInfo } from './types.js';
3-
import { getAuth0Config, TIGRIS_CLAIMS_NAMESPACE } from './config.js';
2+
3+
import { getAuth0Config, TIGRIS_CLAIMS_NAMESPACE } from './client.js';
4+
import type { OrganizationInfo } from './storage.js';
45

56
export function isFlyUser(organizationId?: string): boolean {
67
return !!organizationId?.startsWith('flyio_');

src/auth/iam.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* Shared IAM auth helpers
3+
* Consolidates OAuth check + auth check + config building patterns
4+
*/
5+
6+
import { failWithError } from '@utils/exit.js';
7+
import type { MessageContext } from '@utils/messages.js';
8+
9+
import { getAuthClient } from './client.js';
10+
import { isFlyUser } from './fly.js';
11+
import { getLoginMethod, getTigrisConfig } from './provider.js';
12+
import { getCredentials, getSelectedOrganization } from './storage.js';
13+
14+
/**
15+
* Check if current org is Fly.io. Prints message and returns true if so.
16+
*/
17+
export function isFlyOrganization(): boolean {
18+
const selectedOrg = getSelectedOrganization();
19+
if (isFlyUser(selectedOrg ?? undefined)) {
20+
console.log(
21+
'User management is not available for Fly.io organizations.\n' +
22+
'Your users are managed through Fly.io.\n\n' +
23+
'Visit https://fly.io to manage your organization members.'
24+
);
25+
return true;
26+
}
27+
return false;
28+
}
29+
30+
/**
31+
* OAuth-only IAM config. Exits on non-OAuth or unauthenticated.
32+
* Used by IAM policy and user commands.
33+
*/
34+
export async function getOAuthIAMConfig(context: MessageContext) {
35+
const loginMethod = await getLoginMethod();
36+
if (loginMethod !== 'oauth') {
37+
failWithError(
38+
context,
39+
'This operation requires OAuth login.\nRun "tigris login oauth" first.'
40+
);
41+
}
42+
43+
const authClient = getAuthClient();
44+
if (!(await authClient.isAuthenticated())) {
45+
failWithError(
46+
context,
47+
'Not authenticated. Run "tigris login oauth" first.'
48+
);
49+
}
50+
51+
const accessToken = await authClient.getAccessToken();
52+
const selectedOrg = getSelectedOrganization();
53+
const { iamEndpoint, mgmtEndpoint } = getTigrisConfig();
54+
55+
return {
56+
sessionToken: accessToken,
57+
organizationId: selectedOrg ?? undefined,
58+
iamEndpoint,
59+
mgmtEndpoint,
60+
};
61+
}
62+
63+
/**
64+
* Dual-mode IAM config (OAuth or credentials).
65+
* Used by access-key commands.
66+
*/
67+
export async function getIAMConfig(context: MessageContext) {
68+
const loginMethod = await getLoginMethod();
69+
const tigrisConfig = getTigrisConfig();
70+
const selectedOrg = getSelectedOrganization();
71+
72+
if (loginMethod === 'oauth') {
73+
const authClient = getAuthClient();
74+
if (!(await authClient.isAuthenticated())) {
75+
failWithError(
76+
context,
77+
'Not authenticated. Run "tigris login oauth" first.'
78+
);
79+
}
80+
81+
return {
82+
sessionToken: await authClient.getAccessToken(),
83+
organizationId: selectedOrg ?? undefined,
84+
iamEndpoint: tigrisConfig.iamEndpoint,
85+
};
86+
}
87+
88+
const credentials = getCredentials();
89+
if (!credentials) {
90+
failWithError(
91+
context,
92+
'Not authenticated. Run "tigris login" or "tigris configure" first.'
93+
);
94+
}
95+
96+
return {
97+
accessKeyId: credentials.accessKeyId,
98+
secretAccessKey: credentials.secretAccessKey,
99+
organizationId: selectedOrg ?? undefined,
100+
iamEndpoint: tigrisConfig.iamEndpoint,
101+
};
102+
}

0 commit comments

Comments
 (0)