@pega/angular-sdk-overrides 0.26.1 → 0.26.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.
Files changed (74) hide show
  1. package/lib/designSystemExtension/alert/alert.component.html +4 -1
  2. package/lib/designSystemExtension/alert/alert.component.scss +9 -10
  3. package/lib/designSystemExtension/alert/alert.component.ts +6 -2
  4. package/lib/designSystemExtension/material-details-fields/material-details-fields.component.html +30 -21
  5. package/lib/designSystemExtension/operator/operator.component.ts +2 -1
  6. package/lib/designSystemExtension/rich-text-editor/rich-text-editor.component.html +104 -10
  7. package/lib/designSystemExtension/rich-text-editor/rich-text-editor.component.scss +141 -1
  8. package/lib/designSystemExtension/rich-text-editor/rich-text-editor.component.ts +224 -72
  9. package/lib/field/auto-complete/auto-complete.component.html +2 -1
  10. package/lib/field/auto-complete/auto-complete.component.ts +8 -6
  11. package/lib/field/check-box/check-box.component.html +3 -1
  12. package/lib/field/check-box/check-box.component.ts +10 -1
  13. package/lib/field/currency/currency.component.html +2 -1
  14. package/lib/field/currency/currency.component.ts +10 -1
  15. package/lib/field/date/date.component.html +2 -1
  16. package/lib/field/date/date.component.ts +2 -0
  17. package/lib/field/date-time/date-time.component.html +2 -1
  18. package/lib/field/date-time/date-time.component.scss +46 -40
  19. package/lib/field/date-time/date-time.component.ts +2 -0
  20. package/lib/field/decimal/decimal.component.html +2 -1
  21. package/lib/field/decimal/decimal.component.ts +2 -0
  22. package/lib/field/dropdown/dropdown.component.html +2 -1
  23. package/lib/field/dropdown/dropdown.component.ts +10 -1
  24. package/lib/field/email/email.component.html +2 -1
  25. package/lib/field/email/email.component.ts +2 -2
  26. package/lib/field/field.base.ts +27 -1
  27. package/lib/field/integer/integer.component.html +2 -1
  28. package/lib/field/integer/integer.component.ts +4 -2
  29. package/lib/field/location/location.component.html +2 -1
  30. package/lib/field/location/location.component.ts +3 -1
  31. package/lib/field/object-reference/object-reference.component.html +1 -1
  32. package/lib/field/object-reference/object-reference.component.ts +3 -0
  33. package/lib/field/percentage/percentage.component.html +2 -1
  34. package/lib/field/percentage/percentage.component.ts +10 -1
  35. package/lib/field/phone/phone.component.html +2 -1
  36. package/lib/field/phone/phone.component.ts +2 -1
  37. package/lib/field/radio-buttons/radio-buttons.component.html +2 -1
  38. package/lib/field/radio-buttons/radio-buttons.component.ts +10 -1
  39. package/lib/field/rich-text/rich-text.component.scss +1 -166
  40. package/lib/field/rich-text/rich-text.component.ts +1 -1
  41. package/lib/field/semantic-link/semantic-link.component.ts +9 -5
  42. package/lib/field/text-area/text-area.component.html +2 -1
  43. package/lib/field/text-area/text-area.component.ts +2 -1
  44. package/lib/field/text-input/text-input.component.html +2 -1
  45. package/lib/field/text-input/text-input.component.ts +2 -1
  46. package/lib/field/time/time.component.html +2 -1
  47. package/lib/field/time/time.component.ts +2 -1
  48. package/lib/field/url/url.component.html +2 -1
  49. package/lib/field/url/url.component.ts +2 -1
  50. package/lib/field/user-reference/user-reference.component.html +4 -2
  51. package/lib/field/user-reference/user-reference.component.ts +10 -5
  52. package/lib/infra/Containers/flow-container/flow-container.component.ts +33 -40
  53. package/lib/infra/Containers/modal-view-container/modal-view-container.component.ts +46 -50
  54. package/lib/infra/Containers/view-container/view-container.component.ts +32 -31
  55. package/lib/infra/navbar/navbar.component.ts +33 -35
  56. package/lib/infra/root-container/root-container.component.ts +33 -38
  57. package/lib/template/app-shell/app-shell.component.ts +12 -11
  58. package/lib/template/base/details-template-base.ts +2 -1
  59. package/lib/template/default-form/default-form.component.html +19 -3
  60. package/lib/template/default-form/default-form.component.ts +13 -3
  61. package/lib/template/simple-table-manual/helpers.ts +3 -3
  62. package/lib/template/simple-table-manual/simple-table-manual.component.html +29 -11
  63. package/lib/template/simple-table-manual/simple-table-manual.component.ts +69 -11
  64. package/lib/template/single-reference-readonly/single-reference-readonly.component.html +1 -1
  65. package/lib/template/single-reference-readonly/single-reference-readonly.component.scss +4 -0
  66. package/lib/template/single-reference-readonly/single-reference-readonly.component.ts +4 -4
  67. package/lib/template/wss-nav-bar/wss-nav-bar.component.ts +18 -20
  68. package/lib/widget/attachment/AttachmentUtils.ts +18 -10
  69. package/lib/widget/attachment/attachment.component.html +16 -6
  70. package/lib/widget/attachment/attachment.component.ts +12 -2
  71. package/lib/widget/case-history/case-history.component.ts +6 -2
  72. package/lib/widget/file-utility/file-utility.component.ts +25 -29
  73. package/lib/widget/todo/todo.component.ts +7 -2
  74. package/package.json +1 -1
