@squiz/component-cli-lib 1.39.1-alpha.3 → 1.39.1-alpha.30

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.
@@ -4,8 +4,8 @@
4
4
  },
5
5
  "devDependencies": {
6
6
  "autoprefixer": "^10.4.13",
7
- "esbuild": "^0.17.6",
8
- "esbuild-sass-plugin": "^2.4.5",
7
+ "esbuild": "^0.18.10",
8
+ "esbuild-sass-plugin": "^2.10.0",
9
9
  "postcss": "^8.4.21"
10
10
  }
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/component-cli-lib",
3
- "version": "1.39.1-alpha.3",
3
+ "version": "1.39.1-alpha.30",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -13,12 +13,12 @@
13
13
  "author": "",
14
14
  "license": "ISC",
15
15
  "devDependencies": {
16
- "@squiz/component-lib": "1.39.1-alpha.3",
17
- "@squiz/component-web-api-lib": "1.39.1-alpha.3",
18
- "@squiz/dx-common-lib": "1.39.1-alpha.3",
19
- "@squiz/dx-json-schema-lib": "1.39.1-alpha.3",
20
- "@squiz/dx-logger-lib": "1.39.1-alpha.3",
21
- "@squiz/virus-scanner-lib": "1.39.1-alpha.3",
16
+ "@squiz/component-lib": "1.39.1-alpha.30",
17
+ "@squiz/component-web-api-lib": "1.39.1-alpha.30",
18
+ "@squiz/dx-common-lib": "1.39.1-alpha.30",
19
+ "@squiz/dx-json-schema-lib": "1.39.1-alpha.30",
20
+ "@squiz/dx-logger-lib": "1.39.1-alpha.30",
21
+ "@squiz/virus-scanner-lib": "1.39.1-alpha.30",
22
22
  "@types/cli-color": "2.0.2",
23
23
  "@types/express": "4.17.17",
24
24
  "@types/jest": "28.1.8",
@@ -32,12 +32,12 @@
32
32
  "typescript": "4.9.4"
33
33
  },
34
34
  "dependencies": {
35
- "@squiz/render-runtime-lib": "1.39.1-alpha.3",
35
+ "@squiz/render-runtime-lib": "1.39.1-alpha.30",
36
36
  "archiver": "5.3.1",
37
37
  "axios": "1.3.2",
38
38
  "cli-color": "^2.0.2",
39
39
  "open": "^8.4.0",
40
40
  "supertest": "^6.2.3"
41
41
  },
42
- "gitHead": "5be5e45cfe2e31be51e8a09e2a1a934900ff29fe"
42
+ "gitHead": "f8c533a2f8e98ea5ff08c6b0206c4f292f47c4e8"
43
43
  }
@@ -10,8 +10,11 @@ jest.mock('path', () => {
10
10
  };
11
11
  });
12
12
 
13
+ const fsMocked = jest.mocked(fs);
14
+
13
15
  beforeEach(() => {
14
16
  jest.resetAllMocks();
17
+ fsMocked.readJSON.mockResolvedValue({});
15
18
 
16
19
  // Reset our process.env.NODE_ENV variable
17
20
  process.env.NODE_ENV = 'test';
@@ -200,4 +203,56 @@ describe('componentInit', () => {
200
203
  '/Users/username/Projects/squiz/new-component',
201
204
  );
202
205
  });
206
+
207
+ describe('prompts', () => {
208
+ it('should default when no prompts provided', async () => {
209
+ await componentInit({ componentType: 'basic', destination: '' });
210
+
211
+ expect(fsMocked.writeJSON).toHaveBeenCalledWith(
212
+ expect.any(String),
213
+ expect.objectContaining({
214
+ name: 'default',
215
+ namespace: 'default-namespace',
216
+ displayName: 'Default',
217
+ }),
218
+ expect.objectContaining({ spaces: 2 }),
219
+ );
220
+ });
221
+
222
+ it('should overwrite default values with provided values', async () => {
223
+ await componentInit({
224
+ componentType: 'basic',
225
+ destination: '',
226
+ prompts: {
227
+ name: 'my-component',
228
+ namespace: 'my-namespace',
229
+ displayName: 'My Component',
230
+ },
231
+ });
232
+
233
+ expect(fsMocked.writeJSON).toHaveBeenCalledWith(
234
+ expect.any(String),
235
+ expect.objectContaining({
236
+ name: 'my-component',
237
+ namespace: 'my-namespace',
238
+ displayName: 'My Component',
239
+ }),
240
+ expect.objectContaining({ spaces: 2 }),
241
+ );
242
+ });
243
+
244
+ it('should read and write the destination manifest file', async () => {
245
+ await componentInit({
246
+ componentType: 'basic',
247
+ destination: '/Users/username/Projects/squiz/new-component',
248
+ });
249
+
250
+ expect(fsMocked.readJSON).toHaveBeenCalledWith('/Users/username/Projects/squiz/new-component/manifest.json');
251
+ expect(fsMocked.writeJSON).toHaveBeenCalledWith(
252
+ '/Users/username/Projects/squiz/new-component/manifest.json',
253
+ expect.any(Object),
254
+ expect.objectContaining({ spaces: 2 }),
255
+ );
256
+ });
257
+ });
203
258
  });
