@sbroenne/dvq 0.1.1
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/LICENSE +21 -0
- package/README.md +179 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +76 -0
- package/dist/lib.d.ts +41 -0
- package/dist/lib.js +133 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sbroenne
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# dvq
|
|
2
|
+
|
|
3
|
+
`dvq` is a small CLI and Node.js library for querying Dataverse OData endpoints with Azure CLI credentials.
|
|
4
|
+
|
|
5
|
+
## What exists today
|
|
6
|
+
|
|
7
|
+
- CLI for running OData paths against `/api/data/v9.2/`
|
|
8
|
+
- Azure authentication via `az login`
|
|
9
|
+
- Pretty-printed JSON output
|
|
10
|
+
- Library helpers for URL building, token acquisition, single requests, and paginated requests
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @sbroenne/dvq
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Run without installing:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @sbroenne/dvq --help
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Node.js 20 or later
|
|
27
|
+
- Azure CLI (`az`)
|
|
28
|
+
- Access to a Dataverse environment
|
|
29
|
+
|
|
30
|
+
Authenticate once before using the CLI or library:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
az login
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For development and release validation, `npm test` runs the unit suite and `npm run test:integration` runs the live Dataverse checks when `.env.integration` is configured.
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
Set the base environment URL:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export DATAVERSE_URL="https://yourorg.crm.dynamics.com"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also pass `--url` on the CLI instead of setting `DATAVERSE_URL`.
|
|
47
|
+
|
|
48
|
+
## CLI quick start
|
|
49
|
+
|
|
50
|
+
The query is an OData path relative to `/api/data/v9.2/`.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
dvq "accounts?$top=5"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
With an explicit URL:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
dvq --url "https://yourorg.crm.dynamics.com" "accounts?$select=name&$top=5"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
From a file:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
dvq --file query.odata
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
From stdin:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
echo "accounts?$top=5" | dvq --url "https://yourorg.crm.dynamics.com"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Verify auth and connectivity:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
dvq --whoami
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Follow `@odata.nextLink` pages automatically:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
dvq --all "accounts?$select=name"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## CLI usage
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
dvq [options] [query]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Option | Argument | Description |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `[query]` | OData path | Inline query path after `/api/data/v9.2/` |
|
|
95
|
+
| `-f, --file` | `<path>` | Read the OData path from a file |
|
|
96
|
+
| `-a, --all` | — | Follow `@odata.nextLink` pages up to the built-in safety cap |
|
|
97
|
+
| `-u, --url` | `<url>` | Use this Dataverse base URL instead of `DATAVERSE_URL` |
|
|
98
|
+
| `--whoami` | — | Call `WhoAmI` and print the response |
|
|
99
|
+
| `--version` | — | Print the package version |
|
|
100
|
+
| `--help` | — | Show help text |
|
|
101
|
+
|
|
102
|
+
Notes:
|
|
103
|
+
|
|
104
|
+
- If neither `[query]` nor `--file` is given, `dvq` reads stdin when input is piped.
|
|
105
|
+
- Without `--all`, the CLI prints the first JSON response object exactly as returned by Dataverse.
|
|
106
|
+
- With `--all`, the CLI prints a JSON array aggregated across pages.
|
|
107
|
+
|
|
108
|
+
## Library API
|
|
109
|
+
|
|
110
|
+
The package currently exports low-level helpers from `src/lib.ts`, not a single high-level `query()` function.
|
|
111
|
+
|
|
112
|
+
### Common request flow
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { buildUrl, fetchOData, getToken } from '@sbroenne/dvq';
|
|
116
|
+
|
|
117
|
+
const baseUrl = 'https://yourorg.crm.dynamics.com';
|
|
118
|
+
const token = await getToken({ baseUrl });
|
|
119
|
+
const url = buildUrl('accounts?$top=5', baseUrl);
|
|
120
|
+
const data = await fetchOData(url, token);
|
|
121
|
+
|
|
122
|
+
console.log(data);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Fetch all pages
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { getToken, queryAll } from '@sbroenne/dvq';
|
|
129
|
+
|
|
130
|
+
const baseUrl = 'https://yourorg.crm.dynamics.com';
|
|
131
|
+
const token = await getToken({ baseUrl });
|
|
132
|
+
const rows = await queryAll('accounts?$select=name', token, baseUrl);
|
|
133
|
+
|
|
134
|
+
console.log(rows);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Main exports
|
|
138
|
+
|
|
139
|
+
| Export | Purpose |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `resolveDataverseUrl`, `getDataverseUrl` | Resolve the Dataverse base URL from an explicit value or `DATAVERSE_URL` |
|
|
142
|
+
| `getDataverseScope` | Build the `/.default` scope for Azure auth |
|
|
143
|
+
| `buildUrl` | Build a full Dataverse Web API URL from an OData path |
|
|
144
|
+
| `buildHeaders` | Create request headers for Dataverse JSON calls |
|
|
145
|
+
| `getToken` | Acquire an access token using `AzureCliCredential` |
|
|
146
|
+
| `fetchOData` | Execute one HTTP request and parse the JSON response |
|
|
147
|
+
| `queryAll` | Follow paginated `@odata.nextLink` responses and return one array |
|
|
148
|
+
| `readQueryFile`, `readStdin` | CLI-oriented input helpers |
|
|
149
|
+
| `ODataError` | Error type for non-2xx HTTP responses |
|
|
150
|
+
| `API_PATH`, `DEFAULT_API_PATH`, `MAX_PAGES` | Public constants used by the helpers |
|
|
151
|
+
|
|
152
|
+
## Errors and behavior
|
|
153
|
+
|
|
154
|
+
Missing configuration:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
DATAVERSE_URL environment variable is required.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Authentication failure:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
Failed to get token. Run:
|
|
164
|
+
az login
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`queryAll()` stops after `MAX_PAGES` pages and throws if the safety cap is exceeded.
|
|
168
|
+
|
|
169
|
+
## Contributing
|
|
170
|
+
|
|
171
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
172
|
+
|
|
173
|
+
## Security
|
|
174
|
+
|
|
175
|
+
Please do not report suspected vulnerabilities in public issues. See [SECURITY.md](./SECURITY.md) for the current reporting path and maintainer security checklist.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT © 2026 sbroenne
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { MAX_PAGES, buildUrl, fetchOData, getDataverseUrl, getToken, queryAll, readQueryFile, readStdin, } from './lib.js';
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const { version: VERSION } = require('../package.json');
|
|
8
|
+
const HELP_AFTER = `
|
|
9
|
+
Environment:
|
|
10
|
+
DATAVERSE_URL Base URL for your Dataverse org (required unless --url is provided)
|
|
11
|
+
|
|
12
|
+
Prerequisites:
|
|
13
|
+
az login
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
dvq --url https://yourorg.crm.dynamics.com --whoami
|
|
17
|
+
dvq --file query.odata --all
|
|
18
|
+
DATAVERSE_URL=https://yourorg.crm.dynamics.com dvq "accounts?\\$top=5"
|
|
19
|
+
echo "accounts?\\$top=5" | dvq --url https://yourorg.crm.dynamics.com
|
|
20
|
+
`;
|
|
21
|
+
export function createProgram(version = VERSION) {
|
|
22
|
+
const program = new Command()
|
|
23
|
+
.name('dvq')
|
|
24
|
+
.description('Query a Dataverse environment via OData using Azure CLI credentials')
|
|
25
|
+
.version(version)
|
|
26
|
+
.option('-f, --file <path>', 'read an OData query path from a file')
|
|
27
|
+
.option('-a, --all', `follow @odata.nextLink pages (max ${MAX_PAGES})`)
|
|
28
|
+
.option('-u, --url <url>', 'use this Dataverse base URL for the request')
|
|
29
|
+
.option('--whoami', 'print the WhoAmI response to verify auth')
|
|
30
|
+
.argument('[query]', 'inline OData query path (everything after /api/data/v9.2/)')
|
|
31
|
+
.addHelpText('after', HELP_AFTER);
|
|
32
|
+
program.action(async (inlineQuery, opts) => {
|
|
33
|
+
let query = '';
|
|
34
|
+
if (opts.file) {
|
|
35
|
+
query = readQueryFile(opts.file);
|
|
36
|
+
}
|
|
37
|
+
else if (inlineQuery) {
|
|
38
|
+
query = inlineQuery;
|
|
39
|
+
}
|
|
40
|
+
else if (!opts.whoami && !process.stdin.isTTY) {
|
|
41
|
+
query = await readStdin();
|
|
42
|
+
}
|
|
43
|
+
if (!opts.whoami && !query) {
|
|
44
|
+
program.help();
|
|
45
|
+
}
|
|
46
|
+
const baseUrl = getDataverseUrl(opts.url);
|
|
47
|
+
const token = await getToken({ baseUrl });
|
|
48
|
+
if (opts.whoami) {
|
|
49
|
+
const data = await fetchOData(buildUrl('WhoAmI', baseUrl), token);
|
|
50
|
+
console.log(JSON.stringify(data, null, 2));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (opts.all) {
|
|
54
|
+
const results = await queryAll(query, token, baseUrl);
|
|
55
|
+
console.log(JSON.stringify(results, null, 2));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const data = await fetchOData(buildUrl(query, baseUrl), token);
|
|
59
|
+
console.log(JSON.stringify(data, null, 2));
|
|
60
|
+
});
|
|
61
|
+
return program;
|
|
62
|
+
}
|
|
63
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
64
|
+
await createProgram().parseAsync(argv, { from: 'user' });
|
|
65
|
+
}
|
|
66
|
+
const isEntrypoint = process.argv[1]
|
|
67
|
+
? fileURLToPath(import.meta.url) === process.argv[1]
|
|
68
|
+
: false;
|
|
69
|
+
if (isEntrypoint) {
|
|
70
|
+
runCli().catch((error) => {
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
console.error(message);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare const REQUIRED_DATAVERSE_URL_ERROR = "DATAVERSE_URL environment variable is required.";
|
|
2
|
+
export declare const AZ_LOGIN_GUIDANCE = "Failed to get token. Run:\n az login";
|
|
3
|
+
export declare const API_PATH = "/api/data/v9.2/";
|
|
4
|
+
export declare const DEFAULT_API_PATH = "/api/data/v9.2/";
|
|
5
|
+
export declare const MAX_PAGES = 40;
|
|
6
|
+
export interface ResolveDataverseUrlOptions {
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
url?: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
export interface TokenCredentialLike {
|
|
11
|
+
getToken(scope: string): Promise<{
|
|
12
|
+
token: string;
|
|
13
|
+
} | null>;
|
|
14
|
+
}
|
|
15
|
+
interface ODataResponse {
|
|
16
|
+
value?: unknown[];
|
|
17
|
+
'@odata.nextLink'?: string;
|
|
18
|
+
error?: {
|
|
19
|
+
message?: string;
|
|
20
|
+
};
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
export declare function resolveDataverseUrl(options?: ResolveDataverseUrlOptions): string;
|
|
24
|
+
export declare function getDataverseUrl(baseUrl?: string, env?: NodeJS.ProcessEnv): string;
|
|
25
|
+
export declare function getDataverseScope(baseUrl?: string, env?: NodeJS.ProcessEnv): string;
|
|
26
|
+
export declare function buildUrl(odataPath: string, baseUrl?: string, env?: NodeJS.ProcessEnv): string;
|
|
27
|
+
export declare function buildHeaders(token: string): Record<string, string>;
|
|
28
|
+
export declare function getToken(options?: {
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
credential?: TokenCredentialLike;
|
|
31
|
+
env?: NodeJS.ProcessEnv;
|
|
32
|
+
}): Promise<string>;
|
|
33
|
+
export declare class ODataError extends Error {
|
|
34
|
+
statusCode: number;
|
|
35
|
+
constructor(statusCode: number, detail: string);
|
|
36
|
+
}
|
|
37
|
+
export declare function fetchOData(url: string, token: string): Promise<ODataResponse>;
|
|
38
|
+
export declare function queryAll(odataPath: string, token: string, baseUrl?: string, env?: NodeJS.ProcessEnv): Promise<unknown[]>;
|
|
39
|
+
export declare function readQueryFile(filePath: string): string;
|
|
40
|
+
export declare function readStdin(): Promise<string>;
|
|
41
|
+
export {};
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { AzureCliCredential } from '@azure/identity';
|
|
3
|
+
export const REQUIRED_DATAVERSE_URL_ERROR = 'DATAVERSE_URL environment variable is required.';
|
|
4
|
+
export const AZ_LOGIN_GUIDANCE = 'Failed to get token. Run:\n az login';
|
|
5
|
+
export const API_PATH = '/api/data/v9.2/';
|
|
6
|
+
export const DEFAULT_API_PATH = API_PATH;
|
|
7
|
+
export const MAX_PAGES = 40;
|
|
8
|
+
function normalizeOptionalValue(value) {
|
|
9
|
+
const trimmed = value?.trim();
|
|
10
|
+
return trimmed ? trimmed : undefined;
|
|
11
|
+
}
|
|
12
|
+
function trimTrailingSlashes(value) {
|
|
13
|
+
let end = value.length;
|
|
14
|
+
while (end > 0 && value[end - 1] === '/') {
|
|
15
|
+
end -= 1;
|
|
16
|
+
}
|
|
17
|
+
return value.slice(0, end);
|
|
18
|
+
}
|
|
19
|
+
function trimLeadingSlashes(value) {
|
|
20
|
+
let start = 0;
|
|
21
|
+
while (start < value.length && value[start] === '/') {
|
|
22
|
+
start += 1;
|
|
23
|
+
}
|
|
24
|
+
return value.slice(start);
|
|
25
|
+
}
|
|
26
|
+
function normalizeBaseUrl(value) {
|
|
27
|
+
return trimTrailingSlashes(value);
|
|
28
|
+
}
|
|
29
|
+
export function resolveDataverseUrl(options = {}) {
|
|
30
|
+
const explicitUrl = normalizeOptionalValue(options.url);
|
|
31
|
+
if (explicitUrl) {
|
|
32
|
+
return normalizeBaseUrl(explicitUrl);
|
|
33
|
+
}
|
|
34
|
+
const envUrl = normalizeOptionalValue(options.env?.DATAVERSE_URL);
|
|
35
|
+
if (envUrl) {
|
|
36
|
+
return normalizeBaseUrl(envUrl);
|
|
37
|
+
}
|
|
38
|
+
throw new Error(REQUIRED_DATAVERSE_URL_ERROR);
|
|
39
|
+
}
|
|
40
|
+
export function getDataverseUrl(baseUrl, env = process.env) {
|
|
41
|
+
return resolveDataverseUrl({ env, url: baseUrl });
|
|
42
|
+
}
|
|
43
|
+
export function getDataverseScope(baseUrl, env = process.env) {
|
|
44
|
+
return `${getDataverseUrl(baseUrl, env)}/.default`;
|
|
45
|
+
}
|
|
46
|
+
export function buildUrl(odataPath, baseUrl, env = process.env) {
|
|
47
|
+
const queryPath = trimLeadingSlashes(odataPath);
|
|
48
|
+
return `${getDataverseUrl(baseUrl, env)}${API_PATH}${queryPath}`;
|
|
49
|
+
}
|
|
50
|
+
export function buildHeaders(token) {
|
|
51
|
+
return {
|
|
52
|
+
Authorization: `Bearer ${token}`,
|
|
53
|
+
'OData-MaxVersion': '4.0',
|
|
54
|
+
'OData-Version': '4.0',
|
|
55
|
+
Accept: 'application/json',
|
|
56
|
+
Prefer: 'odata.include-annotations="OData.Community.Display.V1.FormattedValue"',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export async function getToken(options = {}) {
|
|
60
|
+
const credential = options.credential ?? new AzureCliCredential();
|
|
61
|
+
try {
|
|
62
|
+
const response = await credential.getToken(getDataverseScope(options.baseUrl, options.env));
|
|
63
|
+
if (!response?.token) {
|
|
64
|
+
throw new Error('Missing token response');
|
|
65
|
+
}
|
|
66
|
+
return response.token;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new Error(AZ_LOGIN_GUIDANCE);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class ODataError extends Error {
|
|
73
|
+
statusCode;
|
|
74
|
+
constructor(statusCode, detail) {
|
|
75
|
+
super(`HTTP ${statusCode}: ${detail}`);
|
|
76
|
+
this.statusCode = statusCode;
|
|
77
|
+
this.name = 'ODataError';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export async function fetchOData(url, token) {
|
|
81
|
+
const response = await fetch(url, { headers: buildHeaders(token) });
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
let detail;
|
|
84
|
+
try {
|
|
85
|
+
const body = await response.json();
|
|
86
|
+
detail = body?.error?.message ?? JSON.stringify(body);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
detail = response.statusText;
|
|
90
|
+
}
|
|
91
|
+
throw new ODataError(response.status, detail);
|
|
92
|
+
}
|
|
93
|
+
return response.json();
|
|
94
|
+
}
|
|
95
|
+
export async function queryAll(odataPath, token, baseUrl, env = process.env) {
|
|
96
|
+
let url = buildUrl(odataPath, baseUrl, env);
|
|
97
|
+
const results = [];
|
|
98
|
+
let hasNextPage = false;
|
|
99
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
100
|
+
const data = await fetchOData(url, token);
|
|
101
|
+
if (data.value) {
|
|
102
|
+
results.push(...data.value);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
return [data];
|
|
106
|
+
}
|
|
107
|
+
const nextLink = data['@odata.nextLink'];
|
|
108
|
+
hasNextPage = Boolean(nextLink);
|
|
109
|
+
if (!nextLink) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
url = nextLink;
|
|
113
|
+
}
|
|
114
|
+
if (hasNextPage) {
|
|
115
|
+
throw new Error(`Query exceeded pagination safety cap (${MAX_PAGES} pages). Narrow the filter or increase MAX_PAGES.`);
|
|
116
|
+
}
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
export function readQueryFile(filePath) {
|
|
120
|
+
return fs.readFileSync(filePath, 'utf8').trim();
|
|
121
|
+
}
|
|
122
|
+
export function readStdin() {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
let data = '';
|
|
125
|
+
process.stdin.setEncoding('utf8');
|
|
126
|
+
process.stdin.on('data', (chunk) => {
|
|
127
|
+
data += chunk;
|
|
128
|
+
});
|
|
129
|
+
process.stdin.on('end', () => resolve(data.trim()));
|
|
130
|
+
process.stdin.on('error', reject);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=lib.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sbroenne/dvq",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLI for querying Dataverse OData endpoints with Azure CLI credentials",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dvq": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/lib.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"test": "npm run test:unit",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"lint": "eslint src/",
|
|
18
|
+
"format": "prettier --write 'src/**/*.ts'",
|
|
19
|
+
"prepublishOnly": "npm run lint && npm run build && npm test",
|
|
20
|
+
"test:unit": "vitest run --exclude src/lib.integration.test.ts",
|
|
21
|
+
"test:integration": "vitest run src/lib.integration.test.ts",
|
|
22
|
+
"format:check": "prettier --check 'src/**/*.ts'",
|
|
23
|
+
"lint:fix": "eslint src/ --fix"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"!dist/**/*.test.*",
|
|
28
|
+
"!dist/**/*.map",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"dataverse",
|
|
34
|
+
"query",
|
|
35
|
+
"odata",
|
|
36
|
+
"dynamics365",
|
|
37
|
+
"cli",
|
|
38
|
+
"azure"
|
|
39
|
+
],
|
|
40
|
+
"homepage": "https://github.com/sbroenne/dvq",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/sbroenne/dvq.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/sbroenne/dvq/issues"
|
|
47
|
+
},
|
|
48
|
+
"author": "sbroenne",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@azure/identity": "^4.13.0",
|
|
58
|
+
"commander": "^14.0.3"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@eslint/js": "^10.0.1",
|
|
62
|
+
"@types/node": "^25.5.0",
|
|
63
|
+
"eslint": "^10.0.3",
|
|
64
|
+
"prettier": "^3.8.1",
|
|
65
|
+
"typescript": "^5.9.3",
|
|
66
|
+
"typescript-eslint": "^8.57.0",
|
|
67
|
+
"vitest": "^4.1.0"
|
|
68
|
+
}
|
|
69
|
+
}
|