dbdiagram 0.1.2 → 0.3.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.
Files changed (42) hide show
  1. package/README.md +13 -0
  2. package/dist/actions/auth/auth-login.action.js +8 -19
  3. package/dist/actions/auth/auth-logout.action.js +11 -3
  4. package/dist/actions/auth/auth-status.action.js +7 -13
  5. package/dist/actions/build/build-document.action.js +29 -45
  6. package/dist/actions/delete.action.js +8 -15
  7. package/dist/actions/list/list-document.action.js +13 -19
  8. package/dist/actions/list/list.action.js +12 -19
  9. package/dist/actions/pull.action.js +8 -17
  10. package/dist/actions/push.action.js +22 -24
  11. package/dist/actions/tokens/token-delete.action.js +8 -14
  12. package/dist/actions/tokens/token-generate.action.js +7 -12
  13. package/dist/actions/tokens/token-list.action.js +10 -17
  14. package/dist/commands/auth/auth-logout.command.js +1 -0
  15. package/dist/commands/auth/auth.command.js +1 -1
  16. package/dist/commands/build/build.command.js +1 -1
  17. package/dist/commands/tokens/tokens.command.js +1 -1
  18. package/dist/config/credential-manager.js +10 -23
  19. package/dist/config/settings-manager.js +14 -31
  20. package/dist/config.js +4 -0
  21. package/dist/constants/cli-error.constant.js +81 -0
  22. package/dist/constants/dot-dbdiagram-dir.constant.js +1 -0
  23. package/dist/errors/cli.error.js +9 -0
  24. package/dist/errors/portal-api.error.js +1 -0
  25. package/dist/hooks/global.hook.js +8 -1
  26. package/dist/index.js +22 -2
  27. package/dist/integrations/telemetry/telemetry.integration.js +77 -0
  28. package/dist/program.js +2 -1
  29. package/dist/services/telemetry.service.js +59 -0
  30. package/dist/types/telemetry.type.js +1 -0
  31. package/dist/utils/agent-detector.util.js +27 -0
  32. package/dist/utils/cli-error.util.js +46 -0
  33. package/dist/utils/command-name.util.js +12 -0
  34. package/dist/utils/date.util.js +4 -0
  35. package/dist/utils/environment-detector.util.js +6 -0
  36. package/dist/utils/file.util.js +31 -0
  37. package/dist/utils/output.util.js +35 -4
  38. package/package.json +4 -2
  39. package/dist/constants/auth-message.constant.js +0 -1
  40. package/dist/errors/portal-error-codes.js +0 -23
  41. package/dist/utils/portal-error.util.js +0 -18
  42. package/dist/utils/table.util.js +0 -10
package/dist/program.js CHANGED
@@ -9,8 +9,9 @@ export function createProgram() {
9
9
  .helpOption('-h, --help', 'Show help information')
10
10
  .enablePositionalOptions(true)
11
11
  .action(() => {
12
- program.help();
12
+ program.outputHelp();
13
13
  })
14
14
  .showHelpAfterError(true);
15
+ program.exitOverride();
15
16
  return program;
16
17
  }
