@harpy-js/core 0.4.7
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 +326 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +53 -0
- package/dist/client/Link.d.ts +5 -0
- package/dist/client/Link.js +62 -0
- package/dist/client/__tests__/getActiveItemId.test.d.ts +1 -0
- package/dist/client/__tests__/getActiveItemId.test.js +38 -0
- package/dist/client/getActiveItemId.d.ts +7 -0
- package/dist/client/getActiveItemId.js +55 -0
- package/dist/client/use-i18n.d.ts +7 -0
- package/dist/client/use-i18n.js +64 -0
- package/dist/core/__tests__/component-analyzer.test.d.ts +1 -0
- package/dist/core/__tests__/component-analyzer.test.js +151 -0
- package/dist/core/__tests__/hydration-manifest.test.d.ts +1 -0
- package/dist/core/__tests__/hydration-manifest.test.js +211 -0
- package/dist/core/__tests__/jsx.engine.test.d.ts +1 -0
- package/dist/core/__tests__/jsx.engine.test.js +118 -0
- package/dist/core/app-setup.d.ts +7 -0
- package/dist/core/app-setup.js +79 -0
- package/dist/core/auto-register.module.d.ts +9 -0
- package/dist/core/auto-register.module.js +18 -0
- package/dist/core/auto-wrap-middleware.d.ts +4 -0
- package/dist/core/auto-wrap-middleware.js +130 -0
- package/dist/core/client-component-wrapper.d.ts +5 -0
- package/dist/core/client-component-wrapper.js +37 -0
- package/dist/core/client-hydration.d.ts +2 -0
- package/dist/core/client-hydration.js +93 -0
- package/dist/core/client-wrapper-browser.d.ts +2 -0
- package/dist/core/client-wrapper-browser.js +22 -0
- package/dist/core/component-analyzer.d.ts +4 -0
- package/dist/core/component-analyzer.js +98 -0
- package/dist/core/component-auto-wrapper.d.ts +2 -0
- package/dist/core/component-auto-wrapper.js +63 -0
- package/dist/core/component-client-wrapper.d.ts +4 -0
- package/dist/core/component-client-wrapper.js +80 -0
- package/dist/core/hydration-generator.d.ts +2 -0
- package/dist/core/hydration-generator.js +98 -0
- package/dist/core/hydration-manifest.d.ts +7 -0
- package/dist/core/hydration-manifest.js +83 -0
- package/dist/core/hydration.d.ts +16 -0
- package/dist/core/hydration.js +72 -0
- package/dist/core/jsx.engine.d.ts +9 -0
- package/dist/core/jsx.engine.js +161 -0
- package/dist/core/live-reload-client.js +32 -0
- package/dist/core/live-reload.controller.d.ts +10 -0
- package/dist/core/live-reload.controller.js +38 -0
- package/dist/core/navigation.service.d.ts +18 -0
- package/dist/core/navigation.service.js +206 -0
- package/dist/core/router.module.d.ts +2 -0
- package/dist/core/router.module.js +21 -0
- package/dist/core/static-assets.controller.d.ts +4 -0
- package/dist/core/static-assets.controller.js +51 -0
- package/dist/core/types/nav.types.d.ts +22 -0
- package/dist/core/types/nav.types.js +2 -0
- package/dist/core/views/layout.d.ts +8 -0
- package/dist/core/views/layout.js +35 -0
- package/dist/decorators/jsx.decorator.d.ts +26 -0
- package/dist/decorators/jsx.decorator.js +10 -0
- package/dist/decorators/layout.decorator.d.ts +4 -0
- package/dist/decorators/layout.decorator.js +29 -0
- package/dist/i18n/__tests__/i18n.helper.test.d.ts +1 -0
- package/dist/i18n/__tests__/i18n.helper.test.js +105 -0
- package/dist/i18n/__tests__/i18n.interceptor.test.d.ts +1 -0
- package/dist/i18n/__tests__/i18n.interceptor.test.js +195 -0
- package/dist/i18n/__tests__/i18n.module.test.d.ts +1 -0
- package/dist/i18n/__tests__/i18n.module.test.js +83 -0
- package/dist/i18n/__tests__/i18n.service.test.d.ts +1 -0
- package/dist/i18n/__tests__/i18n.service.test.js +109 -0
- package/dist/i18n/__tests__/t.test.d.ts +1 -0
- package/dist/i18n/__tests__/t.test.js +66 -0
- package/dist/i18n/i18n-module.options.d.ts +10 -0
- package/dist/i18n/i18n-module.options.js +4 -0
- package/dist/i18n/i18n-switcher.controller.d.ts +12 -0
- package/dist/i18n/i18n-switcher.controller.js +80 -0
- package/dist/i18n/i18n-types.d.ts +8 -0
- package/dist/i18n/i18n-types.js +2 -0
- package/dist/i18n/i18n.helper.d.ts +14 -0
- package/dist/i18n/i18n.helper.js +70 -0
- package/dist/i18n/i18n.interceptor.d.ts +9 -0
- package/dist/i18n/i18n.interceptor.js +99 -0
- package/dist/i18n/i18n.module.d.ts +5 -0
- package/dist/i18n/i18n.module.js +51 -0
- package/dist/i18n/i18n.service.d.ts +12 -0
- package/dist/i18n/i18n.service.js +61 -0
- package/dist/i18n/index.d.ts +10 -0
- package/dist/i18n/index.js +20 -0
- package/dist/i18n/locale.decorator.d.ts +1 -0
- package/dist/i18n/locale.decorator.js +8 -0
- package/dist/i18n/t.d.ts +3 -0
- package/dist/i18n/t.js +16 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +40 -0
- package/package.json +79 -0
- package/scripts/analyze-styles.ts +124 -0
- package/scripts/auto-wrap-exports.ts +239 -0
- package/scripts/build-css.ts +38 -0
- package/scripts/build-hydration.ts +313 -0
- package/scripts/build-page-styles.ts +43 -0
- package/scripts/copy-assets.ts +34 -0
- package/scripts/dev.sh +3 -0
- package/scripts/dev.ts +257 -0
- package/src/cli.ts +71 -0
- package/src/client/Link.tsx +62 -0
- package/src/client/__tests__/getActiveItemId.test.ts +49 -0
- package/src/client/getActiveItemId.ts +54 -0
- package/src/client/use-i18n.ts +111 -0
- package/src/core/__tests__/component-analyzer.test.ts +141 -0
- package/src/core/__tests__/hydration-manifest.test.ts +223 -0
- package/src/core/__tests__/jsx.engine.test.ts +137 -0
- package/src/core/app-setup.ts +114 -0
- package/src/core/auto-register.module.ts +30 -0
- package/src/core/auto-wrap-middleware.ts +165 -0
- package/src/core/client-component-wrapper.ts +72 -0
- package/src/core/client-hydration.tsx +99 -0
- package/src/core/client-wrapper-browser.ts +40 -0
- package/src/core/component-analyzer.ts +89 -0
- package/src/core/component-auto-wrapper.ts +68 -0
- package/src/core/component-client-wrapper.ts +112 -0
- package/src/core/hydration-generator.ts +94 -0
- package/src/core/hydration-manifest.ts +79 -0
- package/src/core/hydration.ts +70 -0
- package/src/core/jsx.engine.ts +205 -0
- package/src/core/live-reload-client.js +32 -0
- package/src/core/live-reload.controller.ts +55 -0
- package/src/core/navigation.service.ts +257 -0
- package/src/core/router.module.ts +9 -0
- package/src/core/static-assets.controller.ts +19 -0
- package/src/core/types/nav.types.ts +53 -0
- package/src/core/views/layout.tsx +61 -0
- package/src/decorators/jsx.decorator.ts +49 -0
- package/src/decorators/layout.decorator.ts +66 -0
- package/src/i18n/__tests__/i18n.helper.test.ts +126 -0
- package/src/i18n/__tests__/i18n.interceptor.test.ts +229 -0
- package/src/i18n/__tests__/i18n.module.test.ts +98 -0
- package/src/i18n/__tests__/i18n.service.test.ts +129 -0
- package/src/i18n/__tests__/t.test.ts +88 -0
- package/src/i18n/i18n-module.options.ts +53 -0
- package/src/i18n/i18n-switcher.controller.ts +99 -0
- package/src/i18n/i18n-types.ts +56 -0
- package/src/i18n/i18n.helper.ts +75 -0
- package/src/i18n/i18n.interceptor.ts +114 -0
- package/src/i18n/i18n.module.ts +45 -0
- package/src/i18n/i18n.service.ts +95 -0
- package/src/i18n/index.ts +37 -0
- package/src/i18n/locale.decorator.ts +10 -0
- package/src/i18n/t.ts +62 -0
- package/src/index.ts +31 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("fs"));
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
jest.mock("fs");
|
|
39
|
+
jest.mock("path");
|
|
40
|
+
describe("Component Analyzer", () => {
|
|
41
|
+
const mockFs = fs;
|
|
42
|
+
const mockPath = path;
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
jest.clearAllMocks();
|
|
45
|
+
});
|
|
46
|
+
describe("client component detection", () => {
|
|
47
|
+
it('should detect "use client" directive', () => {
|
|
48
|
+
const code = '"use client";\nexport function MyComponent() {}';
|
|
49
|
+
expect(code).toContain("use client");
|
|
50
|
+
});
|
|
51
|
+
it("should detect server components (no directive)", () => {
|
|
52
|
+
const code = "export function ServerComponent() {}";
|
|
53
|
+
expect(code).not.toContain("use client");
|
|
54
|
+
});
|
|
55
|
+
it("should handle single quotes", () => {
|
|
56
|
+
const code = "'use client';\nexport function Comp() {}";
|
|
57
|
+
expect(code).toMatch(/['"]use client['"]/);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("React hooks detection", () => {
|
|
61
|
+
it("should detect useState import", () => {
|
|
62
|
+
const code = 'import { useState } from "react";';
|
|
63
|
+
expect(code).toContain("useState");
|
|
64
|
+
});
|
|
65
|
+
it("should detect useEffect import", () => {
|
|
66
|
+
const code = 'import { useEffect } from "react";';
|
|
67
|
+
expect(code).toContain("useEffect");
|
|
68
|
+
});
|
|
69
|
+
it("should detect multiple hooks", () => {
|
|
70
|
+
const code = 'import { useState, useEffect, useRef } from "react";';
|
|
71
|
+
expect(code).toContain("useState");
|
|
72
|
+
expect(code).toContain("useEffect");
|
|
73
|
+
expect(code).toContain("useRef");
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("event handler detection", () => {
|
|
77
|
+
it("should detect onClick handler", () => {
|
|
78
|
+
const code = "<button onClick={handleClick}>Click</button>";
|
|
79
|
+
expect(code).toContain("onClick");
|
|
80
|
+
});
|
|
81
|
+
it("should detect onChange handler", () => {
|
|
82
|
+
const code = "<input onChange={handleChange} />";
|
|
83
|
+
expect(code).toContain("onChange");
|
|
84
|
+
});
|
|
85
|
+
it("should detect onSubmit handler", () => {
|
|
86
|
+
const code = "<form onSubmit={handleSubmit}>";
|
|
87
|
+
expect(code).toContain("onSubmit");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe("component name extraction", () => {
|
|
91
|
+
it("should extract function component name", () => {
|
|
92
|
+
const code = "export function MyComponent() {}";
|
|
93
|
+
const match = code.match(/function\s+(\w+)/);
|
|
94
|
+
expect(match).toBeDefined();
|
|
95
|
+
expect(match[1]).toBe("MyComponent");
|
|
96
|
+
});
|
|
97
|
+
it("should extract arrow function component name", () => {
|
|
98
|
+
const code = "export const MyComponent = () => {}";
|
|
99
|
+
const match = code.match(/const\s+(\w+)\s*=/);
|
|
100
|
+
expect(match).toBeDefined();
|
|
101
|
+
expect(match[1]).toBe("MyComponent");
|
|
102
|
+
});
|
|
103
|
+
it("should extract default export name", () => {
|
|
104
|
+
const code = "export default function Component() {}";
|
|
105
|
+
const match = code.match(/default\s+function\s+(\w+)/);
|
|
106
|
+
expect(match).toBeDefined();
|
|
107
|
+
expect(match[1]).toBe("Component");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe("file system operations", () => {
|
|
111
|
+
it("should filter TypeScript files", () => {
|
|
112
|
+
const files = ["component.tsx", "utils.ts", "styles.css", "test.jsx"];
|
|
113
|
+
const tsxFiles = files.filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx"));
|
|
114
|
+
expect(tsxFiles).toEqual(["component.tsx", "test.jsx"]);
|
|
115
|
+
});
|
|
116
|
+
it("should handle path operations", () => {
|
|
117
|
+
const basePath = "/src";
|
|
118
|
+
const fileName = "components";
|
|
119
|
+
const fullPath = `${basePath}/${fileName}`;
|
|
120
|
+
expect(fullPath).toBe("/src/components");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe("code analysis patterns", () => {
|
|
124
|
+
it("should identify React imports", () => {
|
|
125
|
+
const patterns = [
|
|
126
|
+
'import React from "react"',
|
|
127
|
+
'import * as React from "react"',
|
|
128
|
+
'import { Component } from "react"',
|
|
129
|
+
];
|
|
130
|
+
patterns.forEach((pattern) => {
|
|
131
|
+
expect(pattern).toMatch(/import.*from\s+['"]react['"]/);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
it("should identify JSX syntax", () => {
|
|
135
|
+
const jsxPatterns = [
|
|
136
|
+
"<div>content</div>",
|
|
137
|
+
'<Component prop="value" />',
|
|
138
|
+
"<>{children}</>",
|
|
139
|
+
];
|
|
140
|
+
jsxPatterns.forEach((pattern) => {
|
|
141
|
+
expect(pattern).toMatch(/<[\w>]/);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
it("should detect client-side APIs", () => {
|
|
145
|
+
const clientAPIs = ["window", "document", "localStorage", "navigator"];
|
|
146
|
+
const code = "const width = window.innerWidth;";
|
|
147
|
+
const hasClientAPI = clientAPIs.some((api) => code.includes(api));
|
|
148
|
+
expect(hasClientAPI).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("fs"));
|
|
37
|
+
jest.mock("fs");
|
|
38
|
+
describe("Hydration Manifest", () => {
|
|
39
|
+
const mockFs = fs;
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
jest.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
describe("manifest structure", () => {
|
|
44
|
+
it("should have components array", () => {
|
|
45
|
+
const manifest = { components: [] };
|
|
46
|
+
expect(manifest).toHaveProperty("components");
|
|
47
|
+
expect(Array.isArray(manifest.components)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
it("should store component metadata", () => {
|
|
50
|
+
const component = {
|
|
51
|
+
name: "TestComponent",
|
|
52
|
+
path: "/src/TestComponent.tsx",
|
|
53
|
+
isClient: true,
|
|
54
|
+
hasState: true,
|
|
55
|
+
hasEffects: false,
|
|
56
|
+
hasEventHandlers: true,
|
|
57
|
+
};
|
|
58
|
+
expect(component.name).toBe("TestComponent");
|
|
59
|
+
expect(component.isClient).toBe(true);
|
|
60
|
+
expect(component.hasState).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
it("should handle multiple components", () => {
|
|
63
|
+
const manifest = {
|
|
64
|
+
components: [
|
|
65
|
+
{ name: "Comp1", path: "/Comp1.tsx", isClient: true },
|
|
66
|
+
{ name: "Comp2", path: "/Comp2.tsx", isClient: true },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
expect(manifest.components).toHaveLength(2);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("client component filtering", () => {
|
|
73
|
+
it("should identify client components", () => {
|
|
74
|
+
const components = [
|
|
75
|
+
{ name: "ClientComp", isClient: true },
|
|
76
|
+
{ name: "ServerComp", isClient: false },
|
|
77
|
+
];
|
|
78
|
+
const clientOnly = components.filter((c) => c.isClient);
|
|
79
|
+
expect(clientOnly).toHaveLength(1);
|
|
80
|
+
expect(clientOnly[0].name).toBe("ClientComp");
|
|
81
|
+
});
|
|
82
|
+
it("should handle all server components", () => {
|
|
83
|
+
const components = [
|
|
84
|
+
{ name: "ServerComp1", isClient: false },
|
|
85
|
+
{ name: "ServerComp2", isClient: false },
|
|
86
|
+
];
|
|
87
|
+
const clientOnly = components.filter((c) => c.isClient);
|
|
88
|
+
expect(clientOnly).toHaveLength(0);
|
|
89
|
+
});
|
|
90
|
+
it("should handle all client components", () => {
|
|
91
|
+
const components = [
|
|
92
|
+
{ name: "ClientComp1", isClient: true },
|
|
93
|
+
{ name: "ClientComp2", isClient: true },
|
|
94
|
+
];
|
|
95
|
+
const clientOnly = components.filter((c) => c.isClient);
|
|
96
|
+
expect(clientOnly).toHaveLength(2);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe("JSON serialization", () => {
|
|
100
|
+
it("should serialize to JSON", () => {
|
|
101
|
+
const manifest = {
|
|
102
|
+
components: [
|
|
103
|
+
{
|
|
104
|
+
name: "TestComponent",
|
|
105
|
+
path: "/TestComponent.tsx",
|
|
106
|
+
isClient: true,
|
|
107
|
+
hasState: true,
|
|
108
|
+
hasEffects: false,
|
|
109
|
+
hasEventHandlers: true,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
const json = JSON.stringify(manifest);
|
|
114
|
+
expect(json).toContain("TestComponent");
|
|
115
|
+
expect(json).toContain("isClient");
|
|
116
|
+
});
|
|
117
|
+
it("should deserialize from JSON", () => {
|
|
118
|
+
const json = '{"components":[{"name":"Comp","path":"/Comp.tsx","isClient":true}]}';
|
|
119
|
+
const manifest = JSON.parse(json);
|
|
120
|
+
expect(manifest.components).toHaveLength(1);
|
|
121
|
+
expect(manifest.components[0].name).toBe("Comp");
|
|
122
|
+
});
|
|
123
|
+
it("should preserve component metadata", () => {
|
|
124
|
+
const original = {
|
|
125
|
+
name: "Component",
|
|
126
|
+
path: "/Component.tsx",
|
|
127
|
+
isClient: true,
|
|
128
|
+
hasState: true,
|
|
129
|
+
hasEffects: true,
|
|
130
|
+
hasEventHandlers: true,
|
|
131
|
+
};
|
|
132
|
+
const json = JSON.stringify(original);
|
|
133
|
+
const parsed = JSON.parse(json);
|
|
134
|
+
expect(parsed).toEqual(original);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe("file operations", () => {
|
|
138
|
+
it("should serialize manifest for saving", () => {
|
|
139
|
+
const manifest = { components: [] };
|
|
140
|
+
const filePath = "/output/manifest.json";
|
|
141
|
+
const serialized = JSON.stringify(manifest, null, 2);
|
|
142
|
+
expect(serialized).toContain("components");
|
|
143
|
+
expect(serialized).toContain("[]");
|
|
144
|
+
});
|
|
145
|
+
it("should deserialize manifest from JSON string", () => {
|
|
146
|
+
const mockData = JSON.stringify({
|
|
147
|
+
components: [
|
|
148
|
+
{ name: "LoadedComponent", path: "/Comp.tsx", isClient: true },
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
const manifest = JSON.parse(mockData);
|
|
152
|
+
expect(manifest.components).toHaveLength(1);
|
|
153
|
+
expect(manifest.components[0].name).toBe("LoadedComponent");
|
|
154
|
+
});
|
|
155
|
+
it("should validate file paths", () => {
|
|
156
|
+
const validPath = "/manifest.json";
|
|
157
|
+
const invalidPath = "";
|
|
158
|
+
expect(validPath.length).toBeGreaterThan(0);
|
|
159
|
+
expect(invalidPath.length).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
it("should handle directory paths", () => {
|
|
162
|
+
const dirPath = "/new/directory";
|
|
163
|
+
const parts = dirPath.split("/").filter(Boolean);
|
|
164
|
+
expect(parts).toEqual(["new", "directory"]);
|
|
165
|
+
expect(parts.length).toBe(2);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe("component metadata", () => {
|
|
169
|
+
it("should track component state usage", () => {
|
|
170
|
+
const component = { name: "StatefulComp", hasState: true };
|
|
171
|
+
expect(component.hasState).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
it("should track component effects", () => {
|
|
174
|
+
const component = { name: "EffectComp", hasEffects: true };
|
|
175
|
+
expect(component.hasEffects).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
it("should track event handlers", () => {
|
|
178
|
+
const component = { name: "InteractiveComp", hasEventHandlers: true };
|
|
179
|
+
expect(component.hasEventHandlers).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
it("should combine multiple flags", () => {
|
|
182
|
+
const component = {
|
|
183
|
+
name: "ComplexComp",
|
|
184
|
+
hasState: true,
|
|
185
|
+
hasEffects: true,
|
|
186
|
+
hasEventHandlers: true,
|
|
187
|
+
};
|
|
188
|
+
expect(component.hasState).toBe(true);
|
|
189
|
+
expect(component.hasEffects).toBe(true);
|
|
190
|
+
expect(component.hasEventHandlers).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
describe("deduplication", () => {
|
|
194
|
+
it("should prevent duplicate component entries", () => {
|
|
195
|
+
const components = [
|
|
196
|
+
{ name: "Comp", path: "/Comp.tsx" },
|
|
197
|
+
{ name: "Comp", path: "/Comp.tsx" },
|
|
198
|
+
];
|
|
199
|
+
const unique = Array.from(new Set(components.map((c) => c.name))).map((name) => components.find((c) => c.name === name));
|
|
200
|
+
expect(unique).toHaveLength(1);
|
|
201
|
+
});
|
|
202
|
+
it("should allow same name in different paths", () => {
|
|
203
|
+
const components = [
|
|
204
|
+
{ name: "Button", path: "/ui/Button.tsx" },
|
|
205
|
+
{ name: "Button", path: "/forms/Button.tsx" },
|
|
206
|
+
];
|
|
207
|
+
const uniqueByPath = Array.from(new Set(components.map((c) => c.path))).map((path) => components.find((c) => c.path === path));
|
|
208
|
+
expect(uniqueByPath).toHaveLength(2);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const jsx_engine_1 = require("../jsx.engine");
|
|
37
|
+
const React = __importStar(require("react"));
|
|
38
|
+
describe("JSX Engine", () => {
|
|
39
|
+
let mockApp;
|
|
40
|
+
let mockAdapter;
|
|
41
|
+
let mockReply;
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
mockReply = {
|
|
44
|
+
raw: {
|
|
45
|
+
end: jest.fn(),
|
|
46
|
+
write: jest.fn(),
|
|
47
|
+
setHeader: jest.fn(),
|
|
48
|
+
headersSent: false,
|
|
49
|
+
},
|
|
50
|
+
statusCode: 200,
|
|
51
|
+
request: {},
|
|
52
|
+
status: jest.fn().mockReturnThis(),
|
|
53
|
+
send: jest.fn(),
|
|
54
|
+
};
|
|
55
|
+
mockAdapter = {
|
|
56
|
+
render: null,
|
|
57
|
+
get: jest.fn(),
|
|
58
|
+
post: jest.fn(),
|
|
59
|
+
};
|
|
60
|
+
mockApp = {
|
|
61
|
+
getHttpAdapter: jest.fn().mockReturnValue(mockAdapter),
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
describe("withJsxEngine", () => {
|
|
65
|
+
it("should attach render method to the adapter", () => {
|
|
66
|
+
const mockLayout = (props) => React.createElement("html", null, props.children);
|
|
67
|
+
(0, jsx_engine_1.withJsxEngine)(mockApp, mockLayout);
|
|
68
|
+
expect(mockAdapter.render).toBeDefined();
|
|
69
|
+
expect(typeof mockAdapter.render).toBe("function");
|
|
70
|
+
});
|
|
71
|
+
it("should handle redirect status codes without rendering", async () => {
|
|
72
|
+
const mockLayout = (props) => React.createElement("html", null, props.children);
|
|
73
|
+
(0, jsx_engine_1.withJsxEngine)(mockApp, mockLayout);
|
|
74
|
+
mockReply.statusCode = 301;
|
|
75
|
+
await mockAdapter.render(mockReply, [() => React.createElement("div"), {}], {});
|
|
76
|
+
expect(mockReply.raw.end).toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
it("should handle error status codes without rendering", async () => {
|
|
79
|
+
const mockLayout = (props) => React.createElement("html", null, props.children);
|
|
80
|
+
(0, jsx_engine_1.withJsxEngine)(mockApp, mockLayout);
|
|
81
|
+
mockReply.statusCode = 500;
|
|
82
|
+
await mockAdapter.render(mockReply, [() => React.createElement("div"), {}], {});
|
|
83
|
+
expect(mockReply.raw.end).toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
it("should accept custom layout in options", () => {
|
|
86
|
+
const defaultLayout = (props) => React.createElement("html", null, React.createElement("body", null, props.children));
|
|
87
|
+
const customLayout = (props) => React.createElement("html", null, React.createElement("main", null, props.children));
|
|
88
|
+
(0, jsx_engine_1.withJsxEngine)(mockApp, defaultLayout);
|
|
89
|
+
expect(mockAdapter.render).toBeDefined();
|
|
90
|
+
expect(typeof customLayout).toBe("function");
|
|
91
|
+
});
|
|
92
|
+
it("should support component props", () => {
|
|
93
|
+
const mockLayout = (props) => React.createElement("html", null, props.children);
|
|
94
|
+
(0, jsx_engine_1.withJsxEngine)(mockApp, mockLayout);
|
|
95
|
+
const component = (props) => React.createElement("div", null, `Hello ${props.name}`);
|
|
96
|
+
const rendered = component({ name: "Harpy" });
|
|
97
|
+
expect(rendered.props.children).toContain("Harpy");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe("React rendering", () => {
|
|
101
|
+
it("should render simple React components", () => {
|
|
102
|
+
const element = React.createElement("div", null, "Hello World");
|
|
103
|
+
expect(element).toBeDefined();
|
|
104
|
+
expect(element.type).toBe("div");
|
|
105
|
+
});
|
|
106
|
+
it("should render nested components", () => {
|
|
107
|
+
const child = React.createElement("span", null, "Child");
|
|
108
|
+
const parent = React.createElement("div", null, child);
|
|
109
|
+
expect(parent).toBeDefined();
|
|
110
|
+
expect(parent.type).toBe("div");
|
|
111
|
+
});
|
|
112
|
+
it("should handle component props", () => {
|
|
113
|
+
const element = React.createElement("div", { className: "test", id: "main" }, "Content");
|
|
114
|
+
expect(element.props.className).toBe("test");
|
|
115
|
+
expect(element.props.id).toBe("main");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NestFastifyApplication } from "@nestjs/platform-fastify";
|
|
2
|
+
export interface HarpyAppOptions {
|
|
3
|
+
layout?: any;
|
|
4
|
+
distDir?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function configureHarpyApp(app: NestFastifyApplication, opts?: HarpyAppOptions): Promise<void>;
|
|
7
|
+
export declare function setupHarpyApp(app: NestFastifyApplication, opts?: HarpyAppOptions): Promise<void>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.configureHarpyApp = configureHarpyApp;
|
|
37
|
+
exports.setupHarpyApp = setupHarpyApp;
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
let fastifyStatic;
|
|
40
|
+
let fastifyCookie;
|
|
41
|
+
try {
|
|
42
|
+
fastifyStatic = require("@fastify/static");
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
fastifyStatic = undefined;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
fastifyCookie = require("@fastify/cookie");
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
fastifyCookie = undefined;
|
|
52
|
+
}
|
|
53
|
+
const jsx_engine_1 = require("./jsx.engine");
|
|
54
|
+
async function configureHarpyApp(app, opts = {}) {
|
|
55
|
+
const { layout, distDir = "dist" } = opts;
|
|
56
|
+
if (layout) {
|
|
57
|
+
(0, jsx_engine_1.withJsxEngine)(app, layout);
|
|
58
|
+
}
|
|
59
|
+
const fastify = app.getHttpAdapter().getInstance();
|
|
60
|
+
if (fastifyCookie) {
|
|
61
|
+
await fastify.register(fastifyCookie);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.warn("[harpy-core] optional dependency `@fastify/cookie` is not installed; skipping cookie registration.");
|
|
65
|
+
}
|
|
66
|
+
if (fastifyStatic) {
|
|
67
|
+
await fastify.register(fastifyStatic, {
|
|
68
|
+
root: path.join(process.cwd(), distDir),
|
|
69
|
+
prefix: "/",
|
|
70
|
+
decorateReply: false,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.warn("[harpy-core] optional dependency `@fastify/static` is not installed; static `dist` handler not registered.");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function setupHarpyApp(app, opts = {}) {
|
|
78
|
+
return configureHarpyApp(app, opts);
|
|
79
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { OnModuleInit } from "@nestjs/common";
|
|
2
|
+
import { NavigationService } from "./navigation.service";
|
|
3
|
+
import type { NavigationRegistry } from "./types/nav.types";
|
|
4
|
+
export declare abstract class AutoRegisterModule implements OnModuleInit {
|
|
5
|
+
protected readonly navigationService: NavigationService;
|
|
6
|
+
constructor(navigationService: NavigationService);
|
|
7
|
+
protected abstract registerNavigation(navigation: NavigationRegistry): void;
|
|
8
|
+
onModuleInit(): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AutoRegisterModule = void 0;
|
|
4
|
+
class AutoRegisterModule {
|
|
5
|
+
navigationService;
|
|
6
|
+
constructor(navigationService) {
|
|
7
|
+
this.navigationService = navigationService;
|
|
8
|
+
}
|
|
9
|
+
onModuleInit() {
|
|
10
|
+
try {
|
|
11
|
+
this.registerNavigation(this.navigationService);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
console.warn("[AutoRegisterModule] registerNavigation failed:", err);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.AutoRegisterModule = AutoRegisterModule;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export declare function autoWrapIfUsesClient(component: React.ComponentType<any>): React.ComponentType<any>;
|
|
3
|
+
export declare const createAutoWrapCreateElement: (originalCreateElement: typeof React.createElement) => (type: React.ElementType, props?: Record<string, any> | null, ...children: React.ReactNode[]) => React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
4
|
+
export declare function clearComponentAnalysisCache(): void;
|