create-hedgeboard 1.3.0 → 1.4.0

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.
Files changed (2) hide show
  1. package/index.js +82 -12
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -93,18 +93,85 @@ function fetchBuffer(url, headers = {}) {
93
93
  }
94
94
 
95
95
  // ---------------------------------------------------------------------------
96
- // ZIP extraction (pure JS, no dependencies)
96
+ // Recursive directory copy (pure JS, no shell — replaces cp -r)
97
+ // ---------------------------------------------------------------------------
98
+
99
+ function copyDirRecursive(src, dest) {
100
+ fs.mkdirSync(dest, { recursive: true });
101
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
102
+ const srcPath = path.join(src, entry.name);
103
+ const destPath = path.join(dest, entry.name);
104
+ if (entry.isDirectory()) {
105
+ copyDirRecursive(srcPath, destPath);
106
+ } else {
107
+ fs.copyFileSync(srcPath, destPath);
108
+ }
109
+ }
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // ZIP extraction (pure JS — uses only Node built-in zlib, no dependencies)
97
114
  // ---------------------------------------------------------------------------
98
115
 
99
116
  function extractZip(zipBuffer, destDir) {
100
- // Simple ZIP parser — handles DEFLATE and STORE methods
101
- // For robustness in production, consider using a proper zip library
102
- const { execSync } = require("child_process");
103
- const tmpZip = path.join(require("os").tmpdir(), `hedgeboard-${Date.now()}.zip`);
104
- fs.writeFileSync(tmpZip, zipBuffer);
117
+ const zlib = require("zlib");
105
118
  fs.mkdirSync(destDir, { recursive: true });
106
- execSync(`unzip -o -q "${tmpZip}" -d "${destDir}"`);
107
- fs.unlinkSync(tmpZip);
119
+
120
+ // Parse ZIP central directory (from end-of-central-directory record)
121
+ const buf = Buffer.isBuffer(zipBuffer) ? zipBuffer : Buffer.from(zipBuffer);
122
+ let offset = 0;
123
+
124
+ while (offset < buf.length - 4) {
125
+ // Local file header signature = 0x04034b50
126
+ const sig = buf.readUInt32LE(offset);
127
+ if (sig !== 0x04034b50) break;
128
+
129
+ const method = buf.readUInt16LE(offset + 8);
130
+ const compSize = buf.readUInt32LE(offset + 18);
131
+ const uncompSize = buf.readUInt32LE(offset + 22);
132
+ const nameLen = buf.readUInt16LE(offset + 26);
133
+ const extraLen = buf.readUInt16LE(offset + 28);
134
+ const nameStart = offset + 30;
135
+ const name = buf.toString("utf8", nameStart, nameStart + nameLen);
136
+ const dataStart = nameStart + nameLen + extraLen;
137
+
138
+ offset = dataStart + compSize;
139
+
140
+ // Skip directories (with path traversal guard — P3-9)
141
+ if (name.endsWith("/")) {
142
+ const dirPath = path.join(destDir, name);
143
+ if (!dirPath.startsWith(path.resolve(destDir) + path.sep) &&
144
+ dirPath !== path.resolve(destDir)) {
145
+ console.warn(` Skipping ${name}: path traversal detected`);
146
+ continue;
147
+ }
148
+ fs.mkdirSync(dirPath, { recursive: true });
149
+ continue;
150
+ }
151
+
152
+ // Path traversal guard — prevent ../../.bashrc attacks (P0-8)
153
+ const filePath = path.join(destDir, name);
154
+ if (!filePath.startsWith(path.resolve(destDir) + path.sep) &&
155
+ filePath !== path.resolve(destDir)) {
156
+ console.warn(` Skipping ${name}: path traversal detected`);
157
+ continue;
158
+ }
159
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
160
+
161
+ const rawData = buf.subarray(dataStart, dataStart + compSize);
162
+
163
+ if (method === 0) {
164
+ // STORE — no compression
165
+ fs.writeFileSync(filePath, rawData);
166
+ } else if (method === 8) {
167
+ // DEFLATE — use raw inflate (no zlib header)
168
+ const inflated = zlib.inflateRawSync(rawData);
169
+ fs.writeFileSync(filePath, inflated);
170
+ } else {
171
+ // Unsupported method — skip
172
+ console.warn(` Skipping ${name}: unsupported compression method ${method}`);
173
+ }
174
+ }
108
175
  }
109
176
 
110
177
  // ---------------------------------------------------------------------------
@@ -139,7 +206,7 @@ ${c.dim} ██████╔╝██║ ██║███████
139
206
  ${c.dim} ██╔══██╗██║ ██║██╔══██║██╔══██╗██║ ██║${c.reset}
140
207
  ${c.dim} ██████╔╝╚██████╔╝██║ ██║██║ ██║██████╔╝${c.reset}
141
208
  ${c.dim} ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ${c.reset}
142
- ${c.dim} AI-Native Financial Intelligence — v1.0.8${c.reset}
209
+ ${c.dim} AI-Native Financial Intelligence — v1.3.0${c.reset}
143
210
  `);
144
211
  }
145
212
 
@@ -156,7 +223,9 @@ async function main() {
156
223
  if (key) {
157
224
  process.stdout.write(` ${c.gray}◆${c.reset} Validating API key ${c.dim}...${c.reset} `);
158
225
  try {
159
- const resp = await fetchBuffer(`${APP_URL}/api/data-sources?key=${key}`);
226
+ const resp = await fetchBuffer(`${APP_URL}/api/data-sources`, {
227
+ "x-api-key": key,
228
+ });
160
229
  const data = JSON.parse(resp.toString());
161
230
  if (data.error) throw new Error(data.error);
162
231
  console.log(`${c.green}✓${c.reset}`);
@@ -189,8 +258,8 @@ async function main() {
189
258
  if (fs.existsSync(extractedDir)) {
190
259
  fs.mkdirSync(path.dirname(absDir), { recursive: true });
191
260
  if (fs.existsSync(absDir)) {
192
- const { execSync } = require("child_process");
193
- execSync(`cp -r "${extractedDir}/"* "${absDir}/"`);
261
+ // Merge into existing dir — pure JS, no shell (P3-8)
262
+ copyDirRecursive(extractedDir, absDir);
194
263
  } else {
195
264
  fs.renameSync(extractedDir, absDir);
196
265
  }
@@ -239,6 +308,7 @@ ${c.dim} ───────────────────────
239
308
  ${c.gray}├──${c.reset} ${c.white}viz/${c.reset} ${c.dim}Dashboard templates & charts${c.reset}
240
309
  ${c.gray}├──${c.reset} ${c.white}brand/${c.reset} ${c.dim}Your brand settings${c.reset}
241
310
  ${c.gray}├──${c.reset} ${c.white}modules/${c.reset} ${c.dim}356 analysis recipes + indexes${c.reset}
311
+ ${c.gray}├──${c.reset} ${c.white}docs/${c.reset} ${c.dim}Data catalog — 35 providers, 15 categories${c.reset}
242
312
  ${c.gray}└──${c.reset} ${c.white}output/${c.reset} ${c.dim}Generated dashboards land here${c.reset}
243
313
 
244
314
  ${c.dim} ─────────────────────────────────────${c.reset}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-hedgeboard",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Set up a HedgeBoard financial intelligence workspace",
5
5
  "bin": {
6
6
  "create-hedgeboard": "index.js"