@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.
- package/lib/auth/login/login.js +1 -1
- package/lib/cdp/instance/activate/activate.js +2 -13
- package/lib/cdp/instance/activate/activate.spec.js +87 -61
- 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
package/lib/auth/login/login.js
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
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();
|
|
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(
|
|
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(
|
|
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,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 {};
|