@@ -2,17 +2,34 @@ import fse from 'fs-extra';
2
2
  import path from 'path';
3
3
  import { getLogger, LoggerOptions } from '@squiz/dx-logger-lib';
4
4
 
5
+ export interface InitPrompts {
6
+ name: string;
7
+ displayName: string;
8
+ description: string;
9
+ namespace: string;
10
+ }
11
+
12
+ const DEFAULT_PROMPTS: InitPrompts = {
13
+ name: 'default',
14
+ displayName: 'Default',
15
+ description: 'Default component',
16
+ namespace: 'default-namespace',
17
+ };
18
+
19
+ interface InitOptions {
20
+ componentType: 'basic' | 'advanced';
21
+ destination: string;
22
+ loggingFormat?: LoggerOptions['format'];
23
+ prompts?: Partial<InitPrompts>;
24
+ }
25
+
5
26
  /**
6
27
  * componentInit initializes a new component
7
28
  * @param {object} options - The options object
8
29
  * @param {string} options.componentType - The type of component to initialize
9
30
  * @returns {void}
10
31
  */
11
- export default async function componentInit(options: {
12
- componentType: 'basic' | 'advanced';
13
- destination: string;
14
- loggingFormat?: LoggerOptions['format'];
15
- }) {
32
+ export default async function componentInit(options: InitOptions) {
16
33
  const logger = getLogger({
17
34
  name: 'component-dev',
18
35
  format: options.loggingFormat || 'human',
@@ -43,6 +60,22 @@ export default async function componentInit(options: {
43
60
 
44
61
  // Copy the contents of the source folder to the destination folder
45
62
  await fse.copy(sourceFolder, destination);
63
+ await writeManifestVariables({ destination, prompts: Object.assign({}, DEFAULT_PROMPTS, options.prompts) });
46
64
 
47
65
  logger.info('Component initialized!');
48
66
  }
67
+
68
+ async function writeManifestVariables(options: { destination: string; prompts: InitPrompts }) {
69
+ const { destination, prompts } = options;
70
+
71
+ const manifestPath = path.resolve(destination, 'manifest.json');
72
+
73
+ const manifest = await fse.readJSON(manifestPath);
74
+
75
+ manifest.name = prompts.name;
76
+ manifest.displayName = prompts.displayName;
77
+ manifest.description = prompts.description;
78
+ manifest.namespace = prompts.namespace;
79
+
80
+ await fse.writeJSON(manifestPath, manifest, { spaces: 2 });
81
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { uploadComponentFolder } from './upload-component-folder';
2
2
  export { startDevelopmentRender } from './component-dev';
3
- export { default as componentInit } from './component-init';
3
+ export { default as componentInit, InitPrompts } from './component-init';
4
4
  export { uploadJobFolder } from './upload-job';
@@ -0,0 +1,3 @@
1
+ module.exports = async function (input, info) {
2
+ return `<div>component-html</div>`;
3
+ };
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "http://localhost:3000/schemas/v1.json#",
3
+ "name": "test-page-render",
4
+ "version": "1.0.0",
5
+ "mainFunction": "main",
6
+ "displayName": "some-display-name",
7
+ "namespace": "unit-test-components",
8
+ "description": "some-description",
9
+ "functions": [
10
+ {
11
+ "name": "main",
12
+ "entry": "main.js",
13
+ "input": {
14
+ "type": "object",
15
+ "properties": {
16
+ "text ": {
17
+ "type": "string"
18
+ }
19
+ },
20
+ "required": []
21
+ },
22
+ "output": {
23
+ "responseType": "html"
24
+ }
25
+ }
26
+ ]
27
+ }
@@ -9,12 +9,15 @@ import { ContentApi } from '@squiz/component-web-api-lib';
9
9
  import { config } from 'dotenv';
10
10
  import { execSync } from 'child_process';
11
11
  import { getLogger } from '@squiz/dx-logger-lib';
12
+ import { uploadComponentFolder } from '../upload-component-folder';
13
+ import { buildDevelopmentJwt, DxpServiceId } from '@squiz/dxp-auth-lib';
12
14
 
13
15
  config();
14
16
 
15
17
  interface Config {
16
18
  managementServiceUrl: string;
17
19
  renderServiceUrl: string;
20
+ pageRenderServiceUrl: string;
18
21
  contentServiceUrl: string;
19
22
  tenantId: string;
20
23
  jobServiceUrl: string;
@@ -24,6 +27,7 @@ interface Config {
24
27
 
25
28
  const configObj: Config = {
26
29
  managementServiceUrl: parseEnvVarForVar('COMPONENT_MANAGEMENT_SERVICE_URL').replace(/\/+$/, ''),
30
+ pageRenderServiceUrl: parseEnvVarForVar('PAGE_RENDER_SERVICE_URL').replace(/\/+$/, ''),
27
31
  renderServiceUrl: parseEnvVarForVar('COMPONENT_RENDER_SERVICE_URL').replace(/\/+$/, ''),
28
32
  contentServiceUrl: parseEnvVarForVar('CONTENT_API_URL').replace(/\/+$/, ''),
29
33
  tenantId: parseEnvVarForVar('TENANT_ID'),
@@ -42,46 +46,37 @@ if (!configObj.ci_buildBranch) {
42
46
 
43
47
  export default configObj;
44
48
 
45
- const DXP_COMPONENTS_SERVICE_NAME = 'dxpComponents';
46
- const DXP_CONTENT_STORE_SERVICE_NAME = 'dxpContentStore';
47
-
48
- const validToken = {
49
- organisationId: 'aa',
50
- permission: '',
51
- tenant: configObj.tenantId,
52
- tenantId: 'zz',
53
- userId: 'zz',
54
-
55
- service: {
56
- [DXP_COMPONENTS_SERVICE_NAME]: {
57
- privileges: [
58
- 'COMPONENT_DEPLOY',
59
- 'COMPONENT_DELETE',
60
- 'COMPONENT_READ',
61
- 'COMPONENT_SET_RULES_READ',
62
- 'COMPONENT_SET_RULES_WRITE',
63
-
64
- 'COMPONENT_SET_READ',
65
- 'COMPONENT_SET_WRITE',
66
-
67
- 'COMPONENT_SET_ENVIRONMENT_READ',
68
- 'COMPONENT_SET_ENVIRONMENT_WRITE',
69
- ],
49
+ const authToken =
50
+ 'Bearer ' +
51
+ buildDevelopmentJwt({
52
+ service: {
53
+ [DxpServiceId.contentStore]: {
54
+ privileges: [
55
+ 'CONTENT_SCHEMA_READ',
56
+ 'CONTENT_SCHEMA_WRITE',
57
+ 'CONTENT_ITEM_READ',
58
+ 'CONTENT_ITEM_WRITE',
59
+ 'SETTINGS_READ',
60
+ 'SETTINGS_WRITE',
61
+ 'PAGE_CONTENTS_WRITE',
62
+ 'PAGE_CONTENTS_READ',
63
+ ],
64
+ },
65
+ [DxpServiceId.components]: {
66
+ privileges: [
67
+ 'COMPONENT_READ',
68
+ 'COMPONENT_WRITE',
69
+ 'COMPONENT_DEPLOY',
70
+ 'COMPONENT_SET_WRITE',
71
+ 'COMPONENT_DELETE',
72
+ 'COMPONENT_SET_WRITE',
73
+ 'COMPONENT_SET_READ',
74
+ 'COMPONENT_SET_ENVIRONMENT_READ',
75
+ 'COMPONENT_SET_ENVIRONMENT_WRITE',
76
+ ],
77
+ },
70
78
  },
71
- [DXP_CONTENT_STORE_SERVICE_NAME]: {
72
- privileges: [
73
- 'CONTENT_SCHEMA_READ',
74
- 'CONTENT_SCHEMA_WRITE',
75
- 'CONTENT_ITEM_READ',
76
- 'CONTENT_ITEM_WRITE',
77
- 'SETTINGS_READ',
78
- 'SETTINGS_WRITE',
79
- ],
80
- },
81
- },
82
- };
83
-
84
- export const authToken = `Bearer xxx.${Buffer.from(JSON.stringify(validToken)).toString('base64')}.zzz`;
79
+ });
85
80
 
86
81
  export const managementService = axios.create({
87
82
  baseURL: configObj.managementServiceUrl + '/v1',
@@ -111,6 +106,13 @@ export const contentService = axios.create({
111
106
  },
112
107
  });
113
108
 
109
+ export const pageRenderService = axios.create({
110
+ baseURL: configObj.pageRenderServiceUrl,
111
+ headers: {
112
+ authorization: authToken,
113
+ },
114
+ });
115
+
114
116
  export const jobService = axios.create({
115
117
  baseURL: configObj.jobServiceUrl,
116
118
  headers: {
@@ -127,6 +129,12 @@ export async function getTestComponents() {
127
129
  return await manifestService.listAllComponentManifests();
128
130
  }
129
131
 
132
+ export async function getTestComponent(componentFolderName: string) {
133
+ const componentsDir = path.join(__dirname, '/__components__/', componentFolderName);
134
+ const manifestService = new ManifestServiceForDev(componentsDir, getLogger({ name: 'getTestComponents' }));
135
+ return await manifestService.listAllComponentManifests();
136
+ }
137
+
130
138
  export async function getTestJobs() {
131
139
  const jobsDir = path.join(__dirname, '/__jobs__/');
132
140
  const manifestService = new ManifestServiceForDev(jobsDir, getLogger({ name: 'getTestJobs' }));
@@ -174,6 +182,14 @@ export async function deleteContentItem(contentItemId: string) {
174
182
  }
175
183
  }
176
184
 
185
+ export async function deleteContentSchema(contentSchemaName: string) {
186
+ try {
187
+ await contentService.delete(`/content-item/${contentSchemaName}`);
188
+ } catch (error) {
189
+ //no ops
190
+ }
191
+ }
192
+
177
193
  export async function deleteComponent(manifest: Manifest) {
178
194
  try {
179
195
  await managementService.delete(`/component/${manifest.getName()}`);
@@ -191,6 +207,11 @@ export async function deleteComponents(manifests: Manifest[]) {
191
207
  await Promise.all(manifests.map((manifest) => deleteComponent(manifest)));
192
208
  }
193
209
 
210
+ export async function uploadTestComponent(componentFolderPath: string, tempFolderPath: string): Promise<void> {
211
+ const componentPath = path.join(__dirname, componentFolderPath);
212
+ await uploadComponentFolder(managementServiceRoot, configObj.managementServiceUrl, componentPath, tempFolderPath);
213
+ }
214
+
194
215
  export async function deleteJob(manifest: Manifest) {
195
216
  try {
196
217
  await jobService.delete(`/job/${manifest.getName()}/${manifest.getVersion()}`);
@@ -0,0 +1,151 @@
1
+ import {
2
+ ContentItemWebModeForSave,
3
+ ContentSchemaType,
4
+ ContentSchemaWebModelForCreate,
5
+ PageContentsWebModel,
6
+ } from '@squiz/component-web-api-lib/src/generated/ContentApi';
7
+ import { ComponentSetWebModelForCreate } from '@squiz/component-lib';
8
+ import path from 'path';
9
+ import {
10
+ addComponentSet,
11
+ contentService,
12
+ deleteComponentSet,
13
+ deleteComponents,
14
+ deleteContentItem,
15
+ deleteContentSchema,
16
+ getTestComponent,
17
+ pageRenderService,
18
+ uploadTestComponent,
19
+ } from './helper';
20
+ import fsp from 'fs/promises';
21
+ import { randomInt } from 'crypto';
22
+
23
+ const SCHEMA_NAME = 'test-schema-name' + randomInt(10000);
24
+ const CONTENT_ITEM_ID = 'test-content-id' + randomInt(10000);
25
+ const PAGE_CONTENT_ID = 'test-page-content-id' + randomInt(10000);
26
+ const COMPONENT_SET_WEB_PATH = 'local-development-only' + randomInt(10000);
27
+ const COMPONENT_NAME = 'test-page-render';
28
+ const COMPONENT_NAMESPACE = 'unit-test-components';
29
+ const COMPONENT_CONTENT_ID = 'component-content-id' + randomInt(10000);
30
+ const COMPONENT_CONTENT_SCHEMA_NAME = `${COMPONENT_NAMESPACE}/${COMPONENT_NAME}/1.0.0/main`;
31
+
32
+ const componentContentItem = {
33
+ id: COMPONENT_CONTENT_ID,
34
+ schemaName: COMPONENT_CONTENT_SCHEMA_NAME,
35
+ content: {
36
+ text: 'component-input-string',
37
+ },
38
+ };
39
+
40
+ // This schema emulates the layout input schema
41
+ const contentSchema: ContentSchemaWebModelForCreate = {
42
+ name: SCHEMA_NAME,
43
+ schema: {
44
+ type: 'object',
45
+ properties: {
46
+ nodes: {
47
+ type: 'FormattedText',
48
+ },
49
+ },
50
+ required: ['nodes'],
51
+ additionalProperties: false,
52
+ },
53
+ contentSchemaType: ContentSchemaType.Component,
54
+ };
55
+
56
+ // The content item with the formatted text
57
+ const contentItem: ContentItemWebModeForSave = {
58
+ id: CONTENT_ITEM_ID,
59
+ schemaName: SCHEMA_NAME,
60
+ content: {
61
+ nodes: [
62
+ {
63
+ type: 'tag',
64
+ tag: 'h1',
65
+ children: [
66
+ {
67
+ type: 'text',
68
+ value: 'Hello World!',
69
+ },
70
+ ],
71
+ },
72
+ {
73
+ type: 'component',
74
+ schemaName: `http://localhost:3000/d/${COMPONENT_NAMESPACE}/${COMPONENT_NAME}/1.0.0`,
75
+ componentSet: COMPONENT_SET_WEB_PATH,
76
+ content: {
77
+ contentItemId: COMPONENT_CONTENT_ID,
78
+ },
79
+ },
80
+ {
81
+ type: 'tag',
82
+ tag: 'p',
83
+ children: [
84
+ {
85
+ type: 'text',
86
+ value: 'Bottom text',
87
+ },
88
+ ],
89
+ },
90
+ ],
91
+ },
92
+ };
93
+ // The page content item which references the content item
94
+ const pageContents: PageContentsWebModel = {
95
+ name: 'page-contents-test-1',
96
+ id: PAGE_CONTENT_ID,
97
+ layouts: [
98
+ {
99
+ schemaName: SCHEMA_NAME,
100
+ content: {
101
+ contentItemId: CONTENT_ITEM_ID,
102
+ },
103
+ },
104
+ ],
105
+ };
106
+ //The componentSet for the component in the content item
107
+ const testComponentSet: ComponentSetWebModelForCreate = {
108
+ webPath: COMPONENT_SET_WEB_PATH,
109
+ displayName: 'test-set',
110
+ description: 'test-set',
111
+ headers: {},
112
+ components: {},
113
+ componentVersionRules: {
114
+ [`${COMPONENT_NAMESPACE}/${COMPONENT_NAME}`]: {
115
+ renderableVersionPattern: '1.0.0',
116
+ editableVersions: [],
117
+ excludedVersions: [],
118
+ },
119
+ },
120
+ };
121
+
122
+ describe('PageRenderController- Integration', () => {
123
+ const testFilesDir = path.resolve(__dirname, 'test-files');
124
+ beforeAll(async () => {
125
+ await fsp.rm(testFilesDir, { force: true, recursive: true });
126
+ await fsp.mkdir(testFilesDir);
127
+ // Component Configuration
128
+ await uploadTestComponent('/__components__/test-page-render', testFilesDir);
129
+ await addComponentSet(testComponentSet);
130
+ // Post component items
131
+ await contentService.post('content-item', componentContentItem);
132
+ // Post items
133
+ await contentService.post('content-schema', contentSchema);
134
+ await contentService.post('content-item', contentItem);
135
+ await contentService.post('page-contents', pageContents);
136
+ });
137
+
138
+ afterAll(async () => {
139
+ await deleteContentItem(contentItem.id!);
140
+ await deleteContentItem(componentContentItem.id!);
141
+ await deleteContentSchema(COMPONENT_CONTENT_SCHEMA_NAME);
142
+ await deleteContentSchema(contentSchema.name);
143
+ await deleteComponentSet(COMPONENT_SET_WEB_PATH);
144
+ await deleteComponents(await getTestComponent(COMPONENT_NAME));
145
+ });
146
+
147
+ it('should render a page', async () => {
148
+ const page = await pageRenderService.get(`p/${PAGE_CONTENT_ID}`);
149
+ expect(page.data).toEqual('<h1>Hello World!</h1><div>component-html</div><p>Bottom text</p>');
150
+ });
151
+ });