@tavus/cvi-ui 0.0.1-beta.1
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/.prettierrc.js +24 -0
- package/LICENSE +21 -0
- package/README.md +91 -0
- package/dev-components/components/cvi-provider/index.tsx +9 -0
- package/dev-components/hooks/README.md +499 -0
- package/dev-components/hooks/cvi-events-hooks.tsx +168 -0
- package/dev-components/hooks/use-cvi-call.tsx +24 -0
- package/dev-components/hooks/use-local-camera.tsx +20 -0
- package/dev-components/hooks/use-local-microphone.tsx +20 -0
- package/dev-components/hooks/use-local-screenshare.tsx +32 -0
- package/dev-components/hooks/use-remote-participant-ids.tsx +7 -0
- package/dev-components/hooks/use-replica-ids.tsx +9 -0
- package/dev-components/hooks/use-request-permissions.tsx +24 -0
- package/dev-components/hooks/use-start-haircheck.tsx +60 -0
- package/dist/index.js +237334 -0
- package/dist/types/cli/add.d.ts +20 -0
- package/dist/types/cli/info.d.ts +2 -0
- package/dist/types/cli/init.d.ts +23 -0
- package/dist/types/components/highlighter.d.ts +6 -0
- package/dist/types/components/logger.d.ts +8 -0
- package/dist/types/components/spinner.d.ts +4 -0
- package/dist/types/constants/components.d.ts +59 -0
- package/dist/types/constants/config.d.ts +5 -0
- package/dist/types/constants/errors.d.ts +5 -0
- package/dist/types/constants/frameworks.d.ts +39 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/preflights/preflight-add.d.ts +15 -0
- package/dist/types/preflights/preflight-init.d.ts +9 -0
- package/dist/types/utils/add-components.d.ts +5 -0
- package/dist/types/utils/get-config.d.ts +51 -0
- package/dist/types/utils/get-package-info.d.ts +2 -0
- package/dist/types/utils/get-package-manager.d.ts +4 -0
- package/dist/types/utils/get-project-info.d.ts +10 -0
- package/dist/types/utils/handle-error.d.ts +1 -0
- package/dist/types/utils/resolve-components-tree.d.ts +4 -0
- package/dist/types/utils/resolve-import.d.ts +2 -0
- package/dist/types/utils/update-dependencies.d.ts +4 -0
- package/dist/types/utils/update-files.d.ts +14 -0
- package/dist/typescript-DhnEO4aV.js +12 -0
- package/dist/typescript-XxXP1Woc.js +14 -0
- package/eslint.config.js +12 -0
- package/package.json +67 -0
- package/prepare-scripts/convert-to-js.js +152 -0
- package/prepare-scripts/create-templates.js +265 -0
- package/rollup.config.js +28 -0
- package/src/cli/add.ts +124 -0
- package/src/cli/info.ts +21 -0
- package/src/cli/init.ts +131 -0
- package/src/components/highlighter.ts +8 -0
- package/src/components/logger.ts +22 -0
- package/src/components/spinner.ts +13 -0
- package/src/constants/config.ts +7 -0
- package/src/constants/errors.ts +5 -0
- package/src/constants/frameworks.ts +40 -0
- package/src/index.ts +26 -0
- package/src/preflights/preflight-add.ts +56 -0
- package/src/preflights/preflight-init.ts +77 -0
- package/src/utils/add-components.ts +52 -0
- package/src/utils/get-config.ts +60 -0
- package/src/utils/get-package-info.ts +14 -0
- package/src/utils/get-package-manager.ts +45 -0
- package/src/utils/get-project-info.ts +144 -0
- package/src/utils/handle-error.ts +34 -0
- package/src/utils/resolve-components-tree.ts +35 -0
- package/src/utils/update-dependencies.ts +37 -0
- package/src/utils/update-files.ts +212 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { parse } from '@babel/parser';
|
|
4
|
+
import traverse from '@babel/traverse';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
10
|
+
|
|
11
|
+
const SKIP_DEPENDENCIES = new Set(['react', 'react-dom', '@daily-co/daily-react']);
|
|
12
|
+
|
|
13
|
+
// Function to extract imports from TSX file
|
|
14
|
+
function extractImports(content) {
|
|
15
|
+
const ast = parse(content, {
|
|
16
|
+
sourceType: 'module',
|
|
17
|
+
plugins: ['typescript', 'jsx'],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const dependencies = {
|
|
21
|
+
npm: new Set(),
|
|
22
|
+
local: new Set(),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
traverse.default(ast, {
|
|
26
|
+
ImportDeclaration(path) {
|
|
27
|
+
const source = path.node.source.value;
|
|
28
|
+
// Skip default imports
|
|
29
|
+
if (SKIP_DEPENDENCIES.has(source)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (source.endsWith('.css')) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (source.startsWith('.')) {
|
|
36
|
+
// Extract only the component name from the path
|
|
37
|
+
const componentName = source
|
|
38
|
+
.split('/')
|
|
39
|
+
.pop()
|
|
40
|
+
.replace(/\.(tsx|ts|jsx|js)$/, '');
|
|
41
|
+
dependencies.local.add(componentName);
|
|
42
|
+
} else {
|
|
43
|
+
dependencies.npm.add(source);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return dependencies;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Function to get type from file path
|
|
52
|
+
function getTypeFromPath(filePath, templatesDir) {
|
|
53
|
+
const relativePath = path.relative(templatesDir, filePath);
|
|
54
|
+
const parts = relativePath.split(path.sep);
|
|
55
|
+
// The type is the first folder after templates
|
|
56
|
+
return parts[0];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Function to process a TSX file and its associated CSS
|
|
60
|
+
function processTemplateFile(tsxPath, templatesDir) {
|
|
61
|
+
const content = fs.readFileSync(tsxPath, 'utf-8');
|
|
62
|
+
const dirPath = path.dirname(tsxPath);
|
|
63
|
+
let styles = '';
|
|
64
|
+
|
|
65
|
+
// Check if there's a CSS file in the same directory
|
|
66
|
+
const files = fs.readdirSync(dirPath);
|
|
67
|
+
const cssFile = files.find((file) => file.endsWith('.css'));
|
|
68
|
+
|
|
69
|
+
if (cssFile) {
|
|
70
|
+
const cssPath = path.join(dirPath, cssFile);
|
|
71
|
+
styles = fs.readFileSync(cssPath, 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const dependencies = extractImports(content);
|
|
75
|
+
const type = getTypeFromPath(tsxPath, templatesDir);
|
|
76
|
+
|
|
77
|
+
// Get component name from file path
|
|
78
|
+
const fileName = path.basename(tsxPath, path.extname(tsxPath));
|
|
79
|
+
const componentName = fileName === 'index' ? path.basename(path.dirname(tsxPath)) : fileName;
|
|
80
|
+
|
|
81
|
+
// Add cvi-provider to local dependencies for specific components
|
|
82
|
+
if (componentName === 'hair-check' || componentName === 'conversation') {
|
|
83
|
+
dependencies.local.add('cvi-provider');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const templateData = {
|
|
87
|
+
type,
|
|
88
|
+
content,
|
|
89
|
+
styles,
|
|
90
|
+
...(dependencies.local.size > 0 && { componentsDependencies: Array.from(dependencies.local) }),
|
|
91
|
+
...(dependencies.npm.size > 0 && { dependencies: Array.from(dependencies.npm) }),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return templateData;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Function to create components list file
|
|
98
|
+
function createComponentsList(components, outputDir) {
|
|
99
|
+
const componentsList = components
|
|
100
|
+
.map((comp) => {
|
|
101
|
+
const name = path.basename(comp, path.extname(comp));
|
|
102
|
+
const relativePath = path
|
|
103
|
+
.relative(outputDir, comp)
|
|
104
|
+
.replace(/\\/g, '/')
|
|
105
|
+
.replace('.json', '')
|
|
106
|
+
.replace(/^tsx\//, '');
|
|
107
|
+
return ` { name: '${name}', path: '${relativePath}' }`;
|
|
108
|
+
})
|
|
109
|
+
.join(',\n');
|
|
110
|
+
|
|
111
|
+
const content = `export const components = [\n${componentsList}\n] as const;\n\nexport type Component = typeof components[number];\n`;
|
|
112
|
+
|
|
113
|
+
fs.writeFileSync(path.join(outputDir, 'components.ts'), content);
|
|
114
|
+
console.log('Created components list: components.ts');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Function to create index file for components
|
|
118
|
+
function createComponentsIndex(components, outputDir) {
|
|
119
|
+
const exports = components
|
|
120
|
+
.map((comp) => {
|
|
121
|
+
const name = path.basename(comp, '.json');
|
|
122
|
+
const relativePath = path.relative(outputDir, comp).replace(/\\/g, '/');
|
|
123
|
+
return `export { default as "${name}" } from './${relativePath}';`;
|
|
124
|
+
})
|
|
125
|
+
.join('\n');
|
|
126
|
+
|
|
127
|
+
const content = `${exports}\n`;
|
|
128
|
+
const indexPath = path.join(outputDir, `index.ts`);
|
|
129
|
+
fs.writeFileSync(indexPath, content);
|
|
130
|
+
console.log(`Created index file: ${indexPath}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Function to create main templates index file
|
|
134
|
+
function createTemplatesIndex(tsxDir, jsxDir, outputDir) {
|
|
135
|
+
const content = `export * as tsx from './tsx';\nexport * as jsx from './jsx';\n`;
|
|
136
|
+
const indexPath = path.join(outputDir, 'index.ts');
|
|
137
|
+
fs.writeFileSync(indexPath, content);
|
|
138
|
+
console.log(`Created main index file: ${indexPath}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Main function to process all templates
|
|
142
|
+
async function convertTemplates() {
|
|
143
|
+
const tsxTemplatesDir = path.join(projectRoot, 'dev-components');
|
|
144
|
+
const jsxTemplatesDir = path.join(projectRoot, 'jsx-templates');
|
|
145
|
+
const outputDir = path.join(projectRoot, 'src', 'templates');
|
|
146
|
+
const tsxOutputDir = path.join(outputDir, 'tsx');
|
|
147
|
+
const jsxOutputDir = path.join(outputDir, 'jsx');
|
|
148
|
+
const tsxComponents = [];
|
|
149
|
+
const jsxComponents = [];
|
|
150
|
+
|
|
151
|
+
// Create output directories if they don't exist
|
|
152
|
+
if (!fs.existsSync(tsxOutputDir)) {
|
|
153
|
+
fs.mkdirSync(tsxOutputDir, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
if (!fs.existsSync(jsxOutputDir)) {
|
|
156
|
+
fs.mkdirSync(jsxOutputDir, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Process all TSX files in the dev-components directory
|
|
160
|
+
async function processTSXDirectory(dir) {
|
|
161
|
+
const items = fs.readdirSync(dir);
|
|
162
|
+
|
|
163
|
+
for (const item of items) {
|
|
164
|
+
const itemPath = path.join(dir, item);
|
|
165
|
+
const stat = fs.statSync(itemPath);
|
|
166
|
+
|
|
167
|
+
if (stat.isDirectory()) {
|
|
168
|
+
await processTSXDirectory(itemPath);
|
|
169
|
+
// TODO: Add support for .ts files
|
|
170
|
+
} else if (item.endsWith('.tsx')) {
|
|
171
|
+
const templateData = processTemplateFile(itemPath, tsxTemplatesDir);
|
|
172
|
+
const relativePath = path.relative(tsxTemplatesDir, itemPath);
|
|
173
|
+
|
|
174
|
+
// Check if this is an index file
|
|
175
|
+
const isIndexFile = path.basename(itemPath, path.extname(itemPath)) === 'index';
|
|
176
|
+
|
|
177
|
+
let outputPath;
|
|
178
|
+
if (isIndexFile) {
|
|
179
|
+
// For index files, create a file with the folder name instead of creating a folder
|
|
180
|
+
const parentDir = path.dirname(relativePath);
|
|
181
|
+
outputPath = path.join(tsxOutputDir, `${parentDir}.json`);
|
|
182
|
+
} else {
|
|
183
|
+
// For regular files, use the normal path structure
|
|
184
|
+
outputPath = path.join(tsxOutputDir, relativePath.replace('.tsx', '.json'));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Ensure the output directory structure exists
|
|
188
|
+
const outputDirPath = path.dirname(outputPath);
|
|
189
|
+
if (!fs.existsSync(outputDirPath)) {
|
|
190
|
+
fs.mkdirSync(outputDirPath, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fs.writeFileSync(outputPath, JSON.stringify(templateData, null, 2));
|
|
194
|
+
console.log(`Created TSX template: ${outputPath}`);
|
|
195
|
+
|
|
196
|
+
// Add to components list
|
|
197
|
+
tsxComponents.push(outputPath);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Process all JSX files in the jsx-templates directory
|
|
203
|
+
async function processJSXDirectory(dir) {
|
|
204
|
+
const items = fs.readdirSync(dir);
|
|
205
|
+
|
|
206
|
+
for (const item of items) {
|
|
207
|
+
const itemPath = path.join(dir, item);
|
|
208
|
+
const stat = fs.statSync(itemPath);
|
|
209
|
+
|
|
210
|
+
if (stat.isDirectory()) {
|
|
211
|
+
await processJSXDirectory(itemPath);
|
|
212
|
+
} else if (item.endsWith('.jsx')) {
|
|
213
|
+
const templateData = processTemplateFile(itemPath, jsxTemplatesDir);
|
|
214
|
+
const relativePath = path.relative(jsxTemplatesDir, itemPath);
|
|
215
|
+
|
|
216
|
+
// Check if this is an index file
|
|
217
|
+
const isIndexFile = path.basename(itemPath, path.extname(itemPath)) === 'index';
|
|
218
|
+
|
|
219
|
+
let outputPath;
|
|
220
|
+
if (isIndexFile) {
|
|
221
|
+
// For index files, create a file with the folder name instead of creating a folder
|
|
222
|
+
const parentDir = path.dirname(relativePath);
|
|
223
|
+
outputPath = path.join(jsxOutputDir, `${parentDir}.json`);
|
|
224
|
+
} else {
|
|
225
|
+
// For regular files, use the normal path structure
|
|
226
|
+
outputPath = path.join(jsxOutputDir, relativePath.replace('.jsx', '.json'));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Ensure the output directory structure exists
|
|
230
|
+
const outputDirPath = path.dirname(outputPath);
|
|
231
|
+
if (!fs.existsSync(outputDirPath)) {
|
|
232
|
+
fs.mkdirSync(outputDirPath, { recursive: true });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
fs.writeFileSync(outputPath, JSON.stringify(templateData, null, 2));
|
|
236
|
+
console.log(`Created JSX template: ${outputPath}`);
|
|
237
|
+
|
|
238
|
+
// Add to components list
|
|
239
|
+
jsxComponents.push(outputPath);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Process both TSX and JSX templates
|
|
245
|
+
if (fs.existsSync(tsxTemplatesDir)) {
|
|
246
|
+
await processTSXDirectory(tsxTemplatesDir);
|
|
247
|
+
// Create components list file only for TSX components
|
|
248
|
+
if (tsxComponents.length > 0) {
|
|
249
|
+
createComponentsList(tsxComponents, outputDir);
|
|
250
|
+
createComponentsIndex(tsxComponents, tsxOutputDir);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (fs.existsSync(jsxTemplatesDir)) {
|
|
255
|
+
await processJSXDirectory(jsxTemplatesDir);
|
|
256
|
+
if (jsxComponents.length > 0) {
|
|
257
|
+
createComponentsIndex(jsxComponents, jsxOutputDir);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Create main templates index file
|
|
262
|
+
createTemplatesIndex(tsxOutputDir, jsxOutputDir, outputDir);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
convertTemplates().catch(console.error);
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
2
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
3
|
+
import typescript from '@rollup/plugin-typescript';
|
|
4
|
+
import json from '@rollup/plugin-json';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
input: 'src/index.ts',
|
|
8
|
+
output: {
|
|
9
|
+
dir: 'dist',
|
|
10
|
+
entryFileNames: 'index.js',
|
|
11
|
+
format: 'es',
|
|
12
|
+
banner: '#!/usr/bin/env node',
|
|
13
|
+
},
|
|
14
|
+
external: [
|
|
15
|
+
'react',
|
|
16
|
+
'@daily-co/daily-js',
|
|
17
|
+
'daily-js',
|
|
18
|
+
'npm-run-path',
|
|
19
|
+
'unicorn-magic',
|
|
20
|
+
'execa',
|
|
21
|
+
],
|
|
22
|
+
plugins: [
|
|
23
|
+
json(),
|
|
24
|
+
resolve({ preferBuiltins: true }),
|
|
25
|
+
commonjs(),
|
|
26
|
+
typescript({ tsconfig: './tsconfig.json' }),
|
|
27
|
+
],
|
|
28
|
+
};
|
package/src/cli/add.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { preFlightAdd } from '@/src/preflights/preflight-add';
|
|
5
|
+
import { handleError } from '@/src/utils/handle-error';
|
|
6
|
+
import { highlighter } from '@/src/components/highlighter';
|
|
7
|
+
import { logger } from '@/src/components/logger';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
import prompts from 'prompts';
|
|
11
|
+
import * as ERRORS from '@/src/constants/errors';
|
|
12
|
+
import { CONFIG_NAME } from '@/src/constants/config';
|
|
13
|
+
import { runInit } from '@/src/cli/init';
|
|
14
|
+
import { addComponents } from '@/src/utils/add-components';
|
|
15
|
+
import { components as COMPONENTS } from '../templates/components';
|
|
16
|
+
|
|
17
|
+
export const addOptionsSchema = z.object({
|
|
18
|
+
components: z.array(z.string()).optional(),
|
|
19
|
+
overwrite: z.boolean(),
|
|
20
|
+
cwd: z.string(),
|
|
21
|
+
// TODO: Add path option
|
|
22
|
+
// path: z.string().optional(),
|
|
23
|
+
silent: z.boolean(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const add = new Command()
|
|
27
|
+
.name('add')
|
|
28
|
+
.description('add a CVI component to your project')
|
|
29
|
+
.argument('[components...]', 'the components to add.')
|
|
30
|
+
.option('-o, --overwrite', 'overwrite existing files.', false)
|
|
31
|
+
.option(
|
|
32
|
+
'-c, --cwd <cwd>',
|
|
33
|
+
'the working directory. defaults to the current directory.',
|
|
34
|
+
process.cwd()
|
|
35
|
+
)
|
|
36
|
+
// TODO: Add path option
|
|
37
|
+
// .option('-p, --path <path>', 'the path to add the component to.')
|
|
38
|
+
.option('-s, --silent', 'mute output.', false)
|
|
39
|
+
.action(async (components, opts) => {
|
|
40
|
+
try {
|
|
41
|
+
const options = addOptionsSchema.parse({
|
|
42
|
+
components,
|
|
43
|
+
cwd: path.resolve(opts.cwd),
|
|
44
|
+
...opts,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!options.components?.length) {
|
|
48
|
+
options.components = await showComponentsPrompt(options);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, prefer-const
|
|
52
|
+
let { errors, config } = await preFlightAdd(options);
|
|
53
|
+
|
|
54
|
+
// No cvi-components.json file. Prompt the user to run init.
|
|
55
|
+
if (errors[ERRORS.MISSING_CONFIG]) {
|
|
56
|
+
const { proceed } = await prompts({
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
name: 'proceed',
|
|
59
|
+
message: `You need to create a ${highlighter.info(
|
|
60
|
+
CONFIG_NAME
|
|
61
|
+
)} file to add components. Proceed?`,
|
|
62
|
+
initial: true,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!proceed) {
|
|
66
|
+
logger.break();
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
config = await runInit({
|
|
71
|
+
cwd: options.cwd,
|
|
72
|
+
components: undefined,
|
|
73
|
+
silent: options.silent,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
|
|
78
|
+
logger.break();
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!config) {
|
|
83
|
+
throw new Error(`Failed to read config at ${highlighter.info(options.cwd)}.`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await addComponents(options.components, config, options);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.break();
|
|
89
|
+
handleError(error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
async function showComponentsPrompt(options: z.infer<typeof addOptionsSchema>) {
|
|
94
|
+
if (options.components?.length) {
|
|
95
|
+
return options.components;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { components } = await prompts({
|
|
99
|
+
type: 'multiselect',
|
|
100
|
+
name: 'components',
|
|
101
|
+
message: 'Which components would you like to add?',
|
|
102
|
+
hint: 'Space to select. A to toggle all. Enter to submit.',
|
|
103
|
+
instructions: false,
|
|
104
|
+
choices: COMPONENTS.map((entry) => ({
|
|
105
|
+
title: entry.name,
|
|
106
|
+
value: entry.name,
|
|
107
|
+
selected: options.components?.includes(entry.name),
|
|
108
|
+
})),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!components?.length) {
|
|
112
|
+
logger.warn('No components selected. Exiting.');
|
|
113
|
+
logger.info('');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const result = z.array(z.string()).safeParse(components);
|
|
118
|
+
if (!result.success) {
|
|
119
|
+
logger.error('');
|
|
120
|
+
handleError(new Error('Something went wrong. Please try again.'));
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
return result.data;
|
|
124
|
+
}
|
package/src/cli/info.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getConfig } from '@/src/utils/get-config';
|
|
2
|
+
import { getProjectInfo } from '@/src/utils/get-project-info';
|
|
3
|
+
import { logger } from '@/src/components/logger';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { CONFIG_NAME } from '../constants/config';
|
|
6
|
+
|
|
7
|
+
export const info = new Command()
|
|
8
|
+
.name('info')
|
|
9
|
+
.description('get information about your project')
|
|
10
|
+
.option(
|
|
11
|
+
'-c, --cwd <cwd>',
|
|
12
|
+
'the working directory. defaults to the current directory.',
|
|
13
|
+
process.cwd()
|
|
14
|
+
)
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
logger.info('> project info');
|
|
17
|
+
console.log(await getProjectInfo(opts.cwd));
|
|
18
|
+
logger.break();
|
|
19
|
+
logger.info(`> ${CONFIG_NAME}`);
|
|
20
|
+
console.log(await getConfig(opts.cwd));
|
|
21
|
+
});
|
package/src/cli/init.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { preFlightInit } from '@/src/preflights/preflight-init';
|
|
4
|
+
import { addComponents } from '@/src/utils/add-components';
|
|
5
|
+
import * as ERRORS from '@/src/constants/errors';
|
|
6
|
+
import {
|
|
7
|
+
getConfig,
|
|
8
|
+
rawConfigSchema,
|
|
9
|
+
resolveConfigPaths,
|
|
10
|
+
type Config,
|
|
11
|
+
} from '@/src/utils/get-config';
|
|
12
|
+
import { ProjectInfo } from '@/src/utils/get-project-info';
|
|
13
|
+
import { handleError } from '@/src/utils/handle-error';
|
|
14
|
+
import { highlighter } from '@/src/components/highlighter';
|
|
15
|
+
import { logger } from '@/src/components/logger';
|
|
16
|
+
import { spinner } from '@/src/components/spinner';
|
|
17
|
+
import { Command } from 'commander';
|
|
18
|
+
import prompts from 'prompts';
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { CONFIG_NAME, DEFAULT_DEPENDENCIES } from '../constants/config';
|
|
21
|
+
import { updateDependencies } from '../utils/update-dependencies';
|
|
22
|
+
|
|
23
|
+
export const initOptionsSchema = z.object({
|
|
24
|
+
cwd: z.string(),
|
|
25
|
+
silent: z.boolean(),
|
|
26
|
+
components: z.array(z.string()).optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const init = new Command()
|
|
30
|
+
.name('init')
|
|
31
|
+
.description('initialize your project and install dependencies')
|
|
32
|
+
.option(
|
|
33
|
+
'-c, --cwd <cwd>',
|
|
34
|
+
'the working directory. defaults to the current directory.',
|
|
35
|
+
process.cwd()
|
|
36
|
+
)
|
|
37
|
+
.argument('[components...]', 'the components to add.')
|
|
38
|
+
.option('-s, --silent', 'mute output.', false)
|
|
39
|
+
.action(async (components, opts) => {
|
|
40
|
+
try {
|
|
41
|
+
const options = initOptionsSchema.parse({
|
|
42
|
+
cwd: path.resolve(opts.cwd),
|
|
43
|
+
...opts,
|
|
44
|
+
components,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await runInit(options);
|
|
48
|
+
|
|
49
|
+
logger.log(
|
|
50
|
+
`${highlighter.success(
|
|
51
|
+
'Success!'
|
|
52
|
+
)} Project initialization completed.\nYou may now add components.`
|
|
53
|
+
);
|
|
54
|
+
logger.break();
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.break();
|
|
57
|
+
handleError(error);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export async function runInit(options: z.infer<typeof initOptionsSchema>) {
|
|
62
|
+
let projectInfo;
|
|
63
|
+
const preflight = await preFlightInit(options);
|
|
64
|
+
if (preflight.errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
|
|
65
|
+
process.exit(1);
|
|
66
|
+
} else {
|
|
67
|
+
projectInfo = preflight.projectInfo;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const config = projectInfo
|
|
71
|
+
? await promptForMinimalConfig(projectInfo)
|
|
72
|
+
: await promptForConfig(await getConfig(options.cwd));
|
|
73
|
+
|
|
74
|
+
const { proceed } = await prompts({
|
|
75
|
+
type: 'confirm',
|
|
76
|
+
name: 'proceed',
|
|
77
|
+
message: `Write configuration to ${highlighter.info(CONFIG_NAME)}. Proceed?`,
|
|
78
|
+
initial: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!proceed) {
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Write cvi-components.json.
|
|
86
|
+
const componentSpinner = spinner(`Writing ${CONFIG_NAME}.`).start();
|
|
87
|
+
const targetPath = path.resolve(options.cwd, CONFIG_NAME);
|
|
88
|
+
await fs.writeFile(targetPath, JSON.stringify(config, null, 2), 'utf8');
|
|
89
|
+
componentSpinner.succeed();
|
|
90
|
+
|
|
91
|
+
const fullConfig = await resolveConfigPaths(options.cwd, config);
|
|
92
|
+
|
|
93
|
+
// Install default dependencies
|
|
94
|
+
await updateDependencies(DEFAULT_DEPENDENCIES, fullConfig, {
|
|
95
|
+
silent: options.silent,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Add components.
|
|
99
|
+
if (options.components?.length) {
|
|
100
|
+
await addComponents(options.components, fullConfig, {
|
|
101
|
+
overwrite: true,
|
|
102
|
+
silent: options.silent,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return fullConfig;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function promptForConfig(defaultConfig: Config | null = null) {
|
|
110
|
+
logger.info('');
|
|
111
|
+
const options = await prompts([
|
|
112
|
+
{
|
|
113
|
+
type: 'toggle',
|
|
114
|
+
name: 'typescript',
|
|
115
|
+
message: `Would you like to use ${highlighter.info('TypeScript')} (recommended)?`,
|
|
116
|
+
initial: defaultConfig?.tsx ?? true,
|
|
117
|
+
active: 'yes',
|
|
118
|
+
inactive: 'no',
|
|
119
|
+
},
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
return rawConfigSchema.parse({
|
|
123
|
+
tsx: options.typescript,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function promptForMinimalConfig(defaultConfig: ProjectInfo) {
|
|
128
|
+
return rawConfigSchema.parse({
|
|
129
|
+
tsx: defaultConfig?.isTsx,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { highlighter } from "./highlighter"
|
|
2
|
+
|
|
3
|
+
export const logger = {
|
|
4
|
+
error(...args: unknown[]) {
|
|
5
|
+
console.log(highlighter.error(args.join(" ")))
|
|
6
|
+
},
|
|
7
|
+
warn(...args: unknown[]) {
|
|
8
|
+
console.log(highlighter.warn(args.join(" ")))
|
|
9
|
+
},
|
|
10
|
+
info(...args: unknown[]) {
|
|
11
|
+
console.log(highlighter.info(args.join(" ")))
|
|
12
|
+
},
|
|
13
|
+
success(...args: unknown[]) {
|
|
14
|
+
console.log(highlighter.success(args.join(" ")))
|
|
15
|
+
},
|
|
16
|
+
log(...args: unknown[]) {
|
|
17
|
+
console.log(args.join(" "))
|
|
18
|
+
},
|
|
19
|
+
break() {
|
|
20
|
+
console.log("")
|
|
21
|
+
},
|
|
22
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const FRAMEWORKS = {
|
|
2
|
+
'next-app': {
|
|
3
|
+
name: 'next-app',
|
|
4
|
+
label: 'Next.js',
|
|
5
|
+
},
|
|
6
|
+
'next-pages': {
|
|
7
|
+
name: 'next-pages',
|
|
8
|
+
label: 'Next.js',
|
|
9
|
+
},
|
|
10
|
+
remix: {
|
|
11
|
+
name: 'remix',
|
|
12
|
+
label: 'Remix',
|
|
13
|
+
},
|
|
14
|
+
'react-router': {
|
|
15
|
+
name: 'react-router',
|
|
16
|
+
label: 'React Router',
|
|
17
|
+
},
|
|
18
|
+
vite: {
|
|
19
|
+
name: 'vite',
|
|
20
|
+
label: 'Vite',
|
|
21
|
+
},
|
|
22
|
+
astro: {
|
|
23
|
+
name: 'astro',
|
|
24
|
+
label: 'Astro',
|
|
25
|
+
},
|
|
26
|
+
'tanstack-start': {
|
|
27
|
+
name: 'tanstack-start',
|
|
28
|
+
label: 'TanStack Start',
|
|
29
|
+
},
|
|
30
|
+
gatsby: {
|
|
31
|
+
name: 'gatsby',
|
|
32
|
+
label: 'Gatsby',
|
|
33
|
+
},
|
|
34
|
+
manual: {
|
|
35
|
+
name: 'manual',
|
|
36
|
+
label: 'Manual',
|
|
37
|
+
},
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
export type Framework = (typeof FRAMEWORKS)[keyof typeof FRAMEWORKS];
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { init } from '@/src/cli/init';
|
|
2
|
+
import { add } from '@/src/cli/add';
|
|
3
|
+
import { info } from '@/src/cli/info';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
|
|
6
|
+
import packageJson from '../package.json';
|
|
7
|
+
|
|
8
|
+
process.on('SIGINT', () => process.exit(0));
|
|
9
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const program = new Command()
|
|
13
|
+
.name('tavus-cvi')
|
|
14
|
+
.description('A CLI tool for installing and managing CVI components')
|
|
15
|
+
.version(
|
|
16
|
+
packageJson.version || '0.0.1',
|
|
17
|
+
'-v, --version',
|
|
18
|
+
'display the version number'
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
program.addCommand(init).addCommand(add).addCommand(info);
|
|
22
|
+
|
|
23
|
+
program.parse();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
main();
|