@@ -14,7 +14,8 @@ import { getDataRelationshipContextFromKey } from '@pega/angular-sdk-components'
14
14
  export class SingleReferenceReadonlyComponent implements OnInit, OnDestroy {
15
15
  @Input() pConn$: typeof PConnect;
16
16
  @Input() formGroup$: FormGroup;
17
- @Input() dataRelationshipContext?: any;
17
+ @Input() dataRelationshipContext: any = null;
18
+ @Input() isDetails$ = false;
18
19
 
19
20
  angularPConnectData: AngularPConnectData = {};
20
21
  configProps: any;
@@ -66,9 +67,8 @@ export class SingleReferenceReadonlyComponent implements OnInit, OnDestroy {
66
67
  const hideLabel = this.configProps.hideLabel;
67
68
  // const additionalFields = this.configProps.additionalFields;
68
69
  const displayAs = this.configProps.displayAs ?? 'readonly';
69
- const dataRelationshipContext = (rawViewMetadata?.config as any)?.displayField
70
- ? getDataRelationshipContextFromKey((rawViewMetadata?.config as any)?.displayField)
71
- : this.dataRelationshipContext;
70
+ const dataRelationshipContext =
71
+ this.dataRelationshipContext ?? getDataRelationshipContextFromKey((rawViewMetadata?.config as any)?.displayField ?? '');
72
72
  if (propsToUse.showLabel === false) {
73
73
  propsToUse.label = '';
74
74
  }
@@ -1,4 +1,4 @@
1
- import { ChangeDetectorRef, Component, Input, NgZone, OnDestroy, OnInit } from '@angular/core';
1
+ import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import { MatMenuModule } from '@angular/material/menu';
4
4
  import { MatListModule } from '@angular/material/list';
@@ -52,7 +52,6 @@ export class WssNavBarComponent implements OnInit, OnDestroy {
52
52
  private angularPConnect: AngularPConnectService,
53
53
  private cdRef: ChangeDetectorRef,
54
54
  private psService: ProgressSpinnerService,
55
- private ngZone: NgZone,
56
55
  private utils: Utils
57
56
  ) {}
58
57
 
@@ -102,30 +101,29 @@ export class WssNavBarComponent implements OnInit, OnDestroy {
102
101
  }
103
102
 
104
103
  initComponent() {
105
- this.ngZone.run(() => {
106
- this.navIcon$ = this.utils.getSDKStaticContentUrl().concat('assets/pzpega-logo-mark.svg');
107
- this.navExpandCollapse$ = this.utils.getImageSrc('plus', this.utils.getSDKStaticContentUrl());
104
+ this.navIcon$ = this.utils.getSDKStaticContentUrl().concat('assets/pzpega-logo-mark.svg');
105
+ this.navExpandCollapse$ = this.utils.getImageSrc('plus', this.utils.getSDKStaticContentUrl());
108
106
 
109
- // Then, continue on with other initialization
107
+ // Then, continue on with other initialization
110
108
 
111
- // making a copy, so can add info
112
- this.navPages$ = JSON.parse(JSON.stringify(this.pages$));
109
+ // making a copy, so can add info
110
+ this.navPages$ = JSON.parse(JSON.stringify(this.pages$));
113
111
 
114
- this.navPages$.forEach(page => {
115
- page.iconName = this.utils.getImageSrc(page.pxPageViewIcon, this.utils.getSDKStaticContentUrl());
116
- });
112
+ this.navPages$.forEach(page => {
113
+ page.iconName = this.utils.getImageSrc(page.pxPageViewIcon, this.utils.getSDKStaticContentUrl());
114
+ });
117
115
 
118
- this.actionsAPI = this.pConn$.getActionsApi();
119
- this.createWork = this.actionsAPI.createWork.bind(this.actionsAPI);
120
- this.showPage = this.actionsAPI.showPage.bind(this.actionsAPI);
121
- this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as WssNavBarProps;
122
- this.logout = this.actionsAPI.logout.bind(this.actionsAPI);
116
+ this.actionsAPI = this.pConn$.getActionsApi();
117
+ this.createWork = this.actionsAPI.createWork.bind(this.actionsAPI);
118
+ this.showPage = this.actionsAPI.showPage.bind(this.actionsAPI);
119
+ this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as WssNavBarProps;
120
+ this.logout = this.actionsAPI.logout.bind(this.actionsAPI);
123
121
 
124
- // const oData = this.pConn$.getDataObject();
122
+ // const oData = this.pConn$.getDataObject();
125
123
 
126
- this.portalOperator$ = PCore.getEnvironmentInfo().getOperatorName();
127
- this.portalOperatorInitials$ = this.utils.getInitials(this.portalOperator$ ?? '');
128
- });
124
+ this.portalOperator$ = PCore.getEnvironmentInfo().getOperatorName();
125
+ this.portalOperatorInitials$ = this.utils.getInitials(this.portalOperator$ ?? '');
126
+ this.cdRef.markForCheck();
129
127
  }
130
128
 
131
129
  navPanelButtonClick(oPageData: any) {
@@ -270,18 +270,26 @@ export const onFileDownload = (responseProps, context) => {
270
270
 
271
271
  // Prepares new structure as per Cosmos component
272
272
  export const transformAttachments = attachments => {
273
- const transformedFiles = [...attachments];
274
273
  let deleteIndex = -1;
275
- transformedFiles.forEach(attachment => {
276
- attachment.props.id = attachment.responseProps.ID;
277
- attachment.props.format = attachment.props.name.split('.').pop();
278
- if (attachment.props.error) {
279
- attachment.responseProps.deleteIndex = deleteIndex;
280
- } else {
274
+
275
+ return attachments.map(attachment => {
276
+ const props = {
277
+ ...attachment.props,
278
+ id: attachment.responseProps.ID,
279
+ format: attachment.props.name.split('.').pop()
280
+ };
281
+
282
+ if (!props.error) {
281
283
  deleteIndex += 1;
282
- attachment.responseProps.deleteIndex = deleteIndex;
283
284
  }
284
- });
285
285
 
286
- return transformedFiles;
286
+ return {
287
+ ...attachment,
288
+ props,
289
+ responseProps: {
290
+ ...attachment.responseProps,
291
+ deleteIndex
292
+ }
293
+ };
294
+ });
287
295
  };
@@ -1,6 +1,6 @@
1
1
  <div class="file-upload-container" *ngIf="bVisible$">
2
2
  <span class="attachment-label" [ngClass]="{ 'file-label': bRequired$ }">{{ label$ }}</span>
3
- <div *ngIf="(files.length === 0 && !allowMultiple$) || allowMultiple$">
3
+ <div *ngIf="displayMode !== 'DISPLAY_ONLY' && ((files.length === 0 && !allowMultiple$) || allowMultiple$)">
4
4
  <div
5
5
  style="margin-bottom: 10px"
6
6
  id="attachment-container"
@@ -36,18 +36,28 @@
36
36
  <div *ngIf="file.props.meta" [ngStyle]="{ color: file.props.error ? 'red' : null }">{{ file.props.meta }}</div>
37
37
  </div>
38
38
  <div class="psdk-attachment-action">
39
- <button id="delete-attachment" *ngIf="file.ID" class="psdk-utility-button" (click)="deleteFile(file, i)">
40
- <img class="psdk-utility-card-action-svg-icon" src="{{ deleteIcon }}" />
41
- </button>
42
- <div *ngIf="!file.ID">
39
+ <div *ngIf="displayMode === 'DISPLAY_ONLY'; else editableActions">
43
40
  <button id="setting-button" mat-icon-button [matMenuTriggerFor]="actionMenu">
44
41
  <mat-icon>more_vert</mat-icon>
45
42
  </button>
46
43
  <mat-menu #actionMenu="matMenu">
47
44
  <button mat-menu-item (click)="downloadFile(file.responseProps ? file.responseProps : {})">Download</button>
48
- <button mat-menu-item (click)="deleteFile(file, i)">Delete</button>
49
45
  </mat-menu>
50
46
  </div>
47
+ <ng-template #editableActions>
48
+ <button id="delete-attachment" *ngIf="file.ID" class="psdk-utility-button" (click)="deleteFile(file, i)">
49
+ <img class="psdk-utility-card-action-svg-icon" src="{{ deleteIcon }}" />
50
+ </button>
51
+ <div *ngIf="!file.ID">
52
+ <button id="setting-button" mat-icon-button [matMenuTriggerFor]="actionMenu">
53
+ <mat-icon>more_vert</mat-icon>
54
+ </button>
55
+ <mat-menu #actionMenu="matMenu">
56
+ <button mat-menu-item (click)="downloadFile(file.responseProps ? file.responseProps : {})">Download</button>
57
+ <button mat-menu-item (click)="deleteFile(file, i)">Delete</button>
58
+ </mat-menu>
59
+ </div>
60
+ </ng-template>
51
61
  </div>
52
62
  </div>
53
63
  </div>
@@ -1,4 +1,4 @@
1
- import { Component, OnInit, Input, OnDestroy, ViewChild, ElementRef } from '@angular/core';
1
+ import { ChangeDetectorRef, Component, OnInit, Input, OnDestroy, ViewChild, ElementRef } from '@angular/core';
2
2
  import { FormGroup } from '@angular/forms';
3
3
  import { CommonModule } from '@angular/common';
4
4
  import { MatButtonModule } from '@angular/material/button';
@@ -76,7 +76,8 @@ export class AttachmentComponent implements OnInit, OnDestroy {
76
76
 
77
77
  constructor(
78
78
  private angularPConnect: AngularPConnectService,
79
- private utils: Utils
79
+ private utils: Utils,
80
+ private cdRef: ChangeDetectorRef
80
81
  ) {}
81
82
 
82
83
  ngOnInit(): void {
@@ -96,6 +97,7 @@ export class AttachmentComponent implements OnInit, OnDestroy {
96
97
  PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.ASSIGNMENT_SUBMISSION,
97
98
  () => {
98
99
  this.overrideLocalState = true;
100
+ this.cdRef.markForCheck();
99
101
  },
100
102
  this.caseID
101
103
  );
@@ -207,6 +209,13 @@ export class AttachmentComponent implements OnInit, OnDestroy {
207
209
  isArrayDeepMerge: false,
208
210
  removePropertyFromChangedList: true
209
211
  });
212
+ } else if (this.filesWithError.length === 0) {
213
+ const hasActiveUpload = this.files.some(file => file.inProgress);
214
+ if (!hasActiveUpload) {
215
+ this.files = transformAttachments(this.attachments);
216
+ this.attachmentCount = this.attachments.length;
217
+ this.filesWithError = [];
218
+ }
210
219
  }
211
220
  }
212
221
  }
@@ -336,6 +345,7 @@ export class AttachmentComponent implements OnInit, OnDestroy {
336
345
  }
337
346
  return localFile;
338
347
  });
348
+ this.cdRef.markForCheck();
339
349
  }
