@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.
@@ -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 {};