create-tradejs 1.0.10
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 +19 -0
- package/dist/index.js +378 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# create-tradejs
|
|
2
|
+
|
|
3
|
+
Create a local TradeJS project, start Redis and Timescale, and open the Web UI.
|
|
4
|
+
On the first launch, choose the local `root` password on the install page. The
|
|
5
|
+
app then opens the dashboard, where **Create backtest** starts the first
|
|
6
|
+
backtest flow.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx create-tradejs
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The default project directory is `tradejs-project`. Pass a name to choose a
|
|
13
|
+
different directory:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx create-tradejs my-trading-project
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Docker with the Compose plugin and Node.js 20.19 or newer are required.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
buildProjectFiles: () => buildProjectFiles,
|
|
35
|
+
main: () => main,
|
|
36
|
+
parseArgs: () => parseArgs,
|
|
37
|
+
scaffoldProject: () => scaffoldProject
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
var import_node_child_process = require("child_process");
|
|
41
|
+
var import_node_crypto = require("crypto");
|
|
42
|
+
var import_node_fs = require("fs");
|
|
43
|
+
var import_node_net = __toESM(require("net"));
|
|
44
|
+
var import_node_path = __toESM(require("path"));
|
|
45
|
+
var DEFAULT_PROJECT_NAME = "tradejs-project";
|
|
46
|
+
var DEFAULT_PORT = 3e3;
|
|
47
|
+
var DEFAULT_INFRASTRUCTURE_PORTS = {
|
|
48
|
+
postgres: 5432,
|
|
49
|
+
redis: 6379,
|
|
50
|
+
redisInsight: 5540
|
|
51
|
+
};
|
|
52
|
+
var printUsage = () => {
|
|
53
|
+
console.log(`Create a ready-to-run TradeJS project.
|
|
54
|
+
|
|
55
|
+
Usage:
|
|
56
|
+
npx create-tradejs [project-directory] [options]
|
|
57
|
+
|
|
58
|
+
Options:
|
|
59
|
+
--port <number> Preferred Web UI port (default: 3000)
|
|
60
|
+
--no-install Only generate project files
|
|
61
|
+
--no-infra Skip Docker infrastructure and onboarding seed
|
|
62
|
+
--no-start Do not start the Web UI
|
|
63
|
+
--no-open Do not open a browser
|
|
64
|
+
-h, --help Show this help`);
|
|
65
|
+
};
|
|
66
|
+
var readOptionValue = (argv, index) => {
|
|
67
|
+
const value = argv[index + 1];
|
|
68
|
+
if (!value || value.startsWith("-")) {
|
|
69
|
+
throw new Error(`${argv[index]} requires a value`);
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
};
|
|
73
|
+
var parseArgs = (argv) => {
|
|
74
|
+
let targetDir = "";
|
|
75
|
+
let install = true;
|
|
76
|
+
let infra = true;
|
|
77
|
+
let start = true;
|
|
78
|
+
let open = true;
|
|
79
|
+
let port = DEFAULT_PORT;
|
|
80
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
81
|
+
const arg = argv[index];
|
|
82
|
+
if (arg === "-h" || arg === "--help") {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (arg === "--no-install") {
|
|
86
|
+
install = false;
|
|
87
|
+
infra = false;
|
|
88
|
+
start = false;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (arg === "--no-infra") {
|
|
92
|
+
infra = false;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (arg === "--no-start") {
|
|
96
|
+
start = false;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (arg === "--no-open") {
|
|
100
|
+
open = false;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (arg === "--port") {
|
|
104
|
+
port = Number(readOptionValue(argv, index));
|
|
105
|
+
index += 1;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (arg.startsWith("--port=")) {
|
|
109
|
+
port = Number(arg.slice("--port=".length));
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (arg.startsWith("-")) {
|
|
113
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
114
|
+
}
|
|
115
|
+
if (targetDir) {
|
|
116
|
+
throw new Error(`Unexpected extra project directory: ${arg}`);
|
|
117
|
+
}
|
|
118
|
+
targetDir = arg;
|
|
119
|
+
}
|
|
120
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
121
|
+
throw new Error(`Invalid port: ${port}`);
|
|
122
|
+
}
|
|
123
|
+
if (!install && (infra || start)) {
|
|
124
|
+
throw new Error("--no-install cannot start infrastructure or the Web UI");
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
targetDir: targetDir || DEFAULT_PROJECT_NAME,
|
|
128
|
+
install,
|
|
129
|
+
infra,
|
|
130
|
+
start,
|
|
131
|
+
open,
|
|
132
|
+
port
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
var packageNameFromDir = (targetDir) => {
|
|
136
|
+
const normalized = import_node_path.default.basename(targetDir).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
137
|
+
return normalized || DEFAULT_PROJECT_NAME;
|
|
138
|
+
};
|
|
139
|
+
var createAuthSecret = () => (0, import_node_crypto.randomBytes)(32).toString("hex");
|
|
140
|
+
var buildProjectFiles = (targetDir, port, infrastructurePorts = DEFAULT_INFRASTRUCTURE_PORTS) => {
|
|
141
|
+
const packageName = packageNameFromDir(targetDir);
|
|
142
|
+
const authSecret = createAuthSecret();
|
|
143
|
+
const packageVersion = String(process.env.CREATE_TRADEJS_PACKAGE_VERSION || "").trim() || "latest";
|
|
144
|
+
const packageSpec = (name) => String(process.env[`CREATE_TRADEJS_${name}_PACKAGE`] || "").trim() || packageVersion;
|
|
145
|
+
const infraPackage = String(
|
|
146
|
+
process.env.CREATE_TRADEJS_INFRA_PACKAGE || ""
|
|
147
|
+
).trim();
|
|
148
|
+
const dependencies = {
|
|
149
|
+
"@tradejs/app": packageSpec("APP"),
|
|
150
|
+
"@tradejs/base": packageSpec("BASE"),
|
|
151
|
+
"@tradejs/cli": packageSpec("CLI"),
|
|
152
|
+
"@tradejs/core": packageSpec("CORE"),
|
|
153
|
+
...infraPackage ? { "@tradejs/infra": infraPackage } : {}
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
"package.json": `${JSON.stringify(
|
|
157
|
+
{
|
|
158
|
+
name: packageName,
|
|
159
|
+
version: "0.1.0",
|
|
160
|
+
private: true,
|
|
161
|
+
scripts: {
|
|
162
|
+
dev: "tradejs-app dev",
|
|
163
|
+
backtest: "tradejs backtest",
|
|
164
|
+
doctor: "tradejs doctor --skip-ml",
|
|
165
|
+
"infra-up": "tradejs infra-up",
|
|
166
|
+
"infra-down": "tradejs infra-down"
|
|
167
|
+
},
|
|
168
|
+
dependencies,
|
|
169
|
+
engines: {
|
|
170
|
+
node: ">=20.19"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
null,
|
|
174
|
+
2
|
|
175
|
+
)}
|
|
176
|
+
`,
|
|
177
|
+
"tradejs.config.ts": `import { basePreset } from '@tradejs/base';
|
|
178
|
+
import { defineConfig } from '@tradejs/core/config';
|
|
179
|
+
|
|
180
|
+
export default defineConfig(basePreset);
|
|
181
|
+
`,
|
|
182
|
+
".env": `AUTH_SECRET=${authSecret}
|
|
183
|
+
NEXTAUTH_SECRET=${authSecret}
|
|
184
|
+
NEXTAUTH_URL=http://localhost:${port}
|
|
185
|
+
APP_URL=http://localhost:${port}
|
|
186
|
+
PG_PORT=${infrastructurePorts.postgres}
|
|
187
|
+
REDIS_PORT=${infrastructurePorts.redis}
|
|
188
|
+
REDIS_INSIGHT_PORT=${infrastructurePorts.redisInsight}
|
|
189
|
+
`,
|
|
190
|
+
".gitignore": `node_modules
|
|
191
|
+
.tradejs
|
|
192
|
+
.next
|
|
193
|
+
data
|
|
194
|
+
.env
|
|
195
|
+
`,
|
|
196
|
+
"README.md": `# ${packageName}
|
|
197
|
+
|
|
198
|
+
This project was created with \`create-tradejs\`.
|
|
199
|
+
|
|
200
|
+
## Start
|
|
201
|
+
|
|
202
|
+
\`\`\`bash
|
|
203
|
+
npm run infra-up
|
|
204
|
+
npm run dev
|
|
205
|
+
\`\`\`
|
|
206
|
+
|
|
207
|
+
Open [http://localhost:${port}/routes/dashboard](http://localhost:${port}/routes/dashboard).
|
|
208
|
+
On the first launch, TradeJS asks you to create the local root password.
|
|
209
|
+
`
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
var scaffoldProject = (targetDir, port, infrastructurePorts = DEFAULT_INFRASTRUCTURE_PORTS) => {
|
|
213
|
+
const absoluteTarget = import_node_path.default.resolve(targetDir);
|
|
214
|
+
if ((0, import_node_fs.existsSync)(absoluteTarget) && (0, import_node_fs.readdirSync)(absoluteTarget).length > 0) {
|
|
215
|
+
throw new Error(`Target directory is not empty: ${absoluteTarget}`);
|
|
216
|
+
}
|
|
217
|
+
(0, import_node_fs.mkdirSync)(absoluteTarget, { recursive: true });
|
|
218
|
+
const files = buildProjectFiles(absoluteTarget, port, infrastructurePorts);
|
|
219
|
+
for (const [relativePath, contents] of Object.entries(files)) {
|
|
220
|
+
(0, import_node_fs.writeFileSync)(import_node_path.default.join(absoluteTarget, relativePath), contents, "utf8");
|
|
221
|
+
}
|
|
222
|
+
return absoluteTarget;
|
|
223
|
+
};
|
|
224
|
+
var run = (command, args, cwd) => {
|
|
225
|
+
const result = (0, import_node_child_process.spawnSync)(command, args, {
|
|
226
|
+
cwd,
|
|
227
|
+
env: { ...process.env, PROJECT_CWD: cwd },
|
|
228
|
+
stdio: "inherit"
|
|
229
|
+
});
|
|
230
|
+
if (result.error) {
|
|
231
|
+
throw result.error;
|
|
232
|
+
}
|
|
233
|
+
if (result.status !== 0) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`${command} ${args.join(" ")} failed with code ${result.status}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var localBin = (projectDir, name) => import_node_path.default.join(
|
|
240
|
+
projectDir,
|
|
241
|
+
"node_modules",
|
|
242
|
+
".bin",
|
|
243
|
+
process.platform === "win32" ? `${name}.cmd` : name
|
|
244
|
+
);
|
|
245
|
+
var isPortAvailable = (port) => new Promise((resolve) => {
|
|
246
|
+
const server = import_node_net.default.createServer();
|
|
247
|
+
server.unref();
|
|
248
|
+
server.once("error", () => resolve(false));
|
|
249
|
+
server.listen(port, () => server.close(() => resolve(true)));
|
|
250
|
+
});
|
|
251
|
+
var findAvailablePort = async (preferredPort) => {
|
|
252
|
+
for (let offset = 0; offset < 20; offset += 1) {
|
|
253
|
+
const port = preferredPort + offset;
|
|
254
|
+
if (await isPortAvailable(port)) {
|
|
255
|
+
return port;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
throw new Error(`No available port found starting at ${preferredPort}`);
|
|
259
|
+
};
|
|
260
|
+
var openBrowser = (url) => {
|
|
261
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
262
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
263
|
+
const child = (0, import_node_child_process.spawn)(command, args, { detached: true, stdio: "ignore" });
|
|
264
|
+
child.once("error", (error) => {
|
|
265
|
+
console.warn(`Could not open the browser automatically: ${error.message}`);
|
|
266
|
+
});
|
|
267
|
+
child.unref();
|
|
268
|
+
};
|
|
269
|
+
var waitForApp = async (url, child) => {
|
|
270
|
+
for (let attempt = 0; attempt < 120; attempt += 1) {
|
|
271
|
+
if (child.exitCode !== null) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Web UI exited before it became ready (code ${child.exitCode})`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const response = await fetch(url, { redirect: "manual" });
|
|
278
|
+
if (response.status > 0 && response.status < 500) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
284
|
+
}
|
|
285
|
+
throw new Error(`Timed out waiting for ${url}`);
|
|
286
|
+
};
|
|
287
|
+
var startWebApp = async (projectDir, preferredPort, shouldOpen) => {
|
|
288
|
+
const port = await findAvailablePort(preferredPort);
|
|
289
|
+
const installUrl = `http://localhost:${port}/routes/install`;
|
|
290
|
+
const appBin = localBin(projectDir, "tradejs-app");
|
|
291
|
+
const child = (0, import_node_child_process.spawn)(appBin, ["dev", "--port", String(port)], {
|
|
292
|
+
cwd: projectDir,
|
|
293
|
+
env: {
|
|
294
|
+
...process.env,
|
|
295
|
+
PROJECT_CWD: projectDir,
|
|
296
|
+
PORT: String(port),
|
|
297
|
+
APP_URL: `http://localhost:${port}`,
|
|
298
|
+
NEXTAUTH_URL: `http://localhost:${port}`
|
|
299
|
+
},
|
|
300
|
+
stdio: "inherit"
|
|
301
|
+
});
|
|
302
|
+
const launchError = new Promise((_, reject) => {
|
|
303
|
+
child.once("error", reject);
|
|
304
|
+
});
|
|
305
|
+
await Promise.race([waitForApp(installUrl, child), launchError]);
|
|
306
|
+
console.log(`
|
|
307
|
+
TradeJS Web UI: ${installUrl}`);
|
|
308
|
+
if (shouldOpen) {
|
|
309
|
+
openBrowser(installUrl);
|
|
310
|
+
}
|
|
311
|
+
await new Promise((resolve, reject) => {
|
|
312
|
+
child.once("exit", (code, signal) => {
|
|
313
|
+
if (signal || code === 0) {
|
|
314
|
+
resolve();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
reject(new Error(`Web UI exited with code ${code}`));
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
var main = async () => {
|
|
322
|
+
const options = parseArgs(process.argv.slice(2));
|
|
323
|
+
if (!options) {
|
|
324
|
+
printUsage();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const infrastructurePorts = options.infra ? {
|
|
328
|
+
postgres: await findAvailablePort(5432),
|
|
329
|
+
redis: await findAvailablePort(6379),
|
|
330
|
+
redisInsight: await findAvailablePort(5540)
|
|
331
|
+
} : DEFAULT_INFRASTRUCTURE_PORTS;
|
|
332
|
+
const projectDir = scaffoldProject(
|
|
333
|
+
options.targetDir,
|
|
334
|
+
options.port,
|
|
335
|
+
infrastructurePorts
|
|
336
|
+
);
|
|
337
|
+
console.log(`Created TradeJS project in ${projectDir}`);
|
|
338
|
+
if (!options.install) {
|
|
339
|
+
console.log("Project files generated. Run npm install to continue.");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
console.log("\nInstalling TradeJS packages...");
|
|
343
|
+
run(
|
|
344
|
+
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
345
|
+
["install"],
|
|
346
|
+
projectDir
|
|
347
|
+
);
|
|
348
|
+
if (options.infra) {
|
|
349
|
+
const tradejs = localBin(projectDir, "tradejs");
|
|
350
|
+
console.log("\nStarting Redis and Timescale...");
|
|
351
|
+
run(tradejs, ["infra-init"], projectDir);
|
|
352
|
+
run(tradejs, ["infra-up"], projectDir);
|
|
353
|
+
run(tradejs, ["doctor", "--skip-ml"], projectDir);
|
|
354
|
+
console.log("\nInfrastructure is ready. Finish setup in the Web UI.");
|
|
355
|
+
} else {
|
|
356
|
+
console.log("\nInfrastructure setup was skipped.");
|
|
357
|
+
}
|
|
358
|
+
if (options.start) {
|
|
359
|
+
await startWebApp(projectDir, options.port, options.open);
|
|
360
|
+
} else {
|
|
361
|
+
console.log(`
|
|
362
|
+
Next: cd ${options.targetDir} && npm run dev`);
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
if (require.main === module) {
|
|
366
|
+
main().catch((error) => {
|
|
367
|
+
console.error(`
|
|
368
|
+
create-tradejs failed: ${error.message}`);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
373
|
+
0 && (module.exports = {
|
|
374
|
+
buildProjectFiles,
|
|
375
|
+
main,
|
|
376
|
+
parseArgs,
|
|
377
|
+
scaffoldProject
|
|
378
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-tradejs",
|
|
3
|
+
"version": "1.0.10",
|
|
4
|
+
"description": "Create a ready-to-run TradeJS project with local infrastructure and the Web UI.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tradejs",
|
|
7
|
+
"create",
|
|
8
|
+
"scaffold",
|
|
9
|
+
"backtesting",
|
|
10
|
+
"trading"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://tradejs.dev",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/TradeJS-Dev/TradeJS",
|
|
16
|
+
"directory": "packages/create-tradejs"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/tradejs-dev/tradejs/issues"
|
|
20
|
+
},
|
|
21
|
+
"bin": "dist/index.js",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20.19"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"typecheck": "tsc -p ./tsconfig.json --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^24",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^5.1"
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"author": "aleksnick (https://github.com/aleksnick)"
|
|
43
|
+
}
|