dappbooster 3.0.2 → 3.1.4
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/app.js +3 -3
- package/dist/cli.js +95 -4
- package/dist/components/steps/CloneRepo/CloneRepo.d.ts +1 -7
- package/dist/components/steps/CloneRepo/CloneRepo.js +26 -9
- package/dist/components/steps/FileCleanup.d.ts +0 -6
- package/dist/components/steps/FileCleanup.js +24 -36
- package/dist/components/steps/Install/Install.js +31 -8
- package/dist/components/steps/OptionalPackages.d.ts +0 -7
- package/dist/components/steps/OptionalPackages.js +5 -30
- package/dist/components/steps/PostInstall.d.ts +0 -5
- package/dist/components/steps/PostInstall.js +3 -7
- package/dist/constants/config.d.ts +9 -2
- package/dist/constants/config.js +48 -16
- package/dist/info.d.ts +1 -0
- package/dist/info.js +18 -0
- package/dist/nonInteractive.d.ts +5 -0
- package/dist/nonInteractive.js +85 -0
- package/dist/operations/cleanupFiles.d.ts +3 -0
- package/dist/operations/cleanupFiles.js +77 -0
- package/dist/operations/cloneRepo.d.ts +1 -0
- package/dist/operations/cloneRepo.js +21 -0
- package/dist/operations/createEnvFile.d.ts +1 -0
- package/dist/operations/createEnvFile.js +5 -0
- package/dist/operations/exec.d.ts +6 -0
- package/dist/operations/exec.js +32 -0
- package/dist/operations/index.d.ts +4 -0
- package/dist/operations/index.js +4 -0
- package/dist/operations/installPackages.d.ts +3 -0
- package/dist/operations/installPackages.js +19 -0
- package/dist/utils/utils.d.ts +13 -28
- package/dist/utils/utils.js +25 -32
- package/package.json +22 -9
- package/readme.md +100 -0
- package/dist/components/steps/CloneRepo/Commands.d.ts +0 -12
- package/dist/components/steps/CloneRepo/Commands.js +0 -16
- package/dist/components/steps/Install/CustomInstallation.d.ts +0 -16
- package/dist/components/steps/Install/CustomInstallation.js +0 -26
- package/dist/components/steps/Install/FullInstallation.d.ts +0 -7
- package/dist/components/steps/Install/FullInstallation.js +0 -8
- package/dist/components/steps/Install/InstallAllPackages.d.ts +0 -7
- package/dist/components/steps/Install/InstallAllPackages.js +0 -7
package/dist/app.js
CHANGED
|
@@ -18,13 +18,12 @@ const App = () => {
|
|
|
18
18
|
const finishStep = useCallback(() => setCurrentStep((prevStep) => prevStep + 1), []);
|
|
19
19
|
const onSelectSetupType = useCallback((item) => setSetupType(item), []);
|
|
20
20
|
const onSelectSelectedFeatures = useCallback((selectedItems) => setSelectedFeatures([...selectedItems]), []);
|
|
21
|
+
const skipFeatures = setupType?.value === 'full';
|
|
21
22
|
const steps = useMemo(() => [
|
|
22
23
|
_jsx(ProjectName, { onCompletion: finishStep, onSubmit: setProjectName, projectName: projectName }, 1),
|
|
23
24
|
_jsx(CloneRepo, { onCompletion: finishStep, projectName: projectName }, 2),
|
|
24
25
|
_jsx(InstallationMode, { onCompletion: finishStep, onSelect: onSelectSetupType }, 3),
|
|
25
|
-
|
|
26
|
-
// to allow skipping when testing, etc.
|
|
27
|
-
_jsx(OptionalPackages, { onCompletion: finishStep, onSubmit: onSelectSelectedFeatures, skip: setupType?.value === 'full' }, 4),
|
|
26
|
+
_jsx(OptionalPackages, { onCompletion: finishStep, onSubmit: onSelectSelectedFeatures, skip: skipFeatures }, 4),
|
|
28
27
|
_jsx(Install, { installationConfig: {
|
|
29
28
|
installationType: setupType?.value,
|
|
30
29
|
selectedFeatures: selectedFeatures,
|
|
@@ -44,6 +43,7 @@ const App = () => {
|
|
|
44
43
|
selectedFeatures,
|
|
45
44
|
onSelectSetupType,
|
|
46
45
|
projectName,
|
|
46
|
+
skipFeatures,
|
|
47
47
|
]);
|
|
48
48
|
return (_jsxs(Box, { flexDirection: 'column', rowGap: 1, width: 80, children: [_jsx(MainTitle, {}), steps.map((item, index) => canShowStep(currentStep, index + 1) && item)] }));
|
|
49
49
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,97 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import meow from 'meow';
|
|
5
|
+
import { getInfoOutput } from './info.js';
|
|
6
|
+
import { runNonInteractive } from './nonInteractive.js';
|
|
7
|
+
const cli = meow(`
|
|
8
|
+
Usage
|
|
9
|
+
$ dappbooster [options]
|
|
10
|
+
|
|
11
|
+
Options
|
|
12
|
+
--name <string> Project name (alphanumeric, underscores)
|
|
13
|
+
--mode <full|custom> Installation mode
|
|
14
|
+
--features <list> Comma-separated features (with --mode=custom):
|
|
15
|
+
demo Component demos and example pages
|
|
16
|
+
subgraph TheGraph subgraph integration (requires API key)
|
|
17
|
+
typedoc TypeDoc API documentation generation
|
|
18
|
+
vocs Vocs documentation site
|
|
19
|
+
husky Git hooks with Husky, lint-staged, commitlint
|
|
20
|
+
--non-interactive, --ni Run without prompts (auto-enabled when not a TTY)
|
|
21
|
+
--info Output feature metadata as JSON
|
|
22
|
+
--help Show this help
|
|
23
|
+
--version Show version
|
|
24
|
+
|
|
25
|
+
Non-interactive mode
|
|
26
|
+
Requires --name and --mode. Outputs JSON to stdout.
|
|
27
|
+
Activates automatically when stdout is not a TTY.
|
|
28
|
+
Use --ni to force non-interactive mode in a TTY environment.
|
|
29
|
+
|
|
30
|
+
AI agents: non-interactive mode activates automatically. Run --info
|
|
31
|
+
to discover available features, then pass --name and --mode flags.
|
|
32
|
+
Output is JSON for easy parsing.
|
|
33
|
+
|
|
34
|
+
Examples
|
|
35
|
+
Interactive:
|
|
36
|
+
$ dappbooster
|
|
37
|
+
|
|
38
|
+
Full install (non-interactive):
|
|
39
|
+
$ dappbooster --ni --name my_dapp --mode full
|
|
40
|
+
|
|
41
|
+
Custom install with specific features:
|
|
42
|
+
$ dappbooster --ni --name my_dapp --mode custom --features demo,subgraph
|
|
43
|
+
|
|
44
|
+
Get feature metadata:
|
|
45
|
+
$ dappbooster --info
|
|
46
|
+
`, {
|
|
47
|
+
importMeta: import.meta,
|
|
48
|
+
flags: {
|
|
49
|
+
name: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
},
|
|
52
|
+
mode: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
},
|
|
55
|
+
features: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
},
|
|
58
|
+
nonInteractive: {
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
default: false,
|
|
61
|
+
},
|
|
62
|
+
ni: {
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
info: {
|
|
67
|
+
type: 'boolean',
|
|
68
|
+
default: false,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
if (cli.flags.info) {
|
|
73
|
+
console.log(getInfoOutput());
|
|
74
|
+
}
|
|
75
|
+
else if (cli.flags.nonInteractive || cli.flags.ni || !process.stdout.isTTY) {
|
|
76
|
+
runNonInteractive({
|
|
77
|
+
name: cli.flags.name,
|
|
78
|
+
mode: cli.flags.mode,
|
|
79
|
+
features: cli.flags.features,
|
|
80
|
+
}).catch((error) => {
|
|
81
|
+
if (process.exitCode === 1) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
console.log(JSON.stringify({ success: false, error: message }, null, 2));
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const run = async () => {
|
|
91
|
+
console.clear();
|
|
92
|
+
const { render } = await import('ink');
|
|
93
|
+
const { default: App } = await import('./app.js');
|
|
94
|
+
render(_jsx(App, {}));
|
|
95
|
+
};
|
|
96
|
+
run().catch(console.error);
|
|
97
|
+
}
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type FC } from 'react';
|
|
2
2
|
interface Props {
|
|
3
3
|
projectName: string;
|
|
4
4
|
onCompletion: () => void;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
7
|
-
* Step for cloning the repository.
|
|
8
|
-
* @param projectName
|
|
9
|
-
* @param onCompletion
|
|
10
|
-
* @constructor
|
|
11
|
-
*/
|
|
12
6
|
declare const CloneRepo: FC<Props>;
|
|
13
7
|
export default CloneRepo;
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { cloneRepo } from '../../../operations/index.js';
|
|
5
|
+
import { deriveStepDisplay } from '../../../utils/utils.js';
|
|
2
6
|
import Divider from '../../Divider.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const CloneRepo = ({ projectName, onCompletion }) => {
|
|
8
|
+
const [steps, setSteps] = useState([]);
|
|
9
|
+
const [status, setStatus] = useState('running');
|
|
10
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
11
|
+
const handleProgress = useCallback((step) => {
|
|
12
|
+
setSteps((prev) => [...prev, step]);
|
|
13
|
+
}, []);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
cloneRepo(projectName, handleProgress)
|
|
16
|
+
.then(() => {
|
|
17
|
+
setStatus('done');
|
|
18
|
+
onCompletion();
|
|
19
|
+
})
|
|
20
|
+
.catch((error) => {
|
|
21
|
+
setStatus('error');
|
|
22
|
+
setErrorMessage(error instanceof Error ? error.message : String(error));
|
|
23
|
+
});
|
|
24
|
+
}, [projectName, onCompletion, handleProgress]);
|
|
25
|
+
const { completedSteps, currentStep, failedStep } = deriveStepDisplay(steps, status);
|
|
26
|
+
return (_jsxs(_Fragment, { children: [_jsx(Divider, { title: 'Git tasks' }), completedSteps.map((step) => (_jsxs(Text, { children: [_jsx(Text, { color: 'green', children: '\u2714' }), " ", step] }, step))), currentStep && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: '\u25CB' }), " ", currentStep, " ", _jsx(Text, { dimColor: true, children: "Working..." })] })), failedStep && (_jsxs(Text, { children: [_jsx(Text, { color: 'red', children: '\u2717' }), " ", failedStep, " ", _jsx(Text, { color: 'red', children: "Error" })] })), status === 'error' && _jsxs(Text, { color: 'red', children: ["Failed to clone: ", errorMessage] })] }));
|
|
27
|
+
};
|
|
11
28
|
export default CloneRepo;
|
|
@@ -8,11 +8,5 @@ interface Props {
|
|
|
8
8
|
selectedFeatures?: Array<MultiSelectItem>;
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Performs file cleanup after the installation process
|
|
13
|
-
* @param onCompletion
|
|
14
|
-
* @param installation
|
|
15
|
-
* @param projectName
|
|
16
|
-
*/
|
|
17
11
|
declare const FileCleanup: FC<Props>;
|
|
18
12
|
export default FileCleanup;
|
|
@@ -1,43 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { useMemo } from 'react';
|
|
7
|
-
import { featureSelected, getProjectFolder } from '../../utils/utils.js';
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { cleanupFiles } from '../../operations/index.js';
|
|
5
|
+
import { deriveStepDisplay, getProjectFolder } from '../../utils/utils.js';
|
|
8
6
|
import Divider from '../Divider.js';
|
|
9
|
-
const packageJSONCleanup = (projectFolder, selectedFeatures) => {
|
|
10
|
-
const packageJSONPath = join(projectFolder, 'package.json');
|
|
11
|
-
const packageJSON = JSON.parse(readFileSync(packageJSONPath, 'utf8'));
|
|
12
|
-
if (!featureSelected('subgraph', selectedFeatures)) {
|
|
13
|
-
packageJSON.scripts['subgraph-codegen'] = undefined;
|
|
14
|
-
}
|
|
15
|
-
if (!featureSelected('typedoc', selectedFeatures)) {
|
|
16
|
-
packageJSON.scripts['typedoc:build'] = undefined;
|
|
17
|
-
}
|
|
18
|
-
if (!featureSelected('vocs', selectedFeatures)) {
|
|
19
|
-
packageJSON.scripts['docs:build'] = undefined;
|
|
20
|
-
packageJSON.scripts['docs:dev'] = undefined;
|
|
21
|
-
packageJSON.scripts['docs:preview'] = undefined;
|
|
22
|
-
}
|
|
23
|
-
if (!featureSelected('husky', selectedFeatures)) {
|
|
24
|
-
packageJSON.scripts.prepare = undefined;
|
|
25
|
-
}
|
|
26
|
-
writeFileSync(packageJSONPath, `${JSON.stringify(packageJSON, null, 2)}\n`);
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Performs file cleanup after the installation process
|
|
30
|
-
* @param onCompletion
|
|
31
|
-
* @param installation
|
|
32
|
-
* @param projectName
|
|
33
|
-
*/
|
|
34
7
|
const FileCleanup = ({ onCompletion, installationConfig, projectName }) => {
|
|
35
8
|
const { installationType, selectedFeatures } = installationConfig;
|
|
36
9
|
const projectFolder = useMemo(() => getProjectFolder(projectName), [projectName]);
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
10
|
+
const [steps, setSteps] = useState([]);
|
|
11
|
+
const [status, setStatus] = useState('running');
|
|
12
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
13
|
+
const handleProgress = useCallback((step) => {
|
|
14
|
+
setSteps((prev) => [...prev, step]);
|
|
15
|
+
}, []);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const features = selectedFeatures?.map((f) => f.value) ?? [];
|
|
18
|
+
cleanupFiles(projectFolder, installationType ?? 'full', features, handleProgress)
|
|
19
|
+
.then(() => {
|
|
20
|
+
setStatus('done');
|
|
21
|
+
onCompletion();
|
|
22
|
+
})
|
|
23
|
+
.catch((error) => {
|
|
24
|
+
setStatus('error');
|
|
25
|
+
setErrorMessage(error instanceof Error ? error.message : String(error));
|
|
26
|
+
});
|
|
27
|
+
}, [projectFolder, installationType, selectedFeatures, onCompletion, handleProgress]);
|
|
28
|
+
const { completedSteps, currentStep, failedStep } = deriveStepDisplay(steps, status);
|
|
29
|
+
return (_jsxs(_Fragment, { children: [_jsx(Divider, { title: 'File cleanup' }), completedSteps.map((step) => (_jsxs(Text, { children: [_jsx(Text, { color: 'green', children: '\u2714' }), " ", step] }, step))), currentStep && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: '\u25CB' }), " ", currentStep, " ", _jsx(Text, { dimColor: true, children: "Working..." })] })), failedStep && (_jsxs(Text, { children: [_jsx(Text, { color: 'red', children: '\u2717' }), " ", failedStep, " ", _jsx(Text, { color: 'red', children: "Error" })] })), status === 'error' && _jsxs(Text, { color: 'red', children: ["Cleanup failed: ", errorMessage] })] }));
|
|
42
30
|
};
|
|
43
31
|
export default FileCleanup;
|
|
@@ -1,17 +1,40 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { createEnvFile } from '../../../operations/createEnvFile.js';
|
|
5
|
+
import { installPackages } from '../../../operations/installPackages.js';
|
|
6
|
+
import { deriveStepDisplay, getProjectFolder } from '../../../utils/utils.js';
|
|
6
7
|
import Divider from '../../Divider.js';
|
|
7
|
-
import CustomInstallation from './CustomInstallation.js';
|
|
8
|
-
import FullInstallation from './FullInstallation.js';
|
|
9
8
|
const Install = ({ projectName, onCompletion, installationConfig }) => {
|
|
10
9
|
const { installationType, selectedFeatures } = installationConfig;
|
|
11
10
|
const projectFolder = useMemo(() => getProjectFolder(projectName), [projectName]);
|
|
11
|
+
const [steps, setSteps] = useState([]);
|
|
12
|
+
const [status, setStatus] = useState('running');
|
|
13
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
12
14
|
const title = installationType
|
|
13
15
|
? installationType[0]?.toUpperCase() + installationType.slice(1)
|
|
14
|
-
: '';
|
|
15
|
-
|
|
16
|
+
: 'Full';
|
|
17
|
+
const handleProgress = useCallback((step) => {
|
|
18
|
+
setSteps((prev) => [...prev, step]);
|
|
19
|
+
}, []);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const features = selectedFeatures?.map((f) => f.value) ?? [];
|
|
22
|
+
const run = async () => {
|
|
23
|
+
handleProgress('Creating .env.local file');
|
|
24
|
+
await createEnvFile(projectFolder);
|
|
25
|
+
await installPackages(projectFolder, installationType ?? 'full', features, handleProgress);
|
|
26
|
+
};
|
|
27
|
+
run()
|
|
28
|
+
.then(() => {
|
|
29
|
+
setStatus('done');
|
|
30
|
+
onCompletion();
|
|
31
|
+
})
|
|
32
|
+
.catch((error) => {
|
|
33
|
+
setStatus('error');
|
|
34
|
+
setErrorMessage(error instanceof Error ? error.message : String(error));
|
|
35
|
+
});
|
|
36
|
+
}, [projectFolder, installationType, selectedFeatures, onCompletion, handleProgress]);
|
|
37
|
+
const { completedSteps, currentStep, failedStep } = deriveStepDisplay(steps, status);
|
|
38
|
+
return (_jsxs(_Fragment, { children: [_jsx(Divider, { title: `${title} installation` }), completedSteps.map((step) => (_jsxs(Text, { children: [_jsx(Text, { color: 'green', children: '\u2714' }), " ", step] }, step))), currentStep && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: '\u25CB' }), " ", currentStep, " ", _jsx(Text, { dimColor: true, children: "Working..." })] })), failedStep && (_jsxs(Text, { children: [_jsx(Text, { color: 'red', children: '\u2717' }), " ", failedStep, " ", _jsx(Text, { color: 'red', children: "Error" })] })), status === 'error' && _jsxs(Text, { color: 'red', children: ["Installation failed: ", errorMessage] })] }));
|
|
16
39
|
};
|
|
17
40
|
export default Install;
|
|
@@ -5,12 +5,5 @@ interface Props {
|
|
|
5
5
|
onSubmit: (selectedItems: Array<MultiSelectItem>) => void;
|
|
6
6
|
skip?: boolean;
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
9
|
-
* Step for selecting optional packages. Skipped if installation type is 'full'.
|
|
10
|
-
* @param onCompletion
|
|
11
|
-
* @param onSubmit
|
|
12
|
-
* @param installation
|
|
13
|
-
* @param skip
|
|
14
|
-
*/
|
|
15
8
|
declare const OptionalPackages: FC<Props>;
|
|
16
9
|
export default OptionalPackages;
|
|
@@ -1,41 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Text } from 'ink';
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
+
import { featureDefinitions, featureNames } from '../../constants/config.js';
|
|
4
5
|
import MultiSelect from '../Multiselect/index.js';
|
|
5
|
-
const customPackages =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
label: 'Subgraph support',
|
|
12
|
-
value: 'subgraph',
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
label: 'Typedoc documentation support',
|
|
16
|
-
value: 'typedoc',
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
label: 'Vocs documentation support',
|
|
20
|
-
value: 'vocs',
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
label: 'Husky Git hooks support',
|
|
24
|
-
value: 'husky',
|
|
25
|
-
},
|
|
26
|
-
];
|
|
27
|
-
/**
|
|
28
|
-
* Step for selecting optional packages. Skipped if installation type is 'full'.
|
|
29
|
-
* @param onCompletion
|
|
30
|
-
* @param onSubmit
|
|
31
|
-
* @param installation
|
|
32
|
-
* @param skip
|
|
33
|
-
*/
|
|
6
|
+
const customPackages = featureNames.map((name) => ({
|
|
7
|
+
label: featureDefinitions[name].label,
|
|
8
|
+
value: name,
|
|
9
|
+
}));
|
|
34
10
|
const OptionalPackages = ({ onCompletion, onSubmit, skip = false }) => {
|
|
35
11
|
const [isFocused, setIsFocused] = useState(true);
|
|
36
12
|
// biome-ignore lint/correctness/useExhaustiveDependencies: Run this only once, no matter what
|
|
37
13
|
useEffect(() => {
|
|
38
|
-
// full installation, do nothing
|
|
39
14
|
if (skip) {
|
|
40
15
|
onCompletion();
|
|
41
16
|
}
|
|
@@ -2,18 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import figures from 'figures';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import Link from 'ink-link';
|
|
5
|
-
import {
|
|
5
|
+
import { isFeatureSelected } from '../../utils/utils.js';
|
|
6
6
|
import Divider from '../Divider.js';
|
|
7
7
|
const SubgraphWarningMessage = () => (_jsxs(Box, { flexDirection: 'column', rowGap: 1, children: [_jsx(Box, { alignItems: 'center', borderColor: 'yellow', borderStyle: 'bold', flexDirection: 'column', justifyContent: 'center', padding: 1, children: _jsxs(Text, { color: 'yellow', children: [figures.warning, figures.warning, " ", _jsx(Text, { bold: true, children: "WARNING:" }), " You ", _jsx(Text, { bold: true, children: "MUST" }), " finish the subgraph's configuration manually ", figures.warning, figures.warning] }) }), _jsx(Text, { color: 'whiteBright', children: "Follow these steps:" }), _jsxs(Box, { flexDirection: 'column', children: [_jsxs(Text, { children: ["1- Provide your own API key for ", _jsx(Text, { color: 'gray', children: "PUBLIC_SUBGRAPHS_API_KEY" }), " in", ' ', _jsx(Text, { color: 'gray', children: ".env.local" }), " You can get one", ' ', _jsx(Link, { url: "https://thegraph.com/studio/apikeys", children: "here" })] }), _jsxs(Text, { children: ["2- After the API key is correctly configured, run", ' ', _jsx(Text, { color: 'gray', children: "pnpm subgraph-codegen" }), " in your console from the project's folder"] })] }), _jsxs(Text, { children: ["More configuration info in", ' ', _jsx(Link, { url: 'https://docs.dappbooster.dev/introduction/getting-started', children: "the docs" }), "."] }), _jsxs(Text, { color: 'yellow', bold: true, children: [figures.info, " Only after you have followed the previous steps you may proceed."] })] }));
|
|
8
8
|
const PostInstallMessage = ({ projectName }) => (_jsxs(Box, { flexDirection: 'column', rowGap: 1, paddingBottom: 2, children: [_jsx(Text, { color: 'whiteBright', children: "To start development on your project:" }), _jsxs(Box, { flexDirection: 'column', children: [_jsxs(Text, { children: ["1- Move into the project's folder with ", _jsxs(Text, { color: 'gray', children: ["cd ", projectName] })] }), _jsxs(Text, { children: ["2- Start the development server with ", _jsx(Text, { color: 'gray', children: "pnpm dev" })] })] }), _jsx(Text, { color: 'whiteBright', children: "More info:" }), _jsxs(Box, { flexDirection: 'column', children: [_jsxs(Text, { children: ["- Check out ", _jsx(Text, { color: 'gray', children: ".env.local" }), " for more configurations."] }), _jsxs(Text, { children: ["- Read ", _jsx(Link, { url: "https://docs.dappbooster.dev", children: "the docs" }), " to know more about", ' ', _jsx(Text, { color: 'gray', children: "dAppBooster" }), "!"] }), _jsxs(Text, { children: ["- Report issues with this installer", ' ', _jsx(Link, { url: "https://github.com/BootNodeDev/dAppBoosterInstallScript/issues", children: "here" })] })] })] }));
|
|
9
|
-
/**
|
|
10
|
-
* Component to ask for the project name.
|
|
11
|
-
* @param selectedFeatures
|
|
12
|
-
* @param projectName
|
|
13
|
-
*/
|
|
14
9
|
const PostInstall = ({ installationConfig, projectName }) => {
|
|
15
10
|
const { selectedFeatures, installationType } = installationConfig;
|
|
16
|
-
const
|
|
11
|
+
const features = selectedFeatures?.map((f) => f.value) ?? [];
|
|
12
|
+
const subgraphSupport = isFeatureSelected('subgraph', features);
|
|
17
13
|
return (_jsxs(_Fragment, { children: [_jsx(Divider, { title: 'Post-install instructions' }), _jsxs(Box, { flexDirection: 'column', rowGap: 2, children: [(subgraphSupport || installationType === 'full') && _jsx(SubgraphWarningMessage, {}), _jsx(PostInstallMessage, { projectName: projectName })] })] }));
|
|
18
14
|
};
|
|
19
15
|
export default PostInstall;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export declare const repoUrl = "https://github.com/BootNodeDev/dAppBooster.git";
|
|
2
|
-
export
|
|
3
|
-
|
|
2
|
+
export type FeatureName = 'demo' | 'subgraph' | 'typedoc' | 'vocs' | 'husky';
|
|
3
|
+
export type FeatureDefinition = {
|
|
4
|
+
description: string;
|
|
5
|
+
label: string;
|
|
6
|
+
packages: string[];
|
|
7
|
+
default: boolean;
|
|
8
|
+
postInstall?: string[];
|
|
4
9
|
};
|
|
10
|
+
export declare const featureDefinitions: Record<FeatureName, FeatureDefinition>;
|
|
11
|
+
export declare const featureNames: FeatureName[];
|
package/dist/constants/config.js
CHANGED
|
@@ -1,18 +1,50 @@
|
|
|
1
1
|
export const repoUrl = 'https://github.com/BootNodeDev/dAppBooster.git';
|
|
2
|
-
export const
|
|
3
|
-
|
|
4
|
-
'
|
|
5
|
-
'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
export const featureDefinitions = {
|
|
3
|
+
demo: {
|
|
4
|
+
description: 'Component demos and example pages',
|
|
5
|
+
label: 'Component Demos',
|
|
6
|
+
packages: [],
|
|
7
|
+
default: true,
|
|
8
|
+
},
|
|
9
|
+
subgraph: {
|
|
10
|
+
description: 'TheGraph subgraph integration',
|
|
11
|
+
label: 'Subgraph support',
|
|
12
|
+
packages: [
|
|
13
|
+
'@bootnodedev/db-subgraph',
|
|
14
|
+
'graphql',
|
|
15
|
+
'graphql-request',
|
|
16
|
+
'@graphql-codegen/cli',
|
|
17
|
+
'@graphql-typed-document-node/core',
|
|
18
|
+
],
|
|
19
|
+
default: true,
|
|
20
|
+
postInstall: [
|
|
21
|
+
'Provide your own API key for PUBLIC_SUBGRAPHS_API_KEY in .env.local',
|
|
22
|
+
'Run pnpm subgraph-codegen from the project folder',
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
typedoc: {
|
|
26
|
+
description: 'TypeDoc API documentation generation',
|
|
27
|
+
label: 'Typedoc documentation support',
|
|
28
|
+
packages: [
|
|
29
|
+
'typedoc',
|
|
30
|
+
'typedoc-github-theme',
|
|
31
|
+
'typedoc-plugin-inline-sources',
|
|
32
|
+
'typedoc-plugin-missing-exports',
|
|
33
|
+
'typedoc-plugin-rename-defaults',
|
|
34
|
+
],
|
|
35
|
+
default: true,
|
|
36
|
+
},
|
|
37
|
+
vocs: {
|
|
38
|
+
description: 'Vocs documentation site',
|
|
39
|
+
label: 'Vocs documentation support',
|
|
40
|
+
packages: ['vocs'],
|
|
41
|
+
default: true,
|
|
42
|
+
},
|
|
43
|
+
husky: {
|
|
44
|
+
description: 'Git hooks with Husky, lint-staged, and commitlint',
|
|
45
|
+
label: 'Husky Git hooks support',
|
|
46
|
+
packages: ['husky', 'lint-staged', '@commitlint/cli', '@commitlint/config-conventional'],
|
|
47
|
+
default: true,
|
|
48
|
+
},
|
|
18
49
|
};
|
|
50
|
+
export const featureNames = Object.keys(featureDefinitions);
|
package/dist/info.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getInfoOutput(): string;
|
package/dist/info.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { featureDefinitions } from './constants/config.js';
|
|
2
|
+
export function getInfoOutput() {
|
|
3
|
+
const features = Object.fromEntries(Object.entries(featureDefinitions).map(([name, def]) => [
|
|
4
|
+
name,
|
|
5
|
+
{
|
|
6
|
+
description: def.description,
|
|
7
|
+
default: def.default,
|
|
8
|
+
...(def.postInstall ? { postInstall: def.postInstall } : {}),
|
|
9
|
+
},
|
|
10
|
+
]));
|
|
11
|
+
return JSON.stringify({
|
|
12
|
+
features,
|
|
13
|
+
modes: {
|
|
14
|
+
full: 'Install all features',
|
|
15
|
+
custom: 'Choose features individually',
|
|
16
|
+
},
|
|
17
|
+
}, null, 2);
|
|
18
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { featureNames } from './constants/config.js';
|
|
3
|
+
import { cleanupFiles, cloneRepo, createEnvFile, installPackages } from './operations/index.js';
|
|
4
|
+
import { getPostInstallMessages, getProjectFolder, isValidName, projectDirectoryExists, } from './utils/utils.js';
|
|
5
|
+
function fail(error) {
|
|
6
|
+
const result = { success: false, error };
|
|
7
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8
|
+
process.exitCode = 1;
|
|
9
|
+
throw new Error(error);
|
|
10
|
+
}
|
|
11
|
+
function parseFeatures(featuresFlag) {
|
|
12
|
+
if (!featuresFlag) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
return featuresFlag
|
|
17
|
+
.split(',')
|
|
18
|
+
.map((f) => f.trim())
|
|
19
|
+
.filter((f) => {
|
|
20
|
+
if (f === '' || seen.has(f)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
seen.add(f);
|
|
24
|
+
return true;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function validate(flags) {
|
|
28
|
+
if (!flags.name) {
|
|
29
|
+
fail('Missing required flag: --name');
|
|
30
|
+
}
|
|
31
|
+
if (!flags.mode) {
|
|
32
|
+
fail('Missing required flag: --mode');
|
|
33
|
+
}
|
|
34
|
+
if (!isValidName(flags.name)) {
|
|
35
|
+
fail('Invalid project name: only letters, numbers, and underscores are allowed');
|
|
36
|
+
}
|
|
37
|
+
if (flags.mode !== 'full' && flags.mode !== 'custom') {
|
|
38
|
+
fail("Invalid mode: must be 'full' or 'custom'");
|
|
39
|
+
}
|
|
40
|
+
// --mode=full ignores --features (everything is installed)
|
|
41
|
+
if (flags.mode === 'full') {
|
|
42
|
+
if (projectDirectoryExists(flags.name)) {
|
|
43
|
+
fail(`Project directory '${flags.name}' already exists`);
|
|
44
|
+
}
|
|
45
|
+
return { name: flags.name, mode: flags.mode, features: featureNames };
|
|
46
|
+
}
|
|
47
|
+
if (!flags.features) {
|
|
48
|
+
fail('--mode custom requires --features. Use --info to see available features.');
|
|
49
|
+
}
|
|
50
|
+
const features = parseFeatures(flags.features);
|
|
51
|
+
if (features.length === 0) {
|
|
52
|
+
fail('--features value is empty. Use --info to see available features.');
|
|
53
|
+
}
|
|
54
|
+
const invalidFeatures = features.filter((f) => !featureNames.includes(f));
|
|
55
|
+
if (invalidFeatures.length > 0) {
|
|
56
|
+
fail(`Unknown features: ${invalidFeatures.join(', ')}. Valid features: ${featureNames.join(', ')}`);
|
|
57
|
+
}
|
|
58
|
+
if (projectDirectoryExists(flags.name)) {
|
|
59
|
+
fail(`Project directory '${flags.name}' already exists`);
|
|
60
|
+
}
|
|
61
|
+
return { name: flags.name, mode: flags.mode, features };
|
|
62
|
+
}
|
|
63
|
+
export async function runNonInteractive(flags) {
|
|
64
|
+
const { name, mode, features } = validate(flags);
|
|
65
|
+
try {
|
|
66
|
+
await cloneRepo(name);
|
|
67
|
+
const projectFolder = getProjectFolder(name);
|
|
68
|
+
await createEnvFile(projectFolder);
|
|
69
|
+
await installPackages(projectFolder, mode, features);
|
|
70
|
+
await cleanupFiles(projectFolder, mode, features);
|
|
71
|
+
const result = {
|
|
72
|
+
success: true,
|
|
73
|
+
projectName: name,
|
|
74
|
+
mode,
|
|
75
|
+
features,
|
|
76
|
+
path: projectFolder,
|
|
77
|
+
postInstall: getPostInstallMessages(mode, features),
|
|
78
|
+
};
|
|
79
|
+
console.log(JSON.stringify(result, null, 2));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
+
fail(message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { FeatureName } from '../constants/config.js';
|
|
2
|
+
import type { InstallationType } from '../types/types.js';
|
|
3
|
+
export declare function cleanupFiles(projectFolder: string, mode: InstallationType, features?: FeatureName[], onProgress?: (step: string) => void): Promise<void>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { copyFile, mkdir, rm } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { isFeatureSelected } from '../utils/utils.js';
|
|
5
|
+
function patchPackageJson(projectFolder, features) {
|
|
6
|
+
const packageJsonPath = resolve(projectFolder, 'package.json');
|
|
7
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
8
|
+
if (!isFeatureSelected('subgraph', features)) {
|
|
9
|
+
packageJson.scripts['subgraph-codegen'] = undefined;
|
|
10
|
+
}
|
|
11
|
+
if (!isFeatureSelected('typedoc', features)) {
|
|
12
|
+
packageJson.scripts['typedoc:build'] = undefined;
|
|
13
|
+
}
|
|
14
|
+
if (!isFeatureSelected('vocs', features)) {
|
|
15
|
+
packageJson.scripts['docs:build'] = undefined;
|
|
16
|
+
packageJson.scripts['docs:dev'] = undefined;
|
|
17
|
+
packageJson.scripts['docs:preview'] = undefined;
|
|
18
|
+
}
|
|
19
|
+
if (!isFeatureSelected('husky', features)) {
|
|
20
|
+
packageJson.scripts.prepare = undefined;
|
|
21
|
+
}
|
|
22
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
23
|
+
}
|
|
24
|
+
async function cleanupDemo(projectFolder) {
|
|
25
|
+
const homeFolder = resolve(projectFolder, 'src/components/pageComponents/home');
|
|
26
|
+
await rm(homeFolder, { recursive: true, force: true });
|
|
27
|
+
await mkdir(homeFolder, { recursive: true });
|
|
28
|
+
await copyFile(resolve(projectFolder, '.install-files/home/index.tsx'), resolve(homeFolder, 'index.tsx'));
|
|
29
|
+
}
|
|
30
|
+
async function cleanupSubgraph(projectFolder, features) {
|
|
31
|
+
await rm(resolve(projectFolder, 'src/subgraphs'), { recursive: true, force: true });
|
|
32
|
+
if (isFeatureSelected('demo', features)) {
|
|
33
|
+
const homeFolder = resolve(projectFolder, 'src/components/pageComponents/home');
|
|
34
|
+
await rm(resolve(homeFolder, 'Examples/demos/subgraphs'), { recursive: true, force: true });
|
|
35
|
+
await rm(resolve(homeFolder, 'Examples/index.tsx'), { force: true });
|
|
36
|
+
await copyFile(resolve(projectFolder, '.install-files/home/Examples/index.tsx'), resolve(homeFolder, 'Examples/index.tsx'));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function cleanupTypedoc(projectFolder) {
|
|
40
|
+
await rm(resolve(projectFolder, 'typedoc.json'), { force: true });
|
|
41
|
+
}
|
|
42
|
+
async function cleanupVocs(projectFolder) {
|
|
43
|
+
await rm(resolve(projectFolder, 'vocs.config.ts'), { force: true });
|
|
44
|
+
await rm(resolve(projectFolder, 'docs'), { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
async function cleanupHusky(projectFolder) {
|
|
47
|
+
await rm(resolve(projectFolder, '.husky'), { recursive: true, force: true });
|
|
48
|
+
await rm(resolve(projectFolder, '.lintstagedrc.mjs'), { force: true });
|
|
49
|
+
await rm(resolve(projectFolder, 'commitlint.config.js'), { force: true });
|
|
50
|
+
}
|
|
51
|
+
export async function cleanupFiles(projectFolder, mode, features = [], onProgress) {
|
|
52
|
+
if (mode === 'custom') {
|
|
53
|
+
if (!isFeatureSelected('demo', features)) {
|
|
54
|
+
onProgress?.('Component demos');
|
|
55
|
+
await cleanupDemo(projectFolder);
|
|
56
|
+
}
|
|
57
|
+
if (!isFeatureSelected('subgraph', features)) {
|
|
58
|
+
onProgress?.('Subgraph');
|
|
59
|
+
await cleanupSubgraph(projectFolder, features);
|
|
60
|
+
}
|
|
61
|
+
if (!isFeatureSelected('typedoc', features)) {
|
|
62
|
+
onProgress?.('Typedoc');
|
|
63
|
+
await cleanupTypedoc(projectFolder);
|
|
64
|
+
}
|
|
65
|
+
if (!isFeatureSelected('vocs', features)) {
|
|
66
|
+
onProgress?.('Vocs');
|
|
67
|
+
await cleanupVocs(projectFolder);
|
|
68
|
+
}
|
|
69
|
+
if (!isFeatureSelected('husky', features)) {
|
|
70
|
+
onProgress?.('Husky');
|
|
71
|
+
await cleanupHusky(projectFolder);
|
|
72
|
+
}
|
|
73
|
+
patchPackageJson(projectFolder, features);
|
|
74
|
+
}
|
|
75
|
+
onProgress?.('Install script');
|
|
76
|
+
await rm(resolve(projectFolder, '.install-files'), { recursive: true, force: true });
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cloneRepo(projectName: string, onProgress?: (step: string) => void): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { repoUrl } from '../constants/config.js';
|
|
4
|
+
import { getProjectFolder } from '../utils/utils.js';
|
|
5
|
+
import { exec, execFile } from './exec.js';
|
|
6
|
+
export async function cloneRepo(projectName, onProgress) {
|
|
7
|
+
const projectFolder = getProjectFolder(projectName);
|
|
8
|
+
onProgress?.(`Cloning dAppBooster in ${projectName}`);
|
|
9
|
+
await execFile('git', ['clone', '--depth', '1', '--no-checkout', repoUrl, projectName]);
|
|
10
|
+
onProgress?.('Fetching tags');
|
|
11
|
+
await execFile('git', ['fetch', '--tags'], { cwd: projectFolder });
|
|
12
|
+
onProgress?.('Checking out latest tag');
|
|
13
|
+
// Shell required for $() command substitution
|
|
14
|
+
await exec('git checkout $(git describe --tags $(git rev-list --tags --max-count=1))', {
|
|
15
|
+
cwd: projectFolder,
|
|
16
|
+
});
|
|
17
|
+
onProgress?.('Removing .git folder');
|
|
18
|
+
await rm(resolve(projectFolder, '.git'), { recursive: true, force: true });
|
|
19
|
+
onProgress?.('Initializing Git repository');
|
|
20
|
+
await execFile('git', ['init'], { cwd: projectFolder });
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createEnvFile(projectFolder: string): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
function run(command, args, options) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
const child = spawn(command, args, {
|
|
5
|
+
...options,
|
|
6
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
7
|
+
});
|
|
8
|
+
let stderr = '';
|
|
9
|
+
child.stderr?.on('data', (data) => {
|
|
10
|
+
stderr += data.toString();
|
|
11
|
+
});
|
|
12
|
+
child.on('close', (code, signal) => {
|
|
13
|
+
if (code === 0) {
|
|
14
|
+
resolve();
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
const fallback = signal !== null
|
|
18
|
+
? `Process killed by signal ${signal}`
|
|
19
|
+
: `Command failed with exit code ${code}`;
|
|
20
|
+
const message = stderr.trim() || fallback;
|
|
21
|
+
reject(new Error(message));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
child.on('error', reject);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export async function exec(command, options = {}) {
|
|
28
|
+
await run('/bin/sh', ['-c', command], { cwd: options.cwd });
|
|
29
|
+
}
|
|
30
|
+
export async function execFile(file, args, options = {}) {
|
|
31
|
+
await run(file, args, { cwd: options.cwd });
|
|
32
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { FeatureName } from '../constants/config.js';
|
|
2
|
+
import type { InstallationType } from '../types/types.js';
|
|
3
|
+
export declare function installPackages(projectFolder: string, mode: InstallationType, features?: FeatureName[], onProgress?: (step: string) => void): Promise<void>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getPackagesToRemove } from '../utils/utils.js';
|
|
2
|
+
import { execFile } from './exec.js';
|
|
3
|
+
export async function installPackages(projectFolder, mode, features = [], onProgress) {
|
|
4
|
+
if (mode === 'full') {
|
|
5
|
+
onProgress?.('Installing packages');
|
|
6
|
+
await execFile('pnpm', ['i'], { cwd: projectFolder });
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const packagesToRemove = getPackagesToRemove(features);
|
|
10
|
+
if (packagesToRemove.length === 0) {
|
|
11
|
+
onProgress?.('Installing packages');
|
|
12
|
+
await execFile('pnpm', ['i'], { cwd: projectFolder });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
onProgress?.('Installing packages');
|
|
16
|
+
await execFile('pnpm', ['remove', ...packagesToRemove], { cwd: projectFolder });
|
|
17
|
+
onProgress?.('Executing post-install scripts');
|
|
18
|
+
await execFile('pnpm', ['run', 'postinstall'], { cwd: projectFolder });
|
|
19
|
+
}
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -1,32 +1,17 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type FeatureName } from '../constants/config.js';
|
|
2
2
|
export declare function getProjectFolder(projectName: string): string;
|
|
3
|
-
/**
|
|
4
|
-
* Utility functions for import process
|
|
5
|
-
* @param name
|
|
6
|
-
*/
|
|
7
3
|
export declare function isValidName(name: string): boolean;
|
|
8
|
-
/**
|
|
9
|
-
* Checks if the answer is confirmed
|
|
10
|
-
* @param answer
|
|
11
|
-
* @param errorMessage
|
|
12
|
-
*/
|
|
13
4
|
export declare function isAnswerConfirmed(answer?: string, errorMessage?: string): boolean;
|
|
14
|
-
/**
|
|
15
|
-
* Checks if the step can be shown
|
|
16
|
-
* @param currentStep
|
|
17
|
-
* @param stepToShow
|
|
18
|
-
*/
|
|
19
5
|
export declare function canShowStep(currentStep: number, stepToShow: number): boolean;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export declare function getPackages(feature: string, featuresList: Array<MultiSelectItem> | undefined): string[];
|
|
6
|
+
export declare function isFeatureSelected(feature: FeatureName, selectedFeatures: FeatureName[]): boolean;
|
|
7
|
+
export declare function getPackagesToRemove(selectedFeatures: FeatureName[]): string[];
|
|
8
|
+
export declare function getPostInstallMessages(mode: 'full' | 'custom', selectedFeatures: FeatureName[]): string[];
|
|
9
|
+
export declare function projectDirectoryExists(projectName: string): boolean;
|
|
10
|
+
type StepStatus = 'running' | 'done' | 'error';
|
|
11
|
+
type StepDisplay = {
|
|
12
|
+
completedSteps: string[];
|
|
13
|
+
currentStep: string | undefined;
|
|
14
|
+
failedStep: string | undefined;
|
|
15
|
+
};
|
|
16
|
+
export declare function deriveStepDisplay(steps: string[], status: StepStatus): StepDisplay;
|
|
17
|
+
export {};
|
package/dist/utils/utils.js
CHANGED
|
@@ -1,47 +1,40 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { join } from 'node:path';
|
|
2
3
|
import process from 'node:process';
|
|
3
|
-
import {
|
|
4
|
+
import { featureDefinitions } from '../constants/config.js';
|
|
4
5
|
export function getProjectFolder(projectName) {
|
|
5
6
|
return join(process.cwd(), projectName);
|
|
6
7
|
}
|
|
7
|
-
/**
|
|
8
|
-
* Utility functions for import process
|
|
9
|
-
* @param name
|
|
10
|
-
*/
|
|
11
8
|
export function isValidName(name) {
|
|
12
9
|
return /^[a-zA-Z0-9_]+$/.test(name);
|
|
13
10
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Checks if the answer is confirmed
|
|
16
|
-
* @param answer
|
|
17
|
-
* @param errorMessage
|
|
18
|
-
*/
|
|
19
11
|
export function isAnswerConfirmed(answer, errorMessage) {
|
|
20
12
|
return (answer !== '' && answer !== undefined && (errorMessage === '' || errorMessage === undefined));
|
|
21
13
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Checks if the step can be shown
|
|
24
|
-
* @param currentStep
|
|
25
|
-
* @param stepToShow
|
|
26
|
-
*/
|
|
27
14
|
export function canShowStep(currentStep, stepToShow) {
|
|
28
15
|
return currentStep > stepToShow - 1;
|
|
29
16
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
export function
|
|
45
|
-
|
|
46
|
-
|
|
17
|
+
export function isFeatureSelected(feature, selectedFeatures) {
|
|
18
|
+
return selectedFeatures.includes(feature);
|
|
19
|
+
}
|
|
20
|
+
export function getPackagesToRemove(selectedFeatures) {
|
|
21
|
+
return Object.entries(featureDefinitions)
|
|
22
|
+
.filter(([name]) => !selectedFeatures.includes(name))
|
|
23
|
+
.flatMap(([, def]) => def.packages);
|
|
24
|
+
}
|
|
25
|
+
export function getPostInstallMessages(mode, selectedFeatures) {
|
|
26
|
+
if (mode === 'full') {
|
|
27
|
+
return Object.values(featureDefinitions).flatMap((def) => def.postInstall ?? []);
|
|
28
|
+
}
|
|
29
|
+
return selectedFeatures.flatMap((name) => featureDefinitions[name]?.postInstall ?? []);
|
|
30
|
+
}
|
|
31
|
+
export function projectDirectoryExists(projectName) {
|
|
32
|
+
return existsSync(getProjectFolder(projectName));
|
|
33
|
+
}
|
|
34
|
+
export function deriveStepDisplay(steps, status) {
|
|
35
|
+
return {
|
|
36
|
+
completedSteps: status === 'done' ? steps : steps.slice(0, -1),
|
|
37
|
+
currentStep: status === 'running' ? steps.at(-1) : undefined,
|
|
38
|
+
failedStep: status === 'error' ? steps.at(-1) : undefined,
|
|
39
|
+
};
|
|
47
40
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dappbooster",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/BootNodeDev/dAppBoosterInstallScript"
|
|
8
|
+
},
|
|
5
9
|
"bin": "dist/cli.js",
|
|
6
10
|
"type": "module",
|
|
7
11
|
"engines": {
|
|
8
12
|
"node": ">=20"
|
|
9
13
|
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:coverage": "vitest run --coverage",
|
|
19
|
+
"lint": "pnpm biome check",
|
|
20
|
+
"lint:fix": "pnpm biome check --write"
|
|
21
|
+
},
|
|
10
22
|
"files": [
|
|
11
23
|
"dist"
|
|
12
24
|
],
|
|
@@ -18,8 +30,8 @@
|
|
|
18
30
|
"ink-gradient": "^3.0.0",
|
|
19
31
|
"ink-link": "^4.1.0",
|
|
20
32
|
"ink-select-input": "^6.2.0",
|
|
21
|
-
"ink-spawn": "^0.1.4",
|
|
22
33
|
"ink-text-input": "^6.0.0",
|
|
34
|
+
"meow": "^14.1.0",
|
|
23
35
|
"react": "^18.3.1"
|
|
24
36
|
},
|
|
25
37
|
"devDependencies": {
|
|
@@ -27,13 +39,14 @@
|
|
|
27
39
|
"@sindresorhus/tsconfig": "^7.0.0",
|
|
28
40
|
"@types/node": "^22.15.21",
|
|
29
41
|
"@types/react": "^18.3.22",
|
|
42
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
30
43
|
"ts-node": "^10.9.1",
|
|
31
|
-
"typescript": "^5.8.3"
|
|
44
|
+
"typescript": "^5.8.3",
|
|
45
|
+
"vitest": "^4.1.0"
|
|
32
46
|
},
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"lint:fix": "pnpm biome check --write"
|
|
47
|
+
"pnpm": {
|
|
48
|
+
"onlyBuiltDependencies": [
|
|
49
|
+
"@biomejs/biome"
|
|
50
|
+
]
|
|
38
51
|
}
|
|
39
|
-
}
|
|
52
|
+
}
|
package/readme.md
CHANGED
|
@@ -17,6 +17,106 @@ pnpm dlx dappbooster
|
|
|
17
17
|
|
|
18
18
|
dAppBooster documentation: https://docs.dappbooster.dev/
|
|
19
19
|
|
|
20
|
+
## Non-interactive / CI mode
|
|
21
|
+
|
|
22
|
+
The installer supports a non-interactive mode for CI pipelines and AI agents. It activates automatically when stdout is not a TTY, or explicitly with the `--ni` flag.
|
|
23
|
+
|
|
24
|
+
### Discover available features
|
|
25
|
+
|
|
26
|
+
```shell
|
|
27
|
+
pnpm dlx dappbooster --info
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"features": {
|
|
33
|
+
"demo": {
|
|
34
|
+
"description": "Component demos and example pages",
|
|
35
|
+
"default": true
|
|
36
|
+
},
|
|
37
|
+
"subgraph": {
|
|
38
|
+
"description": "TheGraph subgraph integration",
|
|
39
|
+
"default": true,
|
|
40
|
+
"postInstall": [
|
|
41
|
+
"Provide your own API key for PUBLIC_SUBGRAPHS_API_KEY in .env.local",
|
|
42
|
+
"Run pnpm subgraph-codegen from the project folder"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"typedoc": {
|
|
46
|
+
"description": "TypeDoc API documentation generation",
|
|
47
|
+
"default": true
|
|
48
|
+
},
|
|
49
|
+
"vocs": {
|
|
50
|
+
"description": "Vocs documentation site",
|
|
51
|
+
"default": true
|
|
52
|
+
},
|
|
53
|
+
"husky": {
|
|
54
|
+
"description": "Git hooks with Husky, lint-staged, and commitlint",
|
|
55
|
+
"default": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"modes": {
|
|
59
|
+
"full": "Install all features",
|
|
60
|
+
"custom": "Choose features individually"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Full install
|
|
66
|
+
|
|
67
|
+
```shell
|
|
68
|
+
pnpm dlx dappbooster --ni --name my_dapp --mode full
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"success": true,
|
|
74
|
+
"projectName": "my_dapp",
|
|
75
|
+
"mode": "full",
|
|
76
|
+
"features": ["demo", "subgraph", "typedoc", "vocs", "husky"],
|
|
77
|
+
"path": "/absolute/path/to/my_dapp",
|
|
78
|
+
"postInstall": [
|
|
79
|
+
"Provide your own API key for PUBLIC_SUBGRAPHS_API_KEY in .env.local",
|
|
80
|
+
"Run pnpm subgraph-codegen from the project folder"
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Custom install with selected features
|
|
86
|
+
|
|
87
|
+
```shell
|
|
88
|
+
pnpm dlx dappbooster --ni --name my_dapp --mode custom --features demo,subgraph
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"success": true,
|
|
94
|
+
"projectName": "my_dapp",
|
|
95
|
+
"mode": "custom",
|
|
96
|
+
"features": ["demo", "subgraph"],
|
|
97
|
+
"path": "/absolute/path/to/my_dapp",
|
|
98
|
+
"postInstall": [
|
|
99
|
+
"Provide your own API key for PUBLIC_SUBGRAPHS_API_KEY in .env.local",
|
|
100
|
+
"Run pnpm subgraph-codegen from the project folder"
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Error handling
|
|
106
|
+
|
|
107
|
+
Errors return structured JSON with a non-zero exit code:
|
|
108
|
+
|
|
109
|
+
```shell
|
|
110
|
+
pnpm dlx dappbooster --ni --mode full
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"success": false,
|
|
116
|
+
"error": "Missing required flag: --name"
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
20
120
|
## Development
|
|
21
121
|
|
|
22
122
|
Clone the repo
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { FC } from 'react';
|
|
2
|
-
interface Props {
|
|
3
|
-
projectName: string;
|
|
4
|
-
onCompletion: () => void;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Executes all the commands to clone the dAppBooster repository.
|
|
8
|
-
* @param projectName
|
|
9
|
-
* @param onCompletion
|
|
10
|
-
*/
|
|
11
|
-
declare const Commands: FC<Props>;
|
|
12
|
-
export default Commands;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import * as process from 'node:process';
|
|
4
|
-
import { Box, Text } from 'ink';
|
|
5
|
-
import { Script, Spawn } from 'ink-spawn';
|
|
6
|
-
import { repoUrl } from '../../../constants/config.js';
|
|
7
|
-
/**
|
|
8
|
-
* Executes all the commands to clone the dAppBooster repository.
|
|
9
|
-
* @param projectName
|
|
10
|
-
* @param onCompletion
|
|
11
|
-
*/
|
|
12
|
-
const Commands = ({ projectName, onCompletion }) => {
|
|
13
|
-
const projectFolder = join(process.cwd(), projectName);
|
|
14
|
-
return (_jsx(Box, { flexDirection: 'column', gap: 0, children: _jsxs(Script, { children: [_jsxs(Box, { columnGap: 1, children: [_jsx(Text, { color: 'whiteBright', children: "Cloning dAppBooster in" }), _jsx(Text, { italic: true, children: projectName })] }), _jsx(Spawn, { shell: true, silent: true, successText: 'Done!', failureText: `Failed to clone the project, check if a folder called "${projectName}" already exists and your read/write permissions...`, runningText: 'Working...', command: "git", args: ['clone', '--depth', '1', '--no-checkout', repoUrl, projectName] }), _jsx(Text, { color: 'whiteBright', children: "Fetching tags" }), _jsx(Spawn, { shell: true, cwd: projectFolder, silent: true, command: 'git', args: ['fetch', '--tags'], runningText: 'Working...', successText: 'Done!', failureText: 'Error...' }), _jsx(Text, { color: 'whiteBright', children: "Checking out latest tag" }), _jsx(Spawn, { shell: true, cwd: projectFolder, command: "git", args: ['checkout $(git describe --tags `git rev-list --tags --max-count=1`)'], successText: "Done!", failureText: 'Error...' }), _jsx(Text, { color: 'whiteBright', children: "Removing .git folder" }), _jsx(Spawn, { shell: true, cwd: projectFolder, command: "rm", args: ['-rf', '.git'], successText: "Done!", failureText: 'Error...' }), _jsx(Text, { color: 'whiteBright', children: "Initializing Git repository" }), _jsx(Spawn, { shell: true, cwd: projectFolder, command: "git", args: ['init'], successText: "Done!", failureText: 'Error...', onCompletion: onCompletion })] }) }));
|
|
15
|
-
};
|
|
16
|
-
export default Commands;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { FC } from 'react';
|
|
2
|
-
import type { MultiSelectItem } from '../../../types/types.js';
|
|
3
|
-
interface Props {
|
|
4
|
-
onCompletion: () => void;
|
|
5
|
-
projectFolder: string;
|
|
6
|
-
selectedFeatures?: Array<MultiSelectItem>;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Performs a custom installation based on the selected features: basically we tell `pnpm` what
|
|
10
|
-
* features to remove (everything's included in package.json by default to simplify things)
|
|
11
|
-
* @param onCompletion
|
|
12
|
-
* @param selectedFeatures
|
|
13
|
-
* @param projectFolder
|
|
14
|
-
*/
|
|
15
|
-
declare const CustomInstallation: FC<Props>;
|
|
16
|
-
export default CustomInstallation;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { Script, Spawn } from 'ink-spawn';
|
|
4
|
-
import { getPackages } from '../../../utils/utils.js';
|
|
5
|
-
import InstallAllPackages from './InstallAllPackages.js';
|
|
6
|
-
/**
|
|
7
|
-
* Performs a custom installation based on the selected features: basically we tell `pnpm` what
|
|
8
|
-
* features to remove (everything's included in package.json by default to simplify things)
|
|
9
|
-
* @param onCompletion
|
|
10
|
-
* @param selectedFeatures
|
|
11
|
-
* @param projectFolder
|
|
12
|
-
*/
|
|
13
|
-
const CustomInstallation = ({ onCompletion, selectedFeatures, projectFolder }) => {
|
|
14
|
-
// Collects the packages to remove based on the selected features and makes
|
|
15
|
-
// a string out of them so that we can pass it to `pnpm remove` command.
|
|
16
|
-
const packagesToRemove = [
|
|
17
|
-
...getPackages('subgraph', selectedFeatures),
|
|
18
|
-
...getPackages('typedoc', selectedFeatures),
|
|
19
|
-
...getPackages('vocs', selectedFeatures),
|
|
20
|
-
...getPackages('husky', selectedFeatures),
|
|
21
|
-
]
|
|
22
|
-
.join(' ')
|
|
23
|
-
.trim();
|
|
24
|
-
return (_jsx(Box, { flexDirection: 'column', gap: 0, children: !packagesToRemove ? (_jsx(Script, { children: _jsx(InstallAllPackages, { projectFolder: projectFolder, onCompletion: onCompletion }) })) : (_jsxs(Script, { children: [_jsx(Text, { color: 'whiteBright', children: "Installing packages" }), _jsx(Spawn, { shell: true, cwd: projectFolder, silent: true, command: 'pnpm', args: ['remove', packagesToRemove], runningText: 'Working...', successText: 'Done!', failureText: 'Error...' }), _jsx(Text, { color: 'whiteBright', children: "Executing post-install scripts" }), _jsx(Spawn, { shell: true, cwd: projectFolder, silent: true, command: 'pnpm', args: ['run', 'postinstall'], runningText: 'Working...', successText: 'Done!', failureText: 'Error...', onCompletion: onCompletion })] })) }));
|
|
25
|
-
};
|
|
26
|
-
export default CustomInstallation;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box } from 'ink';
|
|
3
|
-
import { Script } from 'ink-spawn';
|
|
4
|
-
import InstallAllPackages from './InstallAllPackages.js';
|
|
5
|
-
const FullInstallation = ({ onCompletion, projectFolder }) => {
|
|
6
|
-
return (_jsx(Box, { flexDirection: 'column', gap: 0, children: _jsx(Script, { children: _jsx(InstallAllPackages, { projectFolder: projectFolder, onCompletion: onCompletion }) }) }));
|
|
7
|
-
};
|
|
8
|
-
export default FullInstallation;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Text } from 'ink';
|
|
3
|
-
import { Spawn } from 'ink-spawn';
|
|
4
|
-
const InstallAllPackages = ({ projectFolder, onCompletion }) => {
|
|
5
|
-
return (_jsxs(_Fragment, { children: [_jsx(Text, { color: 'whiteBright', children: "Installing packages" }), _jsx(Spawn, { shell: true, cwd: projectFolder, silent: true, command: 'pnpm', args: ['i'], runningText: 'Working...', successText: 'Done!', failureText: 'Error...', onCompletion: onCompletion })] }));
|
|
6
|
-
};
|
|
7
|
-
export default InstallAllPackages;
|