@quadrokit/client 0.2.4 β†’ 0.2.5

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,7 +1,7 @@
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
+ import { catalogFetchInit, defaultLoginUrlFromCatalogUrl, fetch4DAdminSessionCookie, vLog, wrapFetchError, } from './generate/catalog-session.mjs';
5
5
  import { writeGenerated } from './generate/codegen.mjs';
6
6
  import { applyGenerateDotenv } from './generate/dotenv-cwd.mjs';
7
7
  function parseArgs(argv) {
@@ -11,6 +11,8 @@ function parseArgs(argv) {
11
11
  let accessKey;
12
12
  let loginUrl;
13
13
  let out = '.quadrokit/generated';
14
+ let verbose = false;
15
+ let insecureTls = false;
14
16
  for (let i = 0; i < argv.length; i++) {
15
17
  const a = argv[i];
16
18
  if (a === '--url' && argv[i + 1]) {
@@ -28,16 +30,36 @@ function parseArgs(argv) {
28
30
  else if (a === '--out' && argv[i + 1]) {
29
31
  out = argv[++i];
30
32
  }
33
+ else if (a === '--verbose' || a === '-v') {
34
+ verbose = true;
35
+ }
36
+ else if (a === '--insecure-tls') {
37
+ insecureTls = true;
38
+ }
31
39
  else if (!a.startsWith('-') && !command) {
32
40
  command = a;
33
41
  }
34
42
  }
35
- return { command, url, token, accessKey, loginUrl, out };
43
+ return { command, url, token, accessKey, loginUrl, out, verbose, insecureTls };
44
+ }
45
+ function envFlag(name) {
46
+ const v = process.env[name]?.trim().toLowerCase();
47
+ return v === '1' || v === 'true' || v === 'yes';
36
48
  }
37
- async function loadCatalog(url, auth) {
49
+ function buildDebug(parsed) {
50
+ return {
51
+ verbose: parsed.verbose || envFlag('QUADROKIT_GENERATE_VERBOSE'),
52
+ insecureTls: parsed.insecureTls || envFlag('QUADROKIT_INSECURE_TLS'),
53
+ };
54
+ }
55
+ async function loadCatalog(url, auth, debug) {
56
+ vLog(debug, 'πŸ¦™', 'QuadroKit catalog generate', 'Loading catalog…');
57
+ vLog(debug, 'πŸ“', 'Catalog URL', url);
38
58
  if (url.startsWith('file:')) {
39
- const path = fileURLToPath(url);
40
- const text = await readFile(path, 'utf8');
59
+ const p = fileURLToPath(url);
60
+ vLog(debug, 'πŸ“‚', 'Local file', p);
61
+ const text = await readFile(p, 'utf8');
62
+ vLog(debug, 'βœ…', 'Catalog JSON', `Read ${text.length} bytes from disk`);
41
63
  return JSON.parse(text);
42
64
  }
43
65
  const headers = {
@@ -49,29 +71,43 @@ async function loadCatalog(url, auth) {
49
71
  if (!login) {
50
72
  try {
51
73
  login = defaultLoginUrlFromCatalogUrl(url);
74
+ vLog(debug, 'πŸ”—', 'Derived login URL', login);
52
75
  }
53
76
  catch {
54
77
  throw new Error('quadrokit: --access-key requires an http(s) catalog --url, or pass --login-url');
55
78
  }
56
79
  }
57
- headers.Cookie = await fetch4DAdminSessionCookie(login, key);
80
+ headers.Cookie = await fetch4DAdminSessionCookie(login, key, debug);
58
81
  }
59
82
  else if (auth?.token) {
60
- headers.Authorization = `Bearer ${auth.token}`;
83
+ vLog(debug, 'πŸ”’', 'Catalog auth', 'Authorization: Bearer (token set)');
84
+ }
85
+ else {
86
+ vLog(debug, 'πŸ”“', 'Catalog auth', 'No access key or bearer token');
61
87
  }
62
- const res = await fetch(url, { headers });
88
+ const extra = catalogFetchInit(debug);
89
+ let res;
90
+ try {
91
+ res = await fetch(url, { headers, ...extra });
92
+ }
93
+ catch (e) {
94
+ throw wrapFetchError('Catalog fetch', e, debug);
95
+ }
96
+ vLog(debug, 'πŸ“₯', 'Catalog HTTP', `${res.status} ${res.statusText}`);
63
97
  if (!res.ok) {
64
98
  const t = await res.text();
65
99
  throw new Error(`Failed to fetch catalog (${res.status}): ${t.slice(0, 500)}`);
66
100
  }
67
- return res.json();
101
+ const catalog = (await res.json());
102
+ vLog(debug, 'βœ…', 'Catalog JSON', 'Parsed β€” writing generated files…');
103
+ return catalog;
68
104
  }
69
105
  async function main() {
70
106
  const argv = process.argv.slice(2);
71
107
  const parsed = parseArgs(argv);
72
108
  const { command, token, accessKey: accessKeyArg, loginUrl: loginUrlArg, out } = parsed;
73
109
  if (command !== 'generate') {
74
- console.error(`Usage: quadrokit-client generate [--url <catalog_url>] [auth] [--out <dir>]
110
+ console.error(`Usage: quadrokit-client generate [--url <catalog_url>] [auth] [options] [--out <dir>]
75
111
 
76
112
  --url defaults to \${VITE_4D_ORIGIN}/rest/\\$catalog (from env or .env in cwd), else http://127.0.0.1:7080/rest/\\$catalog
77
113
 
@@ -80,11 +116,16 @@ Auth (generator only; not used by the app runtime):
80
116
  --login-url <url> Full login URL (default: {catalog origin}/api/login)
81
117
  --token <secret> Authorization: Bearer (when you do not use --access-key)
82
118
 
83
- Env / .env: VITE_4D_ORIGIN, QUADROKIT_ACCESS_KEY, QUADROKIT_LOGIN_URL, QUADROKIT_CATALOG_TOKEN
119
+ Debug / TLS:
120
+ -v, --verbose Friendly step-by-step logs (emojis) on stderr
121
+ --insecure-tls Skip TLS certificate verification (dev/self-signed HTTPS only)
122
+
123
+ Env / .env: VITE_4D_ORIGIN, QUADROKIT_ACCESS_KEY, QUADROKIT_LOGIN_URL, QUADROKIT_CATALOG_TOKEN,
124
+ QUADROKIT_GENERATE_VERBOSE, QUADROKIT_INSECURE_TLS (set to 1 / true / yes)
84
125
 
85
126
  Examples:
86
- quadrokit-client generate
87
- quadrokit-client generate --url https://localhost:7443/rest/\\$catalog --access-key MY_TOKEN
127
+ quadrokit-client generate -v
128
+ quadrokit-client generate --url https://localhost:7443/rest/\\$catalog --access-key MY_TOKEN --insecure-tls -v
88
129
  quadrokit-client generate --url http://localhost:7080/rest/\\$catalog --token secret
89
130
  quadrokit-client generate --url file://./assets/catalog.json --out .quadrokit/generated
90
131
 
@@ -93,6 +134,7 @@ Examples:
93
134
  process.exit(1);
94
135
  }
95
136
  applyGenerateDotenv();
137
+ const debug = buildDebug(parsed);
96
138
  let url = parsed.url;
97
139
  if (!url) {
98
140
  const origin = (process.env.VITE_4D_ORIGIN || 'http://127.0.0.1:7080').replace(/\/$/, '');
@@ -105,9 +147,14 @@ Examples:
105
147
  accessKey,
106
148
  loginUrl,
107
149
  token: accessKey ? undefined : bearer,
108
- });
150
+ }, debug);
109
151
  await writeGenerated(out, catalog);
110
- console.error(`Wrote generated client to ${out}`);
152
+ if (debug.verbose) {
153
+ vLog(debug, 'πŸŽ‰', 'All set', `Generated client β†’ ${out}`);
154
+ }
155
+ else {
156
+ console.error(`Wrote generated client to ${out}`);
157
+ }
111
158
  }
112
159
  main().catch((e) => {
113
160
  console.error(e);
@@ -2,6 +2,17 @@
2
2
  * Generator-only: obtain `4DAdminSID` via POST `/api/login` (multipart `accessKey`).
3
3
  * Not used by the runtime REST client.
4
4
  */
5
+ import { Agent } from 'undici';
6
+ export type CatalogHttpDebug = {
7
+ verbose?: boolean;
8
+ /** Accept self-signed / invalid TLS (generator CLI only β€” never for production apps). */
9
+ insecureTls?: boolean;
10
+ };
11
+ export declare function catalogFetchInit(debug: CatalogHttpDebug | undefined): {
12
+ dispatcher: Agent;
13
+ } | Record<string, never>;
14
+ export declare function vLog(debug: CatalogHttpDebug | undefined, emoji: string, title: string, detail?: string): void;
5
15
  export declare function defaultLoginUrlFromCatalogUrl(catalogUrl: string): string;
6
- export declare function fetch4DAdminSessionCookie(loginUrl: string, accessKey: string): Promise<string>;
16
+ export declare function fetch4DAdminSessionCookie(loginUrl: string, accessKey: string, debug?: CatalogHttpDebug): Promise<string>;
17
+ export declare function wrapFetchError(label: string, e: unknown, debug?: CatalogHttpDebug): Error;
7
18
  //# sourceMappingURL=catalog-session.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"catalog-session.d.ts","sourceRoot":"","sources":["../../src/generate/catalog-session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAE9B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yFAAyF;IACzF,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AAeD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,gBAAgB,GAAG,SAAS,GAClC;IAAE,UAAU,EAAE,KAAK,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAK/C;AAED,wBAAgB,IAAI,CAClB,KAAK,EAAE,gBAAgB,GAAG,SAAS,EACnC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAON;AAED,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGxE;AAED,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,gBAAgB,GACvB,OAAO,CAAC,MAAM,CAAC,CAoCjB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAgBzF"}
@@ -2,25 +2,84 @@
2
2
  * Generator-only: obtain `4DAdminSID` via POST `/api/login` (multipart `accessKey`).
3
3
  * Not used by the runtime REST client.
4
4
  */
5
+ import { Agent } from 'undici';
6
+ let insecureAgent;
7
+ function getInsecureDispatcher() {
8
+ if (!insecureAgent) {
9
+ insecureAgent = new Agent({
10
+ connect: {
11
+ rejectUnauthorized: false,
12
+ },
13
+ });
14
+ }
15
+ return insecureAgent;
16
+ }
17
+ export function catalogFetchInit(debug) {
18
+ if (!debug?.insecureTls) {
19
+ return {};
20
+ }
21
+ return { dispatcher: getInsecureDispatcher() };
22
+ }
23
+ export function vLog(debug, emoji, title, detail) {
24
+ if (!debug?.verbose)
25
+ return;
26
+ if (detail !== undefined && detail !== '') {
27
+ console.error(`${emoji} ${title}\n ${detail}`);
28
+ }
29
+ else {
30
+ console.error(`${emoji} ${title}`);
31
+ }
32
+ }
5
33
  export function defaultLoginUrlFromCatalogUrl(catalogUrl) {
6
34
  const u = new URL(catalogUrl);
7
35
  return `${u.origin}/api/login`;
8
36
  }
9
- export async function fetch4DAdminSessionCookie(loginUrl, accessKey) {
37
+ export async function fetch4DAdminSessionCookie(loginUrl, accessKey, debug) {
38
+ vLog(debug, 'πŸ”', 'Generator login', `POST ${loginUrl} (multipart field accessKey)`);
39
+ if (debug?.insecureTls) {
40
+ vLog(debug, '⚠️', 'TLS certificate verification is off', 'Only for local/dev. Remove --insecure-tls for real servers.');
41
+ }
42
+ vLog(debug, 'πŸ”‘', 'Access key', accessKey ? `provided (${accessKey.length} chars)` : '(empty)');
10
43
  const form = new FormData();
11
44
  form.append('accessKey', accessKey);
12
- const res = await fetch(loginUrl, { method: 'POST', body: form });
45
+ const extra = catalogFetchInit(debug);
46
+ let res;
47
+ try {
48
+ res = await fetch(loginUrl, { method: 'POST', body: form, ...extra });
49
+ }
50
+ catch (e) {
51
+ throw wrapFetchError('Login request', e, debug);
52
+ }
53
+ vLog(debug, 'πŸ“₯', 'Login response', `${res.status} ${res.statusText}`);
13
54
  if (!res.ok) {
14
55
  const t = await res.text();
15
56
  throw new Error(`quadrokit login failed (${res.status}): ${t.slice(0, 500)}`);
16
57
  }
17
58
  const lines = readSetCookieLines(res.headers);
59
+ vLog(debug, 'πŸͺ', 'Set-Cookie headers', `${lines.length} cookie line(s)`);
18
60
  const value = parse4DAdminSidValue(lines);
19
61
  if (!value) {
20
62
  throw new Error('quadrokit login: response had no 4DAdminSID Set-Cookie');
21
63
  }
64
+ vLog(debug, 'βœ…', 'Session cookie', '4DAdminSID received β€” using it for the catalog request');
22
65
  return `4DAdminSID=${value}`;
23
66
  }
67
+ export function wrapFetchError(label, e, debug) {
68
+ const err = e instanceof Error ? e : new Error(String(e));
69
+ if (debug?.verbose) {
70
+ vLog(debug, 'πŸ’₯', `${label} failed`, err.message);
71
+ if (err.cause instanceof Error) {
72
+ vLog(debug, 'πŸ”—', 'Underlying cause', err.cause.message);
73
+ }
74
+ }
75
+ const hint = err.message.includes('certificate') ||
76
+ err.message.includes('CERT') ||
77
+ (err.cause instanceof Error &&
78
+ (err.cause.message.includes('certificate') || err.cause.message.includes('CERT')))
79
+ ? ' Tip: try --insecure-tls for self-signed HTTPS in development.'
80
+ : '';
81
+ return new Error(`${label}: ${err.message}${hint}`, { cause: err });
82
+ }
24
83
  function readSetCookieLines(headers) {
25
84
  const ext = headers;
26
85
  if (typeof ext.getSetCookie === 'function') {
@@ -1 +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"}
1
+ {"version":3,"file":"dotenv-cwd.d.ts","sourceRoot":"","sources":["../../src/generate/dotenv-cwd.ts"],"names":[],"mappings":"AAYA,sGAAsG;AACtG,wBAAgB,mBAAmB,IAAI,IAAI,CAuB1C"}
@@ -5,6 +5,8 @@ const KEYS = [
5
5
  'QUADROKIT_ACCESS_KEY',
6
6
  'QUADROKIT_LOGIN_URL',
7
7
  'QUADROKIT_CATALOG_TOKEN',
8
+ 'QUADROKIT_GENERATE_VERBOSE',
9
+ 'QUADROKIT_INSECURE_TLS',
8
10
  ];
9
11
  /** Merge project `.env` into `process.env` for keys that are not already set (generator CLI only). */
10
12
  export function applyGenerateDotenv() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quadrokit/client",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Typed 4D REST client and catalog code generator for QuadroKit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -29,7 +29,8 @@
29
29
  "generate:fixture": "bun run src/cli.ts generate --url file://../../assets/catalog.json --out ../../.quadrokit/generated-demo"
30
30
  },
31
31
  "dependencies": {
32
- "@quadrokit/shared": "^0.2.4"
32
+ "@quadrokit/shared": "^0.2.5",
33
+ "undici": "^6.21.0"
33
34
  },
34
35
  "peerDependencies": {
35
36
  "typescript": ">=5.4"