@scrapeatlas/cli 0.2.2 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrapeatlas/cli",
3
- "version": "0.2.2",
3
+ "version": "0.6.1",
4
4
  "description": "Official ScrapeAtlas CLI — discover and call social data API endpoints from your terminal.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/http.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
+ import { routeInput } from './route-path.mjs';
2
3
 
3
4
  const { version } = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
4
5
 
@@ -37,9 +38,10 @@ export function origin(value) {
37
38
  return url.origin;
38
39
  }
39
40
  export async function request(base, key, operation, input, timeout) {
40
- const url = new URL(operation.path, base);
41
+ const route = routeInput(operation.path, input);
42
+ const url = new URL(route.path, base);
41
43
  if (operation.method === 'GET')
42
- for (const [name, value] of Object.entries(input)) {
44
+ for (const [name, value] of Object.entries(route.query)) {
43
45
  url.searchParams.set(name, typeof value === 'object' ? JSON.stringify(value) : String(value));
44
46
  }
45
47
  const controller = new AbortController();
@@ -0,0 +1,5 @@
1
+ export function routeInput(
2
+ template: string,
3
+ input?: Record<string, unknown>
4
+ ): { path: string; query: Record<string, unknown> };
5
+ export function matchRoutePath(template: string, path: string): Record<string, string> | null;
@@ -0,0 +1,42 @@
1
+ // Pure route-template handling shared by CLI, MCP, gateway and request examples.
2
+ export function routeInput(template, input = {}) {
3
+ const query = { ...input };
4
+ const path = template.replace(/\{([a-z][a-zA-Z0-9]*)\}/g, (_, name) => {
5
+ const value = input[name];
6
+ if (
7
+ typeof value !== 'string' ||
8
+ !value ||
9
+ /[\/\\\u0000-\u001f\u007f]/.test(value) ||
10
+ value === '.' ||
11
+ value === '..'
12
+ )
13
+ throw new Error('Invalid route parameter');
14
+ delete query[name];
15
+ return encodeURIComponent(value);
16
+ });
17
+ return { path, query };
18
+ }
19
+ export function matchRoutePath(template, path) {
20
+ const expected = template.split('/'),
21
+ actual = path.split('/');
22
+ if (actual.length !== expected.length) return null;
23
+ const values = {};
24
+ for (let i = 0; i < expected.length; i++) {
25
+ const match = /^\{([a-z][a-zA-Z0-9]*)\}$/.exec(expected[i]);
26
+ if (!match) {
27
+ if (expected[i] !== actual[i]) return null;
28
+ continue;
29
+ }
30
+ try {
31
+ values[match[1]] = decodeURIComponent(actual[i]);
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ try {
37
+ routeInput(template, values);
38
+ } catch {
39
+ return null;
40
+ }
41
+ return values;
42
+ }