dbdiagram 0.5.0 → 0.6.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
@@ -93,18 +93,34 @@ dbdiagram push schema.dbml --new # force-create, ignoring
93
93
 
94
94
  ### Pull
95
95
 
96
- Pull DBML content from a diagram on dbdiagram.io.
96
+ Pull DBML content from a diagram on dbdiagram.io or a document on dbdocs.io.
97
97
 
98
98
  **Options:**
99
99
 
100
100
  - `--diagram-id <id>` — The diagram to pull from
101
+ - `--project <name>` — The document to pull from, as `<workspace>/<project>` or just `<project>`
101
102
  - `-o, --out-file <filepath>` — Save to a file (omit to use the configured entry file from settings, or print to stdout)
102
103
 
104
+ **Source resolution:**
105
+
106
+ | Input | Resolves to |
107
+ | --- | --- |
108
+ | `--diagram-id <id>` | The given diagram |
109
+ | `--project <workspace>/<project>` | The given document |
110
+ | `--project <project>`, no `/` | The document `<project>` in the workspace from settings |
111
+ | Neither flag | The diagram from settings (`diagram.id`) |
112
+ | Both flags | The one appearing last on the command line wins |
113
+
103
114
  ```sh
104
115
  dbdiagram pull --diagram-id <id> # print to stdout (or save to the configured entry file)
105
116
  dbdiagram pull --diagram-id <id> -o schema.dbml # save to a specific file
117
+ dbdiagram pull --project myteam/myproject # pull a document, print to stdout
118
+ dbdiagram pull --project myproject -o schema.dbml # pull a document, workspace from settings
106
119
  ```
107
120
 
121
+ A document or diagram with no DBML yet returns empty content: pull warns and skips the write rather than
122
+ overwriting your local file (and its viz file) with nothing, and exits `0`.
123
+
108
124
  ---
109
125
 
110
126
  ### List
@@ -2,18 +2,19 @@ import { readFile } from 'node:fs/promises';
2
2
  import { log, spinner } from '@clack/prompts';
3
3
  import { DBDOCS_CONFIGS } from '../../config.js';
4
4
  import { getCredential } from '../../config/credential-manager.js';
