@squiz/dxp-cli-next 5.24.0 → 5.25.0-develop.2

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.
@@ -52,7 +52,7 @@ const createLoginCommand = () => {
52
52
  .addOption(new commander_1.Option('--dxp-base-url <baseURL>', 'DXP Console URL')
53
53
  .env('DXP_BASE_URL')
54
54
  .default(constants_1.PRODUCTION_URL))
55
- .addOption(new commander_1.Option('--region <region>').choices(['au']).default('au'))
55
+ .addOption(new commander_1.Option('--region <region>').choices(['au', 'us', 'uk']).default('au'))
56
56
  .addOption(new commander_1.Option('--override-session', 'Override the existing authorized session if it exists'))
57
57
  .addOption(new commander_1.Option('--tenant <tenantID>'))
58
58
  .configureOutput({
@@ -32,10 +32,6 @@ const createActivateCommand = () => {
32
32
  const activateCommand = new commander_1.Command('deploy')
33
33
  .name('activate')
34
34
  .description('Activate a CDP instance')
35
- .addOption(new commander_1.Option('-t, --tenant <string>', 'Tenant ID to run against. If not provided will use configured tenant from login'))
36
- .addOption(new commander_1.Option('-r, --region <string>', 'Region for your instance to be activated e.g. au')
37
- .choices(['au', 'uk', 'us'])
38
- .makeOptionMandatory())
39
35
  .configureOutput({
40
36
  outputError(str, write) {
41
37
  write(chalk_1.default.red(str));
@@ -51,7 +47,7 @@ const createActivateCommand = () => {
51
47
  return status < 400 || status === 404;
52
48
  },
53
49
  });
54
- const scvDeployBaseUrl = yield (0, utils_1.buildDXPUrl)(constants_1.SCV_DEPLOY_SERVICE_NAME, options.tenant, options.overrideUrl, options.region);
50
+ const scvDeployBaseUrl = yield (0, utils_1.buildDXPUrl)(constants_1.SCV_DEPLOY_SERVICE_NAME);
55
51
  const apiUrl = `${scvDeployBaseUrl.dxpUrl}/${scvDeployBaseUrl.tenant}`;
56
52
  const getDeployResponse = (yield apiService.client
57
53
  .get(apiUrl)
@@ -66,11 +62,7 @@ const createActivateCommand = () => {
66
62
  }
67
63
  (0, utils_1.logDebug)(`PUT ${apiUrl}`);
68
64
  const activateInstanceAndDeploySchemaResponse = (yield apiService.client
69
- .put(apiUrl, null, {
70
- headers: {
71
- 'Content-Type': 'application/json',
72
- },
73
- })
65
+ .put(apiUrl)
74
66
  .catch((err) => {
75
67
  (0, utils_1.logDebug)(`RAW ERROR: ${JSON.stringify(err)}`);
76
68
  if (err.response) {
@@ -94,9 +86,6 @@ const createActivateCommand = () => {
94
86
  (0, utils_1.handleCommandError)(error);
95
87
  }
96
88
  }));
97
- if (process.env.ENABLE_OVERRIDE_CDP_SCHEMA_URL === 'true') {
98
- activateCommand.addOption(new commander_1.Option('-ou, --overrideUrl <string>', 'Developer option to override the entire DXP url with a custom value'));
99
- }
100
89
  return activateCommand;
101
90
  };
102
91
  exports.default = createActivateCommand;
@@ -35,26 +35,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- const nock_1 = __importDefault(require("nock"));
39
38
  const activate_1 = __importStar(require("./activate"));
40
39
  const deploy_const_1 = require("../../schema/deploy/deploy.const");
41
40
  const utils_1 = require("../../utils");
42
41
  const activate = __importStar(require("./activate"));
42
+ const utils = __importStar(require("../../utils"));
43
+ const axios_1 = __importDefault(require("axios"));
44
+ jest.mock('axios');
43
45
  const mockDomainWithPath = 'http://localhost:9999/__dxp/us/scv-deploy/myTenant';
44
- function createMockArgs(region, tenantID, filePath, mockDomain) {
45
- return [
46
- 'node',
47
- 'dxp-cli',
48
- 'cdp',
49
- 'instance',
50
- 'activate',
51
- '-t',
52
- tenantID,
53
- '-r',
54
- region,
55
- '-ou',
56
- 'http://localhost:9999',
57
- ];
46
+ function createMockArgs() {
47
+ return ['node', 'dxp-cli', 'cdp', 'instance', 'activate'];
58
48
  }
59
49
  describe('cdpInstanceCommand', () => {
60
50
  let mockTenant;
@@ -63,73 +53,59 @@ describe('cdpInstanceCommand', () => {
63
53
  let mockDomain;
64
54
  let logSpy;
65
55
  let errorSpy;
66
- let activateErrorSpy;
67
56
  beforeEach(() => {
68
57
  process.env.ENABLE_CDP_ADMIN = 'true';
69
- process.env.ENABLE_OVERRIDE_CDP_SCHEMA_URL = 'true';
70
58
  mockTenant = 'myTenant';
71
59
  mockRegion = 'us';
72
60
  mockFilePath = './src/__tests__/cdp/scv/schema.json';
73
61
  mockDomain = 'http://localhost:9999';
74
62
  logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
75
63
  errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
76
- activateErrorSpy = jest
77
- .spyOn(activate, 'handleActivateError')
78
- .mockImplementation(() => { });
79
- if (!nock_1.default.isActive()) {
80
- nock_1.default.activate();
81
- }
82
- nock_1.default.cleanAll(); // Ensures each test runs in isolation
83
64
  });
84
65
  afterEach(() => {
85
- jest.clearAllMocks(); // Clear all spies after each test
86
- nock_1.default.cleanAll(); // Clear all HTTP mocks
66
+ jest.clearAllMocks();
87
67
  });
88
68
  it('should throw error when tenant exists', () => __awaiter(void 0, void 0, void 0, function* () {
69
+ const mockedAxiosInstance = {
70
+ get: jest.fn().mockResolvedValue({ status: 200 }),
71
+ interceptors: {
72
+ request: { use: jest.fn() },
73
+ response: { use: jest.fn() },
74
+ },
75
+ };
76
+ axios_1.default.create.mockReturnValue(mockedAxiosInstance);
77
+ jest.spyOn(utils, 'buildDXPUrl').mockResolvedValue({
78
+ dxpUrl: `${mockDomain}/__dxp/us/scv-deploy`,
79
+ tenant: mockTenant,
80
+ });
89
81
  const mockPath = (0, utils_1.createMockUrl)(mockRegion, mockTenant);
90
82
  expect(`${mockDomain}${mockPath}`).toEqual(mockDomainWithPath);
91
- (0, nock_1.default)(mockDomain)
92
- .get(mockPath)
93
- .reply(200, { status: deploy_const_1.CDP_DEPLOY_STATUS_ERROR });
94
83
  const program = (0, activate_1.default)();
95
- yield program.parseAsync(createMockArgs(mockRegion, mockTenant, mockFilePath, mockDomain));
84
+ yield program.parseAsync(createMockArgs());
96
85
  expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining(activate_1.errorMessage));
97
86
  }));
98
- it('should throw error when get tenant returns 403', () => __awaiter(void 0, void 0, void 0, function* () {
99
- const mockPath = (0, utils_1.createMockUrl)(mockRegion, mockTenant);
100
- expect(`${mockDomain}${mockPath}`).toEqual(mockDomainWithPath);
101
- (0, nock_1.default)(mockDomain).get(mockPath).reply(403, {
102
- title: 'Forbidden',
103
- status: 403,
104
- });
105
- const program = (0, activate_1.default)();
106
- yield program.parseAsync(createMockArgs(mockRegion, mockTenant, mockFilePath, mockDomain));
107
- expect(activateErrorSpy).toHaveBeenCalledWith(403, {
108
- status: 403,
109
- title: 'Forbidden',
110
- });
111
- }));
112
87
  it('deploys a default schema and activate an instance', () => __awaiter(void 0, void 0, void 0, function* () {
88
+ jest.spyOn(utils, 'buildDXPUrl').mockResolvedValue({
89
+ dxpUrl: `${mockDomain}/__dxp/us/scv-deploy`,
90
+ tenant: mockTenant,
91
+ });
92
+ jest.spyOn(utils, 'pollForDeployedSchema').mockResolvedValue({});
93
+ const mockedAxiosInstance = {
94
+ get: jest.fn().mockResolvedValue({ status: 404 }),
95
+ put: jest
96
+ .fn()
97
+ .mockResolvedValue({ status: 200, data: { message: 'Success' } }),
98
+ interceptors: {
99
+ request: { use: jest.fn() },
100
+ response: { use: jest.fn() },
101
+ },
102
+ };
103
+ axios_1.default.create.mockReturnValue(mockedAxiosInstance);
113
104
  const mockPath = (0, utils_1.createMockUrl)(mockRegion, mockTenant);
114
105
  expect(`${mockDomain}${mockPath}`).toEqual(mockDomainWithPath);
115
- (0, nock_1.default)(mockDomain)
116
- .get(mockPath)
117
- .reply(404, {
118
- status: deploy_const_1.CDP_DEPLOY_STATUS_ERROR,
119
- })
120
- .put(mockPath)
121
- .reply(200, {
122
- tenantid: 'myTenant',
123
- version: 'unknown',
124
- stack: 'stackName',
125
- status: deploy_const_1.CDP_DEPLOY_STATUS_DEPLOYING,
126
- })
127
- .get(mockPath)
128
- .reply(200, {
129
- status: deploy_const_1.CDP_DEPLOY_STATUS_DEPLOYED,
130
- });
131
106
  const program = (0, activate_1.default)();
132
- yield program.parseAsync(createMockArgs(mockRegion, mockTenant, mockFilePath, mockDomain));
107
+ yield program.parseAsync(createMockArgs());
108
+ expect(mockedAxiosInstance.put).toHaveBeenCalledWith(`${mockDomain}${mockPath}`);
133
109
  // Note the output from the spinner doesn't seem to appear here but this still tests that it
134
110
  // ran without displaying an error.
135
111
  expect(logSpy).toHaveBeenNthCalledWith(1, '');
@@ -138,4 +114,54 @@ describe('cdpInstanceCommand', () => {
138
114
  expect(logSpy).toHaveBeenNthCalledWith(4, '');
139
115
  expect(errorSpy).not.toHaveBeenCalled();
140
116
  }));
117
+ it('should throw error when trying to activate an instance currently', () => __awaiter(void 0, void 0, void 0, function* () {
118
+ jest.spyOn(utils, 'buildDXPUrl').mockResolvedValue({
119
+ dxpUrl: `${mockDomain}/__dxp/us/scv-deploy`,
120
+ tenant: mockTenant,
121
+ });
122
+ jest.spyOn(utils, 'pollForDeployedSchema').mockResolvedValue({});
123
+ const mockedAxios = axios_1.default;
124
+ //GET request (404)
125
+ mockedAxios.get.mockResolvedValueOnce({
126
+ status: 404,
127
+ data: { status: deploy_const_1.CDP_DEPLOY_STATUS_ERROR },
128
+ });
129
+ // PUT request (409)
130
+ mockedAxios.put.mockResolvedValueOnce({
131
+ status: 409,
132
+ data: {
133
+ tenantid: 'myTenant',
134
+ version: 'unknown',
135
+ stack: 'stackName',
136
+ status: deploy_const_1.CDP_DEPLOY_STATUS_DEPLOYING,
137
+ },
138
+ });
139
+ axios_1.default.create.mockReturnValue(mockedAxios);
140
+ const mockPath = (0, utils_1.createMockUrl)(mockRegion, mockTenant);
141
+ expect(`${mockDomain}${mockPath}`).toEqual(mockDomainWithPath);
142
+ const program = (0, activate_1.default)();
143
+ yield program.parseAsync(createMockArgs());
144
+ expect(mockedAxios.put).toHaveBeenCalledWith(`${mockDomain}${mockPath}`);
145
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Currently activating instance. Please try again later.'));
146
+ }));
147
+ it('should throw error when get tenant returns 403', () => __awaiter(void 0, void 0, void 0, function* () {
148
+ const mockedAxiosInstance = {
149
+ get: jest.fn().mockRejectedValue({
150
+ response: { status: 403, data: {} },
151
+ }),
152
+ interceptors: {
153
+ request: { use: jest.fn() },
154
+ response: { use: jest.fn() },
155
+ },
156
+ };
157
+ axios_1.default.create.mockReturnValue(mockedAxiosInstance);
158
+ jest.spyOn(utils, 'buildDXPUrl').mockResolvedValue({
159
+ dxpUrl: `${mockDomain}/__dxp/us/scv-deploy`,
160
+ tenant: mockTenant,
161
+ });
162
+ const handleActivateErrorSpy = jest.spyOn(activate, 'handleActivateError');
163
+ const program = (0, activate_1.default)();
164
+ yield program.parseAsync(createMockArgs());
165
+ expect(handleActivateErrorSpy).toHaveBeenCalledWith(403, {});
166
+ }));
141
167
  });
@@ -0,0 +1,5 @@
1
+ import { Command } from 'commander';
2
+ import { Logger } from '@squiz/dx-logger-lib';
3
+ export declare const logger: Logger;
4
+ declare const createDevCommand: () => Command;
5
+ export default createDevCommand;
@@ -0,0 +1,70 @@
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.logger = void 0;
16
+ const commander_1 = require("commander");
17
+ const dx_logger_lib_1 = require("@squiz/dx-logger-lib");
18
+ const definitions_1 = require("../../utils/definitions");
19
+ const path_1 = __importDefault(require("path"));
20
+ const server_1 = require("../../utils/server");
21
+ const parse_args_1 = require("../../utils/parse-args");
22
+ exports.logger = (0, dx_logger_lib_1.getLogger)({
23
+ name: 'layout-dev',
24
+ format: 'human',
25
+ });
26
+ const createDevCommand = () => {
27
+ const devCommand = new commander_1.Command()
28
+ .name('dev')
29
+ .description('Start a development server for page layouts')
30
+ .option('--config <string>', 'File path to the page layout config file', './page-layout.yaml')
31
+ .option('--port <number>', 'Port to run the development server on', '4040')
32
+ .option('--no-open', 'Do not automatically open browser')
33
+ .option('--stylesheet <path>', 'Path to CSS file to include')
34
+ .option('--zones <items>', 'Zone content mappings', parse_args_1.parseZonesList, {})
35
+ .option('--options <items>', 'Layout options', parse_args_1.parseOptionsList, {})
36
+ .allowUnknownOption(true)
37
+ .allowExcessArguments(true)
38
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
39
+ try {
40
+ // Load layout definition
41
+ exports.logger.info(`Loading layout definition from ${options.config}`);
42
+ const rawLayoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(options.config);
43
+ if (!rawLayoutDefinition) {
44
+ throw new Error(`Failed to load layout definition from ${options.config}`);
45
+ }
46
+ // Confirm for entry property
47
+ const layoutDefinition = Object.assign({}, rawLayoutDefinition);
48
+ exports.logger.info('Starting development server...');
49
+ yield (0, server_1.startDevServer)({
50
+ configPath: path_1.default.resolve(options.config),
51
+ layoutDefinition,
52
+ zoneContent: options.zones,
53
+ layoutOptions: options.options,
54
+ stylesheet: options.stylesheet
55
+ ? path_1.default.resolve(options.stylesheet)
56
+ : undefined,
57
+ port: parseInt(options.port) || 4040,
58
+ openBrowser: options.open !== false,
59
+ });
60
+ }
61
+ catch (error) {
62
+ if (error.stack && process.env.DEBUG) {
63
+ console.error(error.stack);
64
+ }
65
+ exports.logger.error(error.message || 'An unknown error occurred');
66
+ }
67
+ }));
68
+ return devCommand;
69
+ };
70
+ exports.default = createDevCommand;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,162 @@
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
+ jest.mock('../../utils/definitions');
16
+ jest.mock('../../utils/server');
17
+ jest.mock('@squiz/dx-logger-lib', () => ({
18
+ getLogger: () => {
19
+ return {
20
+ info: mockLoggerInfoFn,
21
+ error: mockLoggerErrorFn,
22
+ warn: jest.fn(),
23
+ };
24
+ },
25
+ }));
26
+ const mockLoggerInfoFn = jest.fn();
27
+ const mockLoggerErrorFn = jest.fn();
28
+ const dev_1 = __importDefault(require("./dev"));
29
+ const definitions_1 = require("../../utils/definitions");
30
+ const server_1 = require("../../utils/server");
31
+ const path_1 = __importDefault(require("path"));
32
+ const originalProcessArgv = process.argv;
33
+ function createMockArgs(opts) {
34
+ const args = ['node', 'dxp-cli', 'dev'];
35
+ if (opts.config)
36
+ args.push('--config', opts.config);
37
+ if (opts.port)
38
+ args.push('--port', opts.port.toString());
39
+ if (opts.open === false)
40
+ args.push('--no-open');
41
+ if (opts.stylesheet)
42
+ args.push('--stylesheet', opts.stylesheet);
43
+ if (opts.zoneContent) {
44
+ Object.entries(opts.zoneContent).forEach(([zoneName, filePaths]) => {
45
+ filePaths.forEach(filePath => {
46
+ args.push(`--zones=${zoneName}=${filePath}`);
47
+ });
48
+ });
49
+ }
50
+ if (opts.layoutOptions) {
51
+ Object.entries(opts.layoutOptions).forEach(([optionName, value]) => {
52
+ args.push(`--options=${optionName}=${value}`);
53
+ });
54
+ }
55
+ return args;
56
+ }
57
+ describe('devCommand', () => {
58
+ beforeEach(() => {
59
+ jest.clearAllMocks();
60
+ process.argv = originalProcessArgv;
61
+ });
62
+ it('correctly handles command option defaults', () => __awaiter(void 0, void 0, void 0, function* () {
63
+ const program = (0, dev_1.default)();
64
+ const args = createMockArgs({});
65
+ process.argv = args;
66
+ yield program.parseAsync(args);
67
+ const opts = program.opts();
68
+ expect(opts.config).toEqual('./page-layout.yaml');
69
+ expect(opts.port).toEqual('4040');
70
+ expect(opts.open).toEqual(true);
71
+ expect(opts.stylesheet).toBeUndefined();
72
+ }));
73
+ it('correctly handles command arguments', () => __awaiter(void 0, void 0, void 0, function* () {
74
+ const config = './src/__tests__/layout.yaml';
75
+ const port = '3000';
76
+ const open = false;
77
+ const stylesheet = './src/__tests__/styles.css';
78
+ const program = (0, dev_1.default)();
79
+ const args = createMockArgs({
80
+ config,
81
+ port,
82
+ open,
83
+ stylesheet,
84
+ });
85
+ process.argv = args;
86
+ yield program.parseAsync(args);
87
+ const opts = program.opts();
88
+ expect(opts.config).toEqual(config);
89
+ expect(opts.port).toEqual(port);
90
+ expect(opts.open).toEqual(open);
91
+ expect(opts.stylesheet).toEqual(stylesheet);
92
+ }));
93
+ it('loads the layout definition and starts the server', () => __awaiter(void 0, void 0, void 0, function* () {
94
+ const config = './src/__tests__/layout.yaml';
95
+ const mockLayout = { name: 'Test Layout' };
96
+ definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
97
+ const program = (0, dev_1.default)();
98
+ const args = createMockArgs({ config });
99
+ process.argv = args;
100
+ yield program.parseAsync(args);
101
+ expect(definitions_1.loadLayoutDefinition).toHaveBeenCalledWith(config);
102
+ expect(mockLoggerInfoFn).toHaveBeenNthCalledWith(1, `Loading layout definition from ${config}`);
103
+ expect(mockLoggerInfoFn).toHaveBeenNthCalledWith(2, 'Starting development server...');
104
+ expect(server_1.startDevServer).toHaveBeenCalledWith(expect.objectContaining({
105
+ configPath: path_1.default.resolve(config),
106
+ layoutDefinition: mockLayout,
107
+ }));
108
+ }));
109
+ it('parses and passes zone content to the server', () => __awaiter(void 0, void 0, void 0, function* () {
110
+ const mockLayout = { name: 'Test Layout' };
111
+ definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
112
+ const zoneContent = {
113
+ main: ['./example.main.0.html', './example.main.1.html'],
114
+ aside: ['./example.aside.html'],
115
+ };
116
+ const program = (0, dev_1.default)();
117
+ const args = createMockArgs({ zoneContent });
118
+ process.argv = args;
119
+ yield program.parseAsync(args);
120
+ expect(server_1.startDevServer).toHaveBeenCalledWith(expect.objectContaining({
121
+ zoneContent,
122
+ }));
123
+ }));
124
+ it('parses and passes layout options to the server', () => __awaiter(void 0, void 0, void 0, function* () {
125
+ const mockLayout = { name: 'Test Layout' };
126
+ definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
127
+ const layoutOptions = {
128
+ sizing: 'large',
129
+ theme: 'dark',
130
+ };
131
+ const program = (0, dev_1.default)();
132
+ const args = createMockArgs({ layoutOptions });
133
+ process.argv = args;
134
+ yield program.parseAsync(args);
135
+ expect(server_1.startDevServer).toHaveBeenCalledWith(expect.objectContaining({
136
+ layoutOptions,
137
+ }));
138
+ }));
139
+ it('handles failure to load layout definition', () => __awaiter(void 0, void 0, void 0, function* () {
140
+ const config = './src/__tests__/layout.yaml';
141
+ definitions_1.loadLayoutDefinition.mockResolvedValue(undefined);
142
+ const originalError = mockLoggerErrorFn;
143
+ mockLoggerErrorFn.mockClear();
144
+ const program = (0, dev_1.default)();
145
+ const args = createMockArgs({ config });
146
+ process.argv = args;
147
+ yield program.parseAsync(args);
148
+ expect(mockLoggerErrorFn).toHaveBeenCalledWith(expect.stringContaining(`Failed to load layout definition from ${config}`));
149
+ }));
150
+ it('handles server startup error', () => __awaiter(void 0, void 0, void 0, function* () {
151
+ const mockLayout = { name: 'Test Layout' };
152
+ definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
153
+ server_1.startDevServer.mockRejectedValue(new Error('Server start failed'));
154
+ const originalError = mockLoggerErrorFn;
155
+ mockLoggerErrorFn.mockClear();
156
+ const program = (0, dev_1.default)();
157
+ const args = createMockArgs({});
158
+ process.argv = args;
159
+ yield program.parseAsync(args);
160
+ expect(mockLoggerErrorFn).toHaveBeenCalledWith(expect.stringContaining('Server start failed'));
161
+ }));
162
+ });
@@ -5,11 +5,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const commander_1 = require("commander");
7
7
  const deploy_1 = __importDefault(require("./deploy/deploy"));
8
+ const dev_1 = __importDefault(require("./dev/dev"));
8
9
  const createLayoutsCommand = () => {
9
10
  const layoutsCommand = new commander_1.Command('layouts');
10
11
  layoutsCommand
11
12
  .description('Page Contents Layouts Commands')
12
- .addCommand((0, deploy_1.default)());
13
+ .addCommand((0, deploy_1.default)())
14
+ .addCommand((0, dev_1.default)());
13
15
  return layoutsCommand;
14
16
  };
15
17
  exports.default = createLayoutsCommand;
@@ -6,10 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const commander_1 = require("commander");
7
7
  const layouts_1 = __importDefault(require("./layouts"));
8
8
  const deploy_1 = __importDefault(require("./deploy/deploy"));
9
+ const dev_1 = __importDefault(require("./dev/dev"));
9
10
  jest.mock('./deploy/deploy');
10
11
  deploy_1.default.mockImplementation(() => {
11
12
  return new commander_1.Command('mock-deploy');
12
13
  });
14
+ jest.mock('./dev/dev');
15
+ dev_1.default.mockImplementation(() => {
16
+ return new commander_1.Command('mock-dev');
17
+ });
13
18
  describe('createLayoutsCommand', () => {
14
19
  it('should create a layouts command with deploy subcommand', () => {
15
20
  const layoutsCommand = (0, layouts_1.default)();
@@ -18,5 +23,7 @@ describe('createLayoutsCommand', () => {
18
23
  expect(layoutsCommand.description()).toBe('Page Contents Layouts Commands');
19
24
  expect(deploy_1.default).toHaveBeenCalled();
20
25
  expect(layoutsCommand.commands.map(cmd => cmd.name())).toContain('mock-deploy');
26
+ expect(dev_1.default).toHaveBeenCalled();
27
+ expect(layoutsCommand.commands.map(cmd => cmd.name())).toContain('mock-dev');
21
28
  });
22
29
  });
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  export declare function loadLayoutDefinition(layoutFile: string): Promise<LayoutDefinition>;
3
+ export declare function loadLayoutFromFile(layoutFile: string): Promise<InputLayoutDefinition>;
3
4
  export declare const BaseLayoutDefinition: z.ZodObject<{
4
5
  /**
5
6
  * User defined identifier for a Page Layout
@@ -43,7 +43,7 @@ var __rest = (this && this.__rest) || function (s, e) {
43
43
  return t;
44
44
  };
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.LayoutDefinition = exports.InputLayoutDefinition = exports.BaseLayoutDefinition = exports.loadLayoutDefinition = void 0;
46
+ exports.LayoutDefinition = exports.InputLayoutDefinition = exports.BaseLayoutDefinition = exports.loadLayoutFromFile = exports.loadLayoutDefinition = void 0;
47
47
  const fs = __importStar(require("node:fs/promises"));
48
48
  const path = __importStar(require("node:path"));
49
49
  const zod_1 = require("zod");
@@ -82,6 +82,7 @@ function loadLayoutFromFile(layoutFile) {
82
82
  }
83
83
  });
84
84
  }
85
+ exports.loadLayoutFromFile = loadLayoutFromFile;
85
86
  function loadTemplate(layoutDirectory, templateFile) {
86
87
  return __awaiter(this, void 0, void 0, function* () {
87
88
  try {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Parse zones list from command line
3
+ * Supports multiple formats:
4
+ * - Single pair: 'main=./main.html' or 'main:./main.html'
5
+ * - Comma-separated list: 'main=./main.html,aside=./aside.html' or 'main:./main.html,aside:./aside.html'
6
+ * - Multiple --zones flags: merged together
7
+ *
8
+ * @param value Current value being processed
9
+ * @param previous Previously processed value (used for multiple flags)
10
+ * @returns Record of zone names to file paths
11
+ */
12
+ export declare function parseZonesList(value: string, previous: Record<string, string[]>): {
13
+ [x: string]: string[];
14
+ };
15
+ /**
16
+ * Parse options list from command line
17
+ * Supports multiple formats:
18
+ * - Single pair: 'key=value' or 'key:value'
19
+ * - Comma-separated list: 'key1=value1,key2=value2' or 'key1:value1,key2:value2'
20
+ * - Multiple --options flags: later values override earlier ones
21
+ *
22
+ * @param value Current value being processed
23
+ * @param previous Previously processed value (used for multiple flags)
24
+ * @returns Record of option names to values
25
+ */
26
+ export declare function parseOptionsList(value: string, previous: Record<string, string>): {
27
+ [x: string]: string;
28
+ };
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseOptionsList = exports.parseZonesList = void 0;
4
+ /**
5
+ * Parse zones list from command line
6
+ * Supports multiple formats:
7
+ * - Single pair: 'main=./main.html' or 'main:./main.html'
8
+ * - Comma-separated list: 'main=./main.html,aside=./aside.html' or 'main:./main.html,aside:./aside.html'
9
+ * - Multiple --zones flags: merged together
10
+ *
11
+ * @param value Current value being processed
12
+ * @param previous Previously processed value (used for multiple flags)
13
+ * @returns Record of zone names to file paths
14
+ */
15
+ function parseZonesList(value, previous) {
16
+ const result = Object.assign({}, previous);
17
+ if (!value)
18
+ return result;
19
+ // Split comma-separated values if present
20
+ const parts = value.includes(',') ? value.split(',') : [value];
21
+ for (const part of parts) {
22
+ let zoneName;
23
+ let filePath;
24
+ // Handle both colon and equals separators
25
+ if (part.includes('=')) {
26
+ [zoneName, filePath] = part.split('=');
27
+ }
28
+ else if (part.includes(':')) {
29
+ [zoneName, filePath] = part.split(':');
30
+ }
31
+ else {
32
+ continue; // Skip invalid format
33
+ }
34
+ // Skip empty zone names or file paths
35
+ if (!zoneName || !filePath) {
36
+ continue;
37
+ }
38
+ if (!result[zoneName]) {
39
+ result[zoneName] = [];
40
+ }
41
+ result[zoneName].push(filePath);
42
+ }
43
+ return result;
44
+ }
45
+ exports.parseZonesList = parseZonesList;
46
+ /**
47
+ * Parse options list from command line
48
+ * Supports multiple formats:
49
+ * - Single pair: 'key=value' or 'key:value'
50
+ * - Comma-separated list: 'key1=value1,key2=value2' or 'key1:value1,key2:value2'
51
+ * - Multiple --options flags: later values override earlier ones
52
+ *
53
+ * @param value Current value being processed
54
+ * @param previous Previously processed value (used for multiple flags)
55
+ * @returns Record of option names to values
56
+ */
57
+ function parseOptionsList(value, previous) {
58
+ const result = Object.assign({}, previous);
59
+ if (!value)
60
+ return result;
61
+ // Split comma-separated values if present
62
+ const parts = value.includes(',') ? value.split(',') : [value];
63
+ for (const part of parts) {
64
+ let optionName;
65
+ let optionValue;
66
+ // Handle both colon and equals separators
67
+ if (part.includes('=')) {
68
+ [optionName, optionValue] = part.split('=');
69
+ }
70
+ else if (part.includes(':')) {
71
+ [optionName, optionValue] = part.split(':');
72
+ }
73
+ else {
74
+ continue; // Skip invalid format
75
+ }
76
+ // Skip empty option names or values
77
+ if (!optionName || !optionValue) {
78
+ continue;
79
+ }
80
+ result[optionName] = optionValue;
81
+ }
82
+ return result;
83
+ }
84
+ exports.parseOptionsList = parseOptionsList;
@@ -0,0 +1 @@
1
+ export {};