@pega/dx-component-builder-sdk 0.1.24 → 0.1.25

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.
@@ -1,24 +1,98 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
2
  import { getPegaServerConfig } from '../../util.js';
3
3
 
4
- export const getAuthQuestions = async () => {
5
- const defaultPegaConfig = await getPegaServerConfig();
4
+ import { AUTHENTICATE_SCHEMA } from '../../constants.js';
5
+
6
+ export const getAuthQuestions = async (dxcbConfig) => {
7
+ const defaultPegaConfig = dxcbConfig;
8
+ const aMustAsk = dxcbConfig['questions_askAlways'] ? dxcbConfig['questions_askAlways'].split(',') : [];
9
+ const aNeverAsk = dxcbConfig['questions_askNever'] ? dxcbConfig['questions_askNever'].split(',') : [];
10
+
11
+ const initAnswers = (answers) => {
12
+ // Setup default answers
13
+ answers.serverType = defaultPegaConfig.serverType || "infinity";
14
+ answers.server = defaultPegaConfig.server || undefined;
15
+ answers.isolationId = defaultPegaConfig.isolationId || undefined;
16
+ answers.clientId = defaultPegaConfig.clientId || undefined;
17
+ answers.grantType = defaultPegaConfig.grantType || "authCode";
18
+ answers.clientSecret = defaultPegaConfig.clientSecret || undefined;
19
+ answers.user = defaultPegaConfig.user || undefined;
20
+ answers.password = defaultPegaConfig.password || undefined;
21
+ }
22
+
23
+ const fnStandardWhen = (prop, answers) => {
24
+ if (aNeverAsk.includes(prop)) {
25
+ return false;
26
+ }
27
+ return aMustAsk.includes(prop) || !answers[prop];
28
+ };
6
29
 
7
30
  return [
31
+ {
32
+ name: 'serverType',
33
+ type: 'rawlist',
34
+ message: 'Enter Pega server type',
35
+ default: answers => {
36
+ return defaultPegaConfig.serverType;
37
+ },
38
+ choices: AUTHENTICATE_SCHEMA.serverType,
39
+ validate: value => {
40
+ if (value.trim()) {
41
+ return true;
42
+ }
43
+ return 'Pega server type cannot be empty';
44
+ },
45
+ when: answers => {
46
+ initAnswers(answers);
47
+ return fnStandardWhen('serverType', answers);
48
+ }
49
+ },
8
50
  {
9
51
  name: 'server',
10
52
  message: 'Enter pega server url',
11
- default: defaultPegaConfig.server
53
+ default: defaultPegaConfig.server,
54
+ when: answers =>{
55
+ return fnStandardWhen('server', answers);
56
+ }
57
+ },
58
+ {
59
+ name: 'isolationId',
60
+ message: 'Enter isolation id',
61
+ default: defaultPegaConfig.isolationId,
62
+ when: answers => {
63
+ return answers.serverType === "launchpad" && fnStandardWhen('isolationId', answers);
64
+ }
65
+ },
66
+ {
67
+ name: 'grantType',
68
+ type: 'rawlist',
69
+ message: 'Enter OAuth 2.0 grant type',
70
+ default: defaultPegaConfig.grantType,
71
+ choices: answers => {
72
+ return AUTHENTICATE_SCHEMA.grantType.filter(gt => !(answers.serverType === "launchpad" && gt.value === "passwordCreds"));
73
+ },
74
+ validate: value => {
75
+ if (value.trim()) {
76
+ return true;
77
+ }
78
+ return 'Grant type cannot be empty';
79
+ },
80
+ when: answers => {
81
+ return fnStandardWhen('grantType', answers);
82
+ }
12
83
  },
13
84
  {
14
85
  name: 'user',
15
86
  message: 'Enter user id',
16
87
  default: defaultPegaConfig.user,
17
- validate: value => {
18
- if (value.trim()) {
88
+ validate: (value, answers) => {
89
+ if (value.trim() || answers.grantType === "authCode") {
19
90
  return true;
20
91
  }
21
92
  return 'Username cannot be empty';
93
+ },
94
+ when: answers => {
95
+ return (answers.serverType === "infinity" && answers.grantType === "passwordCreds") && fnStandardWhen('user', answers);
22
96
  }
23
97
  },
24
98
  {
@@ -26,11 +100,14 @@ export const getAuthQuestions = async () => {
26
100
  type: 'password',
27
101
  message: 'Enter password',
28
102
  default: defaultPegaConfig.password,
29
- validate: value => {
30
- if (value.trim()) {
103
+ validate: (value, answers) => {
104
+ if (value.trim() || answers.grantType === "authCode") {
31
105
  return true;
32
106
  }
33
107
  return 'Password cannot be empty';
108
+ },
109
+ when: answers => {
110
+ return (answers.user && (answers.serverType === "infinity" && answers.grantType === "passwordCreds") && fnStandardWhen('password', answers));
34
111
  }
35
112
  },
36
113
  {
@@ -42,6 +119,9 @@ export const getAuthQuestions = async () => {
42
119
  return true;
43
120
  }
44
121
  return 'clientId cannot be empty';
122
+ },
123
+ when: answers => {
124
+ return fnStandardWhen('clientId', answers);
45
125
  }
46
126
  },
47
127
  {
@@ -49,11 +129,14 @@ export const getAuthQuestions = async () => {
49
129
  type: 'password',
50
130
  message: 'Enter clientSecret',
51
131
  default: defaultPegaConfig.clientSecret,
52
- validate: value => {
132
+ validate: (value, answers) => {
53
133
  if (value.trim()) {
54
134
  return true;
55
135
  }
56
136
  return 'clientSecret cannot be empty';
137
+ },
138
+ when: answers => {
139
+ return (answers.grantType !== "authCode" && fnStandardWhen('clientSecret', answers));
57
140
  }
58
141
  }
59
142
  ];
@@ -1,90 +1,166 @@
1
1
  import path from 'path';
2
- import fs from 'fs';
3
- import https from 'https';
2
+ import fs from 'node:fs';
4
3
 
5
4
  import chalk from 'chalk';
6
5
  import inquirer from 'inquirer';
7
- import fetch from 'node-fetch';
8
6
 
9
- import { checkPathAccess, getPegaServerConfig, getTokenUrl, showVersion } from '../../util.js';
10
- import { TOKEN_PATH, SDK_CONFIG_JSON_FILENAME, SERVER_CONFIG, SERVER_REST_URL } from '../../constants.js';
7
+ import { checkPathAccess, showVersion } from '../../util.js';
8
+ import { TOKEN_PATH, TOKEN_URL_V1, AUTHORIZE_URL_V1, LP_TOKEN_URL, LP_AUTHORIZE_URL, SDK_CONFIG_JSON_FILENAME,
9
+ SERVER_CONFIG, DXCB_CONFIG, DXCB_PUBLISH_ACCESS, SERVER_TYPE, SERVER_REST_URL, LP_SERVER_REST_URL } from '../../constants.js';
11
10
 
12
11
  import { getAuthQuestions } from './helper.js';
13
-
12
+ import PegaAuth from '../../auth.js';
14
13
 
15
14
  const pegaConfigJsonPath = path.join(path.resolve(), SDK_CONFIG_JSON_FILENAME);
15
+ let server = null;
16
16
 
17
17
  const generateAccessToken = async config => {
18
- const formData = new URLSearchParams();
19
- formData.append('client_id', config.clientId);
20
- formData.append('client_secret', config.clientSecret);
21
- formData.append('username', config.user);
22
- formData.append('password', config.password);
23
- formData.append('grant_type', 'password');
24
-
25
- const accessTokenUri = await getTokenUrl(config.server);
26
- fetch(accessTokenUri, {
27
- agent: new https.Agent({ rejectUnauthorized: false }),
28
- method: 'POST',
29
- headers: { 'content-type': 'application/x-www-form-urlencoded' },
30
- body: formData.toString()
31
- })
32
- .then(response => {
33
- if (response.ok && response.status === 200) {
34
- return response.json();
35
- } else {
36
- throw new Error(`Failed with status: ${response.status}`);
37
- }
38
- })
39
- .then(data => {
40
- if (data && data.errors) {
41
- const errMessages = data.errors;
42
- errMessages.forEach(msgArr => {
43
- throw new Error(`Failed with error - ${JSON.stringify(msgArr.message)}`);
44
- });
45
- }
46
- if (data.token_type) {
47
- fs.writeFile(TOKEN_PATH, JSON.stringify(data), err => {
48
- if (err) {
49
- console.error(err);
50
- }
51
- console.log(`${chalk.bold.green('Authenticated successfully !!')}`);
52
- });
18
+
19
+ let authSuccess = false;
20
+ const { serverType, isolationId, grantType, clientId, clientSecret, user, password } = config;
21
+ // Try to gracefully deal with missing trailing slashes for server (and later conditionally for redirectUri)
22
+ const server = config.server.endsWith('/') ? config.server : `${config.server}/`;
23
+
24
+ return new Promise((resolve, reject) => {
25
+ const sTokenUri = `${server}${serverType === 'infinity' ? TOKEN_URL_V1 : LP_TOKEN_URL}`;
26
+ const sAuthorizeUri = `${server}${serverType === 'infinity' ? AUTHORIZE_URL_V1 : LP_AUTHORIZE_URL}`;
27
+ // console.log(`grantType: ${config.grantType}`);
28
+
29
+ const authCodeConfig = grantType === 'authCode' ? {
30
+ authorizeUri: sAuthorizeUri,
31
+ redirectUri: config.redirectUri.endsWith('/') ? config.redirectUri : `${config.redirectUri}/`,
32
+ cert: config.cert,
33
+ key: config.key,
34
+ winTitle: 'DX Component Dev',
35
+ winBodyHtml: `Authentication is complete. This browser window may be closed.`
36
+ } : {};
37
+
38
+ // Build auth config structure
39
+ const authConfig = {
40
+ serverType: serverType,
41
+ grantType: grantType,
42
+ tokenUri: sTokenUri,
43
+ clientId: clientId,
44
+ clientSecret: clientSecret,
45
+ ignoreInvalidCerts: true,
46
+ ...authCodeConfig
47
+ };
48
+ if (serverType === 'infinity') {
49
+ authConfig.authService = 'pega';
50
+ if (grantType !== 'clientCreds' && user && password) {
51
+ authConfig.userIdentifier = user;
52
+ authConfig.password = Buffer.from(password).toString('base64');
53
53
  }
54
- })
55
- .catch(e => console.log(`${chalk.bold.red(e)}`));
54
+ }
55
+ if (serverType === 'launchpad') {
56
+ authConfig.isolationId = isolationId;
57
+ // authConfig.noPKCE = true;
58
+ }
59
+ const auth = new PegaAuth(authConfig);
60
+ // console.log(`...about to invoke auth.login\n${JSON.stringify(authConfig)}`);
61
+
62
+ auth.login()
63
+ .then((token) => {
64
+ if (token?.token_type) {
65
+ fs.writeFile(TOKEN_PATH, JSON.stringify(token), err => {
66
+ if (err) {
67
+ console.error(err);
68
+ return reject(authSuccess);
69
+ }
70
+ console.log(`${chalk.bold.green('Authenticated successfully !!')}`);
71
+ authSuccess = true;
72
+ return resolve(authSuccess);
73
+ });
74
+ } else {
75
+ console.log(`${chalk.bold.red('Authentication failed:' + JSON.stringify(token.errors ? token.errors : token))}`);
76
+ return reject(authSuccess);
77
+ }
78
+ })
79
+ .catch((e) => {
80
+ console.log(`${chalk.bold.red(e)}`);
81
+ return reject(authSuccess);
82
+ });
83
+ });
56
84
  };
57
85
 
58
86
  export default async options => {
87
+
88
+ let authOK = false;
89
+
59
90
  showVersion();
60
91
 
61
92
  await checkPathAccess(pegaConfigJsonPath);
62
93
 
63
- let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
64
- data = data && JSON.parse(data);
94
+ let cfgData = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
95
+ let data = null;
96
+ if (cfgData) {
97
+ try {
98
+ data = JSON.parse(cfgData);
99
+ } catch (e) {
100
+ const msg = `Invalid JSON within ${SDK_CONFIG_JSON_FILENAME}`;
101
+ console.log(`${chalk.red(msg)}`);
102
+ process.exit(1);
103
+ }
104
+ }
65
105
 
66
106
  if (!data[SERVER_CONFIG] || !data[SERVER_CONFIG][SERVER_REST_URL]) {
67
107
  console.error(
68
- `${chalk.red.bold('ERROR')} Could not able find publish access path in config.json`
108
+ `${chalk.red.bold('ERROR')} ${SERVER_CONFIG}.${SERVER_REST_URL} property not found within ${SDK_CONFIG_JSON_FILENAME}`
69
109
  );
70
110
  process.exit(1);
71
111
  }
72
112
 
73
- //const serverConfig = data[SERVER_CONFIG][SERVER_REST_URL];
74
- const serverConfig = await getPegaServerConfig();
75
- const params = options.params;
113
+ // Note: serverConfig below is actually dxcbConfig.
114
+ const serverConfig = data[DXCB_CONFIG][DXCB_PUBLISH_ACCESS];
115
+ serverConfig.serverType = data[SERVER_CONFIG][SERVER_TYPE] || "infinity";
116
+ serverConfig.server = data[SERVER_CONFIG][serverConfig.serverType === "infinity" ? SERVER_REST_URL : LP_SERVER_REST_URL];
117
+ const isPwdNonEmpty = serverConfig['password'] ? true : false;
76
118
 
77
- const questions = await getAuthQuestions(params);
119
+ const questions = await getAuthQuestions(serverConfig);
78
120
  const answers = await inquirer.prompt(questions);
79
- serverConfig.server = answers.server;
80
- serverConfig.user = answers.user;
81
- serverConfig.password = answers.password;
82
- serverConfig.clientId = answers.clientId;
83
- serverConfig.clientSecret = answers.clientSecret;
84
- await generateAccessToken(serverConfig);
85
-
86
- data = JSON.stringify(data, null, 2);
87
- fs.writeFile(pegaConfigJsonPath, data, err => {
88
- if (err) throw err;
89
- });
121
+ const aProps = ['serverType','server','isolationId','clientId','grantType','clientSecret','user','password'];
122
+ const isLaunchPad = answers.serverType === "launchpad";
123
+ let bWriteUpdate = false;
124
+ for (let i = 0; i < aProps.length; i += 1) {
125
+ const prop = aProps[i];
126
+ if (serverConfig[prop] !== answers[prop]) {
127
+ // console.log(`property ${prop} changed from ${serverConfig[prop]} to ${answers[prop]}`);
128
+ serverConfig[prop] = answers[prop];
129
+ if (prop !== 'password' || isPwdNonEmpty) {
130
+ bWriteUpdate = true;
131
+ }
132
+ if (prop === 'server') {
133
+ data[SERVER_CONFIG][isLaunchPad ? LP_SERVER_REST_URL : SERVER_REST_URL] = answers.server;
134
+ }
135
+ }
136
+ }
137
+
138
+ try {
139
+ authOK = await generateAccessToken(serverConfig);
140
+ } catch (e) {
141
+ }
142
+
143
+ delete serverConfig.serverType;
144
+ delete serverConfig.server;
145
+
146
+ if (authOK & bWriteUpdate) {
147
+ // Don't write password value (unless it already had a value)
148
+ if (!isPwdNonEmpty) {
149
+ serverConfig['password'] = '';
150
+ }
151
+ console.log(`${chalk.green('Writing updated config...')}`);
152
+ let updCfg = null;
153
+ try {
154
+ updCfg = JSON.stringify(data, null, 2);
155
+ // console.log(`updated config: ${updCfg}`);
156
+ fs.writeFileSync(pegaConfigJsonPath, updCfg, err => {
157
+ if (err) throw err;
158
+ });
159
+ } catch (e) {
160
+
161
+ }
162
+ }
163
+
164
+ process.exit(1);
165
+
90
166
  };
@@ -45,7 +45,12 @@ const copyComponentTemplate = async options => {
45
45
  }
46
46
  else {
47
47
  return copy(options.templateDirectoryConstellation, options.targetDirectoryConstellation, {
48
- clobber: false
48
+ clobber: false,
49
+ filter: (fileName) => {
50
+ // filter out the file config.json or config.json.mustache
51
+ if (fileName.indexOf("config.json") >= 0) return false;
52
+ else return true;
53
+ }
49
54
  });
50
55
  }
51
56
 
@@ -0,0 +1,183 @@
1
+ import { useState } from 'react';
2
+ import {{COMPONENT_CLASS_NAME}} from './index.tsx';
3
+ import { pyReviewRaw } from './mock.stories';
4
+ import { TextField } from '@material-ui/core';
5
+ import MuiPhoneNumber from 'material-ui-phone-number';
6
+ import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers';
7
+ import DayjsUtils from '@date-io/dayjs';
8
+
9
+ export default {
10
+ title: '{{COMPONENT_CLASS_NAME}}',
11
+ component: {{COMPONENT_CLASS_NAME}}
12
+ };
13
+
14
+ const renderField = resolvedProps => {
15
+ const { displayMode, value = '', label = '', onChange} = resolvedProps;
16
+
17
+ const variant = displayMode === 'LABELS_LEFT' ? 'inline' : 'stacked';
18
+ let val = '';
19
+ if (label === 'Service Date')
20
+ val = <MuiPickersUtilsProvider utils={DayjsUtils}><KeyboardDatePicker
21
+ variant='inline'
22
+ inputVariant='outlined'
23
+ placeholder='mm/dd/yyyy'
24
+ fullWidth
25
+ format='MM/DD/YYYY'
26
+ mask='__/__/____'
27
+ size='small'
28
+ label={label}
29
+ value={value || null}
30
+ onChange={onChange}
31
+ />
32
+ </MuiPickersUtilsProvider>
33
+
34
+ {{! pick new delimiteres for mustache }}
35
+ {{=<% %>=}}
36
+
37
+ if (label === 'Email')
38
+ val = <TextField type='email' value={value} size='small' variant='outlined' style={{ fontSize: '14px' }} label={label} onChange={onChange}></TextField>;
39
+
40
+ if (label === 'First Name' || label === 'Last Name' || label === 'Middle Name')
41
+ val = <TextField value={value} size='small' variant='outlined' style={{ fontSize: '14px' }} label={label} onChange={onChange}></TextField>;
42
+
43
+ if (label === 'Phone Number')
44
+ val = <MuiPhoneNumber value={value} variant='outlined' size='small' style={{ fontSize: '14px' }} defaultCountry='us' label={label} onChange={onChange}></MuiPhoneNumber>;
45
+
46
+ <%={{ }}=%> {{! revert delimiteres for mustache }}
47
+
48
+ if (variant === 'inline') {
49
+ val = value || <span aria-hidden='true'>&ndash;&ndash;</span>;
50
+ }
51
+
52
+ return val;
53
+ };
54
+
55
+ export const Base{{COMPONENT_CLASS_NAME}} = () => {
56
+ const [firstName, setFirstName] = useState('John');
57
+ const [middleName, setMiddleName] = useState('');
58
+ const [lastName, setLastName] = useState('Joe');
59
+ const [phone, setPhone] = useState('+16397975093');
60
+ const [serviceDate, setServiceDate] = useState('2023-01-25');
61
+ const [email, setEmail] = useState('john@doe.com');
62
+ const regionChildrenResolved = [
63
+ {
64
+ readOnly: undefined,
65
+ value: firstName,
66
+ label: 'First Name',
67
+ hasSuggestions: false,
68
+ onChange: val => {
69
+ setFirstName(val.target.value);
70
+ }
71
+ },
72
+ {
73
+ readOnly: undefined,
74
+ value: middleName,
75
+ label: 'Middle Name',
76
+ hasSuggestions: false,
77
+ onChange: val => {
78
+ setMiddleName(val.target.value);
79
+ }
80
+ },
81
+ {
82
+ readOnly: undefined,
83
+ value: lastName,
84
+ label: 'Last Name',
85
+ hasSuggestions: false,
86
+ onChange: val => {
87
+ setLastName(val.target.value);
88
+ }
89
+ },
90
+ {
91
+ readOnly: undefined,
92
+ value: email,
93
+ label: 'Email',
94
+ hasSuggestions: false,
95
+ onChange: val => {
96
+ setEmail(val.target.value);
97
+ }
98
+ },
99
+ {
100
+ readOnly: undefined,
101
+ value: phone,
102
+ label: 'Phone Number',
103
+ datasource: {
104
+ fields: {
105
+ value: undefined
106
+ },
107
+ source: [
108
+ {
109
+ value: '+1'
110
+ },
111
+ {
112
+ value: '+91'
113
+ },
114
+ {
115
+ value: '+48'
116
+ },
117
+ {
118
+ value: '+44'
119
+ }
120
+ ]
121
+ },
122
+ hasSuggestions: false,
123
+ onChange: val => {
124
+ setPhone(val);
125
+ }
126
+ },
127
+ {
128
+ readOnly: undefined,
129
+ value: serviceDate,
130
+ label: 'Service Date',
131
+ hasSuggestions: false,
132
+ onChange: date => {
133
+ const changeValue = date && date.isValid() ? date.toISOString() : null;
134
+ setServiceDate(changeValue);
135
+ }
136
+
137
+ }
138
+ ];
139
+ const props = {
140
+ template: 'DefaultForm',
141
+ getPConnect: () => {
142
+ return {
143
+ getChildren: () => {
144
+ return pyReviewRaw.children;
145
+ },
146
+ createComponent: config => {
147
+ switch (config.config.value) {
148
+ case '@P .FirstName':
149
+ return renderField(regionChildrenResolved[0]);
150
+ case '@P .MiddleName':
151
+ return renderField(regionChildrenResolved[1]);
152
+ case '@P .LastName':
153
+ return renderField(regionChildrenResolved[2]);
154
+ case '@P .Email':
155
+ return renderField(regionChildrenResolved[3]);
156
+ case '@P .PhoneNumber':
157
+ return renderField(regionChildrenResolved[4]);
158
+ case '@P .ServiceDate':
159
+ return renderField(regionChildrenResolved[5]);
160
+ default:
161
+ break;
162
+ }
163
+ }
164
+ };
165
+ }
166
+ };
167
+
168
+ const regionAChildren = pyReviewRaw.children[0].children.map(child => {
169
+ return props.getPConnect().createComponent(child);
170
+ });
171
+ const regionBChildren = pyReviewRaw.children[1].children.map(child => {
172
+ return props.getPConnect().createComponent(child);
173
+ });
174
+
175
+ return (
176
+ <>
177
+ <{{COMPONENT_CLASS_NAME}} {...props}>
178
+ {regionAChildren}
179
+ {regionBChildren}
180
+ </{{COMPONENT_CLASS_NAME}}>
181
+ </>
182
+ );
183
+ };