@squiz/dxp-cli-next 5.24.0-develop.4 → 5.25.0-develop.1
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/lib/cdp/instance/activate/activate.js +2 -10
- package/lib/cdp/instance/activate/activate.spec.js +18 -17
- package/lib/page/layouts/dev/dev.d.ts +5 -0
- package/lib/page/layouts/dev/dev.js +70 -0
- package/lib/page/layouts/dev/dev.spec.d.ts +1 -0
- package/lib/page/layouts/dev/dev.spec.js +162 -0
- package/lib/page/layouts/layouts.js +3 -1
- package/lib/page/layouts/layouts.spec.js +7 -0
- package/lib/page/utils/definitions.d.ts +1 -0
- package/lib/page/utils/definitions.js +2 -1
- package/lib/page/utils/parse-args.d.ts +28 -0
- package/lib/page/utils/parse-args.js +84 -0
- package/lib/page/utils/parse-args.spec.d.ts +1 -0
- package/lib/page/utils/parse-args.spec.js +146 -0
- package/lib/page/utils/render.d.ts +33 -0
- package/lib/page/utils/render.js +58 -0
- package/lib/page/utils/render.spec.d.ts +1 -0
- package/lib/page/utils/render.spec.js +134 -0
- package/lib/page/utils/server.d.ts +12 -0
- package/lib/page/utils/server.js +201 -0
- package/lib/page/utils/server.spec.d.ts +1 -0
- package/lib/page/utils/server.spec.js +275 -0
- package/package.json +2 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const parse_args_1 = require("./parse-args");
|
|
4
|
+
describe('parseZonesList', () => {
|
|
5
|
+
it('returns empty object when given empty string', () => {
|
|
6
|
+
const result = (0, parse_args_1.parseZonesList)('', {});
|
|
7
|
+
expect(result).toEqual({});
|
|
8
|
+
});
|
|
9
|
+
it('parses single zone with equals separator', () => {
|
|
10
|
+
const result = (0, parse_args_1.parseZonesList)('main=./main.html', {});
|
|
11
|
+
expect(result).toEqual({
|
|
12
|
+
main: ['./main.html'],
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
it('parses single zone with colon separator', () => {
|
|
16
|
+
const result = (0, parse_args_1.parseZonesList)('main:./main.html', {});
|
|
17
|
+
expect(result).toEqual({
|
|
18
|
+
main: ['./main.html'],
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
it('parses comma-separated zones with equals separator', () => {
|
|
22
|
+
const result = (0, parse_args_1.parseZonesList)('header=./header.html,main=./main.html,footer=./footer.html', {});
|
|
23
|
+
expect(result).toEqual({
|
|
24
|
+
header: ['./header.html'],
|
|
25
|
+
main: ['./main.html'],
|
|
26
|
+
footer: ['./footer.html'],
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
it('parses comma-separated zones with colon separator', () => {
|
|
30
|
+
const result = (0, parse_args_1.parseZonesList)('header:./header.html,main:./main.html,footer:./footer.html', {});
|
|
31
|
+
expect(result).toEqual({
|
|
32
|
+
header: ['./header.html'],
|
|
33
|
+
main: ['./main.html'],
|
|
34
|
+
footer: ['./footer.html'],
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
it('accumulates multiple values for the same zone', () => {
|
|
38
|
+
const previous = { main: ['./main.0.html'] };
|
|
39
|
+
const result = (0, parse_args_1.parseZonesList)('main=./main.1.html', previous);
|
|
40
|
+
expect(result).toEqual({
|
|
41
|
+
main: ['./main.0.html', './main.1.html'],
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
it('accumulates values from multiple zone flags', () => {
|
|
45
|
+
let result = (0, parse_args_1.parseZonesList)('main=./main.html', {});
|
|
46
|
+
result = (0, parse_args_1.parseZonesList)('sidebar=./sidebar.html', result);
|
|
47
|
+
result = (0, parse_args_1.parseZonesList)('footer=./footer.html', result);
|
|
48
|
+
expect(result).toEqual({
|
|
49
|
+
main: ['./main.html'],
|
|
50
|
+
sidebar: ['./sidebar.html'],
|
|
51
|
+
footer: ['./footer.html'],
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it('handles mixed syntax with equals and colons', () => {
|
|
55
|
+
const result = (0, parse_args_1.parseZonesList)('header=./header.html,main:./main.html', {});
|
|
56
|
+
expect(result).toEqual({
|
|
57
|
+
header: ['./header.html'],
|
|
58
|
+
main: ['./main.html'],
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
it('ignores malformed inputs', () => {
|
|
62
|
+
const result = (0, parse_args_1.parseZonesList)('malformed,main=,=./file.html', {});
|
|
63
|
+
expect(result).toEqual({});
|
|
64
|
+
});
|
|
65
|
+
it('handles complex example with multiple formats', () => {
|
|
66
|
+
let result = (0, parse_args_1.parseZonesList)('header:./header.html', {});
|
|
67
|
+
result = (0, parse_args_1.parseZonesList)('main:./main.0.html,main:./main.1.html', result);
|
|
68
|
+
result = (0, parse_args_1.parseZonesList)('sidebar=./sidebar.0.html', result);
|
|
69
|
+
expect(result).toEqual({
|
|
70
|
+
header: ['./header.html'],
|
|
71
|
+
main: ['./main.0.html', './main.1.html'],
|
|
72
|
+
sidebar: ['./sidebar.0.html'],
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('parseOptionsList', () => {
|
|
77
|
+
it('returns empty object when given empty string', () => {
|
|
78
|
+
const result = (0, parse_args_1.parseOptionsList)('', {});
|
|
79
|
+
expect(result).toEqual({});
|
|
80
|
+
});
|
|
81
|
+
it('parses single option with equals separator', () => {
|
|
82
|
+
const result = (0, parse_args_1.parseOptionsList)('theme=dark', {});
|
|
83
|
+
expect(result).toEqual({
|
|
84
|
+
theme: 'dark',
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
it('parses single option with colon separator', () => {
|
|
88
|
+
const result = (0, parse_args_1.parseOptionsList)('theme:dark', {});
|
|
89
|
+
expect(result).toEqual({
|
|
90
|
+
theme: 'dark',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
it('parses comma-separated options with equals separator', () => {
|
|
94
|
+
const result = (0, parse_args_1.parseOptionsList)('theme=dark,containerSize=standard,sidebarPosition=right', {});
|
|
95
|
+
expect(result).toEqual({
|
|
96
|
+
theme: 'dark',
|
|
97
|
+
containerSize: 'standard',
|
|
98
|
+
sidebarPosition: 'right',
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
it('parses comma-separated options with colon separator', () => {
|
|
102
|
+
const result = (0, parse_args_1.parseOptionsList)('theme:dark,containerSize:standard,sidebarPosition:right', {});
|
|
103
|
+
expect(result).toEqual({
|
|
104
|
+
theme: 'dark',
|
|
105
|
+
containerSize: 'standard',
|
|
106
|
+
sidebarPosition: 'right',
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
it('later options override earlier ones', () => {
|
|
110
|
+
let result = (0, parse_args_1.parseOptionsList)('theme=light', {});
|
|
111
|
+
result = (0, parse_args_1.parseOptionsList)('theme=dark', result);
|
|
112
|
+
expect(result).toEqual({
|
|
113
|
+
theme: 'dark',
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it('accumulates values from multiple option flags', () => {
|
|
117
|
+
let result = (0, parse_args_1.parseOptionsList)('theme=dark', {});
|
|
118
|
+
result = (0, parse_args_1.parseOptionsList)('containerSize=standard', result);
|
|
119
|
+
result = (0, parse_args_1.parseOptionsList)('sidebarPosition=right', result);
|
|
120
|
+
expect(result).toEqual({
|
|
121
|
+
theme: 'dark',
|
|
122
|
+
containerSize: 'standard',
|
|
123
|
+
sidebarPosition: 'right',
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
it('handles mixed syntax with equals and colons', () => {
|
|
127
|
+
const result = (0, parse_args_1.parseOptionsList)('theme=dark,containerSize:standard', {});
|
|
128
|
+
expect(result).toEqual({
|
|
129
|
+
theme: 'dark',
|
|
130
|
+
containerSize: 'standard',
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
it('ignores malformed inputs', () => {
|
|
134
|
+
const result = (0, parse_args_1.parseOptionsList)('malformed,theme=,=dark', {});
|
|
135
|
+
expect(result).toEqual({});
|
|
136
|
+
});
|
|
137
|
+
it('handles complex example with multiple formats', () => {
|
|
138
|
+
let result = (0, parse_args_1.parseOptionsList)('containerSize:standard', {});
|
|
139
|
+
result = (0, parse_args_1.parseOptionsList)('sidebarPosition=right,colorTheme=dark', result);
|
|
140
|
+
expect(result).toEqual({
|
|
141
|
+
containerSize: 'standard',
|
|
142
|
+
sidebarPosition: 'right',
|
|
143
|
+
colorTheme: 'dark',
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface LayoutDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
displayName?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
entry?: string;
|
|
6
|
+
template?: string;
|
|
7
|
+
zones: Record<string, ZoneDefinition>;
|
|
8
|
+
options?: Record<string, OptionDefinition>;
|
|
9
|
+
}
|
|
10
|
+
export interface ExtendedLayoutDefinition extends LayoutDefinition {
|
|
11
|
+
template: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ZoneDefinition {
|
|
14
|
+
displayName: string;
|
|
15
|
+
description: string;
|
|
16
|
+
minNodes: number;
|
|
17
|
+
maxNodes?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface OptionDefinition {
|
|
20
|
+
displayName: string;
|
|
21
|
+
description: string;
|
|
22
|
+
values: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Renders a layout using Handlebars templating
|
|
26
|
+
*
|
|
27
|
+
* @param templateContent The Handlebars template string
|
|
28
|
+
* @param zoneContents Content for each zone as single concatenated strings
|
|
29
|
+
* @param layoutOptions Options for the layout
|
|
30
|
+
* @param layoutDefinition Optional layout definition object to pass to the template
|
|
31
|
+
* @returns The rendered HTML
|
|
32
|
+
*/
|
|
33
|
+
export declare function renderLayout(templateContent: string, zoneContents: Record<string, string>, layoutOptions: Record<string, any>, layoutDefinition?: ExtendedLayoutDefinition): Promise<string>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.renderLayout = void 0;
|
|
16
|
+
const handlebars_1 = __importDefault(require("handlebars"));
|
|
17
|
+
/**
|
|
18
|
+
* Renders a layout using Handlebars templating
|
|
19
|
+
*
|
|
20
|
+
* @param templateContent The Handlebars template string
|
|
21
|
+
* @param zoneContents Content for each zone as single concatenated strings
|
|
22
|
+
* @param layoutOptions Options for the layout
|
|
23
|
+
* @param layoutDefinition Optional layout definition object to pass to the template
|
|
24
|
+
* @returns The rendered HTML
|
|
25
|
+
*/
|
|
26
|
+
function renderLayout(templateContent, zoneContents, layoutOptions, layoutDefinition) {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
try {
|
|
29
|
+
const template = handlebars_1.default.compile(templateContent);
|
|
30
|
+
const zones = {};
|
|
31
|
+
for (const [zoneName, content] of Object.entries(zoneContents)) {
|
|
32
|
+
// Wrap the content in SafeString to prevent HTML escaping
|
|
33
|
+
zones[zoneName] = new handlebars_1.default.SafeString(content);
|
|
34
|
+
}
|
|
35
|
+
const flattenedOptions = {};
|
|
36
|
+
for (const [key, value] of Object.entries(layoutOptions)) {
|
|
37
|
+
const optionValue = value;
|
|
38
|
+
if (optionValue &&
|
|
39
|
+
typeof optionValue === 'object' &&
|
|
40
|
+
'selectedValue' in optionValue) {
|
|
41
|
+
flattenedOptions[key] = optionValue.selectedValue;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
flattenedOptions[key] = optionValue;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return template({
|
|
48
|
+
zones: zones,
|
|
49
|
+
options: flattenedOptions,
|
|
50
|
+
layout: layoutDefinition,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return `<div class="error">Error rendering template: ${error.message}</div>`;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
exports.renderLayout = renderLayout;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const render_1 = require("./render");
|
|
16
|
+
const handlebars_1 = __importDefault(require("handlebars"));
|
|
17
|
+
describe('renderLayout', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.resetAllMocks();
|
|
20
|
+
});
|
|
21
|
+
it('should render a simple template with zone content', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
22
|
+
const templateContent = '<div class="container">{{zones.main}}</div>';
|
|
23
|
+
const zoneContents = {
|
|
24
|
+
main: '<p>Content 1</p><p>Content 2</p>',
|
|
25
|
+
};
|
|
26
|
+
const layoutOptions = {};
|
|
27
|
+
const result = yield (0, render_1.renderLayout)(templateContent, zoneContents, layoutOptions);
|
|
28
|
+
expect(result).toContain('<div class="container">');
|
|
29
|
+
expect(result).toContain('<p>Content 1</p>');
|
|
30
|
+
expect(result).toContain('<p>Content 2</p>');
|
|
31
|
+
}));
|
|
32
|
+
it('should apply layout options to the template', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
|
+
const templateContent = '<div class="container {{options.size}}">{{zones.main}}</div>';
|
|
34
|
+
const zoneContents = {
|
|
35
|
+
main: '<p>Content</p>',
|
|
36
|
+
};
|
|
37
|
+
const layoutOptions = {
|
|
38
|
+
size: 'large',
|
|
39
|
+
};
|
|
40
|
+
const result = yield (0, render_1.renderLayout)(templateContent, zoneContents, layoutOptions);
|
|
41
|
+
expect(result).toContain('<div class="container large">');
|
|
42
|
+
expect(result).toContain('<p>Content</p>');
|
|
43
|
+
}));
|
|
44
|
+
it('should handle multiple zones', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
+
const templateContent = `
|
|
46
|
+
<div class="container">
|
|
47
|
+
<div class="main">{{zones.main}}</div>
|
|
48
|
+
<div class="sidebar">{{zones.sidebar}}</div>
|
|
49
|
+
</div>
|
|
50
|
+
`;
|
|
51
|
+
const zoneContents = {
|
|
52
|
+
main: '<p>Main Content</p>',
|
|
53
|
+
sidebar: '<div>Sidebar Item 1</div><div>Sidebar Item 2</div>',
|
|
54
|
+
};
|
|
55
|
+
const layoutOptions = {};
|
|
56
|
+
const result = yield (0, render_1.renderLayout)(templateContent, zoneContents, layoutOptions);
|
|
57
|
+
expect(result).toContain('<div class="main"><p>Main Content</p></div>');
|
|
58
|
+
expect(result).toContain('<div class="sidebar"><div>Sidebar Item 1</div><div>Sidebar Item 2</div></div>');
|
|
59
|
+
}));
|
|
60
|
+
it('should handle complex option values with selectedValue property', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
const templateContent = `
|
|
62
|
+
<div class="theme-{{options.theme}}">
|
|
63
|
+
<div class="main">{{zones.main}}</div>
|
|
64
|
+
<div class="sidebar">{{zones.sidebar}}</div>
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
67
|
+
const zoneContents = {
|
|
68
|
+
main: '<p>Content</p>',
|
|
69
|
+
};
|
|
70
|
+
const layoutOptions = {
|
|
71
|
+
theme: { selectedValue: 'dark' },
|
|
72
|
+
};
|
|
73
|
+
const result = yield (0, render_1.renderLayout)(templateContent, zoneContents, layoutOptions);
|
|
74
|
+
expect(result).toContain('<div class="theme-dark">');
|
|
75
|
+
}));
|
|
76
|
+
it('should use SafeString to prevent HTML escaping', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
77
|
+
const templateContent = '<div>{{zones.content}}</div>';
|
|
78
|
+
const zoneContents = {
|
|
79
|
+
content: '<strong>Bold Text</strong>',
|
|
80
|
+
};
|
|
81
|
+
const result = yield (0, render_1.renderLayout)(templateContent, zoneContents, {});
|
|
82
|
+
expect(result).toContain('<strong>Bold Text</strong>');
|
|
83
|
+
}));
|
|
84
|
+
it('should handle empty zone content gracefully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
85
|
+
const templateContent = '<div>{{zones.main}}</div>';
|
|
86
|
+
const zoneContents = {
|
|
87
|
+
main: '',
|
|
88
|
+
};
|
|
89
|
+
const result = yield (0, render_1.renderLayout)(templateContent, zoneContents, {});
|
|
90
|
+
expect(result).toEqual('<div></div>');
|
|
91
|
+
}));
|
|
92
|
+
it('should include layout definition in template data when provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
const templateContent = '<div class="{{layout.name}}">{{zones.main}}</div>';
|
|
94
|
+
const zoneContents = {
|
|
95
|
+
main: '<p>Content</p>',
|
|
96
|
+
};
|
|
97
|
+
const layoutDefinition = {
|
|
98
|
+
name: 'test-layout',
|
|
99
|
+
displayName: 'Test Layout',
|
|
100
|
+
template: templateContent,
|
|
101
|
+
zones: {
|
|
102
|
+
main: {
|
|
103
|
+
displayName: 'Main Content',
|
|
104
|
+
description: 'Main content area',
|
|
105
|
+
minNodes: 1,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const result = yield (0, render_1.renderLayout)(templateContent, zoneContents, {}, layoutDefinition);
|
|
110
|
+
expect(result).toContain('<div class="test-layout">');
|
|
111
|
+
}));
|
|
112
|
+
it('should handle rendering errors gracefully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
|
+
const templateContent = '{{#invalid}}Invalid Handlebars Syntax{{/invalid}}';
|
|
114
|
+
const zoneContents = { main: '<p>Content</p>' };
|
|
115
|
+
handlebars_1.default.compile = jest.fn().mockImplementation(() => {
|
|
116
|
+
throw new Error('Handlebars compilation error');
|
|
117
|
+
});
|
|
118
|
+
const result = yield (0, render_1.renderLayout)(templateContent, zoneContents, {});
|
|
119
|
+
expect(result).toContain('<div class="error">');
|
|
120
|
+
expect(result).toContain('Handlebars compilation error');
|
|
121
|
+
}));
|
|
122
|
+
it('should handle compilation errors during template execution', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
|
+
const templateContent = '{{invalidHelper}}';
|
|
124
|
+
const zoneContents = { main: '<p>Content</p>' };
|
|
125
|
+
handlebars_1.default.compile = jest.fn().mockImplementation(() => {
|
|
126
|
+
return () => {
|
|
127
|
+
throw new Error('Template execution error');
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
const result = yield (0, render_1.renderLayout)(templateContent, zoneContents, {});
|
|
131
|
+
expect(result).toContain('<div class="error">');
|
|
132
|
+
expect(result).toContain('Template execution error');
|
|
133
|
+
}));
|
|
134
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ExtendedLayoutDefinition } from './render';
|
|
2
|
+
interface DevServerOptions {
|
|
3
|
+
configPath: string;
|
|
4
|
+
layoutDefinition: ExtendedLayoutDefinition;
|
|
5
|
+
zoneContent: Record<string, string[]>;
|
|
6
|
+
layoutOptions: Record<string, string>;
|
|
7
|
+
stylesheet?: string;
|
|
8
|
+
port: number;
|
|
9
|
+
openBrowser: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function startDevServer(options: DevServerOptions): Promise<void>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
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 (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.startDevServer = void 0;
|
|
39
|
+
const definitions_1 = require("../utils/definitions");
|
|
40
|
+
const render_1 = require("./render");
|
|
41
|
+
const fs = __importStar(require("fs/promises"));
|
|
42
|
+
const fs_1 = require("fs");
|
|
43
|
+
const http = __importStar(require("http"));
|
|
44
|
+
const opener_1 = __importDefault(require("opener"));
|
|
45
|
+
const dx_logger_lib_1 = require("@squiz/dx-logger-lib");
|
|
46
|
+
const logger = (0, dx_logger_lib_1.getLogger)({
|
|
47
|
+
name: 'layout-dev-server',
|
|
48
|
+
format: 'human',
|
|
49
|
+
});
|
|
50
|
+
function startDevServer(options) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const { configPath, layoutOptions, port, openBrowser } = options;
|
|
53
|
+
// Create HTTP server with stateless request handling
|
|
54
|
+
const server = http.createServer((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
const url = new URL(req.url || '/', `http://localhost:${port}`);
|
|
56
|
+
// Serve main layout page on root
|
|
57
|
+
if (url.pathname === '/') {
|
|
58
|
+
try {
|
|
59
|
+
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(configPath);
|
|
60
|
+
if (!layoutDefinition || !layoutDefinition.template) {
|
|
61
|
+
throw new Error('Template content not found in layout definition');
|
|
62
|
+
}
|
|
63
|
+
const zoneContents = {};
|
|
64
|
+
// Concatenate all files for this zone into a single string
|
|
65
|
+
for (const [zoneName, filePaths] of Object.entries(options.zoneContent)) {
|
|
66
|
+
let combinedContent = '';
|
|
67
|
+
for (const filePath of filePaths) {
|
|
68
|
+
try {
|
|
69
|
+
const content = yield fs.readFile(filePath, 'utf-8');
|
|
70
|
+
combinedContent += content;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
logger.warn(`Failed to load file ${filePath}: ${error}`);
|
|
74
|
+
combinedContent += `<!-- Error loading ${filePath}: ${error} -->`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
zoneContents[zoneName] = combinedContent;
|
|
78
|
+
}
|
|
79
|
+
// Load stylesheet content fresh for each request
|
|
80
|
+
let stylesheetContent = '';
|
|
81
|
+
if (options.stylesheet && (0, fs_1.existsSync)(options.stylesheet)) {
|
|
82
|
+
stylesheetContent = yield fs.readFile(options.stylesheet, 'utf-8');
|
|
83
|
+
}
|
|
84
|
+
// Use the renderLayout helper
|
|
85
|
+
const html = yield (0, render_1.renderLayout)(layoutDefinition.template, zoneContents, layoutOptions, layoutDefinition);
|
|
86
|
+
// for hot-reload script
|
|
87
|
+
const fullHtml = `
|
|
88
|
+
<!DOCTYPE html>
|
|
89
|
+
<html>
|
|
90
|
+
<head>
|
|
91
|
+
<meta charset="UTF-8">
|
|
92
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
93
|
+
<title>${layoutDefinition.displayName || layoutDefinition.name} - Layout Development</title>
|
|
94
|
+
<style>${stylesheetContent}</style>
|
|
95
|
+
<script>
|
|
96
|
+
// Client-side polling for changes
|
|
97
|
+
let lastModified = new Date().toISOString();
|
|
98
|
+
|
|
99
|
+
// Poll the server every second to check for changes
|
|
100
|
+
function checkForChanges() {
|
|
101
|
+
fetch('/check-changes?lastModified=' + encodeURIComponent(lastModified))
|
|
102
|
+
.then(response => response.json())
|
|
103
|
+
.then(data => {
|
|
104
|
+
if (data.hasChanges) {
|
|
105
|
+
console.log('Changes detected, reloading at', new Date().toISOString());
|
|
106
|
+
lastModified = data.lastModified;
|
|
107
|
+
location.reload();
|
|
108
|
+
} else {
|
|
109
|
+
lastModified = data.lastModified;
|
|
110
|
+
setTimeout(checkForChanges, 1000);
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
.catch(err => {
|
|
114
|
+
console.error('Error checking for changes:', err);
|
|
115
|
+
// Retry after a delay
|
|
116
|
+
setTimeout(checkForChanges, 5000);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Start checking for changes
|
|
121
|
+
checkForChanges();
|
|
122
|
+
</script>
|
|
123
|
+
</head>
|
|
124
|
+
<body>
|
|
125
|
+
${html}
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
|
128
|
+
`;
|
|
129
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
130
|
+
res.end(fullHtml);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
logger.error(`Error rendering layout: ${error}`);
|
|
134
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
135
|
+
res.end(`Error rendering layout: ${error}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Simplified hot reloading endpoint
|
|
139
|
+
else if (url.pathname === '/check-changes') {
|
|
140
|
+
const clientLastModified = url.searchParams.get('lastModified') || new Date(0).toISOString();
|
|
141
|
+
try {
|
|
142
|
+
const layoutTemplatePath = (yield (0, definitions_1.loadLayoutFromFile)(configPath)).entry;
|
|
143
|
+
// Check all relevant files for modifications
|
|
144
|
+
const watchPaths = [
|
|
145
|
+
configPath,
|
|
146
|
+
layoutTemplatePath,
|
|
147
|
+
...Object.values(options.zoneContent).flat(),
|
|
148
|
+
...(options.stylesheet ? [options.stylesheet] : []),
|
|
149
|
+
];
|
|
150
|
+
let hasChanges = false;
|
|
151
|
+
let latestModified = clientLastModified;
|
|
152
|
+
for (const filePath of watchPaths) {
|
|
153
|
+
if ((0, fs_1.existsSync)(filePath)) {
|
|
154
|
+
const stats = yield fs.stat(filePath);
|
|
155
|
+
const fileModified = stats.mtime.toISOString();
|
|
156
|
+
if (fileModified > clientLastModified) {
|
|
157
|
+
hasChanges = true;
|
|
158
|
+
}
|
|
159
|
+
if (fileModified > latestModified) {
|
|
160
|
+
latestModified = fileModified;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
165
|
+
res.end(JSON.stringify({
|
|
166
|
+
hasChanges,
|
|
167
|
+
lastModified: latestModified,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
logger.error(`Error checking file changes: ${error}`);
|
|
172
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
173
|
+
res.end(JSON.stringify({
|
|
174
|
+
error: `Error checking changes: ${error}`,
|
|
175
|
+
hasChanges: false,
|
|
176
|
+
lastModified: clientLastModified,
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// Redirect to the base path for any other routes
|
|
182
|
+
res.writeHead(302, {
|
|
183
|
+
Location: '/',
|
|
184
|
+
});
|
|
185
|
+
res.end();
|
|
186
|
+
}
|
|
187
|
+
}));
|
|
188
|
+
server.listen(port, () => {
|
|
189
|
+
const url = `http://localhost:${port}/`;
|
|
190
|
+
logger.info(`Layout development server started at ${url}`);
|
|
191
|
+
if (openBrowser) {
|
|
192
|
+
(0, opener_1.default)(url);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
process.on('SIGINT', () => {
|
|
196
|
+
logger.info('Shutting down server...');
|
|
197
|
+
server.close();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
exports.startDevServer = startDevServer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|