@quadrokit/client 0.2.1 → 0.2.3

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/dist/cli.mjs CHANGED
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { defaultLoginUrlFromCatalogUrl, fetch4DAdminSessionCookie, } from './generate/catalog-session.mjs';
4
5
  import { writeGenerated } from './generate/codegen.mjs';
6
+ import { applyGenerateDotenv } from './generate/dotenv-cwd.mjs';
5
7
  function parseArgs(argv) {
6
8
  let command = '';
7
9
  let url;
8
10
  let token;
11
+ let accessKey;
12
+ let loginUrl;
9
13
  let out = '.quadrokit/generated';
10
14
  for (let i = 0; i < argv.length; i++) {
11
15
  const a = argv[i];
@@ -15,6 +19,12 @@ function parseArgs(argv) {
15
19
  else if (a === '--token' && argv[i + 1]) {
16
20
  token = argv[++i];
17
21
  }
22
+ else if (a === '--access-key' && argv[i + 1]) {
23
+ accessKey = argv[++i];
24
+ }
25
+ else if (a === '--login-url' && argv[i + 1]) {
26
+ loginUrl = argv[++i];
27
+ }
18
28
  else if (a === '--out' && argv[i + 1]) {
19
29
  out = argv[++i];
20
30
  }
@@ -22,9 +32,9 @@ function parseArgs(argv) {
22
32
  command = a;
23
33
  }
24
34
  }
25
- return { command, url, token, out };
35
+ return { command, url, token, accessKey, loginUrl, out };
26
36
  }
27
- async function loadCatalog(url, token) {
37
+ async function loadCatalog(url, auth) {
28
38
  if (url.startsWith('file:')) {
29
39
  const path = fileURLToPath(url);
30
40
  const text = await readFile(path, 'utf8');
@@ -33,8 +43,21 @@ async function loadCatalog(url, token) {
33
43
  const headers = {
34
44
  Accept: 'application/json',
35
45
  };
36
- if (token) {
37
- headers.Authorization = `Bearer ${token}`;
46
+ const key = auth?.accessKey?.trim();
47
+ if (key) {
48
+ let login = auth?.loginUrl?.trim();
49
+ if (!login) {
50
+ try {
51
+ login = defaultLoginUrlFromCatalogUrl(url);
52
+ }
53
+ catch {
54
+ throw new Error('quadrokit: --access-key requires an http(s) catalog --url, or pass --login-url');
55
+ }
56
+ }
57
+ headers.Cookie = await fetch4DAdminSessionCookie(login, key);
58
+ }
59
+ else if (auth?.token) {
60
+ headers.Authorization = `Bearer ${auth.token}`;
38
61
  }
39
62
  const res = await fetch(url, { headers });
40
63
  if (!res.ok) {
@@ -45,21 +68,44 @@ async function loadCatalog(url, token) {
45
68
  }
46
69
  async function main() {
47
70
  const argv = process.argv.slice(2);
48
- const { command, url, token, out } = parseArgs(argv);
71
+ const parsed = parseArgs(argv);
72
+ const { command, token, accessKey: accessKeyArg, loginUrl: loginUrlArg, out } = parsed;
49
73
  if (command !== 'generate') {
50
- console.error(`Usage: quadrokit-client generate --url <catalog_url> [--token <token>] [--out <dir>]
74
+ console.error(`Usage: quadrokit-client generate [--url <catalog_url>] [auth] [--out <dir>]
75
+
76
+ --url defaults to \${VITE_4D_ORIGIN}/rest/\\$catalog (from env or .env in cwd), else http://127.0.0.1:7080/rest/\\$catalog
77
+
78
+ Auth (generator only; not used by the app runtime):
79
+ --access-key <key> POST multipart accessKey to /api/login, then use 4DAdminSID for the catalog
80
+ --login-url <url> Full login URL (default: {catalog origin}/api/login)
81
+ --token <secret> Authorization: Bearer (when you do not use --access-key)
82
+
83
+ Env / .env: VITE_4D_ORIGIN, QUADROKIT_ACCESS_KEY, QUADROKIT_LOGIN_URL, QUADROKIT_CATALOG_TOKEN
51
84
 
52
85
  Examples:
86
+ quadrokit-client generate
87
+ quadrokit-client generate --url https://localhost:7443/rest/\\$catalog --access-key MY_TOKEN
53
88
  quadrokit-client generate --url http://localhost:7080/rest/\\$catalog --token secret
54
89
  quadrokit-client generate --url file://./assets/catalog.json --out .quadrokit/generated
90
+
91
+ bunx -p @quadrokit/client quadrokit-client generate
55
92
  `);
56
93
  process.exit(1);
57
94
  }
95
+ applyGenerateDotenv();
96
+ let url = parsed.url;
58
97
  if (!url) {
59
- console.error('Error: --url is required for generate.');
60
- process.exit(1);
98
+ const origin = (process.env.VITE_4D_ORIGIN || 'http://127.0.0.1:7080').replace(/\/$/, '');
99
+ url = `${origin}/rest/$catalog`;
61
100
  }
62
- const catalog = await loadCatalog(url, token);
101
+ const accessKey = accessKeyArg?.trim() || process.env.QUADROKIT_ACCESS_KEY?.trim() || undefined;
102
+ const loginUrl = loginUrlArg?.trim() || process.env.QUADROKIT_LOGIN_URL?.trim() || undefined;
103
+ const bearer = token?.trim() || process.env.QUADROKIT_CATALOG_TOKEN?.trim() || undefined;
104
+ const catalog = await loadCatalog(url, {
105
+ accessKey,
106
+ loginUrl,
107
+ token: accessKey ? undefined : bearer,
108
+ });
63
109
  await writeGenerated(out, catalog);
64
110
  console.error(`Wrote generated client to ${out}`);
65
111
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Generator-only: obtain `4DAdminSID` via POST `/api/login` (multipart `accessKey`).
3
+ * Not used by the runtime REST client.
4
+ */
5
+ export declare function defaultLoginUrlFromCatalogUrl(catalogUrl: string): string;
6
+ export declare function fetch4DAdminSessionCookie(loginUrl: string, accessKey: string): Promise<string>;
7
+ //# sourceMappingURL=catalog-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-session.d.ts","sourceRoot":"","sources":["../../src/generate/catalog-session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGxE;AAED,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAcjB"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Generator-only: obtain `4DAdminSID` via POST `/api/login` (multipart `accessKey`).
3
+ * Not used by the runtime REST client.
4
+ */
5
+ export function defaultLoginUrlFromCatalogUrl(catalogUrl) {
6
+ const u = new URL(catalogUrl);
7
+ return `${u.origin}/api/login`;
8
+ }
9
+ export async function fetch4DAdminSessionCookie(loginUrl, accessKey) {
10
+ const form = new FormData();
11
+ form.append('accessKey', accessKey);
12
+ const res = await fetch(loginUrl, { method: 'POST', body: form });
13
+ if (!res.ok) {
14
+ const t = await res.text();
15
+ throw new Error(`quadrokit login failed (${res.status}): ${t.slice(0, 500)}`);
16
+ }
17
+ const lines = readSetCookieLines(res.headers);
18
+ const value = parse4DAdminSidValue(lines);
19
+ if (!value) {
20
+ throw new Error('quadrokit login: response had no 4DAdminSID Set-Cookie');
21
+ }
22
+ return `4DAdminSID=${value}`;
23
+ }
24
+ function readSetCookieLines(headers) {
25
+ const ext = headers;
26
+ if (typeof ext.getSetCookie === 'function') {
27
+ return ext.getSetCookie();
28
+ }
29
+ const c = headers.get('set-cookie');
30
+ return c ? [c] : [];
31
+ }
32
+ function parse4DAdminSidValue(setCookieLines) {
33
+ for (const line of setCookieLines) {
34
+ const firstPart = line.split(';')[0]?.trim();
35
+ if (!firstPart)
36
+ continue;
37
+ const eq = firstPart.indexOf('=');
38
+ if (eq <= 0)
39
+ continue;
40
+ const name = firstPart.slice(0, eq);
41
+ const val = firstPart.slice(eq + 1);
42
+ if (name === '4DAdminSID' && val)
43
+ return val;
44
+ }
45
+ return null;
46
+ }
@@ -0,0 +1,3 @@
1
+ /** Merge project `.env` into `process.env` for keys that are not already set (generator CLI only). */
2
+ export declare function applyGenerateDotenv(): void;
3
+ //# sourceMappingURL=dotenv-cwd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dotenv-cwd.d.ts","sourceRoot":"","sources":["../../src/generate/dotenv-cwd.ts"],"names":[],"mappings":"AAUA,sGAAsG;AACtG,wBAAgB,mBAAmB,IAAI,IAAI,CAuB1C"}
@@ -0,0 +1,37 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ const KEYS = [
4
+ 'VITE_4D_ORIGIN',
5
+ 'QUADROKIT_ACCESS_KEY',
6
+ 'QUADROKIT_LOGIN_URL',
7
+ 'QUADROKIT_CATALOG_TOKEN',
8
+ ];
9
+ /** Merge project `.env` into `process.env` for keys that are not already set (generator CLI only). */
10
+ export function applyGenerateDotenv() {
11
+ const envPath = path.join(process.cwd(), '.env');
12
+ if (!existsSync(envPath))
13
+ return;
14
+ const wanted = new Set(KEYS.filter((k) => !process.env[k]));
15
+ if (wanted.size === 0)
16
+ return;
17
+ const raw = readFileSync(envPath, 'utf8');
18
+ for (const line of raw.split('\n')) {
19
+ const t = line.trim();
20
+ if (!t || t.startsWith('#'))
21
+ continue;
22
+ const eq = t.indexOf('=');
23
+ if (eq <= 0)
24
+ continue;
25
+ const key = t.slice(0, eq).trim();
26
+ if (!wanted.has(key))
27
+ continue;
28
+ let v = t.slice(eq + 1).trim();
29
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
30
+ v = v.slice(1, -1);
31
+ }
32
+ process.env[key] = v;
33
+ wanted.delete(key);
34
+ if (wanted.size === 0)
35
+ return;
36
+ }
37
+ }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@quadrokit/client",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Typed 4D REST client and catalog code generator for QuadroKit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
8
  "bin": {
9
- "quadrokit-client": "./dist/cli.mjs"
9
+ "quadrokit-client": "./dist/cli.mjs",
10
+ "quadrokit": "./dist/cli.mjs"
10
11
  },
11
12
  "exports": {
12
13
  ".": {
@@ -28,7 +29,7 @@
28
29
  "generate:fixture": "bun run src/cli.ts generate --url file://../../assets/catalog.json --out ../../.quadrokit/generated-demo"
29
30
  },
30
31
  "dependencies": {
31
- "@quadrokit/shared": "^0.2.0"
32
+ "@quadrokit/shared": "^0.2.3"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "typescript": ">=5.4"