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.
Files changed (3) hide show
  1. package/README.md +19 -0
  2. package/dist/index.js +378 -0
  3. 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
+ }