@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
|
@@ -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,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,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
|
+
}
|