@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.
Files changed (91) hide show
  1. package/.editorconfig +12 -0
  2. package/build.ts +38 -0
  3. package/dist/apps/index.d.ts +2 -0
  4. package/dist/apps/index.js +2 -0
  5. package/dist/apps/page-manager/bulk-create.d.ts +1 -0
  6. package/dist/apps/page-manager/bulk-create.js +120 -0
  7. package/dist/apps/page-manager/index.d.ts +5 -0
  8. package/dist/apps/page-manager/index.js +24 -0
  9. package/dist/apps/page-manager/single-create.d.ts +1 -0
  10. package/dist/apps/page-manager/single-create.js +78 -0
  11. package/dist/apps/page-manager/templates/contents.astro.txt +12 -0
  12. package/dist/apps/page-manager/templates/page.astro.txt +4 -0
  13. package/dist/apps/page-manager/utils.d.ts +10 -0
  14. package/dist/apps/page-manager/utils.js +57 -0
  15. package/dist/apps/utils.d.ts +5 -0
  16. package/dist/apps/utils.js +43 -0
  17. package/dist/apps/veeva-config-manager/create.d.ts +1 -0
  18. package/dist/apps/veeva-config-manager/create.js +136 -0
  19. package/dist/apps/veeva-config-manager/index.d.ts +3 -0
  20. package/dist/apps/veeva-config-manager/index.js +16 -0
  21. package/dist/apps/veeva-config-manager/templates/config.ts.txt +60 -0
  22. package/dist/env/schema.d.ts +15 -0
  23. package/dist/env/schema.js +28 -0
  24. package/dist/lib/const.d.ts +15 -0
  25. package/dist/lib/const.js +14 -0
  26. package/dist/lib/env.d.ts +8 -0
  27. package/dist/lib/env.js +17 -0
  28. package/dist/lib/index.d.ts +5 -0
  29. package/dist/lib/index.js +5 -0
  30. package/dist/lib/logger.d.ts +9 -0
  31. package/dist/lib/logger.js +72 -0
  32. package/dist/lib/parse-argv.d.ts +31 -0
  33. package/dist/lib/parse-argv.js +109 -0
  34. package/dist/lib/parse-env.d.ts +45 -0
  35. package/dist/lib/parse-env.js +124 -0
  36. package/dist/lib/utils.d.ts +8 -0
  37. package/dist/lib/utils.js +37 -0
  38. package/dist/tasks/copy-files.d.ts +9 -0
  39. package/dist/tasks/copy-files.js +15 -0
  40. package/dist/tasks/create-csv.d.ts +15 -0
  41. package/dist/tasks/create-csv.js +186 -0
  42. package/dist/tasks/create-zips.d.ts +5 -0
  43. package/dist/tasks/create-zips.js +16 -0
  44. package/dist/tasks/deploy.d.ts +10 -0
  45. package/dist/tasks/deploy.js +49 -0
  46. package/dist/tasks/generate-thumbs.d.ts +12 -0
  47. package/dist/tasks/generate-thumbs.js +152 -0
  48. package/dist/tasks/index.d.ts +1 -0
  49. package/dist/tasks/index.js +1 -0
  50. package/dist/tasks/update-shared.d.ts +5 -0
  51. package/dist/tasks/update-shared.js +41 -0
  52. package/dist/types/config.d.ts +52 -0
  53. package/dist/types/env.d.ts +7 -0
  54. package/dist/types/index.d.ts +9 -0
  55. package/dist/types/veeva.d.ts +58 -0
  56. package/dist/veeva/slideManager.d.ts +33 -0
  57. package/dist/veeva/slideManager.js +120 -0
  58. package/package.json +39 -0
  59. package/src/apps/index.ts +2 -0
  60. package/src/apps/page-manager/bulk-create.ts +131 -0
  61. package/src/apps/page-manager/index.ts +31 -0
  62. package/src/apps/page-manager/single-create.ts +97 -0
  63. package/src/apps/page-manager/templates/contents.astro.txt +12 -0
  64. package/src/apps/page-manager/templates/page.astro.txt +4 -0
  65. package/src/apps/page-manager/utils.ts +70 -0
  66. package/src/apps/utils.ts +47 -0
  67. package/src/apps/veeva-config-manager/create.ts +153 -0
  68. package/src/apps/veeva-config-manager/index.ts +20 -0
  69. package/src/apps/veeva-config-manager/templates/config.ts.txt +60 -0
  70. package/src/env/schema.ts +43 -0
  71. package/src/lib/const.ts +17 -0
  72. package/src/lib/env.ts +27 -0
  73. package/src/lib/index.ts +5 -0
  74. package/src/lib/logger.ts +84 -0
  75. package/src/lib/parse-argv.ts +125 -0
  76. package/src/lib/parse-env.ts +147 -0
  77. package/src/lib/utils.ts +37 -0
  78. package/src/tasks/copy-files.ts +29 -0
  79. package/src/tasks/create-csv.ts +259 -0
  80. package/src/tasks/create-zips.ts +21 -0
  81. package/src/tasks/deploy.ts +72 -0
  82. package/src/tasks/generate-thumbs.ts +179 -0
  83. package/src/tasks/index.ts +1 -0
  84. package/src/tasks/update-shared.ts +49 -0
  85. package/src/types/config.d.ts +52 -0
  86. package/src/types/env.d.ts +7 -0
  87. package/src/types/index.d.ts +9 -0
  88. package/src/types/veeva.d.ts +58 -0
  89. package/src/veeva/readme.md +77 -0
  90. package/src/veeva/slideManager.ts +139 -0
  91. package/tsconfig.json +27 -0
