@hasna/connectors 1.4.0 → 1.4.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/bin/index.js +293 -967
- package/bin/mcp.js +11 -3
- package/bin/serve.js +11 -3
- package/connectors/googlephotos/src/cli/index.ts +1 -1
- package/connectors/yousearch/README.md +1 -1
- package/connectors/zendesk/CLAUDE.md +1 -1
- package/connectors/zendesk/Makefile +3 -1
- package/connectors/zendesk/README.md +3 -2
- package/connectors/zendesk/SCAFFOLD.md +2 -2
- package/connectors/zendesk/nginx-connector.conf +8 -8
- package/connectors/zendesk/nginx.conf +8 -8
- package/connectors/zendesk/src/cli/index.ts +19 -8
- package/connectors/zendesk/src/server/index.ts +1 -1
- package/connectors/zendesk/src/utils/config.test.ts +70 -0
- package/connectors/zendesk/src/utils/config.ts +12 -2
- package/connectors/zendesk/src/utils/remote-url.test.ts +72 -0
- package/dashboard/dist/assets/index-DlEljRrW.js +285 -0
- package/dashboard/dist/index.html +1 -1
- package/dist/index.js +11 -3
- package/package.json +11 -3
- package/connectors/tiktok-events-api/.test-home/.bun/install/cache/@t@/b0fec00ef4c1a326.pile +0 -0
- package/dashboard/dist/assets/index-XPlN-9Xy.js +0 -280
- package/dist/cli/commands/sync.d.ts +0 -2
- package/dist/db/pg-migrations.d.ts +0 -6
package/bin/mcp.js
CHANGED
|
@@ -5804,7 +5804,7 @@ var package_default;
|
|
|
5804
5804
|
var init_package = __esm(() => {
|
|
5805
5805
|
package_default = {
|
|
5806
5806
|
name: "@hasna/connectors",
|
|
5807
|
-
version: "1.4.
|
|
5807
|
+
version: "1.4.1",
|
|
5808
5808
|
description: "Open source connector library - Install API connectors with a single command",
|
|
5809
5809
|
type: "module",
|
|
5810
5810
|
bin: {
|
|
@@ -5817,7 +5817,15 @@ var init_package = __esm(() => {
|
|
|
5817
5817
|
"dist/",
|
|
5818
5818
|
"dashboard/dist/",
|
|
5819
5819
|
"connectors/",
|
|
5820
|
-
"README.md"
|
|
5820
|
+
"README.md",
|
|
5821
|
+
"!connectors/**/.test-home/**",
|
|
5822
|
+
"!connectors/**/node_modules/**",
|
|
5823
|
+
"!connectors/**/bun.lock",
|
|
5824
|
+
"!connectors/**/package-lock.json",
|
|
5825
|
+
"!connectors/**/yarn.lock",
|
|
5826
|
+
"!connectors/**/pnpm-lock.yaml",
|
|
5827
|
+
"!connectors/**/.env",
|
|
5828
|
+
"!connectors/**/.env.local"
|
|
5821
5829
|
],
|
|
5822
5830
|
exports: {
|
|
5823
5831
|
".": {
|
|
@@ -5832,7 +5840,7 @@ var init_package = __esm(() => {
|
|
|
5832
5840
|
main: "./dist/index.js",
|
|
5833
5841
|
types: "./dist/index.d.ts",
|
|
5834
5842
|
scripts: {
|
|
5835
|
-
build: "cd dashboard && bun run build && cd .. && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk --external conf && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/serve.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/social/index.ts --outfile ./dist/social/index.js --target bun && tsc --emitDeclarationOnly --outDir ./dist && tsc -p tsconfig.social.json",
|
|
5843
|
+
build: "cd dashboard && bun run build && cd .. && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk --external conf --external @hasna/events && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/serve.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/social/index.ts --outfile ./dist/social/index.js --target bun && tsc --emitDeclarationOnly --outDir ./dist && tsc -p tsconfig.social.json",
|
|
5836
5844
|
"build:dashboard": "cd dashboard && bun run build",
|
|
5837
5845
|
postinstall: 'mkdir -p $HOME/.hasna/connectors 2>/dev/null || true; [ "$SKIP_DASHBOARD" = "1" ] || [ ! -f dashboard/package.json ] || [ -d dashboard/node_modules ] || (cd dashboard && bun install)',
|
|
5838
5846
|
dev: "bun run ./src/cli/index.tsx",
|
package/bin/serve.js
CHANGED
|
@@ -6434,7 +6434,7 @@ var package_default;
|
|
|
6434
6434
|
var init_package = __esm(() => {
|
|
6435
6435
|
package_default = {
|
|
6436
6436
|
name: "@hasna/connectors",
|
|
6437
|
-
version: "1.4.
|
|
6437
|
+
version: "1.4.1",
|
|
6438
6438
|
description: "Open source connector library - Install API connectors with a single command",
|
|
6439
6439
|
type: "module",
|
|
6440
6440
|
bin: {
|
|
@@ -6447,7 +6447,15 @@ var init_package = __esm(() => {
|
|
|
6447
6447
|
"dist/",
|
|
6448
6448
|
"dashboard/dist/",
|
|
6449
6449
|
"connectors/",
|
|
6450
|
-
"README.md"
|
|
6450
|
+
"README.md",
|
|
6451
|
+
"!connectors/**/.test-home/**",
|
|
6452
|
+
"!connectors/**/node_modules/**",
|
|
6453
|
+
"!connectors/**/bun.lock",
|
|
6454
|
+
"!connectors/**/package-lock.json",
|
|
6455
|
+
"!connectors/**/yarn.lock",
|
|
6456
|
+
"!connectors/**/pnpm-lock.yaml",
|
|
6457
|
+
"!connectors/**/.env",
|
|
6458
|
+
"!connectors/**/.env.local"
|
|
6451
6459
|
],
|
|
6452
6460
|
exports: {
|
|
6453
6461
|
".": {
|
|
@@ -6462,7 +6470,7 @@ var init_package = __esm(() => {
|
|
|
6462
6470
|
main: "./dist/index.js",
|
|
6463
6471
|
types: "./dist/index.d.ts",
|
|
6464
6472
|
scripts: {
|
|
6465
|
-
build: "cd dashboard && bun run build && cd .. && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk --external conf && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/serve.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/social/index.ts --outfile ./dist/social/index.js --target bun && tsc --emitDeclarationOnly --outDir ./dist && tsc -p tsconfig.social.json",
|
|
6473
|
+
build: "cd dashboard && bun run build && cd .. && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk --external conf --external @hasna/events && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/serve.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/social/index.ts --outfile ./dist/social/index.js --target bun && tsc --emitDeclarationOnly --outDir ./dist && tsc -p tsconfig.social.json",
|
|
6466
6474
|
"build:dashboard": "cd dashboard && bun run build",
|
|
6467
6475
|
postinstall: 'mkdir -p $HOME/.hasna/connectors 2>/dev/null || true; [ "$SKIP_DASHBOARD" = "1" ] || [ ! -f dashboard/package.json ] || [ -d dashboard/node_modules ] || (cd dashboard && bun install)',
|
|
6468
6476
|
dev: "bun run ./src/cli/index.tsx",
|
|
@@ -122,7 +122,7 @@ authCmd
|
|
|
122
122
|
const userInfo = await getUserInfo(result.tokens!.accessToken);
|
|
123
123
|
const email = userInfo.email;
|
|
124
124
|
|
|
125
|
-
// Convert email to profile slug:
|
|
125
|
+
// Convert email to profile slug: user@example.com → userexamplecom
|
|
126
126
|
const profileSlug = email.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
|
|
127
127
|
|
|
128
128
|
// Create profile if it doesn't exist
|
|
@@ -25,7 +25,7 @@ export YOUSEARCH_API_KEY=your-api-key
|
|
|
25
25
|
connect-yousearch search "latest AI news" --count 5
|
|
26
26
|
|
|
27
27
|
# Web search with domain filters (POST)
|
|
28
|
-
connect-yousearch search-post "
|
|
28
|
+
connect-yousearch search-post "agent frameworks" --include-domains "example.com,github.com"
|
|
29
29
|
|
|
30
30
|
# Multi-step research
|
|
31
31
|
connect-yousearch research "What are the latest developments in AI agents?" --effort deep
|
|
@@ -43,7 +43,7 @@ connect-zendesk/
|
|
|
43
43
|
- **EC2 Instance**: `hasna-prod-connect-zendesk`
|
|
44
44
|
- **RDS Database**: `hasna-prod-connect-zendesk`
|
|
45
45
|
- **S3 Bucket**: `hasna-prod-connect-zendesk`
|
|
46
|
-
- **Remote API**: `
|
|
46
|
+
- **Remote API**: deployment-specific; set `ZENDESK_REMOTE_API_URL` (no shipped default)
|
|
47
47
|
|
|
48
48
|
## Key Patterns
|
|
49
49
|
|
|
@@ -17,6 +17,8 @@ EC2_USER ?= ec2-user
|
|
|
17
17
|
DEPLOY_PATH ?= /home/ec2-user/connectors/connect-zendesk
|
|
18
18
|
SERVICE_NAME ?= connect-zendesk
|
|
19
19
|
PORT ?= 21010
|
|
20
|
+
# Public URL the deployed server is reachable at. Deployment-specific — no default.
|
|
21
|
+
REMOTE_API_URL ?=
|
|
20
22
|
|
|
21
23
|
# Default target
|
|
22
24
|
help:
|
|
@@ -92,7 +94,7 @@ deploy-ec2: deploy-sync
|
|
|
92
94
|
|
|
93
95
|
deploy: build deploy-ec2
|
|
94
96
|
@echo "✅ Deployment complete"
|
|
95
|
-
@echo "Server running at
|
|
97
|
+
@echo "Server running at $(if $(REMOTE_API_URL),$(REMOTE_API_URL),<REMOTE_API_URL not set>)"
|
|
96
98
|
|
|
97
99
|
logs:
|
|
98
100
|
ssh $(EC2_USER)@$(EC2_HOST) "sudo journalctl -u $(SERVICE_NAME) -f"
|
|
@@ -11,7 +11,7 @@ This connector provides programmatic access to Zendesk's Support API, including
|
|
|
11
11
|
| EC2 Instance | `hasna-prod-connect-zendesk` |
|
|
12
12
|
| RDS Database | `hasna-prod-connect-zendesk` |
|
|
13
13
|
| S3 Bucket | `hasna-prod-connect-zendesk` |
|
|
14
|
-
| Remote API | `
|
|
14
|
+
| Remote API | configured per deployment via `ZENDESK_REMOTE_API_URL` (no default) |
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
@@ -279,7 +279,8 @@ This connector is deployed to:
|
|
|
279
279
|
- **Database**: `hasna-prod-connect-zendesk`
|
|
280
280
|
- **S3**: `hasna-prod-connect-zendesk`
|
|
281
281
|
|
|
282
|
-
The remote API is
|
|
282
|
+
The remote API host is deployment-specific and has no built-in default. Point the CLI at your
|
|
283
|
+
deployment with `ZENDESK_REMOTE_API_URL` or `connect-zendesk config set-remote-url <url>`.
|
|
283
284
|
|
|
284
285
|
## License
|
|
285
286
|
|
|
@@ -43,7 +43,7 @@ Each connector follows this naming pattern:
|
|
|
43
43
|
| EC2 Instance | `hasna-prod-connect-{name}` | `hasna-prod-connect-notion` |
|
|
44
44
|
| RDS Database | `hasna-prod-connect-{name}` | `hasna-prod-connect-notion` |
|
|
45
45
|
| S3 Bucket | `hasna-prod-connect-{name}` | `hasna-prod-connect-notion` |
|
|
46
|
-
| Remote API | `https
|
|
46
|
+
| Remote API | `https://<connect-host>/{name}` | `https://connect.example.com/notion` |
|
|
47
47
|
|
|
48
48
|
## Project Structure
|
|
49
49
|
|
|
@@ -175,4 +175,4 @@ The connector is deployed to AWS infrastructure:
|
|
|
175
175
|
|
|
176
176
|
1. Build the project: `make build`
|
|
177
177
|
2. Deploy to EC2: `make deploy-ec2`
|
|
178
|
-
3.
|
|
178
|
+
3. Point the CLI at the deployment via `<NAME>_REMOTE_API_URL` (no default is shipped)
|
|
@@ -54,7 +54,7 @@ http {
|
|
|
54
54
|
|
|
55
55
|
server {
|
|
56
56
|
listen 80;
|
|
57
|
-
server_name connect.
|
|
57
|
+
server_name connect.example.com;
|
|
58
58
|
|
|
59
59
|
location / {
|
|
60
60
|
root /usr/share/nginx/html;
|
|
@@ -127,7 +127,7 @@ http {
|
|
|
127
127
|
|
|
128
128
|
server {
|
|
129
129
|
listen 80;
|
|
130
|
-
server_name gmail.connect.
|
|
130
|
+
server_name gmail.connect.example.com;
|
|
131
131
|
|
|
132
132
|
location / {
|
|
133
133
|
proxy_pass http://127.0.0.1:3001;
|
|
@@ -140,7 +140,7 @@ http {
|
|
|
140
140
|
|
|
141
141
|
server {
|
|
142
142
|
listen 80;
|
|
143
|
-
server_name googlecontacts.connect.
|
|
143
|
+
server_name googlecontacts.connect.example.com;
|
|
144
144
|
|
|
145
145
|
location / {
|
|
146
146
|
proxy_pass http://127.0.0.1:3002;
|
|
@@ -153,7 +153,7 @@ http {
|
|
|
153
153
|
|
|
154
154
|
server {
|
|
155
155
|
listen 80;
|
|
156
|
-
server_name googledrive.connect.
|
|
156
|
+
server_name googledrive.connect.example.com;
|
|
157
157
|
|
|
158
158
|
location / {
|
|
159
159
|
proxy_pass http://127.0.0.1:3003;
|
|
@@ -166,7 +166,7 @@ http {
|
|
|
166
166
|
|
|
167
167
|
server {
|
|
168
168
|
listen 80;
|
|
169
|
-
server_name linear.connect.
|
|
169
|
+
server_name linear.connect.example.com;
|
|
170
170
|
|
|
171
171
|
location / {
|
|
172
172
|
proxy_pass http://127.0.0.1:3004;
|
|
@@ -179,7 +179,7 @@ http {
|
|
|
179
179
|
|
|
180
180
|
server {
|
|
181
181
|
listen 80;
|
|
182
|
-
server_name notion.connect.
|
|
182
|
+
server_name notion.connect.example.com;
|
|
183
183
|
|
|
184
184
|
location / {
|
|
185
185
|
proxy_pass http://127.0.0.1:3005;
|
|
@@ -192,7 +192,7 @@ http {
|
|
|
192
192
|
|
|
193
193
|
server {
|
|
194
194
|
listen 80;
|
|
195
|
-
server_name clickbank.connect.
|
|
195
|
+
server_name clickbank.connect.example.com;
|
|
196
196
|
|
|
197
197
|
location / {
|
|
198
198
|
proxy_pass http://127.0.0.1:3013;
|
|
@@ -205,7 +205,7 @@ http {
|
|
|
205
205
|
|
|
206
206
|
server {
|
|
207
207
|
listen 80;
|
|
208
|
-
server_name zendesk.connect.
|
|
208
|
+
server_name zendesk.connect.example.com;
|
|
209
209
|
|
|
210
210
|
location / {
|
|
211
211
|
proxy_pass http://127.0.0.1:21010;
|
|
@@ -12,10 +12,10 @@ upstream connect_zendesk {
|
|
|
12
12
|
keepalive 32;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
# Main server block for connect.
|
|
16
|
-
# Add this location block to the existing connect.
|
|
15
|
+
# Main server block for connect.example.com/zendesk
|
|
16
|
+
# Add this location block to the existing connect.example.com server
|
|
17
17
|
#
|
|
18
|
-
# If you have a separate server block for connect.
|
|
18
|
+
# If you have a separate server block for connect.example.com, add:
|
|
19
19
|
#
|
|
20
20
|
# location /zendesk {
|
|
21
21
|
# proxy_pass http://connect_zendesk;
|
|
@@ -30,21 +30,21 @@ upstream connect_zendesk {
|
|
|
30
30
|
# proxy_read_timeout 86400;
|
|
31
31
|
# }
|
|
32
32
|
|
|
33
|
-
# Standalone server configuration (use if connect.
|
|
33
|
+
# Standalone server configuration (use if connect.example.com doesn't exist)
|
|
34
34
|
# Uncomment and modify as needed:
|
|
35
35
|
#
|
|
36
36
|
# server {
|
|
37
37
|
# listen 80;
|
|
38
|
-
# server_name connect.
|
|
38
|
+
# server_name connect.example.com;
|
|
39
39
|
# return 301 https://$host$request_uri;
|
|
40
40
|
# }
|
|
41
41
|
#
|
|
42
42
|
# server {
|
|
43
43
|
# listen 443 ssl http2;
|
|
44
|
-
# server_name connect.
|
|
44
|
+
# server_name connect.example.com;
|
|
45
45
|
#
|
|
46
|
-
# ssl_certificate /etc/letsencrypt/live/connect.
|
|
47
|
-
# ssl_certificate_key /etc/letsencrypt/live/connect.
|
|
46
|
+
# ssl_certificate /etc/letsencrypt/live/connect.example.com/fullchain.pem;
|
|
47
|
+
# ssl_certificate_key /etc/letsencrypt/live/connect.example.com/privkey.pem;
|
|
48
48
|
#
|
|
49
49
|
# location /zendesk {
|
|
50
50
|
# proxy_pass http://connect_zendesk;
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
getConfigDir,
|
|
17
17
|
getBaseConfigDir,
|
|
18
18
|
getExportsDir,
|
|
19
|
-
|
|
19
|
+
findRemoteApiUrl,
|
|
20
20
|
setRemoteApiUrl,
|
|
21
21
|
setProfileOverride,
|
|
22
22
|
getCurrentProfile,
|
|
@@ -238,7 +238,7 @@ configCmd
|
|
|
238
238
|
|
|
239
239
|
configCmd
|
|
240
240
|
.command('set-remote-url <url>')
|
|
241
|
-
.description('Set remote API URL (default
|
|
241
|
+
.description('Set remote API URL (no default; also settable via ZENDESK_REMOTE_API_URL)')
|
|
242
242
|
.action((url: string) => {
|
|
243
243
|
setRemoteApiUrl(url);
|
|
244
244
|
success(`Remote API URL set to: ${url}`);
|
|
@@ -253,13 +253,13 @@ configCmd
|
|
|
253
253
|
const apiToken = getApiToken();
|
|
254
254
|
const baseUrl = getBaseUrl();
|
|
255
255
|
const account = getDefaultAccount();
|
|
256
|
-
const remoteUrl =
|
|
256
|
+
const remoteUrl = findRemoteApiUrl();
|
|
257
257
|
info(`Profile: ${chalk.cyan(profile)}`);
|
|
258
258
|
info(`Email: ${email || chalk.gray('not set')}`);
|
|
259
259
|
info(`API Token: ${apiToken ? `${apiToken.substring(0, 6)}...${apiToken.substring(apiToken.length - 4)}` : chalk.gray('not set')}`);
|
|
260
260
|
info(`Base URL: ${baseUrl || chalk.gray('not set')}`);
|
|
261
261
|
info(`Default Account: ${account || chalk.gray('not set')}`);
|
|
262
|
-
info(`Remote API URL: ${remoteUrl}`);
|
|
262
|
+
info(`Remote API URL: ${remoteUrl || chalk.gray('not set')}`);
|
|
263
263
|
info(`Config Directory: ${getBaseConfigDir()}`);
|
|
264
264
|
info(`Profile Config: ${getConfigDir()}`);
|
|
265
265
|
info(`Exports Directory: ${getExportsDir()}`);
|
|
@@ -274,17 +274,28 @@ configCmd
|
|
|
274
274
|
});
|
|
275
275
|
|
|
276
276
|
// ============================================
|
|
277
|
-
// Remote API Commands (
|
|
277
|
+
// Remote API Commands (host comes from ZENDESK_REMOTE_API_URL / config)
|
|
278
278
|
// ============================================
|
|
279
279
|
const remoteCmd = program
|
|
280
280
|
.command('remote')
|
|
281
281
|
.description('Interact with the remote Zendesk connector API');
|
|
282
282
|
|
|
283
|
+
// The remote host has no shipped default. Commands that need it exit with the
|
|
284
|
+
// connector's usual error convention rather than an uncaught throw.
|
|
285
|
+
function requireRemoteApiUrl(): string {
|
|
286
|
+
const remoteUrl = findRemoteApiUrl();
|
|
287
|
+
if (!remoteUrl) {
|
|
288
|
+
error('Remote API URL is not configured. Set ZENDESK_REMOTE_API_URL or run: connect-zendesk config set-remote-url <url>');
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
return remoteUrl;
|
|
292
|
+
}
|
|
293
|
+
|
|
283
294
|
remoteCmd
|
|
284
295
|
.command('status')
|
|
285
296
|
.description('Check remote API status')
|
|
286
297
|
.action(async () => {
|
|
287
|
-
const remoteUrl =
|
|
298
|
+
const remoteUrl = requireRemoteApiUrl();
|
|
288
299
|
logger.command('remote status', { remoteUrl });
|
|
289
300
|
try {
|
|
290
301
|
const response = await fetch(`${remoteUrl}/status`);
|
|
@@ -301,7 +312,7 @@ remoteCmd
|
|
|
301
312
|
.command('health')
|
|
302
313
|
.description('Check remote API health')
|
|
303
314
|
.action(async () => {
|
|
304
|
-
const remoteUrl =
|
|
315
|
+
const remoteUrl = requireRemoteApiUrl();
|
|
305
316
|
logger.command('remote health', { remoteUrl });
|
|
306
317
|
try {
|
|
307
318
|
const response = await fetch(`${remoteUrl}/health`);
|
|
@@ -321,7 +332,7 @@ remoteCmd
|
|
|
321
332
|
.command('url')
|
|
322
333
|
.description('Show current remote API URL')
|
|
323
334
|
.action(() => {
|
|
324
|
-
info(`Remote API URL: ${
|
|
335
|
+
info(`Remote API URL: ${findRemoteApiUrl() || chalk.gray('not set')}`);
|
|
325
336
|
});
|
|
326
337
|
|
|
327
338
|
// ============================================
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* connect-zendesk server
|
|
4
4
|
* Remote API server for Zendesk connector
|
|
5
|
-
*
|
|
5
|
+
* Deployment host is environment-specific; see nginx.conf for the reverse-proxy template.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const PORT = parseInt(process.env.PORT || '3000');
|
|
@@ -2,6 +2,14 @@ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
|
2
2
|
import { existsSync, rmSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { join } from 'path';
|
|
5
|
+
import {
|
|
6
|
+
findRemoteApiUrl,
|
|
7
|
+
getRemoteApiUrl,
|
|
8
|
+
setRemoteApiUrl,
|
|
9
|
+
setProfileOverride,
|
|
10
|
+
getBaseConfigDir,
|
|
11
|
+
clearConfig,
|
|
12
|
+
} from './config';
|
|
5
13
|
|
|
6
14
|
// We need to test the config module with a custom config dir
|
|
7
15
|
// to avoid messing with actual user config
|
|
@@ -190,4 +198,66 @@ describe('Config utilities', () => {
|
|
|
190
198
|
else delete process.env.ZENDESK_EMAIL;
|
|
191
199
|
});
|
|
192
200
|
});
|
|
201
|
+
|
|
202
|
+
// The remote API URL no longer falls back to a hardcoded deployment host, so
|
|
203
|
+
// these exercise the real module instead of re-implementing the priority
|
|
204
|
+
// logic: an unconfigured URL has to fail loudly rather than silently resolve
|
|
205
|
+
// to a baked-in default, and each configured source has to round-trip.
|
|
206
|
+
describe('remote API URL resolution', () => {
|
|
207
|
+
const REMOTE_URL_TEST_PROFILE = 'remote-url-test';
|
|
208
|
+
const ENV_URL = 'https://env.example.com/zendesk';
|
|
209
|
+
const STORED_URL = 'https://stored.example.com/zendesk';
|
|
210
|
+
|
|
211
|
+
let originalEnv: string | undefined;
|
|
212
|
+
|
|
213
|
+
beforeEach(() => {
|
|
214
|
+
originalEnv = process.env.ZENDESK_REMOTE_API_URL;
|
|
215
|
+
delete process.env.ZENDESK_REMOTE_API_URL;
|
|
216
|
+
// Profiles are the module's own isolation seam: point config reads and
|
|
217
|
+
// writes at a throwaway profile so the real one is never touched.
|
|
218
|
+
setProfileOverride(REMOTE_URL_TEST_PROFILE);
|
|
219
|
+
clearConfig();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
afterEach(() => {
|
|
223
|
+
setProfileOverride(undefined);
|
|
224
|
+
// Derive the throwaway path by name rather than from getConfigDir(), so
|
|
225
|
+
// this stays a delete of the test profile no matter how the override is
|
|
226
|
+
// sequenced above it.
|
|
227
|
+
rmSync(join(getBaseConfigDir(), 'profiles', REMOTE_URL_TEST_PROFILE), {
|
|
228
|
+
recursive: true,
|
|
229
|
+
force: true,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (originalEnv === undefined) delete process.env.ZENDESK_REMOTE_API_URL;
|
|
233
|
+
else process.env.ZENDESK_REMOTE_API_URL = originalEnv;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('is unset and throws when neither env nor config provides a URL', () => {
|
|
237
|
+
expect(findRemoteApiUrl()).toBeUndefined();
|
|
238
|
+
expect(() => getRemoteApiUrl()).toThrow(/ZENDESK_REMOTE_API_URL/);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('resolves from the environment variable', () => {
|
|
242
|
+
process.env.ZENDESK_REMOTE_API_URL = ENV_URL;
|
|
243
|
+
|
|
244
|
+
expect(findRemoteApiUrl()).toBe(ENV_URL);
|
|
245
|
+
expect(getRemoteApiUrl()).toBe(ENV_URL);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('resolves from the stored config value', () => {
|
|
249
|
+
setRemoteApiUrl(STORED_URL);
|
|
250
|
+
|
|
251
|
+
expect(findRemoteApiUrl()).toBe(STORED_URL);
|
|
252
|
+
expect(getRemoteApiUrl()).toBe(STORED_URL);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('environment variable takes precedence over the stored config value', () => {
|
|
256
|
+
setRemoteApiUrl(STORED_URL);
|
|
257
|
+
process.env.ZENDESK_REMOTE_API_URL = ENV_URL;
|
|
258
|
+
|
|
259
|
+
expect(findRemoteApiUrl()).toBe(ENV_URL);
|
|
260
|
+
expect(getRemoteApiUrl()).toBe(ENV_URL);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
193
263
|
});
|
|
@@ -499,10 +499,20 @@ export function clearConfig(): void {
|
|
|
499
499
|
saveConfig({});
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
-
|
|
502
|
+
// The remote API host is deployment-specific and has no shippable default.
|
|
503
|
+
// Configure it with ZENDESK_REMOTE_API_URL or `connect-zendesk config set-remote-url <url>`.
|
|
504
|
+
export function findRemoteApiUrl(): string | undefined {
|
|
505
|
+
return process.env.ZENDESK_REMOTE_API_URL || loadConfig().remoteApiUrl || undefined;
|
|
506
|
+
}
|
|
503
507
|
|
|
504
508
|
export function getRemoteApiUrl(): string {
|
|
505
|
-
|
|
509
|
+
const url = findRemoteApiUrl();
|
|
510
|
+
if (!url) {
|
|
511
|
+
throw new Error(
|
|
512
|
+
'Remote API URL is not configured. Set ZENDESK_REMOTE_API_URL or run: connect-zendesk config set-remote-url <url>',
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
return url;
|
|
506
516
|
}
|
|
507
517
|
|
|
508
518
|
export function setRemoteApiUrl(url: string): void {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
|
|
6
|
+
// The remote API URL used to fall back to a hardcoded deployment host. It no
|
|
7
|
+
// longer has a shipped default, so these lock in how the CLI behaves when it is
|
|
8
|
+
// unset: display commands must still print, and commands that genuinely need
|
|
9
|
+
// the URL must fail with actionable guidance rather than an uncaught throw.
|
|
10
|
+
//
|
|
11
|
+
// Resolution order itself is covered in config.test.ts. This file covers the
|
|
12
|
+
// CLI surface: os.homedir() does not observe runtime process.env.HOME mutation,
|
|
13
|
+
// so each case runs the real CLI in a subprocess with its own HOME.
|
|
14
|
+
|
|
15
|
+
const CLI = join(import.meta.dir, '..', 'cli', 'index.ts');
|
|
16
|
+
const ENV_KEY = 'ZENDESK_REMOTE_API_URL';
|
|
17
|
+
|
|
18
|
+
let home: string;
|
|
19
|
+
|
|
20
|
+
function runCli(args: string[], env: Record<string, string> = {}) {
|
|
21
|
+
const result = Bun.spawnSync({
|
|
22
|
+
cmd: ['bun', 'run', CLI, ...args],
|
|
23
|
+
env: { ...process.env, HOME: home, [ENV_KEY]: '', ...env },
|
|
24
|
+
stdout: 'pipe',
|
|
25
|
+
stderr: 'pipe',
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
code: result.exitCode,
|
|
29
|
+
out: new TextDecoder().decode(result.stdout) + new TextDecoder().decode(result.stderr),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('CLI behaviour when the remote API URL is unset', () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
home = mkdtempSync(join(tmpdir(), 'connect-zendesk-remote-url-'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
rmSync(home, { recursive: true, force: true });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('config show reports it as unset instead of throwing', () => {
|
|
43
|
+
const { code, out } = runCli(['config', 'show']);
|
|
44
|
+
expect(code).toBe(0);
|
|
45
|
+
expect(out).toContain('Remote API URL:');
|
|
46
|
+
expect(out).toContain('not set');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('remote url reports it as unset instead of throwing', () => {
|
|
50
|
+
const { code, out } = runCli(['remote', 'url']);
|
|
51
|
+
expect(code).toBe(0);
|
|
52
|
+
expect(out).toContain('not set');
|
|
53
|
+
expect(out).not.toContain('at getRemoteApiUrl');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test.each(['status', 'health'])(
|
|
57
|
+
'remote %s fails with actionable guidance, not a stack trace',
|
|
58
|
+
(sub) => {
|
|
59
|
+
const { code, out } = runCli(['remote', sub]);
|
|
60
|
+
expect(code).toBe(1);
|
|
61
|
+
expect(out).toContain(ENV_KEY);
|
|
62
|
+
expect(out).toContain('config set-remote-url');
|
|
63
|
+
expect(out).not.toContain('at getRemoteApiUrl');
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
test('no deployment host is baked in as a fallback', () => {
|
|
68
|
+
// A regression here means someone reintroduced a literal default.
|
|
69
|
+
const { out } = runCli(['remote', 'url']);
|
|
70
|
+
expect(out).not.toMatch(/https?:\/\/\S+/);
|
|
71
|
+
});
|
|
72
|
+
});
|