@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,153 @@
1
+ import { readFile, writeFile, readdir } from 'node:fs/promises';
2
+ import { join, resolve as resolvePath } from 'node:path';
3
+
4
+ import { text, select, multiselect, note, spinner, outro } from '@clack/prompts';
5
+ import { sleep, handleCanceled, cleanInput, generateRandomId, titleCase } from '../utils.js';
6
+
7
+ import type { Actions, iOSResolution, CrmTarget } from '../../types';
8
+
9
+ const __dirname = import.meta.dirname;
10
+
11
+
12
+
13
+ export default async function(rootDir: string) {
14
+
15
+ const product = handleCanceled(await text({
16
+ message: 'Enter the product name',
17
+ validate: (value: string | undefined) => {
18
+ if (!value?.trim()) return 'Product name is required';
19
+ return undefined;
20
+ }
21
+ }));
22
+
23
+ const presentation = handleCanceled(await text({
24
+ message: 'Enter a presentation name',
25
+ validate: (value: string | undefined) => {
26
+ if (!value?.trim()) return 'Presentation name is required';
27
+ return undefined;
28
+ }
29
+ }));
30
+
31
+ const presentationId = handleCanceled(await text({
32
+ message: 'Enter a presentation ID',
33
+ initialValue: presentation.toString().toLowerCase().replace(/[^0-9a-z_]/g, '_'),
34
+ validate: (value: string | undefined) => {
35
+ if (!value?.trim()) return 'Presentation ID is required';
36
+ return undefined;
37
+ }
38
+ }));
39
+
40
+ const dimensions = handleCanceled(await select({
41
+ message: 'Select the IVA dimensions',
42
+ options: [{
43
+ label: '1024 x 768',
44
+ value: '1024_768'
45
+ }, {
46
+ label: '1366 x 1024',
47
+ value: '1366_1024'
48
+ }, {
49
+ label: '1376 x 1032',
50
+ value: '1376_1032'
51
+ }],
52
+ initialValue: '1366_1024'
53
+ }));
54
+ const [ width, height ] = dimensions.split('_');
55
+
56
+ const iosResolution = handleCanceled<iOSResolution>(await select({
57
+ message: 'Select the iOS resolution',
58
+ options: [{
59
+ label: 'Default For Device',
60
+ value: 'Default For Device'
61
+ }, {
62
+ label: 'Scale To Fit',
63
+ value: 'Scale To Fit'
64
+ }, {
65
+ label: 'Scale To 1024x768',
66
+ value: 'Scale To 1024x768'
67
+ }],
68
+ initialValue: 'Default For Device'
69
+ }));
70
+
71
+ const disableActions = handleCanceled<Actions[]>(await multiselect({
72
+ message: 'Select any actions you would like to disable globally',
73
+ required: false,
74
+ options: [{
75
+ label: 'History Buttons',
76
+ value: 'History Buttons'
77
+ }, {
78
+ label: 'Navigation Bar',
79
+ value: 'Navigation Bar'
80
+ }, {
81
+ label: 'Pinch to Exit',
82
+ value: 'Pinch to Exit'
83
+ }, {
84
+ label: 'Reactions',
85
+ value: 'Reactions'
86
+ }, {
87
+ label: 'Rotation Lock',
88
+ value: 'Rotation Lock'
89
+ }, {
90
+ label: 'Swipe',
91
+ value: 'Swipe'
92
+ }, {
93
+ label: 'Zoom',
94
+ value: 'Zoom'
95
+ }]
96
+ }));
97
+
98
+ const crmTarget = handleCanceled<CrmTarget>(await select({
99
+ message: 'Select the Veeva platform version',
100
+ options: [{
101
+ label: 'Salesforce',
102
+ value: 'Salesforce'
103
+ }, {
104
+ label: 'Vault',
105
+ value: 'Vault'
106
+ }],
107
+ initialValue: 'Salesforce'
108
+ }));
109
+
110
+ const spin = spinner();
111
+ spin.start('Generating config file...');
112
+ await sleep(); // make it seem like we're doing something
113
+
114
+ const slides = [];
115
+ const slideIds = new Set<string>();
116
+ const pages = await readdir(resolvePath(rootDir, 'src/pages'));
117
+ for (const page of pages) {
118
+ if (page.endsWith('.astro')) {
119
+ const p = page.slice(0, -6);
120
+ let slideId = generateRandomId();
121
+ while (slideIds.has(slideId)) {
122
+ slideId = generateRandomId();
123
+ }
124
+ slideIds.add(slideId);
125
+ slides.push(`{
126
+ path: '${p}',
127
+ title: '${titleCase(p)}',
128
+ externalId: formatExtId('${slideId}')
129
+ }`)
130
+ }
131
+ }
132
+
133
+ let template = await readFile(join(__dirname, 'templates/config.ts.txt'), 'utf8');
134
+ template = template
135
+ .replace(/##EXTERNAL_ID##/g, `${cleanInput(product).substring(0, 3).toUpperCase()}_${generateRandomId()}`)
136
+ .replace(/##PRODUCT##/g, cleanInput(product))
137
+ .replace(/##PRESENTATION_NAME##/g, cleanInput(presentation))
138
+ .replace(/##PRESENTATION_ID##/g, cleanInput(presentationId))
139
+ .replace(/##DIMENSIONS_WIDTH##/g, width)
140
+ .replace(/##DIMENSIONS_HEIGHT##/g, height)
141
+ .replace(/##IOS_RESOLUTION##/g, iosResolution)
142
+ .replace(/##DISABLE_ACTIONS##/g, `[${disableActions.map((a) => { return `'${a}'`}).join(', ')}]`)
143
+ .replace(/##CRM_TARGET##/g, crmTarget)
144
+ .replace(/##SLIDES##/g, `[${slides.join(', ')}]`)
145
+ .replace(/##SLIDE_IDS##/g, [...slideIds].join());
146
+ await writeFile(resolvePath(rootDir, 'veeva-config.ts'), template);
147
+
148
+ await sleep(); // make it seem like we're doing something
149
+ spin.stop('Config file generated!');
150
+
151
+ outro('Happy coding!');
152
+ }
153
+
@@ -0,0 +1,20 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve as resolvePath } from 'node:path';
3
+ import { intro } from '@clack/prompts';
4
+
5
+
6
+ export default async function({ root }: { root?: string } = {}) {
7
+ const rootDir = root ?? process.cwd();
8
+ const exists = existsSync(resolvePath(rootDir, 'veeva-config.ts'));
9
+
10
+ console.log('');
11
+ intro('Veeva Configuration Manager');
12
+
13
+ if (!exists) {
14
+ const create = await import('./create.js');
15
+ await create.default(rootDir);
16
+ } /* else {
17
+ const update = await import('../../../archive/update');
18
+ await update.default();
19
+ } */
20
+ }
@@ -0,0 +1,60 @@
1
+ import type { IVeevaClmBinderFields, IVeevaConfig, VeevaEnv } from "@inizioevoke/veeva-astroclm-core/types";
2
+ import { formatExtId as __formatExtId } from '@inizioevoke/veeva-astroclm-core/lib';
3
+
4
+ interface IVeevaConfigArgs {
5
+ veevaEnv?: VeevaEnv;
6
+ isTraining?: boolean;
7
+ }
8
+ export default function ({ veevaEnv = 'client', isTraining = false }: IVeevaConfigArgs = {}): IVeevaConfig {
9
+ const isEvokeBuild = veevaEnv === 'evoke';
10
+ const EXT_ID = `##EXTERNAL_ID##${isTraining ? '_TRN' : ''}`;
11
+ const PRESENTATION_NAME = '##PRESENTATION_NAME##';
12
+
13
+ const formatExtId = (id: string) => {
14
+ return __formatExtId(EXT_ID, id);
15
+ };
16
+
17
+ const binderFields: IVeevaClmBinderFields = {
18
+ // language: isEvokeBuild ? undefined : 'English',
19
+ // crmOrg: isEvokeBuild ? undefined : { crmId: ['CRM_ORD_ID'] },
20
+ // crmProduct: isEvokeBuild ? undefined : { id: 'VEEVA_ID_FROM_URL' },
21
+ // crmDetailGroup: isEvokeBuild ? undefined : { id: 'VEEVA_ID_FROM_URL' },
22
+ // detailGroup: undefined
23
+ };
24
+
25
+ return {
26
+ dimensions: { width: ##DIMENSIONS_WIDTH##, height: ##DIMENSIONS_HEIGHT## },
27
+ thumbnails: {
28
+ default: {
29
+ queryStringParams: {
30
+ screenshots: true
31
+ }
32
+ }
33
+ },
34
+ presentation: {
35
+ externalId: EXT_ID,
36
+ presentationId: `##PRESENTATION_ID##${isTraining ? '_trn' : ''}`,
37
+ product: '##PRODUCT##',
38
+ name: `${PRESENTATION_NAME}${isTraining ? ' [Training]' : ''}`,
39
+ country: 'United States',
40
+ isTraining,
41
+ crmTarget: '##CRM_TARGET##',
42
+ ...binderFields,
43
+
44
+ shared: {
45
+ externalId: formatExtId('SHARED'),
46
+ ...binderFields
47
+ },
48
+
49
+ slideSettings: {
50
+ mediaType: 'HTML',
51
+ disableActions: ##DISABLE_ACTIONS##,
52
+ iosResolution: '##IOS_RESOLUTION##',
53
+ ...binderFields
54
+ },
55
+ slides: ##SLIDES##
56
+ }
57
+ };
58
+ }
59
+
60
+ // SLIDE_IDS: [##SLIDE_IDS##]
@@ -0,0 +1,43 @@
1
+ import { envField } from 'astro/config';
2
+ import {
3
+ BUILD_TARGETS, WEB_ENVS, VEEVA_ENVS, VEEVA_PLATFORMS, LOG_LEVELS, LOG_TIMES, LOG_OUTPUTS,
4
+ DEFAULT_BUILD_TARGET, DEFAULT_WEB_ENV, DEFAULT_VEEVA_ENV, DEFAULT_VEEVA_PLATFORM, DEFAULT_LOG_LEVEL, DEFAULT_LOG_TIME, DEFAULT_LOG_OUTPUT,
5
+ } from '../lib/const.js';
6
+
7
+ const envPublicOptional: {
8
+ context: 'client',
9
+ access: 'public',
10
+ optional: true
11
+ } = {
12
+ context: 'client',
13
+ access: 'public',
14
+ optional: true
15
+ };
16
+
17
+ type BooleanField = ReturnType<typeof envField.boolean>;
18
+ type EnumField = ReturnType<typeof envField.enum>;
19
+
20
+ export type EnvFieldName = 'BUILD_TARGET' | 'WEB_ENV' | 'WEB_SCALING' | 'VEEVA_TRAINING' | 'VEEVA_ENV' | 'VEEVA_PLATFORM' | 'LOG_LEVEL' | 'LOG_TIME' | 'LOG_OUTPUT'
21
+
22
+ export const BUILD_TARGET: EnumField = envField.enum({ ...envPublicOptional, values: BUILD_TARGETS, default: DEFAULT_BUILD_TARGET });
23
+ export const WEB_ENV: EnumField = envField.enum({ ...envPublicOptional, values: WEB_ENVS, default: DEFAULT_WEB_ENV });
24
+ export const WEB_SCALING: BooleanField = envField.boolean({ ...envPublicOptional, default: false });
25
+ export const VEEVA_TRAINING: BooleanField = envField.boolean({ ...envPublicOptional, default: false });
26
+ export const VEEVA_ENV: EnumField = envField.enum({ ...envPublicOptional, values: VEEVA_ENVS, default: DEFAULT_VEEVA_ENV });
27
+ export const VEEVA_PLATFORM: EnumField = envField.enum({ ...envPublicOptional, values: VEEVA_PLATFORMS, default: DEFAULT_VEEVA_PLATFORM });
28
+ export const LOG_LEVEL: EnumField = envField.enum({ ...envPublicOptional, values: LOG_LEVELS, default: DEFAULT_LOG_LEVEL });
29
+ export const LOG_TIME: EnumField = envField.enum({ ...envPublicOptional, values: LOG_TIMES, default: DEFAULT_LOG_TIME });
30
+ export const LOG_OUTPUT: EnumField = envField.enum({ ...envPublicOptional, values: LOG_OUTPUTS, default: DEFAULT_LOG_OUTPUT});
31
+
32
+ const schema: Record<EnvFieldName, BooleanField | EnumField> = {
33
+ BUILD_TARGET,
34
+ WEB_ENV,
35
+ WEB_SCALING,
36
+ VEEVA_TRAINING,
37
+ VEEVA_ENV,
38
+ VEEVA_PLATFORM,
39
+ LOG_LEVEL,
40
+ LOG_TIME,
41
+ LOG_OUTPUT
42
+ };
43
+ export default schema;
@@ -0,0 +1,17 @@
1
+ import type { BuildTarget, WebEnv, VeevaEnv, VeevaPlatform, LogLevel, LogTime, LogOutput } from '../types';
2
+
3
+ export const BUILD_TARGETS: BuildTarget[] = ['veeva', 'web'];
4
+ export const WEB_ENVS: WebEnv[] = ['development', 'production'];
5
+ export const VEEVA_ENVS: VeevaEnv[] = ['client', 'evoke'];
6
+ export const VEEVA_PLATFORMS: VeevaPlatform[] = ['salesforce', 'vault'];
7
+ export const LOG_LEVELS: LogLevel[] = ['disabled', 'error', 'debug'];
8
+ export const LOG_TIMES: LogTime[] = ['none', 'date', 'time', 'datetime', 'performance'];
9
+ export const LOG_OUTPUTS: LogOutput[] = ['console', 'screen'];
10
+
11
+ export const DEFAULT_BUILD_TARGET: BuildTarget = 'web';
12
+ export const DEFAULT_WEB_ENV: WebEnv = 'development';
13
+ export const DEFAULT_VEEVA_ENV: VeevaEnv = 'client';
14
+ export const DEFAULT_VEEVA_PLATFORM: VeevaPlatform = 'salesforce';
15
+ export const DEFAULT_LOG_LEVEL: LogLevel = 'disabled';
16
+ export const DEFAULT_LOG_TIME: LogTime = 'time';
17
+ export const DEFAULT_LOG_OUTPUT: LogOutput = 'console';
package/src/lib/env.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { BuildTarget, VeevaEnv, VeevaPlatform } from '../types';
2
+ import * as Constants from './const.js';
3
+ import parseEnv from "./parse-env.js";
4
+
5
+ export interface Env {
6
+ BUILD_TARGET: BuildTarget;
7
+ VEEVA_ENV: VeevaEnv;
8
+ VEEVA_PLATFORM: VeevaPlatform;
9
+ VEEVA_TRAINING: boolean;
10
+ }
11
+
12
+ export default async function loadEnv(mode?: string): Promise<Env> {
13
+ const env = await parseEnv({
14
+ BUILD_TARGET: Constants.BUILD_TARGETS,
15
+ VEEVA_ENV: Constants.VEEVA_ENVS,
16
+ VEEVA_PLATFORM: Constants.VEEVA_PLATFORMS,
17
+ VEEVA_TRAINING: 'boolean'
18
+ }, {
19
+ mode
20
+ });
21
+ env.BUILD_TARGET = env.BUILD_TARGET ?? Constants.DEFAULT_BUILD_TARGET,
22
+ env.VEEVA_ENV = env.VEEVA_ENV ?? Constants.DEFAULT_VEEVA_ENV,
23
+ env.VEEVA_PLATFORM = env.VEEVA_PLATFORM ?? Constants.DEFAULT_VEEVA_PLATFORM,
24
+ env.VEEVA_TRAINING = env.VEEVA_TRAINING ?? false
25
+
26
+ return env as Env;
27
+ }
@@ -0,0 +1,5 @@
1
+ export * from './const.js';
2
+ export { default as loadEnv } from './env.js';
3
+ export { default as parseArgv } from './parse-argv.js';
4
+ export { default as parseEnv } from './parse-env.js';
5
+ export * from './utils.js';
@@ -0,0 +1,84 @@
1
+ const Style = Object.freeze({
2
+ Background: {
3
+ Black: '\x1b[40m',
4
+ Red: '\x1b[41m',
5
+ Green: '\x1b[42m',
6
+ Yellow: '\x1b[43m',
7
+ Blue: '\x1b[44m',
8
+ Magenta: '\x1b[45m',
9
+ Cyan: '\x1b[46m',
10
+ White: '\x1b[47m',
11
+ Gray: '\x1b[100m',
12
+ BrightRed: '\x1b[101m',
13
+ BrightGreen: '\x1b[102m',
14
+ BrightYellow: '\x1b[103m',
15
+ BrightBlue: '\x1b[104m',
16
+ BrightMagenta: '\x1b[105m',
17
+ BrightCyan: '\x1b[106m',
18
+ BrightWhite: '\x1b[107m'
19
+ },
20
+ Foreground: {
21
+ Black: '\x1b[30m',
22
+ Red: '\x1b[31m',
23
+ Green: '\x1b[32m',
24
+ Yellow: '\x1b[33m',
25
+ Blue: '\x1b[34m',
26
+ Magenta: '\x1b[35m',
27
+ Cyan: '\x1b[36m',
28
+ White: '\x1b[37m',
29
+ Gray: '\x1b[90m',
30
+ BrightRed: '\x1b[91m',
31
+ BrightGreen: '\x1b[92m',
32
+ BrightYellow: '\x1b[93m',
33
+ BrightBlue: '\x1b[94m',
34
+ BrightMagenta: '\x1b[95m',
35
+ BrightCyan: '\x1b[96m',
36
+ BrightWhite: '\x1b[97m'
37
+ },
38
+ Bold: '\x1b[1m',
39
+ Inverse: '\x1b[7m',
40
+ Reset: '\x1b[0m',
41
+ Underscore: '\x1b[4m'
42
+ });
43
+
44
+ interface LogMessage {
45
+ task?: string;
46
+ message: string;
47
+ data?: any;
48
+ style?: string;
49
+ }
50
+ function _log({ task, message, data, style }: LogMessage) {
51
+ const now = new Date();
52
+ const ts = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
53
+
54
+ const msg = [`${Style.Foreground.Gray}${ts}${Style.Reset}`];
55
+ if (task) {
56
+ msg.push(`${Style.Foreground.Cyan}[${task}]${Style.Reset}`);
57
+ }
58
+ msg.push(`${style ?? Style.Reset}${message}${Style.Reset}`);
59
+ if (data) {
60
+ msg.push(`${Style.Foreground.Yellow}${data}${Style.Reset}`)
61
+ }
62
+ console.log(msg.join(' '));
63
+ }
64
+
65
+ export function info(message: string) { // task: string, message: string, data?: any) {
66
+ _log({ message, style: Style.Foreground.White });
67
+ // _log({ task, message, data, style: Style.Foreground.White });
68
+ }
69
+
70
+ export function warn(message: string) { // task: string, message: string, data?: any) {
71
+ _log({ message, style: Style.Foreground.Yellow });
72
+ // _log({ task, message, data, style: Style.Foreground.Yellow });
73
+ }
74
+
75
+ export function error(message: string) { // task: string, message: string, data?: any) {
76
+ _log({ message, style: Style.Foreground.Red });
77
+ // _log({ task, message, data, style: Style.Foreground.Red });
78
+ }
79
+
80
+ export default {
81
+ info,
82
+ warn,
83
+ error
84
+ };
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Parses process.argv or any string[] into an object with the given schema
3
+ * Properties should be prefixed with `--`
4
+ * Values should come immediately after the property and be separated by a space or `=`
5
+ * Type `array` can only be a array of `boolean`, `number`, or `string` types
6
+ * Type `boolean` will be `true` if present and not followed by `=[VALUE]`
7
+ * Type `object` property values can only by of `boolean`, `number`, or `string` types
8
+ * Schema entries may also be string‑literal arrays, which are treated as enum
9
+ * definitions; the resulting value will be typed as the union of the literals.
10
+ *
11
+ * @example
12
+ * const argv = parseArgv({
13
+ * a: 'array',
14
+ * b: 'boolean',
15
+ * b2: 'boolean',
16
+ * f: 'float',
17
+ * n: 'number',
18
+ * o: 'object',
19
+ * s: 'string',
20
+ * mode: ['dev','prod'] as const // enum-style union
21
+ * })
22
+ *
23
+ * > npm run build -- --a [one,2,false] --b --b2=false --f 3.14 --n 123 --o {one:1,nope:false,hello:world} --s mystring --mode dev
24
+ *
25
+ */
26
+
27
+ // primitive types that may be specified in the schema. enum definitions are
28
+ // provided using string literal arrays directly on the schema entries.
29
+ type Type = 'array' | 'boolean' | 'float' | 'number' | 'object' | 'string';
30
+
31
+ type Schema<S extends Record<string, Type | readonly string[]>> = {
32
+ [K in keyof S]:
33
+ S[K] extends readonly (infer U)[] ? U | undefined
34
+ : S[K] extends 'array' ? any[] | undefined
35
+ : S[K] extends 'boolean' ? boolean | undefined
36
+ : S[K] extends 'float' ? number | undefined
37
+ : S[K] extends 'number' ? number | undefined
38
+ : S[K] extends 'object' ? Record<string, any> | undefined
39
+ : S[K] extends 'string' ? string | undefined
40
+ : string | undefined
41
+ }
42
+
43
+ export default function<S extends Record<string, Type | readonly string[]>>(schema: S, argv: string[] = process.argv.slice(2)): Schema<S> {
44
+ const data: Schema<S> = {} as Schema<S>;
45
+ const args = argv.slice()
46
+ .map((arg) => { return arg?.trim(); })
47
+ .filter(arg => typeof arg == 'string' && arg !== '');
48
+ for (let i=0; i<args.length; i++) {
49
+ if (args[i].startsWith('--')) {
50
+ const arg = args[i].slice(2);
51
+ if (arg.indexOf('=') !== -1) {
52
+ let [key, value] = arg.split('=') as [string, any];
53
+ if (schema[key]) {
54
+ value = parseValue(schema[key], value);
55
+ data[key as keyof Schema<S>] = value as any;
56
+ }
57
+ } else if (schema[arg] === 'boolean') {
58
+ data[arg as keyof Schema<S>] = true as any;
59
+ } else if (schema[arg]) {
60
+ let value: any = true;
61
+ if (i + 1 < args.length && !args[i+1].startsWith('--')) {
62
+ value = parseValue(schema[arg], args[++i]);
63
+ }
64
+ data[arg as keyof Schema<S>] = value as any;
65
+ }
66
+ }
67
+ }
68
+
69
+ return data;
70
+ }
71
+
72
+ function parseValue(type: Type | readonly string[], value: string): any {
73
+ if (typeof value != 'string') {
74
+ return undefined;
75
+ }
76
+ if (Array.isArray(type)) {
77
+ return (type.includes(value) ? value : value) as any;
78
+ }
79
+ switch (type) {
80
+ case 'array': {
81
+ if (value.startsWith('[')) {
82
+ value = value.substring(1);
83
+ }
84
+ if (value.endsWith(']')) {
85
+ value = value.slice(0, -1);
86
+ }
87
+ const items = value.split(',')
88
+ .map((v) => {
89
+ v = v.trim();
90
+ if (/(true|false|\d+(?:\.\d+)?)/i.test(v)) {
91
+ return JSON.parse(v);
92
+ } else {
93
+ return v;
94
+ }
95
+ });
96
+
97
+ return items;
98
+ }
99
+ case 'boolean':
100
+ return value.toLowerCase() === 'true' || value === '1';
101
+ case 'float':
102
+ return parseFloat(value);
103
+ case 'number':
104
+ return parseInt(value, 10);
105
+ case 'object':
106
+ if (value.startsWith('{') && value.endsWith('}')) {
107
+ const obj = value.substring(1, value.length - 1)
108
+ .split(',')
109
+ .filter(p => p.trim() !== '')
110
+ .map((pair) => {
111
+ let [key, val] = pair.split(':').map(s => s.trim());
112
+ if (/(true|false|\d+(?:\.\d+)?)/i.test(val)) {
113
+ val = JSON.parse(val);
114
+ }
115
+ return [key,val];
116
+ });
117
+ return Object.fromEntries(obj);
118
+ } else {
119
+ return {};
120
+ }
121
+ case 'string':
122
+ default:
123
+ return value;
124
+ }
125
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Parses a .env file into an object with the given schema
3
+ * Values should come immediately after the property and be separated by `=`
4
+ * Values can be quoted or unquoted
5
+ * Type `array` can only be a array of `boolean`, `number`, or `string` types
6
+ * Type `object` property values can only by of `boolean`, `number`, or `string` types
7
+ * Schema entries may also be string‑literal arrays, which are treated as enum
8
+ * definitions; the resulting value will be typed as the union of the literals.
9
+ *
10
+ * @example
11
+ * import parseEnv from './parse-env';
12
+ *
13
+ * const env = parseEnv({
14
+ * a: 'array',
15
+ * b: 'boolean',
16
+ * b2: 'boolean',
17
+ * f: 'float',
18
+ * n: 'number',
19
+ * o: 'object',
20
+ * s: 'string',
21
+ * mode: ['dev','prod'] as const // enum-style union
22
+ * })
23
+ *
24
+ *
25
+ * // .env
26
+ * a="[1,2,3]"
27
+ * b=false
28
+ * b2="true"
29
+ * f="1.23"
30
+ * n=234
31
+ * o={"test":1}
32
+ * s="testing 1 2 3"
33
+ * mode=prod
34
+ */
35
+
36
+ import { existsSync } from "node:fs";
37
+ import { readFile } from "node:fs/promises";
38
+ import { join } from 'node:path';
39
+
40
+ // primitive types that can be declared in the schema
41
+ // (enum definitions are represented by string literal arrays in the schema itself)
42
+ export type EnvPropertyType = 'array' | 'boolean' | 'float' | 'number' | 'object' | 'string';
43
+
44
+ // When the schema entry is an array of literals we treat it as an enum definition
45
+ // and the resulting value is the union of those literals.
46
+ // Otherwise we fall back to the primitive mapping that was already present.
47
+
48
+ export type EnvSchema<S extends Record<string, EnvPropertyType | readonly string[]> = Record<string, any>> = {
49
+ [K in keyof S]:
50
+ S[K] extends readonly (infer U)[] ? U | undefined
51
+ : S[K] extends 'array' ? any[] | undefined
52
+ : S[K] extends 'boolean' ? boolean | undefined
53
+ : S[K] extends 'float' ? number | undefined
54
+ : S[K] extends 'number' ? number | undefined
55
+ : S[K] extends 'object' ? Record<string, any> | undefined
56
+ : S[K] extends 'string' ? string | undefined
57
+ : string | undefined
58
+ }
59
+
60
+ // parse reads a .env file according to the given schema. In addition to
61
+ // the primitive types listed above the schema may use a string-literal
62
+ // array to describe an enum; the returned object will then have the property
63
+ // typed as the union of those literals.
64
+ async function parseEnv<S extends Record<string, EnvPropertyType | readonly string[]>>(schema: S, { mode, cwd, filePath, all = false }: { mode?: string, cwd?: string, filePath?: string, all?: boolean } = {}): Promise<EnvSchema<S>> {
65
+ const env: EnvSchema<S> = {} as EnvSchema<S>;
66
+
67
+ const fp = filePath ? cwd ? join(cwd, filePath) : filePath : join(cwd ?? process.cwd(), `.env${mode ? `.${mode}` : ''}`);
68
+ if (existsSync(fp)) {
69
+ const data: Record<string, string> = {};
70
+ const content = await readFile(fp, 'utf8');
71
+ const lines = content.split(/\r?\n/).filter(l => l.trim() !== '');
72
+ for (const line of lines) {
73
+ const [key, value] = line.split('=');
74
+ data[key.trim()] = value.trim().match(/['"']?([^'"]*)['"]?/)?.[1].trim() ?? '';
75
+ }
76
+ for (const [key, value] of Object.entries(data)) {
77
+ if (schema[key]) {
78
+ env[key as keyof EnvSchema<S>] = parseValue(schema[key], value);
79
+ } else if (all) {
80
+ env[key as keyof EnvSchema<S>] = value as any;
81
+ }
82
+ }
83
+ }
84
+
85
+ return env;
86
+ };
87
+
88
+ function parseValue(type: EnvPropertyType | readonly string[], value: string): any {
89
+ if (typeof value != 'string') {
90
+ return undefined;
91
+ }
92
+ // enums are defined as string literal arrays in the schema
93
+ if (Array.isArray(type)) {
94
+ // return the value directly; optionally we could validate, but at runtime
95
+ // we simply cast back to one of the allowed values (union type ensures
96
+ // callers only use valid strings at compile time if schema is const).
97
+ return (type.includes(value) ? value : value) as any;
98
+ }
99
+ switch (type) {
100
+ case 'array': {
101
+ if (value.startsWith('[')) {
102
+ value = value.substring(1);
103
+ }
104
+ if (value.endsWith(']')) {
105
+ value = value.slice(0, -1);
106
+ }
107
+ const items = value.split(',')
108
+ .map((v) => {
109
+ v = v.trim();
110
+ if (/(true|false|\d+(?:\.\d+)?)/i.test(v)) {
111
+ return JSON.parse(v);
112
+ } else {
113
+ return v;
114
+ }
115
+ });
116
+
117
+ return items;
118
+ }
119
+ case 'boolean':
120
+ return value.toLowerCase() === 'true' || value === '1';
121
+ case 'float':
122
+ return parseFloat(value);
123
+ case 'number':
124
+ return parseInt(value, 10);
125
+ case 'object':
126
+ if (value.startsWith('{') && value.endsWith('}')) {
127
+ const obj = value.substring(1, value.length - 1)
128
+ .split(',')
129
+ .filter(p => p.trim() !== '')
130
+ .map((pair) => {
131
+ let [key, val] = pair.split(':').map(s => s.trim());
132
+ if (/(true|false|\d+(?:\.\d+)?)/i.test(val)) {
133
+ val = JSON.parse(val);
134
+ }
135
+ return [key,val];
136
+ });
137
+ return Object.fromEntries(obj);
138
+ } else {
139
+ return {};
140
+ }
141
+ case 'string':
142
+ default:
143
+ return value;
144
+ }
145
+ }
146
+
147
+ export default parseEnv;