@@ -0,0 +1,186 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import logger from '../lib/logger.js';
4
+ import { pathToSlideZip } from '../lib/utils.js';
5
+ export default async function ({ config, deployDir, buildName, fieldsOnly = false, additional }) {
6
+ logger.info('Creating multichannel loader CSV');
7
+ const headers = (fieldsOnly ? CSV_HEADERS_FIELDS_ONLY : CSV_HEADERS).concat(additional?.headers ?? []);
8
+ const presentation = config.presentation;
9
+ for (const [key, header] of Object.entries(BINDER_PRES_HEADERS)) {
10
+ if (presentation[key]) {
11
+ headers.push(header);
12
+ }
13
+ }
14
+ for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
15
+ if (presentation.slideSettings?.[key] || config.slideSettings?.[key]) {
16
+ headers.push(header);
17
+ }
18
+ }
19
+ const rows = [headers];
20
+ rows.push(createPresentationRow(headers, config).concat(additional?.presentation ?? []));
21
+ for (let i = 0; i < config.presentation.slides.length; i++) {
22
+ rows.push(createSlideRow(headers, config, config.presentation.slides[i]).concat(additional?.slides?.[i] ?? []));
23
+ }
24
+ const shared = createSharedRow(headers, config);
25
+ shared && rows.push(shared.concat(additional?.shared ?? []));
26
+ await writeFile(join(deployDir, `_${config.presentation.presentationId}_${buildName}.csv`), rows.map((r) => { return r.join(','); }).join('\n'), { encoding: 'utf8' });
27
+ }
28
+ const VEEVA_YES = 'YES', VEEVA_NO = 'NO', VEEVA_TYPE_PRESENTATION = 'Presentation', VEEVA_TYPE_SLIDE = 'Slide', VEEVA_TYPE_SHARED = 'Shared', VEEVA_LIFECYCLE_BINDER = 'Binder Lifecycle', VEEVA_LIFECYCLE_CRM_CONTENT = 'CRM Content Lifecycle', VEEVA_IOS_RESOLUTION_DEFAULT = 'Default For Device', VEEVA_MEDIATYPE_HTML = 'HTML',
29
+ // HEADERS
30
+ HEADER_FIELDS_ONLY = 'Fields Only', HEADER_EXTERNAL_ID = 'external_id__v', HEADER_NAME = 'name__v', HEADER_TYPE = 'Type', HEADER_LIFECYCLE = 'lifecycle__v', HEADER_PRES_ID = 'pres.crm_presentation_id__v', HEADER_PRES_TITLE = 'pres.title__v', HEADER_PRES_PRODUCT = 'pres.product__v.name__v', HEADER_PRES_COUNTRY = 'pres.country__v.name__v', HEADER_PRES_LANGUAGE = 'pres.language__v', HEADER_PRES_CLM_CONTENT = 'pres.clm_content__v', HEADER_PRES_CLM_HIDDEN = 'pres.crm_hidden__v', HEADER_PRES_TRAINING = 'pres.crm_training__v', HEADER_PRES_CRM_TARGET = 'pres.crm_target_platform__v', HEADER_PRES_CRM_ORG = 'pres.crm_org__v.crm_org_id__v', HEADER_PRES_CRM_PRODUCT = 'pres.crm_product__v.id', // Get the Veeva ID from the URL
31
+ HEADER_PRES_CRM_CONTENT_GROUP = 'pres.crm_content_group__v.id', // Get the Veeva ID from the URL
32
+ HEADER_PRES_CRM_DETAIL_GROUP = 'pres.crm_detail_group__v.id', // Get the Veeva ID from the URL
33
+ HEADER_PRES_DETAIL_GROUP = 'pres.detail_group__v', HEADER_PRESENTATION = 'Presentation Link', HEADER_SLIDE_TITLE = 'slide.title__v', HEADER_SLIDE_PRODUCT = 'slide.product__v.name__v', HEADER_SLIDE_COUNTRY = 'slide.country__v.name__v', HEADER_SLIDE_LANGUAGE = 'slide.language__v', HEADER_SLIDE_MEDIA_TYPE = 'slide.crm_media_type__v', HEADER_SLIDE_IOS_RESOLUTION = 'slide.ios_resolution__v', HEADER_SLIDE_RELATED_SHARED = 'slide.related_shared_resource__v', HEADER_SLIDE_CLM_CONTENT = 'slide.clm_content__v', HEADER_SLIDE_FILE_NAME = 'slide.filename', HEADER_SLIDE_REACTIONS = 'slide.crm_custom_reaction__v', HEADER_SLIDE_DISABLE_ACTIONS = 'slide.crm_disable_actions__v', HEADER_SLIDE_CRM_TARGET = 'slide.crm_target_platform__v', HEADER_SLIDE_CRM_ORG = 'slide.crm_org__v.crm_org_id__v', HEADER_SLIDE_CRM_PRODUCT = 'slide.crm_product__v.id', // Get the Veeva ID from the URL
34
+ HEADER_SLIDE_CRM_CONTENT_GROUP = 'slide.crm_content_group__v.id', // Get the Veeva ID from the URL
35
+ HEADER_SLIDE_CRM_DETAIL_GROUP = 'slide.crm_detail_group__v.id', // Get the Veeva ID from the URL
36
+ HEADER_SLIDE_CRM_SHARED = 'slide.crm_shared_resource__v', HEADER_SLIDE_DETAIL_GROUP = 'slide.detail_group__v';
37
+ const CSV_HEADERS = [
38
+ HEADER_EXTERNAL_ID,
39
+ HEADER_TYPE,
40
+ HEADER_NAME,
41
+ HEADER_PRES_ID,
42
+ HEADER_PRES_TITLE,
43
+ HEADER_PRES_PRODUCT,
44
+ HEADER_PRES_COUNTRY,
45
+ HEADER_PRES_TRAINING,
46
+ HEADER_PRES_CLM_CONTENT,
47
+ HEADER_PRES_CLM_HIDDEN,
48
+ HEADER_SLIDE_TITLE,
49
+ HEADER_SLIDE_PRODUCT,
50
+ HEADER_SLIDE_COUNTRY,
51
+ HEADER_SLIDE_MEDIA_TYPE,
52
+ HEADER_SLIDE_IOS_RESOLUTION,
53
+ HEADER_SLIDE_DISABLE_ACTIONS,
54
+ HEADER_SLIDE_REACTIONS,
55
+ HEADER_SLIDE_FILE_NAME,
56
+ HEADER_SLIDE_RELATED_SHARED,
57
+ HEADER_SLIDE_CLM_CONTENT,
58
+ HEADER_SLIDE_CRM_SHARED,
59
+ HEADER_PRESENTATION,
60
+ HEADER_LIFECYCLE
61
+ ];
62
+ const CSV_HEADERS_FIELDS_ONLY = [
63
+ HEADER_FIELDS_ONLY,
64
+ ...CSV_HEADERS.slice(0, CSV_HEADERS.indexOf(HEADER_SLIDE_FILE_NAME)),
65
+ ...CSV_HEADERS.slice(CSV_HEADERS.indexOf(HEADER_SLIDE_FILE_NAME) + 1)
66
+ ];
67
+ const BINDER_PRES_HEADERS = {
68
+ language: HEADER_PRES_LANGUAGE,
69
+ crmTarget: HEADER_PRES_CRM_TARGET,
70
+ crmOrg: HEADER_PRES_CRM_ORG,
71
+ crmProduct: HEADER_PRES_CRM_PRODUCT,
72
+ crmContentGroup: HEADER_PRES_CRM_CONTENT_GROUP,
73
+ crmDetailGroup: HEADER_PRES_CRM_DETAIL_GROUP,
74
+ detailGroup: HEADER_PRES_DETAIL_GROUP
75
+ };
76
+ const BINDER_SLIDE_HEADERS = {
77
+ language: HEADER_SLIDE_LANGUAGE,
78
+ crmTarget: HEADER_SLIDE_CRM_TARGET,
79
+ crmOrg: HEADER_SLIDE_CRM_ORG,
80
+ crmProduct: HEADER_SLIDE_CRM_PRODUCT,
81
+ crmContentGroup: HEADER_SLIDE_CRM_CONTENT_GROUP,
82
+ crmDetailGroup: HEADER_SLIDE_CRM_DETAIL_GROUP,
83
+ detailGroup: HEADER_SLIDE_DETAIL_GROUP
84
+ };
85
+ function createPresentationRow(headers, config) {
86
+ const row = new Array(headers.length).fill('');
87
+ const presentation = config.presentation;
88
+ addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
89
+ addColumn(headers, row, HEADER_EXTERNAL_ID, presentation.externalId);
90
+ addColumn(headers, row, HEADER_TYPE, VEEVA_TYPE_PRESENTATION);
91
+ addColumn(headers, row, HEADER_NAME, presentation.name);
92
+ addColumn(headers, row, HEADER_PRES_ID, presentation.presentationId);
93
+ addColumn(headers, row, HEADER_PRES_TITLE, presentation.title ?? presentation.name);
94
+ addColumn(headers, row, HEADER_PRES_PRODUCT, presentation.product);
95
+ addColumn(headers, row, HEADER_PRES_COUNTRY, presentation.country);
96
+ addColumn(headers, row, HEADER_PRES_TRAINING, presentation.isTraining);
97
+ addColumn(headers, row, HEADER_PRES_CLM_CONTENT, VEEVA_YES);
98
+ addColumn(headers, row, HEADER_PRES_CLM_HIDDEN, presentation.isHidden ? VEEVA_YES : VEEVA_NO);
99
+ addColumn(headers, row, HEADER_LIFECYCLE, VEEVA_LIFECYCLE_BINDER);
100
+ // Binder fields
101
+ for (const [key, header] of Object.entries(BINDER_PRES_HEADERS)) {
102
+ addColumn(headers, row, header, presentation[key]);
103
+ }
104
+ return row;
105
+ }
106
+ function createSlideRow(headers, config, slide) {
107
+ const row = new Array(headers.length).fill('');
108
+ addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
109
+ addColumn(headers, row, HEADER_EXTERNAL_ID, slide.externalId);
110
+ addColumn(headers, row, HEADER_TYPE, VEEVA_TYPE_SLIDE);
111
+ addColumn(headers, row, HEADER_NAME, slide.name ?? `${config.presentation.name} - ${slide.title ?? slide.path}`);
112
+ addColumn(headers, row, HEADER_SLIDE_TITLE, slide.title ?? slide.path);
113
+ addColumn(headers, row, HEADER_SLIDE_PRODUCT, config.presentation.product);
114
+ addColumn(headers, row, HEADER_SLIDE_COUNTRY, config.presentation.country);
115
+ addColumn(headers, row, HEADER_SLIDE_MEDIA_TYPE, slide.mediaType ?? config.presentation.slideSettings?.mediaType ?? config.slideSettings?.mediaType ?? VEEVA_MEDIATYPE_HTML);
116
+ addColumn(headers, row, HEADER_SLIDE_IOS_RESOLUTION, slide.iosResolution ?? config.presentation.slideSettings?.iosResolution ?? config.slideSettings?.iosResolution ?? VEEVA_IOS_RESOLUTION_DEFAULT);
117
+ const disable = new Set(config.presentation.slideSettings?.disableActions ?? config.slideSettings?.disableActions ?? []);
118
+ slide.disableActions?.forEach((action) => {
119
+ disable.add(action);
120
+ });
121
+ slide.enableActions?.forEach((action) => {
122
+ disable.delete(action);
123
+ });
124
+ addColumn(headers, row, HEADER_SLIDE_REACTIONS, disable.has('Reactions') ? ',a,b,c' : '');
125
+ disable.delete('Reactions');
126
+ addColumn(headers, row, HEADER_SLIDE_DISABLE_ACTIONS, [...disable].join(','));
127
+ addColumn(headers, row, HEADER_SLIDE_FILE_NAME, pathToSlideZip(slide.path));
128
+ addColumn(headers, row, HEADER_SLIDE_RELATED_SHARED, config.presentation.shared?.externalId ?? '');
129
+ addColumn(headers, row, HEADER_SLIDE_CLM_CONTENT, VEEVA_YES);
130
+ addColumn(headers, row, HEADER_PRESENTATION, config.presentation.externalId);
131
+ addColumn(headers, row, HEADER_LIFECYCLE, VEEVA_LIFECYCLE_CRM_CONTENT);
132
+ // Binder fields
133
+ for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
134
+ addColumn(headers, row, header, slide[key] ?? config.presentation.slideSettings?.[key] ?? config.slideSettings?.[key]);
135
+ }
136
+ return row;
137
+ }
138
+ function createSharedRow(headers, config) {
139
+ const shared = config.presentation.shared;
140
+ if (!shared) {
141
+ return null;
142
+ }
143
+ const row = new Array(headers.length).fill('');
144
+ addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
145
+ addColumn(headers, row, HEADER_TYPE, VEEVA_TYPE_SHARED);
146
+ addColumn(headers, row, HEADER_EXTERNAL_ID, shared.externalId);
147
+ addColumn(headers, row, HEADER_NAME, shared.name ?? `${config.presentation.name} - ${shared.title ?? 'Shared Resource'}`);
148
+ addColumn(headers, row, HEADER_SLIDE_TITLE, shared.title ?? 'Shared Resource');
149
+ addColumn(headers, row, HEADER_SLIDE_PRODUCT, config.presentation.product);
150
+ addColumn(headers, row, HEADER_SLIDE_COUNTRY, config.presentation.country);
151
+ addColumn(headers, row, HEADER_SLIDE_MEDIA_TYPE, shared.mediaType ?? VEEVA_MEDIATYPE_HTML);
152
+ addColumn(headers, row, HEADER_SLIDE_FILE_NAME, pathToSlideZip(shared.path ?? 'shared'));
153
+ addColumn(headers, row, HEADER_SLIDE_CLM_CONTENT, VEEVA_YES);
154
+ addColumn(headers, row, HEADER_SLIDE_CRM_SHARED, VEEVA_YES);
155
+ addColumn(headers, row, HEADER_LIFECYCLE, VEEVA_LIFECYCLE_CRM_CONTENT);
156
+ // Binder fields
157
+ for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
158
+ addColumn(headers, row, header, shared[key] ?? config.presentation.slideSettings?.[key] ?? config.slideSettings?.[key]);
159
+ }
160
+ return row;
161
+ }
162
+ function addColumn(headers, row, header, value) {
163
+ const index = headers.findIndex(h => h === header);
164
+ if (index !== -1) {
165
+ row[index] = formatValue(value);
166
+ }
167
+ }
168
+ function formatValue(value) {
169
+ if (value !== undefined && value !== null) {
170
+ if (typeof value == 'object' && !Array.isArray(value)) {
171
+ value = formatValue(Object.entries(value)?.[0]?.[1]) ?? '';
172
+ }
173
+ else {
174
+ if (Array.isArray(value)) {
175
+ value = value.length > 0 ? value.join(',') : '';
176
+ }
177
+ else if (typeof value == 'boolean') {
178
+ value = value ? VEEVA_YES : VEEVA_NO;
179
+ }
180
+ if (/("|,)/.test(value)) {
181
+ value = `"${value.replace(/"/g, '""')}"`;
182
+ }
183
+ }
184
+ }
185
+ return value ?? '';
186
+ }
@@ -0,0 +1,5 @@
1
+ interface Params {
2
+ deployDir: string;
3
+ }
4
+ export default function ({ deployDir }: Params): Promise<void>;
5
+ export {};
@@ -0,0 +1,16 @@
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
+ export default async function ({ deployDir }) {
7
+ logger.info('Creating ZIP files');
8
+ const dirs = await readdir(deployDir);
9
+ for (const dir of dirs) {
10
+ const dirPath = join(deployDir, dir);
11
+ const zip = new Zip();
12
+ zip.addLocalFolder(dirPath);
13
+ await zip.writeZipPromise(join(deployDir, pathToSlideZip(dir)));
14
+ await rm(dirPath, { recursive: true });
15
+ }
16
+ }
@@ -0,0 +1,10 @@
1
+ import { IVeevaConfig } from '../types';
2
+ interface Opts {
3
+ root: string;
4
+ config: IVeevaConfig;
5
+ env: string;
6
+ csvOnly?: boolean;
7
+ serverPort?: number;
8
+ }
9
+ export default function ({ root, config, env, csvOnly, serverPort }: Opts): Promise<void>;
10
+ export {};
@@ -0,0 +1,49 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, rm, readdir } from 'node:fs/promises';
3
+ import { join } from "node:path";
4
+ import { preview } from "astro";
5
+ import parseArgv from '../lib/parse-argv.js';
6
+ import copyFiles from './copy-files.js';
7
+ import generateThumbs from './generate-thumbs.js';
8
+ import updateShared from './update-shared.js';
9
+ import createZips from './create-zips.js';
10
+ import createCsv from './create-csv.js';
11
+ const argv = parseArgv({
12
+ mode: 'string',
13
+ fieldsOnly: 'boolean'
14
+ });
15
+ export default async function ({ root, config, env, csvOnly = false, serverPort = 4320 }) {
16
+ const rootDir = root ?? process.cwd();
17
+ // we could import the astro config and see if the outDir was set but right now it's not necessary
18
+ const astroDistDir = join(rootDir, 'dist');
19
+ const buildName = `${env}${config.presentation.isTraining === true ? '-training' : ''}`;
20
+ const buildType = argv.mode ?? buildName;
21
+ const deployDir = join(rootDir, 'deploy', buildType);
22
+ if (existsSync(deployDir)) {
23
+ const items = await readdir(deployDir);
24
+ for (const item of items) {
25
+ await rm(join(deployDir, item), { recursive: true });
26
+ }
27
+ }
28
+ else {
29
+ await mkdir(deployDir, { recursive: true });
30
+ }
31
+ if (!csvOnly) {
32
+ await copyFiles({ config, rootDir, astroDistDir, deployDir });
33
+ await updateShared({ deployDir });
34
+ const server = await preview({
35
+ root: rootDir,
36
+ server: {
37
+ port: serverPort
38
+ }
39
+ });
40
+ await generateThumbs({
41
+ server: { host: server.host ?? 'localhost', port: server.port },
42
+ config,
43
+ deployDir,
44
+ });
45
+ await server.stop();
46
+ await createZips({ deployDir });
47
+ }
48
+ await createCsv({ config, deployDir, buildName, fieldsOnly: csvOnly });
49
+ }
@@ -0,0 +1,12 @@
1
+ import type { IVeevaConfig } from "../types";
2
+ interface Params {
3
+ config: IVeevaConfig;
4
+ deployDir: string;
5
+ server: {
6
+ protocol?: 'http' | 'https';
7
+ host: string;
8
+ port: number;
9
+ };
10
+ }
11
+ export default function ({ config, deployDir, server }: Params): Promise<void>;
12
+ export {};
@@ -0,0 +1,152 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import puppeteer from "puppeteer";
4
+ import sharp from 'sharp';
5
+ import logger from '../lib/logger.js';
6
+ export default async function ({ config, deployDir, server }) {
7
+ logger.info('Generating thumbnails');
8
+ initThumbSettings(config);
9
+ await generateSharedThumb(config, deployDir);
10
+ const browser = await puppeteer.launch();
11
+ const page = await browser.newPage();
12
+ await page.setViewport({
13
+ width: config.dimensions.width,
14
+ height: config.dimensions.height,
15
+ hasTouch: true,
16
+ isLandscape: true,
17
+ isMobile: true
18
+ });
19
+ for (const slide of config.presentation.slides) {
20
+ for (const settings of slide.thumbnails.settings) {
21
+ logger.info(`Generating /${slide.path}/`);
22
+ const url = `${server.protocol ?? 'http'}://${server.host}:${server.port}/${slide.path}/${settings.queryStringParams ? `?${objToQS(settings.queryStringParams)}` : ''}`;
23
+ try {
24
+ await page.goto(url, {
25
+ waitUntil: 'networkidle0'
26
+ });
27
+ }
28
+ catch (err) {
29
+ throw err;
30
+ }
31
+ if (settings.delay && settings.delay > 0) {
32
+ logger.info(`Delaying thumbnail generation for ${settings.delay}ms`);
33
+ await sleep(settings.delay);
34
+ }
35
+ let screenshot = await page.screenshot();
36
+ let thumb = await (sharp(screenshot)
37
+ .resize({
38
+ width: settings.width,
39
+ height: settings.height
40
+ })
41
+ .png({
42
+ palette: true,
43
+ compressionLevel: 9,
44
+ quality: 80,
45
+ effort: 10
46
+ })).toBuffer();
47
+ const fileName = typeof settings.filename == 'function' ? settings.filename(config, slide, settings) : settings.filename ?? 'thumb.png';
48
+ await writeFile(join(deployDir, slide.path, fileName), thumb);
49
+ }
50
+ }
51
+ await page.close();
52
+ await browser.close();
53
+ }
54
+ async function generateSharedThumb(config, deployDir) {
55
+ let settings = config.thumbnails?.settings?.find(s => s.filename === 'thumb.png');
56
+ const width = settings?.width ?? 600;
57
+ const height = settings?.height ?? width / 4 * 3;
58
+ const svgImage = `
59
+ <svg width="${width}" height="${height}">
60
+ <style>
61
+ .title { fill: #fff; font-size: 32px; font-weight: bold; font-family: sans-serif; }
62
+ </style>
63
+ <text x="50%" y="40%" text-anchor="middle" class="title">
64
+ <tspan x="50%" dy="1.2em">${config.presentation.name}</tspan>
65
+ <tspan x="50%" dy="1.2em">Shared Resource</tspan>
66
+ </text>
67
+ </svg>`;
68
+ const buffer = await sharp({
69
+ create: {
70
+ width,
71
+ height: height,
72
+ channels: 4,
73
+ background: { r: 0, g: 0, b: 0, alpha: 1 } // Blue background
74
+ }
75
+ })
76
+ .composite([{
77
+ input: Buffer.from(svgImage),
78
+ gravity: 'center'
79
+ }])
80
+ .png()
81
+ .toBuffer();
82
+ await writeFile(join(deployDir, 'shared', 'thumb.png'), buffer);
83
+ }
84
+ function objToQS(data) {
85
+ return Object.entries(data)
86
+ .map(([key, val]) => {
87
+ return `${encodeURIComponent(key)}=${encodeURIComponent(val?.toString() ?? '')}`;
88
+ })
89
+ .join('&');
90
+ }
91
+ function initThumbSettings(config) {
92
+ if (!config.thumbnails) {
93
+ config.thumbnails = {};
94
+ }
95
+ if (!config.thumbnails.default) {
96
+ config.thumbnails.default = {};
97
+ }
98
+ if (!config.thumbnails.default.width) {
99
+ config.thumbnails.default.width = 600;
100
+ }
101
+ if (!config.thumbnails.default.height) {
102
+ config.thumbnails.default.height = config.thumbnails.default.width / 4 * 3;
103
+ }
104
+ if (!config.thumbnails.default.filename) {
105
+ config.thumbnails.default.filename = 'thumb.png';
106
+ }
107
+ if (!config.thumbnails.settings) {
108
+ config.thumbnails.settings = [];
109
+ }
110
+ if (config.thumbnails.settings.length === 0) {
111
+ config.thumbnails.settings.push(config.thumbnails.default);
112
+ }
113
+ else {
114
+ for (let i = 0; i < config.thumbnails.settings.length; i++) {
115
+ config.thumbnails.settings[i] = { ...config.thumbnails.default, ...config.thumbnails.settings[i] };
116
+ }
117
+ }
118
+ for (const slide of config.presentation.slides) {
119
+ if (!slide.thumbnails) {
120
+ slide.thumbnails = config.thumbnails;
121
+ }
122
+ if (!slide.thumbnails.settings) {
123
+ slide.thumbnails.settings = config.thumbnails.settings;
124
+ }
125
+ else {
126
+ switch (slide.thumbnails.strategy) {
127
+ case 'append':
128
+ slide.thumbnails.settings = config.thumbnails.settings.concat(slide.thumbnails.settings.map((sets) => {
129
+ return { ...config.thumbnails.default, ...slide.thumbnails?.default ?? {}, ...sets };
130
+ }));
131
+ break;
132
+ case 'replace':
133
+ break;
134
+ case 'merge':
135
+ default:
136
+ for (let i = 0; i < slide.thumbnails.settings.length; i++) {
137
+ slide.thumbnails.settings[i] = {
138
+ ...(config.thumbnails.settings[i] ?? config.thumbnails.default),
139
+ ...(slide.thumbnails.default ?? {}),
140
+ ...slide.thumbnails.settings[i]
141
+ };
142
+ }
143
+ break;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ function sleep(timeout = 0) {
149
+ return new Promise((resolve) => {
150
+ setTimeout(resolve, timeout);
151
+ });
152
+ }
@@ -0,0 +1 @@
1
+ export { default as deploy } from './deploy.js';
@@ -0,0 +1 @@
1
+ export { default as deploy } from './deploy.js';
@@ -0,0 +1,5 @@
1
+ interface Params {
2
+ deployDir: string;
3
+ }
4
+ export default function ({ deployDir }: Params): Promise<void>;
5
+ export {};
@@ -0,0 +1,41 @@
1
+ import { readdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { join, extname } from 'node:path';
3
+ import logger from '../lib/logger.js';
4
+ export default async function ({ deployDir }) {
5
+ logger.info('Updating /shared/ to ./shared/');
6
+ const dirs = await readdir(deployDir);
7
+ for (const dir of dirs) {
8
+ if (dir === 'shared') {
9
+ await handleShared(deployDir, dir);
10
+ }
11
+ else {
12
+ await handleHTML(deployDir, dir);
13
+ }
14
+ }
15
+ }
16
+ async function handleShared(deployDir, dir) {
17
+ const files = (await readdir(join(deployDir, dir)))
18
+ .filter((f) => {
19
+ switch (extname(f)) {
20
+ case '.css':
21
+ case '.js':
22
+ return true;
23
+ default:
24
+ return false;
25
+ }
26
+ });
27
+ for (const file of files) {
28
+ await updateSharedPath(join(deployDir, dir, file));
29
+ }
30
+ }
31
+ async function handleHTML(deployDir, dir) {
32
+ await updateSharedPath(join(deployDir, dir, 'index.html'));
33
+ }
34
+ async function updateSharedPath(filepath) {
35
+ const str = await readFile(filepath, 'utf8');
36
+ let shared = './shared/';
37
+ if (extname(filepath) === '.css') { // relative paths inside a css
38
+ shared = './';
39
+ }
40
+ await writeFile(filepath, str.replace(/(?:\.*\/)*shared\//g, shared));
41
+ }
@@ -0,0 +1,52 @@
1
+ import type { IVeevaClmPresentation, IVeevaClmSlide, IVeevaClmSharedResource, IVeevaClmSlideSettings, Actions } from "./veeva";
2
+
3
+ export type ThumbnailNameHandler = (config: IVeevaConfig, slide: IVeevaClmSlide, settings: IScreenshotSettings) => string;
4
+
5
+ export interface IDimensions {
6
+ width?: number;
7
+ height?: number;
8
+ }
9
+
10
+ export interface IScreenshotSettings extends IDimensions {
11
+ filename?: string | ThumbnailNameHandler;
12
+ delay?: number;
13
+ queryStringParams?: Record<string, boolean | number | string>;
14
+ outDir?: string | string[] | ThumbnailNameHandler;
15
+ }
16
+ export interface IThumbnailSettings {
17
+ strategy?: 'append' | 'merge' | 'replace',
18
+ default?: IScreenshotSettings;
19
+ settings?: IScreenshotSettings[];
20
+ }
21
+
22
+ export interface IVeevaConfigSlide extends IVeevaClmSlide {
23
+ path: string;
24
+ enableActions?: Actions[];
25
+ thumbnails?: IThumbnailSettings;
26
+ }
27
+
28
+ export interface IVeevaConfigPresentation extends IVeevaClmPresentation {
29
+ shared?: IVeevaClmSharedResource & {
30
+ path?: string;
31
+ },
32
+ slides: IVeevaConfigSlide[]
33
+ }
34
+
35
+
36
+
37
+ export interface IVeevaConfig {
38
+ dimensions: { width: number, height: number };
39
+ slideSettings?: IVeevaClmSlideSettings; // legacy support
40
+ thumbnails?: Omit<IThumbnailSettings, 'strategy'>;
41
+
42
+ presentation: IVeevaConfigPresentation & {
43
+ name: string;
44
+ presentationId: string;
45
+ slideSettings?: IVeevaClmSlideSettings;
46
+ };
47
+ externalPresentations?: {
48
+ name: string;
49
+ slide: string;
50
+ presentationId: string;
51
+ }[]
52
+ }
@@ -0,0 +1,7 @@
1
+ export type BuildTarget = 'veeva' | 'web';
2
+ export type WebEnv = 'development' | 'production';
3
+ export type VeevaEnv = 'client' | 'evoke';
4
+ export type VeevaPlatform = 'salesforce' | 'vault';
5
+ export type LogLevel = 'disabled' | 'error' | 'debug';
6
+ export type LogTime = 'none' | 'date' | 'time' | 'datetime' | 'performance';
7
+ export type LogOutput = 'console' | 'screen';
@@ -0,0 +1,9 @@
1
+ export * from './config';
2
+ export * from './env';
3
+ export * from './veeva';
4
+
5
+ export interface DataEvotrk {
6
+ slide?: string;
7
+ type?: string;
8
+ name?: string;
9
+ }
@@ -0,0 +1,58 @@
1
+ export type MediaType = "HTML" | "Image" | "Video" | "PPTX" | "PDF";
2
+ export type iOSResolution = "Default For Device" | "Scale To 1024x768" | "Scale To Fit";
3
+ export type Actions = "Swipe" | "Pinch to Exit" | "Zoom" | "Rotation Lock" | "History Buttons" | "Navigation Bar" | "Reactions";
4
+ export type CrmTarget = 'Salesforce' | 'Vault';
5
+
6
+ export interface VeevaIdField {
7
+ id: string;
8
+ }
9
+ export interface VeevaCrmIdField<T = string> {
10
+ crmId: T;
11
+ }
12
+ export interface VeevaExternalIdField {
13
+ externalId: string;
14
+ }
15
+
16
+ export interface IVeevaClmBinderFields {
17
+ language?: 'English' & (string & {});
18
+ crmTarget?: CrmTarget;
19
+ crmOrg?: VeevaCrmIdField<string[]>;
20
+ crmProduct?: VeevaIdField;
21
+ crmContentGroup?: VeevaIdField | VeevaIdField[];
22
+ crmDetailGroup?: VeevaIdField;
23
+ detailGroup?: string;
24
+ }
25
+
26
+
27
+ export interface IVeevaClmItem {
28
+ externalId: string;
29
+ name?: string;
30
+ title?: string;
31
+ }
32
+
33
+ export interface IVeevaClmMedia {
34
+ mediaType?: MediaType;
35
+ }
36
+
37
+ export interface IVeevaClmSlideSettings extends IVeevaClmMedia, IVeevaClmBinderFields {
38
+ disableActions?: Actions[];
39
+ iosResolution?: iOSResolution;
40
+ }
41
+
42
+ export interface IVeevaClmSlide extends IVeevaClmItem, IVeevaClmMedia, IVeevaClmSlideSettings {}
43
+ export interface IVeevaClmSharedResource extends IVeevaClmItem, IVeevaClmMedia, IVeevaClmBinderFields {}
44
+
45
+
46
+ export interface IVeevaClmPresentation extends IVeevaClmItem, IVeevaClmBinderFields {
47
+ presentationId?: string;
48
+ product: string;
49
+ country?: 'United States' & (string & {});
50
+ startDate?: string;
51
+ endDate?: string;
52
+ isTraining?: boolean;
53
+ isHidden?: boolean;
54
+
55
+ shared?: IVeevaClmSharedResource;
56
+
57
+ slides: IVeevaClmSlide[]
58
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Create an editor instance that loads the slides section once and allows
3
+ * multiple mutations before committing the change back to disk.
4
+ */
5
+ export declare function createEditor(): {
6
+ removePaths: (targetPaths: string[]) => void;
7
+ insert: (slide: {
8
+ path: string;
9
+ title: string;
10
+ externalId: string;
11
+ }) => void;
12
+ updatePath: (oldPath: string, newPath: string) => void;
13
+ commit: () => void;
14
+ getCurrent: () => string;
15
+ };
16
+ /**
17
+ * Remove every slide whose `path` matches one of the provided keys.
18
+ */
19
+ export declare function removeSlides(targetPaths: string[]): void;
20
+ /**
21
+ * Append a slide object to the end of the slides array. The caller should
22
+ * provide values already formatted as they should appear in the file (quotes
23
+ * around strings, etc.). We simply ensure there is a comma between entries.
24
+ */
25
+ export declare function insertSlide(slide: {
26
+ path: string;
27
+ title: string;
28
+ externalId: string;
29
+ }): void;
30
+ /**
31
+ * Change the `path` value of the first slide object that has `oldPath`.
32
+ */
33
+ export declare function updateSlidePath(oldPath: string, newPath: string): void;