5
- import { DOCUMENT_SOURCE_TYPE } from '../../constants/document.constant.js';
6
- import { AUTH_NOT_LOGGED_IN, 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';
5
+ import { DOCUMENT_SOURCE_CLIENT_TYPE, DOCUMENT_SOURCE_TYPE } from '../../constants/document.constant.js';
6
+ import { AUTH_NOT_LOGGED_IN, SOURCE_MISSING, VERSION_NAME_INVALID, FILE_READ_ERROR, } 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
9
  import { trackEvent } from '../../services/telemetry.service.js';
10
10
  import { prepareBuildDocumentData } from '../../services/dbml/dbml.service.js';
11
+ import { deriveDiagramVizPathFromDBML, readDiagramVizFile } from '../../services/viz/diagram-viz-file.service.js';
12
+ import { toDiagramVizData, toDocumentVizData } from '../../services/viz/diagram-viz-converter.service.js';
11
13
  import { getCliErrorMessageFromCode, toCliError } from '../../utils/cli-error.util.js';
12
14
  import { getDbmlVersion } from '../../utils/dbml.util.js';
13
15
  import { DbmlParseError } from '../../errors/dbml-parse.error.js';
14
16
  import { displayDbmlParseError, displayError, getOutputFormat, printJson } from '../../utils/output.util.js';
15
- import { validateProjectUrlName, validateVersionName, validateWorkspaceUrlName, } from '../../utils/validation.util.js';
16
- const DOCUMENT_SOURCE_CLIENT_TYPE = 'dbdiagram-cli';
17
+ import { validateAndResolveBuildDocumentProjectNames, validateVersionName } from '../../utils/validation.util.js';
17
18
  export async function documentAction(options) {
18
19
  const credential = getCredential();
19
20
  if (!credential) {
@@ -27,33 +28,18 @@ export async function documentAction(options) {
27
28
  return displayError(new CliError(VERSION_NAME_INVALID, versionError), getOutputFormat(options.json));
28
29
  }
29
30
  }
30
- if (!options.workspaceUrlName) {
31
+ const resolvedNames = validateAndResolveBuildDocumentProjectNames(options.workspaceUrlName, options.projectUrlOrName);
32
+ if (resolvedNames instanceof CliError) {
31
33
  process.exitCode = 1;
32
- return displayError(new CliError(WORKSPACE_NAME_MISSING, getCliErrorMessageFromCode(WORKSPACE_NAME_MISSING)), getOutputFormat(options.json));
33
- }
34
- const workspaceError = validateWorkspaceUrlName(options.workspaceUrlName);
35
- if (workspaceError) {
36
- process.exitCode = 1;
37
- return displayError(new CliError(WORKSPACE_NAME_INVALID, workspaceError), getOutputFormat(options.json));
38
- }
39
- if (!options.projectUrlOrName) {
40
- process.exitCode = 1;
41
- return displayError(new CliError(PROJECT_NAME_MISSING, getCliErrorMessageFromCode(PROJECT_NAME_MISSING)), getOutputFormat(options.json));
42
- }
43
- if (options.projectUrlOrName.includes('/')) {
44
- process.exitCode = 1;
45
- return displayError(new CliError(PROJECT_NAME_INVALID, 'Invalid --project value. Expected <workspace>/<project> or <project>.'), getOutputFormat(options.json));
46
- }
47
- const projectError = validateProjectUrlName(options.projectUrlOrName);
48
- if (projectError) {
49
- process.exitCode = 1;
50
- return displayError(new CliError(PROJECT_NAME_INVALID, projectError), getOutputFormat(options.json));
34
+ return displayError(resolvedNames, getOutputFormat(options.json));
51
35
  }
36
+ const { workspaceUrlName: targetWorkspaceUrlName, projectUrlOrName: targetProjectUrlOrName, } = resolvedNames;
52
37
  if (options.source === undefined) {
53
38
  process.exitCode = 1;
54
39
  return displayError(new CliError(SOURCE_MISSING, getCliErrorMessageFromCode(SOURCE_MISSING)), getOutputFormat(options.json));
55
40
  }
56
41
  let content;
42
+ let vizData;
57
43
  if (options.source.type === DOCUMENT_SOURCE_TYPE.FILE) {
58
44
  try {
59
45
  content = await readFile(options.source.value, 'utf8');
@@ -63,11 +49,17 @@ export async function documentAction(options) {
63
49
  process.exitCode = 1;
64
50
  return displayError(new CliError(FILE_READ_ERROR, `Failed to read file: ${message}`), getOutputFormat(options.json));
65
51
  }
52
+ const vizPath = deriveDiagramVizPathFromDBML(options.source.value);
53
+ const vizResult = await readDiagramVizFile(vizPath);
54
+ if (vizResult.ok) {
55
+ vizData = toDocumentVizData(toDiagramVizData(vizResult.state));
56
+ }
66
57
  }
67
58
  else {
68
59
  try {
69
60
  const diagram = await portalIntegration.getDiagram(options.source.value);
70
61
  content = diagram.content;
62
+ vizData = diagram.vizData ? toDocumentVizData(diagram.vizData) : undefined;
71
63
  }
72
64
  catch (error) {
73
65
  process.exitCode = 1;
@@ -98,9 +90,9 @@ export async function documentAction(options) {
98
90
  try {
99
91
  const currentUser = await portalIntegration.getCurrentUser();
100
92
  const { project } = await portalIntegration.createDocument({
101
- projectName: options.projectUrlOrName,
93
+ projectName: targetProjectUrlOrName,
102
94
  projectDescription: preparedDocumentData.description,
103
- workspaceUrlName: options.workspaceUrlName,
95
+ workspaceUrlName: targetWorkspaceUrlName,
104
96
  clientType: DOCUMENT_SOURCE_CLIENT_TYPE,
105
97
  doc: {
106
98
  content,
@@ -109,6 +101,7 @@ export async function documentAction(options) {
109
101
  shallowSchema: preparedDocumentData.shallowSchema,
110
102
  normalizedDatabase: preparedDocumentData.normalizedDatabase,
111
103
  dbmlVersion: getDbmlVersion(),
104
+ vizData,
112
105
  });
113
106
  const { workspaceUrl: workspaceUrlName, urlName: projectUrlName } = project;
114
107
  trackEvent({
@@ -1,4 +1,4 @@
1
- import { spinner } from '@clack/prompts';
1
+ import { log, spinner } from '@clack/prompts';
2
2
  import { getCredential } from '../config/credential-manager.js';
3
3
  import { writeFileWithDir } from '../services/file.service.js';
4
4
  import { portalIntegration } from '../integrations/portal/portal.integration.js';
@@ -7,7 +7,9 @@ import { fromServerFormat } from '../services/viz/diagram-viz-converter.service.
7
7
  import { logConsole } from '../utils/logger.util.js';
8
8
  import { CliError } from '../errors/cli.error.js';
9
9
  import { AUTH_NOT_LOGGED_IN, DIAGRAM_ID_MISSING } from '../constants/cli-error.constant.js';
10
+ import { PULL_SOURCE_TYPE } from '../constants/pull.constant.js';
10
11
  import { getCliErrorMessageFromCode, toCliError } from '../utils/cli-error.util.js';
12
+ import { validateAndResolvePullDocumentProjectNames } from '../utils/validation.util.js';
11
13
  import { displayError } from '../utils/output.util.js';
12
14
  export async function pullAction(options) {
13
15
  const credential = getCredential();
@@ -16,33 +18,68 @@ export async function pullAction(options) {
16
18
  process.exitCode = 1;
17
19
  return;
18
20
  }
19
- if (!options.diagramId) {
21
+ const { source } = options;
22
+ if (!source) {
20
23
  displayError(new CliError(DIAGRAM_ID_MISSING, getCliErrorMessageFromCode(DIAGRAM_ID_MISSING)), 'plain');
21
24
  process.exitCode = 1;
22
25
  return;
23
26
  }
27
+ let target;
28
+ if (source.type === PULL_SOURCE_TYPE.DOCUMENT) {
29
+ const resolved = validateAndResolvePullDocumentProjectNames(source.workspaceUrlName, source.projectUrlOrName);
30
+ if (resolved instanceof CliError) {
31
+ displayError(resolved, 'plain');
32
+ process.exitCode = 1;
33
+ return;
34
+ }
35
+ target = { kind: 'document', ...resolved };
36
+ }
37
+ else {
38
+ target = { kind: 'diagram', diagramId: source.value };
39
+ }
40
+ const isDocumentSource = target.kind === 'document';
24
41
  const s = spinner();
25
- s.start('Fetching diagram...');
42
+ s.start(isDocumentSource ? 'Fetching document...' : 'Fetching diagram...');
26
43
  try {
27
- const diagram = await portalIntegration.getDiagram(options.diagramId);
44
+ let content;
45
+ let vizData;
46
+ let successLabel;
47
+ if (target.kind === 'document') {
48
+ const { workspaceUrlName, projectUrlOrName } = target;
49
+ const document = await portalIntegration.getDocument(workspaceUrlName, projectUrlOrName);
50
+ content = document.content;
51
+ vizData = document.vizData;
52
+ successLabel = `${workspaceUrlName}/${projectUrlOrName}`;
53
+ }
54
+ else {
55
+ const diagram = await portalIntegration.getDiagram(target.diagramId);
56
+ content = diagram.content;
57
+ vizData = diagram.vizData;
58
+ successLabel = diagram.name ? `"${diagram.name}"` : 'your diagram';
59
+ }
28
60
  if (options.outFile) {
29
- await writeFileWithDir(options.outFile, diagram.content);
30
- if (diagram.vizData) {
61
+ if (!content || !content.trim()) {
62
+ s.stop();
63
+ log.warn('Pulled content is empty. Skipping write to avoid overwriting local files.');
64
+ process.exitCode = 0;
65
+ return;
66
+ }
67
+ await writeFileWithDir(options.outFile, content);
68
+ if (vizData) {
31
69
  const vizPath = deriveDiagramVizPathFromDBML(options.outFile);
32
70
  const existingResult = await readDiagramVizFile(vizPath);
33
- const state = fromServerFormat(diagram.vizData, existingResult.ok ? existingResult.state : undefined);
71
+ const state = fromServerFormat(vizData, existingResult.ok ? existingResult.state : undefined);
34
72
  await writeDiagramVizFile(vizPath, state);
35
73
  }
36
- const label = diagram.name ? `"${diagram.name}"` : 'your diagram';
37
- s.stop(`Saved ${label} to ${options.outFile}`);
74
+ s.stop(`Saved ${successLabel} to ${options.outFile}`);
38
75
  }
39
76
  else {
40
- s.stop('Diagram fetched successfully.');
41
- logConsole(diagram.content);
77
+ s.stop(isDocumentSource ? 'Document fetched successfully.' : 'Diagram fetched successfully.');
78
+ logConsole(content);
42
79
  }
43
80
  }
44
81
  catch (error) {
45
- s.stop('Failed to fetch diagram.');
82
+ s.stop(isDocumentSource ? 'Failed to fetch document.' : 'Failed to fetch diagram.');
46
83
  displayError(toCliError(error), 'plain');
47
84
  process.exitCode = 1;
48
85
  }
@@ -4,7 +4,7 @@ import { getCredential } from '../config/credential-manager.js';
4
4
  import { portalIntegration } from '../integrations/portal/portal.integration.js';
5
5
  import { trackEvent } from '../services/telemetry.service.js';
6
6
  import { deriveDiagramVizPathFromDBML, readDiagramVizFile } from '../services/viz/diagram-viz-file.service.js';
7
- import { toServerFormat } from '../services/viz/diagram-viz-converter.service.js';
7
+ import { toDiagramVizData } from '../services/viz/diagram-viz-converter.service.js';
8
8
  import { preparePushDiagramData } from '../services/dbml/dbml.service.js';
9
9
  import { DBDIAGRAM_CONFIGS } from '../config.js';
10
10
  import { CliError } from '../errors/cli.error.js';
@@ -48,7 +48,7 @@ export async function pushAction(filepath, options, thisCommand) {
48
48
  let vizData;
49
49
  if (vizResult.ok) {
50
50
  const { diagramViews } = preparedPushData;
51
- vizData = toServerFormat(vizResult.state, diagramViews);
51
+ vizData = toDiagramVizData(vizResult.state, diagramViews);
52
52
  }
53
53
  const s = spinner();
54
54
  s.start('Pushing diagram to dbdiagram...');
@@ -1,11 +1,19 @@
1
1
  import { pullAction } from '../actions/pull.action.js';
2
2
  import { pullPreAction } from '../hooks/pull.hook.js';
3
+ import { PULL_SOURCE_TYPE } from '../constants/pull.constant.js';
3
4
  export function registerPullCommand(program) {
4
- program
5
+ const cmd = program
5
6
  .command('pull')
6
- .description('Pull the DBML content of a diagram to your local machine')
7
+ .description('Pull the DBML content of a diagram or dbdocs document to your local machine')
7
8
  .option('--diagram-id <id>', 'The diagram ID to pull from')
9
+ .option('--project <name>', 'The dbdocs document to pull from, as <workspace>/<project> or just <project>')
8
10
  .option('--out-file, -o <filepath>', 'Save to a file (omit to use the configured entry file, or stdout)')
9
11
  .hook('preAction', pullPreAction)
10
12
  .action(pullAction);
13
+ cmd.on('option:diagram-id', (val) => {
14
+ cmd.setOptionValue('sourceFlag', { type: PULL_SOURCE_TYPE.DIAGRAM, value: val });
15
+ });
16
+ cmd.on('option:project', (val) => {
17
+ cmd.setOptionValue('sourceFlag', { type: PULL_SOURCE_TYPE.DOCUMENT, value: val });
18
+ });
11
19
  }
@@ -16,9 +16,16 @@ export const WORKSPACE_NAME_MISSING = 'workspace_name_missing';
16
16
  export const WORKSPACE_NAME_INVALID = 'workspace_name_invalid';
17
17
  export const WORKSPACE_NOT_FOUND = 'workspace_not_found';
18
18
  export const WORKSPACE_PERMISSION_DENIED = 'workspace_permission_denied';
19
+ export const WORKSPACE_PERMISSION_DENIED_CREATE_PROJECT = 'workspace_permission_denied_create_project';
20
+ export const WORKSPACE_PERMISSION_DENIED_LIST_PROJECT = 'workspace_permission_denied_list_project';
19
21
  export const PROJECT_NAME_MISSING = 'project_name_missing';
20
22
  export const PROJECT_NAME_INVALID = 'project_name_invalid';
21
23
  export const VERSION_NAME_INVALID = 'version_name_invalid';
24
+ export const PROJECT_NOT_FOUND = 'project_not_found';
25
+ export const PROJECT_PERMISSION_DENIED = 'project_permission_denied';
26
+ export const PROJECT_PASSWORD_REQUIRED = 'project_password_required';
27
+ export const PROJECT_PERMISSION_DENIED_EDIT = 'project_permission_denied_edit';
28
+ export const LINK_USER_FAILED = 'link_user_failed';
22
29
  export const TOKEN_NOT_FOUND = 'token_not_found';
23
30
  export const TOKEN_LIMIT_EXCEEDED = 'token_limit_exceeded';
24
31
  export const FILE_PATH_MISSING = 'file_path_missing';
@@ -56,14 +63,22 @@ export const CLI_ERROR_MESSAGES = {
56
63
  + 'or set workspaceUrlName in settings.json',
57
64
  [WORKSPACE_NOT_FOUND]: 'Workspace not found.',
58
65
  [WORKSPACE_PERMISSION_DENIED]: 'You do not have permission to access this workspace.',
66
+ [WORKSPACE_PERMISSION_DENIED_CREATE_PROJECT]: 'You do not have permission to create a project in this workspace.',
67
+ [WORKSPACE_PERMISSION_DENIED_LIST_PROJECT]: 'You do not have permission to list projects in this workspace.',
59
68
  [PROJECT_NAME_MISSING]: 'Project name/url name is required. Use --project <workspace>/<project> '
60
69
  + 'or set projectUrlName in settings.json',
70
+ [PROJECT_NOT_FOUND]: 'Document not found.',
71
+ [PROJECT_PERMISSION_DENIED]: 'You do not have permission to access this document.',
72
+ [PROJECT_PASSWORD_REQUIRED]: 'This document is password-protected and cannot be accessed via the CLI.',
73
+ [PROJECT_PERMISSION_DENIED_EDIT]: 'You do not have edit permission on this document.',
61
74
  [TOKEN_NOT_FOUND]: 'Token not found.',
62
75
  [TOKEN_LIMIT_EXCEEDED]: 'Token limit reached. Delete an existing token before creating a new one.',
63
76
  [FILE_PATH_MISSING]: 'Missing required argument: filepath. Provide it as an argument or set dbml.entry in settings.json.',
64
77
  [SOURCE_MISSING]: 'One source must be specified: --from-file or --from-diagram',
65
78
  [CONFIRMATION_REQUIRED]: 'Confirmation required. Pass --force (-f) to delete in a non-interactive environment.',
66
79
  [INTERNAL_SERVER_ERROR]: 'An internal server error occurred. Please try again later.',
80
+ [LINK_USER_FAILED]: 'Failed to link your account with dbdocs. This is a portal-side issue, not an authentication '
81
+ + 'problem; please try again or contact support.',
67
82
  [NETWORK_ERROR]: 'Network error: could not reach the server. Check your connection and try again.',
68
83
  };
69
84
  export const PORTAL_TO_CLI_CODE = {
@@ -74,6 +89,13 @@ export const PORTAL_TO_CLI_CODE = {
74
89
  diagram_limit_exceeded: DIAGRAM_LIMIT_EXCEEDED,
75
90
  workspace_not_found: WORKSPACE_NOT_FOUND,
76
91
  workspace_permission_denied_view: WORKSPACE_PERMISSION_DENIED,
92
+ project_not_found: PROJECT_NOT_FOUND,
93
+ project_permission_denied_view: PROJECT_PERMISSION_DENIED,
94
+ project_permission_denied_view_password_required: PROJECT_PASSWORD_REQUIRED,
95
+ project_permission_denied_edit: PROJECT_PERMISSION_DENIED_EDIT,
96
+ workspace_permission_denied_create_project: WORKSPACE_PERMISSION_DENIED_CREATE_PROJECT,
97
+ workspace_permission_denied_list_project: WORKSPACE_PERMISSION_DENIED_LIST_PROJECT,
98
+ link_user_failed: LINK_USER_FAILED,
77
99
  token_not_found: TOKEN_NOT_FOUND,
78
100
  token_limit_exceeded: TOKEN_LIMIT_EXCEEDED,
79
101
  internal_server_error: INTERNAL_SERVER_ERROR,
@@ -6,3 +6,4 @@ export const PROJECT_URL_NAME_PATTERN = /^[A-Za-z0-9_\-@.\s]+$/;
6
6
  export const WORKSPACE_URL_NAME_PATTERN = /^[A-Za-z0-9_\-@.\s]+$/;
7
7
  export const URL_NAME_MAX_LENGTH = 255;
8
8
  export const VERSION_NAME_MAX_LENGTH = 255;
9
+ export const DOCUMENT_SOURCE_CLIENT_TYPE = 'dbdiagram-cli';
@@ -0,0 +1,4 @@
1
+ export const PULL_SOURCE_TYPE = {
2
+ DIAGRAM: 'diagram',
3
+ DOCUMENT: 'document',
4
+ };
@@ -1,20 +1,20 @@
1
1
  import { getSettings } from '../config/settings-manager.js';
2
2
  import { DOCUMENT_SOURCE_TYPE } from '../constants/document.constant.js';
3
+ import { parseProjectFlag } from '../utils/document.util.js';
4
+ export function normalizeBuildDocumentFlags(cmd) {
5
+ const projectFlag = cmd.getOptionValue('project');
6
+ if (projectFlag === undefined || projectFlag === null)
7
+ return;
8
+ const { workspaceUrlName, projectUrlOrName } = parseProjectFlag(projectFlag);
9
+ if (workspaceUrlName !== undefined) {
10
+ cmd.setOptionValue('workspaceUrlName', workspaceUrlName);
11
+ }
12
+ cmd.setOptionValue('projectUrlOrName', projectUrlOrName);
13
+ }
3
14
  export function resolveBuildDocumentFlags(cmd, settings) {
4
15
  if (cmd.getOptionValue('source') == null && settings.dbml?.entry) {
5
16
  cmd.setOptionValue('source', { type: DOCUMENT_SOURCE_TYPE.FILE, value: settings.dbml.entry });
6
17
  }
7
- const projectFlag = cmd.getOptionValue('project');
8
- if (projectFlag != null) {
9
- const slashIndex = projectFlag.indexOf('/');
10
- if (slashIndex >= 0) {
11
- cmd.setOptionValue('workspaceUrlName', projectFlag.slice(0, slashIndex));
12
- cmd.setOptionValue('projectUrlOrName', projectFlag.slice(slashIndex + 1));
13
- }
14
- else {
15
- cmd.setOptionValue('projectUrlOrName', projectFlag);
16
- }
17
- }
18
18
  if (cmd.getOptionValue('workspaceUrlName') == null && settings.document?.workspaceUrlName) {
19
19
  cmd.setOptionValue('workspaceUrlName', settings.document.workspaceUrlName);
20
20
  }
@@ -23,6 +23,7 @@ export function resolveBuildDocumentFlags(cmd, settings) {
23
23
  }
24
24
  }
25
25
  export function buildDocumentPreAction(thisCommand) {
26
+ normalizeBuildDocumentFlags(thisCommand);
26
27
  const settings = getSettings();
27
28
  if (!settings)
28
29
  return;
@@ -1,13 +1,33 @@
1
1
  import { getSettings } from '../config/settings-manager.js';
2
+ import { PULL_SOURCE_TYPE } from '../constants/pull.constant.js';
3
+ import { parseProjectFlag } from '../utils/document.util.js';
4
+ export function normalizePullFlags(cmd) {
5
+ const sourceFlag = cmd.getOptionValue('sourceFlag');
6
+ if (sourceFlag === undefined || sourceFlag === null)
7
+ return;
8
+ const source = sourceFlag.type === PULL_SOURCE_TYPE.DOCUMENT
9
+ ? { type: PULL_SOURCE_TYPE.DOCUMENT, ...parseProjectFlag(sourceFlag.value) }
10
+ : { type: PULL_SOURCE_TYPE.DIAGRAM, value: sourceFlag.value };
11
+ cmd.setOptionValue('source', source);
12
+ }
2
13
  export function resolvePullFlags(cmd, settings) {
3
- if (cmd.getOptionValue('diagramId') == null && settings.diagram?.id) {
4
- cmd.setOptionValue('diagramId', settings.diagram.id);
14
+ const source = cmd.getOptionValue('source');
15
+ if (source == null) {
16
+ if (settings.diagram?.id) {
17
+ cmd.setOptionValue('source', { type: PULL_SOURCE_TYPE.DIAGRAM, value: settings.diagram.id });
18
+ }
19
+ }
20
+ else if (source.type === PULL_SOURCE_TYPE.DOCUMENT) {
21
+ if (source.workspaceUrlName == null && settings.document?.workspaceUrlName) {
22
+ cmd.setOptionValue('source', { ...source, workspaceUrlName: settings.document.workspaceUrlName });
23
+ }
5
24
  }
6
25
  if (cmd.getOptionValue('outFile') == null && settings.dbml?.entry) {
7
26
  cmd.setOptionValue('outFile', settings.dbml.entry);
8
27
  }
9
28
  }
10
29
  export function pullPreAction(thisCommand) {
30
+ normalizePullFlags(thisCommand);
11
31
  const settings = getSettings();
12
32
  if (!settings)
13
33
  return;
@@ -14,6 +14,10 @@ async function createDocument(req) {
14
14
  const { data } = await portalHttpClient.post(`${CLI_BASE_PATH}/projects`, req);
15
15
  return data;
16
16
  }
17
+ async function getDocument(workspaceUrlName, projectUrlName) {
18
+ const { data } = await portalHttpClient.get(`${CLI_BASE_PATH}/workspaces/${workspaceUrlName}/projects/${projectUrlName}`);
19
+ return data;
20
+ }
17
21
  async function updateDiagram(req) {
18
22
  const { name, dbml, vizData } = req;
19
23
  const { data } = await portalHttpClient.put(`${CLI_BASE_PATH}/diagrams/${req.diagramId}`, { name, dbml, vizData });
@@ -58,6 +62,7 @@ export const portalIntegration = {
58
62
  updateDiagram,
59
63
  createDocument,
60
64
  getDiagram,
65
+ getDocument,
61
66
  deleteDiagram,
62
67
  listDiagrams,
63
68
  listWorkspaces,
@@ -48,7 +48,7 @@ const toDetailLevel = (value) => (Object.values(DetailLevel).includes(value)
48
48
  const toRelationshipMode = (value) => (Object.values(RelationshipMode).includes(value)
49
49
  ? value
50
50
  : DEFAULT_RELATIONSHIP_MODE);
51
- export const toServerFormat = (state, diagramViews) => {
51
+ export const toDiagramVizData = (state, diagramViews) => {
52
52
  const { defaultView, views } = state;
53
53
  const viewEntitiesMap = new Map(diagramViews?.map((v) => [v.name, v.visibleEntities]));
54
54
  return {
@@ -71,6 +71,23 @@ export const toServerFormat = (state, diagramViews) => {
71
71
  })),
72
72
  };
73
73
  };
74
+ export const toDocumentVizData = (vizData) => {
75
+ return {
76
+ diagram: vizData.diagram,
77
+ referencePaths: vizData.referencePaths,
78
+ stickyNoteLayouts: vizData.stickyNoteLayouts,
79
+ tableGroups: vizData.tableGroups,
80
+ detailLevel: vizData.detailLevel,
81
+ views: vizData.views.map((view) => ({
82
+ name: view.name,
83
+ detailLevel: view.detailLevel,
84
+ tablePositions: view.tablePositions,
85
+ tableGroupCollapsedStates: view.tableGroupCollapsedStates,
86
+ stickyNoteLayouts: view.stickyNoteLayouts,
87
+ referencePaths: view.referencePaths,
88
+ })),
89
+ };
90
+ };
74
91
  const viewFromServer = (view) => {
75
92
  return {
76
93
  detailLevel: toDetailLevel(view.detailLevel),
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export const parseProjectFlag = (value) => {
2
+ const slashIndex = value.indexOf('/');
3
+ if (slashIndex >= 0) {
4
+ return { workspaceUrlName: value.slice(0, slashIndex), projectUrlOrName: value.slice(slashIndex + 1) };
5
+ }
6
+ return { projectUrlOrName: value };
7
+ };
@@ -1,4 +1,7 @@
1
+ import { PROJECT_NAME_INVALID, PROJECT_NAME_MISSING, WORKSPACE_NAME_INVALID, WORKSPACE_NAME_MISSING, } from '../constants/cli-error.constant.js';
1
2
  import { PROJECT_URL_NAME_PATTERN, URL_NAME_MAX_LENGTH, VERSION_NAME_MAX_LENGTH, WORKSPACE_URL_NAME_PATTERN, } from '../constants/document.constant.js';
3
+ import { CliError } from '../errors/cli.error.js';
4
+ import { getCliErrorMessageFromCode } from './cli-error.util.js';
2
5
  const INVALID_URL_NAME_MESSAGE = 'Invalid name. Must contain only letters, numbers, spaces, and _ - @ . characters.';
3
6
  export function validateProjectUrlName(value) {
4
7
  if (value.trim() === '') {
@@ -30,3 +33,41 @@ export function validateVersionName(value) {
30
33
  }
31
34
  return null;
32
35
  }
36
+ function toWorkspaceNameFormatError(workspaceUrlName) {
37
+ const workspaceError = validateWorkspaceUrlName(workspaceUrlName);
38
+ return workspaceError ? new CliError(WORKSPACE_NAME_INVALID, workspaceError) : null;
39
+ }
40
+ function toProjectNameError(projectUrlOrName) {
41
+ if (projectUrlOrName.includes('/')) {
42
+ return new CliError(PROJECT_NAME_INVALID, 'Invalid --project value. Expected <workspace>/<project> or <project>.');
43
+ }
44
+ const projectError = validateProjectUrlName(projectUrlOrName);
45
+ return projectError ? new CliError(PROJECT_NAME_INVALID, projectError) : null;
46
+ }
47
+ export function validateAndResolveBuildDocumentProjectNames(workspaceUrlName, projectUrlOrName) {
48
+ if (!workspaceUrlName) {
49
+ return new CliError(WORKSPACE_NAME_MISSING, getCliErrorMessageFromCode(WORKSPACE_NAME_MISSING));
50
+ }
51
+ const workspaceFormatError = toWorkspaceNameFormatError(workspaceUrlName);
52
+ if (workspaceFormatError)
53
+ return workspaceFormatError;
54
+ if (!projectUrlOrName) {
55
+ return new CliError(PROJECT_NAME_MISSING, getCliErrorMessageFromCode(PROJECT_NAME_MISSING));
56
+ }
57
+ const projectError = toProjectNameError(projectUrlOrName);
58
+ if (projectError)
59
+ return projectError;
60
+ return { workspaceUrlName, projectUrlOrName };
61
+ }
62
+ export function validateAndResolvePullDocumentProjectNames(workspaceUrlName, projectUrlOrName) {
63
+ if (!workspaceUrlName) {
64
+ return new CliError(WORKSPACE_NAME_MISSING, getCliErrorMessageFromCode(WORKSPACE_NAME_MISSING));
65
+ }
66
+ const workspaceFormatError = toWorkspaceNameFormatError(workspaceUrlName);
67
+ if (workspaceFormatError)
68
+ return workspaceFormatError;
69
+ const projectError = toProjectNameError(projectUrlOrName);
70
+ if (projectError)
71
+ return projectError;
72
+ return { workspaceUrlName, projectUrlOrName };
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbdiagram",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "dbdiagram": "dist/index.js"