@harpy-js/core 0.5.2 ā 0.5.3
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.js +3 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/scripts/auto-wrap-exports.ts +7 -6
- package/scripts/build-hydration.ts +14 -15
- package/scripts/build-page-styles.ts +6 -5
- package/scripts/build.ts +93 -0
- package/scripts/dev.ts +40 -31
- package/scripts/logger.ts +53 -0
- package/scripts/start.ts +41 -0
- package/src/cli.ts +3 -1
- package/src/index.ts +1 -0
package/dist/cli.js
CHANGED
|
@@ -8,11 +8,13 @@ const scripts = {
|
|
|
8
8
|
"build-hydration": path.join(__dirname, "../scripts/build-hydration.ts"),
|
|
9
9
|
"auto-wrap": path.join(__dirname, "../scripts/auto-wrap-exports.ts"),
|
|
10
10
|
"build-styles": path.join(__dirname, "../scripts/build-page-styles.ts"),
|
|
11
|
+
build: path.join(__dirname, "../scripts/build.ts"),
|
|
12
|
+
start: path.join(__dirname, "../scripts/start.ts"),
|
|
11
13
|
dev: path.join(__dirname, "../scripts/dev.ts"),
|
|
12
14
|
};
|
|
13
15
|
if (!command || !scripts[command]) {
|
|
14
16
|
console.error("Usage: harpy <command>");
|
|
15
|
-
console.error("Commands: build-hydration, auto-wrap, build-styles
|
|
17
|
+
console.error("Commands: build, start, dev, build-hydration, auto-wrap, build-styles");
|
|
16
18
|
process.exit(1);
|
|
17
19
|
}
|
|
18
20
|
const scriptPath = scripts[command];
|
package/dist/index.d.ts
CHANGED
|
@@ -19,3 +19,4 @@ export { configureHarpyApp, HarpyAppOptions } from "./core/app-setup";
|
|
|
19
19
|
export { setupHarpyApp } from "./core/app-setup";
|
|
20
20
|
export { default as Link } from "./client/Link";
|
|
21
21
|
export { buildHrefIndex, getActiveItemIdFromIndex, getActiveItemIdFromManifest, } from "./client/getActiveItemId";
|
|
22
|
+
export { useI18n } from "./client/use-i18n";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getActiveItemIdFromManifest = exports.getActiveItemIdFromIndex = exports.buildHrefIndex = exports.Link = exports.setupHarpyApp = exports.configureHarpyApp = exports.AutoRegisterModule = exports.NavigationService = exports.RouterModule = exports.DefaultSeoService = exports.BaseSeoService = exports.SeoModule = exports.WithLayout = exports.JsxRender = exports.StaticAssetsController = exports.LiveReloadController = exports.withJsxEngine = exports.getHydrationManifest = exports.getChunkPath = exports.initializeHydrationContext = exports.hydrationContext = exports.autoWrapClientComponent = void 0;
|
|
6
|
+
exports.useI18n = exports.getActiveItemIdFromManifest = exports.getActiveItemIdFromIndex = exports.buildHrefIndex = exports.Link = exports.setupHarpyApp = exports.configureHarpyApp = exports.AutoRegisterModule = exports.NavigationService = exports.RouterModule = exports.DefaultSeoService = exports.BaseSeoService = exports.SeoModule = exports.WithLayout = exports.JsxRender = exports.StaticAssetsController = exports.LiveReloadController = exports.withJsxEngine = exports.getHydrationManifest = exports.getChunkPath = exports.initializeHydrationContext = exports.hydrationContext = exports.autoWrapClientComponent = void 0;
|
|
7
7
|
var client_component_wrapper_1 = require("./core/client-component-wrapper");
|
|
8
8
|
Object.defineProperty(exports, "autoWrapClientComponent", { enumerable: true, get: function () { return client_component_wrapper_1.autoWrapClientComponent; } });
|
|
9
9
|
var hydration_1 = require("./core/hydration");
|
|
@@ -42,3 +42,5 @@ var getActiveItemId_1 = require("./client/getActiveItemId");
|
|
|
42
42
|
Object.defineProperty(exports, "buildHrefIndex", { enumerable: true, get: function () { return getActiveItemId_1.buildHrefIndex; } });
|
|
43
43
|
Object.defineProperty(exports, "getActiveItemIdFromIndex", { enumerable: true, get: function () { return getActiveItemId_1.getActiveItemIdFromIndex; } });
|
|
44
44
|
Object.defineProperty(exports, "getActiveItemIdFromManifest", { enumerable: true, get: function () { return getActiveItemId_1.getActiveItemIdFromManifest; } });
|
|
45
|
+
var use_i18n_1 = require("./client/use-i18n");
|
|
46
|
+
Object.defineProperty(exports, "useI18n", { enumerable: true, get: function () { return use_i18n_1.useI18n; } });
|
package/package.json
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
import * as fs from "fs";
|
|
13
13
|
import * as path from "path";
|
|
14
|
+
import { Logger } from "./logger";
|
|
15
|
+
|
|
16
|
+
const logger = new Logger("AutoWrapper");
|
|
14
17
|
|
|
15
18
|
const PROJECT_ROOT = process.cwd();
|
|
16
19
|
const SRC_DIR = path.join(PROJECT_ROOT, "src");
|
|
@@ -213,16 +216,16 @@ function transformCompiledFile(
|
|
|
213
216
|
* Main function
|
|
214
217
|
*/
|
|
215
218
|
function main(): void {
|
|
216
|
-
|
|
219
|
+
logger.log("Auto-wrapping client component exports...");
|
|
217
220
|
|
|
218
221
|
const clientComponents = findClientComponentsInSource();
|
|
219
222
|
|
|
220
223
|
if (clientComponents.size === 0) {
|
|
221
|
-
|
|
224
|
+
logger.warn("No client components found");
|
|
222
225
|
return;
|
|
223
226
|
}
|
|
224
227
|
|
|
225
|
-
|
|
228
|
+
logger.log(`Found ${clientComponents.size} client component(s)`);
|
|
226
229
|
|
|
227
230
|
let wrapped = 0;
|
|
228
231
|
for (const [compiledPath, componentName] of clientComponents) {
|
|
@@ -231,9 +234,7 @@ function main(): void {
|
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
236
|
|
|
234
|
-
|
|
235
|
-
`\n⨠Auto-wrap complete: ${wrapped}/${clientComponents.size} components wrapped\n`,
|
|
236
|
-
);
|
|
237
|
+
logger.log(`Auto-wrap complete: ${wrapped}/${clientComponents.size} components wrapped`);
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
main();
|
|
@@ -13,6 +13,9 @@ import { execSync } from "child_process";
|
|
|
13
13
|
import * as crypto from "crypto";
|
|
14
14
|
import * as fs from "fs";
|
|
15
15
|
import * as path from "path";
|
|
16
|
+
import { Logger } from "./logger";
|
|
17
|
+
|
|
18
|
+
const logger = new Logger("HydrationBuilder");
|
|
16
19
|
|
|
17
20
|
const PROJECT_ROOT = process.cwd();
|
|
18
21
|
const SRC_DIR = path.join(PROJECT_ROOT, "src");
|
|
@@ -181,11 +184,11 @@ function main(): void {
|
|
|
181
184
|
fs.mkdirSync(HYDRATION_ENTRIES_DIR, { recursive: true });
|
|
182
185
|
}
|
|
183
186
|
|
|
184
|
-
|
|
187
|
+
logger.log("Detecting client components...");
|
|
185
188
|
const clientComponents = findClientComponents();
|
|
186
189
|
|
|
187
190
|
if (clientComponents.length === 0) {
|
|
188
|
-
|
|
191
|
+
logger.warn("No client components found");
|
|
189
192
|
// Still ensure chunks directory exists and clear manifest
|
|
190
193
|
if (!fs.existsSync(CHUNKS_DIR)) {
|
|
191
194
|
fs.mkdirSync(CHUNKS_DIR, { recursive: true });
|
|
@@ -207,7 +210,7 @@ function main(): void {
|
|
|
207
210
|
fs.mkdirSync(CHUNKS_DIR, { recursive: true });
|
|
208
211
|
}
|
|
209
212
|
|
|
210
|
-
|
|
213
|
+
logger.log("Generating hydration entries...");
|
|
211
214
|
|
|
212
215
|
// Generate hydration entry files
|
|
213
216
|
const entryFiles: { path: string; componentName: string }[] = [];
|
|
@@ -223,11 +226,10 @@ function main(): void {
|
|
|
223
226
|
path: entryPath,
|
|
224
227
|
componentName: component.componentName,
|
|
225
228
|
});
|
|
226
|
-
console.log(` ā ${component.componentName}.tsx`);
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
// Build shared vendor bundle first
|
|
230
|
-
|
|
232
|
+
logger.log("Building shared vendor bundle...");
|
|
231
233
|
const vendorEntryPath = path.join(HYDRATION_ENTRIES_DIR, "_vendor.js");
|
|
232
234
|
const vendorContent = `
|
|
233
235
|
import React from 'react';
|
|
@@ -242,16 +244,15 @@ window.ReactDOM = ReactDOM;
|
|
|
242
244
|
|
|
243
245
|
const vendorOutputPath = path.join(CHUNKS_DIR, VENDOR_BUNDLE);
|
|
244
246
|
try {
|
|
245
|
-
const vendorCommand = `npx esbuild "${vendorEntryPath}" --bundle --minify --target=es2020 --format=iife --outfile="${vendorOutputPath}" --platform=browser --tree-shaking=true --define:process.env.NODE_ENV
|
|
247
|
+
const vendorCommand = `npx esbuild "${vendorEntryPath}" --bundle --minify --target=es2020 --format=iife --outfile="${vendorOutputPath}" --platform=browser --tree-shaking=true --define:process.env.NODE_ENV='"production"'`;
|
|
246
248
|
execSync(vendorCommand, { stdio: "inherit" });
|
|
247
|
-
console.log(` ā vendor.js (React + ReactDOM)`);
|
|
248
249
|
} catch (error) {
|
|
249
|
-
|
|
250
|
+
logger.error(`Failed to bundle vendor: ${error}`);
|
|
250
251
|
process.exit(1);
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
// Bundle each entry file separately with cache-busted names
|
|
254
|
-
|
|
255
|
+
logger.log("Bundling hydration scripts...");
|
|
255
256
|
|
|
256
257
|
// Create React shim files for aliasing
|
|
257
258
|
const SHIMS_DIR = path.join(DIST_DIR, ".shims");
|
|
@@ -301,27 +302,25 @@ module.exports = {
|
|
|
301
302
|
|
|
302
303
|
try {
|
|
303
304
|
// Use aliases to redirect React imports to window.React from vendor bundle
|
|
304
|
-
const command = `npx esbuild "${entry.path}" --bundle --minify --target=es2020 --format=iife --keep-names --outfile="${outputPath}" --platform=browser --tree-shaking=true --define:process.env.NODE_ENV
|
|
305
|
+
const command = `npx esbuild "${entry.path}" --bundle --minify --target=es2020 --format=iife --keep-names --outfile="${outputPath}" --platform=browser --tree-shaking=true --define:process.env.NODE_ENV='"production"' --alias:react=${reactShimPath} --alias:react-dom=${reactDomShimPath} --alias:react-dom/client=${reactDomClientShimPath} --alias:react/jsx-runtime=${jsxRuntimeShimPath}`;
|
|
305
306
|
execSync(command, { stdio: "inherit" });
|
|
306
307
|
manifest[entry.componentName] = chunkFilename;
|
|
307
|
-
console.log(` ā ${entry.componentName} -> ${chunkFilename}`);
|
|
308
308
|
} catch (error) {
|
|
309
|
-
|
|
309
|
+
logger.error(`Failed to bundle ${entry.componentName}: ${error}`);
|
|
310
310
|
process.exit(1);
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
// Write manifest file for server-side lookup
|
|
315
|
-
|
|
315
|
+
logger.log("Writing hydration manifest...");
|
|
316
316
|
fs.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2), "utf-8");
|
|
317
|
-
console.log(` ā Manifest written to ${MANIFEST_FILE}`);
|
|
318
317
|
|
|
319
318
|
// Clean up temporary entries directory
|
|
320
319
|
if (fs.existsSync(HYDRATION_ENTRIES_DIR)) {
|
|
321
320
|
fs.rmSync(HYDRATION_ENTRIES_DIR, { recursive: true });
|
|
322
321
|
}
|
|
323
322
|
|
|
324
|
-
|
|
323
|
+
logger.log("Hydration build complete!");
|
|
325
324
|
}
|
|
326
325
|
|
|
327
326
|
main();
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
import { execSync } from "child_process";
|
|
8
8
|
import * as fs from "fs";
|
|
9
9
|
import * as path from "path";
|
|
10
|
+
import { Logger } from "./logger";
|
|
10
11
|
|
|
12
|
+
const logger = new Logger("StylesBuilder");
|
|
11
13
|
const projectRoot = process.cwd();
|
|
12
14
|
const distDir = path.join(projectRoot, "dist");
|
|
13
15
|
const stylesDir = path.join(distDir, "styles");
|
|
@@ -15,7 +17,7 @@ const srcAssetsDir = path.join(projectRoot, "src/assets");
|
|
|
15
17
|
const outputCssPath = path.join(stylesDir, "styles.css");
|
|
16
18
|
|
|
17
19
|
async function main(): Promise<void> {
|
|
18
|
-
|
|
20
|
+
logger.log("Building styles...");
|
|
19
21
|
|
|
20
22
|
try {
|
|
21
23
|
// Ensure styles directory exists
|
|
@@ -24,7 +26,7 @@ async function main(): Promise<void> {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// Compile Tailwind CSS
|
|
27
|
-
|
|
29
|
+
logger.log("Compiling Tailwind CSS...");
|
|
28
30
|
execSync(
|
|
29
31
|
`NODE_ENV=production postcss ${path.join(srcAssetsDir, "styles.css")} -o ${outputCssPath}`,
|
|
30
32
|
{
|
|
@@ -32,10 +34,9 @@ async function main(): Promise<void> {
|
|
|
32
34
|
},
|
|
33
35
|
);
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
console.log("⨠Styles build complete!");
|
|
37
|
+
logger.log("Styles build complete!");
|
|
37
38
|
} catch (error: any) {
|
|
38
|
-
|
|
39
|
+
logger.error(`CSS generation failed: ${error.message}`);
|
|
39
40
|
process.exit(1);
|
|
40
41
|
}
|
|
41
42
|
}
|
package/scripts/build.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { Logger } from "./logger";
|
|
5
|
+
|
|
6
|
+
const logger = new Logger("Builder");
|
|
7
|
+
|
|
8
|
+
async function runCommand(cmd: string, args: string[] = []): Promise<void> {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const proc = spawn(cmd, args, { stdio: "inherit", shell: true });
|
|
11
|
+
proc.on("close", (code) => {
|
|
12
|
+
if (code !== 0) {
|
|
13
|
+
reject(new Error(`Command failed with code ${code}`));
|
|
14
|
+
} else {
|
|
15
|
+
resolve();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function buildNestApp(): Promise<void> {
|
|
22
|
+
logger.log("Building NestJS application...");
|
|
23
|
+
try {
|
|
24
|
+
await runCommand("nest", ["build"]);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
logger.error(`NestJS build failed: ${error}`);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function buildHydration(): Promise<void> {
|
|
32
|
+
logger.log("Building hydration components...");
|
|
33
|
+
try {
|
|
34
|
+
const tsxPath = require("child_process")
|
|
35
|
+
.execSync("which tsx", { encoding: "utf-8" })
|
|
36
|
+
.trim();
|
|
37
|
+
await runCommand(tsxPath, [
|
|
38
|
+
require("path").join(__dirname, "build-hydration.ts"),
|
|
39
|
+
]);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
logger.error(`Hydration build failed: ${error}`);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function autoWrap(): Promise<void> {
|
|
47
|
+
logger.log("Auto-wrapping client components...");
|
|
48
|
+
try {
|
|
49
|
+
const tsxPath = require("child_process")
|
|
50
|
+
.execSync("which tsx", { encoding: "utf-8" })
|
|
51
|
+
.trim();
|
|
52
|
+
await runCommand(tsxPath, [
|
|
53
|
+
require("path").join(__dirname, "auto-wrap-exports.ts"),
|
|
54
|
+
]);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.error(`Auto-wrap failed: ${error}`);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function buildStyles(): Promise<void> {
|
|
62
|
+
logger.log("Building styles...");
|
|
63
|
+
try {
|
|
64
|
+
const tsxPath = require("child_process")
|
|
65
|
+
.execSync("which tsx", { encoding: "utf-8" })
|
|
66
|
+
.trim();
|
|
67
|
+
await runCommand(tsxPath, [
|
|
68
|
+
require("path").join(__dirname, "build-page-styles.ts"),
|
|
69
|
+
]);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logger.error(`Styles build failed: ${error}`);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function main(): Promise<void> {
|
|
77
|
+
try {
|
|
78
|
+
logger.log("Starting production build...");
|
|
79
|
+
|
|
80
|
+
await buildNestApp();
|
|
81
|
+
await buildHydration();
|
|
82
|
+
await autoWrap();
|
|
83
|
+
await buildStyles();
|
|
84
|
+
|
|
85
|
+
logger.log("Production build complete!");
|
|
86
|
+
process.exit(0);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.error(`Build failed: ${error}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main();
|
package/scripts/dev.ts
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import * as http from "http";
|
|
6
|
+
import { Logger } from "./logger";
|
|
7
|
+
|
|
8
|
+
const logger = new Logger("DevServer");
|
|
6
9
|
|
|
7
10
|
let nestProcess: any = null;
|
|
8
11
|
let isRebuilding = false;
|
|
@@ -43,43 +46,52 @@ async function runCommand(cmd: string, args: string[] = []): Promise<void> {
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
async function buildHydration(): Promise<void> {
|
|
46
|
-
|
|
49
|
+
logger.log("Building hydration components...");
|
|
47
50
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
const tsxPath = require("child_process")
|
|
52
|
+
.execSync("which tsx", { encoding: "utf-8" })
|
|
53
|
+
.trim();
|
|
54
|
+
await runCommand(tsxPath, [
|
|
55
|
+
require("path").join(__dirname, "build-hydration.ts"),
|
|
56
|
+
]);
|
|
50
57
|
} catch (error) {
|
|
51
|
-
|
|
58
|
+
logger.error(`Hydration build failed: ${error}`);
|
|
52
59
|
throw error;
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
async function autoWrap(): Promise<void> {
|
|
57
|
-
|
|
64
|
+
logger.log("Auto-wrapping client components...");
|
|
58
65
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
const tsxPath = require("child_process")
|
|
67
|
+
.execSync("which tsx", { encoding: "utf-8" })
|
|
68
|
+
.trim();
|
|
69
|
+
await runCommand(tsxPath, [
|
|
70
|
+
require("path").join(__dirname, "auto-wrap-exports.ts"),
|
|
71
|
+
]);
|
|
61
72
|
} catch (error) {
|
|
62
|
-
|
|
63
|
-
throw error;
|
|
73
|
+
logger.error(`Auto-wrap failed: ${error}`);
|
|
64
74
|
}
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
async function buildStyles(): Promise<void> {
|
|
68
|
-
|
|
78
|
+
logger.log("Building styles...");
|
|
69
79
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
const tsxPath = require("child_process")
|
|
81
|
+
.execSync("which tsx", { encoding: "utf-8" })
|
|
82
|
+
.trim();
|
|
83
|
+
await runCommand(tsxPath, [
|
|
84
|
+
require("path").join(__dirname, "build-page-styles.ts"),
|
|
85
|
+
]);
|
|
72
86
|
} catch (error) {
|
|
73
|
-
|
|
74
|
-
throw error;
|
|
87
|
+
logger.error(`Styles build failed: ${error}`);
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
90
|
|
|
78
91
|
async function startNestServer(): Promise<void> {
|
|
79
92
|
return new Promise((resolve) => {
|
|
80
|
-
|
|
93
|
+
logger.log("Starting NestJS application...");
|
|
81
94
|
// Run compiled dist/main.js instead of using ts-node
|
|
82
|
-
// This ensures auto-wrapped components are used
|
|
83
95
|
nestProcess = spawn("node", ["--watch", "dist/main.js"], {
|
|
84
96
|
stdio: "pipe",
|
|
85
97
|
shell: false,
|
|
@@ -106,15 +118,14 @@ async function startNestServer(): Promise<void> {
|
|
|
106
118
|
setTimeout(async () => {
|
|
107
119
|
if (isRebuilding) return;
|
|
108
120
|
isRebuilding = true;
|
|
109
|
-
|
|
121
|
+
logger.log("Rebuilding assets after code change...");
|
|
110
122
|
try {
|
|
111
123
|
await buildHydration();
|
|
112
124
|
await autoWrap();
|
|
113
125
|
await buildStyles();
|
|
114
|
-
console.log("ā
Assets rebuilt\n");
|
|
115
126
|
triggerBrowserReload();
|
|
116
127
|
} catch (error) {
|
|
117
|
-
|
|
128
|
+
logger.error(`Asset rebuild failed: ${error}`);
|
|
118
129
|
} finally {
|
|
119
130
|
isRebuilding = false;
|
|
120
131
|
}
|
|
@@ -139,10 +150,9 @@ async function startNestServer(): Promise<void> {
|
|
|
139
150
|
}
|
|
140
151
|
|
|
141
152
|
function watchSourceChanges(): void {
|
|
142
|
-
|
|
153
|
+
logger.log("Watching source files for changes...");
|
|
143
154
|
|
|
144
155
|
let debounceTimer: NodeJS.Timeout | null = null;
|
|
145
|
-
const watchedFiles = new Set<string>();
|
|
146
156
|
|
|
147
157
|
// Watch src for CSS changes only (TS/TSX changes are handled by NestJS watch + stdout detection)
|
|
148
158
|
fs.watch("src", { recursive: true }, async (eventType, filename) => {
|
|
@@ -168,14 +178,13 @@ function watchSourceChanges(): void {
|
|
|
168
178
|
|
|
169
179
|
debounceTimer = setTimeout(async () => {
|
|
170
180
|
isRebuilding = true;
|
|
171
|
-
|
|
181
|
+
logger.log(`CSS file changed: ${filename}`);
|
|
172
182
|
|
|
173
183
|
try {
|
|
174
184
|
await buildStyles();
|
|
175
|
-
console.log("ā
Styles rebuilt\n");
|
|
176
185
|
triggerBrowserReload();
|
|
177
186
|
} catch (error) {
|
|
178
|
-
|
|
187
|
+
logger.error(`Style rebuild failed: ${error}`);
|
|
179
188
|
} finally {
|
|
180
189
|
watchedFiles.delete(filename);
|
|
181
190
|
isRebuilding = false;
|
|
@@ -188,7 +197,7 @@ let tscProcess: any = null;
|
|
|
188
197
|
|
|
189
198
|
async function startTypeScriptWatch(): Promise<void> {
|
|
190
199
|
return new Promise((resolve) => {
|
|
191
|
-
|
|
200
|
+
logger.log("Starting TypeScript compiler in watch mode...");
|
|
192
201
|
tscProcess = spawn("pnpm", ["nest", "build", "--watch"], {
|
|
193
202
|
stdio: "pipe",
|
|
194
203
|
shell: true,
|
|
@@ -215,13 +224,13 @@ async function startTypeScriptWatch(): Promise<void> {
|
|
|
215
224
|
|
|
216
225
|
async function main(): Promise<void> {
|
|
217
226
|
try {
|
|
218
|
-
|
|
227
|
+
logger.log("Initializing development environment...");
|
|
219
228
|
|
|
220
229
|
// First: Start TypeScript compiler in watch mode
|
|
221
230
|
await startTypeScriptWatch();
|
|
222
231
|
|
|
223
232
|
// Build initial assets after first compilation
|
|
224
|
-
|
|
233
|
+
logger.log("Building initial assets...");
|
|
225
234
|
await buildHydration();
|
|
226
235
|
await autoWrap();
|
|
227
236
|
await buildStyles();
|
|
@@ -229,27 +238,27 @@ async function main(): Promise<void> {
|
|
|
229
238
|
// Now start the node server with compiled dist files
|
|
230
239
|
await startNestServer();
|
|
231
240
|
|
|
232
|
-
|
|
241
|
+
logger.log("Development server ready!");
|
|
233
242
|
|
|
234
243
|
// Watch for source changes
|
|
235
244
|
watchSourceChanges();
|
|
236
245
|
|
|
237
246
|
// Handle graceful shutdown
|
|
238
247
|
process.on("SIGINT", async () => {
|
|
239
|
-
|
|
248
|
+
logger.log("Stopping development server...");
|
|
240
249
|
if (tscProcess) tscProcess.kill();
|
|
241
250
|
if (nestProcess) nestProcess.kill();
|
|
242
251
|
process.exit(0);
|
|
243
252
|
});
|
|
244
253
|
|
|
245
254
|
process.on("SIGTERM", async () => {
|
|
246
|
-
|
|
255
|
+
logger.log("Stopping development server...");
|
|
247
256
|
if (tscProcess) tscProcess.kill();
|
|
248
257
|
if (nestProcess) nestProcess.kill();
|
|
249
258
|
process.exit(0);
|
|
250
259
|
});
|
|
251
260
|
} catch (error) {
|
|
252
|
-
|
|
261
|
+
logger.error(`Fatal error: ${error}`);
|
|
253
262
|
process.exit(1);
|
|
254
263
|
}
|
|
255
264
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger utility to format logs matching NestJS style
|
|
3
|
+
* Format: [Harpy] PID - DATE, TIME LEVEL [Context] Message +Xms
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const pid = process.pid;
|
|
7
|
+
|
|
8
|
+
function getTimestamp(): string {
|
|
9
|
+
const now = new Date();
|
|
10
|
+
const date = now.toLocaleDateString("en-US");
|
|
11
|
+
const time = now.toLocaleTimeString("en-US");
|
|
12
|
+
return `${date}, ${time}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class Logger {
|
|
16
|
+
private context: string;
|
|
17
|
+
private lastLogTime: number = Date.now();
|
|
18
|
+
|
|
19
|
+
constructor(context: string) {
|
|
20
|
+
this.context = context;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private formatMessage(level: string, message: string, levelColor: string): string {
|
|
24
|
+
const timestamp = getTimestamp();
|
|
25
|
+
const elapsed = Date.now() - this.lastLogTime;
|
|
26
|
+
this.lastLogTime = Date.now();
|
|
27
|
+
|
|
28
|
+
const harpyPid = `\x1b[32m[Harpy] ${pid}\x1b[0m`; // green
|
|
29
|
+
const timestampFormatted = `\x1b[37m${timestamp}\x1b[0m`; // white
|
|
30
|
+
const levelColored = `\x1b[${levelColor}m${level.padEnd(5)}\x1b[0m`;
|
|
31
|
+
const contextColored = `\x1b[33m[${this.context}]\x1b[0m`; // yellow
|
|
32
|
+
const messageColored = `\x1b[${levelColor}m${message}\x1b[0m`;
|
|
33
|
+
const elapsedColored = `\x1b[33m+${elapsed}ms\x1b[0m`; // yellow
|
|
34
|
+
|
|
35
|
+
return `${harpyPid} - ${timestampFormatted} ${levelColored} ${contextColored} ${messageColored} ${elapsedColored}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
log(message: string): void {
|
|
39
|
+
console.log(this.formatMessage("LOG", message, "32")); // green
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
error(message: string): void {
|
|
43
|
+
console.error(this.formatMessage("ERROR", message, "31")); // red
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
warn(message: string): void {
|
|
47
|
+
console.warn(this.formatMessage("WARN", message, "33")); // yellow
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
verbose(message: string): void {
|
|
51
|
+
console.log(this.formatMessage("LOG", message, "36")); // cyan
|
|
52
|
+
}
|
|
53
|
+
}
|
package/scripts/start.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { Logger } from "./logger";
|
|
5
|
+
|
|
6
|
+
const logger = new Logger("Server");
|
|
7
|
+
|
|
8
|
+
function startServer(): void {
|
|
9
|
+
logger.log("Starting production server...");
|
|
10
|
+
|
|
11
|
+
const serverProcess = spawn("node", ["dist/main.js"], {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
shell: false,
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
serverProcess.on("error", (error) => {
|
|
18
|
+
logger.error(`Failed to start server: ${error.message}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
serverProcess.on("exit", (code) => {
|
|
23
|
+
if (code !== 0) {
|
|
24
|
+
logger.error(`Server exited with code ${code}`);
|
|
25
|
+
process.exit(code || 1);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Handle termination signals
|
|
30
|
+
process.on("SIGINT", () => {
|
|
31
|
+
logger.log("Shutting down server...");
|
|
32
|
+
serverProcess.kill("SIGINT");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
process.on("SIGTERM", () => {
|
|
36
|
+
logger.log("Shutting down server...");
|
|
37
|
+
serverProcess.kill("SIGTERM");
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
startServer();
|
package/src/cli.ts
CHANGED
|
@@ -10,12 +10,14 @@ const scripts: Record<string, string> = {
|
|
|
10
10
|
"build-hydration": path.join(__dirname, "../scripts/build-hydration.ts"),
|
|
11
11
|
"auto-wrap": path.join(__dirname, "../scripts/auto-wrap-exports.ts"),
|
|
12
12
|
"build-styles": path.join(__dirname, "../scripts/build-page-styles.ts"),
|
|
13
|
+
build: path.join(__dirname, "../scripts/build.ts"),
|
|
14
|
+
start: path.join(__dirname, "../scripts/start.ts"),
|
|
13
15
|
dev: path.join(__dirname, "../scripts/dev.ts"),
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
if (!command || !scripts[command]) {
|
|
17
19
|
console.error("Usage: harpy <command>");
|
|
18
|
-
console.error("Commands: build-hydration, auto-wrap, build-styles
|
|
20
|
+
console.error("Commands: build, start, dev, build-hydration, auto-wrap, build-styles");
|
|
19
21
|
process.exit(1);
|
|
20
22
|
}
|
|
21
23
|
|
package/src/index.ts
CHANGED