@@ -0,0 +1,59 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import packageJson from '../../package.json' with { type: 'json' };
4
+ import { telemetryIntegration } from '../integrations/telemetry/telemetry.integration.js';
5
+ import { detectAgent } from '../utils/agent-detector.util.js';
6
+ import { detectExecuteEnvironment } from '../utils/environment-detector.util.js';
7
+ import { getGlobalDir, getTelemetryFilePath, readAndParseJsonFileSafe } from '../utils/file.util.js';
8
+ const events = [];
9
+ let deviceId;
10
+ let invocationId;
11
+ export const getOrCreateDeviceId = () => {
12
+ const telemetryFilePath = getTelemetryFilePath();
13
+ const telemetrySettings = readAndParseJsonFileSafe(telemetryFilePath);
14
+ if (telemetrySettings && telemetrySettings.deviceId) {
15
+ return telemetrySettings.deviceId;
16
+ }
17
+ const deviceId = crypto.randomUUID();
18
+ fs.mkdirSync(getGlobalDir(), { recursive: true });
19
+ fs.writeFileSync(telemetryFilePath, JSON.stringify({ deviceId }, null, 2));
20
+ return deviceId;
21
+ };
22
+ export const constructCliClientContext = () => {
23
+ const { actor, agentName } = detectAgent();
24
+ return {
25
+ deviceId: deviceId ?? '',
26
+ invocationId: invocationId ?? '',
27
+ cliVersion: packageJson.version,
28
+ nodeVersion: process.versions.node,
29
+ os: process.platform,
30
+ arch: process.arch,
31
+ actor,
32
+ ...(agentName ? { agentName } : {}),
33
+ environment: detectExecuteEnvironment(),
34
+ };
35
+ };
36
+ export const initTelemetry = () => {
37
+ try {
38
+ deviceId = getOrCreateDeviceId();
39
+ invocationId = crypto.randomUUID();
40
+ }
41
+ catch {
42
+ }
43
+ };
44
+ export const trackEvent = (event) => {
45
+ try {
46
+ events.push({ ...event, timestamp: Date.now() });
47
+ }
48
+ catch {
49
+ }
50
+ };
51
+ export const flushTelemetry = () => {
52
+ try {
53
+ if (!events.length)
54
+ return;
55
+ telemetryIntegration.send(events, constructCliClientContext());
56
+ }
57
+ catch {
58
+ }
59
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ const AGENT_ENV_VAR_TO_NAME = {
2
+ CURSOR_TRACE_ID: 'cursor',
3
+ CURSOR_AGENT: 'cursor-cli',
4
+ CURSOR_EXTENSION_HOST_ROLE: 'cursor-cli',
5
+ GEMINI_CLI: 'gemini',
6
+ CODEX_SANDBOX: 'codex',
7
+ CODEX_CI: 'codex',
8
+ CODEX_THREAD_ID: 'codex',
9
+ CLAUDECODE: 'claude',
10
+ CLAUDE_CODE: 'claude',
11
+ REPL_ID: 'replit',
12
+ COPILOT_MODEL: 'github-copilot',
13
+ COPILOT_ALLOW_ALL: 'github-copilot',
14
+ COPILOT_GITHUB_TOKEN: 'github-copilot',
15
+ ANTIGRAVITY_AGENT: 'antigravity',
16
+ AUGMENT_AGENT: 'augment-cli',
17
+ OPENCODE_CLIENT: 'opencode',
18
+ OPENCODE: 'opencode',
19
+ };
20
+ export const detectAgent = () => {
21
+ for (const [envVar, agentName] of Object.entries(AGENT_ENV_VAR_TO_NAME)) {
22
+ if (process.env[envVar]) {
23
+ return { actor: 'ai_agent', agentName };
24
+ }
25
+ }
26
+ return { actor: 'human' };
27
+ };
@@ -0,0 +1,46 @@
1
+ import { CliError } from '../errors/cli.error.js';
2
+ import { CLI_ERROR_MESSAGES, PORTAL_TO_CLI_CODE, REAUTH_MESSAGE, AUTH_INVALID_CREDENTIALS, AUTH_LOGIN_TIMEOUT, AUTH_LOGIN_CANCELLED, AUTH_LOGIN_STATE_MISMATCH, AUTH_LOGIN_INVALID_CALLBACK, AUTH_LOGIN_EXCHANGE_FAILED, AUTH_LOGIN_SERVER_ERROR, AUTH_LOGIN_FAILED, VALIDATION_FAILED, UNEXPECTED_ERROR, } from '../constants/cli-error.constant.js';
3
+ import { PortalApiError } from '../errors/portal-api.error.js';
4
+ import { PortalAuthenticationError, PORTAL_AUTH_ERROR_CODES } from '../libs/portal/index.js';
5
+ export function isPortalApiError(error) {
6
+ return error instanceof PortalApiError;
7
+ }
8
+ export const getCliErrorMessageFromCode = (errorCode) => {
9
+ return CLI_ERROR_MESSAGES[errorCode] ?? `An unexpected error occurred [${errorCode}]`;
10
+ };
11
+ export const fromPortalError = (error) => {
12
+ if (error.errorCode.startsWith('auth_')) {
13
+ return new CliError(AUTH_INVALID_CREDENTIALS, REAUTH_MESSAGE);
14
+ }
15
+ if (error.errorCode === 'validation_invalid_payload') {
16
+ return new CliError(VALIDATION_FAILED, error.message);
17
+ }
18
+ const mappedCliErrorCode = PORTAL_TO_CLI_CODE[error.errorCode];
19
+ if (mappedCliErrorCode) {
20
+ return new CliError(mappedCliErrorCode, getCliErrorMessageFromCode(mappedCliErrorCode));
21
+ }
22
+ return new CliError(UNEXPECTED_ERROR, `An unexpected error occurred [${error.errorCode}]`);
23
+ };
24
+ const PORTAL_AUTH_TO_CLI_CODE = {
25
+ [PORTAL_AUTH_ERROR_CODES.loginTimeout]: AUTH_LOGIN_TIMEOUT,
26
+ [PORTAL_AUTH_ERROR_CODES.loginCancelled]: AUTH_LOGIN_CANCELLED,
27
+ [PORTAL_AUTH_ERROR_CODES.stateMismatch]: AUTH_LOGIN_STATE_MISMATCH,
28
+ [PORTAL_AUTH_ERROR_CODES.invalidCallback]: AUTH_LOGIN_INVALID_CALLBACK,
29
+ [PORTAL_AUTH_ERROR_CODES.exchangeFailed]: AUTH_LOGIN_EXCHANGE_FAILED,
30
+ [PORTAL_AUTH_ERROR_CODES.loopbackServerError]: AUTH_LOGIN_SERVER_ERROR,
31
+ [PORTAL_AUTH_ERROR_CODES.unknownError]: AUTH_LOGIN_FAILED,
32
+ };
33
+ export const fromPortalAuthError = (error) => {
34
+ const cliCode = PORTAL_AUTH_TO_CLI_CODE[error.errorCode] ?? AUTH_LOGIN_FAILED;
35
+ return new CliError(cliCode, getCliErrorMessageFromCode(cliCode));
36
+ };
37
+ export const toCliError = (error) => {
38
+ if (error instanceof CliError)
39
+ return error;
40
+ if (error instanceof PortalAuthenticationError)
41
+ return fromPortalAuthError(error);
42
+ if (isPortalApiError(error))
43
+ return fromPortalError(error);
44
+ const message = error instanceof Error ? error.message : String(error);
45
+ return new CliError(UNEXPECTED_ERROR, message);
46
+ };
@@ -0,0 +1,12 @@
1
+ export function resolveCommandName(actionCommand) {
2
+ const nameSegments = [];
3
+ let currentCommand = actionCommand;
4
+ while (currentCommand && currentCommand.parent) {
5
+ nameSegments.unshift(currentCommand.name());
6
+ currentCommand = currentCommand.parent;
7
+ }
8
+ if (nameSegments.length === 0) {
9
+ return actionCommand.name();
10
+ }
11
+ return nameSegments.join(' ');
12
+ }
@@ -0,0 +1,4 @@
1
+ import { DateTime } from 'luxon';
2
+ export function formatShortDateTime(iso) {
3
+ return DateTime.fromISO(iso).toLocaleString(DateTime.DATETIME_SHORT);
4
+ }
@@ -0,0 +1,6 @@
1
+ import { isCI } from 'ci-info';
2
+ export const detectExecuteEnvironment = () => {
3
+ if (isCI || !process.stdout.isTTY)
4
+ return 'unattended';
5
+ return 'interactive';
6
+ };
@@ -0,0 +1,31 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { DOT_DBDIAGRAM_DIR, CREDENTIALS_FILE_NAME, SETTINGS_FILE_NAME, TELEMETRY_FILE_NAME, } from '../constants/dot-dbdiagram-dir.constant.js';
5
+ export const readAndParseJsonFileSafe = (filePath) => {
6
+ try {
7
+ const contents = readFileSync(filePath, 'utf8');
8
+ return JSON.parse(contents);
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ };
14
+ export const getProjectDir = () => {
15
+ return join(process.cwd(), DOT_DBDIAGRAM_DIR);
16
+ };
17
+ export const getGlobalDir = () => {
18
+ return join(homedir(), DOT_DBDIAGRAM_DIR);
19
+ };
20
+ export const getSettingsFilePath = () => {
21
+ return join(getProjectDir(), SETTINGS_FILE_NAME);
22
+ };
23
+ export const getProjectCredentialsFilePath = () => {
24
+ return join(getProjectDir(), CREDENTIALS_FILE_NAME);
25
+ };
26
+ export const getGlobalCredentialsFilePath = () => {
27
+ return join(getGlobalDir(), CREDENTIALS_FILE_NAME);
28
+ };
29
+ export const getTelemetryFilePath = () => {
30
+ return join(getGlobalDir(), TELEMETRY_FILE_NAME);
31
+ };
@@ -1,18 +1,49 @@
1
1
  import Table from 'cli-table3';
2
+ import { log } from '@clack/prompts';
2
3
  import { logConsole } from './logger.util.js';
4
+ export const getOutputFormat = (json) => (json ? 'json' : 'plain');
3
5
  export function printJson(data) {
4
6
  logConsole(JSON.stringify(data, null, 2));
5
7
  }
8
+ export const displayError = (error, format) => {
9
+ if (format === 'json') {
10
+ printJson({ errorCode: error.errorCode, message: error.message });
11
+ }
12
+ else {
13
+ log.error(error.message);
14
+ }
15
+ };
16
+ const NO_BORDER_CHAR_CONFIG = {
17
+ top: '',
18
+ 'top-mid': '',
19
+ 'top-left': '',
20
+ 'top-right': '',
21
+ bottom: '',
22
+ 'bottom-mid': '',
23
+ 'bottom-left': '',
24
+ 'bottom-right': '',
25
+ left: '',
26
+ 'left-mid': '',
27
+ mid: '',
28
+ 'mid-mid': '',
29
+ right: '',
30
+ 'right-mid': '',
31
+ middle: '',
32
+ };
6
33
  export function printTable(data, config) {
7
34
  const table = new Table({
8
35
  head: config.head,
9
- style: { head: [] },
10
- colWidths: config.colWidths,
11
- wordWrap: true,
12
- wrapOnWordBoundary: false,
36
+ style: {
37
+ head: ['gray'],
38
+ 'padding-left': 0,
39
+ 'padding-right': 2,
40
+ },
41
+ chars: NO_BORDER_CHAR_CONFIG,
13
42
  });
14
43
  for (const item of data) {
15
44
  table.push(config.rowMapper(item));
45
+ const spacerRow = new Array(config.head.length).fill('');
46
+ table.push(spacerRow);
16
47
  }
17
48
  logConsole(table.toString());
18
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbdiagram",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "dbdiagram": "dist/index.js"
@@ -31,7 +31,9 @@
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.4.0",
33
33
  "@dbml/core": "8.2.5",
34
+ "@snowplow/node-tracker": "^4.9.0",
34
35
  "axios": "^1.16.1",
36
+ "ci-info": "^4.4.0",
35
37
  "cli-table3": "^0.6.5",
36
38
  "commander": "^15.0.0",
37
39
  "dotenv": "^16.4.0",
@@ -50,7 +52,7 @@
50
52
  "eslint-import-resolver-typescript": "^4.4.4",
51
53
  "eslint-plugin-import-x": "^4.16.2",
52
54
  "globals": "^17.6.0",
53
- "tsx": "^4.21.0",
55
+ "tsx": "^4.23.0",
54
56
  "typescript": "^5.7.0",
55
57
  "typescript-eslint": "^8.59.3",
56
58
  "vitest": "^4.1.0"
@@ -1 +0,0 @@
1
- export const NOT_LOGGED_IN_MESSAGE = 'Not logged in. Run `dbdiagram auth login`, or set DBDIAGRAM_TOKEN, to authenticate.';
@@ -1,23 +0,0 @@
1
- export const DIAGRAM_NOT_FOUND = 'diagram_not_found';
2
- export const DIAGRAM_PERMISSION_DENIED_VIEW = 'diagram_permission_denied_view';
3
- export const DIAGRAM_PERMISSION_DENIED_EDIT = 'diagram_permission_denied_edit';
4
- export const DIAGRAM_PERMISSION_DENIED_DELETE = 'diagram_permission_denied_delete';
5
- export const DIAGRAM_LIMIT_EXCEEDED = 'diagram_limit_exceeded';
6
- export const WORKSPACE_NOT_FOUND = 'workspace_not_found';
7
- export const WORKSPACE_PERMISSION_DENIED_VIEW = 'workspace_permission_denied_view';
8
- export const TOKEN_NOT_FOUND = 'token_not_found';
9
- export const TOKEN_LIMIT_EXCEEDED = 'token_limit_exceeded';
10
- export const VALIDATION_INVALID_PAYLOAD = 'validation_invalid_payload';
11
- export const INTERNAL_SERVER_ERROR = 'internal_server_error';
12
- export const PORTAL_ERROR_MESSAGES = {
13
- [DIAGRAM_NOT_FOUND]: 'Diagram not found.',
14
- [DIAGRAM_PERMISSION_DENIED_VIEW]: 'You do not have access to this diagram.',
15
- [DIAGRAM_PERMISSION_DENIED_EDIT]: 'You do not have edit permission on this diagram.',
16
- [DIAGRAM_PERMISSION_DENIED_DELETE]: 'You do not have permission to delete this diagram.',
17
- [DIAGRAM_LIMIT_EXCEEDED]: 'Diagram limit exceeded for your current plan.',
18
- [WORKSPACE_NOT_FOUND]: 'Workspace not found.',
19
- [WORKSPACE_PERMISSION_DENIED_VIEW]: 'You do not have access to this workspace.',
20
- [TOKEN_NOT_FOUND]: 'Token not found.',
21
- [TOKEN_LIMIT_EXCEEDED]: 'Token limit reached. Delete an existing token before creating a new one.',
22
- [INTERNAL_SERVER_ERROR]: 'An internal server error occurred. Please try again later.',
23
- };
@@ -1,18 +0,0 @@
1
- import { PortalApiError } from '../errors/portal-api.error.js';
2
- import { PORTAL_ERROR_MESSAGES, VALIDATION_INVALID_PAYLOAD } from '../errors/portal-error-codes.js';
3
- export function isPortalApiError(error) {
4
- return error instanceof PortalApiError;
5
- }
6
- export function getPortalApiErrorMessage(error) {
7
- if (error.errorCode.startsWith('auth_')) {
8
- return [
9
- 'Authentication failed: your credentials are invalid or expired. Try one of:',
10
- ' - Replace DBDIAGRAM_TOKEN with a valid token',
11
- ' - Unset DBDIAGRAM_TOKEN, then run `dbdiagram auth login` to re-authenticate',
12
- ].join('\n');
13
- }
14
- if (error.errorCode === VALIDATION_INVALID_PAYLOAD) {
15
- return error.message;
16
- }
17
- return PORTAL_ERROR_MESSAGES[error.errorCode] ?? `An unexpected error occurred [${error.errorCode}]`;
18
- }
@@ -1,10 +0,0 @@
1
- export function computeColWidths(fractions) {
2
- const terminalWidth = process.stdout.columns || 100;
3
- const numCols = fractions.length;
4
- const overhead = numCols * 3 + 1;
5
- const available = Math.max(terminalWidth - overhead, numCols * 5);
6
- const widths = fractions.map((f) => Math.max(Math.floor(available * f), 5));
7
- const used = widths.reduce((a, b) => a + b, 0);
8
- widths[widths.length - 1] += available - used;
9
- return widths;
10
- }