eptaadmin-sdk 0.1.4 → 0.1.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/README.md +42 -0
- package/bin/prefetch.js +104 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -83,6 +83,48 @@ const avatars = await client.getValue("acme/clients/avatar");
|
|
|
83
83
|
// ["https://your-eptaadmin-instance.example.com/api/v1/workspaces/acme/uploads/6f2dff985af5d290.png"]
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
## Build-time prefetch (for static/SPA builds)
|
|
87
|
+
|
|
88
|
+
If you're shipping a static single-page app (Vite, CRA, etc.), you usually don't want the production bundle calling out to your EptaAdmin instance at runtime — that means exposing your API key client-side, an extra network round-trip, and a hard runtime dependency on EptaAdmin staying up. The `eptaadmin-prefetch` CLI (installed alongside the SDK) fetches your data sources once, at **build time**, and writes them to plain JSON files your app imports like any other static asset.
|
|
89
|
+
|
|
90
|
+
Add a config file (`eptaadmin.config.json`, resolved from your current working directory):
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"baseUrl": "https://admin.example.com",
|
|
95
|
+
"apiKeyEnv": "EPTAADMIN_API_KEY",
|
|
96
|
+
"sources": [
|
|
97
|
+
{ "workspace": "acme", "dataSource": "home", "out": "src/data/home.json" }
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The API key itself is read from the environment variable named by `apiKeyEnv` (default `EPTAADMIN_API_KEY`) — never put the key in the config file, since that file is typically committed.
|
|
103
|
+
|
|
104
|
+
Run it before your build, e.g. as a `prebuild` script in `package.json`:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"scripts": {
|
|
109
|
+
"prebuild": "eptaadmin-prefetch",
|
|
110
|
+
"build": "vite build"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
EPTAADMIN_API_KEY=eak_your_key npm run build
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Each output file matches `getDataSource()`'s `columns` shape exactly, so your app just does:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
import homeData from "./data/home.json";
|
|
123
|
+
// homeData.hero_title[0], homeData.testimonial_author, ...
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The command exits with a non-zero status if any source fails to fetch, so a broken EptaAdmin connection fails your CI build loudly instead of silently shipping stale or missing data.
|
|
127
|
+
|
|
86
128
|
## Error handling
|
|
87
129
|
|
|
88
130
|
Failed requests reject with an `EptaAdminError` carrying the HTTP status and the server's error message:
|
package/bin/prefetch.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* eptaadmin-prefetch — fetches EptaAdmin data sources at BUILD time and
|
|
4
|
+
* writes them to static JSON files, so a production SPA build never calls
|
|
5
|
+
* out to your EptaAdmin instance at runtime. Import the generated file(s)
|
|
6
|
+
* like any other static asset instead of calling EptaAdminClient in the
|
|
7
|
+
* shipped bundle.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx eptaadmin-prefetch [--config eptaadmin.config.json]
|
|
11
|
+
*
|
|
12
|
+
* Typical setup: add a "prebuild" script to package.json —
|
|
13
|
+
* "prebuild": "eptaadmin-prefetch"
|
|
14
|
+
* — so it always runs right before "build".
|
|
15
|
+
*
|
|
16
|
+
* Config file (JSON), resolved relative to the current working directory:
|
|
17
|
+
* {
|
|
18
|
+
* "baseUrl": "https://admin.example.com",
|
|
19
|
+
* "apiKeyEnv": "EPTAADMIN_API_KEY", // env var holding the API key — never put the key itself in this file
|
|
20
|
+
* "sources": [
|
|
21
|
+
* { "workspace": "acme", "dataSource": "home", "out": "src/data/home.json" }
|
|
22
|
+
* ]
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Each output file is the same shape as EptaAdminClient#getDataSource()'s
|
|
26
|
+
* "columns" object: { "columnKey": [value, ...] }.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
30
|
+
import { dirname, resolve } from "node:path";
|
|
31
|
+
import { EptaAdminClient } from "../src/index.js";
|
|
32
|
+
|
|
33
|
+
function parseArgs(argv) {
|
|
34
|
+
const args = { config: "eptaadmin.config.json" };
|
|
35
|
+
for (let i = 0; i < argv.length; i++) {
|
|
36
|
+
if (argv[i] === "--config" && argv[i + 1]) {
|
|
37
|
+
args.config = argv[++i];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return args;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function loadConfig(path) {
|
|
44
|
+
let raw;
|
|
45
|
+
try {
|
|
46
|
+
raw = await readFile(path, "utf8");
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new Error(`could not read config file "${path}": ${err.message}`);
|
|
49
|
+
}
|
|
50
|
+
let config;
|
|
51
|
+
try {
|
|
52
|
+
config = JSON.parse(raw);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
throw new Error(`config file "${path}" is not valid JSON: ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
if (!config.baseUrl) throw new Error(`config is missing "baseUrl"`);
|
|
57
|
+
if (!Array.isArray(config.sources) || config.sources.length === 0) {
|
|
58
|
+
throw new Error(`config must list at least one entry under "sources"`);
|
|
59
|
+
}
|
|
60
|
+
return config;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
const { config: configPath } = parseArgs(process.argv.slice(2));
|
|
65
|
+
const config = await loadConfig(configPath);
|
|
66
|
+
|
|
67
|
+
const apiKeyEnv = config.apiKeyEnv || "EPTAADMIN_API_KEY";
|
|
68
|
+
const apiKey = process.env[apiKeyEnv];
|
|
69
|
+
if (!apiKey) {
|
|
70
|
+
throw new Error(`environment variable "${apiKeyEnv}" is not set (or empty) — it must hold a personal API key from your EptaAdmin profile page`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const client = new EptaAdminClient({ apiKey, baseUrl: config.baseUrl });
|
|
74
|
+
|
|
75
|
+
let failures = 0;
|
|
76
|
+
for (const source of config.sources) {
|
|
77
|
+
const { workspace, dataSource, out } = source;
|
|
78
|
+
if (!workspace || !dataSource || !out) {
|
|
79
|
+
console.error(`✗ skipping invalid source entry (needs "workspace", "dataSource" and "out"): ${JSON.stringify(source)}`);
|
|
80
|
+
failures++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const data = await client.getDataSource(workspace, dataSource);
|
|
85
|
+
const outPath = resolve(process.cwd(), out);
|
|
86
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
87
|
+
await writeFile(outPath, JSON.stringify(data.columns, null, 2) + "\n", "utf8");
|
|
88
|
+
console.log(`✓ ${workspace}/${dataSource} → ${out}`);
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error(`✗ ${workspace}/${dataSource}: ${err.message}`);
|
|
91
|
+
failures++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (failures > 0) {
|
|
96
|
+
console.error(`\neptaadmin-prefetch: ${failures} source(s) failed — failing the build rather than shipping stale or missing data.`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
main().catch((err) => {
|
|
102
|
+
console.error(`eptaadmin-prefetch: ${err.message}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eptaadmin-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Client SDK for reading your EptaAdmin workspace data from your own project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -13,9 +13,13 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"src",
|
|
16
|
+
"bin",
|
|
16
17
|
"index.d.ts",
|
|
17
18
|
"README.md"
|
|
18
19
|
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"eptaadmin-prefetch": "./bin/prefetch.js"
|
|
22
|
+
},
|
|
19
23
|
"engines": {
|
|
20
24
|
"node": ">=18"
|
|
21
25
|
},
|