dbdiagram 0.2.0 → 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.
package/README.md CHANGED
@@ -181,6 +181,19 @@ yarn build # compile to dist/
181
181
  yarn start # run compiled output
182
182
  ```
183
183
 
184
+ ### Telemetry in development
185
+
186
+ Copy `.env.example` to `.env`. Its defaults point telemetry at a local collector
187
+ (`TRACKING_COLLECTOR_URL=http://localhost:9090`) and tag events with a non-production app id (`TRACKING_APP_ID=dbdiagram-cli-test`), so development never writes to production analytics.
188
+
189
+ To disable tracking entirely, point the collector at a local address with nothing listening:
190
+
191
+ ```sh
192
+ TRACKING_COLLECTOR_URL=http://localhost:9090 yarn dev push ...
193
+ ```
194
+
195
+ Sends fail silently (the CLI never retries and never surfaces telemetry errors), so no events leave your machine. To instead inspect what would be sent, run [Snowplow Micro](https://docs.snowplow.io/docs/data-product-studio/data-quality/snowplow-micro/) on that port and read `curl localhost:9090/micro/good` (passed validation) and `/micro/bad` (failed).
196
+
184
197
  ## Test
185
198
 
186
199
  ```sh
@@ -6,6 +6,7 @@ import { DOCUMENT_SOURCE_TYPE } from '../../constants/document.constant.js';
6
6
  import { AUTH_NOT_LOGGED_IN, DBML_PARSE_ERROR, FILE_READ_ERROR, PROJECT_NAME_INVALID, PROJECT_NAME_MISSING, SOURCE_MISSING, VERSION_NAME_INVALID, WORKSPACE_NAME_INVALID, WORKSPACE_NAME_MISSING, } from '../../constants/cli-error.constant.js';
7
7
  import { CliError } from '../../errors/cli.error.js';
8
8
  import { portalIntegration } from '../../integrations/portal/portal.integration.js';
9
+ import { trackEvent } from '../../services/telemetry.service.js';
9
10
  import { prepareBuildDocumentData } from '../../services/dbml/dbml.service.js';
10
11
  import { getCliErrorMessageFromCode, toCliError } from '../../utils/cli-error.util.js';
11
12
  import { formatDbmlError, getDbmlVersion } from '../../utils/dbml.util.js';
@@ -88,6 +89,7 @@ export async function documentAction(options) {
88
89
  const s = spinner();
89
90
  s.start('Building document on dbdocs...');
90
91
  try {
92
+ const currentUser = await portalIntegration.getCurrentUser();
91
93
  const { project } = await portalIntegration.createDocument({
92
94
  projectName: options.projectUrlOrName,
93
95
  projectDescription: preparedDocumentData.description,
@@ -101,10 +103,16 @@ export async function documentAction(options) {
101
103
  normalizedDatabase: preparedDocumentData.normalizedDatabase,
102
104
  dbmlVersion: getDbmlVersion(),
103
105
  });
104
- const url = `${DBDOCS_CONFIGS.baseUrl}/${project.workspaceUrl}/${project.urlName}`;
106
+ const { workspaceUrl: workspaceUrlName, urlName: projectUrlName } = project;
107
+ trackEvent({
108
+ name: 'cli_document_build',
109
+ properties: { workspaceUrlName, projectUrlName },
110
+ userId: currentUser.id.toString(),
111
+ });
112
+ const url = `${DBDOCS_CONFIGS.baseUrl}/${workspaceUrlName}/${projectUrlName}`;
105
113
  if (options.json) {
106
114
  s.stop();
107
- printJson({ projectUrlName: `${project.workspaceUrl}/${project.urlName}`, url });
115
+ printJson({ projectUrlName: `${workspaceUrlName}/${projectUrlName}`, url });
108
116
  }
109
117
  else {
110
118
  s.stop(`Document built successfully. Visit: ${url}`);
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import { spinner } from '@clack/prompts';
3
3
  import { getCredential } from '../config/credential-manager.js';
4
4
  import { portalIntegration } from '../integrations/portal/portal.integration.js';
5
+ import { trackEvent } from '../services/telemetry.service.js';
5
6
  import { deriveDiagramVizPathFromDBML, readDiagramVizFile } from '../services/viz/diagram-viz-file.service.js';
6
7
  import { toServerFormat } from '../services/viz/diagram-viz-converter.service.js';
7
8
  import { preparePushDiagramData } from '../services/dbml/dbml.service.js';
@@ -49,16 +50,27 @@ export async function pushAction(filepath, options, thisCommand) {
49
50
  const s = spinner();
50
51
  s.start('Pushing diagram to dbdiagram...');
51
52
  try {
53
+ const currentUser = await portalIntegration.getCurrentUser();
52
54
  let diagramId;
53
55
  if (!options.new && options.diagramId) {
54
56
  ({ diagramId } = await portalIntegration.updateDiagram({
55
57
  diagramId: options.diagramId, name: options.name, dbml, vizData,
56
58
  }));
59
+ trackEvent({
60
+ name: 'cli_diagram_push',
61
+ properties: { action: 'update', diagramId, workspaceId: options.workspace ?? null },
62
+ userId: currentUser.id.toString(),
63
+ });
57
64
  }
58
65
  else {
59
66
  ({ diagramId } = await portalIntegration.createDiagram({
60
67
  name: options.name, dbml, workspaceId: options.workspace, vizData,
61
68
  }));
69
+ trackEvent({
70
+ name: 'cli_diagram_push',
71
+ properties: { action: 'create', diagramId, workspaceId: options.workspace ?? null },
72
+ userId: currentUser.id.toString(),
73
+ });
62
74
  }
63
75
  const diagramUrl = `${DBDIAGRAM_CONFIGS.baseUrl}/d/${diagramId}`;
64
76
  if (options.json) {
@@ -8,7 +8,7 @@ export function registerAuthCommand(program) {
8
8
  .addHelpText('after', '\nAlternatively, set the DBDIAGRAM_TOKEN env var to authenticate with a\n' +
9
9
  'CLI token (see: dbdiagram tokens generate).')
10
10
  .action(() => {
11
- auth.help();
11
+ auth.outputHelp();
12
12
  });
13
13
  registerLoginCommand(auth);
14
14
  registerLogoutCommand(auth);
@@ -4,7 +4,7 @@ export function registerBuildCommand(program) {
4
4
  .command('build')
5
5
  .description('Publish documentation to dbdocs.io')
6
6
  .action(() => {
7
- build.help();
7
+ build.outputHelp();
8
8
  });
9
9
  registerDocumentCommand(build);
10
10
  }
@@ -7,7 +7,7 @@ export function registerTokensCommand(program) {
7
7
  .description('Manage your CLI tokens')
8
8
  .addHelpText('after', '\nSet a token value as DBDIAGRAM_TOKEN env var to authenticate without browser login.')
9
9
  .action(() => {
10
- tokens.help();
10
+ tokens.outputHelp();
11
11
  });
12
12
  registerTokenGenerateCommand(tokens);
13
13
  registerTokenListCommand(tokens);
@@ -1,40 +1,27 @@
1
1
  import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { DOT_DBDIAGRAM_DIR, CREDENTIALS_FILE_NAME } from '../constants/dot-dbdiagram-dir.constant.js';
2
+ import { getGlobalCredentialsFilePath, getGlobalDir, getProjectCredentialsFilePath, readAndParseJsonFileSafe, } from '../utils/file.util.js';
5
3
  let cachedCredential = null;
6
- function readJsonSafe(filePath) {
7
- try {
8
- const contents = fs.readFileSync(filePath, 'utf8');
9
- return JSON.parse(contents);
10
- }
11
- catch {
12
- return null;
13
- }
14
- }
15
4
  export function resolveCredential() {
16
5
  if (process.env.DBDIAGRAM_TOKEN) {
17
6
  cachedCredential = { token: process.env.DBDIAGRAM_TOKEN };
18
7
  return cachedCredential;
19
8
  }
20
- const projectPath = path.join(process.cwd(), DOT_DBDIAGRAM_DIR, CREDENTIALS_FILE_NAME);
21
- const projectCred = readJsonSafe(projectPath);
22
- if (projectCred && typeof projectCred.token === 'string') {
23
- cachedCredential = { token: projectCred.token };
9
+ const projectCredential = readAndParseJsonFileSafe(getProjectCredentialsFilePath());
10
+ if (projectCredential && typeof projectCredential.token === 'string') {
11
+ cachedCredential = { token: projectCredential.token };
24
12
  return cachedCredential;
25
13
  }
26
- const userPath = path.join(os.homedir(), DOT_DBDIAGRAM_DIR, CREDENTIALS_FILE_NAME);
27
- const userCred = readJsonSafe(userPath);
28
- if (userCred && typeof userCred.token === 'string') {
29
- cachedCredential = { token: userCred.token };
14
+ const globalCredential = readAndParseJsonFileSafe(getGlobalCredentialsFilePath());
15
+ if (globalCredential && typeof globalCredential.token === 'string') {
16
+ cachedCredential = { token: globalCredential.token };
30
17
  return cachedCredential;
31
18
  }
32
19
  cachedCredential = null;
33
20
  return null;
34
21
  }
35
22
  export function storeCredential(token) {
36
- const dir = path.join(os.homedir(), DOT_DBDIAGRAM_DIR);
37
- const filePath = path.join(dir, CREDENTIALS_FILE_NAME);
23
+ const dir = getGlobalDir();
24
+ const filePath = getGlobalCredentialsFilePath();
38
25
  fs.mkdirSync(dir, { recursive: true });
39
26
  fs.writeFileSync(filePath, JSON.stringify({ token }, null, 2), { mode: 0o600 });
40
27
  cachedCredential = { token };
@@ -46,7 +33,7 @@ export function getAccessToken() {
46
33
  return cachedCredential?.token ?? null;
47
34
  }
48
35
  export function clearCredential() {
49
- const filePath = path.join(os.homedir(), DOT_DBDIAGRAM_DIR, CREDENTIALS_FILE_NAME);
36
+ const filePath = getGlobalCredentialsFilePath();
50
37
  fs.rmSync(filePath, { force: true });
51
38
  cachedCredential = null;
52
39
  }
@@ -1,12 +1,8 @@
1
1
  import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { DOT_DBDIAGRAM_DIR, SETTINGS_FILE_NAME } from '../constants/dot-dbdiagram-dir.constant.js';
2
+ import { getProjectDir, getSettingsFilePath, readAndParseJsonFileSafe } from '../utils/file.util.js';
4
3
  let cachedSettings = null;
5
- function settingsPath() {
6
- return path.join(process.cwd(), DOT_DBDIAGRAM_DIR, SETTINGS_FILE_NAME);
7
- }
8
4
  export function settingsExists() {
9
- return fs.existsSync(settingsPath());
5
+ return fs.existsSync(getSettingsFilePath());
10
6
  }
11
7
  export function defaultSettings() {
12
8
  return {
@@ -15,30 +11,17 @@ export function defaultSettings() {
15
11
  document: { source: 'file', workspaceUrlName: '', projectUrlName: '' },
16
12
  };
17
13
  }
18
- export function readSettings() {
19
- try {
20
- const contents = fs.readFileSync(settingsPath(), 'utf8');
21
- return JSON.parse(contents);
22
- }
23
- catch {
24
- return null;
25
- }
26
- }
27
- export function writeSettings(data) {
28
- const dir = path.join(process.cwd(), DOT_DBDIAGRAM_DIR);
29
- fs.mkdirSync(dir, { recursive: true });
30
- fs.writeFileSync(settingsPath(), JSON.stringify(data, null, 2), 'utf8');
14
+ export const readSettings = () => {
15
+ return readAndParseJsonFileSafe(getSettingsFilePath());
16
+ };
17
+ export const writeSettings = (data) => {
18
+ fs.mkdirSync(getProjectDir(), { recursive: true });
19
+ fs.writeFileSync(getSettingsFilePath(), JSON.stringify(data, null, 2), 'utf8');
31
20
  cachedSettings = data;
32
- }
33
- export function resolveSettings() {
34
- try {
35
- const contents = fs.readFileSync(settingsPath(), 'utf8');
36
- cachedSettings = JSON.parse(contents);
37
- }
38
- catch {
39
- cachedSettings = null;
40
- }
41
- }
42
- export function getSettings() {
21
+ };
22
+ export const resolveSettings = () => {
23
+ cachedSettings = readAndParseJsonFileSafe(getSettingsFilePath());
24
+ };
25
+ export const getSettings = () => {
43
26
  return cachedSettings;
44
- }
27
+ };
package/dist/config.js CHANGED
@@ -12,3 +12,7 @@ export const DBDIAGRAM_CONFIGS = {
12
12
  export const DBDOCS_CONFIGS = {
13
13
  baseUrl: process.env.DBDOCS_BASE_URL || 'https://dbdocs.io',
14
14
  };
15
+ export const TELEMETRY_CONFIGS = {
16
+ collectorUrl: process.env.TRACKING_COLLECTOR_URL || 'https://snowplow.holistics.io',
17
+ appId: process.env.TRACKING_APP_ID || 'dbdiagram-cli',
18
+ };
@@ -7,3 +7,4 @@ export const DIAGRAM_VIZ_FILE_VERSIONS = {
7
7
  v1: '1.0.0',
8
8
  v2: '2.0.0',
9
9
  };
10
+ export const TELEMETRY_FILE_NAME = 'telemetry.json';
@@ -1,6 +1,13 @@
1
1
  import { resolveCredential } from '../config/credential-manager.js';
2
2
  import { resolveSettings } from '../config/settings-manager.js';
3
- export function globalPreAction(_thisCommand, _actionCommand) {
3
+ import { flushTelemetry, initTelemetry, trackEvent } from '../services/telemetry.service.js';
4
+ import { resolveCommandName } from '../utils/command-name.util.js';
5
+ export function globalPreAction(_thisCommand, actionCommand) {
4
6
  resolveCredential();
5
7
  resolveSettings();
8
+ initTelemetry();
9
+ trackEvent({ name: 'cli_command_run', properties: { command: resolveCommandName(actionCommand) } });
10
+ }
11
+ export function globalPostAction() {
12
+ flushTelemetry();
6
13
  }
package/dist/index.js CHANGED
@@ -1,12 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import 'dotenv/config';
3
+ import { CommanderError } from 'commander';
3
4
  import { load } from './commands/command.loader.js';
4
5
  import { createProgram } from './program.js';
5
- import { globalPreAction } from './hooks/global.hook.js';
6
+ import { globalPostAction, globalPreAction } from './hooks/global.hook.js';
7
+ import { flushTelemetry, initTelemetry, trackEvent } from './services/telemetry.service.js';
8
+ const getAttemptedCommand = () => {
9
+ const operands = process.argv.slice(2).filter((arg) => !arg.startsWith('-'));
10
+ return operands.length ? operands.join(' ') : 'unknown';
11
+ };
6
12
  const bootstrap = async () => {
7
13
  const program = createProgram();
8
14
  load(program);
9
15
  program.hook('preAction', globalPreAction);
10
- await program.parseAsync(process.argv);
16
+ program.hook('postAction', globalPostAction);
17
+ try {
18
+ await program.parseAsync(process.argv);
19
+ }
20
+ catch (error) {
21
+ if (!(error instanceof CommanderError))
22
+ throw error;
23
+ if (error.code === 'commander.unknownCommand' ||
24
+ error.code === 'commander.excessArguments') {
25
+ initTelemetry();
26
+ trackEvent({ name: 'cli_command_run', properties: { command: getAttemptedCommand() } });
27
+ flushTelemetry();
28
+ }
29
+ process.exitCode = error.exitCode;
30
+ }
11
31
  };
12
32
  bootstrap();
@@ -0,0 +1,77 @@
1
+ import { buildSelfDescribingEvent, newTracker } from '@snowplow/node-tracker';
2
+ import { TELEMETRY_CONFIGS } from '../../config.js';
3
+ export const constructSnowplowSchemaUrl = (name, version = '1-0-0') => `iglu:io.dbdiagram/${name}/jsonschema/${version}`;
4
+ let tracker;
5
+ export const getTracker = () => {
6
+ if (tracker)
7
+ return tracker;
8
+ tracker = newTracker({
9
+ namespace: 'cli',
10
+ appId: TELEMETRY_CONFIGS.appId,
11
+ encodeBase64: true,
12
+ }, {
13
+ endpoint: TELEMETRY_CONFIGS.collectorUrl,
14
+ eventMethod: 'post',
15
+ bufferSize: 1,
16
+ retryFailedRequests: false,
17
+ onRequestFailure: () => { },
18
+ onRequestSuccess() { },
19
+ });
20
+ return tracker;
21
+ };
22
+ export const toEventData = (event) => {
23
+ const { name, properties } = event;
24
+ switch (name) {
25
+ case 'cli_command_run':
26
+ return { command: properties.command };
27
+ case 'cli_diagram_push': {
28
+ const { action, diagramId, workspaceId } = properties;
29
+ return {
30
+ action,
31
+ diagram_id: diagramId,
32
+ workspace_id: workspaceId,
33
+ };
34
+ }
35
+ case 'cli_document_build': {
36
+ const { workspaceUrlName, projectUrlName } = properties;
37
+ return {
38
+ workspace_url_name: workspaceUrlName,
39
+ project_url_name: projectUrlName,
40
+ };
41
+ }
42
+ }
43
+ };
44
+ export const toClientContextEntity = (client) => ({
45
+ schema: constructSnowplowSchemaUrl('cli_client_context'),
46
+ data: {
47
+ device_id: client.deviceId,
48
+ invocation_id: client.invocationId,
49
+ cli_version: client.cliVersion,
50
+ node_version: client.nodeVersion,
51
+ os: client.os,
52
+ arch: client.arch,
53
+ actor: client.actor,
54
+ ...(client.agentName ? { agent_name: client.agentName } : {}),
55
+ environment: client.environment,
56
+ },
57
+ });
58
+ export const toUserContextEntity = (userId) => ({
59
+ schema: constructSnowplowSchemaUrl('cli_user_context'),
60
+ data: { user_id: userId },
61
+ });
62
+ export const send = (events, client) => {
63
+ const tracker = getTracker();
64
+ const clientContextEntity = toClientContextEntity(client);
65
+ for (const event of events) {
66
+ const contexts = [clientContextEntity];
67
+ if ('userId' in event) {
68
+ contexts.push(toUserContextEntity(event.userId));
69
+ }
70
+ tracker.track(buildSelfDescribingEvent({
71
+ event: { schema: constructSnowplowSchemaUrl(event.name), data: toEventData(event) },
72
+ }), contexts, { type: 'ttm', value: event.timestamp });
73
+ }
74
+ };
75
+ export const telemetryIntegration = {
76
+ send,
77
+ };
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,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,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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbdiagram",
3
- "version": "0.2.0",
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",