340
350
 
341
351
  populateErrorAndUpdateRedux(file) {
@@ -1,4 +1,4 @@
1
- import { Component, OnInit, Input } from '@angular/core';
1
+ import { ChangeDetectorRef, Component, OnInit, Input } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import { MatTableDataSource, MatTableModule } from '@angular/material/table';
4
4
  import { Utils } from '@pega/angular-sdk-components';
@@ -23,7 +23,10 @@ export class CaseHistoryComponent implements OnInit {
23
23
  displayedColumns$ = Array<any>();
24
24
  waitingForData = false;
25
25
 
26
- constructor(private utils: Utils) {}
26
+ constructor(
27
+ private utils: Utils,
28
+ private cdRef: ChangeDetectorRef
29
+ ) {}
27
30
 
28
31
  ngOnInit(): void {
29
32
  this.configProps$ = this.pConn$.getConfigProps();
@@ -54,6 +57,7 @@ export class CaseHistoryComponent implements OnInit {
54
57
  this.repeatList$ = new MatTableDataSource(tableDataResults);
55
58
 
56
59
  this.waitingForData = false;
60
+ this.cdRef.markForCheck();
57
61
  });
58
62
  }
59
63
  // This method isn't being used anywhere, keeping it as a reference for now
@@ -1,4 +1,4 @@
1
- import { Component, OnInit, Input, NgZone, forwardRef, OnDestroy } from '@angular/core';
1
+ import { ChangeDetectorRef, Component, OnInit, Input, forwardRef, OnDestroy } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import { MatButtonModule } from '@angular/material/button';
4
4
  import { MatInputModule } from '@angular/material/input';
@@ -74,7 +74,7 @@ export class FileUtilityComponent implements OnInit, OnDestroy {
74
74
  constructor(
75
75
  private angularPConnect: AngularPConnectService,
76
76
  private utils: Utils,
77
- private ngZone: NgZone
77
+ private cdRef: ChangeDetectorRef
78
78
  ) {}
79
79
 
80
80
  ngOnInit(): void {
@@ -273,9 +273,8 @@ export class FileUtilityComponent implements OnInit, OnDestroy {
273
273
  for (let fileIndex = 0; fileIndex < arFileList.length; fileIndex++) {
274
274
  if (arFileList[fileIndex].id == itemId) {
275
275
  // remove the file from the list and redraw
276
- this.ngZone.run(() => {
277
- arFileList.splice(fileIndex, 1);
278
- });
276
+ arFileList.splice(fileIndex, 1);
277
+ this.cdRef.markForCheck();
279
278
  break;
280
279
  }
281
280
  }
@@ -293,9 +292,8 @@ export class FileUtilityComponent implements OnInit, OnDestroy {
293
292
  // remove the file from the list and redraw
294
293
  localLinksList.splice(linkIndex, 1);
295
294
 
296
- this.ngZone.run(() => {
297
- this.arLinksList$ = localLinksList.slice();
298
- });
295
+ this.arLinksList$ = localLinksList.slice();
296
+ this.cdRef.markForCheck();
299
297
 
300
298
  break;
301
299
  }
@@ -482,24 +480,23 @@ export class FileUtilityComponent implements OnInit, OnDestroy {
482
480
  oLink.primary.icon = 'open';
483
481
  oLink.secondary.text = url;
484
482
 
485
- this.ngZone.run(() => {
486
- // need to create a new array or summary list won't detect changes
487
- this.arLinksList$ = localList.slice();
488
- this.arLinksList$.push(oLink);
483
+ // need to create a new array or summary list won't detect changes
484
+ this.arLinksList$ = localList.slice();
485
+ this.arLinksList$.push(oLink);
489
486
 
490
- // list for actually attachments
491
- const link: any = {};
492
- link.id = oLink.id;
493
- link.linkTitle = this.link_title$;
494
- link.type = oLink.type;
495
- link.url = url;
487
+ // list for actually attachments
488
+ const link: any = {};
489
+ link.id = oLink.id;
490
+ link.linkTitle = this.link_title$;
491
+ link.type = oLink.type;
492
+ link.url = url;
496
493
 
497
- this.arLinks$.push(link);
494
+ this.arLinks$.push(link);
498
495
 
499
- // clear values
500
- this.link_title$ = '';
501
- this.link_url$ = '';
502
- });
496
+ // clear values
497
+ this.link_title$ = '';
498
+ this.link_url$ = '';
499
+ this.cdRef.markForCheck();
503
500
  }
504
501
 
505
502
  _changeTitle(event: any) {
@@ -575,15 +572,13 @@ export class FileUtilityComponent implements OnInit, OnDestroy {
575
572
  createModal(modalType: string) {
576
573
  switch (modalType) {
577
574
  case 'addLocalFile':
578
- this.ngZone.run(() => {
579
- this.bShowFileModal$ = true;
580
- });
575
+ this.bShowFileModal$ = true;
576
+ this.cdRef.markForCheck();
581
577
 
582
578
  break;
583
579
  case 'addLocalLink':
584
- this.ngZone.run(() => {
585
- this.bShowLinkModal$ = true;
586
- });
580
+ this.bShowLinkModal$ = true;
581
+ this.cdRef.markForCheck();
587
582
  break;
588
583
  default:
589
584
  break;
@@ -716,6 +711,7 @@ export class FileUtilityComponent implements OnInit, OnDestroy {
716
711
  })
717
712
  .finally(() => {
718
713
  this.lu_bLoading$ = false;
714
+ this.cdRef.markForCheck();
719
715
  });
720
716
  }
721
717
  }
@@ -1,4 +1,4 @@
1
- import { Component, OnInit, Input, OnDestroy } from '@angular/core';
1
+ import { ChangeDetectorRef, Component, OnInit, Input, OnDestroy } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import { MatButtonModule } from '@angular/material/button';
4
4
  import { publicConstants } from '@pega/pcore-pconnect-typedefs/constants';
@@ -97,7 +97,8 @@ export class TodoComponent implements OnInit, OnDestroy {
97
97
  constructor(
98
98
  private psService: ProgressSpinnerService,
99
99
  private erService: ErrorMessagesService,
100
- private utils: Utils
100
+ private utils: Utils,
101
+ private cdRef: ChangeDetectorRef
101
102
  ) {}
102
103
 
103
104
  ngOnInit() {
@@ -167,6 +168,7 @@ export class TodoComponent implements OnInit, OnDestroy {
167
168
  deferLoadWorklistItems(responseData) {
168
169
  this.count = responseData.totalCount;
169
170
  this.arAssignments$ = responseData.data;
171
+ this.cdRef.markForCheck();
170
172
  }
171
173
 
172
174
  getID(assignment: any) {
@@ -221,6 +223,7 @@ export class TodoComponent implements OnInit, OnDestroy {
221
223
  fetchMyWorkList(this.myWorkList$.datapage, this.pConn$.getComponentConfig()?.myWorkList.fields, this.count, false, this.context$).then(
222
224
  response => {
223
225
  this.arAssignments$ = response.data;
226
+ this.cdRef.markForCheck();
224
227
  }
225
228
  );
226
229
  } else {
@@ -268,6 +271,7 @@ export class TodoComponent implements OnInit, OnDestroy {
268
271
  .openAssignment(id, classname, options)
269
272
  .then(() => {
270
273
  this.psService.sendMessage(false);
274
+ this.cdRef.markForCheck();
271
275
  if (this.bLogging) {
272
276
  console.log(`openAssignment completed`);
273
277
  }
@@ -275,6 +279,7 @@ export class TodoComponent implements OnInit, OnDestroy {
275
279
  .catch(() => {
276
280
  this.psService.sendMessage(false);
277
281
  this.erService.sendMessage('show', 'Failed to open');
282
+ this.cdRef.markForCheck();
278
283
  });
279
284
  }
280
285
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pega/angular-sdk-overrides",
3
- "version": "0.26.1",
3
+ "version": "0.26.2",
4
4
  "description": "Angular SDK - Code for overriding components",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"