@quadrokit/client 0.2.4 β 0.2.6
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
|
-
|
|
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
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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":"
|
|
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.
|
|
3
|
+
"version": "0.2.6",
|
|
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.
|
|
32
|
+
"@quadrokit/shared": "^0.2.6",
|
|
33
|
+
"undici": "^6.21.0"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
36
|
"typescript": ">=5.4"
|