@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,120 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ const configPath = path.resolve(__dirname, 'config.ts');
4
+ function readConfig() {
5
+ return fs.readFileSync(configPath, 'utf8');
6
+ }
7
+ function writeConfig(text) {
8
+ fs.writeFileSync(configPath, text, 'utf8');
9
+ }
10
+ function mutateSlides(mutate) {
11
+ const content = readConfig();
12
+ const slidesSection = /slides\s*:\s*\[([\s\S]*?)\]/m;
13
+ const match = content.match(slidesSection);
14
+ if (!match) {
15
+ throw new Error('Unable to locate slides array in config.ts');
16
+ }
17
+ const before = content.slice(0, match.index);
18
+ const after = content.slice((match.index ?? 0) + match[0].length);
19
+ const current = match[1];
20
+ const updated = mutate(current);
21
+ const newSection = `slides: [${updated}]`;
22
+ writeConfig(before + newSection + after);
23
+ }
24
+ /**
25
+ * Create an editor instance that loads the slides section once and allows
26
+ * multiple mutations before committing the change back to disk.
27
+ */
28
+ export function createEditor() {
29
+ const slidesSection = /slides\s*:\s*\[([\s\S]*?)\]/m;
30
+ let content = readConfig();
31
+ const match = content.match(slidesSection);
32
+ if (!match) {
33
+ throw new Error('Unable to locate slides array in config.ts');
34
+ }
35
+ const prefix = content.slice(0, match.index);
36
+ const suffix = content.slice((match.index ?? 0) + match[0].length);
37
+ let current = match[1];
38
+ function commit() {
39
+ const newSection = `slides: [${current}]`;
40
+ writeConfig(prefix + newSection + suffix);
41
+ }
42
+ function removePaths(targetPaths) {
43
+ if (targetPaths.length === 0)
44
+ return;
45
+ for (const p of targetPaths) {
46
+ const esc = p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
47
+ const re = new RegExp(`\{[\s\S]*?path:\s*['"]${esc}['"][\s\S]*?\}(,?)`, 'm');
48
+ current = current.replace(re, '');
49
+ }
50
+ current = current.replace(/,\s*,/g, ',');
51
+ current = current.replace(/,\s*$/m, '');
52
+ }
53
+ function insert(slide) {
54
+ const trimmed = current.trim();
55
+ const formatted = `
56
+ { path: '${slide.path}', title: '${slide.title}', externalId: '${slide.externalId}' }`;
57
+ if (trimmed === '') {
58
+ current = formatted + '\n';
59
+ }
60
+ else {
61
+ current = trimmed.replace(/\s*$/, '') + ',' + formatted + '\n';
62
+ }
63
+ }
64
+ function updatePath(oldPath, newPath) {
65
+ const escOld = oldPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
66
+ current = current.replace(new RegExp(`(path:\s*['"])${escOld}(['"])`), `$1${newPath}$2`);
67
+ }
68
+ return {
69
+ removePaths,
70
+ insert,
71
+ updatePath,
72
+ commit,
73
+ // expose current for inspection/testing
74
+ getCurrent: () => current
75
+ };
76
+ }
77
+ /**
78
+ * Remove every slide whose `path` matches one of the provided keys.
79
+ */
80
+ export function removeSlides(targetPaths) {
81
+ if (targetPaths.length === 0)
82
+ return;
83
+ mutateSlides(current => {
84
+ let txt = current;
85
+ for (const p of targetPaths) {
86
+ const esc = p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
87
+ const re = new RegExp(`\{[\s\S]*?path:\s*['"]${esc}['"][\s\S]*?\}(,?)`, 'm');
88
+ txt = txt.replace(re, '');
89
+ }
90
+ // clean up extra commas
91
+ txt = txt.replace(/,\s*,/g, ',');
92
+ txt = txt.replace(/,\s*$/m, '');
93
+ return txt;
94
+ });
95
+ }
96
+ /**
97
+ * Append a slide object to the end of the slides array. The caller should
98
+ * provide values already formatted as they should appear in the file (quotes
99
+ * around strings, etc.). We simply ensure there is a comma between entries.
100
+ */
101
+ export function insertSlide(slide) {
102
+ mutateSlides(current => {
103
+ const trimmed = current.trim();
104
+ const formatted = `
105
+ { path: '${slide.path}', title: '${slide.title}', externalId: '${slide.externalId}' }`;
106
+ if (trimmed === '') {
107
+ return formatted + '\n';
108
+ }
109
+ return trimmed.replace(/\s*$/, '') + ',' + formatted + '\n';
110
+ });
111
+ }
112
+ /**
113
+ * Change the `path` value of the first slide object that has `oldPath`.
114
+ */
115
+ export function updateSlidePath(oldPath, newPath) {
116
+ mutateSlides(current => {
117
+ const escOld = oldPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
118
+ return current.replace(new RegExp(`(path:\s*['"])${escOld}(['"])`), `$1${newPath}$2`);
119
+ });
120
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@inizioevoke/veeva-astroclm-core",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "module",
8
+ "scripts": {
9
+ "prepublishOnly": "npm run build",
10
+ "build": "npx tsx build.ts --cmd pre && tsc && npx tsx build.ts --cmd post"
11
+ },
12
+ "exports": {
13
+ "./apps": "./dist/apps/index.js",
14
+ "./integrations": "./dist/integrations/index.js",
15
+ "./lib": "./dist/lib/index.js",
16
+ "./tasks": "./dist/tasks/index.js",
17
+ "./types": "./dist/types/index.d.ts"
18
+ },
19
+ "peerDependencies": {
20
+ "astro": ">=5.0.0",
21
+ "typescript": ">=5.0.0"
22
+ },
23
+ "dependencies": {
24
+ "@clack/prompts": "^1.4.0",
25
+ "adm-zip": "^0.5.17",
26
+ "puppeteer": "^24.43.1",
27
+ "sharp": "^0.34.5"
28
+ },
29
+ "devDependencies": {
30
+ "@astrojs/check": "^0.9.9",
31
+ "@astrojs/ts-plugin": "^1.10.7",
32
+ "@types/adm-zip": "^0.5.8",
33
+ "@types/node": "^25.7.0",
34
+ "astro": "^6.3.1",
35
+ "tsx": "^4.21.0",
36
+ "typescript": "^6.0.3",
37
+ "yaml": "^2.9.0"
38
+ }
39
+ }
@@ -0,0 +1,2 @@
1
+ export { default as pageManager } from './page-manager/index.js';
2
+ export { default as veevaConfigManager } from './veeva-config-manager/index.js';
@@ -0,0 +1,131 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile, writeFile, readdir, rm } from 'node:fs/promises';
3
+ import { join, resolve as resolvePath } from 'node:path';
4
+
5
+ import { parse } from 'yaml';
6
+
7
+ import { intro, text, select, multiselect, note, spinner, outro, box } from '@clack/prompts';
8
+ import { sleep, handleCanceled, cleanInput, generateRandomId, titleCase } from '../utils.js';
9
+ import { createPageAssets, getLayouts } from './utils.js';
10
+
11
+ const __dirname = import.meta.dirname;
12
+
13
+ export default async function bulkCreate(rootDir: string) {
14
+ const contentDir = join(rootDir, 'src/contents');
15
+ const pagesDir = join(rootDir, 'src/pages');
16
+
17
+ const bulkPath = handleCanceled(await text({
18
+ message: 'Enter the path, relative to the project root, of the YAML file containing the content to create',
19
+ initialValue: '_contents.yml',
20
+ validate: (value: string | undefined) => {
21
+ if (!value?.trim()) return 'The path is required';
22
+ if (!existsSync(resolvePath(rootDir, value.trim()))) return 'The file does not exist'
23
+ return undefined;
24
+ }
25
+ }));
26
+
27
+ const layouts = await getLayouts(rootDir);
28
+ const layout = handleCanceled(await select({
29
+ message: 'Select the layout each page should use',
30
+ options: (() => {
31
+ return layouts.map((layout) => {
32
+ const label = layout.replace(/\\/g, '/');
33
+ return {
34
+ label,
35
+ value: label
36
+ };
37
+ })
38
+ })()
39
+ }));
40
+
41
+ const created: string[] = [];
42
+ const existing: string[] = [];
43
+ const errors: string[] = [];
44
+
45
+ const spin = spinner();
46
+ spin.start('Generating page assets...');
47
+ await sleep();
48
+
49
+ const data = (parse(await readFile(resolvePath(rootDir, bulkPath), 'utf8')) as string[])
50
+ .map((d) => { return d.trim(); });
51
+
52
+ for (const contentPath of data) {
53
+ if (existsSync(join(contentDir, contentPath)) && existsSync(join(pagesDir, `${contentPath.replace(/\//g, '-')}.astro`))) {
54
+ existing.push(contentPath);
55
+ spin.message(`Exists: ${contentPath}`);
56
+ await sleep();
57
+ } else {
58
+ try {
59
+ await createPageAssets({ rootDir, contentPath, layout });
60
+ created.push(contentPath);
61
+ spin.message(`Generated: ${contentPath}`);
62
+ } catch (err) {
63
+ errors.push(contentPath);
64
+ spin.message(`Error: ${contentPath}`);
65
+ }
66
+ await sleep();
67
+ }
68
+ }
69
+
70
+ spin.stop('Page assets generated!')
71
+
72
+ const result: string[] = [];
73
+ let insertNewLine = false;
74
+ if (created.length) {
75
+ result.push('The following content/pages were created:');
76
+ created.sort();
77
+ for (const c of created) {
78
+ result.push(` - ${c}`);
79
+ }
80
+ insertNewLine = true;
81
+ }
82
+ if (existing.length) {
83
+ if (insertNewLine) {
84
+ result.push('');
85
+ }
86
+ result.push('The following content/pages already exist:');
87
+ existing.sort();
88
+ for (const c of existing) {
89
+ result.push(` - ${c}`);
90
+ }
91
+ insertNewLine = true;
92
+ }
93
+ if (errors.length) {
94
+ if (insertNewLine) {
95
+ result.push('');
96
+ }
97
+ result.push('The following content/pages experienced an error while creating:');
98
+ errors.sort();
99
+ for (const c of errors) {
100
+ result.push(` - ${c}`);
101
+ }
102
+ insertNewLine = true;
103
+ }
104
+ box(result.join('\n'));
105
+
106
+ const deleteFile = handleCanceled<boolean>(await select({
107
+ message: `Would you like to delete ${bulkPath}?`,
108
+ options: [{
109
+ label: 'Yes, delete the file',
110
+ value: true
111
+ }, {
112
+ label: 'No, keep the file',
113
+ value: false
114
+ }]
115
+ }));
116
+
117
+ if (deleteFile) {
118
+ const deleteSpinner = spinner();
119
+ deleteSpinner.start(`Deleting ${bulkPath}`);
120
+ await sleep();
121
+ try {
122
+ await rm(resolvePath(rootDir, bulkPath), { force: true });
123
+ deleteSpinner.stop('File deleted')
124
+ } catch (err) {
125
+ deleteSpinner.error('An error occurred while attempting to delete the file');
126
+ }
127
+ }
128
+
129
+ outro('Happy coding!');
130
+
131
+ }
@@ -0,0 +1,31 @@
1
+ import { resolve } from 'node:path';
2
+ import { intro, select } from '@clack/prompts';
3
+ import { handleCanceled } from '../utils.js';
4
+
5
+ interface Opts {
6
+ root?: string;
7
+ }
8
+ export default async function({ root }: Opts = {}) {
9
+ const rootDir = root ?? process.cwd();
10
+
11
+ intro('Page Manager');
12
+
13
+ const action = handleCanceled<'single' | 'bulk'>(await select({
14
+ message: 'How would you like to create pages?',
15
+ options: [{
16
+ label: 'One at a time',
17
+ value: 'single'
18
+ }, {
19
+ label: 'Bulk create',
20
+ value: 'bulk'
21
+ }]
22
+ }));
23
+
24
+ if (action === 'bulk') {
25
+ const bulkCreate = await import('./bulk-create.js');
26
+ await bulkCreate.default(rootDir);
27
+ } else {
28
+ const singleCreate = await import('./single-create.js');
29
+ await singleCreate.default(rootDir);
30
+ }
31
+ }
@@ -0,0 +1,97 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { writeFile, readdir, readFile, mkdir } from 'node:fs/promises';
3
+ import { basename, join, resolve as resolvePath } from 'node:path';
4
+
5
+ import { text, select, multiselect, note, spinner, outro } from '@clack/prompts';
6
+ import { sleep, handleCanceled, cleanInput, generateRandomId, titleCase } from '../utils.js';
7
+ import { getLayouts, createPageAssets } from './utils.js';
8
+
9
+
10
+
11
+
12
+ export default async function singleCreate(rootDir: string) {
13
+ const contentDir = join(rootDir, 'src/contents');
14
+ const pagesDir = join(rootDir, 'src/pages');
15
+ const layouts = await getLayouts(rootDir);
16
+
17
+ note('Page content will be stored in /src/contents\nPage routes will be created in /src/pages');
18
+
19
+ const contentPath = handleCanceled(await text({
20
+ message: 'Enter the path for the new page content relative to /src/contents',
21
+ validate: (value: string | undefined): string | undefined => {
22
+ if (!value?.trim()) {
23
+ return 'The content path is required';
24
+ }
25
+
26
+ const contentPath = value!.trim();
27
+ if (existsSync(join(contentDir, contentPath))) {
28
+ return 'The content path already exists';
29
+ }
30
+ const pageName = contentPath.replace(/\//g, '-');
31
+ if (existsSync(join(pagesDir, `${pageName}.astro`))) {
32
+ return `The page /src/pages/${pageName}.astro already exists`;
33
+ }
34
+ return undefined;
35
+ }
36
+ }));
37
+
38
+ // const name = handleCanceled(await text({
39
+ // message: 'Enter the name of the content file',
40
+ // initialValue: `${basename(path)}.astro`
41
+ // }));
42
+
43
+ // const pageName = handleCanceled(await text({
44
+ // message: 'Enter the name of the page',
45
+ // initialValue: `${path.toLowerCase().replace(/\//g, '-')}.astro`
46
+ // }));
47
+
48
+ const contentName = `${basename(contentPath)}`;
49
+ const pageName = `${contentPath.toLowerCase().replace(/\//g, '-')}.astro`
50
+
51
+ const layout = handleCanceled(await select({
52
+ message: 'Select the layout the page should use',
53
+ options: (() => {
54
+ return layouts.map((layout) => {
55
+ const label = layout.replace(/\\/g, '/');
56
+ return {
57
+ label,
58
+ value: label
59
+ };
60
+ })
61
+ })()
62
+ }));
63
+
64
+ const spin = spinner();
65
+ spin.start('Generating page assets');
66
+ await sleep();
67
+
68
+ await createPageAssets({ rootDir, contentPath, contentName, pageName, layout });
69
+
70
+ await sleep();
71
+ spin.stop('Page assets generated!');
72
+
73
+
74
+ // let path: string | undefined = undefined;
75
+ // do {
76
+ // path = handleCanceled(await text({
77
+ // message: 'Enter the path for the new page content relative to /src/contents',
78
+ // validate: (value: string | undefined) => {
79
+ // if (!value?.trim()) return 'The content path is required';
80
+ // return undefined;
81
+ // }
82
+ // }));
83
+
84
+ // const contentPath = path!.trim().toLowerCase();
85
+ // console.log(join(contentDir, contentPath));
86
+ // if (existsSync(join(contentDir, contentPath))) {
87
+ // console.log('The content path already exists');
88
+ // path = undefined;
89
+ // }
90
+ // const pageName = contentPath.replace(/\//g, '-');
91
+ // console.log(join(pagesDir, `${pageName}.astro`));
92
+ // if (existsSync(join(pagesDir, `${pageName}.astro`))) {
93
+ // console.log(`The page /src/pages/${pageName}.astro already exists`);
94
+ // path = undefined;
95
+ // }
96
+ // } while (path === undefined);
97
+ }
@@ -0,0 +1,12 @@
1
+ ---
2
+ import ##LAYOUT_NAME## from '@layouts/##LAYOUT_PATH##';
3
+
4
+ import './##CONTENT_NAME##.scss';
5
+ ---
6
+ <##LAYOUT_NAME##>
7
+ <!-- Insert page content here -->
8
+
9
+ <script>
10
+ import './##CONTENT_NAME##';
11
+ </script>
12
+ </##LAYOUT_NAME##>
@@ -0,0 +1,4 @@
1
+ ---
2
+ import Page from '@contents/##CONTENTS_PATH##/##CONTENTS_NAME##.astro';
3
+ ---
4
+ <Page />
@@ -0,0 +1,70 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
3
+ import { basename, join, resolve as resolvePath } from 'node:path';
4
+
5
+ export async function getLayouts(rootDir: string): Promise<string[]> {
6
+ const layouts = await readdir(join(rootDir, 'src/layouts'), { recursive: true });
7
+ return layouts.filter(l => l.endsWith('.astro')).sort();
8
+ }
9
+
10
+ interface CreateParams {
11
+ rootDir: string;
12
+ contentPath: string;
13
+ contentName?: string;
14
+ pageName?: string;
15
+ layout: string;
16
+ }
17
+ export async function createPageAssets({ rootDir, contentPath, contentName, pageName, layout }: CreateParams) {
18
+ const contentDir = join(rootDir, 'src/contents');
19
+ const pagesDir = join(rootDir, 'src/pages');
20
+
21
+ if (!contentName) {
22
+ contentName = basename(contentPath);
23
+ }
24
+ if (!pageName) {
25
+ pageName = `${contentPath
26
+ .split('/')
27
+ .map((p, i, arr) => {
28
+ if (i > 0) {
29
+ let str = p;
30
+ // prevent the page name from having repeated words like "dir-dir-1"
31
+ if (p.startsWith(arr[i-1]) && p.length > arr[i-1].length + 1) {
32
+ str = p.substring(arr[i-1].length);
33
+ if (/[0-9a-z]/i.test(str[0])) {
34
+ return str;
35
+ } else {
36
+ return str.substring(str.match(/[0-9a-z]/i)?.index ?? 0);
37
+ }
38
+ }
39
+ return str;
40
+ } else {
41
+ return p
42
+ }
43
+ })
44
+ .join('-')}.astro`;
45
+
46
+ if (existsSync(join(pagesDir, pageName))) {
47
+ pageName = `${contentPath.replace(/\//g, '-')}.astro`;
48
+ }
49
+ }
50
+
51
+ let pageTemplate = await readFile(join(import.meta.dirname, 'templates/page.astro.txt'), 'utf8');
52
+ pageTemplate = pageTemplate
53
+ .replace(/##CONTENTS_PATH##/g, contentPath)
54
+ .replace(/##CONTENTS_NAME##/g, contentName)
55
+
56
+ await writeFile(join(pagesDir, pageName), pageTemplate);
57
+
58
+ let contentTemplate = await readFile(join(import.meta.dirname, 'templates/contents.astro.txt'), 'utf8');
59
+ contentTemplate = contentTemplate
60
+ .replace(/##LAYOUT_NAME##/g, basename(layout).replace(/\.astro$/, ''))
61
+ .replace(/##LAYOUT_PATH##/g, layout)
62
+ .replace(/##CONTENT_NAME##/g, contentName);
63
+
64
+ if (!existsSync(join(contentDir, contentPath))) {
65
+ await mkdir(join(contentDir, contentPath), { recursive: true });
66
+ }
67
+ await writeFile(join(contentDir, contentPath, `${contentName}.astro`), contentTemplate);
68
+ await writeFile(join(contentDir, contentPath, `${contentName}.scss`), '');
69
+ await writeFile(join(contentDir, contentPath, `${contentName}.ts`), '');
70
+ }
@@ -0,0 +1,47 @@
1
+ import { getRandomValues } from 'node:crypto';
2
+ import { isCancel, cancel } from '@clack/prompts';
3
+
4
+ export function sleep(timeout = 500) {
5
+ return new Promise((resolve) => {
6
+ setTimeout(resolve, timeout);
7
+ });
8
+ }
9
+
10
+ export function handleCanceled<T = string>(value: unknown): T {
11
+ if (isCancel(value)) {
12
+ cancel('Canceled');
13
+ process.exit(0);
14
+ } else {
15
+ return value as T;
16
+ }
17
+ }
18
+
19
+ export function cleanInput(value: string) {
20
+ return value.trim().replace(/ /g, ' ');
21
+ }
22
+
23
+ export const generateRandomId = (() => {
24
+ function chars(count: number) {
25
+ return [...getRandomValues(new Uint16Array(count))]
26
+ .map((n) => {
27
+ return n.toString(36).toUpperCase()
28
+ })
29
+ .join('');
30
+ }
31
+ return (length = 8) =>{
32
+ let id = chars(Math.round(length / 4) + 1);
33
+ while (id.length < length) {
34
+ id += chars(1);
35
+ }
36
+ return id.substring(0, length);
37
+ }
38
+ })();
39
+
40
+ export function titleCase(value: string) {
41
+ return value.split('-')
42
+ .filter(v => v.trim() !== '')
43
+ .map((v) => {
44
+ return `${v[0].toUpperCase()}${v.substring(1)}`
45
+ })
46
+ .join(' ');
47
+ }