@inizioevoke/veeva-astroclm-core 1.0.0
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/.editorconfig +12 -0
- package/build.ts +38 -0
- package/dist/apps/index.d.ts +2 -0
- package/dist/apps/index.js +2 -0
- package/dist/apps/page-manager/bulk-create.d.ts +1 -0
- package/dist/apps/page-manager/bulk-create.js +120 -0
- package/dist/apps/page-manager/index.d.ts +5 -0
- package/dist/apps/page-manager/index.js +24 -0
- package/dist/apps/page-manager/single-create.d.ts +1 -0
- package/dist/apps/page-manager/single-create.js +78 -0
- package/dist/apps/page-manager/templates/contents.astro.txt +12 -0
- package/dist/apps/page-manager/templates/page.astro.txt +4 -0
- package/dist/apps/page-manager/utils.d.ts +10 -0
- package/dist/apps/page-manager/utils.js +57 -0
- package/dist/apps/utils.d.ts +5 -0
- package/dist/apps/utils.js +43 -0
- package/dist/apps/veeva-config-manager/create.d.ts +1 -0
- package/dist/apps/veeva-config-manager/create.js +136 -0
- package/dist/apps/veeva-config-manager/index.d.ts +3 -0
- package/dist/apps/veeva-config-manager/index.js +16 -0
- package/dist/apps/veeva-config-manager/templates/config.ts.txt +60 -0
- package/dist/env/schema.d.ts +15 -0
- package/dist/env/schema.js +28 -0
- package/dist/lib/const.d.ts +15 -0
- package/dist/lib/const.js +14 -0
- package/dist/lib/env.d.ts +8 -0
- package/dist/lib/env.js +17 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.js +5 -0
- package/dist/lib/logger.d.ts +9 -0
- package/dist/lib/logger.js +72 -0
- package/dist/lib/parse-argv.d.ts +31 -0
- package/dist/lib/parse-argv.js +109 -0
- package/dist/lib/parse-env.d.ts +45 -0
- package/dist/lib/parse-env.js +124 -0
- package/dist/lib/utils.d.ts +8 -0
- package/dist/lib/utils.js +37 -0
- package/dist/tasks/copy-files.d.ts +9 -0
- package/dist/tasks/copy-files.js +15 -0
- package/dist/tasks/create-csv.d.ts +15 -0
- package/dist/tasks/create-csv.js +186 -0
- package/dist/tasks/create-zips.d.ts +5 -0
- package/dist/tasks/create-zips.js +16 -0
- package/dist/tasks/deploy.d.ts +10 -0
- package/dist/tasks/deploy.js +49 -0
- package/dist/tasks/generate-thumbs.d.ts +12 -0
- package/dist/tasks/generate-thumbs.js +152 -0
- package/dist/tasks/index.d.ts +1 -0
- package/dist/tasks/index.js +1 -0
- package/dist/tasks/update-shared.d.ts +5 -0
- package/dist/tasks/update-shared.js +41 -0
- package/dist/types/config.d.ts +52 -0
- package/dist/types/env.d.ts +7 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/veeva.d.ts +58 -0
- package/dist/veeva/slideManager.d.ts +33 -0
- package/dist/veeva/slideManager.js +120 -0
- package/package.json +39 -0
- package/src/apps/index.ts +2 -0
- package/src/apps/page-manager/bulk-create.ts +131 -0
- package/src/apps/page-manager/index.ts +31 -0
- package/src/apps/page-manager/single-create.ts +97 -0
- package/src/apps/page-manager/templates/contents.astro.txt +12 -0
- package/src/apps/page-manager/templates/page.astro.txt +4 -0
- package/src/apps/page-manager/utils.ts +70 -0
- package/src/apps/utils.ts +47 -0
- package/src/apps/veeva-config-manager/create.ts +153 -0
- package/src/apps/veeva-config-manager/index.ts +20 -0
- package/src/apps/veeva-config-manager/templates/config.ts.txt +60 -0
- package/src/env/schema.ts +43 -0
- package/src/lib/const.ts +17 -0
- package/src/lib/env.ts +27 -0
- package/src/lib/index.ts +5 -0
- package/src/lib/logger.ts +84 -0
- package/src/lib/parse-argv.ts +125 -0
- package/src/lib/parse-env.ts +147 -0
- package/src/lib/utils.ts +37 -0
- package/src/tasks/copy-files.ts +29 -0
- package/src/tasks/create-csv.ts +259 -0
- package/src/tasks/create-zips.ts +21 -0
- package/src/tasks/deploy.ts +72 -0
- package/src/tasks/generate-thumbs.ts +179 -0
- package/src/tasks/index.ts +1 -0
- package/src/tasks/update-shared.ts +49 -0
- package/src/types/config.d.ts +52 -0
- package/src/types/env.d.ts +7 -0
- package/src/types/index.d.ts +9 -0
- package/src/types/veeva.d.ts +58 -0
- package/src/veeva/readme.md +77 -0
- package/src/veeva/slideManager.ts +139 -0
- package/tsconfig.json +27 -0
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { DataEvotrk } from '../types';
|
|
2
|
+
|
|
3
|
+
export function formatExtId(...ids: string[]) {
|
|
4
|
+
return ids.map((id) => { return id.toUpperCase(); }).join('_');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function pathToSlideZip(path: string) {
|
|
8
|
+
return `${flattenPath(path, { slashes: false })}.zip`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function flattenPath(path: string, { slashes = true }: { slashes?: boolean } = {}) {
|
|
12
|
+
return `${slashes ? '/' : ''}${path.toLowerCase().split('/').filter(s => s !== '').join('-')}${slashes ? '/' : ''}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function urlToId(url: string | URL, separator = '_' ) {
|
|
16
|
+
return (url instanceof URL ? url.pathname : url).split('/').filter(s => s !== '').map((s) => { return s.toLowerCase().replace(/[^0-9a-z_]/g, separator)}).join(separator);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function dataEvotrkToString(data: DataEvotrk): string {
|
|
20
|
+
data.slide = data.slide ?? '';
|
|
21
|
+
data.type = data.type ?? '';
|
|
22
|
+
data.name = data.name ?? '';
|
|
23
|
+
return Object.entries((data))
|
|
24
|
+
.sort(([key1], [key2]) => {
|
|
25
|
+
if (key1 === 'slide') return -1;
|
|
26
|
+
if (key2 === 'slide') return 1;
|
|
27
|
+
if (key1 === 'type') return -1;
|
|
28
|
+
if (key2 === 'type') return 1;
|
|
29
|
+
if (key1 === 'name') return -1;
|
|
30
|
+
if (key2 === 'name') return 1;
|
|
31
|
+
return 0;
|
|
32
|
+
})
|
|
33
|
+
.map(([key, value]) => {
|
|
34
|
+
return value;
|
|
35
|
+
})
|
|
36
|
+
.join('|')
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { rm, cp, readdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import logger from '../lib/logger.js';
|
|
7
|
+
import type { IVeevaConfig } from '../types';
|
|
8
|
+
|
|
9
|
+
interface Params {
|
|
10
|
+
config: IVeevaConfig
|
|
11
|
+
rootDir: string;
|
|
12
|
+
astroDistDir: string;
|
|
13
|
+
deployDir: string;
|
|
14
|
+
}
|
|
15
|
+
export default async function({ config, rootDir, astroDistDir, deployDir }: Params) {
|
|
16
|
+
|
|
17
|
+
logger.info(`Copying files to ${deployDir.replace(rootDir, '')}`);
|
|
18
|
+
|
|
19
|
+
const dirContent = await readdir(astroDistDir, { withFileTypes: true });
|
|
20
|
+
for(const c of dirContent) {
|
|
21
|
+
if (c.isDirectory() && (c.name === 'shared' || config.presentation.slides.find(s => s.path === c.name))) {
|
|
22
|
+
await cp(join(c.parentPath, c.name), join(deployDir, c.name), { recursive: true });
|
|
23
|
+
|
|
24
|
+
if (c.name === 'shared') {
|
|
25
|
+
writeFile(join(deployDir, c.name, 'index.html'), `<html><head><meta charset="UTF-8"><title>${config.presentation.name} - Shared Resource</title></head><body><h1>${config.presentation.name} - Shared Resource</h1></body></html>`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import type { IVeevaConfig, IVeevaConfigSlide, Actions, IVeevaClmBinderFields } from '../types';
|
|
4
|
+
import logger from '../lib/logger.js';
|
|
5
|
+
import { pathToSlideZip } from '../lib/utils.js';
|
|
6
|
+
|
|
7
|
+
interface Params {
|
|
8
|
+
config: IVeevaConfig
|
|
9
|
+
deployDir: string;
|
|
10
|
+
buildName: string;
|
|
11
|
+
fieldsOnly?: boolean;
|
|
12
|
+
additional?: {
|
|
13
|
+
headers: string[];
|
|
14
|
+
presentation: string[];
|
|
15
|
+
slides: string[][];
|
|
16
|
+
shared: string[];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export default async function({ config, deployDir, buildName, fieldsOnly = false, additional }: Params) {
|
|
20
|
+
logger.info('Creating multichannel loader CSV');
|
|
21
|
+
const headers: string[] = (fieldsOnly ? CSV_HEADERS_FIELDS_ONLY : CSV_HEADERS).concat(additional?.headers ?? []);
|
|
22
|
+
const presentation = config.presentation;
|
|
23
|
+
|
|
24
|
+
for (const [key, header] of Object.entries(BINDER_PRES_HEADERS)) {
|
|
25
|
+
if (presentation[key as keyof IVeevaClmBinderFields]) {
|
|
26
|
+
headers.push(header);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
|
|
30
|
+
if (presentation.slideSettings?.[key as keyof IVeevaClmBinderFields] || config.slideSettings?.[key as keyof IVeevaClmBinderFields]) {
|
|
31
|
+
headers.push(header);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const rows: string[][] = [headers];
|
|
36
|
+
rows.push(createPresentationRow(headers, config).concat(additional?.presentation ?? []));
|
|
37
|
+
for (let i=0; i<config.presentation.slides.length; i++) {
|
|
38
|
+
rows.push(createSlideRow(headers, config, config.presentation.slides[i]).concat(additional?.slides?.[i] ?? []));
|
|
39
|
+
}
|
|
40
|
+
const shared = createSharedRow(headers, config);
|
|
41
|
+
shared && rows.push(shared.concat(additional?.shared ?? []));
|
|
42
|
+
|
|
43
|
+
await writeFile(join(deployDir, `_${config.presentation.presentationId}_${buildName}.csv`), rows.map((r)=>{return r.join(',');}).join('\n'), { encoding: 'utf8' });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
const
|
|
48
|
+
VEEVA_YES = 'YES',
|
|
49
|
+
VEEVA_NO = 'NO',
|
|
50
|
+
VEEVA_TYPE_PRESENTATION = 'Presentation',
|
|
51
|
+
VEEVA_TYPE_SLIDE = 'Slide',
|
|
52
|
+
VEEVA_TYPE_SHARED = 'Shared',
|
|
53
|
+
VEEVA_LIFECYCLE_BINDER = 'Binder Lifecycle',
|
|
54
|
+
VEEVA_LIFECYCLE_CRM_CONTENT = 'CRM Content Lifecycle',
|
|
55
|
+
VEEVA_IOS_RESOLUTION_DEFAULT = 'Default For Device',
|
|
56
|
+
VEEVA_MEDIATYPE_HTML = 'HTML',
|
|
57
|
+
|
|
58
|
+
// HEADERS
|
|
59
|
+
HEADER_FIELDS_ONLY = 'Fields Only',
|
|
60
|
+
HEADER_EXTERNAL_ID = 'external_id__v',
|
|
61
|
+
HEADER_NAME = 'name__v',
|
|
62
|
+
HEADER_TYPE = 'Type',
|
|
63
|
+
HEADER_LIFECYCLE = 'lifecycle__v',
|
|
64
|
+
|
|
65
|
+
HEADER_PRES_ID = 'pres.crm_presentation_id__v',
|
|
66
|
+
HEADER_PRES_TITLE = 'pres.title__v',
|
|
67
|
+
HEADER_PRES_PRODUCT = 'pres.product__v.name__v',
|
|
68
|
+
HEADER_PRES_COUNTRY = 'pres.country__v.name__v',
|
|
69
|
+
HEADER_PRES_LANGUAGE = 'pres.language__v',
|
|
70
|
+
HEADER_PRES_CLM_CONTENT = 'pres.clm_content__v',
|
|
71
|
+
HEADER_PRES_CLM_HIDDEN = 'pres.crm_hidden__v',
|
|
72
|
+
HEADER_PRES_TRAINING = 'pres.crm_training__v',
|
|
73
|
+
HEADER_PRES_CRM_TARGET = 'pres.crm_target_platform__v',
|
|
74
|
+
HEADER_PRES_CRM_ORG = 'pres.crm_org__v.crm_org_id__v',
|
|
75
|
+
HEADER_PRES_CRM_PRODUCT = 'pres.crm_product__v.id', // Get the Veeva ID from the URL
|
|
76
|
+
HEADER_PRES_CRM_CONTENT_GROUP = 'pres.crm_content_group__v.id', // Get the Veeva ID from the URL
|
|
77
|
+
HEADER_PRES_CRM_DETAIL_GROUP = 'pres.crm_detail_group__v.id', // Get the Veeva ID from the URL
|
|
78
|
+
HEADER_PRES_DETAIL_GROUP = 'pres.detail_group__v',
|
|
79
|
+
|
|
80
|
+
HEADER_PRESENTATION = 'Presentation Link',
|
|
81
|
+
HEADER_SLIDE_TITLE = 'slide.title__v',
|
|
82
|
+
HEADER_SLIDE_PRODUCT = 'slide.product__v.name__v',
|
|
83
|
+
HEADER_SLIDE_COUNTRY = 'slide.country__v.name__v',
|
|
84
|
+
HEADER_SLIDE_LANGUAGE = 'slide.language__v',
|
|
85
|
+
HEADER_SLIDE_MEDIA_TYPE = 'slide.crm_media_type__v',
|
|
86
|
+
HEADER_SLIDE_IOS_RESOLUTION = 'slide.ios_resolution__v',
|
|
87
|
+
HEADER_SLIDE_RELATED_SHARED = 'slide.related_shared_resource__v',
|
|
88
|
+
HEADER_SLIDE_CLM_CONTENT = 'slide.clm_content__v',
|
|
89
|
+
HEADER_SLIDE_FILE_NAME = 'slide.filename',
|
|
90
|
+
HEADER_SLIDE_REACTIONS = 'slide.crm_custom_reaction__v',
|
|
91
|
+
HEADER_SLIDE_DISABLE_ACTIONS = 'slide.crm_disable_actions__v',
|
|
92
|
+
HEADER_SLIDE_CRM_TARGET = 'slide.crm_target_platform__v',
|
|
93
|
+
HEADER_SLIDE_CRM_ORG = 'slide.crm_org__v.crm_org_id__v',
|
|
94
|
+
HEADER_SLIDE_CRM_PRODUCT = 'slide.crm_product__v.id', // Get the Veeva ID from the URL
|
|
95
|
+
HEADER_SLIDE_CRM_CONTENT_GROUP = 'slide.crm_content_group__v.id', // Get the Veeva ID from the URL
|
|
96
|
+
HEADER_SLIDE_CRM_DETAIL_GROUP = 'slide.crm_detail_group__v.id', // Get the Veeva ID from the URL
|
|
97
|
+
HEADER_SLIDE_CRM_SHARED = 'slide.crm_shared_resource__v',
|
|
98
|
+
HEADER_SLIDE_DETAIL_GROUP = 'slide.detail_group__v';
|
|
99
|
+
|
|
100
|
+
const CSV_HEADERS = [
|
|
101
|
+
HEADER_EXTERNAL_ID,
|
|
102
|
+
HEADER_TYPE,
|
|
103
|
+
HEADER_NAME,
|
|
104
|
+
HEADER_PRES_ID,
|
|
105
|
+
HEADER_PRES_TITLE,
|
|
106
|
+
HEADER_PRES_PRODUCT,
|
|
107
|
+
HEADER_PRES_COUNTRY,
|
|
108
|
+
HEADER_PRES_TRAINING,
|
|
109
|
+
HEADER_PRES_CLM_CONTENT,
|
|
110
|
+
HEADER_PRES_CLM_HIDDEN,
|
|
111
|
+
HEADER_SLIDE_TITLE,
|
|
112
|
+
HEADER_SLIDE_PRODUCT,
|
|
113
|
+
HEADER_SLIDE_COUNTRY,
|
|
114
|
+
HEADER_SLIDE_MEDIA_TYPE,
|
|
115
|
+
HEADER_SLIDE_IOS_RESOLUTION,
|
|
116
|
+
HEADER_SLIDE_DISABLE_ACTIONS,
|
|
117
|
+
HEADER_SLIDE_REACTIONS,
|
|
118
|
+
HEADER_SLIDE_FILE_NAME,
|
|
119
|
+
HEADER_SLIDE_RELATED_SHARED,
|
|
120
|
+
HEADER_SLIDE_CLM_CONTENT,
|
|
121
|
+
HEADER_SLIDE_CRM_SHARED,
|
|
122
|
+
HEADER_PRESENTATION,
|
|
123
|
+
HEADER_LIFECYCLE
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const CSV_HEADERS_FIELDS_ONLY = [
|
|
127
|
+
HEADER_FIELDS_ONLY,
|
|
128
|
+
...CSV_HEADERS.slice(0, CSV_HEADERS.indexOf(HEADER_SLIDE_FILE_NAME)),
|
|
129
|
+
...CSV_HEADERS.slice(CSV_HEADERS.indexOf(HEADER_SLIDE_FILE_NAME) + 1)
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
const BINDER_PRES_HEADERS: Record<keyof IVeevaClmBinderFields, string> = {
|
|
133
|
+
language: HEADER_PRES_LANGUAGE,
|
|
134
|
+
crmTarget: HEADER_PRES_CRM_TARGET,
|
|
135
|
+
crmOrg: HEADER_PRES_CRM_ORG,
|
|
136
|
+
crmProduct: HEADER_PRES_CRM_PRODUCT,
|
|
137
|
+
crmContentGroup: HEADER_PRES_CRM_CONTENT_GROUP,
|
|
138
|
+
crmDetailGroup: HEADER_PRES_CRM_DETAIL_GROUP,
|
|
139
|
+
detailGroup: HEADER_PRES_DETAIL_GROUP
|
|
140
|
+
};
|
|
141
|
+
const BINDER_SLIDE_HEADERS: Record<keyof IVeevaClmBinderFields, string> = {
|
|
142
|
+
language: HEADER_SLIDE_LANGUAGE,
|
|
143
|
+
crmTarget: HEADER_SLIDE_CRM_TARGET,
|
|
144
|
+
crmOrg: HEADER_SLIDE_CRM_ORG,
|
|
145
|
+
crmProduct: HEADER_SLIDE_CRM_PRODUCT,
|
|
146
|
+
crmContentGroup: HEADER_SLIDE_CRM_CONTENT_GROUP,
|
|
147
|
+
crmDetailGroup: HEADER_SLIDE_CRM_DETAIL_GROUP,
|
|
148
|
+
detailGroup: HEADER_SLIDE_DETAIL_GROUP
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
function createPresentationRow(headers: string[], config: IVeevaConfig): string[] {
|
|
153
|
+
const row: string[] = new Array(headers.length).fill('');
|
|
154
|
+
const presentation = config.presentation;
|
|
155
|
+
addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
|
|
156
|
+
addColumn(headers, row, HEADER_EXTERNAL_ID, presentation.externalId);
|
|
157
|
+
addColumn(headers, row, HEADER_TYPE, VEEVA_TYPE_PRESENTATION);
|
|
158
|
+
addColumn(headers, row, HEADER_NAME, presentation.name);
|
|
159
|
+
addColumn(headers, row, HEADER_PRES_ID, presentation.presentationId);
|
|
160
|
+
addColumn(headers, row, HEADER_PRES_TITLE, presentation.title ?? presentation.name);
|
|
161
|
+
addColumn(headers, row, HEADER_PRES_PRODUCT, presentation.product);
|
|
162
|
+
addColumn(headers, row, HEADER_PRES_COUNTRY, presentation.country);
|
|
163
|
+
addColumn(headers, row, HEADER_PRES_TRAINING, presentation.isTraining);
|
|
164
|
+
addColumn(headers, row, HEADER_PRES_CLM_CONTENT, VEEVA_YES);
|
|
165
|
+
addColumn(headers, row, HEADER_PRES_CLM_HIDDEN, presentation.isHidden ? VEEVA_YES : VEEVA_NO);
|
|
166
|
+
addColumn(headers, row, HEADER_LIFECYCLE, VEEVA_LIFECYCLE_BINDER);
|
|
167
|
+
// Binder fields
|
|
168
|
+
for (const [key, header] of Object.entries(BINDER_PRES_HEADERS)) {
|
|
169
|
+
addColumn(headers, row, header, presentation[key as keyof IVeevaClmBinderFields]);
|
|
170
|
+
}
|
|
171
|
+
return row;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function createSlideRow(headers: string[], config: IVeevaConfig, slide: IVeevaConfigSlide): string[] {
|
|
175
|
+
const row: string[] = new Array(headers.length).fill('');
|
|
176
|
+
addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
|
|
177
|
+
addColumn(headers, row, HEADER_EXTERNAL_ID, slide.externalId);
|
|
178
|
+
addColumn(headers, row, HEADER_TYPE, VEEVA_TYPE_SLIDE);
|
|
179
|
+
addColumn(headers, row, HEADER_NAME, slide.name ?? `${config.presentation.name} - ${slide.title ?? slide.path}`);
|
|
180
|
+
addColumn(headers, row, HEADER_SLIDE_TITLE, slide.title ?? slide.path);
|
|
181
|
+
addColumn(headers, row, HEADER_SLIDE_PRODUCT, config.presentation.product);
|
|
182
|
+
addColumn(headers, row, HEADER_SLIDE_COUNTRY, config.presentation.country);
|
|
183
|
+
addColumn(headers, row, HEADER_SLIDE_MEDIA_TYPE, slide.mediaType ?? config.presentation.slideSettings?.mediaType ?? config.slideSettings?.mediaType ?? VEEVA_MEDIATYPE_HTML);
|
|
184
|
+
addColumn(headers, row, HEADER_SLIDE_IOS_RESOLUTION, slide.iosResolution ?? config.presentation.slideSettings?.iosResolution ?? config.slideSettings?.iosResolution ?? VEEVA_IOS_RESOLUTION_DEFAULT);
|
|
185
|
+
|
|
186
|
+
const disable: Set<Actions> = new Set<Actions>(config.presentation.slideSettings?.disableActions ?? config.slideSettings?.disableActions ?? []);
|
|
187
|
+
slide.disableActions?.forEach((action) => {
|
|
188
|
+
disable.add(action);
|
|
189
|
+
});
|
|
190
|
+
slide.enableActions?.forEach((action) => {
|
|
191
|
+
disable.delete(action);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
addColumn(headers, row, HEADER_SLIDE_REACTIONS, disable.has('Reactions') ? ',a,b,c' : '');
|
|
195
|
+
disable.delete('Reactions');
|
|
196
|
+
addColumn(headers, row, HEADER_SLIDE_DISABLE_ACTIONS, [...disable].join(','));
|
|
197
|
+
|
|
198
|
+
addColumn(headers, row, HEADER_SLIDE_FILE_NAME, pathToSlideZip(slide.path));
|
|
199
|
+
addColumn(headers, row, HEADER_SLIDE_RELATED_SHARED, config.presentation.shared?.externalId ?? '');
|
|
200
|
+
addColumn(headers, row, HEADER_SLIDE_CLM_CONTENT, VEEVA_YES);
|
|
201
|
+
addColumn(headers, row, HEADER_PRESENTATION, config.presentation.externalId);
|
|
202
|
+
|
|
203
|
+
addColumn(headers, row, HEADER_LIFECYCLE, VEEVA_LIFECYCLE_CRM_CONTENT);
|
|
204
|
+
|
|
205
|
+
// Binder fields
|
|
206
|
+
for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
|
|
207
|
+
addColumn(headers, row, header, slide[key as keyof IVeevaClmBinderFields] ?? config.presentation.slideSettings?.[key as keyof IVeevaClmBinderFields] ?? config.slideSettings?.[key as keyof IVeevaClmBinderFields]);
|
|
208
|
+
}
|
|
209
|
+
return row;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function createSharedRow(headers: string[], config: IVeevaConfig): string[] | null {
|
|
213
|
+
const shared = config.presentation.shared;
|
|
214
|
+
if (!shared) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const row: string[] = new Array(headers.length).fill('');
|
|
218
|
+
addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
|
|
219
|
+
addColumn(headers, row, HEADER_TYPE, VEEVA_TYPE_SHARED);
|
|
220
|
+
addColumn(headers, row, HEADER_EXTERNAL_ID, shared.externalId);
|
|
221
|
+
addColumn(headers, row, HEADER_NAME, shared.name ?? `${config.presentation.name} - ${shared.title ?? 'Shared Resource'}`);
|
|
222
|
+
addColumn(headers, row, HEADER_SLIDE_TITLE, shared.title ?? 'Shared Resource');
|
|
223
|
+
addColumn(headers, row, HEADER_SLIDE_PRODUCT, config.presentation.product);
|
|
224
|
+
addColumn(headers, row, HEADER_SLIDE_COUNTRY, config.presentation.country);
|
|
225
|
+
addColumn(headers, row, HEADER_SLIDE_MEDIA_TYPE, shared.mediaType ?? VEEVA_MEDIATYPE_HTML);
|
|
226
|
+
addColumn(headers, row, HEADER_SLIDE_FILE_NAME, pathToSlideZip(shared.path ?? 'shared'));
|
|
227
|
+
addColumn(headers, row, HEADER_SLIDE_CLM_CONTENT, VEEVA_YES);
|
|
228
|
+
addColumn(headers, row, HEADER_SLIDE_CRM_SHARED, VEEVA_YES);
|
|
229
|
+
addColumn(headers, row, HEADER_LIFECYCLE, VEEVA_LIFECYCLE_CRM_CONTENT);
|
|
230
|
+
// Binder fields
|
|
231
|
+
for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
|
|
232
|
+
addColumn(headers, row, header, shared[key as keyof IVeevaClmBinderFields] ?? config.presentation.slideSettings?.[key as keyof IVeevaClmBinderFields] ?? config.slideSettings?.[key as keyof IVeevaClmBinderFields]);
|
|
233
|
+
}
|
|
234
|
+
return row;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function addColumn(headers: string[], row: string[], header: string, value?: any) { // boolean | string | string[]) {
|
|
238
|
+
const index = headers.findIndex(h => h === header);
|
|
239
|
+
if (index !== -1) {
|
|
240
|
+
row[index] = formatValue(value);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function formatValue(value?: any): string {
|
|
244
|
+
if (value !== undefined && value !== null) {
|
|
245
|
+
if (typeof value == 'object' && !Array.isArray(value)) {
|
|
246
|
+
value = formatValue(Object.entries(value)?.[0]?.[1]) ?? '';
|
|
247
|
+
} else {
|
|
248
|
+
if (Array.isArray(value)) {
|
|
249
|
+
value = value.length > 0 ? value.join(',') : '';
|
|
250
|
+
} else if (typeof value == 'boolean') {
|
|
251
|
+
value = value ? VEEVA_YES : VEEVA_NO;
|
|
252
|
+
}
|
|
253
|
+
if (/("|,)/.test(value)) {
|
|
254
|
+
value = `"${value.replace(/"/g, '""')}"`;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return value ?? '';
|
|
259
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { rm, readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import Zip from 'adm-zip';
|
|
4
|
+
import logger from '../lib/logger.js';
|
|
5
|
+
import { pathToSlideZip } from '../lib/utils.js';
|
|
6
|
+
|
|
7
|
+
interface Params {
|
|
8
|
+
deployDir: string;
|
|
9
|
+
}
|
|
10
|
+
export default async function({ deployDir }: Params) {
|
|
11
|
+
logger.info('Creating ZIP files');
|
|
12
|
+
const dirs = await readdir(deployDir);
|
|
13
|
+
|
|
14
|
+
for (const dir of dirs) {
|
|
15
|
+
const dirPath = join(deployDir, dir);
|
|
16
|
+
const zip = new Zip();
|
|
17
|
+
zip.addLocalFolder(dirPath);
|
|
18
|
+
await zip.writeZipPromise(join(deployDir, pathToSlideZip(dir)));
|
|
19
|
+
await rm(dirPath, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, rm, readdir } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve as resolvePath } from "node:path"
|
|
4
|
+
import { preview } from "astro";
|
|
5
|
+
import { IVeevaConfig } from '../types';
|
|
6
|
+
import loadEnv from '../lib/env.js';
|
|
7
|
+
import logger from '../lib/logger.js';
|
|
8
|
+
import parseArgv from '../lib/parse-argv.js';
|
|
9
|
+
import copyFiles from './copy-files.js';
|
|
10
|
+
import generateThumbs from './generate-thumbs.js';
|
|
11
|
+
import updateShared from './update-shared.js';
|
|
12
|
+
import createZips from './create-zips.js';
|
|
13
|
+
import createCsv from './create-csv.js';
|
|
14
|
+
|
|
15
|
+
const argv = parseArgv({
|
|
16
|
+
mode: 'string',
|
|
17
|
+
fieldsOnly: 'boolean'
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// const { BUILD_TARGET, VEEVA_ENV, VEEVA_TRAINING } = await loadEnv(argv.mode);
|
|
21
|
+
|
|
22
|
+
interface Opts {
|
|
23
|
+
root: string;
|
|
24
|
+
config: IVeevaConfig;
|
|
25
|
+
env: string;
|
|
26
|
+
csvOnly?: boolean;
|
|
27
|
+
serverPort?: number;
|
|
28
|
+
}
|
|
29
|
+
export default async function({ root, config, env, csvOnly = false, serverPort = 4320 }: Opts) {
|
|
30
|
+
const rootDir = root ?? process.cwd();
|
|
31
|
+
// we could import the astro config and see if the outDir was set but right now it's not necessary
|
|
32
|
+
const astroDistDir = join(rootDir, 'dist');
|
|
33
|
+
const buildName = `${env}${config.presentation.isTraining === true ? '-training' : ''}`;
|
|
34
|
+
const buildType = argv.mode ?? buildName;
|
|
35
|
+
const deployDir = join(rootDir, 'deploy', buildType);
|
|
36
|
+
|
|
37
|
+
if (existsSync(deployDir)) {
|
|
38
|
+
const items = await readdir(deployDir);
|
|
39
|
+
for (const item of items) {
|
|
40
|
+
await rm(join(deployDir, item), { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
await mkdir(deployDir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!csvOnly) {
|
|
47
|
+
await copyFiles({ config, rootDir, astroDistDir, deployDir });
|
|
48
|
+
await updateShared({ deployDir });
|
|
49
|
+
|
|
50
|
+
const server = await preview({
|
|
51
|
+
root: rootDir,
|
|
52
|
+
server: {
|
|
53
|
+
port: serverPort
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await generateThumbs({
|
|
58
|
+
server: { host: server.host ?? 'localhost', port: server.port },
|
|
59
|
+
config,
|
|
60
|
+
deployDir,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await server.stop();
|
|
64
|
+
|
|
65
|
+
await createZips({ deployDir });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await createCsv({ config, deployDir, buildName, fieldsOnly: csvOnly })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import puppeteer, { type Viewport } from "puppeteer";
|
|
4
|
+
import sharp from 'sharp';
|
|
5
|
+
|
|
6
|
+
import logger from '../lib/logger.js';
|
|
7
|
+
import type { IVeevaConfig } from "../types";
|
|
8
|
+
|
|
9
|
+
interface Params {
|
|
10
|
+
config: IVeevaConfig;
|
|
11
|
+
deployDir: string;
|
|
12
|
+
server: {
|
|
13
|
+
protocol?: 'http' | 'https';
|
|
14
|
+
host: string;
|
|
15
|
+
port: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default async function({ config, deployDir, server }: Params) {
|
|
20
|
+
logger.info('Generating thumbnails');
|
|
21
|
+
initThumbSettings(config);
|
|
22
|
+
await generateSharedThumb(config, deployDir);
|
|
23
|
+
|
|
24
|
+
const browser = await puppeteer.launch();
|
|
25
|
+
const page = await browser.newPage();
|
|
26
|
+
await page.setViewport({
|
|
27
|
+
width: config.dimensions.width,
|
|
28
|
+
height: config.dimensions.height,
|
|
29
|
+
hasTouch: true,
|
|
30
|
+
isLandscape: true,
|
|
31
|
+
isMobile: true
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
for (const slide of config.presentation.slides) {
|
|
35
|
+
for (const settings of slide.thumbnails!.settings!) {
|
|
36
|
+
logger.info(`Generating /${slide.path}/`);
|
|
37
|
+
const url = `${server.protocol ?? 'http'}://${server.host}:${server.port}/${slide.path}/${settings.queryStringParams ? `?${objToQS(settings.queryStringParams)}` : ''}`
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await page.goto(url, {
|
|
41
|
+
waitUntil: 'networkidle0'
|
|
42
|
+
});
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
if (settings.delay && settings.delay > 0) {
|
|
47
|
+
logger.info(`Delaying thumbnail generation for ${settings.delay}ms`);
|
|
48
|
+
await sleep(settings.delay);
|
|
49
|
+
}
|
|
50
|
+
let screenshot = await page.screenshot();
|
|
51
|
+
let thumb = await (
|
|
52
|
+
sharp(screenshot)
|
|
53
|
+
.resize({
|
|
54
|
+
width: settings.width,
|
|
55
|
+
height: settings.height
|
|
56
|
+
})
|
|
57
|
+
.png({
|
|
58
|
+
palette: true,
|
|
59
|
+
compressionLevel: 9,
|
|
60
|
+
quality: 80,
|
|
61
|
+
effort: 10
|
|
62
|
+
})
|
|
63
|
+
).toBuffer();
|
|
64
|
+
|
|
65
|
+
const fileName = typeof settings.filename == 'function' ? settings.filename(config, slide, settings) : settings.filename ?? 'thumb.png';
|
|
66
|
+
await writeFile(join(deployDir, slide.path, fileName), thumb);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await page.close();
|
|
71
|
+
await browser.close();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function generateSharedThumb(config: IVeevaConfig, deployDir: string) {
|
|
75
|
+
let settings = config.thumbnails?.settings?.find(s => s.filename === 'thumb.png');
|
|
76
|
+
const width = settings?.width ?? 600;
|
|
77
|
+
const height = settings?.height ?? width / 4 * 3;
|
|
78
|
+
const svgImage = `
|
|
79
|
+
<svg width="${width}" height="${height}">
|
|
80
|
+
<style>
|
|
81
|
+
.title { fill: #fff; font-size: 32px; font-weight: bold; font-family: sans-serif; }
|
|
82
|
+
</style>
|
|
83
|
+
<text x="50%" y="40%" text-anchor="middle" class="title">
|
|
84
|
+
<tspan x="50%" dy="1.2em">${config.presentation.name}</tspan>
|
|
85
|
+
<tspan x="50%" dy="1.2em">Shared Resource</tspan>
|
|
86
|
+
</text>
|
|
87
|
+
</svg>`;
|
|
88
|
+
|
|
89
|
+
const buffer = await sharp({
|
|
90
|
+
create: {
|
|
91
|
+
width,
|
|
92
|
+
height: height,
|
|
93
|
+
channels: 4,
|
|
94
|
+
background: { r: 0, g: 0, b: 0, alpha: 1 } // Blue background
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
.composite([{
|
|
98
|
+
input: Buffer.from(svgImage),
|
|
99
|
+
gravity: 'center'
|
|
100
|
+
}])
|
|
101
|
+
.png()
|
|
102
|
+
.toBuffer();
|
|
103
|
+
|
|
104
|
+
await writeFile(join(deployDir, 'shared', 'thumb.png'), buffer);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function objToQS(data: Record<string, any>) {
|
|
108
|
+
return Object.entries(data)
|
|
109
|
+
.map(([key, val]) => {
|
|
110
|
+
return `${encodeURIComponent(key)}=${encodeURIComponent(val?.toString() ?? '')}`;
|
|
111
|
+
})
|
|
112
|
+
.join('&');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function initThumbSettings(config: IVeevaConfig) {
|
|
116
|
+
if (!config.thumbnails) {
|
|
117
|
+
config.thumbnails = {};
|
|
118
|
+
}
|
|
119
|
+
if (!config.thumbnails.default) {
|
|
120
|
+
config.thumbnails.default = {};
|
|
121
|
+
}
|
|
122
|
+
if (!config.thumbnails.default.width) {
|
|
123
|
+
config.thumbnails.default.width = 600;
|
|
124
|
+
}
|
|
125
|
+
if (!config.thumbnails.default.height) {
|
|
126
|
+
config.thumbnails.default.height = config.thumbnails.default.width / 4 * 3;
|
|
127
|
+
}
|
|
128
|
+
if (!config.thumbnails.default.filename) {
|
|
129
|
+
config.thumbnails.default.filename = 'thumb.png';
|
|
130
|
+
}
|
|
131
|
+
if (!config.thumbnails.settings) {
|
|
132
|
+
config.thumbnails.settings = [];
|
|
133
|
+
}
|
|
134
|
+
if (config.thumbnails.settings.length === 0) {
|
|
135
|
+
config.thumbnails.settings.push(config.thumbnails.default);
|
|
136
|
+
} else {
|
|
137
|
+
for (let i=0; i<config.thumbnails.settings.length; i++) {
|
|
138
|
+
config.thumbnails.settings[i] = {...config.thumbnails.default, ...config.thumbnails.settings[i]};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const slide of config.presentation.slides) {
|
|
143
|
+
if (!slide.thumbnails) {
|
|
144
|
+
slide.thumbnails = config.thumbnails;
|
|
145
|
+
}
|
|
146
|
+
if (!slide.thumbnails.settings) {
|
|
147
|
+
slide.thumbnails.settings = config.thumbnails.settings;
|
|
148
|
+
} else {
|
|
149
|
+
switch (slide.thumbnails.strategy) {
|
|
150
|
+
case 'append':
|
|
151
|
+
slide.thumbnails.settings = config.thumbnails.settings.concat(
|
|
152
|
+
slide.thumbnails.settings.map((sets) => {
|
|
153
|
+
return {...config.thumbnails!.default, ...slide.thumbnails?.default ?? {}, ...sets}
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
break;
|
|
157
|
+
case 'replace':
|
|
158
|
+
break;
|
|
159
|
+
case 'merge':
|
|
160
|
+
default:
|
|
161
|
+
for (let i=0; i<slide.thumbnails.settings.length; i++) {
|
|
162
|
+
slide.thumbnails.settings[i] = {
|
|
163
|
+
...(config.thumbnails.settings[i] ?? config.thumbnails.default),
|
|
164
|
+
...(slide.thumbnails.default ?? {}),
|
|
165
|
+
...slide.thumbnails.settings[i]
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function sleep(timeout = 0) {
|
|
176
|
+
return new Promise((resolve) => {
|
|
177
|
+
setTimeout(resolve, timeout);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as deploy } from './deploy.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join, extname } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import logger from '../lib/logger.js';
|
|
5
|
+
|
|
6
|
+
interface Params {
|
|
7
|
+
deployDir: string;
|
|
8
|
+
}
|
|
9
|
+
export default async function({ deployDir }: Params) {
|
|
10
|
+
logger.info('Updating /shared/ to ./shared/');
|
|
11
|
+
|
|
12
|
+
const dirs = await readdir(deployDir);
|
|
13
|
+
for (const dir of dirs) {
|
|
14
|
+
if (dir === 'shared') {
|
|
15
|
+
await handleShared(deployDir, dir);
|
|
16
|
+
} else {
|
|
17
|
+
await handleHTML(deployDir, dir)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function handleShared(deployDir: string, dir: string) {
|
|
23
|
+
const files = (await readdir(join(deployDir, dir)))
|
|
24
|
+
.filter((f) => {
|
|
25
|
+
switch (extname(f)) {
|
|
26
|
+
case '.css':
|
|
27
|
+
case '.js':
|
|
28
|
+
return true;
|
|
29
|
+
default:
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
await updateSharedPath(join(deployDir, dir, file));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function handleHTML(deployDir: string, dir: string) {
|
|
39
|
+
await updateSharedPath(join(deployDir, dir, 'index.html'));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function updateSharedPath(filepath: string) {
|
|
43
|
+
const str = await readFile(filepath, 'utf8');
|
|
44
|
+
let shared = './shared/';
|
|
45
|
+
if (extname(filepath) === '.css') { // relative paths inside a css
|
|
46
|
+
shared = './';
|
|
47
|
+
}
|
|
48
|
+
await writeFile(filepath, str.replace(/(?:\.*\/)*shared\//g, shared));
|
|
49
|
+
}
|