pni 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/dist/add-three-app.d.ts +6 -0
- package/dist/add-three-app.js +111 -0
- package/dist/app.d.ts +11 -0
- package/dist/app.js +143 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +71 -0
- package/dist/components/FeatureSelector.d.ts +21 -0
- package/dist/components/FeatureSelector.js +175 -0
- package/dist/components/ProgressIndicator.d.ts +7 -0
- package/dist/components/ProgressIndicator.js +46 -0
- package/dist/components/Summary.d.ts +8 -0
- package/dist/components/Summary.js +51 -0
- package/dist/components/WelcomeHeader.d.ts +2 -0
- package/dist/components/WelcomeHeader.js +8 -0
- package/dist/template_code/three/README.md +146 -0
- package/dist/template_code/three/World.js +133 -0
- package/dist/template_code/three/camera.js +30 -0
- package/dist/template_code/three/components/GlobeSphere.js +608 -0
- package/dist/template_code/three/components/cube.js +27 -0
- package/dist/template_code/three/components/lights.js +16 -0
- package/dist/template_code/three/components/sphere.js +26 -0
- package/dist/template_code/three/components/torus.js +25 -0
- package/dist/template_code/three/scene.js +28 -0
- package/dist/template_code/three/systems/Loop.js +43 -0
- package/dist/template_code/three/systems/Resizer.js +26 -0
- package/dist/template_code/three/systems/controls.js +19 -0
- package/dist/template_code/three/systems/post-processing.js +50 -0
- package/dist/template_code/three/systems/renderer.js +17 -0
- package/dist/template_code/three/utils/deviceDetector.js +141 -0
- package/dist/template_code/three/utils/gltfLoader.js +14 -0
- package/dist/template_code/three/utils/loadKTX2Texture.js +42 -0
- package/dist/template_code/three/utils/textureLoader.js +21 -0
- package/dist/utils/add-three.d.ts +7 -0
- package/dist/utils/add-three.js +288 -0
- package/dist/utils/app-creation.d.ts +4 -0
- package/dist/utils/app-creation.js +35 -0
- package/dist/utils/config-generator.d.ts +6 -0
- package/dist/utils/config-generator.js +508 -0
- package/dist/utils/css-variables.d.ts +4 -0
- package/dist/utils/css-variables.js +316 -0
- package/dist/utils/dependencies.d.ts +11 -0
- package/dist/utils/dependencies.js +68 -0
- package/dist/utils/package-manager.d.ts +4 -0
- package/dist/utils/package-manager.js +56 -0
- package/dist/utils/project-detection.d.ts +2 -0
- package/dist/utils/project-detection.js +60 -0
- package/dist/utils/shadcn-setup.d.ts +2 -0
- package/dist/utils/shadcn-setup.js +46 -0
- package/package.json +81 -0
- package/readme.md +119 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { addThree } from './utils/add-three.js';
|
|
4
|
+
import ProgressIndicator from './components/ProgressIndicator.js';
|
|
5
|
+
import { resolve } from 'path';
|
|
6
|
+
export default function AddThreeApp({ dir }) {
|
|
7
|
+
const [step, setStep] = useState('checking');
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
const [result, setResult] = useState(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
async function initialize() {
|
|
12
|
+
try {
|
|
13
|
+
const cwd = dir ? resolve(dir) : process.cwd();
|
|
14
|
+
setStep('copying');
|
|
15
|
+
const result = await addThree(cwd);
|
|
16
|
+
setResult(result);
|
|
17
|
+
setStep('completed');
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
21
|
+
setStep('error');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
initialize();
|
|
25
|
+
}, [dir]);
|
|
26
|
+
if (step === 'checking') {
|
|
27
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
28
|
+
React.createElement(Text, { color: "cyan", bold: true }, "\uD83D\uDD0D Three.js Setup"),
|
|
29
|
+
React.createElement(Text, null, " "),
|
|
30
|
+
React.createElement(ProgressIndicator, { message: "Checking Three.js installation...", status: "in-progress" })));
|
|
31
|
+
}
|
|
32
|
+
if (step === 'copying') {
|
|
33
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
34
|
+
React.createElement(Text, { color: "cyan", bold: true }, "\uD83D\uDCE6 Adding Three.js Template"),
|
|
35
|
+
React.createElement(Text, null, " "),
|
|
36
|
+
React.createElement(ProgressIndicator, { message: "Copying template files...", status: "in-progress" })));
|
|
37
|
+
}
|
|
38
|
+
if (step === 'error') {
|
|
39
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
40
|
+
React.createElement(Box, { borderStyle: "round", borderColor: "red", paddingX: 2, paddingY: 1, marginBottom: 1 },
|
|
41
|
+
React.createElement(Text, { color: "red", bold: true }, "\u2717 Error")),
|
|
42
|
+
React.createElement(Box, { paddingLeft: 2 },
|
|
43
|
+
React.createElement(Text, { color: "red" }, error))));
|
|
44
|
+
}
|
|
45
|
+
if (step === 'completed' && result) {
|
|
46
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
47
|
+
React.createElement(Box, { borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, marginBottom: 1 },
|
|
48
|
+
React.createElement(Text, { color: "green", bold: true }, "\u2728 Three.js template added successfully!")),
|
|
49
|
+
React.createElement(Box, { flexDirection: "column", paddingLeft: 1 },
|
|
50
|
+
React.createElement(Text, { color: "cyan", bold: true }, "\uD83D\uDCC1 Files Created"),
|
|
51
|
+
React.createElement(Text, null, " "),
|
|
52
|
+
React.createElement(Box, { flexDirection: "column", paddingLeft: 2 },
|
|
53
|
+
React.createElement(Text, null,
|
|
54
|
+
React.createElement(Text, { color: "yellow" }, "\u2022"),
|
|
55
|
+
" ",
|
|
56
|
+
React.createElement(Text, { color: "white" },
|
|
57
|
+
result.threePath,
|
|
58
|
+
"/"),
|
|
59
|
+
" ",
|
|
60
|
+
React.createElement(Text, { color: "gray" }, "(template files)")),
|
|
61
|
+
React.createElement(Text, null,
|
|
62
|
+
React.createElement(Text, { color: "yellow" }, "\u2022"),
|
|
63
|
+
" ",
|
|
64
|
+
React.createElement(Text, { color: "white" },
|
|
65
|
+
result.projectType === 'vue' ? 'src' : 'app',
|
|
66
|
+
"/composables/"),
|
|
67
|
+
React.createElement(Text, { color: "green" },
|
|
68
|
+
result.directoryName,
|
|
69
|
+
"/usethree.",
|
|
70
|
+
result.fileExtension)),
|
|
71
|
+
React.createElement(Text, null,
|
|
72
|
+
React.createElement(Text, { color: "yellow" }, "\u2022"),
|
|
73
|
+
" ",
|
|
74
|
+
React.createElement(Text, { color: "white" },
|
|
75
|
+
result.projectType === 'vue' ? 'src' : 'app',
|
|
76
|
+
"/composables/"),
|
|
77
|
+
React.createElement(Text, { color: "green" },
|
|
78
|
+
result.directoryName,
|
|
79
|
+
"/useThreeAdvanced.",
|
|
80
|
+
result.fileExtension))),
|
|
81
|
+
React.createElement(Text, null, " "),
|
|
82
|
+
React.createElement(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, paddingY: 1 },
|
|
83
|
+
React.createElement(Text, { color: "cyan", bold: true }, "\uD83D\uDE80 Next Steps"),
|
|
84
|
+
React.createElement(Text, null, " "),
|
|
85
|
+
React.createElement(Box, { flexDirection: "column", paddingLeft: 2 },
|
|
86
|
+
React.createElement(Text, null,
|
|
87
|
+
React.createElement(Text, { color: "yellow" }, "1."),
|
|
88
|
+
" ",
|
|
89
|
+
React.createElement(Text, { color: "white" }, "Import and use the composable in your "),
|
|
90
|
+
React.createElement(Text, { color: "green", bold: true }, result.projectType === 'vue' ? 'Vue' : 'Nuxt'),
|
|
91
|
+
React.createElement(Text, { color: "white" }, " component")),
|
|
92
|
+
React.createElement(Text, null,
|
|
93
|
+
React.createElement(Text, { color: "yellow" }, "2."),
|
|
94
|
+
" ",
|
|
95
|
+
React.createElement(Text, { color: "gray" }, "Example: "),
|
|
96
|
+
React.createElement(Text, { color: "cyan" }, "import"),
|
|
97
|
+
" ",
|
|
98
|
+
React.createElement(Text, { color: "yellow" },
|
|
99
|
+
'{',
|
|
100
|
+
" useThree ",
|
|
101
|
+
'}'),
|
|
102
|
+
" ",
|
|
103
|
+
React.createElement(Text, { color: "cyan" }, "from"),
|
|
104
|
+
" ",
|
|
105
|
+
React.createElement(Text, { color: "green" },
|
|
106
|
+
"'@/composables/",
|
|
107
|
+
result.directoryName,
|
|
108
|
+
"/usethree'")))))));
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type Props = {
|
|
3
|
+
nuxt?: boolean;
|
|
4
|
+
vue?: boolean;
|
|
5
|
+
threejs?: boolean;
|
|
6
|
+
cssVars?: boolean;
|
|
7
|
+
dir?: string;
|
|
8
|
+
nonInteractive?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export default function App({ nuxt, vue, threejs, cssVars, dir, nonInteractive, }: Props): React.JSX.Element | null;
|
|
11
|
+
export {};
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { detectProjectType } from './utils/project-detection.js';
|
|
4
|
+
import { createApp } from './utils/app-creation.js';
|
|
5
|
+
import { detectPackageManager, getInstallCommand, getDevInstallCommand, } from './utils/package-manager.js';
|
|
6
|
+
import { getDependencies } from './utils/dependencies.js';
|
|
7
|
+
import { generateConfigFiles } from './utils/config-generator.js';
|
|
8
|
+
import { generateCSSVariables, updateIndexHtml, createTypographyPage } from './utils/css-variables.js';
|
|
9
|
+
import { setupShadcnNuxt } from './utils/shadcn-setup.js';
|
|
10
|
+
import FeatureSelector from './components/FeatureSelector.js';
|
|
11
|
+
import ProgressIndicator from './components/ProgressIndicator.js';
|
|
12
|
+
import Summary from './components/Summary.js';
|
|
13
|
+
import WelcomeHeader from './components/WelcomeHeader.js';
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
import { join, resolve } from 'path';
|
|
16
|
+
export default function App({ nuxt = false, vue = false, threejs = false, cssVars = false, dir, nonInteractive = false, }) {
|
|
17
|
+
const [step, setStep] = useState('detecting');
|
|
18
|
+
const [projectType, setProjectType] = useState('none');
|
|
19
|
+
const [features, setFeatures] = useState(null);
|
|
20
|
+
const [error, setError] = useState(null);
|
|
21
|
+
const [projectPath, setProjectPath] = useState('');
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
async function initialize() {
|
|
24
|
+
try {
|
|
25
|
+
const cwd = dir ? resolve(dir) : process.cwd();
|
|
26
|
+
setProjectPath(cwd);
|
|
27
|
+
const detected = await detectProjectType(cwd);
|
|
28
|
+
setProjectType(detected);
|
|
29
|
+
setStep('selecting');
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
33
|
+
setStep('error');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
initialize();
|
|
37
|
+
}, [dir]);
|
|
38
|
+
const handleFeatureSelect = async (selectedFeatures) => {
|
|
39
|
+
setFeatures(selectedFeatures);
|
|
40
|
+
const finalProjectType = nuxt
|
|
41
|
+
? 'nuxt'
|
|
42
|
+
: vue
|
|
43
|
+
? 'vue'
|
|
44
|
+
: selectedFeatures.projectType === 'none'
|
|
45
|
+
? 'nuxt' // Default to nuxt if none selected
|
|
46
|
+
: selectedFeatures.projectType;
|
|
47
|
+
// finalProjectType is guaranteed to be 'nuxt' or 'vue' at this point
|
|
48
|
+
try {
|
|
49
|
+
const cwd = dir ? resolve(dir) : process.cwd();
|
|
50
|
+
let workingPath = cwd;
|
|
51
|
+
// Create app if needed
|
|
52
|
+
if (projectType === 'none' && selectedFeatures.projectName) {
|
|
53
|
+
setStep('creating');
|
|
54
|
+
const parentDir = dir ? resolve(dir) : process.cwd();
|
|
55
|
+
await createApp(finalProjectType, parentDir, selectedFeatures.projectName);
|
|
56
|
+
workingPath = join(parentDir, selectedFeatures.projectName);
|
|
57
|
+
setProjectPath(workingPath);
|
|
58
|
+
}
|
|
59
|
+
// Install dependencies
|
|
60
|
+
setStep('installing');
|
|
61
|
+
const pm = await detectPackageManager(workingPath);
|
|
62
|
+
const deps = getDependencies(finalProjectType, selectedFeatures.threejs, selectedFeatures.cssVars);
|
|
63
|
+
if (deps.production.length > 0) {
|
|
64
|
+
const installCmd = getInstallCommand(pm, deps.production);
|
|
65
|
+
execSync(installCmd, { cwd: workingPath, stdio: 'inherit' });
|
|
66
|
+
}
|
|
67
|
+
if (deps.dev.length > 0) {
|
|
68
|
+
const devInstallCmd = getDevInstallCommand(pm, deps.dev);
|
|
69
|
+
execSync(devInstallCmd, { cwd: workingPath, stdio: 'inherit' });
|
|
70
|
+
}
|
|
71
|
+
// Generate configuration files
|
|
72
|
+
setStep('configuring');
|
|
73
|
+
await generateConfigFiles(finalProjectType, workingPath, selectedFeatures.threejs, selectedFeatures.cssVars);
|
|
74
|
+
// Generate CSS variables (always enabled - will be overwritten after shadcn-setup)
|
|
75
|
+
if (finalProjectType === 'nuxt') {
|
|
76
|
+
// For Nuxt, first create basic tailwind.css
|
|
77
|
+
await generateCSSVariables(finalProjectType, workingPath, true);
|
|
78
|
+
// Then run shadcn setup
|
|
79
|
+
setStep('configuring');
|
|
80
|
+
await setupShadcnNuxt(workingPath, pm);
|
|
81
|
+
// Finally, replace with full CSS content
|
|
82
|
+
await generateCSSVariables(finalProjectType, workingPath, false);
|
|
83
|
+
// Create typography page after CSS variables are set up
|
|
84
|
+
if (selectedFeatures.cssVars) {
|
|
85
|
+
await createTypographyPage(workingPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
await generateCSSVariables(finalProjectType, workingPath);
|
|
90
|
+
// For Vue projects, update index.html with stylesheet link
|
|
91
|
+
if (finalProjectType === 'vue' && selectedFeatures.cssVars) {
|
|
92
|
+
await updateIndexHtml(workingPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
setStep('completed');
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
99
|
+
setStep('error');
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
if (step === 'detecting') {
|
|
103
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
104
|
+
React.createElement(WelcomeHeader, null),
|
|
105
|
+
React.createElement(Text, { color: "cyan", bold: true }, "\uD83D\uDD0D Analyzing project..."),
|
|
106
|
+
React.createElement(Text, null, " "),
|
|
107
|
+
React.createElement(ProgressIndicator, { message: "Detecting project type...", status: "in-progress" })));
|
|
108
|
+
}
|
|
109
|
+
if (step === 'selecting') {
|
|
110
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
111
|
+
React.createElement(WelcomeHeader, null),
|
|
112
|
+
React.createElement(FeatureSelector, { detectedType: projectType, onSelect: handleFeatureSelect, nonInteractive: nonInteractive, flags: { nuxt, vue, threejs, cssVars } })));
|
|
113
|
+
}
|
|
114
|
+
if (step === 'creating') {
|
|
115
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
116
|
+
React.createElement(Text, { color: "cyan", bold: true }, "\uD83D\uDCE6 Creating project..."),
|
|
117
|
+
React.createElement(Text, null, " "),
|
|
118
|
+
React.createElement(ProgressIndicator, { message: "Setting up project structure...", status: "in-progress" })));
|
|
119
|
+
}
|
|
120
|
+
if (step === 'installing') {
|
|
121
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
122
|
+
React.createElement(Text, { color: "yellow", bold: true }, "\uD83D\uDCE5 Installing dependencies..."),
|
|
123
|
+
React.createElement(Text, null, " "),
|
|
124
|
+
React.createElement(ProgressIndicator, { message: "This may take a few moments...", status: "in-progress" })));
|
|
125
|
+
}
|
|
126
|
+
if (step === 'configuring') {
|
|
127
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
128
|
+
React.createElement(Text, { color: "magenta", bold: true }, "\u2699\uFE0F Configuring project..."),
|
|
129
|
+
React.createElement(Text, null, " "),
|
|
130
|
+
React.createElement(ProgressIndicator, { message: "Generating configuration files...", status: "in-progress" })));
|
|
131
|
+
}
|
|
132
|
+
if (step === 'error') {
|
|
133
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
134
|
+
React.createElement(Box, { borderStyle: "round", borderColor: "red", paddingX: 2, paddingY: 1, marginBottom: 1 },
|
|
135
|
+
React.createElement(Text, { color: "red", bold: true }, "\u2717 Error")),
|
|
136
|
+
React.createElement(Box, { paddingLeft: 2 },
|
|
137
|
+
React.createElement(Text, { color: "red" }, error))));
|
|
138
|
+
}
|
|
139
|
+
if (step === 'completed' && features) {
|
|
140
|
+
return React.createElement(Summary, { features: features, projectPath: projectPath });
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render } from 'ink';
|
|
4
|
+
import meow from 'meow';
|
|
5
|
+
import App from './app.js';
|
|
6
|
+
import AddThreeApp from './add-three-app.js';
|
|
7
|
+
// import { render as renderTestingLibrary } from 'ink-testing-library';
|
|
8
|
+
const cli = meow(`
|
|
9
|
+
Usage
|
|
10
|
+
$ pni [options]
|
|
11
|
+
$ pni add three [options]
|
|
12
|
+
|
|
13
|
+
Options
|
|
14
|
+
--nuxt Force Nuxt project type
|
|
15
|
+
--vue Force Vue project type
|
|
16
|
+
--threejs Include Three.js setup
|
|
17
|
+
--css-vars Include CSS variables (shadcn-style) setup
|
|
18
|
+
--dir <path> Target directory (default: current directory)
|
|
19
|
+
--non-interactive Skip prompts, use flags only
|
|
20
|
+
|
|
21
|
+
Examples
|
|
22
|
+
$ pni
|
|
23
|
+
Interactive setup
|
|
24
|
+
|
|
25
|
+
$ pni --nuxt --threejs --css-vars
|
|
26
|
+
Create Nuxt app with Three.js and CSS variables
|
|
27
|
+
|
|
28
|
+
$ pni --vue --threejs
|
|
29
|
+
Create Vue app with Three.js
|
|
30
|
+
|
|
31
|
+
$ pni --css-vars
|
|
32
|
+
Add CSS variables to existing project
|
|
33
|
+
|
|
34
|
+
$ pni add three
|
|
35
|
+
Add Three.js template to existing project
|
|
36
|
+
`, {
|
|
37
|
+
importMeta: import.meta,
|
|
38
|
+
flags: {
|
|
39
|
+
nuxt: {
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
default: false,
|
|
42
|
+
},
|
|
43
|
+
vue: {
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
default: false,
|
|
46
|
+
},
|
|
47
|
+
threejs: {
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
default: false,
|
|
50
|
+
},
|
|
51
|
+
cssVars: {
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
default: false,
|
|
54
|
+
},
|
|
55
|
+
dir: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
},
|
|
58
|
+
nonInteractive: {
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
default: false,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
// Check for subcommands
|
|
65
|
+
const [command, subcommand] = cli.input;
|
|
66
|
+
if (command === 'add' && subcommand === 'three') {
|
|
67
|
+
render(React.createElement(AddThreeApp, { dir: cli.flags.dir }));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
render(React.createElement(App, { nuxt: cli.flags.nuxt, vue: cli.flags.vue, threejs: cli.flags.threejs, cssVars: cli.flags.cssVars, dir: cli.flags.dir, nonInteractive: cli.flags.nonInteractive }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ProjectType } from '../utils/project-detection.js';
|
|
3
|
+
export interface SelectedFeatures {
|
|
4
|
+
projectType: ProjectType;
|
|
5
|
+
threejs: boolean;
|
|
6
|
+
cssVars: boolean;
|
|
7
|
+
projectName?: string;
|
|
8
|
+
}
|
|
9
|
+
type Props = {
|
|
10
|
+
detectedType: ProjectType;
|
|
11
|
+
onSelect: (features: SelectedFeatures) => void;
|
|
12
|
+
nonInteractive?: boolean;
|
|
13
|
+
flags?: {
|
|
14
|
+
nuxt?: boolean;
|
|
15
|
+
vue?: boolean;
|
|
16
|
+
threejs?: boolean;
|
|
17
|
+
cssVars?: boolean;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export default function FeatureSelector({ detectedType, onSelect, nonInteractive, flags, }: Props): React.JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Text, Box, useInput } from 'ink';
|
|
3
|
+
export default function FeatureSelector({ detectedType, onSelect, nonInteractive = false, flags = {}, }) {
|
|
4
|
+
const [step, setStep] = useState(0);
|
|
5
|
+
const [projectType, setProjectType] = useState(flags.nuxt
|
|
6
|
+
? 'nuxt'
|
|
7
|
+
: flags.vue
|
|
8
|
+
? 'vue'
|
|
9
|
+
: detectedType !== 'none'
|
|
10
|
+
? detectedType
|
|
11
|
+
: 'nuxt');
|
|
12
|
+
const [threejs, setThreejs] = useState(flags.threejs ?? false);
|
|
13
|
+
const [projectName, setProjectName] = useState('');
|
|
14
|
+
const cssVars = true; // Always enabled - CSS will be overwritten after shadcn-setup
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (nonInteractive) {
|
|
17
|
+
// Auto-select based on flags or detected type
|
|
18
|
+
onSelect({
|
|
19
|
+
projectType,
|
|
20
|
+
threejs,
|
|
21
|
+
cssVars,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}, [nonInteractive, projectType, threejs, cssVars, onSelect]);
|
|
25
|
+
useInput((input, key) => {
|
|
26
|
+
if (nonInteractive) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (key.return) {
|
|
30
|
+
if (step === 0 && detectedType === 'none') {
|
|
31
|
+
// Project type selected
|
|
32
|
+
setStep(1);
|
|
33
|
+
}
|
|
34
|
+
else if (step === 1 && detectedType === 'none') {
|
|
35
|
+
// Project name entered
|
|
36
|
+
setStep(2);
|
|
37
|
+
}
|
|
38
|
+
else if (step === (detectedType === 'none' ? 2 : 0)) {
|
|
39
|
+
// Three.js question answered - CSS vars are always enabled
|
|
40
|
+
onSelect({
|
|
41
|
+
projectType,
|
|
42
|
+
threejs,
|
|
43
|
+
cssVars: true, // Always enable CSS vars
|
|
44
|
+
projectName: detectedType === 'none' ? projectName : undefined,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (key.upArrow || key.downArrow) {
|
|
49
|
+
if (step === 0 && detectedType === 'none') {
|
|
50
|
+
setProjectType(prev => (prev === 'nuxt' ? 'vue' : 'nuxt'));
|
|
51
|
+
}
|
|
52
|
+
else if (step === (detectedType === 'none' ? 2 : 0)) {
|
|
53
|
+
// Three.js question
|
|
54
|
+
setThreejs(prev => !prev);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (input === 'y' || input === 'Y') {
|
|
58
|
+
if (step === (detectedType === 'none' ? 2 : 0)) {
|
|
59
|
+
setThreejs(true);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (input === 'n' || input === 'N') {
|
|
63
|
+
if (step === (detectedType === 'none' ? 2 : 0)) {
|
|
64
|
+
setThreejs(false);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Handle project name input
|
|
68
|
+
if (step === 1 &&
|
|
69
|
+
detectedType === 'none' &&
|
|
70
|
+
!key.return &&
|
|
71
|
+
!key.upArrow &&
|
|
72
|
+
!key.downArrow) {
|
|
73
|
+
if (key.backspace || key.delete) {
|
|
74
|
+
setProjectName(prev => prev.slice(0, -1));
|
|
75
|
+
}
|
|
76
|
+
else if (input.length === 1) {
|
|
77
|
+
setProjectName(prev => prev + input);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
if (nonInteractive) {
|
|
82
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
83
|
+
React.createElement(Text, { color: "cyan", bold: true }, "\u26A1 Quick Setup (Non-Interactive)"),
|
|
84
|
+
React.createElement(Text, null, " "),
|
|
85
|
+
React.createElement(Box, { flexDirection: "column", paddingLeft: 2 },
|
|
86
|
+
React.createElement(Text, null,
|
|
87
|
+
React.createElement(Text, { color: "magenta" }, "Project Type:"),
|
|
88
|
+
" ",
|
|
89
|
+
React.createElement(Text, { color: "green", bold: true }, projectType)),
|
|
90
|
+
React.createElement(Text, null,
|
|
91
|
+
React.createElement(Text, { color: "magenta" }, "Three.js:"),
|
|
92
|
+
" ",
|
|
93
|
+
React.createElement(Text, { color: threejs ? 'green' : 'gray', bold: true }, threejs ? 'Yes' : 'No')),
|
|
94
|
+
React.createElement(Text, null,
|
|
95
|
+
React.createElement(Text, { color: "magenta" }, "CSS Vars:"),
|
|
96
|
+
" ",
|
|
97
|
+
React.createElement(Text, { color: "green", bold: true }, "Yes (auto)")))));
|
|
98
|
+
}
|
|
99
|
+
if (detectedType === 'none') {
|
|
100
|
+
if (step === 0) {
|
|
101
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
102
|
+
React.createElement(Text, { color: "yellow", bold: true }, "\uD83D\uDCE6 No project detected"),
|
|
103
|
+
React.createElement(Text, { color: "gray" }, "Select project type:"),
|
|
104
|
+
React.createElement(Text, null, " "),
|
|
105
|
+
React.createElement(Box, { flexDirection: "column", paddingLeft: 2 },
|
|
106
|
+
React.createElement(Text, null,
|
|
107
|
+
React.createElement(Text, { color: projectType === 'nuxt' ? 'cyan' : 'gray' }, projectType === 'nuxt' ? '→' : ' '),
|
|
108
|
+
React.createElement(Text, { color: projectType === 'nuxt' ? 'cyan' : 'white', bold: true },
|
|
109
|
+
"[",
|
|
110
|
+
React.createElement(Text, { color: projectType === 'nuxt' ? 'green' : 'gray' }, projectType === 'nuxt' ? '●' : '○'),
|
|
111
|
+
"] Nuxt")),
|
|
112
|
+
React.createElement(Text, null,
|
|
113
|
+
React.createElement(Text, { color: projectType === 'vue' ? 'cyan' : 'gray' }, projectType === 'vue' ? '→' : ' '),
|
|
114
|
+
React.createElement(Text, { color: projectType === 'vue' ? 'cyan' : 'white', bold: true },
|
|
115
|
+
"[",
|
|
116
|
+
React.createElement(Text, { color: projectType === 'vue' ? 'green' : 'gray' }, projectType === 'vue' ? '●' : '○'),
|
|
117
|
+
"] Vue"))),
|
|
118
|
+
React.createElement(Text, null, " "),
|
|
119
|
+
React.createElement(Text, { color: "gray" },
|
|
120
|
+
"Press ",
|
|
121
|
+
React.createElement(Text, { color: "cyan" }, "\u2191/\u2193"),
|
|
122
|
+
" to select, ",
|
|
123
|
+
React.createElement(Text, { color: "cyan" }, "Enter"),
|
|
124
|
+
" to continue")));
|
|
125
|
+
}
|
|
126
|
+
if (step === 1) {
|
|
127
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
128
|
+
React.createElement(Text, { color: "cyan", bold: true }, "\uD83D\uDCDD Project Setup"),
|
|
129
|
+
React.createElement(Text, null, " "),
|
|
130
|
+
React.createElement(Text, null,
|
|
131
|
+
React.createElement(Text, { color: "magenta" }, "Enter project name:"),
|
|
132
|
+
" ",
|
|
133
|
+
React.createElement(Text, { color: "green", bold: true }, projectName),
|
|
134
|
+
React.createElement(Text, { color: "cyan", bold: true }, "_")),
|
|
135
|
+
React.createElement(Text, null, " "),
|
|
136
|
+
React.createElement(Text, { color: "gray" },
|
|
137
|
+
"Type the project name and press ",
|
|
138
|
+
React.createElement(Text, { color: "cyan" }, "Enter"),
|
|
139
|
+
" when done")));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (step === (detectedType === 'none' ? 2 : 0)) {
|
|
143
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
144
|
+
React.createElement(Text, { color: "cyan", bold: true }, "\uD83C\uDFA8 Additional Features"),
|
|
145
|
+
React.createElement(Text, null, " "),
|
|
146
|
+
React.createElement(Text, { color: "yellow", bold: true }, "Include Three.js setup?"),
|
|
147
|
+
React.createElement(Text, null, " "),
|
|
148
|
+
React.createElement(Box, { flexDirection: "column", paddingLeft: 2 },
|
|
149
|
+
React.createElement(Text, null,
|
|
150
|
+
React.createElement(Text, { color: threejs ? 'cyan' : 'gray' }, threejs ? '→' : ' '),
|
|
151
|
+
React.createElement(Text, { color: threejs ? 'cyan' : 'white', bold: true },
|
|
152
|
+
"[",
|
|
153
|
+
React.createElement(Text, { color: threejs ? 'green' : 'gray' }, threejs ? '✓' : ' '),
|
|
154
|
+
"] ",
|
|
155
|
+
React.createElement(Text, { color: threejs ? 'green' : 'white' }, "Yes"))),
|
|
156
|
+
React.createElement(Text, null,
|
|
157
|
+
React.createElement(Text, { color: !threejs ? 'cyan' : 'gray' }, !threejs ? '→' : ' '),
|
|
158
|
+
React.createElement(Text, { color: !threejs ? 'cyan' : 'white', bold: true },
|
|
159
|
+
"[",
|
|
160
|
+
React.createElement(Text, { color: !threejs ? 'red' : 'gray' }, !threejs ? '✗' : ' '),
|
|
161
|
+
"] ",
|
|
162
|
+
React.createElement(Text, { color: !threejs ? 'red' : 'white' }, "No")))),
|
|
163
|
+
React.createElement(Text, null, " "),
|
|
164
|
+
React.createElement(Text, { color: "gray" },
|
|
165
|
+
"Use ",
|
|
166
|
+
React.createElement(Text, { color: "cyan" }, "\u2191/\u2193"),
|
|
167
|
+
" to toggle, ",
|
|
168
|
+
React.createElement(Text, { color: "cyan" }, "y/n"),
|
|
169
|
+
" to select, or ",
|
|
170
|
+
React.createElement(Text, { color: "cyan" }, "Enter"),
|
|
171
|
+
" to continue")));
|
|
172
|
+
}
|
|
173
|
+
return (React.createElement(Box, null,
|
|
174
|
+
React.createElement(Text, { color: "cyan" }, "Processing...")));
|
|
175
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Text, Box } from 'ink';
|
|
3
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
4
|
+
export default function ProgressIndicator({ message, status = 'in-progress', }) {
|
|
5
|
+
const [frame, setFrame] = useState(0);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (status !== 'in-progress') {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const timer = setInterval(() => {
|
|
11
|
+
setFrame(prev => (prev + 1) % SPINNER_FRAMES.length);
|
|
12
|
+
}, 100);
|
|
13
|
+
return () => {
|
|
14
|
+
clearInterval(timer);
|
|
15
|
+
};
|
|
16
|
+
}, [status]);
|
|
17
|
+
const getStatusIcon = () => {
|
|
18
|
+
switch (status) {
|
|
19
|
+
case 'completed':
|
|
20
|
+
return React.createElement(Text, { color: "green", bold: true }, "\u2713");
|
|
21
|
+
case 'error':
|
|
22
|
+
return React.createElement(Text, { color: "red", bold: true }, "\u2717");
|
|
23
|
+
case 'in-progress':
|
|
24
|
+
return React.createElement(Text, { color: "cyan", bold: true }, SPINNER_FRAMES[frame]);
|
|
25
|
+
case 'pending':
|
|
26
|
+
default:
|
|
27
|
+
return React.createElement(Text, { color: "gray" }, "\u25CB");
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const getMessageColor = () => {
|
|
31
|
+
switch (status) {
|
|
32
|
+
case 'completed':
|
|
33
|
+
return 'green';
|
|
34
|
+
case 'error':
|
|
35
|
+
return 'red';
|
|
36
|
+
case 'in-progress':
|
|
37
|
+
return 'cyan';
|
|
38
|
+
case 'pending':
|
|
39
|
+
default:
|
|
40
|
+
return 'gray';
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return (React.createElement(Box, null,
|
|
44
|
+
React.createElement(Box, { marginRight: 1 }, getStatusIcon()),
|
|
45
|
+
React.createElement(Text, { color: getMessageColor() }, message)));
|
|
46
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SelectedFeatures } from './FeatureSelector.js';
|
|
3
|
+
type Props = {
|
|
4
|
+
features: SelectedFeatures;
|
|
5
|
+
projectPath: string;
|
|
6
|
+
};
|
|
7
|
+
export default function Summary({ features, projectPath }: Props): React.JSX.Element;
|
|
8
|
+
export {};
|